From f05c916773b30d40b02db377dd9d152ff218aee1 Mon Sep 17 00:00:00 2001 From: sergerad Date: Wed, 20 Nov 2024 13:49:27 +1300 Subject: [PATCH] Initial import --- CHANGELOG.md | 243 ++++ Dockerfile | 14 +- Makefile | 4 + README.md | 340 +----- SECURITY.md | 174 +-- accounts/immutable/backend.go | 55 + accounts/immutable/backend_test.go | 83 ++ accounts/immutable/errors.go | 24 + accounts/immutable/keystore.go | 91 ++ accounts/immutable/keystore_test.go | 61 + accounts/immutable/secretsmanager.go | 67 ++ accounts/immutable/store.go | 28 + accounts/immutable/testdata/address.txt | 1 + .../immutable/testdata/keystore/keystore.json | 1 + accounts/immutable/testdata/password.txt | 1 + accounts/immutable/wallet.go | 130 ++ accounts/immutable/wallet_test.go | 156 +++ build/ci.go | 6 +- cmd/evm/internal/t8ntool/execution.go | 7 + cmd/geth/config.go | 62 + cmd/geth/immutable.go | 148 +++ cmd/geth/immutable/env/env.go | 68 ++ cmd/geth/immutable/keys/keys.go | 132 +++ cmd/geth/immutable/node/node.go | 245 ++++ cmd/geth/immutable/rewind/rewind.go | 79 ++ cmd/geth/immutable/rewind/rewind_test.go | 59 + cmd/geth/immutable/role/role.go | 77 ++ cmd/geth/immutable/settings/forks.go | 55 + cmd/geth/immutable/settings/forks_test.go | 62 + .../immutable/settings/genesis/devnet.json | 122 ++ .../immutable/settings/genesis/mainnet.json | 44 + .../immutable/settings/genesis/testnet.json | 44 + cmd/geth/immutable/settings/network.go | 104 ++ cmd/geth/immutable/settings/network_test.go | 67 ++ cmd/geth/immutable/settings/settings.go | 71 ++ .../immutable/store/localstore/localstore.go | 43 + cmd/geth/immutable/store/store.go | 36 + cmd/geth/immutable_artefact_store_mock.go | 67 ++ cmd/geth/immutable_bootstrap.go | 181 +++ cmd/geth/immutable_bootstrap_command.go | 143 +++ cmd/geth/immutable_bootstrap_test.go | 336 ++++++ cmd/geth/immutable_bootstrapper.go | 82 ++ cmd/geth/immutable_configure.go | 139 +++ cmd/geth/immutable_configure_test.go | 141 +++ cmd/geth/immutable_decode_command.go | 43 + cmd/geth/immutable_eth_config.go | 140 +++ cmd/geth/immutable_local.go | 131 ++ cmd/geth/immutable_local_bootstrapper.go | 439 +++++++ cmd/geth/immutable_premine.go | 88 ++ cmd/geth/immutable_premine_test.go | 57 + cmd/geth/immutable_remote_store.go | 32 + cmd/geth/immutable_rewind_command.go | 108 ++ cmd/geth/immutable_run_boot_node_command.go | 137 +++ cmd/geth/immutable_vote.go | 138 +++ cmd/geth/main.go | 38 +- cmd/geth/testdata/acl_list.txt | 1 + cmd/geth/testdata/blockedkey.prv | 1 + cmd/geth/testdata/blocklist.txt | 1 + cmd/geth/testdata/dev.toml | 79 ++ cmd/geth/testdata/dev_bad_recommit.toml | 79 ++ cmd/geth/testdata/key.addr | 1 + cmd/immutable/README.md | 28 + cmd/immutable/categories.go | 19 + cmd/immutable/flags.go | 141 +++ cmd/immutable/remote/aws/s3.go | 52 + cmd/immutable/remote/aws/secretsmanager.go | 133 +++ .../remote/aws/secretsmanager_test.go | 50 + cmd/utils/flags.go | 200 +++- cmd/utils/immutable_flags.go | 50 + common/types.go | 3 + consensus/clique/clique.go | 143 ++- consensus/clique/clique_immutable_test.go | 124 ++ consensus/clique/clique_test.go | 53 + .../misc/eip1559/immutable_eip1559_test.go | 62 + core/blockchain.go | 58 + core/blockchain_test.go | 25 +- core/chain_makers.go | 2 +- core/evm.go | 8 +- core/forkid/forkid.go | 1 + core/gen_genesis.go | 4 +- core/genesis.go | 11 + core/immutable_blockchain_test.go | 138 +++ core/immutable_genesis.go | 37 + core/immutable_genesis_test.go | 171 +++ core/state_prefetcher.go | 2 +- core/state_processor.go | 8 +- core/state_transition.go | 6 +- core/txpool/blobpool/blobpool.go | 9 + core/txpool/blobpool/limbo.go | 5 +- core/txpool/errors.go | 4 + .../accesscontrol/address_provider.go | 32 + .../contract_creation_controller.go | 77 ++ .../contract_creation_controller_test.go | 105 ++ .../immutable/accesscontrol/controller.go | 76 ++ .../accesscontrol/controller_test.go | 150 +++ .../immutable/accesscontrol/sdn_provider.go | 75 ++ .../accesscontrol/sdn_provider_test.go | 67 ++ .../accesscontrol/testdata/blocklist.txt | 1 + .../accesscontrol/testdata/empty.txt | 0 .../accesscontrol/testdata/gibberish.txt | 1 + .../accesscontrol/testdata/newlines.txt | 5 + core/txpool/immutable_access_controller.go | 30 + .../legacypool/immmutable_legacypool_test.go | 385 ++++++ core/txpool/legacypool/legacypool.go | 104 +- core/txpool/subpool.go | 4 + core/txpool/txpool.go | 32 +- core/txpool/validation.go | 15 + core/types/block.go | 6 + diff/README.md | 19 + diff/fork.yaml | 168 +++ eth/api_backend.go | 27 +- eth/backend.go | 35 +- eth/downloader/downloader.go | 6 + eth/ethconfig/config.go | 13 + eth/fetcher/block_fetcher.go | 85 +- eth/fetcher/block_fetcher_test.go | 4 +- eth/fetcher/long/immutable_long.go | 51 + eth/fetcher/tx_fetcher.go | 16 + eth/filters/filter.go | 10 + eth/gasestimator/gasestimator.go | 2 +- eth/gasprice/gasprice.go | 15 +- eth/gasprice/immutable_gasprice_test.go | 242 ++++ eth/handler.go | 74 +- eth/handler_eth.go | 21 +- eth/peerset.go | 13 + eth/peerset_test.go | 38 + eth/protocols/eth/handler.go | 36 + eth/protocols/snap/gentrie_test.go | 3 +- eth/state_accessor.go | 2 +- eth/tracers/api.go | 17 +- eth/tracers/api_test.go | 2 +- ethclient/gethclient/immutable_gethclient.go | 48 + go.mod | 50 +- go.sum | 396 ++++--- immutable/config/mainnet-public.toml | 88 ++ immutable/config/mainnet.toml | 80 ++ immutable/config/testnet-public.toml | 89 ++ immutable/config/testnet.toml | 80 ++ internal/era/era.go | 3 +- internal/era/iterator.go | 3 +- internal/ethapi/api.go | 2 +- internal/ethapi/api_test.go | 2 +- internal/shutdowncheck/shutdown_tracker.go | 6 + miner/worker.go | 118 +- node/api.go | 6 +- node/config.go | 10 + node/defaults.go | 7 + node/immutable_newrelic.go | 111 ++ node/immutable_newrelic_test.go | 56 + node/node.go | 72 +- node/rpcstack.go | 24 +- node/rpcstack_test.go | 4 +- p2p/discover/common.go | 3 +- p2p/discover/table.go | 14 +- p2p/discover/v4_udp.go | 8 +- p2p/enode/localnode.go | 7 +- p2p/server.go | 9 +- params/config.go | 26 +- params/immutable_config.go | 59 + params/immutable_config_test.go | 78 ++ params/protocol_params.go | 4 +- params/version.go | 8 +- tests/immutable/README.md | 25 + tests/immutable/acl_test.go | 165 +++ tests/immutable/cancun/blobbasefee/abi.json | 15 + .../cancun/blobbasefee/blobbasefee.go | 234 ++++ .../cancun/blobbasefee/blobbasefee.sol | 10 + .../immutable/cancun/blobbasefee/bytecode.bin | 1 + tests/immutable/cancun/blobhash/abi.json | 21 + tests/immutable/cancun/blobhash/blobhash.go | 234 ++++ tests/immutable/cancun/blobhash/blobhash.sol | 12 + tests/immutable/cancun/blobhash/bytecode.bin | 1 + tests/immutable/cancun/mcopy/abi.json | 15 + tests/immutable/cancun/mcopy/bytecode.bin | 1 + tests/immutable/cancun/mcopy/mcopy.go | 234 ++++ tests/immutable/cancun/mcopy/mcopy.sol | 14 + .../cancun/selfdestruct/constructor/abi.json | 7 + .../selfdestruct/constructor/bytecode.bin | 1 + .../constructor/selfdestructconstructor.go | 203 ++++ .../constructor/selfdestructconstructor.sol | 10 + .../cancun/selfdestruct/function/abi.json | 9 + .../cancun/selfdestruct/function/bytecode.bin | 1 + .../function/selfdestructfunction.go | 224 ++++ .../function/selfdestructfunction.sol | 12 + .../cancun/transientstorage/abi.json | 18 + .../cancun/transientstorage/bytecode.bin | 1 + .../transientstorage/transientstorage.go | 358 ++++++ .../transientstorage/transientstorage.sol | 15 + tests/immutable/cancun_test.go | 327 +++++ tests/immutable/cliqueclient_test.go | 69 ++ tests/immutable/erc20/abi.json | 277 +++++ tests/immutable/erc20/bytecode.bin | 1 + tests/immutable/erc20/erc20.go | 801 +++++++++++++ tests/immutable/erc20_test.go | 113 ++ tests/immutable/erc721/abi.json | 366 ++++++ tests/immutable/erc721/bytecode.bin | 1 + tests/immutable/erc721/erc721.go | 1055 +++++++++++++++++ tests/immutable/erc721_test.go | 90 ++ tests/immutable/evm/client.go | 219 ++++ tests/immutable/evm/client_test.go | 57 + tests/immutable/evm/client_transactor.go | 152 +++ tests/immutable/evm/tip_estimator.go | 43 + tests/immutable/evm/tip_estimator_test.go | 36 + tests/immutable/flag_test.go | 196 +++ tests/immutable/main_test.go | 122 ++ tests/immutable/price_test.go | 119 ++ tests/immutable/randao/abi.json | 28 + tests/immutable/randao/bytecode.bin | 1 + tests/immutable/randao/randao.go | 265 +++++ tests/immutable/randao/randao.sol | 13 + tests/immutable/randao_test.go | 56 + tests/immutable/shanghai/abi.json | 9 + tests/immutable/shanghai/bytecode.bin | 1 + tests/immutable/shanghai/shanghai.go | 224 ++++ tests/immutable/shanghai/shanghai.sol | 10 + tests/immutable/shanghai_test.go | 58 + tests/immutable/utils.go | 104 ++ tests/state_test.go | 2 +- tests/state_test_util.go | 2 +- 219 files changed, 16520 insertions(+), 826 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 accounts/immutable/backend.go create mode 100644 accounts/immutable/backend_test.go create mode 100644 accounts/immutable/errors.go create mode 100644 accounts/immutable/keystore.go create mode 100644 accounts/immutable/keystore_test.go create mode 100644 accounts/immutable/secretsmanager.go create mode 100644 accounts/immutable/store.go create mode 100644 accounts/immutable/testdata/address.txt create mode 100644 accounts/immutable/testdata/keystore/keystore.json create mode 100644 accounts/immutable/testdata/password.txt create mode 100644 accounts/immutable/wallet.go create mode 100644 accounts/immutable/wallet_test.go create mode 100644 cmd/geth/immutable.go create mode 100644 cmd/geth/immutable/env/env.go create mode 100644 cmd/geth/immutable/keys/keys.go create mode 100644 cmd/geth/immutable/node/node.go create mode 100644 cmd/geth/immutable/rewind/rewind.go create mode 100644 cmd/geth/immutable/rewind/rewind_test.go create mode 100644 cmd/geth/immutable/role/role.go create mode 100644 cmd/geth/immutable/settings/forks.go create mode 100644 cmd/geth/immutable/settings/forks_test.go create mode 100644 cmd/geth/immutable/settings/genesis/devnet.json create mode 100644 cmd/geth/immutable/settings/genesis/mainnet.json create mode 100644 cmd/geth/immutable/settings/genesis/testnet.json create mode 100644 cmd/geth/immutable/settings/network.go create mode 100644 cmd/geth/immutable/settings/network_test.go create mode 100644 cmd/geth/immutable/settings/settings.go create mode 100644 cmd/geth/immutable/store/localstore/localstore.go create mode 100644 cmd/geth/immutable/store/store.go create mode 100644 cmd/geth/immutable_artefact_store_mock.go create mode 100644 cmd/geth/immutable_bootstrap.go create mode 100644 cmd/geth/immutable_bootstrap_command.go create mode 100644 cmd/geth/immutable_bootstrap_test.go create mode 100644 cmd/geth/immutable_bootstrapper.go create mode 100644 cmd/geth/immutable_configure.go create mode 100644 cmd/geth/immutable_configure_test.go create mode 100644 cmd/geth/immutable_decode_command.go create mode 100644 cmd/geth/immutable_eth_config.go create mode 100644 cmd/geth/immutable_local.go create mode 100644 cmd/geth/immutable_local_bootstrapper.go create mode 100644 cmd/geth/immutable_premine.go create mode 100644 cmd/geth/immutable_premine_test.go create mode 100644 cmd/geth/immutable_remote_store.go create mode 100644 cmd/geth/immutable_rewind_command.go create mode 100644 cmd/geth/immutable_run_boot_node_command.go create mode 100644 cmd/geth/immutable_vote.go create mode 100644 cmd/geth/testdata/acl_list.txt create mode 100644 cmd/geth/testdata/blockedkey.prv create mode 100644 cmd/geth/testdata/blocklist.txt create mode 100644 cmd/geth/testdata/dev.toml create mode 100644 cmd/geth/testdata/dev_bad_recommit.toml create mode 100644 cmd/geth/testdata/key.addr create mode 100644 cmd/immutable/README.md create mode 100644 cmd/immutable/categories.go create mode 100644 cmd/immutable/flags.go create mode 100644 cmd/immutable/remote/aws/s3.go create mode 100644 cmd/immutable/remote/aws/secretsmanager.go create mode 100644 cmd/immutable/remote/aws/secretsmanager_test.go create mode 100644 cmd/utils/immutable_flags.go create mode 100644 consensus/clique/clique_immutable_test.go create mode 100644 consensus/misc/eip1559/immutable_eip1559_test.go create mode 100644 core/immutable_blockchain_test.go create mode 100644 core/immutable_genesis.go create mode 100644 core/immutable_genesis_test.go create mode 100644 core/txpool/immutable/accesscontrol/address_provider.go create mode 100644 core/txpool/immutable/accesscontrol/contract_creation_controller.go create mode 100644 core/txpool/immutable/accesscontrol/contract_creation_controller_test.go create mode 100644 core/txpool/immutable/accesscontrol/controller.go create mode 100644 core/txpool/immutable/accesscontrol/controller_test.go create mode 100644 core/txpool/immutable/accesscontrol/sdn_provider.go create mode 100644 core/txpool/immutable/accesscontrol/sdn_provider_test.go create mode 100644 core/txpool/immutable/accesscontrol/testdata/blocklist.txt create mode 100644 core/txpool/immutable/accesscontrol/testdata/empty.txt create mode 100644 core/txpool/immutable/accesscontrol/testdata/gibberish.txt create mode 100644 core/txpool/immutable/accesscontrol/testdata/newlines.txt create mode 100644 core/txpool/immutable_access_controller.go create mode 100644 core/txpool/legacypool/immmutable_legacypool_test.go create mode 100644 diff/README.md create mode 100644 diff/fork.yaml create mode 100644 eth/fetcher/long/immutable_long.go create mode 100644 eth/gasprice/immutable_gasprice_test.go create mode 100644 eth/peerset_test.go create mode 100644 ethclient/gethclient/immutable_gethclient.go create mode 100644 immutable/config/mainnet-public.toml create mode 100644 immutable/config/mainnet.toml create mode 100644 immutable/config/testnet-public.toml create mode 100644 immutable/config/testnet.toml create mode 100644 node/immutable_newrelic.go create mode 100644 node/immutable_newrelic_test.go create mode 100644 params/immutable_config.go create mode 100644 params/immutable_config_test.go create mode 100644 tests/immutable/README.md create mode 100644 tests/immutable/acl_test.go create mode 100644 tests/immutable/cancun/blobbasefee/abi.json create mode 100644 tests/immutable/cancun/blobbasefee/blobbasefee.go create mode 100644 tests/immutable/cancun/blobbasefee/blobbasefee.sol create mode 100644 tests/immutable/cancun/blobbasefee/bytecode.bin create mode 100644 tests/immutable/cancun/blobhash/abi.json create mode 100644 tests/immutable/cancun/blobhash/blobhash.go create mode 100644 tests/immutable/cancun/blobhash/blobhash.sol create mode 100644 tests/immutable/cancun/blobhash/bytecode.bin create mode 100644 tests/immutable/cancun/mcopy/abi.json create mode 100644 tests/immutable/cancun/mcopy/bytecode.bin create mode 100644 tests/immutable/cancun/mcopy/mcopy.go create mode 100644 tests/immutable/cancun/mcopy/mcopy.sol create mode 100644 tests/immutable/cancun/selfdestruct/constructor/abi.json create mode 100644 tests/immutable/cancun/selfdestruct/constructor/bytecode.bin create mode 100644 tests/immutable/cancun/selfdestruct/constructor/selfdestructconstructor.go create mode 100644 tests/immutable/cancun/selfdestruct/constructor/selfdestructconstructor.sol create mode 100644 tests/immutable/cancun/selfdestruct/function/abi.json create mode 100644 tests/immutable/cancun/selfdestruct/function/bytecode.bin create mode 100644 tests/immutable/cancun/selfdestruct/function/selfdestructfunction.go create mode 100644 tests/immutable/cancun/selfdestruct/function/selfdestructfunction.sol create mode 100644 tests/immutable/cancun/transientstorage/abi.json create mode 100644 tests/immutable/cancun/transientstorage/bytecode.bin create mode 100644 tests/immutable/cancun/transientstorage/transientstorage.go create mode 100644 tests/immutable/cancun/transientstorage/transientstorage.sol create mode 100644 tests/immutable/cancun_test.go create mode 100644 tests/immutable/cliqueclient_test.go create mode 100644 tests/immutable/erc20/abi.json create mode 100644 tests/immutable/erc20/bytecode.bin create mode 100644 tests/immutable/erc20/erc20.go create mode 100644 tests/immutable/erc20_test.go create mode 100644 tests/immutable/erc721/abi.json create mode 100644 tests/immutable/erc721/bytecode.bin create mode 100644 tests/immutable/erc721/erc721.go create mode 100644 tests/immutable/erc721_test.go create mode 100644 tests/immutable/evm/client.go create mode 100644 tests/immutable/evm/client_test.go create mode 100644 tests/immutable/evm/client_transactor.go create mode 100644 tests/immutable/evm/tip_estimator.go create mode 100644 tests/immutable/evm/tip_estimator_test.go create mode 100644 tests/immutable/flag_test.go create mode 100644 tests/immutable/main_test.go create mode 100644 tests/immutable/price_test.go create mode 100644 tests/immutable/randao/abi.json create mode 100644 tests/immutable/randao/bytecode.bin create mode 100644 tests/immutable/randao/randao.go create mode 100644 tests/immutable/randao/randao.sol create mode 100644 tests/immutable/randao_test.go create mode 100644 tests/immutable/shanghai/abi.json create mode 100644 tests/immutable/shanghai/bytecode.bin create mode 100644 tests/immutable/shanghai/shanghai.go create mode 100644 tests/immutable/shanghai/shanghai.sol create mode 100644 tests/immutable/shanghai_test.go create mode 100644 tests/immutable/utils.go diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..c1b90520e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,243 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to +[Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [v1.0.0-beta.14] + +* Limit the number of bytes read by NR RPC middleware + +## [v1.0.0-beta.13] + +* Remove deployer allowlist +* Add `GETH_FLAG_IMMUTABLE_LONG_RANGE_SYNC` flag to allow snap sync from genesis + +## [v1.0.0-beta.12] + +#### Cancun +This release enables Cancun fork on all Immutable zkEVM networks. +| Network | Unix Timestamp | Date | +| -------- | ------- | ------- | +| Devnet | 1724796000 | Tue Aug 27 22:00:00 UTC 2024 | +| Testnet | 1727128800 | Mon Sep 23 22:00:00 UTC 2024 | +| Mainnet | 1728338400 | Mon Oct 7 22:00:00 UTC 2024 | + +* Enable `ExcessBlobGas`, `BlobGasUsed`, `ParentBeaconRoot` + * All values are `0x0` +* Enable `WithdrawalsHash`, and `Withdrawals` headers + * `Withdrawals` are empty and `WithdrawalsHash` is the corresponding digest +* Enable `TSTORE`, `TLOAD`, and `MCOPY` op codes +* Enable `BLOBHASH` op code +* Enable Point Evaluation precompile +* Update `SELFDESTRUCT` op code +* Update clique stack to support Cancun +* Disable blob transactions +* Re-order logic that sets blob block headers and beacon root header to after `engine.Prepare` in order to interoperate with clique which produces variable blocktimes (rather than fixed slots) + + +#### Other + +* Add `--gossipdefault` flag to toggle default geth tx gossiping +* Add GETH_FLAG_P2P_SUBNET env var to limit inbound messages based on subnet +* Add specific peer message handling in `eth/handler.go` to disable ingestion of state +* Add `--rpcproxy` flag to toggle RPC proxy forwarding +* Add RPC proxy forwarding to Immutable zkEVM +* Add `--disabletxpoolgossip` flag to disable tx gossiping +* Add `--gossipdefault` flag to toggle default geth tx gossiping +* Add GETH_FLAG_P2P_SUBNET env var to limit inbound messages based on subnet + +## [v1.0.0-beta.11] + +* Added partner public role to init container logic + +## [v1.0.0-beta.10] + +* Add log to correlate block period with block number and hash + +## [v1.0.0-beta.9] + +* Reject forkids that do not contain prevrandao fork +* Disable more RPC namespaces +* Correct the embedded mainnet.toml's price limit value to 10 gwei + +## [v1.0.0-beta.8] + +* Add prevrandao fork to forkid +* Pull v1.13.15 from upstream: [ethereum/go-ethereum](https://github.com/ethereum/go-ethereum) +* Log peer fullnames rather than abbreviated +* Do not rate limit peer connections that match on the supplied networks from the net restrict configuration +* Reduce p2p discv4 default refresh interval + +## [v1.0.0-beta.7] + +* Create Prevrandao fork +* Fix issues syncing with testnet and mainnet relating to Prevrandao fork + +## [v1.0.0-beta.6] + +* Update geth version logic to be based on immutable/go-ethereum releases +* Add `downloader/sync` metric + +## [v1.0.0-beta.5] + +* Pull v1.13.14 from upstream: [ethereum/go-ethereum](https://github.com/ethereum/go-ethereum) +* Make `DefaultBaseFeeChangeDenominator` consistent with upstream (8) and mutate it based on chain configuration + * If chain configuration matches Immutable zkEVM network ID and clique settings, set `DefaultBaseFeeChangeDenominator` to 50 +* Validate chain configuration based on expected values if Immutable zkEVM network ID is specified +* Use minimum price limit instead of last price to represent the priority fee of each fetched empty block inside suggested tip cap endpoint. +* Add block period, block propagation, and suggested tip cap metrics + +## [v1.0.0-beta.4] + +- Fix PREVRANDAO opcode on shanghai network by setting random to mixhash + +## [v1.0.0-beta.3] + +- Testing release flow + +## [v1.0.0-beta.2] + +- Enforce minimum price limit on suggested tip cap endpoint + +## [v1.0.0-beta.1] + +This release enables Shanghai fork on all Immutable zkEVM networks. +| Network | Unix Timestamp | Date | +| -------- | ------- | ------- | +| Devnet | 1709067600 | Tue Feb 27 21:00:00 UTC 2024 | +| Testnet | 1710280800 | Tue Mar 12 22:00:00 UTC 2024 | +| Mainnet | 1711490400 | Tue Mar 26 22:00:00 UTC 2024 | + +The following changes were made to support the forks: +- Update clique to + - allow for Shanghai to be enabled; and + - log more detail around existence of withdrawals and withdrawals hashes +- Update geth node initialization from genesis + - Add command to be used instead of `geth init` + ```sh + geth immutable bootstrap rpc --zkevm testnet --datadir "..." + ``` + - genesis.json has been removed from Docker image +- Update geth node run configuration + - Add `--zkevm [testnet|mainnet]` flag to automatically configure genesis and fork overrides + +## [v0.0.16] + +- Add `geth immutable run boot` command for running boot node on cluster +- Move boot node p2p keys to Secrets Manager +- Update logs around allowlists +- Added more logging for ACL initialisation and a log when ACL rejects a TX from pool + +## [v0.0.15] + +- Re-enabled single sequencer invariants + +## [v0.0.14] + +- Change pod initialization process to use Put instead of Push + +## [v0.0.13] + +- Implement Immutable wallet backend + - Add AWS Secrets Manager backend store implementation for private key access via AWS + - Add local keystore backend store implementation for testing purposes + - Add `GETH_FLAG_IMMUTABLE_AWS_REGION` and `POD_NAMESPACE` env var support for configuring aws wallet backend + - Add `GETH_FLAG_PASSWORD_FILEPATH` env var to existing password filepath flag +- Added flag to disable Clique endpoints on RPC server +- Added Clique Client +- Added CLI for Clique Voting + +## [v0.0.12] + +- Revert dialHistoryExpiration change back to 35s + +## [v0.0.11] + +- Revert change to always drop peers when find node errors are greater than the threshold +- Reduce dialHistoryExpiration from 35 seconds to 5 + +## [v0.0.10] + +- Decrease `seedMaxAge` to 1 second to ensure that nodes always query boot node for peer IPs +- Always drop peers when find node errors are greater than the threshold + +## [v0.0.9] + +- Automation / CD updates only. Releasing with latest prod workflows. + +## [v0.0.8] + +- Added max block range for logs queried using eth_getLogs (#346) +- Default mainnet configuration (`/etc/geth/mainnet.toml`) and genesis (`/etc/geth/mainnet.json`) added to Docker image + +## [v0.0.7] + +- Automation / CD updates only: immutable init command + +## [v0.0.6] + +- Automation / CD updates only + +## [v0.0.5] + +- Reduce price limit 100->10 Gwei + +## [v0.0.4] + +### Added + +- Default testnet configuration (`/etc/geth/testnet.toml`) and genesis (`/etc/geth/testnet.json`) added to Docker image + +## [v0.0.3] + +### Added + +- Add NewRelic agent to geth runtime for application metrics + +### Fixed + +- Price limit being 100 Gwei rather than 100 Gwei - 1 Wei +- Fixed bug in ACLs that caused transactions to be blocked when they should not have been + +## [v0.0.2] + +### Changed + +- Change to add access control layer to geth legacypool, which safeguards txs + from entering txpool if the sender is part of a collective SDN list (#17) +- Mempool Rebroadcasting: Legacy pool now rebroadcasts pending transactions + whenever a pool reorg is triggered. Pool reorgs are triggered when a new block + is received - (#103) +- Transaction Broadcasting: Transactions are broadcast to all peers, not a + square root. All peers get the transaction, regardless of whether they may + have previously received it. All peers get the full transaction payload, i.e. + no more announcements. (#103) +- Changed `maxUncleDist` from 7 to 0. This means old blocks are rejected, + reducing chance of reorgs. (#156) +- Changed `maxQueueDist` from 32 to 256. This means more blocks in the future + can be received without being rejected. This is to account for our increased + block production due to reduced block time. (#156) +- Changed `blockLimit` from 64 to 256. Since we only have a small amount of + peers, more will come from the same set of peers, so we need to allow for + this. (#156) +- Added invariants to prevents reorgs (#175) +- Added check for NoLocals on legacytxpool to reject underpriced TXs +- Changed the `DefaultBaseFeeChangeDenominator` from 8 to 50. This makes the max + base fee rate of change 2%, instead of 12.5%. With a block time of 2 seconds, + we match Ethereum in that it would take 72 seconds for the base fee to double. + (#178) +- Mine a tx even if its effective tipcap (tipcap-basefee) is less than miner's minimum tipcap (which is pricelimit, or 100 gwei) (#258) + +### Added + +- CLI commands for bootstrapping local and remote Immutable chains +- Added CLI parameter options for the geth immutable bootstraper, which can + potentially takes in a list of filepaths for blocklists and allowlists (#83) +- Added flags to disable Admin/Txpool/Engine/Debug endpoints on RPC server + +### Fixed + +[unreleased]: https://github.com/immutable/go-ethereum/compare/v1.11.6-beta.1...HEAD diff --git a/Dockerfile b/Dockerfile index ed69a0478..4670fefcf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,17 +12,25 @@ RUN apk add --no-cache gcc musl-dev linux-headers git 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 +FROM alpine:3.18.4 RUN apk add --no-cache ca-certificates COPY --from=builder /go-ethereum/build/bin/geth /usr/local/bin/ -EXPOSE 8545 8546 30303 30303/udp +# Default config +RUN mkdir /etc/geth +ADD ./immutable/config/testnet.toml /etc/geth/testnet.toml +ADD ./immutable/config/mainnet.toml /etc/geth/mainnet.toml + +# Public config +ADD ./immutable/config/testnet-public.toml /etc/geth/testnet-public.toml +ADD ./immutable/config/mainnet-public.toml /etc/geth/mainnet-public.toml + +EXPOSE 8545 8546 30300 30300/udp ENTRYPOINT ["geth"] # Add some metadata labels to help programatic image consumption diff --git a/Makefile b/Makefile index 99b8ba54b..9472d9d31 100644 --- a/Makefile +++ b/Makefile @@ -43,6 +43,10 @@ devtools: @type "solc" 2> /dev/null || echo 'Please install solc' @type "protoc" 2> /dev/null || echo 'Please install protoc' +# CHANGE(immutable): immutable specific makes +immutable-test: + go test -run Immutable ./... | grep -v "\[no tests to run\]" | grep -v "\[no test files\]" + #? help: Get more info on make commands. help: Makefile @echo " Choose a command run in go-ethereum:" diff --git a/README.md b/README.md index 1e8dba809..20869a741 100644 --- a/README.md +++ b/README.md @@ -1,55 +1,49 @@ -## Go Ethereum +# Immutable Geth -Golang execution layer implementation of the Ethereum protocol. +Golang execution layer implementation of the Ethereum protocol. Modified for the purposes of the Immutable zkEVM. -[![API Reference]( -https://pkg.go.dev/badge/github.com/ethereum/go-ethereum -)](https://pkg.go.dev/github.com/ethereum/go-ethereum?tab=doc) -[![Go Report Card](https://goreportcard.com/badge/github.com/ethereum/go-ethereum)](https://goreportcard.com/report/github.com/ethereum/go-ethereum) -[![Travis](https://app.travis-ci.com/ethereum/go-ethereum.svg?branch=master)](https://app.travis-ci.com/github/ethereum/go-ethereum) -[![Discord](https://img.shields.io/badge/discord-join%20chat-blue.svg)](https://discord.gg/nthXNEv) +All modifications made by Immutable are either contained in files named `*immutable*.go` or have `CHANGE(immutable):` comments above or inside the modified lines of code. -Automated builds are available for stable releases and the unstable master branch. Binary -archives are published at https://geth.ethereum.org/downloads/. +## Build -## Building the source +With Golang 1.20 installed, run: -For prerequisites and detailed build instructions please read the [Installation Instructions](https://geth.ethereum.org/docs/getting-started/installing-geth). - -Building `geth` requires both a Go (version 1.19 or later) and a C compiler. You can install -them using your favourite package manager. Once the dependencies are installed, run - -```shell +``` make geth ``` or, to build the full suite of utilities: -```shell +``` make all ``` -## Executables +The built client binary is `./build/bin/geth` + +You can run a local network with your built binary via the `immutable bootstrap local` command: +``` +now=$(date +%s) +./build/bin/geth immutable bootstrap local \ +--override.shanghai="$now" \ +--override.prevrandao="$now" \ +--override.cancun="$now" +``` + +You can run the E2E tests against your built binary via: +``` +.github/scripts/bootstrap_test.sh +``` -The go-ethereum project comes with several wrappers/executables found in the `cmd` -directory. +## Docker -| Command | Description | -| :--------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **`geth`** | Our main Ethereum CLI client. It is the entry point into the Ethereum network (main-, test- or private net), capable of running as a full node (default), archive node (retaining all historical state) or a light node (retrieving data live). It can be used by other processes as a gateway into the Ethereum network via JSON RPC endpoints exposed on top of HTTP, WebSocket and/or IPC transports. `geth --help` and the [CLI page](https://geth.ethereum.org/docs/fundamentals/command-line-options) for command line options. | -| `clef` | Stand-alone signing tool, which can be used as a backend signer for `geth`. | -| `devp2p` | Utilities to interact with nodes on the networking layer, without running a full blockchain. | -| `abigen` | Source code generator to convert Ethereum contract definitions into easy-to-use, compile-time type-safe Go packages. It operates on plain [Ethereum contract ABIs](https://docs.soliditylang.org/en/develop/abi-spec.html) with expanded functionality if the contract bytecode is also available. However, it also accepts Solidity source files, making development much more streamlined. Please see our [Native DApps](https://geth.ethereum.org/docs/developers/dapp-developer/native-bindings) page for details. | -| `bootnode` | Stripped down version of our Ethereum client implementation that only takes part in the network node discovery protocol, but does not run any of the higher level application protocols. It can be used as a lightweight bootstrap node to aid in finding peers in private networks. | -| `evm` | Developer utility version of the EVM (Ethereum Virtual Machine) that is capable of running bytecode snippets within a configurable environment and execution mode. Its purpose is to allow isolated, fine-grained debugging of EVM opcodes (e.g. `evm --code 60ff60ff --debug run`). | -| `rlpdump` | Developer utility tool to convert binary RLP ([Recursive Length Prefix](https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp)) dumps (data encoding used by the Ethereum protocol both network as well as consensus wise) to user-friendlier hierarchical representation (e.g. `rlpdump --hex CE0183FFFFFFC4C304050583616263`). | +The client is distributed as the following Docker image: +``` +docker pull ghcr.io/immutable/go-ethereum/go-ethereum:latest +``` -## Running `geth` +## Run -Going through all the possible command line flags is out of scope here (please consult our -[CLI Wiki page](https://geth.ethereum.org/docs/fundamentals/command-line-options)), -but we've enumerated a few common parameter combos to get you up to speed quickly -on how you can run your own `geth` instance. +If you wish to join the P2P network, you must follow [these](https://docs.axelar.dev/validator/external-chains/immutable) instructions. ### Hardware Requirements @@ -67,265 +61,9 @@ Recommended: * High-performance SSD with at least 1TB of free space * 25+ MBit/sec download Internet service -### Full node on the main Ethereum network - -By far the most common scenario is people wanting to simply interact with the Ethereum -network: create accounts; transfer funds; deploy and interact with contracts. For this -particular use case, the user doesn't care about years-old historical data, so we can -sync quickly to the current state of the network. To do so: - -```shell -$ geth console -``` - -This command will: - * Start `geth` in snap sync mode (default, can be changed with the `--syncmode` flag), - causing it to download more data in exchange for avoiding processing the entire history - of the Ethereum network, which is very CPU intensive. - * Start the built-in interactive [JavaScript console](https://geth.ethereum.org/docs/interacting-with-geth/javascript-console), - (via the trailing `console` subcommand) through which you can interact using [`web3` methods](https://github.com/ChainSafe/web3.js/blob/0.20.7/DOCUMENTATION.md) - (note: the `web3` version bundled within `geth` is very old, and not up to date with official docs), - as well as `geth`'s own [management APIs](https://geth.ethereum.org/docs/interacting-with-geth/rpc). - This tool is optional and if you leave it out you can always attach it to an already running - `geth` instance with `geth attach`. - -### A Full node on the Görli test network - -Transitioning towards developers, if you'd like to play around with creating Ethereum -contracts, you almost certainly would like to do that without any real money involved until -you get the hang of the entire system. In other words, instead of attaching to the main -network, you want to join the **test** network with your node, which is fully equivalent to -the main network, but with play-Ether only. - -```shell -$ geth --goerli console -``` - -The `console` subcommand has the same meaning as above and is equally -useful on the testnet too. - -Specifying the `--goerli` flag, however, will reconfigure your `geth` instance a bit: - - * Instead of connecting to the main Ethereum network, the client will connect to the Görli - test network, which uses different P2P bootnodes, different network IDs and genesis - states. - * Instead of using the default data directory (`~/.ethereum` on Linux for example), `geth` - will nest itself one level deeper into a `goerli` subfolder (`~/.ethereum/goerli` on - Linux). Note, on OSX and Linux this also means that attaching to a running testnet node - requires the use of a custom endpoint since `geth attach` will try to attach to a - production node endpoint by default, e.g., - `geth attach /goerli/geth.ipc`. Windows users are not affected by - this. - -*Note: Although some internal protective measures prevent transactions from -crossing over between the main network and test network, you should always -use separate accounts for play and real money. Unless you manually move -accounts, `geth` will by default correctly separate the two networks and will not make any -accounts available between them.* - -### Configuration - -As an alternative to passing the numerous flags to the `geth` binary, you can also pass a -configuration file via: - -```shell -$ geth --config /path/to/your_config.toml -``` - -To get an idea of how the file should look like you can use the `dumpconfig` subcommand to -export your existing configuration: - -```shell -$ geth --your-favourite-flags dumpconfig -``` - -*Note: This works only with `geth` v1.6.0 and above.* - -#### Docker quick start - -One of the quickest ways to get Ethereum up and running on your machine is by using -Docker: - -```shell -docker run -d --name ethereum-node -v /Users/alice/ethereum:/root \ - -p 8545:8545 -p 30303:30303 \ - ethereum/client-go -``` - -This will start `geth` in snap-sync mode with a DB memory allowance of 1GB, as the -above command does. It will also create a persistent volume in your home directory for -saving your blockchain as well as map the default ports. There is also an `alpine` tag -available for a slim version of the image. - -Do not forget `--http.addr 0.0.0.0`, if you want to access RPC from other containers -and/or hosts. By default, `geth` binds to the local interface and RPC endpoints are not -accessible from the outside. - -### Programmatically interfacing `geth` nodes - -As a developer, sooner rather than later you'll want to start interacting with `geth` and the -Ethereum network via your own programs and not manually through the console. To aid -this, `geth` has built-in support for a JSON-RPC based APIs ([standard APIs](https://ethereum.github.io/execution-apis/api-documentation/) -and [`geth` specific APIs](https://geth.ethereum.org/docs/interacting-with-geth/rpc)). -These can be exposed via HTTP, WebSockets and IPC (UNIX sockets on UNIX based -platforms, and named pipes on Windows). - -The IPC interface is enabled by default and exposes all the APIs supported by `geth`, -whereas the HTTP and WS interfaces need to manually be enabled and only expose a -subset of APIs due to security reasons. These can be turned on/off and configured as -you'd expect. - -HTTP based JSON-RPC API options: - - * `--http` Enable the HTTP-RPC server - * `--http.addr` HTTP-RPC server listening interface (default: `localhost`) - * `--http.port` HTTP-RPC server listening port (default: `8545`) - * `--http.api` API's offered over the HTTP-RPC interface (default: `eth,net,web3`) - * `--http.corsdomain` Comma separated list of domains from which to accept cross origin requests (browser enforced) - * `--ws` Enable the WS-RPC server - * `--ws.addr` WS-RPC server listening interface (default: `localhost`) - * `--ws.port` WS-RPC server listening port (default: `8546`) - * `--ws.api` API's offered over the WS-RPC interface (default: `eth,net,web3`) - * `--ws.origins` Origins from which to accept WebSocket requests - * `--ipcdisable` Disable the IPC-RPC server - * `--ipcapi` API's offered over the IPC-RPC interface (default: `admin,debug,eth,miner,net,personal,txpool,web3`) - * `--ipcpath` Filename for IPC socket/pipe within the datadir (explicit paths escape it) - -You'll need to use your own programming environments' capabilities (libraries, tools, etc) to -connect via HTTP, WS or IPC to a `geth` node configured with the above flags and you'll -need to speak [JSON-RPC](https://www.jsonrpc.org/specification) on all transports. You -can reuse the same connection for multiple requests! - -**Note: Please understand the security implications of opening up an HTTP/WS based -transport before doing so! Hackers on the internet are actively trying to subvert -Ethereum nodes with exposed APIs! Further, all browser tabs can access locally -running web servers, so malicious web pages could try to subvert locally available -APIs!** - -### Operating a private network - -Maintaining your own private network is more involved as a lot of configurations taken for -granted in the official networks need to be manually set up. - -#### Defining the private genesis state - -First, you'll need to create the genesis state of your networks, which all nodes need to be -aware of and agree upon. This consists of a small JSON file (e.g. call it `genesis.json`): - -```json -{ - "config": { - "chainId": , - "homesteadBlock": 0, - "eip150Block": 0, - "eip155Block": 0, - "eip158Block": 0, - "byzantiumBlock": 0, - "constantinopleBlock": 0, - "petersburgBlock": 0, - "istanbulBlock": 0, - "berlinBlock": 0, - "londonBlock": 0 - }, - "alloc": {}, - "coinbase": "0x0000000000000000000000000000000000000000", - "difficulty": "0x20000", - "extraData": "", - "gasLimit": "0x2fefd8", - "nonce": "0x0000000000000042", - "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "timestamp": "0x00" -} -``` - -The above fields should be fine for most purposes, although we'd recommend changing -the `nonce` to some random value so you prevent unknown remote nodes from being able -to connect to you. If you'd like to pre-fund some accounts for easier testing, create -the accounts and populate the `alloc` field with their addresses. - -```json -"alloc": { - "0x0000000000000000000000000000000000000001": { - "balance": "111111111" - }, - "0x0000000000000000000000000000000000000002": { - "balance": "222222222" - } -} -``` - -With the genesis state defined in the above JSON file, you'll need to initialize **every** -`geth` node with it prior to starting it up to ensure all blockchain parameters are correctly -set: - -```shell -$ geth init path/to/genesis.json -``` - -#### Creating the rendezvous point - -With all nodes that you want to run initialized to the desired genesis state, you'll need to -start a bootstrap node that others can use to find each other in your network and/or over -the internet. The clean way is to configure and run a dedicated bootnode: - -```shell -$ bootnode --genkey=boot.key -$ bootnode --nodekey=boot.key -``` - -With the bootnode online, it will display an [`enode` URL](https://ethereum.org/en/developers/docs/networking-layer/network-addresses/#enode) -that other nodes can use to connect to it and exchange peer information. Make sure to -replace the displayed IP address information (most probably `[::]`) with your externally -accessible IP to get the actual `enode` URL. - -*Note: You could also use a full-fledged `geth` node as a bootnode, but it's the less -recommended way.* - -#### Starting up your member nodes - -With the bootnode operational and externally reachable (you can try -`telnet ` to ensure it's indeed reachable), start every subsequent `geth` -node pointed to the bootnode for peer discovery via the `--bootnodes` flag. It will -probably also be desirable to keep the data directory of your private network separated, so -do also specify a custom `--datadir` flag. - -```shell -$ geth --datadir=path/to/custom/data/folder --bootnodes= -``` - -*Note: Since your network will be completely cut off from the main and test networks, you'll -also need to configure a miner to process transactions and create new blocks for you.* - -#### Running a private miner - - -In a private network setting a single CPU miner instance is more than enough for -practical purposes as it can produce a stable stream of blocks at the correct intervals -without needing heavy resources (consider running on a single thread, no need for multiple -ones either). To start a `geth` instance for mining, run it with all your usual flags, extended -by: - -```shell -$ geth --mine --miner.threads=1 --miner.etherbase=0x0000000000000000000000000000000000000000 -``` - -Which will start mining blocks and transactions on a single CPU thread, crediting all -proceedings to the account specified by `--miner.etherbase`. You can further tune the mining -by changing the default gas limit blocks converge to (`--miner.targetgaslimit`) and the price -transactions are accepted at (`--miner.gasprice`). - ## Contribution -Thank you for considering helping 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 Discord Server](https://discord.gg/invite/nthXNEv) -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. +We welcome any contributions and aim to respond promptly to issues and pull requests. Please make sure your contributions adhere to our coding guidelines: @@ -333,26 +71,14 @@ Please make sure your contributions adhere to our coding guidelines: 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" - -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. - -### Contributing to geth.ethereum.org - -For contributions to the [go-ethereum website](https://geth.ethereum.org), please checkout and raise pull requests against the `website` branch. -For more detailed instructions please see the `website` branch [README](https://github.com/ethereum/go-ethereum/tree/website#readme) or the -[contributing](https://geth.ethereum.org/docs/developers/geth-developer/contributing) page of the website. + * Pull requests need to be based on and opened against the `main` branch. ## License -The go-ethereum library (i.e. all code outside of the `cmd` directory) is licensed under the +The Immutable go-ethereum library (i.e. all code outside of the `cmd` directory) is licensed under the [GNU Lesser General Public License v3.0](https://www.gnu.org/licenses/lgpl-3.0.en.html), also included in our repository in the `COPYING.LESSER` file. -The go-ethereum binaries (i.e. all code inside of the `cmd` directory) are licensed under the +The Immutable go-ethereum binaries (i.e. all code inside of the `cmd` directory) are licensed under the [GNU General Public License v3.0](https://www.gnu.org/licenses/gpl-3.0.en.html), also included in our repository in the `COPYING` file. diff --git a/SECURITY.md b/SECURITY.md index 41b900d5e..b72cf86d6 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,175 +1,15 @@ # 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 +Security vulnerabilities should be disclosed to the project maintainers through our public [immutable bug bounty program](https://bugcrowd.com/immutable-og) or email us at security@immutable.com. -| 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) | +## Security Patches -## Reporting a Vulnerability +Security vulnerabilities will be patched as soon as responsibly possible, and published as an advisory on this repository. -**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. +## Supported Versions -Fingerprint: `AE96 ED96 9E47 9B00 84F3 E17F E88D 3334 FA5F 6A0A` +Please see [Releases](https://github.com/immutable/geth/releases). We recommend using the [most recently released version](https://github.com/immutable/geth/releases/latest). -``` ------BEGIN PGP PUBLIC KEY BLOCK----- -Version: SKS 1.1.6 -Comment: Hostname: pgp.mit.edu +Security patches will be released for the latest minor of a given major release. For example, if an issue is found in versions >=1.1.0 and the latest is 1.8.0, the patch will be released only in version 1.8.1. -mQINBFgl3tgBEAC8A1tUBkD9YV+eLrOmtgy+/JS/H9RoZvkg3K1WZ8IYfj6iIRaYneAk3Bp1 -82GUPVz/zhKr2g0tMXIScDR3EnaDsY+Qg+JqQl8NOG+Cikr1nnkG2on9L8c8yiqry1ZTCmYM -qCa2acTFqnyuXJ482aZNtB4QG2BpzfhW4k8YThpegk/EoRUim+y7buJDtoNf7YILlhDQXN8q -lHB02DWOVUihph9tUIFsPK6BvTr9SIr/eG6j6k0bfUo9pexOn7LS4SojoJmsm/5dp6AoKlac -48cZU5zwR9AYcq/nvkrfmf2WkObg/xRdEvKZzn05jRopmAIwmoC3CiLmqCHPmT5a29vEob/y -PFE335k+ujjZCPOu7OwjzDk7M0zMSfnNfDq8bXh16nn+ueBxJ0NzgD1oC6c2PhM+XRQCXCho -yI8vbfp4dGvCvYqvQAE1bWjqnumZ/7vUPgZN6gDfiAzG2mUxC2SeFBhacgzDvtQls+uuvm+F -nQOUgg2Hh8x2zgoZ7kqV29wjaUPFREuew7e+Th5BxielnzOfVycVXeSuvvIn6cd3g/s8mX1c -2kLSXJR7+KdWDrIrR5Az0kwAqFZt6B6QTlDrPswu3mxsm5TzMbny0PsbL/HBM+GZEZCjMXxB -8bqV2eSaktjnSlUNX1VXxyOxXA+ZG2jwpr51egi57riVRXokrQARAQABtDRFdGhlcmV1bSBG -b3VuZGF0aW9uIEJ1ZyBCb3VudHkgPGJvdW50eUBldGhlcmV1bS5vcmc+iQIcBBEBCAAGBQJa -FCY6AAoJEHoMA3Q0/nfveH8P+gJBPo9BXZL8isUfbUWjwLi81Yi70hZqIJUnz64SWTqBzg5b -mCZ69Ji5637THsxQetS2ARabz0DybQ779FhD/IWnqV9T3KuBM/9RzJtuhLzKCyMrAINPMo28 -rKWdunHHarpuR4m3tL2zWJkle5QVYb+vkZXJJE98PJw+N4IYeKKeCs2ubeqZu636GA0sMzzB -Jn3m/dRRA2va+/zzbr6F6b51ynzbMxWKTsJnstjC8gs8EeI+Zcd6otSyelLtCUkk3h5sTvpV -Wv67BNSU0BYsMkxyFi9PUyy07Wixgeas89K5jG1oOtDva/FkpRHrTE/WA5OXDRcLrHJM+SwD -CwqcLQqJd09NxwUW1iKeBmPptTiOGu1Gv2o7aEyoaWrHRBO7JuYrQrj6q2B3H1Je0zjAd2qt -09ni2bLwLn4LA+VDpprNTO+eZDprv09s2oFSU6NwziHybovu0y7X4pADGkK2evOM7c86PohX -QRQ1M1T16xLj6wP8/Ykwl6v/LUk7iDPXP3GPILnh4YOkwBR3DsCOPn8098xy7FxEELmupRzt -Cj9oC7YAoweeShgUjBPzb+nGY1m6OcFfbUPBgFyMMfwF6joHbiVIO+39+Ut2g2ysZa7KF+yp -XqVDqyEkYXsOLb25OC7brt8IJEPgBPwcHK5GNag6RfLxnQV+iVZ9KNH1yQgSiQI+BBMBAgAo -AhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAUCWglh+gUJBaNgWAAKCRDojTM0+l9qCgQ2 -D/4udJpV4zGIZW1yNaVvtd3vfKsTLi7GIRJLUBqVb2Yx/uhnN8jTl/tAhCVosCQ1pzvi9kMl -s8qO1vu2kw5EWFFkwK96roI8pTql3VIjwhRVQrCkR7oAk/eUd1U/nt2q6J4UTYeVgqbq4dsI -ZZTRyPJMD667YpuAIcaah+w9j/E5xksYQdMeprnDrQkkBCb4FIMqfDzBPKvEa8DcQr949K85 -kxhr6LDq9i5l4Egxt2JdH8DaR4GLca6+oHy0MyPs/bZOsfmZUObfM2oZgPpqYM96JanhzO1j -dpnItyBii2pc+kNx5nMOf4eikE/MBv+WUJ0TttWzApGGmFUzDhtuEvRH9NBjtJ/pMrYspIGu -O/QNY5KKOKQTvVIlwGcm8dTsSkqtBDSUwZyWbfKfKOI1/RhM9dC3gj5/BOY57DYYV4rdTK01 -ZtYjuhdfs2bhuP1uF/cgnSSZlv8azvf7Egh7tHPnYxvLjfq1bJAhCIX0hNg0a81/ndPAEFky -fSko+JPKvdSvsUcSi2QQ4U2HX//jNBjXRfG4F0utgbJnhXzEckz6gqt7wSDZH2oddVuO8Ssc -T7sK+CdXthSKnRyuI+sGUpG+6glpKWIfYkWFKNZWuQ+YUatY3QEDHXTIioycSmV8p4d/g/0S -V6TegidLxY8bXMkbqz+3n6FArRffv5MH7qt3cYkCPgQTAQIAKAUCWCXhOwIbAwUJAeEzgAYL -CQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQ6I0zNPpfagrN/w/+Igp3vtYdNunikw3yHnYf -Jkm0MmaMDUM9mtsaXVN6xb9n25N3Xa3GWCpmdsbYZ8334tI/oQ4/NHq/bEI5WFH5F1aFkMkm -5AJVLuUkipCtmCZ5NkbRPJA9l0uNUUE6uuFXBhf4ddu7jb0jMetRF/kifJHVCCo5fISUNhLp -7bwcWq9qgDQNZNYMOo4s9WX5Tl+5x4gTZdd2/cAYt49h/wnkw+huM+Jm0GojpLqIQ1jZiffm -otf5rF4L+JhIIdW0W4IIh1v9BhHVllXw+z9oj0PALstT5h8/DuKoIiirFJ4DejU85GR1KKAS -DeO19G/lSpWj1rSgFv2N2gAOxq0X+BbQTua2jdcY6JpHR4H1JJ2wzfHsHPgDQcgY1rGlmjVF -aqU73WV4/hzXc/HshK/k4Zd8uD4zypv6rFsZ3UemK0aL2zXLVpV8SPWQ61nS03x675SmDlYr -A80ENfdqvsn00JQuBVIv4Tv0Ub7NfDraDGJCst8rObjBT/0vnBWTBCebb2EsnS2iStIFkWdz -/WXs4L4Yzre1iJwqRjiuqahZR5jHsjAUf2a0O29HVHE7zlFtCFmLPClml2lGQfQOpm5klGZF -rmvus+qZ9rt35UgWHPZezykkwtWrFOwspwuCWaPDto6tgbRJZ4ftitpdYYM3dKW9IGJXBwrt -BQrMsu+lp0vDF+yJAlUEEwEIAD8CGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAFiEErpbt -lp5HmwCE8+F/6I0zNPpfagoFAmEAEJwFCQycmLgACgkQ6I0zNPpfagpWoBAAhOcbMAUw6Zt0 -GYzT3sR5/c0iatezPzXEXJf9ebzR8M5uPElXcxcnMx1dvXZmGPXPJKCPa99WCu1NZYy8F+Wj -GTOY9tfIkvSxhys1p/giPAmvid6uQmD+bz7ivktnyzCkDWfMA+l8lsCSEqVlaq6y5T+a6SWB -6TzC2S0MPb/RrC/7DpwyrNYWumvyVJh09adm1Mw/UGgst/sZ8eMaRYEd3X0yyT1CBpX4zp2E -qQj9IEOTizvzv1x2jkHe5ZUeU3+nTBNlhSA+WFHUi0pfBdo2qog3Mv2EC1P2qMKoSdD5tPbA -zql1yKoHHnXOMsqdftGwbiv2sYXWvrYvmaCd3Ys/viOyt3HOy9uV2ZEtBd9Yqo9x/NZj8QMA -nY5k8jjrIXbUC89MqrJsQ6xxWQIg5ikMT7DvY0Ln89ev4oJyVvwIQAwCm4jUzFNm9bZLYDOP -5lGJCV7tF5NYVU7NxNM8vescKc40mVNK/pygS5mxhK9QYOUjZsIv8gddrl1TkqrFMuxFnTyN -WvzE29wFu/n4N1DkF+ZBqS70SlRvB+Hjz5LrDgEzF1Wf1eA/wq1dZbvMjjDVIc2VGlYp8Cp2 -8ob23c1seTtYXTNYgSR5go4EpH+xi+bIWv01bQQ9xGwBbT5sm4WUeWOcmX4QewzLZ3T/wK9+ -N4Ye/hmU9O34FwWJOY58EIe0OUV0aGVyZXVtIEZvdW5kYXRpb24gU2VjdXJpdHkgVGVhbSA8 -c2VjdXJpdHlAZXRoZXJldW0ub3JnPokCHAQRAQgABgUCWhQmOgAKCRB6DAN0NP5372LSEACT -wZk1TASWZj5QF7rmkIM1GEyBxLE+PundNcMgM9Ktj1315ED8SmiukNI4knVS1MY99OIgXhQl -D1foF2GKdTomrwwC4012zTNyUYCY60LnPZ6Z511HG+rZgZtZrbkz0IiUpwAlhGQND77lBqem -J3K+CFX2XpDA/ojui/kqrY4cwMT5P8xPJkwgpRgw/jgdcZyJTsXdHblV9IGU4H1Vd1SgcfAf -Db3YxDUlBtzlp0NkZqxen8irLIXUQvsfuIfRUbUSkWoK/n3U/gOCajAe8ZNF07iX4OWjH4Sw -NDA841WhFWcGE+d8+pfMVfPASU3UPKH72uw86b2VgR46Av6voyMFd1pj+yCA+YAhJuOpV4yL -QaGg2Z0kVOjuNWK/kBzp1F58DWGh4YBatbhE/UyQOqAAtR7lNf0M3QF9AdrHTxX8oZeqVW3V -Fmi2mk0NwCIUv8SSrZr1dTchp04OtyXe5gZBXSfzncCSRQIUDC8OgNWaOzAaUmK299v4bvye -uSCxOysxC7Q1hZtjzFPKdljS81mRlYeUL4fHlJU9R57bg8mriSXLmn7eKrSEDm/EG5T8nRx7 -TgX2MqJs8sWFxD2+bboVEu75yuFmZ//nmCBApAit9Hr2/sCshGIEpa9MQ6xJCYUxyqeJH+Cc -Aja0UfXhnK2uvPClpJLIl4RE3gm4OXeE1IkCPgQTAQIAKAIbAwYLCQgHAwIGFQgCCQoLBBYC -AwECHgECF4AFAloJYfoFCQWjYFgACgkQ6I0zNPpfagr4MQ//cfp3GSbSG8dkqgctW67Fy7cQ -diiTmx3cwxY+tlI3yrNmdjtrIQMzGdqtY6LNz7aN87F8mXNf+DyVHX9+wd1Y8U+E+hVCTzKC -sefUfxTz6unD9TTcGqaoelgIPMn4IiKz1RZE6eKpfDWe6q78W1Y6x1bE0qGNSjqT/QSxpezF -E/OAm/t8RRxVxDtqz8LfH2zLea5zaC+ADj8EqgY9vX9TQa4DyVV8MgOyECCCadJQCD5O5hIA -B2gVDWwrAUw+KBwskXZ7Iq4reJTKLEmt5z9zgtJ/fABwaCFt66ojwg0/RjbO9cNA3ZwHLGwU -C6hkb6bRzIoZoMfYxVS84opiqf/Teq+t/XkBYCxbSXTJDA5MKjcVuw3N6YKWbkGP/EfQThe7 -BfAKFwwIw5YmsWjHK8IQj6R6hBxzTz9rz8y1Lu8EAAFfA7OJKaboI2qbOlauH98OuOUmVtr1 -TczHO+pTcgWVN0ytq2/pX5KBf4vbmULNbg3HFRq+gHx8CW+jyXGkcqjbgU/5FwtDxeqRTdGJ -SyBGNBEU6pBNolyynyaKaaJjJ/biY27pvjymL5rlz95BH3Dn16Z4RRmqwlT6eq/wFYginujg -CCE1icqOSE+Vjl7V8tV8AcgANkXKdbBE+Q8wlKsGI/kS1w4XFAYcaNHFT8qNeS8TSFXFhvU8 -HylYxO79t56JAj4EEwECACgFAlgl3tgCGwMFCQHhM4AGCwkIBwMCBhUIAgkKCwQWAgMBAh4B -AheAAAoJEOiNMzT6X2oKmUMP/0hnaL6bVyepAq2LIdvIUbHfagt/Oo/KVfZs4bkM+xJOitJR -0kwZV9PTihXFdzhL/YNWc2+LtEBtKItqkJZKmWC0E6OPXGVuU6hfFPebuzVccYJfm0Q3Ej19 -VJI9Uomf59Bpak8HYyEED7WVQjoYn7XVPsonwus/9+LDX+c5vutbrUdbjga3KjHbewD93X4O -wVVoXyHEmU2Plyg8qvzFbNDylCWO7N2McO6SN6+7DitGZGr2+jO+P2R4RT1cnl2V3IRVcWZ0 -OTspPSnRGVr2fFiHN/+v8G/wHPLQcJZFvYPfUGNdcYbTmhWdiY0bEYXFiNrgzCCsyad7eKUR -WN9QmxqmyqLDjUEDJCAh19ES6Vg3tqGwXk+uNUCoF30ga0TxQt6UXZJDEQFAGeASQ/RqE/q1 -EAuLv8IGM8o7IqKO2pWfLuqsY6dTbKBwDzz9YOJt7EOGuPPQbHxaYStTushZmJnm7hi8lhVG -jT7qsEJdE95Il+I/mHWnXsCevaXjZugBiyV9yvOq4Hwwe2s1zKfrnQ4u0cadvGAh2eIqum7M -Y3o6nD47aJ3YmEPX/WnhI56bACa2GmWvUwjI4c0/er3esSPYnuHnM9L8Am4qQwMVSmyU80tC -MI7A9e13Mvv+RRkYFLJ7PVPdNpbW5jqX1doklFpKf6/XM+B+ngYneU+zgCUBiQJVBBMBCAA/ -AhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgBYhBK6W7ZaeR5sAhPPhf+iNMzT6X2oKBQJh -ABCQBQkMnJi4AAoJEOiNMzT6X2oKAv0P+gJ3twBp5efNWyVLcIg4h4cOo9uD0NPvz8/fm2gX -FoOJL3MeigtPuSVfE9kuTaTuRbArzuFtdvH6G/kcRQvOlO4zyiIRHCk1gDHoIvvtn6RbRhVm -/Xo4uGIsFHst7n4A7BjicwEK5Op6Ih5Hoq19xz83YSBgBVk2fYEJIRyJiKFbyPjH0eSYe8v+ -Ra5/F85ugLx1P6mMVkW+WPzULns89riW7BGTnZmXFHZp8nO2pkUlcI7F3KRG7l4kmlC50ox6 -DiG/6AJCVulbAClky9C68TmJ/R1RazQxU/9IqVywsydq66tbJQbm5Z7GEti0C5jjbSRJL2oT -1xC7Rilr85PMREkPL3vegJdgj5PKlffZ/MocD/0EohiQ7wFpejFD4iTljeh0exRUwCRb6655 -9ib34JSQgU8Hl4JJu+mEgd9v0ZHD0/1mMD6fnAR84zca+O3cdASbnQmzTOKcGzLIrkE8TEnU -+2UZ8Ol7SAAqmBgzY1gKOilUho6dkyCAwNL+QDpvrITDPLEFPsjyB/M2KudZSVEn+Rletju1 -qkMW31qFMNlsbwzMZw+0USeGcs31Cs0B2/WQsro99CExlhS9auUFkmoVjJmYVTIYOM0zuPa4 -OyGspqPhRu5hEsmMDPDWD7Aad5k4GTqogQNnuKyRliZjXXrDZqFD5nfsJSL8Ky/sJGEMuQIN -BFgl3tgBEACbgq6HTN5gEBi0lkD/MafInmNi+59U5gRGYqk46WlfRjhHudXjDpgD0lolGb4h -YontkMaKRlCg2Rvgjvk3Zve0PKWjKw7gr8YBa9fMFY8BhAXI32OdyI9rFhxEZFfWAfwKVmT1 -9BdeAQRFvcfd+8w8f1XVc+zddULMJFBTr+xKDlIRWwTkdLPQeWbjo0eHl/g4tuLiLrTxVbnj -26bf+2+1DbM/w5VavzPrkviHqvKe/QP/gay4QDViWvFgLb90idfAHIdsPgflp0VDS5rVHFL6 -D73rSRdIRo3I8c8mYoNjSR4XDuvgOkAKW9LR3pvouFHHjp6Fr0GesRbrbb2EG66iPsR99MQ7 -FqIL9VMHPm2mtR+XvbnKkH2rYyEqaMbSdk29jGapkAWle4sIhSKk749A4tGkHl08KZ2N9o6G -rfUehP/V2eJLaph2DioFL1HxRryrKy80QQKLMJRekxigq8greW8xB4zuf9Mkuou+RHNmo8Pe -bHjFstLigiD6/zP2e+4tUmrT0/JTGOShoGMl8Rt0VRxdPImKun+4LOXbfOxArOSkY6i35+gs -gkkSy1gTJE0BY3S9auT6+YrglY/TWPQ9IJxWVOKlT+3WIp5wJu2bBKQ420VLqDYzkoWytel/ -bM1ACUtipMiIVeUs2uFiRjpzA1Wy0QHKPTdSuGlJPRrfcQARAQABiQIlBBgBAgAPAhsMBQJa -CWIIBQkFo2BYAAoJEOiNMzT6X2oKgSwQAKKs7BGF8TyZeIEO2EUK7R2bdQDCdSGZY06tqLFg -3IHMGxDMb/7FVoa2AEsFgv6xpoebxBB5zkhUk7lslgxvKiSLYjxfNjTBltfiFJ+eQnf+OTs8 -KeR51lLa66rvIH2qUzkNDCCTF45H4wIDpV05AXhBjKYkrDCrtey1rQyFp5fxI+0IQ1UKKXvz -ZK4GdxhxDbOUSd38MYy93nqcmclGSGK/gF8XiyuVjeifDCM6+T1NQTX0K9lneidcqtBDvlgg -JTLJtQPO33o5EHzXSiud+dKth1uUhZOFEaYRZoye1YE3yB0TNOOE8fXlvu8iuIAMBSDL9ep6 -sEIaXYwoD60I2gHdWD0lkP0DOjGQpi4ouXM3Edsd5MTi0MDRNTij431kn8T/D0LCgmoUmYYM -BgbwFhXr67axPZlKjrqR0z3F/Elv0ZPPcVg1tNznsALYQ9Ovl6b5M3cJ5GapbbvNWC7yEE1q -Scl9HiMxjt/H6aPastH63/7wcN0TslW+zRBy05VNJvpWGStQXcngsSUeJtI1Gd992YNjUJq4 -/Lih6Z1TlwcFVap+cTcDptoUvXYGg/9mRNNPZwErSfIJ0Ibnx9wPVuRN6NiCLOt2mtKp2F1p -M6AOQPpZ85vEh6I8i6OaO0w/Z0UHBwvpY6jDUliaROsWUQsqz78Z34CVj4cy6vPW2EF4iQIl -BBgBAgAPBQJYJd7YAhsMBQkB4TOAAAoJEOiNMzT6X2oKTjgP/1ojCVyGyvHMLUgnX0zwrR5Q -1M5RKFz6kHwKjODVLR3Isp8I935oTQt3DY7yFDI4t0GqbYRQMtxcNEb7maianhK2trCXfhPs -6/L04igjDf5iTcmzamXN6xnh5xkz06hZJJCMuu4MvKxC9MQHCVKAwjswl/9H9JqIBXAY3E2l -LpX5P+5jDZuPxS86p3+k4Rrdp9KTGXjiuEleM3zGlz5BLWydqovOck7C2aKh27ETFpDYY0z3 -yQ5AsPJyk1rAr0wrH6+ywmwWlzuQewavnrLnJ2M8iMFXpIhyHeEIU/f7o8f+dQk72rZ9CGzd -cqig2za/BS3zawZWgbv2vB2elNsIllYLdir45jxBOxx2yvJvEuu4glz78y4oJTCTAYAbMlle -5gVdPkVcGyvvVS9tinnSaiIzuvWrYHKWll1uYPm2Q1CDs06P5I7bUGAXpgQLUh/XQguy/0sX -GWqW3FS5JzP+XgcR/7UASvwBdHylubKbeqEpB7G1s+m+8C67qOrc7EQv3Jmy1YDOkhEyNig1 -rmjplLuir3tC1X+D7dHpn7NJe7nMwFx2b2MpMkLA9jPPAGPp/ekcu5sxCe+E0J/4UF++K+CR -XIxgtzU2UJfp8p9x+ygbx5qHinR0tVRdIzv3ZnGsXrfxnWfSOaB582cU3VRN9INzHHax8ETa -QVDnGO5uQa+FiQI8BBgBCAAmAhsMFiEErpbtlp5HmwCE8+F/6I0zNPpfagoFAmEAELYFCQyc -mN4ACgkQ6I0zNPpfagoqAQ/+MnDjBx8JWMd/XjeFoYKx/Oo0ntkInV+ME61JTBls4PdVk+TB -8PWZdPQHw9SnTvRmykFeznXIRzuxkowjrZYXdPXBxY2b1WyD5V3Ati1TM9vqpaR4osyPs2xy -I4dzDssh9YvUsIRL99O04/65lGiYeBNuACq+yK/7nD/ErzBkDYJHhMCdadbVWUACxvVIDvro -yQeVLKMsHqMCd8BTGD7VDs79NXskPnN77pAFnkzS4Z2b8SNzrlgTc5pUiuZHIXPIpEYmsYzh -ucTU6uI3dN1PbSFHK5tG2pHb4ZrPxY3L20Dgc2Tfu5/SDApZzwvvKTqjdO891MEJ++H+ssOz -i4O1UeWKs9owWttan9+PI47ozBSKOTxmMqLSQ0f56Np9FJsV0ilGxRKfjhzJ4KniOMUBA7mP -+m+TmXfVtthJred4sHlJMTJNpt+sCcT6wLMmyc3keIEAu33gsJj3LTpkEA2q+V+ZiP6Q8HRB -402ITklABSArrPSE/fQU9L8hZ5qmy0Z96z0iyILgVMLuRCCfQOMWhwl8yQWIIaf1yPI07xur -epy6lH7HmxjjOR7eo0DaSxQGQpThAtFGwkWkFh8yki8j3E42kkrxvEyyYZDXn2YcI3bpqhJx -PtwCMZUJ3kc/skOrs6bOI19iBNaEoNX5Dllm7UHjOgWNDQkcCuOCxucKano= -=arte ------END PGP PUBLIC KEY BLOCK------ -``` +Only critical severity bug fixes will be backported to past major releases. diff --git a/accounts/immutable/backend.go b/accounts/immutable/backend.go new file mode 100644 index 000000000..75f7ce0d7 --- /dev/null +++ b/accounts/immutable/backend.go @@ -0,0 +1,55 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package immutable + +import ( + "context" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/event" +) + +// Backend implements accounts.Backend. +// It is a simple backend that only supports one account. +// The wallet is created on startup and never upated (Subscribe is noop). +type Backend struct { + wallet accounts.Wallet +} + +// NewBackend constructs a backend instance using the secret store provided. +func NewBackend(ctx context.Context, store SecretStore) (*Backend, error) { + key, err := store.GetPrivateKey(ctx) + if err != nil { + return nil, err + } + return &Backend{ + wallet: newWallet(key), + }, nil +} + +// Wallets returns the single wallet supported by this backend. +func (b *Backend) Wallets() []accounts.Wallet { + return []accounts.Wallet{b.wallet} +} + +// Subscribe is noop for this backend because wallet never changes. +func (b *Backend) Subscribe(sink chan<- accounts.WalletEvent) event.Subscription { + return event.NewSubscription(func(quit <-chan struct{}) error { + <-quit + return nil + }) +} diff --git a/accounts/immutable/backend_test.go b/accounts/immutable/backend_test.go new file mode 100644 index 000000000..38b5ff566 --- /dev/null +++ b/accounts/immutable/backend_test.go @@ -0,0 +1,83 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package immutable + +import ( + "context" + "os" + "testing" + "time" + + "github.com/ethereum/go-ethereum/accounts" +) + +func TestImmutableBackend_WithKeyStore_ValidAddress(t *testing.T) { + // Initialize keystore + ks, err := NewKeystore("./testdata/keystore", "./testdata/password.txt") + if err != nil { + t.Fatal(err) + } + // Initialize backend + b, err := NewBackend(context.Background(), ks) + if err != nil { + t.Fatal(err) + } + // Get wallet + wallets := b.Wallets() + if len(wallets) != 1 { + t.Fatalf("expected 1 wallet, got %d", len(wallets)) + } + if len(wallets[0].Accounts()) != 1 { + t.Fatalf("expected 1 account, got %d", len(wallets[0].Accounts())) + } + // Compare wallet address + addressBytes, err := os.ReadFile("./testdata/address.txt") + if err != nil { + t.Fatal(err) + } + if wallets[0].Accounts()[0].Address.Hex() != string(addressBytes) { + t.Fatalf("expected address %s, got %s", string(addressBytes), wallets[0].Accounts()[0].Address.Hex()) + } +} + +func TestImmutableBackend_WithKeyStore_SubscribeNoOp(t *testing.T) { + // Initialize keystore + ks, err := NewKeystore("./testdata/keystore", "./testdata/password.txt") + if err != nil { + t.Fatal(err) + } + // Initialize backend + b, err := NewBackend(context.Background(), ks) + if err != nil { + t.Fatal(err) + } + + // Set up subscription + sink := make(chan accounts.WalletEvent) + sub := b.Subscribe(sink) + // Close subscription + go func() { + time.Sleep(1 * time.Second) + sub.Unsubscribe() + }() + // Run subscription until closed + for err := range sub.Err() { + if err != nil { + t.Fatal(err) + } + } +} diff --git a/accounts/immutable/errors.go b/accounts/immutable/errors.go new file mode 100644 index 000000000..7a524ab4c --- /dev/null +++ b/accounts/immutable/errors.go @@ -0,0 +1,24 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package immutable + +import "errors" + +var ( + // ErrInvalidAccount is returned when a wallet is asked to sign some data for an account it does not contain. + ErrInvalidAccount = errors.New("account specified is not provided by this wallet") +) diff --git a/accounts/immutable/keystore.go b/accounts/immutable/keystore.go new file mode 100644 index 000000000..906d75777 --- /dev/null +++ b/accounts/immutable/keystore.go @@ -0,0 +1,91 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package immutable + +import ( + "context" + "crypto/ecdsa" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/ethereum/go-ethereum/accounts/keystore" +) + +// KeyStore implements the SecretStore interface based on a keystore file on disk. +// It is intended to be used for testing only. It replicates the existing geth keystore +// functionality in a much simpler manner because we only ever deal with a single, static keystore file. +// We use this type to verify our custom backend works as expected. +type KeyStore struct { + key *ecdsa.PrivateKey + json string + pw string +} + +// NewKeystore instantiates a new KeyStore by reading the keystore file from disk and +// decrypting it with the provided password. There must only be one keystore file in the +// provided directory. +func NewKeystore(keystoreDirpath, passwordFilepath string) (*KeyStore, error) { + // Read keystore file + files, err := os.ReadDir(keystoreDirpath) + if err != nil { + return nil, fmt.Errorf("failed to read keystore dir: %w", err) + } + if len(files) != 1 || files[0].IsDir() { + return nil, fmt.Errorf("keystore dir must contain exactly one file") + } + // Read json file + keystoreJSON, err := os.ReadFile(filepath.Join(keystoreDirpath, files[0].Name())) + if err != nil { + return nil, fmt.Errorf("failed to read keystore file: %w", err) + } + // Read pw file + content, err := os.ReadFile(passwordFilepath) + if err != nil { + return nil, fmt.Errorf("failed to read password file: %w", err) + } + pw := strings.TrimRight(string(content), "\r\n") + + // Decrypt keystore + key, err := keystore.DecryptKey(keystoreJSON, pw) + if err != nil { + return nil, fmt.Errorf("failed to decrypt keystore: %w", err) + } + + return &KeyStore{ + key: key.PrivateKey, + json: string(keystoreJSON), + pw: pw, + }, nil +} + +// GetPrivateKey returns the wallet's private key after retrieving its hex-encoded format from +// keystore file on disk. +func (ks *KeyStore) GetPrivateKey(ctx context.Context) (*ecdsa.PrivateKey, error) { + return ks.key, nil +} + +// JSON returns the keystore file's content as a string. +func (ks *KeyStore) JSON() string { + return ks.json +} + +// Password returns the password used to decrypt the keystore file. +func (ks *KeyStore) Password() string { + return ks.pw +} diff --git a/accounts/immutable/keystore_test.go b/accounts/immutable/keystore_test.go new file mode 100644 index 000000000..0781ed43f --- /dev/null +++ b/accounts/immutable/keystore_test.go @@ -0,0 +1,61 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package immutable + +import ( + "context" + "encoding/json" + "os" + "testing" + + "github.com/ethereum/go-ethereum/crypto" +) + +func TestImmutableKeyStore_ReadKeyStoreFile_ValidKeystore(t *testing.T) { + ks, err := NewKeystore("./testdata/keystore", "./testdata/password.txt") + if err != nil { + t.Fatal(err) + } + key, err := ks.GetPrivateKey(context.Background()) + if err != nil { + t.Fatal(err) + } + + // Check address + addr := crypto.PubkeyToAddress(key.PublicKey) + addressBytes, err := os.ReadFile("./testdata/address.txt") + if err != nil { + t.Fatal(err) + } + if addr.Hex() != string(addressBytes) { + t.Fatalf("expected address %s, got %s", string(addressBytes), addr) + } + + // Check JSON + if err := json.Unmarshal([]byte(ks.JSON()), &struct{}{}); err != nil { + t.Fatal(err) + } + + // Check pw + pwBytes, err := os.ReadFile("./testdata/password.txt") + if err != nil { + t.Fatal(err) + } + if string(pwBytes) != ks.Password() { + t.Fatalf("expected pw %s, got %s", string(pwBytes), ks.Password()) + } +} diff --git a/accounts/immutable/secretsmanager.go b/accounts/immutable/secretsmanager.go new file mode 100644 index 000000000..c1053e4d5 --- /dev/null +++ b/accounts/immutable/secretsmanager.go @@ -0,0 +1,67 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package immutable + +import ( + "context" + "crypto/ecdsa" + "encoding/hex" + "fmt" + + "github.com/ethereum/go-ethereum/cmd/immutable/remote/aws" + "github.com/ethereum/go-ethereum/crypto" +) + +// AWSSecretsManagerStore implements the SecretStore interface. +// It uses an AWS Secrets Manager client to retrieve the private key intended +// to be used by a wallet. +type AWSSecretsManagerStore struct { + sm *aws.SecretsManager + validatorSecretKey string +} + +// NewAWSSecretsManagerStore instantiates a new AWSSecretsManagerStore. +func NewAWSSecretsManagerStore(region, validatorSecretKey string) (*AWSSecretsManagerStore, error) { + sm, err := aws.NewSecretsManager(region) + if err != nil { + return nil, err + } + return &AWSSecretsManagerStore{ + sm: sm, + validatorSecretKey: validatorSecretKey, + }, nil +} + +// GetPrivateKey returns the wallet's private key after retrieving its hex-encoded format from +// AWS Secrets Manager. +func (s *AWSSecretsManagerStore) GetPrivateKey(ctx context.Context) (*ecdsa.PrivateKey, error) { + // Get key from AWS Secrets Manager + privKeyHex, err := s.sm.GetSecret(ctx, s.validatorSecretKey) + if err != nil { + return nil, fmt.Errorf("failed to get priv key from store: %w", err) + } + // Decode hex key + privKeyRaw, err := hex.DecodeString(privKeyHex) + if err != nil { + return nil, fmt.Errorf("failed to decode priv key hex: %w", err) + } + key, err := crypto.ToECDSA(privKeyRaw) + if err != nil { + return nil, fmt.Errorf("failed to convert raw priv key to ECDSA: %w", err) + } + return key, nil +} diff --git a/accounts/immutable/store.go b/accounts/immutable/store.go new file mode 100644 index 000000000..279c53eff --- /dev/null +++ b/accounts/immutable/store.go @@ -0,0 +1,28 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package immutable + +import ( + "context" + "crypto/ecdsa" +) + +// SecretStore is an interface that allows the backend to retrieve data it needs +// to instantiate wallets. +type SecretStore interface { + GetPrivateKey(ctx context.Context) (*ecdsa.PrivateKey, error) +} diff --git a/accounts/immutable/testdata/address.txt b/accounts/immutable/testdata/address.txt new file mode 100644 index 000000000..c729c687e --- /dev/null +++ b/accounts/immutable/testdata/address.txt @@ -0,0 +1 @@ +0x084E5222E772B6f2C0a3F04F84D9BAB836e0eb4E \ No newline at end of file diff --git a/accounts/immutable/testdata/keystore/keystore.json b/accounts/immutable/testdata/keystore/keystore.json new file mode 100644 index 000000000..179bcdc63 --- /dev/null +++ b/accounts/immutable/testdata/keystore/keystore.json @@ -0,0 +1 @@ +{"address":"084e5222e772b6f2c0a3f04f84d9bab836e0eb4e","crypto":{"cipher":"aes-128-ctr","ciphertext":"4987547a93d0e928cabae873d38f6368f5a0090db5a22932ff74a6e58ee105e2","cipherparams":{"iv":"d941112519f96c63aeb09c691276b611"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"4e07f3aa37f669a58aa92ce2ea4a1249cf4366e1e9d534f38507290f4a8e82cd"},"mac":"e068f021e9890f9a997049939e90c2a9db2c4e8723107c1e634fac1a37c14baf"},"id":"ee6efd23-c845-4281-a2fb-169fb7d017c7","version":3} \ No newline at end of file diff --git a/accounts/immutable/testdata/password.txt b/accounts/immutable/testdata/password.txt new file mode 100644 index 000000000..7aa311adf --- /dev/null +++ b/accounts/immutable/testdata/password.txt @@ -0,0 +1 @@ +password \ No newline at end of file diff --git a/accounts/immutable/wallet.go b/accounts/immutable/wallet.go new file mode 100644 index 000000000..135aa5a5e --- /dev/null +++ b/accounts/immutable/wallet.go @@ -0,0 +1,130 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package immutable + +import ( + "crypto/ecdsa" + "errors" + "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" +) + +// wallet implements accounts.Wallet +type wallet struct { + key *ecdsa.PrivateKey + account accounts.Account +} + +func newWallet(key *ecdsa.PrivateKey) *wallet { + return &wallet{ + key: key, + account: accounts.Account{ + Address: crypto.PubkeyToAddress(key.PublicKey), + URL: accounts.URL{}, + }, + } +} + +// Accounts returns the single account supported by this wallet. +func (w *wallet) Accounts() []accounts.Account { + return []accounts.Account{ + w.account, + } +} + +// Contains returns whether a particular account is or is not wrapped by this wallet instance. +func (w *wallet) Contains(account accounts.Account) bool { + return w.account.Address == account.Address +} + +// 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) { + if !w.Contains(account) { + return nil, ErrInvalidAccount + } + // Sign the hash using plain ECDSA operations + return crypto.Sign(crypto.Keccak256(data), w.key) +} + +// SignText signs the hash of the given text with the given account. +func (w *wallet) SignText(account accounts.Account, text []byte) ([]byte, error) { + if !w.Contains(account) { + return nil, ErrInvalidAccount + } + // Sign the hash using plain ECDSA operations + return crypto.Sign(accounts.TextHash(text), w.key) +} + +// SignTx sends the transaction to the Immutable signer. +func (w *wallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { + if !w.Contains(account) { + return nil, ErrInvalidAccount + } + signer := types.LatestSignerForChainID(chainID) + return types.SignTx(tx, signer, w.key) +} + +// SignTextWithPassphrase is not implemented for Immutable signer. +func (w *wallet) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) { + return []byte{}, errors.New("password-operations not supported on immutable signers") +} + +// SignTxWithPassphrase is not implemented for Immutable signer. +func (w *wallet) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { + return nil, errors.New("password-operations not supported on immutable signers") +} + +// SignDataWithPassphrase is not implemented for Immutable signer. +func (w *wallet) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) { + return nil, errors.New("password-operations not supported on immutable signers") +} + +// Open is not implemented for Immutable signer. +func (w *wallet) Open(passphrase string) error { + return errors.New("operation not supported on immutable signers") +} + +// Closed is not implemented for Immutable signer. +func (w *wallet) Close() error { + return errors.New("operation not supported on immutable signers") +} + +// Derive is not implemented for Immutable signer. +func (w *wallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) { + return accounts.Account{}, errors.New("operation not supported on immutable signers") +} + +// SelfDerive is noop for Immutable signer. +func (w *wallet) SelfDerive(bases []accounts.DerivationPath, chain ethereum.ChainStateReader) {} + +// Status is required by the accounts.Wallet interface but is not required by the Immutable signer +// because it is not used by the backend. +func (w *wallet) Status() (string, error) { + return "unlocked", nil +} + +// URL is required by the accounts.Wallet interface but is not required by the Immutable signer. +func (w *wallet) URL() accounts.URL { + return accounts.URL{ + Scheme: "immutable", + Path: "", + } +} diff --git a/accounts/immutable/wallet_test.go b/accounts/immutable/wallet_test.go new file mode 100644 index 000000000..f8e6cab37 --- /dev/null +++ b/accounts/immutable/wallet_test.go @@ -0,0 +1,156 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package immutable + +import ( + "context" + "math/big" + "os" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/accounts/keystore" + "github.com/ethereum/go-ethereum/core/types" + "github.com/google/go-cmp/cmp" +) + +func TestImmutableWallet_FromKeystore_SignaturesNoErrors(t *testing.T) { + // Initialize keystore + ks, err := NewKeystore("./testdata/keystore", "./testdata/password.txt") + if err != nil { + t.Fatal(err) + } + // Initialize backend + b, err := NewBackend(context.Background(), ks) + if err != nil { + t.Fatal(err) + } + // Get wallet and sign data + w := b.Wallets()[0] + + tests := []struct { + data []byte + tx *types.Transaction + chainID *big.Int + }{ + { + data: []byte{1, 2, 3}, + tx: types.NewTx(&types.LegacyTx{}), + chainID: big.NewInt(1), + }, + } + for _, test := range tests { + if _, err := w.SignData(w.Accounts()[0], "", test.data); err != nil { + t.Fatal(err) + } + if _, err := w.SignText(w.Accounts()[0], test.data); err != nil { + t.Fatal(err) + } + if _, err := w.SignTx(w.Accounts()[0], test.tx, test.chainID); err != nil { + t.Fatal(err) + } + } +} + +func TestImmutableWallet_FromKeystore_SignaturesValid(t *testing.T) { + // Get default keystore wallet + ks := keystore.NewKeyStore("./testdata/keystore", keystore.StandardScryptN, keystore.StandardScryptP) + pwBytes, err := os.ReadFile("./testdata/password.txt") + if err != nil { + t.Fatal(err) + } + pw := strings.TrimRight(string(pwBytes), "\r\n") + if err := ks.Unlock(ks.Accounts()[0], pw); err != nil { + t.Fatal(err) + } + if len(ks.Accounts()) != 1 { + t.Fatalf("expected 1 account, got %d", len(ks.Accounts())) + } + if len(ks.Wallets()) != 1 { + t.Fatalf("expected 1 wallet, got %d", len(ks.Wallets())) + } + + // Get immutable wallet + immutableKeystore, err := NewKeystore("./testdata/keystore", "./testdata/password.txt") + if err != nil { + t.Fatal(err) + } + b, err := NewBackend(context.Background(), immutableKeystore) + if err != nil { + t.Fatal(err) + } + + tests := []struct { + walletLeft accounts.Wallet + walletRight accounts.Wallet + }{ + { + walletLeft: ks.Wallets()[0], + walletRight: b.Wallets()[0], + }, + } + for _, test := range tests { + // Sign data + data := []byte{1, 2, 3} + signatureLeft, err := test.walletLeft.SignData(test.walletLeft.Accounts()[0], "", data) + if err != nil { + t.Fatal(err) + } + signatureRight, err := test.walletRight.SignData(test.walletRight.Accounts()[0], "", data) + if err != nil { + t.Fatal(err) + } + if !cmp.Equal(signatureLeft, signatureRight) { + t.Fatalf("expected signature %s, got %s", string(signatureLeft), string(signatureRight)) + } + // Sign text + signatureLeft, err = test.walletLeft.SignText(test.walletLeft.Accounts()[0], data) + if err != nil { + t.Fatal(err) + } + signatureRight, err = test.walletRight.SignText(test.walletRight.Accounts()[0], data) + if err != nil { + t.Fatal(err) + } + if !cmp.Equal(signatureLeft, signatureRight) { + t.Fatalf("expected signature %s, got %s", string(signatureLeft), string(signatureRight)) + } + // Sign tx + tx := types.NewTx(&types.LegacyTx{}) + chainID := big.NewInt(1) + txLeft, err := test.walletLeft.SignTx(test.walletLeft.Accounts()[0], tx, chainID) + if err != nil { + t.Fatal(err) + } + txRight, err := test.walletRight.SignTx(test.walletRight.Accounts()[0], tx, chainID) + if err != nil { + t.Fatal(err) + } + lv, lr, ls := txLeft.RawSignatureValues() + rv, rr, rs := txRight.RawSignatureValues() + if !cmp.Equal(lv.String(), rv.String()) { + t.Fatalf("expected v %d, got %d", lv, rv) + } + if !cmp.Equal(lr.String(), rr.String()) { + t.Fatalf("expected r %d, got %d", lr, rr) + } + if !cmp.Equal(ls.String(), rs.String()) { + t.Fatalf("expected s %d, got %d", ls, rs) + } + } +} diff --git a/build/ci.go b/build/ci.go index 4d8dba6ce..8c05da985 100644 --- a/build/ci.go +++ b/build/ci.go @@ -310,9 +310,9 @@ func doTest(cmdline []string) { // 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") + // CHANGE(immutable): update flags for CI workflow + // Use -p for pkg parallelism and --parallel for test func parallelism. + gotest.Args = append(gotest.Args, "-p", "1", "--parallel", "4") if *coverage { gotest.Args = append(gotest.Args, "-covermode=atomic", "-cover") } diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index cb975054c..5a56873a3 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -200,6 +200,13 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, rejectedTxs = append(rejectedTxs, &rejectedTx{i, err.Error()}) continue } + // CHANGE(immutable): Reject blob transactions. + if chainConfig.IsImmutableZKEVM() && tx.Type() == types.BlobTxType { + errMsg := "blob tx not allowed" + log.Warn("rejected blob tx", "index", i, "hash", tx.Hash(), "error", errMsg) + rejectedTxs = append(rejectedTxs, &rejectedTx{i, errMsg}) + 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) diff --git a/cmd/geth/config.go b/cmd/geth/config.go index 5f52f1df5..e45bb270d 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -18,19 +18,25 @@ package main import ( "bufio" + "context" "errors" "fmt" "os" "reflect" "runtime" "strings" + "time" "unicode" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/external" + "github.com/ethereum/go-ethereum/accounts/immutable" "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/accounts/scwallet" "github.com/ethereum/go-ethereum/accounts/usbwallet" + "github.com/ethereum/go-ethereum/cmd/geth/immutable/keys" + inode "github.com/ethereum/go-ethereum/cmd/geth/immutable/node" + "github.com/ethereum/go-ethereum/cmd/geth/immutable/role" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -177,6 +183,9 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) { v := ctx.Uint64(utils.OverrideVerkle.Name) cfg.Eth.OverrideVerkle = &v } + // CHANGE(immutable): Add overrides + immutableOverrides(ctx, &cfg) + backend, eth := utils.RegisterEthService(stack, &cfg.Eth) // Create gauge with geth system and build information @@ -327,6 +336,59 @@ func setAccountManagerBackends(conf *node.Config, am *accounts.Manager, keydir s scryptP = keystore.LightScryptP } + // CHANGE(immutable): Add immutable backend with local keystore for testing custom backend + pwFilepath := os.Getenv("GETH_FLAG_PASSWORD_FILEPATH") + if len(pwFilepath) > 0 { + log.Info("Using immutable backend with local keystore") + // Initialize keystore + store, err := immutable.NewKeystore(keydir, pwFilepath) + if err != nil { + return fmt.Errorf("error creating immutable keystore: %v, dir (%s)", err, keydir) + } + // Initialize and register backend + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + backend, err := immutable.NewBackend(ctx, store) + if err != nil { + return fmt.Errorf("error creating immutable backend: %v", err) + } + am.AddBackend(backend) + return nil + } + // CHANGE(immutable): Add immutable backend with AWS Secrets Manager + awsRegion := os.Getenv("GETH_FLAG_IMMUTABLE_AWS_REGION") + if len(awsRegion) > 0 { + // Read pod name for validator ordinal + podOrdinal, err := ordinalFromPodNameEnvVar() + if err != nil { + return err + } + secretIDTemplate, err := keys.SecretIDTemplate() + if err != nil { + return err + } + validatorName := inode.CanonicalNodeName(role.Validator, podOrdinal) + validatorSecretID, err := keys.SecretID(secretIDTemplate, validatorName) + if err != nil { + return err + } + // Initialize AWS Secrets Manager Store + log.Info("Using immutable backend with AWS Secrets Manager", "region", awsRegion, "validator", validatorName) + store, err := immutable.NewAWSSecretsManagerStore(awsRegion, validatorSecretID) + if err != nil { + return fmt.Errorf("failed to create AWS SecretsManager Store: %v ", err) + } + // Initialize and register backend + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + backend, err := immutable.NewBackend(ctx, store) + if err != nil { + return fmt.Errorf("error creating immutable backend: %v", err) + } + am.AddBackend(backend) + return nil + } + // Assemble the supported backends if len(conf.ExternalSigner) > 0 { log.Info("Using external signer", "url", conf.ExternalSigner) diff --git a/cmd/geth/immutable.go b/cmd/geth/immutable.go new file mode 100644 index 000000000..1d648fe9d --- /dev/null +++ b/cmd/geth/immutable.go @@ -0,0 +1,148 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package main + +import ( + "github.com/ethereum/go-ethereum/cmd/immutable" + "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/internal/flags" + "github.com/urfave/cli/v2" +) + +var ( + immutableCommand = &cli.Command{ + Name: "immutable", + Usage: `A set of commands to manage bootstrap and deployment for Immutable infrastructure. +Local bootstrap will also run the validator node that is configured by the bootstrap.`, + Description: "", + Subcommands: []*cli.Command{ + { + Name: "bootstrap", + Usage: "bootstrap geth network configurations", + Subcommands: []*cli.Command{ + { + Name: "local", + Usage: "bootstrap local geth network configurations", + Action: bootstrapLocalCommand, + Flags: flags.Merge([]cli.Flag{ + immutable.GasLimit, + immutable.BootCount, + immutable.ValidatorCount, + immutable.RPCCount, + immutable.BlockListFilepath, + immutable.Env, + immutable.DataDirpath, + utils.OverrideShanghai, + utils.OverridePrevrandao, + utils.OverrideCancun, + utils.SyncModeFlag, + configFileFlag, + utils.GCModeFlag, + }), + }, + { + Name: "node", + Usage: "bootstrap node from within a k8s pod", + Action: bootstrapK8sNodeCommand, + Flags: flags.Merge([]cli.Flag{ + immutable.Role, + immutable.Region, + utils.ImmutableNetworkFlag, + immutable.DataDirpath, + }), + }, + { + Name: "rpc", + Usage: "bootstrap rpc node", + Action: bootstrapExternalNodeCommand, + Flags: flags.Merge([]cli.Flag{ + utils.ImmutableNetworkFlag, + immutable.DataDirpath, + }), + }, + }, + }, + { + Name: "run", + Usage: "run a process in the geth network", + Subcommands: []*cli.Command{ + { + Name: "boot", + Usage: "run a boot node", + Action: runBootNodeCommand, + Flags: flags.Merge([]cli.Flag{ + immutable.Region, + utils.ListenPortFlag, + utils.KeyStoreDirFlag, + }), + }, + }, + }, + { + Name: "configure", + Usage: "configure geth node environment before every time runtime executes", + Action: configureNodeInPodCommand, + Flags: flags.Merge([]cli.Flag{ + immutable.Role, + immutable.ConfigFilepath, + immutable.DataDirpath, + }), + }, + { + Name: "rewind", + Usage: "rewind chain to a specific block", + ArgsUsage: "[? | ]", + Action: runRewindChainCommand, + Flags: flags.Merge([]cli.Flag{ + immutable.OverrideFlag(immutable.DataDirpath, true), + }), + }, + { + Name: "decode", + Usage: "decode an encoded resource", + Action: runDecodeCommand, + Flags: flags.Merge([]cli.Flag{ + immutable.PublicKey, + }), + }, + { + Name: "vote", + Usage: "vote in or out new validators by connecting to multiple validators simultaneously", + Subcommands: []*cli.Command{ + { + Name: "add", + Usage: "vote in a validator", + Action: addValidatorCommand, + Flags: flags.Merge([]cli.Flag{ + immutable.Voters, + immutable.ValidatorAddress, + }), + }, + { + Name: "remove", + Usage: "vote out a validator", + Action: removeValidatorCommand, + Flags: flags.Merge([]cli.Flag{ + immutable.Voters, + immutable.ValidatorAddress, + }), + }, + }, + }, + }, + } +) diff --git a/cmd/geth/immutable/env/env.go b/cmd/geth/immutable/env/env.go new file mode 100644 index 000000000..2fcc6f16b --- /dev/null +++ b/cmd/geth/immutable/env/env.go @@ -0,0 +1,68 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package env + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/cmd/geth/immutable/settings" +) + +// Environment represents an Immutable zkEVM environment +// and assists referencing resources correctly, such as AWS secrets, k8s YAMLs, and +// configuration files. +type Environment struct { + string +} + +func (e Environment) String() string { + return e.string +} + +// ChainID returns the chain ID for the environment +func (e Environment) ChainID() int { + switch e { + case Devnet: + return settings.DevnetNetworkID + case Testnet: + return settings.TestnetNetworkID + case Mainnet: + return settings.MainnetNetworkID + default: + panic(fmt.Sprintf("unsupported env: %s", e)) + } +} + +var ( + Devnet = Environment{"dev"} + Testnet = Environment{"sandbox"} + Mainnet = Environment{"prod"} +) + +// NewFromString returns an Environment from a string +func NewFromString(env string) (Environment, error) { + switch env { + case Devnet.String(): + return Devnet, nil + case Testnet.String(): + return Testnet, nil + case Mainnet.String(): + return Mainnet, nil + default: + return Environment{}, fmt.Errorf("unsupported env: %s", env) + } +} diff --git a/cmd/geth/immutable/keys/keys.go b/cmd/geth/immutable/keys/keys.go new file mode 100644 index 000000000..748b215a3 --- /dev/null +++ b/cmd/geth/immutable/keys/keys.go @@ -0,0 +1,132 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package keys + +import ( + "context" + "crypto/ecdsa" + "encoding/hex" + "errors" + "fmt" + "os" + "strings" + + "github.com/ethereum/go-ethereum/cmd/immutable/remote/aws" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" +) + +const ( + // EmptyPrivateKeySecretValue is the value of the private key secret before it is set + // by the node-in-pod bootstrap operation. + EmptyPrivateKeySecretValue = "EMPTY" + // SecretKeyEnvVar is the environment variable used to retrieve the secret path from the environment. + SecretKeyEnvVar = "KEY_PATH" + // RolePlaceHolder is the placeholder for the role in the secret ID template. + RolePlaceHolder = "{POD_ROLE}" +) + +var ( + ErrPrivateKeyInvalid = errors.New("private key is invalid") + ErrPrivateKeyNotFound = errors.New("private key is not found") +) + +// SecretIDTemplate retrieves the path to the secret key from the environment. +func SecretIDTemplate() (string, error) { + path := os.Getenv(SecretKeyEnvVar) + if path == "" { + return "", fmt.Errorf("failed to retrieve secret path from environment") + } + return path, nil +} + +// SecretID is the identifier used to store a private key (decrypted keystore) in a secure remote store. +// The secret path must contain the placeholder {POD_ROLE}, which will be populated role of the node. +// They secret path must also be scoped to the correct environment. +func SecretID(secretIDTemplate, owner string) (string, error) { + if !strings.Contains(secretIDTemplate, RolePlaceHolder) { + return "", fmt.Errorf("secret ID template must contain the placeholder %s", RolePlaceHolder) + } + return strings.Replace(secretIDTemplate, RolePlaceHolder, owner, 1), nil +} + +// Store is used to retrieve and store private keys +type Store interface { + PutSecretString(key, content string) error + GetSecret(ctx context.Context, id string) (string, error) +} + +// Render will retrieve or create a private key for a node. +// The address pertaining to the key will be logged. +func Render( + ctx context.Context, + store Store, + secretID string, +) (*ecdsa.PrivateKey, error) { + // Try and pull existing private key + existingKeyHex, err := store.GetSecret(ctx, secretID) + if err != nil { + if aws.IsNotFound(err) { + return nil, errors.Join(ErrPrivateKeyNotFound, err) + } + return nil, err + } + // Key content indicates it has not been initialized + if existingKeyHex == EmptyPrivateKeySecretValue { + log.Info("private key is empty, creating new key secret") + newKey, err := crypto.GenerateKey() + if err != nil { + return nil, fmt.Errorf("failed to generate random private key: %w", err) + } + newKeyHex := hex.EncodeToString(crypto.FromECDSA(newKey)) + // Push the key to the store + if err := store.PutSecretString(secretID, newKeyHex); err != nil { + return nil, fmt.Errorf("failed to push private key to secrets manager: %w", err) + } + logPublicKey(&newKey.PublicKey) + return newKey, nil + } + // Decode existing private key + log.Info("decoding existing private key") + existingKey, err := crypto.HexToECDSA(existingKeyHex) + if err != nil { + return nil, errors.Join(ErrPrivateKeyInvalid, err) + } + logPublicKey(&existingKey.PublicKey) + return existingKey, nil +} + +func logPublicKey(pubKey *ecdsa.PublicKey) { + log.Info("key", "public", hex.EncodeToString(crypto.FromECDSAPub(pubKey)), "address", crypto.PubkeyToAddress(*pubKey).Hex()) +} + +// Retrieve will retrieve a private key from the store. +func Retrieve( + ctx context.Context, + store Store, + secretID string, +) (*ecdsa.PrivateKey, error) { + keyHex, err := store.GetSecret(ctx, secretID) + if err != nil { + return nil, fmt.Errorf("failed to retrieve private key for: %w", err) + } + privKey, err := crypto.HexToECDSA(keyHex) + if err != nil { + return nil, errors.Join(ErrPrivateKeyInvalid, err) + } + return privKey, nil +} diff --git a/cmd/geth/immutable/node/node.go b/cmd/geth/immutable/node/node.go new file mode 100644 index 000000000..c8949d59f --- /dev/null +++ b/cmd/geth/immutable/node/node.go @@ -0,0 +1,245 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package node + +import ( + "fmt" + "net/url" + "os" + "os/exec" + "path/filepath" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/accounts/keystore" + "github.com/ethereum/go-ethereum/cmd/geth/immutable/role" + "github.com/ethereum/go-ethereum/cmd/geth/immutable/settings" +) + +const ( + PortP2P = 30300 + PortRPC = 8545 + PortWS = 8535 + PortMetrics = 6060 + PortAuthRPC = 8550 + PortPprof = 7070 +) + +// Options are node-specific values +type Options struct { + Role role.Role + Ordinal int + Password string + URL *url.URL + ChainDirpath string +} + +// Config contains the configuration relevant for all nodes +type Config struct { + Network settings.Network + ConfigFilepath string + BlockListFilepath string +} + +// Node is a runnable geth node that is produced by the LocalBootstrapper. +// It depends on files rendered by the bootstrap process. +type Node struct { + account accounts.Account + dirpath string + password string + u *url.URL + + args []string + environ []string +} + +// New creates a new node +func New(opts Options, conf Config) (*Node, error) { + // Create indexed node directory + nodeDirpath := filepath.Join(opts.ChainDirpath, CanonicalNodeName(opts.Role, opts.Ordinal)) + if err := os.MkdirAll(nodeDirpath, os.ModePerm); err != nil { + return nil, fmt.Errorf("failed to create node directory: %w", err) + } + + // Store the encrypted key file to disk + account, err := keystore.StoreKey( + keystoreDirpath(nodeDirpath), + opts.Password, + keystore.StandardScryptN, + keystore.StandardScryptP, + ) + if err != nil { + return nil, fmt.Errorf("failed to create key: %w", err) + } + + // Store the account address to disk + if err := os.WriteFile(filepath.Join(nodeDirpath, "address"), []byte(account.Address.Hex()), 0644); err != nil { + return nil, fmt.Errorf("failed to write address to file: %w", err) + } + + passwordFilepath := filepath.Join(nodeDirpath, opts.Password) + environ := []string{ + "GETH_FLAG_PASSWORD_FILEPATH", + passwordFilepath, + } + args := []string{} + switch opts.Role { + case role.Boot: + args = []string{ + "./build/bin/geth", + "immutable", + "run", + "boot", + "--keystore", filepath.Join(nodeDirpath, "keystore"), + "--port", opts.URL.Port(), + } + case role.Validator: + args = []string{ + "./build/bin/geth", + "--datadir", nodeDirpath, + "--log.debug", + "--networkid", fmt.Sprint(conf.Network.ID()), + "--metrics", + "--metrics.addr", "127.0.0.1", + "--metrics.port", fmt.Sprint(PortMetrics + opts.Ordinal), + "--authrpc.port", fmt.Sprint(PortAuthRPC + opts.Ordinal), + "--verbosity", "4", + "--port", opts.URL.Port(), + "--password", passwordFilepath, + "--rpc.debugdisable", + "--rpc.txpooldisable", + "--config", conf.ConfigFilepath, + "--pprof", + "--pprof.port", fmt.Sprint(PortPprof + opts.Ordinal), + "--miner.etherbase", account.Address.Hex(), + "--mine", + // NOTE: Add these flags to enable RPC on validator + "--http", + "--http.port", fmt.Sprint(PortRPC + opts.Ordinal), + "--cache", "128", + "--cache.database", "35", + "--cache.trie", "35", + "--cache.gc", "10", + "--cache.snapshot", "20", + } + case role.RPC: + // TODO: This assumes 1 validator, better to parameterize + rpcPortOffset := opts.Ordinal + 1 // Avoid port collisions on same host + args = []string{ + "./build/bin/geth", + "--datadir", nodeDirpath, + "--log.debug", + "--networkid", fmt.Sprint(conf.Network.ID()), + "--metrics", + "--metrics.addr", "127.0.0.1", + "--metrics.port", fmt.Sprint(PortMetrics + rpcPortOffset), + "--authrpc.port", fmt.Sprint(PortAuthRPC + rpcPortOffset), + "--http", + "--http.port", fmt.Sprint(PortRPC + rpcPortOffset), + "--ws.port", fmt.Sprint(PortWS + rpcPortOffset), + "--txpool.blocklistfilepaths", conf.BlockListFilepath, + "--rpc.debugdisable", + "--rpc.txpooldisable", + "--rpc.cliquedisable", + "--rpc.minerdisable", + "--rpc.personaldisable", + "--verbosity", "4", + "--port", opts.URL.Port(), + "--config", conf.ConfigFilepath, + "--pprof", + "--pprof.port", fmt.Sprint(PortPprof + rpcPortOffset), + "--cache", "128", + "--cache.database", "40", + "--cache.trie", "40", + "--cache.gc", "0", + "--cache.snapshot", "20", + } + } + + return &Node{ + account: account, + dirpath: nodeDirpath, + password: opts.Password, + u: opts.URL, + args: args, + environ: environ, + }, nil +} + +// URL returns the enode URL of the node +func (n Node) URL() *url.URL { + return n.u +} + +// Dirpath returns the directory path of the node's files +func (n Node) Dirpath() string { + return n.dirpath +} + +// Account returns the account of the node +func (n Node) Account() accounts.Account { + return n.account +} + +// Password returns the password of the node +func (n Node) Password() string { + return n.password +} + +// Run runs the node as a sub-process +func (n Node) Run(optionalArgs []string) error { + args := append(n.args, optionalArgs...) + cmd, err := prepareCmd(n.environ, args...) + if err != nil { + return err + } + return cmd.Run() +} + +func keystoreDirpath(baseDirpath string) string { + return filepath.Join(baseDirpath, "keystore") +} + +// CanonicalNodeName returns the canonical name of a node based on role and ordinal +func CanonicalNodeName(role role.Role, ordinal int) string { + return role.String() + "-" + fmt.Sprint(ordinal) +} + +func prepareCmd(environ []string, args ...string) (*exec.Cmd, error) { + if len(args) < 2 { + return nil, fmt.Errorf("not enough args") + } + + cmd := exec.Command(args[0], args[1:]...) + cmd.Stderr = os.Stderr + cmd.Stdout = os.Stdout + + environLog := "" + if len(environ)%2 != 0 { + return nil, fmt.Errorf("environ must be key-value pairs") + } + if len(environ) > 0 { + environLog = "export" + } + cmd.Env = os.Environ() + for i := 0; i < len(environ); i += 2 { + environStr := fmt.Sprintf("%s=%s", environ[i], environ[i+1]) + cmd.Env = append(cmd.Env, environStr) + environLog = fmt.Sprintf("%s %s", environLog, environStr) + } + fmt.Printf("<<<<<<<<<<<<\n%s; %s\n>>>>>>>>>>>>>\n", environLog, cmd.String()) + return cmd, nil +} diff --git a/cmd/geth/immutable/rewind/rewind.go b/cmd/geth/immutable/rewind/rewind.go new file mode 100644 index 000000000..39df0849f --- /dev/null +++ b/cmd/geth/immutable/rewind/rewind.go @@ -0,0 +1,79 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package rewind + +import ( + "errors" + "fmt" + "io/fs" + "os" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "gopkg.in/yaml.v2" +) + +// Record is a single entry in the rewind history. +type Record struct { + BlockNumber uint64 + BlockHash common.Hash + Timestamp time.Time +} + +// History is a list of all records in the rewind history. +type History []Record + +// Contains checks if a block is in the rewind history. +func (h History) Contains(hash common.Hash) bool { + for _, r := range h { + if r.BlockHash == hash { + return true + } + } + return false +} + +// ReadRewindHistory reads the rewind history from the expected YAML file. +func ReadRewindHistory(filepath string) (history History, err error) { + fileData, err := os.ReadFile(filepath) + if err != nil && !errors.Is(err, fs.ErrNotExist) { + return nil, fmt.Errorf("error reading rewind history file: %v", err) + } + if errors.Is(err, fs.ErrNotExist) { + log.Info("No rewind history file found, creating a new one.") + } else { + log.Info("Reading existing rewind history file.") + if err := yaml.Unmarshal(fileData, &history); err != nil { + return nil, fmt.Errorf("error unmarshalling rewind history YAML: %v", err) + } + return history, nil + } + return []Record{}, nil +} + +// WriteRewindHistory writes the rewind history to the expected YAML file. +func WriteRewindHistory(history History, filepath string) error { + yamlData, err := yaml.Marshal(&history) + if err != nil { + return fmt.Errorf("error marshalling rewind history YAML: %v", err) + } + if err := os.WriteFile(filepath, yamlData, 0644); err != nil { + return fmt.Errorf("error writing rewind history file: %v", err) + } + return nil +} diff --git a/cmd/geth/immutable/rewind/rewind_test.go b/cmd/geth/immutable/rewind/rewind_test.go new file mode 100644 index 000000000..ea1589d56 --- /dev/null +++ b/cmd/geth/immutable/rewind/rewind_test.go @@ -0,0 +1,59 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package rewind + +import ( + "path/filepath" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +func TestRewind_FileDoesNotExist_HistoryEmpty(t *testing.T) { + history, err := ReadRewindHistory("test.yaml") + require.NoError(t, err) + require.NotNil(t, history) + require.Empty(t, history) +} + +func TestRewind_WriteRecord_HistoryNotEmptyAndRecordExists(t *testing.T) { + // Write file + filepath := filepath.Join(t.TempDir(), "test.yaml") + firstHash := common.HexToHash("0x123") + history := History{{BlockNumber: 1, BlockHash: firstHash, Timestamp: time.Now()}} + err := WriteRewindHistory(history, filepath) + require.NoError(t, err) + + // Write file again, with a new record + secondHash := common.HexToHash("0x456") + history = append(history, Record{BlockNumber: 2, BlockHash: secondHash, Timestamp: time.Now()}) + err = WriteRewindHistory(history, filepath) + require.NoError(t, err) + + // Read file + history, err = ReadRewindHistory(filepath) + require.NoError(t, err) + + // Verify contents + require.NotNil(t, history) + require.Len(t, history, 2) + require.True(t, history.Contains(firstHash)) + require.True(t, history.Contains(secondHash)) + require.False(t, history.Contains(common.HexToHash("0x789"))) +} diff --git a/cmd/geth/immutable/role/role.go b/cmd/geth/immutable/role/role.go new file mode 100644 index 000000000..b87bce136 --- /dev/null +++ b/cmd/geth/immutable/role/role.go @@ -0,0 +1,77 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package role + +import "fmt" + +var ( + Validator = Role{"validator"} + Boot = Role{"boot"} + RPC = Role{"rpc"} + Partner = Role{"partner"} + PartnerPublic = Role{"partner-public"} + AllRoles = []Role{Validator, Boot, RPC, Partner, PartnerPublic} +) + +// Count returns the number of supported roles +func Count() int { + return len(AllRoles) +} + +// Role is the role of a node in the network +type Role struct { + string +} + +// String returns the string representation of the role +func (r Role) String() string { + return r.string +} + +// Labels returns the key/value labels for the role +// which is assigned to k8s resources +func (r Role) Labels() map[string]string { + return map[string]string{ + "app": "zkevm-geth", + "zkevm": fmt.Sprintf("geth-%s", r.String()), + } +} + +// ExternalSecretName returns the name of the k8s external secret +// used for the role +func (r Role) ExternalSecretName() string { + return fmt.Sprintf("zkevm-geth-%s-chain", r.String()) +} + +// NewFromString returns the role from a string +// which must be one of the predefined Role* values +func NewFromString(name string) (Role, error) { + switch name { + case Validator.String(): + return Validator, nil + case Boot.String(): + return Boot, nil + case RPC.String(): + return RPC, nil + case Partner.String(): + return Partner, nil + case PartnerPublic.String(): + return PartnerPublic, nil + default: + return Role{}, fmt.Errorf("no such role for: %s", name) + } +} diff --git a/cmd/geth/immutable/settings/forks.go b/cmd/geth/immutable/settings/forks.go new file mode 100644 index 000000000..a67f07e03 --- /dev/null +++ b/cmd/geth/immutable/settings/forks.go @@ -0,0 +1,55 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package settings + +import ( + "fmt" + "time" +) + +// Fork represents a human-readable date/time for a specific network fork. +// It can be used to validate unix timestamps (e.g. in genesis.json) so that +// it is easier to validate the correctness of the fork timestamps. +type Fork struct { + time.Time +} + +func newFork(timestamp string) Fork { + const ( + forkTimestampLocation = "UTC" + forkTimestampLayout = time.UnixDate + ) + loc, err := time.LoadLocation(forkTimestampLocation) + if err != nil { + panic(err) + } + t, err := time.ParseInLocation(forkTimestampLayout, timestamp, loc) + if err != nil { + panic(err) + } + + if t.UTC().Format(forkTimestampLayout) != timestamp { + panic(fmt.Sprintf("fork time %s does not match supplied %s", t.UTC().Format(forkTimestampLayout), timestamp)) + } + + return Fork{t} +} + +// IsEnabledAt returns true if the given unix timestamp is past the fork timestamp +func (fork Fork) IsEnabledAt(time int64) bool { + return time >= fork.Unix() +} diff --git a/cmd/geth/immutable/settings/forks_test.go b/cmd/geth/immutable/settings/forks_test.go new file mode 100644 index 000000000..0a66650d8 --- /dev/null +++ b/cmd/geth/immutable/settings/forks_test.go @@ -0,0 +1,62 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package settings + +import ( + "fmt" + "testing" + "time" +) + +func TestForks_IsPastTimestamp(t *testing.T) { + // Test certain timestamps + var tests = []struct { + unixTimestamp int64 + isPast bool + forks []Fork + }{ + {0, false, []Fork{ + DevnetShanghaiFork, TestnetShanghaiFork, MainnetShanghaiFork, + DevnetPrevrandaoFork, TestnetPrevrandaoFork, MainnetPrevrandaoFork, + DevnetCancunFork, TestnetCancunFork, MainnetCancunFork, + }}, + // Enabled now + {time.Now().Unix(), true, []Fork{ + DevnetShanghaiFork, TestnetShanghaiFork, MainnetShanghaiFork, + DevnetPrevrandaoFork, TestnetPrevrandaoFork, MainnetPrevrandaoFork, + DevnetCancunFork, TestnetCancunFork, MainnetCancunFork, + }}, + // Scheduled forks that are not yet enabled (may be empty list) + {time.Now().Unix(), false, []Fork{}}, + } + for _, test := range tests { + for _, fork := range test.forks { + name := fmt.Sprintf("unix %d %s", test.unixTimestamp, fork) + t.Run(name, func(t *testing.T) { + if got, want := fork.IsEnabledAt(test.unixTimestamp), test.isPast; got != want { + t.Errorf("got %v, want %v", got, want) + } + if !fork.IsEnabledAt(fork.Unix()) { + t.Errorf("fork timestamp should be past itself") + } + if fork.IsEnabledAt(fork.Unix() - 10) { + t.Errorf("fork timestamp should not be past itself - 10") + } + }) + } + } +} diff --git a/cmd/geth/immutable/settings/genesis/devnet.json b/cmd/geth/immutable/settings/genesis/devnet.json new file mode 100644 index 000000000..ece27ca71 --- /dev/null +++ b/cmd/geth/immutable/settings/genesis/devnet.json @@ -0,0 +1,122 @@ +{ + "config": { + "chainId": 15003, + "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, + "prevrandaoTime": 1709067600, + "shanghaiTime": 1709067600, + "cancunTime": 1724796000, + "clique": { + "period": 2, + "epoch": 30000 + }, + "isReorgBlocked": true + }, + "nonce": "0x0", + "timestamp": "0x0", + "extraData": "0x000000000000000000000000000000000000000000000000000000000000000092e13fb4e00a5daecf5b61553b678ad56e6985150000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "gasLimit": "0x5f5e100", + "difficulty": "0x1", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "alloc": { + "000000000013b7b1b08b3c8efe02e866f746bd38": { + "balance": "0x6765c793fa10079d0000000" + }, + "0ccb0a3fc5ca38fcd9ffd8a667cb83e3194250d7": { + "balance": "0x6765c793fa10079d0000000" + }, + "2e969d22e6654e064f461cf8b1314cc0864a4914": { + "balance": "0x6765c793fa10079d0000000" + }, + "340bc2c77514ede2a23fd4f42f411a8e351d8ee6": { + "balance": "0x6765c793fa10079d0000000" + }, + "3e290fe8f2a5db60a81cb47ea296e0299048dd71": { + "balance": "0x6765c793fa10079d0000000" + }, + "4a73506a31db769ac442b17ca9a1679f44757bbf": { + "balance": "0x6765c793fa10079d0000000" + }, + "4aedf28a437b94749037cc39f83f4422469cf2f7": { + "balance": "0x6765c793fa10079d0000000" + }, + "5abfc3e307b037325bfc6988ae265dcb211ec533": { + "balance": "0x6765c793fa10079d0000000" + }, + "7442ed1e3c9fd421f47d12a2742aff5dafbf43f8": { + "balance": "0x6765c793fa10079d0000000" + }, + "784578949a4a50dea641fb15dd2b11c72e76919a": { + "balance": "0x6765c793fa10079d0000000" + }, + "7c3e6ce6fd293fc66d9d73d49fd546cce1e19f0e": { + "balance": "0x6765c793fa10079d0000000" + }, + "8318a871cc140d9f77a1999f84875ac36eecc04e": { + "balance": "0x6765c793fa10079d0000000" + }, + "9c1634bebc88653d2aebf4c14a3031f62092b1d9": { + "balance": "0x6765c793fa10079d0000000" + }, + "a6c368164eb270c31592c1830ed25c2bf5d34bae": { + "balance": "0x6765c793fa10079d0000000" + }, + "b3343666188a694120c18c4985c57e4c0913a6f0": { + "balance": "0x6765c793fa10079d0000000" + }, + "c606830d8341bc9f5f5dd7615e9313d2655b505d": { + "balance": "0x6765c793fa10079d0000000" + }, + "c8714f989ce817e5d21349888077aa5db4a9bcf6": { + "balance": "0x6765c793fa10079d0000000" + }, + "cc5c8cea877f2f351f38c190867bbd31fafadd22": { + "balance": "0x6765c793fa10079d0000000" + }, + "d509997ab62fda51c32e64e69fb090df8894105e": { + "balance": "0x6765c793fa10079d0000000" + }, + "d9275eb8276e14b9e28d5f9b12e90ddaaf3586ef": { + "balance": "0x6765c793fa10079d0000000" + }, + "deadc0de8a3b037925a895843f96b0c525fbc31f": { + "balance": "0x6765c793fa10079d0000000" + }, + "e567ea84e1eb3ffdc8f5aa420bf14a16eee6a809": { + "balance": "0x6765c793fa10079d0000000" + }, + "eac347177dba4a190b632c7d9b8da2abff57c772": { + "balance": "0x6765c793fa10079d0000000" + }, + "eb7ffb9fb0c80437120f6f97ede60ab59055eae0": { + "balance": "0x6765c793fa10079d0000000" + }, + "ebbf4c07a63986204c37cc5a188aabf53564c583": { + "balance": "0x6765c793fa10079d0000000" + }, + "efe12952541356ffc969a343a81d1ce7d2806179": { + "balance": "0x6765c793fa10079d0000000" + }, + "f6372939ce2d14a68a629b8e4785e9dcb4eda0cf": { + "balance": "0x6765c793fa10079d0000000" + } + }, + "number": "0x0", + "gasUsed": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "baseFeePerGas": null, + "excessBlobGas": null, + "blobGasUsed": null +} \ No newline at end of file diff --git a/cmd/geth/immutable/settings/genesis/mainnet.json b/cmd/geth/immutable/settings/genesis/mainnet.json new file mode 100644 index 000000000..3b71328a6 --- /dev/null +++ b/cmd/geth/immutable/settings/genesis/mainnet.json @@ -0,0 +1,44 @@ +{ + "config": { + "chainId": 13371, + "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, + "prevrandaoTime": 1710899402, + "shanghaiTime": 1711490400, + "cancunTime": 1728338400, + "clique": { + "period": 2, + "epoch": 30000 + }, + "isReorgBlocked": true + }, + "nonce": "0x0", + "timestamp": "0x0", + "extraData": "0x000000000000000000000000000000000000000000000000000000000000000055e2ebc94b3314d387f423ef4424a95547f3f8c40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "gasLimit": "0x5f5e100", + "difficulty": "0x1", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "alloc": { + "dda0d9448ebe3ea43afece5fa6401f5795c19333": { + "balance": "0x6765c793fa10079d0000000" + } + }, + "number": "0x0", + "gasUsed": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "baseFeePerGas": null, + "excessBlobGas": null, + "blobGasUsed": null +} \ No newline at end of file diff --git a/cmd/geth/immutable/settings/genesis/testnet.json b/cmd/geth/immutable/settings/genesis/testnet.json new file mode 100644 index 000000000..c7dd17566 --- /dev/null +++ b/cmd/geth/immutable/settings/genesis/testnet.json @@ -0,0 +1,44 @@ +{ + "config": { + "chainId": 13473, + "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, + "prevrandaoTime": 1710280800, + "shanghaiTime": 1710280800, + "cancunTime": 1727128800, + "clique": { + "period": 2, + "epoch": 30000 + }, + "isReorgBlocked": true + }, + "nonce": "0x0", + "timestamp": "0x0", + "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000512069234755cc4bb2cc59b79b7541ed5a03babb0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "gasLimit": "0x5f5e100", + "difficulty": "0x1", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "alloc": { + "e567ea84e1eb3ffdc8f5aa420bf14a16eee6a809": { + "balance": "0x6765c793fa10079d0000000" + } + }, + "number": "0x0", + "gasUsed": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "baseFeePerGas": null, + "excessBlobGas": null, + "blobGasUsed": null +} \ No newline at end of file diff --git a/cmd/geth/immutable/settings/network.go b/cmd/geth/immutable/settings/network.go new file mode 100644 index 000000000..43434a051 --- /dev/null +++ b/cmd/geth/immutable/settings/network.go @@ -0,0 +1,104 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package settings + +import ( + "fmt" + + _ "embed" +) + +var ( + //go:embed genesis/mainnet.json + immutableGenesisMainnetJSON string + //go:embed genesis/testnet.json + immutableGenesisTestnetJSON string + //go:embed genesis/devnet.json + immutableGenesisDevnetJSON string +) + +// Network is the name of an Immutable zkEVM network +type Network struct { + string + genesisJSON string + id int + cancun Fork +} + +// String returns the string representation of the network +func (n Network) String() string { + return n.string +} + +// GenesisJSON returns the JSON string for the genesis block of the network +func (n Network) GenesisJSON() string { + return n.genesisJSON +} + +// ID returns the network ID of the chain +func (n Network) ID() int { + return n.id +} + +// Cancun returns the Cancun fork for the network. +func (n Network) Cancun() Fork { + return n.cancun +} + +// RPC returns the RPC endpoint for the network. +func (n Network) RPC() (string, error) { + switch n.id { + case MainnetNetworkID: + return MainnetRPC, nil + case TestnetNetworkID: + return TestnetRPC, nil + case DevnetNetworkID: + return DevnetRPC, nil + default: + return "", fmt.Errorf("unsupported RPC for network: %s", n.string) + } +} + +// NewNetwork returns a new Immutable zkEVM network with the specified name +// which must be one of "mainnet", "testnet", or "devnet" +func NewNetwork(name string) (*Network, error) { + switch name { + case "devnet": + return &Network{ + string: name, + genesisJSON: immutableGenesisDevnetJSON, + id: DevnetNetworkID, + cancun: DevnetCancunFork, + }, nil + case "testnet": + return &Network{ + string: name, + genesisJSON: immutableGenesisTestnetJSON, + id: TestnetNetworkID, + cancun: TestnetCancunFork, + }, nil + case "mainnet": + return &Network{ + string: name, + genesisJSON: immutableGenesisMainnetJSON, + id: MainnetNetworkID, + cancun: MainnetCancunFork, + }, nil + default: + return nil, fmt.Errorf("unsupported network: %s", name) + } +} diff --git a/cmd/geth/immutable/settings/network_test.go b/cmd/geth/immutable/settings/network_test.go new file mode 100644 index 000000000..9a52b8aaf --- /dev/null +++ b/cmd/geth/immutable/settings/network_test.go @@ -0,0 +1,67 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package settings + +import ( + "encoding/json" + "testing" +) + +func TestGenesis_GetJSON(t *testing.T) { + var tests = []struct { + network string + networkID int + }{ + {"devnet", DevnetNetworkID}, + {"testnet", TestnetNetworkID}, + {"mainnet", MainnetNetworkID}, + } + previousJSONs := []string{} + for _, test := range tests { + t.Run(test.network, func(t *testing.T) { + network, err := NewNetwork(test.network) + if err != nil { + t.Fatal(err) + } + // ID + if network.ID() != test.networkID { + t.Errorf("expected network ID %d, got %d", test.networkID, network.ID()) + } + // JSON + if len(network.GenesisJSON()) < 1000 { // All files should be at least 1000 bytes + t.Errorf("expected non-empty genesis JSON") + } + // Valid JSON + genesis := map[string]interface{}{} + if err := json.Unmarshal([]byte(network.GenesisJSON()), &genesis); err != nil { + t.Fatal(err) + } + // Unique JSON + if len(previousJSONs) > 0 { + for _, previousJSON := range previousJSONs { + if previousJSON == network.GenesisJSON() { + t.Fatalf("expected unique genesis JSONs, got duplicate for %s and previous", test.network) + } + } + } + previousJSONs = append(previousJSONs, network.GenesisJSON()) + }) + } + if len(previousJSONs) != len(tests) { + t.Fatalf("expected %d unique genesis JSONs, got %d", len(tests), len(previousJSONs)) + } +} diff --git a/cmd/geth/immutable/settings/settings.go b/cmd/geth/immutable/settings/settings.go new file mode 100644 index 000000000..d6fbf7db7 --- /dev/null +++ b/cmd/geth/immutable/settings/settings.go @@ -0,0 +1,71 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package settings + +const ( + // PriceLimit is the minimum gas price that RPC nodes will accept for transactions. + PriceLimit = 10 * 1e9 + // SecondsPerBlock is the amount of time between blocks. + SecondsPerBlock = 2 + // BaseFeeChangeDenominator is set to a value which leads to a smaller + // max base fee rate of change (12.5% -> 2%). This accounts for our short block time + // of 2 seconds, preventing large price fluctuations. At 50, we match Ethereum in that + // it would take 72 seconds for the baseFee to double. + BaseFeeChangeDenominator = 50 + + // MainnetNetworkID is the network ID for the mainnet. + MainnetNetworkID = 13371 + // TestnetNetworkID is the network ID for the testnet. + TestnetNetworkID = 13473 + // DevnetNetworkID is the network ID for the devnet. + DevnetNetworkID = 15003 + + // MainnetRPC is the mainnet RPC endpoint. + MainnetRPC = "https://rpc.immutable.com" + // TestnetRPC is the testnet RPC endpoint. + TestnetRPC = "https://rpc.testnet.immutable.com" + // DevnetRPC is the devnet RPC endpoint. + DevnetRPC = "https://rpc.dev.immutable.com" +) + +var ( + // DevnetShanghaiFork is the timestamp of the Shanghai devnet fork. + DevnetShanghaiFork = newFork("Tue Feb 27 21:00:00 UTC 2024") + // TestnetShanghaiFork is the timestamp of the Shanghai testnet fork. + TestnetShanghaiFork = newFork("Tue Mar 12 22:00:00 UTC 2024") + // MainnetShanghaiFork is the timestamp of the Shanghai mainnet fork. + MainnetShanghaiFork = newFork("Tue Mar 26 22:00:00 UTC 2024") + + // DevnetPrevrandaoFork is the timestamp of the Prevrandao devnet fork. + DevnetPrevrandaoFork = DevnetShanghaiFork + // TestnetPrevrandaoFork is the timestamp of the Prevrandao testnet fork. + TestnetPrevrandaoFork = TestnetShanghaiFork + // MainnetPrevrandaoFork is the timestamp of the Prevrandao mainnet fork. + // Only mainnet has a Prevrandao fork separate from the Shanghai fork. + // Block 0x41DDE4. + // TZ=UTC gdate --date @1710899402 + MainnetPrevrandaoFork = newFork("Wed Mar 20 01:50:02 UTC 2024") + + // DevnetCancunFork is the timestamp of the Cancun devnet fork. + DevnetCancunFork = newFork("Tue Aug 27 22:00:00 UTC 2024") + // TestnetCancunFork is the timestamp of the Cancun testnet fork. + TestnetCancunFork = newFork("Mon Sep 23 22:00:00 UTC 2024") + // MainnetCancunFork is the timestamp of the Cancun mainnet fork. + // Unixdate format expects an extra whitespace if the day is a single digit. + // e.g. Mon Jan _2 15:04:05 MST 2006 + MainnetCancunFork = newFork("Mon Oct 7 22:00:00 UTC 2024") +) diff --git a/cmd/geth/immutable/store/localstore/localstore.go b/cmd/geth/immutable/store/localstore/localstore.go new file mode 100644 index 000000000..f784e1e82 --- /dev/null +++ b/cmd/geth/immutable/store/localstore/localstore.go @@ -0,0 +1,43 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package localstore + +import ( + "fmt" + "os" + + "github.com/ethereum/go-ethereum/log" +) + +// Store is used to push configuration artefacts to local files +type Store struct { +} + +// New returns a new store +func New() *Store { + return &Store{} +} + +// StoreConfigFile writes a configuration file to disk. We use this to +// update k8s configmaps. +func (s *Store) StoreConfigFile(filepath string, content []byte) error { + log.Info("Writing config file", "filepath", filepath) + if err := os.WriteFile(filepath, content, 0644); err != nil { + return fmt.Errorf("failed to write config file: %w", err) + } + return nil +} diff --git a/cmd/geth/immutable/store/store.go b/cmd/geth/immutable/store/store.go new file mode 100644 index 000000000..b5139aba5 --- /dev/null +++ b/cmd/geth/immutable/store/store.go @@ -0,0 +1,36 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package store + +import ( + "github.com/ethereum/go-ethereum/cmd/geth/immutable/store/localstore" + "github.com/ethereum/go-ethereum/cmd/immutable/remote/aws" +) + +// Store is used to push configuration artefacts to +// local files and remote resources +type Store struct { + localstore.Store + *aws.SecretsManager +} + +// New returns a new store +func New(sm *aws.SecretsManager) *Store { + return &Store{ + SecretsManager: sm, + } +} diff --git a/cmd/geth/immutable_artefact_store_mock.go b/cmd/geth/immutable_artefact_store_mock.go new file mode 100644 index 000000000..b00649322 --- /dev/null +++ b/cmd/geth/immutable_artefact_store_mock.go @@ -0,0 +1,67 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package main + +import ( + "context" + "sync" + + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/secretsmanager" +) + +type mockArtefactStore struct { + secretFiles *sync.Map + configFiles *sync.Map + secretStrings *sync.Map +} + +func (mas *mockArtefactStore) PushSecretFile(key string, content []byte) error { + if mas.secretFiles == nil { + mas.secretFiles = new(sync.Map) + } + mas.secretFiles.Store(key, content) + return nil +} + +func (mas *mockArtefactStore) PutSecretString(key string, content string) error { + return mas.PushSecretString(key, content) +} + +func (mas *mockArtefactStore) StoreConfigFile(filepath string, content []byte) error { + if mas.configFiles == nil { + mas.configFiles = new(sync.Map) + } + mas.configFiles.Store(filepath, content) + return nil +} +func (mas *mockArtefactStore) PushSecretString(key, content string) error { + if mas.secretStrings == nil { + mas.secretStrings = new(sync.Map) + } + mas.secretStrings.Store(key, content) + return nil +} +func (mas *mockArtefactStore) GetSecret(ctx context.Context, key string) (string, error) { + if mas.secretStrings == nil { + mas.secretStrings = new(sync.Map) + } + if secret, exists := mas.secretStrings.Load(key); exists { + return secret.(string), nil + } + return "", awserr.New(secretsmanager.ErrCodeResourceNotFoundException, "not found", nil) +} diff --git a/cmd/geth/immutable_bootstrap.go b/cmd/geth/immutable_bootstrap.go new file mode 100644 index 000000000..a4614dc04 --- /dev/null +++ b/cmd/geth/immutable_bootstrap.go @@ -0,0 +1,181 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package main + +import ( + "context" + "crypto/ecdsa" + "errors" + "fmt" + "io" + "os" + "path/filepath" + + "github.com/ethereum/go-ethereum/cmd/geth/immutable/env" + "github.com/ethereum/go-ethereum/cmd/geth/immutable/keys" + "github.com/ethereum/go-ethereum/cmd/geth/immutable/role" + "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + ethnode "github.com/ethereum/go-ethereum/node" + "github.com/urfave/cli/v2" +) + +// renderValidatorKey will create the validator's private key if it doesn't exist in the store. +// It will write the address corresponding to the key to a file at the provided dir path. +func renderValidatorKey( + ctx context.Context, + store keys.Store, + dataDirpath string, + secretID string, +) error { + // Get or generate priv key + privKey, err := keys.Render( + ctx, + store, + secretID, + ) + if err != nil { + return err + } + + // Store address on file at root data directory + return storeAddr(privKey, filepath.Join(dataDirpath, "address")) +} + +// renderP2PKey will create a new P2P key if it doesn't exist in the store. +// This is only required for boot node b/c the other nodes rely on its public key. +func renderP2PKey( + ctx context.Context, + store keys.Store, + r role.Role, + secretID string, +) error { + if r != role.Boot { + return fmt.Errorf("only boot node can render P2P key, got %s", r.String()) + } + + // Get or generate priv key + _, err := keys.Render( + ctx, + store, + secretID, + ) + + return err +} + +// storeAddr will derive address from key and write it to a file at the provided filepath. +// This is required to have per-pod address allocation without running geth containers with a script. +func storeAddr(key *ecdsa.PrivateKey, filepath string) error { + if key == nil { + return fmt.Errorf("priv key is nil") + } + addr := crypto.PubkeyToAddress(key.PublicKey) + if err := os.WriteFile(filepath, []byte(addr.Hex()), 0644); err != nil { + return fmt.Errorf("failed to write address to file: %w", err) + } + return nil +} + +// renderChainState will initialize the chain and lightchain dbs with the genesis block. +// It will write files and directories to the provided root dir path. +func renderChainState(destDirpath string, genesis *core.Genesis) error { + // Do not re-initialize, assume already initialized + gethDirpath := filepath.Join(destDirpath, "geth") + empty, err := isEmptyOrDoesNotExist(gethDirpath) + if err != nil { + return err + } + if !empty { + log.Info("geth dir is not empty, skipping bootstrap", "gethDirpath", gethDirpath) + return nil + } + // Instantiate node based on config and dir + c := &cli.Context{} // NOTE: This context is empty so it can only provide default/nil values + cfg := loadBaseConfig(c) + cfg.Node.DataDir = destDirpath + stack, err := ethnode.New(&cfg.Node) + if err != nil { + return fmt.Errorf("failed to create node: %w", err) + } + defer stack.Close() + + // Init genesis for chain and lightchain dbs of node + for _, name := range []string{"chaindata", "lightchaindata"} { + // Create and open leveldb + chaindb, err := stack.OpenDatabaseWithFreezer(name, 0, 0, "", "", false) + if err != nil { + return fmt.Errorf("failed to open db: %w", err) + } + defer chaindb.Close() + + // Create and open trie db + triedb := utils.MakeTrieDatabaseNoContext(cfg.Eth.StateScheme, chaindb, false, false, false) + defer triedb.Close() + + // Write genesis block to trie and save to leveldb + if _, _, err := core.SetupGenesisBlock(chaindb, triedb, genesis); err != nil { + return fmt.Errorf("failed to write genesis block to db: %w", err) + } + } + log.Info("rendered chain state") + return nil +} + +// ordinalFromPodName will extract the last element of the POD_NAME +// env var delimited by '-' and return it as an ordinal. +func ordinalFromPodNameEnvVar() (int, error) { + // Read pod name for validator ordinal + podName := os.Getenv("POD_NAME") + if podName == "" { + return 0, fmt.Errorf("POD_NAME must be set") + } + return ordinalFromPod(podName) +} + +// envFromPodNamespaceEnvVar will extract the environment from the POD_NAMESPACE +// env var. +func envFromPodNamespaceEnvVar() (env.Environment, error) { + // Read pod namespace for the environment + ns := os.Getenv("POD_NAMESPACE") + if ns == "" { + return env.Environment{}, fmt.Errorf("POD_NAMESPACE must be set") + } + return env.NewFromString(ns) +} + +// isEmptyOrDoesNotExist returns true if the directory is empty or does not exist +func isEmptyOrDoesNotExist(dirpath string) (bool, error) { + f, err := os.Open(dirpath) + if err != nil { + if os.IsNotExist(err) { + return true, nil + } + return false, fmt.Errorf("failed to open directory: %w", err) + } + defer f.Close() + + if _, err := f.Readdirnames(1); err != nil { + if errors.Is(err, io.EOF) { + return true, nil + } + return false, err + } + return false, nil +} diff --git a/cmd/geth/immutable_bootstrap_command.go b/cmd/geth/immutable_bootstrap_command.go new file mode 100644 index 000000000..322e4ca01 --- /dev/null +++ b/cmd/geth/immutable_bootstrap_command.go @@ -0,0 +1,143 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package main + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/cmd/geth/immutable/keys" + "github.com/ethereum/go-ethereum/cmd/geth/immutable/node" + "github.com/ethereum/go-ethereum/cmd/geth/immutable/role" + "github.com/ethereum/go-ethereum/cmd/immutable" + "github.com/ethereum/go-ethereum/cmd/immutable/remote/aws" + "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/core" + "github.com/urfave/cli/v2" +) + +// getSecretID will return the secret ID for the node. +// The secret ID will be based on the role and ordinal of the node. +func getSecretID(role role.Role) (string, error) { + // Ordinal. + ordinal, err := ordinalFromPodNameEnvVar() + if err != nil { + return "", err + } + // Secret ID template. + secretIDTemplate, err := keys.SecretIDTemplate() + if err != nil { + return "", err + } + // Secret ID. + secretID, err := keys.SecretID(secretIDTemplate, node.CanonicalNodeName(role, ordinal)) + if err != nil { + return "", err + } + return secretID, nil +} + +func bootstrapK8sNodeCommand(c *cli.Context) error { + // Role + r, err := role.NewFromString(c.String(immutable.Role.Name)) + if err != nil { + return err + } + + // Setup store + store, err := aws.NewSecretsManager(c.String(immutable.Region.Name)) + if err != nil { + return err + } + + // Only retrieve secret IDs for boot and validator nodes. Other nodes do not store secret material. + var secretID string + switch r { + case role.Boot, role.Validator: + secretID, err = getSecretID(r) + if err != nil { + return err + } + default: + secretID = "" + } + + // Get bootstrapper and run it + b, err := bootstrapFactory( + r, + store, + c.String(immutable.Region.Name), + c.String(immutable.DataDirpath.Name), + secretID, + core.ImmutableGenesisBlock(c.String(utils.ImmutableNetworkFlag.Name)), + ) + if err != nil { + return err + } + return b.Bootstrap(c.Context) +} + +func bootstrapExternalNodeCommand(c *cli.Context) error { + // Get bootstrapper and run it + b, err := bootstrapFactory( + role.RPC, + nil, + "", + c.String(immutable.DataDirpath.Name), + "", + core.ImmutableGenesisBlock(c.String(utils.ImmutableNetworkFlag.Name)), + ) + if err != nil { + return err + } + return b.Bootstrap(c.Context) +} + +// bootstrapFactory will create a bootstrapper based on the role +// of the relevant node. Each node has different bootstrap requirements. +func bootstrapFactory( + r role.Role, + store keys.Store, + region string, + dataDirpath string, + secretID string, + genesis *core.Genesis, +) (Bootstrapper, error) { + switch r { + case role.Boot: + return &BootBootstrapper{ + store: store, + region: region, + dataDirpath: dataDirpath, + secretID: secretID, + }, nil + case role.Validator: + return &ValidatorBootstrapper{ + store: store, + region: region, + dataDirpath: dataDirpath, + genesis: genesis, + secretID: secretID, + }, nil + case role.Partner, role.RPC, role.PartnerPublic: + return &RPCBootstrapper{ + dataDirpath: dataDirpath, + genesis: genesis, + }, nil + default: + return nil, fmt.Errorf("unsupported role %s", r.String()) + } +} diff --git a/cmd/geth/immutable_bootstrap_test.go b/cmd/geth/immutable_bootstrap_test.go new file mode 100644 index 000000000..db038115f --- /dev/null +++ b/cmd/geth/immutable_bootstrap_test.go @@ -0,0 +1,336 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package main + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/ethereum/go-ethereum/cmd/geth/immutable/keys" + "github.com/ethereum/go-ethereum/cmd/geth/immutable/node" + "github.com/ethereum/go-ethereum/cmd/geth/immutable/role" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/crypto" +) + +const TestSecretIDTemplate = "{POD_ROLE}" + +func TestImmutableBootstrap_AllRoles_CreateKeysAndChainState(t *testing.T) { + ctx := context.Background() + // Temp dir + testDirpath, err := os.MkdirTemp("", "immutable-test") + if err != nil { + t.Fatal(err) + } + defer os.Remove(testDirpath) + + // Configuration + ordinal := 0 + + // Subtest specification + type tests struct { + role role.Role + name string + store keys.Store + addr string + err error + secretID string + } + ts := []tests{} + // These roles generate keys + for _, r := range []role.Role{role.Validator, role.Boot} { + // secretID := + ts = append(ts, []tests{ + { + r, + fmt.Sprintf("%sWithoutKey", r.String()), + &mockArtefactStore{}, + "", + keys.ErrPrivateKeyNotFound, + "", + }, + { + r, + fmt.Sprintf("%sWithKey", r.String()), + testNewStoreWithKey(t, r, ordinal), + "0x02F0d131F1f97aef08aEc6E3291B957d9Efe7105", + nil, + node.CanonicalNodeName(r, ordinal), + }, + { + r, + fmt.Sprintf("%sWithEmptyKey", r.String()), + testNewStoreWithEmptyKey(t, r, ordinal), + "", + keys.ErrPrivateKeyInvalid, + node.CanonicalNodeName(r, ordinal), + }, + { + r, + fmt.Sprintf("%sWithReadyKey", r.String()), + testNewStoreWithReadyKey(t, r, ordinal), + "", + nil, + node.CanonicalNodeName(r, ordinal), + }, + }...) + } + // These roles don't generate keys + for _, r := range []role.Role{role.RPC, role.Partner, role.PartnerPublic} { + ts = append(ts, []tests{ + { + r, + fmt.Sprintf("%sWithoutKey", r.String()), + &mockArtefactStore{}, + "", + nil, + "", + }, + }...) + } + // Run subtests + for i := range ts { + test := ts[i] + t.Run(test.name, func(t *testing.T) { + // Make subdir + subtestDirpath := filepath.Join(testDirpath, test.name) + if err := os.Mkdir(subtestDirpath, os.ModePerm); err != nil { + t.Fatal(err) + } + // Run bootstrap func + b, err := bootstrapFactory( + test.role, + test.store, + "us-east-2", + subtestDirpath, + test.secretID, + core.ImmutableGenesisBlock("devnet"), + ) + if err != nil { + t.Fatal(err) + } + if err := b.Bootstrap(context.Background()); err != nil { + if test.err == nil { + t.Fatalf("unexpected error %v, %+v", err, test) + } + if !errors.Is(err, test.err) { + t.Fatalf("expected error %v, got %v, %+v", test.err, err, test) + } + // Expected an error, nothing else to test + return + } + + // Key validation + if test.role == role.Boot || test.role == role.Validator { + // Now read all the secrets that should have been generated and stored and validate them + // Key hex to compare against keystore + secretID, err := keys.SecretID(TestSecretIDTemplate, node.CanonicalNodeName(test.role, ordinal)) + if err != nil { + t.Fatal(err) + } + keyHex, err := test.store.GetSecret(ctx, secretID) + if err != nil { + t.Fatal(err) + } + // Validate secret contains valid private key + if _, err := crypto.HexToECDSA(keyHex); err != nil { + t.Fatal(err) + } + } + + // Address file validation + if test.role == role.Validator { + addr, err := os.ReadFile(filepath.Join(subtestDirpath, "address")) + if err != nil { + t.Fatal(err) + } + if !common.IsHexAddress(string(addr)) { + t.Fatalf("address file does not contain a valid address %s", string(addr)) + } + if test.addr != "" { + if string(addr) != test.addr { + t.Fatalf("expected address %s, got %s", test.addr, string(addr)) + } + } + } + + // Chain state validation + if test.role != role.Boot { + testRenderedChainStateDirs(t, subtestDirpath) + } + }) + } +} + +func testNewStoreWithKey(t *testing.T, r role.Role, ordinal int) keys.Store { + t.Helper() + storeWithKey := &mockArtefactStore{} + secretID, err := keys.SecretID(TestSecretIDTemplate, node.CanonicalNodeName(r, ordinal)) + if err != nil { + t.Fatal(err) + } + if err := storeWithKey.PushSecretString( + secretID, + "48aa455c373ec5ce7fefb0e54f44a215decdc85b9047bc4d09801e038909bdbe", + ); err != nil { + t.Fatal(err) + } + return storeWithKey +} + +func testNewStoreWithEmptyKey(t *testing.T, r role.Role, ordinal int) keys.Store { + t.Helper() + storeWithNoKey := &mockArtefactStore{} + secretID, err := keys.SecretID(TestSecretIDTemplate, node.CanonicalNodeName(r, ordinal)) + if err != nil { + t.Fatal(err) + } + if err := storeWithNoKey.PushSecretString( + secretID, + "", + ); err != nil { + t.Fatal(err) + } + return storeWithNoKey +} + +func testNewStoreWithReadyKey(t *testing.T, r role.Role, ordinal int) keys.Store { + t.Helper() + storeWithReadyKey := &mockArtefactStore{} + secretID, err := keys.SecretID(TestSecretIDTemplate, node.CanonicalNodeName(r, ordinal)) + if err != nil { + t.Fatal(err) + } + if err := storeWithReadyKey.PushSecretString( + secretID, + keys.EmptyPrivateKeySecretValue, + ); err != nil { + t.Fatal(err) + } + return storeWithReadyKey +} + +func testRenderedChainStateDirs(t *testing.T, dirpath string) { + // Check data dir contains expected files + dirTests := []struct { + dirpath string + empty bool + }{ + {dirpath, false}, + {filepath.Join(dirpath, "geth"), false}, + {filepath.Join(dirpath, "geth", "chaindata"), false}, + {filepath.Join(dirpath, "geth", "lightchaindata"), false}, + {filepath.Join(dirpath, "keystore"), true}, + } + for _, dirTest := range dirTests { + empty, err := isEmptyOrDoesNotExist(dirTest.dirpath) + if err != nil { + t.Fatal(err) + } + if empty != dirTest.empty { + t.Fatalf("dir %s state is incorrect", dirTest.dirpath) + } + } + expectedFiles := []string{ + filepath.Join(dirpath, "geth", "LOCK"), + filepath.Join(dirpath, "geth", "nodekey"), + } + for _, expectedFile := range expectedFiles { + if _, err := os.Stat(expectedFile); os.IsNotExist(err) { + t.Fatalf("expected file %s to exist", expectedFile) + } + } +} + +func TestImmutablePodEnvVars(t *testing.T) { + tests := []struct { + podName string + ordinal int + podNamespace string + podValid bool + nsValid bool + }{ + {"", 0, "", false, false}, + {"zkevm-geth-validator-0", 0, "dev", true, true}, + {"zkevm-geth-validator-1", 1, "sandbox", true, true}, + {"zkevm-geth-validator-2", 2, "prod", true, true}, + {"zkevm-geth-validator-3", 3, "abc", true, false}, + {"zkevm-geth-validator-", 0, "abc", false, false}, + } + + for _, test := range tests { + if err := os.Setenv("POD_NAME", test.podName); err != nil { + t.Fatal(err) + } + if err := os.Setenv("POD_NAMESPACE", test.podNamespace); err != nil { + t.Fatal(err) + } + if _, err := envFromPodNamespaceEnvVar(); err != nil && test.nsValid { + t.Fatalf("expected pod namespace %s to be valid", test.podNamespace) + } + if ordinal, err := ordinalFromPodNameEnvVar(); err != nil && test.podValid { + t.Fatalf("expected pod name %s to be valid", test.podName) + } else { + if ordinal != test.ordinal { + t.Fatalf("expected ordinal %d, got %d", test.ordinal, ordinal) + } + } + } +} + +func TestImmutableIsEmpty(t *testing.T) { + dir := filepath.Join(os.TempDir(), "immutable-test") + if err := os.MkdirAll(dir, os.ModePerm); err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + + tests := []struct { + dirpath string + isEmpty bool + }{ + { + dirpath: dir, + isEmpty: true, + }, + { + dirpath: "./testdata", + isEmpty: false, + }, + { + dirpath: "./does-not-exist", + isEmpty: true, + }, + } + + for _, test := range tests { + t.Run(test.dirpath, func(t *testing.T) { + isEmpty, err := isEmptyOrDoesNotExist(test.dirpath) + if err != nil { + t.Fatal(err) + } + if isEmpty != test.isEmpty { + t.Errorf("expected %v, got %v", test.isEmpty, isEmpty) + } + }) + } +} diff --git a/cmd/geth/immutable_bootstrapper.go b/cmd/geth/immutable_bootstrapper.go new file mode 100644 index 000000000..626e58dc8 --- /dev/null +++ b/cmd/geth/immutable_bootstrapper.go @@ -0,0 +1,82 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package main + +import ( + "context" + + "github.com/ethereum/go-ethereum/cmd/geth/immutable/keys" + "github.com/ethereum/go-ethereum/cmd/geth/immutable/role" + "github.com/ethereum/go-ethereum/core" +) + +// Bootstrapper is the interface for bootstrapping a node in a k8s pod +type Bootstrapper interface { + Bootstrap(ctx context.Context) error +} + +// BootBootstrapper is the bootstrapper for boot nodes +type BootBootstrapper struct { + store keys.Store + region string + dataDirpath string // TODO: remove field when boot migration done + secretID string +} + +// Bootstrap bootstraps a boot node +func (bb *BootBootstrapper) Bootstrap(ctx context.Context) error { + return renderP2PKey( + ctx, + bb.store, + role.Boot, + bb.secretID, + ) +} + +// ValidatorBootstrapper is the bootstrapper for validator nodes +type ValidatorBootstrapper struct { + store keys.Store + region string + dataDirpath string + genesis *core.Genesis + secretID string +} + +// Bootstrap bootstraps a validator node +func (vb *ValidatorBootstrapper) Bootstrap(ctx context.Context) error { + if err := renderValidatorKey( + ctx, + vb.store, + vb.dataDirpath, + vb.secretID, + ); err != nil { + return err + } + + return renderChainState(vb.dataDirpath, vb.genesis) +} + +// RPCBootstrapper is the bootstrapper for RPC and Partner nodes +type RPCBootstrapper struct { + dataDirpath string + genesis *core.Genesis +} + +// Bootstrap bootstraps an RPC or Partner node +func (rb *RPCBootstrapper) Bootstrap(ctx context.Context) error { + return renderChainState(rb.dataDirpath, rb.genesis) +} diff --git a/cmd/geth/immutable_configure.go b/cmd/geth/immutable_configure.go new file mode 100644 index 000000000..2ef730ab6 --- /dev/null +++ b/cmd/geth/immutable_configure.go @@ -0,0 +1,139 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package main + +import ( + "bufio" + _ "embed" + "fmt" + "os" + "strconv" + "strings" + + "github.com/ethereum/go-ethereum/cmd/geth/immutable/role" + "github.com/ethereum/go-ethereum/cmd/immutable" + "github.com/ethereum/go-ethereum/log" + "github.com/urfave/cli/v2" + "golang.org/x/exp/slices" +) + +var ( + // ErrOrdinalInvariant is returned when a node's ordinal is not 0 when only a single node is supported + ErrOrdinalInvariant = fmt.Errorf("node ordinal must be 0") + // ErrConfigRecommitInvariant is returned when config.toml has an invalid recommit value + ErrConfigRecommitInvariant = fmt.Errorf("config.toml has invalid recommit value") + + // supportedRoles are the roles that are supported by configure command + supportedRoles = role.AllRoles + // singletonRoles are the roles that are only allowed to run with 1 instance + singletonRoles = []role.Role{role.Boot, role.Validator} + + // In the case of a rotation of the single validator key, we need to remove the singleton + // invariant for the validator role, as we need to increase the replica count + // singletonRoles = []role.Role{role.Boot} +) + +// configureNodePaths are all the filepaths that must be provided +// to the configuration of a geth node in a k8s pod +type configureNodePaths struct { + // ConfigFilepath is the path to the node's config.toml + ConfigFilepath string + // DataDirpath is the path to the node's data directory + DataDirpath string +} + +func configureNodeInPodCommand(c *cli.Context) error { + // Role + r, err := role.NewFromString(c.String(immutable.Role.Name)) + if err != nil { + return err + } + // Filepaths + filepaths := configureNodePaths{ + ConfigFilepath: c.String(immutable.ConfigFilepath.Name), + DataDirpath: c.String(immutable.DataDirpath.Name), + } + + // Env vars + pod := os.Getenv("POD_NAME") + if pod == "" { + return fmt.Errorf("POD_NAME env var must be set") + } + return configureNodeInPod(r, pod, filepaths) +} + +// configureNodeInPod will configure a geth node in a k8s (statefulset) pod based on its ordinal +func configureNodeInPod(r role.Role, pod string, filepaths configureNodePaths) error { + // Verify that the role is supported + if !slices.Contains(supportedRoles, r) { + return fmt.Errorf("unsupported role: %s", r.String()) + } + + // Node ordinal + ordinal, err := ordinalFromPod(pod) + if err != nil { + return err + } + // Verify single instances + if slices.Contains(singletonRoles, r) { + if ordinal != 0 { + return ErrOrdinalInvariant + } + } + + // Check config.toml + return validateConfigTOML(filepaths.ConfigFilepath) +} + +// validatorConfigTOML will read the node's config.toml and check invariants +func validateConfigTOML(configFilepath string) error { + // Open the file + f, err := os.Open(configFilepath) + if err != nil { + return fmt.Errorf("failed to open toml file (%s): %w", configFilepath, err) + } + defer f.Close() + + // Decode the TOML + cfg := &gethConfig{} + if err := tomlSettings.NewDecoder(bufio.NewReader(f)).Decode(cfg); err != nil { + return fmt.Errorf("failed to decode toml file (%s): %w", configFilepath, err) + } + + // Validate invariants + if cfg.Eth.Miner.Recommit != ImmutableEthConfig(0).Miner.Recommit { + return ErrConfigRecommitInvariant + } + + // TODO: add more invariant checks here + + log.Info("validated config", "filepath", configFilepath) + return nil +} + +// ordinalFromPod extracts the ordinal (suffix) from pod name string +func ordinalFromPod(pod string) (int, error) { + podElems := strings.Split(pod, "-") + if len(podElems) < 2 { + return 0, fmt.Errorf("pod name (%s) is not in the expected format", pod) + } + ordinal, err := strconv.Atoi(podElems[len(podElems)-1]) + if err != nil { + return 0, fmt.Errorf("pod name (%s) suffix is not a number", pod) + } + return ordinal, nil +} diff --git a/cmd/geth/immutable_configure_test.go b/cmd/geth/immutable_configure_test.go new file mode 100644 index 000000000..974a2bf41 --- /dev/null +++ b/cmd/geth/immutable_configure_test.go @@ -0,0 +1,141 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package main + +import ( + "errors" + "os" + "path/filepath" + "testing" + + "github.com/ethereum/go-ethereum/cmd/geth/immutable/role" +) + +func TestImmutableConfigure(t *testing.T) { + // Skip this test if rotating the validator keys. Invariants will be disabled. + // t.Skip() + dir := filepath.Join(os.TempDir(), "immutable-test") + if err := os.MkdirAll(dir, os.ModePerm); err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + + tests := []struct { + name string + r role.Role + pod string + ordinal int + filepaths configureNodePaths + err error + }{ + { + name: "validator valid config", + r: role.Validator, + pod: "zkevm-geth-validator-0", + ordinal: 0, + filepaths: configureNodePaths{ + ConfigFilepath: "../../cmd/geth/testdata/dev.toml", + DataDirpath: dir, + }, + err: nil, + }, + { + name: "validator invalid config", + r: role.Validator, + pod: "zkevm-geth-validator-0", + ordinal: 0, + filepaths: configureNodePaths{ + ConfigFilepath: "../../cmd/geth/testdata/dev_bad_recommit.toml", + DataDirpath: dir, + }, + err: ErrConfigRecommitInvariant, + }, + { + name: "validator invalid ordinal", + r: role.Validator, + pod: "zkevm-geth-validator-1", + ordinal: 1, + filepaths: configureNodePaths{ + ConfigFilepath: "../../cmd/geth/testdata/dev.toml", + DataDirpath: dir, + }, + err: ErrOrdinalInvariant, + }, + { + name: "boot valid config", + r: role.Boot, + pod: "zkevm-geth-boot-0", + ordinal: 0, + filepaths: configureNodePaths{ + ConfigFilepath: "../../cmd/geth/testdata/dev.toml", + DataDirpath: dir, + }, + err: nil, + }, + { + name: "boot invalid ordinal", + r: role.Boot, + pod: "zkevm-geth-boot-1", + ordinal: 1, + filepaths: configureNodePaths{ + ConfigFilepath: "../../cmd/geth/testdata/dev.toml", + DataDirpath: dir, + }, + err: ErrOrdinalInvariant, + }, + { + name: "rpc valid config", + r: role.RPC, + pod: "zkevm-geth-rpc-2", + ordinal: 2, + filepaths: configureNodePaths{ + ConfigFilepath: "../../cmd/geth/testdata/dev.toml", + DataDirpath: dir, + }, + err: nil, + }, + { + name: "partner valid config", + r: role.Partner, + pod: "zkevm-geth-partner-3", + ordinal: 3, + filepaths: configureNodePaths{ + ConfigFilepath: "../../cmd/geth/testdata/dev.toml", + DataDirpath: dir, + }, + err: nil, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := configureNodeInPod(test.r, test.pod, test.filepaths) + if err != nil { + if test.err == nil { + t.Fatal(err) + } + if !errors.Is(err, test.err) { + t.Fatalf("error: %v, expected: %v", err, test.err) + } + // Got the error we expected + return + } else if test.err != nil { + t.Fatalf("expected error: %v", test.err) + } + }) + } +} diff --git a/cmd/geth/immutable_decode_command.go b/cmd/geth/immutable_decode_command.go new file mode 100644 index 000000000..f7a62370f --- /dev/null +++ b/cmd/geth/immutable_decode_command.go @@ -0,0 +1,43 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package main + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/cmd/immutable" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/urfave/cli/v2" +) + +func runDecodeCommand(c *cli.Context) error { + // Read encoded inputs + pubKeyHex := c.String(immutable.PublicKey.Name) + + // Perform decode + enodeURL := fmt.Sprintf("enode://%s@%s:%s", pubKeyHex, "127.0.0.1", "30300") + enode, err := enode.Parse(enode.ValidSchemes, enodeURL) + if err != nil { + return fmt.Errorf("failed to parse enode URL: %v", err) + } + + // Log the decoded info + log.Info("success", "id", enode.ID()) + + return nil +} diff --git a/cmd/geth/immutable_eth_config.go b/cmd/geth/immutable_eth_config.go new file mode 100644 index 000000000..50c3d29a2 --- /dev/null +++ b/cmd/geth/immutable_eth_config.go @@ -0,0 +1,140 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package main + +import ( + "math/big" + "time" + + "github.com/ethereum/go-ethereum/cmd/geth/immutable/settings" + "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" + "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/ethconfig" + "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/urfave/cli/v2" +) + +func immutableOverrides(ctx *cli.Context, cfg *gethConfig) { + // Cannot set these flags at the same time + utils.CheckExclusive( + ctx, + utils.ImmutableNetworkFlag, + utils.OverrideShanghai, + ) + utils.CheckExclusive( + ctx, + utils.ImmutableNetworkFlag, + utils.OverridePrevrandao, + ) + utils.CheckExclusive( + ctx, + utils.ImmutableNetworkFlag, + utils.OverrideCancun, + ) + // Set overrides based on network flag. + if ctx.IsSet(utils.ImmutableNetworkFlag.Name) { + genesis := core.ImmutableGenesisBlock(ctx.String(utils.ImmutableNetworkFlag.Name)) + cfg.Eth.OverrideShanghai = genesis.Config.ShanghaiTime + cfg.Eth.OverridePrevrandao = genesis.Config.PrevrandaoTime + cfg.Eth.OverrideCancun = genesis.Config.CancunTime + // All overrides are handled in the genesis block, so we can terminate here + return + } + // zkEVM flag was not set so lets check individual override flags for testing + if ctx.IsSet(utils.OverridePrevrandao.Name) { + val := ctx.Uint64(utils.OverridePrevrandao.Name) + cfg.Eth.OverridePrevrandao = &val + } + if ctx.IsSet(utils.OverrideShanghai.Name) { + val := ctx.Uint64(utils.OverrideShanghai.Name) + cfg.Eth.OverrideShanghai = &val + } + if ctx.IsSet(utils.OverrideCancun.Name) { + val := ctx.Uint64(utils.OverrideCancun.Name) + cfg.Eth.OverrideCancun = &val + } +} + +// ImmutableEthConfig is the default content of config.toml that +// all zkEVM geth nodes should be configured to use. +func ImmutableEthConfig(chainID int) ethconfig.Config { + return ethconfig.Config{ + SyncMode: downloader.FullSync, + NoPruning: true, // See cmd/utils/flags.go:1727 + NoPrefetch: false, + NetworkId: uint64(chainID), + TxLookupLimit: 2350000, + TransactionHistory: 2350000, + StateScheme: rawdb.HashScheme, + LightPeers: 0, + DatabaseCache: 512, + SnapshotCache: 0, // Not snapshot, full sync + TrieCleanCache: 154 + 102, // See cmd/utils/flags.go:1786 + TrieDirtyCache: 256, + TrieTimeout: 60 * time.Minute, + FilterLogCacheSize: 32, + Miner: miner.Config{ + GasCeil: 30000000, + NewPayloadTimeout: 2 * time.Second, + Recommit: 999999999 * time.Second, + // GasPrice must be >= price limit for eth_maxPriorityFeePerGas + GasPrice: big.NewInt(settings.PriceLimit), + }, + TxPool: legacypool.Config{ + // PriceLimit enforces mimimum tip cap and/or tx gas price + PriceLimit: settings.PriceLimit, + NoLocals: true, + Journal: "transactions.rlp", + Rejournal: time.Hour, + PriceBump: 10, + AccountSlots: 16, + GlobalSlots: 4096 + 1024, // urgent + floating queue capacity with 4:1 ratio + AccountQueue: 64, + GlobalQueue: 1024, + Lifetime: 1 * time.Hour, + }, + BlobPool: blobpool.Config{ + Datadir: "blobpool", + Datacap: 1, // If this is below 1, it will be set to a default of a few GB + PriceBump: 100, + }, + RPCGasCap: 300000000, + RPCEVMTimeout: 5 * time.Second, + GPO: ethconfig.FullNodeGPO, + RPCTxFeeCap: 0, + } +} + +func nodeConfig(enodes []*enode.Node) node.Config { + return node.Config{ + AllowUnprotectedTxs: true, + InsecureUnlockAllowed: false, + IPCPath: "geth.ipc", + P2P: p2p.Config{ + BootstrapNodes: enodes, + MaxPeers: 100, // TODO: peer limit can be inferred from node count, but we need to understand surging behaviour of p2p/discovery + NAT: nil, + }, + } +} diff --git a/cmd/geth/immutable_local.go b/cmd/geth/immutable_local.go new file mode 100644 index 000000000..930507207 --- /dev/null +++ b/cmd/geth/immutable_local.go @@ -0,0 +1,131 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package main + +import ( + "fmt" + "os" + "time" + + "github.com/ethereum/go-ethereum/cmd/immutable" + "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/urfave/cli/v2" + "golang.org/x/sync/errgroup" +) + +const ( + // nodePassword is used by every node to encrypt their keystore + nodePassword = "password" +) + +func bootstrapLocalCommand(c *cli.Context) error { + // Bootstrap flags + gasLimit := c.Uint64(immutable.GasLimit.Name) + bootCount := c.Int(immutable.BootCount.Name) + validatorCount := c.Int(immutable.ValidatorCount.Name) + rpcCount := c.Int(immutable.RPCCount.Name) + blockListFilepath := c.String(immutable.BlockListFilepath.Name) + externalConfigFilepath := c.String(configFileFlag.Name) + remoteNetwork := c.String(immutable.Env.Name) + rootDirpath := c.String(immutable.DataDirpath.Name) + if rootDirpath == "" { + rootDirpath = os.TempDir() + } + + // Flags fed directly into geth client + gethFlags := []string{} + if remoteNetwork != "" { + gethFlags = append(gethFlags, "--zkevm", remoteNetwork) + if validatorCount > 0 { + return fmt.Errorf("cannot have validators in %s", remoteNetwork) + } + } + if c.IsSet(utils.GCModeFlag.Name) { + gethFlags = append(gethFlags, "--gcmode", c.String(utils.GCModeFlag.Name)) + } else { + gethFlags = append(gethFlags, "--gcmode", "archive") + } + if c.IsSet(utils.OverrideShanghai.Name) { + shanghaiTimestamp := c.Uint64(utils.OverrideShanghai.Name) + gethFlags = append(gethFlags, "--override.shanghai", fmt.Sprint(shanghaiTimestamp)) + } + if c.IsSet(utils.OverridePrevrandao.Name) { + prevrandaoTimestamp := c.Uint64(utils.OverridePrevrandao.Name) + gethFlags = append(gethFlags, "--override.prevrandao", fmt.Sprint(prevrandaoTimestamp)) + } + if c.IsSet(utils.OverrideCancun.Name) { + cancunTimestamp := c.Uint64(utils.OverrideCancun.Name) + gethFlags = append(gethFlags, "--override.cancun", fmt.Sprint(cancunTimestamp)) + } + if c.IsSet(utils.SyncModeFlag.Name) { + syncMode := c.String(utils.SyncModeFlag.Name) + gethFlags = append(gethFlags, "--syncmode", syncMode) + } else { + gethFlags = append(gethFlags, "--syncmode", "full") + } + if validatorCount+rpcCount > 9 { + // The 8545/8535 ports will clash if node count is high enough. + // This is an easy limitation to avoid, but there is no need for now. + // Local bootstrap is only used for testing. + return fmt.Errorf("cannot have more than 9 nodes") + } + + // Construct the bootstrapper and run the nodes + opts := bootstrapOptions{ + rootDirpath: rootDirpath, + validatorCount: validatorCount, + bootCount: bootCount, + rpcCount: rpcCount, + gasLimit: gasLimit, + blockListFilepath: blockListFilepath, + remoteNetwork: remoteNetwork, + remoteConfigFilepath: externalConfigFilepath, + } + bootstrapper, err := NewLocalBootstrapper(&opts) + if err != nil { + return err + } + defer bootstrapper.Clean() + + g, _ := errgroup.WithContext(c.Context) + + boots, validators, rpcs := bootstrapper.boots, bootstrapper.validators, bootstrapper.rpcs + for i := range boots { + i := i // NOTE: https://golang.org/doc/faq#closures_and_goroutines + g.Go(func() error { + return boots[i].Run([]string{}) + }) + } + // Wait for bootnodes + time.Sleep(3 * time.Second) // TODO: feed an io.Writer into stdout/stderr of bootnode exes and check ready output before proceeding + + for i := range validators { + i := i // NOTE: https://golang.org/doc/faq#closures_and_goroutines + g.Go(func() error { + return validators[i].Run(gethFlags) + }) + } + + for i := range rpcs { + i := i // NOTE: https://golang.org/doc/faq#closures_and_goroutines + g.Go(func() error { + return rpcs[i].Run(gethFlags) + }) + } + + return g.Wait() +} diff --git a/cmd/geth/immutable_local_bootstrapper.go b/cmd/geth/immutable_local_bootstrapper.go new file mode 100644 index 000000000..0724bd0c7 --- /dev/null +++ b/cmd/geth/immutable_local_bootstrapper.go @@ -0,0 +1,439 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package main + +import ( + "encoding/json" + "fmt" + "math/big" + "net/url" + "os" + "path/filepath" + + "github.com/ethereum/go-ethereum/cmd/geth/immutable/env" + "github.com/ethereum/go-ethereum/cmd/geth/immutable/node" + "github.com/ethereum/go-ethereum/cmd/geth/immutable/role" + "github.com/ethereum/go-ethereum/cmd/geth/immutable/settings" + "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + ethnode "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/params" + "github.com/urfave/cli/v2" +) + +// Premine contains addresses and amounts of premined balances +// in the genesis block +type Premine struct { + Address common.Address + Wei *big.Int +} + +// ChainOptions contains all the options for generating a genesis +type ChainOptions struct { + GasLimit uint64 + SecondsPerBlock uint64 + + Validators []common.Address + Premines []Premine + + ChainID int + Dirpath string +} + +// Genesis contains the genesis and bootstrap information about it +type Genesis struct { + // Genesis is the content of the genesis + Genesis *core.Genesis + // Filepath is the path to the genesis file + Filepath string + // JSON is a convenience field to avoid having to re-marshal the genesis + JSON []byte +} + +// LocalBootstrapper is used to generate genesis files and validator credentials +type LocalBootstrapper struct { + rootEnvDirpath string + boots []node.Node + validators []node.Node + rpcs []node.Node +} + +type bootstrapOptions struct { + rootDirpath string + validatorCount int + bootCount int + rpcCount int + gasLimit uint64 + + blockListFilepath string + + // Set these if you want to bootstrap against a remote network only. + remoteNetwork string + remoteConfigFilepath string + + // NOTE: This type should not be used here (compartmentalization bad). + // We have not refactored geth to improve the bootstrapper in this regard. + ctx *cli.Context +} + +// NewLocalBootstrapper will bootstrap a set of nodes to be run locally. +// It may be used to start a new network or start a node pointing to an existing, remote network. +func NewLocalBootstrapper(opts *bootstrapOptions) (*LocalBootstrapper, error) { + // Default to devnet configuration + networkName := "devnet" + + // If a remote network is specified, use that network's configuration + if opts.remoteNetwork != "" { + networkName = opts.remoteNetwork + } + network, err := settings.NewNetwork(networkName) + if err != nil { + return nil, err + } + + // Set up the paths we will bootstrap files to + rootEnvDirpath := filepath.Join(opts.rootDirpath, network.String()) + chainSubdir := fmt.Sprint("chain", "-", network.ID()) + chainDirpath := filepath.Join(rootEnvDirpath, chainSubdir) + + // Config TOMLs are either created in this process or derived from an existing file + configFilepath := opts.remoteConfigFilepath + if configFilepath == "" { + configFilepath = filepath.Join(chainDirpath, "config.toml") + } + + // Clear chaindir + if err := os.RemoveAll(chainDirpath); err != nil { + return nil, fmt.Errorf("failed to clear netdir: %w", err) + } + // Create dir + if err := os.MkdirAll(chainDirpath, os.ModePerm); err != nil { + return nil, fmt.Errorf("failed to create datadir: %w", err) + } + + // Create the nodes + nodeOpts := make([]node.Options, opts.bootCount+opts.validatorCount+opts.rpcCount) + for i := range nodeOpts { + u, err := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", node.PortP2P+i)) + if err != nil { + return nil, fmt.Errorf("failed to parse url: %w", err) + } + role, ordinal := genesisNodeRoleAndOrdinal(opts.bootCount, opts.validatorCount, opts.rpcCount, i) + nodeOpts[i] = node.Options{ + Role: role, + Ordinal: ordinal, + Password: nodePassword, + URL: u, + ChainDirpath: chainDirpath, + } + } + nodeConf := node.Config{ + Network: *network, + ConfigFilepath: configFilepath, + BlockListFilepath: opts.blockListFilepath, + } + nodes, err := createNodes(nodeOpts, nodeConf) + if err != nil { + return nil, err + } + + // Store the node passwords to files + for i := range nodes { + if err := os.WriteFile(localPasswordFilepath(nodes[i]), []byte(nodes[i].Password()), os.ModePerm); err != nil { + return nil, fmt.Errorf("failed to create local pw file: %w", err) + } + } + + // Create the remaining chain artifacts + boots, validators, rpcs := splitGenesisNodes(nodes, opts.bootCount, opts.validatorCount) + var gen *Genesis + if opts.remoteNetwork == "" { + bridgeEOAAddress := "0x02F0d131F1f97aef08aEc6E3291B957d9Efe7105" // Also in cmd/geth/testdata/key.addr + chainOpts := ChainOptions{ + GasLimit: opts.gasLimit, + SecondsPerBlock: settings.SecondsPerBlock, + Validators: nodesToAddresses(validators), + Premines: immutablePremines(env.Devnet, common.HexToAddress(bridgeEOAAddress)), + Dirpath: chainDirpath, + ChainID: network.ID(), + } + // Generate a clique genesis + var err error + gen, err = Clique(chainOpts) + if err != nil { + return nil, err + } + } else { + // Derive a genesis from the remote network + network, err := settings.NewNetwork(opts.remoteNetwork) + if err != nil { + return nil, err + } + gen = &Genesis{ + Filepath: fmt.Sprintf("./cmd/geth/immutable/genesis/%s.json", network.String()), + Genesis: core.ImmutableGenesisBlock(network.String()), + JSON: []byte(network.GenesisJSON()), + } + } + + // Make sure we run an immutable network + if !gen.Genesis.Config.IsValidImmutableZKEVM() { + return nil, fmt.Errorf("invalid genesis config: %+v", *gen.Genesis.Config) + } + + // Init the geth nodes with genesis + if err := renderLocalChainState(opts.ctx, gen, nodes); err != nil { + return nil, err + } + + // Render the config if we are not connecting to an existing network + if opts.remoteNetwork == "" { + if err := renderLocalConfig(boots, configFilepath, network.ID()); err != nil { + return nil, err + } + } + + return &LocalBootstrapper{ + rootEnvDirpath: rootEnvDirpath, + boots: boots, + validators: validators, + rpcs: rpcs, + }, nil +} + +func (b *LocalBootstrapper) Clean() { + os.RemoveAll(b.rootEnvDirpath) +} + +// createNodes will generate EOA credentials and store them in encrypted keystore files +func createNodes(opts []node.Options, conf node.Config) ([]node.Node, error) { + // Generate validator accounts + nodeCount := len(opts) + nodes := make([]node.Node, 0, nodeCount) + for i := 0; i < nodeCount; i++ { + // Construct the node + node, err := node.New(opts[i], conf) + if err != nil { + return nil, err + } + + nodes = append(nodes, *node) + } + + return nodes, nil +} + +// Clique will generate a genesis for a clique chain and store it to file +func Clique(opts ChainOptions) (*Genesis, error) { + gen, err := clique(opts) + if err != nil { + return nil, err + } + if err := os.WriteFile(gen.Filepath, gen.JSON, os.ModePerm); err != nil { + return nil, fmt.Errorf("failed to write genesis file: %w", err) + } + return gen, nil +} + +func clique(opts ChainOptions) (*Genesis, error) { + // To encode the signer addresses in extradata, + // concatenate 32 zero bytes, all signer addresses and 65 further zero bytes. + // This is based on the clique spec: https://eips.ethereum.org/EIPS/eip-225 + extraDataLength := crypto.DigestLength + common.AddressLength*len(opts.Validators) + crypto.SignatureLength + extraData := make([]byte, extraDataLength) + for i := range opts.Validators { + from := crypto.DigestLength + common.AddressLength*i + to := from + common.AddressLength + copy(extraData[from:to], opts.Validators[i].Bytes()) + } + + // Premine + alloc := types.GenesisAlloc{} + for i := range opts.Premines { + alloc[opts.Premines[i].Address] = types.Account{ + Balance: opts.Premines[i].Wei, + } + } + + // Marshal the genesis + gen := core.Genesis{ + Config: ¶ms.ChainConfig{ + ChainID: new(big.Int).SetUint64(uint64(opts.ChainID)), + HomesteadBlock: common.Big0, + EIP150Block: common.Big0, + EIP155Block: common.Big0, + EIP158Block: common.Big0, + ByzantiumBlock: common.Big0, + ConstantinopleBlock: common.Big0, + PetersburgBlock: common.Big0, + IstanbulBlock: common.Big0, + MuirGlacierBlock: common.Big0, + BerlinBlock: common.Big0, + LondonBlock: common.Big0, + ArrowGlacierBlock: common.Big0, + GrayGlacierBlock: common.Big0, + MergeNetsplitBlock: common.Big0, + Clique: ¶ms.CliqueConfig{ + Period: opts.SecondsPerBlock, + Epoch: 30000, + }, + IsReorgBlocked: true, + // So as to reflect devnet, testnet, and mainnet, these forks should not be enabled in genesis. + ShanghaiTime: nil, + PrevrandaoTime: nil, + CancunTime: nil, // If genesis block has Cancun enabled, you must set the blob-related headers too. + }, + + Difficulty: big.NewInt(1), + GasLimit: opts.GasLimit, + Mixhash: common.Hash{}, + ExtraData: extraData, + Alloc: alloc, + } + json, err := json.MarshalIndent(gen, "", " ") + if err != nil { + return nil, fmt.Errorf("failed to marshal genesis: %w", err) + } + + // Write genesis file + path := filepath.Join(opts.Dirpath, "genesis.json") + return &Genesis{ + Genesis: &gen, + Filepath: path, + JSON: json, + }, nil +} + +// renderLocalChainState will initialize the chaindata and lightchaindata dbs +func renderLocalChainState(c *cli.Context, gen *Genesis, nodes []node.Node) error { + // Init node dbs and write genesis to dbs + for i := range nodes { + // Instantiate node based on config and dir + cfg := loadBaseConfig(c) + cfg.Node.DataDir = nodes[i].Dirpath() + stack, err := ethnode.New(&cfg.Node) + if err != nil { + return fmt.Errorf("failed to create node: %w", err) + } + defer stack.Close() + + // Init genesis for chain and lightchain dbs of node + for _, name := range []string{"chaindata", "lightchaindata"} { + // Create and open leveldb + chaindb, err := stack.OpenDatabaseWithFreezer(name, 0, 0, "", "", false) + if err != nil { + return fmt.Errorf("failed to open db: %w", err) + } + defer chaindb.Close() + + // Create and open trie db + triedb := utils.MakeTrieDatabaseNoContext(cfg.Eth.StateScheme, chaindb, false, false, false) + defer triedb.Close() + + // Write genesis block to trie and save to leveldb + if _, _, err := core.SetupGenesisBlock(chaindb, triedb, gen.Genesis); err != nil { + return fmt.Errorf("failed to write genesis block to db: %w", err) + } + } + } + + return nil +} + +// renderLocalConfig will write the config file for each node. +// This needs to be run after nodekeys are generated by renderLocalChainState. +func renderLocalConfig(boots []node.Node, configFilepath string, chainID int) (err error) { + enodes := make([]*enode.Node, len(boots)) + for i := range enodes { + en, err := enodeFromNodeKey(boots[i]) + if err != nil { + return err + } + enodes[i] = en + } + + f, err := os.OpenFile(configFilepath, os.O_RDWR|os.O_CREATE, 0755) + if err != nil { + return fmt.Errorf("failed to open config file: %w", err) + } + defer f.Close() + conf := &gethConfig{ + Eth: ImmutableEthConfig(chainID), + Node: nodeConfig(enodes), + } + + if err := tomlSettings.NewEncoder(f).Encode(conf); err != nil { + return fmt.Errorf("failed to encode config: %w", err) + } + return nil +} + +// enodeFromNodeKey reads the nodekey file and parses the enode. +// The nodekey files are available after chain state has been rendered. +func enodeFromNodeKey(node node.Node) (*enode.Node, error) { + // Derive the public key + nodeKeyFilepath := filepath.Join(node.Dirpath(), "geth", "nodekey") + nodeKey, err := crypto.LoadECDSA(nodeKeyFilepath) + if err != nil { + return nil, fmt.Errorf("failed to read nodekey file: %w", err) + } + + // Parse the enode + enodeURL := fmt.Sprintf("enode://%x@%s:%s", crypto.FromECDSAPub(&nodeKey.PublicKey)[1:], node.URL().Hostname(), node.URL().Port()) + enode, err := enode.Parse(enode.ValidSchemes, enodeURL) + if err != nil { + return nil, fmt.Errorf("failed to parse enode: %w", err) + } + return enode, nil +} + +func nodesToAddresses(nodes []node.Node) []common.Address { + addrs := make([]common.Address, len(nodes)) + for i := range nodes { + addrs[i] = nodes[i].Account().Address + } + return addrs +} + +func genesisNodeRoleAndOrdinal(bootCount, validatorCount, rpcCount, index int) (role.Role, int) { + // Boots + if index < bootCount { + return role.Boot, index + } + // Validators + if index < bootCount+validatorCount { + return role.Validator, index - bootCount + } + // RPCs + if index < bootCount+validatorCount+rpcCount { + return role.RPC, index - bootCount - validatorCount + } + // Partner + return role.Partner, index - bootCount - validatorCount - rpcCount +} + +func splitGenesisNodes(nodes []node.Node, bootCount, validatorCount int) (boots, validators, rpcs []node.Node) { + return nodes[:bootCount], nodes[bootCount : bootCount+validatorCount], nodes[bootCount+validatorCount:] +} + +func localPasswordFilepath(node node.Node) string { + return filepath.Join(node.Dirpath(), node.Password()) +} diff --git a/cmd/geth/immutable_premine.go b/cmd/geth/immutable_premine.go new file mode 100644 index 000000000..c3deb74bc --- /dev/null +++ b/cmd/geth/immutable_premine.go @@ -0,0 +1,88 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package main + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/cmd/geth/immutable/env" + "github.com/ethereum/go-ethereum/common" +) + +var ( + // totalSupplyWei is the amount of IMX that is pre-funded to the bridge EOA + totalSupplyWei = new(big.Int).Mul(big.NewInt(1e18), big.NewInt(2e9)) + + // immutableDevPremines is a list of EOAs that are pre-funded with a large amount of ETH + // on dev networks. This is used to allow developers to deploy contracts without having + // to request funds from a faucet. + immutableDevPremineAddresses = []string{ + "0x340bC2c77514ede2a23Fd4F42F411A8e351d8eE6", + "0xebbf4C07a63986204C37cc5A188AaBF53564C583", + "0xdEAdC0de8a3B037925a895843f96b0c525FBC31f", + "0xeFE12952541356Ffc969A343A81D1cE7D2806179", + "0x4AEdf28A437b94749037cC39f83F4422469CF2F7", + "0x8318a871CC140d9f77a1999f84875AC36EeCC04E", + "0xCc5C8CEa877f2F351F38c190867BbD31FaFadD22", + "0x000000000013B7b1B08B3c8EFE02E866F746bD38", + "0xa6C368164Eb270C31592c1830Ed25c2bf5D34BAE", + "0xC606830D8341bc9F5F5Dd7615E9313d2655B505D", + "0x784578949A4A50DeA641Fb15dd2B11C72E76919a", + "0xEac347177DbA4a190B632C7d9b8da2AbfF57c772", + "0xD509997AB62fDA51c32E64E69Fb090DF8894105e", + "0xF6372939CE2d14A68A629B8E4785E9dCB4EdA0cf", + "0x9C1634bebC88653D2Aebf4c14a3031f62092b1D9", + "0xb3343666188A694120C18c4985C57e4C0913A6F0", + "0x2E969d22e6654e064F461cf8B1314Cc0864a4914", + "0xd9275Eb8276E14b9e28d5f9B12e90dDAAF3586Ef", + "0x3e290FE8F2A5dB60A81cb47EA296e0299048Dd71", + "0x4A73506a31DB769AC442b17ca9A1679f44757Bbf", + "0x7C3E6CE6fd293Fc66d9d73d49fd546CCE1e19F0e", + "0xEB7FFb9fb0c80437120f6F97EdE60aB59055EAE0", + "0xe567Ea84e1eB3fFdc8F5aA420BF14A16eeE6A809", + "0xC8714F989cE817e5d21349888077Aa5Db4A9BCf6", + "0x0CCB0a3fc5Ca38fcd9FfD8a667Cb83e3194250d7", + "0x5ABFc3E307b037325BFC6988Ae265dcB211Ec533", + "0x7442eD1e3c9FD421F47d12A2742AfF5DaFBf43f8", + "0x4A73506a31DB769AC442b17ca9A1679f44757Bbf", + "0x7C3E6CE6fd293Fc66d9d73d49fd546CCE1e19F0e", + "0xEB7FFb9fb0c80437120f6F97EdE60aB59055EAE0", + "0xed557863FFD4C87537BA8264098B22483c6145f2", + "0x7924BF4cBb25f7bA2aB1335e293afe6a7E78235a", + } +) + +func immutablePremines(envr env.Environment, bridgeEOA common.Address) []Premine { + // Always premine bridge EOA + premines := []Premine{ + { + Address: bridgeEOA, + Wei: totalSupplyWei, + }, + } + // Only premine devnet + if envr != env.Devnet { + return premines + } + for _, address := range immutableDevPremineAddresses { + premines = append(premines, Premine{ + Address: common.HexToAddress(address), + Wei: totalSupplyWei, + }) + } + return premines +} diff --git a/cmd/geth/immutable_premine_test.go b/cmd/geth/immutable_premine_test.go new file mode 100644 index 000000000..c66c62ed7 --- /dev/null +++ b/cmd/geth/immutable_premine_test.go @@ -0,0 +1,57 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package main + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/cmd/geth/immutable/env" + "github.com/ethereum/go-ethereum/common" +) + +func TestImmutablePremine(t *testing.T) { + testAddr := common.HexToAddress("0x2E969d22e6654e064F461cf8B1314Cc0864a4914") + type test struct { + env env.Environment + premineCount int + premineWei *big.Int + } + + tests := []test{ + {env: env.Devnet, premineCount: len(immutableDevPremineAddresses) + 1, premineWei: totalSupplyWei}, + {env: env.Testnet, premineCount: 1, premineWei: totalSupplyWei}, + {env: env.Mainnet, premineCount: 1, premineWei: totalSupplyWei}, + } + + for _, tc := range tests { + got := immutablePremines(tc.env, testAddr) + if len(got) != tc.premineCount { + t.Fatalf("expected: %v, got: %v, %+v", tc.premineCount, len(got), tc) + } + for _, premine := range got { + if premine.Wei.String() != tc.premineWei.String() { + t.Fatalf("expected: %v, got: %v, %+v", tc.premineWei, premine.Wei, tc) + } + } + if len(got) == 1 { + if got[0].Address != testAddr { + t.Fatalf("expected: %v, got: %v, %+v", testAddr, got[0].Address, tc) + } + } + } +} diff --git a/cmd/geth/immutable_remote_store.go b/cmd/geth/immutable_remote_store.go new file mode 100644 index 000000000..077390a59 --- /dev/null +++ b/cmd/geth/immutable_remote_store.go @@ -0,0 +1,32 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package main + +import ( + "context" +) + +// ArtefactStore handles configuration artefacts created by the +// bootstrap process. +type ArtefactStore interface { + PushSecretFile(key string, content []byte) error + StoreConfigFile(filepath string, content []byte) error + PushSecretString(key, content string) error + PutSecretString(key, content string) error + GeneratePassword() (string, error) + GetSecret(ctx context.Context, id string) (string, error) +} diff --git a/cmd/geth/immutable_rewind_command.go b/cmd/geth/immutable_rewind_command.go new file mode 100644 index 000000000..63f1f0959 --- /dev/null +++ b/cmd/geth/immutable_rewind_command.go @@ -0,0 +1,108 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package main + +import ( + "fmt" + "path/filepath" + "strconv" + "time" + + "github.com/ethereum/go-ethereum/cmd/geth/immutable/rewind" + "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/types" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/urfave/cli/v2" +) + +const ( + rewindHistoryFilename = "rewind_history.yaml" +) + +// rewindChainCommand is the command to rewind the chain to a specific block. +// If this is run on the validator node, it will proceed to create new blocks from +// the point of the block to which the chain is rewound. +func runRewindChainCommand(ctx *cli.Context) error { + if ctx.Args().Len() < 1 { + utils.Fatalf("This command requires an argument (block number or hash).") + } + + // Set up the node handle. + stack, _ := makeConfigNode(ctx) + defer stack.Close() + chain, db := utils.MakeChain(ctx, stack, false) + defer db.Close() + + // Get the header to which the chain should be rewound. + header, err := getRewindHeader(ctx, db) + if err != nil { + return err + } + + // If the rewind has already occurred, do not repeat. + historyFilepath := filepath.Join(stack.DataDir(), rewindHistoryFilename) + history, err := rewind.ReadRewindHistory(historyFilepath) + if err != nil { + return err + } + log.Info("Rewind history", "history", history) + if history.Contains(header.Hash()) { + log.Info("Chain has already been rewound to this block (%s).", header.Hash().Hex()) + return nil + } + + // Perform the rewind. + log.Info("Rewinding chain to block", "block", header.Number) + if err := chain.SetHead(header.Number.Uint64()); err != nil { + return err + } + + // Record the fact that the chain has been rewound. + history = append(history, rewind.Record{ + BlockNumber: header.Number.Uint64(), + BlockHash: header.Hash(), + Timestamp: time.Now(), + }) + return rewind.WriteRewindHistory(history, historyFilepath) +} + +// getRewindHeader returns the header to which the chain should be rewound +// based on arguments passed to the command. +func getRewindHeader(ctx *cli.Context, db ethdb.Database) (*types.Header, error) { + // Parse the argument which may be hex or a number for a block. + arg := ctx.Args().First() + if hashish(arg) { + // Parse hex. + hash := common.HexToHash(arg) + if number := rawdb.ReadHeaderNumber(db, hash); number != nil { + return rawdb.ReadHeader(db, hash, *number), nil + } + return nil, fmt.Errorf("block %x not found", hash) + } + // Parse number. + number, err := strconv.ParseUint(arg, 10, 64) + if err != nil { + return nil, err + } + if hash := rawdb.ReadCanonicalHash(db, number); hash != (common.Hash{}) { + return rawdb.ReadHeader(db, hash, number), nil + } + return nil, fmt.Errorf("header for block %d not found", number) +} diff --git a/cmd/geth/immutable_run_boot_node_command.go b/cmd/geth/immutable_run_boot_node_command.go new file mode 100644 index 000000000..ab24971c5 --- /dev/null +++ b/cmd/geth/immutable_run_boot_node_command.go @@ -0,0 +1,137 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package main + +import ( + "crypto/ecdsa" + "fmt" + "net" + "os" + + "github.com/ethereum/go-ethereum/accounts/immutable" + "github.com/ethereum/go-ethereum/cmd/geth/immutable/keys" + "github.com/ethereum/go-ethereum/cmd/geth/immutable/node" + "github.com/ethereum/go-ethereum/cmd/geth/immutable/role" + "github.com/ethereum/go-ethereum/cmd/immutable/remote/aws" + "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p/discover" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/urfave/cli/v2" +) + +func runBootNodeCommand(c *cli.Context) error { + // Retrieve the boot node's p2p key + var p2pKey *ecdsa.PrivateKey + + pwFilepath := os.Getenv("GETH_FLAG_PASSWORD_FILEPATH") + if pwFilepath != "" { + // Retrieve the key from local keystore (for local development only) + log.Info("retrieving P2P key from local keystore") + keyStoreDirpath := c.String(utils.KeyStoreDirFlag.Name) + store, err := immutable.NewKeystore(keyStoreDirpath, pwFilepath) + if err != nil { + return err + } + p2pKey, err = store.GetPrivateKey(c.Context) + if err != nil { + return err + } + } else { + // Retrieve the key from AWS + log.Info("retrieving P2P key from Secrets Manager") + awsRegion := os.Getenv("GETH_FLAG_IMMUTABLE_AWS_REGION") + if awsRegion == "" { + return fmt.Errorf("GETH_FLAG_IMMUTABLE_AWS_REGION is required") + } + store, err := aws.NewSecretsManager(awsRegion) + if err != nil { + return fmt.Errorf("failed to create AWS SecretsManager Store: %v ", err) + } + podOrdinal, err := ordinalFromPodNameEnvVar() + if err != nil { + return err + } + secretIDTemplate, err := keys.SecretIDTemplate() + if err != nil { + return err + } + secretID, err := keys.SecretID(secretIDTemplate, node.CanonicalNodeName(role.Boot, podOrdinal)) + if err != nil { + return err + } + p2pKey, err = keys.Retrieve( + c.Context, + store, + secretID, + ) + if err != nil { + return fmt.Errorf("failed to retrieve P2P key: %v", err) + } + } + + // Run the boot node + return runBootNode(p2pKey, c.Int(utils.ListenPortFlag.Name)) +} + +func runBootNode(p2pKey *ecdsa.PrivateKey, port int) error { + // Log + glogger := log.NewGlogHandler(log.NewTerminalHandler(os.Stderr, false)) + glogger.Verbosity(4) + glogger.Vmodule("") + log.SetDefault(log.NewLogger(glogger)) + + // UDP session + listenAddr := fmt.Sprintf(":%d", port) + addr, err := net.ResolveUDPAddr("udp", listenAddr) + if err != nil { + return fmt.Errorf("failed to resolve UDP address: %w", err) + } + conn, err := net.ListenUDP("udp", addr) + if err != nil { + return fmt.Errorf("failed to listen UDP: %w", err) + } + defer conn.Close() + + // Enode DB + db, _ := enode.OpenDB("") + ln := enode.NewLocalNode(db, p2pKey) + + // Notice + listenerAddr := conn.LocalAddr().(*net.UDPAddr) + printNotice(&p2pKey.PublicKey, *listenerAddr) + + // Run + cfg := discover.Config{ + PrivateKey: p2pKey, + NetRestrict: nil, + } + if _, err := discover.ListenUDP(conn, ln, cfg); err != nil { + return fmt.Errorf("failed to listen UDP: %w", err) + } + + // Block + 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()) +} diff --git a/cmd/geth/immutable_vote.go b/cmd/geth/immutable_vote.go new file mode 100644 index 000000000..7bbe77fc4 --- /dev/null +++ b/cmd/geth/immutable_vote.go @@ -0,0 +1,138 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package main + +import ( + "context" + "errors" + "fmt" + "strings" + "time" + + "github.com/ethereum/go-ethereum/cmd/immutable" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient/gethclient" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rpc" + "github.com/urfave/cli/v2" + "golang.org/x/exp/slices" +) + +var ( + VoteWaitTime = time.Second * 10 +) + +func addValidatorCommand(c *cli.Context) error { + return processValidator(c, true) +} + +func removeValidatorCommand(c *cli.Context) error { + return processValidator(c, false) +} + +func processValidator(c *cli.Context, add bool) error { + ctx := c.Context + voterURLs := c.StringSlice(immutable.Voters.Name) + validatorNameStr := c.String(immutable.ValidatorAddress.Name) + if !common.IsHexAddress(validatorNameStr) { + return fmt.Errorf("address %s is not valid", validatorNameStr) + } + + validator := common.HexToAddress(validatorNameStr) + + if len(voterURLs) == 0 { + return errors.New("no voters provided") + } + + voterClients, err := urlsToClients(ctx, voterURLs) + if err != nil { + return err + } + // Validate the operation against validator (read only) + for i := range voterClients { + signers, err := voterClients[i].GetSigners(ctx, nil) + if err != nil { + return fmt.Errorf("failed to get signers: %w", err) + } + // if adding a validator, the validator should not be present + if add && slices.Contains(signers, validator) { + return fmt.Errorf("validator %s is already part of validator set on validator %d", validator.String(), i) + } + // if removing a validator (i.e. !add), the validator should be present + if !add && !slices.Contains(signers, validator) { + return fmt.Errorf("validator %s is not part of validator set on validator %d", validator.String(), i) + } + log.Info("initial validator set", "validator", i, "validators", addressesToCSV(signers)) + } + + log.Info("Sleeping 15 seconds to give chance to interrupt") + time.Sleep(time.Second * 15) + + // Execute votes against each existing validator + for i := range voterClients { + err := voterClients[i].Propose(ctx, validator, add) + if err != nil { + return fmt.Errorf("failed to propose: %w", err) + } + log.Info("proposing for validator", "validator", i, "add", add, "new", validator.String()) + } + + log.Info("Sleeping 10 seconds to let votes propagate and finalize") + time.Sleep(VoteWaitTime) + + failed := false + // Compare the new validator set to what is expected + for i := range voterClients { + signers, err := voterClients[i].GetSigners(ctx, nil) + if err != nil { + return err + } + + // if we are adding a validator, success means that the set contains the new signer + // if we are removing a validator, success means the set does not contain the signer + success := (add && slices.Contains(signers, validator)) || (!add && !slices.Contains(signers, validator)) + if !success { + failed = true + } + log.Info("final validator set", "validator", i, "validators", addressesToCSV(signers), "success", success) + } + if failed { + return fmt.Errorf("voting failed") + } + return nil +} + +// addressesToCSV converts a slice of common.Address to a string. +func addressesToCSV(addresses []common.Address) string { + strAddresses := make([]string, 0, len(addresses)) + for _, addr := range addresses { + strAddresses = append(strAddresses, addr.String()) + } + return strings.Join(strAddresses, ", ") +} + +func urlsToClients(ctx context.Context, voterURLs []string) ([]*gethclient.Client, error) { + voterClients := make([]*gethclient.Client, 0, len(voterURLs)) + for i := range voterURLs { + rpcClient, err := rpc.DialContext(ctx, voterURLs[i]) + if err != nil { + return nil, err + } + voterClients = append(voterClients, gethclient.New(rpcClient)) + } + return voterClients, nil +} diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 2f7d37fdd..123c79b75 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -67,6 +67,9 @@ var ( utils.SmartCardDaemonPathFlag, utils.OverrideCancun, utils.OverrideVerkle, + // CHANGE(immutable): Add fork overrides + utils.OverridePrevrandao, + utils.OverrideShanghai, utils.EnablePersonal, utils.TxPoolLocalsFlag, utils.TxPoolNoLocalsFlag, @@ -79,6 +82,8 @@ var ( utils.TxPoolAccountQueueFlag, utils.TxPoolGlobalQueueFlag, utils.TxPoolLifetimeFlag, + // CHANGE(immutable): added flags to configure tx pool acls + utils.TxPoolBlockListFilePaths, utils.BlobPoolDataDirFlag, utils.BlobPoolDataCapFlag, utils.BlobPoolPriceBumpFlag, @@ -120,6 +125,8 @@ var ( utils.MinerGasLimitFlag, utils.MinerGasPriceFlag, utils.MinerEtherbaseFlag, + // CHANGE(immutable): added flag for etherebase file + utils.MinerEtherbaseFileFlag, utils.MinerExtraDataFlag, utils.MinerRecommitIntervalFlag, utils.MinerNewPayloadTimeout, @@ -146,6 +153,12 @@ var ( configFileFlag, utils.LogDebugFlag, utils.LogBacktraceAtFlag, + // CHANGE(immutable): Add flag for using geths default gossiping. + utils.ImmutableGossipDefaultFlag, + // CHANGE(immutable): Add flag for disabling tx pool gossiping. + utils.ImmutableDisableTxPoolGossipFlag, + // CHANGE(immutable): Add flag for rpc proxy forwarding. + utils.ImmutableRPCProxyFlag, }, utils.NetworkFlags, utils.DatabaseFlags) rpcFlags = []cli.Flag{ @@ -170,6 +183,13 @@ var ( utils.WSAllowedOriginsFlag, utils.WSPathPrefixFlag, utils.IPCDisabledFlag, + utils.IsTxPoolRPCDisabledFlag, + utils.IsEngineRPCDisabledFlag, + utils.IsDebugRPCDisabledFlag, + utils.IsAdminRPCDisabledFlag, + utils.IsCliqueRPCDisabledFlag, + utils.IsMinerRPCDisabledFlag, + utils.IsPersonalRPCDisabledFlag, utils.IPCPathFlag, utils.InsecureUnlockAllowedFlag, utils.RPCGlobalGasCapFlag, @@ -235,6 +255,8 @@ func init() { snapshotCommand, // See verkle.go verkleCommand, + // CHANGE(immutable): Add immutable subcommand + immutableCommand, } if logTestCommand != nil { app.Commands = append(app.Commands, logTestCommand) @@ -287,6 +309,10 @@ func prepare(ctx *cli.Context) { case ctx.IsSet(utils.HoleskyFlag.Name): log.Info("Starting Geth on Holesky testnet...") + // CHANGE(immutable): Add network log + case ctx.IsSet(utils.ImmutableNetworkFlag.Name): + log.Info(fmt.Sprintf("Starting Geth on Immutable zkEVM %s...", ctx.String(utils.ImmutableNetworkFlag.Name))) + 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: @@ -304,9 +330,9 @@ func prepare(ctx *cli.Context) { 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...") + // CHANGE(immutable): RM misleading log + //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) { @@ -340,6 +366,12 @@ func geth(ctx *cli.Context) error { stack, backend := makeFullNode(ctx) defer stack.Close() + // CHANGE(immutable): Disallow running misconfigured Immutable zkEVM settings + if backend.ChainConfig().IsImmutableZKEVM() && + !backend.ChainConfig().IsValidImmutableZKEVM() { + return fmt.Errorf("Immutable zkEVM network ID sepcified but the configuration is invalid: %+v", *backend.ChainConfig()) + } + startNode(ctx, stack, backend, false) stack.Wait() return nil diff --git a/cmd/geth/testdata/acl_list.txt b/cmd/geth/testdata/acl_list.txt new file mode 100644 index 000000000..9f9ee18d3 --- /dev/null +++ b/cmd/geth/testdata/acl_list.txt @@ -0,0 +1 @@ +0xed557863FFD4C87537BA8264098B22483c6145f2 diff --git a/cmd/geth/testdata/blockedkey.prv b/cmd/geth/testdata/blockedkey.prv new file mode 100644 index 000000000..43462b087 --- /dev/null +++ b/cmd/geth/testdata/blockedkey.prv @@ -0,0 +1 @@ +c9fa59f5d81e6725e568e61c6844d7f341e1612168ea11931e6ff986cf00c278 diff --git a/cmd/geth/testdata/blocklist.txt b/cmd/geth/testdata/blocklist.txt new file mode 100644 index 000000000..5e845e5e9 --- /dev/null +++ b/cmd/geth/testdata/blocklist.txt @@ -0,0 +1 @@ +0x72a5843cc08275C8171E582972Aa4fDa8C397B2A,0x7F19720A857F834887FC9A7bC0a0fBe7Fc7f8102,0x1da5821544e25c636c1417ba96ade4cf6d2f9b5a,0x7Db418b5D567A4e0E8c59Ad71BE1FcE48f3E6107 diff --git a/cmd/geth/testdata/dev.toml b/cmd/geth/testdata/dev.toml new file mode 100644 index 000000000..8fa720cbb --- /dev/null +++ b/cmd/geth/testdata/dev.toml @@ -0,0 +1,79 @@ +[Eth] +NetworkId = 15003 +SyncMode = "full" +EthDiscoveryURLs = [] +SnapDiscoveryURLs = [] +NoPruning = true +NoPrefetch = false +TxLookupLimit = 2350000 +TransactionHistory = 2350000 +StateScheme = "hash" +DatabaseCache = 512 +DatabaseFreezer = "" +TrieCleanCache = 256 +TrieDirtyCache = 256 +TrieTimeout = 3600000000000 +SnapshotCache = 0 +Preimages = false +FilterLogCacheSize = 32 +EnablePreimageRecording = false +RPCGasCap = 50000000 +RPCEVMTimeout = 5000000000 +RPCTxFeeCap = 0e+00 + +[Eth.Miner] +GasFloor = 0 +GasCeil = 30000000 +GasPrice = 10000000000 +Recommit = 999999999000000000 +NewPayloadTimeout = 2000000000 + +[Eth.TxPool] +Locals = [] +NoLocals = true +Journal = "transactions.rlp" +Rejournal = 3600000000000 +PriceLimit = 10000000000 +PriceBump = 10 +AccountSlots = 16 +GlobalSlots = 5120 +AccountQueue = 64 +GlobalQueue = 1024 +Lifetime = 3600000000000 + +[Eth.BlobPool] +Datadir = "blobpool" +Datacap = 0 +PriceBump = 100 + +[Eth.GPO] +Blocks = 120 +Percentile = 60 +MaxHeaderHistory = 1024 +MaxBlockHistory = 1024 +MaxPrice = 500000000000 +IgnorePrice = 2 + +[Node] +DataDir = "" +IPCPath = "geth.ipc" +HTTPHost = "" +HTTPModules = [] +WSHost = "" +WSModules = [] + +[Node.P2P] +MaxPeers = 100 +NoDiscovery = false +BootstrapNodes = ["enode://26ed2b67b49284e3aca7a04fae065681bf28a7a1c7958e5039472e213cc1ae0b743d987c8a9832e17b42f423e7800ce74d0ad23f477c5fa1a1adb51808d67aa4@127.0.0.1:30300"] +StaticNodes = [] +TrustedNodes = [] +ListenAddr = "" +DiscAddr = "" +EnableMsgEvents = false + +[Node.HTTPTimeouts] +ReadTimeout = 0 +ReadHeaderTimeout = 0 +WriteTimeout = 0 +IdleTimeout = 0 diff --git a/cmd/geth/testdata/dev_bad_recommit.toml b/cmd/geth/testdata/dev_bad_recommit.toml new file mode 100644 index 000000000..08a3b8335 --- /dev/null +++ b/cmd/geth/testdata/dev_bad_recommit.toml @@ -0,0 +1,79 @@ +[Eth] +NetworkId = 15003 +SyncMode = "full" +EthDiscoveryURLs = [] +SnapDiscoveryURLs = [] +NoPruning = true +NoPrefetch = false +TxLookupLimit = 2350000 +TransactionHistory = 2350000 +StateScheme = "hash" +DatabaseCache = 512 +DatabaseFreezer = "" +TrieCleanCache = 256 +TrieDirtyCache = 256 +TrieTimeout = 3600000000000 +SnapshotCache = 0 +Preimages = false +FilterLogCacheSize = 32 +EnablePreimageRecording = false +RPCGasCap = 50000000 +RPCEVMTimeout = 5000000000 +RPCTxFeeCap = 0e+00 + +[Eth.Miner] +GasFloor = 0 +GasCeil = 30000000 +GasPrice = 10000000000 +Recommit = 123 +NewPayloadTimeout = 2000000000 + +[Eth.TxPool] +Locals = [] +NoLocals = true +Journal = "transactions.rlp" +Rejournal = 3600000000000 +PriceLimit = 10000000000 +PriceBump = 10 +AccountSlots = 16 +GlobalSlots = 5120 +AccountQueue = 64 +GlobalQueue = 1024 +Lifetime = 3600000000000 + +[Eth.BlobPool] +Datadir = "blobpool" +Datacap = 0 +PriceBump = 100 + +[Eth.GPO] +Blocks = 120 +Percentile = 60 +MaxHeaderHistory = 1024 +MaxBlockHistory = 1024 +MaxPrice = 500000000000 +IgnorePrice = 2 + +[Node] +DataDir = "" +IPCPath = "geth.ipc" +HTTPHost = "" +HTTPModules = [] +WSHost = "" +WSModules = [] + +[Node.P2P] +MaxPeers = 100 +NoDiscovery = false +BootstrapNodes = ["enode://26ed2b67b49284e3aca7a04fae065681bf28a7a1c7958e5039472e213cc1ae0b743d987c8a9832e17b42f423e7800ce74d0ad23f477c5fa1a1adb51808d67aa4@127.0.0.1:30300"] +StaticNodes = [] +TrustedNodes = [] +ListenAddr = "" +DiscAddr = "" +EnableMsgEvents = false + +[Node.HTTPTimeouts] +ReadTimeout = 0 +ReadHeaderTimeout = 0 +WriteTimeout = 0 +IdleTimeout = 0 diff --git a/cmd/geth/testdata/key.addr b/cmd/geth/testdata/key.addr new file mode 100644 index 000000000..1368edfe6 --- /dev/null +++ b/cmd/geth/testdata/key.addr @@ -0,0 +1 @@ +0x02F0d131F1f97aef08aEc6E3291B957d9Efe7105 \ No newline at end of file diff --git a/cmd/immutable/README.md b/cmd/immutable/README.md new file mode 100644 index 000000000..1b161b59d --- /dev/null +++ b/cmd/immutable/README.md @@ -0,0 +1,28 @@ +## Immutable + +This is a fork of go-ethereum that is specific to Immutables deployment and infrastructure. +Several commands have been added to the geth-cli to support Immutable's operations. All subcommands +will be accessible under the `immutable` parent command. + +## Run a local network + +Build the binary: +```sh +make geth +``` + +To bootstrap a minimal chain locally, run: +```sh +./build/bin/geth immutable bootstrap local +``` + +this will set up a network with 1 validator (http://localhost:8545) and 1 RPC (http://localhost:8546) node. + +Use --help to learn about the other ways of configuring your local network: +```sh +./build/bin/geth immutable bootstrap --help +``` + +## Test against a running network + +See `.github/scripts/bootstrap_test.sh` on how to run go tests against a running local network. diff --git a/cmd/immutable/categories.go b/cmd/immutable/categories.go new file mode 100644 index 000000000..8cb39c920 --- /dev/null +++ b/cmd/immutable/categories.go @@ -0,0 +1,19 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package immutable + +const ImmutableCategory = "IMMUTABLE FORK" diff --git a/cmd/immutable/flags.go b/cmd/immutable/flags.go new file mode 100644 index 000000000..aa7dc48fa --- /dev/null +++ b/cmd/immutable/flags.go @@ -0,0 +1,141 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package immutable + +import ( + "github.com/urfave/cli/v2" +) + +var ( + Env = &cli.StringFlag{ + Name: "env", + Usage: "Network context (dev, sandbox, prod)", + Category: ImmutableCategory, + Required: false, + } + GasLimit = &cli.Uint64Flag{ + Name: "gaslimit", + Usage: "Gas limit of the network", + Value: 100000000, + Category: ImmutableCategory, + } + BootCount = &cli.IntFlag{ + Name: "boots", + Usage: "Number of boot nodes to bootstrap", + Category: ImmutableCategory, + Value: 1, + } + ValidatorCount = &cli.IntFlag{ + Name: "validators", + Usage: "Number of validators to bootstrap", + Category: ImmutableCategory, + Value: 1, + } + RPCCount = &cli.IntFlag{ + Name: "rpcs", + Usage: "Number of RPC nodes to bootstrap", + Category: ImmutableCategory, + Value: 1, + } + PartnerCount = &cli.IntFlag{ + Name: "partners", + Usage: "Number of partner RPC nodes to bootstrap", + Category: ImmutableCategory, + Value: 1, + } + RPCScaleCount = &cli.IntFlag{ + Name: "rpcs", + Usage: "Number of RPC nodes to scale out by", + Category: ImmutableCategory, + Value: 0, + } + PartnerScaleCount = &cli.IntFlag{ + Name: "partners", + Usage: "Number of partner nodes to scale out by", + Category: ImmutableCategory, + Value: 0, + } + Region = &cli.StringFlag{ + Name: "region", + Usage: "AWS region to use for remote bootstrap", + Category: ImmutableCategory, + Value: "us-east-2", + } + BlockListFilepath = &cli.StringFlag{ + Name: "blocklistfilepath", + Usage: "File path to blocklist file", + Category: ImmutableCategory, + Required: false, + } + Role = &cli.StringFlag{ + Name: "role", + Usage: "Role of the node (boot, validator, rpc, partner, partner-public)", + Category: ImmutableCategory, + Required: true, + } + DataDirpath = &cli.StringFlag{ + Name: "datadir", + Usage: "Data directory for node", + Category: ImmutableCategory, + Required: false, + } + ConfigFilepath = &cli.StringFlag{ + Name: "config", + Usage: "Config filepath of the node", + Category: ImmutableCategory, + Required: true, + } + ManifestFilepath = &cli.StringFlag{ + Name: "manifest", + Usage: "Manifest filepath of the node", + Category: ImmutableCategory, + Required: true, + } + ChainDirpath = &cli.StringFlag{ + Name: "chain", + Usage: "Chain directory for node", + Category: ImmutableCategory, + Required: true, + } + Voters = &cli.StringSliceFlag{ + Name: "voters", + Category: ImmutableCategory, + Usage: "List of URLs for voting validators, e.g. http://localhost:8030/", + Required: true, + } + ValidatorAddress = &cli.StringFlag{ + Name: "validator", + Usage: "a validator address to vote in or out", + Category: ImmutableCategory, + Required: true, + } + PublicKey = &cli.StringFlag{ + Name: "pubkey", + Usage: "public key", + Category: ImmutableCategory, + Required: true, + } +) + +func OverrideFlag(flag *cli.StringFlag, required bool) *cli.StringFlag { + return &cli.StringFlag{ + Name: flag.Name, + Usage: flag.Usage, + Category: flag.Category, + Required: required, + } +} diff --git a/cmd/immutable/remote/aws/s3.go b/cmd/immutable/remote/aws/s3.go new file mode 100644 index 000000000..2e7dd6f5b --- /dev/null +++ b/cmd/immutable/remote/aws/s3.go @@ -0,0 +1,52 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package aws + +import ( + "bytes" + "fmt" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/s3" + "github.com/ethereum/go-ethereum/log" +) + +type ObjectStore struct { + os *s3.S3 +} + +func NewObjectStore(region string) (*ObjectStore, error) { + os := s3.New(session.Must(session.NewSession()), + aws.NewConfig().WithRegion(region)) + return &ObjectStore{ + os: os, + }, nil +} + +func (os ObjectStore) PutObject(bucket, key string, content []byte) error { + log.Info("Pushing S3 object", "name", fmt.Sprintf("%s/%s", bucket, key)) + if _, err := os.os.PutObject(&s3.PutObjectInput{ + Bucket: &bucket, + Key: &key, + Body: bytes.NewReader(content), + ACL: aws.String("private"), + }); err != nil { + return fmt.Errorf("failed to put object: %w", err) + } + return nil +} diff --git a/cmd/immutable/remote/aws/secretsmanager.go b/cmd/immutable/remote/aws/secretsmanager.go new file mode 100644 index 000000000..e08ec89e0 --- /dev/null +++ b/cmd/immutable/remote/aws/secretsmanager.go @@ -0,0 +1,133 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package aws + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/secretsmanager" + "github.com/ethereum/go-ethereum/log" +) + +type SecretsManager struct { + sm *secretsmanager.SecretsManager +} + +func NewSecretsManager(region string) (*SecretsManager, error) { + sess, err := session.NewSession() + if err != nil { + return nil, fmt.Errorf("secret: failed to create session: %w", err) + } + man := &SecretsManager{ + sm: secretsmanager.New(sess, aws.NewConfig().WithRegion(region)), + } + return man, nil +} + +// GetSecret returns the secret value for the given secret id +func (sm SecretsManager) GetSecret(ctx context.Context, id string) (string, error) { + out, err := sm.sm.GetSecretValueWithContext( + ctx, + &secretsmanager.GetSecretValueInput{ + SecretId: aws.String(id), + }, + ) + if err != nil { + return "", fmt.Errorf("failed to get secret (%s): %w", id, err) + } + return *out.SecretString, nil +} + +// GeneratePassword generates a random password +func (sm SecretsManager) GeneratePassword() (string, error) { + // Create new + pw, err := sm.sm.GetRandomPassword(&secretsmanager.GetRandomPasswordInput{}) + if err != nil { + return "", fmt.Errorf("failed to generate password: %w", err) + } + return *pw.RandomPassword, nil +} + +// PushSecretFile pushes a secret to the secret store +func (sm SecretsManager) PushSecretFile(secretName string, content []byte) error { + return sm.push(&secretsmanager.CreateSecretInput{ + Name: aws.String(secretName), + SecretBinary: content, + ForceOverwriteReplicaSecret: aws.Bool(true), + }) +} + +// PutSecretString updates a secret in the secret store +func (sm SecretsManager) PutSecretString(key string, content string) error { + if _, err := sm.sm.PutSecretValue(&secretsmanager.PutSecretValueInput{ + SecretId: aws.String(key), + SecretString: aws.String(content), + }); err != nil { + return fmt.Errorf("failed to update secret (%s): %w", key, err) + } + return nil +} + +// PushSecretString pushes a secret to the secret store +func (sm SecretsManager) PushSecretString(secretName string, content string) error { + return sm.push(&secretsmanager.CreateSecretInput{ + Name: aws.String(secretName), + SecretString: aws.String(content), + ForceOverwriteReplicaSecret: aws.Bool(true), + }) +} + +func (sm SecretsManager) push(input *secretsmanager.CreateSecretInput) error { + log.Info("Creating secret", "secretName", *input.Name) + if _, err := sm.sm.CreateSecret(input); err != nil { + return fmt.Errorf("failed to create secret: %w", err) + } + return nil +} + +func (sm SecretsManager) delete(secretName string) error { //nolint: unused + if _, err := sm.sm.DeleteSecret(&secretsmanager.DeleteSecretInput{ + SecretId: aws.String(secretName), + ForceDeleteWithoutRecovery: aws.Bool(true), + }); err != nil && !IsNotFound(err) { + return fmt.Errorf("failed to delete secret: %w", err) + } + log.Info("Waiting for deletion of secret", "secretName", secretName) + for range time.NewTicker(time.Second * 5).C { + if _, err := sm.sm.GetSecretValue(&secretsmanager.GetSecretValueInput{ + SecretId: aws.String(secretName), + }); err != nil && IsNotFound(err) { + return nil + } + } + return nil +} + +// IsNotFound returns true if the error is a not found error specific to AWS SDK. +func IsNotFound(err error) bool { + var awsErr awserr.Error + if !errors.As(err, &awsErr) { + return false + } + return awsErr.Code() == secretsmanager.ErrCodeResourceNotFoundException +} diff --git a/cmd/immutable/remote/aws/secretsmanager_test.go b/cmd/immutable/remote/aws/secretsmanager_test.go new file mode 100644 index 000000000..39d41d496 --- /dev/null +++ b/cmd/immutable/remote/aws/secretsmanager_test.go @@ -0,0 +1,50 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package aws + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/secretsmanager" +) + +func TestImmutableSecretsManager_SecretIsNotFound(t *testing.T) { + tests := []struct { + err error + isNotFound bool + }{ + { + awserr.New(secretsmanager.ErrCodeResourceNotFoundException, "Secret not found", nil), + true, + }, + { + fmt.Errorf("err: %w", awserr.New(secretsmanager.ErrCodeResourceNotFoundException, "Secret not found", nil)), + true, + }, + { + fmt.Errorf("bad"), + false, + }, + } + for _, test := range tests { + if IsNotFound(test.err) != test.isNotFound { + t.Fatalf("expected %v, got %v: %s", test.isNotFound, IsNotFound(test.err), test.err.Error()) + } + } +} diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index b813e5297..48cfaa883 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -36,6 +36,7 @@ import ( "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/keystore" + "github.com/ethereum/go-ethereum/cmd/geth/immutable/settings" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/fdlimit" "github.com/ethereum/go-ethereum/core" @@ -155,6 +156,34 @@ var ( Usage: "Holesky network: pre-configured proof-of-stake test network", Category: flags.EthCategory, } + // CHANGE(immutable): Add network flag for zkEVM. + ImmutableNetworkFlag = &cli.StringFlag{ + Name: "zkevm", + Usage: "Immutable zkEVM network (mainnet, testnet, devnet)", + Category: flags.EthCategory, + EnvVars: []string{"GETH_FLAG_IMMUTABLE_NETWORK"}, + } + // CHANGE(immutable): Add flag for toggling default peer gossiping. + ImmutableGossipDefaultFlag = &cli.BoolFlag{ + Name: "gossipdefault", + Usage: "If set, use the default geth gossiping behaviour, only gossiping to a subset of peers", + Category: flags.EthCategory, + EnvVars: []string{"GETH_FLAG_IMMUTABLE_GOSSIPDEFAULT"}, + } + // CHANGE(immutable): Add flag to disable tx gossiping. + ImmutableDisableTxPoolGossipFlag = &cli.BoolFlag{ + Name: "disabletxpoolgossip", + Usage: "If set, disable tx gossiping (egress)", + Category: flags.EthCategory, + EnvVars: []string{"GETH_FLAG_IMMUTABLE_DISABLETXPOOLGOSSIP"}, + } + // CHANGE(immutable): Add flag to forward to the Immutable RPC. + ImmutableRPCProxyFlag = &cli.BoolFlag{ + Name: "rpcproxy", + Usage: "If set, forward transactions to the Immutable RPC", + Category: flags.EthCategory, + EnvVars: []string{"GETH_FLAG_IMMUTABLE_RPCPROXY"}, + } // Dev mode DeveloperFlag = &cli.BoolFlag{ Name: "dev", @@ -252,6 +281,17 @@ var ( Usage: "Manually specify the Verkle fork timestamp, overriding the bundled setting", Category: flags.EthCategory, } + // CHANGE(immutable): Add fork override flags + OverridePrevrandao = &cli.Uint64Flag{ + Name: "override.prevrandao", + Usage: "Manually specify the Prevrandao fork timestamp. Intended for testing only, use --zkevm.* instead", + Category: flags.EthCategory, + } + OverrideShanghai = &cli.Uint64Flag{ + Name: "override.shanghai", + Usage: "Manually specify the Shanghai fork timestamp. Intended for testing only, use --zkevm.* instead", + Category: flags.EthCategory, + } SyncModeFlag = &flags.TextMarshalerFlag{ Name: "syncmode", Usage: `Blockchain sync mode ("snap" or "full")`, @@ -346,6 +386,13 @@ var ( Value: ethconfig.Defaults.TxPool.Lifetime, Category: flags.TxPoolCategory, } + // CHANGE(immutable): allow the setting of ACLs block/allow lists for txpool + TxPoolBlockListFilePaths = &cli.StringSliceFlag{ + Name: "txpool.blocklistfilepaths", + Usage: "TxPoolBlockListFilePaths allows you to specify one or more file paths containing blocklists for the transaction pool", + Category: flags.TxPoolCategory, + Value: &cli.StringSlice{}, + } // Blob transaction pool settings BlobPoolDataDirFlag = &cli.StringFlag{ Name: "blobpool.datadir", @@ -446,6 +493,14 @@ var ( Name: "miner.etherbase", Usage: "0x prefixed public address for block mining rewards", Category: flags.MinerCategory, + // CHANGE(immutable): Add env var + EnvVars: []string{"GETH_FLAG_MINER_ETHERBASE"}, + } + // CHANGE(immutable): Add flag for etherbase address file + MinerEtherbaseFileFlag = &cli.StringFlag{ + Name: "miner.etherbase-file", + Usage: "filepath to file containing 0x prefixed public address for block mining rewards", + Category: flags.MinerCategory, } MinerExtraDataFlag = &cli.StringFlag{ Name: "miner.extradata", @@ -471,12 +526,30 @@ var ( Usage: "Comma separated list of accounts to unlock", Value: "", Category: flags.AccountCategory, + // CHANGE(immutable): Add env var + EnvVars: []string{"GETH_FLAG_UNLOCK_ACCOUNT"}, } PasswordFileFlag = &cli.PathFlag{ Name: "password", Usage: "Password file to use for non-interactive password input", TakesFile: true, Category: flags.AccountCategory, + // CHANGE(immutable): Add flag for local keystore pw + EnvVars: []string{"GETH_FLAG_PASSWORD_FILEPATH"}, + } + // CHANGE(immutable): Add AWS region flag + ImmutableAWSRegion = &cli.BoolFlag{ + Name: "aws.region", + Usage: "name of AWS region used for secret key backends", + Category: flags.AccountCategory, + EnvVars: []string{"GETH_FLAG_IMMUTABLE_AWS_REGION"}, + } + // CHANGE(immutable): Add pod namespace flag + ImmutablePodNamespace = &cli.BoolFlag{ + Name: "k8s.namespace", + Usage: "kubernetes namespace pod is running in. Used for secret key backends", + Category: flags.AccountCategory, + EnvVars: []string{"POD_NAMESPACE"}, } ExternalSignerFlag = &cli.StringFlag{ Name: "signer", @@ -516,6 +589,49 @@ var ( Value: ethconfig.Defaults.RPCTxFeeCap, Category: flags.APICategory, } + // CHANGE(immutable): add a flag option to disable the debug rpc endpoints + IsDebugRPCDisabledFlag = &cli.BoolFlag{ + Name: "rpc.debugdisable", + Usage: "Disable the Debug RPC endpoints", + Value: node.DefaultConfig.DisableDebug, + Category: flags.APICategory, + } + IsAdminRPCDisabledFlag = &cli.BoolFlag{ + Name: "rpc.admindisable", + Usage: "Disable the Admin RPC endpoints", + Value: node.DefaultConfig.DisableAdmin, + Category: flags.APICategory, + } + IsTxPoolRPCDisabledFlag = &cli.BoolFlag{ + Name: "rpc.txpooldisable", + Usage: "Disable the txpool RPC endpoints", + Value: node.DefaultConfig.DisableTxPool, + Category: flags.APICategory, + } + IsEngineRPCDisabledFlag = &cli.BoolFlag{ + Name: "rpc.enginedisable", + Usage: "Disable the engine RPC endpoints", + Value: node.DefaultConfig.DisableEngine, + Category: flags.APICategory, + } + IsCliqueRPCDisabledFlag = &cli.BoolFlag{ + Name: "rpc.cliquedisable", + Usage: "Disable the clique RPC endpoints", + Value: node.DefaultConfig.DisableClique, + Category: flags.APICategory, + } + IsMinerRPCDisabledFlag = &cli.BoolFlag{ + Name: "rpc.minerdisable", + Usage: "Disable the miner RPC endpoints", + Value: node.DefaultConfig.DisableMiner, + Category: flags.APICategory, + } + IsPersonalRPCDisabledFlag = &cli.BoolFlag{ + Name: "rpc.personaldisable", + Usage: "Disable the personal RPC endpoints", + Value: node.DefaultConfig.DisablePersonal, + Category: flags.APICategory, + } // Authenticated RPC HTTP settings AuthListenFlag = &cli.StringFlag{ Name: "authrpc.addr", @@ -761,6 +877,8 @@ var ( Name: "netrestrict", Usage: "Restricts network communication to the given IP networks (CIDR masks)", Category: flags.NetworkingCategory, + // CHANGE(immutable): Add env var + EnvVars: []string{"GETH_FLAG_NET_RESTRICT"}, } DNSDiscoveryFlag = &cli.StringFlag{ Name: "discovery.dns", @@ -911,14 +1029,15 @@ Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server. ) var ( - // TestnetFlags is the flag group of all built-in supported testnets. - TestnetFlags = []cli.Flag{ + // CHANGE(immutable): append Immutable network flags + // NetworkFlags is the flag group of all built-in supported networks. + NetworkFlags = []cli.Flag{ + MainnetFlag, + ImmutableNetworkFlag, GoerliFlag, SepoliaFlag, HoleskyFlag, } - // 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{ @@ -1268,10 +1387,24 @@ func MakeAddress(ks *keystore.KeyStore, account string) (accounts.Account, error // setEtherbase retrieves the etherbase from the directly specified command line flags. func setEtherbase(ctx *cli.Context, cfg *ethconfig.Config) { - if !ctx.IsSet(MinerEtherbaseFlag.Name) { + // CHANGE(immutable): Handle etherbase file env var + // Get address from one flag or another + addr := "" + if ctx.IsSet(MinerEtherbaseFileFlag.Name) { + // Read the address from the specified file + addrBytes, err := os.ReadFile(ctx.String(MinerEtherbaseFileFlag.Name)) + if err != nil { + Fatalf("Failed to read etherbase addr file: %w", err) + return + } + addr = string(addrBytes) + } else if ctx.IsSet(MinerEtherbaseFlag.Name) { + addr = ctx.String(MinerEtherbaseFlag.Name) + } else { + // None specified return } - addr := ctx.String(MinerEtherbaseFlag.Name) + // Handle the address if strings.HasPrefix(addr, "0x") || strings.HasPrefix(addr, "0X") { addr = addr[2:] } @@ -1393,6 +1526,28 @@ func SetNodeConfig(ctx *cli.Context, cfg *node.Config) { log.Info(fmt.Sprintf("Using %s as db engine", dbEngine)) cfg.DBEngine = dbEngine } + // CHANGE(immutable): add a flag to disable the txpool/engine/debug/admin rpc endpoints + if ctx.IsSet(IsDebugRPCDisabledFlag.Name) { + cfg.DisableDebug = ctx.Bool(IsDebugRPCDisabledFlag.Name) + } + if ctx.IsSet(IsAdminRPCDisabledFlag.Name) { + cfg.DisableAdmin = ctx.Bool(IsAdminRPCDisabledFlag.Name) + } + if ctx.IsSet(IsTxPoolRPCDisabledFlag.Name) { + cfg.DisableTxPool = ctx.Bool(IsTxPoolRPCDisabledFlag.Name) + } + if ctx.IsSet(IsEngineRPCDisabledFlag.Name) { + cfg.DisableEngine = ctx.Bool(IsEngineRPCDisabledFlag.Name) + } + if ctx.IsSet(IsCliqueRPCDisabledFlag.Name) { + cfg.DisableClique = ctx.Bool(IsCliqueRPCDisabledFlag.Name) + } + if ctx.IsSet(IsMinerRPCDisabledFlag.Name) { + cfg.DisableMiner = ctx.Bool(IsMinerRPCDisabledFlag.Name) + } + if ctx.IsSet(IsPersonalRPCDisabledFlag.Name) { + cfg.DisablePersonal = ctx.Bool(IsPersonalRPCDisabledFlag.Name) + } // 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") @@ -1493,6 +1648,10 @@ func setTxPool(ctx *cli.Context, cfg *legacypool.Config) { if ctx.IsSet(TxPoolLifetimeFlag.Name) { cfg.Lifetime = ctx.Duration(TxPoolLifetimeFlag.Name) } + // CHANGE(immutable): added flags to configure tx pool acls + if ctx.IsSet(TxPoolBlockListFilePaths.Name) { + cfg.BlockListFilePaths = ctx.StringSlice(TxPoolBlockListFilePaths.Name) + } } func setMiner(ctx *cli.Context, cfg *miner.Config) { @@ -1585,7 +1744,7 @@ func CheckExclusive(ctx *cli.Context, args ...interface{}) { // SetEthConfig applies eth-related command line flags to the config. func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { // Avoid conflicting network flags - CheckExclusive(ctx, MainnetFlag, DeveloperFlag, GoerliFlag, SepoliaFlag, HoleskyFlag) + CheckExclusive(ctx, MainnetFlag, DeveloperFlag, GoerliFlag, SepoliaFlag, HoleskyFlag, ImmutableNetworkFlag) CheckExclusive(ctx, DeveloperFlag, ExternalSignerFlag) // Can't use both ephemeral unlocked and external signer // Set configurations from CLI flags @@ -1728,8 +1887,32 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { cfg.EthDiscoveryURLs = SplitAndTrim(urls) } } + // CHANGE(immutable): Handle gossiping configuration. + cfg.GossipDefault = ctx.Bool(ImmutableGossipDefaultFlag.Name) + // CHANGE(immutable): Handle disable txpool gossip configuration. + cfg.DisableTxPoolGossip = ctx.Bool(ImmutableDisableTxPoolGossipFlag.Name) // Override any default configs for hard coded networks. + // CHANGE(immutable): Handle proxy RPC forwarding configuration. Ensure this is only on RPC nodes + // and is set correctly depending on the Immutable network flag. + CheckExclusive(ctx, ImmutableRPCProxyFlag, MiningEnabledFlag) + if ctx.IsSet(ImmutableRPCProxyFlag.Name) && ctx.IsSet(ImmutableNetworkFlag.Name) { + network, err := settings.NewNetwork(ctx.String(ImmutableNetworkFlag.Name)) + if err != nil { + Fatalf(err.Error()) + } + cfg.RPCProxyURL, err = network.RPC() + if err != nil { + Fatalf(err.Error()) + } + log.Info("Setting RPC Proxy on node", "url", cfg.RPCProxyURL) + } switch { + // CHANGE(immutable): handle Immutable networks + case ctx.IsSet(ImmutableNetworkFlag.Name): + cfg.Genesis = core.ImmutableGenesisBlock(ctx.String(ImmutableNetworkFlag.Name)) + if !ctx.IsSet(NetworkIdFlag.Name) { + cfg.NetworkId = cfg.Genesis.Config.ChainID.Uint64() + } case ctx.Bool(MainnetFlag.Name): if !ctx.IsSet(NetworkIdFlag.Name) { cfg.NetworkId = 1 @@ -2055,6 +2238,9 @@ func DialRPCWithHeaders(endpoint string, headers []string) (*rpc.Client, error) func MakeGenesis(ctx *cli.Context) *core.Genesis { var genesis *core.Genesis switch { + // CHANGE(immutable): handle Immutable networks + case ctx.IsSet(ImmutableNetworkFlag.Name): + genesis = core.ImmutableGenesisBlock(ctx.String(ImmutableNetworkFlag.Name)) case ctx.Bool(MainnetFlag.Name): genesis = core.DefaultGenesisBlock() case ctx.Bool(HoleskyFlag.Name): diff --git a/cmd/utils/immutable_flags.go b/cmd/utils/immutable_flags.go new file mode 100644 index 000000000..b2538cf3e --- /dev/null +++ b/cmd/utils/immutable_flags.go @@ -0,0 +1,50 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package utils + +import ( + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/triedb" + "github.com/ethereum/go-ethereum/triedb/hashdb" + "github.com/ethereum/go-ethereum/triedb/pathdb" +) + +// MakeTrieDatabaseNoContext is MakeTrieDatabase without being coupled to cli context to allow for further reuse. +func MakeTrieDatabaseNoContext(stateScheme string, disk ethdb.Database, preimage bool, readOnly bool, isVerkle bool) *triedb.Database { + config := &triedb.Config{ + Preimages: preimage, + IsVerkle: isVerkle, + } + scheme, err := rawdb.ParseStateScheme(stateScheme, 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/common/types.go b/common/types.go index aadca87f8..ceec2cc55 100644 --- a/common/types.go +++ b/common/types.go @@ -48,6 +48,9 @@ var ( // MaxAddress represents the maximum possible address value. MaxAddress = HexToAddress("0xffffffffffffffffffffffffffffffffffffffff") + // MinHash represents the minimum possible hash value. + MinHash = HexToHash("0x0") // CHANGE(immutable): Add MinHash var. + // MaxHash represents the maximum possible hash value. MaxHash = HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") ) diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index c693189ea..8ae1af318 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -34,6 +34,7 @@ import ( "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/misc" "github.com/ethereum/go-ethereum/consensus/misc/eip1559" + "github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" @@ -68,6 +69,8 @@ var ( diffInTurn = big.NewInt(2) // Block difficulty for in-turn signatures diffNoTurn = big.NewInt(1) // Block difficulty for out-of-turn signatures + + ErrWithdrawalsDetected = errors.New("withdrawals have been passed to consensus layer") ) // Various error messages to mark blocks invalid. These should be private to @@ -299,24 +302,20 @@ func (c *Clique) verifyHeader(chain consensus.ChainHeaderReader, header *types.H if header.GasLimit > params.MaxGasLimit { return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, params.MaxGasLimit) } - if chain.Config().IsShanghai(header.Number, header.Time) { - return errors.New("clique does not support shanghai fork") - } + // CHANGE(immutable): Allow Shanghai // 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) + // Before Cancun, withdrawalsHash must be nil. After Cancun, it must be EmptyWithdrawalsHash + isCancun := chain.Config().IsCancun(header.Number, header.Time) + if !isCancun && header.WithdrawalsHash != nil { + return fmt.Errorf("invalid withdrawalsHash: have %x, expected nil before Cancun", header.WithdrawalsHash) + } + if isCancun { + if header.WithdrawalsHash == nil { + return fmt.Errorf("invalid withdrawalsHash: nil, expected EmptyWithdrawalsHash after Cancun") + } + if *header.WithdrawalsHash != types.EmptyWithdrawalsHash { + return fmt.Errorf("invalid withdrawalsHash: have %x, expected EmptyWithdrawalsHash after Cancun", header.WithdrawalsHash) + } } // All basic checks passed, verify cascading fields return c.verifyCascadingFields(chain, header, parents) @@ -361,6 +360,32 @@ func (c *Clique) verifyCascadingFields(chain consensus.ChainHeaderReader, header // Verify the header's EIP-1559 attributes. return err } + + // CHANGE(immutable): Allow Cancun + isCancun := chain.Config().IsCancun(header.Number, header.Time) + if isCancun { + // Perform default verification to ensure we are consistent + if err := eip4844.VerifyEIP4844Header(parent, header); err != nil { + return err + } + // Immutable zkEVM should have 0 values for all headers. + // VerifyEIP4844Header has checked that Blob fields are non nil, + // hence at this point we know they are not nil + if err := enforceCancunHeaderInvariants(header); err != nil { + return err + } + } else { + // If the chain is not Cancun, we expect fields to be nil + 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) + } + } + // Retrieve the snapshot needed to verify this header and cache it snap, err := c.snapshot(chain, number-1, header.ParentHash, parents) if err != nil { @@ -587,9 +612,24 @@ func (c *Clique) Finalize(chain consensus.ChainHeaderReader, header *types.Heade // 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, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt, withdrawals []*types.Withdrawal) (*types.Block, error) { - if len(withdrawals) > 0 { - return nil, errors.New("clique does not support withdrawals") + // CHANGE(immutable): Withdrawals should not be passed in here. + if withdrawals != nil { + log.Warn("Withdrawals passed to consensus layer", "block number", header.Number.String()) } + // CHANGE(immutable): Handle various forks. + // The order of the forks in the switch statement matters - based on fork order. + switch { + case chain.Config().IsCancun(header.Number, header.Time): + // Withdrawals should exist but be empty + withdrawals = make([]*types.Withdrawal, 0) + case chain.Config().IsImmutableZKEVMPrevrandao(header.Time): + withdrawals = nil + case chain.Config().IsShanghai(header.Number, header.Time): + withdrawals = nil + default: + withdrawals = nil + } + // Finalize block c.Finalize(chain, header, state, txs, uncles, nil) @@ -597,7 +637,8 @@ func (c *Clique) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header * header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) // Assemble and return the final block for sealing. - return types.NewBlock(header, txs, nil, receipts, trie.NewStackTrie(nil)), nil + // CHANGE(immutable): Use NewBlockWithWithdrawals so that the WithdrawalsHash is appropriately set + return types.NewBlockWithWithdrawals(header, txs, nil, receipts, withdrawals, trie.NewStackTrie(nil)), nil } // Authorize injects a private key into the consensus engine to mint new blocks @@ -655,6 +696,15 @@ func (c *Clique) Seal(chain consensus.ChainHeaderReader, block *types.Block, res log.Trace("Out-of-turn signing requested", "wiggle", common.PrettyDuration(wiggle)) } + // CHANGE(immutable): Allow Cancun. We do this check in Seal as an extra sanity check so that blocks are halted from + // being propagated to peers to insulate them potential issues. + isCancun := chain.Config().IsCancun(header.Number, header.Time) + if isCancun { + if err := enforceCancunHeaderInvariants(header); err != nil { + log.Error("Failed to Seal block due to failed header invariants", "err", err) + return err + } + } // Sign all the things! sighash, err := signFn(accounts.Account{Address: signer}, accounts.MimetypeClique, CliqueRLP(header)) if err != nil { @@ -666,12 +716,16 @@ func (c *Clique) Seal(chain consensus.ChainHeaderReader, block *types.Block, res go func() { select { case <-stop: + // CHANAGE(immutable): log interrupt + log.Info("interrupted on clique seal", "block number", header.Number.String()) return case <-time.After(delay): } select { case results <- block.WithSeal(header): + // CHANGE(immutable): log seal success + log.Info("Sealed new block in clique", "block number", header.Number.String()) default: log.Warn("Sealing result is not read by miner", "sealhash", SealHash(header)) } @@ -763,19 +817,60 @@ func encodeSigHeader(w io.Writer, header *types.Header) { if header.BaseFee != nil { enc = append(enc, header.BaseFee) } + // CHANGE(immutable): Allow Shanghai if header.WithdrawalsHash != nil { - panic("unexpected withdrawal hash value in clique") + // CHANGE(immutable): Post-Cancun blocks will have a non-nil WithdrawalsHash set to EmptyWithdrawalsHash + if *header.WithdrawalsHash != types.EmptyWithdrawalsHash { + panic(fmt.Sprintf("withdrawalsHash (%x) should be EmptyWithdrawalsHash", *header.WithdrawalsHash)) + } + enc = append(enc, *header.WithdrawalsHash) } if header.ExcessBlobGas != nil { - panic("unexpected excess blob gas value in clique") + // CHANGE(immutable): Allow Cancun + if *header.ExcessBlobGas != 0 { + panic(fmt.Sprintf("excessBlobGas (%d) should be 0", *header.ExcessBlobGas)) + } + enc = append(enc, *header.ExcessBlobGas) } if header.BlobGasUsed != nil { - panic("unexpected blob gas used value in clique") + // CHANGE(immutable): Allow Cancun + if *header.BlobGasUsed != 0 { + panic(fmt.Sprintf("blobGasUsed (%d) should be 0", *header.BlobGasUsed)) + } + enc = append(enc, *header.BlobGasUsed) } if header.ParentBeaconRoot != nil { - panic("unexpected parent beacon root value in clique") + // CHANGE(immutable): Allow Cancun + if *header.ParentBeaconRoot != common.MinHash { + panic(fmt.Sprintf("parentBeaconRoot (%x) should be 0x0", *header.ParentBeaconRoot)) + } + enc = append(enc, *header.ParentBeaconRoot) } if err := rlp.Encode(w, enc); err != nil { panic("can't encode: " + err.Error()) } } + +// CHANGE(immutable): In post-merge networks, the ParentBeaconRoot is verified by the consensus client +// and propagated to the Execution Client. However, in a Pre-merge network, there is no existing mechanism to verify +// that it is correct. Therefore, we enforce that it is always set to 0x0. We also disable blobs and hence enforce +// their absence here. +func enforceCancunHeaderInvariants(header *types.Header) error { + // These should never be nil given that it is checked in other areas but is added here for completeness + if header.ParentBeaconRoot == nil || header.BlobGasUsed == nil || header.ExcessBlobGas == nil { + return fmt.Errorf("invalid header with nil values: %v, %v, %v", + header.ParentBeaconRoot, + header.BlobGasUsed, + header.ExcessBlobGas) + } + if *header.BlobGasUsed != 0 { + return fmt.Errorf("invalid BlobGasUsed: have %d, expected 0", *header.BlobGasUsed) + } + if *header.ExcessBlobGas != 0 { + return fmt.Errorf("invalid ExcessBlobGas: have %d, expected 0", *header.ExcessBlobGas) + } + if header.ParentBeaconRoot.Cmp(common.MinHash) != 0 { + return fmt.Errorf("invalid parentBeaconRoot, have %#x, expected zero hash", header.ParentBeaconRoot) + } + return nil +} diff --git a/consensus/clique/clique_immutable_test.go b/consensus/clique/clique_immutable_test.go new file mode 100644 index 000000000..a4dfbad76 --- /dev/null +++ b/consensus/clique/clique_immutable_test.go @@ -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 . + +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" +) + +func TestImmutableClique_ForksEnabled_BlocksValid(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) + ) + // Configs + zero := uint64(0) + shanghaiConfig := params.AllCliqueProtocolChanges + shanghaiConfig.ShanghaiTime = &zero + immutableConfig := params.AllCliqueProtocolChanges + immutableConfig.ShanghaiTime = &zero + immutableConfig.PrevrandaoTime = &zero + immutableConfig.CancunTime = &zero + + // Test fixtures + var tests = []struct { + name string + config *params.ChainConfig + blockModifier func(*core.BlockGen) + }{ + {"shanghai", immutableConfig, func(*core.BlockGen) {}}, + {"shanghaiWithdrawals", immutableConfig, func(block *core.BlockGen) { + block.AddWithdrawal(&types.Withdrawal{ + Index: 0, + Validator: 0, + Address: addr, + Amount: 1, + }) + }}, + {"immutable", immutableConfig, func(*core.BlockGen) {}}, + {"immutableWithdrawals", immutableConfig, func(block *core.BlockGen) { + block.AddWithdrawal(&types.Withdrawal{ + Index: 0, + Validator: 0, + Address: addr, + Amount: 1, + }) + }}, + } + + // Run + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + genspec := &core.Genesis{ + Config: test.config, + 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) + + test.blockModifier(block) + }) + 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) + } + }) + } +} diff --git a/consensus/clique/clique_test.go b/consensus/clique/clique_test.go index 8ef8dbffa..16896d8d7 100644 --- a/consensus/clique/clique_test.go +++ b/consensus/clique/clique_test.go @@ -27,6 +27,7 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" + "github.com/stretchr/testify/require" ) // This test case is a repro of an annoying bug that took us forever to catch. @@ -123,3 +124,55 @@ func TestSealHash(t *testing.T) { t.Errorf("have %x, want %x", have, want) } } + +func TestImmutableEnforceCancunHeaderInvariants_NonZeroParentBeaconRoot_ReturnsError(t *testing.T) { + header := &types.Header{ParentBeaconRoot: &common.MaxHash, BlobGasUsed: newUint64(0), ExcessBlobGas: newUint64(0)} + err := enforceCancunHeaderInvariants(header) + require.EqualError(t, err, "invalid parentBeaconRoot, have 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, expected zero hash") +} + +func TestImmutableEnforceCancunHeaderInvariants_NonZeroBlobGasUsed_ReturnsError(t *testing.T) { + header := &types.Header{ParentBeaconRoot: &common.MinHash, BlobGasUsed: newUint64(1), ExcessBlobGas: newUint64(0)} + err := enforceCancunHeaderInvariants(header) + require.EqualError(t, err, "invalid BlobGasUsed: have 1, expected 0") +} + +func TestImmutableEnforceCancunHeaderInvariants_NonZeroExcessBlobGas_ReturnsError(t *testing.T) { + header := &types.Header{ParentBeaconRoot: &common.MinHash, BlobGasUsed: newUint64(0), ExcessBlobGas: newUint64(1)} + err := enforceCancunHeaderInvariants(header) + require.EqualError(t, err, "invalid ExcessBlobGas: have 1, expected 0") +} + +func TestImmutableEnforceCancunHeaderInvariants_NilParentBeaconRoot_ReturnsError(t *testing.T) { + header := &types.Header{ParentBeaconRoot: nil, BlobGasUsed: newUint64(0), ExcessBlobGas: newUint64(0)} + err := enforceCancunHeaderInvariants(header) + require.ErrorContains(t, err, "invalid header with nil values") +} + +func TestImmutableEnforceCancunHeaderInvariants_NilBlobGasUsed_ReturnsError(t *testing.T) { + header := &types.Header{ParentBeaconRoot: &common.MaxHash, BlobGasUsed: nil, ExcessBlobGas: newUint64(0)} + err := enforceCancunHeaderInvariants(header) + require.ErrorContains(t, err, "invalid header with nil values") +} + +func TestImmutableEnforceCancunHeaderInvariants_NilExcessBlobGas_ReturnsError(t *testing.T) { + header := &types.Header{ParentBeaconRoot: &common.MinHash, BlobGasUsed: newUint64(0), ExcessBlobGas: nil} + err := enforceCancunHeaderInvariants(header) + require.ErrorContains(t, err, "invalid header with nil values") +} + +func TestImmutableEnforceCancunHeaderInvariants_ValidValues_ReturnsNil(t *testing.T) { + validA := common.HexToHash("0x0") + validB := common.Hash{} + validC := common.BigToHash(big.NewInt(0)) + validD := common.MinHash + + validHashes := []common.Hash{validA, validB, validC, validD} + for _, h := range validHashes { + header := &types.Header{ParentBeaconRoot: &h, BlobGasUsed: newUint64(0), ExcessBlobGas: newUint64(0)} + err := enforceCancunHeaderInvariants(header) + require.NoError(t, err) + } +} + +func newUint64(val uint64) *uint64 { return &val } diff --git a/consensus/misc/eip1559/immutable_eip1559_test.go b/consensus/misc/eip1559/immutable_eip1559_test.go new file mode 100644 index 000000000..038479b24 --- /dev/null +++ b/consensus/misc/eip1559/immutable_eip1559_test.go @@ -0,0 +1,62 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package eip1559 + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/cmd/geth/immutable/settings" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" +) + +func TestImmutableCalcBaseFee(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, 998000000}, // usage below target + {params.InitialBaseFee, 20000000, 11000000, 1002000000}, // 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), + } + // Set config that modifies base fee calculation + // (cannot import core pkg for genesis due to import cycle) + c := config() + c.Clique = ¶ms.CliqueConfig{ + Period: settings.SecondsPerBlock, + } + c.IsReorgBlocked = true + c.ChainID = big.NewInt(settings.DevnetNetworkID) + if !c.IsValidImmutableZKEVM() { + t.Fatalf("test %d: invalid immutable zkevm config", i) + } + if have, want := CalcBaseFee(c, parent), big.NewInt(test.expectedBaseFee); have.Cmp(want) != 0 { + t.Fatalf("test %d: have %d want %d, ", i, have, want) + } + } +} diff --git a/core/blockchain.go b/core/blockchain.go index b1bbc3d59..29e4af048 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -82,6 +82,11 @@ var ( blockValidationTimer = metrics.NewRegisteredTimer("chain/validation", nil) blockExecutionTimer = metrics.NewRegisteredTimer("chain/execution", nil) blockWriteTimer = metrics.NewRegisteredTimer("chain/write", nil) + // CHANGE(immutable): Add metric for blocks + // blockPeriodTimer records number of seconds between adjacent blocks + blockPeriodTimer = metrics.NewRegisteredTimer("chain/block/period", nil) + // blockPropagationTimer records time taken to receive a block and insert it into the chain + blockPropagationTimer = metrics.NewRegisteredTimer("chain/block/propagation", nil) blockReorgMeter = metrics.NewRegisteredMeter("chain/reorg/executes", nil) blockReorgAddMeter = metrics.NewRegisteredMeter("chain/reorg/add", nil) @@ -94,6 +99,9 @@ var ( errChainStopped = errors.New("blockchain is stopped") errInvalidOldChain = errors.New("invalid old chain") errInvalidNewChain = errors.New("invalid new chain") + + // ErrReorgAttempted CHANGE(immutable) is a new error type returned when a fork invariant is hit. + ErrReorgAttempted = errors.New("reorg was attempted") ) const ( @@ -1437,6 +1445,12 @@ func (bc *BlockChain) WriteBlockAndSetHead(block *types.Block, receipts []*types } defer bc.chainmu.Unlock() + // CHANGE(immutable): Record block period and log info + parentTime := time.Unix(int64(bc.CurrentBlock().Time), 0) + headTime := time.Unix(int64(block.Time()), 0) + blockPeriod := headTime.Sub(parentTime) + blockPeriodTimer.Update(blockPeriod) + log.Info("Writing block and setting head", "period", blockPeriod.Milliseconds(), "number", block.Number(), "hash", block.Hash()) return bc.writeBlockAndSetHead(block, receipts, logs, state, emitHeadEvent) } @@ -1484,6 +1498,7 @@ func (bc *BlockChain) writeBlockAndSetHead(block *types.Block, receipts []*types } else { bc.chainSideFeed.Send(ChainSideEvent{Block: block}) } + return status, nil } @@ -1822,6 +1837,9 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) blockWriteTimer.Update(time.Since(wstart) - statedb.AccountCommits - statedb.StorageCommits - statedb.SnapshotCommits - statedb.TrieDBCommits) blockInsertTimer.UpdateSince(start) + // CHANGE(immutable): Record block metrics + blockPropagationTimer.Update(time.Since(time.Unix(int64(block.Time()), 0))) + // Report the import stats before returning the various results stats.processed++ stats.usedGas += usedGas @@ -2104,6 +2122,22 @@ func (bc *BlockChain) collectLogs(b *types.Block, removed bool) []*types.Log { // 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 { + // CHANGE(immutable): Add logging for 'potential' reorgs + if bc.chainConfig.IsReorgBlocked { + // This is not necessarily a reorg. It could be a future block that does not cause our old head to be reverted + // We track it for visibility. This is a path that often occurs when a node is syncing. + // An example such chain is as follows + // Old Chain: A -> B -> C -> D + // NewChain: A -> B -> C -> D -> E -> F + // If such a chain is observed, it is considered a 'potential reorg' + log.Info("Potential Reorg detected", + "oldHead.Number", oldHead.Number.String(), + "newHead.Number", newHead.Number().String(), + "oldHead.Hash", oldHead.Hash().String(), + "newHead.Hash", newHead.Hash().String(), + "newHead.ParentHash", newHead.ParentHash().String()) + } + var ( newChain types.Blocks oldChain types.Blocks @@ -2139,6 +2173,30 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Block) error { if newBlock == nil { return errInvalidNewChain } + + // CHANGE(immutable): Logic to check if this is a reorg attempt + if bc.chainConfig.IsReorgBlocked { + // We allow 'forward-reorgs', i.e. where blocks from the further in the future can be appended + // to the chain, even if they don't follow the oldChain directly. + // By this point, we have reduced the lengths of the chains to a common ancestor. If they don't have a + // common ancestor after reducing to the same length, it means we are attempting to remove blocks from + // the original chain which is not allowed. + // An example chain that has reached this point is: + // oldChain: A -> B -> C -> D + // newChain: A -> B -> C -> E + if oldBlock.Hash() != newBlock.Hash() { + // Mark as a reorg for metrics and alerting. Although a reorg was technically prevented, + log.Error("Reorg detected", + "oldHead.Number", oldHead.Number.String(), + "newHead.Number", newHead.Number().String(), + "oldHead.Hash", oldHead.Hash().String(), + "newHead.Hash", newHead.Hash().String(), + "newHead.ParentHash", newHead.ParentHash().String()) + blockReorgMeter.Mark(1) + return ErrReorgAttempted + } + } + // Both sides of the reorg are at the same number, reduce both until the common // ancestor is found for { diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 876d662f7..b1815640a 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -53,13 +53,15 @@ var ( // 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. +// CHANGE(immutable): Wraps newCanonicalWithGenesis to allow for customization 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, - } - ) + genesis := &Genesis{ + BaseFee: big.NewInt(params.InitialBaseFee), + Config: params.AllEthashProtocolChanges, + } + return newCanonicalWithGenesis(engine, n, full, scheme, genesis) +} +func newCanonicalWithGenesis(engine consensus.Engine, n int, full bool, scheme string, genesis *Genesis) (ethdb.Database, *Genesis, *BlockChain, error) { // Initialize a fresh chain with only a genesis block blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil, nil) @@ -83,7 +85,7 @@ 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 +// Test fork of length N starting from block i. These will not trigger a reorg 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) @@ -222,9 +224,16 @@ func testLastBlock(t *testing.T, scheme string) { // Test inserts the blocks/headers after the fork choice rule is changed. // The chain is reorged to whatever specified. +// CHANGE(immutable): Wraps testInsertAfterMergeConfigurable to allow for customization func testInsertAfterMerge(t *testing.T, blockchain *BlockChain, i, n int, full bool, scheme string) { + testInsertAfterMergeConfigurable(t, blockchain, i, n, full, scheme, newCanonical) +} + +func testInsertAfterMergeConfigurable(t *testing.T, blockchain *BlockChain, i, n int, full bool, scheme string, + newCanonicalChain func(engine consensus.Engine, n int, full bool, scheme string) (ethdb.Database, *Genesis, *BlockChain, error), +) { // Copy old chain up to #i into a new db - genDb, _, blockchain2, err := newCanonical(ethash.NewFaker(), i, full, scheme) + genDb, _, blockchain2, err := newCanonicalChain(ethash.NewFaker(), i, full, scheme) if err != nil { t.Fatal("could not make new canonical in testFork", err) } diff --git a/core/chain_makers.go b/core/chain_makers.go index 733030fd1..b6ebcc884 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -98,7 +98,7 @@ func (b *BlockGen) Difficulty() *big.Int { func (b *BlockGen) SetParentBeaconRoot(root common.Hash) { b.header.ParentBeaconRoot = &root var ( - blockContext = NewEVMBlockContext(b.header, b.cm, &b.header.Coinbase) + blockContext = NewEVMBlockContext(b.header, b.cm, &b.header.Coinbase, b.cm.config) vmenv = vm.NewEVM(blockContext, vm.TxContext{}, b.statedb, b.cm.config, vm.Config{}) ) ProcessBeaconBlockRoot(root, vmenv, b.statedb) diff --git a/core/evm.go b/core/evm.go index 73f6d7bc2..726fadaa0 100644 --- a/core/evm.go +++ b/core/evm.go @@ -24,6 +24,7 @@ import ( "github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/params" "github.com/holiman/uint256" ) @@ -37,8 +38,9 @@ type ChainContext interface { GetHeader(common.Hash, uint64) *types.Header } +// CHANGE(immutable): Pass in ChainConfig to NewEVMBlockContext to allow determination that network is Immutable zkEVM // NewEVMBlockContext creates a new context for use in the EVM. -func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address) vm.BlockContext { +func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address, params *params.ChainConfig) vm.BlockContext { var ( beneficiary common.Address baseFee *big.Int @@ -58,7 +60,9 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common if header.ExcessBlobGas != nil { blobBaseFee = eip4844.CalcBlobFee(*header.ExcessBlobGas) } - if header.Difficulty.Cmp(common.Big0) == 0 { + // CHANGE(immutable): Enable EIP-4399 based on prevrandao fork + if params.IsImmutableZKEVMPrevrandao(header.Time) || + header.Difficulty.Cmp(common.Big0) == 0 { random = &header.MixDigest } return vm.BlockContext{ diff --git a/core/forkid/forkid.go b/core/forkid/forkid.go index 76825d3be..3fe6a04c1 100644 --- a/core/forkid/forkid.go +++ b/core/forkid/forkid.go @@ -293,5 +293,6 @@ func gatherForks(config *params.ChainConfig, genesis uint64) ([]uint64, []uint64 for len(forksByTime) > 0 && forksByTime[0] <= genesis { forksByTime = forksByTime[1:] } + return forksByBlock, forksByTime } diff --git a/core/gen_genesis.go b/core/gen_genesis.go index b8acf9df7..f59a0b530 100644 --- a/core/gen_genesis.go +++ b/core/gen_genesis.go @@ -27,7 +27,7 @@ func (g Genesis) MarshalJSON() ([]byte, error) { 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"` + 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"` @@ -70,7 +70,7 @@ func (g *Genesis) UnmarshalJSON(input []byte) error { 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"` + 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"` diff --git a/core/genesis.go b/core/genesis.go index 54570ac61..f0fa9e3f4 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -210,6 +210,9 @@ func (e *GenesisMismatchError) Error() string { type ChainOverrides struct { OverrideCancun *uint64 OverrideVerkle *uint64 + // CHANGE(immutable): Add Prevrandao and Shanghai overrides + OverridePrevrandao *uint64 + OverrideShanghai *uint64 } // SetupGenesisBlock writes or updates the genesis block in db. @@ -241,6 +244,14 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *triedb.Database, g if overrides != nil && overrides.OverrideVerkle != nil { config.VerkleTime = overrides.OverrideVerkle } + // CHANGE(immutable): Add Prevrandao override + if overrides != nil && overrides.OverridePrevrandao != nil { + config.PrevrandaoTime = overrides.OverridePrevrandao + } + // CHANGE(immutable): Add Shanghai override + if overrides != nil && overrides.OverrideShanghai != nil { + config.ShanghaiTime = overrides.OverrideShanghai + } } } // Just commit the new block if there is no stored genesis block. diff --git a/core/immutable_blockchain_test.go b/core/immutable_blockchain_test.go new file mode 100644 index 000000000..0785855f0 --- /dev/null +++ b/core/immutable_blockchain_test.go @@ -0,0 +1,138 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package core + +import ( + "errors" + "math/big" + "testing" + + "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/ethdb" + "github.com/ethereum/go-ethereum/params" +) + +const scheme = rawdb.HashScheme + +// All tests use full blocks as light chains are not relevant to Immutable +const full = true + +// newCanonicalWithInvariants creates a chain database, and injects a deterministic canonical +// full blockchain. The chain is configured with reorgs blocked. +// The database and genesis specification for block generation +// are also returned in case more test blocks are needed later. Evolved from newCanonical +func newCanonicalWithInvariants(engine consensus.Engine, n int, full bool, scheme string) (ethdb.Database, *Genesis, *BlockChain, error) { + chainCfg := *params.AllEthashProtocolChanges + chainCfg.IsReorgBlocked = true + var ( + genesis = &Genesis{ + BaseFee: big.NewInt(params.InitialBaseFee), + Config: &chainCfg, + } + ) + return newCanonicalWithGenesis(engine, n, full, scheme, genesis) +} + +// testFullBlockReorgWithInvariants will generate a blockchain with no blocks. It will then create two sequences of full blocks +// of lengths defined by `first` and `second`. It will then attempt to insert the two sequences of blocks into the blockchain. +// It will then check that the chain is a validate sequence of linked hashes. +func testFullBlockReorgWithInvariants(t *testing.T, first, second []int64, expectReorg bool, expectedChainLength uint64) { + // Create a pristine chain and database + genDb, _, blockchain, err := newCanonicalWithInvariants(ethash.NewFaker(), 0, full, scheme) + if err != nil { + t.Fatalf("failed to create pristine chain: %v", err) + } + defer blockchain.Stop() + + // Generate the first chain for inserting + firstBlocks, _ := GenerateChain(params.TestChainConfig, blockchain.GetBlockByHash(blockchain.CurrentBlock().Hash()), ethash.NewFaker(), genDb, len(first), func(i int, b *BlockGen) { + b.OffsetTime(first[i]) + }) + secondBlocks, _ := GenerateChain(params.TestChainConfig, blockchain.GetBlockByHash(blockchain.CurrentBlock().Hash()), ethash.NewFaker(), genDb, len(second), func(i int, b *BlockGen) { + b.OffsetTime(second[i]) + }) + + if _, err := blockchain.InsertChain(firstBlocks); err != nil { + t.Fatalf("failed to insert firstBlocks chain: %v", err) + } + _, err = blockchain.InsertChain(secondBlocks) + if expectReorg && !errors.Is(err, ErrReorgAttempted) { + t.Fatalf("expected reorg error but got: %v", err) + } + if !expectReorg && err != nil { + t.Fatalf("failed to insert secondBlocks chain: %v", err) + } + // Check that the chain is valid number and link wise + 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()) + } + } + chainLength := blockchain.CurrentBlock().Number.Uint64() + if chainLength != expectedChainLength { + t.Errorf("expected chain of length %d but got %d", expectedChainLength, chainLength) + } +} + +// TestInvariantExtendCanonicalBlocks this tests that we can extend a given chain by adding canonical blocks +// It works by generating a chain of length `i` and extending it by blocks `n`. Evolved from TestExtendCanonicalBlocksAfterMerge +// Will not trigger a reorg since the canonical chain is never reverted +func TestImmutableInvariantExtendCanonicalBlocks(t *testing.T) { + length := 5 + + // Make first chain starting from genesis + _, _, processor, err := newCanonicalWithInvariants(ethash.NewFaker(), length, full, scheme) + if err != nil { + t.Fatalf("failed to make new canonical chain: %v", err) + } + defer processor.Stop() + + testInsertAfterMergeConfigurable(t, processor, length, 1, full, scheme, newCanonicalWithInvariants) + testInsertAfterMergeConfigurable(t, processor, length, 10, full, scheme, newCanonicalWithInvariants) + testInsertAfterMergeConfigurable(t, processor, length, 100, full, scheme, newCanonicalWithInvariants) + testInsertAfterMergeConfigurable(t, processor, length, 1000, full, scheme, newCanonicalWithInvariants) + testInsertAfterMergeConfigurable(t, processor, length, 1000, full, scheme, newCanonicalWithInvariants) +} + +func TestImmutableInvariantReorg(t *testing.T) { + // Since the blocks being inserted in the second sequence do not reorg the first sequence, we do not expect a reorg + // and expect a final length of 6, as the second chain extends the canonical chain + testFullBlockReorgWithInvariants(t, []int64{15, 16, 17, 18}, []int64{15, 16, 17, 18, 19, 20, 21, 22, 23, 24}, false, 10) + + // The second chain blocks attempts to reorg the last block of the first chain. It should fail and the final + // chain should remain at the length of the initial chain + testFullBlockReorgWithInvariants(t, []int64{15, 16, 17, 18}, []int64{15, 16, 17, -1, 18, 19, 20, 21, 22, 23}, true, 4) + + // The second chain attempts to do a deep reorg, of multiple blocks + testFullBlockReorgWithInvariants(t, []int64{15, 16, 17, 18}, []int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, true, 4) + + // The second chain is different to the first chain, but since it is shorter, it will not cause a reorg + testFullBlockReorgWithInvariants(t, []int64{15, 16, 17, 18, 19, 20}, []int64{1, 2}, false, 6) + + // Both chains are of equal length. Due to randomness, in the protocol, this will attempt to replace the head ~half the time + // Comment it out due to test flakiness. + //testFullBlockReorgWithInvariants(t, []int64{15, 16, 17, 18}, []int64{1, 2, 3, 4}, true, 4) + + // Short chains, where new chain extends the canonical chain + testFullBlockReorgWithInvariants(t, []int64{15}, []int64{15, 16}, false, 2) + + // No initial chain, new chain becomes the canonical chain + testFullBlockReorgWithInvariants(t, []int64{}, []int64{15, 16, 17}, false, 3) +} diff --git a/core/immutable_genesis.go b/core/immutable_genesis.go new file mode 100644 index 000000000..824c83ab7 --- /dev/null +++ b/core/immutable_genesis.go @@ -0,0 +1,37 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package core + +import ( + "encoding/json" + + "github.com/ethereum/go-ethereum/cmd/geth/immutable/settings" +) + +// ImmutableGenesisBlock returns the immutable genesis block for the specified network (mainnet, testnet, devnet). +func ImmutableGenesisBlock(network string) *Genesis { + net, err := settings.NewNetwork(network) + if err != nil { + panic(err) + } + // Unmarshal the JSON string into a Genesis struct + genesis := &Genesis{} + if err := json.Unmarshal([]byte(net.GenesisJSON()), genesis); err != nil { + panic(err) + } + return genesis +} diff --git a/core/immutable_genesis_test.go b/core/immutable_genesis_test.go new file mode 100644 index 000000000..260c78b6d --- /dev/null +++ b/core/immutable_genesis_test.go @@ -0,0 +1,171 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package core + +import ( + "math/big" + "path/filepath" + "testing" + "time" + + "github.com/ethereum/go-ethereum/cmd/geth/immutable/settings" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/stretchr/testify/assert" +) + +// TestImmutableGenesis_ImmutableGenesis_ParsedAndValid tests that the immutable genesis blocks are valid +// so that nodes are configured correctly pre and post genesis. +func TestImmutableGenesis_ImmutableGenesis_ParsedAndValid(t *testing.T) { + var tests = []struct { + name string + addr common.Address + extraData []byte + prevrandao settings.Fork + shanghai settings.Fork + cancun settings.Fork + }{ + { + "mainnet", + common.HexToAddress("dda0d9448ebe3ea43afece5fa6401f5795c19333"), + hexutil.MustDecode("0x000000000000000000000000000000000000000000000000000000000000000055e2ebc94b3314d387f423ef4424a95547f3f8c40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), + settings.MainnetPrevrandaoFork, + settings.MainnetShanghaiFork, + settings.MainnetCancunFork, + }, + { + "testnet", + common.HexToAddress("e567ea84e1eb3ffdc8f5aa420bf14a16eee6a809"), + hexutil.MustDecode("0x0000000000000000000000000000000000000000000000000000000000000000512069234755cc4bb2cc59b79b7541ed5a03babb0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), + settings.TestnetPrevrandaoFork, + settings.TestnetShanghaiFork, + settings.TestnetCancunFork, + }, + { + "devnet", + common.HexToAddress("000000000013b7b1b08b3c8efe02e866f746bd38"), + hexutil.MustDecode("0x000000000000000000000000000000000000000000000000000000000000000092e13fb4e00a5daecf5b61553b678ad56e6985150000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), + settings.DevnetPrevrandaoFork, + settings.DevnetShanghaiFork, + settings.DevnetCancunFork, + }, + } + for _, test := range tests { + g := ImmutableGenesisBlock(test.name) + t.Run(test.name+"genesis", func(t *testing.T) { + assert.Equal(t, uint64(0x0), g.Nonce) + assert.Equal(t, uint64(0x0), g.Timestamp) + assert.Equal(t, test.extraData, g.ExtraData) + assert.Equal(t, big.NewInt(0x1), g.Difficulty) + assert.Equal(t, uint64(0x5f5e100), g.GasLimit) + assert.Equal(t, common.BigToHash(common.Big0), g.Mixhash) + assert.Equal(t, common.BigToAddress(common.Big0), g.Coinbase) + assert.Contains(t, g.Alloc, test.addr) + assert.Equal(t, hexutil.MustDecodeBig("0x6765c793fa10079d0000000"), g.Alloc[test.addr].Balance) + assert.Equal(t, uint64(0), g.Number) + assert.Equal(t, uint64(0), g.GasUsed) + assert.Equal(t, common.BigToHash(common.Big0), g.ParentHash) + var ( + bigNil *big.Int + uintNil *uint64 + ) + assert.Equal(t, bigNil, g.BaseFee) + assert.Equal(t, uintNil, g.ExcessBlobGas) + assert.Equal(t, uintNil, g.BlobGasUsed) + + // Make sure that genesis json matches settings.go + assert.Equal(t, test.cancun.Unix(), int64(*g.Config.CancunTime)) + assert.Equal(t, test.prevrandao.Unix(), int64(*g.Config.PrevrandaoTime)) + assert.Equal(t, test.shanghai.Unix(), int64(*g.Config.ShanghaiTime)) + }) + t.Run(test.name+"config", func(t *testing.T) { + c := g.Config + assert.True(t, c.IsReorgBlocked) + + // NOTE: this test ensures that our nodes are configured to use the correct fork timetsamp! + // Existence + assert.NotNil(t, c.PrevrandaoTime) + assert.NotNil(t, c.ShanghaiTime) + // Values against human readable ones + assert.Equal(t, test.prevrandao.Unix(), int64(*c.PrevrandaoTime)) + assert.Equal(t, test.shanghai.Unix(), int64(*c.ShanghaiTime)) + // General network config + assert.True(t, c.IsValidImmutableZKEVM(), "Expected to be immutable zkevm") + + now := uint64(time.Now().Unix()) + // Prevrandao fork + assert.True(t, c.IsImmutableZKEVMPrevrandao(now), test.prevrandao.IsEnabledAt(int64(now))) + // Shanghai fork + assert.Equal(t, c.IsShanghai(common.Big0, now), test.shanghai.IsEnabledAt(int64(now))) + // Cancun fork + assert.Equal(t, c.IsCancun(common.Big0, now), test.cancun.IsEnabledAt(int64(now))) + // Check network IDs + network, err := settings.NewNetwork(test.name) + assert.NoError(t, err) + assert.Equal(t, network.ID(), int(c.ChainID.Uint64()), + "Expected network ID (%d) to match genesis chain ID (%d)", network.ID(), c.ChainID.Uint64()) + }) + } +} + +func TestImmutableTestCheckCompatible(t *testing.T) { + var tests = []struct { + name string + network string + prevrandaoTimestamp *big.Int + shanghaTimestamp *big.Int + cancunTimestamp *big.Int + mergeNetSplitBlock *big.Int + }{ + // Defaults + {"", "devnet", nil, nil, nil, nil}, {"", "testnet", nil, nil, nil, nil}, {"", "mainnet", nil, nil, nil, nil}, + // Non-defaults + {"devnet shanghai and cancun before merge", "devnet", big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(10000)}, + // Actual + {"devnet actual", "devnet", + big.NewInt(settings.DevnetPrevrandaoFork.Unix()), big.NewInt(settings.DevnetShanghaiFork.Unix()), big.NewInt(settings.DevnetCancunFork.Unix()), big.NewInt(10000)}, + {"testnet actual", "testnet", + big.NewInt(settings.TestnetPrevrandaoFork.Unix()), big.NewInt(settings.TestnetShanghaiFork.Unix()), big.NewInt(settings.TestnetCancunFork.Unix()), big.NewInt(10000)}, + {"testnet actual", "mainnet", + big.NewInt(settings.MainnetPrevrandaoFork.Unix()), big.NewInt(settings.MainnetShanghaiFork.Unix()), big.NewInt(settings.MainnetCancunFork.Unix()), big.NewInt(10000)}, + } + + for _, test := range tests { + name := test.name + if name == "" { + name = test.network + } + t.Run(name, func(t *testing.T) { + var genesis Genesis + genesisFilepath := filepath.Join("..", "cmd", "geth", "immutable", "settings", "genesis", test.network+".json") + err := common.LoadJSON(genesisFilepath, &genesis) + if err != nil { + t.Fatalf("Failed to load genesis file: %v", err) + } + if test.shanghaTimestamp != nil { + ts := test.shanghaTimestamp.Uint64() + genesis.Config.ShanghaiTime = &ts + } + if test.mergeNetSplitBlock != nil { + genesis.Config.MergeNetsplitBlock = test.mergeNetSplitBlock + } + if genesis.Config.CheckConfigForkOrder(); err != nil { + t.Fatalf("Failed to check config order: %v", err) + } + }) + } +} diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go index ff867309d..5cc959492 100644 --- a/core/state_prefetcher.go +++ b/core/state_prefetcher.go @@ -51,7 +51,7 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c var ( header = block.Header() gaspool = new(GasPool).AddGas(block.GasLimit()) - blockContext = NewEVMBlockContext(header, p.bc, nil) + blockContext = NewEVMBlockContext(header, p.bc, nil, p.config) evm = vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg) signer = types.MakeSigner(p.config, header.Number, header.Time) ) diff --git a/core/state_processor.go b/core/state_processor.go index 9e32ab4e5..613a20d69 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -72,7 +72,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg misc.ApplyDAOHardFork(statedb) } var ( - context = NewEVMBlockContext(header, p.bc, nil) + context = NewEVMBlockContext(header, p.bc, nil, p.config) vmenv = vm.NewEVM(context, vm.TxContext{}, statedb, p.config, cfg) signer = types.MakeSigner(p.config, header.Number, header.Time) ) @@ -136,6 +136,10 @@ func applyTransaction(msg *Message, config *params.ChainConfig, gp *GasPool, sta receipt.GasUsed = result.UsedGas if tx.Type() == types.BlobTxType { + // CHANGE(immutable): Disable blob transactions as it is not yet a feature of Immutable zkEVM. + if config.IsImmutableZKEVM() { + return nil, errors.New("blob transactions are not supported") + } receipt.BlobGasUsed = uint64(len(tx.BlobHashes()) * params.BlobTxBlobGasPerBlob) receipt.BlobGasPrice = evm.Context.BlobBaseFee } @@ -164,7 +168,7 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo return nil, err } // Create a new context to be used in the EVM environment - blockContext := NewEVMBlockContext(header, bc, author) + blockContext := NewEVMBlockContext(header, bc, author, config) txContext := NewEVMTxContext(msg) vmenv := vm.NewEVM(blockContext, txContext, statedb, config, cfg) return applyTransaction(msg, config, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv) diff --git a/core/state_transition.go b/core/state_transition.go index 9c4f76d1c..b1005c22d 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -26,6 +26,7 @@ import ( "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/log" "github.com/ethereum/go-ethereum/params" "github.com/holiman/uint256" ) @@ -458,7 +459,10 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { fee.Mul(fee, effectiveTipU256) st.state.AddBalance(st.evm.Context.Coinbase, fee) } - + // CHANGE(immutable) add error logging + if vmerr != nil { + log.Warn("vm execution error", "err", vmerr) + } return &ExecutionResult{ UsedGas: st.gasUsed(), RefundedGas: gasRefund, diff --git a/core/txpool/blobpool/blobpool.go b/core/txpool/blobpool/blobpool.go index 3ed698c1b..f219586f4 100644 --- a/core/txpool/blobpool/blobpool.go +++ b/core/txpool/blobpool/blobpool.go @@ -339,6 +339,15 @@ func (p *BlobPool) Filter(tx *types.Transaction) bool { return tx.Type() == types.BlobTxType } +// FilterWithError returns error or nil whether the given transaction can be consumed by the blob pool. +// CHANGE(immutable): extended filter to use filter with error function to allow access control per tx +func (p *BlobPool) FilterWithError(tx *types.Transaction) error { + if !p.Filter(tx) { + return core.ErrTxTypeNotSupported + } + return nil +} + // 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. diff --git a/core/txpool/blobpool/limbo.go b/core/txpool/blobpool/limbo.go index ec754f689..602e57afc 100644 --- a/core/txpool/blobpool/limbo.go +++ b/core/txpool/blobpool/limbo.go @@ -116,9 +116,12 @@ 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") + // CHANGE(immutable): Decrease log severity because our BAU involves nil final header. + log.Info("Nil finalized block cannot evict old blobs") return } + // CHANGE(immutable): Additional log line. + log.Warn("Evicting old blobs", "block", *final) for block, ids := range l.groups { if block > final.Number.Uint64() { continue diff --git a/core/txpool/errors.go b/core/txpool/errors.go index 3a6a91397..61c5489ca 100644 --- a/core/txpool/errors.go +++ b/core/txpool/errors.go @@ -55,6 +55,10 @@ var ( // one. Future transactions should only be able to replace other future transactions. ErrFutureReplacePending = errors.New("future transaction tries to replace pending") + // ErrTxIsUnauthorized indicates that the sender is not authorized to submit transaction + // CHANGE(immutable): ErrTxIsUnauthorized error for transactions that are blocked/filtered out + ErrTxIsUnauthorized = errors.New("transaction is not allowed, sender is not authorized to perform transaction") + // 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 diff --git a/core/txpool/immutable/accesscontrol/address_provider.go b/core/txpool/immutable/accesscontrol/address_provider.go new file mode 100644 index 000000000..618dc70fc --- /dev/null +++ b/core/txpool/immutable/accesscontrol/address_provider.go @@ -0,0 +1,32 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package accesscontrol + +import "github.com/ethereum/go-ethereum/common" + +// AddressProvider is an interface for providing a list of Ethereum addresses. +// Implementations of this interface should return a map where the keys are +// Ethereum addresses (common.Address) and the values are of an empty struct +// (struct{}{}). +// +// This interface is typically used for providing a list of +// addresses that are used for access control purposes, such as blocklists +// or allowlists. +type AddressProvider interface { + // Provide a list of hex addressses + Provide() map[common.Address]struct{} +} diff --git a/core/txpool/immutable/accesscontrol/contract_creation_controller.go b/core/txpool/immutable/accesscontrol/contract_creation_controller.go new file mode 100644 index 000000000..a1d565183 --- /dev/null +++ b/core/txpool/immutable/accesscontrol/contract_creation_controller.go @@ -0,0 +1,77 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package accesscontrol + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +// ContractCreation is a an access controller that specifically targets transactions which is a contract creation transactions. +type ContractCreation struct { + // Map of filename to address provider + providers map[string]AddressProvider + // isAnAllowList indicates that it is an allowlist otherwise the inverse is a blocklist + isAnAllowList bool +} + +// NewContractCreation initializes an access controller that specifically controls contract creation txs. +// +// Parameters: +// - filePaths: A slice of strings containing file paths to blocklist +// files, usually an sdn file that comes in the format of xml +// - isAnAllowList: Indicates if the controller is an allow controller or +// a block controller. +func NewContractCreation(filePaths []string, isAnAllowList bool) (*ContractCreation, error) { + providers := make(map[string]AddressProvider, len(filePaths)) + + for _, filename := range filePaths { + sdnProvider, err := newCSVProvider(filename) + if err != nil { + return nil, fmt.Errorf("couldn't initialize access controller provider: %w", err) + } + providers[filename] = sdnProvider + } + + return &ContractCreation{ + providers: providers, + isAnAllowList: isAnAllowList, + }, nil +} + +func (c *ContractCreation) IsBlocklist() bool { + return !c.isAnAllowList +} + +func (c *ContractCreation) IsAllowed(addr common.Address, tx *types.Transaction) bool { + // Only control contract creation transactions, + // By definition contract creation has its tx.To() as nil. + if tx.To() != nil { + return true + } + for _, list := range c.providers { + addresses := list.Provide() + if _, exist := addresses[addr]; exist { + return c.isAnAllowList + } + } + + // If the address is not in the list and it's not an allow list, return true + return !c.isAnAllowList +} diff --git a/core/txpool/immutable/accesscontrol/contract_creation_controller_test.go b/core/txpool/immutable/accesscontrol/contract_creation_controller_test.go new file mode 100644 index 000000000..9c86dea04 --- /dev/null +++ b/core/txpool/immutable/accesscontrol/contract_creation_controller_test.go @@ -0,0 +1,105 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package accesscontrol + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" +) + +func TestImmutableAccessControl_ContractCreationController_IsAllowed(t *testing.T) { + key, _ := crypto.GenerateKey() + tests := []struct { + name string + providers map[string]AddressProvider + addressToCheck common.Address + tx *types.Transaction + isAnAllowList bool + expectedAllowed bool + }{ + { + name: "Address is in block list and tx is of contract creation", + isAnAllowList: false, + providers: map[string]AddressProvider{ + "list": &MockAddressProvider{ + addresses: map[common.Address]struct{}{ + common.HexToAddress("0x1234567890123456789012345678901234567890"): {}, + }, + }, + }, + tx: contractCreation(1234, 123, key), + addressToCheck: common.HexToAddress("0x1234567890123456789012345678901234567890"), + expectedAllowed: false, + }, + { + name: "Address is in block list and tx is not of contract creation", + isAnAllowList: false, + providers: map[string]AddressProvider{ + "list": &MockAddressProvider{ + addresses: map[common.Address]struct{}{ + common.HexToAddress("0x1234567890123456789012345678901234567890"): {}, + }, + }, + }, + tx: transaction(1234, 123, key), + addressToCheck: common.HexToAddress("0x1234567890123456789012345678901234567890"), + expectedAllowed: true, + }, + { + name: "Empty blocklist but tx is of contract creation", + isAnAllowList: false, + providers: map[string]AddressProvider{ + "list": &MockAddressProvider{ + addresses: map[common.Address]struct{}{}, + }, + }, + tx: contractCreation(1234, 123, key), + addressToCheck: common.HexToAddress("0x11111111111111111111111111"), + expectedAllowed: true, + }, + { + name: "Nil blocklist sets but tx is of contract creation", + isAnAllowList: false, + providers: map[string]AddressProvider{ + "list": &MockAddressProvider{ + addresses: nil, + }, + }, + tx: contractCreation(1234, 123, key), + addressToCheck: common.HexToAddress("0x11111111111111111111111111"), + expectedAllowed: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + controller := &ContractCreation{ + providers: test.providers, + isAnAllowList: test.isAnAllowList, + } + + allowed := controller.IsAllowed(test.addressToCheck, test.tx) + + if allowed != test.expectedAllowed { + t.Errorf("Expected allowed=%t, got allowed=%t", test.expectedAllowed, allowed) + } + }) + } +} diff --git a/core/txpool/immutable/accesscontrol/controller.go b/core/txpool/immutable/accesscontrol/controller.go new file mode 100644 index 000000000..10e0e6ca4 --- /dev/null +++ b/core/txpool/immutable/accesscontrol/controller.go @@ -0,0 +1,76 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package accesscontrol + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +type Controller struct { + // Map of filename to address provider + providers map[string]AddressProvider + // isAnAllowList indicates that it is an allowlist otherwise the inverse is a blocklist + isAnAllowList bool +} + +// New initializes an access controller with blocklists specified by file paths. +// +// Parameters: +// - filePaths: A slice of strings containing file paths to blocklist +// files, usually an sdn file that comes in the format of txt as comma separated values +// - isAnAllowList: Indicates if the controller is an allow controller or +// a block controller. +func New(filePaths []string, isAnAllowList bool) (*Controller, error) { + providers := make(map[string]AddressProvider, len(filePaths)) + + for _, filename := range filePaths { + csvProvider, err := newCSVProvider(filename) + if err != nil { + return nil, fmt.Errorf("couldn't initialize access controller provider: %w", err) + } + providers[filename] = csvProvider + } + + return &Controller{ + providers: providers, + isAnAllowList: isAnAllowList, + }, nil +} + +func (c *Controller) IsBlocklist() bool { + return !c.isAnAllowList +} + +func (c *Controller) IsAllowed(addr common.Address, tx *types.Transaction) bool { + for _, list := range c.providers { + addresses := list.Provide() + if _, exist := addresses[addr]; exist { + return c.isAnAllowList + } + if c.IsBlocklist() && tx.To() != nil { + if _, exist := addresses[*tx.To()]; exist { + return c.isAnAllowList + } + } + } + + // If the address is not in the list and it's not an allow list, return true + return !c.isAnAllowList +} diff --git a/core/txpool/immutable/accesscontrol/controller_test.go b/core/txpool/immutable/accesscontrol/controller_test.go new file mode 100644 index 000000000..6840d5f55 --- /dev/null +++ b/core/txpool/immutable/accesscontrol/controller_test.go @@ -0,0 +1,150 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package accesscontrol + +import ( + "crypto/ecdsa" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" +) + +// MockAddressProvider is a mock implementation of AddressProvider for testing. +type MockAddressProvider struct { + addresses map[common.Address]struct{} +} + +func (m *MockAddressProvider) Provide() map[common.Address]struct{} { + return m.addresses +} + +func contractCreation(nonce uint64, gaslimit uint64, key *ecdsa.PrivateKey) *types.Transaction { + tx, _ := types.SignTx(types.NewContractCreation(nonce, big.NewInt(0), gaslimit, big.NewInt(1), nil), types.NewEIP155Signer(big.NewInt(1337)), key) + return tx +} + +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.NewEIP155Signer(big.NewInt(1337)), key) + return tx +} + +func TestImmutableAccessControl_Controller_IsAllowed(t *testing.T) { + tests := []struct { + name string + providers map[string]AddressProvider + addressToCheck common.Address + isAnAllowList bool + expectedAllowed bool + }{ + { + name: "AddressInBlockedAddresses", + isAnAllowList: false, + providers: map[string]AddressProvider{ + "list": &MockAddressProvider{ + addresses: map[common.Address]struct{}{ + common.HexToAddress("0x1234567890123456789012345678901234567890"): {}, + }, + }, + }, + addressToCheck: common.HexToAddress("0x1234567890123456789012345678901234567890"), + expectedAllowed: false, + }, + { + name: "AddressNotInBlockedAddresses", + isAnAllowList: false, + providers: map[string]AddressProvider{ + "list": &MockAddressProvider{ + addresses: map[common.Address]struct{}{}, + }, + }, + addressToCheck: common.HexToAddress("0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefab"), + expectedAllowed: true, + }, + { + name: "AddressInAllowedAddresses", + isAnAllowList: true, + providers: map[string]AddressProvider{ + "list": &MockAddressProvider{ + addresses: map[common.Address]struct{}{ + common.HexToAddress("0x1234567890123456789012345678901234567890"): {}, + }, + }, + }, + addressToCheck: common.HexToAddress("0x1234567890123456789012345678901234567890"), + expectedAllowed: true, + }, + { + name: "AddressNotInAllowedAddresses", + isAnAllowList: true, + providers: map[string]AddressProvider{ + "list": &MockAddressProvider{ + addresses: map[common.Address]struct{}{ + common.HexToAddress("0x1234567890123456789012345678901234567890"): {}, + }, + }, + }, + addressToCheck: common.HexToAddress("0x11111111111111111111111111"), + expectedAllowed: false, + }, + { + name: "EmptyAddresses", + isAnAllowList: false, + providers: map[string]AddressProvider{ + "list": &MockAddressProvider{ + addresses: map[common.Address]struct{}{}, + }, + }, + addressToCheck: common.HexToAddress("0x11111111111111111111111111"), + expectedAllowed: true, + }, + { + name: "NilAddresses", + isAnAllowList: false, + providers: map[string]AddressProvider{ + "list": &MockAddressProvider{ + addresses: nil, + }, + }, + addressToCheck: common.HexToAddress("0x11111111111111111111111111"), + expectedAllowed: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + controller := &Controller{ + providers: test.providers, + isAnAllowList: test.isAnAllowList, + } + + key, _ := crypto.GenerateKey() + tx := transaction(012, 1234, key) + allowed := controller.IsAllowed(test.addressToCheck, tx) + + if allowed != test.expectedAllowed { + t.Errorf("Expected allowed=%t, got allowed=%t", test.expectedAllowed, allowed) + } + }) + } +} diff --git a/core/txpool/immutable/accesscontrol/sdn_provider.go b/core/txpool/immutable/accesscontrol/sdn_provider.go new file mode 100644 index 000000000..83c583b4c --- /dev/null +++ b/core/txpool/immutable/accesscontrol/sdn_provider.go @@ -0,0 +1,75 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package accesscontrol + +import ( + "errors" + "os" + "strings" + + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum/go-ethereum/common" +) + +type CSVProvider struct { + filePath string + + // Contains a set of blockchain addresses, struct{} is used here as value to optimize memory footprint + addresses map[common.Address]struct{} +} + +func newCSVProvider(filePath string) (*CSVProvider, error) { + addresses, err := load(filePath) + if err != nil { + return nil, err + } + + return &CSVProvider{ + filePath: filePath, + addresses: addresses, + }, nil +} + +func load(filePath string) (map[common.Address]struct{}, error) { + addresses := make(map[common.Address]struct{}) + + log.Info("Loading ACL file", "filepath", filePath) + byteValue, err := os.ReadFile(filePath) + if err != nil { + return nil, err + } + log.Info("Loaded ACL file", "filepath", filePath, "content", string(byteValue)) + // Split the file content by comma to get individual Ethereum addresses + ethAddresses := strings.Split(string(byteValue), ",") + for _, ethAddress := range ethAddresses { + ethAddress = strings.TrimSpace(ethAddress) // Just to be sure there's no leading or trailing whitespace + if common.IsHexAddress(ethAddress) { + addresses[common.HexToAddress(ethAddress)] = struct{}{} + } + } + // if we can't parse any address, the file might be empty or corrupted + if len(addresses) == 0 { + return nil, errors.New("file is empty or does not contain any valid addresses") + } + + return addresses, nil +} + +func (s *CSVProvider) Provide() map[common.Address]struct{} { + return s.addresses +} diff --git a/core/txpool/immutable/accesscontrol/sdn_provider_test.go b/core/txpool/immutable/accesscontrol/sdn_provider_test.go new file mode 100644 index 000000000..491e76aa7 --- /dev/null +++ b/core/txpool/immutable/accesscontrol/sdn_provider_test.go @@ -0,0 +1,67 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package accesscontrol + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +func TestImmutableCSVProvider_load(t *testing.T) { + tests := []struct { + filePath string + expectedError bool + // Use string here for better readability/debugging + expectedAddress []string + }{ + {"testdata/nonexistent.txt", true, []string{}}, + {"testdata/empty.txt", true, []string{}}, + {"testdata/gibberish.txt", true, []string{}}, + {"testdata/blocklist.txt", false, []string{ + common.HexToAddress("0x72a5843cc08275C8171E582972Aa4fDa8C397B2A").String(), + common.HexToAddress("0x7F19720A857F834887FC9A7bC0a0fBe7Fc7f8102").String(), + common.HexToAddress("0x1da5821544e25c636c1417ba96ade4cf6d2f9b5a").String(), + common.HexToAddress("0x7Db418b5D567A4e0E8c59Ad71BE1FcE48f3E6107").String(), + }}, + {"testdata/newlines.txt", false, []string{ + common.HexToAddress("0x72a5843cc08275C8171E582972Aa4fDa8C397B2A").String(), + common.HexToAddress("0x7F19720A857F834887FC9A7bC0a0fBe7Fc7f8102").String(), + common.HexToAddress("0x1da5821544e25c636c1417ba96ade4cf6d2f9b5a").String(), + common.HexToAddress("0x7Db418b5D567A4e0E8c59Ad71BE1FcE48f3E6107").String(), + }}, + } + + for _, test := range tests { + t.Run(test.filePath, func(t *testing.T) { + sdnProvider, err := newCSVProvider(test.filePath) + + if test.expectedError && err == nil { + t.Errorf("Expected an error, but got none") + } else if !test.expectedError { + require.NoError(t, err, "Expected no error, but got an error") + // Check if the loaded addresses match the expected addresses + loadedAddresses := make([]string, 0, len(sdnProvider.addresses)) + for addr := range sdnProvider.addresses { + loadedAddresses = append(loadedAddresses, addr.String()) + } + require.ElementsMatch(t, test.expectedAddress, loadedAddresses, "Loaded addresses do not match expected addresses") + } + }) + } +} diff --git a/core/txpool/immutable/accesscontrol/testdata/blocklist.txt b/core/txpool/immutable/accesscontrol/testdata/blocklist.txt new file mode 100644 index 000000000..482ce1ccb --- /dev/null +++ b/core/txpool/immutable/accesscontrol/testdata/blocklist.txt @@ -0,0 +1 @@ +0x72a5843cc08275C8171E582972Aa4fDa8C397B2A, 0x7F19720A857F834887FC9A7bC0a0fBe7Fc7f8102, 0x1da5821544e25c636c1417ba96ade4cf6d2f9b5a, 0x7Db418b5D567A4e0E8c59Ad71BE1FcE48f3E6107 diff --git a/core/txpool/immutable/accesscontrol/testdata/empty.txt b/core/txpool/immutable/accesscontrol/testdata/empty.txt new file mode 100644 index 000000000..e69de29bb diff --git a/core/txpool/immutable/accesscontrol/testdata/gibberish.txt b/core/txpool/immutable/accesscontrol/testdata/gibberish.txt new file mode 100644 index 000000000..92e9edc91 --- /dev/null +++ b/core/txpool/immutable/accesscontrol/testdata/gibberish.txt @@ -0,0 +1 @@ +Bob diff --git a/core/txpool/immutable/accesscontrol/testdata/newlines.txt b/core/txpool/immutable/accesscontrol/testdata/newlines.txt new file mode 100644 index 000000000..eb742a97a --- /dev/null +++ b/core/txpool/immutable/accesscontrol/testdata/newlines.txt @@ -0,0 +1,5 @@ +0x72a5843cc08275C8171E582972Aa4fDa8C397B2A, +0x7F19720A857F834887FC9A7bC0a0fBe7Fc7f8102, +0x1da5821544e25c636c1417ba96ade4cf6d2f9b5a, +0x7Db418b5D567A4e0E8c59Ad71BE1FcE48f3E6107, + diff --git a/core/txpool/immutable_access_controller.go b/core/txpool/immutable_access_controller.go new file mode 100644 index 000000000..a09eb2cae --- /dev/null +++ b/core/txpool/immutable_access_controller.go @@ -0,0 +1,30 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package txpool + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +// AccessController defines access control for Ethereum addresses with relation to allowlists and blocklists. +type AccessController interface { + // IsAllowed returns a bool indicating whether an address is allowed to perform its corresponding transaction + IsAllowed(sender common.Address, tx *types.Transaction) bool + // IsBlocklist returns a bool indicating whether the controller is a blocklist type or is an allowlist type + IsBlocklist() bool +} diff --git a/core/txpool/legacypool/immmutable_legacypool_test.go b/core/txpool/legacypool/immmutable_legacypool_test.go new file mode 100644 index 000000000..458338819 --- /dev/null +++ b/core/txpool/legacypool/immmutable_legacypool_test.go @@ -0,0 +1,385 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package legacypool + +import ( + "crypto/ecdsa" + "errors" + "fmt" + "math/big" + "os" + "path/filepath" + "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/state" + "github.com/ethereum/go-ethereum/core/txpool" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/params" +) + +type MockAccessController struct { + isBlocklist bool + isAllowed bool +} + +func (m *MockAccessController) IsBlocklist() bool { + return m.isBlocklist +} + +func (m *MockAccessController) IsAllowed(from common.Address, tx *types.Transaction) bool { + return m.isAllowed +} + +func contractCreation(nonce uint64, gaslimit uint64, key *ecdsa.PrivateKey) *types.Transaction { + tx, _ := types.SignTx(types.NewContractCreation(nonce, big.NewInt(0), gaslimit, big.NewInt(1), nil), types.HomesteadSigner{}, key) + return tx +} + +func pricedTransactionTo(nonce uint64, gaslimit uint64, gasprice *big.Int, key *ecdsa.PrivateKey, to common.Address) *types.Transaction { + tx, _ := types.SignTx(types.NewTransaction(nonce, to, big.NewInt(100), gaslimit, gasprice, nil), types.HomesteadSigner{}, key) + return tx +} + +func TestImmutableFilterWithError(t *testing.T) { + signer := types.NewEIP155Signer(big.NewInt(1337)) + testCases := []struct { + description string + pool *LegacyPool + expectedErr error + }{ + { + description: "Blocked by Blocklist", + pool: &LegacyPool{ + accessControllers: []txpool.AccessController{ + &MockAccessController{isBlocklist: true, isAllowed: false}, + &MockAccessController{isBlocklist: true, isAllowed: false}, + }, + signer: signer, + }, + expectedErr: txpool.ErrTxIsUnauthorized, + }, + { + description: "No access controllers, so it should pass", + pool: &LegacyPool{ + accessControllers: []txpool.AccessController{}, + signer: signer, + }, + expectedErr: nil, + }, + { + description: "An access controller with only a single block list that allows", + pool: &LegacyPool{ + accessControllers: []txpool.AccessController{ + &MockAccessController{isBlocklist: true, isAllowed: true}, + }, + signer: signer, + }, + expectedErr: nil, + }, + } + + key, _ := crypto.GenerateKey() + to := common.HexToAddress("0x1111") + + tx, err := types.SignTx(types.NewTransaction(uint64(10), to, big.NewInt(1000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, key) + if err != nil { + t.Errorf("Couldn't create test transaction: %v", err) + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + err := tc.pool.FilterWithError(tx) + if err != tc.expectedErr { + t.Errorf("Expected error: %v, got: %v", tc.expectedErr, err) + } + }) + } +} + +type filterWithErrorTest struct { + name string + expectedError error + inputTx *types.Transaction +} + +func TestImmutableFilterWithErrorBlocklist(t *testing.T) { + // Fixed keys + sdnKey, _ := crypto.GenerateKey() + sdnAddress := crypto.PubkeyToAddress(sdnKey.PublicKey) + + randomKey, _ := crypto.GenerateKey() + + tests := []filterWithErrorTest{ + + { + name: "BlockedTxBecauseSenderAreInBlocklist", + expectedError: txpool.ErrTxIsUnauthorized, + inputTx: pricedDataTransaction(1, 100000, big.NewInt(1), sdnKey, 50), + }, + { + name: "BlockedTxBecauseReceiverInBlocklist", + expectedError: txpool.ErrTxIsUnauthorized, + inputTx: pricedTransactionTo(1, 100000, big.NewInt(1), randomKey, sdnAddress), + }, + { + name: "NormalTransaction", + expectedError: nil, + inputTx: pricedDataTransaction(1, 100000, big.NewInt(1), randomKey, 50), + }, + } + // Setup test + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed)) + blocklist := filepath.Join(t.TempDir(), "blocklist.txt") + blocklistFile, err := os.Create(blocklist) + if err != nil { + t.Fatal("Failed to create temporary blocklist") + } + + _, err = blocklistFile.WriteString(crypto.PubkeyToAddress(sdnKey.PublicKey).Hex()) + if err != nil { + t.Fatal("Failed to write to blocklist file") + } + config := testTxPoolConfig + config.BlockListFilePaths = []string{blocklist} + + pool := New(config, blockchain) + pool.Init(config.PriceLimit, blockchain.CurrentBlock(), makeAddressReserver()) + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + if err := pool.FilterWithError(tc.inputTx); err != tc.expectedError { + t.Errorf("Test %s: expected error %v, got %v", tc.name, tc.expectedError, err) + } + }) + } +} + +func TestImmutableACLFileWithNewLinesShouldBeBlocked(t *testing.T) { + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed)) + + key, _ := crypto.GenerateKey() + key2, _ := crypto.GenerateKey() + key3, _ := crypto.GenerateKey() + + keys := []*ecdsa.PrivateKey{key, key2, key3} + + addresses := []any{ + crypto.PubkeyToAddress(key.PublicKey).Hex(), + crypto.PubkeyToAddress(key2.PublicKey).Hex(), + crypto.PubkeyToAddress(key3.PublicKey).Hex(), + } + + blocklist := filepath.Join(t.TempDir(), "blocklist.txt") + file, err := os.Create(blocklist) + if err != nil { + t.Fatal("Failed to create temporary file") + } + + _, err = fmt.Fprintf(file, "%s,\n%s,\n%s", addresses...) + if err != nil { + t.Fatal("Failed to write to file") + } + + config := testTxPoolConfig + config.BlockListFilePaths = []string{blocklist} + + pool := New(config, blockchain) + pool.Init(config.PriceLimit, blockchain.CurrentBlock(), makeAddressReserver()) + + for _, key := range keys { + tx := contractCreation(1, 100000, key) + if err := pool.FilterWithError(tx); err != txpool.ErrTxIsUnauthorized { + t.Fatalf("Transaction was not blocked: %v", err) + } + } +} + +func TestImmutableFilterWithErrorBlockedTransaction(t *testing.T) { + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed)) + + key, _ := crypto.GenerateKey() + address := crypto.PubkeyToAddress(key.PublicKey) + + blocklist := filepath.Join(t.TempDir(), "blocklist.txt") + file, err := os.Create(blocklist) + if err != nil { + t.Fatal("Failed to create temporary file") + } + + _, err = file.WriteString(address.Hex()) + if err != nil { + t.Fatal("Failed to write to file") + } + + config := testTxPoolConfig + config.BlockListFilePaths = []string{blocklist} + + pool := New(config, blockchain) + pool.Init(config.PriceLimit, blockchain.CurrentBlock(), makeAddressReserver()) + + tx := contractCreation(1, 100000, key) + + if err := pool.FilterWithError(tx); err != txpool.ErrTxIsUnauthorized { + t.Fatalf("Transaction was not blocked: %v", err) + } +} + +func TestImmutableMinGasPriceEnforced(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) + } +} + +func TestImmutableRebroadcasting(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 2 pending transactions and 1 queued transaction. pending txs should be broadcast + pool.addRemotesSync([]*types.Transaction{ + transaction(0, 100000, key), + transaction(1, 100000, key), + transaction(4, 100000, key), + }) + + pending, queued := pool.Stats() + if pending != 2 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) + } + if queued != 1 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) + } + if err := validateEvents(events, 2); err != nil { + t.Fatalf("add tx event firing failed: %v", err) + } + if err := validatePoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + + // Reset is called by txpool.go when a new block is received. + // We simulate 'a new block' by calling it here + pool.Reset(nil, nil) + + pending, queued = pool.Stats() + if pending != 2 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) + } + if queued != 1 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) + } + // The pending transactions should be rebroadcast. Queued transactions should not. + if err := validateEvents(events, 2); err != nil { + t.Fatalf("rebroadcast event firing failed: %v", err) + } + if err := validatePoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + + // Adding a new pending transaction should only result in the new transaction being broadcast, + // since there was no new block + pool.addRemotesSync([]*types.Transaction{ + transaction(2, 100000, key), + }) + 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) + } + // Only the new transaction should be broadcast + if err := validateEvents(events, 1); err != nil { + t.Fatalf("add tx event firing failed: %v", err) + } + if err := validatePoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + + // A new block should result in rebroadcast of all 3 pending transactions + pool.Reset(nil, nil) + + 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) + } + // The pending transactions should be rebroadcast. Queued transactions should not. + if err := validateEvents(events, 3); err != nil { + t.Fatalf("rebroadcast event firing failed: %v", err) + } + if err := validatePoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } +} diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 4e1d26acf..8da35413a 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -32,6 +32,7 @@ import ( "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/txpool/immutable/accesscontrol" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" @@ -135,6 +136,9 @@ type Config struct { GlobalQueue uint64 // Maximum number of non-executable transaction slots for all accounts Lifetime time.Duration // Maximum amount of time non-executable transaction are queued + + // CHANGE(immutable): Added access controllers filepaths for blocklist + BlockListFilePaths []string `toml:",omitempty"` } // DefaultConfig contains the default configurations for the transaction pool. @@ -151,6 +155,9 @@ var DefaultConfig = Config{ GlobalQueue: 1024, Lifetime: 3 * time.Hour, + + // CHANGE(immutable): Added access controllers filepaths for blocklist + BlockListFilePaths: []string{}, } // sanitize checks the provided user configurations and changes anything that's @@ -189,6 +196,14 @@ func (config *Config) sanitize() Config { log.Warn("Sanitizing invalid txpool lifetime", "provided", conf.Lifetime, "updated", DefaultConfig.Lifetime) conf.Lifetime = DefaultConfig.Lifetime } + // CHANGE(immutable): Ensure each file in BlockListFiles exists + for _, filePath := range config.BlockListFilePaths { + if err := txpool.EnsureFileCanBeRead(filePath); err != nil { + log.Warn("Sanitizing invalid txpool block list filepaths", err.Error(), "provided", conf.BlockListFilePaths, "updated", DefaultConfig.BlockListFilePaths) + conf.BlockListFilePaths = DefaultConfig.BlockListFilePaths + break + } + } return conf } @@ -231,6 +246,9 @@ type LegacyPool struct { 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. + + // CHANGE(immutable): Added a list of access controllers to legacy pool + accessControllers []txpool.AccessController // List of access controllers that determines whether a sender is allowed to perform a tx } type txpoolResetRequest struct { @@ -259,6 +277,8 @@ func New(config Config, chain BlockChain) *LegacyPool { reorgDoneCh: make(chan chan struct{}), reorgShutdownCh: make(chan struct{}), initDoneCh: make(chan struct{}), + // CHANGE(immutable): Added a list of access controllers to legacy pool + accessControllers: []txpool.AccessController{}, } pool.locals = newAccountSet(pool.signer) for _, addr := range config.Locals { @@ -284,6 +304,34 @@ func (pool *LegacyPool) Filter(tx *types.Transaction) bool { } } +// FilterWithError +// CHANGE(immutable): +// Exclude transactions that are not valid based on access control structure +func (pool *LegacyPool) FilterWithError(tx *types.Transaction) error { + if !pool.Filter(tx) { + return core.ErrTxTypeNotSupported + } + from, err := types.Sender(pool.signer, tx) + if err != nil { + return txpool.ErrInvalidSender + } + + if len(pool.accessControllers) == 0 { + // No access controllers, allow the transaction to pass + return nil + } + + // Check for every access controllers that this transaction is allowed to go through + for _, accessControl := range pool.accessControllers { + if !accessControl.IsAllowed(from, tx) { + log.Warn("Transaction is not allowed by access control", "from", from, "tx", tx.Hash(), "isBlockList", accessControl.IsBlocklist()) + // If any access control doesn't allow + return txpool.ErrTxIsUnauthorized + } + } + return nil +} + // 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 @@ -323,6 +371,18 @@ func (pool *LegacyPool) Init(gasTip uint64, head *types.Header, reserve txpool.A log.Warn("Failed to rotate transaction journal", "err", err) } } + + // CHANGE(immutable): Initialize accessControllers on pool based on pool acls config + acls := []txpool.AccessController{} + if len(pool.config.BlockListFilePaths) > 0 { + blockListACL, err := accesscontrol.New(pool.config.BlockListFilePaths, false) + if err != nil { + return err + } + acls = append(acls, blockListACL) + } + pool.accessControllers = acls + pool.wg.Add(1) go pool.loop() return nil @@ -613,7 +673,8 @@ func (pool *LegacyPool) validateTxBasics(tx *types.Transaction, local bool) erro MaxSize: txMaxSize, MinTip: pool.gasTip.Load().ToBig(), } - if local { + // CHANGE(immutable): check if NoLocals has been set to reject underpriced transactions + if local && !pool.config.NoLocals { opts.MinTip = new(big.Int) } if err := txpool.ValidateTransaction(tx, pool.currentHead.Load(), pool.signer, opts); err != nil { @@ -998,6 +1059,7 @@ func (pool *LegacyPool) Add(txs []*types.Transaction, local, sync bool) []error invalidTxMeter.Mark(1) continue } + // Accumulate all unknown transactions for deeper processing news = append(news, tx) } @@ -1318,6 +1380,19 @@ func (pool *LegacyPool) runReorg(done chan struct{}, reset *txpoolResetRequest, dropBetweenReorgHistogram.Update(int64(pool.changesSinceReorg)) pool.changesSinceReorg = 0 // Reset change counter + + // CHANGE(immutable): Get the list of pending transactions and add them to a rebroadcast list + // Pending transactions are those that could be included in a block successfully, as opposed to the + // queued transactions which are not ready to be included in a block. Only pending transactions are + // rebroadcast to avoid making the network too noisy. We only want to rebroadcast on a new block + // hence, we check if reset is nil. Reset will be non-nil if a new block was received. + rebroadcastList := make([]types.Transactions, 0, len(pool.pending)) + if reset != nil { + for _, txs := range pool.pending { + rebroadcastList = append(rebroadcastList, txs.Flatten()) + } + } + pool.mu.Unlock() // Notify subsystems for newly added transactions @@ -1328,12 +1403,29 @@ func (pool *LegacyPool) runReorg(done chan struct{}, reset *txpoolResetRequest, } events[addr].Put(tx) } - if len(events) > 0 { - var txs []*types.Transaction - for _, set := range events { - txs = append(txs, set.Flatten()...) + + // CHANGE(immutable): deduplicate the broadcast lists so that transactions are not broadcast multiple times + // at the same time. + uniqueTxs := make(map[common.Hash]*types.Transaction) + for _, set := range events { + for _, tx := range set.Flatten() { + uniqueTxs[tx.Hash()] = tx + } + } + for _, set := range rebroadcastList { + for _, tx := range set { + uniqueTxs[tx.Hash()] = tx + } + } + var txs []*types.Transaction + for _, tx := range uniqueTxs { + txs = append(txs, tx) + } + + if len(txs) > 0 { + if numSubscribers := pool.txFeed.Send(core.NewTxsEvent{Txs: txs}); numSubscribers == 0 { + log.Warn("Transaction pool is not being rebroadcast to any subscribers") } - pool.txFeed.Send(core.NewTxsEvent{Txs: txs}) } } diff --git a/core/txpool/subpool.go b/core/txpool/subpool.go index 9881ed1b8..c7ab4e5fa 100644 --- a/core/txpool/subpool.go +++ b/core/txpool/subpool.go @@ -95,6 +95,10 @@ type SubPool interface { // to this particular subpool. Filter(tx *types.Transaction) bool + // FilterWithError is a selector used to decide whether a transaction whould be added + // to this particular subpool. + FilterWithError(tx *types.Transaction) error + // 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. diff --git a/core/txpool/txpool.go b/core/txpool/txpool.go index be7435247..13349cf68 100644 --- a/core/txpool/txpool.go +++ b/core/txpool/txpool.go @@ -315,19 +315,31 @@ func (p *TxPool) Add(txs []*types.Transaction, local bool, sync bool) []error { // // 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. + // + // CHANGE(immutable): Adding a error to the subpool split to indicate the specifity of + // why this transaction is not included in any subpool + type SplitError struct { + index int + err error + } txsets := make([][]*types.Transaction, len(p.subpools)) - splits := make([]int, len(txs)) + splits := make([]SplitError, 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) { + err := subpool.FilterWithError(tx) + if err == nil { txsets[j] = append(txsets[j], tx) - splits[i] = j + splits[i] = SplitError{index: j, err: nil} + break + } else if err == ErrTxIsUnauthorized { + // CHANGE(immutable): Subpool exists that handles this tx but it is blocked and/or unauthorized + splits[i] = SplitError{index: j, err: err} break + } else { + // Mark this transaction belonging to no-subpool + splits[i] = SplitError{index: -1, err: err} } } } @@ -340,13 +352,13 @@ func (p *TxPool) Add(txs []*types.Transaction, local bool, sync bool) []error { 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 + if split.err != nil { + errs[i] = split.err continue } // Find which subpool handled it and pull in the corresponding error - errs[i] = errsets[split][0] - errsets[split] = errsets[split][1:] + errs[i] = errsets[split.index][0] + errsets[split.index] = errsets[split.index][1:] } return errs } diff --git a/core/txpool/validation.go b/core/txpool/validation.go index 8913859e8..520556027 100644 --- a/core/txpool/validation.go +++ b/core/txpool/validation.go @@ -20,6 +20,7 @@ import ( "crypto/sha256" "fmt" "math/big" + "os" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" @@ -72,6 +73,10 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types 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()) } + // CHANGE(immutable): Reject blob transactions + if opts.Config.IsImmutableZKEVM() && tx.Type() == types.BlobTxType { + return fmt.Errorf("%w: type %d rejected, blob transactions not supported", 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) @@ -247,3 +252,13 @@ func ValidateTransactionWithState(tx *types.Transaction, signer types.Signer, op } return nil } + +// EnsureFileCanBeRead checks if a file exists at the specified path +// CHANGE(immutable): utility function to help check for file existence +func EnsureFileCanBeRead(filePath string) error { + _, err := os.Stat(filePath) + if err != nil { + return fmt.Errorf("File couldn't be read: %s, %w", filePath, err) + } + return nil +} diff --git a/core/types/block.go b/core/types/block.go index 1a357baa3..ae2225e6d 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -165,6 +165,12 @@ func (h *Header) EmptyReceipts() bool { return h.ReceiptHash == EmptyReceiptsHash } +// EmptyWithdrawalsHash returns true if the WithdrawalsHash is EmptyWithdrawalsHash. +// CHANGE(immutable): Add EmptyWithdrawalsHash +func (h *Header) EmptyWithdrawalsHash() bool { + return h.WithdrawalsHash != nil && *h.WithdrawalsHash == EmptyWithdrawalsHash +} + // 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 { diff --git a/diff/README.md b/diff/README.md new file mode 100644 index 000000000..766e9604a --- /dev/null +++ b/diff/README.md @@ -0,0 +1,19 @@ +# Immutable Geth Fork Diff + +You can use this file to generate an `index.html` containing a structured breakdown of all the diffs introduced by Immutable Geth from upstream. + +## How To + +First install the tool forkdiff: +``` +go install github.com/protolambda/forkdiff +``` + +The tool does not seem to work with remote repositories. So you must manually clone and set up the tags you want to work with. + +For example, if you fetch and tag appropriately in this repository, you can use this command: +``` +forkdiff -fork ./diff/fork.yaml -repo . -upstream-repo . -out /tmp/index.html +``` + +Check the expected tags in the `diff/fork.yaml` file. diff --git a/diff/fork.yaml b/diff/fork.yaml new file mode 100644 index 000000000..222a63d80 --- /dev/null +++ b/diff/fork.yaml @@ -0,0 +1,168 @@ +title: "immutable-geth - go-ethereum fork diff overview" +footer: | + Fork-diff overview of [`immutable-geth`](https://github.com/immutable/geth), a fork of [`go-ethereum`](https://github.com/ethereum/go-ethereum). +base: + name: go-ethereum + url: https://github.com/ethereum/go-ethereum + ref: refs/tags/v1.13.15 +fork: + name: immutable-geth + url: https://github.com/immutable/go-ethereum + ref: refs/tags/audit.1 +def: + title: "immutable-geth" + description: | + All modifications made by Immutable to the go-ethereum codebase are listed here. + The diff is based on the `v1.13.15` tag of go-ethereum and the `audit.1` tag of immutable-geth. + sub: + # Uncomment these to pull the diffs out of the main pkg sections + #- title: "Priority Diffs" + # description: This section contains prioritized for the purposes of security analysis + # globs: + # - "core/blockchain.go" + # - "core/evm.go" + # - "core/state_processor.go" + # - "core/types/block.go" + # - "core/txpool/validation.go" + # - "core/txpool/immutable/accesscontrol/contract_creation_controller.go" + # - "core/txpool/immutable/accesscontrol/controller.go" + # - "core/txpool/immutable/accesscontrol/sdn_provider.go" + # - "core/txpool/legacypool/legacypool.go" + # - "core/txpool/txpool.go" + # - "params/config.go" + # - "params/protocol_params.go" + # - "node/api.go" + # - "node/immutable_newrelic.go" + # - "node/node.go" + # - "node/rpcstack.go" + # - "consensus/clique/clique.go" + # - "miner/worker.go" + # - "cmd/geth/config.go" + # - "cmd/immutable/remote/aws/secretsmanager.go" + - title: "Go Source Changes" + description: This section contains diffs organized by each root Go pkg of the repository. + sub: + - title: "core" + description: "" + sub: + - title: "Reorg Invariant" + globs: + - "core/blockchain.go" + - title: "Immutable Genesis / Forks" + globs: + - "core/genesis.go" + - "core/immutable_genesis.go" + - "core/evm.go" + - "core/gen_genesis.go" + - "core/forkid/forkid.go" + - title: "Blobs" + globs: + - "core/txpool/blobpool/blobpool.go" + - "core/txpool/blobpool/limbo.go" + - "core/txpool/validation.go" + - "core/state_processor.go" + - title: "ACL" + globs: + - "core/**/accesscontrol/**/*.go" + - "core/txpool/errors.go" + - "core/txpool/legacypool/legacypool.go" + - "core/txpool/subpool.go" + - "core/txpool/txpool.go" + - "core/txpool/immutable_access_controller.go" + globs: + - "core/**/*.go" + - title: "node" + description: "" + sub: + - title: "New Relic" + globs: + - "node/api.go" + - "node/immutable_newrelic.go" + - "node/node.go" + - title: "RPC" + globs: + - "node/rpcstack.go" + - "node/defaults.go" + - "node/config.go" + globs: + - "node/**/*.go" + - title: "consensus" + description: "" + globs: + - "consensus/**/*.go" + - title: "miner" + description: "" + globs: + - "miner/**/*.go" + - title: "common" + description: "" + globs: + - "common/**/*.go" + - title: "internal" + description: "" + globs: + - "internal/**/*.go" + - title: "cmd" + description: "" + sub: + - title: "Bootstrap" + description: "Logic for bootstrapping Immutable geth instances both locally and inside K8s pods" + globs: + - "cmd/geth/immutable.go" + - "cmd/geth/immutable/**/*.go" + - "cmd/**/immutable_*.go" + - "cmd/immutable/*.go" + - title: "Sequencer" + globs: + - "cmd/geth/config.go" + - "cmd/immutable/remote/aws/**/*.go" + globs: + - "cmd/**/*.go" + - title: "accounts" + description: "" + globs: + - "accounts/**/*.go" + - title: "p2p" + description: "" + globs: + - "p2p/**/*.go" + - title: "eth" + description: "" + globs: + - "eth/**/*.go" + - "internal/ethapi/api.go" + - title: "params" + description: "" + globs: + - "params/**/*.go" + - title: "ethclient" + description: "" + globs: + - "ethclient/gethclient/immutable_gethclient.go" + - title: "e2e" + description: "E2E test suite for Immutable geth" + globs: + - "tests/immutable/**/*" + - title: "Configurations" + description: Miscellaneous configuration files for Immutable geth. + globs: + - "**/genesis/**.json" + - "immutable/**/*.toml" + +# ignored globally, does not count towards line count +ignore: + - "**/*_test.go" + - "tests/state_test_util.go" + - "**/testdata/**" + - "**/*t8ntool/*.go" + - ".circleci/*" + - "deployment/**/*" + - "*.sum" + - "go.mod" + - "fork.yaml" + - "Makefile" + - ".golangci.yml" + - ".github/**" + - "**/*.gob" # data asset, not code + - "core/vm/testdata/precompiles/p256Verify.json" # data asset, not code + - "eth/tracers/internal/tracetest/testdata/**/*.json" diff --git a/eth/api_backend.go b/eth/api_backend.go index 65adccd85..99e839db8 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -19,12 +19,14 @@ package eth import ( "context" "errors" + "fmt" "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/common/hexutil" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/bloombits" @@ -37,11 +39,17 @@ import ( "github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" ) +var ( + // CHANGE(immutable): Add metric for tipcap + suggestTipCapGauge = metrics.NewRegisteredGauge("api/tipcap", nil) +) + // EthAPIBackend implements ethapi.Backend and tracers.Backend for full nodes type EthAPIBackend struct { extRPCEnabled bool @@ -258,7 +266,7 @@ func (b *EthAPIBackend) GetEVM(ctx context.Context, msg *core.Message, state *st if blockCtx != nil { context = *blockCtx } else { - context = core.NewEVMBlockContext(header, b.eth.BlockChain(), nil) + context = core.NewEVMBlockContext(header, b.eth.BlockChain(), nil, b.ChainConfig()) } return vm.NewEVM(context, txContext, state, b.ChainConfig(), *vmConfig) } @@ -288,6 +296,15 @@ func (b *EthAPIBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscri } func (b *EthAPIBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error { + // CHANGE(immutable): Handle RPC forwarding. + if b.eth.rpcProxyClient != nil { + data, err := signedTx.MarshalBinary() + if err != nil { + return fmt.Errorf("failed to marshall binary for rpc proxy forwarding: %s", err.Error()) + } + return b.eth.rpcProxyClient.CallContext(ctx, nil, "eth_sendRawTransaction", hexutil.Encode(data)) + } + return b.eth.txPool.Add([]*types.Transaction{signedTx}, true, false)[0] } @@ -363,7 +380,13 @@ func (b *EthAPIBackend) SyncProgress() ethereum.SyncProgress { } func (b *EthAPIBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { - return b.gpo.SuggestTipCap(ctx) + // CHANGE(immutable): Record metric for tipcap + tipcap, err := b.gpo.SuggestTipCap(ctx) + if err != nil { + return nil, err + } + suggestTipCapGauge.Update(tipcap.Int64()) + return tipcap, nil } 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, err error) { diff --git a/eth/backend.go b/eth/backend.go index 0a0813aaf..54386061c 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -18,11 +18,13 @@ package eth import ( + "context" "errors" "fmt" "math/big" "runtime" "sync" + "time" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" @@ -101,6 +103,9 @@ type Ethereum struct { 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 + + // CHANGE(immutable): Add RPC client for forwarding to Immutable RPC. + rpcProxyClient *rpc.Client } // New creates a new Ethereum object (including the @@ -213,6 +218,13 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { if config.OverrideVerkle != nil { overrides.OverrideVerkle = config.OverrideVerkle } + // CHANGE(immutable): Add Prevrandao and Shanghai overrides + if config.OverridePrevrandao != nil { + overrides.OverridePrevrandao = config.OverridePrevrandao + } + if config.OverrideShanghai != nil { + overrides.OverrideShanghai = config.OverrideShanghai + } eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, config.Genesis, &overrides, eth.engine, vmConfig, eth.shouldPreserve, &config.TransactionHistory) if err != nil { return nil, err @@ -245,6 +257,10 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { BloomCache: uint64(cacheLimit), EventMux: eth.eventMux, RequiredBlocks: config.RequiredBlocks, + // CHANGE(immutable): gossip configuration + GossipDefault: config.GossipDefault, + // CHANGE(immutable): disable txpool gossip configuration + DisableTxPoolGossip: config.DisableTxPoolGossip, }); err != nil { return nil, err } @@ -273,6 +289,17 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { return nil, err } + // CHANGE(immutable): Handle immutable RPC client. + if config.RPCProxyURL != "" { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + client, err := rpc.DialContext(ctx, config.RPCProxyURL) + if err != nil { + return nil, err + } + eth.rpcProxyClient = client + } + // Start the RPC service eth.netRPCService = ethapi.NewNetAPI(eth.p2pServer, networkID) @@ -332,8 +359,7 @@ func (s *Ethereum) APIs() []rpc.API { }, { Namespace: "net", Service: s.netRPCService, - }, - }...) + }}...) } func (s *Ethereum) ResetWithGenesisBlock(gb *types.Block) { @@ -548,5 +574,10 @@ func (s *Ethereum) Stop() error { s.chainDb.Close() s.eventMux.Stop() + // CHANGE(immutable): Handle shutdown of client. + if s.rpcProxyClient != nil { + s.rpcProxyClient.Close() + } + return nil } diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index 6e7c5dcf0..78a0967a5 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -34,6 +34,7 @@ import ( "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/triedb" ) @@ -56,6 +57,9 @@ var ( 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 + + // CHANGE(immutable): Add metrics + syncDurationTimer = metrics.NewRegisteredTimer("downloader/sync", nil) ) var ( @@ -472,6 +476,8 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td, ttd * } defer func(start time.Time) { log.Debug("Synchronisation terminated", "elapsed", common.PrettyDuration(time.Since(start))) + // CHANGE(immutable): Record sync duration metric + syncDurationTimer.Update(time.Since(start)) }(time.Now()) // Look up the sync boundaries: the common ancestor and the target block diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index ad664afb5..1ff6951a7 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -159,6 +159,19 @@ type Config struct { // OverrideVerkle (TODO: remove after the fork) OverrideVerkle *uint64 `toml:",omitempty"` + + // CHANGE(immutable): Add Prevrandao and Shanghai overrides. + OverridePrevrandao *uint64 `toml:",omitempty"` + OverrideShanghai *uint64 `toml:",omitempty"` + + // CHANGE(immutable): Add gossip configuration. + GossipDefault bool `toml:",omitempty"` + + // CHANGE(immutable): Disable txpool gossip configuration. + DisableTxPoolGossip bool `toml:",omitempty"` + + // CHANGE(immutable): Proxy to Immutable RPC configuration. + RPCProxyURL string `toml:",omitempty"` } // CreateConsensusEngine creates a consensus engine for the given chain config. diff --git a/eth/fetcher/block_fetcher.go b/eth/fetcher/block_fetcher.go index 126eaaea7..f4eda6e66 100644 --- a/eth/fetcher/block_fetcher.go +++ b/eth/fetcher/block_fetcher.go @@ -26,6 +26,7 @@ import ( "github.com/ethereum/go-ethereum/common/prque" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/fetcher/long" "github.com/ethereum/go-ethereum/eth/protocols/eth" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" @@ -40,13 +41,18 @@ const ( ) const ( - maxUncleDist = 7 // Maximum allowed backward distance from the chain head - maxQueueDist = 32 // Maximum allowed distance from the chain head to queue + // CHANGE(immutable): Set maxUncleDist to 0 to prevent chains from acceptable blocks older + // than the current head, resulting in reorgs + maxUncleDist = 0 // Maximum allowed backward distance from the chain head hashLimit = 256 // Maximum number of unique blocks or headers a peer may have announced - blockLimit = 64 // Maximum number of unique blocks a peer may have delivered + // CHANGE(immutable): Set blockLimit to 256 as all blocks are coming from a handful of peers + blockLimit = 256 // Maximum number of unique blocks a peer may have delivered ) var ( + // CHANGE(immutable): Set maxQueueDist to a large number if env var is set + maxQueueDist = long.BlockFetchDistance() // Maximum allowed distance from the chain head to queue + blockAnnounceInMeter = metrics.NewRegisteredMeter("eth/fetcher/block/announces/in", nil) blockAnnounceOutTimer = metrics.NewRegisteredTimer("eth/fetcher/block/announces/out", nil) blockAnnounceDropMeter = metrics.NewRegisteredMeter("eth/fetcher/block/announces/drop", nil) @@ -125,7 +131,9 @@ type bodyFilterTask struct { peer string // The source peer of block bodies transactions [][]*types.Transaction // Collection of transactions per block bodies uncles [][]*types.Header // Collection of uncles per block bodies - time time.Time // Arrival time of the blocks' contents + // CHANGE(immutable): Block Fetcher tasks can have withdrawals + withdrawals [][]*types.Withdrawal // Collection of withdrawals per block bodies + time time.Time // Arrival time of the blocks' contents } // blockOrHeaderInject represents a schedules import operation. @@ -302,7 +310,7 @@ func (f *BlockFetcher) FilterHeaders(peer string, headers []*types.Header, time // FilterBodies extracts all the block bodies that were explicitly requested by // the fetcher, returning those that should be handled differently. -func (f *BlockFetcher) FilterBodies(peer string, transactions [][]*types.Transaction, uncles [][]*types.Header, time time.Time) ([][]*types.Transaction, [][]*types.Header) { +func (f *BlockFetcher) FilterBodies(peer string, transactions [][]*types.Transaction, uncles [][]*types.Header, withdrawals [][]*types.Withdrawal, time time.Time) ([][]*types.Transaction, [][]*types.Header, [][]*types.Withdrawal) { log.Trace("Filtering bodies", "peer", peer, "txs", len(transactions), "uncles", len(uncles)) // Send the filter channel to the fetcher @@ -311,20 +319,20 @@ func (f *BlockFetcher) FilterBodies(peer string, transactions [][]*types.Transac select { case f.bodyFilter <- filter: case <-f.quit: - return nil, nil + return nil, nil, nil } // Request the filtering of the body list select { - case filter <- &bodyFilterTask{peer: peer, transactions: transactions, uncles: uncles, time: time}: + case filter <- &bodyFilterTask{peer: peer, transactions: transactions, uncles: uncles, withdrawals: withdrawals, time: time}: case <-f.quit: - return nil, nil + return nil, nil, nil } // Retrieve the bodies remaining after filtering select { case task := <-filter: - return task.transactions, task.uncles + return task.transactions, task.uncles, task.withdrawals case <-f.quit: - return nil, nil + return nil, nil, nil } } @@ -396,7 +404,7 @@ func (f *BlockFetcher) loop() { break } // If we have a valid block number, check that it's potentially useful - if dist := int64(notification.number) - int64(f.chainHeight()); dist < -maxUncleDist || dist > maxQueueDist { + if dist := int64(notification.number) - int64(f.chainHeight()); dist < -maxUncleDist || dist > int64(maxQueueDist) { log.Debug("Peer discarded announcement", "peer", notification.origin, "number", notification.number, "hash", notification.hash, "distance", dist) blockAnnounceDropMeter.Mark(1) break @@ -541,8 +549,9 @@ func (f *BlockFetcher) loop() { case res := <-resCh: res.Done <- nil // Ignoring withdrawals here, since the block fetcher is not used post-merge. - txs, uncles, _ := res.Res.(*eth.BlockBodiesResponse).Unpack() - f.FilterBodies(peer, txs, uncles, time.Now()) + // CHANGE(immutable): We continue to use block fetcher in a pre-merge network with withdrawals + txs, uncles, withdrawals := res.Res.(*eth.BlockBodiesResponse).Unpack() + f.FilterBodies(peer, txs, uncles, withdrawals, time.Now()) case <-timeout.C: // The peer didn't respond in time. The request @@ -660,12 +669,16 @@ func (f *BlockFetcher) loop() { blocks := []*types.Block{} // abort early if there's nothing explicitly requested if len(f.completing) > 0 { - for i := 0; i < len(task.transactions) && i < len(task.uncles); i++ { + // CHANGE(immutable): `i` should also consider the length of withdrawals. However, transactions, uncles, + // and withdrawals should be the same length given that they are set in BlockBodiesResponse.Unpack(). + // By adding it here, we enforce this assumption + for i := 0; i < len(task.transactions) && i < len(task.uncles) && i < len(task.withdrawals); i++ { // Match up a body to any possible completion request var ( - matched = false - uncleHash common.Hash // calculated lazily and reused - txnHash common.Hash // calculated lazily and reused + matched = false + uncleHash common.Hash // calculated lazily and reused + txnHash common.Hash // calculated lazily and reused + withdrawalsHash common.Hash // calculated lazily and reused ) for hash, announce := range f.completing { if f.queued[hash] != nil || announce.origin != task.peer { @@ -683,10 +696,31 @@ func (f *BlockFetcher) loop() { if txnHash != announce.header.TxHash { continue } + + // CHANGE(immutable): Just as the transactions trie hash is verified against the transactions, + // the same must be done with withdrawals for any given block as we cannot assume that the header hash + // is correctly derived from the body + // If the header withdrawals hash is nil, the withdrawals must be nil + if announce.header.WithdrawalsHash == nil { + if task.withdrawals[i] != nil { + log.Warn("Discarded block due to mismatched withdrawalsHash, expected nil") + continue + } + } else { + // If the withdrawals hash is not nil, the derived hash should match + if withdrawalsHash == (common.Hash{}) { + withdrawalsHash = types.DeriveSha(types.Withdrawals(task.withdrawals[i]), trie.NewStackTrie(nil)) + } + if withdrawalsHash != *announce.header.WithdrawalsHash { + log.Warn("Discarded block due to mismatched withdrawalsHash", "expected", *announce.header.WithdrawalsHash, "received", withdrawalsHash) + continue + } + } + // Mark the body matched, reassemble if still unknown matched = true if f.getBlock(hash) == nil { - block := types.NewBlockWithHeader(announce.header).WithBody(task.transactions[i], task.uncles[i]) + block := types.NewBlockWithHeader(announce.header).WithBody(task.transactions[i], task.uncles[i]).WithWithdrawals(task.withdrawals[i]) block.ReceivedAt = task.time blocks = append(blocks, block) } else { @@ -696,6 +730,7 @@ func (f *BlockFetcher) loop() { if matched { task.transactions = append(task.transactions[:i], task.transactions[i+1:]...) task.uncles = append(task.uncles[:i], task.uncles[i+1:]...) + task.withdrawals = append(task.withdrawals[:i], task.withdrawals[i+1:]...) i-- continue } @@ -770,14 +805,16 @@ func (f *BlockFetcher) enqueue(peer string, header *types.Header, block *types.B // Ensure the peer isn't DOSing us count := f.queues[peer] + 1 if count > blockLimit { - log.Debug("Discarded delivered header or block, exceeded allowance", "peer", peer, "number", number, "hash", hash, "limit", blockLimit) + // CHANGE(immutable): Changed to warning log as we only expect blocks from trusted peers that should be giving us valid blocks + log.Warn("Discarded delivered header or block, exceeded allowance", "peer", peer, "number", number, "hash", hash, "limit", blockLimit) blockBroadcastDOSMeter.Mark(1) f.forgetHash(hash) return } // Discard any past or too distant blocks - if dist := int64(number) - int64(f.chainHeight()); dist < -maxUncleDist || dist > maxQueueDist { - log.Debug("Discarded delivered header or block, too far away", "peer", peer, "number", number, "hash", hash, "distance", dist) + if dist := int64(number) - int64(f.chainHeight()); dist < -maxUncleDist || dist > int64(maxQueueDist) { + // CHANGE(immutable): Changed to warning log as we only expect blocks from trusted peers that should be giving us valid blocks + log.Warn("Discarded delivered header or block, too far away", "peer", peer, "number", number, "hash", hash, "distance", dist) blockBroadcastDropMeter.Mark(1) f.forgetHash(hash) return @@ -850,6 +887,12 @@ func (f *BlockFetcher) importBlocks(peer string, block *types.Block) { log.Debug("Unknown parent of propagated block", "peer", peer, "number", block.Number(), "hash", hash, "parent", block.ParentHash()) return } + + // CHANGE(immutable): Ensure empty withdrawals after cancun before broadcast + if block.Header().EmptyWithdrawalsHash() { + block = block.WithWithdrawals(make([]*types.Withdrawal, 0)) + } + // Quickly validate the header and propagate the block if it passes switch err := f.verifyHeader(block.Header()); err { case nil: diff --git a/eth/fetcher/block_fetcher_test.go b/eth/fetcher/block_fetcher_test.go index cb7cbaf79..a55e5ca88 100644 --- a/eth/fetcher/block_fetcher_test.go +++ b/eth/fetcher/block_fetcher_test.go @@ -880,7 +880,7 @@ func TestHashMemoryExhaustionAttack(t *testing.T) { } tester.fetcher.Notify("attacker", attack[i], 1 /* don't distance drop */, time.Now(), attackerHeaderFetcher, attackerBodyFetcher) } - if count := announces.Load(); count != hashLimit+maxQueueDist { + if count := announces.Load(); count != hashLimit+int32(maxQueueDist) { t.Fatalf("queued announce count mismatch: have %d, want %d", count, hashLimit+maxQueueDist) } // Wait for fetches to complete @@ -933,7 +933,7 @@ func TestBlockMemoryExhaustionAttack(t *testing.T) { tester.fetcher.Enqueue("valid", blocks[hashes[len(hashes)-3-i]]) } time.Sleep(100 * time.Millisecond) - if queued := enqueued.Load(); queued != blockLimit+maxQueueDist-1 { + if queued := enqueued.Load(); queued != blockLimit+int32(maxQueueDist)-1 { t.Fatalf("queued block count mismatch: have %d, want %d", queued, blockLimit+maxQueueDist-1) } // Insert the missing piece (and sanity check the import) diff --git a/eth/fetcher/long/immutable_long.go b/eth/fetcher/long/immutable_long.go new file mode 100644 index 000000000..2127f0b2a --- /dev/null +++ b/eth/fetcher/long/immutable_long.go @@ -0,0 +1,51 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package long + +import ( + "math" + "os" + + "github.com/ethereum/go-ethereum/log" +) + +const ( + longSyncEnvVar = "GETH_FLAG_IMMUTABLE_LONG_RANGE_SYNC" +) + +var ( + // blockFetchDistance is increased if the relevant env var is set. + blockFetchDistance = 256 +) + +func init() { + // Enable long fetch if env var is set. + if isLongFetch := os.Getenv(longSyncEnvVar); isLongFetch != "" { + log.Info("Long range sync enabled") + blockFetchDistance = math.MaxInt32 + } else { + log.Info("Long range sync disabled") + } +} + +// BlockFetchDistance returns the maximum number of blocks that should be accepted +// when fetching blocks from peers. +// The value will be large enough for any range of blocks if the relevant env var is set. +// Otherwise it will return a reasonable value for normal operation. +func BlockFetchDistance() int { + return blockFetchDistance +} diff --git a/eth/fetcher/tx_fetcher.go b/eth/fetcher/tx_fetcher.go index ea7892d8d..9ca0480ae 100644 --- a/eth/fetcher/tx_fetcher.go +++ b/eth/fetcher/tx_fetcher.go @@ -29,6 +29,7 @@ import ( "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/txpool/legacypool" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" @@ -105,6 +106,10 @@ var ( 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) + + // CHANGE(immutable): Add metric for tx pool overflow as alert condition. Metric alerting is preffered + // over log alerting https://immutable.atlassian.net/wiki/spaces/SRE/pages/2184086495/Newrelic+Data+Types+-+What+and+When+to+use + txPoolOverflowCounter = metrics.NewRegisteredCounter("eth/fetcher/transaction/txpool/overflow", nil) ) // txAnnounce is the notification of the availability of a batch @@ -351,6 +356,17 @@ func (f *TxFetcher) Enqueue(peer string, txs []*types.Transaction, direct bool) otherreject++ } added = append(added, batch[j].Hash()) + + // CHANGE(immutable) add tx pool logging and metrics, this includes user errors but can also + // indicate other system level errors. + if err != nil { + if errors.Is(err, legacypool.ErrTxPoolOverflow) { + log.Error("tx pool overflow", "err", err, "tx", batch[j].Hash().String()) + txPoolOverflowCounter.Inc(1) + } else { + log.Info("failed to add tx", "err", err, "tx", batch[j].Hash().String()) + } + } metas = append(metas, txMetadata{ kind: batch[j].Type(), size: uint32(batch[j].Size()), diff --git a/eth/filters/filter.go b/eth/filters/filter.go index 83e3284a2..b2760c7bd 100644 --- a/eth/filters/filter.go +++ b/eth/filters/filter.go @@ -19,6 +19,7 @@ package filters import ( "context" "errors" + "fmt" "math/big" "github.com/ethereum/go-ethereum/common" @@ -27,6 +28,9 @@ import ( "github.com/ethereum/go-ethereum/rpc" ) +// CHANGE(immutable): Add a max filter range for logs queried using getLogs +const maxFilterBlockRange = 5000 + // Filter can be used to retrieve and filter logs. type Filter struct { sys *FilterSystem @@ -157,6 +161,12 @@ func (f *Filter) Logs(ctx context.Context) ([]*types.Log, error) { return nil, err } + // CHANGE(immutable): Add a max filter block range + // TODO: Change this to a CLI flag + if f.end-f.begin > maxFilterBlockRange { + return nil, fmt.Errorf("exceeded maximum block range: %d", maxFilterBlockRange) + } + logChan, errChan := f.rangeLogsAsync(ctx) var logs []*types.Log for { diff --git a/eth/gasestimator/gasestimator.go b/eth/gasestimator/gasestimator.go index f07f98956..6f18c1ed5 100644 --- a/eth/gasestimator/gasestimator.go +++ b/eth/gasestimator/gasestimator.go @@ -208,7 +208,7 @@ func run(ctx context.Context, call *core.Message, opts *Options) (*core.Executio // Assemble the call and the call context var ( msgContext = core.NewEVMTxContext(call) - evmContext = core.NewEVMBlockContext(opts.Header, opts.Chain, nil) + evmContext = core.NewEVMBlockContext(opts.Header, opts.Chain, nil, opts.Config) dirtyState = opts.State.Copy() evm = vm.NewEVM(evmContext, msgContext, dirtyState, opts.Config, vm.Config{NoBaseFee: true}) diff --git a/eth/gasprice/gasprice.go b/eth/gasprice/gasprice.go index b71964981..40c33ea37 100644 --- a/eth/gasprice/gasprice.go +++ b/eth/gasprice/gasprice.go @@ -21,6 +21,7 @@ import ( "math/big" "sync" + "github.com/ethereum/go-ethereum/cmd/geth/immutable/settings" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/lru" "github.com/ethereum/go-ethereum/core" @@ -193,7 +194,12 @@ func (oracle *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) { // - 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} + // CHANGE(immutable): Handle empty blocks + if oracle.backend.ChainConfig().IsImmutableZKEVM() { + res.values = []*big.Int{big.NewInt(settings.PriceLimit)} + } else { + 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 @@ -214,6 +220,13 @@ func (oracle *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) { if price.Cmp(oracle.maxPrice) > 0 { price = new(big.Int).Set(oracle.maxPrice) } + // CHANGE(immutable): Enforce the price limit + if oracle.backend.ChainConfig().IsImmutableZKEVM() { + limit := big.NewInt(settings.PriceLimit) + if price.Cmp(limit) < 0 { + price = limit + } + } oracle.cacheLock.Lock() oracle.lastHead = headHash oracle.lastPrice = price diff --git a/eth/gasprice/immutable_gasprice_test.go b/eth/gasprice/immutable_gasprice_test.go new file mode 100644 index 000000000..335a95781 --- /dev/null +++ b/eth/gasprice/immutable_gasprice_test.go @@ -0,0 +1,242 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package gasprice + +import ( + "context" + "math" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/cmd/geth/immutable/settings" + "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/event" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rpc" +) + +type testImmutableBackend struct { + chain *core.BlockChain + pending bool // pending block available + latest int64 +} + +func (b *testImmutableBackend) 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 = rpc.BlockNumber(b.latest) + } + if number == rpc.PendingBlockNumber { + if b.pending { + number = rpc.BlockNumber(b.latest) + 1 + } else { + return nil, nil + } + } + return b.chain.GetHeaderByNumber(uint64(number)), nil +} + +func (b *testImmutableBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) { + if number > rpc.BlockNumber(b.latest) { + 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 = rpc.BlockNumber(b.latest) + } + if number == rpc.PendingBlockNumber { + if b.pending { + number = rpc.BlockNumber(b.latest) + 1 + } else { + return nil, nil + } + } + return b.chain.GetBlockByNumber(uint64(number)), nil +} + +func (b *testImmutableBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) { + return b.chain.GetReceiptsByHash(hash), nil +} + +func (b *testImmutableBackend) PendingBlockAndReceipts() (*types.Block, types.Receipts) { + if b.pending { + block := b.chain.GetBlockByNumber(uint64(b.latest) + 1) + return block, b.chain.GetReceiptsByHash(block.Hash()) + } + return nil, nil +} + +func (b *testImmutableBackend) ChainConfig() *params.ChainConfig { + return b.chain.Config() +} + +func (b *testImmutableBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription { + return nil +} + +func (b *testImmutableBackend) CurrentHeader() *types.Header { + return b.chain.CurrentHeader() +} + +func (b *testImmutableBackend) GetBlockByNumber(number uint64) *types.Block { + return b.chain.GetBlockByNumber(number) +} + +func (b *testImmutableBackend) teardown() { + b.chain.Stop() +} + +func newTestImmutableBackend(t *testing.T, pending bool, height int, head int, txns map[uint64]*types.DynamicFeeTx) *testImmutableBackend { + 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)}}, + BaseFee: big.NewInt(49), + } + ) + config.ChainID = big.NewInt(settings.TestnetNetworkID) + signer := types.LatestSigner(gspec.Config) + londonBlock := big.NewInt(0) + config.LondonBlock = londonBlock + config.ArrowGlacierBlock = londonBlock + config.GrayGlacierBlock = londonBlock + never := uint64(100000000000000000) + config.PrevrandaoTime = &never + config.ShanghaiTime = nil // Cannot have shanghai in genesis (withdrawals header) + engine := ethash.NewFaker() + + _, blocks, _ := core.GenerateChainWithGenesis(gspec, engine, height, func(i int, b *core.BlockGen) { + b.SetCoinbase(common.Address{1}) + txdata, ok := txns[b.Number().Uint64()] + if ok { + b.AddTx(types.MustSignNewTx(key, signer, &types.DynamicFeeTx{ + ChainID: gspec.Config.ChainID, + Nonce: b.TxNonce(addr), + To: &common.Address{}, + Gas: 30000, + GasFeeCap: txdata.GasFeeCap, + GasTipCap: txdata.GasTipCap, + Data: []byte{}, + })) + } + }) + + // Construct testing chain + chain, err := core.NewBlockChain(rawdb.NewMemoryDatabase(), &core.CacheConfig{TrieCleanNoPrefetch: true}, gspec, nil, engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("Failed to create local chain, %v", err) + } + chain.InsertChain(blocks) + chain.SetFinalized(chain.GetBlockByNumber(uint64(head)).Header()) + chain.SetSafe(chain.GetBlockByNumber(uint64(head)).Header()) + return &testImmutableBackend{chain: chain, pending: pending, latest: int64(head)} +} + +func TestImmutablePriceLimitFloor(t *testing.T) { + priceData := make(map[uint64]*types.DynamicFeeTx, 0) + for i := 0; i <= 100; i++ { + priceData[uint64(i)] = &types.DynamicFeeTx{ + GasFeeCap: big.NewInt(10 * params.GWei), + GasTipCap: big.NewInt(10 * params.GWei), + } + } + config := Config{ + Blocks: 20, + Percentile: 60, + Default: big.NewInt(params.GWei), + } + backend := newTestImmutableBackend(t, false, 101, 100, priceData) + oracle := NewOracle(backend, config) + + got, err := oracle.SuggestTipCap(context.Background()) + backend.teardown() + if err != nil { + t.Fatalf("Failed to retrieve recommended gas price: %v", err) + } + expected := big.NewInt(settings.PriceLimit) + if got.Cmp(expected) != 0 { + t.Fatalf("Gas price mismatch, want %d, got %d", expected, got) + } +} + +func TestImmutableHandleEmptyBlocks(t *testing.T) { + priceData := make(map[uint64]*types.DynamicFeeTx, 0) + for i := 0; i <= 50; i++ { + priceData[uint64(i)] = &types.DynamicFeeTx{ + GasFeeCap: big.NewInt(550 * params.GWei), + GasTipCap: big.NewInt(500 * params.GWei), + } + } + config := Config{ + Blocks: 20, + Percentile: 60, + Default: big.NewInt(params.GWei), + } + var cases = []struct { + head *big.Int // Head number + expect *big.Int // Expected gasprice suggestion + }{ + // Past 20 blocks all 500 gweis, so expect tip of 500 Gwei + {big.NewInt(50), big.NewInt(params.GWei * int64(500))}, + // Past 20 blocks are all empty, so pull 40 blocks, 23 prices have 10 Gwei and 17 prices have 500 Gwei. 60% is top 17 prices, which is 500 Gwei. + {big.NewInt(73), big.NewInt(params.GWei * int64(500))}, + // Past 20 blocks are all empty, so pull 40 blocks, 24 prices have 10 Gwei and 16 prices have 500 Gwei. 60% is top 17 prices, which is 10 Gwei. + {big.NewInt(74), big.NewInt(params.GWei * int64(10))}, + {big.NewInt(75), big.NewInt(params.GWei * int64(10))}, + } + for _, c := range cases { + backend := newTestImmutableBackend(t, false, int(c.head.Int64())+1, int(c.head.Int64()), priceData) + oracle := NewOracle(backend, config) + + 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 index 0343a5787..882bcd1b5 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -52,7 +52,8 @@ const ( // 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 + // CHANGE(immutable): unused + txMaxBroadcastSize = 4096 //nolint:unused ) var syncChallengeTimeout = 15 * time.Second // Time allowance for a node to reply to the sync progress challenge @@ -93,6 +94,10 @@ type handlerConfig struct { 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 + // CHANGE(immutable): gossip configuration + GossipDefault bool // Whether to use default gossip configuration + // CHANGE(immutable): disable txpool gossip configuration + DisableTxPoolGossip bool } type handler struct { @@ -128,6 +133,10 @@ type handler struct { handlerStartCh chan struct{} handlerDoneCh chan struct{} + // CHANGE(immutable): gossip configuration + gossipDefault bool + // CHANGE(immutable): disable txpool gossip configuration + disableTxPoolGossip bool } // newHandler returns a handler for all Ethereum chain management protocol. @@ -149,6 +158,10 @@ func newHandler(config *handlerConfig) (*handler, error) { quitSync: make(chan struct{}), handlerDoneCh: make(chan struct{}), handlerStartCh: make(chan struct{}), + // CHANGE(immutable): gossip configuration + gossipDefault: config.GossipDefault, + // CHANGE(immutable): disable txpool gossip configuration + disableTxPoolGossip: config.DisableTxPoolGossip, } if config.Sync == downloader.FullSync { // The database seems empty as the current block is the genesis. Yet the snap @@ -368,7 +381,8 @@ func (h *handler) runEthPeer(peer *eth.Peer, handler eth.Handler) error { return p2p.DiscTooManyPeers } } - peer.Log().Debug("Ethereum peer connected", "name", peer.Name()) + // CHANGE(immutable): log fullname + peer.Log().Debug("Ethereum peer connected", "name", peer.Fullname()) // Register the peer locally if err := h.peers.registerPeer(peer, snap); err != nil { @@ -439,7 +453,8 @@ func (h *handler) runEthPeer(peer *eth.Peer, handler eth.Handler) error { 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()) + // CHANGE(immutable): log fullname + peer.Log().Warn("Required block challenge timed out, dropping", "addr", peer.RemoteAddr(), "type", peer.Fullname()) h.removePeer(peer.ID()) } }(number, hash, req) @@ -597,9 +612,11 @@ func (h *handler) BroadcastBlock(block *types.Block, propagate bool) { } // 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. +// CHANGE(immutable): +// - Add conditional logic to send transactions to all peers or the geth default which is a subset of them +// - When gossipping to all peers, txs will be gossiped regardless of whether the node has received the transaction before +// - No announcements should be sent, as all peers should receive the full tx +// - When gossiping to a subset the gossiping will use geths default logic, where a subset of peers will receive the txs func (h *handler) BroadcastTransactions(txs types.Transactions) { var ( blobTxs int // Number of blob transactions to announce only @@ -613,26 +630,43 @@ func (h *handler) BroadcastTransactions(txs types.Transactions) { txset = make(map[*ethPeer][]common.Hash) // Set peer->hash to transfer directly annos = make(map[*ethPeer][]common.Hash) // Set peer->hash to announce ) - // Broadcast transactions to a batch of peers not knowing about it + // CHANGE(immutable): Broadcast transactions to all peers, regardless of whether + // they are known to have previously received it. This is to prevent loss of transactions + // in smaller networks. for _, tx := range txs { - peers := h.peers.peersWithoutTransaction(tx.Hash()) - - var numDirect int switch { case tx.Type() == types.BlobTxType: blobTxs++ case tx.Size() > txMaxBroadcastSize: largeTxs++ - default: - numDirect = int(math.Sqrt(float64(len(peers)))) - } - // Send the tx unconditionally to a subset of our peers - for _, peer := range peers[:numDirect] { - txset[peer] = append(txset[peer], tx.Hash()) } - // For the remaining peers, send announcement only - for _, peer := range peers[numDirect:] { - annos[peer] = append(annos[peer], tx.Hash()) + + // CHANGE(immutable): conditionally send to a subset of peers (default), otherwise send to all peers + if h.gossipDefault { + // CHANGE(immutable): Send the txs to a subset of peers. This is the default behaviour for geth. + peers := h.peers.peersWithoutTransaction(tx.Hash()) + var numDirect int + if tx.Size() <= txMaxBroadcastSize { + numDirect = int(math.Sqrt(float64(len(peers)))) + } + + // Send the tx unconditionally to a subset of our peers + for _, peer := range peers[:numDirect] { + txset[peer] = append(txset[peer], tx.Hash()) + } + + // For the remaining peers, send announcement only + for _, peer := range peers[numDirect:] { + annos[peer] = append(annos[peer], tx.Hash()) + } + } else { + // CHANGE(immutable): Send the tx unconditionally to all our peers, rather than a subset. + // Sending transactions to a subset of peers is only applicable for large scale networks. + // All peers will receive the full transaction payload instead of an announcement. + peers := h.peers.allPeers() + for _, peer := range peers[:] { + txset[peer] = append(txset[peer], tx.Hash()) + } } } for peer, hashes := range txset { @@ -641,6 +675,8 @@ func (h *handler) BroadcastTransactions(txs types.Transactions) { peer.AsyncSendTransactions(hashes) } for peer, hashes := range annos { + // CHANGE(immutable): Add warning logs for announcements as we do not expect them to occur + log.Warn("Sending announcements unexpectedly", "announced hashes", len(annos)) annPeers++ annCount += len(hashes) peer.AsyncSendPooledTransactionHashes(hashes) diff --git a/eth/handler_eth.go b/eth/handler_eth.go index f1284c10e..28f26dee3 100644 --- a/eth/handler_eth.go +++ b/eth/handler_eth.go @@ -33,8 +33,23 @@ import ( // packets that are sent as replies or broadcasts. type ethHandler handler +// CHANGE(immutable): add nill pool for disabling tx gossping +// NilPool implements the TxPool interface but does nothing. This is used to disable txpool gossiping +type NilPool struct{} + +// CHANGE(immutable): add nill pool for disabling tx gossping +func (n NilPool) Get(hash common.Hash) *types.Transaction { + return nil +} + func (h *ethHandler) Chain() *core.BlockChain { return h.chain } -func (h *ethHandler) TxPool() eth.TxPool { return h.txpool } +func (h *ethHandler) TxPool() eth.TxPool { + // CHANGE(immutable): disable tx pool gossiping + if h.disableTxPoolGossip { + return NilPool{} + } + 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 { @@ -52,6 +67,10 @@ func (h *ethHandler) PeerInfo(id enode.ID) interface{} { // AcceptTxs retrieves whether transaction processing is enabled on the node // or if inbound transactions should simply be dropped. func (h *ethHandler) AcceptTxs() bool { + // CHANGE(immutable): disable tx pool gossiping + if h.disableTxPoolGossip { + return false + } return h.synced.Load() } diff --git a/eth/peerset.go b/eth/peerset.go index c0c11e3e8..c755be7cd 100644 --- a/eth/peerset.go +++ b/eth/peerset.go @@ -222,6 +222,19 @@ func (ps *peerSet) peersWithoutTransaction(hash common.Hash) []*ethPeer { return list } +// CHANGE(immutable): +// allPeers is used to return all peers, used for obtaining a list of broadcast recipients +func (ps *peerSet) allPeers() []*ethPeer { + ps.lock.RLock() + defer ps.lock.RUnlock() + + list := make([]*ethPeer, 0, len(ps.peers)) + for _, p := range ps.peers { + 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`. diff --git a/eth/peerset_test.go b/eth/peerset_test.go new file mode 100644 index 000000000..c3749510d --- /dev/null +++ b/eth/peerset_test.go @@ -0,0 +1,38 @@ +package eth + +import ( + "testing" + + "github.com/ethereum/go-ethereum/eth/protocols/eth" + "github.com/ethereum/go-ethereum/p2p" +) + +func TestAllPeers_EmptyPeerSet_ZeroPeers(t *testing.T) { + t.Parallel() + testPeerSet := newPeerSet() + peers := testPeerSet.allPeers() + if len(peers) != 0 { + t.Fatalf("expected 0 peers but got %d", len(peers)) + } +} + +func TestAllPeers_NonEmptyPeerset_NonZeroPeers(t *testing.T) { + t.Parallel() + testPeerSet := newPeerSet() + + // create peers + expectedNumPeers := 10 + for i := 0; i < expectedNumPeers; i++ { + p2pPeer := p2p.NewPeer([32]byte{byte(i)}, "", []p2p.Cap{}) + peer := eth.NewPeer(1, p2pPeer, nil, nil) + err := testPeerSet.registerPeer(peer, nil) + if err != nil { + t.Fatalf(err.Error()) + } + } + + peers := testPeerSet.allPeers() + if len(peers) != expectedNumPeers { + t.Fatalf("expected %d peers but got %d", expectedNumPeers, len(peers)) + } +} diff --git a/eth/protocols/eth/handler.go b/eth/protocols/eth/handler.go index 2d69ecdc8..d7fc9a774 100644 --- a/eth/protocols/eth/handler.go +++ b/eth/protocols/eth/handler.go @@ -19,16 +19,20 @@ package eth import ( "fmt" "math/big" + "net/netip" + "os" "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/params" + "golang.org/x/exp/slices" ) const ( @@ -177,6 +181,9 @@ var eth68 = map[uint64]msgHandler{ PooledTransactionsMsg: handlePooledTransactions, } +// CHANGE(immutable): Codes to filter out from public peers. These codes are for consuming state from peers. +var ignoredCodes = []uint64{NewBlockHashesMsg, NewBlockMsg, TransactionsMsg, NewPooledTransactionHashesMsg, PooledTransactionsMsg} + // 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 { @@ -188,6 +195,35 @@ func handleMessage(backend Backend, peer *Peer) error { if msg.Size > maxMessageSize { return fmt.Errorf("%w: %v > %v", errMsgTooLarge, msg.Size, maxMessageSize) } + + // CHANGE(immutable): Ignore messages from peers not in the private subnet if configured to do so. + if subnetStr, isLocalOnly := os.LookupEnv("GETH_FLAG_P2P_SUBNET"); isLocalOnly { + // Parse the subnet string. + subnet, err := netip.ParsePrefix(subnetStr) + if err != nil { + return fmt.Errorf("failed to parse private subnet string (%s): %v", subnetStr, err) + } + // Convert the peer IP to an address. + peerAddr, err := netip.ParseAddr(peer.Node().IP().String()) + if err != nil { + return fmt.Errorf("failed to parse peer IP: %v", err) + } + // Handle specific msg codes if not in the subnet. + if !subnet.Contains(peerAddr) { + // We only want to discard messages that would be consuming state, e.g. NewBlockMsg. + // We don't want to discard messages that are requesting state, e.g. GetBlockHeadersMsg, + // as this is required for recieving state in syncing for example. + if slices.Contains(ignoredCodes, msg.Code) { + // External peers should be configured to not send state to the public peer node. + // Log the peer information so that we can follow up as to why the peer is sending messages. + log.Warn("ignoring message from peer outside of required subnet", + "subnet", subnet.String(), "peerAddr", peerAddr.String(), "msgCode", msg.Code, + "peerID", peer.ID(), "peerFullname", peer.Fullname()) + return msg.Discard() + } + } + } + defer msg.Discard() var handlers = eth68 diff --git a/eth/protocols/snap/gentrie_test.go b/eth/protocols/snap/gentrie_test.go index 1fb2dbce7..dce8ae3e4 100644 --- a/eth/protocols/snap/gentrie_test.go +++ b/eth/protocols/snap/gentrie_test.go @@ -19,9 +19,10 @@ package snap import ( "bytes" "math/rand" - "slices" "testing" + "golang.org/x/exp/slices" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/crypto" diff --git a/eth/state_accessor.go b/eth/state_accessor.go index 526361a2b..7e368592d 100644 --- a/eth/state_accessor.go +++ b/eth/state_accessor.go @@ -242,7 +242,7 @@ func (eth *Ethereum) stateAtTransaction(ctx context.Context, block *types.Block, // 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) + context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil, eth.blockchain.Config()) if idx == txIndex { return msg, context, statedb, release, nil } diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 683310820..5e8db7f28 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -266,7 +266,7 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed 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) + blockCtx = core.NewEVMBlockContext(task.block.Header(), api.chainContext(ctx), nil, api.backend.ChainConfig()) ) // Trace all the transactions contained within for i, tx := range task.block.Transactions() { @@ -523,7 +523,7 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config 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) + vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, api.backend.ChainConfig()) deleteEmptyObjects = chainConfig.IsEIP158(block.Number()) ) for i, tx := range block.Transactions() { @@ -599,7 +599,7 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac txs = block.Transactions() blockHash = block.Hash() is158 = api.backend.ChainConfig().IsEIP158(block.Number()) - blockCtx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) + blockCtx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, api.backend.ChainConfig()) signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time()) results = make([]*txTraceResult, len(txs)) ) @@ -632,7 +632,7 @@ func (api *API) traceBlockParallel(ctx context.Context, block *types.Block, stat var ( txs = block.Transactions() blockHash = block.Hash() - blockCtx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) + blockCtx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, api.backend.ChainConfig()) signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time()) results = make([]*txTraceResult, len(txs)) pend sync.WaitGroup @@ -744,7 +744,7 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block 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) + vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, api.backend.ChainConfig()) canon = true ) // Check if there are any overrides: the caller may wish to enable a future @@ -910,7 +910,7 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc } defer release() - vmctx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) + vmctx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, api.backend.ChainConfig()) // Apply the customization rules if required. if config != nil { if err := config.StateOverrides.Apply(statedb); err != nil { @@ -1019,6 +1019,11 @@ func overrideConfig(original *params.ChainConfig, override *params.ChainConfig) copy.MergeNetsplitBlock = block canon = false } + // CHANGE(immutable): Add prevrandao fork + if timestamp := override.PrevrandaoTime; timestamp != nil { + copy.PrevrandaoTime = timestamp + canon = false + } if timestamp := override.ShanghaiTime; timestamp != nil { copy.ShanghaiTime = timestamp canon = false diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go index d8e4b9a4e..fc2ba47e2 100644 --- a/eth/tracers/api_test.go +++ b/eth/tracers/api_test.go @@ -172,7 +172,7 @@ func (b *testBackend) StateAtTransaction(ctx context.Context, block *types.Block 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) + context := core.NewEVMBlockContext(block.Header(), b.chain, nil, b.chainConfig) if idx == txIndex { return msg, context, statedb, release, nil } diff --git a/ethclient/gethclient/immutable_gethclient.go b/ethclient/gethclient/immutable_gethclient.go new file mode 100644 index 000000000..197723822 --- /dev/null +++ b/ethclient/gethclient/immutable_gethclient.go @@ -0,0 +1,48 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package gethclient + +// This file introduces clique specific APIs to the gethclient. A separate client could have been created +// however, many private functions would have to be duplicated and the separation effort is not justifiable + +import ( + "context" + "math/big" + + "github.com/ethereum/go-ethereum/common" +) + +// GetSigners Retrieves the list of authorized signers at the specified block number. +func (ec *Client) GetSigners(ctx context.Context, blockNumber *big.Int) ([]common.Address, error) { + var signers []common.Address + err := ec.c.CallContext( + ctx, &signers, "clique_getSigners", toBlockNumArg(blockNumber), + ) + return signers, err +} + +// Propose Adds a new authorization proposal that the signer will attempt to push through. +// If the auth parameter is true, the local signer votes for the given address to be included +// in the set of authorized signers. +// With auth set to false, the vote is against the address. +func (ec *Client) Propose(ctx context.Context, validator common.Address, auth bool) error { + var result interface{} + err := ec.c.CallContext( + ctx, &result, "clique_propose", validator.String(), auth, + ) + return err +} diff --git a/go.mod b/go.mod index 7a54b1ff7..ec0d698c9 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,8 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.0 github.com/Microsoft/go-winio v0.6.1 github.com/VictoriaMetrics/fastcache v1.12.1 - github.com/aws/aws-sdk-go-v2 v1.21.2 + github.com/aws/aws-sdk-go v1.50.31 + github.com/aws/aws-sdk-go-v2 v1.25.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 @@ -51,11 +52,15 @@ require ( github.com/mattn/go-colorable v0.1.13 github.com/mattn/go-isatty v0.0.17 github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416 + github.com/newrelic/go-agent/v3 v3.26.0 + github.com/newrelic/go-agent/v3/integrations/nrlogrus v1.0.1 github.com/olekukonko/tablewriter v0.0.5 github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 github.com/protolambda/bls12-381-util v0.0.0-20220416220906-d8552aa452c7 github.com/rs/cors v1.7.0 + github.com/sethvargo/go-retry v0.2.4 github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible + github.com/sirupsen/logrus v1.9.0 github.com/status-im/keycard-go v0.2.0 github.com/stretchr/testify v1.8.4 github.com/supranational/blst v0.3.11 @@ -63,10 +68,10 @@ require ( 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.17.0 + golang.org/x/crypto v0.19.0 golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa golang.org/x/sync v0.5.0 - golang.org/x/sys v0.16.0 + golang.org/x/sys v0.17.0 golang.org/x/text v0.14.0 golang.org/x/time v0.3.0 golang.org/x/tools v0.15.0 @@ -75,8 +80,21 @@ require ( ) 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/BurntSushi/toml v1.3.2 // indirect + github.com/andybalholm/brotli v1.0.5 // indirect + github.com/getsentry/sentry-go v0.27.0 // indirect + github.com/ilyakaznacheev/cleanenv v1.5.0 // indirect + github.com/joho/godotenv v1.5.1 // indirect + github.com/onsi/gomega v1.27.6 // indirect + google.golang.org/genproto v0.0.0-20230227214838-9b19f0bdc514 // indirect + google.golang.org/grpc v1.54.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 // indirect +) + +require ( + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.10.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 // 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 @@ -87,14 +105,13 @@ require ( 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/aws/smithy-go v1.20.1 // 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.2.0 // indirect - github.com/cockroachdb/errors v1.8.1 // indirect - github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f // indirect - github.com/cockroachdb/redact v1.0.8 // indirect - github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2 // indirect + github.com/cockroachdb/errors v1.11.1 // 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 @@ -106,7 +123,7 @@ require ( github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/google/go-cmp v0.5.9 // indirect + github.com/google/go-cmp v0.5.9 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 @@ -114,7 +131,7 @@ require ( github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/kilic/bls12-381 v0.1.0 // indirect - github.com/klauspost/compress v1.15.15 // indirect + github.com/klauspost/compress v1.16.3 // 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 @@ -133,14 +150,15 @@ require ( 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/rogpeppe/go-internal v1.10.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/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.49.0 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect golang.org/x/mod v0.14.0 // indirect - golang.org/x/net v0.18.0 // indirect - google.golang.org/protobuf v1.27.1 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect + golang.org/x/net v0.21.0 // indirect + google.golang.org/protobuf v1.30.0 // indirect rsc.io/tmplfunc v0.0.3 // indirect ) diff --git a/go.sum b/go.sum index bb4ded5c2..adbbab7ae 100644 --- a/go.sum +++ b/go.sum @@ -13,51 +13,177 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV 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 v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= +cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E= +cloud.google.com/go/accesscontextmanager v1.6.0/go.mod h1:8XCvZWfYw3K/ji0iVnp+6pu7huxoQTLmxAbVjbloTtM= +cloud.google.com/go/aiplatform v1.35.0/go.mod h1:7MFT/vCaOyZT/4IIFfxH4ErVg/4ku6lKv3w0+tFTgXQ= +cloud.google.com/go/analytics v0.17.0/go.mod h1:WXFa3WSym4IZ+JiKmavYdJwGG/CvpqiqczmL59bTD9M= +cloud.google.com/go/apigateway v1.5.0/go.mod h1:GpnZR3Q4rR7LVu5951qfXPJCHquZt02jf7xQx7kpqN8= +cloud.google.com/go/apigeeconnect v1.5.0/go.mod h1:KFaCqvBRU6idyhSNyn3vlHXc8VMDJdRmwDF6JyFRqZ8= +cloud.google.com/go/apigeeregistry v0.5.0/go.mod h1:YR5+s0BVNZfVOUkMa5pAR2xGd0A473vA5M7j247o1wM= +cloud.google.com/go/apikeys v0.5.0/go.mod h1:5aQfwY4D+ewMMWScd3hm2en3hCj+BROlyrt3ytS7KLI= +cloud.google.com/go/appengine v1.6.0/go.mod h1:hg6i0J/BD2cKmDJbaFSYHFyZkgBEfQrDg/X0V5fJn84= +cloud.google.com/go/area120 v0.7.0/go.mod h1:a3+8EUD1SX5RUcCs3MY5YasiO1z6yLiNLRiFrykbynY= +cloud.google.com/go/artifactregistry v1.11.1/go.mod h1:lLYghw+Itq9SONbCa1YWBoWs1nOucMH0pwXN1rOBZFI= +cloud.google.com/go/asset v1.11.1/go.mod h1:fSwLhbRvC9p9CXQHJ3BgFeQNM4c9x10lqlrdEUYXlJo= +cloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E= +cloud.google.com/go/automl v1.12.0/go.mod h1:tWDcHDp86aMIuHmyvjuKeeHEGq76lD7ZqfGLN6B0NuU= +cloud.google.com/go/baremetalsolution v0.5.0/go.mod h1:dXGxEkmR9BMwxhzBhV0AioD0ULBmuLZI8CdwalUxuss= +cloud.google.com/go/batch v0.7.0/go.mod h1:vLZN95s6teRUqRQ4s3RLDsH8PvboqBK+rn1oevL159g= +cloud.google.com/go/beyondcorp v0.4.0/go.mod h1:3ApA0mbhHx6YImmuubf5pyW8srKnCEPON32/5hj+RmM= 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/bigquery v1.47.0/go.mod h1:sA9XOgy0A8vQK9+MWhEQTY6Tix87M/ZurWFIxmF9I/E= +cloud.google.com/go/billing v1.12.0/go.mod h1:yKrZio/eu+okO/2McZEbch17O5CB5NpZhhXG6Z766ss= +cloud.google.com/go/binaryauthorization v1.5.0/go.mod h1:OSe4OU1nN/VswXKRBmciKpo9LulY41gch5c68htf3/Q= +cloud.google.com/go/certificatemanager v1.6.0/go.mod h1:3Hh64rCKjRAX8dXgRAyOcY5vQ/fE1sh8o+Mdd6KPgY8= +cloud.google.com/go/channel v1.11.0/go.mod h1:IdtI0uWGqhEeatSB62VOoJ8FSUhJ9/+iGkJVqp74CGE= +cloud.google.com/go/cloudbuild v1.6.0/go.mod h1:UIbc/w9QCbH12xX+ezUsgblrWv+Cv4Tw83GiSMHOn9M= +cloud.google.com/go/clouddms v1.5.0/go.mod h1:QSxQnhikCLUw13iAbffF2CZxAER3xDGNHjsTAkQJcQA= +cloud.google.com/go/cloudtasks v1.9.0/go.mod h1:w+EyLsVkLWHcOaqNEyvcKAsWp9p29dL6uL9Nst1cI7Y= +cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w= +cloud.google.com/go/container v1.13.1/go.mod h1:6wgbMPeQRw9rSnKBCAJXnds3Pzj03C4JHamr8asWKy4= +cloud.google.com/go/containeranalysis v0.7.0/go.mod h1:9aUL+/vZ55P2CXfuZjS4UjQ9AgXoSw8Ts6lemfmxBxI= +cloud.google.com/go/datacatalog v1.12.0/go.mod h1:CWae8rFkfp6LzLumKOnmVh4+Zle4A3NXLzVJ1d1mRm0= +cloud.google.com/go/dataflow v0.8.0/go.mod h1:Rcf5YgTKPtQyYz8bLYhFoIV/vP39eL7fWNcSOyFfLJE= +cloud.google.com/go/dataform v0.6.0/go.mod h1:QPflImQy33e29VuapFdf19oPbE4aYTJxr31OAPV+ulA= +cloud.google.com/go/datafusion v1.6.0/go.mod h1:WBsMF8F1RhSXvVM8rCV3AeyWVxcC2xY6vith3iw3S+8= +cloud.google.com/go/datalabeling v0.7.0/go.mod h1:WPQb1y08RJbmpM3ww0CSUAGweL0SxByuW2E+FU+wXcM= +cloud.google.com/go/dataplex v1.5.2/go.mod h1:cVMgQHsmfRoI5KFYq4JtIBEUbYwc3c7tXmIDhRmNNVQ= +cloud.google.com/go/dataproc v1.12.0/go.mod h1:zrF3aX0uV3ikkMz6z4uBbIKyhRITnxvr4i3IjKsKrw4= +cloud.google.com/go/dataqna v0.7.0/go.mod h1:Lx9OcIIeqCrw1a6KdO3/5KMP1wAmTc0slZWwP12Qq3c= 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/datastore v1.10.0/go.mod h1:PC5UzAmDEkAmkfaknstTYbNpgE49HAgW2J1gcgUfmdM= +cloud.google.com/go/datastream v1.6.0/go.mod h1:6LQSuswqLa7S4rPAOZFVjHIG3wJIjZcZrw8JDEDJuIs= +cloud.google.com/go/deploy v1.6.0/go.mod h1:f9PTHehG/DjCom3QH0cntOVRm93uGBDt2vKzAPwpXQI= +cloud.google.com/go/dialogflow v1.31.0/go.mod h1:cuoUccuL1Z+HADhyIA7dci3N5zUssgpBJmCzI6fNRB4= +cloud.google.com/go/dlp v1.9.0/go.mod h1:qdgmqgTyReTz5/YNSSuueR8pl7hO0o9bQ39ZhtgkWp4= +cloud.google.com/go/documentai v1.16.0/go.mod h1:o0o0DLTEZ+YnJZ+J4wNfTxmDVyrkzFvttBXXtYRMHkM= +cloud.google.com/go/domains v0.8.0/go.mod h1:M9i3MMDzGFXsydri9/vW+EWz9sWb4I6WyHqdlAk0idE= +cloud.google.com/go/edgecontainer v0.3.0/go.mod h1:FLDpP4nykgwwIfcLt6zInhprzw0lEi2P1fjO6Ie0qbc= +cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU= +cloud.google.com/go/essentialcontacts v1.5.0/go.mod h1:ay29Z4zODTuwliK7SnX8E86aUF2CTzdNtvv42niCX0M= +cloud.google.com/go/eventarc v1.10.0/go.mod h1:u3R35tmZ9HvswGRBnF48IlYgYeBcPUCjkr4BTdem2Kw= +cloud.google.com/go/filestore v1.5.0/go.mod h1:FqBXDWBp4YLHqRnVGveOkHDf8svj9r5+mUDLupOWEDs= +cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE= +cloud.google.com/go/functions v1.10.0/go.mod h1:0D3hEOe3DbEvCXtYOZHQZmD+SzYsi1YbI7dGvHfldXw= +cloud.google.com/go/gaming v1.9.0/go.mod h1:Fc7kEmCObylSWLO334NcO+O9QMDyz+TKC4v1D7X+Bc0= +cloud.google.com/go/gkebackup v0.4.0/go.mod h1:byAyBGUwYGEEww7xsbnUTBHIYcOPy/PgUWUtOeRm9Vg= +cloud.google.com/go/gkeconnect v0.7.0/go.mod h1:SNfmVqPkaEi3bF/B3CNZOAYPYdg7sU+obZ+QTky2Myw= +cloud.google.com/go/gkehub v0.11.0/go.mod h1:JOWHlmN+GHyIbuWQPl47/C2RFhnFKH38jH9Ascu3n0E= +cloud.google.com/go/gkemulticloud v0.5.0/go.mod h1:W0JDkiyi3Tqh0TJr//y19wyb1yf8llHVto2Htf2Ja3Y= +cloud.google.com/go/gsuiteaddons v1.5.0/go.mod h1:TFCClYLd64Eaa12sFVmUyG62tk4mdIsI7pAnSXRkcFo= +cloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY= +cloud.google.com/go/iap v1.6.0/go.mod h1:NSuvI9C/j7UdjGjIde7t7HBz+QTwBcapPE07+sSRcLk= +cloud.google.com/go/ids v1.3.0/go.mod h1:JBdTYwANikFKaDP6LtW5JAi4gubs57SVNQjemdt6xV4= +cloud.google.com/go/iot v1.5.0/go.mod h1:mpz5259PDl3XJthEmh9+ap0affn/MqNSP4My77Qql9o= +cloud.google.com/go/kms v1.8.0/go.mod h1:4xFEhYFqvW+4VMELtZyxomGSYtSQKzM178ylFW4jMAg= +cloud.google.com/go/language v1.9.0/go.mod h1:Ns15WooPM5Ad/5no/0n81yUetis74g3zrbeJBE+ptUY= +cloud.google.com/go/lifesciences v0.8.0/go.mod h1:lFxiEOMqII6XggGbOnKiyZ7IBwoIqA84ClvoezaA/bo= +cloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M= +cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo= +cloud.google.com/go/managedidentities v1.5.0/go.mod h1:+dWcZ0JlUmpuxpIDfyP5pP5y0bLdRwOS4Lp7gMni/LA= +cloud.google.com/go/maps v0.6.0/go.mod h1:o6DAMMfb+aINHz/p/jbcY+mYeXBoZoxTfdSQ8VAJaCw= +cloud.google.com/go/mediatranslation v0.7.0/go.mod h1:LCnB/gZr90ONOIQLgSXagp8XUW1ODs2UmUMvcgMfI2I= +cloud.google.com/go/memcache v1.9.0/go.mod h1:8oEyzXCu+zo9RzlEaEjHl4KkgjlNDaXbCQeQWlzNFJM= +cloud.google.com/go/metastore v1.10.0/go.mod h1:fPEnH3g4JJAk+gMRnrAnoqyv2lpUCqJPWOodSaf45Eo= +cloud.google.com/go/monitoring v1.12.0/go.mod h1:yx8Jj2fZNEkL/GYZyTLS4ZtZEZN8WtDEiEqG4kLK50w= +cloud.google.com/go/networkconnectivity v1.10.0/go.mod h1:UP4O4sWXJG13AqrTdQCD9TnLGEbtNRqjuaaA7bNjF5E= +cloud.google.com/go/networkmanagement v1.6.0/go.mod h1:5pKPqyXjB/sgtvB5xqOemumoQNB7y95Q7S+4rjSOPYY= +cloud.google.com/go/networksecurity v0.7.0/go.mod h1:mAnzoxx/8TBSyXEeESMy9OOYwo1v+gZ5eMRnsT5bC8k= +cloud.google.com/go/notebooks v1.7.0/go.mod h1:PVlaDGfJgj1fl1S3dUwhFMXFgfYGhYQt2164xOMONmE= +cloud.google.com/go/optimization v1.3.1/go.mod h1:IvUSefKiwd1a5p0RgHDbWCIbDFgKuEdB+fPPuP0IDLI= +cloud.google.com/go/orchestration v1.6.0/go.mod h1:M62Bevp7pkxStDfFfTuCOaXgaaqRAga1yKyoMtEoWPQ= +cloud.google.com/go/orgpolicy v1.10.0/go.mod h1:w1fo8b7rRqlXlIJbVhOMPrwVljyuW5mqssvBtU18ONc= +cloud.google.com/go/osconfig v1.11.0/go.mod h1:aDICxrur2ogRd9zY5ytBLV89KEgT2MKB2L/n6x1ooPw= +cloud.google.com/go/oslogin v1.9.0/go.mod h1:HNavntnH8nzrn8JCTT5fj18FuJLFJc4NaZJtBnQtKFs= +cloud.google.com/go/phishingprotection v0.7.0/go.mod h1:8qJI4QKHoda/sb/7/YmMQ2omRLSLYSu9bU0EKCNI+Lk= +cloud.google.com/go/policytroubleshooter v1.5.0/go.mod h1:Rz1WfV+1oIpPdN2VvvuboLVRsB1Hclg3CKQ53j9l8vw= +cloud.google.com/go/privatecatalog v0.7.0/go.mod h1:2s5ssIFO69F5csTXcwBP7NPFTZvps26xGzvQ2PQaBYg= 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/pubsub v1.28.0/go.mod h1:vuXFpwaVoIPQMGXqRyUQigu/AX1S3IWugR9xznmcXX8= +cloud.google.com/go/pubsublite v1.6.0/go.mod h1:1eFCS0U11xlOuMFV/0iBqw3zP12kddMeCbj/F3FSj9k= +cloud.google.com/go/recaptchaenterprise/v2 v2.6.0/go.mod h1:RPauz9jeLtB3JVzg6nCbe12qNoaa8pXc4d/YukAmcnA= +cloud.google.com/go/recommendationengine v0.7.0/go.mod h1:1reUcE3GIu6MeBz/h5xZJqNLuuVjNg1lmWMPyjatzac= +cloud.google.com/go/recommender v1.9.0/go.mod h1:PnSsnZY7q+VL1uax2JWkt/UegHssxjUVVCrX52CuEmQ= +cloud.google.com/go/redis v1.11.0/go.mod h1:/X6eicana+BWcUda5PpwZC48o37SiFVTFSs0fWAJ7uQ= +cloud.google.com/go/resourcemanager v1.5.0/go.mod h1:eQoXNAiAvCf5PXxWxXjhKQoTMaUSNrEfg+6qdf/wots= +cloud.google.com/go/resourcesettings v1.5.0/go.mod h1:+xJF7QSG6undsQDfsCJyqWXyBwUoJLhetkRMDRnIoXA= +cloud.google.com/go/retail v1.12.0/go.mod h1:UMkelN/0Z8XvKymXFbD4EhFJlYKRx1FGhQkVPU5kF14= +cloud.google.com/go/run v0.8.0/go.mod h1:VniEnuBwqjigv0A7ONfQUaEItaiCRVujlMqerPPiktM= +cloud.google.com/go/scheduler v1.8.0/go.mod h1:TCET+Y5Gp1YgHT8py4nlg2Sew8nUHMqcpousDgXJVQc= +cloud.google.com/go/secretmanager v1.10.0/go.mod h1:MfnrdvKMPNra9aZtQFvBcvRU54hbPD8/HayQdlUgJpU= +cloud.google.com/go/security v1.12.0/go.mod h1:rV6EhrpbNHrrxqlvW0BWAIawFWq3X90SduMJdFwtLB8= +cloud.google.com/go/securitycenter v1.18.1/go.mod h1:0/25gAzCM/9OL9vVx4ChPeM/+DlfGQJDwBy/UC8AKK0= +cloud.google.com/go/servicecontrol v1.10.0/go.mod h1:pQvyvSRh7YzUF2efw7H87V92mxU8FnFDawMClGCNuAA= +cloud.google.com/go/servicedirectory v1.8.0/go.mod h1:srXodfhY1GFIPvltunswqXpVxFPpZjf8nkKQT7XcXaY= +cloud.google.com/go/servicemanagement v1.6.0/go.mod h1:aWns7EeeCOtGEX4OvZUWCCJONRZeFKiptqKf1D0l/Jc= +cloud.google.com/go/serviceusage v1.5.0/go.mod h1:w8U1JvqUqwJNPEOTQjrMHkw3IaIFLoLsPLvsE3xueec= +cloud.google.com/go/shell v1.6.0/go.mod h1:oHO8QACS90luWgxP3N9iZVuEiSF84zNyLytb+qE2f9A= +cloud.google.com/go/spanner v1.44.0/go.mod h1:G8XIgYdOK+Fbcpbs7p2fiprDw4CaZX63whnSMLVBxjk= +cloud.google.com/go/speech v1.14.1/go.mod h1:gEosVRPJ9waG7zqqnsHpYTOoAS4KouMRLDFMekpJ0J0= 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= +cloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4= +cloud.google.com/go/talent v1.5.0/go.mod h1:G+ODMj9bsasAEJkQSzO2uHQWXHHXUomArjWQQYkqK6c= +cloud.google.com/go/texttospeech v1.6.0/go.mod h1:YmwmFT8pj1aBblQOI3TfKmwibnsfvhIBzPXcW4EBovc= +cloud.google.com/go/tpu v1.5.0/go.mod h1:8zVo1rYDFuW2l4yZVY0R0fb/v44xLh3llq7RuV61fPM= +cloud.google.com/go/trace v1.8.0/go.mod h1:zH7vcsbAhklH8hWFig58HvxcxyQbaIqMarMg9hn5ECA= +cloud.google.com/go/translate v1.5.0/go.mod h1:29YDSYveqqpA1CQFD7NQuP49xymq17RXNaUDdc0mNu0= +cloud.google.com/go/video v1.12.0/go.mod h1:MLQew95eTuaNDEGriQdcYn0dTwf9oWiA4uYebxM5kdg= +cloud.google.com/go/videointelligence v1.10.0/go.mod h1:LHZngX1liVtUhZvi2uNS0VQuOzNi2TkY1OakiuoUOjU= +cloud.google.com/go/vision/v2 v2.6.0/go.mod h1:158Hes0MvOS9Z/bDMSFpjwsUrZ5fPrdwuyyvKSGAGMY= +cloud.google.com/go/vmmigration v1.5.0/go.mod h1:E4YQ8q7/4W9gobHjQg4JJSgXXSgY21nA5r8swQV+Xxc= +cloud.google.com/go/vmwareengine v0.2.2/go.mod h1:sKdctNJxb3KLZkE/6Oui94iw/xs9PRNC2wnNLXsHvH8= +cloud.google.com/go/vpcaccess v1.6.0/go.mod h1:wX2ILaNhe7TlVa4vC5xce1bCnqE3AeH27RV31lnmZes= +cloud.google.com/go/webrisk v1.8.0/go.mod h1:oJPDuamzHXgUc+b8SiHRcVInZQuybnvEW72PqTc7sSg= +cloud.google.com/go/websecurityscanner v1.5.0/go.mod h1:Y6xdCPy81yi0SQnDY1xdNTNpfY1oAgXUlcfN3B3eSng= +cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= -github.com/Azure/azure-sdk-for-go/sdk/azcore 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/azcore v1.10.0 h1:n1DH8TPV4qqPTje2RcUBYwtrTWlabVp4n46+74X2pn4= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.10.0/go.mod h1:HDcZnuGbiyppErN6lB+idp4CKhjbc8gwjto6OPpyggM= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 h1:vcYCAze6p19qBW7MhZybIsqD8sMV8js0NyQM8JDnVtg= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0/go.mod h1:OQeznEEkTZ9OrhHJoDD8ZDq51FHgXjqtP9z6bEwBq9U= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 h1:LqbJ/WzJUwBf8UiaSzgX7aMclParm9/5Vgp+TY51uBQ= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2/go.mod h1:yInRyqWXAuaPrgI7p70+lDDgh3mlBohis29jGMISnmc= 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/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/CloudyKit/fastprinter v0.0.0-20170127035650-74b38d55f37a/go.mod h1:EFZQ978U7x8IRnstaskI3IysnWY5Ao3QgZUKOXlsAdw= -github.com/CloudyKit/jet v2.1.3-0.20180809161101-62edd43e4f88+incompatible/go.mod h1:HPYO+50pSWkPoj9Q/eq0aRGByCL6ScRlUmiEX5Zgm+w= +github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= +github.com/CloudyKit/jet/v6 v6.2.0/go.mod h1:d3ypHeIRNo2+XyqnGA8s+aphtcVpjP5hPwP/Lzo7Ro4= github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= -github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= -github.com/Joker/jade v1.0.1-0.20190614124447-d475f43051e7/go.mod h1:6E6s8o2AE4KhCrqr6GRJjdC/gNfTdxkIXvuGZZda2VM= +github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= +github.com/Joker/jade v1.1.3/go.mod h1:T+2WLyt7VH6Lp0TRxQrUYEs64nRc83wkMQrfeIQKduM= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= +github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06/go.mod h1:7erjKLwalezA0k99cWs5L11HWOAPNjdUZ6RxH1BXbbM= github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= github.com/VictoriaMetrics/fastcache v1.12.1 h1:i0mICQuojGDL3KblA7wUNlY5lOK6a4bwt3uRKnkZU40= github.com/VictoriaMetrics/fastcache v1.12.1/go.mod h1:tX04vaqcNoQeGLD+ra5pU5sWkuxnzWhEzLwhP9w653o= -github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= +github.com/aclements/go-moremath v0.0.0-20210112150236-f10218a38794/go.mod h1:7e+I0LQFUI9AXWxOfsQROs9xPhoJtbsyWcjJqDd4KPY= 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= @@ -65,9 +191,13 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/aws/aws-sdk-go-v2 v1.21.2 h1:+LXZ0sgo8quN9UOKXXzAWRT3FWd4NxeXWOZom9pE7GA= +github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= +github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/aws/aws-sdk-go v1.50.31 h1:gx2NRLLEDUmQFC4YUsfMUKkGCwpXVO8ijUecq/nOQGA= +github.com/aws/aws-sdk-go v1.50.31/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/aws/aws-sdk-go-v2 v1.21.2/go.mod h1:ErQhvNuEMhJjweavOYhxVkn2RUx7kQXVATHrjKtxIpM= +github.com/aws/aws-sdk-go-v2 v1.25.2 h1:/uiG1avJRgLGiQM9X3qJM8+Qa6KRGK5rRPuXE0HUM+w= +github.com/aws/aws-sdk-go-v2 v1.25.2/go.mod h1:Evoc5AsmtveRt1komDwIsjHFyrP5tDuF1D1U+6z6pNo= 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= @@ -90,9 +220,10 @@ github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.3 h1:HFiiRkf1SdaAmV3/BHOFZ9Dj github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.3/go.mod h1:a7bHA82fyUXOm+ZSWKU6PIoBxrjSprdLoM8xPYvzYVg= github.com/aws/aws-sdk-go-v2/service/sts v1.23.2 h1:0BkLfgeDjfZnZ+MhB3ONb01u9pwFYTCZVhlsSSBvlbU= github.com/aws/aws-sdk-go-v2/service/sts v1.23.2/go.mod h1:Eows6e1uQEsc4ZaHANmsPRzAKcVDrcmjjWiih2+HUUQ= -github.com/aws/smithy-go v1.15.0 h1:PS/durmlzvAFpQHDs4wi4sNNP9ExsqZh6IlfdHXgKK8= github.com/aws/smithy-go v1.15.0/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= -github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= +github.com/aws/smithy-go v1.20.1 h1:4SZlSlMr36UEqC7XOyRVb27XMeZubNcBNN+9IgEPIQw= +github.com/aws/smithy-go v1.20.1/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= +github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= 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= @@ -102,7 +233,9 @@ github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6 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/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= 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= @@ -119,18 +252,18 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cloudflare/cloudflare-go v0.79.0 h1:ErwCYDjFCYppDJlDJ/5WhsSmzegAUe2+K9qgFyQDg3M= github.com/cloudflare/cloudflare-go v0.79.0/go.mod h1:gkHQf9xEubaQPEuerBuoinR9P8bf8a05Lq0X6WKy1Oc= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cockroachdb/datadriven v1.0.0/go.mod h1:5Ib8Meh+jk1RlHIXej6Pzevx/NLlNvQB9pmSBZErGA4= +github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= -github.com/cockroachdb/errors v1.6.1/go.mod h1:tm6FTP5G81vwJ5lC0SizQo374JNCOPrHyXGitRJoDqM= -github.com/cockroachdb/errors v1.8.1 h1:A5+txlVZfOqFBDa4mGz2bUWSp0aHElvHX2bKkdbQu+Y= -github.com/cockroachdb/errors v1.8.1/go.mod h1:qGwQn6JmZ+oMjuLwjWzUNqblqk0xl4CVV3SQbGwK7Ac= -github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f h1:o/kfcElHqOiXqcou5a3rIlMc7oJbMQkeLk0VQJ7zgqY= -github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= +github.com/cockroachdb/errors v1.11.1 h1:xSEW75zKaKCWzR3OfxXUxgrk/NtT4G1MiOv5lWZazG8= +github.com/cockroachdb/errors v1.11.1/go.mod h1:8MUxA3Gi6b25tYlFEBGLf+D8aISL+M4MIpiWMSNRfxw= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593 h1:aPEJyR4rPBvDmeyi+l/FS/VtA00IWvjeFvjen1m1l1A= github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593/go.mod h1:6hk1eMY/u5t+Cf18q5lFMUA1Rc+Sm5I6Ra1QuPyxXCo= -github.com/cockroachdb/redact v1.0.8 h1:8QG/764wK+vmEYoOlfobpe12EQcS81ukx/a4hdVMxNw= -github.com/cockroachdb/redact v1.0.8/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= -github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2 h1:IKgmqgMQlVJIZj19CdocBeSfSaiCbEBZGKODaixqtHM= +github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30= +github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2/go.mod h1:8BT+cPK6xvFOcRlk0R8eg+OTkcqI6baNH4xAkpiYVvQ= github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= @@ -139,10 +272,6 @@ github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/Yj 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/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.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-20231025140028-3c0104f4b233 h1:d28BXYi+wUpz1KBmiF9bWrjEMacUEREV6MBi2ODnrfQ= @@ -162,28 +291,26 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1 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/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo= github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= +github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= github.com/dop251/goja v0.0.0-20230806174421-c933cf95e127 h1:qwcF+vdFrvPSEUDSX5RVoRccG8a5DhOdWdQ4zN62zzo= github.com/dop251/goja v0.0.0-20230806174421-c933cf95e127/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4= github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= +github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= github.com/ethereum/c-kzg-4844 v0.4.0 h1:3MS1s4JtA868KpJxroZoepdV0ZKBp3u/O5HcZ7R3nlY= github.com/ethereum/c-kzg-4844 v0.4.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= -github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= @@ -193,26 +320,27 @@ github.com/fjl/gencodec v0.0.0-20230517082657-f9840df7b83e h1:bBLctRc7kr01YGvaDf github.com/fjl/gencodec v0.0.0-20230517082657-f9840df7b83e/go.mod h1:AzA8Lj6YtixmJWL+wkKoBGsLWy9gFrAzi4g+5bCKwpY= github.com/fjl/memsize v0.0.2 h1:27txuSD9or+NZlnOWdKUxeBzTAUkWCVh+4Gf2dWFOzA= github.com/fjl/memsize v0.0.2/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= -github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4/go.mod h1:T9YF2M40nIgbVgp3rreNmTged+9HrbNTIQf1PsaIiTA= +github.com/flosch/pongo2/v4 v4.0.2/go.mod h1:B5ObFANs/36VwxxlgKpdchIJHMvHB562PW+BWPhwZD8= 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/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 h1:BAIP2GihuqhwdILrV+7GJel5lyPV3u1+PgzrWLc0TkE= github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46/go.mod h1:QNpY22eby74jVhqH4WhDLDwxc/vqsern6pW+u2kbkpc= github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= +github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps= +github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= +github.com/ghemawat/stream v0.0.0-20171120220530-696b145b53b9/go.mod h1:106OIgooyS7OzLDOpUGgm9fA3bQENb/cFSyyBmMoJDs= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= -github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= -github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs= -github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= -github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-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= @@ -222,32 +350,34 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb 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-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= 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-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= 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/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= -github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= -github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= +github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= -github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM= github.com/golang-jwt/jwt/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/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= 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= @@ -280,7 +410,6 @@ github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= -github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -296,7 +425,6 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -319,25 +447,24 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/graph-gophers/graphql-go v1.3.0 h1:Eb9x/q6MFpCLz7jBCiP/WTxjSDrYLR1QY41SORZyNJ0= github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= +github.com/guptarohit/asciigraph v0.5.5/go.mod h1:dYl5wwK4gNsnFf9Zp+l06rFiDZ5YtXM6x7SRWZ3KGag= 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/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/holiman/billy v0.0.0-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= @@ -347,10 +474,11 @@ github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXei 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/hydrogen18/memlistener v0.0.0-20141126152155-54553eb933fb/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE= +github.com/hydrogen18/memlistener v1.0.0/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= -github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= +github.com/ilyakaznacheev/cleanenv v1.5.0 h1:0VNZXggJE2OYdXE87bfSSwGxeiGt9moSR2lOrsHHvr4= +github.com/ilyakaznacheev/cleanenv v1.5.0/go.mod h1:a5aDzaJrLCQZsazHol1w8InnDcOX0OColm64SlIi6gk= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb-client-go/v2 v2.4.0 h1:HGBfZYStlx3Kqvsv1h2pJixbCl/jhnFtxpKFAv9Tu5k= github.com/influxdata/influxdb-client-go/v2 v2.4.0/go.mod h1:vLNHdxTJkIf2mSLvGrpj8TCcISApPoXkaxP8g9uRlW8= @@ -358,10 +486,7 @@ github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c h1:qSH 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/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI= -github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0= -github.com/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0/go.mod h1:pMCz62A0xJL6I+umB2YTlFRwWXaDFA0jy+5HzGiJjqI= -github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= +github.com/iris-contrib/schema v0.0.6/go.mod h1:iYszG0IOsuIsfzjymw1kMzTL8YQcCWlm65f3wX8J5iA= 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= @@ -370,6 +495,9 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y 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/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 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= @@ -377,33 +505,27 @@ github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= -github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= -github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= github.com/karalabe/usb v0.0.2 h1:M6QQBNxF+CQ8OFvxrT90BA0qBOXymndZnk5q235mFc4= github.com/karalabe/usb v0.0.2/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= -github.com/kataras/golog v0.0.9/go.mod h1:12HJgwBIZFNGL0EJnMRhmvGA0PQGx8VFwrZtM4CqbAk= -github.com/kataras/iris/v12 v12.0.1/go.mod h1:udK4vLQKkdDqMGJJVd/msuMtN6hpYJhg/lSzuxjhO+U= -github.com/kataras/neffos v0.0.10/go.mod h1:ZYmJC07hQPW67eKuzlfY7SO3bC0mw83A3j6im82hfqw= -github.com/kataras/pio v0.0.0-20190103105442-ea782b38602d/go.mod h1:NV88laa9UiiDuX9AhMbDPkGYSPugBOV6yTZB1l2K9Z0= +github.com/kataras/blocks v0.0.7/go.mod h1:UJIU97CluDo0f+zEjbnbkeMRlvYORtmc1304EeyXf4I= +github.com/kataras/golog v0.1.8/go.mod h1:rGPAin4hYROfk1qT9wZP6VY2rsb4zzc37QpdPjdkqVw= +github.com/kataras/iris/v12 v12.2.0/go.mod h1:BLzBpEunc41GbE68OUaQlqX4jzi791mx5HU04uPb90Y= +github.com/kataras/pio v0.0.11/go.mod h1:38hH6SWH6m4DKSYmRhlrCJ5WItwWgCVrTNU62XZyUvI= +github.com/kataras/sitemap v0.0.6/go.mod h1:dW4dOCNs896OR1HmG+dMLdT7JjDk7mYBzoIRwuj5jA4= +github.com/kataras/tunnel v0.0.4/go.mod h1:9FkU4LaeifdMWqZu7o20ojmW4B7hdhv2CMLwfnHGpYw= github.com/kilic/bls12-381 v0.1.0 h1:encrdjqKMEvabVQ7qYOKu1OvhqpK4s47wDYtNiPtlp4= github.com/kilic/bls12-381 v0.1.0/go.mod h1:vDTTHJONJ6G+P2R74EhnyotQDTliQDnFEwhdmfzw1ig= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.9.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= -github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= -github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/compress v1.16.3 h1:XuJt9zzcnaz6a16/OU53ZjWp/v7/42WcR5t2a0PcNQY= +github.com/klauspost/compress v1.16.3/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 v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 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= @@ -418,13 +540,17 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g= github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= +github.com/labstack/echo/v4 v4.10.0/go.mod h1:S/T/5fy/GigaXnHTkh0ZGe4LpkkQysvRjFMSUTkDRNQ= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= +github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/mailgun/raymond/v2 v2.0.48/go.mod h1:lsgvL50kgt1ylcFJYZiULi5fjPBkkhNfj4KA0W54Z18= 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/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 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= @@ -432,7 +558,6 @@ github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 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= @@ -444,17 +569,12 @@ github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/mediocregopher/mediocre-go-lib v0.0.0-20181029021733-cb65787f37ed/go.mod h1:dSsfyI2zABAdhcbvkXqgxOxrCsbYeHCPgrZkku60dSg= -github.com/mediocregopher/radix/v3 v3.3.0/go.mod h1:EmfVyvspXz1uZEyPBMyGK+kjWiKQGvsUt6O3Pj+LDCQ= -github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= +github.com/microcosm-cc/bluemonday v1.0.23/go.mod h1:mN70sk7UkkF8TUr2IGBpNN0jAgStuPzlK76QuruE/z4= 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/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A= @@ -467,36 +587,39 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ 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/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/naoina/go-stringutil v0.1.0 h1:rCUeRUHjBjGTSHl0VC00jUPLz8/F9dDzYI70Hzifhks= github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416 h1:shk/vn9oCoOTmwcouEdwIeOtOGA/ELRUw/GwvxwfT+0= github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= -github.com/nats-io/nats.go v1.8.1/go.mod h1:BrFz9vVn0fU3AcH9Vn4Kd7W0NpJ651tD5omQ3M8LwxM= -github.com/nats-io/nkeys v0.0.2/go.mod h1:dab7URMsZm6Z/jp9Z5UGa87Uutgc2mVpXLC4B7TDb/4= -github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/newrelic/go-agent/v3 v3.0.0/go.mod h1:H28zDNUC0U/b7kLoY4EFOhuth10Xu/9dchozUiOseQQ= +github.com/newrelic/go-agent/v3 v3.26.0 h1:xJkqiQgLtC3ys5zoBxD91ITm7sVHZNEF+7/mqmFjnl0= +github.com/newrelic/go-agent/v3 v3.26.0/go.mod h1:sE2WdlLF3B/xI5HUuIHTa7Aht1gpcIY65pzaUoN1pJs= +github.com/newrelic/go-agent/v3/integrations/nrlogrus v1.0.1 h1:Tv985B4QriX/KxNw5ugvTEOMQgpcq/RpZgO+XqhQJQU= +github.com/newrelic/go-agent/v3/integrations/nrlogrus v1.0.1/go.mod h1:JpiVn2lqR9Vk6Iq7mYGQPJhKEnthbba4QqM8Jb1JTW0= 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.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts= 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/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= +github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= 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/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= 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= @@ -505,6 +628,7 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE 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= @@ -531,38 +655,36 @@ github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1 github.com/protolambda/bls12-381-util v0.0.0-20220416220906-d8552aa452c7 h1:cZC+usqsYgHtlBaGulVnZ1hfKAi8iWtujBnRLQE698c= github.com/protolambda/bls12-381-util v0.0.0-20220416220906-d8552aa452c7/go.mod h1:IToEjHuttnUzwZI5KBSM/LOOW3qLbbrHOEfp3SbECGY= github.com/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/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= -github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= -github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= +github.com/sethvargo/go-retry v0.2.4 h1:T+jHEQy/zKJf5s95UkguisicE0zuF9y7+/vgz08Ocec= +github.com/sethvargo/go-retry v0.2.4/go.mod h1:1afjQuvh7s4gflMObvjLPaWgluLLyhA1wmVZ6KLpICw= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.1.0/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9+mHxBEeo3Hbg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 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= @@ -574,32 +696,31 @@ github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbe 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/tdewolff/minify/v2 v2.12.4/go.mod h1:h+SRvSIX3kwgwTFOpSckvSxgax3uy8kZTSF1Ojrr3bk= +github.com/tdewolff/parse/v2 v2.6.4/go.mod h1:woz0cgbLwFdtbjJu8PIKxhW05KplTFQkOdX78o+Jgrs= github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U= -github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= -github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w= +github.com/valyala/fasthttp v1.49.0 h1:9FdvCpmxB74LH4dPb7IJ1cOSsluR07XG3I1txXWwJpE= +github.com/valyala/fasthttp v1.49.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA= 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/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= 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/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= -github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= -github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= -github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= +github.com/yosssi/ace v0.0.5/go.mod h1:ALfIzm2vT7t5ZE7uoIZqF3TQ7SAOyupFZnkrF5id+K0= 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= @@ -613,18 +734,16 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/automaxprocs v1.5.2 h1:2LxUOGiR3O6tw8ui5sZa2LAaHnsviZdVOUZw4fvbnME= go.uber.org/automaxprocs v1.5.2/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-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.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= 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= @@ -664,11 +783,9 @@ golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -677,7 +794,6 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -699,14 +815,16 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v 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.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= -golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= 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/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= +golang.org/x/perf v0.0.0-20230113213139-801c7ef9e5c5/go.mod h1:UBKtEnL8aqnd+0JHqZ+2qoMDwtuy6cYhhKNoHLBiTQc= 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= @@ -725,7 +843,6 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -735,7 +852,6 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -777,6 +893,7 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc 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-20220715151400-c0bba94af5f8/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= @@ -784,11 +901,12 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.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/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= 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= @@ -808,15 +926,11 @@ golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= @@ -859,7 +973,6 @@ golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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= @@ -883,7 +996,7 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180518175338-11a468237815/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/appengine v1.6.7/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= @@ -913,7 +1026,8 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/genproto v0.0.0-20230227214838-9b19f0bdc514 h1:rtNKfB++wz5mtDY2t5C8TXlU5y52ojSu7tZo0z7u8eQ= +google.golang.org/genproto v0.0.0-20230227214838-9b19f0bdc514/go.mod h1:TvhZT5f700eVlTNwND1xoEZQeWTB2RY/65kplwl/bFA= 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= @@ -926,6 +1040,8 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa 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/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag= +google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= 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= @@ -938,8 +1054,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj 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.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 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= @@ -948,9 +1064,7 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN 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/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= -gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= -gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= @@ -973,6 +1087,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh 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= +olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 h1:slmdOY3vp8a7KQbHkL+FLbvbkgMqmXojpFUO/jENuqQ= +olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3/go.mod h1:oVgVk4OWVDi43qWBEyGhXgYxt7+ED4iYNpTngSLX2Iw= 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= diff --git a/immutable/config/mainnet-public.toml b/immutable/config/mainnet-public.toml new file mode 100644 index 000000000..4e7aa117b --- /dev/null +++ b/immutable/config/mainnet-public.toml @@ -0,0 +1,88 @@ +[Eth] +NetworkId = 13371 +SyncMode = "full" +EthDiscoveryURLs = [] +SnapDiscoveryURLs = [] +NoPruning = true +NoPrefetch = false +TxLookupLimit = 2350000 +TransactionHistory = 2350000 +StateScheme = "hash" +DatabaseCache = 512 +DatabaseFreezer = "" +TrieCleanCache = 256 +TrieDirtyCache = 256 +TrieTimeout = 3600000000000 +SnapshotCache = 0 +Preimages = false +FilterLogCacheSize = 32 +EnablePreimageRecording = false +RPCGasCap = 50000000 +RPCEVMTimeout = 5000000000 +RPCTxFeeCap = 0e+00 + +[Eth.Miner] +GasFloor = 0 +GasCeil = 30000000 +GasPrice = 10000000000 +Recommit = 999999999000000000 +NewPayloadTimeout = 2000000000 + +[Eth.TxPool] +Locals = [] +NoLocals = true +Journal = "transactions.rlp" +Rejournal = 3600000000000 +PriceLimit = 10000000000 +PriceBump = 10 +AccountSlots = 16 +GlobalSlots = 5120 +AccountQueue = 64 +GlobalQueue = 1024 +Lifetime = 3600000000000 + +[Eth.BlobPool] +Datadir = "blobpool" +Datacap = 1 +PriceBump = 100 + +[Eth.GPO] +Blocks = 20 +Percentile = 60 +MaxHeaderHistory = 1024 +MaxBlockHistory = 1024 +MaxPrice = 500000000000 +IgnorePrice = 2 + +[Node] +DataDir = "" +IPCPath = "geth.ipc" +HTTPHost = "" +HTTPModules = [] +WSHost = "" +WSModules = [] +AllowUnprotectedTxs = false + +[Node.P2P] +MaxPeers = 100 +NoDiscovery = false +BootstrapNodes = [] +StaticNodes = [ + "enode://55b31c45e8c1dbd3e6f551dc9b5ade80eed38e754f033348bd479d98b1db1aee298d93af5c0992f50a7c5cdb686790ade04c584018b2634da193bc8c42d72209@partner-public-0.p2p.immutable.com:30300", + "enode://2f421178a1b51a1bf9b10dc7e7a0e7cb64446e11b5c9a6bf9697fe619918f3835435833d5df7354d3e43382748fe29f5da3e4e7242fa58fa45af493c421e0cda@partner-public-1.p2p.immutable.com:30300", + "enode://7864b41535dbbaa31edc4db45157ec8967ac1f9fcea39b4ad17d4506f0618e38f339a2717b39c3d1198dec0154316c6a05408267eeb4d5d8973c6966a21dac90@partner-public-2.p2p.immutable.com:30300", +] +TrustedNodes = [ + "enode://55b31c45e8c1dbd3e6f551dc9b5ade80eed38e754f033348bd479d98b1db1aee298d93af5c0992f50a7c5cdb686790ade04c584018b2634da193bc8c42d72209@partner-public-0.p2p.immutable.com:30300", + "enode://2f421178a1b51a1bf9b10dc7e7a0e7cb64446e11b5c9a6bf9697fe619918f3835435833d5df7354d3e43382748fe29f5da3e4e7242fa58fa45af493c421e0cda@partner-public-1.p2p.immutable.com:30300", + "enode://7864b41535dbbaa31edc4db45157ec8967ac1f9fcea39b4ad17d4506f0618e38f339a2717b39c3d1198dec0154316c6a05408267eeb4d5d8973c6966a21dac90@partner-public-2.p2p.immutable.com:30300", +] +ListenAddr = "" +DiscAddr = "" +EnableMsgEvents = false + +[Node.HTTPTimeouts] +ReadTimeout = 0 +ReadHeaderTimeout = 0 +WriteTimeout = 0 +IdleTimeout = 0 diff --git a/immutable/config/mainnet.toml b/immutable/config/mainnet.toml new file mode 100644 index 000000000..a20066695 --- /dev/null +++ b/immutable/config/mainnet.toml @@ -0,0 +1,80 @@ +[Eth] +NetworkId = 13371 +SyncMode = "full" +EthDiscoveryURLs = [] +SnapDiscoveryURLs = [] +NoPruning = true +NoPrefetch = false +TxLookupLimit = 2350000 +TransactionHistory = 2350000 +StateScheme = "hash" +DatabaseCache = 512 +DatabaseFreezer = "" +TrieCleanCache = 256 +TrieDirtyCache = 256 +TrieTimeout = 3600000000000 +SnapshotCache = 0 +Preimages = false +FilterLogCacheSize = 32 +EnablePreimageRecording = false +RPCGasCap = 50000000 +RPCEVMTimeout = 5000000000 +RPCTxFeeCap = 0e+00 + +[Eth.TxPool] +Locals = [] +NoLocals = true +Journal = "transactions.rlp" +Rejournal = 3600000000000 +PriceLimit = 10000000000 +PriceBump = 10 +AccountSlots = 16 +GlobalSlots = 5120 +AccountQueue = 64 +GlobalQueue = 1024 +Lifetime = 3600000000000 + +[Eth.BlobPool] +Datadir = "blobpool" +Datacap = 0 +PriceBump = 100 + +[Eth.GPO] +Blocks = 20 +Percentile = 60 +MaxHeaderHistory = 1024 +MaxBlockHistory = 1024 +MaxPrice = 500000000000 +IgnorePrice = 2 + +[Node] +DataDir = "" +IPCPath = "geth.ipc" +HTTPHost = "" +HTTPModules = [] +WSHost = "" +WSModules = [] + +[Node.P2P] +MaxPeers = 100 +NoDiscovery = true +BootstrapNodes = [] +StaticNodes = [ +"enode://eb7397171926b31cebc7a6036a5c21060390b4ccdefcf6bad4a047b99a63acc6ed9c9aee057aea0107bb40e1595158d1035de9b5290985dc3462de809fc592ac@partner-0.p2p.immutable.com:30300", +"enode://221c21671dd3fcb20caebec9f4caf3d282202d5184e583ea9c1502204822d8ee6b4919588745d71ef21f223f2167c377ac70cf7dd4c7bc41436ef359c85ebe00@partner-1.p2p.immutable.com:30300", +"enode://7fc82edda3dd04b4e50f14e3e68a186520c65e69eda04bfc649725463e57c9f1120e495f57d4a7f7de848a921e35c6fb4ba122867bf6765c72b648f2ac2b1d9e@partner-2.p2p.immutable.com:30300" +] +TrustedNodes = [ +"enode://eb7397171926b31cebc7a6036a5c21060390b4ccdefcf6bad4a047b99a63acc6ed9c9aee057aea0107bb40e1595158d1035de9b5290985dc3462de809fc592ac@partner-0.p2p.immutable.com:30300", +"enode://221c21671dd3fcb20caebec9f4caf3d282202d5184e583ea9c1502204822d8ee6b4919588745d71ef21f223f2167c377ac70cf7dd4c7bc41436ef359c85ebe00@partner-1.p2p.immutable.com:30300", +"enode://7fc82edda3dd04b4e50f14e3e68a186520c65e69eda04bfc649725463e57c9f1120e495f57d4a7f7de848a921e35c6fb4ba122867bf6765c72b648f2ac2b1d9e@partner-2.p2p.immutable.com:30300" +] +ListenAddr = "" +DiscAddr = "" +EnableMsgEvents = false + +[Node.HTTPTimeouts] +ReadTimeout = 0 +ReadHeaderTimeout = 0 +WriteTimeout = 0 +IdleTimeout = 0 diff --git a/immutable/config/testnet-public.toml b/immutable/config/testnet-public.toml new file mode 100644 index 000000000..e7bac56e3 --- /dev/null +++ b/immutable/config/testnet-public.toml @@ -0,0 +1,89 @@ +[Eth] +NetworkId = 13473 +SyncMode = "full" +EthDiscoveryURLs = [] +SnapDiscoveryURLs = [] +NoPruning = true +NoPrefetch = false +TxLookupLimit = 2350000 +TransactionHistory = 2350000 +StateScheme = "hash" +DatabaseCache = 512 +DatabaseFreezer = "" +TrieCleanCache = 256 +TrieDirtyCache = 256 +TrieTimeout = 3600000000000 +SnapshotCache = 0 +Preimages = false +FilterLogCacheSize = 32 +EnablePreimageRecording = false +RPCGasCap = 50000000 +RPCEVMTimeout = 5000000000 +RPCTxFeeCap = 0e+00 + +[Eth.Miner] +GasFloor = 0 +GasCeil = 30000000 +GasPrice = 10000000000 +Recommit = 999999999000000000 +NewPayloadTimeout = 2000000000 + +[Eth.TxPool] +Locals = [] +NoLocals = true +Journal = "transactions.rlp" +Rejournal = 3600000000000 +PriceLimit = 10000000000 +PriceBump = 10 +AccountSlots = 16 +GlobalSlots = 5120 +AccountQueue = 64 +GlobalQueue = 1024 +Lifetime = 3600000000000 + +[Eth.BlobPool] +Datadir = "blobpool" +Datacap = 1 +PriceBump = 100 + +[Eth.GPO] +Blocks = 20 +Percentile = 60 +MaxHeaderHistory = 1024 +MaxBlockHistory = 1024 +MaxPrice = 500000000000 +IgnorePrice = 2 + +[Node] +DataDir = "" +IPCPath = "geth.ipc" +HTTPHost = "" +HTTPModules = [] +WSHost = "" +WSModules = [] +AllowUnprotectedTxs = true + +[Node.P2P] +MaxPeers = 100 +NoDiscovery = false +BootstrapNodes = [] +StaticNodes = [ + "enode://8c99adb00fd67c2b8f47fb4cc21fd1d9da469385f24c38ea593fea0ba986b7faa070aeb5646af25922bc06f0e2435dca47dc42df10c1dc138f735bda3a9925fe@partner-public-0.p2p.testnet.immutable.com:30300", + "enode://7abec29f54c9e6bbf3c2cc124734d154e1cbf08c5890446a6438108581e89cef3932603e2412c299bb33892f5d08e87264f293e55703df2aace9e8e9c9958b06@partner-public-1.p2p.testnet.immutable.com:30300", + "enode://92d767213118c6c5986eff443f82b8d6b2db284b2cadadd309c96d3b49cc8ded5f2ca05712f5301f392e937dbdfff50812c09875fb54d066e7a2f7b8bbd0c3fb@partner-public-2.p2p.testnet.immutable.com:30300", +] +TrustedNodes = [ + "enode://8c99adb00fd67c2b8f47fb4cc21fd1d9da469385f24c38ea593fea0ba986b7faa070aeb5646af25922bc06f0e2435dca47dc42df10c1dc138f735bda3a9925fe@partner-public-0.p2p.testnet.immutable.com:30300", + "enode://7abec29f54c9e6bbf3c2cc124734d154e1cbf08c5890446a6438108581e89cef3932603e2412c299bb33892f5d08e87264f293e55703df2aace9e8e9c9958b06@partner-public-1.p2p.testnet.immutable.com:30300", + "enode://92d767213118c6c5986eff443f82b8d6b2db284b2cadadd309c96d3b49cc8ded5f2ca05712f5301f392e937dbdfff50812c09875fb54d066e7a2f7b8bbd0c3fb@partner-public-2.p2p.testnet.immutable.com:30300", +] + +ListenAddr = "" +DiscAddr = "" +EnableMsgEvents = false + +[Node.HTTPTimeouts] +ReadTimeout = 0 +ReadHeaderTimeout = 0 +WriteTimeout = 0 +IdleTimeout = 0 diff --git a/immutable/config/testnet.toml b/immutable/config/testnet.toml new file mode 100644 index 000000000..6ad033c98 --- /dev/null +++ b/immutable/config/testnet.toml @@ -0,0 +1,80 @@ +[Eth] +NetworkId = 13473 +SyncMode = "full" +EthDiscoveryURLs = [] +SnapDiscoveryURLs = [] +NoPruning = true +NoPrefetch = false +TxLookupLimit = 2350000 +TransactionHistory = 2350000 +StateScheme = "hash" +DatabaseCache = 512 +DatabaseFreezer = "" +TrieCleanCache = 256 +TrieDirtyCache = 256 +TrieTimeout = 3600000000000 +SnapshotCache = 0 +Preimages = false +FilterLogCacheSize = 32 +EnablePreimageRecording = false +RPCGasCap = 50000000 +RPCEVMTimeout = 5000000000 +RPCTxFeeCap = 0e+00 + +[Eth.TxPool] +Locals = [] +NoLocals = true +Journal = "transactions.rlp" +Rejournal = 3600000000000 +PriceLimit = 10000000000 +PriceBump = 10 +AccountSlots = 16 +GlobalSlots = 5120 +AccountQueue = 64 +GlobalQueue = 1024 +Lifetime = 3600000000000 + +[Eth.BlobPool] +Datadir = "blobpool" +Datacap = 0 +PriceBump = 100 + +[Eth.GPO] +Blocks = 20 +Percentile = 60 +MaxHeaderHistory = 1024 +MaxBlockHistory = 1024 +MaxPrice = 500000000000 +IgnorePrice = 2 + +[Node] +DataDir = "" +IPCPath = "geth.ipc" +HTTPHost = "" +HTTPModules = [] +WSHost = "" +WSModules = [] + +[Node.P2P] +MaxPeers = 100 +NoDiscovery = true +BootstrapNodes = [] +StaticNodes = [ + "enode://24d4248973a456502fcff378749d9c7290a6875b9f5116d6e77572932727ae6676637c7e98f1749ccd81c7b7d340f4413a029aa4fdb976650c51c35b38ef3233@partner-0.p2p.testnet.immutable.com:30300", + "enode://6e3da16b1b5792574b2ee717c8f37d0efc7a1ba934de439419869e43ff40d529c175773dcd0afc76c826c036e0068a8749ae836d5f5e53de89e96382cf4b1558@partner-1.p2p.testnet.immutable.com:30300", + "enode://eccb3c547028bcde38fc88e2a1517c8adcc4820e69869b7d6831a287e311df7a1c47f05adec0d5813ed4734f23485f15f33583c57e9674894eee4547856cb902@partner-2.p2p.testnet.immutable.com:30300", +] +TrustedNodes = [ + "enode://24d4248973a456502fcff378749d9c7290a6875b9f5116d6e77572932727ae6676637c7e98f1749ccd81c7b7d340f4413a029aa4fdb976650c51c35b38ef3233@partner-0.p2p.testnet.immutable.com:30300", + "enode://6e3da16b1b5792574b2ee717c8f37d0efc7a1ba934de439419869e43ff40d529c175773dcd0afc76c826c036e0068a8749ae836d5f5e53de89e96382cf4b1558@partner-1.p2p.testnet.immutable.com:30300", + "enode://eccb3c547028bcde38fc88e2a1517c8adcc4820e69869b7d6831a287e311df7a1c47f05adec0d5813ed4734f23485f15f33583c57e9674894eee4547856cb902@partner-2.p2p.testnet.immutable.com:30300", +] +ListenAddr = "" +DiscAddr = "" +EnableMsgEvents = false + +[Node.HTTPTimeouts] +ReadTimeout = 0 +ReadHeaderTimeout = 0 +WriteTimeout = 0 +IdleTimeout = 0 diff --git a/internal/era/era.go b/internal/era/era.go index a0e701b7e..8e5ad3cbf 100644 --- a/internal/era/era.go +++ b/internal/era/era.go @@ -150,7 +150,8 @@ func (e *Era) GetBlockByNumber(num uint64) (*types.Block, error) { if err := rlp.Decode(r, &body); err != nil { return nil, err } - return types.NewBlockWithHeader(&header).WithBody(body.Transactions, body.Uncles), nil + // CHANGE(immutable): Add Withdrawals support + return types.NewBlockWithHeader(&header).WithBody(body.Transactions, body.Uncles).WithWithdrawals(body.Withdrawals), nil } // Accumulator reads the accumulator entry in the Era1 file. diff --git a/internal/era/iterator.go b/internal/era/iterator.go index e74a8154b..85ed1a466 100644 --- a/internal/era/iterator.go +++ b/internal/era/iterator.go @@ -73,7 +73,8 @@ func (it *Iterator) Block() (*types.Block, error) { if err := rlp.Decode(it.inner.Body, &body); err != nil { return nil, err } - return types.NewBlockWithHeader(&header).WithBody(body.Transactions, body.Uncles), nil + // CHANGE(immutable): Add Withdrawals support + return types.NewBlockWithHeader(&header).WithBody(body.Transactions, body.Uncles).WithWithdrawals(body.Withdrawals), nil } // Receipts returns the receipts for the iterator's current position. diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 863849f4d..4f55ee352 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1093,7 +1093,7 @@ func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.S defer cancel() // Get a new instance of the EVM. - blockCtx := core.NewEVMBlockContext(header, NewChainContext(ctx, b), nil) + blockCtx := core.NewEVMBlockContext(header, NewChainContext(ctx, b), nil, b.ChainConfig()) if blockOverrides != nil { blockOverrides.Apply(&blockCtx) } diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index a6f7405eb..9517b08ba 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -567,7 +567,7 @@ func (b testBackend) GetEVM(ctx context.Context, msg *core.Message, state *state vmConfig = b.chain.GetVMConfig() } txContext := core.NewEVMTxContext(msg) - context := core.NewEVMBlockContext(header, b.chain, nil) + context := core.NewEVMBlockContext(header, b.chain, nil, b.ChainConfig()) if blockContext != nil { context = *blockContext } diff --git a/internal/shutdowncheck/shutdown_tracker.go b/internal/shutdowncheck/shutdown_tracker.go index c95b4f02f..2e0e70c8d 100644 --- a/internal/shutdowncheck/shutdown_tracker.go +++ b/internal/shutdowncheck/shutdown_tracker.go @@ -58,6 +58,8 @@ func (t *ShutdownTracker) MarkStartup() { "age", common.PrettyAge(t)) } } + // CHANGE(immutable): log shutdown tracker steps + log.Info("Shutdown tracker started marker") } // Start runs an event loop that updates the current marker's timestamp every 5 minutes. @@ -69,6 +71,8 @@ func (t *ShutdownTracker) Start() { select { case <-ticker.C: rawdb.UpdateUncleanShutdownMarker(t.db) + // CHANGE(immutable): log shutdown tracker steps + log.Info("Shutdown tracker updated marker") case <-t.stopCh: return } @@ -82,4 +86,6 @@ func (t *ShutdownTracker) Stop() { t.stopCh <- struct{}{} // Clear last marker. rawdb.PopUncleanShutdownMarker(t.db) + // CHANGE(immutable): log shutdown tracker steps + log.Info("Shutdown tracker stopped") } diff --git a/miner/worker.go b/miner/worker.go index 9a3610623..78cb6136a 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -35,6 +35,7 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "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/trie" "github.com/holiman/uint256" @@ -60,7 +61,8 @@ const ( // maxRecommitInterval is the maximum time interval to recreate the sealing block with // any newly arrived transactions. - maxRecommitInterval = 15 * time.Second + // CHANGE(immutable): set to max to avoid reorg + maxRecommitInterval = 999999999 * time.Second // intervalAdjustRatio is the impact a single interval adjustment has on sealing work // resubmitting interval. @@ -78,6 +80,21 @@ var ( errBlockInterruptedByNewHead = errors.New("new head arrived while building block") errBlockInterruptedByRecommit = errors.New("recommit interrupt while building block") errBlockInterruptedByTimeout = errors.New("timeout while building block") + + // CHANGE(immutable): add metrics to geth worker loop + gasUsedGuage = metrics.NewRegisteredGauge("worker/block/gasused", nil) + blockTxGauge = metrics.NewRegisteredGauge("worker/block/tx", nil) + txExecutionTimer = metrics.NewRegisteredTimer("worker/block/txexecution", nil) + blockConstructionTimer = metrics.NewRegisteredTimer("worker/block/blockconstruction", nil) + commitWorkTimer = metrics.NewRegisteredTimer("worker/block/commitwork", nil) + prepareWorkTimer = metrics.NewRegisteredTimer("worker/block/preparework", nil) + taskLoopTimer = metrics.NewRegisteredTimer("worker/block/taskloop", nil) + resultLoopTimer = metrics.NewRegisteredTimer("worker/block/resultloop", nil) + + // CHANGE(immutable): define start time for block construction as process is across multiple functions + blockConstructionStartTime time.Time + taskLoopStartTime time.Time + blockConstructionTrigger bool ) // environment is the worker's current environment and holds all @@ -464,6 +481,9 @@ func (w *worker) newWorkLoop(recommit time.Duration) { commit(commitInterruptNewHead) case head := <-w.chainHeadCh: + // CHANGE(immutable): capture start time for block construction + blockConstructionStartTime = time.Now() + blockConstructionTrigger = true clearPending(head.Block.NumberU64()) timestamp = time.Now().Unix() commit(commitInterruptNewHead) @@ -499,11 +519,11 @@ func (w *worker) newWorkLoop(recommit time.Duration) { before := recommit target := float64(recommit.Nanoseconds()) / adjust.ratio recommit = recalcRecommit(minRecommit, recommit, target, true) - log.Trace("Increase miner recommit interval", "from", before, "to", recommit) + log.Warn("Increase miner recommit interval", "from", before, "to", recommit) } else { before := recommit recommit = recalcRecommit(minRecommit, recommit, float64(minRecommit.Nanoseconds()), false) - log.Trace("Decrease miner recommit interval", "from", before, "to", recommit) + log.Warn("Decrease miner recommit interval", "from", before, "to", recommit) } if w.resubmitHook != nil { @@ -604,7 +624,11 @@ func (w *worker) taskLoop() { ) // interrupt aborts the in-flight sealing task. - interrupt := func() { + // CHANGE(immutable): add task parameter for logging + interrupt := func(task *task) { + if task != nil { + log.Info("Interrupting sealing task", "block number", task.block.Number().String()) + } if stopCh != nil { close(stopCh) stopCh = nil @@ -613,6 +637,8 @@ func (w *worker) taskLoop() { for { select { case task := <-w.taskCh: + // CHANGE(immutable): capture time we receive a new task + taskLoopStartTime = time.Now() if w.newTaskHook != nil { w.newTaskHook(task) } @@ -622,9 +648,9 @@ func (w *worker) taskLoop() { continue } // Interrupt previous sealing operation - interrupt() + interrupt(task) stopCh, prev = make(chan struct{}), sealHash - + // Log the interrupt here w/ block number if w.skipSealHook != nil && w.skipSealHook(task) { continue } @@ -638,8 +664,10 @@ func (w *worker) taskLoop() { delete(w.pendingTasks, sealHash) w.pendingMu.Unlock() } + // Log here when the sealing task is complete case <-w.exitCh: - interrupt() + // CHANGE(immutable): populate empty task for logging + interrupt(nil) return } } @@ -652,6 +680,11 @@ func (w *worker) resultLoop() { for { select { case block := <-w.resultCh: + // CHANGE(immutable): capture taskloop processing time + taskLoopTimer.UpdateSince(taskLoopStartTime) + // CHANGE(immutable): capture result loop start time + start := time.Now() + // Short circuit when receiving empty result. if block == nil { continue @@ -709,6 +742,21 @@ func (w *worker) resultLoop() { // Broadcast the block and announce chain insertion event w.mux.Post(core.NewMinedBlockEvent{Block: block}) + // CHANGE(immutable): capture block construction time + if blockConstructionTrigger { + blockConstructionTimer.UpdateSince(blockConstructionStartTime) + blockConstructionTrigger = false + } + + // CHANGE(immutable): update tx count in block based on receipts + blockTxGauge.Update(int64(len(task.receipts))) + + // CHANGE(immutable): capture gas used in block + gasUsedGuage.Update(int64(block.GasUsed())) + + // CHANGE(immutable): capture result loop processing time + resultLoopTimer.UpdateSince(start) + case <-w.exitCh: return } @@ -755,13 +803,23 @@ func (w *worker) updateSnapshot(env *environment) { func (w *worker) commitTransaction(env *environment, tx *types.Transaction) ([]*types.Log, error) { if tx.Type() == types.BlobTxType { + // CHANGE(immutable) disable blob transactions + if w.chainConfig.IsImmutableZKEVM() { + return nil, fmt.Errorf("blob transactions are not supported") + } return w.commitBlobTransaction(env, tx) } receipt, err := w.applyTransaction(env, tx) if err != nil { + // CHANGE(immutable) add error logging + log.Warn("Transaction failed, discarding", "hash", tx.Hash(), "err", err) return nil, err } env.txs = append(env.txs, tx) + if receipt.Status == types.ReceiptStatusFailed { + // CHANGE(immutable) add error logging + log.Warn("receipt status : failed", "hash", tx.Hash()) + } env.receipts = append(env.receipts, receipt) return receipt.Logs, nil } @@ -905,6 +963,7 @@ func (w *worker) commitTransactions(env *environment, plainTxs, blobTxs *transac txs.Pop() } } + if !w.isRunning() && len(coalescedLogs) > 0 { // We don't push the pendingLogsEvent while we are sealing. The reason is that // when we are sealing, the worker will regenerate a sealing block every 3 seconds. @@ -984,8 +1043,18 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) { header.GasLimit = core.CalcGasLimit(parentGasLimit, w.config.GasCeil) } } - // Apply EIP-4844, EIP-4788. + // Run the consensus preparation with the default or customized consensus engine. + if err := w.engine.Prepare(w.chain, header); err != nil { + log.Error("Failed to prepare header for sealing", "err", err) + return nil, err + } + // CHANGE(immutable): Move this logic after engine.Prepare because + // clique can update the block time which would cause the logic below to + // panic if the change in time brought a parent block into cancun fork time. + // This change means that consensus implementations will not have access to + // blob related fields and parent beacon root. if w.chainConfig.IsCancun(header.Number, header.Time) { + // Apply EIP-4844, EIP-4788. var excessBlobGas uint64 if w.chainConfig.IsCancun(parent.Number, parent.Time) { excessBlobGas = eip4844.CalcExcessBlobGas(*parent.ExcessBlobGas, *parent.BlobGasUsed) @@ -997,11 +1066,6 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) { header.ExcessBlobGas = &excessBlobGas header.ParentBeaconRoot = genParams.beaconRoot } - // Run the consensus preparation with the default or customized consensus engine. - if err := w.engine.Prepare(w.chain, header); err != nil { - log.Error("Failed to prepare header for sealing", "err", err) - return nil, err - } // Could potentially happen if starting to mine in an odd state. // Note genParams.coinbase can be different with header.Coinbase // since clique algorithm can modify the coinbase field in header. @@ -1011,7 +1075,7 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) { return nil, err } if header.ParentBeaconRoot != nil { - context := core.NewEVMBlockContext(header, w.chain, nil) + context := core.NewEVMBlockContext(header, w.chain, nil, w.chainConfig) vmenv := vm.NewEVM(context, vm.TxContext{}, env.state, w.chainConfig, vm.Config{}) core.ProcessBeaconBlockRoot(*header.ParentBeaconRoot, vmenv, env.state) } @@ -1022,14 +1086,19 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) { // into the given sealing block. The transaction selection and ordering strategy can // be customized with the plugin in the future. func (w *worker) fillTransactions(interrupt *atomic.Int32, env *environment) error { - w.mu.RLock() - tip := w.tip - w.mu.RUnlock() - + // CHANGE(immutable): Disable tip enforcement so that we mine txs + // that are priced below price limit + base fee. // Retrieve the pending transactions pre-filtered by the 1559/4844 dynamic fees + var tip *uint256.Int + if !w.chainConfig.IsImmutableZKEVM() { + w.mu.RLock() + tip = w.tip + w.mu.RUnlock() + } filter := txpool.PendingFilter{ MinTip: tip, } + if env.header.BaseFee != nil { filter.BaseFee = uint256.MustFromBig(env.header.BaseFee) } @@ -1073,6 +1142,7 @@ func (w *worker) fillTransactions(interrupt *atomic.Int32, env *environment) err return err } } + return nil } @@ -1128,12 +1198,21 @@ func (w *worker) commitWork(interrupt *atomic.Int32, timestamp int64) { work, err := w.prepareWork(&generateParams{ timestamp: uint64(timestamp), coinbase: coinbase, + // CHANGE(immutable): Add the BeaconRoot value of MinHash as an input to work generation, + // so that the block producer appropriately sets the Beacon Root + beaconRoot: &common.MinHash, }) if err != nil { return } + // CHANGE(immutable): capture time spent preparing work + prepareWorkTimer.UpdateSince(start) + // Fill pending transactions from the txpool into the block. err = w.fillTransactions(interrupt, work) + // CHANGE(immutable): update tx execution timer + txExecutionTimer.UpdateSince(start) + switch { case err == nil: // The entire block is filled, decrease resubmit interval in case @@ -1199,6 +1278,9 @@ func (w *worker) commit(env *environment, interval func(), update bool, start ti "txs", env.tcount, "gas", block.GasUsed(), "fees", feesInEther, "elapsed", common.PrettyDuration(time.Since(start))) + // CHANGE(immutable): capture time spent committing sealing work + commitWorkTimer.UpdateSince(start) + case <-w.exitCh: log.Info("Worker has exited") } diff --git a/node/api.go b/node/api.go index f81f394be..947aed15b 100644 --- a/node/api.go +++ b/node/api.go @@ -203,7 +203,8 @@ func (api *adminAPI) StartHTTP(host *string, port *int, cors *string, apis *stri if err := api.node.http.setListenAddr(*host, *port); err != nil { return false, err } - if err := api.node.http.enableRPC(api.node.rpcAPIs, config); err != nil { + // CHANGE(immutable) add NR agent + if err := api.node.http.enableRPC(api.node.NewRelic, api.node.rpcAPIs, config); err != nil { return false, err } if err := api.node.http.start(); err != nil { @@ -278,7 +279,8 @@ func (api *adminAPI) StartWS(host *string, port *int, allowedOrigins *string, ap return false, err } openApis, _ := api.node.getAPIs() - if err := server.enableWS(openApis, config); err != nil { + // CHANGE(immutable) add NR agent + if err := server.enableWS(api.node.NewRelic, openApis, config); err != nil { return false, err } if err := server.start(); err != nil { diff --git a/node/config.go b/node/config.go index 949db887e..b830b7009 100644 --- a/node/config.go +++ b/node/config.go @@ -210,6 +210,16 @@ type Config struct { // EnablePersonal enables the deprecated personal namespace. EnablePersonal bool `toml:"-"` + // CHANGE(immutable): Disable specific namespaces from the rpc server, + // By default they're enabled + DisableDebug bool `toml:"-"` + DisableAdmin bool `toml:"-"` + DisableEngine bool `toml:"-"` + DisableTxPool bool `toml:"-"` + DisableClique bool `toml:"-"` + DisableMiner bool `toml:"-"` + DisablePersonal bool `toml:"-"` + DBEngine string `toml:",omitempty"` } diff --git a/node/defaults.go b/node/defaults.go index 307d9e186..4fad4e09c 100644 --- a/node/defaults.go +++ b/node/defaults.go @@ -73,6 +73,13 @@ var DefaultConfig = Config{ NAT: nat.Any(), }, DBEngine: "", // Use whatever exists, will default to Pebble if non-existent and supported + + // CHANGE(immutable): Disable specific namespaces from the rpc server, + // By default they're enabled + DisableDebug: false, + DisableAdmin: false, + DisableEngine: false, + DisableTxPool: false, } // DefaultDataDir is the default data directory to use for the databases and other diff --git a/node/immutable_newrelic.go b/node/immutable_newrelic.go new file mode 100644 index 000000000..9f874c78f --- /dev/null +++ b/node/immutable_newrelic.go @@ -0,0 +1,111 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package node + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + + newrelic "github.com/newrelic/go-agent/v3/newrelic" + log "github.com/sirupsen/logrus" +) + +var ( + errBodySizeExceedsLimit = fmt.Errorf("request body size exceeds limit") +) + +type RequestBody struct { + Jsonrpc string `json:"jsonrpc"` + Id int64 `json:"id"` + Method string `json:"method"` +} + +func newRelicMiddleware(nrApp *newrelic.Application, next http.Handler) http.Handler { //nolint:unused + // CHANGE(immutable) add NR agent + if nrApp == nil { + log.Error("Failed to initialise New Relic middlware: nrApp is nil") + return next + } + _, handler := newrelic.WrapHandleFunc(nrApp, "/", func(w http.ResponseWriter, req *http.Request) { + txn := newrelic.FromContext(req.Context()) + txn.AddAttribute("x-api-key", req.Header.Get("x-api-key")) + + // Capture ethAddress from headers in order to monitor usage and errors + txn.AddAttribute("x-imx-eth-address", req.Header.Get("x-imx-eth-address")) + // Capture SDK version from headers in order to monitor usage and errors + txn.AddAttribute("x-sdk-version", req.Header.Get("x-sdk-version")) + txn.AddAttribute("x-forwarded-for", req.Header.Get("x-forwarded-for")) + txn.AddAttribute("x-zkevm-rpc-sticky", req.Header.Get("x-zkevm-rpc-sticky")) + txn.AddAttribute("k6-load-test-id", req.Header.Get("k6-load-test-id")) + // the W3C trace context header entries. + // see: https://github.com/w3c/trace-context/blob/main/spec/20-http_request_header_format.md + // RFC: https://www.w3.org/TR/trace-context/ + // NR, k6 and any other services following the W3C standard can generate these header entries. + txn.AddAttribute("traceparent", req.Header.Get("traceparent")) + txn.AddAttribute("tracestate", req.Header.Get("tracestate")) + + // Capture the request body + mb := int64(1024 * 1024) + body, bodyReader, err := limitedTeeRead(req.Body, mb) + if err != nil { + log.WithError(err).Error("Failed to read request body") + // respond to the client with a 503 error. + // We want to respond here because we can't use the body in subsequent handlers anyway. + http.Error(w, "Failed to read request body", http.StatusInternalServerError) + return + } + requestBody := RequestBody{} + if err = json.Unmarshal(body, &requestBody); err != nil { + log.WithError(err).Error("Failed to parse request body") + } else { + // Add the RPC method to the transaction attributes + txn.AddAttribute("rpcMethod", requestBody.Method) + } + + // Reset the request body to the original state + req.Body = bodyReader + + // Next handler + ctx := newrelic.NewContext(req.Context(), txn) + next.ServeHTTP(w, req.WithContext(ctx)) + }) + return http.HandlerFunc(handler) +} + +// limitedTeeRead reads the request body while writing it back to an io.ReadCloser +// that can be used to read the same data again. +// It returns the content of the input reader, the new io.ReadCloser and an error +// if the content size exceeds the limit. +// +// The returned io.ReadCloser uses the same underlying buffer as the returned []byte. +// This is done for performance reasons. Care should therefore be taken to ensure +// that the returned content buffer is not be written to. As this would affect the +// content of the returned io.ReadCloser downstream. +func limitedTeeRead(reader io.ReadCloser, limitBytes int64) ([]byte, io.ReadCloser, error) { + limitReader := io.LimitReader(reader, limitBytes) + content, err := io.ReadAll(limitReader) + if err != nil { + return nil, nil, err + } + if len(content) >= int(limitBytes) { + return nil, nil, errBodySizeExceedsLimit + } + return content, io.NopCloser(bytes.NewBuffer(content)), nil +} diff --git a/node/immutable_newrelic_test.go b/node/immutable_newrelic_test.go new file mode 100644 index 000000000..4c5c0bbee --- /dev/null +++ b/node/immutable_newrelic_test.go @@ -0,0 +1,56 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package node + +import ( + "bytes" + "errors" + "io" + "testing" +) + +func TestLimitedTeeReader_Undersized(t *testing.T) { + body := []byte("xxxxxxx") + limitBytes := int64(len(body)) * 2 + oldReader := io.NopCloser(bytes.NewReader(body)) + content, newReader, err := limitedTeeRead(oldReader, limitBytes) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !bytes.Equal(content, body) { + t.Fatalf("expected content to be the same, got: %v", content) + } + readContent, err := io.ReadAll(newReader) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !bytes.Equal(readContent, body) { + t.Fatalf("expected read content to be the same, got: %v", readContent) + } +} + +func TestLimitedTeeReader_Oversized(t *testing.T) { + first := []byte("xxxxxxx") + second := []byte("yyyyyyy") + body := append(first, second...) + limitBytes := int64(len(first)) + readCloser := io.NopCloser(bytes.NewReader(body)) + _, _, err := limitedTeeRead(readCloser, limitBytes) + if !errors.Is(err, errBodySizeExceedsLimit) { + t.Fatalf("expected error specific error, got: %v", err) + } +} diff --git a/node/node.go b/node/node.go index dfa83d58c..41599677e 100644 --- a/node/node.go +++ b/node/node.go @@ -38,6 +38,8 @@ import ( "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/rpc" "github.com/gofrs/flock" + "github.com/newrelic/go-agent/v3/integrations/nrlogrus" + "github.com/newrelic/go-agent/v3/newrelic" ) // Node is a container on which services can be registered. @@ -53,7 +55,7 @@ type Node struct { server *p2p.Server // Currently running P2P networking layer startStopLock sync.Mutex // Start/Stop are protected by an additional lock state int // Tracks state of node lifecycle - + NewRelic *newrelic.Application lock sync.Mutex lifecycles []Lifecycle // All registered backends, services, and auxiliary services that have a lifecycle rpcAPIs []rpc.API // List of APIs currently provided by the node @@ -103,6 +105,27 @@ func New(conf *Config) (*Node, error) { } server := rpc.NewServer() server.SetBatchLimits(conf.BatchRequestLimit, conf.BatchResponseMaxSize) + + // Initialise New Relic agent + // CHANGE(immutable) add NR agent + conf.Logger.Info("initialising New Relic agent...") + var nrApp *newrelic.Application + var err error + if os.Getenv("NEW_RELIC_APP_NAME") != "" { + nrApp, err = newrelic.NewApplication( + newrelic.ConfigFromEnvironment(), + newrelic.ConfigEnabled(true), + nrlogrus.ConfigStandardLogger(), + newrelic.ConfigDistributedTracerEnabled(true), + ) + if err != nil { + log.Error("Failed to initialise New Relic agent: ", err) + nrApp = nil + } + } else { + log.Error("Failed to initialise New Relic agent: NEW_RELIC_APP_NAME missing") + } + node := &Node{ config: conf, inprocHandler: server, @@ -111,6 +134,7 @@ func New(conf *Config) (*Node, error) { stop: make(chan struct{}), server: &p2p.Server{Config: conf.P2P}, databases: make(map[*closeTrackingDB]struct{}), + NewRelic: nrApp, } // Register built-in APIs. @@ -209,11 +233,18 @@ func (n *Node) Close() error { n.lock.Lock() state := n.state n.lock.Unlock() + + // CHANGE(immutable): log node close procedure + logFunc := func(state string) { + n.log.Info("Node close procedure", "state", state) + } switch state { case initializingState: + logFunc("initializing") // The node was never started. return n.doClose(nil) case runningState: + logFunc("running") // The node was started, release resources acquired by Start(). var errs []error if err := n.stopServices(n.lifecycles); err != nil { @@ -221,6 +252,7 @@ func (n *Node) Close() error { } return n.doClose(errs) case closedState: + logFunc("closed") return ErrNodeStopped default: panic(fmt.Sprintf("node is in unknown state %d", state)) @@ -413,7 +445,8 @@ func (n *Node) startRPC() error { if err := server.setListenAddr(n.config.HTTPHost, port); err != nil { return err } - if err := server.enableRPC(openAPIs, httpConfig{ + // CHANGE(immutable) add NR agent + if err := server.enableRPC(n.NewRelic, openAPIs, httpConfig{ CorsAllowedOrigins: n.config.HTTPCors, Vhosts: n.config.HTTPVirtualHosts, Modules: n.config.HTTPModules, @@ -431,7 +464,7 @@ func (n *Node) startRPC() error { if err := server.setListenAddr(n.config.WSHost, port); err != nil { return err } - if err := server.enableWS(openAPIs, wsConfig{ + if err := server.enableWS(n.NewRelic, openAPIs, wsConfig{ Modules: n.config.WSModules, Origins: n.config.WSOrigins, prefix: n.config.WSPathPrefix, @@ -455,7 +488,8 @@ func (n *Node) startRPC() error { batchResponseSizeLimit: engineAPIBatchResponseSizeLimit, httpBodyLimit: engineAPIBodyLimit, } - err := server.enableRPC(allAPIs, httpConfig{ + // CHANGE(immutable) add NR agent + err := server.enableRPC(n.NewRelic, allAPIs, httpConfig{ CorsAllowedOrigins: DefaultAuthCors, Vhosts: n.config.AuthVirtualHosts, Modules: DefaultAuthModules, @@ -472,7 +506,7 @@ func (n *Node) startRPC() error { if err := server.setListenAddr(n.config.AuthAddr, port); err != nil { return err } - if err := server.enableWS(allAPIs, wsConfig{ + if err := server.enableWS(n.NewRelic, allAPIs, wsConfig{ Modules: DefaultAuthModules, Origins: DefaultAuthOrigins, prefix: DefaultAuthPrefix, @@ -597,6 +631,30 @@ func (n *Node) RegisterAPIs(apis []rpc.API) { // authentication, and the complete set func (n *Node) getAPIs() (unauthenticated, all []rpc.API) { for _, api := range n.rpcAPIs { + // CHANGE(immutable): the rpc apis returned here are the ones used by rpc + // We've made a modification here to filter out certain rpc API depending on + // config flags + if n.config.DisableAdmin && api.Namespace == "admin" { + continue + } + if n.config.DisableEngine && api.Namespace == "engine" { + continue + } + if n.config.DisableTxPool && api.Namespace == "txpool" { + continue + } + if n.config.DisableDebug && api.Namespace == "debug" { + continue + } + if n.config.DisableClique && api.Namespace == "clique" { + continue + } + if n.config.DisableMiner && api.Namespace == "miner" { + continue + } + if n.config.DisablePersonal && api.Namespace == "personal" { + continue + } if !api.Authenticated { unauthenticated = append(unauthenticated, api) } @@ -615,8 +673,8 @@ func (n *Node) RegisterHandler(name, path string, handler http.Handler) { if n.state != initializingState { panic("can't register HTTP handler on running/stopped node") } - - n.http.mux.Handle(path, handler) + // CHANGE(immutable): wrap the handler with newrelic middleware + n.http.mux.Handle(path, newRelicMiddleware(n.NewRelic, handler)) n.http.handlerNames[path] = name } diff --git a/node/rpcstack.go b/node/rpcstack.go index d80d5271a..b61d53e89 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -32,6 +32,7 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rpc" + "github.com/newrelic/go-agent/v3/newrelic" "github.com/rs/cors" ) @@ -294,7 +295,8 @@ func (h *httpServer) doStop() { } // enableRPC turns on JSON-RPC over HTTP on the server. -func (h *httpServer) enableRPC(apis []rpc.API, config httpConfig) error { +// CHANGE(immutable) add NR agent +func (h *httpServer) enableRPC(nrApp *newrelic.Application, apis []rpc.API, config httpConfig) error { h.mu.Lock() defer h.mu.Unlock() @@ -312,10 +314,13 @@ func (h *httpServer) enableRPC(apis []rpc.API, config httpConfig) error { return err } h.httpConfig = config - h.httpHandler.Store(&rpcHandler{ - Handler: NewHTTPHandlerStack(srv, config.CorsAllowedOrigins, config.Vhosts, config.jwtSecret), + // CHANGE(immutable): wrap the RPC handler with newrelic middleware + + rpcHandler := &rpcHandler{ + Handler: NewHTTPHandlerStack(newRelicMiddleware(nrApp, srv), config.CorsAllowedOrigins, config.Vhosts, config.jwtSecret), server: srv, - }) + } + h.httpHandler.Store(rpcHandler) return nil } @@ -330,7 +335,8 @@ func (h *httpServer) disableRPC() bool { } // enableWS turns on JSON-RPC over WebSocket on the server. -func (h *httpServer) enableWS(apis []rpc.API, config wsConfig) error { +// CHANGE(immutable) add NR agent +func (h *httpServer) enableWS(nrApp *newrelic.Application, apis []rpc.API, config wsConfig) error { h.mu.Lock() defer h.mu.Unlock() @@ -347,10 +353,12 @@ func (h *httpServer) enableWS(apis []rpc.API, config wsConfig) error { return err } h.wsConfig = config - h.wsHandler.Store(&rpcHandler{ - Handler: NewWSHandlerStack(srv.WebsocketHandler(config.Origins), config.jwtSecret), + // CHANGE(immutable): wrap the websocket handler with newrelic middleware + wsHandler := &rpcHandler{ + Handler: NewWSHandlerStack(newRelicMiddleware(nrApp, srv.WebsocketHandler(config.Origins)), config.jwtSecret), server: srv, - }) + } + h.wsHandler.Store(wsHandler) return nil } diff --git a/node/rpcstack_test.go b/node/rpcstack_test.go index e41cc51ad..0ab828828 100644 --- a/node/rpcstack_test.go +++ b/node/rpcstack_test.go @@ -242,9 +242,9 @@ func createAndStartServer(t *testing.T, conf *httpConfig, ws bool, wsConf *wsCon timeouts = &rpc.DefaultHTTPTimeouts } srv := newHTTPServer(testlog.Logger(t, log.LvlDebug), *timeouts) - assert.NoError(t, srv.enableRPC(apis(), *conf)) + assert.NoError(t, srv.enableRPC(nil, apis(), *conf)) if ws { - assert.NoError(t, srv.enableWS(nil, *wsConf)) + assert.NoError(t, srv.enableWS(nil, nil, *wsConf)) } assert.NoError(t, srv.setListenAddr("localhost", 0)) assert.NoError(t, srv.start()) diff --git a/p2p/discover/common.go b/p2p/discover/common.go index c9f0477de..0f7a3a4c5 100644 --- a/p2p/discover/common.go +++ b/p2p/discover/common.go @@ -65,7 +65,8 @@ func (cfg Config) withDefaults() Config { cfg.PingInterval = 10 * time.Second } if cfg.RefreshInterval == 0 { - cfg.RefreshInterval = 30 * time.Minute + // CHANGE(immutable): Reduce refresh interval. + cfg.RefreshInterval = 20 * time.Second } // Debug/test settings: diff --git a/p2p/discover/table.go b/p2p/discover/table.go index 2b7a28708..c14da4af5 100644 --- a/p2p/discover/table.go +++ b/p2p/discover/table.go @@ -58,7 +58,8 @@ const ( copyNodesInterval = 30 * time.Second seedMinTableTime = 5 * time.Minute seedCount = 30 - seedMaxAge = 5 * 24 * time.Hour + // CHANGE(immutable): Reduce seed max age from 120hrs + seedMaxAge = 1 * time.Second ) // Table is the 'node table', a Kademlia-like index of neighbor nodes. The table keeps @@ -315,15 +316,8 @@ func (tab *Table) doRefresh(done chan struct{}) { // Run self lookup to discover new neighbor nodes. tab.net.lookupSelf() - // The Kademlia paper specifies that the bucket refresh should - // perform a lookup in the least recently used bucket. We cannot - // adhere to this because the findnode target is a 512bit value - // (not hash-sized) and it is not easily possible to generate a - // sha3 preimage that falls into a chosen bucket. - // We perform a few lookups with a random target instead. - for i := 0; i < 3; i++ { - tab.net.lookupRandom() - } + // CHANGE(immutable): Remove the random lookup. + // Self lookup is sufficient to discover requisite nodes. } func (tab *Table) loadSeedNodes() { diff --git a/p2p/discover/v4_udp.go b/p2p/discover/v4_udp.go index 988f16b01..c142b9065 100644 --- a/p2p/discover/v4_udp.go +++ b/p2p/discover/v4_udp.go @@ -370,7 +370,13 @@ func (t *UDPv4) RequestENR(n *enode.Node) (*enode.Node, error) { return n, nil // response record is older } if err := netutil.CheckRelayIP(addr.IP, respN.IP()); err != nil { - return nil, fmt.Errorf("invalid IP in response record: %v", err) + // CHANGE(immutable): Log both IPs + return nil, fmt.Errorf( + "invalid IP in response record: %v. sender (%s), addr (%s)", + err, + addr.IP.String(), + respN.IP().String(), + ) } return respN, nil } diff --git a/p2p/enode/localnode.go b/p2p/enode/localnode.go index a18204e75..2714d2b93 100644 --- a/p2p/enode/localnode.go +++ b/p2p/enode/localnode.go @@ -207,7 +207,12 @@ func (ln *LocalNode) SetFallbackIP(ip net.IP) { func (ln *LocalNode) SetFallbackUDP(port int) { ln.mu.Lock() defer ln.mu.Unlock() - + // CHANGE(immutable): handle overflow case. + // Where 65535 is the maximum uint16 value (https://go.dev/src/builtin/builtin.go). + if port > 65535 { + log.Warn("port overflow, setting to 65535", "port", port) + port = 65535 + } ln.endpoint4.fallbackUDP = uint16(port) ln.endpoint6.fallbackUDP = uint16(port) ln.updateEndpoints() diff --git a/p2p/server.go b/p2p/server.go index 975a3bb91..052d70d5f 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -766,7 +766,8 @@ running: // The handshakes are done and it passed all checks. p := srv.launchPeer(c) peers[c.node.ID()] = p - srv.log.Debug("Adding p2p peer", "peercount", len(peers), "id", p.ID(), "conn", c.flags, "addr", p.RemoteAddr(), "name", p.Name()) + // CHANGE(immutable): log fullname + srv.log.Debug("Adding p2p peer", "peercount", len(peers), "id", p.ID(), "conn", c.flags, "addr", p.RemoteAddr(), "name", p.Fullname()) srv.dialsched.peerAdded(c) if p.Inbound() { inboundCount++ @@ -916,6 +917,12 @@ func (srv *Server) checkInboundConn(remoteIP net.IP) error { if srv.NetRestrict != nil && !srv.NetRestrict.Contains(remoteIP) { return errors.New("not in netrestrict list") } + + // CHANGE(immutable): Do not rate limit connections that match on NetRestrict + if srv.NetRestrict != nil && srv.NetRestrict.Contains(remoteIP) { + return nil + } + // Reject Internet peers that try too often. now := srv.clock.Now() srv.inboundHistory.expire(now, nil) diff --git a/params/config.go b/params/config.go index 21ede457f..a68403742 100644 --- a/params/config.go +++ b/params/config.go @@ -20,6 +20,7 @@ import ( "fmt" "math/big" + "github.com/ethereum/go-ethereum/cmd/geth/immutable/settings" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/params/forks" ) @@ -348,6 +349,9 @@ type ChainConfig struct { // Fork scheduling was switched from blocks to timestamps here + // CHANGE(immutable): Add PrevrandaoTime + PrevrandaoTime *uint64 `json:"prevrandaoTime,omitempty"` // Prevrandao switch time (nil = no fork, 0 = already on prevrandao) + ShanghaiTime *uint64 `json:"shanghaiTime,omitempty"` // Shanghai switch time (nil = no fork, 0 = already on shanghai) CancunTime *uint64 `json:"cancunTime,omitempty"` // Cancun switch time (nil = no fork, 0 = already on cancun) PragueTime *uint64 `json:"pragueTime,omitempty"` // Prague switch time (nil = no fork, 0 = already on prague) @@ -365,6 +369,10 @@ type ChainConfig struct { // Various consensus engines Ethash *EthashConfig `json:"ethash,omitempty"` Clique *CliqueConfig `json:"clique,omitempty"` + + // CHANGE(immutable): Add new chain config paramaters + // IsReorgBlocked will cause the node to treat chain reorg scenarios as an error state + IsReorgBlocked bool `json:"isReorgBlocked,omitempty"` } // EthashConfig is the consensus engine configs for proof-of-work based sealing. @@ -396,6 +404,9 @@ func (c *ChainConfig) Description() string { network = "unknown" } banner += fmt.Sprintf("Chain ID: %v (%s)\n", c.ChainID, network) + + // CHANGE(immutable): Add fork invariants info to the startup banner + banner += fmt.Sprintf("Reorg Invariants Enabled: %t\n", c.IsReorgBlocked) switch { case c.Ethash != nil: if c.TerminalTotalDifficulty == nil { @@ -448,7 +459,8 @@ func (c *ChainConfig) Description() string { // Add a special section for the merge as it's non-obvious if c.TerminalTotalDifficulty == nil { - banner += "The Merge is not yet available for this network!\n" + // CHANGE(immutable): Disable misleading logs. Clique uses difficulty header field for consensus. Immutable zkEVM does not use TerminalTotalDifficulty + //banner += "The Merge is not yet available for this network!\n" banner += " - Hard-fork specification: https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/paris.md\n" } else { banner += "Merge configured:\n" @@ -463,6 +475,10 @@ func (c *ChainConfig) Description() string { // Create a list of forks post-merge banner += "Post-Merge hard forks (timestamp based):\n" + // CHANGE(immutable): Add section for Prevrandao fork + if c.PrevrandaoTime != nil { + banner += fmt.Sprintf(" - Prevrandao: @%-10v\n", *c.PrevrandaoTime) + } if c.ShanghaiTime != nil { banner += fmt.Sprintf(" - Shanghai: @%-10v (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/shanghai.md)\n", *c.ShanghaiTime) } @@ -629,6 +645,8 @@ func (c *ChainConfig) CheckConfigForkOrder() error { {name: "arrowGlacierBlock", block: c.ArrowGlacierBlock, optional: true}, {name: "grayGlacierBlock", block: c.GrayGlacierBlock, optional: true}, {name: "mergeNetsplitBlock", block: c.MergeNetsplitBlock, optional: true}, + // CHANGE(immutable): Add prevrandao fork + {name: "prevrandaoTime", timestamp: c.PrevrandaoTime, optional: true}, {name: "shanghaiTime", timestamp: c.ShanghaiTime}, {name: "cancunTime", timestamp: c.CancunTime, optional: true}, {name: "pragueTime", timestamp: c.PragueTime, optional: true}, @@ -744,7 +762,11 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, headNumber *big.Int, // BaseFeeChangeDenominator bounds the amount the base fee can change between blocks. func (c *ChainConfig) BaseFeeChangeDenominator() uint64 { - return DefaultBaseFeeChangeDenominator + // CHANGE(immutable): Provide base fee change denominator specific to Immutable zkEVM + if c.IsImmutableZKEVM() { + return settings.BaseFeeChangeDenominator + } + return defaultBaseFeeChangeDenominator } // ElasticityMultiplier bounds the maximum gas limit an EIP-1559 block may have. diff --git a/params/immutable_config.go b/params/immutable_config.go new file mode 100644 index 000000000..2761dd2e5 --- /dev/null +++ b/params/immutable_config.go @@ -0,0 +1,59 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package params + +import ( + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/cmd/geth/immutable/settings" +) + +// IsImmutableZKEVM returns true if the configured chain ID pertains to an Immutable zkEVM network ID +func (c *ChainConfig) IsImmutableZKEVM() bool { + return c != nil && c.ChainID != nil && + (c.ChainID.Int64() == settings.MainnetNetworkID || + c.ChainID.Int64() == settings.TestnetNetworkID || + c.ChainID.Int64() == settings.DevnetNetworkID) +} + +// IsImmutableZKEVMPrevrandao returns true if the chain configuration has prevrandao fork enabled +// for the specified timestamp. Mainnet is the only network that has a separate prevrandao fork +// to the shanghai fork. +func (c *ChainConfig) IsImmutableZKEVMPrevrandao(blockTime uint64) bool { + if !c.IsImmutableZKEVM() { + return false + } + if c.PrevrandaoTime == nil { + panic(fmt.Sprintf("prevrandaoTime not set for Immutable zkEVM network %d", c.ChainID.Int64())) + } + return *c.PrevrandaoTime <= blockTime +} + +// IsImmutableZKEVMShanghai returns true if the chain configuration has shanghai fork enabled +// and the specified block number and timestamp is past the shanghai timestamp +func (c *ChainConfig) IsImmutableZKEVMShanghai(blockNum *big.Int, blockTime uint64) bool { + return c.IsImmutableZKEVM() && c.IsShanghai(blockNum, blockTime) +} + +// IsValidImmutableZKEVM returns true if the chain configuration is valid for an Immutable zkEVM network +func (c *ChainConfig) IsValidImmutableZKEVM() bool { + return c.IsImmutableZKEVM() && + c.IsReorgBlocked && + c.Clique != nil && + c.Clique.Period == settings.SecondsPerBlock +} diff --git a/params/immutable_config_test.go b/params/immutable_config_test.go new file mode 100644 index 000000000..04fe4965d --- /dev/null +++ b/params/immutable_config_test.go @@ -0,0 +1,78 @@ +// 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 params + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/cmd/geth/immutable/settings" +) + +func TestImmutableConfig_ForkChecks_Valid(t *testing.T) { + tests := []struct { + name string + chainID *big.Int + shanghaiTimestamp uint64 + prevrandaoTimestamp uint64 + cancunTimestamp uint64 + }{ + { + name: "mainnet", + chainID: big.NewInt(settings.MainnetNetworkID), + shanghaiTimestamp: uint64(settings.MainnetShanghaiFork.Unix()), + prevrandaoTimestamp: uint64(settings.MainnetPrevrandaoFork.Unix()), + cancunTimestamp: uint64(settings.MainnetCancunFork.Unix()), + }, + { + name: "testnet", + chainID: big.NewInt(settings.TestnetNetworkID), + shanghaiTimestamp: uint64(settings.TestnetShanghaiFork.Unix()), + prevrandaoTimestamp: uint64(settings.TestnetPrevrandaoFork.Unix()), + cancunTimestamp: uint64(settings.TestnetCancunFork.Unix()), + }, + { + name: "devnet", + chainID: big.NewInt(settings.DevnetNetworkID), + shanghaiTimestamp: uint64(settings.DevnetShanghaiFork.Unix()), + prevrandaoTimestamp: uint64(settings.DevnetPrevrandaoFork.Unix()), + cancunTimestamp: uint64(settings.DevnetCancunFork.Unix()), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := ChainConfig{ + ChainID: tt.chainID, + ShanghaiTime: &tt.shanghaiTimestamp, + PrevrandaoTime: &tt.prevrandaoTimestamp, + LondonBlock: big.NewInt(0), + } + if !c.IsImmutableZKEVMShanghai(c.LondonBlock, tt.shanghaiTimestamp) { + t.Errorf("expected %v to be shanghai", tt.shanghaiTimestamp) + } + if c.IsImmutableZKEVMShanghai(c.LondonBlock, tt.shanghaiTimestamp-10) { + t.Errorf("expected %v to not be shanghai", tt.shanghaiTimestamp-10) + } + if !c.IsImmutableZKEVMPrevrandao(tt.prevrandaoTimestamp) { + t.Errorf("expected %v to be prevrandao", tt.prevrandaoTimestamp) + } + if c.IsImmutableZKEVMPrevrandao(tt.prevrandaoTimestamp - 10) { + t.Errorf("expected %v to not be prevrandao", tt.prevrandaoTimestamp-10) + } + }) + } +} diff --git a/params/protocol_params.go b/params/protocol_params.go index 7eb63e89a..a7604b198 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -124,7 +124,9 @@ const ( // Introduced in Tangerine Whistle (Eip 150) CreateBySelfdestructGas uint64 = 25000 - DefaultBaseFeeChangeDenominator = 8 // Bounds the amount the base fee can change between blocks. + // CHANGE(immutable): move defaultBaseFeeChangeDenominator to private field so that we know it is not used elsewhere. + // The respective getter method from ChainConfig type must always be used to access this value. + defaultBaseFeeChangeDenominator = 8 // Bounds the amount the base fee can change between blocks. DefaultElasticityMultiplier = 2 // Bounds the maximum gas limit an EIP-1559 block may have. InitialBaseFee = 1000000000 // Initial base fee for EIP-1559 blocks. diff --git a/params/version.go b/params/version.go index a2c258ff5..5f2ea5328 100644 --- a/params/version.go +++ b/params/version.go @@ -21,10 +21,10 @@ import ( ) const ( - VersionMajor = 1 // Major version component of the current release - VersionMinor = 13 // Minor version component of the current release - VersionPatch = 15 // Patch version component of the current release - VersionMeta = "stable" // Version metadata to append to the version string + VersionMajor = 1 // Major version component of the current release + VersionMinor = 0 // Minor version component of the current release + VersionPatch = 0 // Patch version component of the current release + VersionMeta = "beta.14" // Version metadata to append to the version string ) // Version holds the textual version string. diff --git a/tests/immutable/README.md b/tests/immutable/README.md new file mode 100644 index 000000000..8358afd3d --- /dev/null +++ b/tests/immutable/README.md @@ -0,0 +1,25 @@ +# Immutable E2E Tests + +Use this pkg to run E2E tests against local or remote deployments. + +You can use the CI scripts to spin up a local network and test: + +```sh +.github/scripts/bootstrap_test.sh +``` + +If you want to run individual tests for example: + +```sh +export PRIV_KEY="some throwaway key" +./build/bin/geth immutable bootstrap local --override.prevrandao=$(($(date +%s) + 5)) --override.cancun=$(($(date +%s) + 5)) --override.shanghai=$(($(date +%s) + 5)) +go test -v ./tests/immutable/... -run TestImmutable_Cancun_4844TransactionsDisabled +``` + +That will run the local chain with all required forks 5 seconds after genesis. These forks must be enabled after genesis. + +You can use `-privkey` instead of an env var if you wish. For example: + +```sh +go test -v ./tests/immutable/... -privkey="/path/to/file" -run TestImmutable_Cancun_4844TransactionsDisabled +``` diff --git a/tests/immutable/acl_test.go b/tests/immutable/acl_test.go new file mode 100644 index 000000000..e7fba2f8e --- /dev/null +++ b/tests/immutable/acl_test.go @@ -0,0 +1,165 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package test + +import ( + "context" + "crypto/ecdsa" + "fmt" + "math/big" + "testing" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/cmd/geth/immutable/settings" + "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" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/tests/immutable/erc20" + "github.com/ethereum/go-ethereum/tests/immutable/evm" + "github.com/stretchr/testify/require" +) + +func dynamicTx( + t *testing.T, + ctx context.Context, + client *ethclient.Client, + value *big.Int, + keyFrom *ecdsa.PrivateKey, + to common.Address, +) *types.Transaction { + t.Helper() + + chainID, err := client.ChainID(ctx) + require.NoError(t, err) + + addrFrom := crypto.PubkeyToAddress(keyFrom.PublicKey) + nonce, err := client.PendingNonceAt(ctx, addrFrom) + require.NoError(t, err) + + tx := types.MustSignNewTx(keyFrom, + types.NewLondonSigner(chainID), + &types.DynamicFeeTx{ + Nonce: nonce, + Gas: 22000, + To: &to, + Value: value, + GasTipCap: big.NewInt(settings.PriceLimit), + GasFeeCap: big.NewInt(settings.PriceLimit), + }) + return tx +} + +func TestImmutableACL_BlockedTransaction_ExpectedToReturnUnathorizedError(t *testing.T) { + if config.blockedUser == nil { + t.Skip("blockedUser/blockedprivkey was not set and is nil") + } + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + + client, xerr := evm.NewClient(ctx, config.rpcURL, 1, 1) + require.NoError(t, xerr) + + // Simple deploy ERC20 test + _, _, _, err := erc20.DeployERC20(legacyTxOpts(t, ctx, client.EthClient(), config.blockedUser), client.EthClient()) + require.Error(t, err) + require.Equal(t, + txpool.ErrTxIsUnauthorized.Error(), + err.Error(), + fmt.Sprintf("unexpected error: %v, should be: %v", err, txpool.ErrTxIsUnauthorized)) +} + +func TestImmutableACL_Blocklist(t *testing.T) { + if config.blockedUser == nil { + t.Skip("blockedUser/blockedprivkey was not set and is nil") + } + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + + client, err := evm.NewClient(ctx, config.rpcURL, 1, 1) + require.NoError(t, err) + + // Simple deploy ERC20 test with blocked address should fail + _, _, _, err = erc20.DeployERC20(legacyTxOpts(t, ctx, client.EthClient(), config.blockedUser), client.EthClient()) + require.Error(t, err) + require.Equal(t, + txpool.ErrTxIsUnauthorized.Error(), + err.Error(), + fmt.Sprintf("unexpected error: %v, should be: %v", err, txpool.ErrTxIsUnauthorized)) + + t.Log("Deploy with blockeduser - should fail - Done") + + // Simple deploy ERC20 test with not blocked address should pass + txOpts := legacyTxOpts(t, ctx, client.EthClient(), config.testUser) + _, deployTx, coin, err := erc20.DeployERC20(txOpts, client.EthClient()) + require.NoError(t, err) + deployReceipt, err := bind.WaitMined(ctx, client, deployTx) + require.NoError(t, err) + require.Equal(t, deployReceipt.Status, uint64(1)) + t.Logf("ERC20 Deployed to address %s with TX Hash %s", deployReceipt.ContractAddress.String(), deployTx.Hash().String()) + + // Simple transfer test should also pass + aliceKey, _ := crypto.GenerateKey() + alice := crypto.PubkeyToAddress(aliceKey.PublicKey) + + xferTx, err := coin.Transfer(txOpts, alice, big.NewInt(1)) + require.NoError(t, err) + _, err = bind.WaitMined(ctx, client, xferTx) + require.NoError(t, err) + t.Log("Transfer to alice - Done") + + // Simple native transfer from testuser to alice + aliceTx := dynamicTx(t, ctx, client.EthClient(), big.NewInt(1), config.testUser.key, alice) + err = client.EthClient().SendTransaction(ctx, aliceTx) + require.NoError(t, err) + t.Log("Transfer native to alice - should pass - Done") + + // Simple native transfer from testuser to blocked user + tx := dynamicTx(t, ctx, client.EthClient(), big.NewInt(1), config.testUser.key, config.blockedUser.address) + err = client.EthClient().SendTransaction(ctx, tx) + require.Error(t, err) + require.Equal(t, + txpool.ErrTxIsUnauthorized.Error(), + err.Error(), + fmt.Sprintf("unexpected error: %v, should be: %v", err, txpool.ErrTxIsUnauthorized), + ) + t.Log("Transfer to blockeduser - should fail - Done") +} + +func TestImmutableACL_SimpleTransferTest(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + + client, xerr := evm.NewClient(ctx, config.rpcURL, 1, 1) + require.NoError(t, xerr) + + // Simple transfer test should also pass + aliceKey, _ := crypto.GenerateKey() + alice := crypto.PubkeyToAddress(aliceKey.PublicKey) + + // Simple native transfer from testuser to alice + aliceTx := dynamicTx(t, ctx, client.EthClient(), big.NewInt(1), config.testUser.key, alice) + err := client.EthClient().SendTransaction(ctx, aliceTx) + require.NoError(t, err) + t.Log("Transfer native to alice - should pass", aliceTx.Hash()) + + _, err = bind.WaitMined(ctx, client, aliceTx) + require.NoError(t, err) + t.Log("Transfer native to alice - should pass - Done") +} diff --git a/tests/immutable/cancun/blobbasefee/abi.json b/tests/immutable/cancun/blobbasefee/abi.json new file mode 100644 index 000000000..390ece2b2 --- /dev/null +++ b/tests/immutable/cancun/blobbasefee/abi.json @@ -0,0 +1,15 @@ +[ + { + "inputs": [], + "name": "blobBaseFee", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/tests/immutable/cancun/blobbasefee/blobbasefee.go b/tests/immutable/cancun/blobbasefee/blobbasefee.go new file mode 100644 index 000000000..718ab9cec --- /dev/null +++ b/tests/immutable/cancun/blobbasefee/blobbasefee.go @@ -0,0 +1,234 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package blobbasefee + +import ( + "errors" + "math/big" + "strings" + + 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 +) + +// BlobBaseFeeMetaData contains all meta data concerning the BlobBaseFee contract. +var BlobBaseFeeMetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[],\"name\":\"blobBaseFee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]", + Bin: "0x6080604052348015600e575f80fd5b5060ae80601a5f395ff3fe6080604052348015600e575f80fd5b50600436106026575f3560e01c8063f820614014602a575b5f80fd5b60306044565b604051603b91906061565b60405180910390f35b5f4a905090565b5f819050919050565b605b81604b565b82525050565b5f60208201905060725f8301846054565b9291505056fea2646970667358221220f36b1e170b1a918aa653ca6f6ee810faa51fe43e25828bb9b8caedb51031bee264736f6c63430008190033", +} + +// BlobBaseFeeABI is the input ABI used to generate the binding from. +// Deprecated: Use BlobBaseFeeMetaData.ABI instead. +var BlobBaseFeeABI = BlobBaseFeeMetaData.ABI + +// BlobBaseFeeBin is the compiled bytecode used for deploying new contracts. +// Deprecated: Use BlobBaseFeeMetaData.Bin instead. +var BlobBaseFeeBin = BlobBaseFeeMetaData.Bin + +// DeployBlobBaseFee deploys a new Ethereum contract, binding an instance of BlobBaseFee to it. +func DeployBlobBaseFee(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *BlobBaseFee, error) { + parsed, err := BlobBaseFeeMetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(BlobBaseFeeBin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &BlobBaseFee{BlobBaseFeeCaller: BlobBaseFeeCaller{contract: contract}, BlobBaseFeeTransactor: BlobBaseFeeTransactor{contract: contract}, BlobBaseFeeFilterer: BlobBaseFeeFilterer{contract: contract}}, nil +} + +// BlobBaseFee is an auto generated Go binding around an Ethereum contract. +type BlobBaseFee struct { + BlobBaseFeeCaller // Read-only binding to the contract + BlobBaseFeeTransactor // Write-only binding to the contract + BlobBaseFeeFilterer // Log filterer for contract events +} + +// BlobBaseFeeCaller is an auto generated read-only Go binding around an Ethereum contract. +type BlobBaseFeeCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// BlobBaseFeeTransactor is an auto generated write-only Go binding around an Ethereum contract. +type BlobBaseFeeTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// BlobBaseFeeFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type BlobBaseFeeFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// BlobBaseFeeSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type BlobBaseFeeSession struct { + Contract *BlobBaseFee // 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 +} + +// BlobBaseFeeCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type BlobBaseFeeCallerSession struct { + Contract *BlobBaseFeeCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// BlobBaseFeeTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type BlobBaseFeeTransactorSession struct { + Contract *BlobBaseFeeTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// BlobBaseFeeRaw is an auto generated low-level Go binding around an Ethereum contract. +type BlobBaseFeeRaw struct { + Contract *BlobBaseFee // Generic contract binding to access the raw methods on +} + +// BlobBaseFeeCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type BlobBaseFeeCallerRaw struct { + Contract *BlobBaseFeeCaller // Generic read-only contract binding to access the raw methods on +} + +// BlobBaseFeeTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type BlobBaseFeeTransactorRaw struct { + Contract *BlobBaseFeeTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewBlobBaseFee creates a new instance of BlobBaseFee, bound to a specific deployed contract. +func NewBlobBaseFee(address common.Address, backend bind.ContractBackend) (*BlobBaseFee, error) { + contract, err := bindBlobBaseFee(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &BlobBaseFee{BlobBaseFeeCaller: BlobBaseFeeCaller{contract: contract}, BlobBaseFeeTransactor: BlobBaseFeeTransactor{contract: contract}, BlobBaseFeeFilterer: BlobBaseFeeFilterer{contract: contract}}, nil +} + +// NewBlobBaseFeeCaller creates a new read-only instance of BlobBaseFee, bound to a specific deployed contract. +func NewBlobBaseFeeCaller(address common.Address, caller bind.ContractCaller) (*BlobBaseFeeCaller, error) { + contract, err := bindBlobBaseFee(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &BlobBaseFeeCaller{contract: contract}, nil +} + +// NewBlobBaseFeeTransactor creates a new write-only instance of BlobBaseFee, bound to a specific deployed contract. +func NewBlobBaseFeeTransactor(address common.Address, transactor bind.ContractTransactor) (*BlobBaseFeeTransactor, error) { + contract, err := bindBlobBaseFee(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &BlobBaseFeeTransactor{contract: contract}, nil +} + +// NewBlobBaseFeeFilterer creates a new log filterer instance of BlobBaseFee, bound to a specific deployed contract. +func NewBlobBaseFeeFilterer(address common.Address, filterer bind.ContractFilterer) (*BlobBaseFeeFilterer, error) { + contract, err := bindBlobBaseFee(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &BlobBaseFeeFilterer{contract: contract}, nil +} + +// bindBlobBaseFee binds a generic wrapper to an already deployed contract. +func bindBlobBaseFee(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := BlobBaseFeeMetaData.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 (_BlobBaseFee *BlobBaseFeeRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _BlobBaseFee.Contract.BlobBaseFeeCaller.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 (_BlobBaseFee *BlobBaseFeeRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _BlobBaseFee.Contract.BlobBaseFeeTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_BlobBaseFee *BlobBaseFeeRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _BlobBaseFee.Contract.BlobBaseFeeTransactor.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 (_BlobBaseFee *BlobBaseFeeCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _BlobBaseFee.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 (_BlobBaseFee *BlobBaseFeeTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _BlobBaseFee.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_BlobBaseFee *BlobBaseFeeTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _BlobBaseFee.Contract.contract.Transact(opts, method, params...) +} + +// BlobBaseFee is a free data retrieval call binding the contract method 0xf8206140. +// +// Solidity: function blobBaseFee() view returns(uint256) +func (_BlobBaseFee *BlobBaseFeeCaller) BlobBaseFee(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _BlobBaseFee.contract.Call(opts, &out, "blobBaseFee") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// BlobBaseFee is a free data retrieval call binding the contract method 0xf8206140. +// +// Solidity: function blobBaseFee() view returns(uint256) +func (_BlobBaseFee *BlobBaseFeeSession) BlobBaseFee() (*big.Int, error) { + return _BlobBaseFee.Contract.BlobBaseFee(&_BlobBaseFee.CallOpts) +} + +// BlobBaseFee is a free data retrieval call binding the contract method 0xf8206140. +// +// Solidity: function blobBaseFee() view returns(uint256) +func (_BlobBaseFee *BlobBaseFeeCallerSession) BlobBaseFee() (*big.Int, error) { + return _BlobBaseFee.Contract.BlobBaseFee(&_BlobBaseFee.CallOpts) +} diff --git a/tests/immutable/cancun/blobbasefee/blobbasefee.sol b/tests/immutable/cancun/blobbasefee/blobbasefee.sol new file mode 100644 index 000000000..df517f9e8 --- /dev/null +++ b/tests/immutable/cancun/blobbasefee/blobbasefee.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.25; + +contract BlobBaseFee { + function blobBaseFee() external view returns (uint) { + return block.blobbasefee; + } +} + +// build/bin/abigen --abi ./tests/immutable/cancun/blobbasefee/abi.json --pkg blobbasefee --type BlobBaseFee --out ./tests/immutable/cancun/blobbasefee/blobbasefee.go --bin ./tests/immutable/cancun/blobbasefee/bytecode.bin diff --git a/tests/immutable/cancun/blobbasefee/bytecode.bin b/tests/immutable/cancun/blobbasefee/bytecode.bin new file mode 100644 index 000000000..cb7ac7d19 --- /dev/null +++ b/tests/immutable/cancun/blobbasefee/bytecode.bin @@ -0,0 +1 @@ +6080604052348015600e575f80fd5b5060ae80601a5f395ff3fe6080604052348015600e575f80fd5b50600436106026575f3560e01c8063f820614014602a575b5f80fd5b60306044565b604051603b91906061565b60405180910390f35b5f4a905090565b5f819050919050565b605b81604b565b82525050565b5f60208201905060725f8301846054565b9291505056fea2646970667358221220f36b1e170b1a918aa653ca6f6ee810faa51fe43e25828bb9b8caedb51031bee264736f6c63430008190033 \ No newline at end of file diff --git a/tests/immutable/cancun/blobhash/abi.json b/tests/immutable/cancun/blobhash/abi.json new file mode 100644 index 000000000..0f0abc753 --- /dev/null +++ b/tests/immutable/cancun/blobhash/abi.json @@ -0,0 +1,21 @@ +[ + { + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "blobHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/tests/immutable/cancun/blobhash/blobhash.go b/tests/immutable/cancun/blobhash/blobhash.go new file mode 100644 index 000000000..743ad985d --- /dev/null +++ b/tests/immutable/cancun/blobhash/blobhash.go @@ -0,0 +1,234 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package blobhash + +import ( + "errors" + "math/big" + "strings" + + 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 +) + +// BlobhashMetaData contains all meta data concerning the Blobhash contract. +var BlobhashMetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"index\",\"type\":\"uint256\"}],\"name\":\"blobHash\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]", + Bin: "0x6080604052348015600e575f80fd5b506101198061001c5f395ff3fe6080604052348015600e575f80fd5b50600436106026575f3560e01c80630ba54e3214602a575b5f80fd5b60406004803603810190603c91906090565b6054565b604051604b919060cc565b60405180910390f35b5f81499050919050565b5f80fd5b5f819050919050565b6072816062565b8114607b575f80fd5b50565b5f81359050608a81606b565b92915050565b5f6020828403121560a25760a1605e565b5b5f60ad84828501607e565b91505092915050565b5f819050919050565b60c68160b6565b82525050565b5f60208201905060dd5f83018460bf565b9291505056fea26469706673582212208da4d91290c36a37fd192cd72cc7b29ba6328b2de08606cf869614c5ddd9b37364736f6c63430008190033", +} + +// BlobhashABI is the input ABI used to generate the binding from. +// Deprecated: Use BlobhashMetaData.ABI instead. +var BlobhashABI = BlobhashMetaData.ABI + +// BlobhashBin is the compiled bytecode used for deploying new contracts. +// Deprecated: Use BlobhashMetaData.Bin instead. +var BlobhashBin = BlobhashMetaData.Bin + +// DeployBlobhash deploys a new Ethereum contract, binding an instance of Blobhash to it. +func DeployBlobhash(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *Blobhash, error) { + parsed, err := BlobhashMetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(BlobhashBin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &Blobhash{BlobhashCaller: BlobhashCaller{contract: contract}, BlobhashTransactor: BlobhashTransactor{contract: contract}, BlobhashFilterer: BlobhashFilterer{contract: contract}}, nil +} + +// Blobhash is an auto generated Go binding around an Ethereum contract. +type Blobhash struct { + BlobhashCaller // Read-only binding to the contract + BlobhashTransactor // Write-only binding to the contract + BlobhashFilterer // Log filterer for contract events +} + +// BlobhashCaller is an auto generated read-only Go binding around an Ethereum contract. +type BlobhashCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// BlobhashTransactor is an auto generated write-only Go binding around an Ethereum contract. +type BlobhashTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// BlobhashFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type BlobhashFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// BlobhashSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type BlobhashSession struct { + Contract *Blobhash // 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 +} + +// BlobhashCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type BlobhashCallerSession struct { + Contract *BlobhashCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// BlobhashTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type BlobhashTransactorSession struct { + Contract *BlobhashTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// BlobhashRaw is an auto generated low-level Go binding around an Ethereum contract. +type BlobhashRaw struct { + Contract *Blobhash // Generic contract binding to access the raw methods on +} + +// BlobhashCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type BlobhashCallerRaw struct { + Contract *BlobhashCaller // Generic read-only contract binding to access the raw methods on +} + +// BlobhashTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type BlobhashTransactorRaw struct { + Contract *BlobhashTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewBlobhash creates a new instance of Blobhash, bound to a specific deployed contract. +func NewBlobhash(address common.Address, backend bind.ContractBackend) (*Blobhash, error) { + contract, err := bindBlobhash(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &Blobhash{BlobhashCaller: BlobhashCaller{contract: contract}, BlobhashTransactor: BlobhashTransactor{contract: contract}, BlobhashFilterer: BlobhashFilterer{contract: contract}}, nil +} + +// NewBlobhashCaller creates a new read-only instance of Blobhash, bound to a specific deployed contract. +func NewBlobhashCaller(address common.Address, caller bind.ContractCaller) (*BlobhashCaller, error) { + contract, err := bindBlobhash(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &BlobhashCaller{contract: contract}, nil +} + +// NewBlobhashTransactor creates a new write-only instance of Blobhash, bound to a specific deployed contract. +func NewBlobhashTransactor(address common.Address, transactor bind.ContractTransactor) (*BlobhashTransactor, error) { + contract, err := bindBlobhash(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &BlobhashTransactor{contract: contract}, nil +} + +// NewBlobhashFilterer creates a new log filterer instance of Blobhash, bound to a specific deployed contract. +func NewBlobhashFilterer(address common.Address, filterer bind.ContractFilterer) (*BlobhashFilterer, error) { + contract, err := bindBlobhash(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &BlobhashFilterer{contract: contract}, nil +} + +// bindBlobhash binds a generic wrapper to an already deployed contract. +func bindBlobhash(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := BlobhashMetaData.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 (_Blobhash *BlobhashRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _Blobhash.Contract.BlobhashCaller.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 (_Blobhash *BlobhashRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Blobhash.Contract.BlobhashTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Blobhash *BlobhashRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Blobhash.Contract.BlobhashTransactor.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 (_Blobhash *BlobhashCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _Blobhash.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 (_Blobhash *BlobhashTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Blobhash.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Blobhash *BlobhashTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Blobhash.Contract.contract.Transact(opts, method, params...) +} + +// BlobHash is a free data retrieval call binding the contract method 0x0ba54e32. +// +// Solidity: function blobHash(uint256 index) view returns(bytes32) +func (_Blobhash *BlobhashCaller) BlobHash(opts *bind.CallOpts, index *big.Int) ([32]byte, error) { + var out []interface{} + err := _Blobhash.contract.Call(opts, &out, "blobHash", index) + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// BlobHash is a free data retrieval call binding the contract method 0x0ba54e32. +// +// Solidity: function blobHash(uint256 index) view returns(bytes32) +func (_Blobhash *BlobhashSession) BlobHash(index *big.Int) ([32]byte, error) { + return _Blobhash.Contract.BlobHash(&_Blobhash.CallOpts, index) +} + +// BlobHash is a free data retrieval call binding the contract method 0x0ba54e32. +// +// Solidity: function blobHash(uint256 index) view returns(bytes32) +func (_Blobhash *BlobhashCallerSession) BlobHash(index *big.Int) ([32]byte, error) { + return _Blobhash.Contract.BlobHash(&_Blobhash.CallOpts, index) +} diff --git a/tests/immutable/cancun/blobhash/blobhash.sol b/tests/immutable/cancun/blobhash/blobhash.sol new file mode 100644 index 000000000..9f55be52a --- /dev/null +++ b/tests/immutable/cancun/blobhash/blobhash.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.25; + +contract Blobhash { + function storeBlobHash(uint256 index) external { + assembly { + sstore(0, blobhash(index)) + } + } +} + +// build/bin/abigen --abi ./tests/immutable/cancun/blobhash/abi.json --pkg blobhash --type Blobhash --out ./tests/immutable/cancun/blobhash/blobhash.go --bin ./tests/immutable/cancun/blobhash/bytecode.bin diff --git a/tests/immutable/cancun/blobhash/bytecode.bin b/tests/immutable/cancun/blobhash/bytecode.bin new file mode 100644 index 000000000..f0f91c024 --- /dev/null +++ b/tests/immutable/cancun/blobhash/bytecode.bin @@ -0,0 +1 @@ +6080604052348015600e575f80fd5b506101198061001c5f395ff3fe6080604052348015600e575f80fd5b50600436106026575f3560e01c80630ba54e3214602a575b5f80fd5b60406004803603810190603c91906090565b6054565b604051604b919060cc565b60405180910390f35b5f81499050919050565b5f80fd5b5f819050919050565b6072816062565b8114607b575f80fd5b50565b5f81359050608a81606b565b92915050565b5f6020828403121560a25760a1605e565b5b5f60ad84828501607e565b91505092915050565b5f819050919050565b60c68160b6565b82525050565b5f60208201905060dd5f83018460bf565b9291505056fea26469706673582212208da4d91290c36a37fd192cd72cc7b29ba6328b2de08606cf869614c5ddd9b37364736f6c63430008190033 \ No newline at end of file diff --git a/tests/immutable/cancun/mcopy/abi.json b/tests/immutable/cancun/mcopy/abi.json new file mode 100644 index 000000000..c671621a8 --- /dev/null +++ b/tests/immutable/cancun/mcopy/abi.json @@ -0,0 +1,15 @@ +[ + { + "inputs": [], + "name": "memoryCopy", + "outputs": [ + { + "internalType": "bytes32", + "name": "x", + "type": "bytes32" + } + ], + "stateMutability": "pure", + "type": "function" + } +] \ No newline at end of file diff --git a/tests/immutable/cancun/mcopy/bytecode.bin b/tests/immutable/cancun/mcopy/bytecode.bin new file mode 100644 index 000000000..51e09279b --- /dev/null +++ b/tests/immutable/cancun/mcopy/bytecode.bin @@ -0,0 +1 @@ +6080604052348015600e575f80fd5b5060b980601a5f395ff3fe6080604052348015600e575f80fd5b50600436106026575f3560e01c80632dbaeee914602a575b5f80fd5b60306044565b604051603b9190606c565b60405180910390f35b5f60506020526020805f5e5f51905090565b5f819050919050565b6066816056565b82525050565b5f602082019050607d5f830184605f565b9291505056fea2646970667358221220710c9da5e12c71adc7661a10c85eaef4d3563725e2fa50835f0b6875b0c5d3cd64736f6c63430008190033 \ No newline at end of file diff --git a/tests/immutable/cancun/mcopy/mcopy.go b/tests/immutable/cancun/mcopy/mcopy.go new file mode 100644 index 000000000..0ba249eb9 --- /dev/null +++ b/tests/immutable/cancun/mcopy/mcopy.go @@ -0,0 +1,234 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package mcopy + +import ( + "errors" + "math/big" + "strings" + + 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 +) + +// McopyMetaData contains all meta data concerning the Mcopy contract. +var McopyMetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[],\"name\":\"memoryCopy\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"x\",\"type\":\"bytes32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"}]", + Bin: "0x6080604052348015600e575f80fd5b5060b980601a5f395ff3fe6080604052348015600e575f80fd5b50600436106026575f3560e01c80632dbaeee914602a575b5f80fd5b60306044565b604051603b9190606c565b60405180910390f35b5f60506020526020805f5e5f51905090565b5f819050919050565b6066816056565b82525050565b5f602082019050607d5f830184605f565b9291505056fea2646970667358221220710c9da5e12c71adc7661a10c85eaef4d3563725e2fa50835f0b6875b0c5d3cd64736f6c63430008190033", +} + +// McopyABI is the input ABI used to generate the binding from. +// Deprecated: Use McopyMetaData.ABI instead. +var McopyABI = McopyMetaData.ABI + +// McopyBin is the compiled bytecode used for deploying new contracts. +// Deprecated: Use McopyMetaData.Bin instead. +var McopyBin = McopyMetaData.Bin + +// DeployMcopy deploys a new Ethereum contract, binding an instance of Mcopy to it. +func DeployMcopy(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *Mcopy, error) { + parsed, err := McopyMetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(McopyBin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &Mcopy{McopyCaller: McopyCaller{contract: contract}, McopyTransactor: McopyTransactor{contract: contract}, McopyFilterer: McopyFilterer{contract: contract}}, nil +} + +// Mcopy is an auto generated Go binding around an Ethereum contract. +type Mcopy struct { + McopyCaller // Read-only binding to the contract + McopyTransactor // Write-only binding to the contract + McopyFilterer // Log filterer for contract events +} + +// McopyCaller is an auto generated read-only Go binding around an Ethereum contract. +type McopyCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// McopyTransactor is an auto generated write-only Go binding around an Ethereum contract. +type McopyTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// McopyFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type McopyFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// McopySession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type McopySession struct { + Contract *Mcopy // 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 +} + +// McopyCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type McopyCallerSession struct { + Contract *McopyCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// McopyTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type McopyTransactorSession struct { + Contract *McopyTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// McopyRaw is an auto generated low-level Go binding around an Ethereum contract. +type McopyRaw struct { + Contract *Mcopy // Generic contract binding to access the raw methods on +} + +// McopyCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type McopyCallerRaw struct { + Contract *McopyCaller // Generic read-only contract binding to access the raw methods on +} + +// McopyTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type McopyTransactorRaw struct { + Contract *McopyTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewMcopy creates a new instance of Mcopy, bound to a specific deployed contract. +func NewMcopy(address common.Address, backend bind.ContractBackend) (*Mcopy, error) { + contract, err := bindMcopy(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &Mcopy{McopyCaller: McopyCaller{contract: contract}, McopyTransactor: McopyTransactor{contract: contract}, McopyFilterer: McopyFilterer{contract: contract}}, nil +} + +// NewMcopyCaller creates a new read-only instance of Mcopy, bound to a specific deployed contract. +func NewMcopyCaller(address common.Address, caller bind.ContractCaller) (*McopyCaller, error) { + contract, err := bindMcopy(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &McopyCaller{contract: contract}, nil +} + +// NewMcopyTransactor creates a new write-only instance of Mcopy, bound to a specific deployed contract. +func NewMcopyTransactor(address common.Address, transactor bind.ContractTransactor) (*McopyTransactor, error) { + contract, err := bindMcopy(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &McopyTransactor{contract: contract}, nil +} + +// NewMcopyFilterer creates a new log filterer instance of Mcopy, bound to a specific deployed contract. +func NewMcopyFilterer(address common.Address, filterer bind.ContractFilterer) (*McopyFilterer, error) { + contract, err := bindMcopy(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &McopyFilterer{contract: contract}, nil +} + +// bindMcopy binds a generic wrapper to an already deployed contract. +func bindMcopy(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := McopyMetaData.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 (_Mcopy *McopyRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _Mcopy.Contract.McopyCaller.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 (_Mcopy *McopyRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Mcopy.Contract.McopyTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Mcopy *McopyRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Mcopy.Contract.McopyTransactor.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 (_Mcopy *McopyCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _Mcopy.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 (_Mcopy *McopyTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Mcopy.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Mcopy *McopyTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Mcopy.Contract.contract.Transact(opts, method, params...) +} + +// MemoryCopy is a free data retrieval call binding the contract method 0x2dbaeee9. +// +// Solidity: function memoryCopy() pure returns(bytes32 x) +func (_Mcopy *McopyCaller) MemoryCopy(opts *bind.CallOpts) ([32]byte, error) { + var out []interface{} + err := _Mcopy.contract.Call(opts, &out, "memoryCopy") + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// MemoryCopy is a free data retrieval call binding the contract method 0x2dbaeee9. +// +// Solidity: function memoryCopy() pure returns(bytes32 x) +func (_Mcopy *McopySession) MemoryCopy() ([32]byte, error) { + return _Mcopy.Contract.MemoryCopy(&_Mcopy.CallOpts) +} + +// MemoryCopy is a free data retrieval call binding the contract method 0x2dbaeee9. +// +// Solidity: function memoryCopy() pure returns(bytes32 x) +func (_Mcopy *McopyCallerSession) MemoryCopy() ([32]byte, error) { + return _Mcopy.Contract.MemoryCopy(&_Mcopy.CallOpts) +} diff --git a/tests/immutable/cancun/mcopy/mcopy.sol b/tests/immutable/cancun/mcopy/mcopy.sol new file mode 100644 index 000000000..7f695210c --- /dev/null +++ b/tests/immutable/cancun/mcopy/mcopy.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.25; + +contract A { + function memoryCopy() external pure returns (bytes32 x) { + assembly { + mstore(0x20, 0x50) // Store 0x50 at word 1 in memory + mcopy(0, 0x20, 0x20) // Copies 0x50 to word 0 in memory + x := mload(0) // Returns 32 bytes "0x50" + } + } +} + +// build/bin/abigen --abi ./tests/immutable/cancun/mcopy/abi.json --pkg mcopy --type mcopy --out ./tests/immutable/cancun/mcopy/mcopy.go --bin ./tests/immutable/cancun/mcopy/bytecode.bin diff --git a/tests/immutable/cancun/selfdestruct/constructor/abi.json b/tests/immutable/cancun/selfdestruct/constructor/abi.json new file mode 100644 index 000000000..f7ad70bce --- /dev/null +++ b/tests/immutable/cancun/selfdestruct/constructor/abi.json @@ -0,0 +1,7 @@ +[ + { + "type": "constructor", + "inputs": [{ "name": "recipient", "type": "address", "internalType": "address" }], + "stateMutability": "payable" + } +] diff --git a/tests/immutable/cancun/selfdestruct/constructor/bytecode.bin b/tests/immutable/cancun/selfdestruct/constructor/bytecode.bin new file mode 100644 index 000000000..4ee7dc859 --- /dev/null +++ b/tests/immutable/cancun/selfdestruct/constructor/bytecode.bin @@ -0,0 +1 @@ +608060405260405160593803806059833981016040819052601e91602a565b806001600160a01b0316ff5b600060208284031215603b57600080fd5b81516001600160a01b0381168114605157600080fd5b939250505056fe \ No newline at end of file diff --git a/tests/immutable/cancun/selfdestruct/constructor/selfdestructconstructor.go b/tests/immutable/cancun/selfdestruct/constructor/selfdestructconstructor.go new file mode 100644 index 000000000..23b26878d --- /dev/null +++ b/tests/immutable/cancun/selfdestruct/constructor/selfdestructconstructor.go @@ -0,0 +1,203 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package selfdestructconstructor + +import ( + "errors" + "math/big" + "strings" + + 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 +) + +// SelfDestructConstructorMetaData contains all meta data concerning the SelfDestructConstructor contract. +var SelfDestructConstructorMetaData = &bind.MetaData{ + ABI: "[{\"type\":\"constructor\",\"inputs\":[{\"name\":\"recipient\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"payable\"}]", + Bin: "0x608060405260405160593803806059833981016040819052601e91602a565b806001600160a01b0316ff5b600060208284031215603b57600080fd5b81516001600160a01b0381168114605157600080fd5b939250505056fe", +} + +// SelfDestructConstructorABI is the input ABI used to generate the binding from. +// Deprecated: Use SelfDestructConstructorMetaData.ABI instead. +var SelfDestructConstructorABI = SelfDestructConstructorMetaData.ABI + +// SelfDestructConstructorBin is the compiled bytecode used for deploying new contracts. +// Deprecated: Use SelfDestructConstructorMetaData.Bin instead. +var SelfDestructConstructorBin = SelfDestructConstructorMetaData.Bin + +// DeploySelfDestructConstructor deploys a new Ethereum contract, binding an instance of SelfDestructConstructor to it. +func DeploySelfDestructConstructor(auth *bind.TransactOpts, backend bind.ContractBackend, recipient common.Address) (common.Address, *types.Transaction, *SelfDestructConstructor, error) { + parsed, err := SelfDestructConstructorMetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(SelfDestructConstructorBin), backend, recipient) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &SelfDestructConstructor{SelfDestructConstructorCaller: SelfDestructConstructorCaller{contract: contract}, SelfDestructConstructorTransactor: SelfDestructConstructorTransactor{contract: contract}, SelfDestructConstructorFilterer: SelfDestructConstructorFilterer{contract: contract}}, nil +} + +// SelfDestructConstructor is an auto generated Go binding around an Ethereum contract. +type SelfDestructConstructor struct { + SelfDestructConstructorCaller // Read-only binding to the contract + SelfDestructConstructorTransactor // Write-only binding to the contract + SelfDestructConstructorFilterer // Log filterer for contract events +} + +// SelfDestructConstructorCaller is an auto generated read-only Go binding around an Ethereum contract. +type SelfDestructConstructorCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// SelfDestructConstructorTransactor is an auto generated write-only Go binding around an Ethereum contract. +type SelfDestructConstructorTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// SelfDestructConstructorFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type SelfDestructConstructorFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// SelfDestructConstructorSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type SelfDestructConstructorSession struct { + Contract *SelfDestructConstructor // 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 +} + +// SelfDestructConstructorCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type SelfDestructConstructorCallerSession struct { + Contract *SelfDestructConstructorCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// SelfDestructConstructorTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type SelfDestructConstructorTransactorSession struct { + Contract *SelfDestructConstructorTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// SelfDestructConstructorRaw is an auto generated low-level Go binding around an Ethereum contract. +type SelfDestructConstructorRaw struct { + Contract *SelfDestructConstructor // Generic contract binding to access the raw methods on +} + +// SelfDestructConstructorCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type SelfDestructConstructorCallerRaw struct { + Contract *SelfDestructConstructorCaller // Generic read-only contract binding to access the raw methods on +} + +// SelfDestructConstructorTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type SelfDestructConstructorTransactorRaw struct { + Contract *SelfDestructConstructorTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewSelfDestructConstructor creates a new instance of SelfDestructConstructor, bound to a specific deployed contract. +func NewSelfDestructConstructor(address common.Address, backend bind.ContractBackend) (*SelfDestructConstructor, error) { + contract, err := bindSelfDestructConstructor(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &SelfDestructConstructor{SelfDestructConstructorCaller: SelfDestructConstructorCaller{contract: contract}, SelfDestructConstructorTransactor: SelfDestructConstructorTransactor{contract: contract}, SelfDestructConstructorFilterer: SelfDestructConstructorFilterer{contract: contract}}, nil +} + +// NewSelfDestructConstructorCaller creates a new read-only instance of SelfDestructConstructor, bound to a specific deployed contract. +func NewSelfDestructConstructorCaller(address common.Address, caller bind.ContractCaller) (*SelfDestructConstructorCaller, error) { + contract, err := bindSelfDestructConstructor(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &SelfDestructConstructorCaller{contract: contract}, nil +} + +// NewSelfDestructConstructorTransactor creates a new write-only instance of SelfDestructConstructor, bound to a specific deployed contract. +func NewSelfDestructConstructorTransactor(address common.Address, transactor bind.ContractTransactor) (*SelfDestructConstructorTransactor, error) { + contract, err := bindSelfDestructConstructor(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &SelfDestructConstructorTransactor{contract: contract}, nil +} + +// NewSelfDestructConstructorFilterer creates a new log filterer instance of SelfDestructConstructor, bound to a specific deployed contract. +func NewSelfDestructConstructorFilterer(address common.Address, filterer bind.ContractFilterer) (*SelfDestructConstructorFilterer, error) { + contract, err := bindSelfDestructConstructor(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &SelfDestructConstructorFilterer{contract: contract}, nil +} + +// bindSelfDestructConstructor binds a generic wrapper to an already deployed contract. +func bindSelfDestructConstructor(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := SelfDestructConstructorMetaData.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 (_SelfDestructConstructor *SelfDestructConstructorRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _SelfDestructConstructor.Contract.SelfDestructConstructorCaller.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 (_SelfDestructConstructor *SelfDestructConstructorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _SelfDestructConstructor.Contract.SelfDestructConstructorTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_SelfDestructConstructor *SelfDestructConstructorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _SelfDestructConstructor.Contract.SelfDestructConstructorTransactor.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 (_SelfDestructConstructor *SelfDestructConstructorCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _SelfDestructConstructor.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 (_SelfDestructConstructor *SelfDestructConstructorTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _SelfDestructConstructor.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_SelfDestructConstructor *SelfDestructConstructorTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _SelfDestructConstructor.Contract.contract.Transact(opts, method, params...) +} diff --git a/tests/immutable/cancun/selfdestruct/constructor/selfdestructconstructor.sol b/tests/immutable/cancun/selfdestruct/constructor/selfdestructconstructor.sol new file mode 100644 index 000000000..9c46d05ac --- /dev/null +++ b/tests/immutable/cancun/selfdestruct/constructor/selfdestructconstructor.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.25; + +contract SelfDestructConstructor { + constructor(address recipient) payable { + selfdestruct(payable(recipient)); + } +} + +// build/bin/abigen --abi ./tests/immutable/cancun/selfdestruct/constructor/abi.json --pkg selfdestructconstructor --type SelfDestructConstructor --out ./tests/immutable/cancun/selfdestruct/constructor/selfdestructconstructor.go --bin ./tests/immutable/cancun/selfdestruct/constructor/bytecode.bin diff --git a/tests/immutable/cancun/selfdestruct/function/abi.json b/tests/immutable/cancun/selfdestruct/function/abi.json new file mode 100644 index 000000000..b2b6c4560 --- /dev/null +++ b/tests/immutable/cancun/selfdestruct/function/abi.json @@ -0,0 +1,9 @@ +[ + { + "type": "function", + "name": "selfDestruct", + "inputs": [{ "name": "recipient", "type": "address", "internalType": "address" }], + "outputs": [], + "stateMutability": "nonpayable" + } +] diff --git a/tests/immutable/cancun/selfdestruct/function/bytecode.bin b/tests/immutable/cancun/selfdestruct/function/bytecode.bin new file mode 100644 index 000000000..b836777df --- /dev/null +++ b/tests/immutable/cancun/selfdestruct/function/bytecode.bin @@ -0,0 +1 @@ +6080604052348015600f57600080fd5b5060b880601d6000396000f3fe60806040526004361060205760003560e01c80633f5a0bdd14602b57600080fd5b36602657005b600080fd5b348015603657600080fd5b50604660423660046054565b6048565b005b806001600160a01b0316ff5b600060208284031215606557600080fd5b81356001600160a01b0381168114607b57600080fd5b939250505056fea2646970667358221220f9c68da8ceb7e06771a69cb7acfbe7b52c3c45d91e44a58a1243d16d54606eae64736f6c63430008190033 \ No newline at end of file diff --git a/tests/immutable/cancun/selfdestruct/function/selfdestructfunction.go b/tests/immutable/cancun/selfdestruct/function/selfdestructfunction.go new file mode 100644 index 000000000..778df49db --- /dev/null +++ b/tests/immutable/cancun/selfdestruct/function/selfdestructfunction.go @@ -0,0 +1,224 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package selfdestructfunction + +import ( + "errors" + "math/big" + "strings" + + 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 +) + +// SelfDestructFunctionMetaData contains all meta data concerning the SelfDestructFunction contract. +var SelfDestructFunctionMetaData = &bind.MetaData{ + ABI: "[{\"type\":\"function\",\"name\":\"selfDestruct\",\"inputs\":[{\"name\":\"recipient\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"}]", + Bin: "0x6080604052348015600f57600080fd5b5060b880601d6000396000f3fe60806040526004361060205760003560e01c80633f5a0bdd14602b57600080fd5b36602657005b600080fd5b348015603657600080fd5b50604660423660046054565b6048565b005b806001600160a01b0316ff5b600060208284031215606557600080fd5b81356001600160a01b0381168114607b57600080fd5b939250505056fea2646970667358221220f9c68da8ceb7e06771a69cb7acfbe7b52c3c45d91e44a58a1243d16d54606eae64736f6c63430008190033", +} + +// SelfDestructFunctionABI is the input ABI used to generate the binding from. +// Deprecated: Use SelfDestructFunctionMetaData.ABI instead. +var SelfDestructFunctionABI = SelfDestructFunctionMetaData.ABI + +// SelfDestructFunctionBin is the compiled bytecode used for deploying new contracts. +// Deprecated: Use SelfDestructFunctionMetaData.Bin instead. +var SelfDestructFunctionBin = SelfDestructFunctionMetaData.Bin + +// DeploySelfDestructFunction deploys a new Ethereum contract, binding an instance of SelfDestructFunction to it. +func DeploySelfDestructFunction(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *SelfDestructFunction, error) { + parsed, err := SelfDestructFunctionMetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(SelfDestructFunctionBin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &SelfDestructFunction{SelfDestructFunctionCaller: SelfDestructFunctionCaller{contract: contract}, SelfDestructFunctionTransactor: SelfDestructFunctionTransactor{contract: contract}, SelfDestructFunctionFilterer: SelfDestructFunctionFilterer{contract: contract}}, nil +} + +// SelfDestructFunction is an auto generated Go binding around an Ethereum contract. +type SelfDestructFunction struct { + SelfDestructFunctionCaller // Read-only binding to the contract + SelfDestructFunctionTransactor // Write-only binding to the contract + SelfDestructFunctionFilterer // Log filterer for contract events +} + +// SelfDestructFunctionCaller is an auto generated read-only Go binding around an Ethereum contract. +type SelfDestructFunctionCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// SelfDestructFunctionTransactor is an auto generated write-only Go binding around an Ethereum contract. +type SelfDestructFunctionTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// SelfDestructFunctionFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type SelfDestructFunctionFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// SelfDestructFunctionSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type SelfDestructFunctionSession struct { + Contract *SelfDestructFunction // 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 +} + +// SelfDestructFunctionCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type SelfDestructFunctionCallerSession struct { + Contract *SelfDestructFunctionCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// SelfDestructFunctionTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type SelfDestructFunctionTransactorSession struct { + Contract *SelfDestructFunctionTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// SelfDestructFunctionRaw is an auto generated low-level Go binding around an Ethereum contract. +type SelfDestructFunctionRaw struct { + Contract *SelfDestructFunction // Generic contract binding to access the raw methods on +} + +// SelfDestructFunctionCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type SelfDestructFunctionCallerRaw struct { + Contract *SelfDestructFunctionCaller // Generic read-only contract binding to access the raw methods on +} + +// SelfDestructFunctionTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type SelfDestructFunctionTransactorRaw struct { + Contract *SelfDestructFunctionTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewSelfDestructFunction creates a new instance of SelfDestructFunction, bound to a specific deployed contract. +func NewSelfDestructFunction(address common.Address, backend bind.ContractBackend) (*SelfDestructFunction, error) { + contract, err := bindSelfDestructFunction(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &SelfDestructFunction{SelfDestructFunctionCaller: SelfDestructFunctionCaller{contract: contract}, SelfDestructFunctionTransactor: SelfDestructFunctionTransactor{contract: contract}, SelfDestructFunctionFilterer: SelfDestructFunctionFilterer{contract: contract}}, nil +} + +// NewSelfDestructFunctionCaller creates a new read-only instance of SelfDestructFunction, bound to a specific deployed contract. +func NewSelfDestructFunctionCaller(address common.Address, caller bind.ContractCaller) (*SelfDestructFunctionCaller, error) { + contract, err := bindSelfDestructFunction(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &SelfDestructFunctionCaller{contract: contract}, nil +} + +// NewSelfDestructFunctionTransactor creates a new write-only instance of SelfDestructFunction, bound to a specific deployed contract. +func NewSelfDestructFunctionTransactor(address common.Address, transactor bind.ContractTransactor) (*SelfDestructFunctionTransactor, error) { + contract, err := bindSelfDestructFunction(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &SelfDestructFunctionTransactor{contract: contract}, nil +} + +// NewSelfDestructFunctionFilterer creates a new log filterer instance of SelfDestructFunction, bound to a specific deployed contract. +func NewSelfDestructFunctionFilterer(address common.Address, filterer bind.ContractFilterer) (*SelfDestructFunctionFilterer, error) { + contract, err := bindSelfDestructFunction(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &SelfDestructFunctionFilterer{contract: contract}, nil +} + +// bindSelfDestructFunction binds a generic wrapper to an already deployed contract. +func bindSelfDestructFunction(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := SelfDestructFunctionMetaData.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 (_SelfDestructFunction *SelfDestructFunctionRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _SelfDestructFunction.Contract.SelfDestructFunctionCaller.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 (_SelfDestructFunction *SelfDestructFunctionRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _SelfDestructFunction.Contract.SelfDestructFunctionTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_SelfDestructFunction *SelfDestructFunctionRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _SelfDestructFunction.Contract.SelfDestructFunctionTransactor.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 (_SelfDestructFunction *SelfDestructFunctionCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _SelfDestructFunction.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 (_SelfDestructFunction *SelfDestructFunctionTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _SelfDestructFunction.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_SelfDestructFunction *SelfDestructFunctionTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _SelfDestructFunction.Contract.contract.Transact(opts, method, params...) +} + +// SelfDestruct is a paid mutator transaction binding the contract method 0x3f5a0bdd. +// +// Solidity: function selfDestruct(address recipient) returns() +func (_SelfDestructFunction *SelfDestructFunctionTransactor) SelfDestruct(opts *bind.TransactOpts, recipient common.Address) (*types.Transaction, error) { + return _SelfDestructFunction.contract.Transact(opts, "selfDestruct", recipient) +} + +// SelfDestruct is a paid mutator transaction binding the contract method 0x3f5a0bdd. +// +// Solidity: function selfDestruct(address recipient) returns() +func (_SelfDestructFunction *SelfDestructFunctionSession) SelfDestruct(recipient common.Address) (*types.Transaction, error) { + return _SelfDestructFunction.Contract.SelfDestruct(&_SelfDestructFunction.TransactOpts, recipient) +} + +// SelfDestruct is a paid mutator transaction binding the contract method 0x3f5a0bdd. +// +// Solidity: function selfDestruct(address recipient) returns() +func (_SelfDestructFunction *SelfDestructFunctionTransactorSession) SelfDestruct(recipient common.Address) (*types.Transaction, error) { + return _SelfDestructFunction.Contract.SelfDestruct(&_SelfDestructFunction.TransactOpts, recipient) +} diff --git a/tests/immutable/cancun/selfdestruct/function/selfdestructfunction.sol b/tests/immutable/cancun/selfdestruct/function/selfdestructfunction.sol new file mode 100644 index 000000000..0b3eb67d2 --- /dev/null +++ b/tests/immutable/cancun/selfdestruct/function/selfdestructfunction.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.25; + +contract SelfDestructFunction { + function selfDestruct(address recipient) external { + selfdestruct(payable(recipient)); + } + + receive() external payable {} +} + +// build/bin/abigen --abi ./tests/immutable/cancun/selfdestruct/function/abi.json --pkg selfdestructfunction --type SelfDestructFunction --out ./tests/immutable/cancun/selfdestruct/function/selfdestructfunction.go --bin ./tests/immutable/cancun/selfdestruct/function/bytecode.bin diff --git a/tests/immutable/cancun/transientstorage/abi.json b/tests/immutable/cancun/transientstorage/abi.json new file mode 100644 index 000000000..5c2965074 --- /dev/null +++ b/tests/immutable/cancun/transientstorage/abi.json @@ -0,0 +1,18 @@ +[ + { + "type": "function", + "name": "tStoreLoad", + "inputs": [ + { "name": "key", "type": "uint256", "internalType": "uint256" }, + { "name": "value", "type": "uint256", "internalType": "uint256" } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "event", + "name": "Value", + "inputs": [{ "name": "value", "type": "uint256", "indexed": false, "internalType": "uint256" }], + "anonymous": false + } +] diff --git a/tests/immutable/cancun/transientstorage/bytecode.bin b/tests/immutable/cancun/transientstorage/bytecode.bin new file mode 100644 index 000000000..fa0be7b29 --- /dev/null +++ b/tests/immutable/cancun/transientstorage/bytecode.bin @@ -0,0 +1 @@ +608060405234801561001057600080fd5b5060f48061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806366a82e4a14602d575b600080fd5b603c6038366004607c565b603e565b005b80825d50604051815c808252907f2a27502c345a4cd966daa061d5537f54cd60d2d20b73680b3bf195c91e806a4b9060200160405180910390a15050565b60008060408385031215608e57600080fd5b5050803592602090910135915056fea2646970667358221220632312d849916834c328572910b3d44422cfb2ab00f8a234e5eaab69d71d0b6a64736f6c637823302e382e32322d63692e323032332e392e32312b636f6d6d69742e33633536396462390054 \ No newline at end of file diff --git a/tests/immutable/cancun/transientstorage/transientstorage.go b/tests/immutable/cancun/transientstorage/transientstorage.go new file mode 100644 index 000000000..e694b034a --- /dev/null +++ b/tests/immutable/cancun/transientstorage/transientstorage.go @@ -0,0 +1,358 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package transientstorage + +import ( + "errors" + "math/big" + "strings" + + 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 +) + +// TransientStorageMetaData contains all meta data concerning the TransientStorage contract. +var TransientStorageMetaData = &bind.MetaData{ + ABI: "[{\"type\":\"function\",\"name\":\"tStoreLoad\",\"inputs\":[{\"name\":\"key\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"value\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"event\",\"name\":\"Value\",\"inputs\":[{\"name\":\"value\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false}]", + Bin: "0x608060405234801561001057600080fd5b5060f48061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806366a82e4a14602d575b600080fd5b603c6038366004607c565b603e565b005b80825d50604051815c808252907f2a27502c345a4cd966daa061d5537f54cd60d2d20b73680b3bf195c91e806a4b9060200160405180910390a15050565b60008060408385031215608e57600080fd5b5050803592602090910135915056fea2646970667358221220632312d849916834c328572910b3d44422cfb2ab00f8a234e5eaab69d71d0b6a64736f6c637823302e382e32322d63692e323032332e392e32312b636f6d6d69742e33633536396462390054", +} + +// TransientStorageABI is the input ABI used to generate the binding from. +// Deprecated: Use TransientStorageMetaData.ABI instead. +var TransientStorageABI = TransientStorageMetaData.ABI + +// TransientStorageBin is the compiled bytecode used for deploying new contracts. +// Deprecated: Use TransientStorageMetaData.Bin instead. +var TransientStorageBin = TransientStorageMetaData.Bin + +// DeployTransientStorage deploys a new Ethereum contract, binding an instance of TransientStorage to it. +func DeployTransientStorage(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *TransientStorage, error) { + parsed, err := TransientStorageMetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(TransientStorageBin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &TransientStorage{TransientStorageCaller: TransientStorageCaller{contract: contract}, TransientStorageTransactor: TransientStorageTransactor{contract: contract}, TransientStorageFilterer: TransientStorageFilterer{contract: contract}}, nil +} + +// TransientStorage is an auto generated Go binding around an Ethereum contract. +type TransientStorage struct { + TransientStorageCaller // Read-only binding to the contract + TransientStorageTransactor // Write-only binding to the contract + TransientStorageFilterer // Log filterer for contract events +} + +// TransientStorageCaller is an auto generated read-only Go binding around an Ethereum contract. +type TransientStorageCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// TransientStorageTransactor is an auto generated write-only Go binding around an Ethereum contract. +type TransientStorageTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// TransientStorageFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type TransientStorageFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// TransientStorageSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type TransientStorageSession struct { + Contract *TransientStorage // 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 +} + +// TransientStorageCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type TransientStorageCallerSession struct { + Contract *TransientStorageCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// TransientStorageTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type TransientStorageTransactorSession struct { + Contract *TransientStorageTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// TransientStorageRaw is an auto generated low-level Go binding around an Ethereum contract. +type TransientStorageRaw struct { + Contract *TransientStorage // Generic contract binding to access the raw methods on +} + +// TransientStorageCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type TransientStorageCallerRaw struct { + Contract *TransientStorageCaller // Generic read-only contract binding to access the raw methods on +} + +// TransientStorageTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type TransientStorageTransactorRaw struct { + Contract *TransientStorageTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewTransientStorage creates a new instance of TransientStorage, bound to a specific deployed contract. +func NewTransientStorage(address common.Address, backend bind.ContractBackend) (*TransientStorage, error) { + contract, err := bindTransientStorage(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &TransientStorage{TransientStorageCaller: TransientStorageCaller{contract: contract}, TransientStorageTransactor: TransientStorageTransactor{contract: contract}, TransientStorageFilterer: TransientStorageFilterer{contract: contract}}, nil +} + +// NewTransientStorageCaller creates a new read-only instance of TransientStorage, bound to a specific deployed contract. +func NewTransientStorageCaller(address common.Address, caller bind.ContractCaller) (*TransientStorageCaller, error) { + contract, err := bindTransientStorage(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &TransientStorageCaller{contract: contract}, nil +} + +// NewTransientStorageTransactor creates a new write-only instance of TransientStorage, bound to a specific deployed contract. +func NewTransientStorageTransactor(address common.Address, transactor bind.ContractTransactor) (*TransientStorageTransactor, error) { + contract, err := bindTransientStorage(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &TransientStorageTransactor{contract: contract}, nil +} + +// NewTransientStorageFilterer creates a new log filterer instance of TransientStorage, bound to a specific deployed contract. +func NewTransientStorageFilterer(address common.Address, filterer bind.ContractFilterer) (*TransientStorageFilterer, error) { + contract, err := bindTransientStorage(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &TransientStorageFilterer{contract: contract}, nil +} + +// bindTransientStorage binds a generic wrapper to an already deployed contract. +func bindTransientStorage(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := TransientStorageMetaData.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 (_TransientStorage *TransientStorageRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _TransientStorage.Contract.TransientStorageCaller.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 (_TransientStorage *TransientStorageRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _TransientStorage.Contract.TransientStorageTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_TransientStorage *TransientStorageRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _TransientStorage.Contract.TransientStorageTransactor.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 (_TransientStorage *TransientStorageCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _TransientStorage.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 (_TransientStorage *TransientStorageTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _TransientStorage.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_TransientStorage *TransientStorageTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _TransientStorage.Contract.contract.Transact(opts, method, params...) +} + +// TStoreLoad is a paid mutator transaction binding the contract method 0x66a82e4a. +// +// Solidity: function tStoreLoad(uint256 key, uint256 value) returns() +func (_TransientStorage *TransientStorageTransactor) TStoreLoad(opts *bind.TransactOpts, key *big.Int, value *big.Int) (*types.Transaction, error) { + return _TransientStorage.contract.Transact(opts, "tStoreLoad", key, value) +} + +// TStoreLoad is a paid mutator transaction binding the contract method 0x66a82e4a. +// +// Solidity: function tStoreLoad(uint256 key, uint256 value) returns() +func (_TransientStorage *TransientStorageSession) TStoreLoad(key *big.Int, value *big.Int) (*types.Transaction, error) { + return _TransientStorage.Contract.TStoreLoad(&_TransientStorage.TransactOpts, key, value) +} + +// TStoreLoad is a paid mutator transaction binding the contract method 0x66a82e4a. +// +// Solidity: function tStoreLoad(uint256 key, uint256 value) returns() +func (_TransientStorage *TransientStorageTransactorSession) TStoreLoad(key *big.Int, value *big.Int) (*types.Transaction, error) { + return _TransientStorage.Contract.TStoreLoad(&_TransientStorage.TransactOpts, key, value) +} + +// TransientStorageValueIterator is returned from FilterValue and is used to iterate over the raw logs and unpacked data for Value events raised by the TransientStorage contract. +type TransientStorageValueIterator struct { + Event *TransientStorageValue // 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 *TransientStorageValueIterator) 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(TransientStorageValue) + 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(TransientStorageValue) + 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 *TransientStorageValueIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *TransientStorageValueIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// TransientStorageValue represents a Value event raised by the TransientStorage contract. +type TransientStorageValue struct { + Value *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterValue is a free log retrieval operation binding the contract event 0x2a27502c345a4cd966daa061d5537f54cd60d2d20b73680b3bf195c91e806a4b. +// +// Solidity: event Value(uint256 value) +func (_TransientStorage *TransientStorageFilterer) FilterValue(opts *bind.FilterOpts) (*TransientStorageValueIterator, error) { + + logs, sub, err := _TransientStorage.contract.FilterLogs(opts, "Value") + if err != nil { + return nil, err + } + return &TransientStorageValueIterator{contract: _TransientStorage.contract, event: "Value", logs: logs, sub: sub}, nil +} + +// WatchValue is a free log subscription operation binding the contract event 0x2a27502c345a4cd966daa061d5537f54cd60d2d20b73680b3bf195c91e806a4b. +// +// Solidity: event Value(uint256 value) +func (_TransientStorage *TransientStorageFilterer) WatchValue(opts *bind.WatchOpts, sink chan<- *TransientStorageValue) (event.Subscription, error) { + + logs, sub, err := _TransientStorage.contract.WatchLogs(opts, "Value") + 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(TransientStorageValue) + if err := _TransientStorage.contract.UnpackLog(event, "Value", 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 +} + +// ParseValue is a log parse operation binding the contract event 0x2a27502c345a4cd966daa061d5537f54cd60d2d20b73680b3bf195c91e806a4b. +// +// Solidity: event Value(uint256 value) +func (_TransientStorage *TransientStorageFilterer) ParseValue(log types.Log) (*TransientStorageValue, error) { + event := new(TransientStorageValue) + if err := _TransientStorage.contract.UnpackLog(event, "Value", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} diff --git a/tests/immutable/cancun/transientstorage/transientstorage.sol b/tests/immutable/cancun/transientstorage/transientstorage.sol new file mode 100644 index 000000000..50e80ade4 --- /dev/null +++ b/tests/immutable/cancun/transientstorage/transientstorage.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.20; + +contract SimpleTStore { + event Value(uint value); + + function tStoreLoad(uint key, uint value) external returns (uint) { + assembly { + tstore(key, value) + value := tload(key) + } + } +} + +// build/bin/abigen --abi ./tests/immutable/cancun/transientstorage/abi.json --pkg transientstorage --type TransientStorage --out ./tests/immutable/cancun/transientstorage/transientstorage.go --bin ./tests/immutable/cancun/transientstorage/bytecode.bin diff --git a/tests/immutable/cancun_test.go b/tests/immutable/cancun_test.go new file mode 100644 index 000000000..57f3e7196 --- /dev/null +++ b/tests/immutable/cancun_test.go @@ -0,0 +1,327 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package test + +import ( + "context" + "crypto/sha256" + + "math/big" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto/kzg4844" + "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/tests/immutable/cancun/blobbasefee" + "github.com/ethereum/go-ethereum/tests/immutable/cancun/blobhash" + "github.com/ethereum/go-ethereum/tests/immutable/cancun/mcopy" + selfdestructconstructor "github.com/ethereum/go-ethereum/tests/immutable/cancun/selfdestruct/constructor" + selfdestructfunction "github.com/ethereum/go-ethereum/tests/immutable/cancun/selfdestruct/function" + "github.com/ethereum/go-ethereum/tests/immutable/cancun/transientstorage" + "github.com/ethereum/go-ethereum/tests/immutable/evm" + "github.com/stretchr/testify/require" +) + +func TestImmutable_Cancun_4844TransactionsDisabled(t *testing.T) { + if skipCancun(t) { + return + } + // Context and client + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + client, err := evm.NewClient(ctx, config.rpcURL, 1, 1) + require.NoError(t, err) + + // Construct empty block txn + emptyBlob := kzg4844.Blob{} + emptyBlobCommit, err := kzg4844.BlobToCommitment(emptyBlob) + require.NoError(t, err) + emptyBlobProof, err := kzg4844.ComputeBlobProof(emptyBlob, emptyBlobCommit) + require.NoError(t, err) + emptyBlobVHash := kzg4844.CalcBlobHashV1(sha256.New(), &emptyBlobCommit) + blobTx := types.BlobTx{ + Gas: 21000, + BlobHashes: []common.Hash{emptyBlobVHash}, + Value: uint256.NewInt(100), + Sidecar: &types.BlobTxSidecar{ + Blobs: []kzg4844.Blob{emptyBlob}, + Commitments: []kzg4844.Commitment{emptyBlobCommit}, + Proofs: []kzg4844.Proof{emptyBlobProof}, + }, + } + + // Send request and expect rejection + err = client.EthClient().SendTransaction(ctx, types.NewTx(&blobTx)) + require.Error(t, err) + require.Equal(t, "transaction type not supported: type 3 rejected, blob transactions not supported", err.Error()) +} + +func TestImmutable_Cancun_4844BlobHashInstruction_ValidateOpCodeAndValue(t *testing.T) { + if skipCancun(t) { + return + } + // Context and client + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + client, err := evm.NewClient(ctx, config.rpcURL, 1, 1) + require.NoError(t, err) + + // Deploy the contract + txOpts := EIP1559TxOpts(t, ctx, client.EthClient(), config.testUser) + _, deployTx, contract, err := blobhash.DeployBlobhash(txOpts, client.EthClient()) + require.NoError(t, err) + deployReceipt, err := bind.WaitMined(ctx, client, deployTx) + require.Equal(t, deployReceipt.Status, uint64(1)) + require.NoError(t, err) + t.Logf("Contract deployed to address %s with TX Hash %s", deployReceipt.ContractAddress.String(), deployTx.Hash().String()) + + // Transact + opts := &bind.CallOpts{Context: ctx} + blobHash, err := contract.BlobHash(opts, big.NewInt(1)) + require.NoError(t, err) + require.Equal(t, [32]byte{}, blobHash) +} + +func TestImmutable_Cancun_BlockHeaders(t *testing.T) { + if skipCancun(t) { + return + } + // Context and client + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + client, err := evm.NewClient(ctx, config.rpcURL, 1, 1) + require.NoError(t, err) + + // Get latest block + latestBlock, err := client.EthClient().BlockByNumber(ctx, nil) + require.NoError(t, err) + require.NotNil(t, latestBlock) + + // Check beacon root + require.NotNil(t, latestBlock.BeaconRoot()) + require.Equal(t, common.MinHash.String(), latestBlock.BeaconRoot().String()) + + // Check blob fields + require.NotNil(t, latestBlock.BlobGasUsed()) + require.Equal(t, uint64(0), *latestBlock.BlobGasUsed()) + require.NotNil(t, latestBlock.ExcessBlobGas()) + require.Equal(t, uint64(0), *latestBlock.ExcessBlobGas()) + + // Check withdrawals hash and list + require.NotNil(t, latestBlock.Header().WithdrawalsHash) + require.Equal(t, types.EmptyWithdrawalsHash.String(), latestBlock.Header().WithdrawalsHash.String()) + require.NotNil(t, latestBlock.Withdrawals()) + require.Empty(t, latestBlock.Withdrawals()) +} + +func TestImmutable_Cancun_1153TransientStorage_ValidatesTstoreAndTLoad(t *testing.T) { + if skipCancun(t) { + return + } + // Context and client + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + client, err := evm.NewClient(ctx, config.rpcURL, 1, 1) + require.NoError(t, err) + txOpts := EIP1559TxOpts(t, ctx, client.EthClient(), config.testUser) + + // Deploy the contract + _, deployTx, contract, err := transientstorage.DeployTransientStorage(txOpts, client.EthClient()) + require.NoError(t, err) + deployReceipt, err := bind.WaitMined(ctx, client, deployTx) + require.Equal(t, deployReceipt.Status, uint64(1)) + require.NoError(t, err) + t.Logf("Contract deployed to address %s with TX Hash %s", deployReceipt.ContractAddress.String(), deployTx.Hash().String()) + + // Transact + key := big.NewInt(1) + val := big.NewInt(2) + tx, err := contract.TStoreLoad(txOpts, key, val) + require.NoError(t, err) + receipt, err := bind.WaitMined(ctx, client, tx) + require.NoError(t, err) + require.Equal(t, receipt.Status, uint64(1)) + log, err := contract.ParseValue(*receipt.Logs[0]) + require.NoError(t, err) + require.Equal(t, val, log.Value) +} + +func TestImmutable_Cancun_7516BlobBaseFee_ValidateOpCodeAndValue(t *testing.T) { + if skipCancun(t) { + return + } + // Context and client + ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) + defer cancel() + client, err := evm.NewClient(ctx, config.rpcURL, 1, 1) + require.NoError(t, err) + txOpts := EIP1559TxOpts(t, ctx, client.EthClient(), config.testUser) + + // Deploy the contract + _, deployTx, contract, err := blobbasefee.DeployBlobBaseFee(txOpts, client.EthClient()) + require.NoError(t, err) + deployReceipt, err := bind.WaitMined(ctx, client, deployTx) + require.Equal(t, deployReceipt.Status, uint64(1)) + require.NoError(t, err) + t.Logf("Contract deployed to address %s with TX Hash %s", deployReceipt.ContractAddress.String(), deployTx.Hash().String()) + + // Transact + opts := &bind.CallOpts{Context: ctx} + blobBaseFee, err := contract.BlobBaseFee(opts) + require.NoError(t, err) + require.NotNil(t, blobBaseFee) + + // Validate value as per default params + require.Equal(t, uint64(params.BlobTxMinBlobGasprice), blobBaseFee.Uint64()) +} + +func TestImmutable_Cancun_5656Mcopy_ValidateOpCodeAndValue(t *testing.T) { + if skipCancun(t) { + return + } + // Context and client + ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) + defer cancel() + client, err := evm.NewClient(ctx, config.rpcURL, 1, 1) + require.NoError(t, err) + txOpts := EIP1559TxOpts(t, ctx, client.EthClient(), config.testUser) + + // Deploy the contract + _, deployTx, contract, err := mcopy.DeployMcopy(txOpts, client.EthClient()) + require.NoError(t, err) + deployReceipt, err := bind.WaitMined(ctx, client, deployTx) + require.Equal(t, deployReceipt.Status, uint64(1)) + require.NoError(t, err) + t.Logf("Contract deployed to address %s with TX Hash %s", deployReceipt.ContractAddress.String(), deployTx.Hash().String()) + + // Transact + opts := &bind.CallOpts{Context: ctx} + mem, err := contract.MemoryCopy(opts) + require.NoError(t, err) + require.NotNil(t, mem) + + // Validate memory as per cancun/mcopy/mcopy.sol + expected := [32]byte{} + expected[31] = 0x50 + require.Equal(t, expected, mem) +} + +func TestImmutable_Cancun_6780SelfDestruct_ValidateConstructorBehaviour(t *testing.T) { + if skipCancun(t) { + return + } + // Context and client + ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) + defer cancel() + client, err := evm.NewClient(ctx, config.rpcURL, 1, 1) + require.NoError(t, err) + txOpts := EIP1559TxOpts(t, ctx, client.EthClient(), config.testUser) + + // Recipient of self destruct + recipient := genRandPubKey(t) + + // Set tx value + txOpts.Value = big.NewInt(1) + preBal, err := client.EthClient().BalanceAt(ctx, recipient, nil) + require.NoError(t, err) + + // Deploy the contract + addr, deployTx, _, err := selfdestructconstructor.DeploySelfDestructConstructor(txOpts, client.EthClient(), recipient) + require.NoError(t, err) + deployReceipt, err := bind.WaitMined(ctx, client, deployTx) + require.Equal(t, deployReceipt.Status, uint64(1)) + require.NoError(t, err) + t.Logf("Contract deployed to address %s with TX Hash %s", addr.String(), deployTx.Hash().String()) + + // Check balance of recipient + balance, err := client.EthClient().BalanceAt(ctx, recipient, nil) + require.NoError(t, err) + require.Equal(t, new(big.Int).Add(preBal, txOpts.Value), balance) + + // Check balance of contract + balance, err = client.EthClient().BalanceAt(ctx, addr, nil) + require.NoError(t, err) + require.Equal(t, int64(0), balance.Int64()) + + // Check code of contract is now empty + code, err := client.EthClient().CodeAt(ctx, addr, nil) + require.NoError(t, err) + require.Empty(t, code) +} + +func TestImmutable_Cancun_6780SelfDestruct_ValidateFunctionBehaviour(t *testing.T) { + if skipCancun(t) { + return + } + // Context and client + ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) + defer cancel() + client, err := evm.NewClient(ctx, config.rpcURL, 1, 1) + require.NoError(t, err) + txOpts := EIP1559TxOpts(t, ctx, client.EthClient(), config.testUser) + + // Recipient of self destruct + recipient := genRandPubKey(t) + + // Deploy the contract + addr, deployTx, contract, err := selfdestructfunction.DeploySelfDestructFunction(txOpts, client.EthClient()) + require.NoError(t, err) + deployReceipt, err := bind.WaitMined(ctx, client, deployTx) + require.Equal(t, deployReceipt.Status, uint64(1)) + require.NoError(t, err) + + // Fund the contract + fundAmt := big.NewInt(1) + tx := dynamicTx(t, ctx, client.EthClient(), fundAmt, config.testUser.key, addr) + err = client.EthClient().SendTransaction(ctx, tx) + require.NoError(t, err) + receipt, err := bind.WaitMined(ctx, client, tx) + require.NoError(t, err) + require.Equal(t, receipt.Status, uint64(1)) + + // Assert balance + balance, err := client.EthClient().BalanceAt(ctx, addr, nil) + require.NoError(t, err) + require.Equal(t, fundAmt, balance) + + // Self destruct + tx, err = contract.SelfDestruct(txOpts, recipient) + require.NoError(t, err) + receipt, err = bind.WaitMined(ctx, client, tx) + require.NoError(t, err) + require.Equal(t, receipt.Status, uint64(1)) + + // Check balance of contract + balance, err = client.EthClient().BalanceAt(ctx, addr, nil) + require.NoError(t, err) + require.Equal(t, int64(0), balance.Int64()) + + // Check balance of recipient + balance, err = client.EthClient().BalanceAt(ctx, recipient, nil) + require.NoError(t, err) + require.Equal(t, big.NewInt(1), balance) + + // Check that code is not empty + code, err := client.EthClient().CodeAt(ctx, addr, nil) + require.NoError(t, err) + require.NotEmpty(t, code) +} diff --git a/tests/immutable/cliqueclient_test.go b/tests/immutable/cliqueclient_test.go new file mode 100644 index 000000000..3a4d6998f --- /dev/null +++ b/tests/immutable/cliqueclient_test.go @@ -0,0 +1,69 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package test + +import ( + "context" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient/gethclient" + "github.com/ethereum/go-ethereum/log" + gethrpc "github.com/ethereum/go-ethereum/rpc" + "github.com/stretchr/testify/require" +) + +func TestCliqueClient_AddSigner(t *testing.T) { + if config.skipVoting { + t.Skip("Skipping Test as per command-line flag") + } + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + + rpcClient, err := gethrpc.DialContext(ctx, config.validatorURL.String()) + require.NoError(t, err) + + gethClient := gethclient.New(rpcClient) + + signers, err := gethClient.GetSigners(ctx, nil) + require.NoError(t, err) + + // Should have 1 validator by default + require.Equal(t, 1, len(signers), "expected 1 signer") + + // Propose a new validator + newSigner := common.HexToAddress("0x7442eD1e3c9FD421F47d12A2742AfF5DaFBf43f8") + err = gethClient.Propose(ctx, newSigner, true) + require.NoError(t, err) + + // Wait a few seconds for a block + log.Warn("Waiting 10s for voting to propagate") + time.Sleep(10 * time.Second) + + signers, err = gethClient.GetSigners(ctx, nil) + require.NoError(t, err) + + require.Equal(t, 2, len(signers), "expected 2 signers") + if signers[0].Cmp(newSigner) != 0 && signers[1].Cmp(newSigner) != 0 { + t.Fatalf("unexpected new signer. expected %s, got %s and %s", + newSigner.String(), + signers[0].String(), + signers[1].String(), + ) + } +} diff --git a/tests/immutable/erc20/abi.json b/tests/immutable/erc20/abi.json new file mode 100644 index 000000000..87057e772 --- /dev/null +++ b/tests/immutable/erc20/abi.json @@ -0,0 +1,277 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "addedValue", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/tests/immutable/erc20/bytecode.bin b/tests/immutable/erc20/bytecode.bin new file mode 100644 index 000000000..0aff46dca --- /dev/null +++ b/tests/immutable/erc20/bytecode.bin @@ -0,0 +1 @@ +0x60806040523480156200001157600080fd5b506040518060400160405280600781526020017f426974436f696e000000000000000000000000000000000000000000000000008152506040518060400160405280600381526020017f425443000000000000000000000000000000000000000000000000000000000081525081600390816200008f9190620004b6565b508060049081620000a19190620004b6565b505050620000bf3368056bc75e2d63100000620000c560201b60201c565b620006b8565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff160362000137576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016200012e90620005fe565b60405180910390fd5b6200014b600083836200023260201b60201c565b80600260008282546200015f91906200064f565b92505081905550806000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040516200021291906200069b565b60405180910390a36200022e600083836200023760201b60201c565b5050565b505050565b505050565b600081519050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b60006002820490506001821680620002be57607f821691505b602082108103620002d457620002d362000276565b5b50919050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b6000600883026200033e7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82620002ff565b6200034a8683620002ff565b95508019841693508086168417925050509392505050565b6000819050919050565b6000819050919050565b600062000397620003916200038b8462000362565b6200036c565b62000362565b9050919050565b6000819050919050565b620003b38362000376565b620003cb620003c2826200039e565b8484546200030c565b825550505050565b600090565b620003e2620003d3565b620003ef818484620003a8565b505050565b5b8181101562000417576200040b600082620003d8565b600181019050620003f5565b5050565b601f82111562000466576200043081620002da565b6200043b84620002ef565b810160208510156200044b578190505b620004636200045a85620002ef565b830182620003f4565b50505b505050565b600082821c905092915050565b60006200048b600019846008026200046b565b1980831691505092915050565b6000620004a6838362000478565b9150826002028217905092915050565b620004c1826200023c565b67ffffffffffffffff811115620004dd57620004dc62000247565b5b620004e98254620002a5565b620004f68282856200041b565b600060209050601f8311600181146200052e576000841562000519578287015190505b62000525858262000498565b86555062000595565b601f1984166200053e86620002da565b60005b82811015620005685784890151825560018201915060208501945060208101905062000541565b8683101562000588578489015162000584601f89168262000478565b8355505b6001600288020188555050505b505050505050565b600082825260208201905092915050565b7f45524332303a206d696e7420746f20746865207a65726f206164647265737300600082015250565b6000620005e6601f836200059d565b9150620005f382620005ae565b602082019050919050565b600060208201905081810360008301526200061981620005d7565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60006200065c8262000362565b9150620006698362000362565b925082820190508082111562000684576200068362000620565b5b92915050565b620006958162000362565b82525050565b6000602082019050620006b260008301846200068a565b92915050565b61122f80620006c86000396000f3fe608060405234801561001057600080fd5b50600436106100a95760003560e01c80633950935111610071578063395093511461016857806370a082311461019857806395d89b41146101c8578063a457c2d7146101e6578063a9059cbb14610216578063dd62ed3e14610246576100a9565b806306fdde03146100ae578063095ea7b3146100cc57806318160ddd146100fc57806323b872dd1461011a578063313ce5671461014a575b600080fd5b6100b6610276565b6040516100c39190610b0c565b60405180910390f35b6100e660048036038101906100e19190610bc7565b610308565b6040516100f39190610c22565b60405180910390f35b61010461032b565b6040516101119190610c4c565b60405180910390f35b610134600480360381019061012f9190610c67565b610335565b6040516101419190610c22565b60405180910390f35b610152610364565b60405161015f9190610cd6565b60405180910390f35b610182600480360381019061017d9190610bc7565b61036d565b60405161018f9190610c22565b60405180910390f35b6101b260048036038101906101ad9190610cf1565b6103a4565b6040516101bf9190610c4c565b60405180910390f35b6101d06103ec565b6040516101dd9190610b0c565b60405180910390f35b61020060048036038101906101fb9190610bc7565b61047e565b60405161020d9190610c22565b60405180910390f35b610230600480360381019061022b9190610bc7565b6104f5565b60405161023d9190610c22565b60405180910390f35b610260600480360381019061025b9190610d1e565b610518565b60405161026d9190610c4c565b60405180910390f35b60606003805461028590610d8d565b80601f01602080910402602001604051908101604052809291908181526020018280546102b190610d8d565b80156102fe5780601f106102d3576101008083540402835291602001916102fe565b820191906000526020600020905b8154815290600101906020018083116102e157829003601f168201915b5050505050905090565b60008061031361059f565b90506103208185856105a7565b600191505092915050565b6000600254905090565b60008061034061059f565b905061034d858285610770565b6103588585856107fc565b60019150509392505050565b60006012905090565b60008061037861059f565b905061039981858561038a8589610518565b6103949190610ded565b6105a7565b600191505092915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b6060600480546103fb90610d8d565b80601f016020809104026020016040519081016040528092919081815260200182805461042790610d8d565b80156104745780601f1061044957610100808354040283529160200191610474565b820191906000526020600020905b81548152906001019060200180831161045757829003601f168201915b5050505050905090565b60008061048961059f565b905060006104978286610518565b9050838110156104dc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104d390610e93565b60405180910390fd5b6104e982868684036105a7565b60019250505092915050565b60008061050061059f565b905061050d8185856107fc565b600191505092915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b600033905090565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610616576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161060d90610f25565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610685576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161067c90610fb7565b60405180910390fd5b80600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925836040516107639190610c4c565b60405180910390a3505050565b600061077c8484610518565b90507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146107f657818110156107e8576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107df90611023565b60405180910390fd5b6107f584848484036105a7565b5b50505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff160361086b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610862906110b5565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036108da576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016108d190611147565b60405180910390fd5b6108e5838383610a72565b60008060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490508181101561096b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610962906111d9565b60405180910390fd5b8181036000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051610a599190610c4c565b60405180910390a3610a6c848484610a77565b50505050565b505050565b505050565b600081519050919050565b600082825260208201905092915050565b60005b83811015610ab6578082015181840152602081019050610a9b565b60008484015250505050565b6000601f19601f8301169050919050565b6000610ade82610a7c565b610ae88185610a87565b9350610af8818560208601610a98565b610b0181610ac2565b840191505092915050565b60006020820190508181036000830152610b268184610ad3565b905092915050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610b5e82610b33565b9050919050565b610b6e81610b53565b8114610b7957600080fd5b50565b600081359050610b8b81610b65565b92915050565b6000819050919050565b610ba481610b91565b8114610baf57600080fd5b50565b600081359050610bc181610b9b565b92915050565b60008060408385031215610bde57610bdd610b2e565b5b6000610bec85828601610b7c565b9250506020610bfd85828601610bb2565b9150509250929050565b60008115159050919050565b610c1c81610c07565b82525050565b6000602082019050610c376000830184610c13565b92915050565b610c4681610b91565b82525050565b6000602082019050610c616000830184610c3d565b92915050565b600080600060608486031215610c8057610c7f610b2e565b5b6000610c8e86828701610b7c565b9350506020610c9f86828701610b7c565b9250506040610cb086828701610bb2565b9150509250925092565b600060ff82169050919050565b610cd081610cba565b82525050565b6000602082019050610ceb6000830184610cc7565b92915050565b600060208284031215610d0757610d06610b2e565b5b6000610d1584828501610b7c565b91505092915050565b60008060408385031215610d3557610d34610b2e565b5b6000610d4385828601610b7c565b9250506020610d5485828601610b7c565b9150509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b60006002820490506001821680610da557607f821691505b602082108103610db857610db7610d5e565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610df882610b91565b9150610e0383610b91565b9250828201905080821115610e1b57610e1a610dbe565b5b92915050565b7f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760008201527f207a65726f000000000000000000000000000000000000000000000000000000602082015250565b6000610e7d602583610a87565b9150610e8882610e21565b604082019050919050565b60006020820190508181036000830152610eac81610e70565b9050919050565b7f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460008201527f7265737300000000000000000000000000000000000000000000000000000000602082015250565b6000610f0f602483610a87565b9150610f1a82610eb3565b604082019050919050565b60006020820190508181036000830152610f3e81610f02565b9050919050565b7f45524332303a20617070726f766520746f20746865207a65726f20616464726560008201527f7373000000000000000000000000000000000000000000000000000000000000602082015250565b6000610fa1602283610a87565b9150610fac82610f45565b604082019050919050565b60006020820190508181036000830152610fd081610f94565b9050919050565b7f45524332303a20696e73756666696369656e7420616c6c6f77616e6365000000600082015250565b600061100d601d83610a87565b915061101882610fd7565b602082019050919050565b6000602082019050818103600083015261103c81611000565b9050919050565b7f45524332303a207472616e736665722066726f6d20746865207a65726f20616460008201527f6472657373000000000000000000000000000000000000000000000000000000602082015250565b600061109f602583610a87565b91506110aa82611043565b604082019050919050565b600060208201905081810360008301526110ce81611092565b9050919050565b7f45524332303a207472616e7366657220746f20746865207a65726f206164647260008201527f6573730000000000000000000000000000000000000000000000000000000000602082015250565b6000611131602383610a87565b915061113c826110d5565b604082019050919050565b6000602082019050818103600083015261116081611124565b9050919050565b7f45524332303a207472616e7366657220616d6f756e742065786365656473206260008201527f616c616e63650000000000000000000000000000000000000000000000000000602082015250565b60006111c3602683610a87565b91506111ce82611167565b604082019050919050565b600060208201905081810360008301526111f2816111b6565b905091905056fea2646970667358221220d1d2931276936d87e4800092e4cc8234ae552eca686bf3dde9b3c7d892d3cc3564736f6c63430008110033 \ No newline at end of file diff --git a/tests/immutable/erc20/erc20.go b/tests/immutable/erc20/erc20.go new file mode 100644 index 000000000..9c5fcdc29 --- /dev/null +++ b/tests/immutable/erc20/erc20.go @@ -0,0 +1,801 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package erc20 + +import ( + "errors" + "math/big" + "strings" + + 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 +) + +// ERC20MetaData contains all meta data concerning the ERC20 contract. +var ERC20MetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"subtractedValue\",\"type\":\"uint256\"}],\"name\":\"decreaseAllowance\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"addedValue\",\"type\":\"uint256\"}],\"name\":\"increaseAllowance\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + Bin: "0x60806040523480156200001157600080fd5b506040518060400160405280600781526020017f426974436f696e000000000000000000000000000000000000000000000000008152506040518060400160405280600381526020017f425443000000000000000000000000000000000000000000000000000000000081525081600390816200008f9190620004b6565b508060049081620000a19190620004b6565b505050620000bf3368056bc75e2d63100000620000c560201b60201c565b620006b8565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff160362000137576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016200012e90620005fe565b60405180910390fd5b6200014b600083836200023260201b60201c565b80600260008282546200015f91906200064f565b92505081905550806000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040516200021291906200069b565b60405180910390a36200022e600083836200023760201b60201c565b5050565b505050565b505050565b600081519050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b60006002820490506001821680620002be57607f821691505b602082108103620002d457620002d362000276565b5b50919050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b6000600883026200033e7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82620002ff565b6200034a8683620002ff565b95508019841693508086168417925050509392505050565b6000819050919050565b6000819050919050565b600062000397620003916200038b8462000362565b6200036c565b62000362565b9050919050565b6000819050919050565b620003b38362000376565b620003cb620003c2826200039e565b8484546200030c565b825550505050565b600090565b620003e2620003d3565b620003ef818484620003a8565b505050565b5b8181101562000417576200040b600082620003d8565b600181019050620003f5565b5050565b601f82111562000466576200043081620002da565b6200043b84620002ef565b810160208510156200044b578190505b620004636200045a85620002ef565b830182620003f4565b50505b505050565b600082821c905092915050565b60006200048b600019846008026200046b565b1980831691505092915050565b6000620004a6838362000478565b9150826002028217905092915050565b620004c1826200023c565b67ffffffffffffffff811115620004dd57620004dc62000247565b5b620004e98254620002a5565b620004f68282856200041b565b600060209050601f8311600181146200052e576000841562000519578287015190505b62000525858262000498565b86555062000595565b601f1984166200053e86620002da565b60005b82811015620005685784890151825560018201915060208501945060208101905062000541565b8683101562000588578489015162000584601f89168262000478565b8355505b6001600288020188555050505b505050505050565b600082825260208201905092915050565b7f45524332303a206d696e7420746f20746865207a65726f206164647265737300600082015250565b6000620005e6601f836200059d565b9150620005f382620005ae565b602082019050919050565b600060208201905081810360008301526200061981620005d7565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60006200065c8262000362565b9150620006698362000362565b925082820190508082111562000684576200068362000620565b5b92915050565b620006958162000362565b82525050565b6000602082019050620006b260008301846200068a565b92915050565b61122f80620006c86000396000f3fe608060405234801561001057600080fd5b50600436106100a95760003560e01c80633950935111610071578063395093511461016857806370a082311461019857806395d89b41146101c8578063a457c2d7146101e6578063a9059cbb14610216578063dd62ed3e14610246576100a9565b806306fdde03146100ae578063095ea7b3146100cc57806318160ddd146100fc57806323b872dd1461011a578063313ce5671461014a575b600080fd5b6100b6610276565b6040516100c39190610b0c565b60405180910390f35b6100e660048036038101906100e19190610bc7565b610308565b6040516100f39190610c22565b60405180910390f35b61010461032b565b6040516101119190610c4c565b60405180910390f35b610134600480360381019061012f9190610c67565b610335565b6040516101419190610c22565b60405180910390f35b610152610364565b60405161015f9190610cd6565b60405180910390f35b610182600480360381019061017d9190610bc7565b61036d565b60405161018f9190610c22565b60405180910390f35b6101b260048036038101906101ad9190610cf1565b6103a4565b6040516101bf9190610c4c565b60405180910390f35b6101d06103ec565b6040516101dd9190610b0c565b60405180910390f35b61020060048036038101906101fb9190610bc7565b61047e565b60405161020d9190610c22565b60405180910390f35b610230600480360381019061022b9190610bc7565b6104f5565b60405161023d9190610c22565b60405180910390f35b610260600480360381019061025b9190610d1e565b610518565b60405161026d9190610c4c565b60405180910390f35b60606003805461028590610d8d565b80601f01602080910402602001604051908101604052809291908181526020018280546102b190610d8d565b80156102fe5780601f106102d3576101008083540402835291602001916102fe565b820191906000526020600020905b8154815290600101906020018083116102e157829003601f168201915b5050505050905090565b60008061031361059f565b90506103208185856105a7565b600191505092915050565b6000600254905090565b60008061034061059f565b905061034d858285610770565b6103588585856107fc565b60019150509392505050565b60006012905090565b60008061037861059f565b905061039981858561038a8589610518565b6103949190610ded565b6105a7565b600191505092915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b6060600480546103fb90610d8d565b80601f016020809104026020016040519081016040528092919081815260200182805461042790610d8d565b80156104745780601f1061044957610100808354040283529160200191610474565b820191906000526020600020905b81548152906001019060200180831161045757829003601f168201915b5050505050905090565b60008061048961059f565b905060006104978286610518565b9050838110156104dc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104d390610e93565b60405180910390fd5b6104e982868684036105a7565b60019250505092915050565b60008061050061059f565b905061050d8185856107fc565b600191505092915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b600033905090565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610616576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161060d90610f25565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610685576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161067c90610fb7565b60405180910390fd5b80600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925836040516107639190610c4c565b60405180910390a3505050565b600061077c8484610518565b90507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146107f657818110156107e8576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107df90611023565b60405180910390fd5b6107f584848484036105a7565b5b50505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff160361086b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610862906110b5565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036108da576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016108d190611147565b60405180910390fd5b6108e5838383610a72565b60008060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490508181101561096b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610962906111d9565b60405180910390fd5b8181036000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051610a599190610c4c565b60405180910390a3610a6c848484610a77565b50505050565b505050565b505050565b600081519050919050565b600082825260208201905092915050565b60005b83811015610ab6578082015181840152602081019050610a9b565b60008484015250505050565b6000601f19601f8301169050919050565b6000610ade82610a7c565b610ae88185610a87565b9350610af8818560208601610a98565b610b0181610ac2565b840191505092915050565b60006020820190508181036000830152610b268184610ad3565b905092915050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610b5e82610b33565b9050919050565b610b6e81610b53565b8114610b7957600080fd5b50565b600081359050610b8b81610b65565b92915050565b6000819050919050565b610ba481610b91565b8114610baf57600080fd5b50565b600081359050610bc181610b9b565b92915050565b60008060408385031215610bde57610bdd610b2e565b5b6000610bec85828601610b7c565b9250506020610bfd85828601610bb2565b9150509250929050565b60008115159050919050565b610c1c81610c07565b82525050565b6000602082019050610c376000830184610c13565b92915050565b610c4681610b91565b82525050565b6000602082019050610c616000830184610c3d565b92915050565b600080600060608486031215610c8057610c7f610b2e565b5b6000610c8e86828701610b7c565b9350506020610c9f86828701610b7c565b9250506040610cb086828701610bb2565b9150509250925092565b600060ff82169050919050565b610cd081610cba565b82525050565b6000602082019050610ceb6000830184610cc7565b92915050565b600060208284031215610d0757610d06610b2e565b5b6000610d1584828501610b7c565b91505092915050565b60008060408385031215610d3557610d34610b2e565b5b6000610d4385828601610b7c565b9250506020610d5485828601610b7c565b9150509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b60006002820490506001821680610da557607f821691505b602082108103610db857610db7610d5e565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610df882610b91565b9150610e0383610b91565b9250828201905080821115610e1b57610e1a610dbe565b5b92915050565b7f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760008201527f207a65726f000000000000000000000000000000000000000000000000000000602082015250565b6000610e7d602583610a87565b9150610e8882610e21565b604082019050919050565b60006020820190508181036000830152610eac81610e70565b9050919050565b7f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460008201527f7265737300000000000000000000000000000000000000000000000000000000602082015250565b6000610f0f602483610a87565b9150610f1a82610eb3565b604082019050919050565b60006020820190508181036000830152610f3e81610f02565b9050919050565b7f45524332303a20617070726f766520746f20746865207a65726f20616464726560008201527f7373000000000000000000000000000000000000000000000000000000000000602082015250565b6000610fa1602283610a87565b9150610fac82610f45565b604082019050919050565b60006020820190508181036000830152610fd081610f94565b9050919050565b7f45524332303a20696e73756666696369656e7420616c6c6f77616e6365000000600082015250565b600061100d601d83610a87565b915061101882610fd7565b602082019050919050565b6000602082019050818103600083015261103c81611000565b9050919050565b7f45524332303a207472616e736665722066726f6d20746865207a65726f20616460008201527f6472657373000000000000000000000000000000000000000000000000000000602082015250565b600061109f602583610a87565b91506110aa82611043565b604082019050919050565b600060208201905081810360008301526110ce81611092565b9050919050565b7f45524332303a207472616e7366657220746f20746865207a65726f206164647260008201527f6573730000000000000000000000000000000000000000000000000000000000602082015250565b6000611131602383610a87565b915061113c826110d5565b604082019050919050565b6000602082019050818103600083015261116081611124565b9050919050565b7f45524332303a207472616e7366657220616d6f756e742065786365656473206260008201527f616c616e63650000000000000000000000000000000000000000000000000000602082015250565b60006111c3602683610a87565b91506111ce82611167565b604082019050919050565b600060208201905081810360008301526111f2816111b6565b905091905056fea2646970667358221220d1d2931276936d87e4800092e4cc8234ae552eca686bf3dde9b3c7d892d3cc3564736f6c63430008110033", +} + +// ERC20ABI is the input ABI used to generate the binding from. +// Deprecated: Use ERC20MetaData.ABI instead. +var ERC20ABI = ERC20MetaData.ABI + +// ERC20Bin is the compiled bytecode used for deploying new contracts. +// Deprecated: Use ERC20MetaData.Bin instead. +var ERC20Bin = ERC20MetaData.Bin + +// DeployERC20 deploys a new Ethereum contract, binding an instance of ERC20 to it. +func DeployERC20(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *ERC20, error) { + parsed, err := ERC20MetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(ERC20Bin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &ERC20{ERC20Caller: ERC20Caller{contract: contract}, ERC20Transactor: ERC20Transactor{contract: contract}, ERC20Filterer: ERC20Filterer{contract: contract}}, nil +} + +// ERC20 is an auto generated Go binding around an Ethereum contract. +type ERC20 struct { + ERC20Caller // Read-only binding to the contract + ERC20Transactor // Write-only binding to the contract + ERC20Filterer // Log filterer for contract events +} + +// ERC20Caller is an auto generated read-only Go binding around an Ethereum contract. +type ERC20Caller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ERC20Transactor is an auto generated write-only Go binding around an Ethereum contract. +type ERC20Transactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ERC20Filterer is an auto generated log filtering Go binding around an Ethereum contract events. +type ERC20Filterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ERC20Session is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type ERC20Session struct { + Contract *ERC20 // 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 +} + +// ERC20CallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type ERC20CallerSession struct { + Contract *ERC20Caller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// ERC20TransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type ERC20TransactorSession struct { + Contract *ERC20Transactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// ERC20Raw is an auto generated low-level Go binding around an Ethereum contract. +type ERC20Raw struct { + Contract *ERC20 // Generic contract binding to access the raw methods on +} + +// ERC20CallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type ERC20CallerRaw struct { + Contract *ERC20Caller // Generic read-only contract binding to access the raw methods on +} + +// ERC20TransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type ERC20TransactorRaw struct { + Contract *ERC20Transactor // Generic write-only contract binding to access the raw methods on +} + +// NewERC20 creates a new instance of ERC20, bound to a specific deployed contract. +func NewERC20(address common.Address, backend bind.ContractBackend) (*ERC20, error) { + contract, err := bindERC20(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &ERC20{ERC20Caller: ERC20Caller{contract: contract}, ERC20Transactor: ERC20Transactor{contract: contract}, ERC20Filterer: ERC20Filterer{contract: contract}}, nil +} + +// NewERC20Caller creates a new read-only instance of ERC20, bound to a specific deployed contract. +func NewERC20Caller(address common.Address, caller bind.ContractCaller) (*ERC20Caller, error) { + contract, err := bindERC20(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &ERC20Caller{contract: contract}, nil +} + +// NewERC20Transactor creates a new write-only instance of ERC20, bound to a specific deployed contract. +func NewERC20Transactor(address common.Address, transactor bind.ContractTransactor) (*ERC20Transactor, error) { + contract, err := bindERC20(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &ERC20Transactor{contract: contract}, nil +} + +// NewERC20Filterer creates a new log filterer instance of ERC20, bound to a specific deployed contract. +func NewERC20Filterer(address common.Address, filterer bind.ContractFilterer) (*ERC20Filterer, error) { + contract, err := bindERC20(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &ERC20Filterer{contract: contract}, nil +} + +// bindERC20 binds a generic wrapper to an already deployed contract. +func bindERC20(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(ERC20ABI)) + 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 (_ERC20 *ERC20Raw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _ERC20.Contract.ERC20Caller.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 (_ERC20 *ERC20Raw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ERC20.Contract.ERC20Transactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_ERC20 *ERC20Raw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _ERC20.Contract.ERC20Transactor.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 (_ERC20 *ERC20CallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _ERC20.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 (_ERC20 *ERC20TransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ERC20.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_ERC20 *ERC20TransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _ERC20.Contract.contract.Transact(opts, method, params...) +} + +// Allowance is a free data retrieval call binding the contract method 0xdd62ed3e. +// +// Solidity: function allowance(address owner, address spender) view returns(uint256) +func (_ERC20 *ERC20Caller) Allowance(opts *bind.CallOpts, owner common.Address, spender common.Address) (*big.Int, error) { + var out []interface{} + err := _ERC20.contract.Call(opts, &out, "allowance", owner, spender) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// Allowance is a free data retrieval call binding the contract method 0xdd62ed3e. +// +// Solidity: function allowance(address owner, address spender) view returns(uint256) +func (_ERC20 *ERC20Session) Allowance(owner common.Address, spender common.Address) (*big.Int, error) { + return _ERC20.Contract.Allowance(&_ERC20.CallOpts, owner, spender) +} + +// Allowance is a free data retrieval call binding the contract method 0xdd62ed3e. +// +// Solidity: function allowance(address owner, address spender) view returns(uint256) +func (_ERC20 *ERC20CallerSession) Allowance(owner common.Address, spender common.Address) (*big.Int, error) { + return _ERC20.Contract.Allowance(&_ERC20.CallOpts, owner, spender) +} + +// BalanceOf is a free data retrieval call binding the contract method 0x70a08231. +// +// Solidity: function balanceOf(address account) view returns(uint256) +func (_ERC20 *ERC20Caller) BalanceOf(opts *bind.CallOpts, account common.Address) (*big.Int, error) { + var out []interface{} + err := _ERC20.contract.Call(opts, &out, "balanceOf", account) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// BalanceOf is a free data retrieval call binding the contract method 0x70a08231. +// +// Solidity: function balanceOf(address account) view returns(uint256) +func (_ERC20 *ERC20Session) BalanceOf(account common.Address) (*big.Int, error) { + return _ERC20.Contract.BalanceOf(&_ERC20.CallOpts, account) +} + +// BalanceOf is a free data retrieval call binding the contract method 0x70a08231. +// +// Solidity: function balanceOf(address account) view returns(uint256) +func (_ERC20 *ERC20CallerSession) BalanceOf(account common.Address) (*big.Int, error) { + return _ERC20.Contract.BalanceOf(&_ERC20.CallOpts, account) +} + +// Decimals is a free data retrieval call binding the contract method 0x313ce567. +// +// Solidity: function decimals() view returns(uint8) +func (_ERC20 *ERC20Caller) Decimals(opts *bind.CallOpts) (uint8, error) { + var out []interface{} + err := _ERC20.contract.Call(opts, &out, "decimals") + + if err != nil { + return *new(uint8), err + } + + out0 := *abi.ConvertType(out[0], new(uint8)).(*uint8) + + return out0, err + +} + +// Decimals is a free data retrieval call binding the contract method 0x313ce567. +// +// Solidity: function decimals() view returns(uint8) +func (_ERC20 *ERC20Session) Decimals() (uint8, error) { + return _ERC20.Contract.Decimals(&_ERC20.CallOpts) +} + +// Decimals is a free data retrieval call binding the contract method 0x313ce567. +// +// Solidity: function decimals() view returns(uint8) +func (_ERC20 *ERC20CallerSession) Decimals() (uint8, error) { + return _ERC20.Contract.Decimals(&_ERC20.CallOpts) +} + +// Name is a free data retrieval call binding the contract method 0x06fdde03. +// +// Solidity: function name() view returns(string) +func (_ERC20 *ERC20Caller) Name(opts *bind.CallOpts) (string, error) { + var out []interface{} + err := _ERC20.contract.Call(opts, &out, "name") + + if err != nil { + return *new(string), err + } + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + + return out0, err + +} + +// Name is a free data retrieval call binding the contract method 0x06fdde03. +// +// Solidity: function name() view returns(string) +func (_ERC20 *ERC20Session) Name() (string, error) { + return _ERC20.Contract.Name(&_ERC20.CallOpts) +} + +// Name is a free data retrieval call binding the contract method 0x06fdde03. +// +// Solidity: function name() view returns(string) +func (_ERC20 *ERC20CallerSession) Name() (string, error) { + return _ERC20.Contract.Name(&_ERC20.CallOpts) +} + +// Symbol is a free data retrieval call binding the contract method 0x95d89b41. +// +// Solidity: function symbol() view returns(string) +func (_ERC20 *ERC20Caller) Symbol(opts *bind.CallOpts) (string, error) { + var out []interface{} + err := _ERC20.contract.Call(opts, &out, "symbol") + + if err != nil { + return *new(string), err + } + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + + return out0, err + +} + +// Symbol is a free data retrieval call binding the contract method 0x95d89b41. +// +// Solidity: function symbol() view returns(string) +func (_ERC20 *ERC20Session) Symbol() (string, error) { + return _ERC20.Contract.Symbol(&_ERC20.CallOpts) +} + +// Symbol is a free data retrieval call binding the contract method 0x95d89b41. +// +// Solidity: function symbol() view returns(string) +func (_ERC20 *ERC20CallerSession) Symbol() (string, error) { + return _ERC20.Contract.Symbol(&_ERC20.CallOpts) +} + +// TotalSupply is a free data retrieval call binding the contract method 0x18160ddd. +// +// Solidity: function totalSupply() view returns(uint256) +func (_ERC20 *ERC20Caller) TotalSupply(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _ERC20.contract.Call(opts, &out, "totalSupply") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// TotalSupply is a free data retrieval call binding the contract method 0x18160ddd. +// +// Solidity: function totalSupply() view returns(uint256) +func (_ERC20 *ERC20Session) TotalSupply() (*big.Int, error) { + return _ERC20.Contract.TotalSupply(&_ERC20.CallOpts) +} + +// TotalSupply is a free data retrieval call binding the contract method 0x18160ddd. +// +// Solidity: function totalSupply() view returns(uint256) +func (_ERC20 *ERC20CallerSession) TotalSupply() (*big.Int, error) { + return _ERC20.Contract.TotalSupply(&_ERC20.CallOpts) +} + +// Approve is a paid mutator transaction binding the contract method 0x095ea7b3. +// +// Solidity: function approve(address spender, uint256 amount) returns(bool) +func (_ERC20 *ERC20Transactor) Approve(opts *bind.TransactOpts, spender common.Address, amount *big.Int) (*types.Transaction, error) { + return _ERC20.contract.Transact(opts, "approve", spender, amount) +} + +// Approve is a paid mutator transaction binding the contract method 0x095ea7b3. +// +// Solidity: function approve(address spender, uint256 amount) returns(bool) +func (_ERC20 *ERC20Session) Approve(spender common.Address, amount *big.Int) (*types.Transaction, error) { + return _ERC20.Contract.Approve(&_ERC20.TransactOpts, spender, amount) +} + +// Approve is a paid mutator transaction binding the contract method 0x095ea7b3. +// +// Solidity: function approve(address spender, uint256 amount) returns(bool) +func (_ERC20 *ERC20TransactorSession) Approve(spender common.Address, amount *big.Int) (*types.Transaction, error) { + return _ERC20.Contract.Approve(&_ERC20.TransactOpts, spender, amount) +} + +// DecreaseAllowance is a paid mutator transaction binding the contract method 0xa457c2d7. +// +// Solidity: function decreaseAllowance(address spender, uint256 subtractedValue) returns(bool) +func (_ERC20 *ERC20Transactor) DecreaseAllowance(opts *bind.TransactOpts, spender common.Address, subtractedValue *big.Int) (*types.Transaction, error) { + return _ERC20.contract.Transact(opts, "decreaseAllowance", spender, subtractedValue) +} + +// DecreaseAllowance is a paid mutator transaction binding the contract method 0xa457c2d7. +// +// Solidity: function decreaseAllowance(address spender, uint256 subtractedValue) returns(bool) +func (_ERC20 *ERC20Session) DecreaseAllowance(spender common.Address, subtractedValue *big.Int) (*types.Transaction, error) { + return _ERC20.Contract.DecreaseAllowance(&_ERC20.TransactOpts, spender, subtractedValue) +} + +// DecreaseAllowance is a paid mutator transaction binding the contract method 0xa457c2d7. +// +// Solidity: function decreaseAllowance(address spender, uint256 subtractedValue) returns(bool) +func (_ERC20 *ERC20TransactorSession) DecreaseAllowance(spender common.Address, subtractedValue *big.Int) (*types.Transaction, error) { + return _ERC20.Contract.DecreaseAllowance(&_ERC20.TransactOpts, spender, subtractedValue) +} + +// IncreaseAllowance is a paid mutator transaction binding the contract method 0x39509351. +// +// Solidity: function increaseAllowance(address spender, uint256 addedValue) returns(bool) +func (_ERC20 *ERC20Transactor) IncreaseAllowance(opts *bind.TransactOpts, spender common.Address, addedValue *big.Int) (*types.Transaction, error) { + return _ERC20.contract.Transact(opts, "increaseAllowance", spender, addedValue) +} + +// IncreaseAllowance is a paid mutator transaction binding the contract method 0x39509351. +// +// Solidity: function increaseAllowance(address spender, uint256 addedValue) returns(bool) +func (_ERC20 *ERC20Session) IncreaseAllowance(spender common.Address, addedValue *big.Int) (*types.Transaction, error) { + return _ERC20.Contract.IncreaseAllowance(&_ERC20.TransactOpts, spender, addedValue) +} + +// IncreaseAllowance is a paid mutator transaction binding the contract method 0x39509351. +// +// Solidity: function increaseAllowance(address spender, uint256 addedValue) returns(bool) +func (_ERC20 *ERC20TransactorSession) IncreaseAllowance(spender common.Address, addedValue *big.Int) (*types.Transaction, error) { + return _ERC20.Contract.IncreaseAllowance(&_ERC20.TransactOpts, spender, addedValue) +} + +// Transfer is a paid mutator transaction binding the contract method 0xa9059cbb. +// +// Solidity: function transfer(address to, uint256 amount) returns(bool) +func (_ERC20 *ERC20Transactor) Transfer(opts *bind.TransactOpts, to common.Address, amount *big.Int) (*types.Transaction, error) { + return _ERC20.contract.Transact(opts, "transfer", to, amount) +} + +// Transfer is a paid mutator transaction binding the contract method 0xa9059cbb. +// +// Solidity: function transfer(address to, uint256 amount) returns(bool) +func (_ERC20 *ERC20Session) Transfer(to common.Address, amount *big.Int) (*types.Transaction, error) { + return _ERC20.Contract.Transfer(&_ERC20.TransactOpts, to, amount) +} + +// Transfer is a paid mutator transaction binding the contract method 0xa9059cbb. +// +// Solidity: function transfer(address to, uint256 amount) returns(bool) +func (_ERC20 *ERC20TransactorSession) Transfer(to common.Address, amount *big.Int) (*types.Transaction, error) { + return _ERC20.Contract.Transfer(&_ERC20.TransactOpts, to, amount) +} + +// TransferFrom is a paid mutator transaction binding the contract method 0x23b872dd. +// +// Solidity: function transferFrom(address from, address to, uint256 amount) returns(bool) +func (_ERC20 *ERC20Transactor) TransferFrom(opts *bind.TransactOpts, from common.Address, to common.Address, amount *big.Int) (*types.Transaction, error) { + return _ERC20.contract.Transact(opts, "transferFrom", from, to, amount) +} + +// TransferFrom is a paid mutator transaction binding the contract method 0x23b872dd. +// +// Solidity: function transferFrom(address from, address to, uint256 amount) returns(bool) +func (_ERC20 *ERC20Session) TransferFrom(from common.Address, to common.Address, amount *big.Int) (*types.Transaction, error) { + return _ERC20.Contract.TransferFrom(&_ERC20.TransactOpts, from, to, amount) +} + +// TransferFrom is a paid mutator transaction binding the contract method 0x23b872dd. +// +// Solidity: function transferFrom(address from, address to, uint256 amount) returns(bool) +func (_ERC20 *ERC20TransactorSession) TransferFrom(from common.Address, to common.Address, amount *big.Int) (*types.Transaction, error) { + return _ERC20.Contract.TransferFrom(&_ERC20.TransactOpts, from, to, amount) +} + +// ERC20ApprovalIterator is returned from FilterApproval and is used to iterate over the raw logs and unpacked data for Approval events raised by the ERC20 contract. +type ERC20ApprovalIterator struct { + Event *ERC20Approval // 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 *ERC20ApprovalIterator) 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(ERC20Approval) + 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(ERC20Approval) + 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 *ERC20ApprovalIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *ERC20ApprovalIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// ERC20Approval represents a Approval event raised by the ERC20 contract. +type ERC20Approval struct { + Owner common.Address + Spender common.Address + Value *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterApproval is a free log retrieval operation binding the contract event 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925. +// +// Solidity: event Approval(address indexed owner, address indexed spender, uint256 value) +func (_ERC20 *ERC20Filterer) FilterApproval(opts *bind.FilterOpts, owner []common.Address, spender []common.Address) (*ERC20ApprovalIterator, error) { + + var ownerRule []interface{} + for _, ownerItem := range owner { + ownerRule = append(ownerRule, ownerItem) + } + var spenderRule []interface{} + for _, spenderItem := range spender { + spenderRule = append(spenderRule, spenderItem) + } + + logs, sub, err := _ERC20.contract.FilterLogs(opts, "Approval", ownerRule, spenderRule) + if err != nil { + return nil, err + } + return &ERC20ApprovalIterator{contract: _ERC20.contract, event: "Approval", logs: logs, sub: sub}, nil +} + +// WatchApproval is a free log subscription operation binding the contract event 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925. +// +// Solidity: event Approval(address indexed owner, address indexed spender, uint256 value) +func (_ERC20 *ERC20Filterer) WatchApproval(opts *bind.WatchOpts, sink chan<- *ERC20Approval, owner []common.Address, spender []common.Address) (event.Subscription, error) { + + var ownerRule []interface{} + for _, ownerItem := range owner { + ownerRule = append(ownerRule, ownerItem) + } + var spenderRule []interface{} + for _, spenderItem := range spender { + spenderRule = append(spenderRule, spenderItem) + } + + logs, sub, err := _ERC20.contract.WatchLogs(opts, "Approval", ownerRule, spenderRule) + 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(ERC20Approval) + if err := _ERC20.contract.UnpackLog(event, "Approval", 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 +} + +// ParseApproval is a log parse operation binding the contract event 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925. +// +// Solidity: event Approval(address indexed owner, address indexed spender, uint256 value) +func (_ERC20 *ERC20Filterer) ParseApproval(log types.Log) (*ERC20Approval, error) { + event := new(ERC20Approval) + if err := _ERC20.contract.UnpackLog(event, "Approval", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// ERC20TransferIterator is returned from FilterTransfer and is used to iterate over the raw logs and unpacked data for Transfer events raised by the ERC20 contract. +type ERC20TransferIterator struct { + Event *ERC20Transfer // 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 *ERC20TransferIterator) 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(ERC20Transfer) + 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(ERC20Transfer) + 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 *ERC20TransferIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *ERC20TransferIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// ERC20Transfer represents a Transfer event raised by the ERC20 contract. +type ERC20Transfer struct { + From common.Address + To common.Address + Value *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterTransfer is a free log retrieval operation binding the contract event 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef. +// +// Solidity: event Transfer(address indexed from, address indexed to, uint256 value) +func (_ERC20 *ERC20Filterer) FilterTransfer(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*ERC20TransferIterator, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _ERC20.contract.FilterLogs(opts, "Transfer", fromRule, toRule) + if err != nil { + return nil, err + } + return &ERC20TransferIterator{contract: _ERC20.contract, event: "Transfer", logs: logs, sub: sub}, nil +} + +// WatchTransfer is a free log subscription operation binding the contract event 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef. +// +// Solidity: event Transfer(address indexed from, address indexed to, uint256 value) +func (_ERC20 *ERC20Filterer) WatchTransfer(opts *bind.WatchOpts, sink chan<- *ERC20Transfer, from []common.Address, to []common.Address) (event.Subscription, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _ERC20.contract.WatchLogs(opts, "Transfer", fromRule, toRule) + 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(ERC20Transfer) + if err := _ERC20.contract.UnpackLog(event, "Transfer", 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 +} + +// ParseTransfer is a log parse operation binding the contract event 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef. +// +// Solidity: event Transfer(address indexed from, address indexed to, uint256 value) +func (_ERC20 *ERC20Filterer) ParseTransfer(log types.Log) (*ERC20Transfer, error) { + event := new(ERC20Transfer) + if err := _ERC20.contract.UnpackLog(event, "Transfer", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} diff --git a/tests/immutable/erc20_test.go b/tests/immutable/erc20_test.go new file mode 100644 index 000000000..893624d85 --- /dev/null +++ b/tests/immutable/erc20_test.go @@ -0,0 +1,113 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package test + +import ( + "context" + "testing" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/tests/immutable/erc20" + "github.com/ethereum/go-ethereum/tests/immutable/evm" + "github.com/stretchr/testify/require" +) + +func TestImmutableLegacyERC20_CreateAndTransfer(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + + client, err := evm.NewClient(ctx, config.rpcURL, 1, 1) + require.NoError(t, err) + + testERC20(t, ctx, client.EthClient(), legacyTxOpts(t, ctx, client.EthClient(), config.testUser)) +} + +func TestImmutableEIP1559ERC20_CreateAndTransfer(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + + client, err := evm.NewClient(ctx, config.rpcURL, 1, 1) + require.NoError(t, err) + + testERC20(t, ctx, client.EthClient(), EIP1559TxOpts(t, ctx, client.EthClient(), config.testUser)) +} + +func testERC20(t *testing.T, ctx context.Context, client *ethclient.Client, txOpts *bind.TransactOpts) { + t.Helper() + + alice := txOpts.From + bob := common.BigToAddress(common.Big1) + + // Deploy a contract + _, deployTx, coin, err := erc20.DeployERC20(txOpts, client) + require.NoError(t, err) + t.Log("Deploy tx", deployTx.Hash().String()) + deployReceipt, err := bind.WaitMined(ctx, client, deployTx) + require.Equal(t, deployReceipt.Status, uint64(1)) + require.NoError(t, err) + t.Logf("ERC20 Deployed to address %s with TX Hash %s", deployReceipt.ContractAddress.String(), deployTx.Hash().String()) + + // Check total supply non zero + opts := &bind.CallOpts{Context: ctx} + total, err := coin.TotalSupply(opts) + require.NoError(t, err) + require.Greater(t, total.Uint64(), uint64(0)) + + // Get symbol + symbol, err := coin.Symbol(opts) + require.NoError(t, err) + t.Logf("Coin: %s", symbol) + + // Transfer from owner to alice + xferTx, err := coin.Transfer(txOpts, alice, total) + require.NoError(t, err) + receipt, err := bind.WaitMined(ctx, client, xferTx) + require.NoError(t, err) + t.Logf("Transfer complete with TX Hash: %s, Block: %d", receipt.TxHash.String(), receipt.BlockNumber.Uint64()) + + // Check balance + bal, err := coin.BalanceOf(opts, alice) + require.NoError(t, err) + require.Greater(t, bal.Uint64(), uint64(0)) + + // Approve alice + approveTx, err := coin.Approve(txOpts, alice, total) + require.NoError(t, err) + receipt, err = bind.WaitMined(ctx, client, approveTx) + require.NotNil(t, receipt) + t.Logf("Approval complete with TX Hash: %s, Block: %d", receipt.TxHash.String(), receipt.BlockNumber.Uint64()) + require.NoError(t, err) + half := total.Div(total, common.Big2) + + // Transfer from alice to bob + xferTx, err = coin.TransferFrom(txOpts, alice, bob, half) + require.NoError(t, err) + receipt, err = bind.WaitMined(ctx, client, xferTx) + require.NoError(t, err) + t.Logf("Transfer complete with TX Hash: %s, Block: %d", receipt.TxHash.String(), receipt.BlockNumber.Uint64()) + + // Check balances after transfer + balAlice, err := coin.BalanceOf(opts, alice) + require.NoError(t, err) + require.LessOrEqual(t, balAlice.Uint64(), half.Uint64()) + balBob, err := coin.BalanceOf(opts, bob) + require.NoError(t, err) + require.Equal(t, half.Uint64(), balBob.Uint64()) +} diff --git a/tests/immutable/erc721/abi.json b/tests/immutable/erc721/abi.json new file mode 100644 index 000000000..29aebeeda --- /dev/null +++ b/tests/immutable/erc721/abi.json @@ -0,0 +1,366 @@ +[ + { + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "symbol", + "type": "string" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "approved", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "ApprovalForAll", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "getApproved", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "isApprovedForAll", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "collector", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "mintNFT", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "ownerOf", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "setApprovalForAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "tokenURI", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/tests/immutable/erc721/bytecode.bin b/tests/immutable/erc721/bytecode.bin new file mode 100644 index 000000000..3718c5edc --- /dev/null +++ b/tests/immutable/erc721/bytecode.bin @@ -0,0 +1 @@ +0x60806040523480156200001157600080fd5b5060405162002aa438038062002aa4833981810160405281019062000037919062000197565b818181600090805190602001906200005192919062000075565b5080600190805190602001906200006a92919062000075565b50505050506200037a565b82805462000083906200029f565b90600052602060002090601f016020900481019282620000a75760008555620000f3565b82601f10620000c257805160ff1916838001178555620000f3565b82800160010185558215620000f3579182015b82811115620000f2578251825591602001919060010190620000d5565b5b50905062000102919062000106565b5090565b5b808211156200012157600081600090555060010162000107565b5090565b60006200013c620001368462000233565b6200020a565b9050828152602081018484840111156200015557600080fd5b6200016284828562000269565b509392505050565b600082601f8301126200017c57600080fd5b81516200018e84826020860162000125565b91505092915050565b60008060408385031215620001ab57600080fd5b600083015167ffffffffffffffff811115620001c657600080fd5b620001d4858286016200016a565b925050602083015167ffffffffffffffff811115620001f257600080fd5b62000200858286016200016a565b9150509250929050565b60006200021662000229565b9050620002248282620002d5565b919050565b6000604051905090565b600067ffffffffffffffff8211156200025157620002506200033a565b5b6200025c8262000369565b9050602081019050919050565b60005b83811015620002895780820151818401526020810190506200026c565b8381111562000299576000848401525b50505050565b60006002820490506001821680620002b857607f821691505b60208210811415620002cf57620002ce6200030b565b5b50919050565b620002e08262000369565b810181811067ffffffffffffffff821117156200030257620003016200033a565b5b80604052505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000601f19601f8301169050919050565b61271a806200038a6000396000f3fe608060405234801561001057600080fd5b50600436106100ea5760003560e01c80636352211e1161008c578063a22cb46511610066578063a22cb4651461025b578063b88d4fde14610277578063c87b56dd14610293578063e985e9c5146102c3576100ea565b80636352211e146101dd57806370a082311461020d57806395d89b411461023d576100ea565b8063095ea7b3116100c8578063095ea7b31461016d57806323b872dd146101895780633c168eab146101a557806342842e0e146101c1576100ea565b806301ffc9a7146100ef57806306fdde031461011f578063081812fc1461013d575b600080fd5b61010960048036038101906101049190611bd2565b6102f3565b6040516101169190611f29565b60405180910390f35b6101276103d5565b6040516101349190611f44565b60405180910390f35b61015760048036038101906101529190611c24565b610467565b6040516101649190611ec2565b60405180910390f35b61018760048036038101906101829190611b96565b6104ad565b005b6101a3600480360381019061019e9190611a90565b6105c5565b005b6101bf60048036038101906101ba9190611b96565b610625565b005b6101db60048036038101906101d69190611a90565b610633565b005b6101f760048036038101906101f29190611c24565b610653565b6040516102049190611ec2565b60405180910390f35b61022760048036038101906102229190611a2b565b6106da565b60405161023491906120c6565b60405180910390f35b610245610792565b6040516102529190611f44565b60405180910390f35b61027560048036038101906102709190611b5a565b610824565b005b610291600480360381019061028c9190611adf565b61083a565b005b6102ad60048036038101906102a89190611c24565b61089c565b6040516102ba9190611f44565b60405180910390f35b6102dd60048036038101906102d89190611a54565b610904565b6040516102ea9190611f29565b60405180910390f35b60007f80ac58cd000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614806103be57507f5b5e139f000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b806103ce57506103cd82610998565b5b9050919050565b6060600080546103e4906122ba565b80601f0160208091040260200160405190810160405280929190818152602001828054610410906122ba565b801561045d5780601f106104325761010080835404028352916020019161045d565b820191906000526020600020905b81548152906001019060200180831161044057829003601f168201915b5050505050905090565b600061047282610a02565b6004600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050919050565b60006104b882610653565b90508073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415610529576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161052090612086565b60405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff16610548610a4d565b73ffffffffffffffffffffffffffffffffffffffff161480610577575061057681610571610a4d565b610904565b5b6105b6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105ad906120a6565b60405180910390fd5b6105c08383610a55565b505050565b6105d66105d0610a4d565b82610b0e565b610615576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161060c90611f66565b60405180910390fd5b610620838383610ba3565b505050565b61062f8282610e9d565b5050565b61064e8383836040518060200160405280600081525061083a565b505050565b60008061065f836110bb565b9050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614156106d1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c890612066565b60405180910390fd5b80915050919050565b60008073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141561074b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161074290612026565b60405180910390fd5b600360008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b6060600180546107a1906122ba565b80601f01602080910402602001604051908101604052809291908181526020018280546107cd906122ba565b801561081a5780601f106107ef5761010080835404028352916020019161081a565b820191906000526020600020905b8154815290600101906020018083116107fd57829003601f168201915b5050505050905090565b61083661082f610a4d565b83836110f8565b5050565b61084b610845610a4d565b83610b0e565b61088a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161088190611f66565b60405180910390fd5b61089684848484611265565b50505050565b60606108a782610a02565b60006108b16112c1565b905060008151116108d157604051806020016040528060008152506108fc565b806108db846112d8565b6040516020016108ec929190611e9e565b6040516020818303038152906040525b915050919050565b6000600560008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16905092915050565b60007f01ffc9a7000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149050919050565b610a0b816113fc565b610a4a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a4190612066565b60405180910390fd5b50565b600033905090565b816004600083815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550808273ffffffffffffffffffffffffffffffffffffffff16610ac883610653565b73ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560405160405180910390a45050565b600080610b1a83610653565b90508073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161480610b5c5750610b5b8185610904565b5b80610b9a57508373ffffffffffffffffffffffffffffffffffffffff16610b8284610467565b73ffffffffffffffffffffffffffffffffffffffff16145b91505092915050565b8273ffffffffffffffffffffffffffffffffffffffff16610bc382610653565b73ffffffffffffffffffffffffffffffffffffffff1614610c19576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610c1090611fa6565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415610c89576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610c8090611fe6565b60405180910390fd5b610c96838383600161143d565b8273ffffffffffffffffffffffffffffffffffffffff16610cb682610653565b73ffffffffffffffffffffffffffffffffffffffff1614610d0c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610d0390611fa6565b60405180910390fd5b6004600082815260200190815260200160002060006101000a81549073ffffffffffffffffffffffffffffffffffffffff02191690556001600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055506001600360008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540192505081905550816002600083815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550808273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60405160405180910390a4610e988383836001611563565b505050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415610f0d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610f0490612046565b60405180910390fd5b610f16816113fc565b15610f56576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610f4d90611fc6565b60405180910390fd5b610f6460008383600161143d565b610f6d816113fc565b15610fad576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610fa490611fc6565b60405180910390fd5b6001600360008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540192505081905550816002600083815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550808273ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60405160405180910390a46110b7600083836001611563565b5050565b60006002600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050919050565b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415611167576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161115e90612006565b60405180910390fd5b80600560008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff0219169083151502179055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31836040516112589190611f29565b60405180910390a3505050565b611270848484610ba3565b61127c84848484611569565b6112bb576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016112b290611f86565b60405180910390fd5b50505050565b606060405180602001604052806000815250905090565b6060600060016112e784611700565b01905060008167ffffffffffffffff81111561132c577f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040519080825280601f01601f19166020018201604052801561135e5781602001600182028036833780820191505090505b509050600082602001820190505b6001156113f1578080600190039150507f3031323334353637383961626364656600000000000000000000000000000000600a86061a8153600a85816113db577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b04945060008514156113ec576113f1565b61136c565b819350505050919050565b60008073ffffffffffffffffffffffffffffffffffffffff1661141e836110bb565b73ffffffffffffffffffffffffffffffffffffffff1614159050919050565b600181111561155d57600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16146114d15780600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546114c991906121d0565b925050819055505b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161461155c5780600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254611554919061217a565b925050819055505b5b50505050565b50505050565b600061158a8473ffffffffffffffffffffffffffffffffffffffff16611937565b156116f3578373ffffffffffffffffffffffffffffffffffffffff1663150b7a026115b3610a4d565b8786866040518563ffffffff1660e01b81526004016115d59493929190611edd565b602060405180830381600087803b1580156115ef57600080fd5b505af192505050801561162057506040513d601f19601f8201168201806040525081019061161d9190611bfb565b60015b6116a3573d8060008114611650576040519150601f19603f3d011682016040523d82523d6000602084013e611655565b606091505b5060008151141561169b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161169290611f86565b60405180910390fd5b805181602001fd5b63150b7a0260e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149150506116f8565b600190505b949350505050565b600080600090507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008310611784577a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000838161177a577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b0492506040810190505b6d04ee2d6d415b85acef810000000083106117e7576d04ee2d6d415b85acef810000000083816117dd577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b0492506020810190505b662386f26fc10000831061183c57662386f26fc100008381611832577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b0492506010810190505b6305f5e100831061188b576305f5e1008381611881577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b0492506008810190505b61271083106118d65761271083816118cc577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b0492506004810190505b6064831061191f5760648381611915577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b0492506002810190505b600a831061192e576001810190505b80915050919050565b6000808273ffffffffffffffffffffffffffffffffffffffff163b119050919050565b600061196d61196884612106565b6120e1565b90508281526020810184848401111561198557600080fd5b611990848285612278565b509392505050565b6000813590506119a781612688565b92915050565b6000813590506119bc8161269f565b92915050565b6000813590506119d1816126b6565b92915050565b6000815190506119e6816126b6565b92915050565b600082601f8301126119fd57600080fd5b8135611a0d84826020860161195a565b91505092915050565b600081359050611a25816126cd565b92915050565b600060208284031215611a3d57600080fd5b6000611a4b84828501611998565b91505092915050565b60008060408385031215611a6757600080fd5b6000611a7585828601611998565b9250506020611a8685828601611998565b9150509250929050565b600080600060608486031215611aa557600080fd5b6000611ab386828701611998565b9350506020611ac486828701611998565b9250506040611ad586828701611a16565b9150509250925092565b60008060008060808587031215611af557600080fd5b6000611b0387828801611998565b9450506020611b1487828801611998565b9350506040611b2587828801611a16565b925050606085013567ffffffffffffffff811115611b4257600080fd5b611b4e878288016119ec565b91505092959194509250565b60008060408385031215611b6d57600080fd5b6000611b7b85828601611998565b9250506020611b8c858286016119ad565b9150509250929050565b60008060408385031215611ba957600080fd5b6000611bb785828601611998565b9250506020611bc885828601611a16565b9150509250929050565b600060208284031215611be457600080fd5b6000611bf2848285016119c2565b91505092915050565b600060208284031215611c0d57600080fd5b6000611c1b848285016119d7565b91505092915050565b600060208284031215611c3657600080fd5b6000611c4484828501611a16565b91505092915050565b611c5681612204565b82525050565b611c6581612216565b82525050565b6000611c7682612137565b611c80818561214d565b9350611c90818560208601612287565b611c99816123aa565b840191505092915050565b6000611caf82612142565b611cb9818561215e565b9350611cc9818560208601612287565b611cd2816123aa565b840191505092915050565b6000611ce882612142565b611cf2818561216f565b9350611d02818560208601612287565b80840191505092915050565b6000611d1b602d8361215e565b9150611d26826123bb565b604082019050919050565b6000611d3e60328361215e565b9150611d498261240a565b604082019050919050565b6000611d6160258361215e565b9150611d6c82612459565b604082019050919050565b6000611d84601c8361215e565b9150611d8f826124a8565b602082019050919050565b6000611da760248361215e565b9150611db2826124d1565b604082019050919050565b6000611dca60198361215e565b9150611dd582612520565b602082019050919050565b6000611ded60298361215e565b9150611df882612549565b604082019050919050565b6000611e1060208361215e565b9150611e1b82612598565b602082019050919050565b6000611e3360188361215e565b9150611e3e826125c1565b602082019050919050565b6000611e5660218361215e565b9150611e61826125ea565b604082019050919050565b6000611e79603d8361215e565b9150611e8482612639565b604082019050919050565b611e988161226e565b82525050565b6000611eaa8285611cdd565b9150611eb68284611cdd565b91508190509392505050565b6000602082019050611ed76000830184611c4d565b92915050565b6000608082019050611ef26000830187611c4d565b611eff6020830186611c4d565b611f0c6040830185611e8f565b8181036060830152611f1e8184611c6b565b905095945050505050565b6000602082019050611f3e6000830184611c5c565b92915050565b60006020820190508181036000830152611f5e8184611ca4565b905092915050565b60006020820190508181036000830152611f7f81611d0e565b9050919050565b60006020820190508181036000830152611f9f81611d31565b9050919050565b60006020820190508181036000830152611fbf81611d54565b9050919050565b60006020820190508181036000830152611fdf81611d77565b9050919050565b60006020820190508181036000830152611fff81611d9a565b9050919050565b6000602082019050818103600083015261201f81611dbd565b9050919050565b6000602082019050818103600083015261203f81611de0565b9050919050565b6000602082019050818103600083015261205f81611e03565b9050919050565b6000602082019050818103600083015261207f81611e26565b9050919050565b6000602082019050818103600083015261209f81611e49565b9050919050565b600060208201905081810360008301526120bf81611e6c565b9050919050565b60006020820190506120db6000830184611e8f565b92915050565b60006120eb6120fc565b90506120f782826122ec565b919050565b6000604051905090565b600067ffffffffffffffff8211156121215761212061237b565b5b61212a826123aa565b9050602081019050919050565b600081519050919050565b600081519050919050565b600082825260208201905092915050565b600082825260208201905092915050565b600081905092915050565b60006121858261226e565b91506121908361226e565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156121c5576121c461231d565b5b828201905092915050565b60006121db8261226e565b91506121e68361226e565b9250828210156121f9576121f861231d565b5b828203905092915050565b600061220f8261224e565b9050919050565b60008115159050919050565b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b82818337600083830152505050565b60005b838110156122a557808201518184015260208101905061228a565b838111156122b4576000848401525b50505050565b600060028204905060018216806122d257607f821691505b602082108114156122e6576122e561234c565b5b50919050565b6122f5826123aa565b810181811067ffffffffffffffff821117156123145761231361237b565b5b80604052505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000601f19601f8301169050919050565b7f4552433732313a2063616c6c6572206973206e6f7420746f6b656e206f776e6560008201527f72206f7220617070726f76656400000000000000000000000000000000000000602082015250565b7f4552433732313a207472616e7366657220746f206e6f6e20455243373231526560008201527f63656976657220696d706c656d656e7465720000000000000000000000000000602082015250565b7f4552433732313a207472616e736665722066726f6d20696e636f72726563742060008201527f6f776e6572000000000000000000000000000000000000000000000000000000602082015250565b7f4552433732313a20746f6b656e20616c7265616479206d696e74656400000000600082015250565b7f4552433732313a207472616e7366657220746f20746865207a65726f2061646460008201527f7265737300000000000000000000000000000000000000000000000000000000602082015250565b7f4552433732313a20617070726f766520746f2063616c6c657200000000000000600082015250565b7f4552433732313a2061646472657373207a65726f206973206e6f74206120766160008201527f6c6964206f776e65720000000000000000000000000000000000000000000000602082015250565b7f4552433732313a206d696e7420746f20746865207a65726f2061646472657373600082015250565b7f4552433732313a20696e76616c696420746f6b656e2049440000000000000000600082015250565b7f4552433732313a20617070726f76616c20746f2063757272656e74206f776e6560008201527f7200000000000000000000000000000000000000000000000000000000000000602082015250565b7f4552433732313a20617070726f76652063616c6c6572206973206e6f7420746f60008201527f6b656e206f776e6572206f7220617070726f76656420666f7220616c6c000000602082015250565b61269181612204565b811461269c57600080fd5b50565b6126a881612216565b81146126b357600080fd5b50565b6126bf81612222565b81146126ca57600080fd5b50565b6126d68161226e565b81146126e157600080fd5b5056fea2646970667358221220471c9b7b7857e2dd612257c8e7e0f6ab9f6e99964e596f33dcb4b622892b3d7e64736f6c63430008040033 \ No newline at end of file diff --git a/tests/immutable/erc721/erc721.go b/tests/immutable/erc721/erc721.go new file mode 100644 index 000000000..264d24dd8 --- /dev/null +++ b/tests/immutable/erc721/erc721.go @@ -0,0 +1,1055 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package erc721 + +import ( + "errors" + "math/big" + "strings" + + 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 +) + +// ERC721MetaData contains all meta data concerning the ERC721 contract. +var ERC721MetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"symbol\",\"type\":\"string\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"approved\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"approved\",\"type\":\"bool\"}],\"name\":\"ApprovalForAll\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"}],\"name\":\"getApproved\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"}],\"name\":\"isApprovedForAll\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"collector\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"}],\"name\":\"mintNFT\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"}],\"name\":\"ownerOf\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"}],\"name\":\"safeTransferFrom\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"safeTransferFrom\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"approved\",\"type\":\"bool\"}],\"name\":\"setApprovalForAll\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"}],\"name\":\"tokenURI\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + Bin: "0x60806040523480156200001157600080fd5b5060405162002aa438038062002aa4833981810160405281019062000037919062000197565b818181600090805190602001906200005192919062000075565b5080600190805190602001906200006a92919062000075565b50505050506200037a565b82805462000083906200029f565b90600052602060002090601f016020900481019282620000a75760008555620000f3565b82601f10620000c257805160ff1916838001178555620000f3565b82800160010185558215620000f3579182015b82811115620000f2578251825591602001919060010190620000d5565b5b50905062000102919062000106565b5090565b5b808211156200012157600081600090555060010162000107565b5090565b60006200013c620001368462000233565b6200020a565b9050828152602081018484840111156200015557600080fd5b6200016284828562000269565b509392505050565b600082601f8301126200017c57600080fd5b81516200018e84826020860162000125565b91505092915050565b60008060408385031215620001ab57600080fd5b600083015167ffffffffffffffff811115620001c657600080fd5b620001d4858286016200016a565b925050602083015167ffffffffffffffff811115620001f257600080fd5b62000200858286016200016a565b9150509250929050565b60006200021662000229565b9050620002248282620002d5565b919050565b6000604051905090565b600067ffffffffffffffff8211156200025157620002506200033a565b5b6200025c8262000369565b9050602081019050919050565b60005b83811015620002895780820151818401526020810190506200026c565b8381111562000299576000848401525b50505050565b60006002820490506001821680620002b857607f821691505b60208210811415620002cf57620002ce6200030b565b5b50919050565b620002e08262000369565b810181811067ffffffffffffffff821117156200030257620003016200033a565b5b80604052505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000601f19601f8301169050919050565b61271a806200038a6000396000f3fe608060405234801561001057600080fd5b50600436106100ea5760003560e01c80636352211e1161008c578063a22cb46511610066578063a22cb4651461025b578063b88d4fde14610277578063c87b56dd14610293578063e985e9c5146102c3576100ea565b80636352211e146101dd57806370a082311461020d57806395d89b411461023d576100ea565b8063095ea7b3116100c8578063095ea7b31461016d57806323b872dd146101895780633c168eab146101a557806342842e0e146101c1576100ea565b806301ffc9a7146100ef57806306fdde031461011f578063081812fc1461013d575b600080fd5b61010960048036038101906101049190611bd2565b6102f3565b6040516101169190611f29565b60405180910390f35b6101276103d5565b6040516101349190611f44565b60405180910390f35b61015760048036038101906101529190611c24565b610467565b6040516101649190611ec2565b60405180910390f35b61018760048036038101906101829190611b96565b6104ad565b005b6101a3600480360381019061019e9190611a90565b6105c5565b005b6101bf60048036038101906101ba9190611b96565b610625565b005b6101db60048036038101906101d69190611a90565b610633565b005b6101f760048036038101906101f29190611c24565b610653565b6040516102049190611ec2565b60405180910390f35b61022760048036038101906102229190611a2b565b6106da565b60405161023491906120c6565b60405180910390f35b610245610792565b6040516102529190611f44565b60405180910390f35b61027560048036038101906102709190611b5a565b610824565b005b610291600480360381019061028c9190611adf565b61083a565b005b6102ad60048036038101906102a89190611c24565b61089c565b6040516102ba9190611f44565b60405180910390f35b6102dd60048036038101906102d89190611a54565b610904565b6040516102ea9190611f29565b60405180910390f35b60007f80ac58cd000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614806103be57507f5b5e139f000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b806103ce57506103cd82610998565b5b9050919050565b6060600080546103e4906122ba565b80601f0160208091040260200160405190810160405280929190818152602001828054610410906122ba565b801561045d5780601f106104325761010080835404028352916020019161045d565b820191906000526020600020905b81548152906001019060200180831161044057829003601f168201915b5050505050905090565b600061047282610a02565b6004600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050919050565b60006104b882610653565b90508073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415610529576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161052090612086565b60405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff16610548610a4d565b73ffffffffffffffffffffffffffffffffffffffff161480610577575061057681610571610a4d565b610904565b5b6105b6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105ad906120a6565b60405180910390fd5b6105c08383610a55565b505050565b6105d66105d0610a4d565b82610b0e565b610615576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161060c90611f66565b60405180910390fd5b610620838383610ba3565b505050565b61062f8282610e9d565b5050565b61064e8383836040518060200160405280600081525061083a565b505050565b60008061065f836110bb565b9050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614156106d1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c890612066565b60405180910390fd5b80915050919050565b60008073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141561074b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161074290612026565b60405180910390fd5b600360008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b6060600180546107a1906122ba565b80601f01602080910402602001604051908101604052809291908181526020018280546107cd906122ba565b801561081a5780601f106107ef5761010080835404028352916020019161081a565b820191906000526020600020905b8154815290600101906020018083116107fd57829003601f168201915b5050505050905090565b61083661082f610a4d565b83836110f8565b5050565b61084b610845610a4d565b83610b0e565b61088a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161088190611f66565b60405180910390fd5b61089684848484611265565b50505050565b60606108a782610a02565b60006108b16112c1565b905060008151116108d157604051806020016040528060008152506108fc565b806108db846112d8565b6040516020016108ec929190611e9e565b6040516020818303038152906040525b915050919050565b6000600560008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16905092915050565b60007f01ffc9a7000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149050919050565b610a0b816113fc565b610a4a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a4190612066565b60405180910390fd5b50565b600033905090565b816004600083815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550808273ffffffffffffffffffffffffffffffffffffffff16610ac883610653565b73ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560405160405180910390a45050565b600080610b1a83610653565b90508073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161480610b5c5750610b5b8185610904565b5b80610b9a57508373ffffffffffffffffffffffffffffffffffffffff16610b8284610467565b73ffffffffffffffffffffffffffffffffffffffff16145b91505092915050565b8273ffffffffffffffffffffffffffffffffffffffff16610bc382610653565b73ffffffffffffffffffffffffffffffffffffffff1614610c19576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610c1090611fa6565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415610c89576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610c8090611fe6565b60405180910390fd5b610c96838383600161143d565b8273ffffffffffffffffffffffffffffffffffffffff16610cb682610653565b73ffffffffffffffffffffffffffffffffffffffff1614610d0c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610d0390611fa6565b60405180910390fd5b6004600082815260200190815260200160002060006101000a81549073ffffffffffffffffffffffffffffffffffffffff02191690556001600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055506001600360008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540192505081905550816002600083815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550808273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60405160405180910390a4610e988383836001611563565b505050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415610f0d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610f0490612046565b60405180910390fd5b610f16816113fc565b15610f56576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610f4d90611fc6565b60405180910390fd5b610f6460008383600161143d565b610f6d816113fc565b15610fad576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610fa490611fc6565b60405180910390fd5b6001600360008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540192505081905550816002600083815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550808273ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60405160405180910390a46110b7600083836001611563565b5050565b60006002600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050919050565b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415611167576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161115e90612006565b60405180910390fd5b80600560008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff0219169083151502179055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31836040516112589190611f29565b60405180910390a3505050565b611270848484610ba3565b61127c84848484611569565b6112bb576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016112b290611f86565b60405180910390fd5b50505050565b606060405180602001604052806000815250905090565b6060600060016112e784611700565b01905060008167ffffffffffffffff81111561132c577f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040519080825280601f01601f19166020018201604052801561135e5781602001600182028036833780820191505090505b509050600082602001820190505b6001156113f1578080600190039150507f3031323334353637383961626364656600000000000000000000000000000000600a86061a8153600a85816113db577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b04945060008514156113ec576113f1565b61136c565b819350505050919050565b60008073ffffffffffffffffffffffffffffffffffffffff1661141e836110bb565b73ffffffffffffffffffffffffffffffffffffffff1614159050919050565b600181111561155d57600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16146114d15780600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546114c991906121d0565b925050819055505b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161461155c5780600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254611554919061217a565b925050819055505b5b50505050565b50505050565b600061158a8473ffffffffffffffffffffffffffffffffffffffff16611937565b156116f3578373ffffffffffffffffffffffffffffffffffffffff1663150b7a026115b3610a4d565b8786866040518563ffffffff1660e01b81526004016115d59493929190611edd565b602060405180830381600087803b1580156115ef57600080fd5b505af192505050801561162057506040513d601f19601f8201168201806040525081019061161d9190611bfb565b60015b6116a3573d8060008114611650576040519150601f19603f3d011682016040523d82523d6000602084013e611655565b606091505b5060008151141561169b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161169290611f86565b60405180910390fd5b805181602001fd5b63150b7a0260e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149150506116f8565b600190505b949350505050565b600080600090507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008310611784577a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000838161177a577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b0492506040810190505b6d04ee2d6d415b85acef810000000083106117e7576d04ee2d6d415b85acef810000000083816117dd577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b0492506020810190505b662386f26fc10000831061183c57662386f26fc100008381611832577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b0492506010810190505b6305f5e100831061188b576305f5e1008381611881577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b0492506008810190505b61271083106118d65761271083816118cc577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b0492506004810190505b6064831061191f5760648381611915577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b0492506002810190505b600a831061192e576001810190505b80915050919050565b6000808273ffffffffffffffffffffffffffffffffffffffff163b119050919050565b600061196d61196884612106565b6120e1565b90508281526020810184848401111561198557600080fd5b611990848285612278565b509392505050565b6000813590506119a781612688565b92915050565b6000813590506119bc8161269f565b92915050565b6000813590506119d1816126b6565b92915050565b6000815190506119e6816126b6565b92915050565b600082601f8301126119fd57600080fd5b8135611a0d84826020860161195a565b91505092915050565b600081359050611a25816126cd565b92915050565b600060208284031215611a3d57600080fd5b6000611a4b84828501611998565b91505092915050565b60008060408385031215611a6757600080fd5b6000611a7585828601611998565b9250506020611a8685828601611998565b9150509250929050565b600080600060608486031215611aa557600080fd5b6000611ab386828701611998565b9350506020611ac486828701611998565b9250506040611ad586828701611a16565b9150509250925092565b60008060008060808587031215611af557600080fd5b6000611b0387828801611998565b9450506020611b1487828801611998565b9350506040611b2587828801611a16565b925050606085013567ffffffffffffffff811115611b4257600080fd5b611b4e878288016119ec565b91505092959194509250565b60008060408385031215611b6d57600080fd5b6000611b7b85828601611998565b9250506020611b8c858286016119ad565b9150509250929050565b60008060408385031215611ba957600080fd5b6000611bb785828601611998565b9250506020611bc885828601611a16565b9150509250929050565b600060208284031215611be457600080fd5b6000611bf2848285016119c2565b91505092915050565b600060208284031215611c0d57600080fd5b6000611c1b848285016119d7565b91505092915050565b600060208284031215611c3657600080fd5b6000611c4484828501611a16565b91505092915050565b611c5681612204565b82525050565b611c6581612216565b82525050565b6000611c7682612137565b611c80818561214d565b9350611c90818560208601612287565b611c99816123aa565b840191505092915050565b6000611caf82612142565b611cb9818561215e565b9350611cc9818560208601612287565b611cd2816123aa565b840191505092915050565b6000611ce882612142565b611cf2818561216f565b9350611d02818560208601612287565b80840191505092915050565b6000611d1b602d8361215e565b9150611d26826123bb565b604082019050919050565b6000611d3e60328361215e565b9150611d498261240a565b604082019050919050565b6000611d6160258361215e565b9150611d6c82612459565b604082019050919050565b6000611d84601c8361215e565b9150611d8f826124a8565b602082019050919050565b6000611da760248361215e565b9150611db2826124d1565b604082019050919050565b6000611dca60198361215e565b9150611dd582612520565b602082019050919050565b6000611ded60298361215e565b9150611df882612549565b604082019050919050565b6000611e1060208361215e565b9150611e1b82612598565b602082019050919050565b6000611e3360188361215e565b9150611e3e826125c1565b602082019050919050565b6000611e5660218361215e565b9150611e61826125ea565b604082019050919050565b6000611e79603d8361215e565b9150611e8482612639565b604082019050919050565b611e988161226e565b82525050565b6000611eaa8285611cdd565b9150611eb68284611cdd565b91508190509392505050565b6000602082019050611ed76000830184611c4d565b92915050565b6000608082019050611ef26000830187611c4d565b611eff6020830186611c4d565b611f0c6040830185611e8f565b8181036060830152611f1e8184611c6b565b905095945050505050565b6000602082019050611f3e6000830184611c5c565b92915050565b60006020820190508181036000830152611f5e8184611ca4565b905092915050565b60006020820190508181036000830152611f7f81611d0e565b9050919050565b60006020820190508181036000830152611f9f81611d31565b9050919050565b60006020820190508181036000830152611fbf81611d54565b9050919050565b60006020820190508181036000830152611fdf81611d77565b9050919050565b60006020820190508181036000830152611fff81611d9a565b9050919050565b6000602082019050818103600083015261201f81611dbd565b9050919050565b6000602082019050818103600083015261203f81611de0565b9050919050565b6000602082019050818103600083015261205f81611e03565b9050919050565b6000602082019050818103600083015261207f81611e26565b9050919050565b6000602082019050818103600083015261209f81611e49565b9050919050565b600060208201905081810360008301526120bf81611e6c565b9050919050565b60006020820190506120db6000830184611e8f565b92915050565b60006120eb6120fc565b90506120f782826122ec565b919050565b6000604051905090565b600067ffffffffffffffff8211156121215761212061237b565b5b61212a826123aa565b9050602081019050919050565b600081519050919050565b600081519050919050565b600082825260208201905092915050565b600082825260208201905092915050565b600081905092915050565b60006121858261226e565b91506121908361226e565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156121c5576121c461231d565b5b828201905092915050565b60006121db8261226e565b91506121e68361226e565b9250828210156121f9576121f861231d565b5b828203905092915050565b600061220f8261224e565b9050919050565b60008115159050919050565b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b82818337600083830152505050565b60005b838110156122a557808201518184015260208101905061228a565b838111156122b4576000848401525b50505050565b600060028204905060018216806122d257607f821691505b602082108114156122e6576122e561234c565b5b50919050565b6122f5826123aa565b810181811067ffffffffffffffff821117156123145761231361237b565b5b80604052505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000601f19601f8301169050919050565b7f4552433732313a2063616c6c6572206973206e6f7420746f6b656e206f776e6560008201527f72206f7220617070726f76656400000000000000000000000000000000000000602082015250565b7f4552433732313a207472616e7366657220746f206e6f6e20455243373231526560008201527f63656976657220696d706c656d656e7465720000000000000000000000000000602082015250565b7f4552433732313a207472616e736665722066726f6d20696e636f72726563742060008201527f6f776e6572000000000000000000000000000000000000000000000000000000602082015250565b7f4552433732313a20746f6b656e20616c7265616479206d696e74656400000000600082015250565b7f4552433732313a207472616e7366657220746f20746865207a65726f2061646460008201527f7265737300000000000000000000000000000000000000000000000000000000602082015250565b7f4552433732313a20617070726f766520746f2063616c6c657200000000000000600082015250565b7f4552433732313a2061646472657373207a65726f206973206e6f74206120766160008201527f6c6964206f776e65720000000000000000000000000000000000000000000000602082015250565b7f4552433732313a206d696e7420746f20746865207a65726f2061646472657373600082015250565b7f4552433732313a20696e76616c696420746f6b656e2049440000000000000000600082015250565b7f4552433732313a20617070726f76616c20746f2063757272656e74206f776e6560008201527f7200000000000000000000000000000000000000000000000000000000000000602082015250565b7f4552433732313a20617070726f76652063616c6c6572206973206e6f7420746f60008201527f6b656e206f776e6572206f7220617070726f76656420666f7220616c6c000000602082015250565b61269181612204565b811461269c57600080fd5b50565b6126a881612216565b81146126b357600080fd5b50565b6126bf81612222565b81146126ca57600080fd5b50565b6126d68161226e565b81146126e157600080fd5b5056fea2646970667358221220471c9b7b7857e2dd612257c8e7e0f6ab9f6e99964e596f33dcb4b622892b3d7e64736f6c63430008040033", +} + +// ERC721ABI is the input ABI used to generate the binding from. +// Deprecated: Use ERC721MetaData.ABI instead. +var ERC721ABI = ERC721MetaData.ABI + +// ERC721Bin is the compiled bytecode used for deploying new contracts. +// Deprecated: Use ERC721MetaData.Bin instead. +var ERC721Bin = ERC721MetaData.Bin + +// DeployERC721 deploys a new Ethereum contract, binding an instance of ERC721 to it. +func DeployERC721(auth *bind.TransactOpts, backend bind.ContractBackend, name string, symbol string) (common.Address, *types.Transaction, *ERC721, error) { + parsed, err := ERC721MetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(ERC721Bin), backend, name, symbol) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &ERC721{ERC721Caller: ERC721Caller{contract: contract}, ERC721Transactor: ERC721Transactor{contract: contract}, ERC721Filterer: ERC721Filterer{contract: contract}}, nil +} + +// ERC721 is an auto generated Go binding around an Ethereum contract. +type ERC721 struct { + ERC721Caller // Read-only binding to the contract + ERC721Transactor // Write-only binding to the contract + ERC721Filterer // Log filterer for contract events +} + +// ERC721Caller is an auto generated read-only Go binding around an Ethereum contract. +type ERC721Caller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ERC721Transactor is an auto generated write-only Go binding around an Ethereum contract. +type ERC721Transactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ERC721Filterer is an auto generated log filtering Go binding around an Ethereum contract events. +type ERC721Filterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ERC721Session is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type ERC721Session struct { + Contract *ERC721 // 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 +} + +// ERC721CallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type ERC721CallerSession struct { + Contract *ERC721Caller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// ERC721TransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type ERC721TransactorSession struct { + Contract *ERC721Transactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// ERC721Raw is an auto generated low-level Go binding around an Ethereum contract. +type ERC721Raw struct { + Contract *ERC721 // Generic contract binding to access the raw methods on +} + +// ERC721CallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type ERC721CallerRaw struct { + Contract *ERC721Caller // Generic read-only contract binding to access the raw methods on +} + +// ERC721TransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type ERC721TransactorRaw struct { + Contract *ERC721Transactor // Generic write-only contract binding to access the raw methods on +} + +// NewERC721 creates a new instance of ERC721, bound to a specific deployed contract. +func NewERC721(address common.Address, backend bind.ContractBackend) (*ERC721, error) { + contract, err := bindERC721(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &ERC721{ERC721Caller: ERC721Caller{contract: contract}, ERC721Transactor: ERC721Transactor{contract: contract}, ERC721Filterer: ERC721Filterer{contract: contract}}, nil +} + +// NewERC721Caller creates a new read-only instance of ERC721, bound to a specific deployed contract. +func NewERC721Caller(address common.Address, caller bind.ContractCaller) (*ERC721Caller, error) { + contract, err := bindERC721(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &ERC721Caller{contract: contract}, nil +} + +// NewERC721Transactor creates a new write-only instance of ERC721, bound to a specific deployed contract. +func NewERC721Transactor(address common.Address, transactor bind.ContractTransactor) (*ERC721Transactor, error) { + contract, err := bindERC721(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &ERC721Transactor{contract: contract}, nil +} + +// NewERC721Filterer creates a new log filterer instance of ERC721, bound to a specific deployed contract. +func NewERC721Filterer(address common.Address, filterer bind.ContractFilterer) (*ERC721Filterer, error) { + contract, err := bindERC721(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &ERC721Filterer{contract: contract}, nil +} + +// bindERC721 binds a generic wrapper to an already deployed contract. +func bindERC721(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := ERC721MetaData.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 (_ERC721 *ERC721Raw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _ERC721.Contract.ERC721Caller.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 (_ERC721 *ERC721Raw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ERC721.Contract.ERC721Transactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_ERC721 *ERC721Raw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _ERC721.Contract.ERC721Transactor.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 (_ERC721 *ERC721CallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _ERC721.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 (_ERC721 *ERC721TransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ERC721.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_ERC721 *ERC721TransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _ERC721.Contract.contract.Transact(opts, method, params...) +} + +// BalanceOf is a free data retrieval call binding the contract method 0x70a08231. +// +// Solidity: function balanceOf(address owner) view returns(uint256) +func (_ERC721 *ERC721Caller) BalanceOf(opts *bind.CallOpts, owner common.Address) (*big.Int, error) { + var out []interface{} + err := _ERC721.contract.Call(opts, &out, "balanceOf", owner) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// BalanceOf is a free data retrieval call binding the contract method 0x70a08231. +// +// Solidity: function balanceOf(address owner) view returns(uint256) +func (_ERC721 *ERC721Session) BalanceOf(owner common.Address) (*big.Int, error) { + return _ERC721.Contract.BalanceOf(&_ERC721.CallOpts, owner) +} + +// BalanceOf is a free data retrieval call binding the contract method 0x70a08231. +// +// Solidity: function balanceOf(address owner) view returns(uint256) +func (_ERC721 *ERC721CallerSession) BalanceOf(owner common.Address) (*big.Int, error) { + return _ERC721.Contract.BalanceOf(&_ERC721.CallOpts, owner) +} + +// GetApproved is a free data retrieval call binding the contract method 0x081812fc. +// +// Solidity: function getApproved(uint256 tokenId) view returns(address) +func (_ERC721 *ERC721Caller) GetApproved(opts *bind.CallOpts, tokenId *big.Int) (common.Address, error) { + var out []interface{} + err := _ERC721.contract.Call(opts, &out, "getApproved", tokenId) + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// GetApproved is a free data retrieval call binding the contract method 0x081812fc. +// +// Solidity: function getApproved(uint256 tokenId) view returns(address) +func (_ERC721 *ERC721Session) GetApproved(tokenId *big.Int) (common.Address, error) { + return _ERC721.Contract.GetApproved(&_ERC721.CallOpts, tokenId) +} + +// GetApproved is a free data retrieval call binding the contract method 0x081812fc. +// +// Solidity: function getApproved(uint256 tokenId) view returns(address) +func (_ERC721 *ERC721CallerSession) GetApproved(tokenId *big.Int) (common.Address, error) { + return _ERC721.Contract.GetApproved(&_ERC721.CallOpts, tokenId) +} + +// IsApprovedForAll is a free data retrieval call binding the contract method 0xe985e9c5. +// +// Solidity: function isApprovedForAll(address owner, address operator) view returns(bool) +func (_ERC721 *ERC721Caller) IsApprovedForAll(opts *bind.CallOpts, owner common.Address, operator common.Address) (bool, error) { + var out []interface{} + err := _ERC721.contract.Call(opts, &out, "isApprovedForAll", owner, operator) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +// IsApprovedForAll is a free data retrieval call binding the contract method 0xe985e9c5. +// +// Solidity: function isApprovedForAll(address owner, address operator) view returns(bool) +func (_ERC721 *ERC721Session) IsApprovedForAll(owner common.Address, operator common.Address) (bool, error) { + return _ERC721.Contract.IsApprovedForAll(&_ERC721.CallOpts, owner, operator) +} + +// IsApprovedForAll is a free data retrieval call binding the contract method 0xe985e9c5. +// +// Solidity: function isApprovedForAll(address owner, address operator) view returns(bool) +func (_ERC721 *ERC721CallerSession) IsApprovedForAll(owner common.Address, operator common.Address) (bool, error) { + return _ERC721.Contract.IsApprovedForAll(&_ERC721.CallOpts, owner, operator) +} + +// Name is a free data retrieval call binding the contract method 0x06fdde03. +// +// Solidity: function name() view returns(string) +func (_ERC721 *ERC721Caller) Name(opts *bind.CallOpts) (string, error) { + var out []interface{} + err := _ERC721.contract.Call(opts, &out, "name") + + if err != nil { + return *new(string), err + } + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + + return out0, err + +} + +// Name is a free data retrieval call binding the contract method 0x06fdde03. +// +// Solidity: function name() view returns(string) +func (_ERC721 *ERC721Session) Name() (string, error) { + return _ERC721.Contract.Name(&_ERC721.CallOpts) +} + +// Name is a free data retrieval call binding the contract method 0x06fdde03. +// +// Solidity: function name() view returns(string) +func (_ERC721 *ERC721CallerSession) Name() (string, error) { + return _ERC721.Contract.Name(&_ERC721.CallOpts) +} + +// OwnerOf is a free data retrieval call binding the contract method 0x6352211e. +// +// Solidity: function ownerOf(uint256 tokenId) view returns(address) +func (_ERC721 *ERC721Caller) OwnerOf(opts *bind.CallOpts, tokenId *big.Int) (common.Address, error) { + var out []interface{} + err := _ERC721.contract.Call(opts, &out, "ownerOf", tokenId) + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// OwnerOf is a free data retrieval call binding the contract method 0x6352211e. +// +// Solidity: function ownerOf(uint256 tokenId) view returns(address) +func (_ERC721 *ERC721Session) OwnerOf(tokenId *big.Int) (common.Address, error) { + return _ERC721.Contract.OwnerOf(&_ERC721.CallOpts, tokenId) +} + +// OwnerOf is a free data retrieval call binding the contract method 0x6352211e. +// +// Solidity: function ownerOf(uint256 tokenId) view returns(address) +func (_ERC721 *ERC721CallerSession) OwnerOf(tokenId *big.Int) (common.Address, error) { + return _ERC721.Contract.OwnerOf(&_ERC721.CallOpts, tokenId) +} + +// SupportsInterface is a free data retrieval call binding the contract method 0x01ffc9a7. +// +// Solidity: function supportsInterface(bytes4 interfaceId) view returns(bool) +func (_ERC721 *ERC721Caller) SupportsInterface(opts *bind.CallOpts, interfaceId [4]byte) (bool, error) { + var out []interface{} + err := _ERC721.contract.Call(opts, &out, "supportsInterface", interfaceId) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +// SupportsInterface is a free data retrieval call binding the contract method 0x01ffc9a7. +// +// Solidity: function supportsInterface(bytes4 interfaceId) view returns(bool) +func (_ERC721 *ERC721Session) SupportsInterface(interfaceId [4]byte) (bool, error) { + return _ERC721.Contract.SupportsInterface(&_ERC721.CallOpts, interfaceId) +} + +// SupportsInterface is a free data retrieval call binding the contract method 0x01ffc9a7. +// +// Solidity: function supportsInterface(bytes4 interfaceId) view returns(bool) +func (_ERC721 *ERC721CallerSession) SupportsInterface(interfaceId [4]byte) (bool, error) { + return _ERC721.Contract.SupportsInterface(&_ERC721.CallOpts, interfaceId) +} + +// Symbol is a free data retrieval call binding the contract method 0x95d89b41. +// +// Solidity: function symbol() view returns(string) +func (_ERC721 *ERC721Caller) Symbol(opts *bind.CallOpts) (string, error) { + var out []interface{} + err := _ERC721.contract.Call(opts, &out, "symbol") + + if err != nil { + return *new(string), err + } + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + + return out0, err + +} + +// Symbol is a free data retrieval call binding the contract method 0x95d89b41. +// +// Solidity: function symbol() view returns(string) +func (_ERC721 *ERC721Session) Symbol() (string, error) { + return _ERC721.Contract.Symbol(&_ERC721.CallOpts) +} + +// Symbol is a free data retrieval call binding the contract method 0x95d89b41. +// +// Solidity: function symbol() view returns(string) +func (_ERC721 *ERC721CallerSession) Symbol() (string, error) { + return _ERC721.Contract.Symbol(&_ERC721.CallOpts) +} + +// TokenURI is a free data retrieval call binding the contract method 0xc87b56dd. +// +// Solidity: function tokenURI(uint256 tokenId) view returns(string) +func (_ERC721 *ERC721Caller) TokenURI(opts *bind.CallOpts, tokenId *big.Int) (string, error) { + var out []interface{} + err := _ERC721.contract.Call(opts, &out, "tokenURI", tokenId) + + if err != nil { + return *new(string), err + } + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + + return out0, err + +} + +// TokenURI is a free data retrieval call binding the contract method 0xc87b56dd. +// +// Solidity: function tokenURI(uint256 tokenId) view returns(string) +func (_ERC721 *ERC721Session) TokenURI(tokenId *big.Int) (string, error) { + return _ERC721.Contract.TokenURI(&_ERC721.CallOpts, tokenId) +} + +// TokenURI is a free data retrieval call binding the contract method 0xc87b56dd. +// +// Solidity: function tokenURI(uint256 tokenId) view returns(string) +func (_ERC721 *ERC721CallerSession) TokenURI(tokenId *big.Int) (string, error) { + return _ERC721.Contract.TokenURI(&_ERC721.CallOpts, tokenId) +} + +// Approve is a paid mutator transaction binding the contract method 0x095ea7b3. +// +// Solidity: function approve(address to, uint256 tokenId) returns() +func (_ERC721 *ERC721Transactor) Approve(opts *bind.TransactOpts, to common.Address, tokenId *big.Int) (*types.Transaction, error) { + return _ERC721.contract.Transact(opts, "approve", to, tokenId) +} + +// Approve is a paid mutator transaction binding the contract method 0x095ea7b3. +// +// Solidity: function approve(address to, uint256 tokenId) returns() +func (_ERC721 *ERC721Session) Approve(to common.Address, tokenId *big.Int) (*types.Transaction, error) { + return _ERC721.Contract.Approve(&_ERC721.TransactOpts, to, tokenId) +} + +// Approve is a paid mutator transaction binding the contract method 0x095ea7b3. +// +// Solidity: function approve(address to, uint256 tokenId) returns() +func (_ERC721 *ERC721TransactorSession) Approve(to common.Address, tokenId *big.Int) (*types.Transaction, error) { + return _ERC721.Contract.Approve(&_ERC721.TransactOpts, to, tokenId) +} + +// MintNFT is a paid mutator transaction binding the contract method 0x3c168eab. +// +// Solidity: function mintNFT(address collector, uint256 tokenId) returns() +func (_ERC721 *ERC721Transactor) MintNFT(opts *bind.TransactOpts, collector common.Address, tokenId *big.Int) (*types.Transaction, error) { + return _ERC721.contract.Transact(opts, "mintNFT", collector, tokenId) +} + +// MintNFT is a paid mutator transaction binding the contract method 0x3c168eab. +// +// Solidity: function mintNFT(address collector, uint256 tokenId) returns() +func (_ERC721 *ERC721Session) MintNFT(collector common.Address, tokenId *big.Int) (*types.Transaction, error) { + return _ERC721.Contract.MintNFT(&_ERC721.TransactOpts, collector, tokenId) +} + +// MintNFT is a paid mutator transaction binding the contract method 0x3c168eab. +// +// Solidity: function mintNFT(address collector, uint256 tokenId) returns() +func (_ERC721 *ERC721TransactorSession) MintNFT(collector common.Address, tokenId *big.Int) (*types.Transaction, error) { + return _ERC721.Contract.MintNFT(&_ERC721.TransactOpts, collector, tokenId) +} + +// SafeTransferFrom is a paid mutator transaction binding the contract method 0x42842e0e. +// +// Solidity: function safeTransferFrom(address from, address to, uint256 tokenId) returns() +func (_ERC721 *ERC721Transactor) SafeTransferFrom(opts *bind.TransactOpts, from common.Address, to common.Address, tokenId *big.Int) (*types.Transaction, error) { + return _ERC721.contract.Transact(opts, "safeTransferFrom", from, to, tokenId) +} + +// SafeTransferFrom is a paid mutator transaction binding the contract method 0x42842e0e. +// +// Solidity: function safeTransferFrom(address from, address to, uint256 tokenId) returns() +func (_ERC721 *ERC721Session) SafeTransferFrom(from common.Address, to common.Address, tokenId *big.Int) (*types.Transaction, error) { + return _ERC721.Contract.SafeTransferFrom(&_ERC721.TransactOpts, from, to, tokenId) +} + +// SafeTransferFrom is a paid mutator transaction binding the contract method 0x42842e0e. +// +// Solidity: function safeTransferFrom(address from, address to, uint256 tokenId) returns() +func (_ERC721 *ERC721TransactorSession) SafeTransferFrom(from common.Address, to common.Address, tokenId *big.Int) (*types.Transaction, error) { + return _ERC721.Contract.SafeTransferFrom(&_ERC721.TransactOpts, from, to, tokenId) +} + +// SafeTransferFrom0 is a paid mutator transaction binding the contract method 0xb88d4fde. +// +// Solidity: function safeTransferFrom(address from, address to, uint256 tokenId, bytes data) returns() +func (_ERC721 *ERC721Transactor) SafeTransferFrom0(opts *bind.TransactOpts, from common.Address, to common.Address, tokenId *big.Int, data []byte) (*types.Transaction, error) { + return _ERC721.contract.Transact(opts, "safeTransferFrom0", from, to, tokenId, data) +} + +// SafeTransferFrom0 is a paid mutator transaction binding the contract method 0xb88d4fde. +// +// Solidity: function safeTransferFrom(address from, address to, uint256 tokenId, bytes data) returns() +func (_ERC721 *ERC721Session) SafeTransferFrom0(from common.Address, to common.Address, tokenId *big.Int, data []byte) (*types.Transaction, error) { + return _ERC721.Contract.SafeTransferFrom0(&_ERC721.TransactOpts, from, to, tokenId, data) +} + +// SafeTransferFrom0 is a paid mutator transaction binding the contract method 0xb88d4fde. +// +// Solidity: function safeTransferFrom(address from, address to, uint256 tokenId, bytes data) returns() +func (_ERC721 *ERC721TransactorSession) SafeTransferFrom0(from common.Address, to common.Address, tokenId *big.Int, data []byte) (*types.Transaction, error) { + return _ERC721.Contract.SafeTransferFrom0(&_ERC721.TransactOpts, from, to, tokenId, data) +} + +// SetApprovalForAll is a paid mutator transaction binding the contract method 0xa22cb465. +// +// Solidity: function setApprovalForAll(address operator, bool approved) returns() +func (_ERC721 *ERC721Transactor) SetApprovalForAll(opts *bind.TransactOpts, operator common.Address, approved bool) (*types.Transaction, error) { + return _ERC721.contract.Transact(opts, "setApprovalForAll", operator, approved) +} + +// SetApprovalForAll is a paid mutator transaction binding the contract method 0xa22cb465. +// +// Solidity: function setApprovalForAll(address operator, bool approved) returns() +func (_ERC721 *ERC721Session) SetApprovalForAll(operator common.Address, approved bool) (*types.Transaction, error) { + return _ERC721.Contract.SetApprovalForAll(&_ERC721.TransactOpts, operator, approved) +} + +// SetApprovalForAll is a paid mutator transaction binding the contract method 0xa22cb465. +// +// Solidity: function setApprovalForAll(address operator, bool approved) returns() +func (_ERC721 *ERC721TransactorSession) SetApprovalForAll(operator common.Address, approved bool) (*types.Transaction, error) { + return _ERC721.Contract.SetApprovalForAll(&_ERC721.TransactOpts, operator, approved) +} + +// TransferFrom is a paid mutator transaction binding the contract method 0x23b872dd. +// +// Solidity: function transferFrom(address from, address to, uint256 tokenId) returns() +func (_ERC721 *ERC721Transactor) TransferFrom(opts *bind.TransactOpts, from common.Address, to common.Address, tokenId *big.Int) (*types.Transaction, error) { + return _ERC721.contract.Transact(opts, "transferFrom", from, to, tokenId) +} + +// TransferFrom is a paid mutator transaction binding the contract method 0x23b872dd. +// +// Solidity: function transferFrom(address from, address to, uint256 tokenId) returns() +func (_ERC721 *ERC721Session) TransferFrom(from common.Address, to common.Address, tokenId *big.Int) (*types.Transaction, error) { + return _ERC721.Contract.TransferFrom(&_ERC721.TransactOpts, from, to, tokenId) +} + +// TransferFrom is a paid mutator transaction binding the contract method 0x23b872dd. +// +// Solidity: function transferFrom(address from, address to, uint256 tokenId) returns() +func (_ERC721 *ERC721TransactorSession) TransferFrom(from common.Address, to common.Address, tokenId *big.Int) (*types.Transaction, error) { + return _ERC721.Contract.TransferFrom(&_ERC721.TransactOpts, from, to, tokenId) +} + +// ERC721ApprovalIterator is returned from FilterApproval and is used to iterate over the raw logs and unpacked data for Approval events raised by the ERC721 contract. +type ERC721ApprovalIterator struct { + Event *ERC721Approval // 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 *ERC721ApprovalIterator) 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(ERC721Approval) + 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(ERC721Approval) + 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 *ERC721ApprovalIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *ERC721ApprovalIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// ERC721Approval represents a Approval event raised by the ERC721 contract. +type ERC721Approval struct { + Owner common.Address + Approved common.Address + TokenId *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterApproval is a free log retrieval operation binding the contract event 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925. +// +// Solidity: event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId) +func (_ERC721 *ERC721Filterer) FilterApproval(opts *bind.FilterOpts, owner []common.Address, approved []common.Address, tokenId []*big.Int) (*ERC721ApprovalIterator, error) { + + var ownerRule []interface{} + for _, ownerItem := range owner { + ownerRule = append(ownerRule, ownerItem) + } + var approvedRule []interface{} + for _, approvedItem := range approved { + approvedRule = append(approvedRule, approvedItem) + } + var tokenIdRule []interface{} + for _, tokenIdItem := range tokenId { + tokenIdRule = append(tokenIdRule, tokenIdItem) + } + + logs, sub, err := _ERC721.contract.FilterLogs(opts, "Approval", ownerRule, approvedRule, tokenIdRule) + if err != nil { + return nil, err + } + return &ERC721ApprovalIterator{contract: _ERC721.contract, event: "Approval", logs: logs, sub: sub}, nil +} + +// WatchApproval is a free log subscription operation binding the contract event 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925. +// +// Solidity: event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId) +func (_ERC721 *ERC721Filterer) WatchApproval(opts *bind.WatchOpts, sink chan<- *ERC721Approval, owner []common.Address, approved []common.Address, tokenId []*big.Int) (event.Subscription, error) { + + var ownerRule []interface{} + for _, ownerItem := range owner { + ownerRule = append(ownerRule, ownerItem) + } + var approvedRule []interface{} + for _, approvedItem := range approved { + approvedRule = append(approvedRule, approvedItem) + } + var tokenIdRule []interface{} + for _, tokenIdItem := range tokenId { + tokenIdRule = append(tokenIdRule, tokenIdItem) + } + + logs, sub, err := _ERC721.contract.WatchLogs(opts, "Approval", ownerRule, approvedRule, tokenIdRule) + 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(ERC721Approval) + if err := _ERC721.contract.UnpackLog(event, "Approval", 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 +} + +// ParseApproval is a log parse operation binding the contract event 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925. +// +// Solidity: event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId) +func (_ERC721 *ERC721Filterer) ParseApproval(log types.Log) (*ERC721Approval, error) { + event := new(ERC721Approval) + if err := _ERC721.contract.UnpackLog(event, "Approval", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// ERC721ApprovalForAllIterator is returned from FilterApprovalForAll and is used to iterate over the raw logs and unpacked data for ApprovalForAll events raised by the ERC721 contract. +type ERC721ApprovalForAllIterator struct { + Event *ERC721ApprovalForAll // 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 *ERC721ApprovalForAllIterator) 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(ERC721ApprovalForAll) + 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(ERC721ApprovalForAll) + 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 *ERC721ApprovalForAllIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *ERC721ApprovalForAllIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// ERC721ApprovalForAll represents a ApprovalForAll event raised by the ERC721 contract. +type ERC721ApprovalForAll struct { + Owner common.Address + Operator common.Address + Approved bool + Raw types.Log // Blockchain specific contextual infos +} + +// FilterApprovalForAll is a free log retrieval operation binding the contract event 0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31. +// +// Solidity: event ApprovalForAll(address indexed owner, address indexed operator, bool approved) +func (_ERC721 *ERC721Filterer) FilterApprovalForAll(opts *bind.FilterOpts, owner []common.Address, operator []common.Address) (*ERC721ApprovalForAllIterator, error) { + + var ownerRule []interface{} + for _, ownerItem := range owner { + ownerRule = append(ownerRule, ownerItem) + } + var operatorRule []interface{} + for _, operatorItem := range operator { + operatorRule = append(operatorRule, operatorItem) + } + + logs, sub, err := _ERC721.contract.FilterLogs(opts, "ApprovalForAll", ownerRule, operatorRule) + if err != nil { + return nil, err + } + return &ERC721ApprovalForAllIterator{contract: _ERC721.contract, event: "ApprovalForAll", logs: logs, sub: sub}, nil +} + +// WatchApprovalForAll is a free log subscription operation binding the contract event 0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31. +// +// Solidity: event ApprovalForAll(address indexed owner, address indexed operator, bool approved) +func (_ERC721 *ERC721Filterer) WatchApprovalForAll(opts *bind.WatchOpts, sink chan<- *ERC721ApprovalForAll, owner []common.Address, operator []common.Address) (event.Subscription, error) { + + var ownerRule []interface{} + for _, ownerItem := range owner { + ownerRule = append(ownerRule, ownerItem) + } + var operatorRule []interface{} + for _, operatorItem := range operator { + operatorRule = append(operatorRule, operatorItem) + } + + logs, sub, err := _ERC721.contract.WatchLogs(opts, "ApprovalForAll", ownerRule, operatorRule) + 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(ERC721ApprovalForAll) + if err := _ERC721.contract.UnpackLog(event, "ApprovalForAll", 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 +} + +// ParseApprovalForAll is a log parse operation binding the contract event 0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31. +// +// Solidity: event ApprovalForAll(address indexed owner, address indexed operator, bool approved) +func (_ERC721 *ERC721Filterer) ParseApprovalForAll(log types.Log) (*ERC721ApprovalForAll, error) { + event := new(ERC721ApprovalForAll) + if err := _ERC721.contract.UnpackLog(event, "ApprovalForAll", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// ERC721TransferIterator is returned from FilterTransfer and is used to iterate over the raw logs and unpacked data for Transfer events raised by the ERC721 contract. +type ERC721TransferIterator struct { + Event *ERC721Transfer // 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 *ERC721TransferIterator) 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(ERC721Transfer) + 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(ERC721Transfer) + 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 *ERC721TransferIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *ERC721TransferIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// ERC721Transfer represents a Transfer event raised by the ERC721 contract. +type ERC721Transfer struct { + From common.Address + To common.Address + TokenId *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterTransfer is a free log retrieval operation binding the contract event 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef. +// +// Solidity: event Transfer(address indexed from, address indexed to, uint256 indexed tokenId) +func (_ERC721 *ERC721Filterer) FilterTransfer(opts *bind.FilterOpts, from []common.Address, to []common.Address, tokenId []*big.Int) (*ERC721TransferIterator, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + var tokenIdRule []interface{} + for _, tokenIdItem := range tokenId { + tokenIdRule = append(tokenIdRule, tokenIdItem) + } + + logs, sub, err := _ERC721.contract.FilterLogs(opts, "Transfer", fromRule, toRule, tokenIdRule) + if err != nil { + return nil, err + } + return &ERC721TransferIterator{contract: _ERC721.contract, event: "Transfer", logs: logs, sub: sub}, nil +} + +// WatchTransfer is a free log subscription operation binding the contract event 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef. +// +// Solidity: event Transfer(address indexed from, address indexed to, uint256 indexed tokenId) +func (_ERC721 *ERC721Filterer) WatchTransfer(opts *bind.WatchOpts, sink chan<- *ERC721Transfer, from []common.Address, to []common.Address, tokenId []*big.Int) (event.Subscription, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + var tokenIdRule []interface{} + for _, tokenIdItem := range tokenId { + tokenIdRule = append(tokenIdRule, tokenIdItem) + } + + logs, sub, err := _ERC721.contract.WatchLogs(opts, "Transfer", fromRule, toRule, tokenIdRule) + 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(ERC721Transfer) + if err := _ERC721.contract.UnpackLog(event, "Transfer", 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 +} + +// ParseTransfer is a log parse operation binding the contract event 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef. +// +// Solidity: event Transfer(address indexed from, address indexed to, uint256 indexed tokenId) +func (_ERC721 *ERC721Filterer) ParseTransfer(log types.Log) (*ERC721Transfer, error) { + event := new(ERC721Transfer) + if err := _ERC721.contract.UnpackLog(event, "Transfer", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} diff --git a/tests/immutable/erc721_test.go b/tests/immutable/erc721_test.go new file mode 100644 index 000000000..c5d33d633 --- /dev/null +++ b/tests/immutable/erc721_test.go @@ -0,0 +1,90 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package test + +import ( + "context" + "math/big" + "testing" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/tests/immutable/erc721" + "github.com/ethereum/go-ethereum/tests/immutable/evm" + "github.com/stretchr/testify/require" +) + +func TestImmutableLegacyERC721_CreateMintAndTransfer_Successful(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + + client, err := evm.NewClient(ctx, config.rpcURL, 1, 1) + require.NoError(t, err) + + testERC721(t, ctx, client.EthClient(), legacyTxOpts(t, ctx, client.EthClient(), config.testUser)) +} + +func TestImmutableEIP1559ERC721_CreateMintAndTransfer(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + + client, err := evm.NewClient(ctx, config.rpcURL, 1, 1) + require.NoError(t, err) + + testERC721(t, ctx, client.EthClient(), EIP1559TxOpts(t, ctx, client.EthClient(), config.testUser)) +} + +func testERC721(t *testing.T, ctx context.Context, client *ethclient.Client, txOpts *bind.TransactOpts) { + t.Helper() + + alice := txOpts.From + bob := common.BigToAddress(common.Big1) + + // Deploy a contract + _, tx, nft, err := erc721.DeployERC721(txOpts, client, "bored apes", "BAYC") + require.NoError(t, err) + deployReceipt, err := bind.WaitMined(ctx, client, tx) + require.Equal(t, deployReceipt.Status, uint64(1)) + require.NoError(t, err) + t.Logf("ERC721 Deployed to address %s with TX Hash %s", deployReceipt.ContractAddress.String(), tx.Hash().String()) + + // Mint tokens + nftIDs := []*big.Int{common.Big1, common.Big2} + for _, id := range nftIDs { + tx, err := nft.MintNFT(txOpts, alice, id) + require.NoError(t, err) + receipt, err := bind.WaitMined(ctx, client, tx) + require.NoError(t, err) + t.Logf("Mint complete with TX Hash: %s, Block: %d", receipt.TxHash.String(), receipt.BlockNumber.Uint64()) + } + + // Transfer token + for _, id := range nftIDs { + tx, err := nft.TransferFrom(txOpts, alice, bob, id) + require.NoError(t, err) + receipt, err := bind.WaitMined(ctx, client, tx) + require.NoError(t, err) + t.Logf("Transfer complete with TX Hash: %s, Block: %d", receipt.TxHash.String(), receipt.BlockNumber.Uint64()) + } + + // Bad transfer + _, err = nft.TransferFrom(txOpts, alice, bob, nftIDs[0]) + require.Error(t, err) + require.Contains(t, err.Error(), "caller is not token owner or approved") +} diff --git a/tests/immutable/evm/client.go b/tests/immutable/evm/client.go new file mode 100644 index 000000000..20d2d5f7e --- /dev/null +++ b/tests/immutable/evm/client.go @@ -0,0 +1,219 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package evm + +import ( + "context" + "errors" + "fmt" + "math/big" + "net/url" + "time" + + gethrpc "github.com/ethereum/go-ethereum/rpc" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/sethvargo/go-retry" + "github.com/sirupsen/logrus" +) + +type Client struct { + ethClient *ethclient.Client + rpcClient *gethrpc.Client + retryCount uint64 + retryWait time.Duration + // Capable of giving EIP-1559 gas tip estimations according to some internal + // strategy. Receives the ethclient.Client to fetch gas information. + gasTipEstimator GasTipEstimator +} + +const gethExecutionReverted = "execution reverted" + +// TODO: Remove edgeExecutionReverted once edge matches up error strings with geth +const edgeExecutionReverted = "execution was reverted" + +// TODO: Figure out a better way to handle error from hardhat +const hardhatExecutionReverted = "Error: Transaction reverted without a reason string" + +var ErrExecutionReverted = errors.New("execution reverted") + +// NewClient create default instance of an EVM Client +func NewClient(ctx context.Context, u *url.URL, retryCount uint64, retryWait time.Duration) (*Client, error) { + evmClient, err := ethclient.DialContext(ctx, u.String()) + if err != nil { + return nil, fmt.Errorf("failed to dial context for eth client: %w", err) + } + rpcClient, err := gethrpc.DialContext(ctx, u.String()) + if err != nil { + return nil, fmt.Errorf("failed to dial context for rpc client: %w", err) + } + + return &Client{ + ethClient: evmClient, + rpcClient: rpcClient, + retryCount: retryCount, + retryWait: retryWait, + // Suggested minimum per EIP-1559 + gasTipEstimator: NewFixedGasTipEstimator(big.NewInt(1000000000)), + }, nil +} + +// FilterLogs Ethereum RPC FilterLogs +func (d *Client) FilterLogs(ctx context.Context, filter ethereum.FilterQuery) ([]types.Log, error) { + log := logrus.WithContext(ctx).WithField("function", "Client.FilterLogs") + var logs []types.Log + err := d.retryDo(ctx, func(ctx context.Context) error { + var err error + logs, err = d.ethClient.FilterLogs(ctx, filter) + if err != nil { + return logAndReturnRetryableErr(log, err) + } + return nil + }) + if err != nil { + return nil, fmt.Errorf("failed to filter eth logs: %w", err) + } + return logs, nil +} + +// BlockNumber Ethereum RPC BlockNumber +func (d *Client) BlockNumber(ctx context.Context) (uint64, error) { + log := logrus.WithContext(ctx).WithField("function", "Client.BlockNumber") + var blockNum uint64 + err := d.retryDo(ctx, func(ctx context.Context) error { + var err error + blockNum, err = d.ethClient.BlockNumber(ctx) + if err != nil { + return logAndReturnRetryableErr(log, err) + } + return nil + }) + if err != nil { + return 0, fmt.Errorf("failed to get block number: %w", err) + } + return blockNum, nil +} + +// BlockByNumber Ethereum RPC BlockByNumber +func (d *Client) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) { + log := logrus.WithContext(ctx).WithField("function", "Client.BlockByNumber") + var block *types.Block + err := d.retryDo(ctx, func(ctx context.Context) error { + var err error + block, err = d.ethClient.BlockByNumber(ctx, number) + if err != nil { + return logAndReturnRetryableErr(log, err) + } + return nil + }) + if err != nil { + return nil, fmt.Errorf("failed to get transactions by block number %s: %w", number.String(), err) + } + return block, nil +} + +// TransactionReceipt Ethereum RPC TransactionReceipt +func (d *Client) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { + log := logrus.WithContext(ctx).WithField("function", "Client.TransactionReceipt") + var receipt *types.Receipt + err := d.retryDo(ctx, func(ctx context.Context) error { + var err error + receipt, err = d.ethClient.TransactionReceipt(ctx, txHash) + if err != nil { + // Don't log "not found" errors, they are expected + if err.Error() == "not found" { + return retry.RetryableError(err) + } + return logAndReturnRetryableErr(log, err) + } + return nil + }) + if err != nil { + return nil, fmt.Errorf("failed to get transaction recept for transaction %s: %w", txHash.Hex(), err) + } + return receipt, nil +} + +// CodeAt calls the Ethereum RPC CodeAt function. +func (d *Client) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) { + log := logrus.WithContext(ctx).WithField("function", "Client.CodeAt") + var codeAt []byte + err := d.retryDo(ctx, func(ctx context.Context) error { + var err error + codeAt, err = d.ethClient.CodeAt(ctx, contract, blockNumber) + if err != nil { + // NOTE: We have to do string comparison because generated bindings don't return a type we can easily work with + // This error is not retryable. + if err.Error() == gethExecutionReverted || err.Error() == edgeExecutionReverted || err.Error() == hardhatExecutionReverted { + return ErrExecutionReverted + } + return logAndReturnRetryableErr(log, err) + } + return nil + }) + if err != nil { + return nil, fmt.Errorf("failed to get code at for address %s: %w", contract.Hex(), err) + } + return codeAt, nil +} + +// CallContract calls the Ethereum RPC CallContract function. +func (d *Client) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) { + log := logrus.WithContext(ctx).WithField("function", "Client.CallContract") + var callResult []byte + err := d.retryDo(ctx, func(ctx context.Context) error { + var err error + callResult, err = d.ethClient.CallContract(ctx, call, blockNumber) + if err != nil { + // NOTE: We have to do string comparison because generated bindings don't return a type we can easily work with + // This error is not retryable + if err.Error() == gethExecutionReverted || err.Error() == edgeExecutionReverted || err.Error() == hardhatExecutionReverted { + return ErrExecutionReverted + } + return logAndReturnRetryableErr(log, err) + } + return nil + }) + if err != nil { + return nil, err + } + return callResult, nil +} + +func (d *Client) EthClient() *ethclient.Client { + // TODO: can we avoid exposing this? Can we define an interface to accommodate? + return d.ethClient +} + +func (d *Client) retryDo(ctx context.Context, f retry.RetryFunc) error { + // If no retries, just run the func + if d.retryCount == 0 { + return f(ctx) + } + // Run func with retries + b := retry.WithMaxRetries(d.retryCount, retry.NewConstant(d.retryWait)) + err := retry.Do(ctx, b, f) + return err +} + +func logAndReturnRetryableErr(log *logrus.Entry, err error) error { + log.Warnf("query error: %s", err.Error()) + return retry.RetryableError(err) +} diff --git a/tests/immutable/evm/client_test.go b/tests/immutable/evm/client_test.go new file mode 100644 index 000000000..3e54ed689 --- /dev/null +++ b/tests/immutable/evm/client_test.go @@ -0,0 +1,57 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package evm + +import ( + "context" + "math/big" + "net/url" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestImmutableNewEVMClient_ValidArgs_Success(t *testing.T) { + u, err := url.Parse("https://testurl") + require.NoError(t, err) + _, err = NewClient(context.Background(), u, 5, time.Second*2) + require.NoError(t, err) +} + +func TestImmutableNewEVMClient_ZeroRetryCount_Success(t *testing.T) { + u, err := url.Parse("https://testurl") + require.NoError(t, err) + _, err = NewClient(context.Background(), u, 0, time.Second*2) + require.NoError(t, err) +} + +func TestImmutableEVMClient_FixedEstimator_ShouldReturn1GWEI(t *testing.T) { + u, err := url.Parse("https://testurl") + require.NoError(t, err) + + ctx := context.Background() + + client, err := NewClient(ctx, u, 5, time.Second*2) + require.NoError(t, err) + + tip, err := client.SuggestGasTipCap(ctx) + require.NoError(t, err) + + assert.Equal(t, big.NewInt(1000000000), tip) +} diff --git a/tests/immutable/evm/client_transactor.go b/tests/immutable/evm/client_transactor.go new file mode 100644 index 000000000..065ebe355 --- /dev/null +++ b/tests/immutable/evm/client_transactor.go @@ -0,0 +1,152 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package evm + +import ( + "context" + "math/big" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/sirupsen/logrus" +) + +// The below functions implement the bind.ContractTransactor interface: +// +// - HeaderByNumber +// - PendingCodeAt +// - PendingNonceAt +// - SuggestGasPrice +// - SuggestGasTipCap +// - EstimateGas +// - SendTransaction +// +// These are used by bindings when executing contract calls. The reasoning +// behind having a custom implementation is that it allows for retry +// functionality and for custom gas pricing strategies. + +func (client *Client) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { + log := logrus.WithContext(ctx).WithField("function", "Client.HeaderByNumber") + + var header *types.Header + err := client.retryDo(ctx, func(ctx context.Context) error { + var err error + header, err = client.ethClient.HeaderByNumber(ctx, number) + if err != nil { + return logAndReturnRetryableErr(log, err) + } + return nil + }) + + return header, err +} + +func (client *Client) PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error) { + log := logrus.WithContext(ctx).WithField("function", "Client.PendingCodeAt") + + var pendingCode []byte + err := client.retryDo(ctx, func(ctx context.Context) error { + var err error + pendingCode, err = client.ethClient.PendingCodeAt(ctx, account) + if err != nil { + return logAndReturnRetryableErr(log, err) + } + return nil + }) + + return pendingCode, err +} + +func (client *Client) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) { + log := logrus.WithContext(ctx).WithField("function", "Client.PendingNonceAt") + + var pendingNonce uint64 + err := client.retryDo(ctx, func(ctx context.Context) error { + var err error + pendingNonce, err = client.ethClient.PendingNonceAt(ctx, account) + if err != nil { + return logAndReturnRetryableErr(log, err) + } + return nil + }) + + return pendingNonce, err +} + +// SuggestGasPrice is used for pricing legacy transactions. +// +// NOTE: Since we plan to use EIP-1559 transactions, there's no need to +// implement it. Panic to make sure anyone trying to use it knows it's not +// implemented. +func (client *Client) SuggestGasPrice(ctx context.Context) (*big.Int, error) { + panic("unimplemented") +} + +func (client *Client) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { + log := logrus.WithContext(ctx).WithField("function", "Client.SuggestGasTipCap") + log.Warnf("Temporary gas pricing calculation! Returns 0!") + + tip := big.NewInt(0) + err := client.retryDo(ctx, func(ctx context.Context) error { + var err error + + tip, err = client.gasTipEstimator.SuggestGasTipCap(ctx, client.ethClient) + if err != nil { + return logAndReturnRetryableErr(log, err) + } + + return nil + }) + + return tip, err +} + +func (client *Client) EstimateGas(ctx context.Context, call ethereum.CallMsg) (uint64, error) { + log := logrus.WithContext(ctx).WithField("function", "Client.EstimateGas") + + var gas uint64 + err := client.retryDo(ctx, func(ctx context.Context) error { + var err error + gas, err = client.ethClient.EstimateGas(ctx, call) + if err != nil { + // NOTE: We have to do string comparison because generated bindings + // don't return a type we can easily work with This error is not + // retryable. + if err.Error() == gethExecutionReverted { + return ErrExecutionReverted + } + return logAndReturnRetryableErr(log, err) + } + return nil + }) + + return gas, err +} + +func (client *Client) SendTransaction(ctx context.Context, tx *types.Transaction) error { + log := logrus.WithContext(ctx).WithField("function", "Client.SendTransaction") + + return client.retryDo(ctx, func(ctx context.Context) error { + err := client.ethClient.SendTransaction(ctx, tx) + if err != nil { + return logAndReturnRetryableErr(log, err) + } + + return nil + }) +} diff --git a/tests/immutable/evm/tip_estimator.go b/tests/immutable/evm/tip_estimator.go new file mode 100644 index 000000000..383d0e6cb --- /dev/null +++ b/tests/immutable/evm/tip_estimator.go @@ -0,0 +1,43 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package evm + +import ( + "context" + "math/big" + + "github.com/ethereum/go-ethereum/ethclient" +) + +type GasTipEstimator interface { + SuggestGasTipCap(context.Context, *ethclient.Client) (*big.Int, error) +} + +type fixedGasTipEstimator struct { + fixedTipCap *big.Int +} + +func NewFixedGasTipEstimator(tipCap *big.Int) GasTipEstimator { + return fixedGasTipEstimator{tipCap} +} + +func (estimator fixedGasTipEstimator) SuggestGasTipCap( + context.Context, + *ethclient.Client, +) (*big.Int, error) { + return estimator.fixedTipCap, nil +} diff --git a/tests/immutable/evm/tip_estimator_test.go b/tests/immutable/evm/tip_estimator_test.go new file mode 100644 index 000000000..53765ad89 --- /dev/null +++ b/tests/immutable/evm/tip_estimator_test.go @@ -0,0 +1,36 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package evm + +import ( + "context" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/ethclient" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestImmutableFixedEstimator_ShouldReturnFixedAmount(t *testing.T) { + estimator := NewFixedGasTipEstimator(big.NewInt(42)) + + tip, err := estimator.SuggestGasTipCap(context.Background(), ðclient.Client{}) + require.NoError(t, err) + + assert.Equal(t, big.NewInt(42), tip) +} diff --git a/tests/immutable/flag_test.go b/tests/immutable/flag_test.go new file mode 100644 index 000000000..953ea3520 --- /dev/null +++ b/tests/immutable/flag_test.go @@ -0,0 +1,196 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package test + +import ( + "context" + "errors" + "fmt" + "os" + "path" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/rpc" + "github.com/stretchr/testify/require" +) + +func TestImmutable_RPCDisableFlag(t *testing.T) { + // Flags are set under cmd/geth/immutable_local.go + rpcMethods := []string{ + // Debug + "debug_startGoTrace", + "debug_stopGoTrace", + "debug_blockProfile", + "debug_setBlockProfileRate", + "debug_writeBlockProfile", + "debug_mutexProfile", + "debug_setMutexProfileFraction", + "debug_writeMutexProfile", + "debug_writeMemProfile", + "debug_traceBlock", + "debug_traceBlockFromFile", + "debug_traceBadBlock", + "debug_standardTraceBadBlockToFile", + "debug_intermediateRoots", + "debug_standardTraceBlockToFile", + "debug_traceBlockByNumber", + "debug_traceBlockByHash", + "debug_traceTransaction", + "debug_traceCall", + "debug_preimage", + "debug_getBadBlocks", + "debug_storageRangeAt", + "debug_getModifiedAccountsByNumber", + "debug_getModifiedAccountsByHash", + "debug_freezeClient", + "debug_getAccessibleState", + "debug_dbGet", + "debug_dbAncient", + "debug_dbAncients", + "debug_setTrieFlushInterval", + "debug_getTrieFlushInterval", + "debug_accountRange", + "debug_printBlock", + "debug_getRawHeader", + "debug_getRawBlock", + "debug_getRawReceipts", + "debug_getRawTransaction", + "debug_setHead", + "debug_seedHash", + "debug_dumpBlock", + "debug_chaindbProperty", + "debug_chaindbCompact", + "debug_verbosity", + "debug_vmodule", + "debug_backtraceAt", + "debug_stacks", + "debug_freeOSMemory", + "debug_setGCPercent", + "debug_memStats", + "debug_gcStats", + "debug_cpuProfile", + "debug_startCPUProfile", + "debug_stopCPUProfile", + "debug_goTrace", + // Tx pool + "txpool_content", + "txpool_inspect", + "txpool_status", + "txpool_contentFrom", + // Clique + "clique_getSnapshot", + "clique_getSnapshotAtHash", + "clique_getSigner", + "clique_getSigners", + "clique_getSignersAtHash", + "clique_proposals", + "clique_propose", + "clique_discard", + "clique_status", + // Miner + "miner_getHashrate", + "miner_setExtra", + "miner_setGasPrice", + "miner_setRecommitInterval", + "miner_start", + "miner_stop", + "miner_setEtherbase", + "miner_setGasLimit", + // Personal + "personal_listAccounts", + "personal_deriveAccount", + "personal_ecRecover", + "personal_importRawKey", + "personal_listWallets", + "personal_newAccount", + "personal_openWallet", + "personal_sendTransaction", + "personal_sign", + "personal_signTransaction", + "personal_unlockAccount", + "personal_lockAccount", + "personal_unpair", + "personal_initializeWallet", + "personal_initializeWallets", + } + + client, err := rpc.DialContext(context.Background(), config.rpcURL.String()) + require.NoError(t, err) + + for _, rpcMethod := range rpcMethods { + t.Run(fmt.Sprintf("RPCDisabled_%s", rpcMethod), func(t *testing.T) { + var res string + err = client.Call(&res, rpcMethod) + require.Error(t, err) + // + require.Equal(t, fmt.Sprintf("the method %s does not exist/is not available", rpcMethod), err.Error()) + }) + } +} + +func TestImmutable_RPCDisableFlagsOmitted(t *testing.T) { + // Some of these flags are disabled in remote environments but not for local tests but + // it is to check locally the counter when the disabled flags are omitted + isLocalhost := strings.Contains(config.rpcURL.String(), "localhost") || strings.Contains(config.rpcURL.String(), "127.0.0.1") + if !isLocalhost { + t.Skip("Skipping this test because this test is only applicable to localhost setup") + } + + fileName := "1" + // This tests outputs material, defer func to clean it up to avoid duplicate write errors + defer func() { + // Tests will be run from tests/immutable, file will be saved at root + relPath := path.Join("../../", fileName) + + _, err := os.Stat(relPath) + if err == nil { + // Rm the test file + err := os.Remove(relPath) + if err != nil { + t.Fatal(err) + } + } + if err != nil { + if errors.Is(err, os.ErrNotExist) { + t.Fatalf("Test file %s does not exist, expected test to write test file", fileName) + } + + t.Fatal(err) + } + }() + + rpcMethods := map[string][]interface{}{ + "admin_exportChain": {fileName}, + "admin_nodeInfo": {}, // No arguments required + } + + client, err := rpc.DialContext(context.Background(), config.rpcURL.String()) + require.NoError(t, err) + + for rpcMethod, args := range rpcMethods { + t.Run(fmt.Sprintf("RPCEnabled_%s", rpcMethod), func(t *testing.T) { + var res interface{} + if len(args) > 0 { + err = client.Call(&res, rpcMethod, args...) + } else { + err = client.Call(&res, rpcMethod) + } + require.NoError(t, err) + }) + } +} diff --git a/tests/immutable/main_test.go b/tests/immutable/main_test.go new file mode 100644 index 000000000..7659580f2 --- /dev/null +++ b/tests/immutable/main_test.go @@ -0,0 +1,122 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package test + +import ( + "flag" + "log" + "net/url" + "os" + "testing" + "time" + + "github.com/ethereum/go-ethereum/cmd/geth/immutable/settings" +) + +// flags constitutes flag inputs +var flags struct { + PrivKeyFilepath string + BlockedPrivKeyFilepath string + RPCURLStr string + ValidatorAdminURLStr string + SkipVoting bool + NetworkForks string +} + +// config is rendered from env and flags +var config struct { + rpcURL *url.URL + validatorURL *url.URL + testUser *Wallet + blockedUser *Wallet + skipVoting bool + cancun *settings.Fork +} + +func skipCancun(t *testing.T) bool { + t.Helper() + if config.cancun != nil && !config.cancun.IsEnabledAt(time.Now().Unix()) { + t.Logf("Skipping test because now (%v) is before the Cancun fork (%v)", time.Now().UTC(), config.cancun.Time.UTC()) + return true + } + return false +} + +func TestMain(m *testing.M) { + // Read flags + flag.StringVar(&flags.RPCURLStr, "rpc", "http://localhost:8545", "RPC URL of the node") + flag.StringVar(&flags.ValidatorAdminURLStr, "validatoradmin", "http://localhost:8550", "RPC URL of the validator admin endpoint") + flag.StringVar(&flags.PrivKeyFilepath, "privkey", "", "Filepath to private key to use for sending txs") + flag.StringVar(&flags.BlockedPrivKeyFilepath, "blockedprivkey", "", "Filepath to bloccked private key to use for simulating blocked user") + flag.BoolVar(&flags.SkipVoting, "skipvoting", true, "If true, voting tests will be skipped") + flag.StringVar(&flags.NetworkForks, "forks", "", "If (devnet, testnet, mainnet) is set, the test will skip forks before the timestamp of the network") + flag.Parse() + + // Load the wallet + var user *Wallet + var err error + if flags.PrivKeyFilepath != "" { + user, err = loadWalletFromFile(flags.PrivKeyFilepath) + if err != nil { + log.Fatal(err) + } + } else { + privKeyHex := os.Getenv("PRIV_KEY") + if privKeyHex == "" { + log.Fatal("-privkey flag and PRIV_KEY env var not set") + } + user, err = loadWallet(privKeyHex) + if err != nil { + log.Fatal(err) + } + } + + config.testUser = user + + if flags.BlockedPrivKeyFilepath != "" { + blockedUser, err := loadWalletFromFile(flags.BlockedPrivKeyFilepath) + if err != nil { + log.Fatal(err) + } + config.blockedUser = blockedUser + } + + // Render the rpc url + url, err := url.Parse(flags.RPCURLStr) + if err != nil { + log.Fatal("failed to parse RPC URL: ", err) + } + config.rpcURL = url + + // Render the validator admin url + valurl, err := url.Parse(flags.ValidatorAdminURLStr) + if err != nil { + log.Fatal("failed to parse Validator Admin URL: ", err) + } + config.validatorURL = valurl + config.skipVoting = flags.SkipVoting + if flags.NetworkForks != "" { + network, err := settings.NewNetwork(flags.NetworkForks) + if err != nil { + log.Fatal(err) + } + cancun := network.Cancun() + config.cancun = &cancun + } + // Run the tests + os.Exit(m.Run()) +} diff --git a/tests/immutable/price_test.go b/tests/immutable/price_test.go new file mode 100644 index 000000000..6c89a7c8c --- /dev/null +++ b/tests/immutable/price_test.go @@ -0,0 +1,119 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package test + +import ( + "context" + "math/big" + "testing" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/cmd/geth/immutable/settings" + "github.com/ethereum/go-ethereum/tests/immutable/erc20" + "github.com/ethereum/go-ethereum/tests/immutable/evm" + "github.com/stretchr/testify/require" +) + +var ( + priceLimit = big.NewInt(0).SetUint64(settings.PriceLimit) + underPriceLimit = big.NewInt(0).SetUint64(settings.PriceLimit - 1) +) + +func TestImmutablePriceLimit_SuggestTipCap_IsAbovePriceLimit(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + client, err := evm.NewClient(ctx, config.rpcURL, 1, 1) + require.NoError(t, err) + + tipCap, err := client.EthClient().SuggestGasTipCap(ctx) + require.NoError(t, err) + require.Equal(t, priceLimit.Uint64(), tipCap.Uint64()) + t.Log("Suggested tip cap:", tipCap) +} + +func TestImmutablePriceLimit_PricedAboveLimit(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + client, err := evm.NewClient(ctx, config.rpcURL, 1, 1) + require.NoError(t, err) + + // Check that base fee is below price limit + block, err := client.BlockNumber(ctx) + require.NoError(t, err) + feeHistory, err := client.EthClient().FeeHistory(ctx, 1, new(big.Int).SetUint64(block), nil) + require.NoError(t, err) + require.NotEmpty(t, feeHistory.BaseFee) + require.Less(t, feeHistory.BaseFee[0].Uint64(), priceLimit.Uint64()) + t.Log("Base fee:", feeHistory.BaseFee[0].Uint64()) + + // Dynamic tx above price limit but below price limit + basefee + txOpts := EIP1559TxOpts(t, ctx, client.EthClient(), config.testUser) + txOpts.GasFeeCap = priceLimit + txOpts.GasTipCap = priceLimit + t.Log("Sending priced 1559 tx") + testPricedTx(t, ctx, client, txOpts) + + // Legacy tx above price limit but below price limit + basefee + txOpts = legacyTxOpts(t, ctx, client.EthClient(), config.testUser) + txOpts.GasPrice = priceLimit + t.Log("Sending priced legacy tx") + testPricedTx(t, ctx, client, txOpts) +} + +func TestImmutablePriceLimit_PricedBelowLimit(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + client, err := evm.NewClient(ctx, config.rpcURL, 1, 1) + require.NoError(t, err) + + // Dynamic tx below price limit + txOpts := EIP1559TxOpts(t, ctx, client.EthClient(), config.testUser) + txOpts.GasFeeCap = priceLimit + txOpts.GasTipCap = underPriceLimit + t.Log("Sending underpriced 1559 tx") + testUnderPricedTx(t, ctx, client, txOpts) + + // Legacy tx below price limit + txOpts = legacyTxOpts(t, ctx, client.EthClient(), config.testUser) + txOpts.GasPrice = underPriceLimit + t.Log("Sending underpriced legacy tx") + testUnderPricedTx(t, ctx, client, txOpts) +} + +// testPricedTx verifies that a tx is accepted by RPC and mined +func testPricedTx(t *testing.T, ctx context.Context, client *evm.Client, txOpts *bind.TransactOpts) { + t.Helper() + + // Send tx + _, deployTx, _, err := erc20.DeployERC20(txOpts, client.EthClient()) + require.NoError(t, err) + + // Wait for tx to be mined + _, err = bind.WaitMined(ctx, client, deployTx) + require.NoError(t, err) +} + +// testUnderPricedTx verifies that a tx is rejected by RPC +func testUnderPricedTx(t *testing.T, ctx context.Context, client *evm.Client, txOpts *bind.TransactOpts) { + t.Helper() + + // Send tx + _, _, _, err := erc20.DeployERC20(txOpts, client.EthClient()) + require.Error(t, err) + require.Contains(t, err.Error(), "transaction underpriced") +} diff --git a/tests/immutable/randao/abi.json b/tests/immutable/randao/abi.json new file mode 100644 index 000000000..988e2c47f --- /dev/null +++ b/tests/immutable/randao/abi.json @@ -0,0 +1,28 @@ +[ + { + "type": "function", + "name": "difficulty", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "rand", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + } +] \ No newline at end of file diff --git a/tests/immutable/randao/bytecode.bin b/tests/immutable/randao/bytecode.bin new file mode 100644 index 000000000..9a4c44c95 --- /dev/null +++ b/tests/immutable/randao/bytecode.bin @@ -0,0 +1 @@ +0x6080604052348015600f57600080fd5b5060808061001e6000396000f3fe6080604052348015600f57600080fd5b506004361060325760003560e01c806319cae4621460375780633b3dca76146037575b600080fd5b4460405190815260200160405180910390f3fea264697066735822122073d353e8eb69c22f774d562d71a74ea7cb9835935aa5f222c0c41eec2c15a7c364736f6c63430008180033 \ No newline at end of file diff --git a/tests/immutable/randao/randao.go b/tests/immutable/randao/randao.go new file mode 100644 index 000000000..3af1324fd --- /dev/null +++ b/tests/immutable/randao/randao.go @@ -0,0 +1,265 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package randao + +import ( + "errors" + "math/big" + "strings" + + 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 +) + +// RandaoMetaData contains all meta data concerning the Randao contract. +var RandaoMetaData = &bind.MetaData{ + ABI: "[{\"type\":\"function\",\"name\":\"difficulty\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"rand\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"}]", + Bin: "0x6080604052348015600f57600080fd5b5060808061001e6000396000f3fe6080604052348015600f57600080fd5b506004361060325760003560e01c806319cae4621460375780633b3dca76146037575b600080fd5b4460405190815260200160405180910390f3fea264697066735822122073d353e8eb69c22f774d562d71a74ea7cb9835935aa5f222c0c41eec2c15a7c364736f6c63430008180033", +} + +// RandaoABI is the input ABI used to generate the binding from. +// Deprecated: Use RandaoMetaData.ABI instead. +var RandaoABI = RandaoMetaData.ABI + +// RandaoBin is the compiled bytecode used for deploying new contracts. +// Deprecated: Use RandaoMetaData.Bin instead. +var RandaoBin = RandaoMetaData.Bin + +// DeployRandao deploys a new Ethereum contract, binding an instance of Randao to it. +func DeployRandao(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *Randao, error) { + parsed, err := RandaoMetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(RandaoBin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &Randao{RandaoCaller: RandaoCaller{contract: contract}, RandaoTransactor: RandaoTransactor{contract: contract}, RandaoFilterer: RandaoFilterer{contract: contract}}, nil +} + +// Randao is an auto generated Go binding around an Ethereum contract. +type Randao struct { + RandaoCaller // Read-only binding to the contract + RandaoTransactor // Write-only binding to the contract + RandaoFilterer // Log filterer for contract events +} + +// RandaoCaller is an auto generated read-only Go binding around an Ethereum contract. +type RandaoCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// RandaoTransactor is an auto generated write-only Go binding around an Ethereum contract. +type RandaoTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// RandaoFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type RandaoFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// RandaoSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type RandaoSession struct { + Contract *Randao // 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 +} + +// RandaoCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type RandaoCallerSession struct { + Contract *RandaoCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// RandaoTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type RandaoTransactorSession struct { + Contract *RandaoTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// RandaoRaw is an auto generated low-level Go binding around an Ethereum contract. +type RandaoRaw struct { + Contract *Randao // Generic contract binding to access the raw methods on +} + +// RandaoCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type RandaoCallerRaw struct { + Contract *RandaoCaller // Generic read-only contract binding to access the raw methods on +} + +// RandaoTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type RandaoTransactorRaw struct { + Contract *RandaoTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewRandao creates a new instance of Randao, bound to a specific deployed contract. +func NewRandao(address common.Address, backend bind.ContractBackend) (*Randao, error) { + contract, err := bindRandao(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &Randao{RandaoCaller: RandaoCaller{contract: contract}, RandaoTransactor: RandaoTransactor{contract: contract}, RandaoFilterer: RandaoFilterer{contract: contract}}, nil +} + +// NewRandaoCaller creates a new read-only instance of Randao, bound to a specific deployed contract. +func NewRandaoCaller(address common.Address, caller bind.ContractCaller) (*RandaoCaller, error) { + contract, err := bindRandao(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &RandaoCaller{contract: contract}, nil +} + +// NewRandaoTransactor creates a new write-only instance of Randao, bound to a specific deployed contract. +func NewRandaoTransactor(address common.Address, transactor bind.ContractTransactor) (*RandaoTransactor, error) { + contract, err := bindRandao(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &RandaoTransactor{contract: contract}, nil +} + +// NewRandaoFilterer creates a new log filterer instance of Randao, bound to a specific deployed contract. +func NewRandaoFilterer(address common.Address, filterer bind.ContractFilterer) (*RandaoFilterer, error) { + contract, err := bindRandao(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &RandaoFilterer{contract: contract}, nil +} + +// bindRandao binds a generic wrapper to an already deployed contract. +func bindRandao(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := RandaoMetaData.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 (_Randao *RandaoRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _Randao.Contract.RandaoCaller.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 (_Randao *RandaoRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Randao.Contract.RandaoTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Randao *RandaoRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Randao.Contract.RandaoTransactor.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 (_Randao *RandaoCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _Randao.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 (_Randao *RandaoTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Randao.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Randao *RandaoTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Randao.Contract.contract.Transact(opts, method, params...) +} + +// Difficulty is a free data retrieval call binding the contract method 0x19cae462. +// +// Solidity: function difficulty() view returns(uint256) +func (_Randao *RandaoCaller) Difficulty(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _Randao.contract.Call(opts, &out, "difficulty") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// Difficulty is a free data retrieval call binding the contract method 0x19cae462. +// +// Solidity: function difficulty() view returns(uint256) +func (_Randao *RandaoSession) Difficulty() (*big.Int, error) { + return _Randao.Contract.Difficulty(&_Randao.CallOpts) +} + +// Difficulty is a free data retrieval call binding the contract method 0x19cae462. +// +// Solidity: function difficulty() view returns(uint256) +func (_Randao *RandaoCallerSession) Difficulty() (*big.Int, error) { + return _Randao.Contract.Difficulty(&_Randao.CallOpts) +} + +// Rand is a free data retrieval call binding the contract method 0x3b3dca76. +// +// Solidity: function rand() view returns(uint256) +func (_Randao *RandaoCaller) Rand(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _Randao.contract.Call(opts, &out, "rand") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// Rand is a free data retrieval call binding the contract method 0x3b3dca76. +// +// Solidity: function rand() view returns(uint256) +func (_Randao *RandaoSession) Rand() (*big.Int, error) { + return _Randao.Contract.Rand(&_Randao.CallOpts) +} + +// Rand is a free data retrieval call binding the contract method 0x3b3dca76. +// +// Solidity: function rand() view returns(uint256) +func (_Randao *RandaoCallerSession) Rand() (*big.Int, error) { + return _Randao.Contract.Rand(&_Randao.CallOpts) +} diff --git a/tests/immutable/randao/randao.sol b/tests/immutable/randao/randao.sol new file mode 100644 index 000000000..3187f4778 --- /dev/null +++ b/tests/immutable/randao/randao.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +contract RandDao { + function rand() external view returns (uint) { + return block.prevrandao; + } + function difficulty() external view returns (uint) { + return block.difficulty; + } +} + +// build/bin/abigen --abi ./tests/immutable/randao/abi.json --pkg randao --type Randao --out ./tests/immutable/randao/randao.go --bin ./tests/immutable/randao/bytecode.bin diff --git a/tests/immutable/randao_test.go b/tests/immutable/randao_test.go new file mode 100644 index 000000000..4a2d112e6 --- /dev/null +++ b/tests/immutable/randao_test.go @@ -0,0 +1,56 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package test + +import ( + "context" + "testing" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/tests/immutable/evm" + "github.com/ethereum/go-ethereum/tests/immutable/randao" + "github.com/stretchr/testify/require" +) + +func TestImmutable_RandaoOpCode_ShouldDeployAndReturnZero(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + + // Client and 1559 + client, err := evm.NewClient(ctx, config.rpcURL, 1, 1) + require.NoError(t, err) + txOpts := EIP1559TxOpts(t, ctx, client.EthClient(), config.testUser) + + // Deploy Contract + _, deployTx, contract, err := randao.DeployRandao(txOpts, client.EthClient()) + require.NoError(t, err) + deployReceipt, err := bind.WaitMined(ctx, client, deployTx) + require.NoError(t, err) + require.Equal(t, deployReceipt.Status, uint64(1)) + t.Logf("Contract Deployed to address %s with TX Hash %s", deployReceipt.ContractAddress.String(), deployTx.Hash().String()) + + // Transact + opts := &bind.CallOpts{Context: ctx} + rand, err := contract.Rand(opts) + require.NoError(t, err) + require.Equal(t, uint64(0), rand.Uint64()) + + diff, err := contract.Difficulty(opts) + require.NoError(t, err) + require.Equal(t, uint64(0), diff.Uint64()) +} diff --git a/tests/immutable/shanghai/abi.json b/tests/immutable/shanghai/abi.json new file mode 100644 index 000000000..acfc3992f --- /dev/null +++ b/tests/immutable/shanghai/abi.json @@ -0,0 +1,9 @@ +[ + { + "inputs": [], + "name": "SetCoinbase", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/tests/immutable/shanghai/bytecode.bin b/tests/immutable/shanghai/bytecode.bin new file mode 100644 index 000000000..6efc7ef1e --- /dev/null +++ b/tests/immutable/shanghai/bytecode.bin @@ -0,0 +1 @@ +6080604052348015600e575f80fd5b50606f80601a5f395ff3fe6080604052348015600e575f80fd5b50600436106026575f3560e01c8063de4436a614602a575b5f80fd5b60306032565b005b5f4190505056fea2646970667358221220ac8ec15e4867e5d8e3f28efa2be3d944029f496c1c0465c0f2fd611ac8d7d3e664736f6c63430008140033 \ No newline at end of file diff --git a/tests/immutable/shanghai/shanghai.go b/tests/immutable/shanghai/shanghai.go new file mode 100644 index 000000000..1a699843e --- /dev/null +++ b/tests/immutable/shanghai/shanghai.go @@ -0,0 +1,224 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package shanghai + +import ( + "errors" + "math/big" + "strings" + + 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 +) + +// ShanghaiMetaData contains all meta data concerning the Shanghai contract. +var ShanghaiMetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[],\"name\":\"SetCoinbase\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + Bin: "0x6080604052348015600e575f80fd5b50606f80601a5f395ff3fe6080604052348015600e575f80fd5b50600436106026575f3560e01c8063de4436a614602a575b5f80fd5b60306032565b005b5f4190505056fea2646970667358221220ac8ec15e4867e5d8e3f28efa2be3d944029f496c1c0465c0f2fd611ac8d7d3e664736f6c63430008140033", +} + +// ShanghaiABI is the input ABI used to generate the binding from. +// Deprecated: Use ShanghaiMetaData.ABI instead. +var ShanghaiABI = ShanghaiMetaData.ABI + +// ShanghaiBin is the compiled bytecode used for deploying new contracts. +// Deprecated: Use ShanghaiMetaData.Bin instead. +var ShanghaiBin = ShanghaiMetaData.Bin + +// DeployShanghai deploys a new Ethereum contract, binding an instance of Shanghai to it. +func DeployShanghai(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *Shanghai, error) { + parsed, err := ShanghaiMetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(ShanghaiBin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &Shanghai{ShanghaiCaller: ShanghaiCaller{contract: contract}, ShanghaiTransactor: ShanghaiTransactor{contract: contract}, ShanghaiFilterer: ShanghaiFilterer{contract: contract}}, nil +} + +// Shanghai is an auto generated Go binding around an Ethereum contract. +type Shanghai struct { + ShanghaiCaller // Read-only binding to the contract + ShanghaiTransactor // Write-only binding to the contract + ShanghaiFilterer // Log filterer for contract events +} + +// ShanghaiCaller is an auto generated read-only Go binding around an Ethereum contract. +type ShanghaiCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ShanghaiTransactor is an auto generated write-only Go binding around an Ethereum contract. +type ShanghaiTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ShanghaiFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type ShanghaiFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ShanghaiSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type ShanghaiSession struct { + Contract *Shanghai // 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 +} + +// ShanghaiCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type ShanghaiCallerSession struct { + Contract *ShanghaiCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// ShanghaiTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type ShanghaiTransactorSession struct { + Contract *ShanghaiTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// ShanghaiRaw is an auto generated low-level Go binding around an Ethereum contract. +type ShanghaiRaw struct { + Contract *Shanghai // Generic contract binding to access the raw methods on +} + +// ShanghaiCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type ShanghaiCallerRaw struct { + Contract *ShanghaiCaller // Generic read-only contract binding to access the raw methods on +} + +// ShanghaiTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type ShanghaiTransactorRaw struct { + Contract *ShanghaiTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewShanghai creates a new instance of Shanghai, bound to a specific deployed contract. +func NewShanghai(address common.Address, backend bind.ContractBackend) (*Shanghai, error) { + contract, err := bindShanghai(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &Shanghai{ShanghaiCaller: ShanghaiCaller{contract: contract}, ShanghaiTransactor: ShanghaiTransactor{contract: contract}, ShanghaiFilterer: ShanghaiFilterer{contract: contract}}, nil +} + +// NewShanghaiCaller creates a new read-only instance of Shanghai, bound to a specific deployed contract. +func NewShanghaiCaller(address common.Address, caller bind.ContractCaller) (*ShanghaiCaller, error) { + contract, err := bindShanghai(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &ShanghaiCaller{contract: contract}, nil +} + +// NewShanghaiTransactor creates a new write-only instance of Shanghai, bound to a specific deployed contract. +func NewShanghaiTransactor(address common.Address, transactor bind.ContractTransactor) (*ShanghaiTransactor, error) { + contract, err := bindShanghai(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &ShanghaiTransactor{contract: contract}, nil +} + +// NewShanghaiFilterer creates a new log filterer instance of Shanghai, bound to a specific deployed contract. +func NewShanghaiFilterer(address common.Address, filterer bind.ContractFilterer) (*ShanghaiFilterer, error) { + contract, err := bindShanghai(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &ShanghaiFilterer{contract: contract}, nil +} + +// bindShanghai binds a generic wrapper to an already deployed contract. +func bindShanghai(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := ShanghaiMetaData.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 (_Shanghai *ShanghaiRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _Shanghai.Contract.ShanghaiCaller.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 (_Shanghai *ShanghaiRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Shanghai.Contract.ShanghaiTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Shanghai *ShanghaiRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Shanghai.Contract.ShanghaiTransactor.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 (_Shanghai *ShanghaiCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _Shanghai.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 (_Shanghai *ShanghaiTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Shanghai.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Shanghai *ShanghaiTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Shanghai.Contract.contract.Transact(opts, method, params...) +} + +// SetCoinbase is a paid mutator transaction binding the contract method 0xde4436a6. +// +// Solidity: function SetCoinbase() returns() +func (_Shanghai *ShanghaiTransactor) SetCoinbase(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Shanghai.contract.Transact(opts, "SetCoinbase") +} + +// SetCoinbase is a paid mutator transaction binding the contract method 0xde4436a6. +// +// Solidity: function SetCoinbase() returns() +func (_Shanghai *ShanghaiSession) SetCoinbase() (*types.Transaction, error) { + return _Shanghai.Contract.SetCoinbase(&_Shanghai.TransactOpts) +} + +// SetCoinbase is a paid mutator transaction binding the contract method 0xde4436a6. +// +// Solidity: function SetCoinbase() returns() +func (_Shanghai *ShanghaiTransactorSession) SetCoinbase() (*types.Transaction, error) { + return _Shanghai.Contract.SetCoinbase(&_Shanghai.TransactOpts) +} diff --git a/tests/immutable/shanghai/shanghai.sol b/tests/immutable/shanghai/shanghai.sol new file mode 100644 index 000000000..d7fd4c767 --- /dev/null +++ b/tests/immutable/shanghai/shanghai.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +contract Shanghai { + function SetCoinbase() external { + address coinbase = block.coinbase; + } +} + +// build/bin/abigen --abi ./tests/immutable/shanghai/abi.json --pkg shanghai --type Shanghai --out ./tests/immutable/shanghai/shanghai.go --bin ./tests/immutable/shanghai/bytecode.bin \ No newline at end of file diff --git a/tests/immutable/shanghai_test.go b/tests/immutable/shanghai_test.go new file mode 100644 index 000000000..ee68c1d29 --- /dev/null +++ b/tests/immutable/shanghai_test.go @@ -0,0 +1,58 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package test + +import ( + "context" + "testing" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/tests/immutable/evm" + "github.com/ethereum/go-ethereum/tests/immutable/shanghai" + "github.com/stretchr/testify/require" +) + +func TestImmutableShanghai_ShanghaiByteCode_ValidatesPush0AndCoinbaseGas(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + + // Client and 1559 + client, err := evm.NewClient(ctx, config.rpcURL, 1, 1) + require.NoError(t, err) + txOpts := EIP1559TxOpts(t, ctx, client.EthClient(), config.testUser) + + // Deploy the contract, which must have been compiled with solidity version >=0.8.20 + _, deployTx, contract, err := shanghai.DeployShanghai(txOpts, client.EthClient()) + require.NoError(t, err) + deployReceipt, err := bind.WaitMined(ctx, client, deployTx) + require.Equal(t, deployReceipt.Status, uint64(1)) + require.NoError(t, err) + t.Logf("Contract deployed to address %s with TX Hash %s", deployReceipt.ContractAddress.String(), deployTx.Hash().String()) + + // Transact + tx, err := contract.SetCoinbase(txOpts) + require.NoError(t, err) + expectedGasLimit := uint64(400) // Warm coinbase read should be far less than 2100 + intrinsicGas := uint64(21000) // Transactions always cost at least 21000 gas + estimatedGas := tx.Gas() - intrinsicGas + require.Less(t, estimatedGas, expectedGasLimit) + transactReceipt, err := bind.WaitMined(ctx, client, tx) + require.NoError(t, err) + actualGas := transactReceipt.GasUsed - intrinsicGas + require.Less(t, actualGas, expectedGasLimit) +} diff --git a/tests/immutable/utils.go b/tests/immutable/utils.go new file mode 100644 index 000000000..a99c419a2 --- /dev/null +++ b/tests/immutable/utils.go @@ -0,0 +1,104 @@ +// Copyright 2024 The Immutable go-ethereum Authors +// This file is part of the Immutable go-ethereum library. +// +// The Immutable go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Immutable go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Immutable go-ethereum library. If not, see . + +package test + +import ( + "bytes" + "context" + "crypto/ecdsa" + "fmt" + "math/big" + "os" + "testing" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/cmd/geth/immutable/settings" + "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" + "github.com/stretchr/testify/require" +) + +// Wallet represents an account/wallet which holds currency +type Wallet struct { + key *ecdsa.PrivateKey + address common.Address +} + +func loadWalletFromFile(filepath string) (*Wallet, error) { + privKeyFileBytes, err := os.ReadFile(filepath) + if err != nil { + return nil, fmt.Errorf("priv key read file: %w", err) + } + privKeyFileBytes = bytes.ReplaceAll(privKeyFileBytes, []byte{'\n'}, []byte{}) + return loadWallet(string(privKeyFileBytes)) +} + +func loadWallet(privKeyHex string) (*Wallet, error) { + privKey, err := crypto.HexToECDSA(privKeyHex) + if err != nil { + return nil, fmt.Errorf("priv key from bytes: %w", err) + } + address := crypto.PubkeyToAddress(privKey.PublicKey) + return &Wallet{key: privKey, address: address}, nil +} + +func legacyTxOpts(t *testing.T, ctx context.Context, client *ethclient.Client, user *Wallet) *bind.TransactOpts { + t.Helper() + + chainID, err := client.ChainID(ctx) + require.NoError(t, err) + + opts, err := bind.NewKeyedTransactorWithChainID(user.key, chainID) + require.NoError(t, err) + + // Set gasprice above price limit + opts.GasPrice = big.NewInt(0).SetUint64(settings.PriceLimit * 2) + return opts +} + +func EIP1559TxOpts(t *testing.T, ctx context.Context, client *ethclient.Client, user *Wallet) *bind.TransactOpts { + t.Helper() + + chainID, err := client.ChainID(ctx) + require.NoError(t, err) + + return &bind.TransactOpts{ + Signer: func(a common.Address, t *types.Transaction) (*types.Transaction, error) { + return types.SignTx(t, types.NewLondonSigner(chainID), user.key) + }, + From: user.address, + // Nonce nil, otherwise we need to increment or call PendingNonce + // between transactions ourselves + Nonce: nil, + // Leaving these as nil allows go-ethereum to calculate the values + // based on suggested prices from the chain + GasTipCap: nil, + GasFeeCap: nil, + } +} + +func genRandPubKey(t *testing.T) common.Address { + t.Helper() + privateKey, err := crypto.GenerateKey() + require.NoError(t, err) + publicKey := privateKey.Public() + publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) + require.True(t, ok) + return crypto.PubkeyToAddress(*publicKeyECDSA) +} diff --git a/tests/state_test.go b/tests/state_test.go index 1d749d8bc..30e748ad6 100644 --- a/tests/state_test.go +++ b/tests/state_test.go @@ -284,7 +284,7 @@ func runBenchmark(b *testing.B, t *StateTest) { // Prepare the EVM. txContext := core.NewEVMTxContext(msg) - context := core.NewEVMBlockContext(block.Header(), nil, &t.json.Env.Coinbase) + context := core.NewEVMBlockContext(block.Header(), nil, &t.json.Env.Coinbase, config) context.GetHash = vmTestBlockHash context.BaseFee = baseFee evm := vm.NewEVM(context, txContext, state.StateDB, config, vmconfig) diff --git a/tests/state_test_util.go b/tests/state_test_util.go index c916d26d4..6d750e591 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -277,7 +277,7 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh // Prepare the EVM. txContext := core.NewEVMTxContext(msg) - context := core.NewEVMBlockContext(block.Header(), nil, &t.json.Env.Coinbase) + context := core.NewEVMBlockContext(block.Header(), nil, &t.json.Env.Coinbase, config) context.GetHash = vmTestBlockHash context.BaseFee = baseFee context.Random = nil