From 7f84e9c730d942e6f9ac5ecf9548f274bfddead1 Mon Sep 17 00:00:00 2001 From: Georgy Shepelev Date: Thu, 18 Jul 2024 10:40:26 +0400 Subject: [PATCH 01/24] initial relayer struct --- Cargo.lock | 8 ++++++++ Cargo.toml | 1 + checkpoints-relayer/Cargo.toml | 8 ++++++++ checkpoints-relayer/src/main.rs | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 50 insertions(+) create mode 100644 checkpoints-relayer/Cargo.toml create mode 100644 checkpoints-relayer/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 6545ad86..2dfe9d75 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1963,6 +1963,14 @@ dependencies = [ "tree_hash_derive", ] +[[package]] +name = "checkpoints-relayer" +version = "0.1.0" +dependencies = [ + "clap", + "tokio", +] + [[package]] name = "chrono" version = "0.4.38" diff --git a/Cargo.toml b/Cargo.toml index e77df405..48825d12 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ members = [ "gear-programs/*", "gear-programs/checkpoint-light-client/io", "utils-prometheus", + "checkpoints-relayer", ] resolver = "2" diff --git a/checkpoints-relayer/Cargo.toml b/checkpoints-relayer/Cargo.toml new file mode 100644 index 00000000..a263a945 --- /dev/null +++ b/checkpoints-relayer/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "checkpoints-relayer" +version.workspace = true +edition.workspace = true + +[dependencies] +clap.workspace = true +tokio.workspace = true diff --git a/checkpoints-relayer/src/main.rs b/checkpoints-relayer/src/main.rs new file mode 100644 index 00000000..46a22a21 --- /dev/null +++ b/checkpoints-relayer/src/main.rs @@ -0,0 +1,33 @@ +use clap::Parser; +use tokio::sync::mpsc; + +const SIZE_CHANNEL: usize = 100_000; + +#[derive(Debug, Parser)] +struct Args { + /// Specify ProgramId of the Checkpoint-light-client program + #[arg(long)] + program_id: String, + + /// Specify an endpoint providing Beacon API + #[arg(long)] + beacon_endpoint: String, + + /// Address of the VARA RPC endpoint + #[arg( + long, + env = "VARA_RPC" + )] + vara_endpoint: String, +} + +#[tokio::main] +async fn main() { + let Args { + program_id, + beacon_endpoint, + vara_endpoint, + } = Args::parse(); + + let (sender, receiver) = mpsc::channel::<()>(SIZE_CHANNEL); +} From d45049fc39f878379af8ea4efe6b4841e73e2cf1 Mon Sep 17 00:00:00 2001 From: Georgy Shepelev Date: Thu, 18 Jul 2024 10:53:41 +0400 Subject: [PATCH 02/24] move tests to relayer --- Cargo.lock | 12 ++++++++++++ Cargo.toml | 1 + checkpoints-relayer/Cargo.toml | 14 ++++++++++++++ checkpoints-relayer/src/main.rs | 3 +++ .../src/tests/mod.rs | 5 +++-- .../tests/sepolia-finality-update-5_254_112.json | 0 .../tests/sepolia-finality-update-5_263_072.json | 0 gear-programs/checkpoint-light-client/src/lib.rs | 3 --- 8 files changed, 33 insertions(+), 5 deletions(-) rename {gear-programs/checkpoint-light-client => checkpoints-relayer}/src/tests/mod.rs (99%) rename {gear-programs/checkpoint-light-client => checkpoints-relayer}/src/tests/sepolia-finality-update-5_254_112.json (100%) rename {gear-programs/checkpoint-light-client => checkpoints-relayer}/src/tests/sepolia-finality-update-5_263_072.json (100%) diff --git a/Cargo.lock b/Cargo.lock index 2dfe9d75..96be1547 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1967,7 +1967,19 @@ dependencies = [ name = "checkpoints-relayer" version = "0.1.0" dependencies = [ + "anyhow", + "ark-bls12-381", + "ark-serialize 0.4.2", + "checkpoint_light_client", + "checkpoint_light_client-io", "clap", + "futures", + "gclient", + "hex", + "parity-scale-codec", + "reqwest 0.11.27", + "serde", + "serde_json", "tokio", ] diff --git a/Cargo.toml b/Cargo.toml index 48825d12..3159a93b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ bridging_payment = { path = "gear-programs/bridging-payment" } gear_proof_storage = { path = "gear-programs/proof-storage" } checkpoint_light_client-io = { path = "gear-programs/checkpoint-light-client/io", default-features = false } utils-prometheus = { path = "utils-prometheus" } +checkpoint_light_client = { path = "gear-programs/checkpoint-light-client", default-features = false } plonky2 = { git = "https://github.com/gear-tech/plonky2.git", rev = "4a620f4d79efe9233d0e7682df5a2fc625b5420e" } plonky2_field = { git = "https://github.com/gear-tech/plonky2.git", rev = "4a620f4d79efe9233d0e7682df5a2fc625b5420e" } diff --git a/checkpoints-relayer/Cargo.toml b/checkpoints-relayer/Cargo.toml index a263a945..9259e5ad 100644 --- a/checkpoints-relayer/Cargo.toml +++ b/checkpoints-relayer/Cargo.toml @@ -6,3 +6,17 @@ edition.workspace = true [dependencies] clap.workspace = true tokio.workspace = true +checkpoint_light_client-io = { workspace = true, features = ["std"] } +checkpoint_light_client = { workspace = true, features = ["std"] } +parity-scale-codec.workspace = true + +[dev-dependencies] +gclient.workspace = true +ark-bls12-381 = { workspace = true, features = ["std"] } +ark-serialize = { workspace = true, features = ["std"] } +serde = { workspace = true, features = ["std"] } +futures.workspace = true +hex = { workspace = true, features = ["std"] } +reqwest.workspace = true +serde_json.workspace = true +anyhow.workspace = true diff --git a/checkpoints-relayer/src/main.rs b/checkpoints-relayer/src/main.rs index 46a22a21..072ad2c6 100644 --- a/checkpoints-relayer/src/main.rs +++ b/checkpoints-relayer/src/main.rs @@ -1,6 +1,9 @@ use clap::Parser; use tokio::sync::mpsc; +#[cfg(test)] +mod tests; + const SIZE_CHANNEL: usize = 100_000; #[derive(Debug, Parser)] diff --git a/gear-programs/checkpoint-light-client/src/tests/mod.rs b/checkpoints-relayer/src/tests/mod.rs similarity index 99% rename from gear-programs/checkpoint-light-client/src/tests/mod.rs rename to checkpoints-relayer/src/tests/mod.rs index 747420f0..70a3dd1c 100644 --- a/gear-programs/checkpoint-light-client/src/tests/mod.rs +++ b/checkpoints-relayer/src/tests/mod.rs @@ -1,4 +1,4 @@ -use crate::WASM_BINARY; +use checkpoint_light_client::WASM_BINARY; use anyhow::Error as AnyError; use ark_bls12_381::{G1Projective as G1, G2Projective as G2}; use ark_serialize::CanonicalDeserialize; @@ -15,10 +15,11 @@ use checkpoint_light_client_io::{ SyncCommitteeUpdate, }; use gclient::{EventListener, EventProcessor, GearApi, Result}; -use gstd::prelude::*; use reqwest::{Client, RequestBuilder}; use serde::{de::DeserializeOwned, Deserialize}; use tokio::time::{self, Duration}; +use std::cmp; +use parity_scale_codec::{Decode, Encode}; // https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/p2p-interface.md#configuration pub const MAX_REQUEST_LIGHT_CLIENT_UPDATES: u8 = 128; diff --git a/gear-programs/checkpoint-light-client/src/tests/sepolia-finality-update-5_254_112.json b/checkpoints-relayer/src/tests/sepolia-finality-update-5_254_112.json similarity index 100% rename from gear-programs/checkpoint-light-client/src/tests/sepolia-finality-update-5_254_112.json rename to checkpoints-relayer/src/tests/sepolia-finality-update-5_254_112.json diff --git a/gear-programs/checkpoint-light-client/src/tests/sepolia-finality-update-5_263_072.json b/checkpoints-relayer/src/tests/sepolia-finality-update-5_263_072.json similarity index 100% rename from gear-programs/checkpoint-light-client/src/tests/sepolia-finality-update-5_263_072.json rename to checkpoints-relayer/src/tests/sepolia-finality-update-5_263_072.json diff --git a/gear-programs/checkpoint-light-client/src/lib.rs b/gear-programs/checkpoint-light-client/src/lib.rs index 4cbc6ba9..dc89fc13 100644 --- a/gear-programs/checkpoint-light-client/src/lib.rs +++ b/gear-programs/checkpoint-light-client/src/lib.rs @@ -15,6 +15,3 @@ use gstd::{Box, Vec}; #[cfg(not(feature = "std"))] mod wasm; - -#[cfg(test)] -mod tests; From dd51673512d58c6db7df0f2def1c9dcabe222f93 Mon Sep 17 00:00:00 2001 From: Georgy Shepelev Date: Thu, 18 Jul 2024 14:33:31 +0400 Subject: [PATCH 03/24] introduce utils module --- checkpoints-relayer/Cargo.toml | 10 +- checkpoints-relayer/src/main.rs | 2 + checkpoints-relayer/src/tests/mod.rs | 268 +++------------------------ checkpoints-relayer/src/utils.rs | 221 ++++++++++++++++++++++ 4 files changed, 256 insertions(+), 245 deletions(-) create mode 100644 checkpoints-relayer/src/utils.rs diff --git a/checkpoints-relayer/Cargo.toml b/checkpoints-relayer/Cargo.toml index 9259e5ad..7a7d5ad3 100644 --- a/checkpoints-relayer/Cargo.toml +++ b/checkpoints-relayer/Cargo.toml @@ -9,14 +9,14 @@ tokio.workspace = true checkpoint_light_client-io = { workspace = true, features = ["std"] } checkpoint_light_client = { workspace = true, features = ["std"] } parity-scale-codec.workspace = true +serde_json.workspace = true +serde = { workspace = true, features = ["std"] } +reqwest.workspace = true +anyhow.workspace = true +ark-serialize = { workspace = true, features = ["std"] } [dev-dependencies] gclient.workspace = true ark-bls12-381 = { workspace = true, features = ["std"] } -ark-serialize = { workspace = true, features = ["std"] } -serde = { workspace = true, features = ["std"] } futures.workspace = true hex = { workspace = true, features = ["std"] } -reqwest.workspace = true -serde_json.workspace = true -anyhow.workspace = true diff --git a/checkpoints-relayer/src/main.rs b/checkpoints-relayer/src/main.rs index 072ad2c6..9b40bf14 100644 --- a/checkpoints-relayer/src/main.rs +++ b/checkpoints-relayer/src/main.rs @@ -4,6 +4,8 @@ use tokio::sync::mpsc; #[cfg(test)] mod tests; +mod utils; + const SIZE_CHANNEL: usize = 100_000; #[derive(Debug, Parser)] diff --git a/checkpoints-relayer/src/tests/mod.rs b/checkpoints-relayer/src/tests/mod.rs index 70a3dd1c..d9674d75 100644 --- a/checkpoints-relayer/src/tests/mod.rs +++ b/checkpoints-relayer/src/tests/mod.rs @@ -1,28 +1,20 @@ use checkpoint_light_client::WASM_BINARY; -use anyhow::Error as AnyError; -use ark_bls12_381::{G1Projective as G1, G2Projective as G2}; -use ark_serialize::CanonicalDeserialize; use checkpoint_light_client_io::{ ethereum_common::{ base_types::{BytesFixed, FixedArray}, - beacon::{BLSPubKey, Bytes32, SignedBeaconBlockHeader, SyncAggregate, SyncCommittee}, network::Network, utils as eth_utils, SLOTS_PER_EPOCH, }, replay_back, sync_update, tree_hash::TreeHash, - ArkScale, BeaconBlockHeader, G1TypeInfo, G2TypeInfo, Handle, HandleResult, Init, - SyncCommitteeUpdate, + Handle, HandleResult, Init, G2, }; use gclient::{EventListener, EventProcessor, GearApi, Result}; -use reqwest::{Client, RequestBuilder}; -use serde::{de::DeserializeOwned, Deserialize}; +use reqwest::Client; use tokio::time::{self, Duration}; -use std::cmp; use parity_scale_codec::{Decode, Encode}; +use crate::utils::{self, FinalityUpdateResponse}; -// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/p2p-interface.md#configuration -pub const MAX_REQUEST_LIGHT_CLIENT_UPDATES: u8 = 128; const RPC_URL: &str = "http://127.0.0.1:5052"; const FINALITY_UPDATE_5_254_112: &[u8; 4_940] = @@ -30,210 +22,6 @@ const FINALITY_UPDATE_5_254_112: &[u8; 4_940] = const FINALITY_UPDATE_5_263_072: &[u8; 4_941] = include_bytes!("./sepolia-finality-update-5_263_072.json"); -#[derive(Deserialize)] -#[serde(untagged)] -enum LightClientHeader { - Unwrapped(BeaconBlockHeader), - Wrapped(Beacon), -} - -#[derive(Deserialize)] -struct Beacon { - beacon: BeaconBlockHeader, -} - -#[derive(Deserialize, Debug)] -struct BeaconBlockHeaderResponse { - data: BeaconBlockHeaderData, -} - -#[derive(Deserialize, Debug)] -struct BeaconBlockHeaderData { - header: SignedBeaconBlockHeader, -} - -pub fn header_deserialize<'de, D>(deserializer: D) -> Result -where - D: serde::Deserializer<'de>, -{ - let header: LightClientHeader = Deserialize::deserialize(deserializer)?; - - Ok(match header { - LightClientHeader::Unwrapped(header) => header, - LightClientHeader::Wrapped(header) => header.beacon, - }) -} - -#[derive(Deserialize, Debug)] -pub struct Bootstrap { - #[serde(deserialize_with = "header_deserialize")] - pub header: BeaconBlockHeader, - pub current_sync_committee: SyncCommittee, - pub current_sync_committee_branch: Vec, -} - -#[derive(Deserialize, Debug)] -struct BootstrapResponse { - data: Bootstrap, -} - -#[derive(Deserialize)] -struct FinalityUpdateResponse { - data: FinalityUpdate, -} - -#[derive(Deserialize)] -pub struct FinalityUpdate { - #[serde(deserialize_with = "header_deserialize")] - pub attested_header: BeaconBlockHeader, - #[serde(deserialize_with = "header_deserialize")] - pub finalized_header: BeaconBlockHeader, - pub finality_branch: Vec, - pub sync_aggregate: SyncAggregate, - #[serde(deserialize_with = "eth_utils::deserialize_u64")] - pub signature_slot: u64, -} - -#[derive(Debug, Clone, Deserialize)] -pub struct Update { - #[serde(deserialize_with = "header_deserialize")] - pub attested_header: BeaconBlockHeader, - pub next_sync_committee: SyncCommittee, - pub next_sync_committee_branch: Vec, - #[serde(deserialize_with = "header_deserialize")] - pub finalized_header: BeaconBlockHeader, - pub finality_branch: Vec, - pub sync_aggregate: SyncAggregate, - #[serde(deserialize_with = "eth_utils::deserialize_u64")] - pub signature_slot: u64, -} - -#[derive(Debug, Clone, Deserialize)] -struct UpdateData { - data: Update, -} - -type UpdateResponse = Vec; - -async fn get(request_builder: RequestBuilder) -> Result { - let bytes = request_builder - .send() - .await - .map_err(AnyError::from)? - .bytes() - .await - .map_err(AnyError::from)?; - - Ok(serde_json::from_slice::(&bytes).map_err(AnyError::from)?) -} - -async fn get_bootstrap(client: &mut Client, checkpoint: &str) -> Result { - let checkpoint_no_prefix = match checkpoint.starts_with("0x") { - true => &checkpoint[2..], - false => checkpoint, - }; - - let url = format!("{RPC_URL}/eth/v1/beacon/light_client/bootstrap/0x{checkpoint_no_prefix}",); - - get::(client.get(&url)) - .await - .map(|response| response.data) -} - -async fn get_finality_update(client: &mut Client) -> Result { - let url = format!("{RPC_URL}/eth/v1/beacon/light_client/finality_update"); - - get::(client.get(&url)) - .await - .map(|response| response.data) -} - -async fn get_updates(client: &mut Client, period: u64, count: u8) -> Result { - let count = cmp::min(count, MAX_REQUEST_LIGHT_CLIENT_UPDATES); - let url = format!( - "{RPC_URL}/eth/v1/beacon/light_client/updates?start_period={period}&count={count}", - ); - - get::(client.get(&url)).await -} - -async fn get_block_header(client: &Client, slot: u64) -> Result { - let url = format!("{RPC_URL}/eth/v1/beacon/headers/{slot}"); - - get::(client.get(&url)) - .await - .map(|response| response.data.header.message) -} - -fn map_public_keys(compressed_public_keys: &[BLSPubKey]) -> Vec> { - compressed_public_keys - .iter() - .map(|BytesFixed(pub_key_compressed)| { - let pub_key = ::deserialize_compressed_unchecked( - &pub_key_compressed.0[..], - ) - .unwrap(); - let ark_scale: ArkScale = G1TypeInfo(pub_key).into(); - - ark_scale - }) - .collect() -} - -fn sync_update_from_finality( - signature: G2, - finality_update: FinalityUpdate, -) -> SyncCommitteeUpdate { - SyncCommitteeUpdate { - signature_slot: finality_update.signature_slot, - attested_header: finality_update.attested_header, - finalized_header: finality_update.finalized_header, - sync_aggregate: finality_update.sync_aggregate, - sync_committee_next_aggregate_pubkey: None, - sync_committee_signature: G2TypeInfo(signature).into(), - sync_committee_next_pub_keys: None, - sync_committee_next_branch: None, - finality_branch: finality_update - .finality_branch - .into_iter() - .map(|BytesFixed(array)| array.0) - .collect::<_>(), - } -} - -fn sync_update_from_update(update: Update) -> SyncCommitteeUpdate { - let signature = ::deserialize_compressed( - &update.sync_aggregate.sync_committee_signature.0 .0[..], - ) - .unwrap(); - - let next_sync_committee_keys = map_public_keys(&update.next_sync_committee.pubkeys.0); - - SyncCommitteeUpdate { - signature_slot: update.signature_slot, - attested_header: update.attested_header, - finalized_header: update.finalized_header, - sync_aggregate: update.sync_aggregate, - sync_committee_next_aggregate_pubkey: Some(update.next_sync_committee.aggregate_pubkey), - sync_committee_signature: G2TypeInfo(signature).into(), - sync_committee_next_pub_keys: Some(Box::new(FixedArray( - next_sync_committee_keys.try_into().unwrap(), - ))), - sync_committee_next_branch: Some( - update - .next_sync_committee_branch - .into_iter() - .map(|BytesFixed(array)| array.0) - .collect::<_>(), - ), - finality_branch: update - .finality_branch - .into_iter() - .map(|BytesFixed(array)| array.0) - .collect::<_>(), - } -} - async fn common_upload_program( client: &GearApi, code: Vec, @@ -276,12 +64,12 @@ async fn upload_program( #[tokio::test] async fn init_and_updating() -> Result<()> { - let mut client_http = Client::new(); + let client_http = Client::new(); // use the latest finality header as a checkpoint for bootstrapping - let finality_update = get_finality_update(&mut client_http).await?; + let finality_update = utils::get_finality_update(&client_http, RPC_URL).await?; let current_period = eth_utils::calculate_period(finality_update.finalized_header.slot); - let mut updates = get_updates(&mut client_http, current_period, 1).await?; + let mut updates = utils::get_updates(&client_http, RPC_URL, current_period, 1).await?; let update = match updates.pop() { Some(update) if updates.is_empty() => update.data, @@ -291,10 +79,10 @@ async fn init_and_updating() -> Result<()> { let checkpoint = update.finalized_header.tree_hash_root(); let checkpoint_hex = hex::encode(checkpoint); - let bootstrap = get_bootstrap(&mut client_http, &checkpoint_hex).await?; - let sync_update = sync_update_from_update(update); + let bootstrap = utils::get_bootstrap(&client_http, RPC_URL, &checkpoint_hex).await?; + let sync_update = utils::sync_update_from_update(update); - let pub_keys = map_public_keys(&bootstrap.current_sync_committee.pubkeys.0); + let pub_keys = utils::map_public_keys(&bootstrap.current_sync_committee.pubkeys.0); let init = Init { network: Network::Sepolia, sync_committee_current_pub_keys: Box::new(FixedArray(pub_keys.try_into().unwrap())), @@ -319,15 +107,15 @@ async fn init_and_updating() -> Result<()> { println!(); for _ in 0..30 { - let update = get_finality_update(&mut client_http).await?; + let update = utils::get_finality_update(&client_http, RPC_URL).await?; let slot: u64 = update.finalized_header.slot; let current_period = eth_utils::calculate_period(slot); - let mut updates = get_updates(&mut client_http, current_period, 1).await?; + let mut updates = utils::get_updates(&client_http, RPC_URL, current_period, 1).await?; match updates.pop() { Some(update) if updates.is_empty() && update.data.finalized_header.slot >= slot => { println!("update sync committee"); - let payload = Handle::SyncUpdate(sync_update_from_update(update.data)); + let payload = Handle::SyncUpdate(utils::sync_update_from_update(update.data)); let gas_limit = client .calculate_handle_gas(None, program_id.into(), payload.encode(), 0, true) .await? @@ -359,7 +147,7 @@ async fn init_and_updating() -> Result<()> { continue; }; - let payload = Handle::SyncUpdate(sync_update_from_finality(signature, update)); + let payload = Handle::SyncUpdate(utils::sync_update_from_finality(signature, update)); let gas_limit = client .calculate_handle_gas(None, program_id.into(), payload.encode(), 0, true) @@ -390,7 +178,7 @@ async fn init_and_updating() -> Result<()> { #[tokio::test] async fn replaying_back() -> Result<()> { - let mut client_http = Client::new(); + let client_http = Client::new(); let finality_update: FinalityUpdateResponse = serde_json::from_slice(FINALITY_UPDATE_5_254_112).unwrap(); @@ -402,7 +190,7 @@ async fn replaying_back() -> Result<()> { // 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 = get_updates(&mut client_http, current_period - 1, 1).await?; + let mut updates = utils::get_updates(&client_http, RPC_URL, current_period - 1, 1).await?; let update = match updates.pop() { Some(update) if updates.is_empty() => update.data, @@ -411,11 +199,11 @@ async fn replaying_back() -> Result<()> { let checkpoint = update.finalized_header.tree_hash_root(); let checkpoint_hex = hex::encode(checkpoint); - let bootstrap = get_bootstrap(&mut client_http, &checkpoint_hex).await?; + let bootstrap = utils::get_bootstrap(&client_http, RPC_URL, &checkpoint_hex).await?; println!("bootstrap slot = {}", bootstrap.header.slot); println!("update slot = {}", update.finalized_header.slot); - let sync_update = sync_update_from_update(update); + let sync_update = utils::sync_update_from_update(update); let slot_start = sync_update.finalized_header.slot; let slot_end = finality_update.finalized_header.slot; println!( @@ -423,7 +211,7 @@ async fn replaying_back() -> Result<()> { slot_end - slot_start ); - let pub_keys = map_public_keys(&bootstrap.current_sync_committee.pubkeys.0); + let pub_keys = utils::map_public_keys(&bootstrap.current_sync_committee.pubkeys.0); let init = Init { network: Network::Sepolia, sync_committee_current_pub_keys: Box::new(FixedArray(pub_keys.try_into().unwrap())), @@ -451,7 +239,7 @@ async fn replaying_back() -> Result<()> { let count_headers = 26 * SLOTS_PER_EPOCH; let mut requests_headers = Vec::with_capacity(count_headers as usize); for i in 1..count_headers { - requests_headers.push(get_block_header(&client_http, slot_end - i)); + requests_headers.push(utils::get_block_header(&client_http, RPC_URL, slot_end - i)); } let headers = futures::future::join_all(requests_headers) @@ -466,7 +254,7 @@ async fn replaying_back() -> Result<()> { .unwrap(); let payload = Handle::ReplayBackStart { - sync_update: sync_update_from_finality(signature, finality_update), + sync_update: utils::sync_update_from_finality(signature, finality_update), headers, }; @@ -495,7 +283,7 @@ async fn replaying_back() -> Result<()> { for _batch in 0..count_batch { let mut requests_headers = Vec::with_capacity(count_headers as usize); for i in 0..count_headers { - requests_headers.push(get_block_header(&client_http, slot_end - i)); + requests_headers.push(utils::get_block_header(&client_http, RPC_URL, slot_end - i)); } let headers = futures::future::join_all(requests_headers) @@ -529,7 +317,7 @@ async fn replaying_back() -> Result<()> { // remaining headers let mut requests_headers = Vec::with_capacity(count_headers as usize); for i in slot_start..=slot_end { - requests_headers.push(get_block_header(&client_http, i)); + requests_headers.push(utils::get_block_header(&client_http, RPC_URL, i)); } let headers = futures::future::join_all(requests_headers) @@ -562,7 +350,7 @@ async fn replaying_back() -> Result<()> { #[tokio::test] async fn sync_update_requires_replaying_back() -> Result<()> { - let mut client_http = Client::new(); + let client_http = Client::new(); let finality_update: FinalityUpdateResponse = serde_json::from_slice(FINALITY_UPDATE_5_263_072).unwrap(); @@ -574,7 +362,7 @@ async fn sync_update_requires_replaying_back() -> Result<()> { let slot = finality_update.finalized_header.slot; let current_period = eth_utils::calculate_period(slot); - let mut updates = get_updates(&mut client_http, current_period, 1).await?; + let mut updates = utils::get_updates(&client_http, RPC_URL, current_period, 1).await?; let update = match updates.pop() { Some(update) if updates.is_empty() => update.data, @@ -584,10 +372,10 @@ async fn sync_update_requires_replaying_back() -> Result<()> { let checkpoint = update.finalized_header.tree_hash_root(); let checkpoint_hex = hex::encode(checkpoint); - let bootstrap = get_bootstrap(&mut client_http, &checkpoint_hex).await?; - let sync_update = sync_update_from_update(update); + let bootstrap = utils::get_bootstrap(&client_http, RPC_URL, &checkpoint_hex).await?; + let sync_update = utils::sync_update_from_update(update); - let pub_keys = map_public_keys(&bootstrap.current_sync_committee.pubkeys.0); + let pub_keys = utils::map_public_keys(&bootstrap.current_sync_committee.pubkeys.0); let init = Init { network: Network::Sepolia, sync_committee_current_pub_keys: Box::new(FixedArray(pub_keys.try_into().unwrap())), @@ -619,7 +407,7 @@ async fn sync_update_requires_replaying_back() -> Result<()> { ) .unwrap(); - let payload = Handle::SyncUpdate(sync_update_from_finality(signature, finality_update)); + let payload = Handle::SyncUpdate(utils::sync_update_from_finality(signature, finality_update)); let gas_limit = client .calculate_handle_gas(None, program_id.into(), payload.encode(), 0, true) diff --git a/checkpoints-relayer/src/utils.rs b/checkpoints-relayer/src/utils.rs new file mode 100644 index 00000000..dfcf1c3a --- /dev/null +++ b/checkpoints-relayer/src/utils.rs @@ -0,0 +1,221 @@ +use serde::{de::DeserializeOwned, Deserialize}; +use checkpoint_light_client_io::{ + ethereum_common::{ + base_types::{FixedArray, BytesFixed}, + beacon::{BLSPubKey, Bytes32, SignedBeaconBlockHeader, SyncAggregate, SyncCommittee}, + utils as eth_utils, + }, + BeaconBlockHeader, G2TypeInfo, G2, G1TypeInfo, ArkScale, G1, + SyncCommitteeUpdate, +}; +use anyhow::{Result as AnyResult, Error as AnyError}; +use reqwest::{Client, RequestBuilder}; +use ark_serialize::CanonicalDeserialize; +use std::cmp; + +// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/p2p-interface.md#configuration +pub const MAX_REQUEST_LIGHT_CLIENT_UPDATES: u8 = 128; + +#[derive(Deserialize)] +#[serde(untagged)] +pub enum LightClientHeader { + Unwrapped(BeaconBlockHeader), + Wrapped(Beacon), +} + +#[derive(Deserialize)] +pub struct Beacon { + pub beacon: BeaconBlockHeader, +} + +#[derive(Deserialize, Debug)] +pub struct BeaconBlockHeaderResponse { + pub data: BeaconBlockHeaderData, +} + +#[derive(Deserialize, Debug)] +pub struct BeaconBlockHeaderData { + pub header: SignedBeaconBlockHeader, +} + +#[derive(Deserialize, Debug)] +pub struct Bootstrap { + #[serde(deserialize_with = "deserialize_header")] + pub header: BeaconBlockHeader, + pub current_sync_committee: SyncCommittee, + pub current_sync_committee_branch: Vec, +} + +#[derive(Deserialize, Debug)] +pub struct BootstrapResponse { + pub data: Bootstrap, +} + +pub fn deserialize_header<'de, D>(deserializer: D) -> Result +where + D: serde::Deserializer<'de>, +{ + let header: LightClientHeader = Deserialize::deserialize(deserializer)?; + + Ok(match header { + LightClientHeader::Unwrapped(header) => header, + LightClientHeader::Wrapped(header) => header.beacon, + }) +} + +#[derive(Deserialize)] +pub struct FinalityUpdateResponse { + pub data: FinalityUpdate, +} + +#[derive(Deserialize)] +pub struct FinalityUpdate { + #[serde(deserialize_with = "deserialize_header")] + pub attested_header: BeaconBlockHeader, + #[serde(deserialize_with = "deserialize_header")] + pub finalized_header: BeaconBlockHeader, + pub finality_branch: Vec, + pub sync_aggregate: SyncAggregate, + #[serde(deserialize_with = "eth_utils::deserialize_u64")] + pub signature_slot: u64, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct Update { + #[serde(deserialize_with = "deserialize_header")] + pub attested_header: BeaconBlockHeader, + pub next_sync_committee: SyncCommittee, + pub next_sync_committee_branch: Vec, + #[serde(deserialize_with = "deserialize_header")] + pub finalized_header: BeaconBlockHeader, + pub finality_branch: Vec, + pub sync_aggregate: SyncAggregate, + #[serde(deserialize_with = "eth_utils::deserialize_u64")] + pub signature_slot: u64, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct UpdateData { + pub data: Update, +} + +pub type UpdateResponse = Vec; + +pub async fn get(request_builder: RequestBuilder) -> AnyResult { + let bytes = request_builder + .send() + .await + .map_err(AnyError::from)? + .bytes() + .await + .map_err(AnyError::from)?; + + Ok(serde_json::from_slice::(&bytes).map_err(AnyError::from)?) +} + +pub async fn get_bootstrap(client: &Client, rpc_url: &str, checkpoint: &str) -> AnyResult { + let checkpoint_no_prefix = match checkpoint.starts_with("0x") { + true => &checkpoint[2..], + false => checkpoint, + }; + + let url = format!("{rpc_url}/eth/v1/beacon/light_client/bootstrap/0x{checkpoint_no_prefix}",); + + get::(client.get(&url)) + .await + .map(|response| response.data) +} + +pub async fn get_updates(client: &Client, rpc_url: &str, period: u64, count: u8) -> AnyResult { + let count = cmp::min(count, MAX_REQUEST_LIGHT_CLIENT_UPDATES); + let url = format!( + "{rpc_url}/eth/v1/beacon/light_client/updates?start_period={period}&count={count}", + ); + + get::(client.get(&url)).await +} + +pub async fn get_block_header(client: &Client, rpc_url: &str, slot: u64) -> AnyResult { + let url = format!("{rpc_url}/eth/v1/beacon/headers/{slot}"); + + get::(client.get(&url)) + .await + .map(|response| response.data.header.message) +} + +pub async fn get_finality_update(client: &Client, rpc_url: &str) -> AnyResult { + let url = format!("{rpc_url}/eth/v1/beacon/light_client/finality_update"); + + get::(client.get(&url)) + .await + .map(|response| response.data) +} + +pub fn map_public_keys(compressed_public_keys: &[BLSPubKey]) -> Vec> { + compressed_public_keys + .iter() + .map(|BytesFixed(pub_key_compressed)| { + let pub_key = ::deserialize_compressed_unchecked( + &pub_key_compressed.0[..], + ) + .unwrap(); + let ark_scale: ArkScale = G1TypeInfo(pub_key).into(); + + ark_scale + }) + .collect() +} + +pub fn sync_update_from_finality( + signature: G2, + finality_update: FinalityUpdate, +) -> SyncCommitteeUpdate { + SyncCommitteeUpdate { + signature_slot: finality_update.signature_slot, + attested_header: finality_update.attested_header, + finalized_header: finality_update.finalized_header, + sync_aggregate: finality_update.sync_aggregate, + sync_committee_next_aggregate_pubkey: None, + sync_committee_signature: G2TypeInfo(signature).into(), + sync_committee_next_pub_keys: None, + sync_committee_next_branch: None, + finality_branch: finality_update + .finality_branch + .into_iter() + .map(|BytesFixed(array)| array.0) + .collect::<_>(), + } +} + +pub fn sync_update_from_update(update: Update) -> SyncCommitteeUpdate { + let signature = ::deserialize_compressed( + &update.sync_aggregate.sync_committee_signature.0 .0[..], + ) + .unwrap(); + + let next_sync_committee_keys = map_public_keys(&update.next_sync_committee.pubkeys.0); + + SyncCommitteeUpdate { + signature_slot: update.signature_slot, + attested_header: update.attested_header, + finalized_header: update.finalized_header, + sync_aggregate: update.sync_aggregate, + sync_committee_next_aggregate_pubkey: Some(update.next_sync_committee.aggregate_pubkey), + sync_committee_signature: G2TypeInfo(signature).into(), + sync_committee_next_pub_keys: Some(Box::new(FixedArray( + next_sync_committee_keys.try_into().unwrap(), + ))), + sync_committee_next_branch: Some( + update + .next_sync_committee_branch + .into_iter() + .map(|BytesFixed(array)| array.0) + .collect::<_>(), + ), + finality_branch: update + .finality_branch + .into_iter() + .map(|BytesFixed(array)| array.0) + .collect::<_>(), + } +} From 09fbb9805beb3b67de997ca27731a7d2ce891d6c Mon Sep 17 00:00:00 2001 From: Georgy Shepelev Date: Mon, 22 Jul 2024 10:22:10 +0400 Subject: [PATCH 04/24] initial logic --- Cargo.lock | 3 +- checkpoints-relayer/Cargo.toml | 5 +- checkpoints-relayer/src/main.rs | 207 +++++++++++++++++++++++++++++++- 3 files changed, 209 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 96be1547..2ceeba57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1968,7 +1968,6 @@ name = "checkpoints-relayer" version = "0.1.0" dependencies = [ "anyhow", - "ark-bls12-381", "ark-serialize 0.4.2", "checkpoint_light_client", "checkpoint_light_client-io", @@ -1976,7 +1975,9 @@ dependencies = [ "futures", "gclient", "hex", + "log", "parity-scale-codec", + "pretty_env_logger", "reqwest 0.11.27", "serde", "serde_json", diff --git a/checkpoints-relayer/Cargo.toml b/checkpoints-relayer/Cargo.toml index 7a7d5ad3..b4be328b 100644 --- a/checkpoints-relayer/Cargo.toml +++ b/checkpoints-relayer/Cargo.toml @@ -14,9 +14,8 @@ serde = { workspace = true, features = ["std"] } reqwest.workspace = true anyhow.workspace = true ark-serialize = { workspace = true, features = ["std"] } - -[dev-dependencies] +log.workspace = true +pretty_env_logger.workspace = true gclient.workspace = true -ark-bls12-381 = { workspace = true, features = ["std"] } futures.workspace = true hex = { workspace = true, features = ["std"] } diff --git a/checkpoints-relayer/src/main.rs b/checkpoints-relayer/src/main.rs index 9b40bf14..adb73e7d 100644 --- a/checkpoints-relayer/src/main.rs +++ b/checkpoints-relayer/src/main.rs @@ -1,5 +1,14 @@ use clap::Parser; -use tokio::sync::mpsc; +use tokio::{ + sync::mpsc::{self, Sender}, + time::{self, Duration}, +}; +use reqwest::Client; +use utils::FinalityUpdate; +use pretty_env_logger::env_logger::fmt::TimestampPrecision; +use gclient::{EventListener, EventProcessor, GearApi}; +use checkpoint_light_client_io::{ethereum_common::{base_types::BytesFixed, utils as eth_utils, EPOCHS_PER_SYNC_COMMITTEE, SLOTS_PER_EPOCH}, meta::State, sync_update, tree_hash::Hash256, G2TypeInfo, Handle, HandleResult, Slot, G2}; +use parity_scale_codec::Decode; #[cfg(test)] mod tests; @@ -7,6 +16,8 @@ mod tests; mod utils; const SIZE_CHANNEL: usize = 100_000; +const COUNT_FAILURE: usize = 3; +const DELAY_SECS_FINALITY_REQUEST: u64 = 30; #[derive(Debug, Parser)] struct Args { @@ -26,13 +37,205 @@ struct Args { vara_endpoint: String, } +enum Status { + Ok, + NotActual, + Error, + ReplayBackRequired { + replayed_slot: Option, + checkpoint: (Slot, Hash256), + }, +} + #[tokio::main] async fn main() { + pretty_env_logger::formatted_builder() + .filter_level(log::LevelFilter::Info) + .format_target(false) + .format_timestamp(Some(TimestampPrecision::Micros)) + .init(); + let Args { program_id, beacon_endpoint, vara_endpoint, } = Args::parse(); - let (sender, receiver) = mpsc::channel::<()>(SIZE_CHANNEL); + let program_id_no_prefix = match program_id.starts_with("0x") { + true => &program_id[2..], + false => &program_id, + }; + + let Some(program_id) = hex::decode(program_id_no_prefix) + .ok() + .and_then(|bytes| <[u8; 32]>::try_from(bytes).ok()) else { + log::error!("Incorrect ProgramId"); + return; + }; + + let (sender, mut receiver) = mpsc::channel(SIZE_CHANNEL); + let client_http = Client::new(); + + spawn_finality_update_receiver(client_http.clone(), beacon_endpoint.clone(), sender, Duration::from_secs(DELAY_SECS_FINALITY_REQUEST)); + + let client = match GearApi::dev().await { + Ok(client) => client, + Err(e) => { + log::error!("Unable to create GearApi client: {e:?}"); + + return; + } + }; + + let mut listener = match client.subscribe().await { + Ok(listener) => listener, + Err(e) => { + log::error!("Unable to create events listener: {e:?}"); + + return; + } + }; + + let finality_update = match receiver.recv().await { + Some(finality_update) => finality_update, + None => { + log::info!("Receiver of FinalityUpdates has been closed. Exiting."); + + return; + } + }; + + let mut slot_last = finality_update.finalized_header.slot; + + match try_to_apply_sync_update(&client, &mut listener, program_id, finality_update).await { + Status::Ok | Status::NotActual => (), + Status::Error => return, + Status::ReplayBackRequired { replayed_slot, checkpoint } => replay_back(&client, &mut listener, program_id, replayed_slot, checkpoint, slot_last).await, + } + + while let Some(finality_update) = receiver.recv().await { + let slot = finality_update.finalized_header.slot; + if slot == slot_last { + continue; + } + + match try_to_apply_sync_update(&client, &mut listener, program_id, finality_update).await { + Status::Ok => { slot_last = slot; } + Status::NotActual => (), + _ => return, + } + } +} + +fn spawn_finality_update_receiver( + client_http: Client, + beacon_endpoint: String, + sender: Sender, + delay: Duration, +) { + tokio::spawn(async move { + let mut failures = 0; + + loop { + match utils::get_finality_update(&client_http, &beacon_endpoint).await { + Ok(value) => { + if sender.send(value).await.is_err() { + return; + } + } + + Err(e) => { + log::error!("Unable to fetch FinalityUpdate: {e:?}"); + + failures += 1; + if failures >= COUNT_FAILURE { + return; + } + + continue; + } + }; + + time::sleep(delay).await; + } + }); + } + + async fn try_to_apply_sync_update( + client: &GearApi, + listener: &mut EventListener, + program_id: [u8; 32], + finality_update: FinalityUpdate, +) -> Status { + let signature = ::deserialize_compressed( + &finality_update.sync_aggregate.sync_committee_signature.0 .0[..], + ); + + let Ok(signature) = signature else { + log::error!("Failed to deserialize point on G2"); + return Status::Error; + }; + + let payload = Handle::SyncUpdate(utils::sync_update_from_finality(signature, finality_update)); + + let (message_id, _) = match client + .send_message(program_id.into(), payload, 700_000_000_000, 0) + .await + { + Ok(result) => result, + Err(e) => { + log::error!("Failed to send message: {e:?}"); + + return Status::Error; + } + }; + + let (_message_id, payload, _value) = match listener + .reply_bytes_on(message_id) + .await + { + Ok(result) => result, + Err(e) => { + log::error!("Failed to get reply: {e:?}"); + + return Status::Error; + } + }; + let result_decoded = HandleResult::decode(&mut &payload.unwrap()[..]).unwrap(); + log::debug!("Handle result = {result_decoded:?}"); + match result_decoded { + HandleResult::SyncUpdate(Ok(())) => Status::Ok, + HandleResult::SyncUpdate(Err(sync_update::Error::NotActual)) => Status::NotActual, + HandleResult::SyncUpdate(Err(sync_update::Error::ReplayBackRequired { + replayed_slot, + checkpoint + })) => Status::ReplayBackRequired { replayed_slot, checkpoint }, + _ => Status::Error, + } + } + + async fn replay_back( + client: &GearApi, + listener: &mut EventListener, + program_id: [u8; 32], + replayed_slot: Option, + checkpoint: (Slot, Hash256), + slot_last: Slot, +) { + // let (slot_stored, _) = checkpoint; + // let slot_start = match replayed_slot { + // Some(slot_end) => replay_back_slots(slot_stored, slot_end), + // None => slot_stored, + // }; + + // let period_start = 1 + eth_utils::calculate_period(slot_start); + // let period_end = eth_utils::calculate_period(slot_last); + + // for period in period_start..period_end { + + // } + + // replay_back_slots(period_new * EPOCHS_PER_SYNC_COMMITTEE * SLOTS_PER_EPOCH, slot_last); + + todo!() } From 783d5fa044e931082b30a44b3062518ace4d6d39 Mon Sep 17 00:00:00 2001 From: Georgy Shepelev Date: Mon, 22 Jul 2024 11:42:15 +0400 Subject: [PATCH 05/24] introduce slots_batch::Iterator --- checkpoints-relayer/src/tests/mod.rs | 128 +++++++----------- .../src/{utils.rs => utils/mod.rs} | 2 + checkpoints-relayer/src/utils/slots_batch.rs | 68 ++++++++++ 3 files changed, 117 insertions(+), 81 deletions(-) rename checkpoints-relayer/src/{utils.rs => utils/mod.rs} (99%) create mode 100644 checkpoints-relayer/src/utils/slots_batch.rs diff --git a/checkpoints-relayer/src/tests/mod.rs b/checkpoints-relayer/src/tests/mod.rs index d9674d75..17eda45f 100644 --- a/checkpoints-relayer/src/tests/mod.rs +++ b/checkpoints-relayer/src/tests/mod.rs @@ -13,7 +13,7 @@ use gclient::{EventListener, EventProcessor, GearApi, Result}; use reqwest::Client; use tokio::time::{self, Duration}; use parity_scale_codec::{Decode, Encode}; -use crate::utils::{self, FinalityUpdateResponse}; +use crate::utils::{self, FinalityUpdateResponse, slots_batch}; const RPC_URL: &str = "http://127.0.0.1:5052"; @@ -235,55 +235,54 @@ async fn replaying_back() -> Result<()> { println!(); println!(); + let batch_size = 26 * SLOTS_PER_EPOCH; + let mut slots_batch_iter = slots_batch::Iter::new(slot_start, slot_end, batch_size).unwrap(); // start to replay back - let count_headers = 26 * SLOTS_PER_EPOCH; - let mut requests_headers = Vec::with_capacity(count_headers as usize); - for i in 1..count_headers { - requests_headers.push(utils::get_block_header(&client_http, RPC_URL, slot_end - 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: utils::sync_update_from_finality(signature, finality_update), - headers, - }; - - let gas_limit = client - .calculate_handle_gas(None, program_id.into(), payload.encode(), 0, true) - .await? - .min_limit; - println!("ReplayBackStart 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::ReplayBackStart(Ok(replay_back::StatusStart::InProgress)) - )); + 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(utils::get_block_header(&client_http, RPC_URL, i)); + } - // continue to replay back - let mut slot_end = slot_end - count_headers; - let count_headers = 29 * SLOTS_PER_EPOCH; - let count_batch = (slot_end - slot_start) / count_headers; + 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: utils::sync_update_from_finality(signature, finality_update), + headers, + }; + + let gas_limit = client + .calculate_handle_gas(None, program_id.into(), payload.encode(), 0, true) + .await? + .min_limit; + println!("ReplayBackStart 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::ReplayBackStart(Ok(replay_back::StatusStart::InProgress)) + )); + } - for _batch in 0..count_batch { - let mut requests_headers = Vec::with_capacity(count_headers as usize); - for i in 0..count_headers { - requests_headers.push(utils::get_block_header(&client_http, RPC_URL, slot_end - i)); + // 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(utils::get_block_header(&client_http, RPC_URL, i)); } let headers = futures::future::join_all(requests_headers) @@ -308,43 +307,10 @@ async fn replaying_back() -> Result<()> { let result_decoded = HandleResult::decode(&mut &payload.unwrap()[..]).unwrap(); assert!(matches!( result_decoded, - HandleResult::ReplayBack(Some(replay_back::Status::InProcess)) + HandleResult::ReplayBack(Some(replay_back::Status::InProcess | replay_back::Status::Finished)) )); - - slot_end -= count_headers; - } - - // remaining headers - let mut requests_headers = Vec::with_capacity(count_headers as usize); - for i in slot_start..=slot_end { - requests_headers.push(utils::get_block_header(&client_http, RPC_URL, 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 (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::ReplayBack(Some(replay_back::Status::Finished)) - )); - Ok(()) } diff --git a/checkpoints-relayer/src/utils.rs b/checkpoints-relayer/src/utils/mod.rs similarity index 99% rename from checkpoints-relayer/src/utils.rs rename to checkpoints-relayer/src/utils/mod.rs index dfcf1c3a..08020d99 100644 --- a/checkpoints-relayer/src/utils.rs +++ b/checkpoints-relayer/src/utils/mod.rs @@ -13,6 +13,8 @@ use reqwest::{Client, RequestBuilder}; use ark_serialize::CanonicalDeserialize; use std::cmp; +pub mod slots_batch; + // https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/p2p-interface.md#configuration pub const MAX_REQUEST_LIGHT_CLIENT_UPDATES: u8 = 128; diff --git a/checkpoints-relayer/src/utils/slots_batch.rs b/checkpoints-relayer/src/utils/slots_batch.rs new file mode 100644 index 00000000..f29c1c0c --- /dev/null +++ b/checkpoints-relayer/src/utils/slots_batch.rs @@ -0,0 +1,68 @@ +use checkpoint_light_client_io::Slot; + +/// Iterator produces right open intervals of the specific size in backward direction. +pub struct Iter { + slot_start: Slot, slot_end: Slot, batch_size: Slot, +} + +impl Iter { + pub fn new(slot_start: Slot, slot_end: Slot, batch_size: Slot) -> Option { + if batch_size < 2 || slot_start >= slot_end { + return None; + } + + Some(Self { + slot_start, slot_end, batch_size, + }) + } +} + +impl Iterator for Iter { + // [slot_start; slot_end) + type Item = (Slot, Slot); + + fn next(&mut self) -> Option { + if self.slot_start + self.batch_size <= self.slot_end { + let slot_start = self.slot_end - self.batch_size + 1; + let slot_end = self.slot_end; + + self.slot_end = slot_start; + + return Some((slot_start, slot_end)); + } + + if self.slot_start < self.slot_end { + let slot_end = self.slot_end; + + self.slot_end = self.slot_start; + + return Some((self.slot_start, slot_end)); + } + + None + } +} + +#[test] +fn test_slots_batch_iterator() { + assert!(Iter::new(3, 10, 0).is_none()); + assert!(Iter::new(3, 10, 1).is_none()); + assert!(Iter::new(3, 3, 2).is_none()); + assert!(Iter::new(10, 3, 2).is_none()); + assert!(Iter::new(10, 3, 0).is_none()); + + let mut iter = Iter::new(3, 10, 2).unwrap(); + + assert!(matches!(iter.next(), Some((start, end)) if start == 9 && end == 10)); + assert!(matches!(iter.next(), Some((start, end)) if start == 7 && end == 8)); + assert!(matches!(iter.next(), Some((start, end)) if start == 5 && end == 6)); + assert!(matches!(iter.next(), Some((start, end)) if start == 3 && end == 4)); + assert!(iter.next().is_none()); + + let mut iter = Iter::new(3, 10, 3).unwrap(); + + assert!(matches!(iter.next(), Some((start, end)) if start == 8 && end == 10)); + assert!(matches!(iter.next(), Some((start, end)) if start == 5 && end == 7)); + assert!(matches!(iter.next(), Some((start, end)) if start == 3 && end == 4)); + assert!(iter.next().is_none()); +} From 560bda4d36f0df2bb8e0f953f8f902e2398e5e60 Mon Sep 17 00:00:00 2001 From: Georgy Shepelev Date: Tue, 23 Jul 2024 21:03:06 +0400 Subject: [PATCH 06/24] implement replay_back func --- checkpoints-relayer/src/main.rs | 212 ++++++++++++++++-- checkpoints-relayer/src/utils/mod.rs | 2 +- ethereum-common/src/utils.rs | 4 + .../checkpoint-light-client/src/wasm/mod.rs | 5 +- 4 files changed, 201 insertions(+), 22 deletions(-) diff --git a/checkpoints-relayer/src/main.rs b/checkpoints-relayer/src/main.rs index adb73e7d..73df55d3 100644 --- a/checkpoints-relayer/src/main.rs +++ b/checkpoints-relayer/src/main.rs @@ -4,11 +4,12 @@ use tokio::{ time::{self, Duration}, }; use reqwest::Client; -use utils::FinalityUpdate; +use utils::{slots_batch, FinalityUpdate, Update, MAX_REQUEST_LIGHT_CLIENT_UPDATES}; use pretty_env_logger::env_logger::fmt::TimestampPrecision; use gclient::{EventListener, EventProcessor, GearApi}; -use checkpoint_light_client_io::{ethereum_common::{base_types::BytesFixed, utils as eth_utils, EPOCHS_PER_SYNC_COMMITTEE, SLOTS_PER_EPOCH}, meta::State, sync_update, tree_hash::Hash256, G2TypeInfo, Handle, HandleResult, Slot, G2}; +use checkpoint_light_client_io::{ethereum_common::{base_types::BytesFixed, utils as eth_utils, EPOCHS_PER_SYNC_COMMITTEE, SLOTS_PER_EPOCH}, meta::State, replay_back, sync_update, tree_hash::Hash256, G2TypeInfo, Handle, HandleResult, Slot, SyncCommitteeUpdate, G2}; use parity_scale_codec::Decode; +use utils::slots_batch::Iter as SlotsBatchIter; #[cfg(test)] mod tests; @@ -16,6 +17,7 @@ mod tests; mod utils; const SIZE_CHANNEL: usize = 100_000; +const SIZE_BATCH: u64 = 26 * SLOTS_PER_EPOCH; const COUNT_FAILURE: usize = 3; const DELAY_SECS_FINALITY_REQUEST: u64 = 30; @@ -50,11 +52,12 @@ enum Status { #[tokio::main] async fn main() { pretty_env_logger::formatted_builder() - .filter_level(log::LevelFilter::Info) - .format_target(false) .format_timestamp(Some(TimestampPrecision::Micros)) + .parse_default_env() .init(); + log::info!("Started"); + let Args { program_id, beacon_endpoint, @@ -107,10 +110,10 @@ async fn main() { let mut slot_last = finality_update.finalized_header.slot; - match try_to_apply_sync_update(&client, &mut listener, program_id, finality_update).await { + match try_to_apply_sync_update(&client, &mut listener, program_id, finality_update.clone()).await { Status::Ok | Status::NotActual => (), Status::Error => return, - Status::ReplayBackRequired { replayed_slot, checkpoint } => replay_back(&client, &mut listener, program_id, replayed_slot, checkpoint, slot_last).await, + Status::ReplayBackRequired { replayed_slot, checkpoint } => replay_back(&client_http, &beacon_endpoint, &client, &mut listener, program_id, replayed_slot, checkpoint, finality_update).await, } while let Some(finality_update) = receiver.recv().await { @@ -122,7 +125,7 @@ async fn main() { match try_to_apply_sync_update(&client, &mut listener, program_id, finality_update).await { Status::Ok => { slot_last = slot; } Status::NotActual => (), - _ => return, + _ => continue, } } } @@ -134,6 +137,8 @@ fn spawn_finality_update_receiver( delay: Duration, ) { tokio::spawn(async move { + log::info!("Update receiver spawned"); + let mut failures = 0; loop { @@ -215,27 +220,196 @@ fn spawn_finality_update_receiver( } async fn replay_back( + client_http: &Client, + beacon_endpoint: &str, client: &GearApi, listener: &mut EventListener, program_id: [u8; 32], replayed_slot: Option, checkpoint: (Slot, Hash256), - slot_last: Slot, + finality_update: FinalityUpdate, ) { - // let (slot_stored, _) = checkpoint; - // let slot_start = match replayed_slot { - // Some(slot_end) => replay_back_slots(slot_stored, slot_end), - // None => slot_stored, - // }; + log::info!("Replaying back started"); + + let (mut slot_start, _) = checkpoint; + if let Some(slot_end) = replayed_slot { + let Some(slots_batch_iter) = SlotsBatchIter::new(slot_start, slot_end, SIZE_BATCH) else { + log::error!("Failed to create slots_batch::Iter with slot_start = {slot_start}, slot_end = {slot_end}."); + + return; + }; + + replay_back_slots(client_http, beacon_endpoint, client, listener, program_id, slots_batch_iter) + .await; + + return; + } + + let period_start = 1 + eth_utils::calculate_period(slot_start); + let updates = match utils::get_updates(&client_http, beacon_endpoint, period_start, MAX_REQUEST_LIGHT_CLIENT_UPDATES).await + { + Ok(updates) => updates, + Err(e) => { + log::error!("Failed to get updates for period {period_start}: {e:?}"); + + return; + } + }; + + for update in updates { + let slot_end = update.data.finalized_header.slot; + let Some(mut slots_batch_iter) = SlotsBatchIter::new(slot_start, slot_end, SIZE_BATCH) else { + log::error!("Failed to create slots_batch::Iter with slot_start = {slot_start}, slot_end (update) = {slot_end}."); + + return; + }; + + slot_start = slot_end; + + let sync_update = utils::sync_update_from_update(update.data); + if replay_back_slots_start(client_http, beacon_endpoint, client, listener, program_id, slots_batch_iter.next(), sync_update).await.is_none() { + return; + } + + if replay_back_slots(client_http, beacon_endpoint, client, listener, program_id, slots_batch_iter).await.is_none() { + return; + } + } + + let slot_last = finality_update.finalized_header.slot; + let Some(mut slots_batch_iter) = SlotsBatchIter::new(slot_start, slot_last, SIZE_BATCH) else { + log::error!("Failed to create slots_batch::Iter with slot_start = {slot_start}, slot_last = {slot_last}."); + + return; + }; + + let signature = ::deserialize_compressed( + &finality_update.sync_aggregate.sync_committee_signature.0 .0[..], + ); + + let Ok(signature) = signature else { + log::error!("replay_back; Failed to deserialize point on G2"); + return; + }; - // let period_start = 1 + eth_utils::calculate_period(slot_start); - // let period_end = eth_utils::calculate_period(slot_last); + let sync_update = utils::sync_update_from_finality(signature, finality_update); + if replay_back_slots_start(client_http, beacon_endpoint, client, listener, program_id, slots_batch_iter.next(), sync_update).await.is_none() { + return; + } + + replay_back_slots(client_http, beacon_endpoint, client, listener, program_id, slots_batch_iter).await; - // for period in period_start..period_end { + log::info!("Replaying back finished"); +} + +async fn replay_back_slots( + client_http: &Client, + beacon_endpoint: &str, + client: &GearApi, + listener: &mut EventListener, + program_id: [u8; 32], + slots_batch_iter: SlotsBatchIter, +) -> Option<()> { + for (slot_start, slot_end) in slots_batch_iter { + replay_back_slots_inner(client_http, beacon_endpoint, client, listener, program_id, slot_start, slot_end) + .await?; + } - // } + Some(()) +} - // replay_back_slots(period_new * EPOCHS_PER_SYNC_COMMITTEE * SLOTS_PER_EPOCH, slot_last); +async fn replay_back_slots_inner( + client_http: &Client, + beacon_endpoint: &str, + client: &GearApi, + listener: &mut EventListener, + program_id: [u8; 32], + slot_start: Slot, + slot_end: Slot, +) -> Option<()> { + let batch_size = (slot_end - slot_start) as usize; + let mut requests_headers = Vec::with_capacity(batch_size); + for i in slot_start..slot_end { + requests_headers.push(utils::get_block_header(&client_http, &beacon_endpoint, 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 (message_id, _) = client + .send_message(program_id.into(), payload, 700_000_000_000, 0) + .await + .map_err(|e| log::error!("Failed to send ReplayBack message: {e:?}")) + .ok()?; - todo!() + let (_message_id, payload, _value) = listener.reply_bytes_on(message_id) + .await + .map_err(|e| log::error!("Failed to get reply to ReplayBack message: {e:?}")) + .ok()?; + let payload = payload.map_err(|e| log::error!("Failed to get replay payload to ReplayBack: {e:?}")).ok()?; + let result_decoded = HandleResult::decode(&mut &payload[..]) + .map_err(|e| log::error!("Failed to decode HandleResult of ReplayBack: {e:?}")).ok()?; + + log::debug!("replay_back_slots_inner; result_decoded = {result_decoded:?}"); + + matches!( + result_decoded, + HandleResult::ReplayBack(Some(replay_back::Status::InProcess | replay_back::Status::Finished)) + ).then_some(()) +} + +async fn replay_back_slots_start( + client_http: &Client, + beacon_endpoint: &str, + client: &GearApi, + listener: &mut EventListener, + program_id: [u8; 32], + slots: Option<(Slot, Slot)>, + sync_update: SyncCommitteeUpdate, +) -> Option<()> { + let Some((slot_start, slot_end)) = slots else { + return Some(()); + }; + + let mut requests_headers = Vec::with_capacity(SIZE_BATCH as usize); + for i in slot_start..slot_end { + requests_headers.push(utils::get_block_header(&client_http, beacon_endpoint, i)); + } + + let headers = futures::future::join_all(requests_headers) + .await + .into_iter() + .filter_map(|maybe_header| maybe_header.ok()) + .collect::>(); + + let payload = Handle::ReplayBackStart { + sync_update, + headers, + }; + + let (message_id, _) = client + .send_message(program_id.into(), payload, 700_000_000_000, 0) + .await + .map_err(|e| log::error!("Failed to send ReplayBackStart message: {e:?}")) + .ok()?; + + let (_message_id, payload, _value) = listener.reply_bytes_on(message_id) + .await + .map_err(|e| log::error!("Failed to get reply to ReplayBackStart message: {e:?}")) + .ok()?; + let payload = payload.map_err(|e| log::error!("Failed to get replay payload to ReplayBackStart: {e:?}")).ok()?; + let result_decoded = HandleResult::decode(&mut &payload[..]) + .map_err(|e| log::error!("Failed to decode HandleResult of ReplayBackStart: {e:?}")).ok()?; + + log::debug!("replay_back_slots_start; result_decoded = {result_decoded:?}"); + + matches!( + result_decoded, + HandleResult::ReplayBackStart(Ok(replay_back::StatusStart::InProgress| replay_back::StatusStart::Finished)) + ).then_some(()) } diff --git a/checkpoints-relayer/src/utils/mod.rs b/checkpoints-relayer/src/utils/mod.rs index 08020d99..7657a7aa 100644 --- a/checkpoints-relayer/src/utils/mod.rs +++ b/checkpoints-relayer/src/utils/mod.rs @@ -70,7 +70,7 @@ pub struct FinalityUpdateResponse { pub data: FinalityUpdate, } -#[derive(Deserialize)] +#[derive(Clone, Deserialize)] pub struct FinalityUpdate { #[serde(deserialize_with = "deserialize_header")] pub attested_header: BeaconBlockHeader, diff --git a/ethereum-common/src/utils.rs b/ethereum-common/src/utils.rs index 215bc1e5..e70cb231 100644 --- a/ethereum-common/src/utils.rs +++ b/ethereum-common/src/utils.rs @@ -9,6 +9,10 @@ pub fn calculate_period(slot: u64) -> u64 { calculate_epoch(slot) / EPOCHS_PER_SYNC_COMMITTEE } +pub fn calculate_slot(period: u64) -> u64 { + period * SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE +} + pub fn decode_hex_bytes<'de, D>(deserializer: D) -> Result, D::Error> where D: serde::Deserializer<'de>, diff --git a/gear-programs/checkpoint-light-client/src/wasm/mod.rs b/gear-programs/checkpoint-light-client/src/wasm/mod.rs index 9c8fb362..7d0ec489 100644 --- a/gear-programs/checkpoint-light-client/src/wasm/mod.rs +++ b/gear-programs/checkpoint-light-client/src/wasm/mod.rs @@ -51,7 +51,8 @@ async fn init() { panic!("Current sync committee proof is not valid"); } - finalized_header.slot -= 1; + let period = eth_utils::calculate_period(finalized_header.slot) - 1; + finalized_header.slot = eth_utils::calculate_slot(period); match sync_update::verify( &network, &finalized_header, @@ -79,7 +80,7 @@ async fn init() { }) }, - _ => panic!("Incorrect initial sync committee update"), + Ok((finalized_header, sync_committee_next)) => panic!("Incorrect initial sync committee update ({}, {})", finalized_header.is_some(), sync_committee_next.is_some()), } } From 51ad3b0301bae5d8b0f5cae7835bb1ed12dc14c2 Mon Sep 17 00:00:00 2001 From: Georgy Shepelev Date: Tue, 23 Jul 2024 21:13:17 +0400 Subject: [PATCH 07/24] adjust slots_batch::Iter test --- checkpoints-relayer/src/utils/slots_batch.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/checkpoints-relayer/src/utils/slots_batch.rs b/checkpoints-relayer/src/utils/slots_batch.rs index f29c1c0c..44c78325 100644 --- a/checkpoints-relayer/src/utils/slots_batch.rs +++ b/checkpoints-relayer/src/utils/slots_batch.rs @@ -53,16 +53,22 @@ fn test_slots_batch_iterator() { let mut iter = Iter::new(3, 10, 2).unwrap(); + // [9; 10), [8; 9), etc assert!(matches!(iter.next(), Some((start, end)) if start == 9 && end == 10)); + assert!(matches!(iter.next(), Some((start, end)) if start == 8 && end == 9)); assert!(matches!(iter.next(), Some((start, end)) if start == 7 && end == 8)); + assert!(matches!(iter.next(), Some((start, end)) if start == 6 && end == 7)); assert!(matches!(iter.next(), Some((start, end)) if start == 5 && end == 6)); + assert!(matches!(iter.next(), Some((start, end)) if start == 4 && end == 5)); assert!(matches!(iter.next(), Some((start, end)) if start == 3 && end == 4)); assert!(iter.next().is_none()); let mut iter = Iter::new(3, 10, 3).unwrap(); + // [8; 10), [6; 8), [4; 6), [3; 4) assert!(matches!(iter.next(), Some((start, end)) if start == 8 && end == 10)); - assert!(matches!(iter.next(), Some((start, end)) if start == 5 && end == 7)); + assert!(matches!(iter.next(), Some((start, end)) if start == 6 && end == 8)); + assert!(matches!(iter.next(), Some((start, end)) if start == 4 && end == 6)); assert!(matches!(iter.next(), Some((start, end)) if start == 3 && end == 4)); assert!(iter.next().is_none()); } From cfbda75148b902cda8a77519a378527ac6e250ad Mon Sep 17 00:00:00 2001 From: Georgy Shepelev Date: Wed, 24 Jul 2024 11:02:52 +0400 Subject: [PATCH 08/24] get parameters for init_and_updating test from the env --- checkpoints-relayer/src/tests/mod.rs | 31 ++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/checkpoints-relayer/src/tests/mod.rs b/checkpoints-relayer/src/tests/mod.rs index 17eda45f..b13b911e 100644 --- a/checkpoints-relayer/src/tests/mod.rs +++ b/checkpoints-relayer/src/tests/mod.rs @@ -14,6 +14,7 @@ use reqwest::Client; use tokio::time::{self, Duration}; use parity_scale_codec::{Decode, Encode}; use crate::utils::{self, FinalityUpdateResponse, slots_batch}; +use std::env; const RPC_URL: &str = "http://127.0.0.1:5052"; @@ -66,10 +67,15 @@ async fn upload_program( async fn init_and_updating() -> Result<()> { let client_http = Client::new(); + let rpc_url = env::var("RPC_URL") + .unwrap_or(RPC_URL.into()); + // use the latest finality header as a checkpoint for bootstrapping - let finality_update = utils::get_finality_update(&client_http, RPC_URL).await?; + let finality_update = utils::get_finality_update(&client_http, &rpc_url).await?; let current_period = eth_utils::calculate_period(finality_update.finalized_header.slot); - let mut updates = utils::get_updates(&client_http, RPC_URL, current_period, 1).await?; + let mut updates = utils::get_updates(&client_http, &rpc_url, 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, @@ -79,12 +85,21 @@ async fn init_and_updating() -> Result<()> { let checkpoint = update.finalized_header.tree_hash_root(); let checkpoint_hex = hex::encode(checkpoint); - let bootstrap = utils::get_bootstrap(&client_http, RPC_URL, &checkpoint_hex).await?; + println!("checkpoint slot = {}, hash = {}", update.finalized_header.slot, checkpoint_hex); + + let bootstrap = utils::get_bootstrap(&client_http, &rpc_url, &checkpoint_hex).await?; let sync_update = utils::sync_update_from_update(update); + println!("bootstrap slot = {}", bootstrap.header.slot); + let pub_keys = utils::map_public_keys(&bootstrap.current_sync_committee.pubkeys.0); + let network = match env::var("NETWORK") { + Ok(network) if network == "Holesky" => Network::Holesky, + Ok(network) if network == "Mainnet" => Network::Mainnet, + _ => Network::Sepolia, + }; let init = Init { - network: Network::Sepolia, + network, sync_committee_current_pub_keys: Box::new(FixedArray(pub_keys.try_into().unwrap())), sync_committee_current_aggregate_pubkey: bootstrap.current_sync_committee.aggregate_pubkey, sync_committee_current_branch: bootstrap @@ -106,12 +121,16 @@ async fn init_and_updating() -> Result<()> { println!(); println!(); + if env::var("UPDATING").is_err() { + return Ok(()); + } + for _ in 0..30 { - let update = utils::get_finality_update(&client_http, RPC_URL).await?; + let update = utils::get_finality_update(&client_http, &rpc_url).await?; let slot: u64 = update.finalized_header.slot; let current_period = eth_utils::calculate_period(slot); - let mut updates = utils::get_updates(&client_http, RPC_URL, current_period, 1).await?; + let mut updates = utils::get_updates(&client_http, &rpc_url, current_period, 1).await?; match updates.pop() { Some(update) if updates.is_empty() && update.data.finalized_header.slot >= slot => { println!("update sync committee"); From 023bcf4df9759e2feab0940e14ebb4b0618bec91 Mon Sep 17 00:00:00 2001 From: Georgy Shepelev Date: Wed, 24 Jul 2024 12:01:02 +0400 Subject: [PATCH 09/24] use SyncCommitteeUpdate --- checkpoints-relayer/src/main.rs | 105 ++++++++++++++++++++++---------- 1 file changed, 73 insertions(+), 32 deletions(-) diff --git a/checkpoints-relayer/src/main.rs b/checkpoints-relayer/src/main.rs index 73df55d3..0d3d7ebf 100644 --- a/checkpoints-relayer/src/main.rs +++ b/checkpoints-relayer/src/main.rs @@ -79,7 +79,7 @@ async fn main() { let (sender, mut receiver) = mpsc::channel(SIZE_CHANNEL); let client_http = Client::new(); - spawn_finality_update_receiver(client_http.clone(), beacon_endpoint.clone(), sender, Duration::from_secs(DELAY_SECS_FINALITY_REQUEST)); + spawn_sync_update_receiver(client_http.clone(), beacon_endpoint.clone(), sender, Duration::from_secs(DELAY_SECS_FINALITY_REQUEST)); let client = match GearApi::dev().await { Ok(client) => client, @@ -130,10 +130,10 @@ async fn main() { } } -fn spawn_finality_update_receiver( +fn spawn_sync_update_receiver( client_http: Client, beacon_endpoint: String, - sender: Sender, + sender: Sender, delay: Duration, ) { tokio::spawn(async move { @@ -142,25 +142,79 @@ fn spawn_finality_update_receiver( let mut failures = 0; loop { - match utils::get_finality_update(&client_http, &beacon_endpoint).await { - Ok(value) => { - if sender.send(value).await.is_err() { + let finality_update = match utils::get_finality_update(&client_http, &beacon_endpoint).await { + Ok(finality_update) => finality_update, + + Err(e) => { + log::error!("Unable to fetch FinalityUpdate: {e:?}"); + + failures += 1; + if failures >= COUNT_FAILURE { return; } + + time::sleep(delay).await; + continue; } + }; + let period = eth_utils::calculate_period(finality_update.finalized_header.slot); + let mut updates = match utils::get_updates(&client_http, &beacon_endpoint, period, 1).await { + Ok(updates) => updates, Err(e) => { - log::error!("Unable to fetch FinalityUpdate: {e:?}"); + log::error!("Unable to fetch Updates: {e:?}"); + + failures += 1; + if failures >= COUNT_FAILURE { + return; + } + + time::sleep(delay).await; + continue; + } + }; + + let update = match updates.pop() { + Some(update) if updates.is_empty() => update.data, + _ => { + log::error!("Requested single update"); failures += 1; if failures >= COUNT_FAILURE { return; } + time::sleep(delay).await; continue; } }; + let sync_update = if update.finalized_header.slot >= finality_update.finalized_header.slot { + utils::sync_update_from_update(update) + } else { + let signature = ::deserialize_compressed( + &finality_update.sync_aggregate.sync_committee_signature.0 .0[..], + ); + + let Ok(signature) = signature else { + log::error!("Failed to deserialize point on G2"); + + failures += 1; + if failures >= COUNT_FAILURE { + return; + } + + time::sleep(delay).await; + continue; + }; + + utils::sync_update_from_finality(signature, finality_update) + }; + + if sender.send(sync_update).await.is_err() { + return; + } + time::sleep(delay).await; } }); @@ -170,19 +224,9 @@ fn spawn_finality_update_receiver( client: &GearApi, listener: &mut EventListener, program_id: [u8; 32], - finality_update: FinalityUpdate, + sync_update: SyncCommitteeUpdate, ) -> Status { - let signature = ::deserialize_compressed( - &finality_update.sync_aggregate.sync_committee_signature.0 .0[..], - ); - - let Ok(signature) = signature else { - log::error!("Failed to deserialize point on G2"); - return Status::Error; - }; - - let payload = Handle::SyncUpdate(utils::sync_update_from_finality(signature, finality_update)); - + let payload = Handle::SyncUpdate(sync_update); let (message_id, _) = match client .send_message(program_id.into(), payload, 700_000_000_000, 0) .await @@ -227,7 +271,7 @@ fn spawn_finality_update_receiver( program_id: [u8; 32], replayed_slot: Option, checkpoint: (Slot, Hash256), - finality_update: FinalityUpdate, + sync_update: SyncCommitteeUpdate, ) { log::info!("Replaying back started"); @@ -242,6 +286,8 @@ fn spawn_finality_update_receiver( replay_back_slots(client_http, beacon_endpoint, client, listener, program_id, slots_batch_iter) .await; + log::info!("The ongoing replaying back finished"); + return; } @@ -255,7 +301,8 @@ fn spawn_finality_update_receiver( return; } }; - + + let slot_last = sync_update.finalized_header.slot; for update in updates { let slot_end = update.data.finalized_header.slot; let Some(mut slots_batch_iter) = SlotsBatchIter::new(slot_start, slot_end, SIZE_BATCH) else { @@ -274,25 +321,19 @@ fn spawn_finality_update_receiver( if replay_back_slots(client_http, beacon_endpoint, client, listener, program_id, slots_batch_iter).await.is_none() { return; } + + if slot_end == slot_last { + // the provided sync_update is a sync committee update + return; + } } - let slot_last = finality_update.finalized_header.slot; let Some(mut slots_batch_iter) = SlotsBatchIter::new(slot_start, slot_last, SIZE_BATCH) else { log::error!("Failed to create slots_batch::Iter with slot_start = {slot_start}, slot_last = {slot_last}."); return; }; - let signature = ::deserialize_compressed( - &finality_update.sync_aggregate.sync_committee_signature.0 .0[..], - ); - - let Ok(signature) = signature else { - log::error!("replay_back; Failed to deserialize point on G2"); - return; - }; - - let sync_update = utils::sync_update_from_finality(signature, finality_update); if replay_back_slots_start(client_http, beacon_endpoint, client, listener, program_id, slots_batch_iter.next(), sync_update).await.is_none() { return; } From 120d5e1605e443c7f2bfc93eb53ea321dac37177 Mon Sep 17 00:00:00 2001 From: Georgy Shepelev Date: Wed, 24 Jul 2024 14:34:19 +0400 Subject: [PATCH 10/24] process SIGINT --- checkpoints-relayer/src/main.rs | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/checkpoints-relayer/src/main.rs b/checkpoints-relayer/src/main.rs index 0d3d7ebf..d8802385 100644 --- a/checkpoints-relayer/src/main.rs +++ b/checkpoints-relayer/src/main.rs @@ -2,14 +2,15 @@ use clap::Parser; use tokio::{ sync::mpsc::{self, Sender}, time::{self, Duration}, + signal::unix::{self, SignalKind}, }; use reqwest::Client; -use utils::{slots_batch, FinalityUpdate, Update, MAX_REQUEST_LIGHT_CLIENT_UPDATES}; +use utils::{slots_batch::Iter as SlotsBatchIter, FinalityUpdate, Update, MAX_REQUEST_LIGHT_CLIENT_UPDATES}; use pretty_env_logger::env_logger::fmt::TimestampPrecision; use gclient::{EventListener, EventProcessor, GearApi}; use checkpoint_light_client_io::{ethereum_common::{base_types::BytesFixed, utils as eth_utils, EPOCHS_PER_SYNC_COMMITTEE, SLOTS_PER_EPOCH}, meta::State, replay_back, sync_update, tree_hash::Hash256, G2TypeInfo, Handle, HandleResult, Slot, SyncCommitteeUpdate, G2}; use parity_scale_codec::Decode; -use utils::slots_batch::Iter as SlotsBatchIter; +use futures::{pin_mut, future::{self, Either}}; #[cfg(test)] mod tests; @@ -58,6 +59,11 @@ async fn main() { log::info!("Started"); + let Ok(mut signal_interrupt) = unix::signal(SignalKind::interrupt()) else { + log::error!("Failed to set SIGINT handler"); + return; + }; + let Args { program_id, beacon_endpoint, @@ -116,13 +122,29 @@ async fn main() { Status::ReplayBackRequired { replayed_slot, checkpoint } => replay_back(&client_http, &beacon_endpoint, &client, &mut listener, program_id, replayed_slot, checkpoint, finality_update).await, } - while let Some(finality_update) = receiver.recv().await { - let slot = finality_update.finalized_header.slot; + loop { + let future_interrupt = signal_interrupt.recv(); + pin_mut!(future_interrupt); + + let future_update = receiver.recv(); + pin_mut!(future_update); + + let sync_update = match future::select(future_interrupt, future_update).await { + Either::Left((_interrupted, _)) => { + log::info!("Caught SIGINT. Exit"); + return; + } + + Either::Right((Some(sync_update), _)) => sync_update, + Either::Right((None, _)) => return, + }; + + let slot = sync_update.finalized_header.slot; if slot == slot_last { continue; } - match try_to_apply_sync_update(&client, &mut listener, program_id, finality_update).await { + match try_to_apply_sync_update(&client, &mut listener, program_id, sync_update).await { Status::Ok => { slot_last = slot; } Status::NotActual => (), _ => continue, From 688def22c7daa11e1af9c4605a42359617753812 Mon Sep 17 00:00:00 2001 From: Georgy Shepelev Date: Wed, 24 Jul 2024 16:47:13 +0400 Subject: [PATCH 11/24] add command line arguments for VARA RPC endpoint --- checkpoints-relayer/src/main.rs | 44 ++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/checkpoints-relayer/src/main.rs b/checkpoints-relayer/src/main.rs index d8802385..023f4cdf 100644 --- a/checkpoints-relayer/src/main.rs +++ b/checkpoints-relayer/src/main.rs @@ -5,10 +5,10 @@ use tokio::{ signal::unix::{self, SignalKind}, }; use reqwest::Client; -use utils::{slots_batch::Iter as SlotsBatchIter, FinalityUpdate, Update, MAX_REQUEST_LIGHT_CLIENT_UPDATES}; +use utils::{slots_batch::Iter as SlotsBatchIter, MAX_REQUEST_LIGHT_CLIENT_UPDATES}; use pretty_env_logger::env_logger::fmt::TimestampPrecision; -use gclient::{EventListener, EventProcessor, GearApi}; -use checkpoint_light_client_io::{ethereum_common::{base_types::BytesFixed, utils as eth_utils, EPOCHS_PER_SYNC_COMMITTEE, SLOTS_PER_EPOCH}, meta::State, replay_back, sync_update, tree_hash::Hash256, G2TypeInfo, Handle, HandleResult, Slot, SyncCommitteeUpdate, G2}; +use gclient::{EventListener, EventProcessor, GearApi, WSAddress}; +use checkpoint_light_client_io::{ethereum_common::{utils as eth_utils, SLOTS_PER_EPOCH}, replay_back, sync_update, tree_hash::Hash256, Handle, HandleResult, Slot, SyncCommitteeUpdate, G2}; use parity_scale_codec::Decode; use futures::{pin_mut, future::{self, Either}}; @@ -32,12 +32,29 @@ struct Args { #[arg(long)] beacon_endpoint: String, - /// Address of the VARA RPC endpoint + /// Domain of the VARA RPC endpoint #[arg( long, - env = "VARA_RPC" + default_value = "ws://127.0.0.1" )] - vara_endpoint: String, + vara_domain: String, + + /// Port of the VARA RPC endpoint + #[arg( + long, + default_value = "9944" + )] + vara_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" + )] + suri: String, } enum Status { @@ -67,7 +84,9 @@ async fn main() { let Args { program_id, beacon_endpoint, - vara_endpoint, + vara_domain, + vara_port, + suri, } = Args::parse(); let program_id_no_prefix = match program_id.starts_with("0x") { @@ -87,7 +106,7 @@ async fn main() { spawn_sync_update_receiver(client_http.clone(), beacon_endpoint.clone(), sender, Duration::from_secs(DELAY_SECS_FINALITY_REQUEST)); - let client = match GearApi::dev().await { + let client = match GearApi::init_with(WSAddress::new(vara_domain, vara_port), suri).await { Ok(client) => client, Err(e) => { log::error!("Unable to create GearApi client: {e:?}"); @@ -108,7 +127,7 @@ async fn main() { let finality_update = match receiver.recv().await { Some(finality_update) => finality_update, None => { - log::info!("Receiver of FinalityUpdates has been closed. Exiting."); + log::info!("Updates receiver has been closed before the loop. Exiting"); return; } @@ -131,12 +150,15 @@ async fn main() { let sync_update = match future::select(future_interrupt, future_update).await { Either::Left((_interrupted, _)) => { - log::info!("Caught SIGINT. Exit"); + log::info!("Caught SIGINT. Exiting"); return; } Either::Right((Some(sync_update), _)) => sync_update, - Either::Right((None, _)) => return, + Either::Right((None, _)) => { + log::info!("Updates receiver has been closed. Exiting"); + return; + } }; let slot = sync_update.finalized_header.slot; From 0645bceb4524fb6f6105f2ff8c9263ac432aa021 Mon Sep 17 00:00:00 2001 From: Georgy Shepelev Date: Wed, 24 Jul 2024 17:33:55 +0400 Subject: [PATCH 12/24] refactor: new module sync_update --- checkpoints-relayer/src/main.rs | 142 +------------------------ checkpoints-relayer/src/sync_update.rs | 137 ++++++++++++++++++++++++ 2 files changed, 142 insertions(+), 137 deletions(-) create mode 100644 checkpoints-relayer/src/sync_update.rs diff --git a/checkpoints-relayer/src/main.rs b/checkpoints-relayer/src/main.rs index 023f4cdf..7cb3a42a 100644 --- a/checkpoints-relayer/src/main.rs +++ b/checkpoints-relayer/src/main.rs @@ -8,13 +8,14 @@ use reqwest::Client; use utils::{slots_batch::Iter as SlotsBatchIter, MAX_REQUEST_LIGHT_CLIENT_UPDATES}; use pretty_env_logger::env_logger::fmt::TimestampPrecision; use gclient::{EventListener, EventProcessor, GearApi, WSAddress}; -use checkpoint_light_client_io::{ethereum_common::{utils as eth_utils, SLOTS_PER_EPOCH}, replay_back, sync_update, tree_hash::Hash256, Handle, HandleResult, Slot, SyncCommitteeUpdate, G2}; +use checkpoint_light_client_io::{ethereum_common::{utils as eth_utils, SLOTS_PER_EPOCH}, replay_back, tree_hash::Hash256, Handle, HandleResult, Slot, SyncCommitteeUpdate, G2}; use parity_scale_codec::Decode; use futures::{pin_mut, future::{self, Either}}; #[cfg(test)] mod tests; +mod sync_update; mod utils; const SIZE_CHANNEL: usize = 100_000; @@ -104,7 +105,7 @@ async fn main() { let (sender, mut receiver) = mpsc::channel(SIZE_CHANNEL); let client_http = Client::new(); - spawn_sync_update_receiver(client_http.clone(), beacon_endpoint.clone(), sender, Duration::from_secs(DELAY_SECS_FINALITY_REQUEST)); + sync_update::spawn_receiver(client_http.clone(), beacon_endpoint.clone(), sender, Duration::from_secs(DELAY_SECS_FINALITY_REQUEST)); let client = match GearApi::init_with(WSAddress::new(vara_domain, vara_port), suri).await { Ok(client) => client, @@ -135,7 +136,7 @@ async fn main() { let mut slot_last = finality_update.finalized_header.slot; - match try_to_apply_sync_update(&client, &mut listener, program_id, finality_update.clone()).await { + match sync_update::try_to_apply(&client, &mut listener, program_id, finality_update.clone()).await { Status::Ok | Status::NotActual => (), Status::Error => return, Status::ReplayBackRequired { replayed_slot, checkpoint } => replay_back(&client_http, &beacon_endpoint, &client, &mut listener, program_id, replayed_slot, checkpoint, finality_update).await, @@ -166,7 +167,7 @@ async fn main() { continue; } - match try_to_apply_sync_update(&client, &mut listener, program_id, sync_update).await { + match sync_update::try_to_apply(&client, &mut listener, program_id, sync_update).await { Status::Ok => { slot_last = slot; } Status::NotActual => (), _ => continue, @@ -174,139 +175,6 @@ async fn main() { } } -fn spawn_sync_update_receiver( - client_http: Client, - beacon_endpoint: String, - sender: Sender, - delay: Duration, -) { - tokio::spawn(async move { - log::info!("Update receiver spawned"); - - let mut failures = 0; - - loop { - let finality_update = match utils::get_finality_update(&client_http, &beacon_endpoint).await { - Ok(finality_update) => finality_update, - - Err(e) => { - log::error!("Unable to fetch FinalityUpdate: {e:?}"); - - failures += 1; - if failures >= COUNT_FAILURE { - return; - } - - time::sleep(delay).await; - continue; - } - }; - - let period = eth_utils::calculate_period(finality_update.finalized_header.slot); - let mut updates = match utils::get_updates(&client_http, &beacon_endpoint, period, 1).await { - Ok(updates) => updates, - Err(e) => { - log::error!("Unable to fetch Updates: {e:?}"); - - failures += 1; - if failures >= COUNT_FAILURE { - return; - } - - time::sleep(delay).await; - continue; - } - }; - - let update = match updates.pop() { - Some(update) if updates.is_empty() => update.data, - _ => { - log::error!("Requested single update"); - - failures += 1; - if failures >= COUNT_FAILURE { - return; - } - - time::sleep(delay).await; - continue; - } - }; - - let sync_update = if update.finalized_header.slot >= finality_update.finalized_header.slot { - utils::sync_update_from_update(update) - } else { - let signature = ::deserialize_compressed( - &finality_update.sync_aggregate.sync_committee_signature.0 .0[..], - ); - - let Ok(signature) = signature else { - log::error!("Failed to deserialize point on G2"); - - failures += 1; - if failures >= COUNT_FAILURE { - return; - } - - time::sleep(delay).await; - continue; - }; - - utils::sync_update_from_finality(signature, finality_update) - }; - - if sender.send(sync_update).await.is_err() { - return; - } - - time::sleep(delay).await; - } - }); - } - - async fn try_to_apply_sync_update( - client: &GearApi, - listener: &mut EventListener, - program_id: [u8; 32], - sync_update: SyncCommitteeUpdate, -) -> Status { - let payload = Handle::SyncUpdate(sync_update); - let (message_id, _) = match client - .send_message(program_id.into(), payload, 700_000_000_000, 0) - .await - { - Ok(result) => result, - Err(e) => { - log::error!("Failed to send message: {e:?}"); - - return Status::Error; - } - }; - - let (_message_id, payload, _value) = match listener - .reply_bytes_on(message_id) - .await - { - Ok(result) => result, - Err(e) => { - log::error!("Failed to get reply: {e:?}"); - - return Status::Error; - } - }; - let result_decoded = HandleResult::decode(&mut &payload.unwrap()[..]).unwrap(); - log::debug!("Handle result = {result_decoded:?}"); - match result_decoded { - HandleResult::SyncUpdate(Ok(())) => Status::Ok, - HandleResult::SyncUpdate(Err(sync_update::Error::NotActual)) => Status::NotActual, - HandleResult::SyncUpdate(Err(sync_update::Error::ReplayBackRequired { - replayed_slot, - checkpoint - })) => Status::ReplayBackRequired { replayed_slot, checkpoint }, - _ => Status::Error, - } - } - async fn replay_back( client_http: &Client, beacon_endpoint: &str, diff --git a/checkpoints-relayer/src/sync_update.rs b/checkpoints-relayer/src/sync_update.rs new file mode 100644 index 00000000..2080e15c --- /dev/null +++ b/checkpoints-relayer/src/sync_update.rs @@ -0,0 +1,137 @@ +use super::*; +use checkpoint_light_client_io::sync_update::Error; + +pub fn spawn_receiver( + client_http: Client, + beacon_endpoint: String, + sender: Sender, + delay: Duration, +) { + tokio::spawn(async move { + log::info!("Update receiver spawned"); + + let mut failures = 0; + + loop { + let finality_update = match utils::get_finality_update(&client_http, &beacon_endpoint).await { + Ok(finality_update) => finality_update, + + Err(e) => { + log::error!("Unable to fetch FinalityUpdate: {e:?}"); + + failures += 1; + if failures >= COUNT_FAILURE { + return; + } + + time::sleep(delay).await; + continue; + } + }; + + let period = eth_utils::calculate_period(finality_update.finalized_header.slot); + let mut updates = match utils::get_updates(&client_http, &beacon_endpoint, period, 1).await { + Ok(updates) => updates, + Err(e) => { + log::error!("Unable to fetch Updates: {e:?}"); + + failures += 1; + if failures >= COUNT_FAILURE { + return; + } + + time::sleep(delay).await; + continue; + } + }; + + let update = match updates.pop() { + Some(update) if updates.is_empty() => update.data, + _ => { + log::error!("Requested single update"); + + failures += 1; + if failures >= COUNT_FAILURE { + return; + } + + time::sleep(delay).await; + continue; + } + }; + + let sync_update = if update.finalized_header.slot >= finality_update.finalized_header.slot { + utils::sync_update_from_update(update) + } else { + let signature = ::deserialize_compressed( + &finality_update.sync_aggregate.sync_committee_signature.0 .0[..], + ); + + let Ok(signature) = signature else { + log::error!("Failed to deserialize point on G2"); + + failures += 1; + if failures >= COUNT_FAILURE { + return; + } + + time::sleep(delay).await; + continue; + }; + + utils::sync_update_from_finality(signature, finality_update) + }; + + if sender.send(sync_update).await.is_err() { + return; + } + + time::sleep(delay).await; + } + }); +} + +pub async fn try_to_apply( + client: &GearApi, + listener: &mut EventListener, + program_id: [u8; 32], + sync_update: SyncCommitteeUpdate, +) -> Status { + let payload = Handle::SyncUpdate(sync_update); + let (message_id, _) = match client + .send_message(program_id.into(), payload, 700_000_000_000, 0) + .await + { + Ok(result) => result, + Err(e) => { + log::error!("Failed to send message: {e:?}"); + + return Status::Error; + } + }; + + let (_message_id, payload, _value) = match listener + .reply_bytes_on(message_id) + .await + { + Ok(result) => result, + Err(e) => { + log::error!("Failed to get reply: {e:?}"); + + return Status::Error; + } + }; + let result_decoded = HandleResult::decode(&mut &payload.unwrap()[..]).unwrap(); + + log::debug!("Handle result = {result_decoded:?}"); + + match result_decoded { + HandleResult::SyncUpdate(Ok(())) => Status::Ok, + HandleResult::SyncUpdate(Err(Error::NotActual)) => Status::NotActual, + HandleResult::SyncUpdate(Err(Error::ReplayBackRequired { + replayed_slot, + checkpoint + })) => Status::ReplayBackRequired { replayed_slot, checkpoint }, + _ => Status::Error, + } + } From 3700a1fdac5b71d40a2b9f7bb10680d5c6c3f464 Mon Sep 17 00:00:00 2001 From: Georgy Shepelev Date: Wed, 24 Jul 2024 17:40:56 +0400 Subject: [PATCH 13/24] refactor: add new module replay_back --- checkpoints-relayer/src/main.rs | 203 +------------------------ checkpoints-relayer/src/replay_back.rs | 194 +++++++++++++++++++++++ 2 files changed, 200 insertions(+), 197 deletions(-) create mode 100644 checkpoints-relayer/src/replay_back.rs diff --git a/checkpoints-relayer/src/main.rs b/checkpoints-relayer/src/main.rs index 7cb3a42a..09b5694d 100644 --- a/checkpoints-relayer/src/main.rs +++ b/checkpoints-relayer/src/main.rs @@ -8,7 +8,7 @@ use reqwest::Client; use utils::{slots_batch::Iter as SlotsBatchIter, MAX_REQUEST_LIGHT_CLIENT_UPDATES}; use pretty_env_logger::env_logger::fmt::TimestampPrecision; use gclient::{EventListener, EventProcessor, GearApi, WSAddress}; -use checkpoint_light_client_io::{ethereum_common::{utils as eth_utils, SLOTS_PER_EPOCH}, replay_back, tree_hash::Hash256, Handle, HandleResult, Slot, SyncCommitteeUpdate, G2}; +use checkpoint_light_client_io::{ethereum_common::{utils as eth_utils, SLOTS_PER_EPOCH}, tree_hash::Hash256, Handle, HandleResult, Slot, SyncCommitteeUpdate, G2}; use parity_scale_codec::Decode; use futures::{pin_mut, future::{self, Either}}; @@ -16,6 +16,7 @@ use futures::{pin_mut, future::{self, Either}}; mod tests; mod sync_update; +mod replay_back; mod utils; const SIZE_CHANNEL: usize = 100_000; @@ -125,7 +126,7 @@ async fn main() { } }; - let finality_update = match receiver.recv().await { + let sync_update = match receiver.recv().await { Some(finality_update) => finality_update, None => { log::info!("Updates receiver has been closed before the loop. Exiting"); @@ -134,12 +135,12 @@ async fn main() { } }; - let mut slot_last = finality_update.finalized_header.slot; + let mut slot_last = sync_update.finalized_header.slot; - match sync_update::try_to_apply(&client, &mut listener, program_id, finality_update.clone()).await { + match sync_update::try_to_apply(&client, &mut listener, program_id, sync_update.clone()).await { Status::Ok | Status::NotActual => (), Status::Error => return, - Status::ReplayBackRequired { replayed_slot, checkpoint } => replay_back(&client_http, &beacon_endpoint, &client, &mut listener, program_id, replayed_slot, checkpoint, finality_update).await, + Status::ReplayBackRequired { replayed_slot, checkpoint } => replay_back::execute(&client_http, &beacon_endpoint, &client, &mut listener, program_id, replayed_slot, checkpoint, sync_update).await, } loop { @@ -174,195 +175,3 @@ async fn main() { } } } - - async fn replay_back( - client_http: &Client, - beacon_endpoint: &str, - client: &GearApi, - listener: &mut EventListener, - program_id: [u8; 32], - replayed_slot: Option, - checkpoint: (Slot, Hash256), - sync_update: SyncCommitteeUpdate, -) { - log::info!("Replaying back started"); - - let (mut slot_start, _) = checkpoint; - if let Some(slot_end) = replayed_slot { - let Some(slots_batch_iter) = SlotsBatchIter::new(slot_start, slot_end, SIZE_BATCH) else { - log::error!("Failed to create slots_batch::Iter with slot_start = {slot_start}, slot_end = {slot_end}."); - - return; - }; - - replay_back_slots(client_http, beacon_endpoint, client, listener, program_id, slots_batch_iter) - .await; - - log::info!("The ongoing replaying back finished"); - - return; - } - - let period_start = 1 + eth_utils::calculate_period(slot_start); - let updates = match utils::get_updates(&client_http, beacon_endpoint, period_start, MAX_REQUEST_LIGHT_CLIENT_UPDATES).await - { - Ok(updates) => updates, - Err(e) => { - log::error!("Failed to get updates for period {period_start}: {e:?}"); - - return; - } - }; - - let slot_last = sync_update.finalized_header.slot; - for update in updates { - let slot_end = update.data.finalized_header.slot; - let Some(mut slots_batch_iter) = SlotsBatchIter::new(slot_start, slot_end, SIZE_BATCH) else { - log::error!("Failed to create slots_batch::Iter with slot_start = {slot_start}, slot_end (update) = {slot_end}."); - - return; - }; - - slot_start = slot_end; - - let sync_update = utils::sync_update_from_update(update.data); - if replay_back_slots_start(client_http, beacon_endpoint, client, listener, program_id, slots_batch_iter.next(), sync_update).await.is_none() { - return; - } - - if replay_back_slots(client_http, beacon_endpoint, client, listener, program_id, slots_batch_iter).await.is_none() { - return; - } - - if slot_end == slot_last { - // the provided sync_update is a sync committee update - return; - } - } - - let Some(mut slots_batch_iter) = SlotsBatchIter::new(slot_start, slot_last, SIZE_BATCH) else { - log::error!("Failed to create slots_batch::Iter with slot_start = {slot_start}, slot_last = {slot_last}."); - - return; - }; - - if replay_back_slots_start(client_http, beacon_endpoint, client, listener, program_id, slots_batch_iter.next(), sync_update).await.is_none() { - return; - } - - replay_back_slots(client_http, beacon_endpoint, client, listener, program_id, slots_batch_iter).await; - - log::info!("Replaying back finished"); -} - -async fn replay_back_slots( - client_http: &Client, - beacon_endpoint: &str, - client: &GearApi, - listener: &mut EventListener, - program_id: [u8; 32], - slots_batch_iter: SlotsBatchIter, -) -> Option<()> { - for (slot_start, slot_end) in slots_batch_iter { - replay_back_slots_inner(client_http, beacon_endpoint, client, listener, program_id, slot_start, slot_end) - .await?; - } - - Some(()) -} - -async fn replay_back_slots_inner( - client_http: &Client, - beacon_endpoint: &str, - client: &GearApi, - listener: &mut EventListener, - program_id: [u8; 32], - slot_start: Slot, - slot_end: Slot, -) -> Option<()> { - let batch_size = (slot_end - slot_start) as usize; - let mut requests_headers = Vec::with_capacity(batch_size); - for i in slot_start..slot_end { - requests_headers.push(utils::get_block_header(&client_http, &beacon_endpoint, 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 (message_id, _) = client - .send_message(program_id.into(), payload, 700_000_000_000, 0) - .await - .map_err(|e| log::error!("Failed to send ReplayBack message: {e:?}")) - .ok()?; - - let (_message_id, payload, _value) = listener.reply_bytes_on(message_id) - .await - .map_err(|e| log::error!("Failed to get reply to ReplayBack message: {e:?}")) - .ok()?; - let payload = payload.map_err(|e| log::error!("Failed to get replay payload to ReplayBack: {e:?}")).ok()?; - let result_decoded = HandleResult::decode(&mut &payload[..]) - .map_err(|e| log::error!("Failed to decode HandleResult of ReplayBack: {e:?}")).ok()?; - - log::debug!("replay_back_slots_inner; result_decoded = {result_decoded:?}"); - - matches!( - result_decoded, - HandleResult::ReplayBack(Some(replay_back::Status::InProcess | replay_back::Status::Finished)) - ).then_some(()) -} - -async fn replay_back_slots_start( - client_http: &Client, - beacon_endpoint: &str, - client: &GearApi, - listener: &mut EventListener, - program_id: [u8; 32], - slots: Option<(Slot, Slot)>, - sync_update: SyncCommitteeUpdate, -) -> Option<()> { - let Some((slot_start, slot_end)) = slots else { - return Some(()); - }; - - let mut requests_headers = Vec::with_capacity(SIZE_BATCH as usize); - for i in slot_start..slot_end { - requests_headers.push(utils::get_block_header(&client_http, beacon_endpoint, i)); - } - - let headers = futures::future::join_all(requests_headers) - .await - .into_iter() - .filter_map(|maybe_header| maybe_header.ok()) - .collect::>(); - - let payload = Handle::ReplayBackStart { - sync_update, - headers, - }; - - let (message_id, _) = client - .send_message(program_id.into(), payload, 700_000_000_000, 0) - .await - .map_err(|e| log::error!("Failed to send ReplayBackStart message: {e:?}")) - .ok()?; - - let (_message_id, payload, _value) = listener.reply_bytes_on(message_id) - .await - .map_err(|e| log::error!("Failed to get reply to ReplayBackStart message: {e:?}")) - .ok()?; - let payload = payload.map_err(|e| log::error!("Failed to get replay payload to ReplayBackStart: {e:?}")).ok()?; - let result_decoded = HandleResult::decode(&mut &payload[..]) - .map_err(|e| log::error!("Failed to decode HandleResult of ReplayBackStart: {e:?}")).ok()?; - - log::debug!("replay_back_slots_start; result_decoded = {result_decoded:?}"); - - matches!( - result_decoded, - HandleResult::ReplayBackStart(Ok(replay_back::StatusStart::InProgress| replay_back::StatusStart::Finished)) - ).then_some(()) -} diff --git a/checkpoints-relayer/src/replay_back.rs b/checkpoints-relayer/src/replay_back.rs new file mode 100644 index 00000000..6582c094 --- /dev/null +++ b/checkpoints-relayer/src/replay_back.rs @@ -0,0 +1,194 @@ +use super::*; +use checkpoint_light_client_io::replay_back::{Status, StatusStart}; + +pub async fn execute( + client_http: &Client, + beacon_endpoint: &str, + client: &GearApi, + listener: &mut EventListener, + program_id: [u8; 32], + replayed_slot: Option, + checkpoint: (Slot, Hash256), + sync_update: SyncCommitteeUpdate, +) { + log::info!("Replaying back started"); + + let (mut slot_start, _) = checkpoint; + if let Some(slot_end) = replayed_slot { + let Some(slots_batch_iter) = SlotsBatchIter::new(slot_start, slot_end, SIZE_BATCH) else { + log::error!("Failed to create slots_batch::Iter with slot_start = {slot_start}, slot_end = {slot_end}."); + + return; + }; + + replay_back_slots(client_http, beacon_endpoint, client, listener, program_id, slots_batch_iter) + .await; + + log::info!("The ongoing replaying back finished"); + + return; + } + + let period_start = 1 + eth_utils::calculate_period(slot_start); + let updates = match utils::get_updates(&client_http, beacon_endpoint, period_start, MAX_REQUEST_LIGHT_CLIENT_UPDATES).await + { + Ok(updates) => updates, + Err(e) => { + log::error!("Failed to get updates for period {period_start}: {e:?}"); + + return; + } + }; + + let slot_last = sync_update.finalized_header.slot; + for update in updates { + let slot_end = update.data.finalized_header.slot; + let Some(mut slots_batch_iter) = SlotsBatchIter::new(slot_start, slot_end, SIZE_BATCH) else { + log::error!("Failed to create slots_batch::Iter with slot_start = {slot_start}, slot_end (update) = {slot_end}."); + + return; + }; + + slot_start = slot_end; + + let sync_update = utils::sync_update_from_update(update.data); + if replay_back_slots_start(client_http, beacon_endpoint, client, listener, program_id, slots_batch_iter.next(), sync_update).await.is_none() { + return; + } + + if replay_back_slots(client_http, beacon_endpoint, client, listener, program_id, slots_batch_iter).await.is_none() { + return; + } + + if slot_end == slot_last { + // the provided sync_update is a sync committee update + return; + } + } + + let Some(mut slots_batch_iter) = SlotsBatchIter::new(slot_start, slot_last, SIZE_BATCH) else { + log::error!("Failed to create slots_batch::Iter with slot_start = {slot_start}, slot_last = {slot_last}."); + + return; + }; + + if replay_back_slots_start(client_http, beacon_endpoint, client, listener, program_id, slots_batch_iter.next(), sync_update).await.is_none() { + return; + } + + replay_back_slots(client_http, beacon_endpoint, client, listener, program_id, slots_batch_iter).await; + + log::info!("Replaying back finished"); +} + +async fn replay_back_slots( + client_http: &Client, + beacon_endpoint: &str, + client: &GearApi, + listener: &mut EventListener, + program_id: [u8; 32], + slots_batch_iter: SlotsBatchIter, +) -> Option<()> { + for (slot_start, slot_end) in slots_batch_iter { + replay_back_slots_inner(client_http, beacon_endpoint, client, listener, program_id, slot_start, slot_end) + .await?; + } + + Some(()) +} + +async fn replay_back_slots_inner( + client_http: &Client, + beacon_endpoint: &str, + client: &GearApi, + listener: &mut EventListener, + program_id: [u8; 32], + slot_start: Slot, + slot_end: Slot, +) -> Option<()> { + let batch_size = (slot_end - slot_start) as usize; + let mut requests_headers = Vec::with_capacity(batch_size); + for i in slot_start..slot_end { + requests_headers.push(utils::get_block_header(&client_http, &beacon_endpoint, 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 (message_id, _) = client + .send_message(program_id.into(), payload, 700_000_000_000, 0) + .await + .map_err(|e| log::error!("Failed to send ReplayBack message: {e:?}")) + .ok()?; + + let (_message_id, payload, _value) = listener.reply_bytes_on(message_id) + .await + .map_err(|e| log::error!("Failed to get reply to ReplayBack message: {e:?}")) + .ok()?; + let payload = payload.map_err(|e| log::error!("Failed to get replay payload to ReplayBack: {e:?}")).ok()?; + let result_decoded = HandleResult::decode(&mut &payload[..]) + .map_err(|e| log::error!("Failed to decode HandleResult of ReplayBack: {e:?}")).ok()?; + + log::debug!("replay_back_slots_inner; result_decoded = {result_decoded:?}"); + + matches!( + result_decoded, + HandleResult::ReplayBack(Some(Status::InProcess | Status::Finished)) + ).then_some(()) +} + +async fn replay_back_slots_start( + client_http: &Client, + beacon_endpoint: &str, + client: &GearApi, + listener: &mut EventListener, + program_id: [u8; 32], + slots: Option<(Slot, Slot)>, + sync_update: SyncCommitteeUpdate, +) -> Option<()> { + let Some((slot_start, slot_end)) = slots else { + return Some(()); + }; + + let mut requests_headers = Vec::with_capacity(SIZE_BATCH as usize); + for i in slot_start..slot_end { + requests_headers.push(utils::get_block_header(&client_http, beacon_endpoint, i)); + } + + let headers = futures::future::join_all(requests_headers) + .await + .into_iter() + .filter_map(|maybe_header| maybe_header.ok()) + .collect::>(); + + let payload = Handle::ReplayBackStart { + sync_update, + headers, + }; + + let (message_id, _) = client + .send_message(program_id.into(), payload, 700_000_000_000, 0) + .await + .map_err(|e| log::error!("Failed to send ReplayBackStart message: {e:?}")) + .ok()?; + + let (_message_id, payload, _value) = listener.reply_bytes_on(message_id) + .await + .map_err(|e| log::error!("Failed to get reply to ReplayBackStart message: {e:?}")) + .ok()?; + let payload = payload.map_err(|e| log::error!("Failed to get replay payload to ReplayBackStart: {e:?}")).ok()?; + let result_decoded = HandleResult::decode(&mut &payload[..]) + .map_err(|e| log::error!("Failed to decode HandleResult of ReplayBackStart: {e:?}")).ok()?; + + log::debug!("replay_back_slots_start; result_decoded = {result_decoded:?}"); + + matches!( + result_decoded, + HandleResult::ReplayBackStart(Ok(StatusStart::InProgress | StatusStart::Finished)) + ).then_some(()) +} From 3526eb4354a00c552e05730d00d0e6e580aa9576 Mon Sep 17 00:00:00 2001 From: Georgy Shepelev Date: Fri, 26 Jul 2024 14:38:23 +0400 Subject: [PATCH 14/24] replay back: log beacon error responses --- checkpoints-relayer/src/replay_back.rs | 56 ++++++++++++++------------ checkpoints-relayer/src/utils/mod.rs | 24 ++++++++++- 2 files changed, 52 insertions(+), 28 deletions(-) diff --git a/checkpoints-relayer/src/replay_back.rs b/checkpoints-relayer/src/replay_back.rs index 6582c094..36828a2b 100644 --- a/checkpoints-relayer/src/replay_back.rs +++ b/checkpoints-relayer/src/replay_back.rs @@ -1,5 +1,6 @@ use super::*; -use checkpoint_light_client_io::replay_back::{Status, StatusStart}; +use checkpoint_light_client_io::{replay_back::{Status, StatusStart}, BeaconBlockHeader}; +use utils::ErrorNotFound; pub async fn execute( client_http: &Client, @@ -106,19 +107,7 @@ async fn replay_back_slots_inner( slot_start: Slot, slot_end: Slot, ) -> Option<()> { - let batch_size = (slot_end - slot_start) as usize; - let mut requests_headers = Vec::with_capacity(batch_size); - for i in slot_start..slot_end { - requests_headers.push(utils::get_block_header(&client_http, &beacon_endpoint, 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 payload = Handle::ReplayBack(request_headers(client_http, beacon_endpoint, slot_start, slot_end).await?); let (message_id, _) = client .send_message(program_id.into(), payload, 700_000_000_000, 0) @@ -155,20 +144,9 @@ async fn replay_back_slots_start( return Some(()); }; - let mut requests_headers = Vec::with_capacity(SIZE_BATCH as usize); - for i in slot_start..slot_end { - requests_headers.push(utils::get_block_header(&client_http, beacon_endpoint, i)); - } - - let headers = futures::future::join_all(requests_headers) - .await - .into_iter() - .filter_map(|maybe_header| maybe_header.ok()) - .collect::>(); - let payload = Handle::ReplayBackStart { sync_update, - headers, + headers: request_headers(client_http, beacon_endpoint, slot_start, slot_end).await?, }; let (message_id, _) = client @@ -192,3 +170,29 @@ async fn replay_back_slots_start( HandleResult::ReplayBackStart(Ok(StatusStart::InProgress | StatusStart::Finished)) ).then_some(()) } + +pub async fn request_headers( + client_http: &Client, + beacon_endpoint: &str, + slot_start: Slot, + slot_end: Slot, +) -> Option> { + let batch_size = (slot_end - slot_start) as usize; + let mut requests_headers = Vec::with_capacity(batch_size); + for i in slot_start..slot_end { + requests_headers.push(utils::get_block_header(&client_http, &beacon_endpoint, i)); + } + + futures::future::join_all(requests_headers) + .await + .into_iter() + .filter(|maybe_header| { + match maybe_header { + Err(e) if e.downcast_ref::().is_some() => false, + _ => true, + } + }) + .collect::, _>>() + .map_err(|e| log::error!("Failed to fetch block headers ([{slot_start}; {slot_end})): {e:?}")) + .ok() +} diff --git a/checkpoints-relayer/src/utils/mod.rs b/checkpoints-relayer/src/utils/mod.rs index 7657a7aa..706862a6 100644 --- a/checkpoints-relayer/src/utils/mod.rs +++ b/checkpoints-relayer/src/utils/mod.rs @@ -11,7 +11,7 @@ use checkpoint_light_client_io::{ use anyhow::{Result as AnyResult, Error as AnyError}; use reqwest::{Client, RequestBuilder}; use ark_serialize::CanonicalDeserialize; -use std::cmp; +use std::{cmp, error::Error, fmt}; pub mod slots_batch; @@ -103,6 +103,23 @@ pub struct UpdateData { pub type UpdateResponse = Vec; +#[derive(Clone, Debug)] +pub struct ErrorNotFound; + +impl fmt::Display for ErrorNotFound { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt("Not found (404)", f) + } +} + +impl Error for ErrorNotFound {} + +#[derive(Deserialize)] +struct CodeResponse { + code: u64, + message: String, +} + pub async fn get(request_builder: RequestBuilder) -> AnyResult { let bytes = request_builder .send() @@ -112,7 +129,10 @@ pub async fn get(request_builder: RequestBuilder) -> AnyRes .await .map_err(AnyError::from)?; - Ok(serde_json::from_slice::(&bytes).map_err(AnyError::from)?) + match serde_json::from_slice::(&bytes) { + Ok(code_response) if code_response.code == 404 => Err(ErrorNotFound.into()), + _ => Ok(serde_json::from_slice::(&bytes).map_err(AnyError::from)?), + } } pub async fn get_bootstrap(client: &Client, rpc_url: &str, checkpoint: &str) -> AnyResult { From ac1db66749e58a2cb7be9175dbd47492a88a9c8b Mon Sep 17 00:00:00 2001 From: Georgy Shepelev Date: Thu, 25 Jul 2024 22:54:33 +0400 Subject: [PATCH 15/24] add metrics --- Cargo.lock | 2 + checkpoints-relayer/Cargo.toml | 2 + checkpoints-relayer/src/main.rs | 51 ++++++++++++++++-- checkpoints-relayer/src/metrics.rs | 84 ++++++++++++++++++++++++++++++ 4 files changed, 136 insertions(+), 3 deletions(-) create mode 100644 checkpoints-relayer/src/metrics.rs diff --git a/Cargo.lock b/Cargo.lock index 2ceeba57..84ea050e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1978,10 +1978,12 @@ dependencies = [ "log", "parity-scale-codec", "pretty_env_logger", + "prometheus", "reqwest 0.11.27", "serde", "serde_json", "tokio", + "utils-prometheus", ] [[package]] diff --git a/checkpoints-relayer/Cargo.toml b/checkpoints-relayer/Cargo.toml index b4be328b..64ed4b7f 100644 --- a/checkpoints-relayer/Cargo.toml +++ b/checkpoints-relayer/Cargo.toml @@ -19,3 +19,5 @@ pretty_env_logger.workspace = true gclient.workspace = true futures.workspace = true hex = { workspace = true, features = ["std"] } +prometheus.workspace = true +utils-prometheus.workspace = true diff --git a/checkpoints-relayer/src/main.rs b/checkpoints-relayer/src/main.rs index 09b5694d..2ad032bc 100644 --- a/checkpoints-relayer/src/main.rs +++ b/checkpoints-relayer/src/main.rs @@ -11,10 +11,12 @@ use gclient::{EventListener, EventProcessor, GearApi, WSAddress}; use checkpoint_light_client_io::{ethereum_common::{utils as eth_utils, SLOTS_PER_EPOCH}, tree_hash::Hash256, Handle, HandleResult, Slot, SyncCommitteeUpdate, G2}; use parity_scale_codec::Decode; use futures::{pin_mut, future::{self, Either}}; +use metrics::Message as MetricMessage; #[cfg(test)] mod tests; +mod metrics; mod sync_update; mod replay_back; mod utils; @@ -51,12 +53,19 @@ struct Args { /// 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. + /// separated by the ':' char #[arg( long, default_value = "//Alice" )] suri: String, + + /// Address of the prometheus endpoint + #[arg( + long = "prometheus-endpoint", + default_value = "http://127.0.0.1:9090" + )] + endpoint_prometheus: String, } enum Status { @@ -89,8 +98,11 @@ async fn main() { vara_domain, vara_port, suri, + endpoint_prometheus, } = Args::parse(); + let sender_metrics = metrics::spawn(endpoint_prometheus); + let program_id_no_prefix = match program_id.starts_with("0x") { true => &program_id[2..], false => &program_id, @@ -140,7 +152,11 @@ async fn main() { match sync_update::try_to_apply(&client, &mut listener, program_id, sync_update.clone()).await { Status::Ok | Status::NotActual => (), Status::Error => return, - Status::ReplayBackRequired { replayed_slot, checkpoint } => replay_back::execute(&client_http, &beacon_endpoint, &client, &mut listener, program_id, replayed_slot, checkpoint, sync_update).await, + Status::ReplayBackRequired { replayed_slot, checkpoint } => { + replay_back::execute(&client_http, &beacon_endpoint, &client, &mut listener, program_id, replayed_slot, checkpoint, sync_update).await; + log::info!("Exiting"); + return; + } } loop { @@ -163,14 +179,43 @@ async fn main() { } }; + let committee_update = sync_update.sync_committee_next_pub_keys.is_some(); let slot = sync_update.finalized_header.slot; if slot == slot_last { + let metric_message = MetricMessage { + slot, + committee_update, + processed: false, + }; + + if sender_metrics.send(metric_message).await.is_err() { + log::error!("Failed to update metrics. Exiting"); + return; + } + continue; } match sync_update::try_to_apply(&client, &mut listener, program_id, sync_update).await { - Status::Ok => { slot_last = slot; } + Status::Ok => { + slot_last = slot; + + let metric_message = MetricMessage { + slot, + committee_update, + processed: true, + }; + + if sender_metrics.send(metric_message).await.is_err() { + log::error!("Failed to update metrics. Exiting"); + return; + } + } Status::NotActual => (), + Status::ReplayBackRequired { .. } => { + log::info!("Exiting"); + return; + } _ => continue, } } diff --git a/checkpoints-relayer/src/metrics.rs b/checkpoints-relayer/src/metrics.rs new file mode 100644 index 00000000..a00ebec8 --- /dev/null +++ b/checkpoints-relayer/src/metrics.rs @@ -0,0 +1,84 @@ +use super::*; +use prometheus::{IntGauge, IntCounter}; +use utils_prometheus::{impl_metered_service, MetricsBuilder}; + +pub struct Message { + pub slot: Slot, + pub committee_update: bool, + pub processed: bool, +} + +impl_metered_service! { + struct EventListenerMetrics { + fetched_sync_update_slot: IntGauge, + total_fetched_finality_updates: IntCounter, + total_fetched_committee_updates: IntCounter, + processed_finality_updates: IntCounter, + processed_committee_updates: IntCounter, + } +} + +impl EventListenerMetrics { + fn new() -> Self { + Self::new_inner().expect("Failed to create metrics") + } + + fn new_inner() -> prometheus::Result { + Ok(Self { + fetched_sync_update_slot: IntGauge::new( + "checkpoints_relayer_fetched_sync_update_slot", + "checkpoints_relayer_fetched_sync_update_slot", + )?, + total_fetched_finality_updates: IntCounter::new( + "checkpoints_relayer_total_fetched_finality_updates", + "checkpoints_relayer_total_fetched_finality_updates", + )?, + total_fetched_committee_updates: IntCounter::new( + "checkpoints_relayer_total_fetched_committee_updates", + "checkpoints_relayer_total_fetched_committee_updates", + )?, + processed_finality_updates: IntCounter::new( + "checkpoints_relayer_processed_finality_updates", + "checkpoints_relayer_processed_finality_updates", + )?, + processed_committee_updates: IntCounter::new( + "checkpoints_relayer_processed_committee_updates", + "checkpoints_relayer_processed_committee_updates", + )?, + }) + } +} + +pub fn spawn(endpoint_prometheus: String) -> Sender { + let (sender, mut receiver) = mpsc::channel::(100); + + tokio::spawn(async move { + let service = EventListenerMetrics::new(); + MetricsBuilder::new() + .register_service(&service) + .build() + .run(endpoint_prometheus) + .await; + + loop { + let Some(metric_message) = receiver.recv().await else { + return; + }; + + service.fetched_sync_update_slot.set(i64::from_le_bytes(metric_message.slot.to_le_bytes())); + if metric_message.committee_update { + service.total_fetched_committee_updates.inc(); + if metric_message.processed { + service.processed_committee_updates.inc(); + } + } else { + service.total_fetched_finality_updates.inc(); + if metric_message.processed { + service.processed_finality_updates.inc(); + } + } + } + }); + + sender +} From 7637531b036d7cbec80744ae51a125cdc264ddf2 Mon Sep 17 00:00:00 2001 From: Georgy Shepelev Date: Mon, 29 Jul 2024 10:53:57 +0400 Subject: [PATCH 16/24] eliminate unwraps --- checkpoints-relayer/src/main.rs | 2 +- checkpoints-relayer/src/replay_back.rs | 9 ++++- checkpoints-relayer/src/sync_update.rs | 53 +++++++++++++++++--------- checkpoints-relayer/src/tests/mod.rs | 37 ++++++++++++------ checkpoints-relayer/src/utils/mod.rs | 33 ++++++++-------- 5 files changed, 84 insertions(+), 50 deletions(-) diff --git a/checkpoints-relayer/src/main.rs b/checkpoints-relayer/src/main.rs index 2ad032bc..d59e84c8 100644 --- a/checkpoints-relayer/src/main.rs +++ b/checkpoints-relayer/src/main.rs @@ -22,7 +22,7 @@ mod replay_back; mod utils; const SIZE_CHANNEL: usize = 100_000; -const SIZE_BATCH: u64 = 26 * SLOTS_PER_EPOCH; +const SIZE_BATCH: u64 = 44 * SLOTS_PER_EPOCH; const COUNT_FAILURE: usize = 3; const DELAY_SECS_FINALITY_REQUEST: u64 = 30; diff --git a/checkpoints-relayer/src/replay_back.rs b/checkpoints-relayer/src/replay_back.rs index 36828a2b..c33a870b 100644 --- a/checkpoints-relayer/src/replay_back.rs +++ b/checkpoints-relayer/src/replay_back.rs @@ -52,7 +52,14 @@ pub async fn execute( slot_start = slot_end; - let sync_update = utils::sync_update_from_update(update.data); + let Ok(signature) = ::deserialize_compressed( + &update.data.sync_aggregate.sync_committee_signature.0.0[..], + ) else { + log::error!("Failed to deserialize point on G2 (replay back)"); + return; + }; + + let sync_update = utils::sync_update_from_update(signature, update.data); if replay_back_slots_start(client_http, beacon_endpoint, client, listener, program_id, slots_batch_iter.next(), sync_update).await.is_none() { return; } diff --git a/checkpoints-relayer/src/sync_update.rs b/checkpoints-relayer/src/sync_update.rs index 2080e15c..dc92c233 100644 --- a/checkpoints-relayer/src/sync_update.rs +++ b/checkpoints-relayer/src/sync_update.rs @@ -60,25 +60,29 @@ pub fn spawn_receiver( } }; - let sync_update = if update.finalized_header.slot >= finality_update.finalized_header.slot { - utils::sync_update_from_update(update) + let reader_signature = if update.finalized_header.slot >= finality_update.finalized_header.slot { + &update.sync_aggregate.sync_committee_signature.0 .0[..] } else { - let signature = ::deserialize_compressed( - &finality_update.sync_aggregate.sync_committee_signature.0 .0[..], - ); - - let Ok(signature) = signature else { - log::error!("Failed to deserialize point on G2"); + &finality_update.sync_aggregate.sync_committee_signature.0 .0[..] + }; - failures += 1; - if failures >= COUNT_FAILURE { - return; - } + let Ok(signature) = ::deserialize_compressed( + reader_signature, + ) else { + log::error!("Failed to deserialize point on G2"); - time::sleep(delay).await; - continue; - }; - + failures += 1; + if failures >= COUNT_FAILURE { + return; + } + + time::sleep(delay).await; + continue; + }; + + let sync_update = if update.finalized_header.slot >= finality_update.finalized_header.slot { + utils::sync_update_from_update(signature, update) + } else { utils::sync_update_from_finality(signature, finality_update) }; @@ -121,9 +125,22 @@ pub async fn try_to_apply( return Status::Error; } }; - let result_decoded = HandleResult::decode(&mut &payload.unwrap()[..]).unwrap(); + let payload = match payload { + Ok(payload) => payload, + Err(e) => { + log::error!("Failed to get replay payload to SyncUpdate: {e:?}"); + return Status::Error; + } + }; + let result_decoded = match HandleResult::decode(&mut &payload[..]) { + Ok(result_decoded) => result_decoded, + Err(e) => { + log::error!("Failed to decode HandleResult of SyncUpdate: {e:?}"); + return Status::Error; + } + }; - log::debug!("Handle result = {result_decoded:?}"); + log::debug!("try_to_apply; result_decoded = {result_decoded:?}"); match result_decoded { HandleResult::SyncUpdate(Ok(())) => Status::Ok, diff --git a/checkpoints-relayer/src/tests/mod.rs b/checkpoints-relayer/src/tests/mod.rs index b13b911e..23e1c1b0 100644 --- a/checkpoints-relayer/src/tests/mod.rs +++ b/checkpoints-relayer/src/tests/mod.rs @@ -1,7 +1,7 @@ use checkpoint_light_client::WASM_BINARY; use checkpoint_light_client_io::{ ethereum_common::{ - base_types::{BytesFixed, FixedArray}, + base_types::BytesFixed, network::Network, utils as eth_utils, SLOTS_PER_EPOCH, }, @@ -88,11 +88,15 @@ async fn init_and_updating() -> Result<()> { println!("checkpoint slot = {}, hash = {}", update.finalized_header.slot, checkpoint_hex); let bootstrap = utils::get_bootstrap(&client_http, &rpc_url, &checkpoint_hex).await?; - let sync_update = utils::sync_update_from_update(update); + + let signature = ::deserialize_compressed( + &update.sync_aggregate.sync_committee_signature.0 .0[..], + ).unwrap(); + let sync_update = utils::sync_update_from_update(signature, update); println!("bootstrap slot = {}", bootstrap.header.slot); - let pub_keys = utils::map_public_keys(&bootstrap.current_sync_committee.pubkeys.0); + let pub_keys = utils::map_public_keys(&bootstrap.current_sync_committee.pubkeys); let network = match env::var("NETWORK") { Ok(network) if network == "Holesky" => Network::Holesky, Ok(network) if network == "Mainnet" => Network::Mainnet, @@ -100,7 +104,7 @@ async fn init_and_updating() -> Result<()> { }; let init = Init { network, - sync_committee_current_pub_keys: Box::new(FixedArray(pub_keys.try_into().unwrap())), + 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 @@ -134,7 +138,10 @@ async fn init_and_updating() -> Result<()> { match updates.pop() { Some(update) if updates.is_empty() && update.data.finalized_header.slot >= slot => { println!("update sync committee"); - let payload = Handle::SyncUpdate(utils::sync_update_from_update(update.data)); + let signature = ::deserialize_compressed( + &update.data.sync_aggregate.sync_committee_signature.0 .0[..], + ).unwrap(); + let payload = Handle::SyncUpdate(utils::sync_update_from_update(signature, update.data)); let gas_limit = client .calculate_handle_gas(None, program_id.into(), payload.encode(), 0, true) .await? @@ -222,7 +229,10 @@ async fn replaying_back() -> Result<()> { println!("bootstrap slot = {}", bootstrap.header.slot); println!("update slot = {}", update.finalized_header.slot); - let sync_update = utils::sync_update_from_update(update); + let signature = ::deserialize_compressed( + &update.sync_aggregate.sync_committee_signature.0 .0[..], + ).unwrap(); + let sync_update = utils::sync_update_from_update(signature, update); let slot_start = sync_update.finalized_header.slot; let slot_end = finality_update.finalized_header.slot; println!( @@ -230,10 +240,10 @@ async fn replaying_back() -> Result<()> { slot_end - slot_start ); - let pub_keys = utils::map_public_keys(&bootstrap.current_sync_committee.pubkeys.0); + let pub_keys = utils::map_public_keys(&bootstrap.current_sync_committee.pubkeys); let init = Init { network: Network::Sepolia, - sync_committee_current_pub_keys: Box::new(FixedArray(pub_keys.try_into().unwrap())), + 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 @@ -254,7 +264,7 @@ async fn replaying_back() -> Result<()> { println!(); println!(); - let batch_size = 26 * SLOTS_PER_EPOCH; + 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() { @@ -358,12 +368,15 @@ async fn sync_update_requires_replaying_back() -> Result<()> { let checkpoint_hex = hex::encode(checkpoint); let bootstrap = utils::get_bootstrap(&client_http, RPC_URL, &checkpoint_hex).await?; - let sync_update = utils::sync_update_from_update(update); + let signature = ::deserialize_compressed( + &update.sync_aggregate.sync_committee_signature.0 .0[..], + ).unwrap(); + let sync_update = utils::sync_update_from_update(signature, update); - let pub_keys = utils::map_public_keys(&bootstrap.current_sync_committee.pubkeys.0); + let pub_keys = utils::map_public_keys(&bootstrap.current_sync_committee.pubkeys); let init = Init { network: Network::Sepolia, - sync_committee_current_pub_keys: Box::new(FixedArray(pub_keys.try_into().unwrap())), + 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 diff --git a/checkpoints-relayer/src/utils/mod.rs b/checkpoints-relayer/src/utils/mod.rs index 706862a6..e695098e 100644 --- a/checkpoints-relayer/src/utils/mod.rs +++ b/checkpoints-relayer/src/utils/mod.rs @@ -1,12 +1,10 @@ use serde::{de::DeserializeOwned, Deserialize}; use checkpoint_light_client_io::{ ethereum_common::{ - base_types::{FixedArray, BytesFixed}, + base_types::{BytesFixed, FixedArray}, beacon::{BLSPubKey, Bytes32, SignedBeaconBlockHeader, SyncAggregate, SyncCommittee}, utils as eth_utils, - }, - BeaconBlockHeader, G2TypeInfo, G2, G1TypeInfo, ArkScale, G1, - SyncCommitteeUpdate, + }, ArkScale, BeaconBlockHeader, G1TypeInfo, G2TypeInfo, SyncCommitteeKeys, SyncCommitteeUpdate, G1, G2, SYNC_COMMITTEE_SIZE }; use anyhow::{Result as AnyResult, Error as AnyError}; use reqwest::{Client, RequestBuilder}; @@ -173,19 +171,25 @@ pub async fn get_finality_update(client: &Client, rpc_url: &str) -> AnyResult Vec> { - compressed_public_keys +pub fn map_public_keys( + compressed_public_keys: &FixedArray, +) -> Box { + let keys = compressed_public_keys + .0 .iter() .map(|BytesFixed(pub_key_compressed)| { let pub_key = ::deserialize_compressed_unchecked( &pub_key_compressed.0[..], ) - .unwrap(); + .expect("Public keys have the required size"); + let ark_scale: ArkScale = G1TypeInfo(pub_key).into(); ark_scale }) - .collect() + .collect::>(); + + Box::new(FixedArray(keys.try_into().expect("The size of keys array is guaranteed on the type level"))) } pub fn sync_update_from_finality( @@ -209,13 +213,8 @@ pub fn sync_update_from_finality( } } -pub fn sync_update_from_update(update: Update) -> SyncCommitteeUpdate { - let signature = ::deserialize_compressed( - &update.sync_aggregate.sync_committee_signature.0 .0[..], - ) - .unwrap(); - - let next_sync_committee_keys = map_public_keys(&update.next_sync_committee.pubkeys.0); +pub fn sync_update_from_update(signature: G2, update: Update) -> SyncCommitteeUpdate { + let next_sync_committee_keys = map_public_keys(&update.next_sync_committee.pubkeys); SyncCommitteeUpdate { signature_slot: update.signature_slot, @@ -224,9 +223,7 @@ pub fn sync_update_from_update(update: Update) -> SyncCommitteeUpdate { sync_aggregate: update.sync_aggregate, sync_committee_next_aggregate_pubkey: Some(update.next_sync_committee.aggregate_pubkey), sync_committee_signature: G2TypeInfo(signature).into(), - sync_committee_next_pub_keys: Some(Box::new(FixedArray( - next_sync_committee_keys.try_into().unwrap(), - ))), + sync_committee_next_pub_keys: Some(next_sync_committee_keys), sync_committee_next_branch: Some( update .next_sync_committee_branch From 56e0465174e1e08f0ec0866612b268810f78361e Mon Sep 17 00:00:00 2001 From: Georgy Shepelev Date: Mon, 29 Jul 2024 10:57:50 +0400 Subject: [PATCH 17/24] cargo fmt & clippy --- checkpoints-relayer/src/main.rs | 76 ++-- checkpoints-relayer/src/metrics.rs | 6 +- checkpoints-relayer/src/replay_back.rs | 373 +++++++++++-------- checkpoints-relayer/src/sync_update.rs | 114 +++--- checkpoints-relayer/src/tests/mod.rs | 60 +-- checkpoints-relayer/src/utils/mod.rs | 33 +- checkpoints-relayer/src/utils/slots_batch.rs | 8 +- 7 files changed, 399 insertions(+), 271 deletions(-) diff --git a/checkpoints-relayer/src/main.rs b/checkpoints-relayer/src/main.rs index d59e84c8..a419e290 100644 --- a/checkpoints-relayer/src/main.rs +++ b/checkpoints-relayer/src/main.rs @@ -1,24 +1,31 @@ +use checkpoint_light_client_io::{ + ethereum_common::{utils as eth_utils, SLOTS_PER_EPOCH}, + tree_hash::Hash256, + Handle, HandleResult, Slot, SyncCommitteeUpdate, G2, +}; use clap::Parser; +use futures::{ + future::{self, Either}, + pin_mut, +}; +use gclient::{EventListener, EventProcessor, GearApi, WSAddress}; +use metrics::Message as MetricMessage; +use parity_scale_codec::Decode; +use pretty_env_logger::env_logger::fmt::TimestampPrecision; +use reqwest::Client; use tokio::{ + signal::unix::{self, SignalKind}, sync::mpsc::{self, Sender}, time::{self, Duration}, - signal::unix::{self, SignalKind}, }; -use reqwest::Client; use utils::{slots_batch::Iter as SlotsBatchIter, MAX_REQUEST_LIGHT_CLIENT_UPDATES}; -use pretty_env_logger::env_logger::fmt::TimestampPrecision; -use gclient::{EventListener, EventProcessor, GearApi, WSAddress}; -use checkpoint_light_client_io::{ethereum_common::{utils as eth_utils, SLOTS_PER_EPOCH}, tree_hash::Hash256, Handle, HandleResult, Slot, SyncCommitteeUpdate, G2}; -use parity_scale_codec::Decode; -use futures::{pin_mut, future::{self, Either}}; -use metrics::Message as MetricMessage; #[cfg(test)] mod tests; mod metrics; -mod sync_update; mod replay_back; +mod sync_update; mod utils; const SIZE_CHANNEL: usize = 100_000; @@ -37,34 +44,22 @@ struct Args { beacon_endpoint: String, /// Domain of the VARA RPC endpoint - #[arg( - long, - default_value = "ws://127.0.0.1" - )] + #[arg(long, default_value = "ws://127.0.0.1")] vara_domain: String, /// Port of the VARA RPC endpoint - #[arg( - long, - default_value = "9944" - )] + #[arg(long, default_value = "9944")] vara_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" - )] + #[arg(long, default_value = "//Alice")] suri: String, /// Address of the prometheus endpoint - #[arg( - long = "prometheus-endpoint", - default_value = "http://127.0.0.1:9090" - )] + #[arg(long = "prometheus-endpoint", default_value = "http://127.0.0.1:9090")] endpoint_prometheus: String, } @@ -110,15 +105,21 @@ async fn main() { let Some(program_id) = hex::decode(program_id_no_prefix) .ok() - .and_then(|bytes| <[u8; 32]>::try_from(bytes).ok()) else { - log::error!("Incorrect ProgramId"); - return; + .and_then(|bytes| <[u8; 32]>::try_from(bytes).ok()) + else { + log::error!("Incorrect ProgramId"); + return; }; let (sender, mut receiver) = mpsc::channel(SIZE_CHANNEL); let client_http = Client::new(); - sync_update::spawn_receiver(client_http.clone(), beacon_endpoint.clone(), sender, Duration::from_secs(DELAY_SECS_FINALITY_REQUEST)); + sync_update::spawn_receiver( + client_http.clone(), + beacon_endpoint.clone(), + sender, + Duration::from_secs(DELAY_SECS_FINALITY_REQUEST), + ); let client = match GearApi::init_with(WSAddress::new(vara_domain, vara_port), suri).await { Ok(client) => client, @@ -152,8 +153,21 @@ async fn main() { match sync_update::try_to_apply(&client, &mut listener, program_id, sync_update.clone()).await { Status::Ok | Status::NotActual => (), Status::Error => return, - Status::ReplayBackRequired { replayed_slot, checkpoint } => { - replay_back::execute(&client_http, &beacon_endpoint, &client, &mut listener, program_id, replayed_slot, checkpoint, sync_update).await; + Status::ReplayBackRequired { + replayed_slot, + checkpoint, + } => { + replay_back::execute( + &client_http, + &beacon_endpoint, + &client, + &mut listener, + program_id, + replayed_slot, + checkpoint, + sync_update, + ) + .await; log::info!("Exiting"); return; } diff --git a/checkpoints-relayer/src/metrics.rs b/checkpoints-relayer/src/metrics.rs index a00ebec8..5b2712e5 100644 --- a/checkpoints-relayer/src/metrics.rs +++ b/checkpoints-relayer/src/metrics.rs @@ -1,5 +1,5 @@ use super::*; -use prometheus::{IntGauge, IntCounter}; +use prometheus::{IntCounter, IntGauge}; use utils_prometheus::{impl_metered_service, MetricsBuilder}; pub struct Message { @@ -65,7 +65,9 @@ pub fn spawn(endpoint_prometheus: String) -> Sender { return; }; - service.fetched_sync_update_slot.set(i64::from_le_bytes(metric_message.slot.to_le_bytes())); + service + .fetched_sync_update_slot + .set(i64::from_le_bytes(metric_message.slot.to_le_bytes())); if metric_message.committee_update { service.total_fetched_committee_updates.inc(); if metric_message.processed { diff --git a/checkpoints-relayer/src/replay_back.rs b/checkpoints-relayer/src/replay_back.rs index c33a870b..88f65dbd 100644 --- a/checkpoints-relayer/src/replay_back.rs +++ b/checkpoints-relayer/src/replay_back.rs @@ -1,181 +1,259 @@ use super::*; -use checkpoint_light_client_io::{replay_back::{Status, StatusStart}, BeaconBlockHeader}; +use checkpoint_light_client_io::{ + replay_back::{Status, StatusStart}, + BeaconBlockHeader, +}; use utils::ErrorNotFound; +#[allow(clippy::too_many_arguments)] pub async fn execute( - client_http: &Client, - beacon_endpoint: &str, - client: &GearApi, - listener: &mut EventListener, - program_id: [u8; 32], - replayed_slot: Option, - checkpoint: (Slot, Hash256), - sync_update: SyncCommitteeUpdate, + client_http: &Client, + beacon_endpoint: &str, + client: &GearApi, + listener: &mut EventListener, + program_id: [u8; 32], + replayed_slot: Option, + checkpoint: (Slot, Hash256), + sync_update: SyncCommitteeUpdate, ) { - log::info!("Replaying back started"); + log::info!("Replaying back started"); - let (mut slot_start, _) = checkpoint; - if let Some(slot_end) = replayed_slot { - let Some(slots_batch_iter) = SlotsBatchIter::new(slot_start, slot_end, SIZE_BATCH) else { - log::error!("Failed to create slots_batch::Iter with slot_start = {slot_start}, slot_end = {slot_end}."); + let (mut slot_start, _) = checkpoint; + if let Some(slot_end) = replayed_slot { + let Some(slots_batch_iter) = SlotsBatchIter::new(slot_start, slot_end, SIZE_BATCH) else { + log::error!("Failed to create slots_batch::Iter with slot_start = {slot_start}, slot_end = {slot_end}."); - return; - }; + return; + }; - replay_back_slots(client_http, beacon_endpoint, client, listener, program_id, slots_batch_iter) - .await; + replay_back_slots( + client_http, + beacon_endpoint, + client, + listener, + program_id, + slots_batch_iter, + ) + .await; - log::info!("The ongoing replaying back finished"); + log::info!("The ongoing replaying back finished"); - return; - } + return; + } - let period_start = 1 + eth_utils::calculate_period(slot_start); - let updates = match utils::get_updates(&client_http, beacon_endpoint, period_start, MAX_REQUEST_LIGHT_CLIENT_UPDATES).await - { - Ok(updates) => updates, - Err(e) => { - log::error!("Failed to get updates for period {period_start}: {e:?}"); + let period_start = 1 + eth_utils::calculate_period(slot_start); + let updates = match utils::get_updates( + client_http, + beacon_endpoint, + period_start, + MAX_REQUEST_LIGHT_CLIENT_UPDATES, + ) + .await + { + Ok(updates) => updates, + Err(e) => { + log::error!("Failed to get updates for period {period_start}: {e:?}"); - return; - } - }; + return; + } + }; - let slot_last = sync_update.finalized_header.slot; - for update in updates { - let slot_end = update.data.finalized_header.slot; - let Some(mut slots_batch_iter) = SlotsBatchIter::new(slot_start, slot_end, SIZE_BATCH) else { - log::error!("Failed to create slots_batch::Iter with slot_start = {slot_start}, slot_end (update) = {slot_end}."); + let slot_last = sync_update.finalized_header.slot; + for update in updates { + let slot_end = update.data.finalized_header.slot; + let Some(mut slots_batch_iter) = SlotsBatchIter::new(slot_start, slot_end, SIZE_BATCH) + else { + log::error!("Failed to create slots_batch::Iter with slot_start = {slot_start}, slot_end (update) = {slot_end}."); - return; - }; + return; + }; - slot_start = slot_end; + slot_start = slot_end; - let Ok(signature) = ::deserialize_compressed( - &update.data.sync_aggregate.sync_committee_signature.0.0[..], + let Ok(signature) = ::deserialize_compressed( + &update.data.sync_aggregate.sync_committee_signature.0 .0[..], ) else { log::error!("Failed to deserialize point on G2 (replay back)"); return; }; - let sync_update = utils::sync_update_from_update(signature, update.data); - if replay_back_slots_start(client_http, beacon_endpoint, client, listener, program_id, slots_batch_iter.next(), sync_update).await.is_none() { - return; - } - - if replay_back_slots(client_http, beacon_endpoint, client, listener, program_id, slots_batch_iter).await.is_none() { - return; - } - - if slot_end == slot_last { - // the provided sync_update is a sync committee update - return; - } - } - - let Some(mut slots_batch_iter) = SlotsBatchIter::new(slot_start, slot_last, SIZE_BATCH) else { - log::error!("Failed to create slots_batch::Iter with slot_start = {slot_start}, slot_last = {slot_last}."); - - return; - }; + let sync_update = utils::sync_update_from_update(signature, update.data); + if replay_back_slots_start( + client_http, + beacon_endpoint, + client, + listener, + program_id, + slots_batch_iter.next(), + sync_update, + ) + .await + .is_none() + { + return; + } + + if replay_back_slots( + client_http, + beacon_endpoint, + client, + listener, + program_id, + slots_batch_iter, + ) + .await + .is_none() + { + return; + } - if replay_back_slots_start(client_http, beacon_endpoint, client, listener, program_id, slots_batch_iter.next(), sync_update).await.is_none() { - return; - } + if slot_end == slot_last { + // the provided sync_update is a sync committee update + return; + } + } - replay_back_slots(client_http, beacon_endpoint, client, listener, program_id, slots_batch_iter).await; + let Some(mut slots_batch_iter) = SlotsBatchIter::new(slot_start, slot_last, SIZE_BATCH) else { + log::error!("Failed to create slots_batch::Iter with slot_start = {slot_start}, slot_last = {slot_last}."); + + return; + }; + + if replay_back_slots_start( + client_http, + beacon_endpoint, + client, + listener, + program_id, + slots_batch_iter.next(), + sync_update, + ) + .await + .is_none() + { + return; + } - log::info!("Replaying back finished"); + replay_back_slots( + client_http, + beacon_endpoint, + client, + listener, + program_id, + slots_batch_iter, + ) + .await; + + log::info!("Replaying back finished"); } async fn replay_back_slots( - client_http: &Client, - beacon_endpoint: &str, - client: &GearApi, - listener: &mut EventListener, - program_id: [u8; 32], - slots_batch_iter: SlotsBatchIter, + client_http: &Client, + beacon_endpoint: &str, + client: &GearApi, + listener: &mut EventListener, + program_id: [u8; 32], + slots_batch_iter: SlotsBatchIter, ) -> Option<()> { - for (slot_start, slot_end) in slots_batch_iter { - replay_back_slots_inner(client_http, beacon_endpoint, client, listener, program_id, slot_start, slot_end) - .await?; - } + for (slot_start, slot_end) in slots_batch_iter { + replay_back_slots_inner( + client_http, + beacon_endpoint, + client, + listener, + program_id, + slot_start, + slot_end, + ) + .await?; + } - Some(()) + Some(()) } async fn replay_back_slots_inner( - client_http: &Client, - beacon_endpoint: &str, - client: &GearApi, - listener: &mut EventListener, - program_id: [u8; 32], - slot_start: Slot, - slot_end: Slot, + client_http: &Client, + beacon_endpoint: &str, + client: &GearApi, + listener: &mut EventListener, + program_id: [u8; 32], + slot_start: Slot, + slot_end: Slot, ) -> Option<()> { - let payload = Handle::ReplayBack(request_headers(client_http, beacon_endpoint, slot_start, slot_end).await?); - - let (message_id, _) = client - .send_message(program_id.into(), payload, 700_000_000_000, 0) - .await - .map_err(|e| log::error!("Failed to send ReplayBack message: {e:?}")) - .ok()?; - - let (_message_id, payload, _value) = listener.reply_bytes_on(message_id) - .await - .map_err(|e| log::error!("Failed to get reply to ReplayBack message: {e:?}")) - .ok()?; - let payload = payload.map_err(|e| log::error!("Failed to get replay payload to ReplayBack: {e:?}")).ok()?; - let result_decoded = HandleResult::decode(&mut &payload[..]) - .map_err(|e| log::error!("Failed to decode HandleResult of ReplayBack: {e:?}")).ok()?; - - log::debug!("replay_back_slots_inner; result_decoded = {result_decoded:?}"); - - matches!( - result_decoded, - HandleResult::ReplayBack(Some(Status::InProcess | Status::Finished)) - ).then_some(()) + let payload = Handle::ReplayBack( + request_headers(client_http, beacon_endpoint, slot_start, slot_end).await?, + ); + + let (message_id, _) = client + .send_message(program_id.into(), payload, 700_000_000_000, 0) + .await + .map_err(|e| log::error!("Failed to send ReplayBack message: {e:?}")) + .ok()?; + + let (_message_id, payload, _value) = listener + .reply_bytes_on(message_id) + .await + .map_err(|e| log::error!("Failed to get reply to ReplayBack message: {e:?}")) + .ok()?; + let payload = payload + .map_err(|e| log::error!("Failed to get replay payload to ReplayBack: {e:?}")) + .ok()?; + let result_decoded = HandleResult::decode(&mut &payload[..]) + .map_err(|e| log::error!("Failed to decode HandleResult of ReplayBack: {e:?}")) + .ok()?; + + log::debug!("replay_back_slots_inner; result_decoded = {result_decoded:?}"); + + matches!( + result_decoded, + HandleResult::ReplayBack(Some(Status::InProcess | Status::Finished)) + ) + .then_some(()) } async fn replay_back_slots_start( - client_http: &Client, - beacon_endpoint: &str, - client: &GearApi, - listener: &mut EventListener, - program_id: [u8; 32], - slots: Option<(Slot, Slot)>, - sync_update: SyncCommitteeUpdate, + client_http: &Client, + beacon_endpoint: &str, + client: &GearApi, + listener: &mut EventListener, + program_id: [u8; 32], + slots: Option<(Slot, Slot)>, + sync_update: SyncCommitteeUpdate, ) -> Option<()> { - let Some((slot_start, slot_end)) = slots else { - return Some(()); - }; - - let payload = Handle::ReplayBackStart { - sync_update, - headers: request_headers(client_http, beacon_endpoint, slot_start, slot_end).await?, - }; - - let (message_id, _) = client - .send_message(program_id.into(), payload, 700_000_000_000, 0) - .await - .map_err(|e| log::error!("Failed to send ReplayBackStart message: {e:?}")) - .ok()?; - - let (_message_id, payload, _value) = listener.reply_bytes_on(message_id) - .await - .map_err(|e| log::error!("Failed to get reply to ReplayBackStart message: {e:?}")) - .ok()?; - let payload = payload.map_err(|e| log::error!("Failed to get replay payload to ReplayBackStart: {e:?}")).ok()?; - let result_decoded = HandleResult::decode(&mut &payload[..]) - .map_err(|e| log::error!("Failed to decode HandleResult of ReplayBackStart: {e:?}")).ok()?; - - log::debug!("replay_back_slots_start; result_decoded = {result_decoded:?}"); - - matches!( - result_decoded, - HandleResult::ReplayBackStart(Ok(StatusStart::InProgress | StatusStart::Finished)) - ).then_some(()) + let Some((slot_start, slot_end)) = slots else { + return Some(()); + }; + + let payload = Handle::ReplayBackStart { + sync_update, + headers: request_headers(client_http, beacon_endpoint, slot_start, slot_end).await?, + }; + + let (message_id, _) = client + .send_message(program_id.into(), payload, 700_000_000_000, 0) + .await + .map_err(|e| log::error!("Failed to send ReplayBackStart message: {e:?}")) + .ok()?; + + let (_message_id, payload, _value) = listener + .reply_bytes_on(message_id) + .await + .map_err(|e| log::error!("Failed to get reply to ReplayBackStart message: {e:?}")) + .ok()?; + let payload = payload + .map_err(|e| log::error!("Failed to get replay payload to ReplayBackStart: {e:?}")) + .ok()?; + let result_decoded = HandleResult::decode(&mut &payload[..]) + .map_err(|e| log::error!("Failed to decode HandleResult of ReplayBackStart: {e:?}")) + .ok()?; + + log::debug!("replay_back_slots_start; result_decoded = {result_decoded:?}"); + + matches!( + result_decoded, + HandleResult::ReplayBackStart(Ok(StatusStart::InProgress | StatusStart::Finished)) + ) + .then_some(()) } pub async fn request_headers( @@ -187,19 +265,16 @@ pub async fn request_headers( let batch_size = (slot_end - slot_start) as usize; let mut requests_headers = Vec::with_capacity(batch_size); for i in slot_start..slot_end { - requests_headers.push(utils::get_block_header(&client_http, &beacon_endpoint, i)); + requests_headers.push(utils::get_block_header(client_http, beacon_endpoint, i)); } futures::future::join_all(requests_headers) .await .into_iter() - .filter(|maybe_header| { - match maybe_header { - Err(e) if e.downcast_ref::().is_some() => false, - _ => true, - } - }) + .filter(|maybe_header| !matches!(maybe_header, Err(e) if e.downcast_ref::().is_some())) .collect::, _>>() - .map_err(|e| log::error!("Failed to fetch block headers ([{slot_start}; {slot_end})): {e:?}")) + .map_err(|e| { + log::error!("Failed to fetch block headers ([{slot_start}; {slot_end})): {e:?}") + }) .ok() } diff --git a/checkpoints-relayer/src/sync_update.rs b/checkpoints-relayer/src/sync_update.rs index dc92c233..b5ef9fb9 100644 --- a/checkpoints-relayer/src/sync_update.rs +++ b/checkpoints-relayer/src/sync_update.rs @@ -13,37 +13,39 @@ pub fn spawn_receiver( let mut failures = 0; loop { - let finality_update = match utils::get_finality_update(&client_http, &beacon_endpoint).await { - Ok(finality_update) => finality_update, + let finality_update = + match utils::get_finality_update(&client_http, &beacon_endpoint).await { + Ok(finality_update) => finality_update, - Err(e) => { - log::error!("Unable to fetch FinalityUpdate: {e:?}"); + Err(e) => { + log::error!("Unable to fetch FinalityUpdate: {e:?}"); - failures += 1; - if failures >= COUNT_FAILURE { - return; - } + failures += 1; + if failures >= COUNT_FAILURE { + return; + } - time::sleep(delay).await; - continue; - } - }; + time::sleep(delay).await; + continue; + } + }; let period = eth_utils::calculate_period(finality_update.finalized_header.slot); - let mut updates = match utils::get_updates(&client_http, &beacon_endpoint, period, 1).await { - Ok(updates) => updates, - Err(e) => { - log::error!("Unable to fetch Updates: {e:?}"); - - failures += 1; - if failures >= COUNT_FAILURE { - return; + let mut updates = + match utils::get_updates(&client_http, &beacon_endpoint, period, 1).await { + Ok(updates) => updates, + Err(e) => { + log::error!("Unable to fetch Updates: {e:?}"); + + failures += 1; + if failures >= COUNT_FAILURE { + return; + } + + time::sleep(delay).await; + continue; } - - time::sleep(delay).await; - continue; - } - }; + }; let update = match updates.pop() { Some(update) if updates.is_empty() => update.data, @@ -60,11 +62,12 @@ pub fn spawn_receiver( } }; - let reader_signature = if update.finalized_header.slot >= finality_update.finalized_header.slot { - &update.sync_aggregate.sync_committee_signature.0 .0[..] - } else { - &finality_update.sync_aggregate.sync_committee_signature.0 .0[..] - }; + let reader_signature = + if update.finalized_header.slot >= finality_update.finalized_header.slot { + &update.sync_aggregate.sync_committee_signature.0 .0[..] + } else { + &finality_update.sync_aggregate.sync_committee_signature.0 .0[..] + }; let Ok(signature) = ::deserialize_compressed( reader_signature, @@ -80,11 +83,12 @@ pub fn spawn_receiver( continue; }; - let sync_update = if update.finalized_header.slot >= finality_update.finalized_header.slot { - utils::sync_update_from_update(signature, update) - } else { - utils::sync_update_from_finality(signature, finality_update) - }; + let sync_update = + if update.finalized_header.slot >= finality_update.finalized_header.slot { + utils::sync_update_from_update(signature, update) + } else { + utils::sync_update_from_finality(signature, finality_update) + }; if sender.send(sync_update).await.is_err() { return; @@ -105,26 +109,23 @@ pub async fn try_to_apply( let (message_id, _) = match client .send_message(program_id.into(), payload, 700_000_000_000, 0) .await - { - Ok(result) => result, - Err(e) => { - log::error!("Failed to send message: {e:?}"); + { + Ok(result) => result, + Err(e) => { + log::error!("Failed to send message: {e:?}"); - return Status::Error; - } - }; + return Status::Error; + } + }; - let (_message_id, payload, _value) = match listener - .reply_bytes_on(message_id) - .await - { - Ok(result) => result, - Err(e) => { - log::error!("Failed to get reply: {e:?}"); + let (_message_id, payload, _value) = match listener.reply_bytes_on(message_id).await { + Ok(result) => result, + Err(e) => { + log::error!("Failed to get reply: {e:?}"); - return Status::Error; - } - }; + return Status::Error; + } + }; let payload = match payload { Ok(payload) => payload, Err(e) => { @@ -147,8 +148,11 @@ pub async fn try_to_apply( HandleResult::SyncUpdate(Err(Error::NotActual)) => Status::NotActual, HandleResult::SyncUpdate(Err(Error::ReplayBackRequired { replayed_slot, - checkpoint - })) => Status::ReplayBackRequired { replayed_slot, checkpoint }, + checkpoint, + })) => Status::ReplayBackRequired { + replayed_slot, + checkpoint, + }, _ => Status::Error, } - } +} diff --git a/checkpoints-relayer/src/tests/mod.rs b/checkpoints-relayer/src/tests/mod.rs index 23e1c1b0..01a20bf6 100644 --- a/checkpoints-relayer/src/tests/mod.rs +++ b/checkpoints-relayer/src/tests/mod.rs @@ -1,20 +1,18 @@ +use crate::utils::{self, slots_batch, FinalityUpdateResponse}; use checkpoint_light_client::WASM_BINARY; use checkpoint_light_client_io::{ ethereum_common::{ - base_types::BytesFixed, - network::Network, - utils as eth_utils, SLOTS_PER_EPOCH, + base_types::BytesFixed, network::Network, utils as eth_utils, SLOTS_PER_EPOCH, }, replay_back, sync_update, tree_hash::TreeHash, Handle, HandleResult, Init, G2, }; use gclient::{EventListener, EventProcessor, GearApi, Result}; -use reqwest::Client; -use tokio::time::{self, Duration}; use parity_scale_codec::{Decode, Encode}; -use crate::utils::{self, FinalityUpdateResponse, slots_batch}; +use reqwest::Client; use std::env; +use tokio::time::{self, Duration}; const RPC_URL: &str = "http://127.0.0.1:5052"; @@ -67,15 +65,17 @@ async fn upload_program( async fn init_and_updating() -> Result<()> { let client_http = Client::new(); - let rpc_url = env::var("RPC_URL") - .unwrap_or(RPC_URL.into()); + let rpc_url = env::var("RPC_URL").unwrap_or(RPC_URL.into()); // use the latest finality header as a checkpoint for bootstrapping let finality_update = utils::get_finality_update(&client_http, &rpc_url).await?; let current_period = eth_utils::calculate_period(finality_update.finalized_header.slot); let mut updates = utils::get_updates(&client_http, &rpc_url, current_period, 1).await?; - println!("finality_update slot = {}, period = {}", finality_update.finalized_header.slot, current_period); + 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, @@ -85,13 +85,17 @@ async fn init_and_updating() -> Result<()> { let checkpoint = update.finalized_header.tree_hash_root(); let checkpoint_hex = hex::encode(checkpoint); - println!("checkpoint slot = {}, hash = {}", update.finalized_header.slot, checkpoint_hex); + println!( + "checkpoint slot = {}, hash = {}", + update.finalized_header.slot, checkpoint_hex + ); let bootstrap = utils::get_bootstrap(&client_http, &rpc_url, &checkpoint_hex).await?; let signature = ::deserialize_compressed( &update.sync_aggregate.sync_committee_signature.0 .0[..], - ).unwrap(); + ) + .unwrap(); let sync_update = utils::sync_update_from_update(signature, update); println!("bootstrap slot = {}", bootstrap.header.slot); @@ -138,10 +142,13 @@ async fn init_and_updating() -> Result<()> { 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(utils::sync_update_from_update(signature, update.data)); + let signature = + ::deserialize_compressed( + &update.data.sync_aggregate.sync_committee_signature.0 .0[..], + ) + .unwrap(); + let payload = + Handle::SyncUpdate(utils::sync_update_from_update(signature, update.data)); let gas_limit = client .calculate_handle_gas(None, program_id.into(), payload.encode(), 0, true) .await? @@ -173,7 +180,8 @@ async fn init_and_updating() -> Result<()> { continue; }; - let payload = Handle::SyncUpdate(utils::sync_update_from_finality(signature, update)); + let payload = + Handle::SyncUpdate(utils::sync_update_from_finality(signature, update)); let gas_limit = client .calculate_handle_gas(None, program_id.into(), payload.encode(), 0, true) @@ -231,7 +239,8 @@ async fn replaying_back() -> Result<()> { println!("update slot = {}", update.finalized_header.slot); let signature = ::deserialize_compressed( &update.sync_aggregate.sync_committee_signature.0 .0[..], - ).unwrap(); + ) + .unwrap(); let sync_update = utils::sync_update_from_update(signature, update); let slot_start = sync_update.finalized_header.slot; let slot_end = finality_update.finalized_header.slot; @@ -278,27 +287,27 @@ async fn replaying_back() -> Result<()> { .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: utils::sync_update_from_finality(signature, finality_update), headers, }; - + let gas_limit = client .calculate_handle_gas(None, program_id.into(), payload.encode(), 0, true) .await? .min_limit; println!("ReplayBackStart 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!( @@ -336,7 +345,9 @@ async fn replaying_back() -> Result<()> { let result_decoded = HandleResult::decode(&mut &payload.unwrap()[..]).unwrap(); assert!(matches!( result_decoded, - HandleResult::ReplayBack(Some(replay_back::Status::InProcess | replay_back::Status::Finished)) + HandleResult::ReplayBack(Some( + replay_back::Status::InProcess | replay_back::Status::Finished + )) )); } @@ -370,7 +381,8 @@ async fn sync_update_requires_replaying_back() -> Result<()> { let bootstrap = utils::get_bootstrap(&client_http, RPC_URL, &checkpoint_hex).await?; let signature = ::deserialize_compressed( &update.sync_aggregate.sync_committee_signature.0 .0[..], - ).unwrap(); + ) + .unwrap(); let sync_update = utils::sync_update_from_update(signature, update); let pub_keys = utils::map_public_keys(&bootstrap.current_sync_committee.pubkeys); diff --git a/checkpoints-relayer/src/utils/mod.rs b/checkpoints-relayer/src/utils/mod.rs index e695098e..f2b2951c 100644 --- a/checkpoints-relayer/src/utils/mod.rs +++ b/checkpoints-relayer/src/utils/mod.rs @@ -1,14 +1,16 @@ -use serde::{de::DeserializeOwned, Deserialize}; +use anyhow::{Error as AnyError, Result as AnyResult}; +use ark_serialize::CanonicalDeserialize; use checkpoint_light_client_io::{ ethereum_common::{ base_types::{BytesFixed, FixedArray}, beacon::{BLSPubKey, Bytes32, SignedBeaconBlockHeader, SyncAggregate, SyncCommittee}, utils as eth_utils, - }, ArkScale, BeaconBlockHeader, G1TypeInfo, G2TypeInfo, SyncCommitteeKeys, SyncCommitteeUpdate, G1, G2, SYNC_COMMITTEE_SIZE + }, + ArkScale, BeaconBlockHeader, G1TypeInfo, G2TypeInfo, SyncCommitteeKeys, SyncCommitteeUpdate, + G1, G2, SYNC_COMMITTEE_SIZE, }; -use anyhow::{Result as AnyResult, Error as AnyError}; use reqwest::{Client, RequestBuilder}; -use ark_serialize::CanonicalDeserialize; +use serde::{de::DeserializeOwned, Deserialize}; use std::{cmp, error::Error, fmt}; pub mod slots_batch; @@ -133,7 +135,11 @@ pub async fn get(request_builder: RequestBuilder) -> AnyRes } } -pub async fn get_bootstrap(client: &Client, rpc_url: &str, checkpoint: &str) -> AnyResult { +pub async fn get_bootstrap( + client: &Client, + rpc_url: &str, + checkpoint: &str, +) -> AnyResult { let checkpoint_no_prefix = match checkpoint.starts_with("0x") { true => &checkpoint[2..], false => checkpoint, @@ -146,7 +152,12 @@ pub async fn get_bootstrap(client: &Client, rpc_url: &str, checkpoint: &str) -> .map(|response| response.data) } -pub async fn get_updates(client: &Client, rpc_url: &str, period: u64, count: u8) -> AnyResult { +pub async fn get_updates( + client: &Client, + rpc_url: &str, + period: u64, + count: u8, +) -> AnyResult { let count = cmp::min(count, MAX_REQUEST_LIGHT_CLIENT_UPDATES); let url = format!( "{rpc_url}/eth/v1/beacon/light_client/updates?start_period={period}&count={count}", @@ -155,7 +166,11 @@ pub async fn get_updates(client: &Client, rpc_url: &str, period: u64, count: u8) get::(client.get(&url)).await } -pub async fn get_block_header(client: &Client, rpc_url: &str, slot: u64) -> AnyResult { +pub async fn get_block_header( + client: &Client, + rpc_url: &str, + slot: u64, +) -> AnyResult { let url = format!("{rpc_url}/eth/v1/beacon/headers/{slot}"); get::(client.get(&url)) @@ -189,7 +204,9 @@ pub fn map_public_keys( }) .collect::>(); - Box::new(FixedArray(keys.try_into().expect("The size of keys array is guaranteed on the type level"))) + Box::new(FixedArray(keys.try_into().expect( + "The size of keys array is guaranteed on the type level", + ))) } pub fn sync_update_from_finality( diff --git a/checkpoints-relayer/src/utils/slots_batch.rs b/checkpoints-relayer/src/utils/slots_batch.rs index 44c78325..c6f6cb10 100644 --- a/checkpoints-relayer/src/utils/slots_batch.rs +++ b/checkpoints-relayer/src/utils/slots_batch.rs @@ -2,7 +2,9 @@ use checkpoint_light_client_io::Slot; /// Iterator produces right open intervals of the specific size in backward direction. pub struct Iter { - slot_start: Slot, slot_end: Slot, batch_size: Slot, + slot_start: Slot, + slot_end: Slot, + batch_size: Slot, } impl Iter { @@ -12,7 +14,9 @@ impl Iter { } Some(Self { - slot_start, slot_end, batch_size, + slot_start, + slot_end, + batch_size, }) } } From 9389122f95843c79ebc21667d7d1197b56e29715 Mon Sep 17 00:00:00 2001 From: Georgy Shepelev Date: Mon, 29 Jul 2024 11:07:33 +0400 Subject: [PATCH 18/24] add allow(dead_code) to suppress awkward warnings --- checkpoints-relayer/src/utils/mod.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/checkpoints-relayer/src/utils/mod.rs b/checkpoints-relayer/src/utils/mod.rs index f2b2951c..31ccbb32 100644 --- a/checkpoints-relayer/src/utils/mod.rs +++ b/checkpoints-relayer/src/utils/mod.rs @@ -40,6 +40,7 @@ pub struct BeaconBlockHeaderData { pub header: SignedBeaconBlockHeader, } +#[allow(dead_code)] #[derive(Deserialize, Debug)] pub struct Bootstrap { #[serde(deserialize_with = "deserialize_header")] @@ -48,6 +49,7 @@ pub struct Bootstrap { pub current_sync_committee_branch: Vec, } +#[allow(dead_code)] #[derive(Deserialize, Debug)] pub struct BootstrapResponse { pub data: Bootstrap, @@ -114,6 +116,7 @@ impl fmt::Display for ErrorNotFound { impl Error for ErrorNotFound {} +#[allow(dead_code)] #[derive(Deserialize)] struct CodeResponse { code: u64, @@ -135,6 +138,7 @@ pub async fn get(request_builder: RequestBuilder) -> AnyRes } } +#[allow(dead_code)] pub async fn get_bootstrap( client: &Client, rpc_url: &str, From bc140cc04c69a67be7f8ddb75e601cc0deda42a5 Mon Sep 17 00:00:00 2001 From: Georgy Shepelev Date: Mon, 29 Jul 2024 11:12:56 +0400 Subject: [PATCH 19/24] cargo fmt --- gear-programs/checkpoint-light-client/src/wasm/mod.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/gear-programs/checkpoint-light-client/src/wasm/mod.rs b/gear-programs/checkpoint-light-client/src/wasm/mod.rs index 7d0ec489..d4b46f02 100644 --- a/gear-programs/checkpoint-light-client/src/wasm/mod.rs +++ b/gear-programs/checkpoint-light-client/src/wasm/mod.rs @@ -80,7 +80,11 @@ async fn init() { }) }, - Ok((finalized_header, sync_committee_next)) => panic!("Incorrect initial sync committee update ({}, {})", finalized_header.is_some(), sync_committee_next.is_some()), + Ok((finalized_header, sync_committee_next)) => panic!( + "Incorrect initial sync committee update ({}, {})", + finalized_header.is_some(), + sync_committee_next.is_some() + ), } } From 782f8014de4cde353441fc8885f6e14258d44ced Mon Sep 17 00:00:00 2001 From: Georgy Shepelev Date: Wed, 31 Jul 2024 17:08:35 +0400 Subject: [PATCH 20/24] merge checkpoints relayer into the single binary --- Cargo.lock | 27 ++-------- Cargo.toml | 1 - checkpoints-relayer/Cargo.toml | 23 --------- relayer/Cargo.toml | 4 ++ .../src/ethereum_checkpoints}/metrics.rs | 2 +- .../src/ethereum_checkpoints/mod.rs | 51 ++++--------------- .../src/ethereum_checkpoints}/replay_back.rs | 0 .../src/ethereum_checkpoints}/sync_update.rs | 0 .../src/ethereum_checkpoints}/tests/mod.rs | 2 +- .../sepolia-finality-update-5_254_112.json | 0 .../sepolia-finality-update-5_263_072.json | 0 .../src/ethereum_checkpoints}/utils/mod.rs | 0 .../utils/slots_batch.rs | 0 relayer/src/main.rs | 35 +++++++++++++ 14 files changed, 54 insertions(+), 91 deletions(-) delete mode 100644 checkpoints-relayer/Cargo.toml rename {checkpoints-relayer/src => relayer/src/ethereum_checkpoints}/metrics.rs (97%) rename checkpoints-relayer/src/main.rs => relayer/src/ethereum_checkpoints/mod.rs (80%) rename {checkpoints-relayer/src => relayer/src/ethereum_checkpoints}/replay_back.rs (100%) rename {checkpoints-relayer/src => relayer/src/ethereum_checkpoints}/sync_update.rs (100%) rename {checkpoints-relayer/src => relayer/src/ethereum_checkpoints}/tests/mod.rs (99%) rename {checkpoints-relayer/src => relayer/src/ethereum_checkpoints}/tests/sepolia-finality-update-5_254_112.json (100%) rename {checkpoints-relayer/src => relayer/src/ethereum_checkpoints}/tests/sepolia-finality-update-5_263_072.json (100%) rename {checkpoints-relayer/src => relayer/src/ethereum_checkpoints}/utils/mod.rs (100%) rename {checkpoints-relayer/src => relayer/src/ethereum_checkpoints}/utils/slots_batch.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 84ea050e..f63ea7e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1963,29 +1963,6 @@ dependencies = [ "tree_hash_derive", ] -[[package]] -name = "checkpoints-relayer" -version = "0.1.0" -dependencies = [ - "anyhow", - "ark-serialize 0.4.2", - "checkpoint_light_client", - "checkpoint_light_client-io", - "clap", - "futures", - "gclient", - "hex", - "log", - "parity-scale-codec", - "pretty_env_logger", - "prometheus", - "reqwest 0.11.27", - "serde", - "serde_json", - "tokio", - "utils-prometheus", -] - [[package]] name = "chrono" version = "0.4.38" @@ -8550,9 +8527,12 @@ name = "relayer" version = "0.1.0" dependencies = [ "anyhow", + "ark-serialize 0.4.2", "axum", "bridging_payment", "cgo_oligami", + "checkpoint_light_client", + "checkpoint_light_client-io", "clap", "dotenv", "ethereum-client", @@ -8573,6 +8553,7 @@ dependencies = [ "prometheus", "prover", "rand 0.8.5", + "reqwest 0.11.27", "serde", "serde_json", "thiserror", diff --git a/Cargo.toml b/Cargo.toml index 3159a93b..128d273b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,6 @@ members = [ "gear-programs/*", "gear-programs/checkpoint-light-client/io", "utils-prometheus", - "checkpoints-relayer", ] resolver = "2" diff --git a/checkpoints-relayer/Cargo.toml b/checkpoints-relayer/Cargo.toml deleted file mode 100644 index 64ed4b7f..00000000 --- a/checkpoints-relayer/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "checkpoints-relayer" -version.workspace = true -edition.workspace = true - -[dependencies] -clap.workspace = true -tokio.workspace = true -checkpoint_light_client-io = { workspace = true, features = ["std"] } -checkpoint_light_client = { workspace = true, features = ["std"] } -parity-scale-codec.workspace = true -serde_json.workspace = true -serde = { workspace = true, features = ["std"] } -reqwest.workspace = true -anyhow.workspace = true -ark-serialize = { workspace = true, features = ["std"] } -log.workspace = true -pretty_env_logger.workspace = true -gclient.workspace = true -futures.workspace = true -hex = { workspace = true, features = ["std"] } -prometheus.workspace = true -utils-prometheus.workspace = true diff --git a/relayer/Cargo.toml b/relayer/Cargo.toml index cf671d10..bae262ac 100644 --- a/relayer/Cargo.toml +++ b/relayer/Cargo.toml @@ -11,7 +11,10 @@ gear-rpc-client.workspace = true prover.workspace = true anyhow.workspace = true +ark-serialize = { workspace = true, features = ["std"] } axum.workspace = true +checkpoint_light_client-io = { workspace = true, features = ["std"] } +checkpoint_light_client = { workspace = true, features = ["std"] } clap.workspace = true dotenv.workspace = true futures.workspace = true @@ -28,6 +31,7 @@ pretty_env_logger.workspace = true primitive-types = { workspace = true, features = ["std"] } prometheus.workspace = true rand.workspace = true +reqwest.workspace = true serde.workspace = true serde_json.workspace = true thiserror.workspace = true diff --git a/checkpoints-relayer/src/metrics.rs b/relayer/src/ethereum_checkpoints/metrics.rs similarity index 97% rename from checkpoints-relayer/src/metrics.rs rename to relayer/src/ethereum_checkpoints/metrics.rs index 5b2712e5..8f6eb861 100644 --- a/checkpoints-relayer/src/metrics.rs +++ b/relayer/src/ethereum_checkpoints/metrics.rs @@ -1,6 +1,6 @@ use super::*; use prometheus::{IntCounter, IntGauge}; -use utils_prometheus::{impl_metered_service, MetricsBuilder}; +use utils_prometheus::impl_metered_service; pub struct Message { pub slot: Slot, diff --git a/checkpoints-relayer/src/main.rs b/relayer/src/ethereum_checkpoints/mod.rs similarity index 80% rename from checkpoints-relayer/src/main.rs rename to relayer/src/ethereum_checkpoints/mod.rs index a419e290..59dda96b 100644 --- a/checkpoints-relayer/src/main.rs +++ b/relayer/src/ethereum_checkpoints/mod.rs @@ -1,9 +1,9 @@ +use super::*; use checkpoint_light_client_io::{ ethereum_common::{utils as eth_utils, SLOTS_PER_EPOCH}, tree_hash::Hash256, Handle, HandleResult, Slot, SyncCommitteeUpdate, G2, }; -use clap::Parser; use futures::{ future::{self, Either}, pin_mut, @@ -11,7 +11,6 @@ use futures::{ use gclient::{EventListener, EventProcessor, GearApi, WSAddress}; use metrics::Message as MetricMessage; use parity_scale_codec::Decode; -use pretty_env_logger::env_logger::fmt::TimestampPrecision; use reqwest::Client; use tokio::{ signal::unix::{self, SignalKind}, @@ -33,36 +32,6 @@ const SIZE_BATCH: u64 = 44 * SLOTS_PER_EPOCH; const COUNT_FAILURE: usize = 3; const DELAY_SECS_FINALITY_REQUEST: u64 = 30; -#[derive(Debug, Parser)] -struct Args { - /// Specify ProgramId of the Checkpoint-light-client program - #[arg(long)] - program_id: String, - - /// Specify an endpoint providing Beacon API - #[arg(long)] - beacon_endpoint: String, - - /// Domain of the VARA RPC endpoint - #[arg(long, default_value = "ws://127.0.0.1")] - vara_domain: String, - - /// Port of the VARA RPC endpoint - #[arg(long, default_value = "9944")] - vara_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")] - suri: String, - - /// Address of the prometheus endpoint - #[arg(long = "prometheus-endpoint", default_value = "http://127.0.0.1:9090")] - endpoint_prometheus: String, -} - enum Status { Ok, NotActual, @@ -73,13 +42,9 @@ enum Status { }, } -#[tokio::main] -async fn main() { - pretty_env_logger::formatted_builder() - .format_timestamp(Some(TimestampPrecision::Micros)) - .parse_default_env() - .init(); - +pub async fn relay( + args: RelayCheckpointsArgs, +) { log::info!("Started"); let Ok(mut signal_interrupt) = unix::signal(SignalKind::interrupt()) else { @@ -87,14 +52,16 @@ async fn main() { return; }; - let Args { + let RelayCheckpointsArgs { program_id, beacon_endpoint, vara_domain, vara_port, suri, - endpoint_prometheus, - } = Args::parse(); + prometheus_args: PrometheusArgs { + endpoint: endpoint_prometheus, + }, + } = args; let sender_metrics = metrics::spawn(endpoint_prometheus); diff --git a/checkpoints-relayer/src/replay_back.rs b/relayer/src/ethereum_checkpoints/replay_back.rs similarity index 100% rename from checkpoints-relayer/src/replay_back.rs rename to relayer/src/ethereum_checkpoints/replay_back.rs diff --git a/checkpoints-relayer/src/sync_update.rs b/relayer/src/ethereum_checkpoints/sync_update.rs similarity index 100% rename from checkpoints-relayer/src/sync_update.rs rename to relayer/src/ethereum_checkpoints/sync_update.rs diff --git a/checkpoints-relayer/src/tests/mod.rs b/relayer/src/ethereum_checkpoints/tests/mod.rs similarity index 99% rename from checkpoints-relayer/src/tests/mod.rs rename to relayer/src/ethereum_checkpoints/tests/mod.rs index 01a20bf6..6c4b4137 100644 --- a/checkpoints-relayer/src/tests/mod.rs +++ b/relayer/src/ethereum_checkpoints/tests/mod.rs @@ -1,4 +1,4 @@ -use crate::utils::{self, slots_batch, FinalityUpdateResponse}; +use super::utils::{self, slots_batch, FinalityUpdateResponse}; use checkpoint_light_client::WASM_BINARY; use checkpoint_light_client_io::{ ethereum_common::{ diff --git a/checkpoints-relayer/src/tests/sepolia-finality-update-5_254_112.json b/relayer/src/ethereum_checkpoints/tests/sepolia-finality-update-5_254_112.json similarity index 100% rename from checkpoints-relayer/src/tests/sepolia-finality-update-5_254_112.json rename to relayer/src/ethereum_checkpoints/tests/sepolia-finality-update-5_254_112.json diff --git a/checkpoints-relayer/src/tests/sepolia-finality-update-5_263_072.json b/relayer/src/ethereum_checkpoints/tests/sepolia-finality-update-5_263_072.json similarity index 100% rename from checkpoints-relayer/src/tests/sepolia-finality-update-5_263_072.json rename to relayer/src/ethereum_checkpoints/tests/sepolia-finality-update-5_263_072.json diff --git a/checkpoints-relayer/src/utils/mod.rs b/relayer/src/ethereum_checkpoints/utils/mod.rs similarity index 100% rename from checkpoints-relayer/src/utils/mod.rs rename to relayer/src/ethereum_checkpoints/utils/mod.rs diff --git a/checkpoints-relayer/src/utils/slots_batch.rs b/relayer/src/ethereum_checkpoints/utils/slots_batch.rs similarity index 100% rename from checkpoints-relayer/src/utils/slots_batch.rs rename to relayer/src/ethereum_checkpoints/utils/slots_batch.rs diff --git a/relayer/src/main.rs b/relayer/src/main.rs index 6abb31e8..cd838506 100644 --- a/relayer/src/main.rs +++ b/relayer/src/main.rs @@ -11,6 +11,7 @@ use prover::proving::GenesisConfig; use relay_merkle_roots::MerkleRootRelayer; use utils_prometheus::MetricsBuilder; +mod ethereum_checkpoints; mod message_relayer; mod proof_storage; mod prover_interface; @@ -45,6 +46,8 @@ enum CliCommands { /// Relay message to ethereum #[clap(visible_alias("rm"))] RelayMessages(RelayMessagesArgs), + /// Start service constantly relaying Ethereum checkpoints to the Vara program + RelayCheckpoints(RelayCheckpointsArgs), } #[derive(Args)] @@ -124,6 +127,35 @@ struct ProofStorageArgs { gear_fee_payer: Option, } +#[derive(Args)] +struct RelayCheckpointsArgs { + /// Specify ProgramId of the Checkpoint-light-client program + #[arg(long)] + program_id: String, + + /// Specify an endpoint providing Beacon API + #[arg(long)] + beacon_endpoint: String, + + /// Domain of the VARA RPC endpoint + #[arg(long, default_value = "ws://127.0.0.1")] + vara_domain: String, + + /// Port of the VARA RPC endpoint + #[arg(long, default_value = "9944")] + vara_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")] + suri: String, + + #[clap(flatten)] + prometheus_args: PrometheusArgs, +} + #[tokio::main] async fn main() { let _ = dotenv::dotenv(); @@ -136,6 +168,7 @@ async fn main() { .filter(Some("ethereum-client"), log::LevelFilter::Info) .filter(Some("metrics"), log::LevelFilter::Info) .format_timestamp(Some(TimestampPrecision::Seconds)) + .parse_default_env() .init(); let cli = Cli::parse(); @@ -201,6 +234,8 @@ async fn main() { relayer.run().await.unwrap(); } + + CliCommands::RelayCheckpoints(args) => ethereum_checkpoints::relay(args).await, }; } From e6e709e3a27117e05c6d33085a5892178c8d8f38 Mon Sep 17 00:00:00 2001 From: Georgy Shepelev Date: Thu, 1 Aug 2024 16:49:46 +0400 Subject: [PATCH 21/24] fix review remarks --- relayer/src/ethereum_checkpoints/metrics.rs | 69 +----- relayer/src/ethereum_checkpoints/mod.rs | 142 +++++------ .../src/ethereum_checkpoints/replay_back.rs | 16 +- .../src/ethereum_checkpoints/sync_update.rs | 227 ++++++++---------- relayer/src/ethereum_checkpoints/utils/mod.rs | 2 +- .../ethereum_checkpoints/utils/slots_batch.rs | 22 +- relayer/src/main.rs | 1 + 7 files changed, 217 insertions(+), 262 deletions(-) diff --git a/relayer/src/ethereum_checkpoints/metrics.rs b/relayer/src/ethereum_checkpoints/metrics.rs index 8f6eb861..714d9c47 100644 --- a/relayer/src/ethereum_checkpoints/metrics.rs +++ b/relayer/src/ethereum_checkpoints/metrics.rs @@ -1,25 +1,18 @@ -use super::*; use prometheus::{IntCounter, IntGauge}; use utils_prometheus::impl_metered_service; -pub struct Message { - pub slot: Slot, - pub committee_update: bool, - pub processed: bool, -} - impl_metered_service! { - struct EventListenerMetrics { - fetched_sync_update_slot: IntGauge, - total_fetched_finality_updates: IntCounter, - total_fetched_committee_updates: IntCounter, - processed_finality_updates: IntCounter, - processed_committee_updates: IntCounter, + pub struct Updates { + pub fetched_sync_update_slot: IntGauge, + pub total_fetched_finality_updates: IntCounter, + pub total_fetched_committee_updates: IntCounter, + pub processed_finality_updates: IntCounter, + pub processed_committee_updates: IntCounter, } } -impl EventListenerMetrics { - fn new() -> Self { +impl Updates { + pub fn new() -> Self { Self::new_inner().expect("Failed to create metrics") } @@ -27,60 +20,24 @@ impl EventListenerMetrics { Ok(Self { fetched_sync_update_slot: IntGauge::new( "checkpoints_relayer_fetched_sync_update_slot", - "checkpoints_relayer_fetched_sync_update_slot", + "The slot of the last applied update", )?, total_fetched_finality_updates: IntCounter::new( "checkpoints_relayer_total_fetched_finality_updates", - "checkpoints_relayer_total_fetched_finality_updates", + "Total amount of fetched finality updates", )?, total_fetched_committee_updates: IntCounter::new( "checkpoints_relayer_total_fetched_committee_updates", - "checkpoints_relayer_total_fetched_committee_updates", + "Total amount of fetched committee updates", )?, processed_finality_updates: IntCounter::new( "checkpoints_relayer_processed_finality_updates", - "checkpoints_relayer_processed_finality_updates", + "Amount of processed finality updates", )?, processed_committee_updates: IntCounter::new( "checkpoints_relayer_processed_committee_updates", - "checkpoints_relayer_processed_committee_updates", + "Amount of processed committee updates", )?, }) } } - -pub fn spawn(endpoint_prometheus: String) -> Sender { - let (sender, mut receiver) = mpsc::channel::(100); - - tokio::spawn(async move { - let service = EventListenerMetrics::new(); - MetricsBuilder::new() - .register_service(&service) - .build() - .run(endpoint_prometheus) - .await; - - loop { - let Some(metric_message) = receiver.recv().await else { - return; - }; - - service - .fetched_sync_update_slot - .set(i64::from_le_bytes(metric_message.slot.to_le_bytes())); - if metric_message.committee_update { - service.total_fetched_committee_updates.inc(); - if metric_message.processed { - service.processed_committee_updates.inc(); - } - } else { - service.total_fetched_finality_updates.inc(); - if metric_message.processed { - service.processed_finality_updates.inc(); - } - } - } - }); - - sender -} diff --git a/relayer/src/ethereum_checkpoints/mod.rs b/relayer/src/ethereum_checkpoints/mod.rs index 59dda96b..58df8b71 100644 --- a/relayer/src/ethereum_checkpoints/mod.rs +++ b/relayer/src/ethereum_checkpoints/mod.rs @@ -9,7 +9,6 @@ use futures::{ pin_mut, }; use gclient::{EventListener, EventProcessor, GearApi, WSAddress}; -use metrics::Message as MetricMessage; use parity_scale_codec::Decode; use reqwest::Client; use tokio::{ @@ -28,30 +27,13 @@ mod sync_update; mod utils; const SIZE_CHANNEL: usize = 100_000; -const SIZE_BATCH: u64 = 44 * SLOTS_PER_EPOCH; +const SIZE_BATCH: u64 = 40 * SLOTS_PER_EPOCH; const COUNT_FAILURE: usize = 3; const DELAY_SECS_FINALITY_REQUEST: u64 = 30; -enum Status { - Ok, - NotActual, - Error, - ReplayBackRequired { - replayed_slot: Option, - checkpoint: (Slot, Hash256), - }, -} - -pub async fn relay( - args: RelayCheckpointsArgs, -) { +pub async fn relay(args: RelayCheckpointsArgs) { log::info!("Started"); - let Ok(mut signal_interrupt) = unix::signal(SignalKind::interrupt()) else { - log::error!("Failed to set SIGINT handler"); - return; - }; - let RelayCheckpointsArgs { program_id, beacon_endpoint, @@ -63,20 +45,17 @@ pub async fn relay( }, } = args; - let sender_metrics = metrics::spawn(endpoint_prometheus); - let program_id_no_prefix = match program_id.starts_with("0x") { true => &program_id[2..], false => &program_id, }; - let Some(program_id) = hex::decode(program_id_no_prefix) + let program_id = hex::decode(program_id_no_prefix) .ok() .and_then(|bytes| <[u8; 32]>::try_from(bytes).ok()) - else { - log::error!("Incorrect ProgramId"); - return; - }; + .expect("Expecting correct ProgramId"); + + let mut signal_interrupt = unix::signal(SignalKind::interrupt()).expect("Set SIGINT handler"); let (sender, mut receiver) = mpsc::channel(SIZE_CHANNEL); let client_http = Client::new(); @@ -92,44 +71,61 @@ pub async fn relay( Ok(client) => client, Err(e) => { log::error!("Unable to create GearApi client: {e:?}"); - return; } }; - let mut listener = match client.subscribe().await { - Ok(listener) => listener, + let gas_limit_block = match client.block_gas_limit() { + Ok(gas_limit_block) => gas_limit_block, Err(e) => { - log::error!("Unable to create events listener: {e:?}"); - + log::error!("Unable to determine block gas limit: {e:?}"); return; } }; - let sync_update = match receiver.recv().await { - Some(finality_update) => finality_update, - None => { - log::info!("Updates receiver has been closed before the loop. Exiting"); + // use 95% of block gas limit for all extrinsics + let gas_limit = gas_limit_block / 100 * 95; + log::info!("Gas limit for extrinsics: {gas_limit}"); + let mut listener = match client.subscribe().await { + Ok(listener) => listener, + Err(e) => { + log::error!("Unable to create events listener: {e:?}"); return; } }; + let sync_update = receiver + .recv() + .await + .expect("Updates receiver should be open before the loop"); + let mut slot_last = sync_update.finalized_header.slot; - match sync_update::try_to_apply(&client, &mut listener, program_id, sync_update.clone()).await { - Status::Ok | Status::NotActual => (), - Status::Error => return, - Status::ReplayBackRequired { + match sync_update::try_to_apply( + &client, + &mut listener, + program_id, + sync_update.clone(), + gas_limit, + ) + .await + { + Err(e) => { + log::error!("{e:?}"); + return; + } + Ok(Err(sync_update::Error::ReplayBackRequired { replayed_slot, checkpoint, - } => { + })) => { replay_back::execute( &client_http, &beacon_endpoint, &client, &mut listener, program_id, + gas_limit, replayed_slot, checkpoint, sync_update, @@ -138,8 +134,21 @@ pub async fn relay( log::info!("Exiting"); return; } + Ok(Ok(_) | Err(sync_update::Error::NotActual)) => (), + _ => { + slot_last = 0; + } } + let update_metrics = metrics::Updates::new(); + MetricsBuilder::new() + .register_service(&update_metrics) + .build() + .run(endpoint_prometheus) + .await; + + log::info!("Metrics service spawned"); + loop { let future_interrupt = signal_interrupt.recv(); pin_mut!(future_interrupt); @@ -159,45 +168,42 @@ pub async fn relay( return; } }; - - let committee_update = sync_update.sync_committee_next_pub_keys.is_some(); let slot = sync_update.finalized_header.slot; + + update_metrics + .fetched_sync_update_slot + .set(i64::from_le_bytes(slot.to_le_bytes())); + if slot == slot_last { - let metric_message = MetricMessage { - slot, - committee_update, - processed: false, - }; - - if sender_metrics.send(metric_message).await.is_err() { - log::error!("Failed to update metrics. Exiting"); - return; - } + update_metrics.total_fetched_finality_updates.inc(); continue; } - match sync_update::try_to_apply(&client, &mut listener, program_id, sync_update).await { - Status::Ok => { + let committee_update = sync_update.sync_committee_next_pub_keys.is_some(); + match sync_update::try_to_apply(&client, &mut listener, program_id, sync_update, gas_limit) + .await + { + Ok(Ok(_)) => { slot_last = slot; - let metric_message = MetricMessage { - slot, - committee_update, - processed: true, - }; - - if sender_metrics.send(metric_message).await.is_err() { - log::error!("Failed to update metrics. Exiting"); - return; + if committee_update { + update_metrics.total_fetched_committee_updates.inc(); + update_metrics.processed_committee_updates.inc(); + } else { + update_metrics.total_fetched_finality_updates.inc(); + update_metrics.processed_finality_updates.inc(); } } - Status::NotActual => (), - Status::ReplayBackRequired { .. } => { - log::info!("Exiting"); + Ok(Err(sync_update::Error::ReplayBackRequired { .. })) => { + log::info!("Replay back within the main loop. Exiting"); + return; + } + Ok(Err(e)) => log::info!("The program failed with: {e:?}. Skipping"), + Err(e) => { + log::error!("{e:?}"); return; } - _ => continue, } } } diff --git a/relayer/src/ethereum_checkpoints/replay_back.rs b/relayer/src/ethereum_checkpoints/replay_back.rs index 88f65dbd..0709ee8b 100644 --- a/relayer/src/ethereum_checkpoints/replay_back.rs +++ b/relayer/src/ethereum_checkpoints/replay_back.rs @@ -12,6 +12,7 @@ pub async fn execute( client: &GearApi, listener: &mut EventListener, program_id: [u8; 32], + gas_limit: u64, replayed_slot: Option, checkpoint: (Slot, Hash256), sync_update: SyncCommitteeUpdate, @@ -32,6 +33,7 @@ pub async fn execute( client, listener, program_id, + gas_limit, slots_batch_iter, ) .await; @@ -84,6 +86,7 @@ pub async fn execute( client, listener, program_id, + gas_limit, slots_batch_iter.next(), sync_update, ) @@ -99,6 +102,7 @@ pub async fn execute( client, listener, program_id, + gas_limit, slots_batch_iter, ) .await @@ -125,6 +129,7 @@ pub async fn execute( client, listener, program_id, + gas_limit, slots_batch_iter.next(), sync_update, ) @@ -140,6 +145,7 @@ pub async fn execute( client, listener, program_id, + gas_limit, slots_batch_iter, ) .await; @@ -153,6 +159,7 @@ async fn replay_back_slots( client: &GearApi, listener: &mut EventListener, program_id: [u8; 32], + gas_limit: u64, slots_batch_iter: SlotsBatchIter, ) -> Option<()> { for (slot_start, slot_end) in slots_batch_iter { @@ -164,6 +171,7 @@ async fn replay_back_slots( program_id, slot_start, slot_end, + gas_limit, ) .await?; } @@ -171,6 +179,7 @@ async fn replay_back_slots( Some(()) } +#[allow(clippy::too_many_arguments)] async fn replay_back_slots_inner( client_http: &Client, beacon_endpoint: &str, @@ -179,13 +188,14 @@ async fn replay_back_slots_inner( program_id: [u8; 32], slot_start: Slot, slot_end: Slot, + gas_limit: u64, ) -> Option<()> { let payload = Handle::ReplayBack( request_headers(client_http, beacon_endpoint, slot_start, slot_end).await?, ); let (message_id, _) = client - .send_message(program_id.into(), payload, 700_000_000_000, 0) + .send_message(program_id.into(), payload, gas_limit, 0) .await .map_err(|e| log::error!("Failed to send ReplayBack message: {e:?}")) .ok()?; @@ -211,12 +221,14 @@ async fn replay_back_slots_inner( .then_some(()) } +#[allow(clippy::too_many_arguments)] async fn replay_back_slots_start( client_http: &Client, beacon_endpoint: &str, client: &GearApi, listener: &mut EventListener, program_id: [u8; 32], + gas_limit: u64, slots: Option<(Slot, Slot)>, sync_update: SyncCommitteeUpdate, ) -> Option<()> { @@ -230,7 +242,7 @@ async fn replay_back_slots_start( }; let (message_id, _) = client - .send_message(program_id.into(), payload, 700_000_000_000, 0) + .send_message(program_id.into(), payload, gas_limit, 0) .await .map_err(|e| log::error!("Failed to send ReplayBackStart message: {e:?}")) .ok()?; diff --git a/relayer/src/ethereum_checkpoints/sync_update.rs b/relayer/src/ethereum_checkpoints/sync_update.rs index b5ef9fb9..88287d62 100644 --- a/relayer/src/ethereum_checkpoints/sync_update.rs +++ b/relayer/src/ethereum_checkpoints/sync_update.rs @@ -1,5 +1,7 @@ use super::*; -use checkpoint_light_client_io::sync_update::Error; +use anyhow::{anyhow, Result as AnyResult}; +pub use checkpoint_light_client_io::sync_update::Error; +use std::ops::ControlFlow::{self, *}; pub fn spawn_receiver( client_http: Client, @@ -11,148 +13,125 @@ pub fn spawn_receiver( log::info!("Update receiver spawned"); let mut failures = 0; - loop { - let finality_update = - match utils::get_finality_update(&client_http, &beacon_endpoint).await { - Ok(finality_update) => finality_update, - - Err(e) => { - log::error!("Unable to fetch FinalityUpdate: {e:?}"); - - failures += 1; - if failures >= COUNT_FAILURE { - return; - } - - time::sleep(delay).await; - continue; - } - }; - - let period = eth_utils::calculate_period(finality_update.finalized_header.slot); - let mut updates = - match utils::get_updates(&client_http, &beacon_endpoint, period, 1).await { - Ok(updates) => updates, - Err(e) => { - log::error!("Unable to fetch Updates: {e:?}"); - - failures += 1; - if failures >= COUNT_FAILURE { - return; - } - - time::sleep(delay).await; - continue; - } - }; - - let update = match updates.pop() { - Some(update) if updates.is_empty() => update.data, - _ => { - log::error!("Requested single update"); - - failures += 1; - if failures >= COUNT_FAILURE { - return; - } - - time::sleep(delay).await; - continue; - } - }; - - let reader_signature = - if update.finalized_header.slot >= finality_update.finalized_header.slot { - &update.sync_aggregate.sync_committee_signature.0 .0[..] - } else { - &finality_update.sync_aggregate.sync_committee_signature.0 .0[..] - }; - - let Ok(signature) = ::deserialize_compressed( - reader_signature, - ) else { - log::error!("Failed to deserialize point on G2"); - - failures += 1; - if failures >= COUNT_FAILURE { - return; - } - - time::sleep(delay).await; - continue; - }; - - let sync_update = - if update.finalized_header.slot >= finality_update.finalized_header.slot { - utils::sync_update_from_update(signature, update) - } else { - utils::sync_update_from_finality(signature, finality_update) - }; - - if sender.send(sync_update).await.is_err() { - return; + match receive( + &client_http, + &beacon_endpoint, + &sender, + delay, + &mut failures, + ) + .await + { + Break(_) => break, + Continue(_) => (), } - - time::sleep(delay).await; } }); } -pub async fn try_to_apply( - client: &GearApi, - listener: &mut EventListener, - program_id: [u8; 32], - sync_update: SyncCommitteeUpdate, -) -> Status { - let payload = Handle::SyncUpdate(sync_update); - let (message_id, _) = match client - .send_message(program_id.into(), payload, 700_000_000_000, 0) - .await - { - Ok(result) => result, - Err(e) => { - log::error!("Failed to send message: {e:?}"); +async fn receive( + client_http: &Client, + beacon_endpoint: &str, + sender: &Sender, + delay: Duration, + failures: &mut usize, +) -> ControlFlow<()> { + // helper macro + macro_rules! check_and_return { + () => { + *failures += 1; + if *failures >= COUNT_FAILURE { + return Break(()); + } - return Status::Error; - } - }; + time::sleep(delay).await; - let (_message_id, payload, _value) = match listener.reply_bytes_on(message_id).await { - Ok(result) => result, - Err(e) => { - log::error!("Failed to get reply: {e:?}"); + return Continue(()); + }; + } + + let finality_update = match utils::get_finality_update(client_http, beacon_endpoint).await { + Ok(finality_update) => finality_update, - return Status::Error; + Err(e) => { + log::error!("Unable to fetch FinalityUpdate: {e:?}"); + check_and_return!(); } }; - let payload = match payload { - Ok(payload) => payload, + + let period = eth_utils::calculate_period(finality_update.finalized_header.slot); + let mut updates = match utils::get_updates(client_http, beacon_endpoint, period, 1).await { + Ok(updates) => updates, Err(e) => { - log::error!("Failed to get replay payload to SyncUpdate: {e:?}"); - return Status::Error; + log::error!("Unable to fetch Updates: {e:?}"); + check_and_return!(); } }; - let result_decoded = match HandleResult::decode(&mut &payload[..]) { - Ok(result_decoded) => result_decoded, - Err(e) => { - log::error!("Failed to decode HandleResult of SyncUpdate: {e:?}"); - return Status::Error; + + let update = match updates.pop() { + Some(update) if updates.is_empty() => update.data, + _ => { + log::error!("Requested single update"); + check_and_return!(); } }; + let reader_signature = if update.finalized_header.slot >= finality_update.finalized_header.slot + { + &update.sync_aggregate.sync_committee_signature.0 .0[..] + } else { + &finality_update.sync_aggregate.sync_committee_signature.0 .0[..] + }; + + let Ok(signature) = + ::deserialize_compressed(reader_signature) + else { + log::error!("Failed to deserialize point on G2"); + check_and_return!(); + }; + + let sync_update = if update.finalized_header.slot >= finality_update.finalized_header.slot { + utils::sync_update_from_update(signature, update) + } else { + utils::sync_update_from_finality(signature, finality_update) + }; + + if sender.send(sync_update).await.is_err() { + return Break(()); + } + + time::sleep(delay).await; + + Continue(()) +} + +pub async fn try_to_apply( + client: &GearApi, + listener: &mut EventListener, + program_id: [u8; 32], + sync_update: SyncCommitteeUpdate, + gas_limit: u64, +) -> AnyResult> { + let payload = Handle::SyncUpdate(sync_update); + let (message_id, _) = client + .send_message(program_id.into(), payload, gas_limit, 0) + .await + .map_err(|e| anyhow!("Failed to send message: {e:?}"))?; + + let (_message_id, payload, _value) = listener + .reply_bytes_on(message_id) + .await + .map_err(|e| anyhow!("Failed to get reply: {e:?}"))?; + let payload = + payload.map_err(|e| anyhow!("Failed to get replay payload to SyncUpdate: {e:?}"))?; + let result_decoded = HandleResult::decode(&mut &payload[..]) + .map_err(|e| anyhow!("Failed to decode HandleResult of SyncUpdate: {e:?}"))?; + log::debug!("try_to_apply; result_decoded = {result_decoded:?}"); match result_decoded { - HandleResult::SyncUpdate(Ok(())) => Status::Ok, - HandleResult::SyncUpdate(Err(Error::NotActual)) => Status::NotActual, - HandleResult::SyncUpdate(Err(Error::ReplayBackRequired { - replayed_slot, - checkpoint, - })) => Status::ReplayBackRequired { - replayed_slot, - checkpoint, - }, - _ => Status::Error, + HandleResult::SyncUpdate(result) => Ok(result), + _ => Err(anyhow!("Wrong response type")), } } diff --git a/relayer/src/ethereum_checkpoints/utils/mod.rs b/relayer/src/ethereum_checkpoints/utils/mod.rs index 31ccbb32..4552d246 100644 --- a/relayer/src/ethereum_checkpoints/utils/mod.rs +++ b/relayer/src/ethereum_checkpoints/utils/mod.rs @@ -138,7 +138,7 @@ pub async fn get(request_builder: RequestBuilder) -> AnyRes } } -#[allow(dead_code)] +#[cfg(test)] pub async fn get_bootstrap( client: &Client, rpc_url: &str, diff --git a/relayer/src/ethereum_checkpoints/utils/slots_batch.rs b/relayer/src/ethereum_checkpoints/utils/slots_batch.rs index c6f6cb10..1fd30ac1 100644 --- a/relayer/src/ethereum_checkpoints/utils/slots_batch.rs +++ b/relayer/src/ethereum_checkpoints/utils/slots_batch.rs @@ -58,21 +58,21 @@ fn test_slots_batch_iterator() { let mut iter = Iter::new(3, 10, 2).unwrap(); // [9; 10), [8; 9), etc - assert!(matches!(iter.next(), Some((start, end)) if start == 9 && end == 10)); - assert!(matches!(iter.next(), Some((start, end)) if start == 8 && end == 9)); - assert!(matches!(iter.next(), Some((start, end)) if start == 7 && end == 8)); - assert!(matches!(iter.next(), Some((start, end)) if start == 6 && end == 7)); - assert!(matches!(iter.next(), Some((start, end)) if start == 5 && end == 6)); - assert!(matches!(iter.next(), Some((start, end)) if start == 4 && end == 5)); - assert!(matches!(iter.next(), Some((start, end)) if start == 3 && end == 4)); + assert_eq!(iter.next(), Some((9, 10))); + assert_eq!(iter.next(), Some((8, 9))); + assert_eq!(iter.next(), Some((7, 8))); + assert_eq!(iter.next(), Some((6, 7))); + assert_eq!(iter.next(), Some((5, 6))); + assert_eq!(iter.next(), Some((4, 5))); + assert_eq!(iter.next(), Some((3, 4))); assert!(iter.next().is_none()); let mut iter = Iter::new(3, 10, 3).unwrap(); // [8; 10), [6; 8), [4; 6), [3; 4) - assert!(matches!(iter.next(), Some((start, end)) if start == 8 && end == 10)); - assert!(matches!(iter.next(), Some((start, end)) if start == 6 && end == 8)); - assert!(matches!(iter.next(), Some((start, end)) if start == 4 && end == 6)); - assert!(matches!(iter.next(), Some((start, end)) if start == 3 && end == 4)); + assert_eq!(iter.next(), Some((8, 10))); + assert_eq!(iter.next(), Some((6, 8))); + assert_eq!(iter.next(), Some((4, 6))); + assert_eq!(iter.next(), Some((3, 4))); assert!(iter.next().is_none()); } diff --git a/relayer/src/main.rs b/relayer/src/main.rs index cd838506..e318f42a 100644 --- a/relayer/src/main.rs +++ b/relayer/src/main.rs @@ -38,6 +38,7 @@ struct Cli { command: CliCommands, } +#[allow(clippy::enum_variant_names)] #[derive(Subcommand)] enum CliCommands { /// Start service constantly relaying messages to ethereum From da7e2fad43c142509c52c5094a047f954e7afcf3 Mon Sep 17 00:00:00 2001 From: Georgy Shepelev Date: Fri, 2 Aug 2024 17:11:40 +0400 Subject: [PATCH 22/24] fix review remarks --- relayer/src/ethereum_checkpoints/mod.rs | 50 +++--- .../src/ethereum_checkpoints/replay_back.rs | 147 +++++++----------- .../src/ethereum_checkpoints/sync_update.rs | 84 ++++------ relayer/src/ethereum_checkpoints/tests/mod.rs | 90 +++++++++-- relayer/src/main.rs | 10 +- 5 files changed, 186 insertions(+), 195 deletions(-) diff --git a/relayer/src/ethereum_checkpoints/mod.rs b/relayer/src/ethereum_checkpoints/mod.rs index 58df8b71..a087affa 100644 --- a/relayer/src/ethereum_checkpoints/mod.rs +++ b/relayer/src/ethereum_checkpoints/mod.rs @@ -1,4 +1,5 @@ use super::*; +use anyhow::{anyhow, Result as AnyResult}; use checkpoint_light_client_io::{ ethereum_common::{utils as eth_utils, SLOTS_PER_EPOCH}, tree_hash::Hash256, @@ -29,7 +30,7 @@ mod utils; const SIZE_CHANNEL: usize = 100_000; const SIZE_BATCH: u64 = 40 * SLOTS_PER_EPOCH; const COUNT_FAILURE: usize = 3; -const DELAY_SECS_FINALITY_REQUEST: u64 = 30; +const DELAY_SECS_UPDATE_REQUEST: u64 = 30; pub async fn relay(args: RelayCheckpointsArgs) { log::info!("Started"); @@ -60,40 +61,24 @@ pub async fn relay(args: RelayCheckpointsArgs) { let (sender, mut receiver) = mpsc::channel(SIZE_CHANNEL); let client_http = Client::new(); - sync_update::spawn_receiver( - client_http.clone(), - beacon_endpoint.clone(), - sender, - Duration::from_secs(DELAY_SECS_FINALITY_REQUEST), - ); + sync_update::spawn_receiver(client_http.clone(), beacon_endpoint.clone(), sender); - let client = match GearApi::init_with(WSAddress::new(vara_domain, vara_port), suri).await { - Ok(client) => client, - Err(e) => { - log::error!("Unable to create GearApi client: {e:?}"); - return; - } - }; + let client = GearApi::init_with(WSAddress::new(vara_domain, vara_port), suri) + .await + .expect("GearApi client should be created"); - let gas_limit_block = match client.block_gas_limit() { - Ok(gas_limit_block) => gas_limit_block, - Err(e) => { - log::error!("Unable to determine block gas limit: {e:?}"); - return; - } - }; + let gas_limit_block = client + .block_gas_limit() + .expect("Block gas limit should be determined"); // use 95% of block gas limit for all extrinsics let gas_limit = gas_limit_block / 100 * 95; log::info!("Gas limit for extrinsics: {gas_limit}"); - let mut listener = match client.subscribe().await { - Ok(listener) => listener, - Err(e) => { - log::error!("Unable to create events listener: {e:?}"); - return; - } - }; + let mut listener = client + .subscribe() + .await + .expect("Events listener should be created"); let sync_update = receiver .recv() @@ -119,7 +104,7 @@ pub async fn relay(args: RelayCheckpointsArgs) { replayed_slot, checkpoint, })) => { - replay_back::execute( + if let Err(e) = replay_back::execute( &client_http, &beacon_endpoint, &client, @@ -130,8 +115,13 @@ pub async fn relay(args: RelayCheckpointsArgs) { checkpoint, sync_update, ) - .await; + .await + { + log::error!("{e:?}"); + } + log::info!("Exiting"); + return; } Ok(Ok(_) | Err(sync_update::Error::NotActual)) => (), diff --git a/relayer/src/ethereum_checkpoints/replay_back.rs b/relayer/src/ethereum_checkpoints/replay_back.rs index 0709ee8b..3443c381 100644 --- a/relayer/src/ethereum_checkpoints/replay_back.rs +++ b/relayer/src/ethereum_checkpoints/replay_back.rs @@ -1,8 +1,5 @@ use super::*; -use checkpoint_light_client_io::{ - replay_back::{Status, StatusStart}, - BeaconBlockHeader, -}; +use checkpoint_light_client_io::BeaconBlockHeader; use utils::ErrorNotFound; #[allow(clippy::too_many_arguments)] @@ -16,16 +13,13 @@ pub async fn execute( replayed_slot: Option, checkpoint: (Slot, Hash256), sync_update: SyncCommitteeUpdate, -) { +) -> AnyResult<()> { log::info!("Replaying back started"); let (mut slot_start, _) = checkpoint; if let Some(slot_end) = replayed_slot { - let Some(slots_batch_iter) = SlotsBatchIter::new(slot_start, slot_end, SIZE_BATCH) else { - log::error!("Failed to create slots_batch::Iter with slot_start = {slot_start}, slot_end = {slot_end}."); - - return; - }; + let slots_batch_iter = SlotsBatchIter::new(slot_start, slot_end, SIZE_BATCH) + .ok_or(anyhow!("Failed to create slots_batch::Iter with slot_start = {slot_start}, slot_end = {slot_end}."))?; replay_back_slots( client_http, @@ -36,51 +30,38 @@ pub async fn execute( gas_limit, slots_batch_iter, ) - .await; + .await?; log::info!("The ongoing replaying back finished"); - return; + return Ok(()); } let period_start = 1 + eth_utils::calculate_period(slot_start); - let updates = match utils::get_updates( + let updates = utils::get_updates( client_http, beacon_endpoint, period_start, MAX_REQUEST_LIGHT_CLIENT_UPDATES, ) .await - { - Ok(updates) => updates, - Err(e) => { - log::error!("Failed to get updates for period {period_start}: {e:?}"); - - return; - } - }; + .map_err(|e| anyhow!("Failed to get updates for period {period_start}: {e:?}"))?; let slot_last = sync_update.finalized_header.slot; for update in updates { let slot_end = update.data.finalized_header.slot; - let Some(mut slots_batch_iter) = SlotsBatchIter::new(slot_start, slot_end, SIZE_BATCH) - else { - log::error!("Failed to create slots_batch::Iter with slot_start = {slot_start}, slot_end (update) = {slot_end}."); - - return; - }; + let mut slots_batch_iter = SlotsBatchIter::new(slot_start, slot_end, SIZE_BATCH) + .ok_or(anyhow!("Failed to create slots_batch::Iter with slot_start = {slot_start}, slot_end = {slot_end}."))?; slot_start = slot_end; - let Ok(signature) = ::deserialize_compressed( + let signature = ::deserialize_compressed( &update.data.sync_aggregate.sync_committee_signature.0 .0[..], - ) else { - log::error!("Failed to deserialize point on G2 (replay back)"); - return; - }; + ) + .map_err(|e| anyhow!("Failed to deserialize point on G2 (replay back): {e:?}"))?; let sync_update = utils::sync_update_from_update(signature, update.data); - if replay_back_slots_start( + replay_back_slots_start( client_http, beacon_endpoint, client, @@ -90,13 +71,9 @@ pub async fn execute( slots_batch_iter.next(), sync_update, ) - .await - .is_none() - { - return; - } + .await?; - if replay_back_slots( + replay_back_slots( client_http, beacon_endpoint, client, @@ -105,25 +82,18 @@ pub async fn execute( gas_limit, slots_batch_iter, ) - .await - .is_none() - { - return; - } + .await?; if slot_end == slot_last { // the provided sync_update is a sync committee update - return; + return Ok(()); } } - let Some(mut slots_batch_iter) = SlotsBatchIter::new(slot_start, slot_last, SIZE_BATCH) else { - log::error!("Failed to create slots_batch::Iter with slot_start = {slot_start}, slot_last = {slot_last}."); - - return; - }; + let mut slots_batch_iter = SlotsBatchIter::new(slot_start, slot_last, SIZE_BATCH) + .ok_or(anyhow!("Failed to create slots_batch::Iter with slot_start = {slot_start}, slot_last = {slot_last}."))?; - if replay_back_slots_start( + replay_back_slots_start( client_http, beacon_endpoint, client, @@ -133,11 +103,7 @@ pub async fn execute( slots_batch_iter.next(), sync_update, ) - .await - .is_none() - { - return; - } + .await?; replay_back_slots( client_http, @@ -148,9 +114,11 @@ pub async fn execute( gas_limit, slots_batch_iter, ) - .await; + .await?; log::info!("Replaying back finished"); + + Ok(()) } async fn replay_back_slots( @@ -161,7 +129,7 @@ async fn replay_back_slots( program_id: [u8; 32], gas_limit: u64, slots_batch_iter: SlotsBatchIter, -) -> Option<()> { +) -> AnyResult<()> { for (slot_start, slot_end) in slots_batch_iter { replay_back_slots_inner( client_http, @@ -176,7 +144,7 @@ async fn replay_back_slots( .await?; } - Some(()) + Ok(()) } #[allow(clippy::too_many_arguments)] @@ -189,7 +157,7 @@ async fn replay_back_slots_inner( slot_start: Slot, slot_end: Slot, gas_limit: u64, -) -> Option<()> { +) -> AnyResult<()> { let payload = Handle::ReplayBack( request_headers(client_http, beacon_endpoint, slot_start, slot_end).await?, ); @@ -197,28 +165,24 @@ async fn replay_back_slots_inner( let (message_id, _) = client .send_message(program_id.into(), payload, gas_limit, 0) .await - .map_err(|e| log::error!("Failed to send ReplayBack message: {e:?}")) - .ok()?; + .map_err(|e| anyhow!("Failed to send ReplayBack message: {e:?}"))?; let (_message_id, payload, _value) = listener .reply_bytes_on(message_id) .await - .map_err(|e| log::error!("Failed to get reply to ReplayBack message: {e:?}")) - .ok()?; - let payload = payload - .map_err(|e| log::error!("Failed to get replay payload to ReplayBack: {e:?}")) - .ok()?; + .map_err(|e| anyhow!("Failed to get reply to ReplayBack message: {e:?}"))?; + let payload = + payload.map_err(|e| anyhow!("Failed to get replay payload to ReplayBack: {e:?}"))?; let result_decoded = HandleResult::decode(&mut &payload[..]) - .map_err(|e| log::error!("Failed to decode HandleResult of ReplayBack: {e:?}")) - .ok()?; + .map_err(|e| anyhow!("Failed to decode HandleResult of ReplayBack: {e:?}"))?; log::debug!("replay_back_slots_inner; result_decoded = {result_decoded:?}"); - matches!( - result_decoded, - HandleResult::ReplayBack(Some(Status::InProcess | Status::Finished)) - ) - .then_some(()) + match result_decoded { + HandleResult::ReplayBack(Some(_)) => Ok(()), + HandleResult::ReplayBack(None) => Err(anyhow!("Replaying back wasn't started")), + _ => Err(anyhow!("Wrong handle result to ReplayBack")), + } } #[allow(clippy::too_many_arguments)] @@ -231,9 +195,9 @@ async fn replay_back_slots_start( gas_limit: u64, slots: Option<(Slot, Slot)>, sync_update: SyncCommitteeUpdate, -) -> Option<()> { +) -> AnyResult<()> { let Some((slot_start, slot_end)) = slots else { - return Some(()); + return Ok(()); }; let payload = Handle::ReplayBackStart { @@ -244,36 +208,32 @@ async fn replay_back_slots_start( let (message_id, _) = client .send_message(program_id.into(), payload, gas_limit, 0) .await - .map_err(|e| log::error!("Failed to send ReplayBackStart message: {e:?}")) - .ok()?; + .map_err(|e| anyhow!("Failed to send ReplayBackStart message: {e:?}"))?; let (_message_id, payload, _value) = listener .reply_bytes_on(message_id) .await - .map_err(|e| log::error!("Failed to get reply to ReplayBackStart message: {e:?}")) - .ok()?; - let payload = payload - .map_err(|e| log::error!("Failed to get replay payload to ReplayBackStart: {e:?}")) - .ok()?; + .map_err(|e| anyhow!("Failed to get reply to ReplayBackStart message: {e:?}"))?; + let payload = + payload.map_err(|e| anyhow!("Failed to get replay payload to ReplayBackStart: {e:?}"))?; let result_decoded = HandleResult::decode(&mut &payload[..]) - .map_err(|e| log::error!("Failed to decode HandleResult of ReplayBackStart: {e:?}")) - .ok()?; + .map_err(|e| anyhow!("Failed to decode HandleResult of ReplayBackStart: {e:?}"))?; log::debug!("replay_back_slots_start; result_decoded = {result_decoded:?}"); - matches!( - result_decoded, - HandleResult::ReplayBackStart(Ok(StatusStart::InProgress | StatusStart::Finished)) - ) - .then_some(()) + match result_decoded { + HandleResult::ReplayBackStart(Ok(_)) => Ok(()), + HandleResult::ReplayBackStart(Err(e)) => Err(anyhow!("ReplayBackStart failed: {e:?}")), + _ => Err(anyhow!("Wrong handle result to ReplayBackStart")), + } } -pub async fn request_headers( +async fn request_headers( client_http: &Client, beacon_endpoint: &str, slot_start: Slot, slot_end: Slot, -) -> Option> { +) -> AnyResult> { let batch_size = (slot_end - slot_start) as usize; let mut requests_headers = Vec::with_capacity(batch_size); for i in slot_start..slot_end { @@ -286,7 +246,6 @@ pub async fn request_headers( .filter(|maybe_header| !matches!(maybe_header, Err(e) if e.downcast_ref::().is_some())) .collect::, _>>() .map_err(|e| { - log::error!("Failed to fetch block headers ([{slot_start}; {slot_end})): {e:?}") + anyhow!("Failed to fetch block headers ([{slot_start}; {slot_end})): {e:?}") }) - .ok() } diff --git a/relayer/src/ethereum_checkpoints/sync_update.rs b/relayer/src/ethereum_checkpoints/sync_update.rs index 88287d62..226e48b4 100644 --- a/relayer/src/ethereum_checkpoints/sync_update.rs +++ b/relayer/src/ethereum_checkpoints/sync_update.rs @@ -1,5 +1,4 @@ use super::*; -use anyhow::{anyhow, Result as AnyResult}; pub use checkpoint_light_client_io::sync_update::Error; use std::ops::ControlFlow::{self, *}; @@ -7,24 +6,26 @@ pub fn spawn_receiver( client_http: Client, beacon_endpoint: String, sender: Sender, - delay: Duration, ) { tokio::spawn(async move { log::info!("Update receiver spawned"); + let duration = Duration::from_secs(DELAY_SECS_UPDATE_REQUEST); let mut failures = 0; loop { - match receive( - &client_http, - &beacon_endpoint, - &sender, - delay, - &mut failures, - ) - .await - { - Break(_) => break, - Continue(_) => (), + match receive(&client_http, &beacon_endpoint, &sender).await { + Ok(Break(_)) => break, + Ok(Continue(_)) => time::sleep(duration).await, + Err(e) => { + log::error!("{e:?}"); + + failures += 1; + if failures >= COUNT_FAILURE { + break; + } + + time::sleep(duration).await; + } } } }); @@ -34,47 +35,19 @@ async fn receive( client_http: &Client, beacon_endpoint: &str, sender: &Sender, - delay: Duration, - failures: &mut usize, -) -> ControlFlow<()> { - // helper macro - macro_rules! check_and_return { - () => { - *failures += 1; - if *failures >= COUNT_FAILURE { - return Break(()); - } - - time::sleep(delay).await; - - return Continue(()); - }; - } - - let finality_update = match utils::get_finality_update(client_http, beacon_endpoint).await { - Ok(finality_update) => finality_update, - - Err(e) => { - log::error!("Unable to fetch FinalityUpdate: {e:?}"); - check_and_return!(); - } - }; +) -> AnyResult> { + let finality_update = utils::get_finality_update(client_http, beacon_endpoint) + .await + .map_err(|e| anyhow!("Unable to fetch FinalityUpdate: {e:?}"))?; let period = eth_utils::calculate_period(finality_update.finalized_header.slot); - let mut updates = match utils::get_updates(client_http, beacon_endpoint, period, 1).await { - Ok(updates) => updates, - Err(e) => { - log::error!("Unable to fetch Updates: {e:?}"); - check_and_return!(); - } - }; + let mut updates = utils::get_updates(client_http, beacon_endpoint, period, 1) + .await + .map_err(|e| anyhow!("Unable to fetch Updates: {e:?}"))?; let update = match updates.pop() { Some(update) if updates.is_empty() => update.data, - _ => { - log::error!("Requested single update"); - check_and_return!(); - } + _ => return Err(anyhow!("Requested single update")), }; let reader_signature = if update.finalized_header.slot >= finality_update.finalized_header.slot @@ -84,12 +57,9 @@ async fn receive( &finality_update.sync_aggregate.sync_committee_signature.0 .0[..] }; - let Ok(signature) = + let signature = ::deserialize_compressed(reader_signature) - else { - log::error!("Failed to deserialize point on G2"); - check_and_return!(); - }; + .map_err(|e| anyhow!("Failed to deserialize point on G2: {e:?}"))?; let sync_update = if update.finalized_header.slot >= finality_update.finalized_header.slot { utils::sync_update_from_update(signature, update) @@ -98,12 +68,12 @@ async fn receive( }; if sender.send(sync_update).await.is_err() { - return Break(()); + return Ok(Break(())); } - time::sleep(delay).await; + time::sleep(Duration::from_secs(DELAY_SECS_UPDATE_REQUEST)).await; - Continue(()) + Ok(Continue(())) } pub async fn try_to_apply( diff --git a/relayer/src/ethereum_checkpoints/tests/mod.rs b/relayer/src/ethereum_checkpoints/tests/mod.rs index 6c4b4137..3d5aaaaa 100644 --- a/relayer/src/ethereum_checkpoints/tests/mod.rs +++ b/relayer/src/ethereum_checkpoints/tests/mod.rs @@ -15,6 +15,9 @@ use std::env; use tokio::time::{self, Duration}; const RPC_URL: &str = "http://127.0.0.1:5052"; +const NETWORK_MAINNET: &str = "Mainnet"; +const NETWORK_HOLESKY: &str = "Holesky"; +const NETWORK_SEPOLIA: &str = ""; const FINALITY_UPDATE_5_254_112: &[u8; 4_940] = include_bytes!("./sepolia-finality-update-5_254_112.json"); @@ -61,6 +64,77 @@ async fn upload_program( Ok(program_id) } +#[ignore] +#[tokio::test] +async fn init() -> Result<()> { + let client_http = Client::new(); + + let rpc_url = env::var("RPC_URL").unwrap_or(RPC_URL.into()); + + // use the latest finality header as a checkpoint for bootstrapping + let finality_update = utils::get_finality_update(&client_http, &rpc_url).await?; + let current_period = eth_utils::calculate_period(finality_update.finalized_header.slot); + let mut updates = utils::get_updates(&client_http, &rpc_url, 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 = utils::get_bootstrap(&client_http, &rpc_url, &checkpoint_hex).await?; + + let signature = ::deserialize_compressed( + &update.sync_aggregate.sync_committee_signature.0 .0[..], + ) + .unwrap(); + let sync_update = utils::sync_update_from_update(signature, update); + + println!("bootstrap slot = {}", bootstrap.header.slot); + + let pub_keys = utils::map_public_keys(&bootstrap.current_sync_committee.pubkeys); + let network = match env::var("NETWORK") { + Ok(network) if network == NETWORK_HOLESKY => Network::Holesky, + Ok(network) if network == NETWORK_MAINNET => Network::Mainnet, + Ok(network) if network == NETWORK_SEPOLIA => Network::Sepolia, + Ok(network) => panic!("Unknown network: {network}"), + Err(e) => panic!("Failed to read environment variable: {e:?}"), + }; + 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::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_and_updating() -> Result<()> { let client_http = Client::new(); @@ -102,9 +176,11 @@ async fn init_and_updating() -> Result<()> { let pub_keys = utils::map_public_keys(&bootstrap.current_sync_committee.pubkeys); let network = match env::var("NETWORK") { - Ok(network) if network == "Holesky" => Network::Holesky, - Ok(network) if network == "Mainnet" => Network::Mainnet, - _ => Network::Sepolia, + Ok(network) if network == NETWORK_HOLESKY => Network::Holesky, + Ok(network) if network == NETWORK_MAINNET => Network::Mainnet, + Ok(network) if network == NETWORK_SEPOLIA => Network::Sepolia, + Ok(network) => panic!("Unknown network: {network}"), + Err(e) => panic!("Failed to read environment variable: {e:?}"), }; let init = Init { network, @@ -118,7 +194,6 @@ async fn init_and_updating() -> Result<()> { update: sync_update, }; - // let client = GearApi::dev_from_path("../target/release/gear").await?; let client = GearApi::dev().await?; let mut listener = client.subscribe().await?; @@ -129,10 +204,6 @@ async fn init_and_updating() -> Result<()> { println!(); println!(); - if env::var("UPDATING").is_err() { - return Ok(()); - } - for _ in 0..30 { let update = utils::get_finality_update(&client_http, &rpc_url).await?; @@ -210,6 +281,7 @@ async fn init_and_updating() -> Result<()> { Ok(()) } +#[ignore] #[tokio::test] async fn replaying_back() -> Result<()> { let client_http = Client::new(); @@ -262,7 +334,6 @@ async fn replaying_back() -> Result<()> { update: sync_update, }; - // let client = GearApi::dev_from_path("../target/release/gear").await?; let client = GearApi::dev().await?; let mut listener = client.subscribe().await?; @@ -354,6 +425,7 @@ async fn replaying_back() -> Result<()> { Ok(()) } +#[ignore] #[tokio::test] async fn sync_update_requires_replaying_back() -> Result<()> { let client_http = Client::new(); diff --git a/relayer/src/main.rs b/relayer/src/main.rs index e318f42a..4ca1c0c0 100644 --- a/relayer/src/main.rs +++ b/relayer/src/main.rs @@ -131,26 +131,26 @@ struct ProofStorageArgs { #[derive(Args)] struct RelayCheckpointsArgs { /// Specify ProgramId of the Checkpoint-light-client program - #[arg(long)] + #[arg(long, env = "PROGRAM_ID")] program_id: String, /// Specify an endpoint providing Beacon API - #[arg(long)] + #[arg(long, env = "BEACON_ENDPOINT")] beacon_endpoint: String, /// Domain of the VARA RPC endpoint - #[arg(long, default_value = "ws://127.0.0.1")] + #[arg(long, default_value = "ws://127.0.0.1", env = "VARA_DOMAIN")] vara_domain: String, /// Port of the VARA RPC endpoint - #[arg(long, default_value = "9944")] + #[arg(long, default_value = "9944", env = "VARA_PORT")] vara_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")] + #[arg(long, default_value = "//Alice", env = "SURI")] suri: String, #[clap(flatten)] From f741abb32ccffadee171128c2224dfe4277df1f6 Mon Sep 17 00:00:00 2001 From: Georgy Shepelev Date: Mon, 5 Aug 2024 14:14:47 +0400 Subject: [PATCH 23/24] fix review remarks --- relayer/src/ethereum_checkpoints/mod.rs | 4 ++-- relayer/src/ethereum_checkpoints/sync_update.rs | 9 +++------ relayer/src/ethereum_checkpoints/tests/mod.rs | 2 +- relayer/src/main.rs | 6 +++--- 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/relayer/src/ethereum_checkpoints/mod.rs b/relayer/src/ethereum_checkpoints/mod.rs index a087affa..635841e0 100644 --- a/relayer/src/ethereum_checkpoints/mod.rs +++ b/relayer/src/ethereum_checkpoints/mod.rs @@ -40,7 +40,7 @@ pub async fn relay(args: RelayCheckpointsArgs) { beacon_endpoint, vara_domain, vara_port, - suri, + vara_suri, prometheus_args: PrometheusArgs { endpoint: endpoint_prometheus, }, @@ -63,7 +63,7 @@ pub async fn relay(args: RelayCheckpointsArgs) { sync_update::spawn_receiver(client_http.clone(), beacon_endpoint.clone(), sender); - let client = GearApi::init_with(WSAddress::new(vara_domain, vara_port), suri) + let client = GearApi::init_with(WSAddress::new(vara_domain, vara_port), vara_suri) .await .expect("GearApi client should be created"); diff --git a/relayer/src/ethereum_checkpoints/sync_update.rs b/relayer/src/ethereum_checkpoints/sync_update.rs index 226e48b4..dad03014 100644 --- a/relayer/src/ethereum_checkpoints/sync_update.rs +++ b/relayer/src/ethereum_checkpoints/sync_update.rs @@ -10,12 +10,11 @@ pub fn spawn_receiver( tokio::spawn(async move { log::info!("Update receiver spawned"); - let duration = Duration::from_secs(DELAY_SECS_UPDATE_REQUEST); let mut failures = 0; loop { match receive(&client_http, &beacon_endpoint, &sender).await { Ok(Break(_)) => break, - Ok(Continue(_)) => time::sleep(duration).await, + Ok(Continue(_)) => (), Err(e) => { log::error!("{e:?}"); @@ -23,10 +22,10 @@ pub fn spawn_receiver( if failures >= COUNT_FAILURE { break; } - - time::sleep(duration).await; } } + + time::sleep(Duration::from_secs(DELAY_SECS_UPDATE_REQUEST)).await; } }); } @@ -71,8 +70,6 @@ async fn receive( return Ok(Break(())); } - time::sleep(Duration::from_secs(DELAY_SECS_UPDATE_REQUEST)).await; - Ok(Continue(())) } diff --git a/relayer/src/ethereum_checkpoints/tests/mod.rs b/relayer/src/ethereum_checkpoints/tests/mod.rs index 3d5aaaaa..273799de 100644 --- a/relayer/src/ethereum_checkpoints/tests/mod.rs +++ b/relayer/src/ethereum_checkpoints/tests/mod.rs @@ -17,7 +17,7 @@ use tokio::time::{self, Duration}; const RPC_URL: &str = "http://127.0.0.1:5052"; const NETWORK_MAINNET: &str = "Mainnet"; const NETWORK_HOLESKY: &str = "Holesky"; -const NETWORK_SEPOLIA: &str = ""; +const NETWORK_SEPOLIA: &str = "Sepolia"; const FINALITY_UPDATE_5_254_112: &[u8; 4_940] = include_bytes!("./sepolia-finality-update-5_254_112.json"); diff --git a/relayer/src/main.rs b/relayer/src/main.rs index 4ca1c0c0..0d08fd52 100644 --- a/relayer/src/main.rs +++ b/relayer/src/main.rs @@ -131,7 +131,7 @@ struct ProofStorageArgs { #[derive(Args)] struct RelayCheckpointsArgs { /// Specify ProgramId of the Checkpoint-light-client program - #[arg(long, env = "PROGRAM_ID")] + #[arg(long, env = "CHECKPOINT_LIGHT_CLIENT_ADDRESS")] program_id: String, /// Specify an endpoint providing Beacon API @@ -150,8 +150,8 @@ struct RelayCheckpointsArgs { /// 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 = "SURI")] - suri: String, + #[arg(long, default_value = "//Alice", env = "VARA_SURI")] + vara_suri: String, #[clap(flatten)] prometheus_args: PrometheusArgs, From 08aa4ae4c2fe45325af4af7ecc431ac308dbaa64 Mon Sep 17 00:00:00 2001 From: Georgy Shepelev Date: Mon, 5 Aug 2024 15:34:06 +0400 Subject: [PATCH 24/24] refactor tests --- relayer/src/ethereum_checkpoints/tests/mod.rs | 62 +++++++++---------- 1 file changed, 28 insertions(+), 34 deletions(-) diff --git a/relayer/src/ethereum_checkpoints/tests/mod.rs b/relayer/src/ethereum_checkpoints/tests/mod.rs index 273799de..b7b4418a 100644 --- a/relayer/src/ethereum_checkpoints/tests/mod.rs +++ b/relayer/src/ethereum_checkpoints/tests/mod.rs @@ -11,13 +11,9 @@ use checkpoint_light_client_io::{ use gclient::{EventListener, EventProcessor, GearApi, Result}; use parity_scale_codec::{Decode, Encode}; use reqwest::Client; -use std::env; use tokio::time::{self, Duration}; const RPC_URL: &str = "http://127.0.0.1:5052"; -const NETWORK_MAINNET: &str = "Mainnet"; -const NETWORK_HOLESKY: &str = "Holesky"; -const NETWORK_SEPOLIA: &str = "Sepolia"; const FINALITY_UPDATE_5_254_112: &[u8; 4_940] = include_bytes!("./sepolia-finality-update-5_254_112.json"); @@ -64,17 +60,13 @@ async fn upload_program( Ok(program_id) } -#[ignore] -#[tokio::test] -async fn init() -> Result<()> { +async fn init(network: Network) -> Result<()> { let client_http = Client::new(); - let rpc_url = env::var("RPC_URL").unwrap_or(RPC_URL.into()); - // use the latest finality header as a checkpoint for bootstrapping - let finality_update = utils::get_finality_update(&client_http, &rpc_url).await?; + let finality_update = utils::get_finality_update(&client_http, RPC_URL).await?; let current_period = eth_utils::calculate_period(finality_update.finalized_header.slot); - let mut updates = utils::get_updates(&client_http, &rpc_url, current_period, 1).await?; + let mut updates = utils::get_updates(&client_http, RPC_URL, current_period, 1).await?; println!( "finality_update slot = {}, period = {}", @@ -94,7 +86,7 @@ async fn init() -> Result<()> { update.finalized_header.slot, checkpoint_hex ); - let bootstrap = utils::get_bootstrap(&client_http, &rpc_url, &checkpoint_hex).await?; + let bootstrap = utils::get_bootstrap(&client_http, RPC_URL, &checkpoint_hex).await?; let signature = ::deserialize_compressed( &update.sync_aggregate.sync_committee_signature.0 .0[..], @@ -105,13 +97,6 @@ async fn init() -> Result<()> { println!("bootstrap slot = {}", bootstrap.header.slot); let pub_keys = utils::map_public_keys(&bootstrap.current_sync_committee.pubkeys); - let network = match env::var("NETWORK") { - Ok(network) if network == NETWORK_HOLESKY => Network::Holesky, - Ok(network) if network == NETWORK_MAINNET => Network::Mainnet, - Ok(network) if network == NETWORK_SEPOLIA => Network::Sepolia, - Ok(network) => panic!("Unknown network: {network}"), - Err(e) => panic!("Failed to read environment variable: {e:?}"), - }; let init = Init { network, sync_committee_current_pub_keys: pub_keys, @@ -134,17 +119,33 @@ async fn init() -> Result<()> { 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 +} + +#[ignore] +#[tokio::test] +async fn init_mainnet() -> Result<()> { + init(Network::Mainnet).await +} + #[ignore] #[tokio::test] async fn init_and_updating() -> Result<()> { let client_http = Client::new(); - let rpc_url = env::var("RPC_URL").unwrap_or(RPC_URL.into()); - // use the latest finality header as a checkpoint for bootstrapping - let finality_update = utils::get_finality_update(&client_http, &rpc_url).await?; + let finality_update = utils::get_finality_update(&client_http, RPC_URL).await?; let current_period = eth_utils::calculate_period(finality_update.finalized_header.slot); - let mut updates = utils::get_updates(&client_http, &rpc_url, current_period, 1).await?; + let mut updates = utils::get_updates(&client_http, RPC_URL, current_period, 1).await?; println!( "finality_update slot = {}, period = {}", @@ -164,7 +165,7 @@ async fn init_and_updating() -> Result<()> { update.finalized_header.slot, checkpoint_hex ); - let bootstrap = utils::get_bootstrap(&client_http, &rpc_url, &checkpoint_hex).await?; + let bootstrap = utils::get_bootstrap(&client_http, RPC_URL, &checkpoint_hex).await?; let signature = ::deserialize_compressed( &update.sync_aggregate.sync_committee_signature.0 .0[..], @@ -175,15 +176,8 @@ async fn init_and_updating() -> Result<()> { println!("bootstrap slot = {}", bootstrap.header.slot); let pub_keys = utils::map_public_keys(&bootstrap.current_sync_committee.pubkeys); - let network = match env::var("NETWORK") { - Ok(network) if network == NETWORK_HOLESKY => Network::Holesky, - Ok(network) if network == NETWORK_MAINNET => Network::Mainnet, - Ok(network) if network == NETWORK_SEPOLIA => Network::Sepolia, - Ok(network) => panic!("Unknown network: {network}"), - Err(e) => panic!("Failed to read environment variable: {e:?}"), - }; let init = Init { - network, + 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 @@ -205,11 +199,11 @@ async fn init_and_updating() -> Result<()> { println!(); for _ in 0..30 { - let update = utils::get_finality_update(&client_http, &rpc_url).await?; + let update = utils::get_finality_update(&client_http, RPC_URL).await?; let slot: u64 = update.finalized_header.slot; let current_period = eth_utils::calculate_period(slot); - let mut updates = utils::get_updates(&client_http, &rpc_url, current_period, 1).await?; + let mut updates = utils::get_updates(&client_http, RPC_URL, current_period, 1).await?; match updates.pop() { Some(update) if updates.is_empty() && update.data.finalized_header.slot >= slot => { println!("update sync committee");