From cb9abcd5302b062ff3605be57b87ee8d6fd71149 Mon Sep 17 00:00:00 2001 From: njgheorghita Date: Tue, 6 Feb 2024 09:03:15 -0500 Subject: [PATCH] feat(bridge): add support for gossiping from era1 files --- Cargo.lock | 1 + portal-bridge/Cargo.toml | 1 + portal-bridge/src/api/execution.rs | 2 +- portal-bridge/src/bridge/era1.rs | 297 ++++++++++++++++++++++++++++ portal-bridge/src/bridge/history.rs | 56 +++--- portal-bridge/src/bridge/mod.rs | 2 + portal-bridge/src/bridge/utils.rs | 30 +++ portal-bridge/src/main.rs | 69 ++++--- portal-bridge/src/types/era1.rs | 46 +++-- portal-bridge/src/types/mode.rs | 9 + 10 files changed, 434 insertions(+), 79 deletions(-) create mode 100644 portal-bridge/src/bridge/era1.rs create mode 100644 portal-bridge/src/bridge/utils.rs diff --git a/Cargo.lock b/Cargo.lock index c1b80592a..0813097ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4853,6 +4853,7 @@ dependencies = [ "jsonrpsee", "lazy_static", "portalnet", + "rand 0.8.5", "rlp", "rstest 0.18.2", "serde", diff --git a/portal-bridge/Cargo.toml b/portal-bridge/Cargo.toml index a9ead89a9..9294263d0 100644 --- a/portal-bridge/Cargo.toml +++ b/portal-bridge/Cargo.toml @@ -30,6 +30,7 @@ jsonrpsee = { version = "0.20.0", features = [ ] } lazy_static = "1.4.0" portalnet = { path = "../portalnet" } +rand = "0.8.4" rlp = "0.5.0" serde = { version = "1.0.150", features = ["derive", "rc"] } serde_json = "1.0.89" diff --git a/portal-bridge/src/api/execution.rs b/portal-bridge/src/api/execution.rs index 815145a62..1f6d9ccc1 100644 --- a/portal-bridge/src/api/execution.rs +++ b/portal-bridge/src/api/execution.rs @@ -324,7 +324,7 @@ impl ExecutionApi { } /// Create a proof for the given header / epoch acc -async fn construct_proof( +pub async fn construct_proof( header: Header, epoch_acc: &EpochAccumulator, ) -> anyhow::Result { diff --git a/portal-bridge/src/bridge/era1.rs b/portal-bridge/src/bridge/era1.rs new file mode 100644 index 000000000..923a4aaa8 --- /dev/null +++ b/portal-bridge/src/bridge/era1.rs @@ -0,0 +1,297 @@ +use std::{ + fs, + path::PathBuf, + sync::{Arc, Mutex}, +}; + +use anyhow::ensure; +use futures::future::join_all; +use rand::{seq::SliceRandom, thread_rng}; +use tokio::{ + sync::{OwnedSemaphorePermit, Semaphore}, + task::JoinHandle, + time::timeout, +}; +use tracing::{debug, error, info, warn}; + +use crate::{ + api::execution::construct_proof, + bridge::{ + history::{GOSSIP_LIMIT, SERVE_BLOCK_TIMEOUT}, + utils::lookup_epoch_acc, + }, + gossip::gossip_history_content, + stats::{HistoryBlockStats, StatsReporter}, + types::era1::{BlockTuple, Era1}, +}; +use ethportal_api::{ + jsonrpsee::http_client::HttpClient, types::execution::accumulator::EpochAccumulator, + BlockBodyKey, BlockHeaderKey, BlockReceiptsKey, EpochAccumulatorKey, HistoryContentKey, + HistoryContentValue, +}; +use trin_validation::{ + accumulator::MasterAccumulator, constants::EPOCH_SIZE, oracle::HeaderOracle, +}; + +pub struct Era1Bridge { + pub portal_clients: Vec, + pub header_oracle: HeaderOracle, + pub epoch_acc_path: PathBuf, + pub era1_files: Vec, +} + +// todo: fetch era1 files from pandaops source +// todo: validate via checksum, so we don't have to validate content on a per-value basis +impl Era1Bridge { + pub fn new( + portal_clients: Vec, + header_oracle: HeaderOracle, + epoch_acc_path: PathBuf, + ) -> anyhow::Result { + let era1_dir: Result, std::io::Error> = + fs::read_dir(PathBuf::from("./test_assets/era1"))?.collect(); + let era1_files: Vec = era1_dir?.into_iter().map(|entry| entry.path()).collect(); + Ok(Self { + portal_clients, + header_oracle, + epoch_acc_path, + era1_files, + }) + } + + pub async fn launch(&self) { + info!("Launching era1 bridge"); + let mut era1_files = self.era1_files.clone(); + era1_files.shuffle(&mut thread_rng()); + for era1_path in era1_files.into_iter() { + info!("Processing era1 file at path: {:?}", era1_path); + // We are using a semaphore to limit the amount of active gossip transfers to make sure + // we don't overwhelm the trin client + let gossip_send_semaphore = Arc::new(Semaphore::new(GOSSIP_LIMIT)); + + let era1 = Era1::read_from_file( + era1_path + .clone() + .into_os_string() + .into_string() + .expect("to be able to convert era1 path into string"), + ) + .unwrap_or_else(|_| panic!("to be able to read era1 file from path: {era1_path:?}")); + let epoch_index = era1.block_tuples[0].header.header.number / EPOCH_SIZE as u64; + info!("Era1 file read successfully, gossiping block tuples for epoch: {epoch_index}"); + let epoch_acc = match self.get_epoch_acc(epoch_index).await { + Ok(epoch_acc) => epoch_acc, + Err(e) => { + error!("Failed to get epoch acc for epoch: {epoch_index}, error: {e}"); + continue; + } + }; + let master_acc = Arc::new(self.header_oracle.master_acc.clone()); + let mut serve_block_tuple_handles = vec![]; + for block_tuple in era1.block_tuples { + let permit = gossip_send_semaphore + .clone() + .acquire_owned() + .await + .expect("to be able to acquire semaphore"); + let serve_block_tuple_handle = Self::spawn_serve_block_tuple( + self.portal_clients.clone(), + block_tuple, + epoch_acc.clone(), + master_acc.clone(), + permit, + ); + serve_block_tuple_handles.push(serve_block_tuple_handle); + } + // Wait till all block tuples are done gossiping. + // This can't deadlock, because the tokio::spawn has a timeout. + join_all(serve_block_tuple_handles).await; + } + } + + async fn get_epoch_acc(&self, epoch_index: u64) -> anyhow::Result> { + let (epoch_hash, epoch_acc) = lookup_epoch_acc( + epoch_index, + &self.header_oracle.master_acc, + &self.epoch_acc_path, + ) + .await?; + // Gossip epoch acc to network if found locally + let content_key = HistoryContentKey::EpochAccumulator(EpochAccumulatorKey { epoch_hash }); + let content_value = HistoryContentValue::EpochAccumulator(epoch_acc.clone()); + // create unique stats for epoch accumulator, since it's rarely gossiped + let block_stats = Arc::new(Mutex::new(HistoryBlockStats::new( + epoch_index * EPOCH_SIZE as u64, + ))); + debug!("Built EpochAccumulator for Epoch #{epoch_index:?}: now gossiping."); + // spawn gossip in new thread to avoid blocking + let portal_clients = self.portal_clients.clone(); + tokio::spawn(async move { + if let Err(msg) = + gossip_history_content(&portal_clients, content_key, content_value, block_stats) + .await + { + warn!("Failed to gossip epoch accumulator: {msg}"); + } + }); + Ok(Arc::new(epoch_acc)) + } + + fn spawn_serve_block_tuple( + portal_clients: Vec, + block_tuple: BlockTuple, + epoch_acc: Arc, + master_acc: Arc, + permit: OwnedSemaphorePermit, + ) -> JoinHandle<()> { + let number = block_tuple.header.header.number; + info!("Spawning serve_block_tuple for block at height: {number}",); + let block_stats = Arc::new(Mutex::new(HistoryBlockStats::new(number))); + tokio::spawn(async move { + match timeout( + SERVE_BLOCK_TIMEOUT, + Self::serve_block_tuple( + portal_clients, + block_tuple, + epoch_acc, + master_acc, + block_stats.clone(), + )).await + { + Ok(result) => match result { + Ok(_) => debug!("Successfully served block: {number}"), + Err(msg) => warn!("Error serving block: {number}: {msg:?}"), + }, + Err(_) => error!("serve_full_block() timed out on height {number}: this is an indication a bug is present") + }; + drop(permit); + }) + } + + async fn serve_block_tuple( + portal_clients: Vec, + block_tuple: BlockTuple, + epoch_acc: Arc, + master_acc: Arc, + block_stats: Arc>, + ) -> anyhow::Result<()> { + info!( + "Serving block tuple at height: {}", + block_tuple.header.header.number + ); + if let Err(msg) = Self::construct_and_gossip_header( + portal_clients.clone(), + block_tuple.clone(), + epoch_acc, + master_acc, + block_stats.clone(), + ) + .await + { + warn!( + "Error serving block tuple header at height: {:?} - {:?}", + block_tuple.header.header.number, msg + ); + } + + if let Err(msg) = Self::construct_and_gossip_block_body( + portal_clients.clone(), + block_tuple.clone(), + block_stats.clone(), + ) + .await + { + warn!( + "Error serving block tuple block body at height: {:?} - {:?}", + block_tuple.header.header.number, msg + ); + } + + if let Err(msg) = Self::construct_and_gossip_receipts( + portal_clients.clone(), + block_tuple.clone(), + block_stats.clone(), + ) + .await + { + warn!( + "Error serving block tuple receipts at height: {:?} - {:?}", + block_tuple.header.header.number, msg + ); + } + Ok(()) + } + + async fn construct_and_gossip_header( + portal_clients: Vec, + block_tuple: BlockTuple, + epoch_acc: Arc, + master_acc: Arc, + block_stats: Arc>, + ) -> anyhow::Result<()> { + let header = block_tuple.header.header; + // Fetch HeaderRecord from EpochAccumulator for validation + let header_index = header.number % EPOCH_SIZE as u64; + let header_record = &epoch_acc[header_index as usize]; + + // Validate Header + let actual_header_hash = header.hash(); + + ensure!( + header_record.block_hash == actual_header_hash, + "Header hash doesn't match record in local accumulator: {:?} - {:?}", + actual_header_hash, + header_record.block_hash + ); + + // Construct HistoryContentKey + let content_key = HistoryContentKey::BlockHeaderWithProof(BlockHeaderKey { + block_hash: header.hash().to_fixed_bytes(), + }); + // Construct HeaderWithProof + let header_with_proof = construct_proof(header.clone(), &epoch_acc).await?; + // Double check that the proof is valid + master_acc.validate_header_with_proof(&header_with_proof)?; + // Construct HistoryContentValue + let content_value = HistoryContentValue::BlockHeaderWithProof(header_with_proof); + + gossip_history_content( + // remove borrow + &portal_clients, + content_key, + content_value, + block_stats, + ) + .await + } + + async fn construct_and_gossip_block_body( + portal_clients: Vec, + block_tuple: BlockTuple, + block_stats: Arc>, + ) -> anyhow::Result<()> { + let header = block_tuple.header.header; + // Construct HistoryContentKey + let content_key = HistoryContentKey::BlockBody(BlockBodyKey { + block_hash: header.hash().to_fixed_bytes(), + }); + // Construct HistoryContentValue + let content_value = HistoryContentValue::BlockBody(block_tuple.body.body); + gossip_history_content(&portal_clients, content_key, content_value, block_stats).await + } + + async fn construct_and_gossip_receipts( + portal_clients: Vec, + block_tuple: BlockTuple, + block_stats: Arc>, + ) -> anyhow::Result<()> { + let header = block_tuple.header.header; + // Construct HistoryContentKey + let content_key = HistoryContentKey::BlockReceipts(BlockReceiptsKey { + block_hash: header.hash().to_fixed_bytes(), + }); + // Construct HistoryContentValue + let content_value = HistoryContentValue::Receipts(block_tuple.receipts.receipts); + gossip_history_content(&portal_clients, content_key, content_value, block_stats).await + } +} diff --git a/portal-bridge/src/bridge/history.rs b/portal-bridge/src/bridge/history.rs index 3cc31420e..ace23d8f2 100644 --- a/portal-bridge/src/bridge/history.rs +++ b/portal-bridge/src/bridge/history.rs @@ -1,13 +1,10 @@ use std::{ - fs, ops::Range, path::PathBuf, sync::{Arc, Mutex}, }; -use anyhow::anyhow; use futures::future::join_all; -use ssz::Decode; use tokio::{ sync::{OwnedSemaphorePermit, Semaphore}, task::JoinHandle, @@ -17,6 +14,7 @@ use tracing::{debug, error, info, warn, Instrument}; use crate::{ api::execution::ExecutionApi, + bridge::utils::lookup_epoch_acc, gossip::gossip_history_content, stats::{HistoryBlockStats, StatsReporter}, types::{full_header::FullHeader, mode::BridgeMode}, @@ -24,7 +22,7 @@ use crate::{ }; use ethportal_api::{ jsonrpsee::http_client::HttpClient, types::execution::accumulator::EpochAccumulator, - utils::bytes::hex_encode, EpochAccumulatorKey, HistoryContentKey, HistoryContentValue, + EpochAccumulatorKey, HistoryContentKey, HistoryContentValue, }; use trin_validation::{ constants::{EPOCH_SIZE as EPOCH_SIZE_USIZE, MERGE_BLOCK_NUMBER}, @@ -35,8 +33,8 @@ use trin_validation::{ const HEADER_SATURATION_DELAY: u64 = 10; // seconds const LATEST_BLOCK_POLL_RATE: u64 = 5; // seconds pub const EPOCH_SIZE: u64 = EPOCH_SIZE_USIZE as u64; -const GOSSIP_LIMIT: usize = 32; -const SERVE_BLOCK_TIMEOUT: Duration = Duration::from_secs(120); +pub const GOSSIP_LIMIT: usize = 32; +pub const SERVE_BLOCK_TIMEOUT: Duration = Duration::from_secs(120); pub struct HistoryBridge { pub mode: BridgeMode, @@ -70,6 +68,7 @@ impl HistoryBridge { match self.mode.clone() { BridgeMode::Test(path) => self.launch_test(path).await, BridgeMode::Latest => self.launch_latest().await, + BridgeMode::FourFours => panic!("4444s mode not supported in HistoryBridge."), _ => self.launch_backfill().await, } info!("Bridge mode: {:?} complete.", self.mode); @@ -150,14 +149,17 @@ impl HistoryBridge { info!("fetching headers in range: {gossip_range:?}"); let mut epoch_acc = None; - let mut vec_of_serve_full_block_handles = vec![]; + let mut serve_full_block_handles = vec![]; for height in gossip_range { // Using epoch_size chunks & epoch boundaries ensures that every // "chunk" shares an epoch accumulator avoiding the need to // look up the epoch acc on a header by header basis if height <= MERGE_BLOCK_NUMBER && current_epoch_index != height / EPOCH_SIZE { current_epoch_index = height / EPOCH_SIZE; - epoch_acc = match self.get_epoch_acc(current_epoch_index).await { + epoch_acc = match self + .construct_and_gossip_epoch_acc(current_epoch_index) + .await + { Ok(val) => Some(val), Err(msg) => { warn!("Unable to find epoch acc for gossip range: {current_epoch_index}. Skipping iteration: {msg:?}"); @@ -170,7 +172,7 @@ impl HistoryBridge { let permit = gossip_send_semaphore.clone().acquire_owned().await.expect( "acquire_owned() can only error on semaphore close, this should be impossible", ); - vec_of_serve_full_block_handles.push(Self::spawn_serve_full_block( + serve_full_block_handles.push(Self::spawn_serve_full_block( height, epoch_acc.clone(), self.portal_clients.clone(), @@ -179,9 +181,9 @@ impl HistoryBridge { )); } - // wait till all blocks are done gossiping. This can't deadlock, because the tokio::spawn - // has a timeout - join_all(vec_of_serve_full_block_handles).await; + // Wait till all blocks are done gossiping. + // This can't deadlock, because the tokio::spawn has a timeout. + join_all(serve_full_block_handles).await; } fn spawn_serve_full_block( @@ -270,25 +272,19 @@ impl HistoryBridge { /// Attempt to lookup an epoch accumulator from local portal-accumulators path provided via cli /// arg. Gossip the epoch accumulator if found. - async fn get_epoch_acc(&self, epoch_index: u64) -> anyhow::Result> { - let epoch_hash = self.header_oracle.master_acc.historical_epochs[epoch_index as usize]; - let epoch_hash_pretty = hex_encode(epoch_hash); - let epoch_hash_pretty = epoch_hash_pretty.trim_start_matches("0x"); - let epoch_acc_path = format!( - "{}/bridge_content/0x03{epoch_hash_pretty}.portalcontent", - self.epoch_acc_path.display(), - ); - let local_epoch_acc = match fs::read(&epoch_acc_path) { - Ok(val) => EpochAccumulator::from_ssz_bytes(&val).map_err(|err| anyhow!("{err:?}"))?, - Err(_) => { - return Err(anyhow!( - "Unable to find local epoch acc at path: {epoch_acc_path:?}" - )) - } - }; + async fn construct_and_gossip_epoch_acc( + &self, + epoch_index: u64, + ) -> anyhow::Result> { + let (epoch_hash, epoch_acc) = lookup_epoch_acc( + epoch_index, + &self.header_oracle.master_acc, + &self.epoch_acc_path, + ) + .await?; // Gossip epoch acc to network if found locally let content_key = HistoryContentKey::EpochAccumulator(EpochAccumulatorKey { epoch_hash }); - let content_value = HistoryContentValue::EpochAccumulator(local_epoch_acc.clone()); + let content_value = HistoryContentValue::EpochAccumulator(epoch_acc.clone()); // create unique stats for epoch accumulator, since it's rarely gossiped let block_stats = Arc::new(Mutex::new(HistoryBlockStats::new(epoch_index * EPOCH_SIZE))); debug!("Built EpochAccumulator for Epoch #{epoch_index:?}: now gossiping."); @@ -299,7 +295,7 @@ impl HistoryBridge { block_stats, ) .await; - Ok(Arc::new(local_epoch_acc)) + Ok(Arc::new(epoch_acc)) } async fn construct_and_gossip_receipt( diff --git a/portal-bridge/src/bridge/mod.rs b/portal-bridge/src/bridge/mod.rs index 02535ae1a..87513ba54 100644 --- a/portal-bridge/src/bridge/mod.rs +++ b/portal-bridge/src/bridge/mod.rs @@ -1,2 +1,4 @@ pub mod beacon; +pub mod era1; pub mod history; +pub mod utils; diff --git a/portal-bridge/src/bridge/utils.rs b/portal-bridge/src/bridge/utils.rs new file mode 100644 index 000000000..c2e24005b --- /dev/null +++ b/portal-bridge/src/bridge/utils.rs @@ -0,0 +1,30 @@ +use anyhow::anyhow; +use ethereum_types::H256; +use ethportal_api::{types::execution::accumulator::EpochAccumulator, utils::bytes::hex_encode}; +use ssz::Decode; +use std::{fs, path::Path}; +use trin_validation::accumulator::MasterAccumulator; + +/// Lookup the epoch accumulator & epoch hash for the given epoch index. +pub async fn lookup_epoch_acc( + epoch_index: u64, + master_acc: &MasterAccumulator, + epoch_acc_path: &Path, +) -> anyhow::Result<(H256, EpochAccumulator)> { + let epoch_hash = master_acc.historical_epochs[epoch_index as usize]; + let epoch_hash_pretty = hex_encode(epoch_hash); + let epoch_hash_pretty = epoch_hash_pretty.trim_start_matches("0x"); + let epoch_acc_path = format!( + "{}/bridge_content/0x03{epoch_hash_pretty}.portalcontent", + epoch_acc_path.display(), + ); + let epoch_acc = match fs::read(&epoch_acc_path) { + Ok(val) => EpochAccumulator::from_ssz_bytes(&val).map_err(|err| anyhow!("{err:?}"))?, + Err(_) => { + return Err(anyhow!( + "Unable to find local epoch acc at path: {epoch_acc_path:?}" + )) + } + }; + Ok((epoch_hash, epoch_acc)) +} diff --git a/portal-bridge/src/main.rs b/portal-bridge/src/main.rs index e8a27b00b..32b192913 100644 --- a/portal-bridge/src/main.rs +++ b/portal-bridge/src/main.rs @@ -10,9 +10,9 @@ use ethportal_api::{ }; use portal_bridge::{ api::{consensus::ConsensusApi, execution::ExecutionApi}, - bridge::{beacon::BeaconBridge, history::HistoryBridge}, + bridge::{beacon::BeaconBridge, era1::Era1Bridge, history::HistoryBridge}, cli::BridgeConfig, - types::network::NetworkKind, + types::{mode::BridgeMode, network::NetworkKind}, utils::generate_spaced_private_keys, }; use trin_utils::log::init_tracing_logger; @@ -82,31 +82,52 @@ async fn main() -> Result<(), Box> { // Launch History Network portal bridge if bridge_config.network.contains(&NetworkKind::History) { - let execution_api = ExecutionApi::new( - bridge_config.el_provider, - bridge_config.mode.clone(), - bridge_config.el_provider_daily_request_limit, - ) - .await?; - let bridge_handle = tokio::spawn(async move { - let master_acc = MasterAccumulator::default(); - let header_oracle = HeaderOracle::new(master_acc); + match bridge_config.mode { + BridgeMode::FourFours => { + let master_acc = MasterAccumulator::default(); + let header_oracle = HeaderOracle::new(master_acc); + let era1_bridge = Era1Bridge::new( + portal_clients.expect("Failed to create history JSON-RPC clients"), + header_oracle, + bridge_config.epoch_acc_path, + ) + .unwrap(); + let bridge_handle = tokio::spawn(async move { + era1_bridge + .launch() + .instrument(tracing::info_span!("history(era1)")) + .await; + }); + bridge_tasks.push(bridge_handle); + } + _ => { + let execution_api = ExecutionApi::new( + bridge_config.el_provider, + bridge_config.mode.clone(), + bridge_config.el_provider_daily_request_limit, + ) + .await?; + let bridge_handle = tokio::spawn(async move { + let master_acc = MasterAccumulator::default(); + let header_oracle = HeaderOracle::new(master_acc); - let bridge = HistoryBridge::new( - bridge_config.mode, - execution_api, - portal_clients.expect("Failed to create history JSON-RPC clients"), - header_oracle, - bridge_config.epoch_acc_path, - ); + let bridge = HistoryBridge::new( + bridge_config.mode, + execution_api, + portal_clients.expect("Failed to create history JSON-RPC clients"), + header_oracle, + bridge_config.epoch_acc_path, + ); - bridge - .launch() - .instrument(tracing::info_span!("history")) - .await; - }); + bridge + .launch() + .instrument(tracing::info_span!("history")) + .await; + }); - bridge_tasks.push(bridge_handle); + bridge_tasks.push(bridge_handle); + } + } } futures::future::join_all(bridge_tasks).await; diff --git a/portal-bridge/src/types/era1.rs b/portal-bridge/src/types/era1.rs index 6118ac4b7..4a95f2a51 100644 --- a/portal-bridge/src/types/era1.rs +++ b/portal-bridge/src/types/era1.rs @@ -26,17 +26,15 @@ use std::{ const ERA1_ENTRY_COUNT: usize = 8192 * 4 + 3; -#[allow(dead_code)] -struct Era1 { - version: VersionEntry, - block_tuples: Vec, - accumulator: AccumulatorEntry, - block_index: BlockIndexEntry, +pub struct Era1 { + pub version: VersionEntry, + pub block_tuples: Vec, + pub accumulator: AccumulatorEntry, + pub block_index: BlockIndexEntry, } -#[allow(dead_code)] impl Era1 { - fn read_from_file(path: String) -> anyhow::Result { + pub fn read_from_file(path: String) -> anyhow::Result { let buf = fs::read(path)?; Self::read(&buf) } @@ -67,6 +65,7 @@ impl Era1 { }) } + #[allow(dead_code)] fn write(&self) -> anyhow::Result> { let mut entries: Vec = vec![]; let version_entry: Entry = self.version.clone().try_into()?; @@ -91,13 +90,12 @@ impl Era1 { } } -#[allow(dead_code)] #[derive(Clone, Eq, PartialEq, Debug)] -struct BlockTuple { - header: HeaderEntry, - body: BodyEntry, - receipts: ReceiptsEntry, - total_difficulty: TotalDifficultyEntry, +pub struct BlockTuple { + pub header: HeaderEntry, + pub body: BodyEntry, + pub receipts: ReceiptsEntry, + pub total_difficulty: TotalDifficultyEntry, } impl TryFrom<&[Entry; 4]> for BlockTuple { @@ -131,7 +129,7 @@ impl TryInto<[Entry; 4]> for BlockTuple { } #[derive(Clone, Debug, Eq, PartialEq)] -struct VersionEntry { +pub struct VersionEntry { version: Entry, } @@ -170,8 +168,8 @@ impl TryInto for VersionEntry { } #[derive(Clone, Eq, PartialEq, Debug)] -struct HeaderEntry { - header: Header, +pub struct HeaderEntry { + pub header: Header, } impl TryFrom<&Entry> for HeaderEntry { @@ -208,8 +206,8 @@ impl TryInto for HeaderEntry { } #[derive(Clone, Eq, PartialEq, Debug)] -struct BodyEntry { - body: BlockBody, +pub struct BodyEntry { + pub body: BlockBody, } impl TryFrom<&Entry> for BodyEntry { @@ -246,8 +244,8 @@ impl TryInto for BodyEntry { } #[derive(Clone, Eq, PartialEq, Debug)] -struct ReceiptsEntry { - receipts: Receipts, +pub struct ReceiptsEntry { + pub receipts: Receipts, } impl TryFrom<&Entry> for ReceiptsEntry { @@ -289,7 +287,7 @@ impl TryInto for ReceiptsEntry { } #[derive(Clone, Eq, PartialEq, Debug)] -struct TotalDifficultyEntry { +pub struct TotalDifficultyEntry { total_difficulty: U256, } @@ -329,7 +327,7 @@ impl TryInto for TotalDifficultyEntry { } #[derive(Clone, Eq, PartialEq, Debug)] -struct AccumulatorEntry { +pub struct AccumulatorEntry { accumulator: H256, } @@ -370,7 +368,7 @@ impl TryInto for AccumulatorEntry { // block-index := starting-number | index | index | index ... | count #[derive(Clone, Eq, PartialEq, Debug)] -struct BlockIndexEntry { +pub struct BlockIndexEntry { block_index: BlockIndex, } diff --git a/portal-bridge/src/types/mode.rs b/portal-bridge/src/types/mode.rs index d31019ffa..dc100ee5e 100644 --- a/portal-bridge/src/types/mode.rs +++ b/portal-bridge/src/types/mode.rs @@ -12,10 +12,13 @@ use trin_validation::constants::EPOCH_SIZE; /// - ex: "b123" executes block 123 /// - BlockRange: generates test data from block x to y /// - ex: "r10-12" backfills a block range from #10 to #12 (inclusive) +/// - FourFours: gossips randomly sequenced era1 files +/// - ex: "fourfours" #[derive(Clone, Debug, PartialEq, Default, Eq)] pub enum BridgeMode { #[default] Latest, + FourFours, Backfill(ModeType), Single(ModeType), Test(PathBuf), @@ -28,6 +31,11 @@ impl BridgeMode { let (is_single_mode, mode_type) = match self { BridgeMode::Backfill(val) => (false, val), BridgeMode::Single(val) => (true, val), + BridgeMode::FourFours => { + return Err(anyhow!( + "BridgeMode `fourfours` does not have a block range" + )) + } BridgeMode::Latest => { return Err(anyhow!("BridgeMode `latest` does not have a block range")) } @@ -69,6 +77,7 @@ impl FromStr for BridgeMode { fn from_str(s: &str) -> Result { match s { "latest" => Ok(BridgeMode::Latest), + "fourfours" => Ok(BridgeMode::FourFours), val => { let index = val .find(':')