diff --git a/Cargo.lock b/Cargo.lock index 9efdb18dc..f82b94070 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -257,7 +257,7 @@ dependencies = [ [[package]] name = "alloy-evm" version = "0.20.1" -source = "git+https://github.com/SeismicSystems/seismic-evm.git?rev=86a0ecf8e7148eb04cb8a4371c0f1f97004ec84f#86a0ecf8e7148eb04cb8a4371c0f1f97004ec84f" +source = "git+https://github.com/SeismicSystems/seismic-evm.git?rev=c33f3076495d153eefbde07dd81c3a575f3d19b8#c33f3076495d153eefbde07dd81c3a575f3d19b8" dependencies = [ "alloy-consensus", "alloy-eips", @@ -678,7 +678,7 @@ dependencies = [ [[package]] name = "alloy-seismic-evm" version = "0.20.1" -source = "git+https://github.com/SeismicSystems/seismic-evm.git?rev=86a0ecf8e7148eb04cb8a4371c0f1f97004ec84f#86a0ecf8e7148eb04cb8a4371c0f1f97004ec84f" +source = "git+https://github.com/SeismicSystems/seismic-evm.git?rev=c33f3076495d153eefbde07dd81c3a575f3d19b8#c33f3076495d153eefbde07dd81c3a575f3d19b8" dependencies = [ "alloy-consensus", "alloy-eips", @@ -5514,7 +5514,7 @@ dependencies = [ [[package]] name = "op-revm" version = "10.0.0" -source = "git+https://github.com/SeismicSystems/seismic-revm.git?rev=dc05eece19f14516ab9f996611fa7c63e1d1a8ae#dc05eece19f14516ab9f996611fa7c63e1d1a8ae" +source = "git+https://github.com/SeismicSystems/seismic-revm.git?rev=2861f865d2b9249ebb0da4946be5052ec8ab41a0#2861f865d2b9249ebb0da4946be5052ec8ab41a0" dependencies = [ "auto_impl", "revm", @@ -9502,6 +9502,7 @@ dependencies = [ "c-kzg", "derive_more 2.1.1", "reth-chainspec", + "reth-execution-types", "reth-primitives-traits", "reth-provider", "reth-seismic-chainspec", @@ -9509,6 +9510,7 @@ dependencies = [ "reth-transaction-pool", "seismic-alloy-consensus", "tokio", + "tracing", ] [[package]] @@ -10054,7 +10056,7 @@ dependencies = [ [[package]] name = "revm" version = "29.0.0" -source = "git+https://github.com/SeismicSystems/seismic-revm.git?rev=dc05eece19f14516ab9f996611fa7c63e1d1a8ae#dc05eece19f14516ab9f996611fa7c63e1d1a8ae" +source = "git+https://github.com/SeismicSystems/seismic-revm.git?rev=2861f865d2b9249ebb0da4946be5052ec8ab41a0#2861f865d2b9249ebb0da4946be5052ec8ab41a0" dependencies = [ "revm-bytecode", "revm-context", @@ -10072,7 +10074,7 @@ dependencies = [ [[package]] name = "revm-bytecode" version = "6.2.2" -source = "git+https://github.com/SeismicSystems/seismic-revm.git?rev=dc05eece19f14516ab9f996611fa7c63e1d1a8ae#dc05eece19f14516ab9f996611fa7c63e1d1a8ae" +source = "git+https://github.com/SeismicSystems/seismic-revm.git?rev=2861f865d2b9249ebb0da4946be5052ec8ab41a0#2861f865d2b9249ebb0da4946be5052ec8ab41a0" dependencies = [ "bitvec", "phf", @@ -10083,7 +10085,7 @@ dependencies = [ [[package]] name = "revm-context" version = "9.0.2" -source = "git+https://github.com/SeismicSystems/seismic-revm.git?rev=dc05eece19f14516ab9f996611fa7c63e1d1a8ae#dc05eece19f14516ab9f996611fa7c63e1d1a8ae" +source = "git+https://github.com/SeismicSystems/seismic-revm.git?rev=2861f865d2b9249ebb0da4946be5052ec8ab41a0#2861f865d2b9249ebb0da4946be5052ec8ab41a0" dependencies = [ "bitvec", "cfg-if", @@ -10099,7 +10101,7 @@ dependencies = [ [[package]] name = "revm-context-interface" version = "10.1.0" -source = "git+https://github.com/SeismicSystems/seismic-revm.git?rev=dc05eece19f14516ab9f996611fa7c63e1d1a8ae#dc05eece19f14516ab9f996611fa7c63e1d1a8ae" +source = "git+https://github.com/SeismicSystems/seismic-revm.git?rev=2861f865d2b9249ebb0da4946be5052ec8ab41a0#2861f865d2b9249ebb0da4946be5052ec8ab41a0" dependencies = [ "alloy-eip2930", "alloy-eip7702", @@ -10114,7 +10116,7 @@ dependencies = [ [[package]] name = "revm-database" version = "7.0.5" -source = "git+https://github.com/SeismicSystems/seismic-revm.git?rev=dc05eece19f14516ab9f996611fa7c63e1d1a8ae#dc05eece19f14516ab9f996611fa7c63e1d1a8ae" +source = "git+https://github.com/SeismicSystems/seismic-revm.git?rev=2861f865d2b9249ebb0da4946be5052ec8ab41a0#2861f865d2b9249ebb0da4946be5052ec8ab41a0" dependencies = [ "alloy-eips", "revm-bytecode", @@ -10127,7 +10129,7 @@ dependencies = [ [[package]] name = "revm-database-interface" version = "7.0.5" -source = "git+https://github.com/SeismicSystems/seismic-revm.git?rev=dc05eece19f14516ab9f996611fa7c63e1d1a8ae#dc05eece19f14516ab9f996611fa7c63e1d1a8ae" +source = "git+https://github.com/SeismicSystems/seismic-revm.git?rev=2861f865d2b9249ebb0da4946be5052ec8ab41a0#2861f865d2b9249ebb0da4946be5052ec8ab41a0" dependencies = [ "auto_impl", "either", @@ -10139,7 +10141,7 @@ dependencies = [ [[package]] name = "revm-handler" version = "10.0.0" -source = "git+https://github.com/SeismicSystems/seismic-revm.git?rev=dc05eece19f14516ab9f996611fa7c63e1d1a8ae#dc05eece19f14516ab9f996611fa7c63e1d1a8ae" +source = "git+https://github.com/SeismicSystems/seismic-revm.git?rev=2861f865d2b9249ebb0da4946be5052ec8ab41a0#2861f865d2b9249ebb0da4946be5052ec8ab41a0" dependencies = [ "auto_impl", "derive-where", @@ -10157,7 +10159,7 @@ dependencies = [ [[package]] name = "revm-inspector" version = "10.0.0" -source = "git+https://github.com/SeismicSystems/seismic-revm.git?rev=dc05eece19f14516ab9f996611fa7c63e1d1a8ae#dc05eece19f14516ab9f996611fa7c63e1d1a8ae" +source = "git+https://github.com/SeismicSystems/seismic-revm.git?rev=2861f865d2b9249ebb0da4946be5052ec8ab41a0#2861f865d2b9249ebb0da4946be5052ec8ab41a0" dependencies = [ "auto_impl", "either", @@ -10174,7 +10176,7 @@ dependencies = [ [[package]] name = "revm-inspectors" version = "0.29.0" -source = "git+https://github.com/SeismicSystems/seismic-revm-inspectors.git?rev=e75adca2bed85c68ebecf1b130decd08693f2f0b#e75adca2bed85c68ebecf1b130decd08693f2f0b" +source = "git+https://github.com/SeismicSystems/seismic-revm-inspectors.git?rev=24652f3b9ef6a8057bc46203b26d1d9857f7ae30#24652f3b9ef6a8057bc46203b26d1d9857f7ae30" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -10197,7 +10199,7 @@ dependencies = [ [[package]] name = "revm-interpreter" version = "25.0.2" -source = "git+https://github.com/SeismicSystems/seismic-revm.git?rev=dc05eece19f14516ab9f996611fa7c63e1d1a8ae#dc05eece19f14516ab9f996611fa7c63e1d1a8ae" +source = "git+https://github.com/SeismicSystems/seismic-revm.git?rev=2861f865d2b9249ebb0da4946be5052ec8ab41a0#2861f865d2b9249ebb0da4946be5052ec8ab41a0" dependencies = [ "revm-bytecode", "revm-context-interface", @@ -10209,7 +10211,7 @@ dependencies = [ [[package]] name = "revm-precompile" version = "27.0.0" -source = "git+https://github.com/SeismicSystems/seismic-revm.git?rev=dc05eece19f14516ab9f996611fa7c63e1d1a8ae#dc05eece19f14516ab9f996611fa7c63e1d1a8ae" +source = "git+https://github.com/SeismicSystems/seismic-revm.git?rev=2861f865d2b9249ebb0da4946be5052ec8ab41a0#2861f865d2b9249ebb0da4946be5052ec8ab41a0" dependencies = [ "ark-bls12-381", "ark-bn254", @@ -10233,7 +10235,7 @@ dependencies = [ [[package]] name = "revm-primitives" version = "20.2.1" -source = "git+https://github.com/SeismicSystems/seismic-revm.git?rev=dc05eece19f14516ab9f996611fa7c63e1d1a8ae#dc05eece19f14516ab9f996611fa7c63e1d1a8ae" +source = "git+https://github.com/SeismicSystems/seismic-revm.git?rev=2861f865d2b9249ebb0da4946be5052ec8ab41a0#2861f865d2b9249ebb0da4946be5052ec8ab41a0" dependencies = [ "alloy-primitives", "num_enum", @@ -10244,7 +10246,7 @@ dependencies = [ [[package]] name = "revm-state" version = "7.0.5" -source = "git+https://github.com/SeismicSystems/seismic-revm.git?rev=dc05eece19f14516ab9f996611fa7c63e1d1a8ae#dc05eece19f14516ab9f996611fa7c63e1d1a8ae" +source = "git+https://github.com/SeismicSystems/seismic-revm.git?rev=2861f865d2b9249ebb0da4946be5052ec8ab41a0#2861f865d2b9249ebb0da4946be5052ec8ab41a0" dependencies = [ "bitflags 2.10.0", "revm-bytecode", @@ -10782,7 +10784,7 @@ dependencies = [ [[package]] name = "seismic-alloy-consensus" version = "0.0.1" -source = "git+https://github.com/SeismicSystems/seismic-alloy.git?rev=584184c5c3df59d2876ca498b1b976505e5536e8#584184c5c3df59d2876ca498b1b976505e5536e8" +source = "git+https://github.com/SeismicSystems/seismic-alloy.git?rev=c8fad0b93da53f2b423d2f6720abdb4a6cbb9082#c8fad0b93da53f2b423d2f6720abdb4a6cbb9082" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -10808,7 +10810,7 @@ dependencies = [ [[package]] name = "seismic-alloy-genesis" version = "0.0.1" -source = "git+https://github.com/SeismicSystems/seismic-alloy.git?rev=584184c5c3df59d2876ca498b1b976505e5536e8#584184c5c3df59d2876ca498b1b976505e5536e8" +source = "git+https://github.com/SeismicSystems/seismic-alloy.git?rev=c8fad0b93da53f2b423d2f6720abdb4a6cbb9082#c8fad0b93da53f2b423d2f6720abdb4a6cbb9082" dependencies = [ "alloy-genesis", "alloy-primitives", @@ -10821,7 +10823,7 @@ dependencies = [ [[package]] name = "seismic-alloy-network" version = "0.0.1" -source = "git+https://github.com/SeismicSystems/seismic-alloy.git?rev=584184c5c3df59d2876ca498b1b976505e5536e8#584184c5c3df59d2876ca498b1b976505e5536e8" +source = "git+https://github.com/SeismicSystems/seismic-alloy.git?rev=c8fad0b93da53f2b423d2f6720abdb4a6cbb9082#c8fad0b93da53f2b423d2f6720abdb4a6cbb9082" dependencies = [ "alloy-consensus", "alloy-eip7702", @@ -10848,7 +10850,7 @@ dependencies = [ [[package]] name = "seismic-alloy-provider" version = "0.0.1" -source = "git+https://github.com/SeismicSystems/seismic-alloy.git?rev=584184c5c3df59d2876ca498b1b976505e5536e8#584184c5c3df59d2876ca498b1b976505e5536e8" +source = "git+https://github.com/SeismicSystems/seismic-alloy.git?rev=c8fad0b93da53f2b423d2f6720abdb4a6cbb9082#c8fad0b93da53f2b423d2f6720abdb4a6cbb9082" dependencies = [ "alloy-contract", "alloy-network", @@ -10869,7 +10871,7 @@ dependencies = [ [[package]] name = "seismic-alloy-rpc-types" version = "0.0.1" -source = "git+https://github.com/SeismicSystems/seismic-alloy.git?rev=584184c5c3df59d2876ca498b1b976505e5536e8#584184c5c3df59d2876ca498b1b976505e5536e8" +source = "git+https://github.com/SeismicSystems/seismic-alloy.git?rev=c8fad0b93da53f2b423d2f6720abdb4a6cbb9082#c8fad0b93da53f2b423d2f6720abdb4a6cbb9082" dependencies = [ "alloy-consensus", "alloy-eips", @@ -10933,7 +10935,7 @@ dependencies = [ [[package]] name = "seismic-revm" version = "1.0.0" -source = "git+https://github.com/SeismicSystems/seismic-revm.git?rev=dc05eece19f14516ab9f996611fa7c63e1d1a8ae#dc05eece19f14516ab9f996611fa7c63e1d1a8ae" +source = "git+https://github.com/SeismicSystems/seismic-revm.git?rev=2861f865d2b9249ebb0da4946be5052ec8ab41a0#2861f865d2b9249ebb0da4946be5052ec8ab41a0" dependencies = [ "auto_impl", "hkdf", diff --git a/Cargo.toml b/Cargo.toml index d1c95c3f2..c9b9684c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -768,18 +768,18 @@ alloy-dyn-abi = { git = "https://github.com/SeismicSystems/seismic-alloy-core.gi alloy-trie = { git = "https://github.com/SeismicSystems/seismic-trie.git", rev = "2787e2265d492fa8eaee00424585b8f7e522178f" } # revm -revm = { git = "https://github.com/SeismicSystems/seismic-revm.git", rev = "dc05eece19f14516ab9f996611fa7c63e1d1a8ae" } -revm-bytecode = { git = "https://github.com/SeismicSystems/seismic-revm.git", rev = "dc05eece19f14516ab9f996611fa7c63e1d1a8ae" } -revm-database = { git = "https://github.com/SeismicSystems/seismic-revm.git", rev = "dc05eece19f14516ab9f996611fa7c63e1d1a8ae" } -revm-state = { git = "https://github.com/SeismicSystems/seismic-revm.git", rev = "dc05eece19f14516ab9f996611fa7c63e1d1a8ae" } -revm-primitives = { git = "https://github.com/SeismicSystems/seismic-revm.git", rev = "dc05eece19f14516ab9f996611fa7c63e1d1a8ae" } -revm-interpreter = { git = "https://github.com/SeismicSystems/seismic-revm.git", rev = "dc05eece19f14516ab9f996611fa7c63e1d1a8ae" } -revm-inspector = { git = "https://github.com/SeismicSystems/seismic-revm.git", rev = "dc05eece19f14516ab9f996611fa7c63e1d1a8ae" } -revm-context = { git = "https://github.com/SeismicSystems/seismic-revm.git", rev = "dc05eece19f14516ab9f996611fa7c63e1d1a8ae" } -revm-context-interface = { git = "https://github.com/SeismicSystems/seismic-revm.git", rev = "dc05eece19f14516ab9f996611fa7c63e1d1a8ae" } -revm-database-interface = { git = "https://github.com/SeismicSystems/seismic-revm.git", rev = "dc05eece19f14516ab9f996611fa7c63e1d1a8ae" } -op-revm = { git = "https://github.com/SeismicSystems/seismic-revm.git", rev = "dc05eece19f14516ab9f996611fa7c63e1d1a8ae" } -seismic-revm = { git = "https://github.com/SeismicSystems/seismic-revm.git", rev = "dc05eece19f14516ab9f996611fa7c63e1d1a8ae" } +revm = { git = "https://github.com/SeismicSystems/seismic-revm.git", rev = "2861f865d2b9249ebb0da4946be5052ec8ab41a0" } +revm-bytecode = { git = "https://github.com/SeismicSystems/seismic-revm.git", rev = "2861f865d2b9249ebb0da4946be5052ec8ab41a0" } +revm-database = { git = "https://github.com/SeismicSystems/seismic-revm.git", rev = "2861f865d2b9249ebb0da4946be5052ec8ab41a0" } +revm-state = { git = "https://github.com/SeismicSystems/seismic-revm.git", rev = "2861f865d2b9249ebb0da4946be5052ec8ab41a0" } +revm-primitives = { git = "https://github.com/SeismicSystems/seismic-revm.git", rev = "2861f865d2b9249ebb0da4946be5052ec8ab41a0" } +revm-interpreter = { git = "https://github.com/SeismicSystems/seismic-revm.git", rev = "2861f865d2b9249ebb0da4946be5052ec8ab41a0" } +revm-inspector = { git = "https://github.com/SeismicSystems/seismic-revm.git", rev = "2861f865d2b9249ebb0da4946be5052ec8ab41a0" } +revm-context = { git = "https://github.com/SeismicSystems/seismic-revm.git", rev = "2861f865d2b9249ebb0da4946be5052ec8ab41a0" } +revm-context-interface = { git = "https://github.com/SeismicSystems/seismic-revm.git", rev = "2861f865d2b9249ebb0da4946be5052ec8ab41a0" } +revm-database-interface = { git = "https://github.com/SeismicSystems/seismic-revm.git", rev = "2861f865d2b9249ebb0da4946be5052ec8ab41a0" } +op-revm = { git = "https://github.com/SeismicSystems/seismic-revm.git", rev = "2861f865d2b9249ebb0da4946be5052ec8ab41a0" } +seismic-revm = { git = "https://github.com/SeismicSystems/seismic-revm.git", rev = "2861f865d2b9249ebb0da4946be5052ec8ab41a0" } revm-inspectors = { git = "https://github.com/SeismicSystems/seismic-revm-inspectors.git", rev = "24652f3b9ef6a8057bc46203b26d1d9857f7ae30" } diff --git a/crates/e2e-test-utils/src/setup_import.rs b/crates/e2e-test-utils/src/setup_import.rs index b0232d915..268a4e457 100644 --- a/crates/e2e-test-utils/src/setup_import.rs +++ b/crates/e2e-test-utils/src/setup_import.rs @@ -11,7 +11,7 @@ use reth_db::DatabaseEnv; use reth_engine_local::LocalPayloadAttributesBuilder; use reth_evm::ConfigureEvm; use reth_node_api::{NodeTypes, NodeTypesWithDBAdapter, PayloadTypes, TreeConfig}; -use reth_node_builder::{EngineNodeLauncher, Node, NodeBuilder, NodeConfig, NodeHandle}; +use reth_node_builder::{EngineNodeLauncher, NodeBuilder, NodeConfig, NodeHandle}; use reth_node_core::args::{DiscoveryArgs, NetworkArgs, RpcServerArgs}; use reth_node_ethereum::EthereumNode; use reth_provider::{ diff --git a/crates/seismic/chainspec/res/genesis/dev.json b/crates/seismic/chainspec/res/genesis/dev.json index 2f1e0d098..f703b154d 100644 --- a/crates/seismic/chainspec/res/genesis/dev.json +++ b/crates/seismic/chainspec/res/genesis/dev.json @@ -76,11 +76,14 @@ "0x90f79bf6eb2c4f870365e785982e1f101e93b906": { "balance": "0xD3C21BCECCEDA1000000" }, - "0xd412c5ecd343e264381ff15afc0ad78a67b79f35": { + "0xD9b09DCAe1B5D2fFd36200E12f2617414D5fcC30": { "balance": "0x2000000000000000000000000000000000000000000000000000000000000000" }, "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266": { "balance": "0xD3C21BCECCEDA1000000" + }, + "0x2aA4C90C2F9D8A699A0C87B573411Db0d34BE61B":{ + "balance": "0xD3C21BCECCEDA1000000" } }, "coinbase": "0x0000000000000000000000000000000000000000", diff --git a/crates/seismic/chainspec/src/lib.rs b/crates/seismic/chainspec/src/lib.rs index f86b10217..ecaae4834 100644 --- a/crates/seismic/chainspec/src/lib.rs +++ b/crates/seismic/chainspec/src/lib.rs @@ -27,7 +27,7 @@ pub const SEISMIC_MAINNET_GENESIS_HASH: B256 = /// Calculated by rlp encoding the genesis header and hashing it /// Currently matches the mainnet genesis hash because they have matching hardforks pub const SEISMIC_DEV_GENESIS_HASH: B256 = - b256!("0x78ab9057bb67f95a6182969c5d755ac02802c98c0d2f0d8daeb52f4bddc60be5"); + b256!("0x79b52ea2cc4088327fca3a7591ced55f29d13aa05c33fa7cc02e936db799727f"); /// Seismic devnet specification /// diff --git a/crates/seismic/evm/Cargo.toml b/crates/seismic/evm/Cargo.toml index 8b1d6cfe4..98bb7d6ef 100644 --- a/crates/seismic/evm/Cargo.toml +++ b/crates/seismic/evm/Cargo.toml @@ -41,7 +41,7 @@ reth-seismic-primitives = { workspace = true } reth-ethereum-primitives = { workspace = true, features = ["serde", "serde-bincode-compat", "reth-codec"]} # revm -revm.workspace = true +revm = { workspace = true} seismic-revm.workspace = true # misc diff --git a/crates/seismic/node/src/node.rs b/crates/seismic/node/src/node.rs index 0e5cac745..c032fb1fb 100644 --- a/crates/seismic/node/src/node.rs +++ b/crates/seismic/node/src/node.rs @@ -510,6 +510,10 @@ where .kzg_settings(ctx.kzg_settings()?) .with_local_transactions_config(pool_config.local_transactions_config.clone()) .with_additional_tasks(ctx.config().txpool.additional_validation_tasks) + // Gas is paid in USDC on Seismic, not native ETH. Disable the native balance check + // so transactions from accounts with zero ETH are not rejected. Actual gas payment + // is enforced by the Seismic revm implementation at execution time. + .disable_balance_check() .build_with_tasks(ctx.task_executor().clone(), blob_store.clone()); // Wrap the eth validator with seismic-specific validation @@ -544,10 +548,11 @@ reth_transaction_pool::maintain::LocalTransactionBackupConfig::with_local_txs_ba }, ); - // spawn the maintenance task + // spawn the maintenance task with USDC balance augmentation + let balance_hook = reth_seismic_txpool::SeismicBalanceHook; ctx.task_executor().spawn_critical( "txpool maintenance task", - reth_transaction_pool::maintain::maintain_transaction_pool_future( + reth_transaction_pool::maintain::maintain_transaction_pool_future_with_hook( client, pool, chain_events, @@ -556,6 +561,7 @@ reth_transaction_pool::maintain::LocalTransactionBackupConfig::with_local_txs_ba max_tx_lifetime: transaction_pool.config().max_queued_lifetime, ..Default::default() }, + balance_hook, ), ); // debug!(target: "reth::cli", "Spawned txpool maintenance task"); diff --git a/crates/seismic/rpc/src/eth/mod.rs b/crates/seismic/rpc/src/eth/mod.rs index 2ae297c3c..bf6a8256e 100644 --- a/crates/seismic/rpc/src/eth/mod.rs +++ b/crates/seismic/rpc/src/eth/mod.rs @@ -18,8 +18,10 @@ use crate::{ SeismicEthApiError, }; use alloy_consensus::TxEip4844; -use alloy_primitives::U256; -use reth_evm::ConfigureEvm; +use alloy_eips::BlockId; +use alloy_primitives::{Address, U256}; +use futures::Future; +use reth_evm::{ConfigureEvm, SpecFor, TxEnvFor}; use reth_node_api::{FullNodeComponents, HeaderTy}; use reth_node_builder::rpc::{EthApiBuilder, EthApiCtx}; use reth_rpc::{ @@ -28,11 +30,11 @@ use reth_rpc::{ }; use reth_rpc_eth_api::{ helpers::{ - pending_block::BuildPendingEnv, spec::SignersForApi, AddDevSigners, EthApiSpec, EthFees, - EthState, LoadFee, LoadPendingBlock, LoadState, SpawnBlocking, Trace, + pending_block::BuildPendingEnv, spec::SignersForApi, AddDevSigners, EthApiSpec, EthCall, + EthFees, EthState, LoadFee, LoadPendingBlock, LoadState, SpawnBlocking, Trace, }, - EthApiTypes, FromEvmError, FullEthApiServer, RpcConvert, RpcConverter, RpcNodeCore, - RpcNodeCoreExt, SignableTxRequest, + EthApiTypes, FromEthApiError, FromEvmError, FullEthApiServer, RpcConvert, RpcConverter, + RpcNodeCore, RpcNodeCoreExt, SignableTxRequest, }; use reth_rpc_eth_types::{EthStateCache, FeeHistoryCache, GasPriceOracle}; use reth_rpc_layer::Whitelist; @@ -42,6 +44,7 @@ use reth_tasks::{ TaskSpawner, }; use seismic_alloy_network::SeismicReth; +use seismic_revm::SeismicTransaction; use std::{fmt, marker::PhantomData, sync::Arc}; use reth_rpc_convert::transaction::{EthTxEnvError, TryIntoTxEnv}; @@ -297,8 +300,18 @@ where impl EthState for SeismicEthApi where N: RpcNodeCore, - Rpc: RpcConvert, - Self: LoadPendingBlock, + SeismicEthApiError: FromEvmError, + TxEnvFor: From>, + SeismicTransaction: Into>, + Rpc: RpcConvert< + Primitives = N::Primitives, + Error = SeismicEthApiError, + TxEnv = TxEnvFor, + Spec = SpecFor, + >, + <::NetworkTypes as reth_rpc_eth_api::RpcTypes>::TransactionRequest: + From, + Self: LoadPendingBlock + EthCall, { #[inline] fn max_proof_window(&self) -> u64 { @@ -309,6 +322,63 @@ where fn storage_apis_enabled(&self) -> bool { self.inner.storage_apis_enabled() } + + /// Returns the higher of the native balance or the USDC predeploy balance (scaled to 18 + /// decimals) for `address`. USDC uses 6 decimals; we multiply by 10^12 so both balances + /// are comparable in 18-decimal wei units. + /// + /// The USDC predeploy on Seismic is a bytecode-less account whose `_balances` mapping + /// (slot 3) is read/written directly by the seismic-revm handler. We therefore read + /// the balance from raw storage instead of executing a `balanceOf` call (which would + /// return empty bytes since there is no contract code). + fn balance( + &self, + address: Address, + block_id: Option, + ) -> impl Future> + Send { + use alloy_primitives::keccak256; + + /// USDC predeploy address on Seismic. + const USDC_CONTRACT: Address = + alloy_primitives::address!("0x790701048922E265105fd6a4467a2901c2201C43"); + + /// Scale factor to convert USDC (6 decimals) to 18 decimals: 10^12. + const USDC_DECIMAL_SCALE: U256 = U256::from_limbs([1_000_000_000_000u64, 0, 0, 0]); + + /// Storage slot of the `_balances` mapping in the USDC predeploy. + const BALANCES_SLOT: u8 = 3; + + // Compute storage key: keccak256(abi.encode(address, uint256(BALANCES_SLOT))). + // The address is left-padded to 32 bytes; the slot occupies the last byte of the + // second word. + let mut buf = [0u8; 64]; + #[allow(clippy::indexing_slicing)] + buf[12..32].copy_from_slice(address.as_slice()); + #[allow(clippy::indexing_slicing)] + { + buf[63] = BALANCES_SLOT; + } + let storage_key = keccak256(buf); + + self.spawn_blocking_io_fut(move |this| async move { + let state = this.state_at_block_id_or_latest(block_id).await?; + + // Read native balance. + let native_balance = state + .account_balance(&address) + .map_err(Self::Error::from_eth_err)? + .unwrap_or_default(); + + // Read USDC balance from contract storage and scale 6→18 decimals. + let usdc_balance = state + .storage(USDC_CONTRACT, storage_key) + .map_err(Self::Error::from_eth_err)? + .map(|s| s.value.saturating_mul(USDC_DECIMAL_SCALE)) + .unwrap_or_default(); + + Ok(std::cmp::max(native_balance, usdc_balance)) + }) + } } impl EthFees for SeismicEthApi diff --git a/crates/seismic/txpool/Cargo.toml b/crates/seismic/txpool/Cargo.toml index 815bb0933..d5fa99fbb 100644 --- a/crates/seismic/txpool/Cargo.toml +++ b/crates/seismic/txpool/Cargo.toml @@ -20,6 +20,7 @@ seismic-alloy-consensus.workspace = true # reth reth-chainspec.workspace = true +reth-execution-types.workspace = true reth-primitives-traits.workspace = true reth-provider.workspace = true reth-transaction-pool = {workspace = true, features = ["serde", "reth-codec", "serde-bincode-compat"]} @@ -30,6 +31,7 @@ reth-seismic-primitives = {workspace = true, features = ["serde", "reth-codec", # misc c-kzg.workspace = true derive_more.workspace = true +tracing.workspace = true [dev-dependencies] reth-seismic-chainspec.workspace = true diff --git a/crates/seismic/txpool/src/lib.rs b/crates/seismic/txpool/src/lib.rs index 2d0113fde..fbe3209e7 100644 --- a/crates/seismic/txpool/src/lib.rs +++ b/crates/seismic/txpool/src/lib.rs @@ -10,10 +10,13 @@ use reth_transaction_pool::{CoinbaseTipOrdering, Pool, TransactionValidationTaskExecutor}; +mod maintain; mod recent_block_cache; mod transaction; +pub mod usdc; mod validator; +pub use maintain::SeismicBalanceHook; pub use recent_block_cache::{RecentBlockCache, SEISMIC_TX_RECENT_BLOCK_LOOKBACK}; pub use transaction::SeismicPooledTransaction; pub use validator::SeismicTransactionValidator; diff --git a/crates/seismic/txpool/src/maintain.rs b/crates/seismic/txpool/src/maintain.rs new file mode 100644 index 000000000..769bdbfa7 --- /dev/null +++ b/crates/seismic/txpool/src/maintain.rs @@ -0,0 +1,157 @@ +//! Seismic-specific pool maintenance hook that augments native balances with +//! USDC predeploy balances. + +use alloy_primitives::{Address, B256}; +use reth_execution_types::{ChangedAccount, ExecutionOutcome}; +use reth_provider::StateProvider; +use reth_transaction_pool::maintain::ChangedAccountsHook; +use std::collections::HashSet; +use tracing::debug; + +/// A [`ChangedAccountsHook`] that reads each sender's USDC predeploy balance +/// and sets `balance = max(native, usdc_scaled)` so the pool can make accurate +/// demotion decisions for accounts paying gas in USDC. +/// +/// The hook uses the [`StateProvider`] passed by the maintenance loop rather +/// than opening its own snapshot, so the USDC balance is always read from the +/// same block as the native balance and nonce. +#[derive(Debug, Default)] +pub struct SeismicBalanceHook; + +impl ChangedAccountsHook for SeismicBalanceHook { + fn transform(&self, state: &dyn StateProvider, accounts: &mut Vec) { + for acc in accounts.iter_mut() { + let usdc = crate::usdc::read_usdc_balance(state, &acc.address); + let new_balance = std::cmp::max(acc.balance, usdc); + if new_balance != acc.balance { + debug!( + target: "seismic::txpool", + address = %acc.address, + native_balance = %acc.balance, + usdc_scaled_balance = %usdc, + effective_balance = %new_balance, + "augmenting changed account balance with USDC" + ); + acc.balance = new_balance; + } + } + } + + fn extend_reload_queued_senders( + &self, + queued_senders: &HashSet
, + old: Option<&ExecutionOutcome>, + new: &ExecutionOutcome, + dirty_addresses: &mut HashSet
, + ) { + dirty_addresses.extend(queued_senders_with_changed_usdc_slots(queued_senders, old, new)); + } +} + +fn queued_senders_with_changed_usdc_slots<'a, R>( + queued_senders: &'a HashSet
, + old: Option<&ExecutionOutcome>, + new: &ExecutionOutcome, +) -> impl Iterator + 'a { + let changed_slots = changed_usdc_storage_slots(new) + .into_iter() + .chain(old.into_iter().flat_map(changed_usdc_storage_slots)) + .collect::>(); + + queued_senders.iter().copied().filter(move |address| { + changed_slots.contains(&crate::usdc::usdc_balance_storage_key(address)) + }) +} + +fn changed_usdc_storage_slots(state: &ExecutionOutcome) -> impl Iterator + '_ { + state + .bundle_accounts_iter() + .filter_map(|(address, account)| (address == crate::usdc::USDC_CONTRACT).then_some(account)) + .flat_map(|account| { + account.storage.iter().filter_map(|(slot, value)| { + value.is_changed().then(|| B256::from(slot.to_be_bytes::<32>())) + }) + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::{map::HashMap, FlaggedStorage, U256}; + use reth_execution_types::{BundleStateInit, RevertsInit}; + + #[test] + fn reloads_only_queued_senders_with_changed_usdc_slots() { + let affected = alloy_primitives::address!("000000000000000000000000000000000000000a"); + let unaffected = alloy_primitives::address!("000000000000000000000000000000000000000b"); + let queued_senders = HashSet::from([affected, unaffected]); + let state = ExecutionOutcome::<()>::new_init( + { + let mut init = BundleStateInit::default(); + init.insert( + crate::usdc::USDC_CONTRACT, + ( + None, + None, + HashMap::from_iter([( + crate::usdc::usdc_balance_storage_key(&affected), + (FlaggedStorage::ZERO, FlaggedStorage::from(U256::from(1))), + )]), + ), + ); + init + }, + RevertsInit::default(), + [], + vec![], + 0, + vec![], + ); + + let dirty = queued_senders_with_changed_usdc_slots(&queued_senders, None, &state) + .collect::>(); + + assert_eq!(dirty, HashSet::from([affected])); + } + + #[test] + fn includes_changed_slots_from_old_and_new_state() { + let queued_sender = alloy_primitives::address!("000000000000000000000000000000000000000a"); + let queued_senders = HashSet::from([queued_sender]); + let old = ExecutionOutcome::<()>::new_init( + { + let mut init = BundleStateInit::default(); + init.insert( + crate::usdc::USDC_CONTRACT, + ( + None, + None, + HashMap::from_iter([( + crate::usdc::usdc_balance_storage_key(&queued_sender), + (FlaggedStorage::ZERO, FlaggedStorage::from(U256::from(1))), + )]), + ), + ); + init + }, + RevertsInit::default(), + [], + vec![], + 0, + vec![], + ); + let new = ExecutionOutcome::<()>::new_init( + BundleStateInit::default(), + RevertsInit::default(), + [], + vec![], + 0, + vec![], + ); + + let dirty = queued_senders_with_changed_usdc_slots(&queued_senders, Some(&old), &new) + .collect::>(); + + assert_eq!(dirty, HashSet::from([queued_sender])); + } +} diff --git a/crates/seismic/txpool/src/usdc.rs b/crates/seismic/txpool/src/usdc.rs new file mode 100644 index 000000000..369149eed --- /dev/null +++ b/crates/seismic/txpool/src/usdc.rs @@ -0,0 +1,98 @@ +//! Utilities for reading USDC balances from contract storage. +//! +//! The Seismic USDC predeploy stores its `_balances` mapping at storage slot 3. +//! We read balances directly from storage +//! (cheap) rather than executing an EVM `balanceOf` call (expensive). + +use alloy_primitives::{keccak256, Address, B256, U256}; +use reth_provider::StateProvider; + +/// USDC predeploy address on Seismic. +pub const USDC_CONTRACT: Address = + alloy_primitives::address!("0x790701048922E265105fd6a4467a2901c2201C43"); + +/// Scale factor to convert USDC (6 decimals) to 18 decimals: 10^12. +pub const USDC_DECIMAL_SCALE: U256 = U256::from_limbs([1_000_000_000_000u64, 0, 0, 0]); + +/// Storage slot of the `_balances` mapping in the USDC predeploy contract. +const BALANCES_MAPPING_SLOT: U256 = U256::from_limbs([3, 0, 0, 0]); + +/// Computes the storage key for `_balances[address]`. +/// +/// For a Solidity `mapping(address => uint256)` at slot `s`, the value for key +/// `k` is stored at `keccak256(abi.encode(k, s))` — i.e. `k` left-padded to 32 +/// bytes concatenated with `s` as a 32-byte big-endian integer. +pub(crate) fn usdc_balance_storage_key(address: &Address) -> B256 { + let mut buf = [0u8; 64]; + // address is 20 bytes, right-aligned in the first 32-byte word + buf[12..32].copy_from_slice(address.as_slice()); + // second 32-byte word: slot number (0) — already zeroed + #[allow(clippy::indexing_slicing)] + BALANCES_MAPPING_SLOT.to_be_bytes::<32>().iter().enumerate().for_each(|(i, &b)| { + buf[32 + i] = b; + }); + keccak256(buf) +} + +/// Reads the USDC balance for `address` from contract storage and scales it to +/// 18 decimals. Returns `U256::ZERO` on any error (missing account, missing +/// slot, provider failure). +pub fn read_usdc_balance(state: &dyn StateProvider, address: &Address) -> U256 { + let key = usdc_balance_storage_key(address); + match state.storage(USDC_CONTRACT, key) { + Ok(Some(flagged)) => flagged.value.saturating_mul(USDC_DECIMAL_SCALE), + _ => U256::ZERO, + } +} + +/// Returns the *effective* balance: `max(native_balance, usdc_balance_scaled)`. +/// +/// This is used as the balance for transaction pool ordering and demotion +/// decisions so that accounts paying gas in USDC are treated equivalently to +/// accounts paying in native token. +pub fn effective_balance( + state: &dyn StateProvider, + address: &Address, + native_balance: U256, +) -> U256 { + let usdc = read_usdc_balance(state, address); + std::cmp::max(native_balance, usdc) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn storage_key_is_deterministic() { + let addr = alloy_primitives::address!("0000000000000000000000000000000000000001"); + let k1 = usdc_balance_storage_key(&addr); + let k2 = usdc_balance_storage_key(&addr); + assert_eq!(k1, k2); + } + + #[test] + fn storage_key_differs_per_address() { + let a = alloy_primitives::address!("0000000000000000000000000000000000000001"); + let b = alloy_primitives::address!("0000000000000000000000000000000000000002"); + assert_ne!(usdc_balance_storage_key(&a), usdc_balance_storage_key(&b)); + } + + /// Replicates the seismic-revm `erc_address_storage` computation byte-for-byte + /// to confirm we read the same slot the EVM writes to. + #[test] + fn storage_key_matches_seismic_revm() { + let addr = alloy_primitives::address!("0123456789abcdef0123456789abcdef01234567"); + + // seismic-revm erc_address_storage(addr): + // buf[12..32] = addr + // buf[63] = 3 + // keccak256(buf) + let mut expected_buf = [0u8; 64]; + expected_buf[12..32].copy_from_slice(addr.as_slice()); + expected_buf[63] = 3; + let expected = keccak256(expected_buf); + + assert_eq!(usdc_balance_storage_key(&addr), expected); + } +} diff --git a/crates/seismic/txpool/src/validator.rs b/crates/seismic/txpool/src/validator.rs index dd5396fe9..00774e28f 100644 --- a/crates/seismic/txpool/src/validator.rs +++ b/crates/seismic/txpool/src/validator.rs @@ -2,9 +2,9 @@ use crate::recent_block_cache::RecentBlockCache; use alloy_consensus::BlockHeader; -use alloy_primitives::{Sealable, TxKind, B256}; +use alloy_primitives::{Sealable, TxKind, B256, U256}; use reth_chainspec::ChainSpecProvider; -use reth_primitives_traits::{transaction::error::InvalidTransactionError, Block}; +use reth_primitives_traits::{transaction::error::InvalidTransactionError, Block, GotExpected}; use reth_provider::{BlockReaderIdExt, StateProviderFactory}; use reth_seismic_primitives::{transaction::error::SeismicTxError, SeismicTransactionSigned}; use reth_transaction_pool::{ @@ -139,9 +139,60 @@ where } } - // All validations passed, return valid + // Compute the effective balance: max(native, usdc_scaled). + // Gas on Seismic can be paid in either native token or USDC, so + // we consider both when deciding pool admission. + let sender = *valid_tx.transaction().sender_ref(); + let cost = *valid_tx.transaction().cost(); + let (eff_balance, usdc_raw) = match self.inner.client().latest() { + Ok(state) => { + let usdc = crate::usdc::read_usdc_balance(&*state, &sender); + (std::cmp::max(balance, usdc), usdc) + } + // If we can't read state, fall back to native balance only. + Err(err) => { + tracing::warn!( + target: "seismic::txpool", + %err, + %sender, + "failed to read state for USDC balance check" + ); + (balance, U256::ZERO) + } + }; + + tracing::debug!( + target: "seismic::txpool", + %sender, + tx_hash = %valid_tx.hash(), + native_balance = %balance, + usdc_scaled_balance = %usdc_raw, + effective_balance = %eff_balance, + cost = %cost, + "seismic validator effective balance check" + ); + + // Reject if the sender cannot afford the transaction with either token. + if cost > eff_balance { + tracing::debug!( + target: "seismic::txpool", + %sender, + tx_hash = %valid_tx.hash(), + effective_balance = %eff_balance, + cost = %cost, + "rejecting tx: effective balance insufficient for cost" + ); + return TransactionValidationOutcome::Invalid( + valid_tx.into_transaction(), + InvalidTransactionError::InsufficientFunds( + GotExpected { got: eff_balance, expected: cost }.into(), + ) + .into(), + ); + } + TransactionValidationOutcome::Valid { - balance, + balance: eff_balance, state_nonce, transaction: valid_tx, propagate, diff --git a/crates/transaction-pool/src/config.rs b/crates/transaction-pool/src/config.rs index c6fb4ecc8..4d43b5818 100644 --- a/crates/transaction-pool/src/config.rs +++ b/crates/transaction-pool/src/config.rs @@ -72,6 +72,11 @@ pub struct PoolConfig { /// /// This restricts how many executable transaction a delegated sender can stack. pub max_inflight_delegated_slot_limit: usize, + /// When true, the pool will not demote transactions to the queued sub-pool due to + /// insufficient native balance. This is useful for chains where gas can be paid in an + /// alternative token (e.g. USDC on Seismic) so that native balance is not relevant for + /// transaction ordering. + pub disable_balance_check: bool, } impl PoolConfig { @@ -91,6 +96,14 @@ impl PoolConfig { self } + /// Disables native balance checks in the pool, so transactions are never demoted to the + /// queued sub-pool due to insufficient native balance. Useful for chains where gas is paid + /// in an alternative token. + pub const fn with_disabled_balance_check(mut self) -> Self { + self.disable_balance_check = true; + self + } + /// Configures how many slots are available for a delegated sender. pub const fn with_max_inflight_delegated_slots( mut self, @@ -129,6 +142,7 @@ impl Default for PoolConfig { max_new_pending_txs_notifications: MAX_NEW_PENDING_TXS_NOTIFICATIONS, max_queued_lifetime: MAX_QUEUED_TRANSACTION_LIFETIME, max_inflight_delegated_slot_limit: DEFAULT_MAX_INFLIGHT_DELEGATED_SLOTS, + disable_balance_check: false, } } } diff --git a/crates/transaction-pool/src/maintain.rs b/crates/transaction-pool/src/maintain.rs index 83bfc5534..09f0a99b8 100644 --- a/crates/transaction-pool/src/maintain.rs +++ b/crates/transaction-pool/src/maintain.rs @@ -17,12 +17,14 @@ use futures_util::{ }; use reth_chain_state::CanonStateNotification; use reth_chainspec::{ChainSpecProvider, EthChainSpec}; -use reth_execution_types::ChangedAccount; +use reth_execution_types::{ChangedAccount, ExecutionOutcome}; use reth_fs_util::FsPathError; use reth_primitives_traits::{ transaction::signed::SignedTransaction, NodePrimitives, SealedHeader, }; -use reth_storage_api::{errors::provider::ProviderError, BlockReaderIdExt, StateProviderFactory}; +use reth_storage_api::{ + errors::provider::ProviderError, BlockReaderIdExt, StateProvider, StateProviderFactory, +}; use reth_tasks::TaskSpawner; use serde::{Deserialize, Serialize}; use std::{ @@ -91,6 +93,37 @@ impl LocalTransactionBackupConfig { } } +/// Hook to transform changed accounts before they are passed to the pool. +/// +/// This is used by Seismic to augment native balances with USDC balances so that +/// the pool can make accurate demotion decisions for accounts paying gas in USDC. +pub trait ChangedAccountsHook: Send + Sync + 'static { + /// Transforms the changed accounts list in place. Implementations may read + /// additional state (e.g. ERC-20 storage) and adjust the `balance` field of + /// each [`ChangedAccount`]. + /// + /// The [`StateProvider`] is the same snapshot the maintenance loop used to + /// load native balance/nonce, so implementations always see a consistent + /// view of the chain. + fn transform(&self, state: &dyn StateProvider, accounts: &mut Vec); + + /// Extends the dirty set with queued senders whose executability may have + /// changed due to off-account state updates in the canonical update. + fn extend_reload_queued_senders( + &self, + _queued_senders: &HashSet
, + _old: Option<&ExecutionOutcome>, + _new: &ExecutionOutcome, + _dirty_addresses: &mut HashSet
, + ) { + } +} + +/// No-op implementation for chains that don't need balance augmentation. +impl ChangedAccountsHook for () { + fn transform(&self, _state: &dyn StateProvider, _accounts: &mut Vec) {} +} + /// Returns a spawnable future for maintaining the state of the transaction pool. pub fn maintain_transaction_pool_future( client: Client, @@ -109,9 +142,34 @@ where P: TransactionPoolExt> + 'static, St: Stream> + Send + Unpin + 'static, Tasks: TaskSpawner + 'static, +{ + maintain_transaction_pool_future_with_hook(client, pool, events, task_spawner, config, ()) +} + +/// Like [`maintain_transaction_pool_future`] but accepts a [`ChangedAccountsHook`] +/// that can transform account balances before the pool processes them. +pub fn maintain_transaction_pool_future_with_hook( + client: Client, + pool: P, + events: St, + task_spawner: Tasks, + config: MaintainPoolConfig, + hook: H, +) -> BoxFuture<'static, ()> +where + N: NodePrimitives, + Client: StateProviderFactory + + BlockReaderIdExt
+ + ChainSpecProvider> + + Clone + + 'static, + P: TransactionPoolExt> + 'static, + St: Stream> + Send + Unpin + 'static, + Tasks: TaskSpawner + 'static, + H: ChangedAccountsHook, { async move { - maintain_transaction_pool(client, pool, events, task_spawner, config).await; + maintain_transaction_pool_with_hook(client, pool, events, task_spawner, config, hook).await; } .boxed() } @@ -120,11 +178,34 @@ where /// /// This listens for any new blocks and reorgs and updates the transaction pool's state accordingly pub async fn maintain_transaction_pool( + client: Client, + pool: P, + events: St, + task_spawner: Tasks, + config: MaintainPoolConfig, +) where + N: NodePrimitives, + Client: StateProviderFactory + + BlockReaderIdExt
+ + ChainSpecProvider> + + Clone + + 'static, + P: TransactionPoolExt> + 'static, + St: Stream> + Send + Unpin + 'static, + Tasks: TaskSpawner + 'static, +{ + maintain_transaction_pool_with_hook(client, pool, events, task_spawner, config, ()).await +} + +/// Like [`maintain_transaction_pool`] but accepts a [`ChangedAccountsHook`] that +/// can transform account balances before the pool processes them. +pub async fn maintain_transaction_pool_with_hook( client: Client, pool: P, mut events: St, task_spawner: Tasks, config: MaintainPoolConfig, + hook: H, ) where N: NodePrimitives, Client: StateProviderFactory @@ -135,6 +216,7 @@ pub async fn maintain_transaction_pool( P: TransactionPoolExt> + 'static, St: Stream> + Send + Unpin + 'static, Tasks: TaskSpawner + 'static, + H: ChangedAccountsHook, { let metrics = MaintainPoolMetrics::default(); let MaintainPoolConfig { max_update_depth, max_reload_accounts, .. } = config; @@ -286,10 +368,13 @@ pub async fn maintain_transaction_pool( } // handle the result of the account reload match reloaded { - Some(Ok(Ok(LoadedAccounts { accounts, failed_to_load }))) => { + Some(Ok(Ok(LoadedAccounts { mut accounts, failed_to_load }))) => { // reloaded accounts successfully // extend accounts we failed to load from database dirty_addresses.extend(failed_to_load); + if let Ok(state) = client.history_by_block_hash(pool_info.last_seen_block_hash) { + hook.transform(&*state, &mut accounts); + } // update the pool with the loaded accounts pool.update_accounts(accounts); } @@ -369,6 +454,9 @@ pub async fn maintain_transaction_pool( // also include all accounts from new chain // we can use extend here because they are unique changed_accounts.extend(new_changed_accounts.into_iter().map(|entry| entry.0)); + if let Ok(state) = client.history_by_block_hash(new_tip.hash()) { + hook.transform(&*state, &mut changed_accounts); + } // all transactions mined in the new chain let new_mined_transactions: HashSet<_> = new_blocks.transaction_hashes().collect(); @@ -421,6 +509,14 @@ pub async fn maintain_transaction_pool( metrics.inc_reinserted_transactions(pruned_old_transactions.len()); let _ = pool.add_external_transactions(pruned_old_transactions).await; + let queued_senders = queued_senders(&pool); + hook.extend_reload_queued_senders( + &queued_senders, + Some(old_state), + new_state, + &mut dirty_addresses, + ); + // keep track of new mined blob transactions blob_store_tracker.add_new_chain_blocks(&new_blocks); } @@ -473,6 +569,9 @@ pub async fn maintain_transaction_pool( dirty_addresses.remove(&acc.address); changed_accounts.push(acc); } + if let Ok(tip_state) = client.history_by_block_hash(tip.hash()) { + hook.transform(&*tip_state, &mut changed_accounts); + } let mined_transactions = blocks.transaction_hashes().collect(); @@ -495,6 +594,14 @@ pub async fn maintain_transaction_pool( }; pool.on_canonical_state_change(update); + let queued_senders = queued_senders(&pool); + hook.extend_reload_queued_senders( + &queued_senders, + None, + state, + &mut dirty_addresses, + ); + // keep track of mined blob transactions blob_store_tracker.add_new_chain_blocks(&blocks); } @@ -603,6 +710,10 @@ where Ok(res) } +fn queued_senders(pool: &P) -> HashSet
{ + pool.queued_transactions().into_iter().map(|tx| tx.sender()).collect() +} + /// Loads transactions from a file, decodes them from the JSON or RLP format, and /// inserts them into the transaction pool on node boot up. /// The file is removed after the transactions have been successfully processed. diff --git a/crates/transaction-pool/src/pool/mod.rs b/crates/transaction-pool/src/pool/mod.rs index 97f248003..e0f8aba2e 100644 --- a/crates/transaction-pool/src/pool/mod.rs +++ b/crates/transaction-pool/src/pool/mod.rs @@ -510,7 +510,16 @@ where let added = pool.add_transaction(tx, balance, state_nonce, bytecode_hash)?; let hash = *added.hash(); - let state = match added.subpool() { + let subpool = added.subpool(); + debug!( + target: "txpool", + tx_hash = %hash, + ?subpool, + reported_balance = %balance, + state_nonce, + "transaction added to pool" + ); + let state = match subpool { SubPool::Pending => AddedTransactionState::Pending, _ => AddedTransactionState::Queued, }; diff --git a/crates/transaction-pool/src/pool/txpool.rs b/crates/transaction-pool/src/pool/txpool.rs index 00f11216a..ae79245c8 100644 --- a/crates/transaction-pool/src/pool/txpool.rs +++ b/crates/transaction-pool/src/pool/txpool.rs @@ -1263,6 +1263,9 @@ pub(crate) struct AllTransactions { local_transactions_config: LocalTransactionConfig, /// All accounts with a pooled authorization auths: FxHashMap>, + /// When true, skip native balance checks so transactions are never demoted due to + /// insufficient native balance. Used for chains where gas is paid in an alternative token. + disable_balance_check: bool, /// All Transactions metrics metrics: AllTransactionsMetrics, } @@ -1276,6 +1279,7 @@ impl AllTransactions { local_transactions_config: config.local_transactions_config.clone(), minimal_protocol_basefee: config.minimal_protocol_basefee, block_gas_limit: config.gas_limit, + disable_balance_check: config.disable_balance_check, ..Default::default() } } @@ -1418,7 +1422,9 @@ impl AllTransactions { tx.state.insert(TxState::NO_NONCE_GAPS); tx.state.insert(TxState::NO_PARKED_ANCESTORS); tx.cumulative_cost = U256::ZERO; - if tx.transaction.cost() > &info.balance { + if self.disable_balance_check { + tx.state.insert(TxState::ENOUGH_BALANCE); + } else if tx.transaction.cost() > &info.balance { // sender lacks sufficient funds to pay for this transaction tx.state.remove(TxState::ENOUGH_BALANCE); } else { @@ -1479,7 +1485,9 @@ impl AllTransactions { // If the account changed in the block, check the balance. if let Some(changed_balance) = changed_balance { - if &cumulative_cost > changed_balance { + if self.disable_balance_check { + tx.state.insert(TxState::ENOUGH_BALANCE); + } else if &cumulative_cost > changed_balance { // sender lacks sufficient funds to pay for this transaction tx.state.remove(TxState::ENOUGH_BALANCE); } else { @@ -1942,6 +1950,7 @@ impl AllTransactions { // The next transaction of this sender let on_chain_id = TransactionId::new(transaction.sender_id(), on_chain_nonce); + let skip_balance_check = self.disable_balance_check; { // Tracks the next nonce we expect if the transactions are gapless let mut next_nonce = on_chain_id.nonce; @@ -1970,7 +1979,9 @@ impl AllTransactions { // Update for next transaction cumulative_cost = tx.next_cumulative_cost(); - if cumulative_cost > on_chain_balance { + if skip_balance_check { + tx.state.insert(TxState::ENOUGH_BALANCE); + } else if cumulative_cost > on_chain_balance { // sender lacks sufficient funds to pay for this transaction tx.state.remove(TxState::ENOUGH_BALANCE); } else { @@ -2061,6 +2072,7 @@ impl Default for AllTransactions { price_bumps: Default::default(), local_transactions_config: Default::default(), auths: Default::default(), + disable_balance_check: false, metrics: Default::default(), } }