Skip to content

Commit

Permalink
Eth2-to-Near-relay: improve init of Eth2 client contract (#817)
Browse files Browse the repository at this point in the history
* Improve init eth2 client contract
* separate LightClientSnapshotWithProof
* get init block root if not provided
* fix code to get the checkpoint

Co-authored-by: Olga Kunyavskaya <[email protected]>
Co-authored-by: Kirill <[email protected]>
  • Loading branch information
3 people authored Sep 22, 2022
1 parent c55f79a commit 5a5cd65
Show file tree
Hide file tree
Showing 8 changed files with 127 additions and 28 deletions.
1 change: 1 addition & 0 deletions eth2near/eth2near-block-relay-rs/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions eth2near/eth2near-block-relay-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ futures = { version = "0.3.21", default-features = false }
async-std = "1.12.0"
hex = "*"
toml = "0.5.9"
eth2-utility = { path = "../../contracts/near/eth2-utility" }
finality-update-verify = { path = "../finality-update-verify" }
atomic_refcell = "0.1.8"
bitvec = "*"
Expand Down
40 changes: 40 additions & 0 deletions eth2near/eth2near-block-relay-rs/src/beacon_rpc_client.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::execution_block_proof::ExecutionBlockProof;
use crate::light_client_snapshot_with_proof::LightClientSnapshotWithProof;
use crate::relay_errors::{
ExecutionPayloadError, FailOnGettingJson, MissSyncAggregationError, NoBlockForSlotError,
SignatureSlotNotFoundError,
Expand Down Expand Up @@ -37,6 +38,7 @@ impl BeaconRPCClient {
const URL_GET_LIGHT_CLIENT_UPDATE_API: &'static str = "eth/v1/beacon/light_client/updates";
const URL_FINALITY_LIGHT_CLIENT_UPDATE_PATH: &'static str =
"eth/v1/beacon/light_client/finality_update/";
const URL_GET_BOOTSTRAP: &'static str = "eth/v1/beacon/light_client/bootstrap";
const URL_STATE_PATH: &'static str = "eth/v2/debug/beacon/states";

const SLOTS_PER_EPOCH: u64 = 32;
Expand Down Expand Up @@ -139,6 +141,44 @@ impl BeaconRPCClient {
})
}

// Fetch a bootstrapping state with a proof to a trusted block root.
// The trusted block root should be fetched with similar means to a weak subjectivity checkpoint.
// Only block roots for checkpoints are guaranteed to be available.
pub fn get_bootstrap(
&self,
block_root: String,
) -> Result<LightClientSnapshotWithProof, Box<dyn Error>> {
let url = format!(
"{}/{}/{}",
self.endpoint_url,
Self::URL_GET_BOOTSTRAP,
block_root
);

let light_client_snapshot_json_str = self.get_json_from_raw_request(&url)?;
let parsed_json: Value = serde_json::from_str(&light_client_snapshot_json_str)?;
let beacon_header: BeaconBlockHeader =
serde_json::from_value(parsed_json["data"]["header"].clone())?;
let current_sync_committee: SyncCommittee =
serde_json::from_value(parsed_json["data"]["current_sync_committee"].clone())?;
let current_sync_committee_branch: Vec<H256> =
serde_json::from_value(parsed_json["data"]["current_sync_committee_branch"].clone())?;

Ok(LightClientSnapshotWithProof {
beacon_header,
current_sync_committee,
current_sync_committee_branch,
})
}

pub fn get_checkpoint_root(&self) -> Result<String, Box<dyn Error>> {
let url = format!("{}/eth/v1/beacon/states/finalized/finality_checkpoints", self.endpoint_url);
let checkpoint_json_str = self.get_json_from_raw_request(&url)?;
let parsed_json: Value = serde_json::from_str(&checkpoint_json_str)?;

Ok(trim_quotes(parsed_json["data"]["finalized"]["root"].to_string()))
}

/// Return the last finalized slot in the Beacon chain
pub fn get_last_finalized_slot_number(&self) -> Result<types::Slot, Box<dyn Error>> {
Ok(self.get_beacon_block_header_for_block_id("finalized")?.slot)
Expand Down
81 changes: 64 additions & 17 deletions eth2near/eth2near-block-relay-rs/src/init_contract.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,48 @@
use crate::beacon_rpc_client::BeaconRPCClient;
use crate::config::Config;
use crate::eth1_rpc_client::Eth1RPCClient;
use crate::light_client_snapshot_with_proof::LightClientSnapshotWithProof;
use contract_wrapper::eth_client_contract::EthClientContract;
use eth2_utility::consensus::{convert_branch, floorlog2, get_subtree_index};
use eth_types::eth2::ExtendedBeaconBlockHeader;
use eth_types::BlockHeader;
use log::info;
use std::{thread, time};
use tree_hash::TreeHash;

const CURRENT_SYNC_COMMITTEE_INDEX: u32 = 54;
const CURRENT_SYNC_COMMITTEE_TREE_DEPTH: u32 = floorlog2(CURRENT_SYNC_COMMITTEE_INDEX);
const CURRENT_SYNC_COMMITTEE_TREE_INDEX: u32 = get_subtree_index(CURRENT_SYNC_COMMITTEE_INDEX);

pub fn verify_light_client_snapshot(
block_root: String,
light_client_snapshot: &LightClientSnapshotWithProof,
) -> bool {
let expected_block_root = format!(
"{:#x}",
light_client_snapshot.beacon_header.tree_hash_root()
);

if block_root != expected_block_root {
return false;
}

let branch = convert_branch(&light_client_snapshot.current_sync_committee_branch);
merkle_proof::verify_merkle_proof(
light_client_snapshot
.current_sync_committee
.tree_hash_root(),
&branch,
CURRENT_SYNC_COMMITTEE_TREE_DEPTH.try_into().unwrap(),
CURRENT_SYNC_COMMITTEE_TREE_INDEX.try_into().unwrap(),
light_client_snapshot.beacon_header.state_root.0,
)
}

pub fn init_contract(
config: &Config,
eth_client_contract: &mut EthClientContract,
mut init_block_root: String,
) -> Result<(), Box<dyn std::error::Error>> {
info!(target: "relay", "=== Contract initialization ===");

Expand All @@ -18,22 +51,19 @@ pub fn init_contract(
config.eth_requests_timeout_seconds,
config.state_requests_timeout_seconds,
);
let eth1_rpc_client = Eth1RPCClient::new(&config.eth1_endpoint);

let start_slot = beacon_rpc_client.get_last_finalized_slot_number().unwrap();
let period = BeaconRPCClient::get_period_for_slot(start_slot.as_u64());
let eth1_rpc_client = Eth1RPCClient::new(&config.eth1_endpoint);

let light_client_update = beacon_rpc_client
.get_finality_light_client_update_with_sync_commity_update()
.unwrap();
let block_id = format!(
"{}",
light_client_update
.finality_update
.header_update
.beacon_header
.slot
);
let finality_slot = light_client_update
.finality_update
.header_update
.beacon_header
.slot;

let block_id = format!("{}", finality_slot);
let finalized_header: ExtendedBeaconBlockHeader =
ExtendedBeaconBlockHeader::from(light_client_update.finality_update.header_update);
let finalized_body = beacon_rpc_client
Expand All @@ -53,17 +83,34 @@ pub fn init_contract(
.sync_committee_update
.unwrap()
.next_sync_committee;
let prev_light_client_update = beacon_rpc_client.get_light_client_update(period - 1)?;
let current_sync_committee = prev_light_client_update
.sync_committee_update
.unwrap()
.next_sync_committee;

if init_block_root.is_empty() {
init_block_root = beacon_rpc_client
.get_checkpoint_root()
.expect("Fail to get last checkpoint");
}

let light_client_snapshot = beacon_rpc_client
.get_bootstrap(init_block_root.clone())
.expect("Unable to fetch bootstrap state");

info!(target: "relay", "init_block_root: {}", init_block_root);

if BeaconRPCClient::get_period_for_slot(light_client_snapshot.beacon_header.slot)
!= BeaconRPCClient::get_period_for_slot(finality_slot)
{
panic!("Period for init_block_root different from current period. Please use snapshot for current period");
}

if !verify_light_client_snapshot(init_block_root, &light_client_snapshot) {
return Err("Invalid light client snapshot".into());
}

eth_client_contract.init_contract(
config.network.to_string(),
finalized_execution_header,
finalized_header,
current_sync_committee,
light_client_snapshot.current_sync_committee,
next_sync_committee,
config.hashes_gc_threshold,
config.max_submitted_blocks_by_account,
Expand Down
3 changes: 2 additions & 1 deletion eth2near/eth2near-block-relay-rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ pub mod execution_block_proof;
pub mod hand_made_finality_light_client_update;
pub mod init_contract;
pub mod last_slot_searcher;
pub mod light_client_snapshot_with_proof;
pub mod logger;
pub mod near_rpc_client;
pub mod prometheus_metrics;
pub mod relay_errors;

pub mod prometheus_metrics;
#[cfg(test)]
pub mod test_utils;
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use eth_types::eth2::{BeaconBlockHeader, SyncCommittee};
use eth_types::H256;
use serde::Serialize;

#[derive(Serialize)]
pub struct LightClientSnapshotWithProof {
pub beacon_header: BeaconBlockHeader,
pub current_sync_committee: SyncCommittee,
pub current_sync_committee_branch: Vec<H256>,
}
7 changes: 6 additions & 1 deletion eth2near/eth2near-block-relay-rs/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ struct Arguments {
/// The eth contract on Near will be initialized
init_contract: bool,

#[clap(long, action = ArgAction::Set, default_value = "")]
/// The trusted block root for checkpoint for contract initialization
/// e.g.: --init-contract --init-block-root 0x9cd0c5a8392d0659426b12384e8440c147510ab93eeaeccb08435a462d7bb1c7
init_block_root: String,

#[clap(long, default_value_t = String::from("info"))]
/// Log level (trace, debug, info, warn, error)
log_level: String,
Expand Down Expand Up @@ -101,7 +106,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {

if args.init_contract {
let mut eth_client_contract = EthClientContract::new(get_eth_contract_wrapper(&config));
init_contract(&config, &mut eth_client_contract).unwrap();
init_contract(&config, &mut eth_client_contract, args.init_block_root).unwrap();
} else {
let mut eth2near_relay = Eth2NearRelay::init(
&config,
Expand Down
12 changes: 3 additions & 9 deletions eth2near/eth2near-block-relay-rs/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,16 +220,13 @@ fn get_config() -> Config {

pub fn get_client_contract(from_file: bool) -> Box<dyn EthClientContractTrait> {
let (relay_account, contract) = create_contract();
let contract_wrapper = Box::new(SandboxContractWrapper::new(
&relay_account,
contract,
));
let contract_wrapper = Box::new(SandboxContractWrapper::new(&relay_account, contract));
let mut eth_client_contract = EthClientContract::new(contract_wrapper);

let config = get_config();
match from_file {
true => test_utils::init_contract_from_files(&mut eth_client_contract),
false => init_contract(&config, &mut eth_client_contract).unwrap(),
false => init_contract(&config, &mut eth_client_contract, "".to_string()).unwrap(),
};

Box::new(eth_client_contract)
Expand Down Expand Up @@ -271,10 +268,7 @@ pub fn get_relay_from_slot(enable_binsearch: bool, slot: u64) -> Eth2NearRelay {
let config = get_config();

let (relay_account, contract) = create_contract();
let contract_wrapper = Box::new(SandboxContractWrapper::new(
&relay_account,
contract,
));
let contract_wrapper = Box::new(SandboxContractWrapper::new(&relay_account, contract));
let mut eth_client_contract = EthClientContract::new(contract_wrapper);

init_contract_from_specific_slot(&mut eth_client_contract, slot);
Expand Down

0 comments on commit 5a5cd65

Please sign in to comment.