diff --git a/.github/workflows/local-testnet.yml b/.github/workflows/local-testnet.yml index 1ca1006c1f..d4982ae194 100644 --- a/.github/workflows/local-testnet.yml +++ b/.github/workflows/local-testnet.yml @@ -25,8 +25,8 @@ jobs: uses: arduino/setup-protoc@e52d9eb8f7b63115df1ac544a1376fdbf5a39612 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - - name: Install ganache - run: npm install ganache@latest --global + - name: Install anvil + run: cargo install --git https://github.com/foundry-rs/foundry --locked anvil # https://github.com/actions/cache/blob/main/examples.md#rust---cargo - uses: actions/cache@v3 diff --git a/.github/workflows/test-suite.yml b/.github/workflows/test-suite.yml index 27c91f2262..4fa61e6aaa 100644 --- a/.github/workflows/test-suite.yml +++ b/.github/workflows/test-suite.yml @@ -58,8 +58,8 @@ jobs: uses: arduino/setup-protoc@e52d9eb8f7b63115df1ac544a1376fdbf5a39612 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - - name: Install ganache - run: sudo npm install -g ganache + - name: Install anvil + run: cargo install --git https://github.com/foundry-rs/foundry --locked anvil - name: Run tests in release run: make test-release release-tests-windows: @@ -78,8 +78,8 @@ jobs: run: | choco install python protoc visualstudio2019-workload-vctools -y npm config set msvs_version 2019 - - name: Install ganache - run: npm install -g ganache --loglevel verbose + - name: Install anvil + run: cargo install --git https://github.com/foundry-rs/foundry --locked anvil - name: Install make run: choco install -y make - uses: KyleMayes/install-llvm-action@v1 @@ -140,8 +140,8 @@ jobs: uses: arduino/setup-protoc@e52d9eb8f7b63115df1ac544a1376fdbf5a39612 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - - name: Install ganache - run: sudo npm install -g ganache + - name: Install anvil + run: cargo install --git https://github.com/foundry-rs/foundry --locked anvil - name: Run tests in debug run: make test-debug state-transition-vectors-ubuntu: @@ -196,8 +196,8 @@ jobs: uses: arduino/setup-protoc@e52d9eb8f7b63115df1ac544a1376fdbf5a39612 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - - name: Install ganache - run: sudo npm install -g ganache + - name: Install anvil + run: cargo install --git https://github.com/foundry-rs/foundry --locked anvil - name: Run the beacon chain sim that starts from an eth1 contract run: cargo run --release --bin simulator eth1-sim merge-transition-ubuntu: @@ -212,8 +212,8 @@ jobs: uses: arduino/setup-protoc@e52d9eb8f7b63115df1ac544a1376fdbf5a39612 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - - name: Install ganache - run: sudo npm install -g ganache + - name: Install anvil + run: cargo install --git https://github.com/foundry-rs/foundry --locked anvil - name: Run the beacon chain sim and go through the merge transition run: cargo run --release --bin simulator eth1-sim --post-merge no-eth1-simulator-ubuntu: @@ -228,8 +228,8 @@ jobs: uses: arduino/setup-protoc@e52d9eb8f7b63115df1ac544a1376fdbf5a39612 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - - name: Install ganache - run: sudo npm install -g ganache + - name: Install anvil + run: cargo install --git https://github.com/foundry-rs/foundry --locked anvil - name: Run the beacon chain sim without an eth1 connection run: cargo run --release --bin simulator no-eth1-sim syncing-simulator-ubuntu: @@ -244,8 +244,8 @@ jobs: uses: arduino/setup-protoc@e52d9eb8f7b63115df1ac544a1376fdbf5a39612 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - - name: Install ganache - run: sudo npm install -g ganache + - name: Install anvil + run: cargo install --git https://github.com/foundry-rs/foundry --locked anvil - name: Run the syncing simulator run: cargo run --release --bin simulator syncing-sim doppelganger-protection-test: @@ -260,8 +260,8 @@ jobs: uses: arduino/setup-protoc@e52d9eb8f7b63115df1ac544a1376fdbf5a39612 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - - name: Install ganache - run: sudo npm install -g ganache + - name: Install anvil + run: cargo install --git https://github.com/foundry-rs/foundry --locked anvil - name: Install lighthouse and lcli run: | make diff --git a/Cargo.lock b/Cargo.lock index 52e15630d2..bd70ad0201 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,16 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" +dependencies = [ + "lazy_static", + "regex", +] + [[package]] name = "account_manager" version = "0.3.5" @@ -934,6 +944,38 @@ dependencies = [ "tree_hash", ] +[[package]] +name = "camino" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c530edf18f37068ac2d977409ed5cd50d53d73bc653c7647b48eb78976ac9ae2" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08a1ec454bc3eead8719cb56e15dbbfecdbc14e4b3a3ae4936cc6e31f5fc0d07" +dependencies = [ + "camino", + "cargo-platform", + "semver 1.0.17", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "cast" version = "0.3.0" @@ -1429,9 +1471,9 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "4.0.0-rc.2" +version = "4.0.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03d928d978dbec61a1167414f5ec534f24bea0d7a0d24dd9b6233d3d8223e585" +checksum = "8d4ba9852b42210c7538b75484f9daa0655e9a3ac04f693747bb0f02cf3cfe16" dependencies = [ "cfg-if", "fiat-crypto", @@ -1911,6 +1953,12 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65d09067bfacaa79114679b279d7f5885b53295b1e2cfb4e79c8e4bd3d633169" +[[package]] +name = "dunce" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bd4b30a6560bbd9b4620f4de34c3f14f60848e58a9b7216801afcb4c7b31c3c" + [[package]] name = "ecdsa" version = "0.14.8" @@ -2170,7 +2218,6 @@ dependencies = [ "tokio", "tree_hash", "types", - "web3", ] [[package]] @@ -2178,11 +2225,14 @@ name = "eth1_test_rig" version = "0.2.0" dependencies = [ "deposit_contract", + "ethers-contract", + "ethers-core", + "ethers-providers", + "hex", "serde_json", "tokio", "types", "unused_port", - "web3", ] [[package]] @@ -2469,6 +2519,65 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ethers-contract" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9c3c3e119a89f0a9a1e539e7faecea815f74ddcf7c90d0b00d1f524db2fdc9c" +dependencies = [ + "ethers-contract-abigen", + "ethers-contract-derive", + "ethers-core", + "ethers-providers", + "futures-util", + "hex", + "once_cell", + "pin-project", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "ethers-contract-abigen" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d4e5ad46aede34901f71afdb7bb555710ed9613d88d644245c657dc371aa228" +dependencies = [ + "Inflector", + "cfg-if", + "dunce", + "ethers-core", + "eyre", + "getrandom 0.2.8", + "hex", + "proc-macro2", + "quote", + "regex", + "reqwest", + "serde", + "serde_json", + "syn 1.0.109", + "toml", + "url", + "walkdir", +] + +[[package]] +name = "ethers-contract-derive" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f192e8e4cf2b038318aae01e94e7644e0659a76219e94bcd3203df744341d61f" +dependencies = [ + "ethers-contract-abigen", + "ethers-core", + "hex", + "proc-macro2", + "quote", + "serde_json", + "syn 1.0.109", +] + [[package]] name = "ethers-core" version = "1.0.2" @@ -2477,12 +2586,14 @@ checksum = "ade3e9c97727343984e1ceada4fdab11142d2ee3472d2c67027d56b1251d4f15" dependencies = [ "arrayvec", "bytes", + "cargo_metadata", "chrono", "elliptic-curve", "ethabi 18.0.0", "generic-array", "hex", "k256", + "once_cell", "open-fastrlp", "rand 0.8.5", "rlp", @@ -2490,6 +2601,7 @@ dependencies = [ "serde", "serde_json", "strum", + "syn 1.0.109", "thiserror", "tiny-keccak", "unicode-xid", @@ -2621,6 +2733,16 @@ dependencies = [ "futures", ] +[[package]] +name = "eyre" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" +dependencies = [ + "indenter", + "once_cell", +] + [[package]] name = "fallible-iterator" version = "0.2.0" @@ -3589,6 +3711,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + [[package]] name = "indexmap" version = "1.9.3" @@ -3741,21 +3869,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "jsonrpc-core" -version = "18.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14f7f76aef2d054868398427f6c54943cf3d1caa9a7ec7d0c38d69df97a965eb" -dependencies = [ - "futures", - "futures-executor", - "futures-util", - "log", - "serde", - "serde_derive", - "serde_json", -] - [[package]] name = "jsonwebtoken" version = "8.3.0" @@ -3851,7 +3964,6 @@ dependencies = [ "tree_hash", "types", "validator_dir", - "web3", ] [[package]] @@ -6949,24 +7061,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "secp256k1" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c42e6f1735c5f00f51e43e28d6634141f2bcad10931b2609ddd74a86d751260" -dependencies = [ - "secp256k1-sys", -] - -[[package]] -name = "secp256k1-sys" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957da2573cde917463ece3570eab4a0b3f19de6f1646cde62e6fd3868f566036" -dependencies = [ - "cc", -] - [[package]] name = "security-framework" version = "2.8.2" @@ -7004,6 +7098,9 @@ name = "semver" version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" +dependencies = [ + "serde", +] [[package]] name = "semver-parser" @@ -7485,14 +7582,14 @@ checksum = "5e9f0ab6ef7eb7353d9119c170a436d1bf248eea575ac42d19d12f4e34130831" [[package]] name = "snow" -version = "0.9.0" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "774d05a3edae07ce6d68ea6984f3c05e9bba8927e3dd591e3b479e5b03213d0d" +checksum = "5ccba027ba85743e09d15c03296797cad56395089b832b48b5a5217880f57733" dependencies = [ "aes-gcm 0.9.4", "blake2", "chacha20poly1305", - "curve25519-dalek 4.0.0-rc.2", + "curve25519-dalek 4.0.0-rc.1", "rand_core 0.6.4", "ring", "rustc_version 0.4.0", @@ -9142,53 +9239,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "web3" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44f258e254752d210b84fe117b31f1e3cc9cbf04c0d747eb7f8cf7cf5e370f6d" -dependencies = [ - "arrayvec", - "base64 0.13.1", - "bytes", - "derive_more", - "ethabi 16.0.0", - "ethereum-types 0.12.1", - "futures", - "futures-timer", - "headers", - "hex", - "idna 0.2.3", - "jsonrpc-core", - "log", - "once_cell", - "parking_lot 0.12.1", - "pin-project", - "reqwest", - "rlp", - "secp256k1", - "serde", - "serde_json", - "soketto", - "tiny-keccak", - "tokio", - "tokio-util 0.6.10", - "url", - "web3-async-native-tls", -] - -[[package]] -name = "web3-async-native-tls" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f6d8d1636b2627fe63518d5a9b38a569405d9c9bc665c43c9c341de57227ebb" -dependencies = [ - "native-tls", - "thiserror", - "tokio", - "url", -] - [[package]] name = "web3signer_tests" version = "0.1.0" @@ -9338,7 +9388,7 @@ dependencies = [ "tokio", "webpki 0.21.4", "webrtc-util", - "x25519-dalek 2.0.0-rc.2", + "x25519-dalek 2.0.0-pre.1", "x509-parser 0.13.2", ] @@ -9718,13 +9768,12 @@ dependencies = [ [[package]] name = "x25519-dalek" -version = "2.0.0-rc.2" +version = "2.0.0-pre.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabd6e16dd08033932fc3265ad4510cc2eab24656058a6dcb107ffe274abcc95" +checksum = "e5da623d8af10a62342bcbbb230e33e58a63255a58012f8653c578e54bab48df" dependencies = [ - "curve25519-dalek 4.0.0-rc.2", + "curve25519-dalek 3.2.0", "rand_core 0.6.4", - "serde", "zeroize", ] diff --git a/beacon_node/eth1/Cargo.toml b/beacon_node/eth1/Cargo.toml index 1148f063d8..cc982aee08 100644 --- a/beacon_node/eth1/Cargo.toml +++ b/beacon_node/eth1/Cargo.toml @@ -7,7 +7,6 @@ edition = "2021" [dev-dependencies] eth1_test_rig = { path = "../../testing/eth1_test_rig" } serde_yaml = "0.8.13" -web3 = { version = "0.18.0", default-features = false, features = ["http-tls", "signing", "ws-tls-tokio"] } sloggers = { version = "2.1.1", features = ["json"] } environment = { path = "../../lighthouse/environment" } diff --git a/beacon_node/eth1/tests/test.rs b/beacon_node/eth1/tests/test.rs index cd680478cc..505e4a4796 100644 --- a/beacon_node/eth1/tests/test.rs +++ b/beacon_node/eth1/tests/test.rs @@ -2,7 +2,7 @@ use environment::{Environment, EnvironmentBuilder}; use eth1::{Config, Eth1Endpoint, Service}; use eth1::{DepositCache, DEFAULT_CHAIN_ID}; -use eth1_test_rig::GanacheEth1Instance; +use eth1_test_rig::{AnvilEth1Instance, Http, Middleware, Provider}; use execution_layer::http::{deposit_methods::*, HttpJsonRpc, Log}; use merkle_proof::verify_merkle_proof; use sensitive_url::SensitiveUrl; @@ -12,7 +12,6 @@ use std::ops::Range; use std::time::Duration; use tree_hash::TreeHash; use types::{DepositData, EthSpec, Hash256, Keypair, MainnetEthSpec, MinimalEthSpec, Signature}; -use web3::{transports::Http, Web3}; const DEPOSIT_CONTRACT_TREE_DEPTH: usize = 32; @@ -53,7 +52,7 @@ fn random_deposit_data() -> DepositData { /// Blocking operation to get the deposit logs from the `deposit_contract`. async fn blocking_deposit_logs( client: &HttpJsonRpc, - eth1: &GanacheEth1Instance, + eth1: &AnvilEth1Instance, range: Range, ) -> Vec { client @@ -65,7 +64,7 @@ async fn blocking_deposit_logs( /// Blocking operation to get the deposit root from the `deposit_contract`. async fn blocking_deposit_root( client: &HttpJsonRpc, - eth1: &GanacheEth1Instance, + eth1: &AnvilEth1Instance, block_number: u64, ) -> Option { client @@ -77,7 +76,7 @@ async fn blocking_deposit_root( /// Blocking operation to get the deposit count from the `deposit_contract`. async fn blocking_deposit_count( client: &HttpJsonRpc, - eth1: &GanacheEth1Instance, + eth1: &AnvilEth1Instance, block_number: u64, ) -> Option { client @@ -86,16 +85,16 @@ async fn blocking_deposit_count( .expect("should get deposit count") } -async fn get_block_number(web3: &Web3) -> u64 { - web3.eth() - .block_number() +async fn get_block_number(client: &Provider) -> u64 { + client + .get_block_number() .await .map(|v| v.as_u64()) .expect("should get block number") } -async fn new_ganache_instance() -> Result { - GanacheEth1Instance::new(DEFAULT_CHAIN_ID.into()).await +async fn new_anvil_instance() -> Result { + AnvilEth1Instance::new(DEFAULT_CHAIN_ID.into()).await } mod eth1_cache { @@ -108,13 +107,13 @@ mod eth1_cache { let log = null_logger(); for follow_distance in 0..3 { - let eth1 = new_ganache_instance() + let eth1 = new_anvil_instance() .await .expect("should start eth1 environment"); let deposit_contract = ð1.deposit_contract; - let web3 = eth1.web3(); + let anvil_client = eth1.json_rpc_client(); - let initial_block_number = get_block_number(&web3).await; + let initial_block_number = get_block_number(&anvil_client).await; let config = Config { endpoint: Eth1Endpoint::NoAuth( @@ -146,7 +145,7 @@ mod eth1_cache { }; for _ in 0..blocks { - eth1.ganache.evm_mine().await.expect("should mine block"); + eth1.anvil.evm_mine().await.expect("should mine block"); } service @@ -189,11 +188,11 @@ mod eth1_cache { async { let log = null_logger(); - let eth1 = new_ganache_instance() + let eth1 = new_anvil_instance() .await .expect("should start eth1 environment"); let deposit_contract = ð1.deposit_contract; - let web3 = eth1.web3(); + let anvil_client = eth1.json_rpc_client(); let cache_len = 4; @@ -203,7 +202,7 @@ mod eth1_cache { SensitiveUrl::parse(eth1.endpoint().as_str()).unwrap(), ), deposit_contract_address: deposit_contract.address(), - lowest_cached_block_number: get_block_number(&web3).await, + lowest_cached_block_number: get_block_number(&anvil_client).await, follow_distance: 0, block_cache_truncation: Some(cache_len), ..Config::default() @@ -216,7 +215,7 @@ mod eth1_cache { let blocks = cache_len * 2; for _ in 0..blocks { - eth1.ganache.evm_mine().await.expect("should mine block") + eth1.anvil.evm_mine().await.expect("should mine block") } service @@ -244,11 +243,11 @@ mod eth1_cache { async { let log = null_logger(); - let eth1 = new_ganache_instance() + let eth1 = new_anvil_instance() .await .expect("should start eth1 environment"); let deposit_contract = ð1.deposit_contract; - let web3 = eth1.web3(); + let anvil_client = eth1.json_rpc_client(); let cache_len = 4; @@ -258,7 +257,7 @@ mod eth1_cache { SensitiveUrl::parse(eth1.endpoint().as_str()).unwrap(), ), deposit_contract_address: deposit_contract.address(), - lowest_cached_block_number: get_block_number(&web3).await, + lowest_cached_block_number: get_block_number(&anvil_client).await, follow_distance: 0, block_cache_truncation: Some(cache_len), ..Config::default() @@ -270,7 +269,7 @@ mod eth1_cache { for _ in 0..4u8 { for _ in 0..cache_len / 2 { - eth1.ganache.evm_mine().await.expect("should mine block") + eth1.anvil.evm_mine().await.expect("should mine block") } service .update_deposit_cache(None) @@ -298,11 +297,11 @@ mod eth1_cache { let n = 16; - let eth1 = new_ganache_instance() + let eth1 = new_anvil_instance() .await .expect("should start eth1 environment"); let deposit_contract = ð1.deposit_contract; - let web3 = eth1.web3(); + let anvil_client = eth1.json_rpc_client(); let service = Service::new( Config { @@ -310,7 +309,7 @@ mod eth1_cache { SensitiveUrl::parse(eth1.endpoint().as_str()).unwrap(), ), deposit_contract_address: deposit_contract.address(), - lowest_cached_block_number: get_block_number(&web3).await, + lowest_cached_block_number: get_block_number(&anvil_client).await, follow_distance: 0, ..Config::default() }, @@ -320,7 +319,7 @@ mod eth1_cache { .unwrap(); for _ in 0..n { - eth1.ganache.evm_mine().await.expect("should mine block") + eth1.anvil.evm_mine().await.expect("should mine block") } futures::try_join!( @@ -341,6 +340,7 @@ mod eth1_cache { } mod deposit_tree { + use super::*; #[tokio::test] @@ -350,13 +350,13 @@ mod deposit_tree { let n = 4; - let eth1 = new_ganache_instance() + let eth1 = new_anvil_instance() .await .expect("should start eth1 environment"); let deposit_contract = ð1.deposit_contract; - let web3 = eth1.web3(); + let anvil_client = eth1.json_rpc_client(); - let start_block = get_block_number(&web3).await; + let start_block = get_block_number(&anvil_client).await; let service = Service::new( Config { @@ -431,13 +431,13 @@ mod deposit_tree { let n = 8; - let eth1 = new_ganache_instance() + let eth1 = new_anvil_instance() .await .expect("should start eth1 environment"); let deposit_contract = ð1.deposit_contract; - let web3 = eth1.web3(); + let anvil_client = eth1.json_rpc_client(); - let start_block = get_block_number(&web3).await; + let start_block = get_block_number(&anvil_client).await; let service = Service::new( Config { @@ -484,11 +484,12 @@ mod deposit_tree { let deposits: Vec<_> = (0..n).map(|_| random_deposit_data()).collect(); - let eth1 = new_ganache_instance() + let eth1 = new_anvil_instance() .await .expect("should start eth1 environment"); + let deposit_contract = ð1.deposit_contract; - let web3 = eth1.web3(); + let anvil_client = eth1.json_rpc_client(); let mut deposit_roots = vec![]; let mut deposit_counts = vec![]; @@ -502,7 +503,7 @@ mod deposit_tree { .deposit(deposit.clone()) .await .expect("should perform a deposit"); - let block_number = get_block_number(&web3).await; + let block_number = get_block_number(&anvil_client).await; deposit_roots.push( blocking_deposit_root(&client, ð1, block_number) .await @@ -518,7 +519,7 @@ mod deposit_tree { let mut tree = DepositCache::default(); // Pull all the deposit logs from the contract. - let block_number = get_block_number(&web3).await; + let block_number = get_block_number(&anvil_client).await; let logs: Vec<_> = blocking_deposit_logs(&client, ð1, 0..block_number) .await .iter() @@ -593,15 +594,15 @@ mod http { #[tokio::test] async fn incrementing_deposits() { async { - let eth1 = new_ganache_instance() + let eth1 = new_anvil_instance() .await .expect("should start eth1 environment"); let deposit_contract = ð1.deposit_contract; - let web3 = eth1.web3(); + let anvil_client = eth1.json_rpc_client(); let client = HttpJsonRpc::new(SensitiveUrl::parse(ð1.endpoint()).unwrap(), None).unwrap(); - let block_number = get_block_number(&web3).await; + let block_number = get_block_number(&anvil_client).await; let logs = blocking_deposit_logs(&client, ð1, 0..block_number).await; assert_eq!(logs.len(), 0); @@ -616,10 +617,10 @@ mod http { ); for i in 1..=8 { - eth1.ganache + eth1.anvil .increase_time(1) .await - .expect("should be able to increase time on ganache"); + .expect("should be able to increase time on anvil"); deposit_contract .deposit(random_deposit_data()) @@ -627,7 +628,7 @@ mod http { .expect("should perform a deposit"); // Check the logs. - let block_number = get_block_number(&web3).await; + let block_number = get_block_number(&anvil_client).await; let logs = blocking_deposit_logs(&client, ð1, 0..block_number).await; assert_eq!(logs.len(), i, "the number of logs should be as expected"); @@ -690,13 +691,13 @@ mod fast { async { let log = null_logger(); - let eth1 = new_ganache_instance() + let eth1 = new_anvil_instance() .await .expect("should start eth1 environment"); let deposit_contract = ð1.deposit_contract; - let web3 = eth1.web3(); + let anvil_client = eth1.json_rpc_client(); - let now = get_block_number(&web3).await; + let now = get_block_number(&anvil_client).await; let spec = MainnetEthSpec::default_spec(); let service = Service::new( Config { @@ -724,7 +725,7 @@ mod fast { .await .expect("should perform a deposit"); // Mine an extra block between deposits to test for corner cases - eth1.ganache.evm_mine().await.expect("should mine block"); + eth1.anvil.evm_mine().await.expect("should mine block"); } service @@ -737,7 +738,7 @@ mod fast { "should have imported n deposits" ); - for block_num in 0..=get_block_number(&web3).await { + for block_num in 0..=get_block_number(&anvil_client).await { let expected_deposit_count = blocking_deposit_count(&client, ð1, block_num).await; let expected_deposit_root = blocking_deposit_root(&client, ð1, block_num).await; @@ -773,13 +774,13 @@ mod persist { async { let log = null_logger(); - let eth1 = new_ganache_instance() + let eth1 = new_anvil_instance() .await .expect("should start eth1 environment"); let deposit_contract = ð1.deposit_contract; - let web3 = eth1.web3(); + let anvil_client = eth1.json_rpc_client(); - let now = get_block_number(&web3).await; + let now = get_block_number(&anvil_client).await; let config = Config { endpoint: Eth1Endpoint::NoAuth( SensitiveUrl::parse(eth1.endpoint().as_str()).unwrap(), diff --git a/beacon_node/genesis/tests/tests.rs b/beacon_node/genesis/tests/tests.rs index aaf6a7bea1..f99fcb55bf 100644 --- a/beacon_node/genesis/tests/tests.rs +++ b/beacon_node/genesis/tests/tests.rs @@ -1,11 +1,11 @@ -//! NOTE: These tests will not pass unless ganache is running on `ENDPOINT` (see below). +//! NOTE: These tests will not pass unless an anvil is running on `ENDPOINT` (see below). //! -//! You can start a suitable instance using the `ganache_test_node.sh` script in the `scripts` +//! You can start a suitable instance using the `anvil_test_node.sh` script in the `scripts` //! dir in the root of the `lighthouse` repo. #![cfg(test)] use environment::{Environment, EnvironmentBuilder}; use eth1::{Eth1Endpoint, DEFAULT_CHAIN_ID}; -use eth1_test_rig::{DelayThenDeposit, GanacheEth1Instance}; +use eth1_test_rig::{AnvilEth1Instance, DelayThenDeposit, Middleware}; use genesis::{Eth1Config, Eth1GenesisService}; use sensitive_url::SensitiveUrl; use state_processing::is_valid_genesis_state; @@ -29,15 +29,14 @@ fn basic() { let mut spec = env.eth2_config().spec.clone(); env.runtime().block_on(async { - let eth1 = GanacheEth1Instance::new(DEFAULT_CHAIN_ID.into()) + let eth1 = AnvilEth1Instance::new(DEFAULT_CHAIN_ID.into()) .await .expect("should start eth1 environment"); let deposit_contract = ð1.deposit_contract; - let web3 = eth1.web3(); + let client = eth1.json_rpc_client(); - let now = web3 - .eth() - .block_number() + let now = client + .get_block_number() .await .map(|v| v.as_u64()) .expect("should get block number"); @@ -89,7 +88,7 @@ fn basic() { .map(|(_, state)| state) .expect("should finish waiting for genesis"); - // Note: using ganache these deposits are 1-per-block, therefore we know there should only be + // Note: using anvil these deposits are 1-per-block, therefore we know there should only be // the minimum number of validators. assert_eq!( state.validators().len(), diff --git a/book/src/setup.md b/book/src/setup.md index a1febe4a02..62580ac1be 100644 --- a/book/src/setup.md +++ b/book/src/setup.md @@ -9,9 +9,9 @@ particularly useful for development but still a good way to ensure you have the base dependencies. The additional requirements for developers are: -- [`ganache v7`](https://github.com/trufflesuite/ganache). This is used to +- [`anvil`](https://github.com/foundry-rs/foundry/tree/master/anvil). This is used to simulate the execution chain during tests. You'll get failures during tests if you - don't have `ganache` available on your `PATH` or if ganache is older than v7. + don't have `anvil` available on your `PATH`. - [`cmake`](https://cmake.org/cmake/help/latest/command/install.html). Used by some dependencies. See [`Installation Guide`](./installation.md) for more info. - [`protoc`](https://github.com/protocolbuffers/protobuf/releases) required for diff --git a/lcli/Cargo.toml b/lcli/Cargo.toml index 9e7f2fdb08..af8df1b6b0 100644 --- a/lcli/Cargo.toml +++ b/lcli/Cargo.toml @@ -34,7 +34,6 @@ lighthouse_version = { path = "../common/lighthouse_version" } directory = { path = "../common/directory" } account_utils = { path = "../common/account_utils" } eth2_wallet = { path = "../crypto/eth2_wallet" } -web3 = { version = "0.18.0", default-features = false, features = ["http-tls", "signing", "ws-tls-tokio"] } eth1_test_rig = { path = "../testing/eth1_test_rig" } sensitive_url = { path = "../common/sensitive_url" } eth2 = { path = "../common/eth2" } diff --git a/lcli/src/deploy_deposit_contract.rs b/lcli/src/deploy_deposit_contract.rs index 1128eb52ab..8919ebdaf5 100644 --- a/lcli/src/deploy_deposit_contract.rs +++ b/lcli/src/deploy_deposit_contract.rs @@ -2,19 +2,18 @@ use clap::ArgMatches; use environment::Environment; use types::EthSpec; -use web3::{transports::Http, Web3}; +use eth1_test_rig::{Http, Provider}; pub fn run(env: Environment, matches: &ArgMatches<'_>) -> Result<(), String> { let eth1_http: String = clap_utils::parse_required(matches, "eth1-http")?; let confirmations: usize = clap_utils::parse_required(matches, "confirmations")?; let validator_count: Option = clap_utils::parse_optional(matches, "validator-count")?; - let transport = - Http::new(ð1_http).map_err(|e| format!("Unable to connect to eth1 HTTP: {:?}", e))?; - let web3 = Web3::new(transport); + let client = Provider::::try_from(ð1_http) + .map_err(|e| format!("Unable to connect to eth1 HTTP: {:?}", e))?; env.runtime().block_on(async { - let contract = eth1_test_rig::DepositContract::deploy(web3, confirmations, None) + let contract = eth1_test_rig::DepositContract::deploy(client, confirmations, None) .await .map_err(|e| format!("Failed to deploy deposit contract: {:?}", e))?; diff --git a/lighthouse/tests/account_manager.rs b/lighthouse/tests/account_manager.rs index 696830a0d1..63d79fceb2 100644 --- a/lighthouse/tests/account_manager.rs +++ b/lighthouse/tests/account_manager.rs @@ -28,10 +28,6 @@ use tempfile::{tempdir, TempDir}; use types::{Keypair, PublicKey}; use validator_dir::ValidatorDir; -// TODO: create tests for the `lighthouse account validator deposit` command. This involves getting -// access to an IPC endpoint during testing or adding support for deposit submission via HTTP and -// using ganache. - /// Returns the `lighthouse account` command. fn account_cmd() -> Command { let lighthouse_bin = env!("CARGO_BIN_EXE_lighthouse"); diff --git a/scripts/local_testnet/README.md b/scripts/local_testnet/README.md index c4050ac934..6a8a05f9cd 100644 --- a/scripts/local_testnet/README.md +++ b/scripts/local_testnet/README.md @@ -17,7 +17,7 @@ make install-lcli Modify `vars.env` as desired. -Start a local eth1 ganache server plus boot node along with `BN_COUNT` +Start a local eth1 anvil server plus boot node along with `BN_COUNT` number of beacon nodes and `VC_COUNT` validator clients. The `start_local_testnet.sh` script takes four options `-v VC_COUNT`, `-d DEBUG_LEVEL`, `-p` to enable builder proposals and `-h` for help. @@ -41,9 +41,9 @@ This is not necessary before `start_local_testnet.sh` as it invokes `stop_local_ These scripts are used by ./start_local_testnet.sh and may be used to manually -Start a local eth1 ganache server +Start a local eth1 anvil server ```bash -./ganache_test_node.sh +./anvil_test_node.sh ``` Assuming you are happy with the configuration in `vars.env`, deploy the deposit contract, make deposits, diff --git a/scripts/local_testnet/anvil_test_node.sh b/scripts/local_testnet/anvil_test_node.sh new file mode 100755 index 0000000000..41be917560 --- /dev/null +++ b/scripts/local_testnet/anvil_test_node.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +set -Eeuo pipefail + +source ./vars.env + +exec anvil \ + --balance 1000000000 \ + --gas-limit 1000000000 \ + --accounts 10 \ + --mnemonic "$ETH1_NETWORK_MNEMONIC" \ + --block-time $SECONDS_PER_ETH1_BLOCK \ + --port 8545 \ + --chain-id "$CHAIN_ID" diff --git a/scripts/local_testnet/ganache_test_node.sh b/scripts/local_testnet/ganache_test_node.sh deleted file mode 100755 index a489c33224..0000000000 --- a/scripts/local_testnet/ganache_test_node.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash - -set -Eeuo pipefail - -source ./vars.env - -exec ganache \ - --defaultBalanceEther 1000000000 \ - --gasLimit 1000000000 \ - --accounts 10 \ - --mnemonic "$ETH1_NETWORK_MNEMONIC" \ - --port 8545 \ - --blockTime $SECONDS_PER_ETH1_BLOCK \ - --chain.chainId "$CHAIN_ID" diff --git a/scripts/local_testnet/start_local_testnet.sh b/scripts/local_testnet/start_local_testnet.sh index e3aba5c3ad..a6f5ec7a8c 100755 --- a/scripts/local_testnet/start_local_testnet.sh +++ b/scripts/local_testnet/start_local_testnet.sh @@ -92,11 +92,11 @@ execute_command_add_PID() { echo "$!" >> $PID_FILE } -# Start ganache, setup things up and start the bootnode. +# Start anvil, setup things up and start the bootnode. # The delays are necessary, hopefully there is a better way :( -# Delay to let ganache to get started -execute_command_add_PID ganache_test_node.log ./ganache_test_node.sh +# Delay to let anvil to get started +execute_command_add_PID anvil_test_node.log ./anvil_test_node.sh sleeping 10 # Setup data diff --git a/scripts/local_testnet/vars.env b/scripts/local_testnet/vars.env index 1ade173286..80c4ef1331 100644 --- a/scripts/local_testnet/vars.env +++ b/scripts/local_testnet/vars.env @@ -4,7 +4,7 @@ DATADIR=~/.lighthouse/local-testnet # Directory for the eth2 config TESTNET_DIR=$DATADIR/testnet -# Mnemonic for the ganache test network +# Mnemonic for the anvil test network ETH1_NETWORK_MNEMONIC="vast thought differ pull jewel broom cook wrist tribe word before omit" # Hardcoded deposit contract based on ETH1_NETWORK_MNEMONIC diff --git a/scripts/tests/doppelganger_protection.sh b/scripts/tests/doppelganger_protection.sh index 95dfff5696..e68ca21516 100755 --- a/scripts/tests/doppelganger_protection.sh +++ b/scripts/tests/doppelganger_protection.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# Requires `lighthouse`, ``lcli`, `ganache`, `curl`, `jq` +# Requires `lighthouse`, ``lcli`, `anvil`, `curl`, `jq` BEHAVIOR=$1 @@ -23,12 +23,12 @@ source ./vars.env exit_if_fails ../local_testnet/clean.sh -echo "Starting ganache" +echo "Starting anvil" -exit_if_fails ../local_testnet/ganache_test_node.sh &> /dev/null & -GANACHE_PID=$! +exit_if_fails ../local_testnet/anvil_test_node.sh &> /dev/null & +ANVIL_PID=$! -# Wait for ganache to start +# Wait for anvil to start sleep 5 echo "Setting up local testnet" @@ -79,7 +79,7 @@ if [[ "$BEHAVIOR" == "failure" ]]; then echo "Shutting down" # Cleanup - kill $BOOT_PID $BEACON_PID $BEACON_PID2 $BEACON_PID3 $GANACHE_PID $VALIDATOR_1_PID $VALIDATOR_2_PID $VALIDATOR_3_PID + kill $BOOT_PID $BEACON_PID $BEACON_PID2 $BEACON_PID3 $ANVIL_PID $VALIDATOR_1_PID $VALIDATOR_2_PID $VALIDATOR_3_PID echo "Done" @@ -144,7 +144,7 @@ if [[ "$BEHAVIOR" == "success" ]]; then # Cleanup cd $PREVIOUS_DIR - kill $BOOT_PID $BEACON_PID $BEACON_PID2 $BEACON_PID3 $GANACHE_PID $VALIDATOR_1_PID $VALIDATOR_2_PID $VALIDATOR_3_PID $VALIDATOR_4_PID + kill $BOOT_PID $BEACON_PID $BEACON_PID2 $BEACON_PID3 $ANVIL_PID $VALIDATOR_1_PID $VALIDATOR_2_PID $VALIDATOR_3_PID $VALIDATOR_4_PID echo "Done" diff --git a/scripts/tests/vars.env b/scripts/tests/vars.env index 778a0afca5..7429a35eb6 100644 --- a/scripts/tests/vars.env +++ b/scripts/tests/vars.env @@ -4,7 +4,7 @@ DATADIR=~/.lighthouse/local-testnet # Directory for the eth2 config TESTNET_DIR=$DATADIR/testnet -# Mnemonic for the ganache test network +# Mnemonic for the anvil test network ETH1_NETWORK_MNEMONIC="vast thought differ pull jewel broom cook wrist tribe word before omit" # Hardcoded deposit contract based on ETH1_NETWORK_MNEMONIC diff --git a/testing/eth1_test_rig/Cargo.toml b/testing/eth1_test_rig/Cargo.toml index 08766f14fc..5c78c09022 100644 --- a/testing/eth1_test_rig/Cargo.toml +++ b/testing/eth1_test_rig/Cargo.toml @@ -6,8 +6,11 @@ edition = "2021" [dependencies] tokio = { version = "1.14.0", features = ["time"] } -web3 = { version = "0.18.0", default-features = false, features = ["http-tls", "signing", "ws-tls-tokio"] } +ethers-core = "1.0.2" +ethers-providers = "1.0.2" +ethers-contract = "1.0.2" types = { path = "../../consensus/types"} serde_json = "1.0.58" deposit_contract = { path = "../../common/deposit_contract"} unused_port = { path = "../../common/unused_port" } +hex = "0.4.2" diff --git a/testing/eth1_test_rig/src/anvil.rs b/testing/eth1_test_rig/src/anvil.rs new file mode 100644 index 0000000000..1b86711c2f --- /dev/null +++ b/testing/eth1_test_rig/src/anvil.rs @@ -0,0 +1,101 @@ +use ethers_core::utils::{Anvil, AnvilInstance}; +use ethers_providers::{Http, Middleware, Provider}; +use serde_json::json; +use std::convert::TryFrom; +use unused_port::unused_tcp4_port; + +/// Provides a dedicated `anvil` instance. +/// +/// Requires that `anvil` is installed and available on `PATH`. +pub struct AnvilCliInstance { + pub port: u16, + pub anvil: AnvilInstance, + pub client: Provider, + chain_id: u64, +} + +impl AnvilCliInstance { + fn new_from_child(anvil_instance: Anvil, chain_id: u64, port: u16) -> Result { + let client = Provider::::try_from(&endpoint(port)) + .map_err(|e| format!("Failed to start HTTP transport connected to anvil: {:?}", e))?; + Ok(Self { + port, + anvil: anvil_instance.spawn(), + client, + chain_id, + }) + } + pub fn new(chain_id: u64) -> Result { + let port = unused_tcp4_port()?; + + let anvil = Anvil::new() + .port(port) + .mnemonic("vast thought differ pull jewel broom cook wrist tribe word before omit") + .arg("--balance") + .arg("1000000000") + .arg("--gas-limit") + .arg("1000000000") + .arg("--accounts") + .arg("10") + .arg("--chain-id") + .arg(format!("{}", chain_id)); + + Self::new_from_child(anvil, chain_id, port) + } + + pub fn fork(&self) -> Result { + let port = unused_tcp4_port()?; + + let anvil = Anvil::new() + .port(port) + .arg("--chain-id") + .arg(format!("{}", self.chain_id())) + .fork(self.endpoint()); + + Self::new_from_child(anvil, self.chain_id, port) + } + + /// Returns the endpoint that this instance is listening on. + pub fn endpoint(&self) -> String { + endpoint(self.port) + } + + /// Returns the chain id of the anvil instance + pub fn chain_id(&self) -> u64 { + self.chain_id + } + + /// Increase the timestamp on future blocks by `increase_by` seconds. + pub async fn increase_time(&self, increase_by: u64) -> Result<(), String> { + self.client + .request("evm_increaseTime", vec![json!(increase_by)]) + .await + .map(|_json_value: u64| ()) + .map_err(|e| format!("Failed to increase time on EVM (is this anvil?): {:?}", e)) + } + + /// Returns the current block number, as u64 + pub async fn block_number(&self) -> Result { + self.client + .get_block_number() + .await + .map(|v| v.as_u64()) + .map_err(|e| format!("Failed to get block number: {:?}", e)) + } + + /// Mines a single block. + pub async fn evm_mine(&self) -> Result<(), String> { + self.client + .request("evm_mine", ()) + .await + .map(|_: String| ()) + .map_err(|_| { + "utils should mine new block with evm_mine (only works with anvil/ganache!)" + .to_string() + }) + } +} + +fn endpoint(port: u16) -> String { + format!("http://127.0.0.1:{}", port) +} diff --git a/testing/eth1_test_rig/src/ganache.rs b/testing/eth1_test_rig/src/ganache.rs deleted file mode 100644 index 898a089ba0..0000000000 --- a/testing/eth1_test_rig/src/ganache.rs +++ /dev/null @@ -1,193 +0,0 @@ -use serde_json::json; -use std::io::prelude::*; -use std::io::BufReader; -use std::process::{Child, Command, Stdio}; -use std::time::{Duration, Instant}; -use unused_port::unused_tcp4_port; -use web3::{transports::Http, Transport, Web3}; - -/// How long we will wait for ganache to indicate that it is ready. -const GANACHE_STARTUP_TIMEOUT_MILLIS: u64 = 10_000; - -/// Provides a dedicated `ganachi-cli` instance with a connected `Web3` instance. -/// -/// Requires that `ganachi-cli` is installed and available on `PATH`. -pub struct GanacheInstance { - pub port: u16, - child: Child, - pub web3: Web3, - chain_id: u64, -} - -impl GanacheInstance { - fn new_from_child(mut child: Child, port: u16, chain_id: u64) -> Result { - let stdout = child - .stdout - .ok_or("Unable to get stdout for ganache child process")?; - - let start = Instant::now(); - let mut reader = BufReader::new(stdout); - loop { - if start + Duration::from_millis(GANACHE_STARTUP_TIMEOUT_MILLIS) <= Instant::now() { - break Err( - "Timed out waiting for ganache to start. Is ganache installed?".to_string(), - ); - } - - let mut line = String::new(); - if let Err(e) = reader.read_line(&mut line) { - break Err(format!("Failed to read line from ganache process: {:?}", e)); - } else if line.starts_with("RPC Listening on") { - break Ok(()); - } else { - continue; - } - }?; - - let transport = Http::new(&endpoint(port)).map_err(|e| { - format!( - "Failed to start HTTP transport connected to ganache: {:?}", - e - ) - })?; - let web3 = Web3::new(transport); - - child.stdout = Some(reader.into_inner()); - - Ok(Self { - port, - child, - web3, - chain_id, - }) - } - - /// Start a new `ganache` process, waiting until it indicates that it is ready to accept - /// RPC connections. - pub fn new(chain_id: u64) -> Result { - let port = unused_tcp4_port()?; - let binary = match cfg!(windows) { - true => "ganache.cmd", - false => "ganache", - }; - let child = Command::new(binary) - .stdout(Stdio::piped()) - .arg("--defaultBalanceEther") - .arg("1000000000") - .arg("--gasLimit") - .arg("1000000000") - .arg("--accounts") - .arg("10") - .arg("--port") - .arg(format!("{}", port)) - .arg("--mnemonic") - .arg("\"vast thought differ pull jewel broom cook wrist tribe word before omit\"") - .arg("--chain.chainId") - .arg(format!("{}", chain_id)) - .spawn() - .map_err(|e| { - format!( - "Failed to start {}. \ - Is it installed and available on $PATH? Error: {:?}", - binary, e - ) - })?; - - Self::new_from_child(child, port, chain_id) - } - - pub fn fork(&self) -> Result { - let port = unused_tcp4_port()?; - let binary = match cfg!(windows) { - true => "ganache.cmd", - false => "ganache", - }; - let child = Command::new(binary) - .stdout(Stdio::piped()) - .arg("--fork") - .arg(self.endpoint()) - .arg("--port") - .arg(format!("{}", port)) - .arg("--chain.chainId") - .arg(format!("{}", self.chain_id)) - .spawn() - .map_err(|e| { - format!( - "Failed to start {}. \ - Is it installed and available on $PATH? Error: {:?}", - binary, e - ) - })?; - - Self::new_from_child(child, port, self.chain_id) - } - - /// Returns the endpoint that this instance is listening on. - pub fn endpoint(&self) -> String { - endpoint(self.port) - } - - /// Returns the chain id of the ganache instance - pub fn chain_id(&self) -> u64 { - self.chain_id - } - - /// Increase the timestamp on future blocks by `increase_by` seconds. - pub async fn increase_time(&self, increase_by: u64) -> Result<(), String> { - self.web3 - .transport() - .execute("evm_increaseTime", vec![json!(increase_by)]) - .await - .map(|_json_value| ()) - .map_err(|e| format!("Failed to increase time on EVM (is this ganache?): {:?}", e)) - } - - /// Returns the current block number, as u64 - pub async fn block_number(&self) -> Result { - self.web3 - .eth() - .block_number() - .await - .map(|v| v.as_u64()) - .map_err(|e| format!("Failed to get block number: {:?}", e)) - } - - /// Mines a single block. - pub async fn evm_mine(&self) -> Result<(), String> { - self.web3 - .transport() - .execute("evm_mine", vec![]) - .await - .map(|_| ()) - .map_err(|_| { - "utils should mine new block with evm_mine (only works with ganache!)".to_string() - }) - } -} - -fn endpoint(port: u16) -> String { - format!("http://127.0.0.1:{}", port) -} - -impl Drop for GanacheInstance { - fn drop(&mut self) { - if cfg!(windows) { - // Calling child.kill() in Windows will only kill the process - // that spawned ganache, leaving the actual ganache process - // intact. You have to kill the whole process tree. What's more, - // if you don't spawn ganache with --keepAliveTimeout=0, Windows - // will STILL keep the server running even after you've ended - // the process tree and it's disappeared from the task manager. - // Unbelievable... - Command::new("taskkill") - .arg("/pid") - .arg(self.child.id().to_string()) - .arg("/T") - .arg("/F") - .output() - .expect("failed to execute taskkill"); - } else { - let _ = self.child.kill(); - } - } -} diff --git a/testing/eth1_test_rig/src/lib.rs b/testing/eth1_test_rig/src/lib.rs index 42081a60e7..0063975ee1 100644 --- a/testing/eth1_test_rig/src/lib.rs +++ b/testing/eth1_test_rig/src/lib.rs @@ -1,77 +1,79 @@ //! Provides utilities for deploying and manipulating the eth2 deposit contract on the eth1 chain. //! -//! Presently used with [`ganache`](https://github.com/trufflesuite/ganache) to simulate +//! Presently used with [`anvil`](https://github.com/foundry-rs/foundry/tree/master/anvil) to simulate //! the deposit contract for testing beacon node eth1 integration. //! //! Not tested to work with actual clients (e.g., geth). It should work fine, however there may be //! some initial issues. -mod ganache; +mod anvil; +use anvil::AnvilCliInstance; use deposit_contract::{ encode_eth1_tx_data, testnet, ABI, BYTECODE, CONTRACT_DEPLOY_GAS, DEPOSIT_GAS, }; -use ganache::GanacheInstance; +use ethers_contract::Contract; +use ethers_core::{ + abi::Abi, + types::{transaction::eip2718::TypedTransaction, Address, Bytes, TransactionRequest, U256}, +}; +pub use ethers_providers::{Http, Middleware, Provider}; use std::time::Duration; use tokio::time::sleep; use types::DepositData; use types::{test_utils::generate_deterministic_keypair, EthSpec, Hash256, Keypair, Signature}; -use web3::contract::{Contract, Options}; -use web3::transports::Http; -use web3::types::{Address, TransactionRequest, U256}; -use web3::Web3; pub const DEPLOYER_ACCOUNTS_INDEX: usize = 0; pub const DEPOSIT_ACCOUNTS_INDEX: usize = 0; -/// Provides a dedicated ganache instance with the deposit contract already deployed. -pub struct GanacheEth1Instance { - pub ganache: GanacheInstance, +/// Provides a dedicated anvil instance with the deposit contract already deployed. +pub struct AnvilEth1Instance { + pub anvil: AnvilCliInstance, pub deposit_contract: DepositContract, } -impl GanacheEth1Instance { +impl AnvilEth1Instance { pub async fn new(chain_id: u64) -> Result { - let ganache = GanacheInstance::new(chain_id)?; - DepositContract::deploy(ganache.web3.clone(), 0, None) + let anvil = AnvilCliInstance::new(chain_id)?; + DepositContract::deploy(anvil.client.clone(), 0, None) .await .map(|deposit_contract| Self { - ganache, + anvil, deposit_contract, }) } pub fn endpoint(&self) -> String { - self.ganache.endpoint() + self.anvil.endpoint() } - pub fn web3(&self) -> Web3 { - self.ganache.web3.clone() + pub fn json_rpc_client(&self) -> Provider { + self.anvil.client.clone() } } /// Deploys and provides functions for the eth2 deposit contract, deployed on the eth1 chain. #[derive(Clone, Debug)] pub struct DepositContract { - web3: Web3, - contract: Contract, + client: Provider, + contract: Contract>, } impl DepositContract { pub async fn deploy( - web3: Web3, + client: Provider, confirmations: usize, password: Option, ) -> Result { - Self::deploy_bytecode(web3, confirmations, BYTECODE, ABI, password).await + Self::deploy_bytecode(client, confirmations, BYTECODE, ABI, password).await } pub async fn deploy_testnet( - web3: Web3, + client: Provider, confirmations: usize, password: Option, ) -> Result { Self::deploy_bytecode( - web3, + client, confirmations, testnet::BYTECODE, testnet::ABI, @@ -81,29 +83,25 @@ impl DepositContract { } async fn deploy_bytecode( - web3: Web3, + client: Provider, confirmations: usize, bytecode: &[u8], abi: &[u8], password: Option, ) -> Result { - let address = deploy_deposit_contract( - web3.clone(), - confirmations, - bytecode.to_vec(), - abi.to_vec(), - password, - ) - .await - .map_err(|e| { - format!( - "Failed to deploy contract: {}. Is scripts/ganache_tests_node.sh running?.", - e - ) - })?; - Contract::from_json(web3.clone().eth(), address, ABI) - .map_err(|e| format!("Failed to init contract: {:?}", e)) - .map(move |contract| Self { web3, contract }) + let abi = Abi::load(abi).map_err(|e| format!("Invalid deposit contract abi: {:?}", e))?; + let address = + deploy_deposit_contract(client.clone(), confirmations, bytecode.to_vec(), password) + .await + .map_err(|e| { + format!( + "Failed to deploy contract: {}. Is scripts/anvil_tests_node.sh running?.", + e + ) + })?; + + let contract = Contract::new(address, abi, client.clone()); + Ok(Self { client, contract }) } /// The deposit contract's address in `0x00ab...` format. @@ -178,9 +176,8 @@ impl DepositContract { /// Performs a non-blocking deposit. pub async fn deposit_async(&self, deposit_data: DepositData) -> Result<(), String> { let from = self - .web3 - .eth() - .accounts() + .client + .get_accounts() .await .map_err(|e| format!("Failed to get accounts: {:?}", e)) .and_then(|accounts| { @@ -189,32 +186,33 @@ impl DepositContract { .cloned() .ok_or_else(|| "Insufficient accounts for deposit".to_string()) })?; - let tx_request = TransactionRequest { - from, - to: Some(self.contract.address()), - gas: Some(U256::from(DEPOSIT_GAS)), - gas_price: None, - max_fee_per_gas: None, - max_priority_fee_per_gas: None, - value: Some(from_gwei(deposit_data.amount)), - // Note: the reason we use this `TransactionRequest` instead of just using the - // function in `self.contract` is so that the `eth1_tx_data` function gets used - // during testing. - // - // It's important that `eth1_tx_data` stays correct and does not suffer from - // code-rot. - data: encode_eth1_tx_data(&deposit_data).map(Into::into).ok(), - nonce: None, - condition: None, - transaction_type: None, - access_list: None, - }; + // Note: the reason we use this `TransactionRequest` instead of just using the + // function in `self.contract` is so that the `eth1_tx_data` function gets used + // during testing. + // + // It's important that `eth1_tx_data` stays correct and does not suffer from + // code-rot. + let tx_request = TransactionRequest::new() + .from(from) + .to(self.contract.address()) + .gas(DEPOSIT_GAS) + .value(from_gwei(deposit_data.amount)) + .data(Bytes::from(encode_eth1_tx_data(&deposit_data).map_err( + |e| format!("Failed to encode deposit data: {:?}", e), + )?)); - self.web3 - .eth() - .send_transaction(tx_request) + let pending_tx = self + .client + .send_transaction(tx_request, None) .await .map_err(|e| format!("Failed to call deposit fn: {:?}", e))?; + + pending_tx + .interval(Duration::from_millis(10)) + .confirmations(0) + .await + .map_err(|e| format!("Transaction failed to resolve: {:?}", e))? + .ok_or_else(|| "Transaction dropped from mempool".to_string())?; Ok(()) } @@ -245,17 +243,13 @@ fn from_gwei(gwei: u64) -> U256 { /// Deploys the deposit contract to the given web3 instance using the account with index /// `DEPLOYER_ACCOUNTS_INDEX`. async fn deploy_deposit_contract( - web3: Web3, + client: Provider, confirmations: usize, bytecode: Vec, - abi: Vec, password_opt: Option, ) -> Result { - let bytecode = String::from_utf8(bytecode).expect("bytecode must be valid utf8"); - - let from_address = web3 - .eth() - .accounts() + let from_address = client + .get_accounts() .await .map_err(|e| format!("Failed to get accounts: {:?}", e)) .and_then(|accounts| { @@ -266,30 +260,42 @@ async fn deploy_deposit_contract( })?; let deploy_address = if let Some(password) = password_opt { - let result = web3 - .personal() - .unlock_account(from_address, &password, None) + let result = client + .request( + "personal_unlockAccount", + vec![from_address.to_string(), password], + ) .await; + match result { - Ok(true) => return Ok(from_address), + Ok(true) => from_address, Ok(false) => return Err("Eth1 node refused to unlock account".to_string()), Err(e) => return Err(format!("Eth1 unlock request failed: {:?}", e)), - }; + } } else { from_address }; - let pending_contract = Contract::deploy(web3.eth(), &abi) - .map_err(|e| format!("Unable to build contract deployer: {:?}", e))? - .confirmations(confirmations) - .options(Options { - gas: Some(U256::from(CONTRACT_DEPLOY_GAS)), - ..Options::default() - }) - .execute(bytecode, (), deploy_address); + let mut bytecode = String::from_utf8(bytecode).unwrap(); + bytecode.retain(|c| c.is_ascii_hexdigit()); + let bytecode = hex::decode(&bytecode[1..]).unwrap(); - pending_contract + let deploy_tx: TypedTransaction = TransactionRequest::new() + .from(deploy_address) + .data(Bytes::from(bytecode)) + .gas(CONTRACT_DEPLOY_GAS) + .into(); + + let pending_tx = client + .send_transaction(deploy_tx, None) .await - .map(|contract| contract.address()) - .map_err(|e| format!("Unable to resolve pending contract: {:?}", e)) + .map_err(|e| format!("Failed to send tx: {:?}", e))?; + + let tx = pending_tx + .interval(Duration::from_millis(500)) + .confirmations(confirmations) + .await + .map_err(|e| format!("Failed to fetch tx receipt: {:?}", e))?; + tx.and_then(|tx| tx.contract_address) + .ok_or_else(|| "Deposit contract not deployed successfully".to_string()) } diff --git a/testing/simulator/src/cli.rs b/testing/simulator/src/cli.rs index 9668ee8cb4..5dc2d5ec84 100644 --- a/testing/simulator/src/cli.rs +++ b/testing/simulator/src/cli.rs @@ -10,7 +10,7 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { .about( "Lighthouse Beacon Chain Simulator creates `n` beacon node and validator clients, \ each with `v` validators. A deposit contract is deployed at the start of the \ - simulation using a local `ganache` instance (you must have `ganache` \ + simulation using a local `anvil` instance (you must have `anvil` \ installed and avaliable on your path). All beacon nodes independently listen \ for genesis from the deposit contract, then start operating. \ \ diff --git a/testing/simulator/src/eth1_sim.rs b/testing/simulator/src/eth1_sim.rs index 1699c0e9ee..a5462da396 100644 --- a/testing/simulator/src/eth1_sim.rs +++ b/testing/simulator/src/eth1_sim.rs @@ -2,7 +2,7 @@ use crate::local_network::{EXECUTION_PORT, TERMINAL_BLOCK, TERMINAL_DIFFICULTY}; use crate::{checks, LocalNetwork, E}; use clap::ArgMatches; use eth1::{Eth1Endpoint, DEFAULT_CHAIN_ID}; -use eth1_test_rig::GanacheEth1Instance; +use eth1_test_rig::AnvilEth1Instance; use execution_layer::http::deposit_methods::Eth1Id; use futures::prelude::*; @@ -110,12 +110,12 @@ pub fn run_eth1_sim(matches: &ArgMatches) -> Result<(), String> { * Deploy the deposit contract, spawn tasks to keep creating new blocks and deposit * validators. */ - let ganache_eth1_instance = GanacheEth1Instance::new(DEFAULT_CHAIN_ID.into()).await?; - let deposit_contract = ganache_eth1_instance.deposit_contract; - let chain_id = ganache_eth1_instance.ganache.chain_id(); - let ganache = ganache_eth1_instance.ganache; - let eth1_endpoint = SensitiveUrl::parse(ganache.endpoint().as_str()) - .expect("Unable to parse ganache endpoint."); + let anvil_eth1_instance = AnvilEth1Instance::new(DEFAULT_CHAIN_ID.into()).await?; + let deposit_contract = anvil_eth1_instance.deposit_contract; + let chain_id = anvil_eth1_instance.anvil.chain_id(); + let anvil = anvil_eth1_instance.anvil; + let eth1_endpoint = SensitiveUrl::parse(anvil.endpoint().as_str()) + .expect("Unable to parse anvil endpoint."); let deposit_contract_address = deposit_contract.address(); // Start a timer that produces eth1 blocks on an interval. @@ -123,7 +123,7 @@ pub fn run_eth1_sim(matches: &ArgMatches) -> Result<(), String> { let mut interval = tokio::time::interval(eth1_block_time); loop { interval.tick().await; - let _ = ganache.evm_mine().await; + let _ = anvil.evm_mine().await; } }); diff --git a/testing/simulator/src/main.rs b/testing/simulator/src/main.rs index 922149537c..a19777c5ab 100644 --- a/testing/simulator/src/main.rs +++ b/testing/simulator/src/main.rs @@ -1,6 +1,6 @@ //! This crate provides a simluation that creates `n` beacon node and validator clients, each with //! `v` validators. A deposit contract is deployed at the start of the simulation using a local -//! `ganache` instance (you must have `ganache` installed and avaliable on your path). All +//! `anvil` instance (you must have `anvil` installed and avaliable on your path). All //! beacon nodes independently listen for genesis from the deposit contract, then start operating. //! //! As the simulation runs, there are checks made to ensure that all components are running