diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a725ce46..64c1af9c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,6 +14,7 @@ concurrency: env: CARGO_TERM_COLOR: always + __GEAR_WASM_BUILDER_NO_FEATURES_TRACKING: 1 jobs: build: @@ -35,7 +36,7 @@ jobs: run: forge build working-directory: ./ethereum - name: Build - run: cargo build --release --all-targets + run: cargo build --package vft-manager --release --all-targets && cargo build --release --all-targets lints: runs-on: kuberunner steps: @@ -55,11 +56,13 @@ jobs: run: forge build working-directory: ./ethereum - name: Run clippy - run: cargo clippy --release --all-targets -- -D warnings $(cat .lints | cut -f1 -d"#" | tr '\n' ' ') + run: cargo clippy --release --all-targets --package vft-manager -- -D warnings && cargo clippy --release --all-targets -- -D warnings $(cat .lints | cut -f1 -d"#" | tr '\n' ' ') - name: Run rustfmt run: cargo fmt -- --check tests: runs-on: kuberunner + env: + NODE_CONTAINER_NAME: gear_node${{ github.run_id }}_${{ github.run_number }} steps: - name: Checkout uses: actions/checkout@v4 @@ -76,18 +79,25 @@ jobs: - name: Build ethereum smart-contracts run: forge build working-directory: ./ethereum + - name: Pull & run Gear node container + run: | + docker pull ghcr.io/gear-tech/node:v1.6.2 + docker run --name $NODE_CONTAINER_NAME --detach --rm --publish 127.0.0.1:9944:9944 ghcr.io/gear-tech/node:v1.6.2 gear --dev --tmp --rpc-external - name: Run tests - run: cargo test --release --workspace + run: cargo test --release --no-run --package vft-manager && cargo test --release --workspace --exclude prover --exclude plonky2_blake2b256 --exclude plonky2_ecdsa --exclude plonky2_ed25519 --exclude plonky2_sha512 - --exclude plonky2_u32 + --exclude plonky2_u32 || { exit_code=$?; if [ x$exit_code != x0 ]; then docker stop $NODE_CONTAINER_NAME; fi; exit $exit_code; } - name: Run solidity tests run: | cd ethereum forge test + - name: Stop Gear node container (if any) + continue-on-error: true + run: docker stop $NODE_CONTAINER_NAME check-zk-circuits-changed: runs-on: kuberunner outputs: @@ -141,7 +151,7 @@ jobs: run: forge build --force --no-cache working-directory: ./ethereum - name: Build workspace - run: cargo build --release --all-targets + run: cargo build --package vft-manager --release --all-targets && cargo build --release --all-targets - name: Check that files match run: | stored=( diff --git a/Cargo.lock b/Cargo.lock index 0bc31c15..e27d3c07 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3040,6 +3040,23 @@ dependencies = [ "parity-scale-codec", ] +[[package]] +name = "deploy-checkpoints" +version = "0.1.0" +dependencies = [ + "anyhow", + "ark-serialize 0.4.2", + "checkpoint_light_client", + "checkpoint_light_client-io", + "clap", + "dotenv", + "ethereum_beacon_client", + "gclient", + "hex", + "parity-scale-codec", + "tokio", +] + [[package]] name = "deploy-to-gear" version = "0.1.0" @@ -3641,6 +3658,7 @@ dependencies = [ "lazy_static", "sails-client-gen", "sails-rs", + "sp-core 21.0.0", "tokio", ] @@ -3750,6 +3768,21 @@ dependencies = [ "uint 0.9.5", ] +[[package]] +name = "ethereum_beacon_client" +version = "0.1.0" +dependencies = [ + "anyhow", + "ark-serialize 0.4.2", + "checkpoint_light_client-io", + "ethereum-common", + "futures", + "hex", + "reqwest 0.11.27", + "serde", + "serde_json", +] + [[package]] name = "ethereum_hashing" version = "0.6.0" @@ -9972,6 +10005,7 @@ dependencies = [ "erc20-relay-client", "ethereum-client", "ethereum-common", + "ethereum_beacon_client", "futures", "gclient", "gear-core 1.6.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -9990,9 +10024,11 @@ dependencies = [ "prover", "rand 0.8.5", "reqwest 0.11.27", + "ruzstd", "sails-rs", "serde", "serde_json", + "sp-core 21.0.0", "thiserror", "tokio", "utils-prometheus", diff --git a/Cargo.toml b/Cargo.toml index 74b17a5c..15c026de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ members = [ "prover", "relayer", "circuits/*", + "ethereum_beacon_client", "ethereum-common", "gear-programs/bridging-payment", "gear-programs/bridging-payment/app", @@ -22,6 +23,7 @@ members = [ "gear-programs/wrapped-vara/app", "gear-programs/wrapped-vara/client", "utils-prometheus", + "tools/deploy-checkpoints", "tools/deploy-to-gear", "tools/genesis-config", ] @@ -41,6 +43,7 @@ plonky2_ed25519 = { path = "./circuits/plonky2_ed25519" } plonky2_ecdsa = { path = "./circuits/plonky2_ecdsa" } plonky2_u32 = { path = "./circuits/plonky2_u32" } ethereum-client = { path = "./ethereum/client" } +ethereum_beacon_client = { path = "ethereum_beacon_client" } ethereum-common = { path = "ethereum-common", default-features = false } bridging-payment = { path = "gear-programs/bridging-payment" } @@ -129,6 +132,7 @@ ring = { git = "https://github.com/gear-tech/ring.git", branch = "gear-v0.17.8", "alloc", ] } rlp = { version = "0.5.2", default-features = false } +ruzstd = "0.5.0" scale-info = { version = "2.10", default-features = false, features = [ "derive", ] } diff --git a/ethereum_beacon_client/Cargo.toml b/ethereum_beacon_client/Cargo.toml new file mode 100644 index 00000000..2498ebee --- /dev/null +++ b/ethereum_beacon_client/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "ethereum_beacon_client" +version.workspace = true +edition.workspace = true + +[dependencies] +anyhow.workspace = true +ark-serialize = { workspace = true, features = ["std"] } +checkpoint_light_client-io = { workspace = true, features = ["std"] } +ethereum-common = { workspace = true, features = ["std"] } +futures.workspace = true +hex = { workspace = true, features = ["std"] } +reqwest.workspace = true +serde = { workspace = true, features = ["std"] } +serde_json.workspace = true diff --git a/relayer/src/ethereum_beacon_client/mod.rs b/ethereum_beacon_client/src/lib.rs similarity index 99% rename from relayer/src/ethereum_beacon_client/mod.rs rename to ethereum_beacon_client/src/lib.rs index 5f3346af..f47a9910 100644 --- a/relayer/src/ethereum_beacon_client/mod.rs +++ b/ethereum_beacon_client/src/lib.rs @@ -175,7 +175,6 @@ impl BeaconClient { Err(anyhow!("Block was not found")) } - #[cfg(test)] pub async fn get_bootstrap( &self, checkpoint: &str, diff --git a/relayer/src/ethereum_beacon_client/slots_batch.rs b/ethereum_beacon_client/src/slots_batch.rs similarity index 100% rename from relayer/src/ethereum_beacon_client/slots_batch.rs rename to ethereum_beacon_client/src/slots_batch.rs diff --git a/relayer/src/ethereum_beacon_client/utils.rs b/ethereum_beacon_client/src/utils.rs similarity index 100% rename from relayer/src/ethereum_beacon_client/utils.rs rename to ethereum_beacon_client/src/utils.rs diff --git a/gear-programs/bridging-payment/Cargo.toml b/gear-programs/bridging-payment/Cargo.toml index 87389873..231b5601 100644 --- a/gear-programs/bridging-payment/Cargo.toml +++ b/gear-programs/bridging-payment/Cargo.toml @@ -16,7 +16,7 @@ bridging-payment = { path = ".", features = ["wasm-binary"] } bridging-payment-client = { path = "client" } vft-manager = { workspace = true, features = ["wasm-binary"] } vft-manager-client.workspace = true -extended-vft.workspace = true +extended-vft = { workspace = true, features = ["wasm-binary"] } extended-vft-client.workspace = true sails-rs = { workspace = true, features = ["gtest"] } diff --git a/gear-programs/erc20-relay/app/Cargo.toml b/gear-programs/erc20-relay/app/Cargo.toml index f0fd8f25..b70e0341 100644 --- a/gear-programs/erc20-relay/app/Cargo.toml +++ b/gear-programs/erc20-relay/app/Cargo.toml @@ -20,6 +20,7 @@ futures.workspace = true gclient.workspace = true gstd.workspace = true sails-rs = { workspace = true, features = ["gclient"] } +sp-core = { workspace = true, features = ["std"] } tokio = { workspace = true, features = ["rt", "macros"] } hex-literal.workspace = true hex.workspace = true diff --git a/gear-programs/erc20-relay/app/tests/gclient.rs b/gear-programs/erc20-relay/app/tests/gclient.rs index 7da85390..84a6b229 100644 --- a/gear-programs/erc20-relay/app/tests/gclient.rs +++ b/gear-programs/erc20-relay/app/tests/gclient.rs @@ -5,27 +5,60 @@ mod vft { } use erc20_relay_client::traits::{Erc20Relay, Erc20RelayFactory}; -use gclient::{Event, EventProcessor, GearApi, GearEvent}; +use gclient::{Event, EventProcessor, GearApi, GearEvent, WSAddress}; use sails_rs::{calls::*, gclient::calls::*, prelude::*}; +use sp_core::crypto::DEV_PHRASE; +use tokio::sync::Mutex; use vft::vft_manager; -async fn spin_up_node() -> (GClientRemoting, GearApi, CodeId, GasUnit) { +static LOCK: Mutex<(u32, Option)> = Mutex::const_new((0, None)); + +async fn connect_to_node() -> (impl Remoting + Clone, GearApi, CodeId, GasUnit, [u8; 4]) { let api = GearApi::dev().await.unwrap(); let gas_limit = api.block_gas_limit().unwrap(); - let remoting = GClientRemoting::new(api.clone()); - let (code_id, _) = api.upload_code(erc20_relay::WASM_BINARY).await.unwrap(); + let (api, code_id, salt) = { + let mut lock = LOCK.lock().await; + let code_id = match lock.1 { + Some(code_id) => code_id, + None => { + let (code_id, _) = api.upload_code(erc20_relay::WASM_BINARY).await.unwrap(); + lock.1 = Some(code_id); + + code_id + } + }; + + let salt = lock.0; + lock.0 += 1; + + let suri = format!("{DEV_PHRASE}//erc20-relay-{salt}:"); + let api2 = GearApi::init_with(WSAddress::dev(), suri).await.unwrap(); + + let account_id: &[u8; 32] = api2.account_id().as_ref(); + api.transfer_keep_alive((*account_id).into(), 100_000_000_000_000) + .await + .unwrap(); + + (api2, code_id, salt) + }; - (remoting, api, code_id, gas_limit) + ( + GClientRemoting::new(api.clone()), + api, + code_id, + gas_limit, + salt.to_le_bytes(), + ) } +#[ignore] #[tokio::test] -#[ignore = "Requires running node"] async fn gas_for_reply() { use erc20_relay_client::{traits::Erc20Relay as _, Erc20Relay, Erc20RelayFactory}; let route = ::ROUTE; - let (remoting, api, code_id, gas_limit) = spin_up_node().await; + let (remoting, api, code_id, gas_limit, salt) = connect_to_node().await; let account_id: ActorId = <[u8; 32]>::from(api.account_id().clone()).into(); let factory = Erc20RelayFactory::new(remoting.clone()); @@ -33,7 +66,7 @@ async fn gas_for_reply() { let program_id = factory .gas_calculation(1_000, 5_500_000_000) .with_gas_limit(gas_limit) - .send_recv(code_id, []) + .send_recv(code_id, salt) .await .unwrap(); @@ -89,11 +122,10 @@ async fn gas_for_reply() { } #[tokio::test] -#[ignore = "Requires running node"] async fn set_vft_manager() { use erc20_relay_client::Config; - let (remoting, _api, code_id, gas_limit) = spin_up_node().await; + let (remoting, _api, code_id, gas_limit, salt) = connect_to_node().await; let factory = erc20_relay_client::Erc20RelayFactory::new(remoting.clone()); @@ -106,7 +138,7 @@ async fn set_vft_manager() { }, ) .with_gas_limit(gas_limit) - .send_recv(code_id, []) + .send_recv(code_id, salt) .await .unwrap(); @@ -161,11 +193,10 @@ async fn set_vft_manager() { } #[tokio::test] -#[ignore = "Requires running node"] async fn update_config() { use erc20_relay_client::Config; - let (remoting, _api, code_id, gas_limit) = spin_up_node().await; + let (remoting, _api, code_id, gas_limit, salt) = connect_to_node().await; let factory = erc20_relay_client::Erc20RelayFactory::new(remoting.clone()); @@ -181,7 +212,7 @@ async fn update_config() { }, ) .with_gas_limit(gas_limit) - .send_recv(code_id, []) + .send_recv(code_id, salt) .await .unwrap(); diff --git a/gear-programs/vft-manager/Cargo.toml b/gear-programs/vft-manager/Cargo.toml index b4be2d12..be3ecd39 100644 --- a/gear-programs/vft-manager/Cargo.toml +++ b/gear-programs/vft-manager/Cargo.toml @@ -14,6 +14,7 @@ sails-idl-gen.workspace = true [dev-dependencies] vft-manager = { path = ".", features = ["wasm-binary"] } vft-manager-client = { path = "client" } +extended-vft = { workspace = true, features = ["wasm-binary"] } extended-vft-client.workspace = true alloy-consensus.workspace = true @@ -26,7 +27,6 @@ gear-core.workspace = true gtest.workspace = true parity-scale-codec.workspace = true scale-info.workspace = true -extended-vft.workspace = true gclient.workspace = true [features] diff --git a/relayer/Cargo.toml b/relayer/Cargo.toml index 7bb4c112..d0cb1158 100644 --- a/relayer/Cargo.toml +++ b/relayer/Cargo.toml @@ -24,6 +24,7 @@ clap.workspace = true derive_more.workspace = true dotenv.workspace = true erc20-relay-client.workspace = true +ethereum_beacon_client.workspace = true ethereum-common.workspace = true futures.workspace = true gear-core.workspace = true @@ -49,3 +50,7 @@ utils-prometheus.workspace = true [build-dependencies] cgo_oligami.workspace = true + +[dev-dependencies] +ruzstd.workspace = true +sp-core = { workspace = true, features = ["std"] } diff --git a/relayer/src/ethereum_checkpoints/replay_back.rs b/relayer/src/ethereum_checkpoints/replay_back.rs index 4f75d7e3..4c9c9f5b 100644 --- a/relayer/src/ethereum_checkpoints/replay_back.rs +++ b/relayer/src/ethereum_checkpoints/replay_back.rs @@ -1,5 +1,5 @@ use super::*; -use crate::ethereum_beacon_client::{self, BeaconClient}; +use ethereum_beacon_client::{self, BeaconClient}; #[allow(clippy::too_many_arguments)] pub async fn execute( diff --git a/relayer/src/ethereum_checkpoints/sync_update.rs b/relayer/src/ethereum_checkpoints/sync_update.rs index c5ed2a8b..c28d9c91 100644 --- a/relayer/src/ethereum_checkpoints/sync_update.rs +++ b/relayer/src/ethereum_checkpoints/sync_update.rs @@ -1,6 +1,6 @@ use super::*; -use crate::ethereum_beacon_client::{self, BeaconClient}; pub use checkpoint_light_client_io::sync_update::Error; +use ethereum_beacon_client::{self, BeaconClient}; use std::ops::ControlFlow::{self, *}; pub fn spawn_receiver(beacon_client: BeaconClient, sender: Sender) { diff --git a/relayer/src/ethereum_checkpoints/tests/chain-data/headers.json.zst b/relayer/src/ethereum_checkpoints/tests/chain-data/headers.json.zst new file mode 100644 index 00000000..bc2aa627 Binary files /dev/null and b/relayer/src/ethereum_checkpoints/tests/chain-data/headers.json.zst differ diff --git a/relayer/src/ethereum_checkpoints/tests/chain-data/holesky-bootstrap-368.json.zst b/relayer/src/ethereum_checkpoints/tests/chain-data/holesky-bootstrap-368.json.zst new file mode 100644 index 00000000..18750393 Binary files /dev/null and b/relayer/src/ethereum_checkpoints/tests/chain-data/holesky-bootstrap-368.json.zst differ diff --git a/relayer/src/ethereum_checkpoints/tests/chain-data/holesky-finality-update-3_016_736.json b/relayer/src/ethereum_checkpoints/tests/chain-data/holesky-finality-update-3_016_736.json new file mode 100644 index 00000000..00405cb1 --- /dev/null +++ b/relayer/src/ethereum_checkpoints/tests/chain-data/holesky-finality-update-3_016_736.json @@ -0,0 +1 @@ +{"version":"deneb","data":{"attested_header":{"beacon":{"slot":"3016814","proposer_index":"1380911","parent_root":"0xf3d5096150566bb2687601fe109ebb1b61e3f30123722763ea93e346a38e1c7b","state_root":"0x98c064397ad127b46cc49178a812c351226531e6ca5ad8fe38c3c6a26f2d3aea","body_root":"0x5cf4167844e5797ec90b4bc5e50575b3252435372587b2717fd5d69a26d593a9"},"execution":{"parent_hash":"0xc7862a6ce91525c77a7970ea1fd76cbf7d22ddb01e403a0898894692bd06885b","fee_recipient":"0x80058a30b84f339befb7abcb25b1412ff0f88ec3","state_root":"0xead225061308ae4e238a3fe5765a2162cf50cd289672666126171db6cad76427","receipts_root":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logs_bloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prev_randao":"0x300670dd2990f2531c903f787d346ef2d384dddec9b409cd508857b9887b0bcb","block_number":"2778156","gas_limit":"30000000","gas_used":"0","timestamp":"1732104168","extra_data":"0x","base_fee_per_gas":"19","block_hash":"0x47a282309fafbf1d768b367a4c382a06d6cef325249a65732b33f5f90b092bfc","transactions_root":"0x7ffe241ea60187fdb0187bfa22de35d1f9bed7ab061d9401fd47e34a54fbede1","withdrawals_root":"0xf489101462d71d96c9a297adc78ee558f107ce072cf4a3c044402d7487d2a692","blob_gas_used":"0","excess_blob_gas":"0"},"execution_branch":["0x7f62fda28c83c818acccaea3982c597c69283878a61277f278255f3e92ea3c48","0xb46f0c01805fe212e15907981b757e6c496b0cb06664224655613dcec82505bb","0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71","0x03d29a307417d82b2beb9649f6e927a7dadc3e38e3afee73a0cc56dcf2ba822a"]},"finalized_header":{"beacon":{"slot":"3016736","proposer_index":"306051","parent_root":"0x0eb0e34db85cafad1969b15b7a6c80d6cc1aac81e052d2bed704cd9d06478c3c","state_root":"0xe5080f936036fd96ff9e304df7df22c39686f62ef0acefb45134496f58aa318d","body_root":"0xf382b98973092f0c3b639badc5f7479522c79ac1df4c52058e561dd8ce5e7e0f"},"execution":{"parent_hash":"0x508949056ec4fa4a413e62b0d584e785455fd7017b0ecfc9be4594d69f2ec718","fee_recipient":"0x4d496ccc28058b1d74b7a19541663e21154f9c84","state_root":"0xa736039d44316bd0d81576db08d13561ff1b280d159649d69faafc3aa8fb8c1d","receipts_root":"0xe25b0bf719439ad2d1d92732b3d0459c41612c4f12148adac0244932030338f1","logs_bloom":"0x400108012040004001004000a09821000009408030001110461040418148300388000020000100002040139040000010002004401018004000504020102c15000680200500804440500c48182001404100851304203420506403212006002b0880070100920042a0820200828008298000810048000800400014a0100002042c400000130107482430080029040240288001309208814000000241010580010002080001902010008c100000000001000002109080cb0002182440004e800802007000022010012000100cd0000120c1081010024085810000002240c10060084154040008500073009020484002002408e0414440800c00009022044500040a","prev_randao":"0x8e593f7dedabf10e73e3da0af0e8ee3df586bded82a0f4f4443b017ba5d7af11","block_number":"2778085","gas_limit":"30000000","gas_used":"14104325","timestamp":"1732103232","extra_data":"0x4e65746865726d696e64","base_fee_per_gas":"10","block_hash":"0x6f9b19ee70c3d2055b81b81bd049dec4e18593cf8f4c5029b2d8a4d709476580","transactions_root":"0xc1461e7c3a33e47628224ac61e89105fa1b5181ee2ed1e5601e90bab86c25b36","withdrawals_root":"0x898e77a54125d09cec9c51e4569c257cfe3744c6f50dcf8ea578cb7decfd680f","blob_gas_used":"262144","excess_blob_gas":"0"},"execution_branch":["0x14356e610075ceb2338fec02053239d81d33338d7573a2f672b26759c9efec1f","0x5de2676a591e5be9def585ced61015cdb4791eef9a11f716f18b82d8c15c9fec","0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71","0x2d7a0064938718309497dd91fbee4e398302d3e54efbe1539c99d802a03d2df2"]},"finality_branch":["0x4170010000000000000000000000000000000000000000000000000000000000","0x96c9bdd4ae2f71dd6f8b9cb87bf98fdc3945cd45c759ad9a17ff6daff478c393","0x1c6652e50d4aee265dc3e358901d9321c6513f54bf3f03e28445744691ba987e","0xd2df90aad668583112318ff455796dc221401bf48716d06e408e6eb1047cf00d","0x45c215c6dc16b001c44bc3f44ff94dbabd400d9acac865be0b0a6866c4fd8b20","0x09babb27f8bd76f101bab630e841a732b7417ee374de6f26be19f7e48f1e34dd"],"sync_aggregate":{"sync_committee_bits":"0xf7ff79ffffdfefffffdffffffff3db7fff5fdf7ffff7ffffffffefbffdffdfffe7fffeffdfffddfffffffffffffffcffffbffffbfefbfdfff7fff5fffffff9f5","sync_committee_signature":"0xb86b74157180feaba0c89863e0d55501aee7011cf8aa7baed99aa784e8edb494e5906e6b945d2f054652a157f8d19bb70da359cbfa58480e0aa30d3658fecd1811d786ddf292d6d54119f079861341e1fda1190dd137146675c18eae9e4599d8"},"signature_slot":"3016815"}} \ No newline at end of file diff --git a/relayer/src/ethereum_checkpoints/tests/chain-data/holesky-finality-update-3_016_768.json b/relayer/src/ethereum_checkpoints/tests/chain-data/holesky-finality-update-3_016_768.json new file mode 100644 index 00000000..1018bbbc --- /dev/null +++ b/relayer/src/ethereum_checkpoints/tests/chain-data/holesky-finality-update-3_016_768.json @@ -0,0 +1 @@ +{"version":"deneb","data":{"attested_header":{"beacon":{"slot":"3016836","proposer_index":"406009","parent_root":"0x61dea92b520f340b12ff6910db21b36762d2ab848f43cd7e0edc2aaf9ca4d07f","state_root":"0x07e40f7e449385635bd0717402e638e2b08fe3a3a425b9f8823ab25c6b241435","body_root":"0xc6bf679b4949307054f774c06c631aacabc44e92454023433313b5bd0281488e"},"execution":{"parent_hash":"0xfe3855d68a64aceaeefba312d7f1a00c54aada91f05682486eff0404e4401265","fee_recipient":"0x3826539cbd8d68dcf119e80b994557b4278cec9f","state_root":"0x67f8767a756ea7a8efd979dbc7d26cbaddec3437a50a101bc19127f9ac1df149","receipts_root":"0x1204b5a7b56227778e70c09c4295eb8041a34e82a170c269d0e98488c33a1a16","logs_bloom":"0x04000c012000004000804000008801000008008020000110428808400000000201000040000000000000100060000004000000000008000000400020002400000000000100080040100c400808000001000101444004201044180120000400008005010012004080000000020008280000000040000010000014801000000448000014000004000010000001000248000000241000400000000001010100011002000004000030010800010000000080000600000007000a192000025000084044000002000001000001000000010000001000000009a0040000020001006200411400000000405020900040000000000810418040800c100000200000000000","prev_randao":"0x69c5cd3337aebedb7e622571005589ee6a2eba79ced812d95f00b43d90b7c917","block_number":"2778176","gas_limit":"30000000","gas_used":"8827286","timestamp":"1732104432","extra_data":"0x","base_fee_per_gas":"14","block_hash":"0x1665b78ca90106d34585749aa8f93fc8f8b4846a92b4401376d0e8fada985d30","transactions_root":"0x31b2bbac3f958cc21c83dc5e7e463ee604160e3932f92e60240036003e4c9bd4","withdrawals_root":"0x1e4247550e2dfb0a0b4574ae24bf3f056275cdd389810510b879738e34685f11","blob_gas_used":"131072","excess_blob_gas":"0"},"execution_branch":["0x0a961438e851ce5df2b1f20076eb2206b80761a82f2a504f94e7e3e979c26682","0x263a2f4199114d22b9f349fc810e9ff9988b079ad9e608bab5af8a2a7b84f069","0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71","0x002adfff27c9d39e280a1d0cac6e2f94b8bd4750db0f3c44f2e8f88f9ed2736d"]},"finalized_header":{"beacon":{"slot":"3016768","proposer_index":"1107558","parent_root":"0x0d704138f785c4d131487fbcd3147566404780d64eb7cd406b18dd616b3ea90a","state_root":"0x6f3e2d71e838bf76c80aab117cea41e44323698c60f889aa145eae5a73d45664","body_root":"0x5fc519f7e87400414baca6b01f239e8ae28b856b39a5455a7d6ad784c7aee979"},"execution":{"parent_hash":"0xad58e879af4ae2d3108d3a44fad1f80d355b08eb7f1bdb2f8d4d76027cea4541","fee_recipient":"0x0c10000000756bd1d14f9c837f022929a97bda45","state_root":"0xc91cedb0d240bd4a2a6d61e5aea251905dbdd792e3622937ef53438f64dfe233","receipts_root":"0x6d9a8f219b1a9cfa1a33b8a6c418dbc369fef12e3fbe4feb53012fe0bdb711e6","logs_bloom":"0x0000880020000040000000000008400000000080200001010000004000000000000001000000000000001080400000002002000000000000000008000020000000282001100800400804400800000041008001000001001044080300040040000100000000004000000001220040000000000080008000000000801000000008800000800005200020001000000000000100201001c02000800001800400000002880000000010000002001000000000000000001001000212208000000000000000000201000100000002000000000008100000000140440000000001084004401000000000000000120040000000000040000040000c000010000000000000","prev_randao":"0xadc5837a3b1823bf43b60ff1c4236815554cd15783a42d395fd9cc3c78caf9af","block_number":"2778114","gas_limit":"30000000","gas_used":"5613764","timestamp":"1732103616","extra_data":"0xd883010e07846765746888676f312e32322e35856c696e7578","base_fee_per_gas":"8","block_hash":"0x81f7474469bbb819c028aa1d2c68270eb942cc6205f339dcb8d48f1aa1604942","transactions_root":"0xc98fedf1e55851846d3419aa89ecd72921f4895351a55c48a14aa22f66878378","withdrawals_root":"0xa9d14d666133217d9cd8365a86d5a51d0215a9da5eaabb21c6c322fd5d1a7d01","blob_gas_used":"262144","excess_blob_gas":"0"},"execution_branch":["0xf20c1eb1ccfcabe6f36e62609c9e6af169387956c9d51d351788963b7f47028b","0xa0dcb324e8397cd3116c60f4510d6b9a30245b712a2466c6ff483f81b73e0433","0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71","0x993a3e2bd50922129441a62dff8a5b66870a96403d70b0434d59918b682a83c4"]},"finality_branch":["0x4270010000000000000000000000000000000000000000000000000000000000","0x96c9bdd4ae2f71dd6f8b9cb87bf98fdc3945cd45c759ad9a17ff6daff478c393","0x1c6652e50d4aee265dc3e358901d9321c6513f54bf3f03e28445744691ba987e","0x600f5ec075f4cc3cccfe1ab3a912a78f19719452524d3459345d4f4d963c94d8","0xeb2620c468a0e2822438dfa3b806f5025c817521d65e55c70447ba3bd82ab338","0xdfa72d40981aa5517b1886e19a9ee71803cbbb38b361c3aa023dd1754ad51f58"],"sync_aggregate":{"sync_committee_bits":"0xf7ff79ffffdfeffffedffffffff3df6fff5fdf7ffff7ffffffffefbffdffdfffeffffeff9fffddfffffffefffffffdfffffffffbfcfffffff77ff5effffff9f5","sync_committee_signature":"0xb8ccc0de5c7774354827c45b2617d5fc87aa36b6f69233659ee251fa2d6e84d34bc3ae3983392fa31c1fe206a38811bd132d3504af5b648c845f4b20bbabd9dd264ebc27bd597259c66c2d681815be3d65e36f952abf226c05dede06ff167736"},"signature_slot":"3016837"}} \ No newline at end of file diff --git a/relayer/src/ethereum_checkpoints/tests/chain-data/holesky-finality-update-3_016_799.json b/relayer/src/ethereum_checkpoints/tests/chain-data/holesky-finality-update-3_016_799.json new file mode 100644 index 00000000..42369c76 --- /dev/null +++ b/relayer/src/ethereum_checkpoints/tests/chain-data/holesky-finality-update-3_016_799.json @@ -0,0 +1 @@ +{"version":"deneb","data":{"attested_header":{"beacon":{"slot":"3016892","proposer_index":"1494374","parent_root":"0x478bae341bb2192e117ceca9aeab6b21f5876ea04ff7608bbdb919642469c84b","state_root":"0xa40ef8ca3e57e25a0be658f2e4804ae10422bf581153be3306a18bd576c5c50a","body_root":"0xcb9dbcdcd300dfbf6e76a24a9f08c63462a44ec45a9eb657decc0679a7e5e1a1"},"execution":{"parent_hash":"0x093e46fdedc58db91b1e651aed9a6927f1108239a5dd0036aae0ef54e3857462","fee_recipient":"0xe73a3602b99f1f913e72f8bdcbc235e206794ac8","state_root":"0x06237a4a671764fcff06a018eb5b9732a326c9a95378fdf49264b4d9fa41a43a","receipts_root":"0x897f1894b8e15f67adbc2963d8744df26083e1fbb8fd29ae0a320c5982c7fc1f","logs_bloom":"0x00000201210000000200000020800002000c401000000010400000000001a0000c00098000000040000010100010100040100082000008008040400000250000002022040300004800900808260100000000002020072068204000080010000001050100000000040b0000001000000000828c00000008080014b01001000400d0000002180410002002000100000800000000000000400000841001840004480208004000802000000000800000804000021020000200004000a0040400400000000002004009000030000000022000000008000001040000000002400050000015000004420010000000420000001100c00000000002000000008008000001","prev_randao":"0x045ea7bf08df99872ced08131f401b9e96bc5b28a60ad82d5de78c061d297f30","block_number":"2778229","gas_limit":"30000000","gas_used":"8281154","timestamp":"1732105104","extra_data":"0xd883010e0b846765746888676f312e32332e31856c696e7578","base_fee_per_gas":"16","block_hash":"0x1118d0ccb3b1fccc531437f9b5d9f915db2215d04f22f9422f1f09a0dc8ad5b0","transactions_root":"0x9134709d2591de4e65ca06cc5e20b4bb42ede18577f6dde7189856934069db44","withdrawals_root":"0x9df8a37c2a5bbf98176fcb6e5c5e3028eb3fa0f062bbc1dd0d3e7801f58b447f","blob_gas_used":"131072","excess_blob_gas":"0"},"execution_branch":["0x6fab37540b004fc1effcb0373d52cc7580d6f5b1ef5214ce227ae03fa9c6d899","0x156cc6d7ba1c6d399ab969dc96f18bdabe4cd791cda3e4acea3725d494f39099","0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71","0xcc5a7b694c8e49a0f590f0b1865d58d8daf45829b77168ebca13c45a16297887"]},"finalized_header":{"beacon":{"slot":"3016799","proposer_index":"6681","parent_root":"0x3acdaa789928b1ab1a4bd74e4a527140d480222c2277524f419cc99b9a5756b9","state_root":"0x8436b5333f421401c3f521c6039a5ac21412bdce19cec1699f6875c136626a2f","body_root":"0x36ad037d9fc3226ea964384c0ada2e815cfb045af58e41470a17d00d859878b8"},"execution":{"parent_hash":"0xf1fc89489f9050fb2328dec57cefe205effc82a985db4ed653a1605a0e176ce7","fee_recipient":"0x0e5dda855eb1de2a212cd1f62b2a3ee49d20c444","state_root":"0x0641a9fc9e28195927eaa334917b7067efa2e0482a91697b564f9ba96743f5b1","receipts_root":"0x51ba413f249c4f3ed472c9ed42f8e0c8018be66fb6ec1bd64ed812cff3b06aef","logs_bloom":"0x00000000000000401000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000040000000000400000000000000000400000000400000000000000000000000000000000000000000000000000000004000000000000000000000000010000200000000000000000002000008000000000010000000000000000000000000000000000000000000000000000000002100000000000000000000000400001000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000","prev_randao":"0x16f2fd4d721bb8c505f20a914f08492af30605445bdfb21aae46f19a760e328d","block_number":"2778143","gas_limit":"30000000","gas_used":"212619","timestamp":"1732103988","extra_data":"0xd883010e00846765746888676f312e32322e32856c696e7578","base_fee_per_gas":"15","block_hash":"0x38811c4c43e2004326df3ca36f907cd1766cf4bd09276cc9324197c610b85f6b","transactions_root":"0xa71f549e67eda6b383da780c4aab290f1bfc97b8e53ae10815b527e4680c5417","withdrawals_root":"0x683eb5773f96412c699686e8406df7fbb80ef10ceef2220c582cf363d1a0ddb9","blob_gas_used":"131072","excess_blob_gas":"0"},"execution_branch":["0x7a88161bc246224ababf3afde52aeff5982d3cdcb700a3c9d19aa4499fb7ee27","0xa8d9229ecb9d87003d2a3445bc7f7f012f0e436ec6e1a77ccf94fef32e51ffec","0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71","0x6713d409ef6733d93a4bd33332d530561005373f808cea3e35d9939b681b02da"]},"finality_branch":["0x4370010000000000000000000000000000000000000000000000000000000000","0x96c9bdd4ae2f71dd6f8b9cb87bf98fdc3945cd45c759ad9a17ff6daff478c393","0x1c6652e50d4aee265dc3e358901d9321c6513f54bf3f03e28445744691ba987e","0x421447c70fece272dd5f2ab5ce6b748014d89c93712a378956ce394b0c523ebd","0xe47eae4db36219ef32b0d4d79f4927423708b15bb3785e2389a7afeba38b58e7","0xc539d2c9f71642520257aac5133470a13ecee307c99541d860d8500cacc465fb"],"sync_aggregate":{"sync_committee_bits":"0xf7ff79ffffdfefffffdffffffff3df7fff5fdf7ffff7ffffffffefbffdffdfffeffffeffdfffddffeffffffffffdfdfffffffffbfefffffff7fff5ffeffff9f5","sync_committee_signature":"0x982107d44f290773a15004204e2cc71c311268da9185027b2a93053f1153f42fa6c99bc8d98b13eedb4ab4292a99b476070310746db48ce08da69237ac0594f87a75dfe18f7f439a30bff90422b4436dfc987c869382811c054ad670098dedd4"},"signature_slot":"3016893"}} \ No newline at end of file diff --git a/relayer/src/ethereum_checkpoints/tests/chain-data/holesky-update-368.json.zst b/relayer/src/ethereum_checkpoints/tests/chain-data/holesky-update-368.json.zst new file mode 100644 index 00000000..2454347e Binary files /dev/null and b/relayer/src/ethereum_checkpoints/tests/chain-data/holesky-update-368.json.zst differ diff --git a/relayer/src/ethereum_checkpoints/tests/sepolia-bootstrap-640.json b/relayer/src/ethereum_checkpoints/tests/chain-data/sepolia-bootstrap-640.json similarity index 100% rename from relayer/src/ethereum_checkpoints/tests/sepolia-bootstrap-640.json rename to relayer/src/ethereum_checkpoints/tests/chain-data/sepolia-bootstrap-640.json diff --git a/relayer/src/ethereum_checkpoints/tests/sepolia-finality-update-5_263_072.json b/relayer/src/ethereum_checkpoints/tests/chain-data/sepolia-finality-update-5_263_072.json similarity index 100% rename from relayer/src/ethereum_checkpoints/tests/sepolia-finality-update-5_263_072.json rename to relayer/src/ethereum_checkpoints/tests/chain-data/sepolia-finality-update-5_263_072.json diff --git a/relayer/src/ethereum_checkpoints/tests/sepolia-update-640.json b/relayer/src/ethereum_checkpoints/tests/chain-data/sepolia-update-640.json similarity index 100% rename from relayer/src/ethereum_checkpoints/tests/sepolia-update-640.json rename to relayer/src/ethereum_checkpoints/tests/chain-data/sepolia-update-640.json diff --git a/relayer/src/ethereum_checkpoints/tests/mod.rs b/relayer/src/ethereum_checkpoints/tests/mod.rs index 6d0704f7..224d5deb 100644 --- a/relayer/src/ethereum_checkpoints/tests/mod.rs +++ b/relayer/src/ethereum_checkpoints/tests/mod.rs @@ -1,28 +1,82 @@ -use crate::ethereum_beacon_client::{self, slots_batch, BeaconClient}; use checkpoint_light_client::WASM_BINARY; use checkpoint_light_client_io::{ ethereum_common::{ base_types::BytesFixed, + beacon::SyncAggregate, network::Network, - utils::{self as eth_utils, BootstrapResponse, FinalityUpdateResponse, UpdateData}, + utils::{BootstrapResponse, FinalityUpdateResponse, UpdateData}, SLOTS_PER_EPOCH, }, - replay_back, sync_update, + replay_back::Status, + sync_update, tree_hash::TreeHash, Handle, HandleResult, Init, G2, }; -use gclient::{EventListener, EventProcessor, GearApi, Result}; +use ethereum_beacon_client::utils; +use ethereum_common::utils::{BeaconBlockHeaderResponse, Bootstrap, Update}; +use gclient::{EventListener, EventProcessor, GearApi, Result, WSAddress}; use parity_scale_codec::{Decode, Encode}; -use tokio::time::{self, Duration}; +use ruzstd::StreamingDecoder; +use sp_core::crypto::DEV_PHRASE; +use std::io::Read; +use tokio::sync::Mutex; + +static LOCK: Mutex = Mutex::const_new(0); + +const SEPOLIA_FINALITY_UPDATE_5_263_072: &[u8; 4_941] = + include_bytes!("./chain-data/sepolia-finality-update-5_263_072.json"); +const SEPOLIA_UPDATE_640: &[u8; 57_202] = include_bytes!("./chain-data/sepolia-update-640.json"); +const SEPOLIA_BOOTSTRAP_640: &[u8; 54_328] = + include_bytes!("./chain-data/sepolia-bootstrap-640.json"); + +const HOLESKY_UPDATE_368: &[u8; 30_468] = + include_bytes!("./chain-data/holesky-update-368.json.zst"); +const HOLESKY_BOOTSTRAP_368: &[u8; 29_297] = + include_bytes!("./chain-data/holesky-bootstrap-368.json.zst"); +const HOLESKY_HEADERS: &[u8; 452_109] = include_bytes!("./chain-data/headers.json.zst"); +const HOLESKY_FINALITY_UPDATE_3_014_736: &[u8; 4_893] = + include_bytes!("./chain-data/holesky-finality-update-3_016_736.json"); +const HOLESKY_FINALITY_UPDATE_3_014_768: &[u8; 4_932] = + include_bytes!("./chain-data/holesky-finality-update-3_016_768.json"); +const HOLESKY_FINALITY_UPDATE_3_014_799: &[u8; 4_980] = + include_bytes!("./chain-data/holesky-finality-update-3_016_799.json"); + +struct NodeClient(pub GearApi); + +impl NodeClient { + async fn new() -> Result { + let api = GearApi::dev().await?; + let mut lock = LOCK.lock().await; + + let salt = *lock; + *lock += 1; + + let suri = format!("{DEV_PHRASE}//ethereum_checkpoints{salt}:"); + let api2 = GearApi::init_with(WSAddress::dev(), suri).await?; + + let account_id: &[u8; 32] = api2.account_id().as_ref(); + api.transfer_keep_alive((*account_id).into(), 100_000_000_000_000) + .await?; + + Ok(Self(api2)) + } -const RPC_URL: &str = "http://127.0.0.1:5052"; + async fn calculate_handle_gas(&self, program_id: [u8; 32], payload: &Handle) -> Result { + Ok(self + .0 + .calculate_handle_gas(None, program_id.into(), payload.encode(), 0, true) + .await? + .min_limit) + } +} -const FINALITY_UPDATE_5_254_112: &[u8; 4_940] = - include_bytes!("./sepolia-finality-update-5_254_112.json"); -const FINALITY_UPDATE_5_263_072: &[u8; 4_941] = - include_bytes!("./sepolia-finality-update-5_263_072.json"); -const UPDATE_640: &[u8; 57_202] = include_bytes!("./sepolia-update-640.json"); -const BOOTSTRAP_640: &[u8; 54_328] = include_bytes!("./sepolia-bootstrap-640.json"); +#[track_caller] +fn decode_signature(sync_aggregate: &SyncAggregate) -> G2 { + ::deserialize_compressed( + &sync_aggregate.sync_committee_signature.0 .0[..], + ) + .unwrap() +} async fn common_upload_program( client: &GearApi, @@ -64,48 +118,55 @@ async fn upload_program( Ok(program_id) } -async fn init(network: Network) -> Result<()> { - let beacon_client = BeaconClient::new(RPC_URL.to_string(), None) - .await - .expect("Failed to connect to beacon node"); +async fn calculate_gas_and_send( + program_id: [u8; 32], + payload: Handle, + client: &NodeClient, +) -> Result<(u64, HandleResult)> { + let gas_limit = client.calculate_handle_gas(program_id, &payload).await?; - // use the latest finality header as a checkpoint for bootstrapping - let finality_update = beacon_client.get_finality_update().await?; - let slot = finality_update.finalized_header.slot; - let current_period = eth_utils::calculate_period(slot); - let mut updates = beacon_client.get_updates(current_period, 1).await?; + let mut listener = client.0.subscribe().await?; + let (message_id, _) = client + .0 + .send_message(program_id.into(), payload, gas_limit, 0) + .await?; - println!( - "finality_update slot = {}, period = {}", - slot, current_period - ); + let (_message_id, payload, _value) = listener.reply_bytes_on(message_id).await?; + let payload = payload.map_err(|e| anyhow::anyhow!("No payload: {e:?}"))?; - let update = match updates.pop() { - Some(update) if updates.is_empty() => update.data, - _ => unreachable!("Requested single update"), - }; + Ok((gas_limit, HandleResult::decode(&mut &payload[..])?)) +} - let checkpoint = update.finalized_header.tree_hash_root(); - let checkpoint_hex = hex::encode(checkpoint); +fn get_bootstrap_and_update() -> (Bootstrap, Update) { + let mut decoder = StreamingDecoder::new(&HOLESKY_BOOTSTRAP_368[..]).unwrap(); + let mut bootstrap = Vec::new(); + decoder.read_to_end(&mut bootstrap).unwrap(); + let BootstrapResponse { data: bootstrap } = serde_json::from_slice(&bootstrap[..]).unwrap(); - println!( - "checkpoint slot = {}, hash = {}", - update.finalized_header.slot, checkpoint_hex - ); + let mut decoder = StreamingDecoder::new(&HOLESKY_UPDATE_368[..]).unwrap(); + let mut update = Vec::new(); + decoder.read_to_end(&mut update).unwrap(); + let mut updates: Vec = serde_json::from_slice(&update[..]).unwrap(); - let bootstrap = beacon_client.get_bootstrap(&checkpoint_hex).await?; + (bootstrap, updates.pop().map(|u| u.data).unwrap()) +} - let signature = ::deserialize_compressed( - &update.sync_aggregate.sync_committee_signature.0 .0[..], - ) - .unwrap(); - let sync_update = ethereum_beacon_client::utils::sync_update_from_update(signature, update); +fn construct_init(network: Network, update: Update, bootstrap: Bootstrap) -> Init { + let checkpoint_update = update.finalized_header.tree_hash_root(); + let checkpoint_bootstrap = bootstrap.header.tree_hash_root(); + assert_eq!( + checkpoint_update, + checkpoint_bootstrap, + "checkpoint_update = {}, checkpoint_bootstrap = {}", + hex::encode(checkpoint_update), + hex::encode(checkpoint_bootstrap) + ); - println!("bootstrap slot = {}", bootstrap.header.slot); + let sync_update = + utils::sync_update_from_update(decode_signature(&update.sync_aggregate), update); + let pub_keys = utils::map_public_keys(&bootstrap.current_sync_committee.pubkeys); - let pub_keys = - ethereum_beacon_client::utils::map_public_keys(&bootstrap.current_sync_committee.pubkeys); - let init = Init { + Init { network, sync_committee_current_pub_keys: pub_keys, sync_committee_current_aggregate_pubkey: bootstrap.current_sync_committee.aggregate_pubkey, @@ -115,334 +176,133 @@ async fn init(network: Network) -> Result<()> { .map(|BytesFixed(bytes)| bytes.0) .collect(), update: sync_update, - }; - - let client = GearApi::dev().await?; - let mut listener = client.subscribe().await?; - - let program_id = upload_program(&client, &mut listener, init).await?; - - println!("program_id = {:?}", hex::encode(program_id)); - - Ok(()) -} - -#[ignore] -#[tokio::test] -async fn init_sepolia() -> Result<()> { - init(Network::Sepolia).await + } } -#[ignore] #[tokio::test] async fn init_holesky() -> Result<()> { - init(Network::Holesky).await -} + let (bootstrap, update) = get_bootstrap_and_update(); + let client = NodeClient::new().await?; + let mut listener = client.0.subscribe().await?; + let init = construct_init(Network::Holesky, update, bootstrap); + let program_id = upload_program(&client.0, &mut listener, init).await?; -#[ignore] -#[tokio::test] -async fn init_mainnet() -> Result<()> { - init(Network::Mainnet).await + println!("program_id = {:?}", hex::encode(program_id)); + + Ok(()) } -#[ignore] #[tokio::test] -async fn init_and_updating() -> Result<()> { - let beacon_client = BeaconClient::new(RPC_URL.to_string(), None) - .await - .expect("Failed to connect to beacon node"); - - // use the latest finality header as a checkpoint for bootstrapping - let finality_update = beacon_client.get_finality_update().await?; - let current_period = eth_utils::calculate_period(finality_update.finalized_header.slot); - let mut updates = beacon_client.get_updates(current_period, 1).await?; - - println!( - "finality_update slot = {}, period = {}", - finality_update.finalized_header.slot, current_period - ); - - let update = match updates.pop() { - Some(update) if updates.is_empty() => update.data, - _ => unreachable!("Requested single update"), - }; - - let checkpoint = update.finalized_header.tree_hash_root(); - let checkpoint_hex = hex::encode(checkpoint); - - println!( - "checkpoint slot = {}, hash = {}", - update.finalized_header.slot, checkpoint_hex - ); - - let bootstrap = beacon_client.get_bootstrap(&checkpoint_hex).await?; - - let signature = ::deserialize_compressed( - &update.sync_aggregate.sync_committee_signature.0 .0[..], - ) - .unwrap(); - let sync_update = ethereum_beacon_client::utils::sync_update_from_update(signature, update); - - println!("bootstrap slot = {}", bootstrap.header.slot); - - let pub_keys = - ethereum_beacon_client::utils::map_public_keys(&bootstrap.current_sync_committee.pubkeys); - let init = Init { - network: Network::Holesky, - sync_committee_current_pub_keys: pub_keys, - sync_committee_current_aggregate_pubkey: bootstrap.current_sync_committee.aggregate_pubkey, - sync_committee_current_branch: bootstrap - .current_sync_committee_branch - .into_iter() - .map(|BytesFixed(bytes)| bytes.0) - .collect(), - update: sync_update, - }; - - let client = GearApi::dev().await?; - let mut listener = client.subscribe().await?; - - let program_id = upload_program(&client, &mut listener, init).await?; +async fn replay_back_and_updating() -> Result<()> { + let client = NodeClient::new().await?; + let mut listener = client.0.subscribe().await?; + let (bootstrap, update) = get_bootstrap_and_update(); + let init = construct_init(Network::Holesky, update, bootstrap); + let program_id = upload_program(&client.0, &mut listener, init).await?; println!("program_id = {:?}", hex::encode(program_id)); println!(); println!(); - for _ in 0..30 { - let update = beacon_client.get_finality_update().await?; - - let slot: u64 = update.finalized_header.slot; - let current_period = eth_utils::calculate_period(slot); - let mut updates = beacon_client.get_updates(current_period, 1).await?; - match updates.pop() { - Some(update) if updates.is_empty() && update.data.finalized_header.slot >= slot => { - println!("update sync committee"); - let signature = - ::deserialize_compressed( - &update.data.sync_aggregate.sync_committee_signature.0 .0[..], - ) - .unwrap(); - let payload = Handle::SyncUpdate( - ethereum_beacon_client::utils::sync_update_from_update(signature, update.data), - ); - let gas_limit = client - .calculate_handle_gas(None, program_id.into(), payload.encode(), 0, true) - .await? - .min_limit; - println!("update gas_limit {gas_limit:?}"); - - let (message_id, _) = client - .send_message(program_id.into(), payload, gas_limit, 0) - .await?; - - let (_message_id, payload, _value) = listener.reply_bytes_on(message_id).await?; - let result_decoded = HandleResult::decode(&mut &payload.unwrap()[..]).unwrap(); - assert!( - matches!(result_decoded, HandleResult::SyncUpdate(result) if result.is_ok()) - ); - } - - _ => { - println!( - "slot = {slot:?}, attested slot = {:?}, signature slot = {:?}", - update.attested_header.slot, update.signature_slot - ); - let signature = ::deserialize_compressed( - &update.sync_aggregate.sync_committee_signature.0 .0[..], - ); - - let Ok(signature) = signature else { - println!("failed to deserialize point on G2"); - continue; - }; - - let payload = Handle::SyncUpdate( - ethereum_beacon_client::utils::sync_update_from_finality(signature, update), - ); - - let gas_limit = client - .calculate_handle_gas(None, program_id.into(), payload.encode(), 0, true) - .await? - .min_limit; - println!("finality_update gas_limit {gas_limit:?}"); - - let (message_id, _) = client - .send_message(program_id.into(), payload, gas_limit, 0) - .await?; - - let (_message_id, payload, _value) = listener.reply_bytes_on(message_id).await?; - let result_decoded = HandleResult::decode(&mut &payload.unwrap()[..]).unwrap(); - assert!( - matches!(result_decoded, HandleResult::SyncUpdate(result) if result.is_ok()) - ); - } - } - - println!(); - println!(); - - time::sleep(Duration::from_secs(6 * 60)).await; - } - - Ok(()) -} - -#[ignore] -#[tokio::test] -async fn replaying_back() -> Result<()> { - let beacon_client = BeaconClient::new(RPC_URL.to_string(), None) - .await - .expect("Failed to connect to beacon node"); - let finality_update: FinalityUpdateResponse = - serde_json::from_slice(FINALITY_UPDATE_5_254_112).unwrap(); + serde_json::from_slice(HOLESKY_FINALITY_UPDATE_3_014_736).unwrap(); let finality_update = finality_update.data; - println!( - "finality_update slot = {}", - finality_update.finalized_header.slot - ); - - // This SyncCommittee operated for about 13K slots, so we make adjustments - let current_period = eth_utils::calculate_period(finality_update.finalized_header.slot); - let mut updates = beacon_client.get_updates(current_period - 1, 1).await?; - let update = match updates.pop() { - Some(update) if updates.is_empty() => update.data, - _ => unreachable!("Requested single update"), - }; - let checkpoint = update.finalized_header.tree_hash_root(); - let checkpoint_hex = hex::encode(checkpoint); + let mut decoder = StreamingDecoder::new(&HOLESKY_HEADERS[..]).unwrap(); + let mut headers = Vec::new(); + decoder.read_to_end(&mut headers).unwrap(); - let bootstrap = beacon_client.get_bootstrap(&checkpoint_hex).await?; - println!("bootstrap slot = {}", bootstrap.header.slot); + let headers: Vec = serde_json::from_slice(&headers[..]).unwrap(); - println!("update slot = {}", update.finalized_header.slot); - let signature = ::deserialize_compressed( - &update.sync_aggregate.sync_committee_signature.0 .0[..], - ) - .unwrap(); - let sync_update = ethereum_beacon_client::utils::sync_update_from_update(signature, update); - let slot_start = sync_update.finalized_header.slot; - let slot_end = finality_update.finalized_header.slot; - println!( - "Replaying back from {slot_start} to {slot_end} ({} headers)", - slot_end - slot_start - ); - - let pub_keys = - ethereum_beacon_client::utils::map_public_keys(&bootstrap.current_sync_committee.pubkeys); - let init = Init { - network: Network::Sepolia, - sync_committee_current_pub_keys: pub_keys, - sync_committee_current_aggregate_pubkey: bootstrap.current_sync_committee.aggregate_pubkey, - sync_committee_current_branch: bootstrap - .current_sync_committee_branch - .into_iter() - .map(|BytesFixed(bytes)| bytes.0) + // start to replay back + let size_batch = 40 * SLOTS_PER_EPOCH as usize; + let payload = Handle::ReplayBackStart { + sync_update: utils::sync_update_from_finality( + decode_signature(&finality_update.sync_aggregate), + finality_update, + ), + headers: headers + .iter() + .rev() + .take(size_batch) + .map(|r| r.data.header.message.clone()) .collect(), - update: sync_update, }; - let client = GearApi::dev().await?; - let mut listener = client.subscribe().await?; - - let program_id = upload_program(&client, &mut listener, init).await?; + let (gas_limit, result) = calculate_gas_and_send(program_id, payload, &client).await?; + println!("ReplayBackStart gas_limit {gas_limit:?}"); - println!("program_id = {:?}", hex::encode(program_id)); - - println!(); - println!(); - - let batch_size = 44 * SLOTS_PER_EPOCH; - let mut slots_batch_iter = slots_batch::Iter::new(slot_start, slot_end, batch_size).unwrap(); - // start to replay back - if let Some((slot_start, slot_end)) = slots_batch_iter.next() { - let mut requests_headers = Vec::with_capacity(batch_size as usize); - for i in slot_start..slot_end { - requests_headers.push(beacon_client.get_block_header(i)); - } - - let headers = futures::future::join_all(requests_headers) - .await - .into_iter() - .filter_map(|maybe_header| maybe_header.ok()) - .collect::>(); - - let signature = ::deserialize_compressed( - &finality_update.sync_aggregate.sync_committee_signature.0 .0[..], - ) - .unwrap(); - - let payload = Handle::ReplayBackStart { - sync_update: ethereum_beacon_client::utils::sync_update_from_finality( - signature, - finality_update, - ), - headers, - }; + assert!( + matches!(result, HandleResult::ReplayBackStart(Ok(_))), + "result = {result:?}" + ); - let gas_limit = client - .calculate_handle_gas(None, program_id.into(), payload.encode(), 0, true) - .await? - .min_limit; - println!("ReplayBackStart gas_limit {gas_limit:?}"); + // replaying the blocks back + let payload = Handle::ReplayBack( + headers + .iter() + .rev() + .skip(size_batch) + .map(|r| r.data.header.message.clone()) + .collect(), + ); + let (gas_limit, result) = calculate_gas_and_send(program_id, payload, &client).await?; + println!("ReplayBack gas_limit {gas_limit:?}"); - let (message_id, _) = client - .send_message(program_id.into(), payload, gas_limit, 0) - .await?; + assert!( + matches!(result, HandleResult::ReplayBack(Some(Status::Finished))), + "result = {result:?}" + ); - let (_message_id, payload, _value) = listener.reply_bytes_on(message_id).await?; - let result_decoded = HandleResult::decode(&mut &payload.unwrap()[..]).unwrap(); - assert!(matches!( - result_decoded, - HandleResult::ReplayBackStart(Ok(replay_back::StatusStart::InProgress)) + // updating + let finality_updates = vec![ + { + let finality_update: FinalityUpdateResponse = + serde_json::from_slice(HOLESKY_FINALITY_UPDATE_3_014_768).unwrap(); + + finality_update.data + }, + { + let finality_update: FinalityUpdateResponse = + serde_json::from_slice(HOLESKY_FINALITY_UPDATE_3_014_799).unwrap(); + + finality_update.data + }, + ]; + for update in finality_updates { + println!( + "slot = {:?}, attested slot = {:?}, signature slot = {:?}", + update.finalized_header.slot, update.attested_header.slot, update.signature_slot + ); + + let payload = Handle::SyncUpdate(utils::sync_update_from_finality( + decode_signature(&update.sync_aggregate), + update, )); - } - // replaying the blocks back - for (slot_start, slot_end) in slots_batch_iter { - let mut requests_headers = Vec::with_capacity(batch_size as usize); - for i in slot_start..slot_end { - requests_headers.push(beacon_client.get_block_header(i)); - } - - let headers = futures::future::join_all(requests_headers) - .await - .into_iter() - .filter_map(|maybe_header| maybe_header.ok()) - .collect::>(); - - let payload = Handle::ReplayBack(headers); - - let gas_limit = client - .calculate_handle_gas(None, program_id.into(), payload.encode(), 0, true) - .await? - .min_limit; - println!("ReplayBack gas_limit {gas_limit:?}"); + let (gas_limit, result) = calculate_gas_and_send(program_id, payload, &client).await?; + println!("gas_limit {gas_limit:?}"); - let (message_id, _) = client - .send_message(program_id.into(), payload, gas_limit, 0) - .await?; + assert!( + matches!( + result, + HandleResult::SyncUpdate(Ok(_) | Err(sync_update::Error::LowVoteCount)) + ), + "result = {result:?}" + ); - let (_message_id, payload, _value) = listener.reply_bytes_on(message_id).await?; - let result_decoded = HandleResult::decode(&mut &payload.unwrap()[..]).unwrap(); - assert!(matches!( - result_decoded, - HandleResult::ReplayBack(Some( - replay_back::Status::InProcess | replay_back::Status::Finished - )) - )); + println!(); + println!(); } Ok(()) } #[tokio::test] -#[ignore = "Fails for now"] async fn sync_update_requires_replaying_back() -> Result<()> { let finality_update: FinalityUpdateResponse = - serde_json::from_slice(FINALITY_UPDATE_5_263_072).unwrap(); + serde_json::from_slice(SEPOLIA_FINALITY_UPDATE_5_263_072).unwrap(); let finality_update = finality_update.data; println!( "finality_update slot = {}", @@ -450,49 +310,19 @@ async fn sync_update_requires_replaying_back() -> Result<()> { ); let slot = finality_update.finalized_header.slot; - let mut updates: Vec = serde_json::from_slice(UPDATE_640).unwrap(); + let BootstrapResponse { data: bootstrap } = + serde_json::from_slice(SEPOLIA_BOOTSTRAP_640).unwrap(); + let mut updates: Vec = serde_json::from_slice(SEPOLIA_UPDATE_640).unwrap(); let update = match updates.pop() { Some(update) if updates.is_empty() => update.data, _ => unreachable!("Requested single update"), }; - let BootstrapResponse { data: bootstrap } = serde_json::from_slice(BOOTSTRAP_640).unwrap(); - - let checkpoint_update = update.finalized_header.tree_hash_root(); - let checkpoint_bootstrap = bootstrap.header.tree_hash_root(); - assert_eq!( - checkpoint_update, - checkpoint_bootstrap, - "checkpoint_update = {}, checkpoint_bootstrap = {}", - hex::encode(checkpoint_update), - hex::encode(checkpoint_bootstrap) - ); - - let signature = ::deserialize_compressed( - &update.sync_aggregate.sync_committee_signature.0 .0[..], - ) - .unwrap(); - let sync_update = ethereum_beacon_client::utils::sync_update_from_update(signature, update); - - let pub_keys = - ethereum_beacon_client::utils::map_public_keys(&bootstrap.current_sync_committee.pubkeys); - let init = Init { - network: Network::Sepolia, - sync_committee_current_pub_keys: pub_keys, - sync_committee_current_aggregate_pubkey: bootstrap.current_sync_committee.aggregate_pubkey, - sync_committee_current_branch: bootstrap - .current_sync_committee_branch - .into_iter() - .map(|BytesFixed(bytes)| bytes.0) - .collect(), - update: sync_update, - }; - - let client = GearApi::dev().await?; - let mut listener = client.subscribe().await?; - - let program_id = upload_program(&client, &mut listener, init).await?; + let client = NodeClient::new().await?; + let mut listener = client.0.subscribe().await?; + let init = construct_init(Network::Sepolia, update, bootstrap); + let program_id = upload_program(&client.0, &mut listener, init).await?; println!("program_id = {:?}", hex::encode(program_id)); @@ -503,34 +333,20 @@ async fn sync_update_requires_replaying_back() -> Result<()> { "slot = {slot:?}, attested slot = {:?}, signature slot = {:?}", finality_update.attested_header.slot, finality_update.signature_slot ); - let signature = ::deserialize_compressed( - &finality_update.sync_aggregate.sync_committee_signature.0 .0[..], - ) - .unwrap(); - let payload = Handle::SyncUpdate(ethereum_beacon_client::utils::sync_update_from_finality( - signature, + let payload = Handle::SyncUpdate(utils::sync_update_from_finality( + decode_signature(&finality_update.sync_aggregate), finality_update, )); - - let gas_limit = client - .calculate_handle_gas(None, program_id.into(), payload.encode(), 0, true) - .await? - .min_limit; + let (gas_limit, result) = calculate_gas_and_send(program_id, payload, &client).await?; println!("finality_update gas_limit {gas_limit:?}"); - let (message_id, _) = client - .send_message(program_id.into(), payload, gas_limit, 0) - .await?; - - let (_message_id, payload, _value) = listener.reply_bytes_on(message_id).await?; - let result_decoded = HandleResult::decode(&mut &payload.unwrap()[..]).unwrap(); assert!( matches!( - result_decoded, + result, HandleResult::SyncUpdate(Err(sync_update::Error::ReplayBackRequired { .. })) ), - "result_decoded = {result_decoded:?}" + "result = {result:?}" ); Ok(()) diff --git a/relayer/src/ethereum_checkpoints/tests/sepolia-finality-update-5_254_112.json b/relayer/src/ethereum_checkpoints/tests/sepolia-finality-update-5_254_112.json deleted file mode 100644 index 92738d8e..00000000 --- a/relayer/src/ethereum_checkpoints/tests/sepolia-finality-update-5_254_112.json +++ /dev/null @@ -1 +0,0 @@ -{"version":"deneb","data":{"attested_header":{"beacon":{"slot":"5254183","proposer_index":"919","parent_root":"0xfcccc773a0738b170bb7254307dbff67ae93afd461c6d42da133acd6d9e0c845","state_root":"0x154722b9d2148e9c2156715fb0f757a915cf506c7f7dac19e514c568422d27cd","body_root":"0xd7b415d731808521e932c9ee94ec8b21218417a06927bbac667b1aee1ff928f3"},"execution":{"parent_hash":"0xdc4df214dde0c859f3fda8069c9a2ad807f58a235f3095667c02be344fc4d05a","fee_recipient":"0xe276bc378a527a8792b353cdca5b5e53263dfb9e","state_root":"0xcfcd8bb4c2703dd302e416c13bc98e77cc56fd6dabb04e4cee957aae274f308f","receipts_root":"0x4159706a6c777b188dfb1a92f30374bbfc561ad2dfef674741a63e509e4fe478","logs_bloom":"0x2aa304283918c704c51a4206281480e246102682122141106052183c9000908014087d21442aa2e4071a045d003e24800114132a6008b24200389b980a6040010687e06500540982520004ba8265048086015b10104a425089098834120810228c1400202a0c45200393041816012d0c220831a40c9210200889065481da097683803212c8b800202262156c0248841a80092820046a080900435381ad0040402a0880092000020c0cc0c1828170180e108124a0906025040b0262000c4b4261495f01a296fc31a08b91cc11199210d9020bc4ab1124a0d442621360006134248ad002504015198e28081254e0008a449348301d0802204b303500008151b002","prev_randao":"0x055ff0e0ea219f1f1798acd48d7c6ef04a7aa53ec1b31f1950f5caf47f99d9d8","block_number":"6139158","gas_limit":"30000000","gas_used":"10150933","timestamp":"1718783796","extra_data":"0x","base_fee_per_gas":"6486887","block_hash":"0x6a1c6cc1288590f860aad07caae1a0150844846e79f7328537754c9ffb51948a","transactions_root":"0xda08b27a3fb71dfd8b24e2d7bcff6539d66ddea393307424a5f51f56d5de9188","withdrawals_root":"0xfdfc4f7fac80825110865f82565d1070bf00ad5309a9dfa286038b759fab2b2e","blob_gas_used":"262144","excess_blob_gas":"393216"},"execution_branch":["0x3a9d60a272e0514b655755f5357b920d92f14cb6fa80434b821e028874ca111d","0x271e4477a4c6d2fc01625dfa2fe4fd786c74455e038669869e537cead45a06cb","0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71","0x0cf30679a2dc5a4f9ad5db4e601ec76b37c13273e0d30ff7a3c92fc1d3e4a3d6"]},"finalized_header":{"beacon":{"slot":"5254112","proposer_index":"1446","parent_root":"0x5f92903c1eded7983b75cdd969ffad3c20c1b37b3e12b57f45f29220a99bdeae","state_root":"0x4added61ed6e8b61e511da828c3f9c7d6e14c655045a8596460bea84e2212243","body_root":"0xb7e79c1e0d2ff6908151cdce8de26db289d3f11ac0701cd39ebf4d1446e6551f"},"execution":{"parent_hash":"0x3f22e88861faf94bb83b910a6499f9344f3d30ad2c288a9f06a99c0560f18fd1","fee_recipient":"0x0000000000000000000000000000000000000000","state_root":"0xd4f9756827fc1539fe3b719389a7ca5d7de029d5b8ebc14666043d16ef57fd9d","receipts_root":"0xc1f2d3ba7f01c3fa6c68df784ad304aff093bde30f5f19f0fe106ff8f65cfa27","logs_bloom":"0x208dc40f590e462a2cd04002710b06680048d210108400507b2061205172a3d80088632124d44ae2d51712cd452441a480bb3d2c1a2014030031b7180a24024915c6ec6f4506844751ad20dee6f39a869790524870c649508085049a2120220137a60625ba26a4002c4320a1280a6f88044921f044d802a5000a223110c0804786766004653c101c202b26490010600806192b882944f285c0010c164d8ac2900b4ae29ef08227c20871c917c8041648f3d246c86844af0b8d024f86425112618a06d00705690845a00118103b0741054082c484100613a5c68315638a4164001f5440902081083f01483482e1992f110a1930404c290633d61583268c9c4032","prev_randao":"0xb76d54a8a2cc93366a05bd5cd917d3668a9f9add7713e3bbabc35ec06dd8de63","block_number":"6139090","gas_limit":"30000000","gas_used":"14001456","timestamp":"1718782944","extra_data":"0xd883010e05846765746888676f312e32322e30856c696e7578","base_fee_per_gas":"2984276","block_hash":"0xe3d21a2b4f2ac8912b285e02ab5f5b88d2fe299f52ca59afa04dce827e3b74cd","transactions_root":"0xd60dd5ae29b0edc1f59e9d9c4c822d9547e02dfd8717d7d38c15c8b02b70edcd","withdrawals_root":"0xff715cd7143e1113f7ff634771b9ad3744c198c270c14f474dbd24f23b17c3a1","blob_gas_used":"0","excess_blob_gas":"0"},"execution_branch":["0xf2a180c996b4c4c66fda705bd1505c4a2bc20ff2223ef5777a9ce7676531f095","0xb46f0c01805fe212e15907981b757e6c496b0cb06664224655613dcec82505bb","0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71","0x2dcce09118b66600d68cc62a5288f3d79772eb63fd6a32e96f4ae675a249435c"]},"finality_branch":["0x5f81020000000000000000000000000000000000000000000000000000000000","0x3d4be5d019ba15ea3ef304a83b8a067f2e79f46a3fac8069306a6c814a0a35eb","0xf21d36e48716b3854a3fede50527a01ee933165c306bd858acb89139a5924308","0x80c208c9d177870fe677d031b558ace9a23702a424cdbd7a0ffbd721a082ab50","0x66582e9a176df6aa3e1fbd56742abf0cd4a341b74b53cfa2f585ef07f6e2be63","0xfdd89560d14da5ebe8e1e0d92632d5955a7b743e0d272ce4da1b548796e5e80a"],"sync_aggregate":{"sync_committee_bits":"0xf77fffffffffffffffffbfdfffbffffefffffffffbfffffffffffffbfbfffffdffbffffffffffdefffffffffbfbfbffffffffffffffbdffffedffffafffffffe","sync_committee_signature":"0xa97db0497cfb9bc298c7ae1cbec1641ec74aee0ef31dde16949de67bcb447c379a5d286c6149642564285d201fe71ab401af426af165d4486ff2f718d2aa34f482f1f96f410b67b3f93c433e690fc6c70eec3e44adb0e01f1e6083deaec36db0"},"signature_slot":"5254184"}} diff --git a/relayer/src/main.rs b/relayer/src/main.rs index 141fcae3..b39caa79 100644 --- a/relayer/src/main.rs +++ b/relayer/src/main.rs @@ -12,7 +12,6 @@ use prover::proving::GenesisConfig; use relay_merkle_roots::MerkleRootRelayer; use utils_prometheus::MetricsBuilder; -mod ethereum_beacon_client; mod ethereum_checkpoints; mod hex_utils; mod message_relayer; diff --git a/relayer/src/message_relayer/common/ethereum/deposit_event_extractor.rs b/relayer/src/message_relayer/common/ethereum/deposit_event_extractor.rs index 9e72d759..1f74c775 100644 --- a/relayer/src/message_relayer/common/ethereum/deposit_event_extractor.rs +++ b/relayer/src/message_relayer/common/ethereum/deposit_event_extractor.rs @@ -4,13 +4,11 @@ use futures::executor::block_on; use prometheus::IntCounter; use sails_rs::H160; +use ethereum_beacon_client::BeaconClient; use ethereum_client::{DepositEventEntry, EthApi}; use utils_prometheus::{impl_metered_service, MeteredService}; -use crate::{ - ethereum_beacon_client::BeaconClient, - message_relayer::common::{EthereumBlockNumber, TxHashWithSlot}, -}; +use crate::message_relayer::common::{EthereumBlockNumber, TxHashWithSlot}; use super::find_slot_by_block_number; diff --git a/relayer/src/message_relayer/common/ethereum/message_paid_event_extractor.rs b/relayer/src/message_relayer/common/ethereum/message_paid_event_extractor.rs index 6f4c7ecc..6c74d382 100644 --- a/relayer/src/message_relayer/common/ethereum/message_paid_event_extractor.rs +++ b/relayer/src/message_relayer/common/ethereum/message_paid_event_extractor.rs @@ -4,13 +4,11 @@ use futures::executor::block_on; use prometheus::IntCounter; use sails_rs::H160; +use ethereum_beacon_client::BeaconClient; use ethereum_client::{EthApi, FeePaidEntry}; use utils_prometheus::{impl_metered_service, MeteredService}; -use crate::{ - ethereum_beacon_client::BeaconClient, - message_relayer::common::{EthereumBlockNumber, TxHashWithSlot}, -}; +use crate::message_relayer::common::{EthereumBlockNumber, TxHashWithSlot}; use super::find_slot_by_block_number; diff --git a/relayer/src/message_relayer/common/ethereum/mod.rs b/relayer/src/message_relayer/common/ethereum/mod.rs index d62ee731..07ec491f 100644 --- a/relayer/src/message_relayer/common/ethereum/mod.rs +++ b/relayer/src/message_relayer/common/ethereum/mod.rs @@ -5,7 +5,7 @@ use alloy_eips::BlockNumberOrTag; use anyhow::anyhow; use ethereum_client::EthApi; -use crate::ethereum_beacon_client::BeaconClient; +use ethereum_beacon_client::BeaconClient; use super::{EthereumBlockNumber, EthereumSlotNumber}; diff --git a/relayer/src/message_relayer/common/gear/message_sender/compose_payload.rs b/relayer/src/message_relayer/common/gear/message_sender/compose_payload.rs index 12654135..98dd581b 100644 --- a/relayer/src/message_relayer/common/gear/message_sender/compose_payload.rs +++ b/relayer/src/message_relayer/common/gear/message_sender/compose_payload.rs @@ -1,4 +1,4 @@ -use crate::ethereum_beacon_client::BeaconClient; +use ethereum_beacon_client::BeaconClient; use alloy::{network::primitives::BlockTransactionsKind, primitives::TxHash, providers::Provider}; use alloy_eips::{BlockId, BlockNumberOrTag}; diff --git a/relayer/src/message_relayer/common/gear/message_sender/mod.rs b/relayer/src/message_relayer/common/gear/message_sender/mod.rs index 5ce36fac..3ff4fca9 100644 --- a/relayer/src/message_relayer/common/gear/message_sender/mod.rs +++ b/relayer/src/message_relayer/common/gear/message_sender/mod.rs @@ -11,12 +11,10 @@ use sails_rs::{ }; use erc20_relay_client::{traits::Erc20Relay as _, Erc20Relay}; +use ethereum_beacon_client::BeaconClient; use utils_prometheus::{impl_metered_service, MeteredService}; -use crate::{ - ethereum_beacon_client::BeaconClient, - message_relayer::common::{EthereumSlotNumber, TxHashWithSlot}, -}; +use crate::message_relayer::common::{EthereumSlotNumber, TxHashWithSlot}; mod compose_payload; diff --git a/relayer/src/message_relayer/eth_to_gear/all_token_transfers.rs b/relayer/src/message_relayer/eth_to_gear/all_token_transfers.rs index 60e51a57..ab3889d1 100644 --- a/relayer/src/message_relayer/eth_to_gear/all_token_transfers.rs +++ b/relayer/src/message_relayer/eth_to_gear/all_token_transfers.rs @@ -3,21 +3,19 @@ use std::iter; use gclient::GearApi as GclientGearApi; use primitive_types::{H160, H256}; +use ethereum_beacon_client::BeaconClient; use ethereum_client::EthApi; use gear_rpc_client::GearApi; use utils_prometheus::MeteredService; -use crate::{ - ethereum_beacon_client::BeaconClient, - message_relayer::common::{ - ethereum::{ - block_listener::BlockListener as EthereumBlockListener, - deposit_event_extractor::DepositEventExtractor, - }, - gear::{ - block_listener::BlockListener as GearBlockListener, - checkpoints_extractor::CheckpointsExtractor, message_sender::MessageSender, - }, +use crate::message_relayer::common::{ + ethereum::{ + block_listener::BlockListener as EthereumBlockListener, + deposit_event_extractor::DepositEventExtractor, + }, + gear::{ + block_listener::BlockListener as GearBlockListener, + checkpoints_extractor::CheckpointsExtractor, message_sender::MessageSender, }, }; diff --git a/relayer/src/message_relayer/eth_to_gear/paid_token_transfers.rs b/relayer/src/message_relayer/eth_to_gear/paid_token_transfers.rs index 338cfbef..7f4ed9ac 100644 --- a/relayer/src/message_relayer/eth_to_gear/paid_token_transfers.rs +++ b/relayer/src/message_relayer/eth_to_gear/paid_token_transfers.rs @@ -3,21 +3,19 @@ use std::iter; use gclient::GearApi as GclientGearApi; use primitive_types::{H160, H256}; +use ethereum_beacon_client::BeaconClient; use ethereum_client::EthApi; use gear_rpc_client::GearApi; use utils_prometheus::MeteredService; -use crate::{ - ethereum_beacon_client::BeaconClient, - message_relayer::common::{ - ethereum::{ - block_listener::BlockListener as EthereumBlockListener, - message_paid_event_extractor::MessagePaidEventExtractor, - }, - gear::{ - block_listener::BlockListener as GearBlockListener, - checkpoints_extractor::CheckpointsExtractor, message_sender::MessageSender, - }, +use crate::message_relayer::common::{ + ethereum::{ + block_listener::BlockListener as EthereumBlockListener, + message_paid_event_extractor::MessagePaidEventExtractor, + }, + gear::{ + block_listener::BlockListener as GearBlockListener, + checkpoints_extractor::CheckpointsExtractor, message_sender::MessageSender, }, }; diff --git a/tools/deploy-checkpoints/Cargo.toml b/tools/deploy-checkpoints/Cargo.toml new file mode 100644 index 00000000..a296206d --- /dev/null +++ b/tools/deploy-checkpoints/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "deploy-checkpoints" +version.workspace = true +edition.workspace = true + +[dependencies] +anyhow.workspace = true +ark-serialize = { workspace = true, features = ["std"] } +checkpoint_light_client = { workspace = true, features = ["std"] } +checkpoint_light_client-io = { workspace = true, features = ["std"] } +clap.workspace = true +dotenv.workspace = true +ethereum_beacon_client.workspace = true +gclient.workspace = true +hex = { workspace = true, features = ["std"] } +parity-scale-codec = { workspace = true, features = ["std"] } +tokio.workspace = true diff --git a/tools/deploy-checkpoints/src/main.rs b/tools/deploy-checkpoints/src/main.rs new file mode 100644 index 00000000..838687ba --- /dev/null +++ b/tools/deploy-checkpoints/src/main.rs @@ -0,0 +1,176 @@ +use anyhow::{anyhow, Result as AnyResult}; +use checkpoint_light_client::WASM_BINARY; +use checkpoint_light_client_io::{ + ethereum_common::{base_types::BytesFixed, network::Network, utils as eth_utils}, + tree_hash::TreeHash, + Init, G2, +}; +use clap::Parser; +use ethereum_beacon_client::{utils, BeaconClient}; +use gclient::{EventListener, EventProcessor, GearApi, WSAddress}; +use parity_scale_codec::Encode; +use std::time::Duration; + +const GEAR_API_RETRIES: u8 = 3; + +#[derive(Parser)] +#[command(author, version, about, long_about = None)] +#[command(propagate_version = true)] +struct Cli { + /// Address of the Gear RPC endpoint + #[arg( + long = "gear-endpoint", + default_value = "wss://testnet.vara.network", + env = "GEAR_RPC" + )] + gear_endpoint: String, + + /// Port of the Gear RPC endpoint + #[arg(long = "gear-port", default_value = "443", env = "GEAR_PORT")] + gear_port: u16, + + /// Substrate URI that identifies a user by a mnemonic phrase or + /// provides default users from the keyring (e.g., "//Alice", "//Bob", + /// etc.). The password for URI should be specified in the same `suri`, + /// separated by the ':' char + #[arg(long, default_value = "//Alice", env = "GEAR_SURI")] + gear_suri: String, + + /// Specify the endpoint providing Beacon API + #[arg( + long, + default_value = "https://www.lightclientdata.org", + env = "BEACON_ENDPOINT" + )] + beacon_endpoint: String, + + /// Specify the timeout in seconds for requests to the Beacon API endpoint + #[arg(long, default_value = "120", env = "BEACON_TIMEOUT")] + beacon_timeout: u64, + + /// Specify the Ethereum network (Mainnet, Holesky or Sepolia) + #[arg(long, default_value = "Mainnet", env = "NETWORK")] + network: String, +} + +#[tokio::main] +async fn main() -> AnyResult<()> { + let _ = dotenv::dotenv(); + + let cli = Cli::parse(); + let network = cli.network.to_lowercase(); + let network = if network == "mainnet" { + Network::Mainnet + } else if network == "holesky" { + Network::Holesky + } else if network == "sepolia" { + Network::Sepolia + } else { + return Err(anyhow!("Network '{network}' is not supported")); + }; + + let beacon_client = BeaconClient::new( + cli.beacon_endpoint, + Some(Duration::from_secs(cli.beacon_timeout)), + ) + .await?; + + // use the latest finalized block as a checkpoint for bootstrapping + let finalized_block = beacon_client.get_block_finalized().await?; + let slot = finalized_block.slot; + let current_period = eth_utils::calculate_period(slot); + let mut updates = beacon_client.get_updates(current_period, 1).await?; + + println!( + "finality_update slot = {}, period = {}", + slot, current_period + ); + + let update = match updates.pop() { + Some(update) if updates.is_empty() => update.data, + _ => unreachable!("Requested single update"), + }; + + let checkpoint = update.finalized_header.tree_hash_root(); + let checkpoint_hex = hex::encode(checkpoint); + + println!( + "checkpoint slot = {}, hash = {}", + update.finalized_header.slot, checkpoint_hex + ); + + let bootstrap = beacon_client.get_bootstrap(&checkpoint_hex).await?; + println!("bootstrap slot = {}", bootstrap.header.slot); + + let signature = ::deserialize_compressed( + &update.sync_aggregate.sync_committee_signature.0 .0[..], + ) + .map_err(|e| anyhow!("Failed to decode signature: {e:?}"))?; + + let sync_update = utils::sync_update_from_update(signature, update); + let pub_keys = utils::map_public_keys(&bootstrap.current_sync_committee.pubkeys); + + let init = Init { + network, + sync_committee_current_pub_keys: pub_keys, + sync_committee_current_aggregate_pubkey: bootstrap.current_sync_committee.aggregate_pubkey, + sync_committee_current_branch: bootstrap + .current_sync_committee_branch + .into_iter() + .map(|BytesFixed(bytes)| bytes.0) + .collect(), + update: sync_update, + }; + + let client = GearApi::builder() + .retries(GEAR_API_RETRIES) + .suri(cli.gear_suri) + .build(WSAddress::new(&cli.gear_endpoint, cli.gear_port)) + .await?; + let mut listener = client.subscribe().await?; + let program_id = upload_program(&client, &mut listener, init).await?; + + println!("program_id = {:?}", hex::encode(program_id)); + + Ok(()) +} + +async fn common_upload_program( + client: &GearApi, + code: Vec, + payload: impl Encode, +) -> AnyResult<([u8; 32], [u8; 32])> { + let encoded_payload = payload.encode(); + let gas_limit = client + .calculate_upload_gas(None, code.clone(), encoded_payload, 0, true) + .await? + .min_limit; + println!("init gas {gas_limit:?}"); + let (message_id, program_id, _) = client + .upload_program( + code, + gclient::now_micros().to_le_bytes(), + payload, + gas_limit, + 0, + ) + .await?; + + Ok((message_id.into(), program_id.into())) +} + +async fn upload_program( + client: &GearApi, + listener: &mut EventListener, + payload: impl Encode, +) -> AnyResult<[u8; 32]> { + let (message_id, program_id) = + common_upload_program(client, WASM_BINARY.to_vec(), payload).await?; + + assert!(listener + .message_processed(message_id.into()) + .await? + .succeed()); + + Ok(program_id) +}