Skip to content

Conversation

@dimartiro
Copy link

@dimartiro dimartiro commented Nov 13, 2025

Description

This PR implements forking functionality for anvil-polkadot, allowing users to fork from a remote Substrate/Polkadot node and run local tests against live network state.

Adds the ability to fork from a running anvil-polkadot node (or compatible Substrate node) using the --fork-url flag. The implementation uses a lazy-loading approach to fetch state from the remote node on-demand, caching values locally for performance

Testing

After compiling the entire project using cargo build --release

1. Start base node

RUST_LOG=info ./target/release/anvil-polkadot --port 9970

2. Modify state on base node

./target/release/cast send 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 \
    --value 5ether \
    --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
    --rpc-url http://localhost:9970

3. Verify modified balance

./target/release/cast balance 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 \
    --rpc-url http://localhost:9970

Expected Output: ~9995 ETH (10000 - 5 - gas)

4. Start fork node (inherits modified state)

RUST_LOG=info ./target/release/anvil-polkadot \
    --port 8545 \
    --fork-url http://localhost:9944

5. Verify fork inherited state

./target/release/cast balance 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 \
    --rpc-url http://localhost:8545

Expected Output: ~9995 ETH ✅ (matches base node)

6. Make changes on fork (doesn't affect base)

./target/release/cast send 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 \
    --value 1ether \
    --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
    --rpc-url http://localhost:8545

7. Verify isolation

./target/release/cast balance 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 \
    --rpc-url http://localhost:9970

Exepected Output: ~9995 ETH (unchanged)

./target/release/cast balance 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 \
    --rpc-url http://localhost:8545

Expected Output: ~9994 ETH (changed)

TODO

  • Add new integration tests to test the fork is working
    • Fork a node with a smart contract deployed and check storage

@dimartiro dimartiro changed the title [WIP] Implement forking support with lazy loading backend Implement forking support with lazy loading backend Nov 13, 2025
@dimartiro dimartiro changed the title Implement forking support with lazy loading backend [WIP] Implement forking support with lazy loading backend Nov 13, 2025
@alindima
Copy link

I pulled in the latest changes from lazy-loading-backend branch on top of the forking-support and tried this out.
I got a panic on step 6 (after executing the balance transfer on the forked network):

2025-11-18T11:24:42.514506Z  INFO eth-rpc::client: Node does not have getAutomine RPC. Defaulting to automine=false. error: Client(JsonRpc(ErrorObject { code: MethodNotFound, message: "Method not found", data: None }))
2025-11-18T11:24:42.514559Z  INFO eth-rpc::client: 🔌 Subscribing to new blocks (BestBlocks)
2025-11-18T11:24:42.514591Z  INFO eth-rpc::client: 🔌 Subscribing to new blocks (FinalizedBlocks)
2025-11-18T11:25:02.237394Z  INFO node::user: eth_getBalance
2025-11-18T11:25:18.010246Z  INFO node::user: eth_chainId
2025-11-18T11:25:18.012071Z  INFO node::user: eth_getTransactionCount
2025-11-18T11:25:18.016610Z  INFO node::user: eth_feeHistory
2025-11-18T11:25:18.016894Z  INFO node::user: eth_estimateGas
2025-11-18T11:25:18.037629Z  INFO node::user: eth_sendRawTransaction
2025-11-18T11:25:18.049158Z  INFO node::user: eth_blockNumber
2025-11-18T11:25:18.049189Z  WARN db::blockchain: Block 0xb3618bf448f724dabb16ed70ac902c1d0929a86bd8a755396071d3d8c2c76bd4 exists in chain but not found when following all leaves backwards
2025-11-18T11:25:18.049209Z  INFO sc_basic_authorship::basic_authorship: 🙌 Starting consensus session on top of parent 0xb3618bf448f724dabb16ed70ac902c1d0929a86bd8a755396071d3d8c2c76bd4 (#1)
2025-11-18T11:25:18.049381Z  INFO node::user: eth_getTransactionReceipt
2025-11-18T11:25:18.049483Z  INFO node::user: eth_getBlockByNumber
2025-11-18T11:25:18.052559Z  INFO node::user: eth_getTransactionReceipt
2025-11-18T11:25:18.053993Z  INFO node::user: eth_getTransactionReceipt
2025-11-18T11:25:18.105454Z  INFO sc_basic_authorship::basic_authorship: 🎁 Prepared block for proposing at 2 (56 ms) hash: 0x759f42d8556ca90e0455ff873c65d4a29b308ac30f6750c9b9516febc03b30a7; parent_hash: 0xb361…6bd4; end: NoMoreTransactions; extrinsics_count: 2
2025-11-18T11:25:18.305392Z  INFO node::user: eth_blockNumber
2025-11-18T11:25:18.305517Z  INFO node::user: eth_getTransactionReceipt
2025-11-18T11:25:18.315519Z  INFO node::user: eth_getBlockByNumber
The application panicked (crashed).
Message:  Dropped receiver: Success(Object {"baseFeePerGas": String("0xf4240"), "blobGasUsed": String("0x0"), "difficulty": String("0x0"), "excessBlobGas": String("0x0"), "extraData": String("0x"), "gasLimit": String("0x1664f5b7bcc00"), "gasUsed": String("0x3333fc1ee"), "hash": String("0xd6ef90f572ef8869ff214e1d344ee76cb345958ccc52876dd76623042b564238"), "logsBloom": String("0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), "miner": String("0x0000000000000000000000000000000000000000"), "mixHash": String("0x0000000000000000000000000000000000000000000000000008e1bc9bf04000"), "nonce": String("0x0000000000000000"), "number": String("0x2"), "parentHash": String("0x1a555946dfeeebaec3da4be474d10e65643dd6fb42cf55bde0779e7ba70258c2"), "receiptsRoot": String("0x79968af4de4ac2a0e5c85f94af50f2de6dcac16542a7802166f4e96dc0b41213"), "sha3Uncles": String("0x0000000000000000000000000000000000000000000000000000000000000000"), "size": String("0x0"), "stateRoot": String("0xc1e82001b3d608ebde06742344f9735188a785a46cd74566527995dca2bcc125"), "timestamp": String("0x691c579d"), "transactions": Array [String("0x7613ee406fabf1d4cbadbf567a4623a8a3f0dc4c0f0cd8ce76de65c179d2d8dd")], "transactionsRoot": String("0xc1e82001b3d608ebde06742344f9735188a785a46cd74566527995dca2bcc125"), "uncles": Array [], "withdrawals": Array [], "withdrawalsRoot": String("0x0000000000000000000000000000000000000000000000000000000000000000")})
Location: crates/anvil-polkadot/src/api_server/server.rs:144

This is a bug. Consider reporting it at https://github.com/foundry-rs/foundry

  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ BACKTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
                                ⋮ 11 frames hidden ⋮                              
  12: <core::pin::Pin<P> as core::future::future::Future>::poll::hda098539ca6decbe
      at <unknown source file>:<unknown line>
  13: <futures_util::future::select::Select<A,B> as core::future::future::Future>::poll::ha9ea646c75eb94f1
      at <unknown source file>:<unknown line>
  14: <tracing_futures::Instrumented<T> as core::future::future::Future>::poll::hc114aafe7ea1fead
      at <unknown source file>:<unknown line>
  15: tokio::runtime::task::core::Core<T,S>::poll::haa983556dc399011
      at <unknown source file>:<unknown line>
  16: tokio::runtime::task::harness::Harness<T,S>::poll::h26edae8526e76b38
      at <unknown source file>:<unknown line>
  17: tokio::runtime::scheduler::multi_thread::worker::Context::run_task::h3672c8cb41e5765c
      at <unknown source file>:<unknown line>
  18: tokio::runtime::scheduler::multi_thread::worker::Context::run::h6d66aec1eaa19239
      at <unknown source file>:<unknown line>
  19: tokio::runtime::context::scoped::Scoped<T>::set::hd0b9f670ca0250d5
      at <unknown source file>:<unknown line>
  20: tokio::runtime::context::runtime::enter_runtime::h6c45339057bc35ab
      at <unknown source file>:<unknown line>
  21: tokio::runtime::scheduler::multi_thread::worker::run::h4f4861b3c2108d6a
      at <unknown source file>:<unknown line>
  22: tokio::runtime::task::core::Core<T,S>::poll::hc238ba9b9a1cb8da
      at <unknown source file>:<unknown line>

Run with COLORBT_SHOW_HIDDEN=1 environment variable to disable frame filtering.
Run with RUST_BACKTRACE=full to include source snippets.
zsh: abort      RUST_LOG=info ./target/release/anvil-polkadot --port 8545 --fork-url

Don't know if I messed something up while fixing conflicts, so would he worth doing this merge yourself and testing again

@dimartiro
Copy link
Author

I pulled in the latest changes from lazy-loading-backend branch on top of the forking-support and tried this out. I got a panic on step 6 (after executing the balance transfer on the forked network):

Don't know if I messed something up while fixing conflicts, so would he worth doing this merge yourself and testing again

Out of curiosity: did you try this flow without the lazy-loading changes, just want to confirm I don't have any special setup on my side.

Regarding the merge itself — I’m currently heads-down on the other PR to get that one landed ASAP. Once that’s done I’ll jump straight onto this one

@alindima
Copy link

Out of curiosity: did you try this flow without the lazy-loading changes, just want to confirm I don't have any special setup on my side.

You mean without pulling in the latest changes, but only with the state of this branch?

@alindima
Copy link

And let's also test with a smart contract. A simple one which stores multiple items in the state to see if the child trie state is also migrated properly

@dimartiro
Copy link
Author

Closed in favor of paritytech#426

@dimartiro dimartiro closed this Nov 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants