From a5d23388d24a50cbabf53135f432d8cf108a0a94 Mon Sep 17 00:00:00 2001 From: karim-en Date: Tue, 8 Aug 2023 02:00:49 +0100 Subject: [PATCH 1/8] Sleep after failed light client submition --- .../src/eth2near_relay.rs | 62 +++++++++---------- 1 file changed, 28 insertions(+), 34 deletions(-) diff --git a/eth2near/eth2near-block-relay-rs/src/eth2near_relay.rs b/eth2near/eth2near-block-relay-rs/src/eth2near_relay.rs index c7ddeb47..312743c9 100644 --- a/eth2near/eth2near-block-relay-rs/src/eth2near_relay.rs +++ b/eth2near/eth2near-block-relay-rs/src/eth2near_relay.rs @@ -43,18 +43,6 @@ macro_rules! skip_fail { }; } -macro_rules! return_on_fail { - ($res:expr, $msg:expr) => { - match $res { - Ok(val) => val, - Err(e) => { - warn!(target: "relay", "{}. Error: {}", $msg, e); - return; - } - } - }; -} - macro_rules! return_val_on_fail { ($res:expr, $msg:expr, $val:expr) => { match $res { @@ -447,8 +435,10 @@ impl Eth2NearRelay { last_finalized_slot_on_near, last_finalized_slot_on_eth, ) { - self.send_light_client_updates(last_finalized_slot_on_near, last_finalized_slot_on_eth); - return true; + return self.send_light_client_updates( + last_finalized_slot_on_near, + last_finalized_slot_on_eth, + ); } return false; @@ -458,13 +448,13 @@ impl Eth2NearRelay { &mut self, last_finalized_slot_on_near: u64, last_finalized_slot_on_eth: u64, - ) { + ) -> bool { info!(target: "relay", "= Sending light client update ="); if self.is_shot_run_mode() { info!(target: "relay", "Try sending light client update from file"); self.send_light_client_update_from_file(); - return; + return true; } if self.get_light_client_update_by_epoch { @@ -472,7 +462,7 @@ impl Eth2NearRelay { last_finalized_slot_on_eth, last_finalized_slot_on_near, ) { - return; + return false; } } @@ -480,12 +470,12 @@ impl Eth2NearRelay { >= last_finalized_slot_on_near + self.max_blocks_for_finalization { info!(target: "relay", "Too big gap between slot of finalized block on NEAR and ETH. Sending hand made light client update"); - self.send_hand_made_light_client_update(last_finalized_slot_on_near); + self.send_hand_made_light_client_update(last_finalized_slot_on_near) } else { self.send_regular_light_client_update( last_finalized_slot_on_eth, last_finalized_slot_on_near, - ); + ) } } @@ -500,7 +490,7 @@ impl Eth2NearRelay { &mut self, last_finalized_slot_on_eth: u64, last_finalized_slot_on_near: u64, - ) { + ) -> bool { let last_eth2_period_on_near_chain = BeaconRPCClient::get_period_for_slot(last_finalized_slot_on_near); info!(target: "relay", "Last finalized slot/period on near={}/{}", last_finalized_slot_on_near, last_eth2_period_on_near_chain); @@ -510,20 +500,22 @@ impl Eth2NearRelay { let light_client_update = if end_period == last_eth2_period_on_near_chain { debug!(target: "relay", "Finalized period on ETH and NEAR are equal. Don't fetch sync commity update"); - return_on_fail!( + return_val_on_fail!( self.beacon_rpc_client.get_finality_light_client_update(), - "Error on getting light client update. Skipping sending light client update" + "Error on getting light client update. Skipping sending light client update", + false ) } else { debug!(target: "relay", "Finalized period on ETH and NEAR are different. Fetching sync commity update"); - return_on_fail!( + return_val_on_fail!( self.beacon_rpc_client .get_light_client_update(last_eth2_period_on_near_chain + 1), - "Error on getting light client update. Skipping sending light client update" + "Error on getting light client update. Skipping sending light client update", + false ) }; - self.send_specific_light_client_update(light_client_update); + self.send_specific_light_client_update(light_client_update) } fn send_regular_light_client_update_by_epoch( @@ -596,10 +588,11 @@ impl Eth2NearRelay { Ok(attested_slot) } - fn send_hand_made_light_client_update(&mut self, last_finalized_slot_on_near: u64) { - let mut attested_slot = return_on_fail!( + fn send_hand_made_light_client_update(&mut self, last_finalized_slot_on_near: u64) -> bool { + let mut attested_slot = return_val_on_fail!( self.get_attested_slot(last_finalized_slot_on_near), - "Error on getting attested slot" + "Error on getting attested slot", + false ); let include_next_sync_committee = @@ -607,7 +600,7 @@ impl Eth2NearRelay { != BeaconRPCClient::get_period_for_slot(attested_slot); loop { - let light_client_update = return_on_fail!( + let light_client_update = return_val_on_fail!( HandMadeFinalityLightClientUpdate::get_finality_light_client_update( &self.beacon_rpc_client, attested_slot, @@ -616,7 +609,8 @@ impl Eth2NearRelay { format!( "Error on getting hand made light client update for attested slot={}.", attested_slot - ) + ), + false ); let finality_update_slot = light_client_update @@ -627,16 +621,16 @@ impl Eth2NearRelay { if finality_update_slot <= last_finalized_slot_on_near { info!(target: "relay", "Finality update slot for hand made light client update <= last finality update on NEAR. Increment gap for attested slot and skipping light client update."); - attested_slot = return_on_fail!( + attested_slot = return_val_on_fail!( self.get_attested_slot(last_finalized_slot_on_near + ONE_EPOCH_IN_SLOTS), - "Error on getting attested slot" + "Error on getting attested slot", + false ); continue; } trace!(target: "relay", "Hand made light client update: {:?}", light_client_update); - self.send_specific_light_client_update(light_client_update); - return; + return self.send_specific_light_client_update(light_client_update); } } From d676b2b831cb9c3cfeb35d0948f7259fcf3f12ce Mon Sep 17 00:00:00 2001 From: karim-en Date: Tue, 12 Sep 2023 22:05:15 +0100 Subject: [PATCH 2/8] Set max number of iteration on waiting for in porgress proposal --- eth2near/contract_wrapper/src/dao_eth_client_contract.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/eth2near/contract_wrapper/src/dao_eth_client_contract.rs b/eth2near/contract_wrapper/src/dao_eth_client_contract.rs index 5f962111..aeff4b5c 100644 --- a/eth2near/contract_wrapper/src/dao_eth_client_contract.rs +++ b/eth2near/contract_wrapper/src/dao_eth_client_contract.rs @@ -63,7 +63,9 @@ impl EthClientContractTrait for DaoEthClientContract { light_client_update, )?; - loop { + let max_num_of_iterations = 10; + let mut count = 0; + while count < max_num_of_iterations { let proposal_status = self.dao_contract.get_proposal(proposal_id); if let Ok(staus) = proposal_status { if staus.proposal.status != dao_types::ProposalStatus::InProgress { @@ -72,6 +74,7 @@ impl EthClientContractTrait for DaoEthClientContract { } thread::sleep(Duration::from_secs(10)); + count += 1; } Ok(execution_outcome) From f394adb72fc555cea10d1c6afa133240f6423225 Mon Sep 17 00:00:00 2001 From: karim-en Date: Fri, 22 Sep 2023 22:02:47 +0100 Subject: [PATCH 3/8] Force submit light client update after timeout --- .../src/dao_eth_client_contract.rs | 40 +++++++++---------- .../src/eth_client_contract.rs | 4 ++ .../src/eth_client_contract_trait.rs | 3 ++ .../src/file_eth_client_contract.rs | 4 ++ .../eth2near-block-relay-rs/src/config.rs | 3 ++ .../src/eth2near_relay.rs | 13 ++++++ 6 files changed, 47 insertions(+), 20 deletions(-) diff --git a/eth2near/contract_wrapper/src/dao_eth_client_contract.rs b/eth2near/contract_wrapper/src/dao_eth_client_contract.rs index aeff4b5c..33039e69 100644 --- a/eth2near/contract_wrapper/src/dao_eth_client_contract.rs +++ b/eth2near/contract_wrapper/src/dao_eth_client_contract.rs @@ -36,26 +36,6 @@ impl EthClientContractTrait for DaoEthClientContract { &mut self, light_client_update: LightClientUpdate, ) -> Result> { - // Check for already submitted updates - let last_proposal_id = self.dao_contract.get_last_proposal_id()?; - if last_proposal_id > 0 { - let last_proposal_output = self.dao_contract.get_proposal(last_proposal_id - 1)?; - if last_proposal_output.proposal.status == dao_types::ProposalStatus::InProgress - && last_proposal_output.proposal.proposer.to_string() - == self - .dao_contract - .contract_wrapper - .get_signer_account_id() - .to_string() - { - return Err(format!( - "A proposal {} has already been submitted by this relayer which is in progress", - last_proposal_id - ) - .into()); - } - } - // Submmit new proposal let (proposal_id, execution_outcome) = self.dao_contract.submit_light_client_update_proposal( @@ -80,6 +60,26 @@ impl EthClientContractTrait for DaoEthClientContract { Ok(execution_outcome) } + fn is_ready_to_submit_light_client_update(&self) -> Result> { + // Check for already submitted updates + let last_proposal_id = self.dao_contract.get_last_proposal_id()?; + if last_proposal_id > 0 { + let last_proposal_output = self.dao_contract.get_proposal(last_proposal_id - 1)?; + if last_proposal_output.proposal.status == dao_types::ProposalStatus::InProgress + && last_proposal_output.proposal.proposer.to_string() + == self + .dao_contract + .contract_wrapper + .get_signer_account_id() + .to_string() + { + return Ok(false); + } + } + + return Ok(true); + } + fn get_finalized_beacon_block_hash(&self) -> Result> { self.eth_client_contract.get_finalized_beacon_block_hash() } diff --git a/eth2near/contract_wrapper/src/eth_client_contract.rs b/eth2near/contract_wrapper/src/eth_client_contract.rs index 927e0029..60ed68cb 100644 --- a/eth2near/contract_wrapper/src/eth_client_contract.rs +++ b/eth2near/contract_wrapper/src/eth_client_contract.rs @@ -114,6 +114,10 @@ impl EthClientContractTrait for EthClientContract { ) } + fn is_ready_to_submit_light_client_update(&self) -> Result> { + Ok(true) + } + fn get_finalized_beacon_block_hash(&self) -> Result> { let result = self.contract_wrapper.call_view_function( "finalized_beacon_block_root".to_string(), diff --git a/eth2near/contract_wrapper/src/eth_client_contract_trait.rs b/eth2near/contract_wrapper/src/eth_client_contract_trait.rs index 96c6f77b..9429dd30 100644 --- a/eth2near/contract_wrapper/src/eth_client_contract_trait.rs +++ b/eth2near/contract_wrapper/src/eth_client_contract_trait.rs @@ -12,6 +12,9 @@ pub trait EthClientContractTrait { light_client_update: LightClientUpdate, ) -> Result>; + /// Check if the client is ready to accept new update + fn is_ready_to_submit_light_client_update(&self) -> Result>; + /// Gets finalized beacon block hash from Ethereum Light Client on NEAR fn get_finalized_beacon_block_hash(&self) -> Result>; diff --git a/eth2near/contract_wrapper/src/file_eth_client_contract.rs b/eth2near/contract_wrapper/src/file_eth_client_contract.rs index fbb91257..32f62142 100644 --- a/eth2near/contract_wrapper/src/file_eth_client_contract.rs +++ b/eth2near/contract_wrapper/src/file_eth_client_contract.rs @@ -57,6 +57,10 @@ impl EthClientContractTrait for FileEthClientContract { .send_light_client_update(light_client_update) } + fn is_ready_to_submit_light_client_update(&self) -> Result> { + Ok(true) + } + fn get_finalized_beacon_block_hash(&self) -> Result> { self.eth_client_contract.get_finalized_beacon_block_hash() } diff --git a/eth2near/eth2near-block-relay-rs/src/config.rs b/eth2near/eth2near-block-relay-rs/src/config.rs index a9ec21a8..cd629bdd 100644 --- a/eth2near/eth2near-block-relay-rs/src/config.rs +++ b/eth2near/eth2near-block-relay-rs/src/config.rs @@ -77,6 +77,9 @@ pub struct Config { // Sleep time in seconds after blocks/light_client_update submission to client pub sleep_time_after_submission_secs: u64, + // Sleep time in seconds waiting for in-progress proposal to be processed + pub sleep_time_on_in_progress_proposal_secs: u64, + /// Max number of stored blocks in the storage of the eth2 client contract. /// Events that happen past this threshold cannot be verified by the client. /// It is used on initialization of the Eth2 client. diff --git a/eth2near/eth2near-block-relay-rs/src/eth2near_relay.rs b/eth2near/eth2near-block-relay-rs/src/eth2near_relay.rs index 312743c9..89d38892 100644 --- a/eth2near/eth2near-block-relay-rs/src/eth2near_relay.rs +++ b/eth2near/eth2near-block-relay-rs/src/eth2near_relay.rs @@ -94,6 +94,7 @@ pub struct Eth2NearRelay { next_light_client_update: Option, sleep_time_on_sync_secs: u64, sleep_time_after_submission_secs: u64, + sleep_time_on_in_progress_proposal_secs: u64, get_light_client_update_by_epoch: bool, } @@ -126,6 +127,7 @@ impl Eth2NearRelay { next_light_client_update, sleep_time_on_sync_secs: config.sleep_time_on_sync_secs, sleep_time_after_submission_secs: config.sleep_time_after_submission_secs, + sleep_time_on_in_progress_proposal_secs: config.sleep_time_on_in_progress_proposal_secs, get_light_client_update_by_epoch: config .get_light_client_update_by_epoch .unwrap_or(false), @@ -651,6 +653,17 @@ impl Eth2NearRelay { return false; } + if !return_val_on_fail!( + self.eth_client_contract + .is_ready_to_submit_light_client_update(), + "Fail to send light client update", + false + ) { + thread::sleep(Duration::from_secs( + self.sleep_time_on_in_progress_proposal_secs, + )); + } + let execution_outcome = return_val_on_fail_and_sleep!( self.eth_client_contract .send_light_client_update(light_client_update.clone()), From 81a01bd9c23d44ff9a1a786a21c571195846c6f8 Mon Sep 17 00:00:00 2001 From: karim-en Date: Fri, 22 Sep 2023 22:15:24 +0100 Subject: [PATCH 4/8] Moveupper the in-progress proposal check --- .../src/eth2near_relay.rs | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/eth2near/eth2near-block-relay-rs/src/eth2near_relay.rs b/eth2near/eth2near-block-relay-rs/src/eth2near_relay.rs index 89d38892..c30f622d 100644 --- a/eth2near/eth2near-block-relay-rs/src/eth2near_relay.rs +++ b/eth2near/eth2near-block-relay-rs/src/eth2near_relay.rs @@ -421,6 +421,21 @@ impl Eth2NearRelay { } fn send_light_client_updates_with_checks(&mut self) -> bool { + if !return_val_on_fail!( + self.eth_client_contract + .is_ready_to_submit_light_client_update(), + "Fail to send light client update", + false + ) { + warn!( + target: "relay", "Wait {} secs, before submiting new light client update", + self.sleep_time_on_in_progress_proposal_secs + ); + thread::sleep(Duration::from_secs( + self.sleep_time_on_in_progress_proposal_secs, + )); + } + let last_finalized_slot_on_near: u64 = return_val_on_fail!( self.get_last_finalized_slot_on_near(), "Error on getting finalized block slot on NEAR. Skipping sending light client update", @@ -653,17 +668,6 @@ impl Eth2NearRelay { return false; } - if !return_val_on_fail!( - self.eth_client_contract - .is_ready_to_submit_light_client_update(), - "Fail to send light client update", - false - ) { - thread::sleep(Duration::from_secs( - self.sleep_time_on_in_progress_proposal_secs, - )); - } - let execution_outcome = return_val_on_fail_and_sleep!( self.eth_client_contract .send_light_client_update(light_client_update.clone()), From c89e605a4cd65ec72245d67c186fa0d4071d4e04 Mon Sep 17 00:00:00 2001 From: karim-en Date: Thu, 30 Nov 2023 02:55:10 +0000 Subject: [PATCH 5/8] Add endpoint api key to config --- eth2near/contract_wrapper/src/dao_contract.rs | 1 + .../contract_wrapper/src/dao_eth_client_contract.rs | 1 + eth2near/contract_wrapper/src/near_contract_wrapper.rs | 10 +++++++++- eth2near/eth2near-block-relay-rs/src/config.rs | 3 +++ eth2near/eth2near-block-relay-rs/src/main.rs | 2 ++ 5 files changed, 16 insertions(+), 1 deletion(-) diff --git a/eth2near/contract_wrapper/src/dao_contract.rs b/eth2near/contract_wrapper/src/dao_contract.rs index 6825557c..3129dc43 100644 --- a/eth2near/contract_wrapper/src/dao_contract.rs +++ b/eth2near/contract_wrapper/src/dao_contract.rs @@ -720,6 +720,7 @@ mod tests { SIGNER_PRIVATE_KEY, DAO_CONTRACT_ACCOUNT_ID, None, + None, ); let mut dao_contract = DAOContract::new(Box::new(near_contract_wrapper)); diff --git a/eth2near/contract_wrapper/src/dao_eth_client_contract.rs b/eth2near/contract_wrapper/src/dao_eth_client_contract.rs index 33039e69..477d12d7 100644 --- a/eth2near/contract_wrapper/src/dao_eth_client_contract.rs +++ b/eth2near/contract_wrapper/src/dao_eth_client_contract.rs @@ -169,6 +169,7 @@ mod tests { &signer_private_key, CONTRACT_ACCOUNT_ID, None, + None, )); let eth_client = eth_client_contract::EthClientContract::new(near_contract_wrapper); diff --git a/eth2near/contract_wrapper/src/near_contract_wrapper.rs b/eth2near/contract_wrapper/src/near_contract_wrapper.rs index 18daa9cd..9400901a 100644 --- a/eth2near/contract_wrapper/src/near_contract_wrapper.rs +++ b/eth2near/contract_wrapper/src/near_contract_wrapper.rs @@ -42,12 +42,18 @@ impl NearContractWrapper { signer_secret_key: &str, contract_account_id: &str, timeout: Option, + api_key: Option, ) -> NearContractWrapper { let signer_account_id = account_id .parse() .expect("Error on parsing account id during creation near contract wrapper"); - let client = + let mut client = JsonRpcClient::with(utils::new_near_rpc_client(timeout)).connect(near_endpoint); + + if let Some(api_key) = api_key { + client = client.header(near_jsonrpc_client::auth::ApiKey::new(api_key).unwrap()); + } + let contract_account = contract_account_id .parse() .expect("Error on parsing contract account id during creation near contract wrapper"); @@ -80,6 +86,7 @@ impl NearContractWrapper { path_to_signer_secret_key: &str, contract_account_id: &str, timeout: Option, + api_key: Option, ) -> NearContractWrapper { let v: Value = serde_json::from_str( &std::fs::read_to_string(path_to_signer_secret_key).expect("Unable to read file"), @@ -96,6 +103,7 @@ impl NearContractWrapper { &signer_secret_key, contract_account_id, timeout, + api_key, ) } } diff --git a/eth2near/eth2near-block-relay-rs/src/config.rs b/eth2near/eth2near-block-relay-rs/src/config.rs index cd629bdd..ef6c89a2 100644 --- a/eth2near/eth2near-block-relay-rs/src/config.rs +++ b/eth2near/eth2near-block-relay-rs/src/config.rs @@ -22,6 +22,9 @@ pub struct Config { // endpoint for a full node on the NEAR chain pub near_endpoint: String, + // api key for the NEAR endpoint + pub near_endpoint_api_key: Option, + // Account id from which relay make requests pub signer_account_id: String, diff --git a/eth2near/eth2near-block-relay-rs/src/main.rs b/eth2near/eth2near-block-relay-rs/src/main.rs index 6bf76cd1..c9132b09 100644 --- a/eth2near/eth2near-block-relay-rs/src/main.rs +++ b/eth2near/eth2near-block-relay-rs/src/main.rs @@ -33,6 +33,7 @@ fn get_eth_contract_wrapper(config: &Config) -> Box { Some(std::time::Duration::from_secs( config.near_requests_timeout_seconds, )), + config.near_endpoint_api_key.clone(), )) } @@ -48,6 +49,7 @@ fn get_dao_contract_wrapper(config: &Config) -> Box { Some(std::time::Duration::from_secs( config.near_requests_timeout_seconds, )), + config.near_endpoint_api_key.clone(), )) } From 5bfa947882ef9de798655e36a7ab01e7d5fd4609 Mon Sep 17 00:00:00 2001 From: karim-en Date: Thu, 30 Nov 2023 03:23:06 +0000 Subject: [PATCH 6/8] Fix `NearRPCClient` --- eth2near/contract_wrapper/src/near_rpc_client.rs | 6 +++++- eth2near/eth2near-block-relay-rs/src/config.rs | 2 +- eth2near/eth2near-block-relay-rs/src/eth2near_relay.rs | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/eth2near/contract_wrapper/src/near_rpc_client.rs b/eth2near/contract_wrapper/src/near_rpc_client.rs index 6fd4f2e8..f767d879 100644 --- a/eth2near/contract_wrapper/src/near_rpc_client.rs +++ b/eth2near/contract_wrapper/src/near_rpc_client.rs @@ -5,13 +5,15 @@ use std::error::Error; pub struct NearRPCClient { endpoint_url: String, client: Client, + api_key: String, } impl NearRPCClient { - pub fn new(endpoint_url: &str) -> Self { + pub fn new(endpoint_url: &str, api_key: &Option) -> Self { Self { endpoint_url: endpoint_url.to_string(), client: reqwest::blocking::Client::new(), + api_key: api_key.clone().unwrap_or_default(), } } @@ -30,6 +32,7 @@ impl NearRPCClient { let res = self .client .post(&self.endpoint_url) + .header("x-api-key", self.api_key.clone()) .json(&json_value) .send()? .text()?; @@ -50,6 +53,7 @@ impl NearRPCClient { let res = self .client .post(&self.endpoint_url) + .header("x-api-key", self.api_key.clone()) .json(&json_value) .send()? .text()?; diff --git a/eth2near/eth2near-block-relay-rs/src/config.rs b/eth2near/eth2near-block-relay-rs/src/config.rs index ef6c89a2..3910f480 100644 --- a/eth2near/eth2near-block-relay-rs/src/config.rs +++ b/eth2near/eth2near-block-relay-rs/src/config.rs @@ -125,7 +125,7 @@ impl Config { } fn check_account_id(&self) { - let near_rpc_client = NearRPCClient::new(&self.near_endpoint); + let near_rpc_client = NearRPCClient::new(&self.near_endpoint, &self.near_endpoint_api_key); // check `signer_account_id` let _signer_account_id: near_sdk::AccountId = self diff --git a/eth2near/eth2near-block-relay-rs/src/eth2near_relay.rs b/eth2near/eth2near-block-relay-rs/src/eth2near_relay.rs index c30f622d..ac73587b 100644 --- a/eth2near/eth2near-block-relay-rs/src/eth2near_relay.rs +++ b/eth2near/eth2near-block-relay-rs/src/eth2near_relay.rs @@ -116,7 +116,7 @@ impl Eth2NearRelay { beacon_rpc_client, eth1_rpc_client: Eth1RPCClient::new(&config.eth1_endpoint), eth_client_contract: eth_contract, - near_rpc_client: NearRPCClient::new(&config.near_endpoint), + near_rpc_client: NearRPCClient::new(&config.near_endpoint, &config.near_endpoint_api_key), headers_batch_size: config.headers_batch_size as u64, ethereum_network: config.ethereum_network.to_string(), interval_between_light_client_updates_submission_in_epochs: config From 4da828e626744c36113f83accb91608c529caa2f Mon Sep 17 00:00:00 2001 From: karim-en Date: Thu, 30 Nov 2023 15:28:29 +0000 Subject: [PATCH 7/8] Implement timeout for final transaction outcome --- .../src/near_contract_wrapper.rs | 50 ++++++++++++++++--- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/eth2near/contract_wrapper/src/near_contract_wrapper.rs b/eth2near/contract_wrapper/src/near_contract_wrapper.rs index 9400901a..ce52249c 100644 --- a/eth2near/contract_wrapper/src/near_contract_wrapper.rs +++ b/eth2near/contract_wrapper/src/near_contract_wrapper.rs @@ -10,10 +10,12 @@ use near_sdk::{Balance, Gas}; use serde_json::Value; use std::error::Error; use std::string::String; +use std::time; use std::vec::Vec; use tokio::runtime::Runtime; pub const MAX_GAS: Gas = Gas(Gas::ONE_TERA.0 * 300); +pub const DEFAULT_WAIT_FINAL_OUTCOME_TIMEOUT_SEC: u64 = 500; /// Implementation of interaction with a contract on NEAR. pub struct NearContractWrapper { @@ -193,15 +195,42 @@ impl ContractWrapper for NearContractWrapper { actions, }; - let request = methods::broadcast_tx_commit::RpcBroadcastTxCommitRequest { + let request = methods::broadcast_tx_async::RpcBroadcastTxAsyncRequest { signed_transaction: transaction.sign(&self.signer), }; - let request_result = rt.block_on(async_std::future::timeout( - std::time::Duration::from_secs(600), - self.client.call(&request), - ))?; - Ok(request_result?) + let hash = rt.block_on(self.client.call(&request))?; + let sent_at = time::Instant::now(); + let tx_info = methods::tx::TransactionInfo::TransactionId { + hash, + account_id: self.signer.account_id.clone(), + }; + + loop { + let response = + rt.block_on(self.client.call(methods::tx::RpcTransactionStatusRequest { + transaction_info: tx_info.clone(), + })); + + let delta = (time::Instant::now() - sent_at).as_secs(); + if delta > DEFAULT_WAIT_FINAL_OUTCOME_TIMEOUT_SEC { + Err(format!( + "Timeout on waiting for final transaction outcome {}", + hash.to_string() + ))?; + } + + match response { + Err(err) => match err.handler_error() { + Some(_err) => { + std::thread::sleep(time::Duration::from_secs(2)); + continue; + } + _ => Err(format!("RpcTransactionError {}", err))?, + }, + Ok(response) => return Ok(response), + } + } } fn call_change_method( @@ -218,4 +247,13 @@ impl ContractWrapper for NearContractWrapper { gas, ) } + + // fn wait_for_tx_final_outcome( + // hash: CryptoHash, + // account_id: AccountId, + // server_addr: url::Url, + // timeout_sec: u64, + // ) -> Result { + + // } } From 03b5031693a88d35e338f3f62221d2b867b68adf Mon Sep 17 00:00:00 2001 From: karim-en Date: Sat, 4 May 2024 17:48:28 +0100 Subject: [PATCH 8/8] Refactor light client update validation --- .../src/eth2near_relay.rs | 38 +--- eth2near/finality-update-verify/src/lib.rs | 206 ++++++++++++------ 2 files changed, 156 insertions(+), 88 deletions(-) diff --git a/eth2near/eth2near-block-relay-rs/src/eth2near_relay.rs b/eth2near/eth2near-block-relay-rs/src/eth2near_relay.rs index ac73587b..0d83a176 100644 --- a/eth2near/eth2near-block-relay-rs/src/eth2near_relay.rs +++ b/eth2near/eth2near-block-relay-rs/src/eth2near_relay.rs @@ -116,7 +116,10 @@ impl Eth2NearRelay { beacon_rpc_client, eth1_rpc_client: Eth1RPCClient::new(&config.eth1_endpoint), eth_client_contract: eth_contract, - near_rpc_client: NearRPCClient::new(&config.near_endpoint, &config.near_endpoint_api_key), + near_rpc_client: NearRPCClient::new( + &config.near_endpoint, + &config.near_endpoint_api_key, + ), headers_batch_size: config.headers_batch_size as u64, ethereum_network: config.ethereum_network.to_string(), interval_between_light_client_updates_submission_in_epochs: config @@ -367,28 +370,16 @@ impl Eth2NearRelay { } } - fn verify_bls_signature_for_finality_update( - &mut self, + fn validate_light_client_update( + &self, light_client_update: &LightClientUpdate, - ) -> Result> { - let signature_slot_period = - BeaconRPCClient::get_period_for_slot(light_client_update.signature_slot); - let finalized_slot_period = BeaconRPCClient::get_period_for_slot( - self.eth_client_contract.get_finalized_beacon_block_slot()?, - ); - + ) -> Result<(), Box> { let light_client_state = self.eth_client_contract.get_light_client_state()?; - let sync_committee = if signature_slot_period == finalized_slot_period { - light_client_state.current_sync_committee - } else { - light_client_state.next_sync_committee - }; - - finality_update_verify::is_correct_finality_update( + finality_update_verify::validate_light_client_update( + &light_client_state, &self.ethereum_network, light_client_update, - sync_committee, ) } } @@ -655,19 +646,12 @@ impl Eth2NearRelay { &mut self, light_client_update: LightClientUpdate, ) -> bool { - let verification_result = return_val_on_fail!( - self.verify_bls_signature_for_finality_update(&light_client_update), + return_val_on_fail!( + self.validate_light_client_update(&light_client_update), "Error on bls verification. Skip sending the light client update", false ); - if verification_result { - info!(target: "relay", "PASS bls signature verification!"); - } else { - warn!(target: "relay", "NOT PASS bls signature verification. Skip sending this light client update"); - return false; - } - let execution_outcome = return_val_on_fail_and_sleep!( self.eth_client_contract .send_light_client_update(light_client_update.clone()), diff --git a/eth2near/finality-update-verify/src/lib.rs b/eth2near/finality-update-verify/src/lib.rs index 0ac80db7..fe5c4e05 100644 --- a/eth2near/finality-update-verify/src/lib.rs +++ b/eth2near/finality-update-verify/src/lib.rs @@ -1,101 +1,185 @@ use bitvec::order::Lsb0; use bitvec::prelude::BitVec; +use eth2_utility::consensus::*; use eth2_utility::consensus::{ - compute_domain, compute_signing_root, get_participant_pubkeys, Network, NetworkConfig, - DOMAIN_SYNC_COMMITTEE, MIN_SYNC_COMMITTEE_PARTICIPANTS, + compute_domain, compute_signing_root, compute_sync_committee_period, get_participant_pubkeys, + Network, NetworkConfig, DOMAIN_SYNC_COMMITTEE, MIN_SYNC_COMMITTEE_PARTICIPANTS, }; -use eth_types::eth2::{BeaconBlockHeader, LightClientUpdate, SyncCommittee}; -use eth_types::H256; +use eth_types::eth2::{LightClientState, LightClientUpdate}; use std::error::Error; use std::str::FromStr; -use types::{Hash256, Slot}; +use tree_hash::TreeHash; #[cfg(test)] pub mod config_for_tests; -fn h256_to_hash256(hash: H256) -> Hash256 { - Hash256::from_slice(hash.0.as_bytes()) -} - -fn tree_hash_h256_to_eth_type_h256(hash: tree_hash::Hash256) -> eth_types::H256 { - eth_types::H256::from(hash.0.as_slice()) -} - -fn to_lighthouse_beacon_block_header( - bridge_beacon_block_header: &BeaconBlockHeader, -) -> types::BeaconBlockHeader { - types::BeaconBlockHeader { - slot: Slot::from(bridge_beacon_block_header.slot), - proposer_index: bridge_beacon_block_header.proposer_index, - parent_root: h256_to_hash256(bridge_beacon_block_header.parent_root), - state_root: h256_to_hash256(bridge_beacon_block_header.state_root), - body_root: h256_to_hash256(bridge_beacon_block_header.body_root), - } -} - -pub fn is_correct_finality_update( - ethereum_network: &str, - light_client_update: &LightClientUpdate, - sync_committee: SyncCommittee, -) -> Result> { - let ethereum_network = Network::from_str(ethereum_network)?; - let config = NetworkConfig::new(ðereum_network); +pub fn validate_light_client_update( + state: &LightClientState, + network: &str, + update: &LightClientUpdate, +) -> Result<(), Box> { + let config = NetworkConfig::new(&Network::from_str(network)?); + let finalized_period = compute_sync_committee_period(state.finalized_beacon_header.header.slot); + verify_finality_branch(state, update, finalized_period, &config)?; + // Verify sync committee has sufficient participants let sync_committee_bits = - BitVec::::from_slice(&light_client_update.sync_aggregate.sync_committee_bits.0); - + BitVec::::from_slice(&update.sync_aggregate.sync_committee_bits.0); let sync_committee_bits_sum: u64 = sync_committee_bits.count_ones().try_into()?; + if sync_committee_bits_sum < MIN_SYNC_COMMITTEE_PARTICIPANTS { - return Ok(false); + return Err("Invalid sync committee bits sum: {}")?; } + if sync_committee_bits_sum * 3 < (sync_committee_bits.len() * 2).try_into()? { - return Ok(false); + return Err("Sync committee bits sum is less than 2/3 threshold")?; + } + + if !verify_bls_signatures(state, update, &config)? { + return Err("Failed to verify the bls signature")?; + } + + Ok(()) +} + +pub fn verify_bls_signatures( + state: &LightClientState, + update: &LightClientUpdate, + config: &NetworkConfig, +) -> Result> { + let finalized_period = compute_sync_committee_period(state.finalized_beacon_header.header.slot); + let signature_period = compute_sync_committee_period(update.signature_slot); + + // Verify signature period does not skip a sync committee period + if signature_period != finalized_period && signature_period != finalized_period + 1 { + return Err(format!( + "The acceptable signature periods are '{}' and '{}' but got {}", + finalized_period, + finalized_period + 1, + signature_period + ))?; } + // Verify sync committee aggregate signature + let sync_committee = if signature_period == finalized_period { + &state.current_sync_committee + } else { + &state.next_sync_committee + }; + + let sync_committee_bits = + BitVec::::from_slice(&update.sync_aggregate.sync_committee_bits.0); let participant_pubkeys = get_participant_pubkeys(&sync_committee.pubkeys.0, &sync_committee_bits); let fork_version = config - .compute_fork_version_by_slot(light_client_update.signature_slot) - .expect("Unsupported fork"); + .compute_fork_version_by_slot(update.signature_slot) + .ok_or("Unsupported fork")?; let domain = compute_domain( DOMAIN_SYNC_COMMITTEE, fork_version, config.genesis_validators_root.into(), ); - - let attested_beacon_header_root = tree_hash::TreeHash::tree_hash_root( - &to_lighthouse_beacon_block_header(&light_client_update.attested_beacon_header), - ); let signing_root = compute_signing_root( - tree_hash_h256_to_eth_type_h256(attested_beacon_header_root), + eth_types::H256(update.attested_beacon_header.tree_hash_root()), domain, ); - let aggregate_signature = bls::AggregateSignature::deserialize( - &light_client_update - .sync_aggregate - .sync_committee_signature - .0, - ) - .map_err(|_err| -> String { "Error on aggregate signature deserialization".to_string() })?; - let mut pubkeys: Vec = vec![]; - for pubkey in participant_pubkeys { - pubkeys.push( - bls::PublicKey::deserialize(&pubkey.0) - .map_err(|_err| -> String { "Error on public key deserialization".to_string() })?, - ); + let aggregate_signature = + bls::AggregateSignature::deserialize(&update.sync_aggregate.sync_committee_signature.0) + .map_err(|_| "Failed to deserialize sync committee signature")?; + let pubkeys: Vec = participant_pubkeys + .iter() + .map(|x| bls::PublicKey::deserialize(&x.0)) + .collect::>() + .map_err(|_| "Failed to deserialize PublicKey")?; + + Ok(aggregate_signature + .fast_aggregate_verify(signing_root.0, &pubkeys.iter().collect::>())) +} + +fn verify_finality_branch( + state: &LightClientState, + update: &LightClientUpdate, + finalized_period: u64, + config: &NetworkConfig, +) -> Result<(), Box> { + // The active header will always be the finalized header because we don't accept updates without the finality update. + let active_header = &update.finality_update.header_update.beacon_header; + + if active_header.slot <= state.finalized_beacon_header.header.slot { + return Err("The active header slot number should be higher than the finalized slot")?; + } + + if update.attested_beacon_header.slot < update.finality_update.header_update.beacon_header.slot + { + return Err( + "The attested header slot should be equal to or higher than the finalized header slot", + )?; + } + + if update.signature_slot <= update.attested_beacon_header.slot { + return Err("The signature slot should be higher than the attested header slot")?; + } + + let update_period = compute_sync_committee_period(active_header.slot); + assert!( + update_period == finalized_period || update_period == finalized_period + 1, + "The acceptable update periods are '{}' and '{}' but got {}", + finalized_period, + finalized_period + 1, + update_period + ); + + // Verify that the `finality_branch`, confirms `finalized_header` + // to match the finalized checkpoint root saved in the state of `attested_header`. + if !verify_merkle_proof( + update + .finality_update + .header_update + .beacon_header + .tree_hash_root() + .into(), + &update.finality_update.finality_branch, + FINALITY_TREE_DEPTH.try_into()?, + FINALITY_TREE_INDEX.try_into()?, + update.attested_beacon_header.state_root, + ) { + return Err("Invalid finality proof")?; + } + + if !config.validate_beacon_block_header_update(&update.finality_update.header_update) { + return Err("Invalid execution block hash proof")?; + } + + // Verify that the `next_sync_committee`, if present, actually is the next sync committee saved in the + // state of the `active_header` + if update_period != finalized_period { + let sync_committee_update = update + .sync_committee_update + .as_ref() + .ok_or("The sync committee update is missed")?; + + if !verify_merkle_proof( + sync_committee_update + .next_sync_committee + .tree_hash_root() + .into(), + &sync_committee_update.next_sync_committee_branch, + SYNC_COMMITTEE_TREE_DEPTH.try_into()?, + SYNC_COMMITTEE_TREE_INDEX.try_into()?, + update.attested_beacon_header.state_root, + ) { + return Err("Invalid next sync committee proof")?; + } } - Ok(aggregate_signature.fast_aggregate_verify( - h256_to_hash256(signing_root), - &pubkeys.iter().collect::>(), - )) + Ok(()) } #[cfg(test)] mod tests { + use super::*; use crate::config_for_tests::ConfigForTests; - use crate::is_correct_finality_update; use eth_types::eth2::LightClientUpdate; use eth_types::eth2::SyncCommittee;