diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index a277bf1e8f..dad24044f3 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -335,6 +335,14 @@ impl From for GasPrice { } } +impl From for GasPrice { + fn from(src: Felt) -> Self { + let mut bytes = [0u8; 16]; + bytes.copy_from_slice(&src.as_be_bytes()[16..]); + Self(u128::from_be_bytes(bytes)) + } +} + impl From for BlockId { fn from(number: BlockNumber) -> Self { Self::Number(number) diff --git a/crates/common/src/signature.rs b/crates/common/src/signature.rs index 8d3527a565..f20348b448 100644 --- a/crates/common/src/signature.rs +++ b/crates/common/src/signature.rs @@ -2,7 +2,7 @@ use fake::Dummy; use crate::BlockCommitmentSignatureElem; -#[derive(Default, Debug, Clone, PartialEq, Dummy)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Dummy)] pub struct BlockCommitmentSignature { pub r: BlockCommitmentSignatureElem, pub s: BlockCommitmentSignatureElem, diff --git a/crates/common/src/state_update.rs b/crates/common/src/state_update.rs index b10cfc5b6d..a16bcc5507 100644 --- a/crates/common/src/state_update.rs +++ b/crates/common/src/state_update.rs @@ -48,6 +48,10 @@ impl ContractClassUpdate { ContractClassUpdate::Replace(x) => *x, } } + + pub fn is_replaced(&self) -> bool { + matches!(self, ContractClassUpdate::Replace(_)) + } } impl StateUpdate { diff --git a/crates/gateway-client/src/lib.rs b/crates/gateway-client/src/lib.rs index 663aea96d1..fb3cf4190a 100644 --- a/crates/gateway-client/src/lib.rs +++ b/crates/gateway-client/src/lib.rs @@ -113,18 +113,6 @@ pub trait GatewayApi: Sync { } } -/// This is a **temporary** measure to keep the sync logic unchanged -/// -/// TODO remove when p2p friendly sync is implemented -#[allow(unused_variables)] -#[mockall::automock] -#[async_trait::async_trait] -pub trait GossipApi: Sync { - async fn propagate_head(&self, block_number: BlockNumber, block_hash: BlockHash) { - // Intentionally does nothing for default impl - } -} - #[async_trait::async_trait] impl GatewayApi for std::sync::Arc { async fn block(&self, block: BlockId) -> Result { @@ -574,12 +562,6 @@ impl GatewayApi for Client { } } -#[async_trait::async_trait] -impl GossipApi for Client {} - -#[async_trait::async_trait] -impl GossipApi for () {} - pub mod test_utils { use super::Client; use starknet_gateway_types::error::KnownStarknetErrorCode; diff --git a/crates/gateway-types/src/reply.rs b/crates/gateway-types/src/reply.rs index fa1f8791e2..462fe74a42 100644 --- a/crates/gateway-types/src/reply.rs +++ b/crates/gateway-types/src/reply.rs @@ -316,7 +316,7 @@ pub(crate) mod transaction { pub ec_op_builtin: u64, pub keccak_builtin: u64, pub poseidon_builtin: u64, - pub segment_arena_builtin: u64, + pub segment_arena_builtin: u64, // TODO REMOVE (?) } impl From for pathfinder_common::receipt::BuiltinCounters { @@ -386,7 +386,7 @@ pub(crate) mod transaction { ec_op_builtin: rng.next_u32() as u64, keccak_builtin: rng.next_u32() as u64, poseidon_builtin: rng.next_u32() as u64, - segment_arena_builtin: rng.next_u32() as u64, + segment_arena_builtin: 0, // Not used in p2p } } } diff --git a/crates/p2p/src/behaviour.rs b/crates/p2p/src/behaviour.rs index 62467ae764..1703e24434 100644 --- a/crates/p2p/src/behaviour.rs +++ b/crates/p2p/src/behaviour.rs @@ -24,11 +24,11 @@ use libp2p::swarm::{ }; use libp2p::StreamProtocol; use libp2p::{autonat, Multiaddr, PeerId}; -use p2p_proto::block::{ - BlockBodiesRequest, BlockBodiesResponse, BlockHeadersRequest, BlockHeadersResponse, -}; +use p2p_proto::class::{ClassesRequest, ClassesResponse}; use p2p_proto::event::{EventsRequest, EventsResponse}; +use p2p_proto::header::{BlockHeadersRequest, BlockHeadersResponse}; use p2p_proto::receipt::{ReceiptsRequest, ReceiptsResponse}; +use p2p_proto::state::{StateDiffsRequest, StateDiffsResponse}; use p2p_proto::transaction::{TransactionsRequest, TransactionsResponse}; use pathfinder_common::ChainId; use std::{cmp, task}; @@ -58,7 +58,8 @@ pub struct Inner { kademlia: kad::Behaviour, gossipsub: gossipsub::Behaviour, headers_sync: p2p_stream::Behaviour, - bodies_sync: p2p_stream::Behaviour, + classes_sync: p2p_stream::Behaviour, + state_diffs_sync: p2p_stream::Behaviour, transactions_sync: p2p_stream::Behaviour, receipts_sync: p2p_stream::Behaviour, events_sync: p2p_stream::Behaviour, @@ -468,7 +469,8 @@ impl Behaviour { .expect("valid gossipsub params"); let headers_sync = request_response_behavior::(); - let bodies_sync = request_response_behavior::(); + let classes_sync = request_response_behavior::(); + let state_diffs_sync = request_response_behavior::(); let transactions_sync = request_response_behavior::(); let receipts_sync = request_response_behavior::(); let events_sync = request_response_behavior::(); @@ -496,7 +498,8 @@ impl Behaviour { kademlia, gossipsub, headers_sync, - bodies_sync, + classes_sync, + state_diffs_sync, transactions_sync, receipts_sync, events_sync, @@ -787,8 +790,12 @@ impl Behaviour { &mut self.inner.headers_sync } - pub fn bodies_sync_mut(&mut self) -> &mut p2p_stream::Behaviour { - &mut self.inner.bodies_sync + pub fn classes_sync_mut(&mut self) -> &mut p2p_stream::Behaviour { + &mut self.inner.classes_sync + } + + pub fn state_diffs_sync_mut(&mut self) -> &mut p2p_stream::Behaviour { + &mut self.inner.state_diffs_sync } pub fn transactions_sync_mut(&mut self) -> &mut p2p_stream::Behaviour { @@ -847,7 +854,8 @@ pub enum Event { Kademlia(kad::Event), Gossipsub(gossipsub::Event), HeadersSync(p2p_stream::Event), - BodiesSync(p2p_stream::Event), + ClassesSync(p2p_stream::Event), + StateDiffsSync(p2p_stream::Event), TransactionsSync(p2p_stream::Event), ReceiptsSync(p2p_stream::Event), EventsSync(p2p_stream::Event), @@ -901,9 +909,15 @@ impl From> for Even } } -impl From> for Event { - fn from(event: p2p_stream::Event) -> Self { - Event::BodiesSync(event) +impl From> for Event { + fn from(event: p2p_stream::Event) -> Self { + Event::ClassesSync(event) + } +} + +impl From> for Event { + fn from(event: p2p_stream::Event) -> Self { + Event::StateDiffsSync(event) } } diff --git a/crates/p2p/src/client/peer_agnostic.rs b/crates/p2p/src/client/peer_agnostic.rs index 8d7b1a1a8a..d4b05ac5f0 100644 --- a/crates/p2p/src/client/peer_agnostic.rs +++ b/crates/p2p/src/client/peer_agnostic.rs @@ -7,31 +7,25 @@ use std::{ }; use anyhow::Context; -use futures::{channel::mpsc, StreamExt}; +use futures::StreamExt; use libp2p::PeerId; -use p2p_proto::block::{BlockBodiesRequest, BlockHeadersRequest, BlockHeadersResponse}; use p2p_proto::common::{Direction, Iteration}; -use p2p_proto::event::EventsRequest; -use p2p_proto::receipt::{Receipt, ReceiptsRequest}; -use p2p_proto::transaction::TransactionsRequest; +use p2p_proto::header::{BlockHeadersRequest, BlockHeadersResponse}; use pathfinder_common::{ - event::Event, transaction::{DeployAccountTransactionV0V1, DeployAccountTransactionV3, TransactionVariant}, - BlockHash, BlockNumber, ContractAddress, SignedBlockHeader, TransactionHash, + BlockNumber, }; +use pathfinder_common::{BlockHash, ContractAddress}; + use rayon::prelude::{IntoParallelIterator, ParallelIterator}; use tokio::sync::RwLock; use crate::client::peer_aware; use crate::client::types::{ - MaybeSignedBlockHeader, RawDeployAccountTransaction, StateUpdateWithDefinitions, + RawDeployAccountTransaction, SignedBlockHeader as P2PSignedBlockHeader, }; use crate::sync::protocol; -mod parse; - -use parse::ParserState; - /// Data received from a specific peer. #[derive(Debug)] pub struct PeerData { @@ -76,7 +70,7 @@ impl Client { self.inner .publish( &self.block_propagation_topic, - p2p_proto::block::NewBlock::Id(block_id), + p2p_proto::header::NewBlock::Id(block_id), ) .await } @@ -115,7 +109,7 @@ impl Client { start: BlockNumber, stop: BlockNumber, reverse: bool, - ) -> impl futures::Stream> { + ) -> impl futures::Stream> { let (mut start, stop, direction) = match reverse { true => (stop, start, Direction::Backward), false => (start, stop, Direction::Forward), @@ -141,7 +135,7 @@ impl Client { }, }; - let responses = match self.inner.send_headers_sync_request(peer, request).await + let mut responses = match self.inner.send_headers_sync_request(peer, request).await { Ok(x) => x, Err(error) => { @@ -151,28 +145,21 @@ impl Client { } }; - let mut responses = responses - .flat_map(|response| futures::stream::iter(response.parts)) - .chunks(2) - .scan((), |(), chunk| async { parse::handle_signed_header_chunk(chunk) }) - .boxed(); - while let Some(signed_header) = responses.next().await { let signed_header = match signed_header { - Ok(signed_header) => signed_header, - Err(error) => { - tracing::debug!(%peer, %error, "Header stream failed"); + BlockHeadersResponse::Header(hdr) => match P2PSignedBlockHeader::try_from(*hdr) { + Ok(hdr) => hdr, + Err(error) => { + tracing::debug!(%peer, %error, "Header stream failed"); + continue 'next_peer; + }, + }, + BlockHeadersResponse::Fin => { + tracing::debug!(%peer, "Header stream Fin"); continue 'next_peer; } }; - // Small sanity check. We cannot reliably check the hash here, - // its easier for the caller to ensure it matches expectations. - if signed_header.header.number != start { - tracing::debug!(%peer, "Wrong block number"); - continue 'next_peer; - } - start = match direction { Direction::Forward => start + 1, // unwrap_or_default is safe as this is the genesis edge case, @@ -188,254 +175,11 @@ impl Client { } } } - - pub async fn block_headers( - &self, - start_block: BlockNumber, - num_blocks: usize, - ) -> anyhow::Result> { - anyhow::ensure!(num_blocks > 0, "0 blocks requested"); - - let limit: u64 = num_blocks.try_into()?; - - for peer in self - .get_update_peers_with_sync_capability(protocol::Headers::NAME) - .await - { - let request = BlockHeadersRequest { - iteration: Iteration { - start: start_block.get().into(), - direction: Direction::Forward, - limit, - step: 1.into(), - }, - }; - let response_receiver = self.inner.send_headers_sync_request(peer, request).await; - - match response_receiver { - Ok(mut receiver) => { - // Limits on max message size and our internal limit of maximum blocks per response - // imply that we can't receive more than 1 response. See static asserts in - // [`pathfinder_lib::p2p_network::sync_handlers`]. - let Some(BlockHeadersResponse { parts }) = receiver.next().await else { - // Try the next peer - break; - }; - - let mut state = parse::block_header::State::Uninitialized; - for part in parts { - if let Err(error) = state.advance(part) { - tracing::debug!(from=%peer, %error, "headers response parsing"); - // Try the next peer - break; - } - } - - if let Some(headers) = state.take_parsed() { - // Success - return Ok(headers); - } else { - // Try the next peer - tracing::debug!(from=%peer, "unexpected end of part"); - break; - } - } - // Try the next peer - Err(error) => { - tracing::debug!(from=%peer, %error, "headers request failed"); - } - } - } - - anyhow::bail!("No valid responses to headers request: start {start_block}, n {num_blocks}") - } - - /// Including new class definitions - pub async fn state_updates( - &self, - start_block_hash: BlockHash, - num_blocks: usize, - ) -> anyhow::Result> { - anyhow::ensure!(num_blocks > 0, "0 blocks requested"); - - let limit: u64 = num_blocks.try_into()?; - - // If at some point, mid-way a peer suddenly replies not according to the spec we just - // dump everything from this peer and try with the next peer. - // We're not permissive when it comes to following the spec. - let peers = self - .get_update_peers_with_sync_capability(protocol::Bodies::NAME) - .await; - for peer in peers { - let request = BlockBodiesRequest { - iteration: Iteration { - start: start_block_hash.0.into(), - direction: Direction::Forward, - limit, - step: 1.into(), - }, - }; - let response_receiver = self.inner.send_bodies_sync_request(peer, request).await; - - match response_receiver { - Ok(rx) => { - if let Some(parsed) = parse::(&peer, rx).await { - return Ok(parsed); - } - } - Err(error) => tracing::debug!(from=%peer, %error, "bodies request failed"), - } - } - - anyhow::bail!( - "No valid responses to bodies request: start {start_block_hash}, n {num_blocks}" - ) - } - - pub async fn transactions( - &self, - start_block_hash: BlockHash, - num_blocks: usize, - ) -> anyhow::Result>> { - anyhow::ensure!(num_blocks > 0, "0 blocks requested"); - - let limit: u64 = num_blocks.try_into()?; - - // If at some point, mid-way a peer suddenly replies not according to the spec we just - // dump everything from this peer and try with the next peer. - // We're not permissive when it comes to following the spec. - let peers = self - .get_update_peers_with_sync_capability(protocol::Transactions::NAME) - .await; - for peer in peers { - let request = TransactionsRequest { - iteration: Iteration { - start: start_block_hash.0.into(), - direction: Direction::Forward, - limit, - step: 1.into(), - }, - }; - let response_receiver = self - .inner - .send_transactions_sync_request(peer, request) - .await; - - match response_receiver { - Ok(rx) => { - if let Some(parsed) = parse::(&peer, rx).await { - let computed = compute_contract_addresses(parsed.deploy_account) - .await - .context( - "compute contract addresses for deploy account transactions", - )?; - - let mut parsed: HashMap<_, _> = parsed - .other - .into_iter() - .map(|(h, txns)| { - (h, txns.into_iter().map(|t| t.into_variant()).collect()) - }) - .collect(); - parsed.extend(computed); - - return Ok(parsed); - } - } - Err(error) => tracing::debug!(from=%peer, %error, "transactions request failed"), - } - } - - anyhow::bail!( - "No valid responses to transactions request: start {start_block_hash}, n {num_blocks}" - ) - } - - pub async fn receipts( - &self, - start_block_hash: BlockHash, - num_blocks: usize, - ) -> anyhow::Result>> { - anyhow::ensure!(num_blocks > 0, "0 blocks requested"); - - let limit: u64 = num_blocks.try_into()?; - - // If at some point, mid-way a peer suddenly replies not according to the spec we just - // dump everything from this peer and try with the next peer. - // We're not permissive when it comes to following the spec. - let peers = self - .get_update_peers_with_sync_capability(protocol::Receipts::NAME) - .await; - for peer in peers { - let request = ReceiptsRequest { - iteration: Iteration { - start: start_block_hash.0.into(), - direction: Direction::Forward, - limit, - step: 1.into(), - }, - }; - let response_receiver = self.inner.send_receipts_sync_request(peer, request).await; - - match response_receiver { - Ok(rx) => { - if let Some(parsed) = parse::(&peer, rx).await { - return Ok(parsed); - } - } - Err(error) => tracing::debug!(from=%peer, %error, "receipts request failed"), - } - } - - anyhow::bail!( - "No valid responses to receipts request: start {start_block_hash}, n {num_blocks}" - ) - } - - pub async fn event( - &self, - start_block_hash: BlockHash, - num_blocks: usize, - ) -> anyhow::Result>>> { - anyhow::ensure!(num_blocks > 0, "0 blocks requested"); - - let limit: u64 = num_blocks.try_into()?; - - // If at some point, mid-way a peer suddenly replies not according to the spec we just - // dump everything from this peer and try with the next peer. - // We're not permissive when it comes to following the spec. - let peers = self - .get_update_peers_with_sync_capability(protocol::Events::NAME) - .await; - for peer in peers { - let request = EventsRequest { - iteration: Iteration { - start: start_block_hash.0.into(), - direction: Direction::Forward, - limit, - step: 1.into(), - }, - }; - let response_receiver = self.inner.send_events_sync_request(peer, request).await; - - match response_receiver { - Ok(rx) => { - if let Some(parsed) = parse::(&peer, rx).await { - return Ok(parsed); - } - } - Err(error) => tracing::debug!(from=%peer, %error, "events request failed"), - } - } - - anyhow::bail!( - "No valid responses to events request: start {start_block_hash}, n {num_blocks}" - ) - } } +// TODO /// Does not block the current thread. -async fn compute_contract_addresses( +async fn _compute_contract_addresses( deploy_account: HashMap>, ) -> anyhow::Result)>> { let jh = tokio::task::spawn_blocking(move || { @@ -498,25 +242,6 @@ async fn compute_contract_addresses( Ok(computed) } -async fn parse( - peer: &PeerId, - mut receiver: mpsc::Receiver, -) -> Option { - let mut state = P::default(); - - while let Some(response) = receiver.next().await { - if let Err(error) = state.advance(response) { - tracing::debug!(from=%peer, %error, "{} response parsing", std::any::type_name::()); - break; - } - } - - state.take_parsed().or_else(|| { - tracing::debug!(from=%peer, "empty response or unexpected end of response"); - None - }) -} - #[derive(Clone, Debug)] struct PeersWithCapability { set: HashMap>, diff --git a/crates/p2p/src/client/peer_agnostic/parse.rs b/crates/p2p/src/client/peer_agnostic/parse.rs deleted file mode 100644 index 8532c93dc9..0000000000 --- a/crates/p2p/src/client/peer_agnostic/parse.rs +++ /dev/null @@ -1,789 +0,0 @@ -use anyhow::Context; -use p2p_proto::{block::BlockHeadersResponsePart, common::ConsensusSignature}; -use pathfinder_common::{ - BlockCommitmentSignature, BlockCommitmentSignatureElem, SignedBlockHeader, -}; - -use crate::client::types::TryFromDto; - -pub(crate) trait ParserState { - type Dto; - type Inner; - type Out; - - fn advance(&mut self, item: Self::Dto) -> anyhow::Result<()> - where - Self: Default + Sized, - { - let current_state = std::mem::take(self); - let next_state = current_state.transition(item)?; - - *self = next_state; - - if self.should_stop() { - anyhow::bail!("no data or premature end of response") - } else { - Ok(()) - } - } - - fn transition(self, item: Self::Dto) -> anyhow::Result - where - Self: Sized; - - fn from_inner(inner: Self::Inner) -> Self::Out; - - fn take_parsed(self) -> Option; - - fn should_stop(&self) -> bool; -} - -macro_rules! impl_take_parsed_and_should_stop { - ($inner_collection: ident) => { - fn take_parsed(self) -> Option<::Out> { - match self { - Self::Delimited { $inner_collection } - | Self::DelimitedWithError { - $inner_collection, .. - } => { - debug_assert!(!$inner_collection.is_empty()); - Some(Self::from_inner($inner_collection)) - } - _ => None, - } - } - - fn should_stop(&self) -> bool { - matches!(self, Self::Empty { .. } | Self::DelimitedWithError { .. }) - } - }; -} - -pub(crate) mod block_header { - use crate::client::types::{BlockHeader, MaybeSignedBlockHeader}; - use anyhow::Context; - use p2p_proto::block::BlockHeadersResponsePart; - use p2p_proto::common::{Error, Fin}; - use pathfinder_common::{ - signature::BlockCommitmentSignature, BlockCommitmentSignatureElem, BlockHash, - }; - use std::collections::HashMap; - - #[derive(Debug, Default)] - pub enum State { - #[default] - Uninitialized, - Header { - current: BlockHash, - headers: HashMap, - }, - Signatures { - headers: HashMap, - }, - Delimited { - headers: HashMap, - }, - DelimitedWithError { - error: Error, - headers: HashMap, - }, - Empty { - error: Option, - }, - } - - impl super::ParserState for State { - type Dto = BlockHeadersResponsePart; - type Inner = HashMap; - type Out = Vec; - - fn transition(self, next: Self::Dto) -> anyhow::Result { - Ok(match (self, next) { - (State::Uninitialized, BlockHeadersResponsePart::Header(header)) => { - let header = BlockHeader::try_from(*header).context("parsing header")?; - Self::Header { - current: header.hash, - headers: [(header.hash, header.into())].into(), - } - } - (State::Uninitialized, BlockHeadersResponsePart::Fin(Fin { error })) => { - Self::Empty { error } - } - ( - State::Header { - current, - mut headers, - }, - BlockHeadersResponsePart::Signatures(signatures), - ) => { - if current != BlockHash(signatures.block.hash.0) { - anyhow::bail!("unexpected part"); - } - - headers - .get_mut(¤t) - .expect("header for this hash is present") - .signatures - .extend(signatures.signatures.into_iter().map(|signature| { - BlockCommitmentSignature { - r: BlockCommitmentSignatureElem(signature.r), - s: BlockCommitmentSignatureElem(signature.s), - } - })); - Self::Signatures { headers } - } - ( - State::Header { headers, .. } | State::Signatures { headers }, - BlockHeadersResponsePart::Fin(Fin { error }), - ) => match error { - Some(error) => State::DelimitedWithError { error, headers }, - None => State::Delimited { headers }, - }, - (State::Delimited { mut headers }, BlockHeadersResponsePart::Header(header)) => { - if headers.contains_key(&BlockHash(header.hash.0)) { - anyhow::bail!("unexpected part"); - } - - let current = BlockHash(header.hash.0); - let header = BlockHeader::try_from(*header).context("parsing header")?; - headers.insert(header.hash, header.into()); - Self::Header { current, headers } - } - (_, _) => anyhow::bail!("unexpected part"), - }) - } - - fn from_inner(inner: Self::Inner) -> Self::Out { - inner.into_values().collect() - } - - impl_take_parsed_and_should_stop!(headers); - } -} - -pub(crate) mod state_update { - use crate::client::types::{StateUpdate, StateUpdateWithDefinitions}; - use p2p_proto::{ - block::{BlockBodiesResponse, BlockBodyMessage}, - common::{BlockId, Error, Fin}, - state::{Class, Classes}, - }; - use pathfinder_common::BlockHash; - use std::collections::HashMap; - - #[derive(Debug, Default)] - pub enum State { - #[default] - Uninitialized, - Diff { - last_id: BlockId, - state_updates: HashMap)>, - }, - Classes { - last_id: BlockId, - state_updates: HashMap)>, - }, - _Proof, // TODO add proof support - Delimited { - state_updates: HashMap)>, - }, - DelimitedWithError { - error: Error, - state_updates: HashMap)>, - }, - Empty { - error: Option, - }, - } - - impl super::ParserState for State { - type Dto = BlockBodiesResponse; - type Inner = HashMap)>; - type Out = Vec; - - fn transition(self, item: Self::Dto) -> anyhow::Result { - let BlockBodiesResponse { id, body_message } = item; - Ok(match (self, id, body_message) { - (State::Uninitialized, Some(id), BlockBodyMessage::Diff(diff)) => State::Diff { - last_id: id, - state_updates: [(id, (diff.into(), Default::default()))].into(), - }, - (State::Uninitialized, _, BlockBodyMessage::Fin(Fin { error })) => { - State::Empty { error } - } - ( - State::Diff { - last_id, - state_updates, - } - | State::Classes { - last_id, - state_updates, - }, - Some(id), - BlockBodyMessage::Fin(Fin { error }), - ) if last_id == id => match error { - Some(error) => State::DelimitedWithError { - error, - state_updates, - }, - None => State::Delimited { state_updates }, - }, - ( - State::Diff { - last_id, - mut state_updates, - } - | State::Classes { - last_id, - mut state_updates, - }, - Some(id), - BlockBodyMessage::Classes(Classes { - domain: _, // TODO - classes, - }), - ) if last_id == id => { - let current = state_updates - .get_mut(&id) - .expect("state update for this id is present"); - current.1.extend(classes); - - State::Classes { - last_id, - state_updates, - } - } - ( - State::Delimited { mut state_updates }, - Some(id), - BlockBodyMessage::Diff(diff), - ) => { - if state_updates.contains_key(&id) { - anyhow::bail!("unexpected response"); - } - - state_updates.insert(id, (diff.into(), Default::default())); - - State::Diff { - last_id: id, - state_updates, - } - } - (_, _, _) => anyhow::bail!("unexpected response"), - }) - } - - fn from_inner(inner: Self::Inner) -> Self::Out { - inner - .into_iter() - .map(|(k, v)| StateUpdateWithDefinitions { - block_hash: BlockHash(k.hash.0), - state_update: v.0, - classes: v.1, - }) - .collect() - } - - impl_take_parsed_and_should_stop!(state_updates); - } -} - -pub(crate) mod transactions { - use crate::client::types::{ - NonDeployAccountTransaction, RawDeployAccountTransaction, RawTransactionVariant, TryFromDto, - }; - use anyhow::Context; - use p2p_proto::common::{BlockId, Error, Fin}; - use p2p_proto::transaction::{Transactions, TransactionsResponse, TransactionsResponseKind}; - use pathfinder_common::BlockHash; - use std::collections::HashMap; - - #[derive(Debug, Default)] - pub enum State { - #[default] - Uninitialized, - Transactions { - last_id: BlockId, - transactions: InnerTransactions, - }, - Delimited { - transactions: InnerTransactions, - }, - DelimitedWithError { - error: Error, - transactions: InnerTransactions, - }, - Empty { - error: Option, - }, - } - - #[derive(Debug)] - pub struct InnerTransactions { - pub deploy_account: HashMap>, - pub other: HashMap>, - } - - impl InnerTransactions { - pub fn is_empty(&self) -> bool { - self.deploy_account.is_empty() && self.other.is_empty() - } - - pub fn contains_key(&self, id: &BlockId) -> bool { - self.deploy_account.contains_key(id) || self.other.contains_key(id) - } - } - - #[derive(Debug)] - pub struct ParsedTransactions { - pub deploy_account: HashMap>, - pub other: HashMap>, - } - - impl From for ParsedTransactions { - fn from(inner: InnerTransactions) -> Self { - Self { - deploy_account: inner - .deploy_account - .into_iter() - .map(|(k, v)| (BlockHash(k.hash.0), v)) - .collect(), - other: inner - .other - .into_iter() - .map(|(k, v)| (BlockHash(k.hash.0), v)) - .collect(), - } - } - } - - fn try_from_dto( - dtos: Vec, - ) -> anyhow::Result<( - Vec, - Vec, - )> { - let mut deploy_account_txns = Vec::new(); - let mut other_txns = Vec::new(); - - for dto in dtos { - let transaction = - RawTransactionVariant::try_from_dto(dto).context("parsing transaction")?; - match transaction { - RawTransactionVariant::DeployAccount(txn) => { - deploy_account_txns.push(txn); - } - RawTransactionVariant::NonDeployAccount(txn) => { - other_txns.push(txn); - } - } - } - - Ok((deploy_account_txns, other_txns)) - } - - impl super::ParserState for State { - type Dto = TransactionsResponse; - type Inner = InnerTransactions; - type Out = ParsedTransactions; - - fn transition(self, next: Self::Dto) -> anyhow::Result { - let TransactionsResponse { id, kind } = next; - Ok(match (self, id, kind) { - // We've just started, accept any transactions from some block - ( - State::Uninitialized, - Some(id), - TransactionsResponseKind::Transactions(Transactions { items }), - ) => { - let mut deploy_account = HashMap::new(); - let mut other = HashMap::new(); - let (new_deploy_account, new_other) = try_from_dto(items)?; - if !deploy_account.is_empty() { - deploy_account.insert(id, new_deploy_account); - } - if !other.is_empty() { - other.insert(id, new_other); - } - - State::Transactions { - last_id: id, - transactions: InnerTransactions { - deploy_account, - other, - }, - } - } - // The peer does not have anything we asked for - (State::Uninitialized, _, TransactionsResponseKind::Fin(Fin { error })) => { - State::Empty { error } - } - // There's more transactions for the same block - ( - State::Transactions { - last_id, - mut transactions, - }, - Some(id), - TransactionsResponseKind::Transactions(Transactions { items }), - ) if last_id == id => { - let (new_deploy_account, new_other) = try_from_dto(items)?; - transactions - .deploy_account - .get_mut(&id) - .expect("this id is present") - .extend(new_deploy_account); - transactions - .other - .get_mut(&id) - .expect("this id is present") - .extend(new_other); - - State::Transactions { - last_id, - transactions, - } - } - // This is the end of the current block - ( - State::Transactions { - last_id, - transactions, - }, - Some(id), - TransactionsResponseKind::Fin(Fin { error }), - ) if last_id == id => match error { - Some(error) => State::DelimitedWithError { - error, - transactions, - }, - None => State::Delimited { transactions }, - }, - // Accepting transactions for some other block we've not seen yet - ( - State::Delimited { mut transactions }, - Some(id), - TransactionsResponseKind::Transactions(Transactions { items }), - ) => { - debug_assert!(!transactions.is_empty()); - - if transactions.contains_key(&id) { - anyhow::bail!("unexpected response"); - } - - let (new_deploy_account, new_other) = try_from_dto(items)?; - - transactions.deploy_account.insert(id, new_deploy_account); - transactions.other.insert(id, new_other); - - State::Transactions { - last_id: id, - - transactions, - } - } - (_, _, _) => anyhow::bail!("unexpected response"), - }) - } - - fn from_inner(inner: Self::Inner) -> Self::Out { - inner.into() - } - - impl_take_parsed_and_should_stop!(transactions); - } -} - -pub(crate) mod receipts { - use p2p_proto::common::{BlockId, Error, Fin}; - use p2p_proto::receipt::{Receipt, Receipts, ReceiptsResponse, ReceiptsResponseKind}; - use pathfinder_common::BlockHash; - use std::collections::HashMap; - - #[derive(Debug, Default)] - pub enum State { - #[default] - Uninitialized, - Receipts { - last_id: BlockId, - receipts: HashMap>, - }, - Delimited { - receipts: HashMap>, - }, - DelimitedWithError { - error: Error, - receipts: HashMap>, - }, - Empty { - error: Option, - }, - } - - impl super::ParserState for State { - type Dto = ReceiptsResponse; - type Inner = HashMap>; - type Out = HashMap>; - - fn transition(self, next: Self::Dto) -> anyhow::Result { - let ReceiptsResponse { id, kind } = next; - Ok(match (self, id, kind) { - // We've just started, accept any receipts from some block - ( - State::Uninitialized, - Some(id), - ReceiptsResponseKind::Receipts(Receipts { items }), - ) => State::Receipts { - last_id: id, - receipts: [(id, items)].into(), - }, - // The peer does not have anything we asked for - (State::Uninitialized, _, ReceiptsResponseKind::Fin(Fin { error })) => { - State::Empty { error } - } - // There's more receipts for the same block - ( - State::Receipts { - last_id, - mut receipts, - }, - Some(id), - ReceiptsResponseKind::Receipts(Receipts { items }), - ) if last_id == id => { - receipts - .get_mut(&id) - .expect("transactions for this id is present") - .extend(items); - - State::Receipts { last_id, receipts } - } - // This is the end of the current block - ( - State::Receipts { last_id, receipts }, - Some(id), - ReceiptsResponseKind::Fin(Fin { error }), - ) if last_id == id => match error { - Some(error) => State::DelimitedWithError { error, receipts }, - None => State::Delimited { receipts }, - }, - // Accepting receipts for some other block we've not seen yet - ( - State::Delimited { mut receipts }, - Some(id), - ReceiptsResponseKind::Receipts(Receipts { items }), - ) => { - debug_assert!(!receipts.is_empty()); - - if receipts.contains_key(&id) { - anyhow::bail!("unexpected response"); - } - - receipts.insert(id, items); - - State::Receipts { - last_id: id, - receipts, - } - } - (_, _, _) => anyhow::bail!("unexpected response"), - }) - } - - fn from_inner(inner: Self::Inner) -> Self::Out { - inner - .into_iter() - .map(|(k, v)| (BlockHash(k.hash.0), v)) - .collect() - } - - impl_take_parsed_and_should_stop!(receipts); - } -} - -pub(crate) mod events { - use p2p_proto::common::{BlockId, Error, Fin}; - use p2p_proto::event::{Event, Events, EventsResponse, EventsResponseKind}; - use pathfinder_common::{BlockHash, TransactionHash}; - use std::collections::HashMap; - - #[derive(Debug, Default)] - pub enum State { - #[default] - Uninitialized, - Events { - last_id: BlockId, - events: HashMap>, - }, - Delimited { - events: HashMap>, - }, - DelimitedWithError { - error: Error, - events: HashMap>, - }, - Empty { - error: Option, - }, - } - - impl super::ParserState for State { - type Dto = EventsResponse; - type Inner = HashMap>; - type Out = - HashMap>>; - - fn transition(self, next: Self::Dto) -> anyhow::Result { - let EventsResponse { id, kind } = next; - Ok(match (self, id, kind) { - // We've just started, accept any events from some block - (State::Uninitialized, Some(id), EventsResponseKind::Events(Events { items })) => { - State::Events { - last_id: id, - events: [(id, items)].into(), - } - } - // The peer does not have anything we asked for - (State::Uninitialized, _, EventsResponseKind::Fin(Fin { error })) => { - State::Empty { error } - } - // There's more events for the same block - ( - State::Events { - last_id, - mut events, - }, - Some(id), - EventsResponseKind::Events(Events { items }), - ) if last_id == id => { - events - .get_mut(&id) - .expect("transactions for this id is present") - .extend(items); - - State::Events { last_id, events } - } - // This is the end of the current block - ( - State::Events { last_id, events }, - Some(id), - EventsResponseKind::Fin(Fin { error }), - ) if last_id == id => match error { - Some(error) => State::DelimitedWithError { error, events }, - None => State::Delimited { events }, - }, - // Accepting events for some other block we've not seen yet - ( - State::Delimited { mut events }, - Some(id), - EventsResponseKind::Events(Events { items }), - ) => { - debug_assert!(!events.is_empty()); - - if events.contains_key(&id) { - anyhow::bail!("unexpected response"); - } - - events.insert(id, items); - - State::Events { - last_id: id, - events, - } - } - (_, _, _) => anyhow::bail!("unexpected response"), - }) - } - - fn from_inner(inner: Self::Inner) -> Self::Out { - use pathfinder_common::{event::Event, ContractAddress, EventData, EventKey}; - - inner - .into_iter() - .map(|(k, v)| { - let mut events = HashMap::<_, Vec>::new(); - v.into_iter().for_each(|e| { - events - .entry(TransactionHash(e.transaction_hash.0)) - .or_default() - .push(Event { - data: e.data.into_iter().map(EventData).collect(), - from_address: ContractAddress(e.from_address), - keys: e.keys.into_iter().map(EventKey).collect(), - }) - }); - - (BlockHash(k.hash.0), events) - }) - .collect() - } - - impl_take_parsed_and_should_stop!(events); - } -} - -/// Parses (header, signature) pairs. Expects chunks(2) as input, -/// will panic if given a chunk with length not one or two. -/// -/// Errors if the chunk is not a (header, signature) pair or a -/// successful [BlockHeadersResponsePart::Fin]. -pub(crate) fn handle_signed_header_chunk( - chunk: Vec, -) -> Option> { - use anyhow::anyhow; - - if let [single] = chunk.as_slice() { - match single { - Header(_) => { - return Some(Err(anyhow!("Stream finalized with header"))); - } - Signatures(_) => { - return Some(Err(anyhow!("Stream finalized with signature"))); - } - Fin(p2p_proto::common::Fin { error: Some(error) }) => { - return Some(Err(anyhow!("Stream finalized with error: {error:?}"))); - } - Fin(_) => return None, - } - } - - let [a, b] = chunk.as_slice() else { - panic!("Expected exactly two items in the chunk"); - }; - - use BlockHeadersResponsePart::*; - let result = match (a, b) { - (Fin(_), _) | (_, Fin(_)) => Err(anyhow!("Received unexpected Fin")), - (Signatures(_), _) => Err(anyhow!("Received signature without header")), - (_, Header(_)) => Err(anyhow!("Received header without signature")), - (Header(header), Signatures(signatures)) => { - if header.hash != signatures.block.hash { - return Some(Err(anyhow!("Signature and header block hash mismatch"))); - } - if header.number != signatures.block.number { - return Some(Err(anyhow!("Signature and header block number mismatch"))); - } - - let header = match pathfinder_common::BlockHeader::try_from_dto(header) - .context("Parsing header") - { - Ok(header) => header, - Err(e) => return Some(Err(e)), - }; - - let signature = match signatures.signatures.as_slice() { - &[ConsensusSignature { r, s }] => BlockCommitmentSignature { - r: BlockCommitmentSignatureElem(r), - s: BlockCommitmentSignatureElem(s), - }, - other => { - return Some(Err(anyhow!("Bad signature length: {}", other.len(),))); - } - }; - - Ok(SignedBlockHeader { header, signature }) - } - }; - - Some(result) -} diff --git a/crates/p2p/src/client/peer_aware.rs b/crates/p2p/src/client/peer_aware.rs index 25d0a118d3..322dc707d7 100644 --- a/crates/p2p/src/client/peer_aware.rs +++ b/crates/p2p/src/client/peer_aware.rs @@ -5,10 +5,11 @@ use std::collections::HashSet; use anyhow::Context; use futures::channel::mpsc::Receiver as ResponseReceiver; use libp2p::{gossipsub::IdentTopic, Multiaddr, PeerId}; -use p2p_proto::block::{ - BlockBodiesRequest, BlockBodiesResponse, BlockHeadersRequest, BlockHeadersResponse, NewBlock, -}; +use p2p_proto::class::{ClassesRequest, ClassesResponse}; use p2p_proto::event::{EventsRequest, EventsResponse}; +use p2p_proto::header::{BlockHeadersRequest, BlockHeadersResponse, NewBlock}; +use p2p_proto::state::{StateDiffsRequest, StateDiffsResponse}; + use p2p_proto::receipt::{ReceiptsRequest, ReceiptsResponse}; use p2p_proto::transaction::{TransactionsRequest, TransactionsResponse}; use tokio::sync::{mpsc, oneshot}; @@ -138,10 +139,17 @@ impl Client { ); impl_send!( - send_bodies_sync_request, - SendBodiesSyncRequest, - BlockBodiesRequest, - BlockBodiesResponse + send_classes_sync_request, + SendClassesSyncRequest, + ClassesRequest, + ClassesResponse + ); + + impl_send!( + send_state_diffs_sync_request, + SendStateDiffsSyncRequest, + StateDiffsRequest, + StateDiffsResponse ); impl_send!( diff --git a/crates/p2p/src/client/types.rs b/crates/p2p/src/client/types.rs index 9aa8ff8cc3..c300ed171e 100644 --- a/crates/p2p/src/client/types.rs +++ b/crates/p2p/src/client/types.rs @@ -1,11 +1,7 @@ //! Conversions between DTOs and common types. //! //! Also includes some "bridging" types which should eventually be removed -use std::{collections::HashMap, time::SystemTime}; - use pathfinder_common::event::Event; -use pathfinder_common::signature::BlockCommitmentSignature; -use pathfinder_common::state_update::SystemContractUpdate; use pathfinder_common::transaction::{ DataAvailabilityMode, DeclareTransactionV0V1, DeclareTransactionV2, DeclareTransactionV3, DeployAccountTransactionV0V1, DeployAccountTransactionV3, DeployTransaction, @@ -13,11 +9,11 @@ use pathfinder_common::transaction::{ ResourceBound, ResourceBounds, TransactionVariant, }; use pathfinder_common::{ - AccountDeploymentDataElem, BlockHash, BlockNumber, BlockTimestamp, CallParam, CasmHash, - ClassHash, ConstructorParam, ContractAddress, ContractAddressSalt, ContractNonce, EntryPoint, - EventData, EventKey, Fee, GasPrice, PaymasterDataElem, SequencerAddress, StarknetVersion, - StateCommitment, StorageAddress, StorageValue, Tip, TransactionNonce, TransactionSignatureElem, - TransactionVersion, + AccountDeploymentDataElem, BlockCommitmentSignature, BlockCommitmentSignatureElem, BlockHash, + BlockNumber, BlockTimestamp, CallParam, CasmHash, ClassHash, ConstructorParam, ContractAddress, + ContractAddressSalt, EntryPoint, EventCommitment, EventData, EventKey, Fee, GasPrice, + PaymasterDataElem, SequencerAddress, StarknetVersion, StateCommitment, Tip, + TransactionCommitment, TransactionNonce, TransactionSignatureElem, TransactionVersion, }; /// We don't want to introduce circular dependencies between crates @@ -28,9 +24,9 @@ pub trait TryFromDto { Self: Sized; } -/// Block header but without most of the commitments -#[derive(Debug, Clone, PartialEq, Eq, Default)] -pub struct BlockHeader { +/// Represents a simplified [`pathfinder_common::SignedBlockHeader`], ie. excluding class commitment and storage commitment. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SignedBlockHeader { pub hash: BlockHash, pub parent_hash: BlockHash, pub number: BlockNumber, @@ -38,84 +34,76 @@ pub struct BlockHeader { pub eth_l1_gas_price: GasPrice, pub sequencer_address: SequencerAddress, pub starknet_version: StarknetVersion, + pub event_commitment: EventCommitment, pub state_commitment: StateCommitment, + pub transaction_commitment: TransactionCommitment, + pub transaction_count: usize, + pub event_count: usize, + pub signature: BlockCommitmentSignature, } -#[derive(Clone, Debug, PartialEq)] -pub struct MaybeSignedBlockHeader { - pub header: BlockHeader, - pub signatures: Vec, -} - -impl From for MaybeSignedBlockHeader { - fn from(header: BlockHeader) -> Self { - Self { - header, - signatures: Default::default(), - } - } -} +impl TryFrom for SignedBlockHeader { + type Error = anyhow::Error; -impl TryFromDto for pathfinder_common::BlockHeader -where - T: AsRef, -{ - fn try_from_dto(dto: T) -> anyhow::Result { - let dto = dto.as_ref(); - Ok(Self { - hash: BlockHash(dto.hash.0), + fn try_from(dto: p2p_proto::header::SignedBlockHeader) -> Result { + anyhow::ensure!(dto.signatures.len() == 1, "expected exactly one signature"); + let signature = dto + .signatures + .into_iter() + .map(|sig| BlockCommitmentSignature { + r: BlockCommitmentSignatureElem(sig.r), + s: BlockCommitmentSignatureElem(sig.s), + }) + .next() + .expect("exactly one element"); + Ok(SignedBlockHeader { + hash: BlockHash(dto.block_hash.0), parent_hash: BlockHash(dto.parent_hash.0), number: BlockNumber::new(dto.number) - .ok_or(anyhow::anyhow!("Invalid block number > i64::MAX"))?, - timestamp: BlockTimestamp::new( - dto.time.duration_since(SystemTime::UNIX_EPOCH)?.as_secs(), - ) - .ok_or(anyhow::anyhow!("Invalid block timestamp"))?, + .ok_or(anyhow::anyhow!("block number > i64::MAX"))?, + timestamp: BlockTimestamp::new(dto.time) + .ok_or(anyhow::anyhow!("block timestamp > i64::MAX"))?, + eth_l1_gas_price: dto.gas_price.into(), sequencer_address: SequencerAddress(dto.sequencer_address.0), - eth_l1_gas_price: GasPrice::from_be_slice(dto.gas_price.as_slice())?, - starknet_version: StarknetVersion::from(dto.starknet_version.clone()), - // State commitments may only be calculated intermittently in the future to save on compute. - state_commitment: StateCommitment(dto.state_commitment.unwrap_or_default().0), - event_commitment: pathfinder_common::EventCommitment(dto.events.root.0), - event_count: dto.events.n_leaves as usize, - transaction_commitment: pathfinder_common::TransactionCommitment( - dto.transactions.root.0, - ), - transaction_count: dto.transactions.n_leaves as usize, - strk_l1_gas_price: Default::default(), - class_commitment: Default::default(), - storage_commitment: Default::default(), + starknet_version: dto.protocol_version.into(), + event_commitment: EventCommitment(dto.events.root.0), + state_commitment: StateCommitment(dto.state.root.0), + transaction_commitment: TransactionCommitment(dto.transactions.root.0), + transaction_count: dto.transactions.n_leaves.try_into()?, + event_count: dto.events.n_leaves.try_into()?, + signature, }) } } -/// Simple state update meant for the temporary p2p client hidden behind -/// the gateway client api, ie.: -/// - does not contain any commitments -/// - does not specify if the class was declared or replaced -/// -/// TODO: remove this once proper p2p friendly sync is implemented -#[derive(Debug, Clone, PartialEq)] -pub struct StateUpdate { - pub contract_updates: HashMap, - pub system_contract_updates: HashMap, -} - -/// Simple state update with class definitions whose hashes have not been computed and compared against the state update yet. -#[derive(Debug, Clone, PartialEq)] -pub struct StateUpdateWithDefinitions { - pub block_hash: BlockHash, - pub state_update: StateUpdate, - pub classes: Vec, -} - -#[derive(Debug, Clone, PartialEq)] -pub struct ContractUpdate { - pub storage: HashMap, - /// The class associated with this update as the result of either a deploy or class replacement transaction. - /// We don't explicitly know if it's one or the other - pub class: Option, - pub nonce: Option, +impl + From<( + pathfinder_common::BlockHeader, + pathfinder_common::BlockCommitmentSignature, + )> for SignedBlockHeader +{ + fn from( + (header, signature): ( + pathfinder_common::BlockHeader, + pathfinder_common::BlockCommitmentSignature, + ), + ) -> Self { + Self { + hash: header.hash, + parent_hash: header.parent_hash, + number: header.number, + timestamp: header.timestamp, + eth_l1_gas_price: header.eth_l1_gas_price, + sequencer_address: header.sequencer_address, + starknet_version: header.starknet_version, + event_commitment: header.event_commitment, + state_commitment: header.state_commitment, + transaction_commitment: header.transaction_commitment, + transaction_count: header.transaction_count, + event_count: header.event_count, + signature, + } + } } /// Deployed contract address has not been computed for deploy account transactions. @@ -173,112 +161,6 @@ pub struct RawDeployAccountTransactionV3 { pub class_hash: ClassHash, } -impl From for BlockHeader { - fn from(value: pathfinder_common::BlockHeader) -> Self { - Self { - hash: value.hash, - parent_hash: value.parent_hash, - number: value.number, - timestamp: value.timestamp, - eth_l1_gas_price: value.eth_l1_gas_price, - sequencer_address: value.sequencer_address, - starknet_version: value.starknet_version, - state_commitment: value.state_commitment, - } - } -} - -impl TryFrom for BlockHeader { - type Error = anyhow::Error; - - fn try_from(dto: p2p_proto::block::BlockHeader) -> anyhow::Result { - Ok(Self { - hash: BlockHash(dto.hash.0), - parent_hash: BlockHash(dto.parent_hash.0), - number: BlockNumber::new(dto.number) - .ok_or(anyhow::anyhow!("Invalid block number > i64::MAX"))?, - timestamp: BlockTimestamp::new( - dto.time.duration_since(SystemTime::UNIX_EPOCH)?.as_secs(), - ) - .ok_or(anyhow::anyhow!("Invalid block timestamp"))?, - sequencer_address: SequencerAddress(dto.sequencer_address.0), - // TODO imo missing in the spec - eth_l1_gas_price: GasPrice::from_be_slice(dto.gas_price.as_slice())?, - // TODO not sure if should be in the spec - starknet_version: StarknetVersion::from(dto.starknet_version), - // TODO remove this field when signature verification is done - // allows to verify block hash and state commitment when present - state_commitment: StateCommitment(dto.state_commitment.unwrap_or_default().0), - }) - } -} - -impl From for StateUpdate { - fn from(s: pathfinder_common::StateUpdate) -> Self { - Self { - contract_updates: s - .contract_updates - .into_iter() - .map(|(k, v)| (k, v.into())) - .collect(), - system_contract_updates: s.system_contract_updates, - } - } -} - -impl From for ContractUpdate { - fn from(c: pathfinder_common::state_update::ContractUpdate) -> Self { - Self { - storage: c.storage, - class: c.class.map(|x| x.class_hash()), - nonce: c.nonce, - } - } -} - -impl From for StateUpdate { - fn from(proto: p2p_proto::state::StateDiff) -> Self { - const SYSTEM_CONTRACT: ContractAddress = ContractAddress::ONE; - let mut system_contract_update = SystemContractUpdate { - storage: Default::default(), - }; - let mut contract_updates = HashMap::new(); - proto.contract_diffs.into_iter().for_each(|diff| { - if diff.address.0 == SYSTEM_CONTRACT.0 { - diff.values.into_iter().for_each(|x| { - system_contract_update - .storage - .insert(StorageAddress(x.key), StorageValue(x.value)); - }); - } else { - contract_updates.insert( - ContractAddress(diff.address.0), - ContractUpdate { - storage: diff - .values - .into_iter() - .map(|x| (StorageAddress(x.key), StorageValue(x.value))) - .collect(), - class: diff.class_hash.map(ClassHash), - nonce: diff.nonce.map(ContractNonce), - }, - ); - } - }); - - let system_contract_updates = if system_contract_update.storage.is_empty() { - Default::default() - } else { - [(SYSTEM_CONTRACT, system_contract_update)].into() - }; - - Self { - contract_updates, - system_contract_updates, - } - } -} - impl NonDeployAccountTransaction { pub fn into_variant(self) -> TransactionVariant { use NonDeployAccountTransaction::*; @@ -350,17 +232,17 @@ impl From for RawDeployAccountTransactionV3 { } } -impl TryFromDto for RawTransactionVariant { +impl TryFromDto for RawTransactionVariant { /// ## Important /// /// This conversion does not compute deployed contract address for deploy account transactions /// ([`TransactionVariant::DeployAccountV0V1`] and [`TransactionVariant::DeployAccountV3`]), /// filling it with a zero address instead. The caller is responsible for performing the computation after the conversion succeeds. - fn try_from_dto(dto: p2p_proto::transaction::Transaction) -> anyhow::Result + fn try_from_dto(dto: p2p_proto::transaction::TransactionVariant) -> anyhow::Result where Self: Sized, { - use p2p_proto::transaction::Transaction::*; + use p2p_proto::transaction::TransactionVariant::*; Ok(match dto { DeclareV0(x) => RawTransactionVariant::NonDeployAccount( @@ -432,7 +314,7 @@ impl TryFromDto for RawTransactionVariant { ), Deploy(x) => RawTransactionVariant::NonDeployAccount( NonDeployAccountTransaction::Deploy(DeployTransaction { - contract_address: ContractAddress(x.address.0), + contract_address: ContractAddress::ZERO, // FIXME: compute deployed contract address contract_address_salt: ContractAddressSalt(x.address_salt), class_hash: ClassHash(x.class_hash.0), constructor_calldata: x.calldata.into_iter().map(ConstructorParam).collect(), @@ -455,11 +337,7 @@ impl TryFromDto for RawTransactionVariant { .collect(), nonce: TransactionNonce(x.nonce), contract_address_salt: ContractAddressSalt(x.address_salt), - constructor_calldata: x - .constructor_calldata - .into_iter() - .map(CallParam) - .collect(), + constructor_calldata: x.calldata.into_iter().map(CallParam).collect(), class_hash: ClassHash(x.class_hash.0), }), ), diff --git a/crates/p2p/src/lib.rs b/crates/p2p/src/lib.rs index ced4cd7c60..c4ac15e235 100644 --- a/crates/p2p/src/lib.rs +++ b/crates/p2p/src/lib.rs @@ -10,11 +10,11 @@ use libp2p::identity::Keypair; use libp2p::kad::RecordKey; use libp2p::swarm; use libp2p::{Multiaddr, PeerId, Swarm}; -use p2p_proto::block::{ - BlockBodiesRequest, BlockBodiesResponse, BlockHeadersRequest, BlockHeadersResponse, NewBlock, -}; +use p2p_proto::class::{ClassesRequest, ClassesResponse}; use p2p_proto::event::{EventsRequest, EventsResponse}; +use p2p_proto::header::{BlockHeadersRequest, BlockHeadersResponse, NewBlock}; use p2p_proto::receipt::{ReceiptsRequest, ReceiptsResponse}; +use p2p_proto::state::{StateDiffsRequest, StateDiffsResponse}; use p2p_proto::transaction::{TransactionsRequest, TransactionsResponse}; use pathfinder_common::{BlockHash, BlockNumber, ChainId}; use peers::Peer; @@ -156,10 +156,15 @@ enum Command { request: BlockHeadersRequest, sender: oneshot::Sender>>, }, - SendBodiesSyncRequest { + SendClassesSyncRequest { peer_id: PeerId, - request: BlockBodiesRequest, - sender: oneshot::Sender>>, + request: ClassesRequest, + sender: oneshot::Sender>>, + }, + SendStateDiffsSyncRequest { + peer_id: PeerId, + request: StateDiffsRequest, + sender: oneshot::Sender>>, }, SendTransactionsSyncRequest { peer_id: PeerId, @@ -205,10 +210,15 @@ pub enum Event { request: BlockHeadersRequest, channel: ResponseSender, }, - InboundBodiesSyncRequest { + InboundClassesSyncRequest { + from: PeerId, + request: ClassesRequest, + channel: ResponseSender, + }, + InboundStateDiffsSyncRequest { from: PeerId, - request: BlockBodiesRequest, - channel: ResponseSender, + request: StateDiffsRequest, + channel: ResponseSender, }, InboundTransactionsSyncRequest { from: PeerId, diff --git a/crates/p2p/src/main_loop.rs b/crates/p2p/src/main_loop.rs index 6d74e6f39b..bb21e771cf 100644 --- a/crates/p2p/src/main_loop.rs +++ b/crates/p2p/src/main_loop.rs @@ -11,9 +11,11 @@ use libp2p::multiaddr::Protocol; use libp2p::swarm::dial_opts::DialOpts; use libp2p::swarm::SwarmEvent; use libp2p::PeerId; -use p2p_proto::block::{BlockBodiesResponse, BlockHeadersResponse}; +use p2p_proto::class::ClassesResponse; use p2p_proto::event::EventsResponse; +use p2p_proto::header::BlockHeadersResponse; use p2p_proto::receipt::ReceiptsResponse; +use p2p_proto::state::StateDiffsResponse; use p2p_proto::transaction::TransactionsResponse; use p2p_proto::{ToProtobuf, TryFromProtobuf}; use p2p_stream::{self, OutboundRequestId}; @@ -53,9 +55,13 @@ struct PendingRequests { OutboundRequestId, oneshot::Sender>>, >, - pub bodies: HashMap< + pub classes: HashMap< OutboundRequestId, - oneshot::Sender>>, + oneshot::Sender>>, + >, + pub state_diffs: HashMap< + OutboundRequestId, + oneshot::Sender>>, >, pub transactions: HashMap< OutboundRequestId, @@ -318,9 +324,9 @@ impl MainLoop { })) => { use prost::Message; - match p2p_proto::proto::block::NewBlock::decode(message.data.as_ref()) { + match p2p_proto::proto::header::NewBlock::decode(message.data.as_ref()) { Ok(new_block) => { - match p2p_proto::block::NewBlock::try_from_protobuf(new_block, "message") { + match p2p_proto::header::NewBlock::try_from_protobuf(new_block, "message") { Ok(new_block) => { tracing::trace!( "Gossipsub Message: [id={}][peer={}] {:?} ({} bytes)", @@ -483,7 +489,7 @@ impl MainLoop { .expect("Block sync request still to be pending") .send(Ok(channel)); } - SwarmEvent::Behaviour(behaviour::Event::BodiesSync( + SwarmEvent::Behaviour(behaviour::Event::ClassesSync( p2p_stream::Event::InboundRequest { request_id, request, @@ -494,7 +500,7 @@ impl MainLoop { tracing::debug!(?request, %peer, %request_id, "Received sync request"); self.event_sender - .send(Event::InboundBodiesSyncRequest { + .send(Event::InboundClassesSyncRequest { from: peer, request, channel, @@ -502,7 +508,7 @@ impl MainLoop { .await .expect("Event receiver not to be dropped"); } - SwarmEvent::Behaviour(behaviour::Event::BodiesSync( + SwarmEvent::Behaviour(behaviour::Event::ClassesSync( p2p_stream::Event::OutboundRequestSentAwaitingResponses { request_id, peer, @@ -513,7 +519,42 @@ impl MainLoop { let _ = self .pending_sync_requests - .bodies + .classes + .remove(&request_id) + .expect("Block sync request still to be pending") + .send(Ok(channel)); + } + SwarmEvent::Behaviour(behaviour::Event::StateDiffsSync( + p2p_stream::Event::InboundRequest { + request_id, + request, + peer, + channel, + }, + )) => { + tracing::debug!(?request, %peer, %request_id, "Received sync request"); + + self.event_sender + .send(Event::InboundStateDiffsSyncRequest { + from: peer, + request, + channel, + }) + .await + .expect("Event receiver not to be dropped"); + } + SwarmEvent::Behaviour(behaviour::Event::StateDiffsSync( + p2p_stream::Event::OutboundRequestSentAwaitingResponses { + request_id, + peer, + channel, + }, + )) => { + tracing::debug!(%peer, %request_id, "Sync request sent"); + + let _ = self + .pending_sync_requests + .state_diffs .remove(&request_id) .expect("Block sync request still to be pending") .send(Ok(channel)); @@ -636,7 +677,7 @@ impl MainLoop { .expect("Block sync request still to be pending") .send(Err(error.into())); } - SwarmEvent::Behaviour(behaviour::Event::BodiesSync( + SwarmEvent::Behaviour(behaviour::Event::ClassesSync( p2p_stream::Event::OutboundFailure { request_id, error, .. }, @@ -644,7 +685,20 @@ impl MainLoop { tracing::warn!(?request_id, ?error, "Outbound request failed"); let _ = self .pending_sync_requests - .bodies + .classes + .remove(&request_id) + .expect("Block sync request still to be pending") + .send(Err(error.into())); + } + SwarmEvent::Behaviour(behaviour::Event::StateDiffsSync( + p2p_stream::Event::OutboundFailure { + request_id, error, .. + }, + )) => { + tracing::warn!(?request_id, ?error, "Outbound request failed"); + let _ = self + .pending_sync_requests + .state_diffs .remove(&request_id) .expect("Block sync request still to be pending") .send(Err(error.into())); @@ -799,7 +853,7 @@ impl MainLoop { .headers .insert(request_id, sender); } - Command::SendBodiesSyncRequest { + Command::SendClassesSyncRequest { peer_id, request, sender, @@ -809,9 +863,27 @@ impl MainLoop { let request_id = self .swarm .behaviour_mut() - .bodies_sync_mut() + .classes_sync_mut() .send_request(&peer_id, request); - self.pending_sync_requests.bodies.insert(request_id, sender); + self.pending_sync_requests + .classes + .insert(request_id, sender); + } + Command::SendStateDiffsSyncRequest { + peer_id, + request, + sender, + } => { + tracing::debug!(?request, "Sending sync request"); + + let request_id = self + .swarm + .behaviour_mut() + .state_diffs_sync_mut() + .send_request(&peer_id, request); + self.pending_sync_requests + .state_diffs + .insert(request_id, sender); } Command::SendTransactionsSyncRequest { peer_id, diff --git a/crates/p2p/src/sync.rs b/crates/p2p/src/sync.rs index 2b404091ab..7a6ff58d83 100644 --- a/crates/p2p/src/sync.rs +++ b/crates/p2p/src/sync.rs @@ -19,14 +19,16 @@ pub mod protocol { } define_protocol!(Headers, "/starknet/headers/1"); - define_protocol!(Bodies, "/starknet/bodies/1"); + define_protocol!(StateDiffs, "/starknet/state_diffs/1"); + define_protocol!(Classes, "/starknet/classes/1"); define_protocol!(Transactions, "/starknet/transactions/1"); define_protocol!(Receipts, "/starknet/receipts/1"); define_protocol!(Events, "/starknet/events/1"); pub const PROTOCOLS: &[&str] = &[ Headers::NAME, - Bodies::NAME, + StateDiffs::NAME, + Classes::NAME, Transactions::NAME, Receipts::NAME, Events::NAME, @@ -38,25 +40,33 @@ pub(crate) mod codec { use async_trait::async_trait; use futures::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; use p2p_proto::consts::MESSAGE_SIZE_LIMIT; - use p2p_proto::{block, event, proto, receipt, transaction}; + use p2p_proto::{class, event, header, proto, receipt, state, transaction}; use p2p_proto::{ToProtobuf, TryFromProtobuf}; use p2p_stream::Codec; use std::marker::PhantomData; pub type Headers = SyncCodec< protocol::Headers, - block::BlockHeadersRequest, - block::BlockHeadersResponse, - proto::block::BlockHeadersRequest, - proto::block::BlockHeadersResponse, + header::BlockHeadersRequest, + header::BlockHeadersResponse, + proto::header::BlockHeadersRequest, + proto::header::BlockHeadersResponse, >; - pub type Bodies = SyncCodec< - protocol::Bodies, - block::BlockBodiesRequest, - block::BlockBodiesResponse, - proto::block::BlockBodiesRequest, - proto::block::BlockBodiesResponse, + pub type StateDiffs = SyncCodec< + protocol::StateDiffs, + state::StateDiffsRequest, + state::StateDiffsResponse, + proto::state::StateDiffsRequest, + proto::state::StateDiffsResponse, + >; + + pub type Classes = SyncCodec< + protocol::Classes, + class::ClassesRequest, + class::ClassesResponse, + proto::class::ClassesRequest, + proto::class::ClassesResponse, >; pub type Transactions = SyncCodec< diff --git a/crates/p2p/src/tests.rs b/crates/p2p/src/tests.rs index 1a5b6056c0..c3d81f8a13 100644 --- a/crates/p2p/src/tests.rs +++ b/crates/p2p/src/tests.rs @@ -9,11 +9,11 @@ use futures::{SinkExt, StreamExt}; use libp2p::identity::Keypair; use libp2p::multiaddr::Protocol; use libp2p::{Multiaddr, PeerId}; -use p2p_proto::block::{ - BlockBodiesRequest, BlockBodiesResponse, BlockHeadersRequest, BlockHeadersResponse, NewBlock, -}; +use p2p_proto::class::{ClassesRequest, ClassesResponse}; use p2p_proto::event::{EventsRequest, EventsResponse}; +use p2p_proto::header::{BlockHeadersRequest, BlockHeadersResponse, NewBlock}; use p2p_proto::receipt::{ReceiptsRequest, ReceiptsResponse}; +use p2p_proto::state::{StateDiffsRequest, StateDiffsResponse}; use p2p_proto::transaction::{TransactionsRequest, TransactionsResponse}; use pathfinder_common::ChainId; use rstest::rstest; @@ -1276,11 +1276,19 @@ define_test!( ); define_test!( - sync_bodies, - BlockBodiesRequest, - BlockBodiesResponse, - InboundBodiesSyncRequest, - send_bodies_sync_request + sync_classes, + ClassesRequest, + ClassesResponse, + InboundClassesSyncRequest, + send_classes_sync_request +); + +define_test!( + sync_state_diffs, + StateDiffsRequest, + StateDiffsResponse, + InboundStateDiffsSyncRequest, + send_state_diffs_sync_request ); define_test!( diff --git a/crates/p2p_proto/build.rs b/crates/p2p_proto/build.rs index eab6d41ea1..bd59a574f0 100644 --- a/crates/p2p_proto/build.rs +++ b/crates/p2p_proto/build.rs @@ -3,17 +3,16 @@ use std::io::Result; fn main() -> Result<()> { prost_build::compile_protos( &[ - "proto/block.proto", + "proto/class.proto", "proto/common.proto", - "proto/consensus.proto", "proto/event.proto", - "proto/mempool.proto", + "proto/header.proto", "proto/receipt.proto", - "proto/snapshot.proto", "proto/state.proto", "proto/transaction.proto", ], &["proto"], )?; + Ok(()) } diff --git a/crates/p2p_proto/proto/block.proto b/crates/p2p_proto/proto/block.proto deleted file mode 100644 index 5a191cbea5..0000000000 --- a/crates/p2p_proto/proto/block.proto +++ /dev/null @@ -1,95 +0,0 @@ -syntax = "proto3"; - -package starknet.block; - -import "common.proto"; -import "google/protobuf/timestamp.proto"; -import "state.proto"; - -// for now, we assume a small consensus, so this fits in 1M. Else, these will be repeated -message Signatures { - starknet.common.BlockID block = 1; - - repeated starknet.common.ConsensusSignature signatures = 2; // - // can be more explicit here about the signature structure as this is not part of account abstraction -} - -// Note: commitments may change to be for the previous blocks like comet/tendermint -// hash of block header sent to L1 -message BlockHeader { - starknet.common.Hash parent_header = 1; - uint64 number = 2; - google.protobuf.Timestamp time = 3; // TODO: see if this needs to be Felt252 or can be converted - starknet.common.Address sequencer_address = 4; - starknet.common.Merkle state_diffs = 5; // By order of (contract, key), taking last in case of duplicates. - // This means the proposer needs to sort after finishing the block (TBD: patricia? ) - // State is optional and appears every X blocks for the last block. This is to support - // snapshot sync and also so that light nodes can sync on state without state diffs. - starknet.common.Patricia state = 6; // hash of contract and class patricia tries. Same as in L1. Later more trees will be included - starknet.common.Hash proof_fact = 7; // for Kth block behind. A hash of the output of the proof - - // The following merkles can be built on the fly while sequencing/validating txs. - starknet.common.Merkle transactions = 8; // By order of execution. TBD: required? the client can execute (powerful machine) and match state diff - starknet.common.Merkle events = 9; // By order of issuance. TBD: in receipts? - starknet.common.Merkle receipts = 10; // By order of issuance. - - // The protocol is not yet stabilized and we would like these fields to be included in the block header - // - // 1. Currently we are not verifying the signatures, so we need the expected block hash value for verification. - // 2. Even when we have signature verification there is a range of old blocks for which we cannot calculate the block hash. - starknet.common.Hash hash = 11; - // Gas price is not included in the spec but it would be nice to have; big endian - bytes gas_price = 12; - // We still rely on starknet version internally for several thins, e.g. sierra compilation - string starknet_version = 13; - // Allows for state commitment verification and block hash calculation in pathfinder - starknet.common.Hash state_commitment = 14; -} - -message BlockProof { - bytes proof = 1; // proof size is currently 142K -} - -// sent to all peers (except the ones this was received from, if any). -// for a fraction of peers, also send the GetBlockHeaders and GetBlockBodies response (as if they asked for it for this block) -message NewBlock { - oneof maybe_full { - starknet.common.BlockID id = 1; - BlockHeadersResponse header = 2; - BlockBodiesResponse body = 3; - } -} - - -// result is (BlockHeader, Signature?)* in order of creation (incr/dec) -message BlockHeadersRequest { - starknet.common.Iteration iteration = 1; -} - -message BlockHeadersResponsePart { - oneof header_message { - BlockHeader header = 1; - Signatures signatures = 2; - starknet.common.Fin fin = 3; // no support for interleaving for now - } -} - -message BlockHeadersResponse { - repeated BlockHeadersResponsePart part = 1; -} - -// result is (StateDiff*, Classes*, BlockProof?)* currently in creation order (incr/dec), but may change in the future -message BlockBodiesRequest { - starknet.common.Iteration iteration = 1; -} - -message BlockBodiesResponse { - optional starknet.common.BlockID id = 1; // may not appear if Fin is sent to end the whole response - - oneof body_message { - starknet.state.StateDiff diff = 2; - starknet.state.Classes classes = 3; - BlockProof proof = 4; - starknet.common.Fin fin = 5; - } -} diff --git a/crates/p2p_proto/proto/class.proto b/crates/p2p_proto/proto/class.proto new file mode 100644 index 0000000000..9d7faf3aa0 --- /dev/null +++ b/crates/p2p_proto/proto/class.proto @@ -0,0 +1,61 @@ +syntax = "proto3"; +import "common.proto"; + +package starknet.class; + +message EntryPoint { + starknet.common.Felt252 selector = 1; + starknet.common.Felt252 offset = 2; +} + +message Cairo0Class { + bytes abi = 1; + repeated EntryPoint externals = 2; + repeated EntryPoint l1_handlers = 3; + repeated EntryPoint constructors = 4; + bytes program = 5; +} + +message SierraEntryPoint { + uint64 index = 1; + starknet.common.Felt252 selector = 2; +} + +message Cairo1EntryPoints { + repeated SierraEntryPoint externals = 1; + repeated SierraEntryPoint l1_handlers = 2; + repeated SierraEntryPoint constructors = 3; +} + +message Cairo1Class { + bytes abi = 1; + Cairo1EntryPoints entry_points = 2; + repeated starknet.common.Felt252 program = 3; + string contract_class_version = 4; + bytes compiled = 5; +} + +// is it better to separate the definition from the hashes? (will need to repeat the hashes +// for the definitions stream) +// or, make the definitions optional? maybe it is enough to know only that a class exists, not its definition +// which may be fetched lazily later. +message Class { + oneof class { + Cairo0Class cairo0 = 1; + Cairo1Class cairo1 = 2; + } + uint32 domain = 3; + starknet.common.Hash class_hash = 4; +} + +message ClassesRequest { + starknet.common.Iteration iteration = 1; +} + +// Responses are sent ordered by the order given in the request. +message ClassesResponse { + oneof class_message { + Class class = 1; + starknet.common.Fin fin = 2; // Fin is sent after the peer sent all the data or when it encountered a block that it doesn't have its classes. + } +} diff --git a/crates/p2p_proto/proto/common.proto b/crates/p2p_proto/proto/common.proto index 882aaecd14..e40def4879 100644 --- a/crates/p2p_proto/proto/common.proto +++ b/crates/p2p_proto/proto/common.proto @@ -30,12 +30,12 @@ message ConsensusSignature { message Merkle { uint32 n_leaves = 1; // needed to know the height, so as to how many nodes to expect in a proof. // and also when receiving all leaves, how many to expect - Hash root = 2; + Hash root = 2; } message Patricia { uint32 height = 1; - Hash root = 2; + Hash root = 2; } message BlockID { @@ -45,7 +45,7 @@ message BlockID { message Iteration { enum Direction { - Forward = 0; + Forward = 0; Backward = 1; } oneof start { @@ -53,19 +53,11 @@ message Iteration { Hash header = 2; } Direction direction = 3; - uint64 limit = 4; - uint64 step = 5; // to allow interleaving from several nodes + uint64 limit = 4; + uint64 step = 5; // to allow interleaving from several nodes // bool interleave = 6; // return results in any order of blocks, per block the messages should still be in the order specified } // mark the end of a stream of messages // TBD: may not be required if we open a stream per request. -message Fin { - enum Error { - busy = 0; - too_much = 1; - unknown = 2; - pruned = 3; - } - optional Error error = 1; -} +message Fin {} diff --git a/crates/p2p_proto/proto/consensus.proto b/crates/p2p_proto/proto/consensus.proto deleted file mode 100644 index 0f853c362a..0000000000 --- a/crates/p2p_proto/proto/consensus.proto +++ /dev/null @@ -1,43 +0,0 @@ -syntax = "proto3"; - -package starknet.consensus; - -import "common.proto"; -import "google/protobuf/timestamp.proto"; -import "state.proto"; -import "transaction.proto"; - -// WIP - will change - -message Proposal { - uint64 block_number = 2; - uint32 round = 3; - uint32 pol = 4; // proof of lock - starknet.common.Hash block_header_hash = 5; - google.protobuf.Timestamp timestamp = 6; - starknet.common.ConsensusSignature signature = 7; -} - -// A block proposal is a series of (Transactions+, StateDiff)* BlockHeader - -message Vote { - enum Type { - UNKNOWN = 0; - Proposal = 1; - Prevote = 2; - Precommit = 3; - }; - - Proposal proposal = 1; - starknet.common.Address validator_address = 2; - int32 validator_index = 3; // ??? - starknet.common.ConsensusSignature signature = 4; -} - -message CreateBlock { - oneof messages { - starknet.transaction.Transactions transactions = 1; - starknet.state.StateDiff state_diff = 2; - Proposal proposal = 3; - } -} diff --git a/crates/p2p_proto/proto/event.proto b/crates/p2p_proto/proto/event.proto index 9939fbec6f..6eb3ec832b 100644 --- a/crates/p2p_proto/proto/event.proto +++ b/crates/p2p_proto/proto/event.proto @@ -1,9 +1,8 @@ syntax = "proto3"; +import "common.proto"; package starknet.event; -import "common.proto"; - message Event { starknet.common.Hash transaction_hash = 1; starknet.common.Felt252 from_address = 2; @@ -15,16 +14,10 @@ message EventsRequest { starknet.common.Iteration iteration = 1; } -message Events { - repeated Event items = 1; -} - -// can be several in a single reply +// Responses are sent ordered by the order given in the request. message EventsResponse { - optional starknet.common.BlockID id = 1; // may not appear if Fin is sent to end the whole response - - oneof responses { - Events events = 2; - starknet.common.Fin fin = 3; + oneof event_message { + Event event = 1; + starknet.common.Fin fin = 2; // Fin is sent after the peer sent all the data or when it encountered a block that it doesn't have its events. } } diff --git a/crates/p2p_proto/proto/header.proto b/crates/p2p_proto/proto/header.proto new file mode 100644 index 0000000000..be6c21fb67 --- /dev/null +++ b/crates/p2p_proto/proto/header.proto @@ -0,0 +1,53 @@ +syntax = "proto3"; +import "common.proto"; +import "state.proto"; + +package starknet.header; + +// Note: commitments may change to be for the previous blocks like comet/tendermint +// hash of block header sent to L1 +message SignedBlockHeader { + starknet.common.Hash block_hash = 1; // For the structure of the block hash, see https://docs.starknet.io/documentation/architecture_and_concepts/Network_Architecture/header/#block_hash + starknet.common.Hash parent_hash = 2; + uint64 number = 3; + uint64 time = 4; // Encoded in Unix time. + starknet.common.Address sequencer_address = 5; + starknet.common.Hash state_diff_commitment = 6; // The state diff commitment returned by the Starknet Feeder Gateway. + // For more info, see https://community.starknet.io/t/introducing-p2p-authentication-and-mismatch-resolution-in-v0-12-2/97993 + starknet.common.Patricia state = 7; // hash of contract and class patricia tries. Same as in L1. Later more trees will be included + // The following merkles can be built on the fly while sequencing/validating txs. + starknet.common.Merkle transactions = 8; // By order of execution. TBD: required? the client can execute (powerful machine) and match state diff + starknet.common.Merkle events = 9; // By order of issuance. TBD: in receipts? + starknet.common.Merkle receipts = 10; // By order of issuance. + string protocol_version = 11; // Starknet version + starknet.common.Felt252 gas_price = 12; + uint64 num_storage_diffs = 13; + uint64 num_nonce_updates = 14; + uint64 num_declared_classes = 15; // Includes both Cairo 0 and Cairo 1. + uint64 num_deployed_contracts = 16; // This includes the replaced classes too. + // for now, we assume a small consensus, so this fits in 1M. Else, these will be repeated and extracted from this message. + repeated starknet.common.ConsensusSignature signatures = 17; // + // can be more explicit here about the signature structure as this is not part of account abstraction +} + +// sent to all peers (except the ones this was received from, if any). +// for a fraction of peers, also send the GetBlockHeaders response (as if they asked for it for this block) +message NewBlock { + oneof maybe_full { + starknet.common.BlockID id = 1; + BlockHeadersResponse header = 2; + } +} + + +message BlockHeadersRequest { + starknet.common.Iteration iteration = 1; +} + +// Responses are sent ordered by the order given in the request. +message BlockHeadersResponse { + oneof header_message { + SignedBlockHeader header = 1; + starknet.common.Fin fin = 2; // Fin is sent after the peer sent all the data or when it encountered a block that it doesn't have its header. + } +} diff --git a/crates/p2p_proto/proto/mempool.proto b/crates/p2p_proto/proto/mempool.proto deleted file mode 100644 index ffb7bafc59..0000000000 --- a/crates/p2p_proto/proto/mempool.proto +++ /dev/null @@ -1,32 +0,0 @@ -syntax = "proto3"; - -package starknet.mempool; - -import "common.proto"; -import "transaction.proto"; - -// Support also non-validating node that wants to know of the mempool (e.g. to estimate fee in case of first price) -// Result is PooledTransactions+ -message PooledTransactionsRequest -{ - message Known { - oneof known { - starknet.common.Hashes txs = 1; // for mempool of 2000 txs, this will be 64K. Can use Hash32 instead (8K)... - uint64 marker = 2; // since last returned marker. - } - } - optional Known known = 1; -} - -// Can be also a push, similar to NewBlock. So a full node that accepts a new transaction from a wallet -// can propagate it without being pulled -// nodes should track state diffs to know when txs have been included (the contract nonce increases) -message PolledTransactionsResponse { - optional uint64 marker = 1; // optional, if the peer supports that. - bool baseline = 2; // means treat all data as baseline, not diff (may be if 'known' was sent but the mempool was reset/reorged - - oneof responses { - starknet.transaction.Transactions pending = 3; // if 'known' is given, they will be only txs added after the known - starknet.common.Fin fin = 4; - } -} diff --git a/crates/p2p_proto/proto/receipt.proto b/crates/p2p_proto/proto/receipt.proto index 20b3ebd9e9..bf8d6f412a 100644 --- a/crates/p2p_proto/proto/receipt.proto +++ b/crates/p2p_proto/proto/receipt.proto @@ -1,9 +1,8 @@ syntax = "proto3"; +import "common.proto"; package starknet.receipt; -import "common.proto"; - message MessageToL1 { starknet.common.Felt252 from_address = 1; repeated starknet.common.Felt252 payload = 2; @@ -14,26 +13,17 @@ message EthereumAddress { bytes elements = 1; } -message MessageToL2 { - EthereumAddress from_address = 1; - repeated starknet.common.Felt252 payload = 2; - starknet.common.Felt252 to_address = 3; - starknet.common.Felt252 entry_point_selector = 4; - starknet.common.Felt252 nonce = 5; -} - message Receipt { message ExecutionResources { message BuiltinCounter { - uint32 bitwise = 1; - uint32 ecdsa = 2; - uint32 ec_op = 3; - uint32 pedersen = 4; - uint32 range_check = 5; - uint32 poseidon = 6; - uint32 keccak = 7; - uint32 output = 8; - uint32 segment_arena = 9; + uint32 bitwise = 1; + uint32 ecdsa = 2; + uint32 ec_op = 3; + uint32 pedersen = 4; + uint32 range_check = 5; + uint32 poseidon = 6; + uint32 keccak = 7; + uint32 output = 8; } BuiltinCounter builtins = 1; @@ -90,11 +80,10 @@ message Receipts { repeated Receipt items = 2; } +// Responses are sent ordered by the order given in the request. message ReceiptsResponse { - optional starknet.common.BlockID id = 1; // may not appear if Fin is sent to end the whole response - - oneof responses { - Receipts receipts = 2; - starknet.common.Fin fin = 3; - } + oneof receipt_message { + Receipt receipt = 1; + starknet.common.Fin fin = 2; // Fin is sent after the peer sent all the data or when it encountered a block that it doesn't have its receipts. + } } diff --git a/crates/p2p_proto/proto/snapshot.proto b/crates/p2p_proto/proto/snapshot.proto deleted file mode 100644 index 47e393d69d..0000000000 --- a/crates/p2p_proto/proto/snapshot.proto +++ /dev/null @@ -1,111 +0,0 @@ -syntax = "proto3"; - -package starknet.snapshot; - -import "common.proto"; -import "state.proto"; - -message PatriciaNode { - message Edge { - uint32 length = 1; - starknet.common.Felt252 path = 2; // as bits of left/right - starknet.common.Felt252 value = 3; - } - message Binary { - starknet.common.Felt252 left = 1; - starknet.common.Felt252 right = 2; - } - - oneof node { - Edge edge = 1; - Binary binary = 2; - } -} - -// non leaf nodes required to build the trie given the range (leaves) -message PatriciaRangeProof { - repeated PatriciaNode nodes = 1; -} - -// leafs of the contract state tree -message ContractState { - starknet.common.Address address = 1; // the key - starknet.common.Hash class = 2; - starknet.common.Hash storage = 3; // patricia - uint64 nonce = 4; -} - -// request a range from the contract state tree that matches the given root (block) -// starts at 'start' and ends no more than 'end'. -// the result is (ContractRange+, PatriciaRangeProof)* -message ContractRangeRequest { - uint32 domain = 1; // volition - starknet.common.Hash state_root = 2; - starknet.common.Address start = 3; - starknet.common.Address end = 4; - uint32 chunks_per_proof = 5; // how many ContractRange items to send before sending a proof -} - -// stream of leaves in the contracts tree -message ContractRange { - repeated ContractState state = 1; -} - -message ContractRangeResponse { - optional starknet.common.Hash root = 1; // may not appear if Fin is sent to end the whole response - optional starknet.common.Hash contracts_root = 2; // may not appear if Fin is sent to end the whole response - optional starknet.common.Hash classes_root = 3; // may not appear if Fin is sent to end the whole response - oneof responses { - ContractRange range = 4; - starknet.common.Fin fin = 5; - } -} - -// duplicate of GetContractRange. Can introduce a 'type' instead. -// result is (Classes+, PatriciaRangeProof)* -message ClassRangeRequest { - starknet.common.Hash root = 1; - starknet.common.Hash start = 2; - starknet.common.Hash end = 3; - uint32 chunks_per_proof = 4; -} - -message ClassRangeResponse { - optional starknet.common.Hash root = 1; // may not appear if Fin is sent to end the whole response - optional starknet.common.Hash contracts_root = 2; // may not appear if Fin is sent to end the whole response - optional starknet.common.Hash classes_root = 3; // may not appear if Fin is sent to end the whole response - oneof responses { - starknet.state.Classes classes = 4; - starknet.common.Fin fin = 5; - } -} - -// A position in some contract's state tree is identified by the state tree's root and the key in it -message StorageLeafQuery { - starknet.common.Hash contract_storage_root = 1; - starknet.common.Felt252 key = 2; -} - -message StorageRangeQuery { - StorageLeafQuery start = 1; - StorageLeafQuery end = 2; -} - -// result is (ContractStorageRange+, PatriciaRangeProof)* -message ContractStorageRequest { - uint32 domain = 1; // volition - starknet.common.Hash state_root = 2; - repeated StorageRangeQuery query = 3; -} - -message ContractStorage { - repeated starknet.state.ContractStoredValue keyValue = 2; -} - -message ContractStorageResponse { - optional starknet.common.Hash state_root = 1; // may not appear if Fin is sent to end the whole response - oneof responses { - ContractStorage storage = 2; - starknet.common.Fin fin = 3; - } -} diff --git a/crates/p2p_proto/proto/state.proto b/crates/p2p_proto/proto/state.proto index 744c376d8e..3dbcf969ca 100644 --- a/crates/p2p_proto/proto/state.proto +++ b/crates/p2p_proto/proto/state.proto @@ -1,73 +1,32 @@ syntax = "proto3"; +import "common.proto"; package starknet.state; -import "common.proto"; - // optimized for flat storage, not through a trie (not sharing key prefixes) message ContractStoredValue { - starknet.common.Felt252 key = 1; + starknet.common.Felt252 key = 1; starknet.common.Felt252 value = 2; } -message StateDiff -{ - // a bit more efficient than the state sync separation - message ContractDiff { - starknet.common.Address address = 1; - optional starknet.common.Felt252 nonce = 2; - optional starknet.common.Felt252 class_hash = 3; // can change for replace_class or new contract - repeated ContractStoredValue values = 4; - } - - uint32 domain = 1; // volition state domain - repeated ContractDiff contract_diffs = 2; -} - -message EntryPoint { - starknet.common.Felt252 selector = 1; - starknet.common.Felt252 offset = 2; +message ContractDiff { + starknet.common.Address address = 1; + optional starknet.common.Felt252 nonce = 2; // Present only if the nonce was updated + optional starknet.common.Felt252 class_hash = 3; // Present only if the contract was deployed or replaced in this block. + optional bool is_replaced = 4; // Present only if the contract was deployed or replaced, in order to determine whether the contract was deployed or replaced. + repeated ContractStoredValue values = 5; + uint32 domain = 6; // volition state domain } -message Cairo0Class { - bytes abi = 1; - repeated EntryPoint externals = 2; - repeated EntryPoint l1_handlers = 3; - repeated EntryPoint constructors = 4; - bytes program = 5; +message StateDiffsRequest { + starknet.common.Iteration iteration = 1; } -message SierraEntryPoint { - uint64 index = 1; - starknet.common.Felt252 selector = 2; -} - -message Cairo1EntryPoints { - repeated SierraEntryPoint externals = 1; - repeated SierraEntryPoint l1_handlers = 2; - repeated SierraEntryPoint constructors = 3; -} - -message Cairo1Class { - bytes abi = 1; - Cairo1EntryPoints entry_points = 2; - repeated starknet.common.Felt252 program = 3; - string contract_class_version = 4; - bytes compiled = 5; -} - -// is it better to separate the definition from the hashes? (will need to repeate the hashes -// for the definitions stream) -// or, make the definitions optional? maybe it is enough to know only that a class exists, not its definition -// which may be fetched lazily later. -message Class { - oneof class { - Cairo0Class cairo0 = 1; - Cairo1Class cairo1 = 2; +// Responses are sent ordered by the order given in the request. +message StateDiffsResponse { + // All of the messages related to a block need to be sent before a message from the next block is sent. + oneof state_diff_message { + ContractDiff contract_diff = 1; // Multiple contract diffs for the same contract may appear continuously if the diff is too large. + starknet.common.Fin fin = 2; // Fin is sent after the peer sent all the data or when it encountered a block that it doesn't have its state diff. } } - -message Classes { - uint32 domain = 1; - repeated Class classes = 2; -} diff --git a/crates/p2p_proto/proto/transaction.proto b/crates/p2p_proto/proto/transaction.proto index c162b57730..97e281ecd9 100644 --- a/crates/p2p_proto/proto/transaction.proto +++ b/crates/p2p_proto/proto/transaction.proto @@ -1,9 +1,8 @@ syntax = "proto3"; +import "common.proto"; package starknet.transaction; -import "common.proto"; - message ResourceLimits { starknet.common.Felt252 max_amount = 1; starknet.common.Felt252 max_price_per_unit = 2; @@ -51,12 +50,12 @@ message Transaction starknet.common.Hash class_hash = 3; starknet.common.Felt252 nonce = 4; starknet.common.Felt252 compiled_class_hash = 5; - ResourceBounds resource_bounds = 6; + ResourceBounds resource_bounds = 6; starknet.common.Felt252 tip = 7; starknet.common.Address paymaster_data = 8; starknet.common.Address account_deployment_data = 9; - string nonce_domain = 10; - string fee_domain = 11; + string nonce_domain = 10; // rename to nonce_data_availability_mode ? + string fee_domain = 11; // rename to fee_data_availability_mode ? } message Deploy { @@ -64,29 +63,29 @@ message Transaction starknet.common.Felt252 address_salt = 2; repeated starknet.common.Felt252 calldata = 3; uint32 version = 4; - starknet.common.Address address = 5; } message DeployAccountV1 { - starknet.common.Felt252 max_fee = 1; - AccountSignature signature = 2; - starknet.common.Hash class_hash = 3; - starknet.common.Felt252 nonce = 4; - starknet.common.Felt252 address_salt = 5; - repeated starknet.common.Felt252 constructor_calldata = 6; + starknet.common.Felt252 max_fee = 1; + AccountSignature signature = 2; + starknet.common.Hash class_hash = 3; + starknet.common.Felt252 nonce = 4; + starknet.common.Felt252 address_salt = 5; + repeated starknet.common.Felt252 calldata = 6; } + // see https://external.integration.starknet.io/feeder_gateway/get_transaction?transactionHash=0x29fd7881f14380842414cdfdd8d6c0b1f2174f8916edcfeb1ede1eb26ac3ef0 message DeployAccountV3 { - AccountSignature signature = 1; - starknet.common.Hash class_hash = 2; - starknet.common.Felt252 nonce = 3; - starknet.common.Felt252 address_salt = 4; - repeated starknet.common.Felt252 calldata = 5; - ResourceBounds resource_bounds = 6; - starknet.common.Felt252 tip = 7; - starknet.common.Address paymaster_data = 8; - string nonce_domain = 9; // rename to nonce_data_availability_mode ? - string fee_domain = 10; // rename to fee_data_availability_mode ? + AccountSignature signature = 1; + starknet.common.Hash class_hash = 2; + starknet.common.Felt252 nonce = 3; + starknet.common.Felt252 address_salt = 4; + repeated starknet.common.Felt252 calldata = 5; + ResourceBounds resource_bounds = 6; + starknet.common.Felt252 tip = 7; + starknet.common.Address paymaster_data = 8; + string nonce_domain = 9; // rename to nonce_data_availability_mode ? + string fee_domain = 10; // rename to fee_data_availability_mode ? } message InvokeV0 { @@ -120,25 +119,26 @@ message Transaction } message L1HandlerV0 { - starknet.common.Felt252 nonce = 1; - starknet.common.Address address = 2; - starknet.common.Felt252 entry_point_selector = 3; - repeated starknet.common.Felt252 calldata = 4; + starknet.common.Felt252 nonce = 1; + starknet.common.Address address = 2; + starknet.common.Felt252 entry_point_selector = 3; + repeated starknet.common.Felt252 calldata = 4; } oneof txn { - DeclareV0 declare_v0 = 1; - DeclareV1 declare_v1 = 2; - DeclareV2 declare_v2 = 3; - DeclareV3 declare_v3 = 4; - Deploy deploy = 5; + DeclareV0 declare_v0 = 1; + DeclareV1 declare_v1 = 2; + DeclareV2 declare_v2 = 3; + DeclareV3 declare_v3 = 4; + Deploy deploy = 5; DeployAccountV1 deploy_account_v1 = 6; DeployAccountV3 deploy_account_v3 = 7; - InvokeV0 invoke_v0 = 8; - InvokeV1 invoke_v1 = 9; - InvokeV3 invoke_v3 = 10; - L1HandlerV0 l1_handler = 11; + InvokeV0 invoke_v0 = 8; + InvokeV1 invoke_v1 = 9; + InvokeV3 invoke_v3 = 10; + L1HandlerV0 l1_handler = 11; } + starknet.common.Hash transaction_hash = 12; } // TBD: can support a flag to return tx hashes only, good for standalone mempool to remove them, @@ -147,16 +147,10 @@ message TransactionsRequest { starknet.common.Iteration iteration = 1; } -// can be several in a single reply -message Transactions { - repeated Transaction items = 1; -} - +// Responses are sent ordered by the order given in the request. message TransactionsResponse { - optional starknet.common.BlockID id = 1; // may not appear if Fin is sent to end the whole response - - oneof responses { - Transactions transactions = 2; - starknet.common.Fin fin = 3; + oneof transaction_message { + Transaction transaction = 1; + starknet.common.Fin fin = 2; // Fin is sent after the peer sent all the data or when it encountered a block that it doesn't have its transactions. } } diff --git a/crates/p2p_proto/src/block.rs b/crates/p2p_proto/src/block.rs deleted file mode 100644 index 53ee2941b3..0000000000 --- a/crates/p2p_proto/src/block.rs +++ /dev/null @@ -1,367 +0,0 @@ -use crate::common::{Address, BlockId, ConsensusSignature, Fin, Hash, Iteration, Merkle, Patricia}; -use crate::state::{Classes, StateDiff}; -use crate::{proto, ToProtobuf, TryFromProtobuf}; -use fake::{Dummy, Fake, Faker}; -use std::fmt::Display; -use std::time::{Duration, SystemTime}; - -#[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf, Dummy)] -#[protobuf(name = "crate::proto::block::Signatures")] -pub struct Signatures { - pub block: BlockId, - pub signatures: Vec, -} - -#[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf)] -#[protobuf(name = "crate::proto::block::BlockHeader")] -pub struct BlockHeader { - #[rename(parent_header)] - pub parent_hash: Hash, - pub number: u64, - pub time: SystemTime, - pub sequencer_address: Address, - pub state_diffs: Merkle, - pub state: Patricia, - pub proof_fact: Hash, - pub transactions: Merkle, - pub events: Merkle, - pub receipts: Merkle, - pub hash: Hash, - pub gas_price: Vec, - pub starknet_version: String, - #[optional] - pub state_commitment: Option, -} - -#[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf, Dummy)] -#[protobuf(name = "crate::proto::block::BlockProof")] -pub struct BlockProof { - pub proof: Vec, -} - -#[derive(Debug, Clone, PartialEq, Eq, Dummy)] -pub enum NewBlock { - Id(BlockId), - Header(BlockHeadersResponse), - Body(BlockBodiesResponse), -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf, Dummy)] -#[protobuf(name = "crate::proto::block::BlockHeadersRequest")] -pub struct BlockHeadersRequest { - pub iteration: Iteration, -} - -#[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf, Dummy)] -#[protobuf(name = "crate::proto::block::BlockHeadersResponse")] -pub struct BlockHeadersResponse { - #[rename(part)] - pub parts: Vec, -} - -#[derive(Debug, Clone, PartialEq, Eq, Dummy)] -pub enum BlockHeadersResponsePart { - Header(Box), - Signatures(Signatures), - Fin(Fin), -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf, Dummy)] -#[protobuf(name = "crate::proto::block::BlockBodiesRequest -")] -pub struct BlockBodiesRequest { - pub iteration: Iteration, -} - -#[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf, Dummy)] -#[protobuf(name = "crate::proto::block::BlockBodiesResponse")] -pub struct BlockBodiesResponse { - #[optional] - pub id: Option, - pub body_message: BlockBodyMessage, -} - -#[derive(Debug, Clone, PartialEq, Eq, Dummy)] -pub enum BlockBodyMessage { - Diff(StateDiff), - Classes(Classes), - Proof(BlockProof), - Fin(Fin), -} - -impl ToProtobuf<::prost_types::Timestamp> for SystemTime { - fn to_protobuf(self) -> ::prost_types::Timestamp { - self.into() - } -} - -impl TryFromProtobuf<::prost_types::Timestamp> for SystemTime { - fn try_from_protobuf( - input: ::prost_types::Timestamp, - field_name: &'static str, - ) -> Result { - let secs = input.seconds.try_into().map_err(|e| { - std::io::Error::new( - std::io::ErrorKind::InvalidData, - format!("Invalid secs in Timestamp {field_name}: {e}"), - ) - })?; - let nanos = input.nanos.try_into().map_err(|e| { - std::io::Error::new( - std::io::ErrorKind::InvalidData, - format!("Invalid nanos in Timestamp {field_name}: {e}"), - ) - })?; - - Self::UNIX_EPOCH - .checked_add(Duration::new(secs, nanos)) - .ok_or(std::io::Error::new( - std::io::ErrorKind::InvalidData, - format!("Invalid Timestamp {field_name}"), - )) - } -} - -impl Dummy for BlockHeader { - fn dummy_with_rng(_: &T, rng: &mut R) -> Self { - Self { - time: SystemTime::now(), - parent_hash: Faker.fake_with_rng(rng), - number: Faker.fake_with_rng(rng), - sequencer_address: Faker.fake_with_rng(rng), - state_diffs: Faker.fake_with_rng(rng), - state: Faker.fake_with_rng(rng), - proof_fact: Faker.fake_with_rng(rng), - transactions: Faker.fake_with_rng(rng), - events: Faker.fake_with_rng(rng), - receipts: Faker.fake_with_rng(rng), - hash: Faker.fake_with_rng(rng), - gas_price: Faker.fake_with_rng(rng), - starknet_version: Faker.fake_with_rng(rng), - state_commitment: Faker.fake_with_rng(rng), - } - } -} - -impl ToProtobuf for NewBlock { - fn to_protobuf(self) -> crate::proto::block::NewBlock { - use crate::proto::block::new_block::MaybeFull::{Body, Header, Id}; - crate::proto::block::NewBlock { - maybe_full: Some(match self { - Self::Id(block_number) => Id(block_number.to_protobuf()), - Self::Header(header) => Header(header.to_protobuf()), - Self::Body(body) => Body(body.to_protobuf()), - }), - } - } -} - -impl TryFromProtobuf for NewBlock { - fn try_from_protobuf( - input: crate::proto::block::NewBlock, - field_name: &'static str, - ) -> Result { - use crate::proto::block::new_block::MaybeFull::{Body, Header, Id}; - let x = input.maybe_full.ok_or_else(|| { - std::io::Error::new( - std::io::ErrorKind::InvalidData, - format!("Failed to parse {field_name}: missing maybe_full field"), - ) - })?; - - Ok(match x { - Id(i) => Self::Id(TryFromProtobuf::try_from_protobuf(i, field_name)?), - Header(h) => Self::Header(TryFromProtobuf::try_from_protobuf(h, field_name)?), - Body(b) => Self::Body(TryFromProtobuf::try_from_protobuf(b, field_name)?), - }) - } -} - -impl BlockHeadersResponse { - pub fn into_fin(self) -> Option { - if self.parts.len() == 1 { - let mut parts = self.parts; - parts.pop().unwrap().into_fin() - } else { - None - } - } -} - -impl Display for BlockHeadersResponse { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "BlockHeadersResponse[")?; - match self.parts.len().cmp(&1) { - std::cmp::Ordering::Less => {} - std::cmp::Ordering::Equal => write!(f, "{}", self.parts[0])?, - std::cmp::Ordering::Greater => write!( - f, - "{},..,{}", - self.parts.first().unwrap(), - self.parts.last().unwrap() - )?, - } - write!(f, "]") - } -} - -impl From for BlockHeadersResponsePart { - fn from(fin: Fin) -> Self { - Self::Fin(fin) - } -} - -impl BlockHeadersResponsePart { - pub fn into_header(self) -> Option { - match self { - Self::Header(header) => Some(*header), - _ => None, - } - } - - pub fn into_signatures(self) -> Option { - match self { - Self::Signatures(signatures) => Some(signatures), - _ => None, - } - } - - pub fn into_fin(self) -> Option { - match self { - Self::Fin(fin) => Some(fin), - _ => None, - } - } - - pub fn id(&self) -> Option { - match self { - Self::Header(header) => Some(BlockId { - hash: header.hash, - number: header.number, - }), - Self::Signatures(signatures) => Some(signatures.block), - Self::Fin(_) => None, - } - } -} - -impl Display for BlockHeadersResponsePart { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Header(header) => write!(f, "({},{})", header.number, header.hash.0), - Self::Signatures(signatures) => write!(f, "{}", signatures.block), - Self::Fin(_) => write!(f, "Fin"), - } - } -} - -impl BlockBodyMessage { - pub fn into_state_diff(self) -> Option { - match self { - Self::Diff(diff) => Some(diff), - _ => None, - } - } - - pub fn into_classes(self) -> Option { - match self { - Self::Classes(classes) => Some(classes), - _ => None, - } - } - - pub fn into_proof(self) -> Option { - match self { - Self::Proof(proof) => Some(proof), - _ => None, - } - } - - pub fn into_fin(self) -> Option { - match self { - Self::Fin(fin) => Some(fin), - _ => None, - } - } -} - -impl ToProtobuf for BlockHeadersResponsePart { - fn to_protobuf(self) -> proto::block::BlockHeadersResponsePart { - use proto::block::block_headers_response_part::HeaderMessage::{Fin, Header, Signatures}; - proto::block::BlockHeadersResponsePart { - header_message: Some(match self { - Self::Header(header) => Header(header.to_protobuf()), - Self::Signatures(signatures) => Signatures(signatures.to_protobuf()), - Self::Fin(fin) => Fin(fin.to_protobuf()), - }), - } - } -} - -impl TryFromProtobuf for BlockHeadersResponsePart { - fn try_from_protobuf( - input: proto::block::BlockHeadersResponsePart, - field_name: &'static str, - ) -> Result { - use proto::block::block_headers_response_part::HeaderMessage::{Fin, Header, Signatures}; - Ok(match input.header_message { - Some(Header(header)) => Self::Header(Box::new(BlockHeader::try_from_protobuf( - header, field_name, - )?)), - Some(Signatures(signatures)) => { - Self::Signatures(self::Signatures::try_from_protobuf(signatures, field_name)?) - } - Some(Fin(fin)) => Self::Fin(self::Fin::try_from_protobuf(fin, field_name)?), - None => { - return Err(std::io::Error::new( - std::io::ErrorKind::InvalidData, - format!("Failed to parse {field_name}: missing header_message field"), - )) - } - }) - } -} - -impl From for BlockBodiesResponse { - fn from(fin: Fin) -> Self { - Self { - id: None, - body_message: BlockBodyMessage::Fin(fin), - } - } -} - -impl BlockBodiesResponse { - pub fn into_fin(self) -> Option { - self.body_message.into_fin() - } -} - -impl ToProtobuf for BlockBodyMessage { - fn to_protobuf(self) -> proto::block::block_bodies_response::BodyMessage { - use proto::block::block_bodies_response::BodyMessage::{Classes, Diff, Fin, Proof}; - match self { - Self::Diff(header) => Diff(header.to_protobuf()), - Self::Classes(signatures) => Classes(signatures.to_protobuf()), - Self::Proof(proof) => Proof(proof.to_protobuf()), - Self::Fin(fin) => Fin(fin.to_protobuf()), - } - } -} - -impl TryFromProtobuf for BlockBodyMessage { - fn try_from_protobuf( - input: proto::block::block_bodies_response::BodyMessage, - field_name: &'static str, - ) -> Result { - use proto::block::block_bodies_response::BodyMessage::{Classes, Diff, Fin, Proof}; - Ok(match input { - Diff(header) => Self::Diff(StateDiff::try_from_protobuf(header, field_name)?), - Classes(signatures) => { - Self::Classes(self::Classes::try_from_protobuf(signatures, field_name)?) - } - Proof(proof) => Self::Proof(BlockProof::try_from_protobuf(proof, field_name)?), - Fin(fin) => Self::Fin(self::Fin::try_from_protobuf(fin, field_name)?), - }) - } -} diff --git a/crates/p2p_proto/src/class.rs b/crates/p2p_proto/src/class.rs new file mode 100644 index 0000000000..a7c24aa109 --- /dev/null +++ b/crates/p2p_proto/src/class.rs @@ -0,0 +1,223 @@ +use std::fmt::Debug; + +use crate::common::{Hash, Iteration}; +use crate::{proto, proto_field, ToProtobuf, TryFromProtobuf}; +use fake::{Dummy, Fake, Faker}; +use pathfinder_crypto::Felt; + +#[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf, Dummy, PartialOrd, Ord)] +#[protobuf(name = "crate::proto::class::EntryPoint")] +pub struct EntryPoint { + pub selector: Felt, + pub offset: Felt, +} + +#[derive(Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf, PartialOrd, Ord)] +#[protobuf(name = "crate::proto::class::Cairo0Class")] +pub struct Cairo0Class { + pub abi: Vec, + pub externals: Vec, + pub l1_handlers: Vec, + pub constructors: Vec, + pub program: Vec, +} + +impl Debug for Cairo0Class { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Cairo0Class") + .field( + "abi", + &std::str::from_utf8(&self.abi) + .unwrap_or(&format!("invalid utf8: {:#x?}", &self.abi)), + ) + .field("externals", &self.externals) + .field("l1_handlers", &self.l1_handlers) + .field("constructors", &self.constructors) + .field( + "program", + &std::str::from_utf8(&self.program) + .unwrap_or(&format!("invalid utf8: {:#x?}", &self.program)), + ) + .finish() + } +} + +impl Dummy for Cairo0Class { + fn dummy_with_rng(_: &T, rng: &mut R) -> Self { + Self { + abi: Faker.fake_with_rng::(rng).into_bytes(), + externals: Faker.fake_with_rng(rng), + l1_handlers: Faker.fake_with_rng(rng), + constructors: Faker.fake_with_rng(rng), + program: serde_json::to_vec(&serde_json::Value::Object(Faker.fake_with_rng(rng))) + .unwrap(), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf, Dummy, PartialOrd, Ord)] +#[protobuf(name = "crate::proto::class::SierraEntryPoint")] +pub struct SierraEntryPoint { + pub index: u64, + pub selector: Felt, +} + +#[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf, Dummy, PartialOrd, Ord)] +#[protobuf(name = "crate::proto::class::Cairo1EntryPoints")] +pub struct Cairo1EntryPoints { + pub externals: Vec, + pub l1_handlers: Vec, + pub constructors: Vec, +} + +#[derive(Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf, PartialOrd, Ord)] +#[protobuf(name = "crate::proto::class::Cairo1Class")] +pub struct Cairo1Class { + pub abi: Vec, + pub entry_points: Cairo1EntryPoints, + pub program: Vec, + pub contract_class_version: String, + pub compiled: Vec, +} + +impl Debug for Cairo1Class { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Cairo1Class") + .field( + "abi", + &std::str::from_utf8(&self.abi) + .unwrap_or(&format!("invalid utf8: {:#x?}", &self.abi)), + ) + .field("entry_points", &self.entry_points) + .field("program", &self.program) + .field("contract_class_version", &self.contract_class_version) + .field( + "compiled", + &std::str::from_utf8(&self.abi) + .unwrap_or(&format!("invalid utf8: {:#x?}", &self.compiled)), + ) + .finish() + } +} + +impl Dummy for Cairo1Class { + fn dummy_with_rng(_: &T, rng: &mut R) -> Self { + Self { + abi: Faker.fake_with_rng::(rng).into_bytes(), + entry_points: Faker.fake_with_rng(rng), + program: Faker.fake_with_rng(rng), + contract_class_version: "0.1.0".into(), + compiled: Faker.fake_with_rng(rng), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Dummy)] +pub enum Class { + Cairo0 { + class: Cairo0Class, + domain: u32, + class_hash: Hash, + }, + Cairo1 { + class: Cairo1Class, + domain: u32, + class_hash: Hash, + }, +} + +impl ToProtobuf for Class { + fn to_protobuf(self) -> proto::class::Class { + use proto::class::class::Class::{Cairo0, Cairo1}; + use proto::class::Class; + match self { + Self::Cairo0 { + class, + domain, + class_hash, + } => Class { + class: Some(Cairo0(class.to_protobuf())), + domain, + class_hash: Some(class_hash.to_protobuf()), + }, + Self::Cairo1 { + class, + domain, + class_hash, + } => Class { + class: Some(Cairo1(class.to_protobuf())), + domain, + class_hash: Some(class_hash.to_protobuf()), + }, + } + } +} + +impl TryFromProtobuf for Class { + fn try_from_protobuf( + input: proto::class::Class, + field_name: &'static str, + ) -> Result { + use proto::class::class::Class::{Cairo0, Cairo1}; + let class_hash = TryFromProtobuf::try_from_protobuf( + proto_field(input.class_hash, field_name)?, + field_name, + )?; + Ok(match proto_field(input.class, field_name)? { + Cairo0(c) => Self::Cairo0 { + class: Cairo0Class::try_from_protobuf(c, field_name)?, + domain: input.domain, + class_hash, + }, + Cairo1(c) => Self::Cairo1 { + class: Cairo1Class::try_from_protobuf(c, field_name)?, + domain: input.domain, + class_hash, + }, + }) + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf, Dummy)] +#[protobuf(name = "crate::proto::class::ClassesRequest")] +pub struct ClassesRequest { + pub iteration: Iteration, +} + +#[allow(clippy::large_enum_variant)] +#[derive(Debug, Default, Clone, PartialEq, Eq, Dummy)] +pub enum ClassesResponse { + Class(Class), + #[default] + Fin, +} + +impl ToProtobuf for ClassesResponse { + fn to_protobuf(self) -> proto::class::ClassesResponse { + use proto::class::classes_response::ClassMessage::{Class, Fin}; + use proto::class::ClassesResponse; + match self { + Self::Class(class) => ClassesResponse { + class_message: Some(Class(class.to_protobuf())), + }, + Self::Fin => ClassesResponse { + class_message: Some(Fin(proto::common::Fin {})), + }, + } + } +} + +impl TryFromProtobuf for ClassesResponse { + fn try_from_protobuf( + input: proto::class::ClassesResponse, + field_name: &'static str, + ) -> Result { + use proto::class::classes_response::ClassMessage::{Class, Fin}; + match proto_field(input.class_message, field_name)? { + Class(c) => Ok(Self::Class(TryFromProtobuf::try_from_protobuf( + c, field_name, + )?)), + Fin(_) => Ok(Self::Fin), + } + } +} diff --git a/crates/p2p_proto/src/common.rs b/crates/p2p_proto/src/common.rs index 1fed2e2643..b37f79d173 100644 --- a/crates/p2p_proto/src/common.rs +++ b/crates/p2p_proto/src/common.rs @@ -73,21 +73,6 @@ pub enum Direction { Backward, } -#[derive(Debug, Copy, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf, Dummy)] -#[protobuf(name = "crate::proto::common::Fin")] -pub struct Fin { - #[optional] - pub error: Option, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Dummy)] -pub enum Error { - Busy, - TooMuch, - Unknown, - Pruned, -} - impl ToProtobuf for Felt { fn to_protobuf(self) -> proto::common::Felt252 { proto::common::Felt252 { @@ -288,45 +273,3 @@ impl TryFromProtobuf for Direction { }) } } - -impl Fin { - pub fn ok() -> Self { - Self { error: None } - } - - pub fn too_much() -> Self { - Self { - error: Some(Error::TooMuch), - } - } - - pub fn unknown() -> Self { - Self { - error: Some(Error::Unknown), - } - } -} - -impl ToProtobuf for Error { - fn to_protobuf(self) -> i32 { - use proto::common::fin::Error::{Busy, Pruned, TooMuch, Unknown}; - match self { - Error::Busy => Busy as i32, - Error::TooMuch => TooMuch as i32, - Error::Unknown => Unknown as i32, - Error::Pruned => Pruned as i32, - } - } -} - -impl TryFromProtobuf for Error { - fn try_from_protobuf(input: i32, _: &'static str) -> Result { - use proto::common::fin::Error::{Busy, Pruned, TooMuch, Unknown}; - Ok(match TryFrom::try_from(input)? { - Busy => Error::Busy, - TooMuch => Error::TooMuch, - Unknown => Error::Unknown, - Pruned => Error::Pruned, - }) - } -} diff --git a/crates/p2p_proto/src/consensus.rs b/crates/p2p_proto/src/consensus.rs deleted file mode 100644 index 167e83e087..0000000000 --- a/crates/p2p_proto/src/consensus.rs +++ /dev/null @@ -1,81 +0,0 @@ -use crate::common::{Address, ConsensusSignature, Hash}; -use crate::state::StateDiff; -use crate::transaction::Transactions; -use crate::{proto, ToProtobuf, TryFromProtobuf}; -use fake::{Dummy, Fake, Faker}; -use std::time::SystemTime; - -#[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf)] -#[protobuf(name = "crate::proto::consensus::Proposal")] -pub struct Proposal { - block_number: u64, - round: u32, - pol: u32, - block_header_hash: Hash, - timestamp: SystemTime, - signature: ConsensusSignature, -} - -#[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf, Dummy)] -#[protobuf(name = "crate::proto::consensus::Vote")] -pub struct Vote { - proposal: Proposal, - validator_address: Address, - validator_index: i32, - signature: ConsensusSignature, -} - -#[derive(Debug, Clone, PartialEq, Eq, Dummy)] -pub enum CreateBlock { - Transactions(Transactions), - StateDiff(StateDiff), - Proposal(Proposal), -} - -impl Dummy for Proposal { - fn dummy_with_rng(_: &T, rng: &mut R) -> Self { - Proposal { - block_number: Faker.fake_with_rng(rng), - round: Faker.fake_with_rng(rng), - pol: Faker.fake_with_rng(rng), - block_header_hash: Faker.fake_with_rng(rng), - timestamp: SystemTime::now(), - signature: Faker.fake_with_rng(rng), - } - } -} - -impl ToProtobuf for CreateBlock { - fn to_protobuf(self) -> proto::consensus::CreateBlock { - use proto::consensus::create_block::Messages::{Proposal, StateDiff, Transactions}; - proto::consensus::CreateBlock { - messages: Some(match self { - Self::Transactions(t) => Transactions(t.to_protobuf()), - Self::StateDiff(s) => StateDiff(s.to_protobuf()), - Self::Proposal(p) => Proposal(p.to_protobuf()), - }), - } - } -} - -impl TryFromProtobuf for CreateBlock { - fn try_from_protobuf( - input: proto::consensus::CreateBlock, - field_name: &'static str, - ) -> Result { - use proto::consensus::create_block::Messages::{Proposal, StateDiff, Transactions}; - let messages = input.messages.ok_or_else(|| { - std::io::Error::new( - std::io::ErrorKind::InvalidData, - format!("{}: missing field `messages`", field_name), - ) - })?; - Ok(match messages { - Transactions(t) => { - Self::Transactions(TryFromProtobuf::try_from_protobuf(t, field_name)?) - } - StateDiff(s) => Self::StateDiff(TryFromProtobuf::try_from_protobuf(s, field_name)?), - Proposal(p) => Self::Proposal(TryFromProtobuf::try_from_protobuf(p, field_name)?), - }) - } -} diff --git a/crates/p2p_proto/src/consts.rs b/crates/p2p_proto/src/consts.rs index 0e0d517dbb..67a81f57b5 100644 --- a/crates/p2p_proto/src/consts.rs +++ b/crates/p2p_proto/src/consts.rs @@ -1,85 +1,4 @@ /// Constants that allow us to estimate the maximum payload of certain types of messages /// Maximum size of an encoded protobuf message in bytes +// FIXME: class related responses are limited to 4MiB, others 1MiB pub const MESSAGE_SIZE_LIMIT: usize = 20 * 1024 * 1024; -/// Upper bound -pub const ENCODED_HEADER_SIZE: usize = 483; -pub const HEADERS_MESSAGE_OVERHEAD: usize = 1; -/// Lower bound -pub const MAX_HEADERS_PER_MESSAGE: usize = - (MESSAGE_SIZE_LIMIT - HEADERS_MESSAGE_OVERHEAD + ENCODED_HEADER_SIZE) / ENCODED_HEADER_SIZE; - -#[cfg(test)] -mod tests { - use super::*; - use crate::proto::block::{ - block_headers_response_part::HeaderMessage, BlockHeader, BlockHeadersResponse, - BlockHeadersResponsePart, - }; - use crate::proto::common::{Address, Hash, Merkle, Patricia}; - use prost::Message; - use prost_types::Timestamp; - - impl Address { - pub fn full() -> Self { - Self { - elements: vec![0xFF; 32], - } - } - } - - impl Hash { - pub fn full() -> Self { - Self { - elements: vec![0xFF; 32], - } - } - } - - impl Merkle { - pub fn full() -> Self { - Self { - root: Some(Hash::full()), - n_leaves: u32::MAX, - } - } - } - - impl Patricia { - pub fn full() -> Self { - Self { - root: Some(Hash::full()), - height: u32::MAX, - } - } - } - - #[test] - fn check_headers_message_size_upper_bound() { - let part = BlockHeadersResponsePart { - header_message: Some(HeaderMessage::Header(BlockHeader { - parent_header: Some(Hash::full()), - number: u64::MAX, - time: Some(Timestamp { - seconds: i64::MAX, - nanos: i32::MAX, - }), - sequencer_address: Some(Address::full()), - state_diffs: Some(Merkle::full()), - state: Some(Patricia::full()), - proof_fact: Some(Hash::full()), - transactions: Some(Merkle::full()), - events: Some(Merkle::full()), - receipts: Some(Merkle::full()), - hash: Some(Hash::full()), - gas_price: vec![0xFF; 32], - starknet_version: "999.999.999".into(), - state_commitment: Some(Hash::full()), - })), - }; - - let len = BlockHeadersResponse { part: vec![part] } - .encode_length_delimited_to_vec() - .len(); - assert_eq!(len, HEADERS_MESSAGE_OVERHEAD + ENCODED_HEADER_SIZE); - } -} diff --git a/crates/p2p_proto/src/event.rs b/crates/p2p_proto/src/event.rs index 434197928e..6e6a4b4f59 100644 --- a/crates/p2p_proto/src/event.rs +++ b/crates/p2p_proto/src/event.rs @@ -1,8 +1,8 @@ use fake::Dummy; use pathfinder_crypto::Felt; -use crate::common::{BlockId, Fin, Hash, Iteration}; -use crate::{proto, ToProtobuf, TryFromProtobuf}; +use crate::common::{Hash, Iteration}; +use crate::{proto, proto_field, ToProtobuf, TryFromProtobuf}; #[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf, Dummy)] #[protobuf(name = "crate::proto::event::Event")] @@ -19,81 +19,34 @@ pub struct EventsRequest { pub iteration: Iteration, } -#[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf, Dummy)] -#[protobuf(name = "crate::proto::event::Events")] -pub struct Events { - pub items: Vec, -} - -#[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf, Dummy)] -#[protobuf(name = "crate::proto::event::EventsResponse")] -pub struct EventsResponse { - #[optional] - pub id: Option, - #[rename(responses)] - pub kind: EventsResponseKind, -} - -#[derive(Debug, Clone, PartialEq, Eq, Dummy)] -pub enum EventsResponseKind { - Events(Events), - Fin(Fin), +#[derive(Debug, Default, Clone, PartialEq, Eq, Dummy)] +pub enum EventsResponse { + Event(Event), + #[default] + Fin, } -impl From for EventsResponse { - fn from(fin: Fin) -> Self { - EventsResponse { - id: None, - kind: EventsResponseKind::Fin(fin), +impl ToProtobuf for EventsResponse { + fn to_protobuf(self) -> proto::event::EventsResponse { + use proto::event::events_response::EventMessage::{Event, Fin}; + proto::event::EventsResponse { + event_message: Some(match self { + Self::Event(event) => Event(event.to_protobuf()), + Self::Fin => Fin(proto::common::Fin {}), + }), } } } -impl EventsResponse { - pub fn into_fin(self) -> Option { - self.kind.into_fin() - } -} - -impl EventsResponseKind { - pub fn into_events(self) -> Option { - match self { - EventsResponseKind::Events(events) => Some(events), - EventsResponseKind::Fin(_) => None, - } - } - - pub fn into_fin(self) -> Option { - match self { - EventsResponseKind::Events(_) => None, - EventsResponseKind::Fin(fin) => Some(fin), - } - } -} - -impl ToProtobuf for EventsResponseKind { - fn to_protobuf(self) -> proto::event::events_response::Responses { - use proto::event::events_response::Responses::{Events, Fin}; - match self { - EventsResponseKind::Events(events) => Events(events.to_protobuf()), - EventsResponseKind::Fin(fin) => Fin(fin.to_protobuf()), - } - } -} - -impl TryFromProtobuf for EventsResponseKind { +impl TryFromProtobuf for EventsResponse { fn try_from_protobuf( - input: proto::event::events_response::Responses, + input: proto::event::EventsResponse, field_name: &'static str, ) -> Result { - use proto::event::events_response::Responses::{Events, Fin}; - match input { - Events(events) => Ok(EventsResponseKind::Events(self::Events::try_from_protobuf( - events, field_name, - )?)), - Fin(fin) => Ok(EventsResponseKind::Fin(self::Fin::try_from_protobuf( - fin, field_name, - )?)), - } + use proto::event::events_response::EventMessage::{Event, Fin}; + Ok(match proto_field(input.event_message, field_name)? { + Event(events) => Self::Event(TryFromProtobuf::try_from_protobuf(events, field_name)?), + Fin(_) => Self::Fin, + }) } } diff --git a/crates/p2p_proto/src/header.rs b/crates/p2p_proto/src/header.rs new file mode 100644 index 0000000000..515268a4bc --- /dev/null +++ b/crates/p2p_proto/src/header.rs @@ -0,0 +1,125 @@ +use crate::common::{Address, BlockId, ConsensusSignature, Hash, Iteration, Merkle, Patricia}; +use crate::{proto, proto_field, ToProtobuf, TryFromProtobuf}; +use fake::{Dummy, Fake, Faker}; +use pathfinder_crypto::Felt; +use std::time::SystemTime; + +#[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf)] +#[protobuf(name = "crate::proto::header::SignedBlockHeader")] +pub struct SignedBlockHeader { + pub block_hash: Hash, + pub parent_hash: Hash, + pub number: u64, + pub time: u64, + pub sequencer_address: Address, + pub state_diff_commitment: Hash, + pub state: Patricia, + pub transactions: Merkle, + pub events: Merkle, + pub receipts: Merkle, + pub protocol_version: String, + pub gas_price: Felt, + pub num_storage_diffs: u64, + pub num_nonce_updates: u64, + pub num_declared_classes: u64, + pub num_deployed_contracts: u64, + pub signatures: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, Dummy)] +pub enum NewBlock { + Id(BlockId), + Header(BlockHeadersResponse), +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf, Dummy)] +#[protobuf(name = "crate::proto::header::BlockHeadersRequest")] +pub struct BlockHeadersRequest { + pub iteration: Iteration, +} + +#[derive(Debug, Default, Clone, PartialEq, Eq, Dummy)] +pub enum BlockHeadersResponse { + Header(Box), + #[default] + Fin, +} + +impl Dummy for SignedBlockHeader { + fn dummy_with_rng(_: &T, rng: &mut R) -> Self { + Self { + block_hash: Faker.fake_with_rng(rng), + time: SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs(), + parent_hash: Faker.fake_with_rng(rng), + number: Faker.fake_with_rng(rng), + sequencer_address: Faker.fake_with_rng(rng), + state_diff_commitment: Faker.fake_with_rng(rng), + state: Faker.fake_with_rng(rng), + transactions: Faker.fake_with_rng(rng), + events: Faker.fake_with_rng(rng), + receipts: Faker.fake_with_rng(rng), + protocol_version: Faker.fake_with_rng(rng), + gas_price: Faker.fake_with_rng(rng), + num_storage_diffs: Faker.fake_with_rng(rng), + num_nonce_updates: Faker.fake_with_rng(rng), + num_declared_classes: Faker.fake_with_rng(rng), + num_deployed_contracts: Faker.fake_with_rng(rng), + signatures: Faker.fake_with_rng(rng), + } + } +} + +impl ToProtobuf for NewBlock { + fn to_protobuf(self) -> proto::header::NewBlock { + use proto::header::new_block::MaybeFull::{Header, Id}; + proto::header::NewBlock { + maybe_full: Some(match self { + Self::Id(block_number) => Id(block_number.to_protobuf()), + Self::Header(header) => Header(header.to_protobuf()), + }), + } + } +} + +impl TryFromProtobuf for NewBlock { + fn try_from_protobuf( + input: proto::header::NewBlock, + field_name: &'static str, + ) -> Result { + use proto::header::new_block::MaybeFull::{Header, Id}; + Ok(match proto_field(input.maybe_full, field_name)? { + Id(i) => Self::Id(TryFromProtobuf::try_from_protobuf(i, field_name)?), + Header(h) => Self::Header(TryFromProtobuf::try_from_protobuf(h, field_name)?), + }) + } +} + +impl ToProtobuf for BlockHeadersResponse { + fn to_protobuf(self) -> proto::header::BlockHeadersResponse { + use proto::header::block_headers_response::HeaderMessage::{Fin, Header}; + proto::header::BlockHeadersResponse { + header_message: Some(match self { + Self::Header(header) => Header(header.to_protobuf()), + Self::Fin => Fin(proto::common::Fin {}), + }), + } + } +} + +impl TryFromProtobuf for BlockHeadersResponse { + fn try_from_protobuf( + input: proto::header::BlockHeadersResponse, + field_name: &'static str, + ) -> Result { + use proto::header::block_headers_response::HeaderMessage::{Fin, Header}; + Ok(match proto_field(input.header_message, field_name)? { + Header(header) => Self::Header(Box::new(SignedBlockHeader::try_from_protobuf( + header, field_name, + )?)), + Fin(_) => Self::Fin, + }) + } +} diff --git a/crates/p2p_proto/src/lib.rs b/crates/p2p_proto/src/lib.rs index e1243fd179..d989ece45f 100644 --- a/crates/p2p_proto/src/lib.rs +++ b/crates/p2p_proto/src/lib.rs @@ -1,30 +1,26 @@ #[allow(clippy::module_inception)] pub mod proto { #[allow(clippy::large_enum_variant)] - pub mod block { - include!(concat!(env!("OUT_DIR"), "/starknet.block.rs")); + pub mod class { + include!(concat!(env!("OUT_DIR"), "/starknet.class.rs")); } pub mod common { include!(concat!(env!("OUT_DIR"), "/starknet.common.rs")); } - pub mod consensus { - include!(concat!(env!("OUT_DIR"), "/starknet.consensus.rs")); - } pub mod event { include!(concat!(env!("OUT_DIR"), "/starknet.event.rs")); } - pub mod mempool { - include!(concat!(env!("OUT_DIR"), "/starknet.mempool.rs")); + #[allow(clippy::large_enum_variant)] + pub mod header { + include!(concat!(env!("OUT_DIR"), "/starknet.header.rs")); } pub mod receipt { include!(concat!(env!("OUT_DIR"), "/starknet.receipt.rs")); } - pub mod snapshot { - include!(concat!(env!("OUT_DIR"), "/starknet.snapshot.rs")); - } pub mod state { include!(concat!(env!("OUT_DIR"), "/starknet.state.rs")); } + #[allow(clippy::large_enum_variant)] pub mod transaction { include!(concat!(env!("OUT_DIR"), "/starknet.transaction.rs")); } @@ -158,14 +154,21 @@ impl, U> TryFromProtobuf> for Vec { } } +fn proto_field(input: Option, field_name: &'static str) -> Result { + input.ok_or_else(|| { + std::io::Error::new( + std::io::ErrorKind::InvalidData, + format!("Missing field {field_name}"), + ) + }) +} + use p2p_proto_derive::*; -pub mod block; +pub mod class; pub mod common; -pub mod consensus; pub mod consts; pub mod event; -pub mod mempool; +pub mod header; pub mod receipt; -pub mod snapshot; pub mod state; pub mod transaction; diff --git a/crates/p2p_proto/src/mempool.rs b/crates/p2p_proto/src/mempool.rs deleted file mode 100644 index e720fccf1c..0000000000 --- a/crates/p2p_proto/src/mempool.rs +++ /dev/null @@ -1,92 +0,0 @@ -use crate::common::{Fin, Hashes}; -use crate::transaction::Transactions; -use crate::{proto, ToProtobuf, TryFromProtobuf}; - -#[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf)] -#[protobuf(name = "crate::proto::mempool::PooledTransactionsRequest")] -struct PooledTransactionsRequest { - #[optional] - known: Option, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Known { - Hashes(Hashes), - Marker(u64), -} - -#[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf)] -#[protobuf(name = "crate::proto::mempool::PolledTransactionsResponse")] -struct PolledTransactionsResponse { - #[optional] - marker: Option, - baseline: bool, - #[rename(responses)] - kind: PolledTransactionsResponseKind, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum PolledTransactionsResponseKind { - Pending(Transactions), - Fin(Fin), -} - -impl ToProtobuf for Known { - fn to_protobuf(self) -> proto::mempool::pooled_transactions_request::Known { - use proto::mempool::pooled_transactions_request::known::Known::{Marker, Txs}; - proto::mempool::pooled_transactions_request::Known { - known: Some(match self { - Known::Hashes(hashes) => Txs(hashes.to_protobuf()), - Known::Marker(marker) => Marker(marker), - }), - } - } -} - -impl TryFromProtobuf for Known { - fn try_from_protobuf( - input: proto::mempool::pooled_transactions_request::Known, - field_name: &'static str, - ) -> Result { - use proto::mempool::pooled_transactions_request::known::Known::{Marker, Txs}; - let inner = input.known.ok_or_else(|| { - std::io::Error::new( - std::io::ErrorKind::InvalidData, - format!("Missing field known in {field_name}"), - ) - })?; - Ok(match inner { - Txs(hashes) => Known::Hashes(Hashes::try_from_protobuf(hashes, field_name)?), - Marker(marker) => Known::Marker(marker), - }) - } -} - -impl ToProtobuf - for PolledTransactionsResponseKind -{ - fn to_protobuf(self) -> proto::mempool::polled_transactions_response::Responses { - use proto::mempool::polled_transactions_response::Responses::{Fin, Pending}; - match self { - Self::Pending(transactions) => Pending(transactions.to_protobuf()), - Self::Fin(fin) => Fin(fin.to_protobuf()), - } - } -} - -impl TryFromProtobuf - for PolledTransactionsResponseKind -{ - fn try_from_protobuf( - input: proto::mempool::polled_transactions_response::Responses, - field_name: &'static str, - ) -> Result { - use proto::mempool::polled_transactions_response::Responses::{Fin, Pending}; - Ok(match input { - Pending(transactions) => { - Self::Pending(Transactions::try_from_protobuf(transactions, field_name)?) - } - Fin(fin) => Self::Fin(self::Fin::try_from_protobuf(fin, field_name)?), - }) - } -} diff --git a/crates/p2p_proto/src/receipt.rs b/crates/p2p_proto/src/receipt.rs index 683b24ab7f..f1c5f98704 100644 --- a/crates/p2p_proto/src/receipt.rs +++ b/crates/p2p_proto/src/receipt.rs @@ -2,8 +2,8 @@ use fake::Dummy; use pathfinder_crypto::Felt; use primitive_types::H160; -use crate::common::{BlockId, Fin, Hash, Iteration}; -use crate::{proto, ToProtobuf, TryFromProtobuf}; +use crate::common::{Hash, Iteration}; +use crate::{proto, proto_field, ToProtobuf, TryFromProtobuf}; #[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf, Dummy)] #[protobuf(name = "crate::proto::receipt::MessageToL1")] @@ -17,16 +17,6 @@ pub struct MessageToL1 { #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct EthereumAddress(pub H160); -#[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf, Dummy)] -#[protobuf(name = "crate::proto::receipt::MessageToL2")] -pub struct MessageToL2 { - pub from_address: EthereumAddress, - pub payload: Vec, - pub to_address: Felt, - pub entry_point_selector: Felt, - pub nonce: Felt, -} - #[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf, Dummy)] #[protobuf(name = "crate::proto::receipt::receipt::ExecutionResources")] pub struct ExecutionResources { @@ -49,7 +39,6 @@ pub mod execution_resources { pub poseidon: u32, pub keccak: u32, pub output: u32, - pub segment_arena: u32, } } @@ -112,25 +101,11 @@ pub struct ReceiptsRequest { pub iteration: Iteration, } -#[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf, Dummy)] -#[protobuf(name = "crate::proto::receipt::Receipts")] -pub struct Receipts { - pub items: Vec, -} - -#[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf, Dummy)] -#[protobuf(name = "crate::proto::receipt::ReceiptsResponse")] -pub struct ReceiptsResponse { - #[optional] - pub id: Option, - #[rename(responses)] - pub kind: ReceiptsResponseKind, -} - -#[derive(Debug, Clone, PartialEq, Eq, Dummy)] -pub enum ReceiptsResponseKind { - Receipts(Receipts), - Fin(Fin), +#[derive(Debug, Default, Clone, PartialEq, Eq, Dummy)] +pub enum ReceiptsResponse { + Receipt(Receipt), + #[default] + Fin, } impl Dummy for EthereumAddress { @@ -139,37 +114,6 @@ impl Dummy for EthereumAddress { } } -impl From for ReceiptsResponse { - fn from(fin: Fin) -> Self { - Self { - id: None, - kind: ReceiptsResponseKind::Fin(fin), - } - } -} - -impl ReceiptsResponse { - pub fn into_fin(self) -> Option { - self.kind.into_fin() - } -} - -impl ReceiptsResponseKind { - pub fn into_receipts(self) -> Option { - match self { - Self::Receipts(r) => Some(r), - _ => None, - } - } - - pub fn into_fin(self) -> Option { - match self { - Self::Fin(f) => Some(f), - _ => None, - } - } -} - impl ToProtobuf for EthereumAddress { fn to_protobuf(self) -> proto::receipt::EthereumAddress { proto::receipt::EthereumAddress { @@ -205,29 +149,15 @@ impl TryFromProtobuf for Receipt { Declare, DeployAccount, DeprecatedDeploy, Invoke, L1Handler, }; - match input.r#type { - Some(receipt) => match receipt { - Invoke(r) => Ok(Receipt::Invoke(TryFromProtobuf::try_from_protobuf( - r, field_name, - )?)), - L1Handler(r) => Ok(Receipt::L1Handler(TryFromProtobuf::try_from_protobuf( - r, field_name, - )?)), - Declare(r) => Ok(Receipt::Declare(TryFromProtobuf::try_from_protobuf( - r, field_name, - )?)), - DeprecatedDeploy(r) => Ok(Receipt::Deploy(TryFromProtobuf::try_from_protobuf( - r, field_name, - )?)), - DeployAccount(r) => Ok(Receipt::DeployAccount(TryFromProtobuf::try_from_protobuf( - r, field_name, - )?)), - }, - None => Err(std::io::Error::new( - std::io::ErrorKind::InvalidData, - format!("Failed to parse {field_name}: missing receipt field"), - )), - } + Ok(match proto_field(input.r#type, field_name)? { + Invoke(r) => Self::Invoke(TryFromProtobuf::try_from_protobuf(r, field_name)?), + L1Handler(r) => Self::L1Handler(TryFromProtobuf::try_from_protobuf(r, field_name)?), + Declare(r) => Self::Declare(TryFromProtobuf::try_from_protobuf(r, field_name)?), + DeprecatedDeploy(r) => Self::Deploy(TryFromProtobuf::try_from_protobuf(r, field_name)?), + DeployAccount(r) => { + Self::DeployAccount(TryFromProtobuf::try_from_protobuf(r, field_name)?) + } + }) } } @@ -248,29 +178,27 @@ impl ToProtobuf for Receipt { } } -impl ToProtobuf for ReceiptsResponseKind { - fn to_protobuf(self) -> proto::receipt::receipts_response::Responses { - use proto::receipt::receipts_response::Responses::{Fin, Receipts}; - match self { - ReceiptsResponseKind::Receipts(r) => Receipts(r.to_protobuf()), - ReceiptsResponseKind::Fin(f) => Fin(f.to_protobuf()), +impl ToProtobuf for ReceiptsResponse { + fn to_protobuf(self) -> proto::receipt::ReceiptsResponse { + use proto::receipt::receipts_response::ReceiptMessage::{Fin, Receipt}; + proto::receipt::ReceiptsResponse { + receipt_message: Some(match self { + Self::Receipt(r) => Receipt(r.to_protobuf()), + Self::Fin => Fin(proto::common::Fin {}), + }), } } } -impl TryFromProtobuf for ReceiptsResponseKind { +impl TryFromProtobuf for ReceiptsResponse { fn try_from_protobuf( - input: proto::receipt::receipts_response::Responses, + input: proto::receipt::ReceiptsResponse, field_name: &'static str, ) -> Result { - use proto::receipt::receipts_response::Responses::{Fin, Receipts}; - match input { - Receipts(r) => Ok(ReceiptsResponseKind::Receipts( - TryFromProtobuf::try_from_protobuf(r, field_name)?, - )), - Fin(f) => Ok(ReceiptsResponseKind::Fin( - TryFromProtobuf::try_from_protobuf(f, field_name)?, - )), - } + use proto::receipt::receipts_response::ReceiptMessage::{Fin, Receipt}; + Ok(match proto_field(input.receipt_message, field_name)? { + Receipt(r) => Self::Receipt(TryFromProtobuf::try_from_protobuf(r, field_name)?), + Fin(_) => Self::Fin, + }) } } diff --git a/crates/p2p_proto/src/snapshot.rs b/crates/p2p_proto/src/snapshot.rs deleted file mode 100644 index 8968f20ab0..0000000000 --- a/crates/p2p_proto/src/snapshot.rs +++ /dev/null @@ -1,267 +0,0 @@ -use crate::common::{Address, Fin, Hash}; -use crate::state::Classes; -use crate::state::ContractStoredValue; -use crate::{proto, ToProtobuf, TryFromProtobuf}; -use fake::Dummy; -use pathfinder_crypto::Felt; - -#[derive(Debug, Copy, Clone, PartialEq, Eq, Dummy)] -pub enum PatriciaNode { - Edge { - length: u32, - path: Felt, - value: Felt, - }, - Binary { - left: Felt, - right: Felt, - }, -} - -#[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf)] -#[protobuf(name = "crate::proto::snapshot::PatriciaRangeProof")] -pub struct PatriciaRangeProof { - pub nodes: Vec, -} - -#[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf)] -#[protobuf(name = "crate::proto::snapshot::ContractState")] -pub struct ContractState { - pub address: Address, - pub class: Hash, - pub storage: Hash, - pub nonce: u64, -} - -#[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf)] -#[protobuf(name = "crate::proto::snapshot::ContractRangeRequest")] -pub struct ContractRangeRequest { - pub domain: u32, - pub state_root: Hash, - pub start: Address, - pub end: Address, - pub chunks_per_proof: u32, -} - -#[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf)] -#[protobuf(name = "crate::proto::snapshot::ContractRange")] -pub struct ContractRange { - pub state: Vec, -} - -#[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf)] -#[protobuf(name = "crate::proto::snapshot::ContractRangeResponse")] -pub struct ContractRangeResponse { - #[optional] - pub root: Option, - #[optional] - pub contracts_root: Option, - #[optional] - pub classes_root: Option, - #[rename(responses)] - pub kind: ContractRangeResponseKind, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum ContractRangeResponseKind { - Range(ContractRange), - Fin(Fin), -} - -#[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf)] -#[protobuf(name = "crate::proto::snapshot::ClassRangeRequest")] -pub struct ClassRangeRequest { - pub root: Hash, - pub start: Hash, - pub end: Hash, - pub chunks_per_proof: u32, -} - -#[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf)] -#[protobuf(name = "crate::proto::snapshot::ClassRangeResponse")] -struct ClassRangeResponse { - #[optional] - pub root: Option, - #[optional] - pub contracts_root: Option, - #[optional] - pub classes_root: Option, - #[rename(responses)] - pub kind: ClassRangeResponseKind, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum ClassRangeResponseKind { - Classes(Classes), - Fin(Fin), -} - -#[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf)] -#[protobuf(name = "crate::proto::snapshot::StorageLeafQuery")] -pub struct StorageLeafQuery { - pub contract_storage_root: Hash, - pub key: Felt, -} - -#[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf)] -#[protobuf(name = "crate::proto::snapshot::StorageRangeQuery")] -pub struct StorageRangeQuery { - pub start: StorageLeafQuery, - pub end: StorageLeafQuery, -} - -#[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf)] -#[protobuf(name = "crate::proto::snapshot::ContractStorageRequest")] -pub struct ContractStorageRequest { - pub domain: u32, - pub state_root: Hash, - pub query: Vec, -} - -#[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf)] -#[protobuf(name = "crate::proto::snapshot::ContractStorage")] -pub struct ContractStorage { - pub key_value: Vec, -} - -#[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf)] -#[protobuf(name = "crate::proto::snapshot::ContractStorageResponse")] -pub struct ContractStorageResponse { - pub state_root: Hash, - #[rename(responses)] - pub kind: ContractStorageResponseKind, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum ContractStorageResponseKind { - Storage(ContractStorage), - Fin(Fin), -} - -impl ToProtobuf for PatriciaNode { - fn to_protobuf(self) -> proto::snapshot::PatriciaNode { - use proto::snapshot::patricia_node::{Binary, Edge, Node}; - let node = Some(match self { - PatriciaNode::Binary { left, right } => Node::Binary(Binary { - left: Some(left.to_protobuf()), - right: Some(right.to_protobuf()), - }), - PatriciaNode::Edge { - length, - path, - value, - } => Node::Edge(Edge { - length, - path: Some(path.to_protobuf()), - value: Some(value.to_protobuf()), - }), - }); - proto::snapshot::PatriciaNode { node } - } -} - -impl TryFromProtobuf for PatriciaNode { - fn try_from_protobuf( - input: proto::snapshot::PatriciaNode, - field_name: &'static str, - ) -> Result { - use proto::snapshot::patricia_node::Node; - let proto::snapshot::PatriciaNode { node } = input; - let node = node.ok_or_else(|| { - std::io::Error::new( - std::io::ErrorKind::InvalidData, - format!("Missing node field in {field_name}"), - ) - })?; - Ok(match node { - Node::Binary(b) => PatriciaNode::Binary { - left: TryFromProtobuf::try_from_protobuf(b.left, field_name)?, - right: TryFromProtobuf::try_from_protobuf(b.right, field_name)?, - }, - Node::Edge(e) => PatriciaNode::Edge { - length: e.length, - path: TryFromProtobuf::try_from_protobuf(e.path, field_name)?, - value: TryFromProtobuf::try_from_protobuf(e.value, field_name)?, - }, - }) - } -} - -impl ToProtobuf for ContractRangeResponseKind { - fn to_protobuf(self) -> proto::snapshot::contract_range_response::Responses { - use proto::snapshot::contract_range_response::Responses::{Fin, Range}; - match self { - Self::Range(range) => Range(range.to_protobuf()), - Self::Fin(fin) => Fin(fin.to_protobuf()), - } - } -} - -impl TryFromProtobuf - for ContractRangeResponseKind -{ - fn try_from_protobuf( - input: proto::snapshot::contract_range_response::Responses, - field_name: &'static str, - ) -> Result { - use proto::snapshot::contract_range_response::Responses::{Fin, Range}; - Ok(match input { - Range(range) => Self::Range(TryFromProtobuf::try_from_protobuf(range, field_name)?), - Fin(fin) => Self::Fin(TryFromProtobuf::try_from_protobuf(fin, field_name)?), - }) - } -} - -impl ToProtobuf for ClassRangeResponseKind { - fn to_protobuf(self) -> proto::snapshot::class_range_response::Responses { - use proto::snapshot::class_range_response::Responses::{Classes, Fin}; - match self { - Self::Classes(classes) => Classes(classes.to_protobuf()), - Self::Fin(fin) => Fin(fin.to_protobuf()), - } - } -} - -impl TryFromProtobuf for ClassRangeResponseKind { - fn try_from_protobuf( - input: proto::snapshot::class_range_response::Responses, - field_name: &'static str, - ) -> Result { - use proto::snapshot::class_range_response::Responses::{Classes, Fin}; - Ok(match input { - Classes(classes) => { - Self::Classes(TryFromProtobuf::try_from_protobuf(classes, field_name)?) - } - Fin(fin) => Self::Fin(TryFromProtobuf::try_from_protobuf(fin, field_name)?), - }) - } -} - -impl ToProtobuf - for ContractStorageResponseKind -{ - fn to_protobuf(self) -> proto::snapshot::contract_storage_response::Responses { - use proto::snapshot::contract_storage_response::Responses::{Fin, Storage}; - match self { - Self::Storage(storage) => Storage(storage.to_protobuf()), - Self::Fin(fin) => Fin(fin.to_protobuf()), - } - } -} - -impl TryFromProtobuf - for ContractStorageResponseKind -{ - fn try_from_protobuf( - input: proto::snapshot::contract_storage_response::Responses, - field_name: &'static str, - ) -> Result { - use proto::snapshot::contract_storage_response::Responses::{Fin, Storage}; - Ok(match input { - Storage(storage) => { - Self::Storage(TryFromProtobuf::try_from_protobuf(storage, field_name)?) - } - Fin(fin) => Self::Fin(TryFromProtobuf::try_from_protobuf(fin, field_name)?), - }) - } -} diff --git a/crates/p2p_proto/src/state.rs b/crates/p2p_proto/src/state.rs index 925609dc70..6a8e25449b 100644 --- a/crates/p2p_proto/src/state.rs +++ b/crates/p2p_proto/src/state.rs @@ -1,8 +1,8 @@ use std::fmt::Debug; -use crate::common::Address; -use crate::{ToProtobuf, TryFromProtobuf}; -use fake::{Dummy, Fake, Faker}; +use crate::common::{Address, Iteration}; +use crate::{proto, proto_field, ToProtobuf, TryFromProtobuf}; +use fake::Dummy; use pathfinder_crypto::Felt; #[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf, Dummy)] @@ -13,171 +13,55 @@ pub struct ContractStoredValue { } #[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf, Dummy)] -#[protobuf(name = "crate::proto::state::state_diff::ContractDiff")] +#[protobuf(name = "crate::proto::state::ContractDiff")] pub struct ContractDiff { pub address: Address, #[optional] pub nonce: Option, #[optional] pub class_hash: Option, + #[optional] + pub is_replaced: Option, pub values: Vec, -} - -#[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf, Dummy)] -#[protobuf(name = "crate::proto::state::StateDiff")] -pub struct StateDiff { pub domain: u32, - pub contract_diffs: Vec, -} - -#[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf, Dummy, PartialOrd, Ord)] -#[protobuf(name = "crate::proto::state::EntryPoint")] -pub struct EntryPoint { - pub selector: Felt, - pub offset: Felt, -} - -#[derive(/*Debug, */ Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf, PartialOrd, Ord)] -#[protobuf(name = "crate::proto::state::Cairo0Class")] -pub struct Cairo0Class { - pub abi: Vec, - pub externals: Vec, - pub l1_handlers: Vec, - pub constructors: Vec, - pub program: Vec, -} - -impl Debug for Cairo0Class { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Cairo0Class") - .field( - "abi", - &std::str::from_utf8(&self.abi) - .unwrap_or(&format!("invalid utf8: {:#x?}", &self.abi)), - ) - .field("externals", &self.externals) - .field("l1_handlers", &self.l1_handlers) - .field("constructors", &self.constructors) - .field( - "program", - &std::str::from_utf8(&self.program) - .unwrap_or(&format!("invalid utf8: {:#x?}", &self.program)), - ) - .finish() - } -} - -impl Dummy for Cairo0Class { - fn dummy_with_rng(_: &T, rng: &mut R) -> Self { - Self { - abi: Faker.fake_with_rng::(rng).into_bytes(), - externals: Faker.fake_with_rng(rng), - l1_handlers: Faker.fake_with_rng(rng), - constructors: Faker.fake_with_rng(rng), - program: serde_json::to_vec(&serde_json::Value::Object(Faker.fake_with_rng(rng))) - .unwrap(), - } - } } -#[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf, Dummy, PartialOrd, Ord)] -#[protobuf(name = "crate::proto::state::SierraEntryPoint")] -pub struct SierraEntryPoint { - pub index: u64, - pub selector: Felt, +#[derive(Debug, Copy, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf, Dummy)] +#[protobuf(name = "crate::proto::state::StateDiffsRequest")] +pub struct StateDiffsRequest { + pub iteration: Iteration, } -#[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf, Dummy, PartialOrd, Ord)] -#[protobuf(name = "crate::proto::state::Cairo1EntryPoints")] -pub struct Cairo1EntryPoints { - pub externals: Vec, - pub l1_handlers: Vec, - pub constructors: Vec, +#[derive(Debug, Default, Clone, PartialEq, Eq, Dummy)] +pub enum StateDiffsResponse { + ContractDiff(ContractDiff), + #[default] + Fin, } -#[derive(Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf, PartialOrd, Ord)] -#[protobuf(name = "crate::proto::state::Cairo1Class")] -pub struct Cairo1Class { - pub abi: Vec, - pub entry_points: Cairo1EntryPoints, - pub program: Vec, - pub contract_class_version: String, - pub compiled: Vec, -} - -impl Debug for Cairo1Class { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Cairo1Class") - .field( - "abi", - &std::str::from_utf8(&self.abi) - .unwrap_or(&format!("invalid utf8: {:#x?}", &self.abi)), - ) - .field("entry_points", &self.entry_points) - .field("program", &self.program) - .field("contract_class_version", &self.contract_class_version) - .field( - "compiled", - &std::str::from_utf8(&self.abi) - .unwrap_or(&format!("invalid utf8: {:#x?}", &self.compiled)), - ) - .finish() - } -} - -impl Dummy for Cairo1Class { - fn dummy_with_rng(_: &T, rng: &mut R) -> Self { - Self { - abi: Faker.fake_with_rng::(rng).into_bytes(), - entry_points: Faker.fake_with_rng(rng), - program: Faker.fake_with_rng(rng), - contract_class_version: "0.1.0".into(), - compiled: Faker.fake_with_rng(rng), - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Dummy)] -pub enum Class { - Cairo0(Cairo0Class), - Cairo1(Cairo1Class), -} - -impl ToProtobuf for Class { - fn to_protobuf(self) -> crate::proto::state::Class { - use crate::proto::state::{ - class::Class::{Cairo0, Cairo1}, - Class, - }; - Class { - class: Some(match self { - Self::Cairo0(c) => Cairo0(c.to_protobuf()), - Self::Cairo1(c) => Cairo1(c.to_protobuf()), +impl ToProtobuf for StateDiffsResponse { + fn to_protobuf(self) -> proto::state::StateDiffsResponse { + use proto::state::state_diffs_response::StateDiffMessage::{ContractDiff, Fin}; + proto::state::StateDiffsResponse { + state_diff_message: Some(match self { + Self::ContractDiff(contract_diff) => ContractDiff(contract_diff.to_protobuf()), + Self::Fin => Fin(proto::common::Fin {}), }), } } } -impl TryFromProtobuf for Class { +impl TryFromProtobuf for StateDiffsResponse { fn try_from_protobuf( - input: crate::proto::state::Class, + input: proto::state::StateDiffsResponse, field_name: &'static str, ) -> Result { - use crate::proto::state::class::Class::{Cairo0, Cairo1}; - match input.class { - Some(Cairo0(c)) => Ok(Self::Cairo0(Cairo0Class::try_from_protobuf(c, field_name)?)), - Some(Cairo1(c)) => Ok(Self::Cairo1(Cairo1Class::try_from_protobuf(c, field_name)?)), - None => Err(std::io::Error::new( - std::io::ErrorKind::InvalidData, - format!("{}: class field missing", field_name), - )), + use proto::state::state_diffs_response::StateDiffMessage::{ContractDiff, Fin}; + match proto_field(input.state_diff_message, field_name)? { + ContractDiff(x) => { + TryFromProtobuf::try_from_protobuf(x, field_name).map(Self::ContractDiff) + } + Fin(_) => Ok(Self::Fin), } } } - -#[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf, Dummy)] -#[protobuf(name = "crate::proto::state::Classes")] -pub struct Classes { - pub domain: u32, - pub classes: Vec, -} diff --git a/crates/p2p_proto/src/transaction.rs b/crates/p2p_proto/src/transaction.rs index 58add664e1..2c7518c0e5 100644 --- a/crates/p2p_proto/src/transaction.rs +++ b/crates/p2p_proto/src/transaction.rs @@ -1,5 +1,5 @@ -use crate::common::{Address, BlockId, Fin, Hash, Iteration}; -use crate::{proto, ToProtobuf, TryFromProtobuf}; +use crate::common::{Address, Hash, Iteration}; +use crate::{proto, proto_field, ToProtobuf, TryFromProtobuf}; use fake::Dummy; use pathfinder_crypto::Felt; @@ -76,7 +76,6 @@ pub struct Deploy { pub class_hash: Hash, pub address_salt: Felt, pub calldata: Vec, - pub address: Address, pub version: u32, } @@ -88,7 +87,7 @@ pub struct DeployAccountV1 { pub class_hash: Hash, pub nonce: Felt, pub address_salt: Felt, - pub constructor_calldata: Vec, + pub calldata: Vec, } #[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf, Dummy)] @@ -159,7 +158,7 @@ pub struct L1HandlerV0 { } #[derive(Debug, Clone, PartialEq, Eq, Dummy)] -pub enum Transaction { +pub enum TransactionVariant { DeclareV0(DeclareV0), DeclareV1(DeclareV1), DeclareV2(DeclareV2), @@ -173,104 +172,62 @@ pub enum Transaction { L1HandlerV0(L1HandlerV0), } -#[derive(Debug, Copy, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf, Dummy)] -#[protobuf(name = "crate::proto::transaction::TransactionsRequest")] -pub struct TransactionsRequest { - pub iteration: Iteration, -} - #[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf, Dummy)] -#[protobuf(name = "crate::proto::transaction::Transactions")] -pub struct Transactions { - pub items: Vec, -} +#[protobuf(name = "crate::proto::transaction::Transaction")] -#[derive(Debug, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf, Dummy)] -#[protobuf(name = "crate::proto::transaction::TransactionsResponse")] -pub struct TransactionsResponse { - #[optional] - pub id: Option, - #[rename(responses)] - pub kind: TransactionsResponseKind, +pub struct Transaction { + #[rename(transaction_hash)] + pub hash: Hash, + #[rename(txn)] + pub variant: TransactionVariant, } -#[derive(Debug, Clone, PartialEq, Eq, Dummy)] -pub enum TransactionsResponseKind { - Transactions(Transactions), - Fin(Fin), -} - -impl From for TransactionsResponse { - fn from(fin: Fin) -> Self { - Self { - id: None, - kind: TransactionsResponseKind::Fin(fin), - } - } -} - -impl TransactionsResponse { - pub fn into_fin(self) -> Option { - self.kind.into_fin() - } +#[derive(Debug, Copy, Clone, PartialEq, Eq, ToProtobuf, TryFromProtobuf, Dummy)] +#[protobuf(name = "crate::proto::transaction::TransactionsRequest")] +pub struct TransactionsRequest { + pub iteration: Iteration, } -impl TransactionsResponseKind { - pub fn into_transactions(self) -> Option { - match self { - Self::Transactions(t) => Some(t), - _ => None, - } - } - - pub fn into_fin(self) -> Option { - match self { - Self::Fin(f) => Some(f), - _ => None, - } - } +#[allow(clippy::large_enum_variant)] +#[derive(Debug, Default, Clone, PartialEq, Eq, Dummy)] +pub enum TransactionsResponse { + Transaction(Transaction), + #[default] + Fin, } -impl ToProtobuf for Transaction { - fn to_protobuf(self) -> proto::transaction::Transaction { +impl ToProtobuf for TransactionVariant { + fn to_protobuf(self) -> proto::transaction::transaction::Txn { use proto::transaction::transaction::Txn::{ DeclareV0, DeclareV1, DeclareV2, DeclareV3, Deploy, DeployAccountV1, DeployAccountV3, InvokeV0, InvokeV1, InvokeV3, L1Handler, }; - proto::transaction::Transaction { - txn: Some(match self { - Self::DeclareV0(txn) => DeclareV0(txn.to_protobuf()), - Self::DeclareV1(txn) => DeclareV1(txn.to_protobuf()), - Self::DeclareV2(txn) => DeclareV2(txn.to_protobuf()), - Self::DeclareV3(txn) => DeclareV3(txn.to_protobuf()), - Self::Deploy(txn) => Deploy(txn.to_protobuf()), - Self::DeployAccountV1(txn) => DeployAccountV1(txn.to_protobuf()), - Self::DeployAccountV3(txn) => DeployAccountV3(txn.to_protobuf()), - Self::InvokeV0(txn) => InvokeV0(txn.to_protobuf()), - Self::InvokeV1(txn) => InvokeV1(txn.to_protobuf()), - Self::InvokeV3(txn) => InvokeV3(txn.to_protobuf()), - Self::L1HandlerV0(txn) => L1Handler(txn.to_protobuf()), - }), + match self { + Self::DeclareV0(txn) => DeclareV0(txn.to_protobuf()), + Self::DeclareV1(txn) => DeclareV1(txn.to_protobuf()), + Self::DeclareV2(txn) => DeclareV2(txn.to_protobuf()), + Self::DeclareV3(txn) => DeclareV3(txn.to_protobuf()), + Self::Deploy(txn) => Deploy(txn.to_protobuf()), + Self::DeployAccountV1(txn) => DeployAccountV1(txn.to_protobuf()), + Self::DeployAccountV3(txn) => DeployAccountV3(txn.to_protobuf()), + Self::InvokeV0(txn) => InvokeV0(txn.to_protobuf()), + Self::InvokeV1(txn) => InvokeV1(txn.to_protobuf()), + Self::InvokeV3(txn) => InvokeV3(txn.to_protobuf()), + Self::L1HandlerV0(txn) => L1Handler(txn.to_protobuf()), } } } -impl TryFromProtobuf for Transaction { +impl TryFromProtobuf for TransactionVariant { fn try_from_protobuf( - input: proto::transaction::Transaction, + input: proto::transaction::transaction::Txn, field_name: &'static str, ) -> Result { use proto::transaction::transaction::Txn::{ DeclareV0, DeclareV1, DeclareV2, DeclareV3, Deploy, DeployAccountV1, DeployAccountV3, InvokeV0, InvokeV1, InvokeV3, L1Handler, }; - let txn = input.txn.ok_or_else(|| { - std::io::Error::new( - std::io::ErrorKind::InvalidData, - format!("Missing field txn in {field_name}"), - ) - })?; - match txn { + match input { DeclareV0(t) => TryFromProtobuf::try_from_protobuf(t, field_name).map(Self::DeclareV0), DeclareV1(t) => TryFromProtobuf::try_from_protobuf(t, field_name).map(Self::DeclareV1), DeclareV2(t) => TryFromProtobuf::try_from_protobuf(t, field_name).map(Self::DeclareV2), @@ -292,29 +249,27 @@ impl TryFromProtobuf for Transaction { } } -impl ToProtobuf for TransactionsResponseKind { - fn to_protobuf(self) -> proto::transaction::transactions_response::Responses { - use proto::transaction::transactions_response::Responses::{Fin, Transactions}; - match self { - Self::Transactions(t) => Transactions(t.to_protobuf()), - Self::Fin(t) => Fin(t.to_protobuf()), +impl ToProtobuf for TransactionsResponse { + fn to_protobuf(self) -> proto::transaction::TransactionsResponse { + use proto::transaction::transactions_response::TransactionMessage::{Fin, Transaction}; + proto::transaction::TransactionsResponse { + transaction_message: Some(match self { + Self::Transaction(t) => Transaction(t.to_protobuf()), + Self::Fin => Fin(proto::common::Fin {}), + }), } } } -impl TryFromProtobuf - for TransactionsResponseKind -{ +impl TryFromProtobuf for TransactionsResponse { fn try_from_protobuf( - input: proto::transaction::transactions_response::Responses, + input: proto::transaction::TransactionsResponse, field_name: &'static str, ) -> Result { - use proto::transaction::transactions_response::Responses::{Fin, Transactions}; - match input { - Transactions(t) => { - TryFromProtobuf::try_from_protobuf(t, field_name).map(Self::Transactions) - } - Fin(t) => TryFromProtobuf::try_from_protobuf(t, field_name).map(Self::Fin), - } + use proto::transaction::transactions_response::TransactionMessage::{Fin, Transaction}; + Ok(match proto_field(input.transaction_message, field_name)? { + Transaction(t) => Self::Transaction(TryFromProtobuf::try_from_protobuf(t, field_name)?), + Fin(_) => Self::Fin, + }) } } diff --git a/crates/p2p_proto_derive/src/lib.rs b/crates/p2p_proto_derive/src/lib.rs index e4e89ce3c5..c59a7855fa 100644 --- a/crates/p2p_proto_derive/src/lib.rs +++ b/crates/p2p_proto_derive/src/lib.rs @@ -101,7 +101,8 @@ fn iterate_to_protobuf(data: &Data) -> Result { + syn::Fields::Unit => Ok(Default::default()), + syn::Fields::Unnamed(_) => { Err(syn::Error::new(data.fields.span(), "expected named struct")) } }, @@ -170,7 +171,8 @@ fn iterate_try_from_protobuf(data: &Data) -> Result { + syn::Fields::Unit => Ok(Default::default()), + syn::Fields::Unnamed(_) => { Err(syn::Error::new(data.fields.span(), "expected named struct")) } }, diff --git a/crates/pathfinder/src/bin/pathfinder/main.rs b/crates/pathfinder/src/bin/pathfinder/main.rs index ab9b5b3a92..c53246d076 100644 --- a/crates/pathfinder/src/bin/pathfinder/main.rs +++ b/crates/pathfinder/src/bin/pathfinder/main.rs @@ -6,11 +6,9 @@ use mimalloc::MiMalloc; use pathfinder_common::{consts::VERGEN_GIT_DESCRIBE, BlockNumber, Chain, ChainId, EthereumChain}; use pathfinder_ethereum::{EthereumApi, EthereumClient}; +use pathfinder_lib::monitoring::{self}; +use pathfinder_lib::state; use pathfinder_lib::state::SyncContext; -use pathfinder_lib::{ - monitoring::{self}, - state, -}; use pathfinder_rpc::context::WebsocketContext; use pathfinder_rpc::SyncState; use pathfinder_storage::Storage; @@ -207,13 +205,8 @@ Hint: This is usually caused by exceeding the file descriptor limit of your syst None => rpc_server, }; - let (p2p_handle, sequencer) = start_p2p( - pathfinder_context.network_id, - p2p_storage, - pathfinder_context.gateway, - config.p2p, - ) - .await?; + let (p2p_handle, gossiper) = + start_p2p(pathfinder_context.network_id, p2p_storage, config.p2p).await?; let sync_context = SyncContext { storage: sync_storage, @@ -221,7 +214,7 @@ Hint: This is usually caused by exceeding the file descriptor limit of your syst chain: pathfinder_context.network, chain_id: pathfinder_context.network_id, core_address: pathfinder_context.l1_core_address, - sequencer, + sequencer: pathfinder_context.gateway, state: sync_state.clone(), head_poll_interval: config.poll_interval, pending_data: tx_pending, @@ -231,6 +224,7 @@ Hint: This is usually caused by exceeding the file descriptor limit of your syst block_cache_size: 1_000, restart_delay: config.debug.restart_delay, verify_tree_hashes: config.verify_tree_hashes, + gossiper, }; let sync_handle = if config.is_sync_enabled { @@ -339,14 +333,10 @@ fn permission_check(base: &std::path::Path) -> Result<(), anyhow::Error> { async fn start_p2p( chain_id: ChainId, storage: Storage, - sequencer: starknet_gateway_client::Client, config: config::P2PConfig, -) -> anyhow::Result<( - tokio::task::JoinHandle<()>, - pathfinder_lib::p2p_network::client::HybridClient, -)> { +) -> anyhow::Result<(tokio::task::JoinHandle<()>, state::Gossiper)> { use p2p::libp2p::identity::Keypair; - use pathfinder_lib::p2p_network::{client::HybridClient, P2PContext}; + use pathfinder_lib::p2p_network::P2PContext; use serde::Deserialize; use std::{path::Path, time::Duration}; use zeroize::Zeroizing; @@ -405,25 +395,21 @@ async fn start_p2p( predefined_peers: config.predefined_peers, }; - let (p2p_client, head_receiver, p2p_handle) = + let (p2p_client, _head_receiver, p2p_handle) = pathfinder_lib::p2p_network::start(context).await?; - Ok(( - p2p_handle, - HybridClient::new(config.proxy, p2p_client, sequencer, head_receiver), - )) + Ok((p2p_handle, state::Gossiper::new(p2p_client))) } #[cfg(not(feature = "p2p"))] async fn start_p2p( _: ChainId, _: Storage, - sequencer: starknet_gateway_client::Client, _: config::P2PConfig, -) -> anyhow::Result<(tokio::task::JoinHandle<()>, starknet_gateway_client::Client)> { - let join_handle = tokio::task::spawn(async move { futures::future::pending().await }); +) -> anyhow::Result<(tokio::task::JoinHandle<()>, state::Gossiper)> { + let join_handle = tokio::task::spawn(futures::future::pending()); - Ok((join_handle, sequencer)) + Ok((join_handle, Default::default())) } /// Spawns the monitoring task at the given address. diff --git a/crates/pathfinder/src/p2p_network.rs b/crates/pathfinder/src/p2p_network.rs index 1e70d4d1b4..7ed2a0dd6b 100644 --- a/crates/pathfinder/src/p2p_network.rs +++ b/crates/pathfinder/src/p2p_network.rs @@ -2,6 +2,7 @@ use anyhow::Context; use p2p::client::peer_agnostic; use p2p::libp2p::{identity::Keypair, multiaddr::Multiaddr}; use p2p::{HeadRx, HeadTx}; +use p2p_proto::header::BlockHeadersResponse; use pathfinder_common::{BlockHash, BlockNumber, ChainId}; use pathfinder_storage::Storage; use tracing::Instrument; @@ -9,7 +10,9 @@ use tracing::Instrument; pub mod client; mod sync_handlers; -use sync_handlers::{get_bodies, get_events, get_headers, get_receipts, get_transactions}; +use sync_handlers::{ + get_classes, get_events, get_headers, get_receipts, get_state_diffs, get_transactions, +}; // Silence clippy pub type P2PNetworkHandle = (peer_agnostic::Client, HeadRx, tokio::task::JoinHandle<()>); @@ -136,10 +139,15 @@ async fn handle_p2p_event( } => { get_headers(storage, request, channel).await?; } - p2p::Event::InboundBodiesSyncRequest { + p2p::Event::InboundClassesSyncRequest { request, channel, .. } => { - get_bodies(storage, request, channel).await?; + get_classes(storage, request, channel).await?; + } + p2p::Event::InboundStateDiffsSyncRequest { + request, channel, .. + } => { + get_state_diffs(storage, request, channel).await?; } p2p::Event::InboundTransactionsSyncRequest { request, channel, .. @@ -158,15 +166,16 @@ async fn handle_p2p_event( } p2p::Event::BlockPropagation { from, new_block } => { tracing::info!(%from, ?new_block, "Block Propagation"); - use p2p_proto::block::NewBlock; + use p2p_proto::header::NewBlock; - let id = match new_block { - NewBlock::Id(id) => Some(id), - NewBlock::Header(h) => h.parts.first().and_then(|part| part.id()), - NewBlock::Body(b) => b.id, + let new_head = match new_block { + NewBlock::Id(id) => BlockNumber::new(id.number).map(|n| (n, BlockHash(id.hash.0))), + NewBlock::Header(BlockHeadersResponse::Header(hdr)) => { + BlockNumber::new(hdr.number).map(|n| (n, BlockHash(hdr.block_hash.0))) + } + NewBlock::Header(BlockHeadersResponse::Fin) => None, }; - let new_head = - id.and_then(|id| BlockNumber::new(id.number).map(|n| (n, BlockHash(id.hash.0)))); + match new_head { Some((new_height, new_hash)) => { tx.send_if_modified(|head| -> bool { @@ -181,7 +190,7 @@ async fn handle_p2p_event( }); } None => { - tracing::warn!("Received block propagation without a valid head: {id:?}") + tracing::warn!("Received block propagation without a valid head") } } } diff --git a/crates/pathfinder/src/p2p_network/client.rs b/crates/pathfinder/src/p2p_network/client.rs index 62ac4a90bc..e906693400 100644 --- a/crates/pathfinder/src/p2p_network/client.rs +++ b/crates/pathfinder/src/p2p_network/client.rs @@ -1,730 +1 @@ -//! Sync related data retrieval from other peers -//! -//! This is a temporary wrapper around proper p2p sync|propagation api that fits into -//! current sequential sync logic and will be removed when __proper__ sync algo is -//! integrated. What it does is just split methods between a "proxy" node -//! that syncs from the gateway and propagates new headers and a -//! "proper" p2p node which only syncs via p2p. - -use anyhow::Context; -use p2p::client::types::{BlockHeader, MaybeSignedBlockHeader, StateUpdateWithDefinitions}; -use p2p::{client::peer_agnostic, HeadRx}; -use p2p_proto::state::{Cairo0Class, Cairo1Class, Class}; -use pathfinder_common::state_update::{ContractClassUpdate, ContractUpdate}; -use pathfinder_common::{ - BlockCommitmentSignature, BlockCommitmentSignatureElem, BlockHash, BlockId, BlockNumber, - ByteCodeOffset, CasmHash, ClassHash, EntryPoint, SierraHash, StateCommitment, - StateDiffCommitment, StateUpdate, TransactionHash, -}; -use pathfinder_crypto::Felt; -use serde::Deserialize; -use serde_json::value::RawValue; -use starknet_gateway_client::{GatewayApi, GossipApi}; -use starknet_gateway_types::class_definition::{self, SierraEntryPoints}; -use starknet_gateway_types::class_hash::from_parts::{ - compute_cairo_class_hash, compute_sierra_class_hash, -}; -use starknet_gateway_types::reply::{self as gw, BlockSignature}; -use starknet_gateway_types::request::add_transaction::{Declare, DeployAccount, InvokeFunction}; -use starknet_gateway_types::request::contract::{SelectorAndFunctionIndex, SelectorAndOffset}; -use starknet_gateway_types::trace; -use starknet_gateway_types::{error::SequencerError, reply::Block}; -use std::borrow::Cow; -use std::collections::{HashMap, HashSet, VecDeque}; -use std::sync::{Arc, Mutex}; - -pub mod types; - -/// Hybrid, as it uses either p2p or the gateway depending on role and api call -#[derive(Clone, Debug)] -pub enum HybridClient { - /// Syncs from the feeder gateway, propagates new headers via p2p/gossipsub - /// Proxies blockchain data to non propagating nodes via p2p - GatewayProxy { - p2p_client: peer_agnostic::Client, - sequencer: starknet_gateway_client::Client, - }, - /// Syncs from p2p network, does not propagate - NonPropagatingP2P { - p2p_client: peer_agnostic::Client, - sequencer: starknet_gateway_client::Client, - head_rx: HeadRx, - /// We need to cache the last two fetched blocks via p2p otherwise sync logic will - /// produce a false reorg from genesis when we loose connection to other p2p nodes. - /// This was we can stay at the same height while we are disconnected. - cache: Cache, - }, -} - -/// We only need to cache 2 blocks: the last one and its parent. -const CACHE_SIZE: usize = 2; - -#[derive(Clone, Debug)] -pub struct Cache { - inner: Arc>>, -} - -#[derive(Clone, Debug)] -pub struct CacheEntry { - pub block: Block, - pub signature: BlockCommitmentSignature, - pub class_definitions: HashMap>, - pub casm_definitions: HashMap>, -} - -impl Default for Cache { - fn default() -> Self { - Self { - inner: Arc::new(Mutex::new(VecDeque::new())), - } - } -} - -impl Cache { - fn get_block(&self, number: BlockNumber) -> Option { - let locked = self.inner.lock().unwrap(); - locked - .iter() - .find_map(|v| (v.block.block_number == number).then_some(v.block.clone())) - } - - fn get_signature(&self, block_hash: BlockHash) -> Option { - let locked = self.inner.lock().unwrap(); - locked.iter().find_map(|entry| { - (entry.block.block_hash == block_hash).then_some(BlockSignature { - // Not used in sync - block_number: entry.block.block_number, - // Only this field is used in sync - signature: [ - BlockCommitmentSignatureElem(entry.signature.r.0), - BlockCommitmentSignatureElem(entry.signature.s.0), - ], - // Not used in sync - signature_input: gw::BlockSignatureInput { - block_hash, - state_diff_commitment: StateDiffCommitment::default(), // This is fine - }, - }) - }) - } - - fn get_state_commitment(&self, block_hash: BlockHash) -> Option { - let locked = self.inner.lock().unwrap(); - locked.iter().find_map(|entry| { - (entry.block.block_hash == block_hash).then_some(entry.block.state_commitment) - }) - } - - fn get_definition(&self, class_hash: ClassHash) -> Option> { - let locked = self.inner.lock().unwrap(); - locked - .iter() - .find_map(|entry| entry.class_definitions.get(&class_hash).cloned()) - } - - fn get_casm(&self, class_hash: ClassHash) -> Option> { - let locked = self.inner.lock().unwrap(); - locked - .iter() - .find_map(|entry| entry.casm_definitions.get(&class_hash).cloned()) - } - - fn clear_if_reorg(&self, header: &BlockHeader) { - if let Some(parent_number) = header.number.get().checked_sub(1) { - let mut locked = self.inner.lock().unwrap(); - if let Some(cached_parent_hash) = locked.iter().find_map(|entry| { - (entry.block.block_number.get() == parent_number).then_some(entry.block.block_hash) - }) { - // If there's a reorg, purge the cache or we'll be stuck - // - // There's a risk we'll falsely reorg to genesis if all other peers get disconnected - // just after the cache is purged - // TODO: consider increasing the cache and just purging the last block - if cached_parent_hash.0 != header.parent_hash.0 { - locked.clear(); - } - } - } - } - - fn insert_block_and_signature(&self, block: Block, signature: BlockCommitmentSignature) { - let mut locked_inner = self.inner.lock().unwrap(); - locked_inner.push_front(CacheEntry { - block, - signature, - class_definitions: Default::default(), - casm_definitions: Default::default(), - }); - if locked_inner.len() > CACHE_SIZE { - locked_inner.pop_back(); - } - } - - fn insert_definitions( - &self, - block_hash: BlockHash, - class_definitions: HashMap>, - casm_definitions: HashMap>, - ) { - let mut locked = self.inner.lock().unwrap(); - let entry = locked - .iter_mut() - .find(|entry| entry.block.block_hash == block_hash) - .expect("block is already cached"); - entry.class_definitions = class_definitions; - entry.casm_definitions = casm_definitions; - } -} - -impl HybridClient { - pub fn new( - i_am_proxy: bool, - p2p_client: peer_agnostic::Client, - sequencer: starknet_gateway_client::Client, - head_rx: HeadRx, - ) -> Self { - if i_am_proxy { - Self::GatewayProxy { - p2p_client, - sequencer, - } - } else { - Self::NonPropagatingP2P { - p2p_client, - sequencer, - head_rx, - cache: Default::default(), - } - } - } - - fn as_sequencer(&self) -> &starknet_gateway_client::Client { - match self { - HybridClient::GatewayProxy { sequencer, .. } => sequencer, - HybridClient::NonPropagatingP2P { sequencer, .. } => sequencer, - } - } -} - -/// A hacky temporary way to wrap p2p related errors -mod error { - use starknet_gateway_types::error::{ - KnownStarknetErrorCode, SequencerError, StarknetError, StarknetErrorCode, - }; - - pub fn block_not_found(message: impl ToString) -> SequencerError { - SequencerError::StarknetError(StarknetError { - code: StarknetErrorCode::Known(KnownStarknetErrorCode::BlockNotFound), - message: message.to_string(), - }) - } - - pub fn class_not_found(message: impl ToString) -> SequencerError { - SequencerError::StarknetError(StarknetError { - code: StarknetErrorCode::Known(KnownStarknetErrorCode::UndeclaredClass), - message: message.to_string(), - }) - } -} - -#[async_trait::async_trait] -impl GatewayApi for HybridClient { - async fn block(&self, block: BlockId) -> Result { - use error::block_not_found; - - match self { - HybridClient::GatewayProxy { sequencer, .. } => sequencer.block(block).await, - HybridClient::NonPropagatingP2P { - p2p_client, cache, .. - } => { - match block { - BlockId::Number(n) => { - if let Some(block) = cache.get_block(n) { - tracing::trace!(number=%n, "HybridClient: using cached block"); - return Ok(block.into()); - } - - let headers = p2p_client.block_headers(n, 1).await.map_err(|error| { - block_not_found(format!("getting headers failed: block {n}, {error}",)) - })?; - - if headers.len() != 1 { - return Err(block_not_found(format!( - "headers len for block {n} is {}, expected 1", - headers.len() - ))); - } - - let MaybeSignedBlockHeader { header, signatures } = - headers.into_iter().next().expect("len is 1"); - - if header.number != n { - return Err(block_not_found("block number mismatch")); - } - - if signatures.len() != 1 { - return Err(block_not_found("expected 1 block header signature")); - } - - let signature = signatures.into_iter().next().expect("len is 1"); - - cache.clear_if_reorg(&header); - - let block_hash = header.hash; - - let mut transactions = p2p_client - .transactions(header.hash, 1) - .await - .map_err(|error| { - block_not_found(format!( - "getting transactions failed: block {n}: {error}", - )) - })?; - - let transactions = transactions.remove(&block_hash).ok_or_else(|| { - block_not_found(format!("no peers with transactions for block {n}",)) - })?; - - let receipts = - p2p_client.receipts(header.hash, 1).await.map_err(|error| { - block_not_found(format!( - "getting receipts failed: block {n}: {error}", - )) - })?; - - use crate::p2p_network::client::types::Receipt; - - let mut receipts = receipts - .into_iter() - .map(|(k, v)| { - v.into_iter() - .map(Receipt::try_from) - .collect::, _>>() - .map(|r| (k, r)) - }) - .collect::, _>>() - .map_err(|error| { - block_not_found(format!( - "failed to parse receipts for block {n}: {error}", - )) - })?; - - let receipts = receipts.remove(&block_hash).ok_or_else(|| { - block_not_found(format!("no peers with receipts for block {n}",)) - })?; - - debug_assert_eq!(transactions.len(), receipts.len()); - - // let mut events = - // p2p_client.event(header.hash, 1).await.map_err(|error| { - // block_not_found(format!( - // "getting events failed: block {n}: {error}", - // )) - // })?; - - // let events = events.remove(&block_hash).ok_or_else(|| { - // block_not_found(format!("no peers with events for block {n}",)) - // })?; - - let block = gw::Block { - block_hash: header.hash, - block_number: header.number, - eth_l1_gas_price: Some(header.eth_l1_gas_price), - strk_l1_gas_price: None, - parent_block_hash: header.parent_hash, - sequencer_address: Some(header.sequencer_address), - state_commitment: header.state_commitment, - // FIXME - status: gw::Status::AcceptedOnL2, - timestamp: header.timestamp, - transaction_receipts: receipts.into_iter().map(|_| todo!()).collect(), - transactions: transactions.into_iter().map(|_| todo!()).collect(), - starknet_version: header.starknet_version, - }; - - cache.insert_block_and_signature(block.clone(), signature); - - Ok(block.into()) - } - BlockId::Latest => { - unreachable!("GatewayApi.head() is used in sync and sync status instead") - } - BlockId::Hash(_) => unreachable!("not used in sync"), - BlockId::Pending => { - unreachable!("pending should be disabled when p2p is enabled") - } - } - } - } - } - - async fn block_without_retry( - &self, - block: BlockId, - ) -> Result { - match self { - HybridClient::GatewayProxy { sequencer, .. } => { - sequencer.block_without_retry(block).await - } - HybridClient::NonPropagatingP2P { .. } => { - unreachable!("used for gas price and not in sync") - } - } - } - - async fn pending_class_by_hash( - &self, - class_hash: ClassHash, - ) -> Result { - use error::class_not_found; - - match self { - HybridClient::GatewayProxy { sequencer, .. } => { - sequencer.pending_class_by_hash(class_hash).await - } - HybridClient::NonPropagatingP2P { cache, .. } => { - let def = cache - .get_definition(class_hash) - .ok_or_else(|| class_not_found(format!("No peers with class {class_hash}")))?; - Ok(def.into()) - } - } - } - - async fn pending_casm_by_hash( - &self, - class_hash: ClassHash, - ) -> Result { - use error::class_not_found; - - match self { - HybridClient::GatewayProxy { sequencer, .. } => { - sequencer.pending_casm_by_hash(class_hash).await - } - HybridClient::NonPropagatingP2P { cache, .. } => { - let def = cache.get_casm(class_hash).ok_or_else(|| { - class_not_found(format!("No peers with casm for class {class_hash}")) - })?; - Ok(def.into()) - } - } - } - - async fn transaction( - &self, - transaction_hash: TransactionHash, - ) -> Result { - self.as_sequencer().transaction(transaction_hash).await - } - - async fn state_update(&self, block: BlockId) -> Result { - use error::block_not_found; - - match self { - HybridClient::GatewayProxy { sequencer, .. } => sequencer.state_update(block).await, - HybridClient::NonPropagatingP2P { - p2p_client, cache, .. - } => match block { - BlockId::Hash(hash) => { - let mut state_updates = - p2p_client.state_updates(hash, 1).await.map_err(|error| { - block_not_found(format!( - "No peers with state update for block {hash}: {error}" - )) - })?; - - if state_updates.len() != 1 { - return Err(block_not_found(format!( - "State updates len is {}, expected 1", - state_updates.len() - ))); - } - - let StateUpdateWithDefinitions { - block_hash, - state_update, - classes, - } = state_updates.swap_remove(0); - - if block_hash != hash { - return Err(block_not_found("Block hash mismatch")); - } - - let mut declared_cairo_classes = HashSet::new(); - let mut declared_sierra_classes = HashMap::new(); - let mut class_definitions = HashMap::new(); - let mut casm_definitions = HashMap::new(); - - for class in classes { - match class { - Class::Cairo0(c0) => { - let jh = tokio::task::spawn_blocking(move || { - cairo_hash_and_def_from_dto(c0) - }); - let (class_hash, class_def) = jh - .await - .map_err(block_not_found)? - .map_err(block_not_found)?; - declared_cairo_classes.insert(class_hash); - class_definitions.insert(class_hash, class_def); - } - Class::Cairo1(c1) => { - let jh = tokio::task::spawn_blocking(move || { - sierra_defs_and_hashes_from_dto(c1) - }); - let (sierra_hash, sierra_def, casm_hash, casm) = jh - .await - .map_err(block_not_found)? - .map_err(block_not_found)?; - declared_sierra_classes.insert(sierra_hash, casm_hash); - let class_hash = ClassHash(sierra_hash.0); - class_definitions.insert(class_hash, sierra_def); - casm_definitions.insert(class_hash, casm); - } - } - } - - cache.insert_definitions(hash, class_definitions, casm_definitions); - - let state_commitment = - cache.get_state_commitment(block_hash).unwrap_or_default(); - - Ok(StateUpdate { - block_hash, - // Luckily this field is only used when polling pending which is disabled with p2p - parent_state_commitment: StateCommitment::default(), - state_commitment, - contract_updates: state_update - .contract_updates - .into_iter() - .map(|(k, v)| { - ( - k, - ContractUpdate { - storage: v.storage, - // It does not matter if we mark class updates as "deploy" or "replace" - // as the way those updates are inserted into our storage is "deploy/replace-agnostic" - class: v.class.map(ContractClassUpdate::Deploy), - nonce: v.nonce, - }, - ) - }) - .collect(), - system_contract_updates: state_update.system_contract_updates, - declared_cairo_classes, - declared_sierra_classes, - }) - } - _ => unreachable!("not used in sync"), - }, - } - } - - async fn eth_contract_addresses(&self) -> Result { - self.as_sequencer().eth_contract_addresses().await - } - - #[allow(clippy::too_many_arguments)] - async fn add_invoke_transaction( - &self, - invoke_function: InvokeFunction, - ) -> Result { - self.as_sequencer() - .add_invoke_transaction(invoke_function) - .await - } - - #[allow(clippy::too_many_arguments)] - async fn add_declare_transaction( - &self, - declare: Declare, - token: Option, - ) -> Result { - self.as_sequencer() - .add_declare_transaction(declare, token) - .await - } - - #[allow(clippy::too_many_arguments)] - async fn add_deploy_account( - &self, - deploy_account: DeployAccount, - ) -> Result { - self.as_sequencer().add_deploy_account(deploy_account).await - } - - /// This is a **temporary** measure to keep the sync logic unchanged - /// - /// TODO remove me when sync is changed to use the high level (ie. peer unaware) p2p API - async fn head(&self) -> Result<(BlockNumber, BlockHash), SequencerError> { - match self { - HybridClient::GatewayProxy { sequencer, .. } => sequencer.head().await, - HybridClient::NonPropagatingP2P { head_rx, .. } => { - let head = *head_rx.borrow(); - tracing::trace!(?head, "HybridClient::head"); - head.ok_or(error::block_not_found( - "Haven't received any gossiped head yet", - )) - } - } - } - - async fn block_traces(&self, block: BlockId) -> Result { - // Not used in sync, so we can just always proxy - self.as_sequencer().block_traces(block).await - } - - async fn transaction_trace( - &self, - transaction: TransactionHash, - ) -> Result { - // Not used in sync, so we can just always proxy - self.as_sequencer().transaction_trace(transaction).await - } - - async fn signature(&self, block: BlockId) -> Result { - match self { - HybridClient::GatewayProxy { sequencer, .. } => sequencer.signature(block).await, - HybridClient::NonPropagatingP2P { cache, .. } => cache - .get_signature(match block { - BlockId::Hash(hash) => hash, - _ => unreachable!("not used in sync"), - }) - .ok_or_else(|| { - error::block_not_found(format!("No peers with signature for block {block:?}")) - }), - } - } -} - -fn cairo_hash_and_def_from_dto(c0: Cairo0Class) -> anyhow::Result<(ClassHash, Vec)> { - let from_dto = |x: Vec| { - x.into_iter() - .map(|e| SelectorAndOffset { - selector: EntryPoint(e.selector), - offset: ByteCodeOffset(e.offset), - }) - .collect::>() - }; - - let abi = c0.abi; - let program = c0.program; - let external = from_dto(c0.externals); - let l1_handler = from_dto(c0.l1_handlers); - let constructor = from_dto(c0.constructors); - - let external_entry_points = external.clone(); - let l1_handler_entry_points = l1_handler.clone(); - let constructor_entry_points = constructor.clone(); - - let class_hash = compute_cairo_class_hash( - &abi, - &program, - external_entry_points, - l1_handler_entry_points, - constructor_entry_points, - ) - .context("compute cairo class hash")?; - - #[derive(Debug, Deserialize)] - struct Abi<'a>(#[serde(borrow)] &'a RawValue); - - let class_def = class_definition::Cairo { - abi: Cow::Borrowed(serde_json::from_slice::>(&abi).unwrap().0), - program: serde_json::from_slice(&program) - .context("verify that cairo class program is UTF-8")?, - entry_points_by_type: class_definition::CairoEntryPoints { - external, - l1_handler, - constructor, - }, - }; - let class_def = serde_json::to_vec(&class_def).context("serialize cairo class definition")?; - Ok((class_hash, class_def)) -} - -fn sierra_defs_and_hashes_from_dto( - c1: Cairo1Class, -) -> Result<(SierraHash, Vec, CasmHash, Vec), SequencerError> { - let from_dto = |x: Vec| { - x.into_iter() - .map(|e| SelectorAndFunctionIndex { - selector: EntryPoint(e.selector), - function_idx: e.index, - }) - .collect::>() - }; - - let abi = std::str::from_utf8(&c1.abi) - .map_err(|e| error::block_not_found(format!("Sierra class abi is not valid UTF-8: {e}")))?; - let entry_points = SierraEntryPoints { - external: from_dto(c1.entry_points.externals), - l1_handler: from_dto(c1.entry_points.l1_handlers), - constructor: from_dto(c1.entry_points.constructors), - }; - let program = c1.program; - let contract_class_version = c1.contract_class_version; - let compiled = c1.compiled; - - let program_clone = program.clone(); - let entry_points_clone = entry_points.clone(); - let sierra_hash = SierraHash( - compute_sierra_class_hash( - abi, - program_clone, - &contract_class_version, - entry_points_clone, - ) - .map_err(|e| error::block_not_found(format!("Failed to compute sierra class hash: {e}")))? - .0, - ); - - use cairo_lang_starknet::casm_contract_class::CasmContractClass; - - let ccc: CasmContractClass = serde_json::from_slice(&compiled).map_err(|e| { - error::block_not_found(format!("Sierra class compiled is not valid UTF-8: {e}")) - })?; - - let casm_hash = CasmHash( - Felt::from_be_bytes(ccc.compiled_class_hash().to_be_bytes()).map_err(|e| { - error::block_not_found(format!("Failed to compute casm class hash: {e}")) - })?, - ); - - let class_def = class_definition::Sierra { - abi: Cow::Borrowed(abi), - sierra_program: program, - contract_class_version: contract_class_version.into(), - entry_points_by_type: entry_points, - }; - - let class_def = serde_json::to_vec(&class_def).map_err(|e| { - error::block_not_found(format!("Failed to serialize sierra class definition: {e}")) - })?; - - Ok((sierra_hash, class_def, casm_hash, compiled)) -} - -#[async_trait::async_trait] -impl GossipApi for HybridClient { - async fn propagate_head(&self, block_number: BlockNumber, block_hash: BlockHash) { - use p2p_proto::common::{BlockId, Hash}; - match self { - HybridClient::GatewayProxy { p2p_client, .. } => { - match p2p_client - .propagate_new_head(BlockId { - number: block_number.get(), - hash: Hash(block_hash.0), - }) - .await - { - Ok(_) => {} - Err(error) => tracing::warn!(%error, "Propagating head failed"), - } - } - HybridClient::NonPropagatingP2P { .. } => { - // This is why it's called non-propagating - } - } - } -} +pub mod conv; diff --git a/crates/pathfinder/src/p2p_network/client/conv.rs b/crates/pathfinder/src/p2p_network/client/conv.rs new file mode 100644 index 0000000000..c0bf94c1b6 --- /dev/null +++ b/crates/pathfinder/src/p2p_network/client/conv.rs @@ -0,0 +1,355 @@ +use std::borrow::Cow; + +use anyhow::{Context, Ok}; +use p2p_proto::{ + class::{Cairo0Class, Cairo1Class}, + receipt::{ + DeclareTransactionReceipt, DeployAccountTransactionReceipt, DeployTransactionReceipt, + InvokeTransactionReceipt, L1HandlerTransactionReceipt, + }, +}; +use pathfinder_common::{ + receipt::{BuiltinCounters, ExecutionResources, ExecutionStatus, L2ToL1Message}, + BlockCommitmentSignature, BlockCommitmentSignatureElem, BlockHash, BlockNumber, BlockTimestamp, + ByteCodeOffset, CasmHash, ClassHash, ContractAddress, EntryPoint, EthereumAddress, + EventCommitment, Fee, GasPrice, L2ToL1MessagePayloadElem, SequencerAddress, SierraHash, + StarknetVersion, StateCommitment, TransactionCommitment, TransactionHash, +}; +use pathfinder_crypto::Felt; +use serde::Deserialize; +use serde_json::value::RawValue; +use starknet_gateway_types::{ + class_definition::{self, SierraEntryPoints}, + class_hash::from_parts::{compute_cairo_class_hash, compute_sierra_class_hash}, + request::contract::{SelectorAndFunctionIndex, SelectorAndOffset}, +}; + +/// Represents a simplified [`pathfinder_common::SignedBlockHeader`], ie. excluding class commitment and storage commitment. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SignedBlockHeader { + pub hash: BlockHash, + pub parent_hash: BlockHash, + pub number: BlockNumber, + pub timestamp: BlockTimestamp, + pub eth_l1_gas_price: GasPrice, + pub sequencer_address: SequencerAddress, + pub starknet_version: StarknetVersion, + pub event_commitment: EventCommitment, + pub state_commitment: StateCommitment, + pub transaction_commitment: TransactionCommitment, + pub transaction_count: usize, + pub event_count: usize, + pub signature: BlockCommitmentSignature, +} + +impl TryFrom for SignedBlockHeader { + type Error = anyhow::Error; + + fn try_from(dto: p2p_proto::header::SignedBlockHeader) -> Result { + anyhow::ensure!(dto.signatures.len() == 1, "expected exactly one signature"); + let signature = dto + .signatures + .into_iter() + .map(|sig| BlockCommitmentSignature { + r: BlockCommitmentSignatureElem(sig.r), + s: BlockCommitmentSignatureElem(sig.s), + }) + .next() + .expect("exactly one element"); + Ok(SignedBlockHeader { + hash: BlockHash(dto.block_hash.0), + parent_hash: BlockHash(dto.parent_hash.0), + number: BlockNumber::new(dto.number) + .ok_or(anyhow::anyhow!("block number > i64::MAX"))?, + timestamp: BlockTimestamp::new(dto.time) + .ok_or(anyhow::anyhow!("block timestamp > i64::MAX"))?, + eth_l1_gas_price: dto.gas_price.into(), + sequencer_address: SequencerAddress(dto.sequencer_address.0), + starknet_version: dto.protocol_version.into(), + event_commitment: EventCommitment(dto.events.root.0), + state_commitment: StateCommitment(dto.state.root.0), + transaction_commitment: TransactionCommitment(dto.transactions.root.0), + transaction_count: dto.transactions.n_leaves.try_into()?, + event_count: dto.events.n_leaves.try_into()?, + signature, + }) + } +} + +impl + From<( + pathfinder_common::BlockHeader, + pathfinder_common::BlockCommitmentSignature, + )> for SignedBlockHeader +{ + fn from( + (header, signature): ( + pathfinder_common::BlockHeader, + pathfinder_common::BlockCommitmentSignature, + ), + ) -> Self { + Self { + hash: header.hash, + parent_hash: header.parent_hash, + number: header.number, + timestamp: header.timestamp, + eth_l1_gas_price: header.eth_l1_gas_price, + sequencer_address: header.sequencer_address, + starknet_version: header.starknet_version, + event_commitment: header.event_commitment, + state_commitment: header.state_commitment, + transaction_commitment: header.transaction_commitment, + transaction_count: header.transaction_count, + event_count: header.event_count, + signature, + } + } +} + +/// Represents a simplified [`pathfinder_common::receipt::Receipt`] (events and transaction index excluded). +#[derive(Clone, Default, Debug, PartialEq)] +pub struct Receipt { + pub actual_fee: Option, + pub execution_resources: Option, + pub l2_to_l1_messages: Vec, + pub execution_status: ExecutionStatus, + pub transaction_hash: TransactionHash, +} + +impl From for Receipt { + fn from(x: pathfinder_common::receipt::Receipt) -> Self { + Self { + transaction_hash: x.transaction_hash, + actual_fee: x.actual_fee, + execution_resources: x.execution_resources, + l2_to_l1_messages: x.l2_to_l1_messages, + execution_status: x.execution_status, + } + } +} + +impl TryFrom for Receipt { + type Error = anyhow::Error; + + fn try_from(proto: p2p_proto::receipt::Receipt) -> anyhow::Result + where + Self: Sized, + { + use p2p_proto::receipt::Receipt::{Declare, Deploy, DeployAccount, Invoke, L1Handler}; + + match proto { + Invoke(InvokeTransactionReceipt { common }) + | Declare(DeclareTransactionReceipt { common }) + | L1Handler(L1HandlerTransactionReceipt { common, .. }) + | Deploy(DeployTransactionReceipt { common, .. }) + | DeployAccount(DeployAccountTransactionReceipt { common, .. }) => Ok(Self { + transaction_hash: TransactionHash(common.transaction_hash.0), + actual_fee: Some(Fee(common.actual_fee)), + execution_resources: Some(ExecutionResources { + builtin_instance_counter: BuiltinCounters { + output_builtin: common.execution_resources.builtins.output.into(), + pedersen_builtin: common.execution_resources.builtins.pedersen.into(), + range_check_builtin: common.execution_resources.builtins.range_check.into(), + ecdsa_builtin: common.execution_resources.builtins.ecdsa.into(), + bitwise_builtin: common.execution_resources.builtins.bitwise.into(), + ec_op_builtin: common.execution_resources.builtins.ec_op.into(), + keccak_builtin: common.execution_resources.builtins.keccak.into(), + poseidon_builtin: common.execution_resources.builtins.poseidon.into(), + segment_arena_builtin: 0, + }, + n_steps: common.execution_resources.steps.into(), + n_memory_holes: common.execution_resources.memory_holes.into(), + }), + l2_to_l1_messages: common + .messages_sent + .into_iter() + .map(|x| L2ToL1Message { + from_address: ContractAddress(x.from_address), + payload: x + .payload + .into_iter() + .map(L2ToL1MessagePayloadElem) + .collect(), + to_address: EthereumAddress(x.to_address.0), + }) + .collect(), + execution_status: if common.revert_reason.is_empty() { + ExecutionStatus::Succeeded + } else { + ExecutionStatus::Reverted { + reason: common.revert_reason, + } + }, + }), + } + } +} + +pub fn cairo_hash_and_def_from_dto(c0: Cairo0Class) -> anyhow::Result<(ClassHash, Vec)> { + let from_dto = |x: Vec| { + x.into_iter() + .map(|e| SelectorAndOffset { + selector: EntryPoint(e.selector), + offset: ByteCodeOffset(e.offset), + }) + .collect::>() + }; + + let abi = c0.abi; + let program = c0.program; + let external = from_dto(c0.externals); + let l1_handler = from_dto(c0.l1_handlers); + let constructor = from_dto(c0.constructors); + + let external_entry_points = external.clone(); + let l1_handler_entry_points = l1_handler.clone(); + let constructor_entry_points = constructor.clone(); + + let class_hash = compute_cairo_class_hash( + &abi, + &program, + external_entry_points, + l1_handler_entry_points, + constructor_entry_points, + ) + .context("compute cairo class hash")?; + + #[derive(Debug, Deserialize)] + struct Abi<'a>(#[serde(borrow)] &'a RawValue); + + let class_def = class_definition::Cairo { + abi: Cow::Borrowed(serde_json::from_slice::>(&abi).unwrap().0), + program: serde_json::from_slice(&program) + .context("verify that cairo class program is UTF-8")?, + entry_points_by_type: class_definition::CairoEntryPoints { + external, + l1_handler, + constructor, + }, + }; + let class_def = serde_json::to_vec(&class_def).context("serialize cairo class definition")?; + Ok((class_hash, class_def)) +} + +pub fn cairo_def_from_dto(c0: Cairo0Class) -> anyhow::Result> { + let from_dto = |x: Vec| { + x.into_iter() + .map(|e| SelectorAndOffset { + selector: EntryPoint(e.selector), + offset: ByteCodeOffset(e.offset), + }) + .collect::>() + }; + + let abi = c0.abi; + let program = c0.program; + let external = from_dto(c0.externals); + let l1_handler = from_dto(c0.l1_handlers); + let constructor = from_dto(c0.constructors); + + #[derive(Debug, Deserialize)] + struct Abi<'a>(#[serde(borrow)] &'a RawValue); + + let class_def = class_definition::Cairo { + abi: Cow::Borrowed(serde_json::from_slice::>(&abi).unwrap().0), + program: serde_json::from_slice(&program) + .context("verify that cairo class program is UTF-8")?, + entry_points_by_type: class_definition::CairoEntryPoints { + external, + l1_handler, + constructor, + }, + }; + let class_def = serde_json::to_vec(&class_def).context("serialize cairo class definition")?; + Ok(class_def) +} + +pub fn sierra_defs_and_hashes_from_dto( + c1: Cairo1Class, +) -> anyhow::Result<(SierraHash, Vec, CasmHash, Vec)> { + let from_dto = |x: Vec| { + x.into_iter() + .map(|e| SelectorAndFunctionIndex { + selector: EntryPoint(e.selector), + function_idx: e.index, + }) + .collect::>() + }; + + let abi = std::str::from_utf8(&c1.abi).context("parsing abi as utf8")?; + let entry_points = SierraEntryPoints { + external: from_dto(c1.entry_points.externals), + l1_handler: from_dto(c1.entry_points.l1_handlers), + constructor: from_dto(c1.entry_points.constructors), + }; + let program = c1.program; + let contract_class_version = c1.contract_class_version; + let compiled = c1.compiled; + + let program_clone = program.clone(); + let entry_points_clone = entry_points.clone(); + let sierra_hash = SierraHash( + compute_sierra_class_hash( + abi, + program_clone, + &contract_class_version, + entry_points_clone, + ) + .context("compute sierra clash hash")? + .0, + ); + + use cairo_lang_starknet::casm_contract_class::CasmContractClass; + + let ccc: CasmContractClass = + serde_json::from_slice(&compiled).context("deserialize casm class")?; + + let casm_hash = CasmHash( + Felt::from_be_bytes(ccc.compiled_class_hash().to_be_bytes()) + .context("compute casm class hash")?, + ); + + let class_def = class_definition::Sierra { + abi: Cow::Borrowed(abi), + sierra_program: program, + contract_class_version: contract_class_version.into(), + entry_points_by_type: entry_points, + }; + + let class_def = serde_json::to_vec(&class_def).context("serialize sierra class definition")?; + + Ok((sierra_hash, class_def, casm_hash, compiled)) +} + +pub fn sierra_defs_from_dto(c1: Cairo1Class) -> anyhow::Result<(Vec, Vec)> { + let from_dto = |x: Vec| { + x.into_iter() + .map(|e| SelectorAndFunctionIndex { + selector: EntryPoint(e.selector), + function_idx: e.index, + }) + .collect::>() + }; + + let abi = std::str::from_utf8(&c1.abi).context("parsing abi as utf8")?; + let entry_points = SierraEntryPoints { + external: from_dto(c1.entry_points.externals), + l1_handler: from_dto(c1.entry_points.l1_handlers), + constructor: from_dto(c1.entry_points.constructors), + }; + let program = c1.program; + let contract_class_version = c1.contract_class_version; + let compiled_def = c1.compiled; + + let sierra_def = class_definition::Sierra { + abi: Cow::Borrowed(abi), + sierra_program: program, + contract_class_version: contract_class_version.into(), + entry_points_by_type: entry_points, + }; + + let sierra_def = + serde_json::to_vec(&sierra_def).context("serialize sierra class definition")?; + + Ok((sierra_def, compiled_def)) +} diff --git a/crates/pathfinder/src/p2p_network/client/types.rs b/crates/pathfinder/src/p2p_network/client/types.rs deleted file mode 100644 index ea64052ea0..0000000000 --- a/crates/pathfinder/src/p2p_network/client/types.rs +++ /dev/null @@ -1,76 +0,0 @@ -use p2p_proto::receipt::{ - DeclareTransactionReceipt, DeployAccountTransactionReceipt, DeployTransactionReceipt, - InvokeTransactionReceipt, L1HandlerTransactionReceipt, -}; -use pathfinder_common::{ - receipt::{BuiltinCounters, ExecutionResources, L2ToL1Message}, - ContractAddress, EthereumAddress, Fee, L2ToL1MessagePayloadElem, TransactionHash, -}; - -/// Represents a simplified receipt (events and execution status excluded). -/// -/// This type is not in the `p2p` to avoid `p2p` dependence on `starknet_gateway_types`. -#[derive(Clone, Debug, PartialEq)] -pub struct Receipt { - pub transaction_hash: TransactionHash, - pub actual_fee: Fee, - pub execution_resources: ExecutionResources, - pub l2_to_l1_messages: Vec, - // Empty means not reverted - pub revert_error: String, -} - -impl TryFrom for Receipt { - type Error = anyhow::Error; - - fn try_from(proto: p2p_proto::receipt::Receipt) -> anyhow::Result - where - Self: Sized, - { - use p2p_proto::receipt::Receipt::{Declare, Deploy, DeployAccount, Invoke, L1Handler}; - - match proto { - Invoke(InvokeTransactionReceipt { common }) - | Declare(DeclareTransactionReceipt { common }) - | L1Handler(L1HandlerTransactionReceipt { common, .. }) - | Deploy(DeployTransactionReceipt { common, .. }) - | DeployAccount(DeployAccountTransactionReceipt { common, .. }) => Ok(Self { - transaction_hash: TransactionHash(common.transaction_hash.0), - actual_fee: Fee(common.actual_fee), - execution_resources: ExecutionResources { - builtin_instance_counter: BuiltinCounters { - output_builtin: common.execution_resources.builtins.output.into(), - pedersen_builtin: common.execution_resources.builtins.pedersen.into(), - range_check_builtin: common.execution_resources.builtins.range_check.into(), - ecdsa_builtin: common.execution_resources.builtins.ecdsa.into(), - bitwise_builtin: common.execution_resources.builtins.bitwise.into(), - ec_op_builtin: common.execution_resources.builtins.ec_op.into(), - keccak_builtin: common.execution_resources.builtins.keccak.into(), - poseidon_builtin: common.execution_resources.builtins.poseidon.into(), - segment_arena_builtin: common - .execution_resources - .builtins - .segment_arena - .into(), - }, - n_steps: common.execution_resources.steps.into(), - n_memory_holes: common.execution_resources.memory_holes.into(), - }, - l2_to_l1_messages: common - .messages_sent - .into_iter() - .map(|x| L2ToL1Message { - from_address: ContractAddress(x.from_address), - payload: x - .payload - .into_iter() - .map(L2ToL1MessagePayloadElem) - .collect(), - to_address: EthereumAddress(x.to_address.0), - }) - .collect(), - revert_error: common.revert_reason, - }), - } - } -} diff --git a/crates/pathfinder/src/p2p_network/sync_handlers.rs b/crates/pathfinder/src/p2p_network/sync_handlers.rs index 8fbb88dd03..c7900b8f16 100644 --- a/crates/pathfinder/src/p2p_network/sync_handlers.rs +++ b/crates/pathfinder/src/p2p_network/sync_handlers.rs @@ -1,21 +1,18 @@ use anyhow::Context; use futures::channel::mpsc; use futures::SinkExt; -use p2p_proto::block::{ - BlockBodiesRequest, BlockBodiesResponse, BlockBodyMessage, BlockHeadersRequest, - BlockHeadersResponse, BlockHeadersResponsePart, Signatures, -}; +use p2p_proto::class::{Class, ClassesRequest, ClassesResponse}; use p2p_proto::common::{ - BlockId, BlockNumberOrHash, ConsensusSignature, Direction, Fin, Hash, Iteration, Step, -}; -use p2p_proto::consts::MAX_HEADERS_PER_MESSAGE; -use p2p_proto::event::{Events, EventsRequest, EventsResponse, EventsResponseKind}; -use p2p_proto::receipt::{Receipts, ReceiptsRequest, ReceiptsResponse, ReceiptsResponseKind}; -use p2p_proto::state::{Class, Classes}; -use p2p_proto::transaction::{ - Transactions, TransactionsRequest, TransactionsResponse, TransactionsResponseKind, + Address, BlockNumberOrHash, ConsensusSignature, Direction, Hash, Iteration, Merkle, Patricia, + Step, }; -use pathfinder_common::{BlockHash, BlockNumber, CasmHash, ClassHash, SierraHash}; +use p2p_proto::event::{EventsRequest, EventsResponse}; +use p2p_proto::header::{BlockHeadersRequest, BlockHeadersResponse, SignedBlockHeader}; +use p2p_proto::receipt::{ReceiptsRequest, ReceiptsResponse}; +use p2p_proto::state::{ContractDiff, ContractStoredValue, StateDiffsRequest, StateDiffsResponse}; +use p2p_proto::transaction::{TransactionsRequest, TransactionsResponse}; +use pathfinder_common::{BlockHash, BlockNumber}; +use pathfinder_crypto::Felt; use pathfinder_storage::Storage; use pathfinder_storage::Transaction; use starknet_gateway_types::class_definition; @@ -24,7 +21,7 @@ pub mod conv; #[cfg(test)] mod tests; -use conv::ToProto; +use conv::ToDto; #[cfg(not(test))] const MAX_BLOCKS_COUNT: u64 = 100; @@ -34,27 +31,30 @@ const MAX_COUNT_IN_TESTS: u64 = 10; #[cfg(test)] const MAX_BLOCKS_COUNT: u64 = MAX_COUNT_IN_TESTS; -const _: () = assert!( - MAX_BLOCKS_COUNT <= MAX_HEADERS_PER_MESSAGE as u64, - "All requested block headers, limited up to MAX_BLOCKS_COUNT should fit into one reply" -); - pub async fn get_headers( storage: Storage, request: BlockHeadersRequest, - mut tx: mpsc::Sender, + tx: mpsc::Sender, ) -> anyhow::Result<()> { - let response = spawn_blocking_get(request, storage, blocking::get_headers).await?; - tx.send(response).await.context("Sending response") + let responses = spawn_blocking_get(request, storage, blocking::get_headers).await?; + send(tx, responses).await } -// TODO consider batching db ops instead doing all in bulk if it's more performant -pub async fn get_bodies( +pub async fn get_classes( storage: Storage, - request: BlockBodiesRequest, - tx: mpsc::Sender, + request: ClassesRequest, + tx: mpsc::Sender, ) -> anyhow::Result<()> { - let responses = spawn_blocking_get(request, storage, blocking::get_bodies).await?; + let responses = spawn_blocking_get(request, storage, blocking::get_classes).await?; + send(tx, responses).await +} + +pub async fn get_state_diffs( + storage: Storage, + request: StateDiffsRequest, + tx: mpsc::Sender, +) -> anyhow::Result<()> { + let responses = spawn_blocking_get(request, storage, blocking::get_state_diffs).await?; send(tx, responses).await } @@ -91,16 +91,22 @@ pub(crate) mod blocking { pub(crate) fn get_headers( tx: Transaction<'_>, request: BlockHeadersRequest, - ) -> anyhow::Result { - let parts = iterate(tx, request.iteration, get_header)?; - Ok(BlockHeadersResponse { parts }) + ) -> anyhow::Result> { + iterate(tx, request.iteration, get_header) + } + + pub(crate) fn get_classes( + tx: Transaction<'_>, + request: ClassesRequest, + ) -> anyhow::Result> { + iterate(tx, request.iteration, get_classes_for_block) } - pub(crate) fn get_bodies( + pub(crate) fn get_state_diffs( tx: Transaction<'_>, - request: BlockBodiesRequest, - ) -> anyhow::Result> { - iterate(tx, request.iteration, get_body) + request: StateDiffsRequest, + ) -> anyhow::Result> { + iterate(tx, request.iteration, get_state_diff) } pub(crate) fn get_transactions( @@ -128,88 +134,71 @@ pub(crate) mod blocking { fn get_header( tx: &Transaction<'_>, block_number: BlockNumber, - parts: &mut Vec, + responses: &mut Vec, ) -> anyhow::Result { if let Some(header) = tx.block_header(block_number.into())? { - let hash = Hash(header.hash.0); - parts.push(BlockHeadersResponsePart::Header(Box::new( - header.to_proto(), - ))); - if let Some(signature) = tx.signature(block_number.into())? { - parts.push(BlockHeadersResponsePart::Signatures(Signatures { - block: BlockId { - number: block_number.get(), - hash, + let txn_count = header + .transaction_count + .try_into() + .context("invalid transaction count")?; + + responses.push(BlockHeadersResponse::Header(Box::new(SignedBlockHeader { + block_hash: Hash(header.hash.0), + parent_hash: Hash(header.parent_hash.0), + number: header.number.get(), + time: header.timestamp.get(), + sequencer_address: Address(header.sequencer_address.0), + state_diff_commitment: Hash(Felt::ZERO), // TODO + state: Patricia { + height: 251, + root: Hash(header.state_commitment.0), + }, + transactions: Merkle { + n_leaves: txn_count, + root: Hash(header.transaction_commitment.0), }, + events: Merkle { + n_leaves: header + .event_count + .try_into() + .context("invalid event count")?, + root: Hash(header.event_commitment.0), + }, + receipts: Merkle { + n_leaves: txn_count, + root: Hash(Felt::ZERO), // TODO + }, + protocol_version: header.starknet_version.take_inner(), + gas_price: Felt::from_u128(header.eth_l1_gas_price.0), + num_storage_diffs: 0, // TODO + num_nonce_updates: 0, // TODO + num_declared_classes: 0, // TODO + num_deployed_contracts: 0, // TODO signatures: vec![ConsensusSignature { r: signature.r.0, s: signature.s.0, }], - })); + }))); } - parts.push(BlockHeadersResponsePart::Fin(Fin::ok())); - Ok(true) } else { Ok(false) } } -#[derive(Debug, Clone, Copy)] -#[cfg_attr(test, derive(fake::Dummy))] -enum ClassId { - Cairo(ClassHash), - Sierra(SierraHash, CasmHash), -} - -impl ClassId { - pub fn class_hash(&self) -> ClassHash { - match self { - ClassId::Cairo(class_hash) => *class_hash, - ClassId::Sierra(sierra_hash, _) => ClassHash(sierra_hash.0), - } - } -} - #[derive(Debug, Clone)] enum ClassDefinition { Cairo(Vec), Sierra { sierra: Vec, casm: Vec }, } -fn get_body( +fn get_classes_for_block( tx: &Transaction<'_>, block_number: BlockNumber, - responses: &mut Vec, + responses: &mut Vec, ) -> anyhow::Result { - let Some(state_diff) = tx.state_update(block_number.into())? else { - return Ok(false); - }; - - let new_classes = state_diff - .declared_cairo_classes - .iter() - .map(|&class_hash| ClassId::Cairo(class_hash)) - .chain( - state_diff - .declared_sierra_classes - .iter() - .map(|(&sierra_hash, &casm_hash)| ClassId::Sierra(sierra_hash, casm_hash)), - ) - .collect::>(); - let block_hash = state_diff.block_hash; - let id = Some(BlockId { - number: block_number.get(), - hash: Hash(block_hash.0), - }); - - responses.push(BlockBodiesResponse { - id, - body_message: BlockBodyMessage::Diff(state_diff.to_proto()), - }); - let get_definition = |block_number: BlockNumber, class_hash| -> anyhow::Result { let definition = tx @@ -231,52 +220,108 @@ fn get_body( }) }; - classes( - block_number, - block_hash, - new_classes, - responses, - get_definition, - )?; - - responses.push(BlockBodiesResponse { - id, - body_message: BlockBodyMessage::Fin(Fin::ok()), - }); + let declared_classes = tx.declared_classes_at(block_number.into())?; + let mut classes = Vec::new(); + + for class_hash in declared_classes { + let class_definition = get_definition(block_number, class_hash)?; + + let class: Class = match class_definition { + ClassDefinition::Cairo(definition) => { + let cairo_class = + serde_json::from_slice::>(&definition)?; + Class::Cairo0 { + class: def_into_dto::cairo(cairo_class), + domain: 0, // TODO + class_hash: Hash(class_hash.0), + } + } + ClassDefinition::Sierra { sierra, casm } => { + let sierra_class = serde_json::from_slice::>(&sierra)?; + + Class::Cairo1 { + class: def_into_dto::sierra(sierra_class, casm), + domain: 0, // TODO + class_hash: Hash(class_hash.0), + } + } + }; + classes.push(ClassesResponse::Class(class)); + } + + responses.extend(classes); + Ok(true) } -fn get_transactions_for_block( +fn get_state_diff( tx: &Transaction<'_>, block_number: BlockNumber, - responses: &mut Vec, + responses: &mut Vec, ) -> anyhow::Result { - let Some(block_hash) = tx.block_hash(block_number.into())? else { + let Some(state_diff) = tx.state_update(block_number.into())? else { return Ok(false); }; + state_diff + .contract_updates + .into_iter() + .for_each(|(address, update)| { + responses.push(StateDiffsResponse::ContractDiff(ContractDiff { + address: Address(address.0), + nonce: update.nonce.map(|n| n.0), + class_hash: update.class.as_ref().map(|c| c.class_hash().0), + is_replaced: update.class.map(|c| c.is_replaced()), + values: update + .storage + .into_iter() + .map(|(k, v)| ContractStoredValue { + key: k.0, + value: v.0, + }) + .collect(), + domain: 0, // TODO + })) + }); + + state_diff + .system_contract_updates + .into_iter() + .for_each(|(address, update)| { + responses.push(StateDiffsResponse::ContractDiff(ContractDiff { + address: Address(address.0), + nonce: None, + class_hash: None, + is_replaced: None, + values: update + .storage + .into_iter() + .map(|(k, v)| ContractStoredValue { + key: k.0, + value: v.0, + }) + .collect(), + domain: 0, // TODO + })) + }); + + Ok(true) +} + +fn get_transactions_for_block( + tx: &Transaction<'_>, + block_number: BlockNumber, + responses: &mut Vec, +) -> anyhow::Result { let Some(txn_data) = tx.transaction_data_for_block(block_number.into())? else { return Ok(false); }; - let id = Some(BlockId { - number: block_number.get(), - hash: Hash(block_hash.0), - }); - - responses.push(TransactionsResponse { - id, - kind: TransactionsResponseKind::Transactions(Transactions { - items: txn_data - .into_iter() - .map(|(txn, _)| txn.to_proto()) - .collect(), - }), - }); - responses.push(TransactionsResponse { - id, - kind: TransactionsResponseKind::Fin(Fin::ok()), - }); + responses.extend( + txn_data + .into_iter() + .map(|(tnx, _)| TransactionsResponse::Transaction(tnx.to_dto())), + ); Ok(true) } @@ -286,29 +331,16 @@ fn get_receipts_for_block( block_number: BlockNumber, responses: &mut Vec, ) -> anyhow::Result { - let Some(block_hash) = tx.block_hash(block_number.into())? else { - return Ok(false); - }; - let Some(txn_data) = tx.transaction_data_for_block(block_number.into())? else { return Ok(false); }; - let id = Some(BlockId { - number: block_number.get(), - hash: Hash(block_hash.0), - }); - - responses.push(ReceiptsResponse { - id, - kind: ReceiptsResponseKind::Receipts(Receipts { - items: txn_data.into_iter().map(ToProto::to_proto).collect(), - }), - }); - responses.push(ReceiptsResponse { - id, - kind: ReceiptsResponseKind::Fin(Fin::ok()), - }); + responses.extend( + txn_data + .into_iter() + .map(ToDto::to_dto) + .map(ReceiptsResponse::Receipt), + ); Ok(true) } @@ -318,43 +350,24 @@ fn get_events_for_block( block_number: BlockNumber, responses: &mut Vec, ) -> anyhow::Result { - let Some(block_hash) = tx.block_hash(block_number.into())? else { - return Ok(false); - }; - let Some(txn_data) = tx.transaction_data_for_block(block_number.into())? else { return Ok(false); }; - let items = txn_data - .into_iter() - .flat_map(|(_, r)| { - std::iter::repeat(r.transaction_hash) - .zip(r.events) - .map(ToProto::to_proto) - }) - .collect::>(); - - let id = Some(BlockId { - number: block_number.get(), - hash: Hash(block_hash.0), - }); - - responses.push(EventsResponse { - id, - kind: EventsResponseKind::Events(Events { items }), - }); - responses.push(EventsResponse { - id, - kind: EventsResponseKind::Fin(Fin::ok()), - }); + responses.extend(txn_data.into_iter().flat_map(|(_, r)| { + std::iter::repeat(r.transaction_hash) + .zip(r.events) + .map(ToDto::to_dto) + .map(EventsResponse::Event) + })); Ok(true) } -/// `block_handler` returns Ok(true) if the iteration should continue and is -/// responsible for delimiting block data with `Fin::ok()` marker. -fn iterate>( +/// Assupmtions: +/// - `block_handler` returns `Ok(true)` if the iteration should continue. +/// - `T::default()` always returns the `Fin` variant of the implementing type. +fn iterate( tx: Transaction<'_>, iteration: Iteration, block_handler: impl Fn(&Transaction<'_>, BlockNumber, &mut Vec) -> anyhow::Result, @@ -367,30 +380,24 @@ fn iterate>( } = iteration; if limit == 0 { - return Ok(vec![T::from(Fin::ok())]); + return Ok(vec![T::default()]); } let mut block_number = match get_start_block_number(start, &tx)? { Some(x) => x, None => { - return Ok(vec![T::from(Fin::unknown())]); + return Ok(vec![T::default()]); } }; - let (limit, mut ending_marker) = if limit > MAX_BLOCKS_COUNT { - (MAX_BLOCKS_COUNT, Some(Fin::too_much())) - } else { - (limit, None) - }; - let mut responses = Vec::new(); + let limit = limit.min(MAX_BLOCKS_COUNT); for i in 0..limit { if block_handler(&tx, block_number, &mut responses)? { - // Block data retrieved successfully, `block_handler` should add `Fin::ok()` marker on its own + // Block data retrieved successfully } else { // No such block - ending_marker = Some(Fin::unknown()); break; } @@ -399,16 +406,13 @@ fn iterate>( Some(x) => x, None => { // Out of range block number value - ending_marker = Some(Fin::unknown()); break; } }; } } - if let Some(end) = ending_marker { - responses.push(T::from(end)); - } + responses.push(T::default()); Ok(responses) } @@ -423,47 +427,6 @@ fn get_start_block_number( }) } -fn classes( - block_number: BlockNumber, - block_hash: BlockHash, - new_class_ids: Vec, - responses: &mut Vec, - mut class_definition_getter: impl FnMut(BlockNumber, ClassHash) -> anyhow::Result, -) -> anyhow::Result<()> { - let mut classes = Vec::new(); - - for class_id in new_class_ids { - let class_definition = class_definition_getter(block_number, class_id.class_hash())?; - - let class: Class = match (class_id, class_definition) { - (ClassId::Cairo(_), ClassDefinition::Cairo(definition)) => { - let cairo_class = - serde_json::from_slice::>(&definition)?; - Class::Cairo0(def_into_dto::cairo(cairo_class)) - } - (ClassId::Sierra(_, _), ClassDefinition::Sierra { sierra, casm }) => { - let sierra_class = serde_json::from_slice::>(&sierra)?; - Class::Cairo1(def_into_dto::sierra(sierra_class, casm)) - } - _ => anyhow::bail!("Class definition type mismatch"), - }; - classes.push(class); - } - - responses.push(BlockBodiesResponse { - id: Some(BlockId { - number: block_number.get(), - hash: Hash(block_hash.0), - }), - body_message: BlockBodyMessage::Classes(Classes { - domain: 0, // TODO - classes, - }), - }); - - Ok(()) -} - async fn spawn_blocking_get( request: Request, storage: Storage, @@ -523,7 +486,7 @@ fn get_next_block_number( } mod def_into_dto { - use p2p_proto::state::{ + use p2p_proto::class::{ Cairo0Class, Cairo1Class, Cairo1EntryPoints, EntryPoint, SierraEntryPoint, }; use starknet_gateway_types::request::contract::{SelectorAndFunctionIndex, SelectorAndOffset}; diff --git a/crates/pathfinder/src/p2p_network/sync_handlers/conv.rs b/crates/pathfinder/src/p2p_network/sync_handlers/conv.rs index 2ec7614ab8..0fa19945c6 100644 --- a/crates/pathfinder/src/p2p_network/sync_handlers/conv.rs +++ b/crates/pathfinder/src/p2p_network/sync_handlers/conv.rs @@ -1,121 +1,31 @@ //! Workaround for the orphan rule - implement conversion fns for types ourside our crate. -use p2p_proto::common::{Address, Hash, Merkle, Patricia}; +use p2p_proto::common::{Address, Hash}; use p2p_proto::receipt::EthereumAddress; use p2p_proto::receipt::{ execution_resources::BuiltinCounter, DeclareTransactionReceipt, DeployAccountTransactionReceipt, DeployTransactionReceipt, ExecutionResources, InvokeTransactionReceipt, L1HandlerTransactionReceipt, MessageToL1, ReceiptCommon, }; -use p2p_proto::state::{ContractDiff, ContractStoredValue, StateDiff}; use p2p_proto::transaction::{AccountSignature, ResourceBounds}; use pathfinder_common::receipt::Receipt as CommonReceipt; use pathfinder_common::transaction::DataAvailabilityMode; use pathfinder_common::transaction::Transaction as CommonTransaction; -use pathfinder_common::{ - event::Event, state_update::ContractUpdate, transaction::ResourceBound, - transaction::Transaction, BlockHeader, StateUpdate, -}; -use pathfinder_common::{ - AccountDeploymentDataElem, PaymasterDataElem, StateCommitment, TransactionHash, -}; +use pathfinder_common::{event::Event, transaction::ResourceBound, transaction::Transaction}; +use pathfinder_common::{AccountDeploymentDataElem, PaymasterDataElem, TransactionHash}; use pathfinder_crypto::Felt; -use std::time::{Duration, SystemTime}; - -pub trait ToProto { - fn to_proto(self) -> T; -} - -impl ToProto for BlockHeader { - fn to_proto(self) -> p2p_proto::block::BlockHeader { - const ZERO_MERKLE: Merkle = Merkle { - n_leaves: 0, - root: Hash(Felt::ZERO), - }; - const ZERO_PATRICIA: Patricia = Patricia { - height: 0, - root: Hash(Felt::ZERO), - }; - p2p_proto::block::BlockHeader { - parent_hash: Hash(self.parent_hash.0), - number: self.number.get(), - time: SystemTime::UNIX_EPOCH // FIXME Dunno how to convert - .checked_add(Duration::from_secs(self.timestamp.get())) - .unwrap(), - sequencer_address: Address(self.sequencer_address.0), - // FIXME: calculate the merkles et al. - state_diffs: ZERO_MERKLE, - state: ZERO_PATRICIA, - proof_fact: Hash(Felt::ZERO), - transactions: ZERO_MERKLE, - events: ZERO_MERKLE, - receipts: ZERO_MERKLE, - // FIXME extra fields added to make sync work - hash: Hash(self.hash.0), - gas_price: self.eth_l1_gas_price.0.to_be_bytes().into(), - starknet_version: self.starknet_version.take_inner(), - state_commitment: (self.state_commitment != StateCommitment::ZERO) - .then_some(Hash(self.state_commitment.0)), - } - } -} -impl ToProto for StateUpdate { - fn to_proto(self) -> p2p_proto::state::StateDiff { - StateDiff { - domain: 0, // FIXME there will initially be 2 trees, dunno which id is which - contract_diffs: self - .system_contract_updates - .into_iter() - .map(|(address, update)| { - let address = Address(address.0); - let values = update - .storage - .into_iter() - .map(|(storage_address, storage_value)| ContractStoredValue { - key: storage_address.0, - value: storage_value.0, - }) - .collect(); - ContractDiff { - address, - nonce: None, - class_hash: None, - values, - } - }) - .chain(self.contract_updates.into_iter().map(|(address, update)| { - let address = Address(address.0); - let ContractUpdate { - storage, - class, - nonce, - } = update; - let values = storage - .into_iter() - .map(|(storage_address, storage_value)| ContractStoredValue { - key: storage_address.0, - value: storage_value.0, - }) - .collect(); - ContractDiff { - address, - nonce: nonce.map(|n| n.0), - class_hash: class.map(|c| c.class_hash().0), - values, - } - })) - .collect(), - } - } +/// Convert pathfinder common (ie. core) type to a p2p dto type +pub trait ToDto { + fn to_dto(self) -> T; } -impl ToProto for Transaction { - fn to_proto(self) -> p2p_proto::transaction::Transaction { +impl ToDto for Transaction { + fn to_dto(self) -> p2p_proto::transaction::Transaction { use p2p_proto::transaction as proto; use pathfinder_common::transaction::TransactionVariant::*; - match self.variant { - DeclareV0(x) => proto::Transaction::DeclareV0(proto::DeclareV0 { + let variant = match self.variant { + DeclareV0(x) => proto::TransactionVariant::DeclareV0(proto::DeclareV0 { sender: Address(x.sender_address.0), max_fee: x.max_fee.0, signature: AccountSignature { @@ -123,7 +33,7 @@ impl ToProto for Transaction { }, class_hash: Hash(x.class_hash.0), }), - DeclareV1(x) => proto::Transaction::DeclareV1(proto::DeclareV1 { + DeclareV1(x) => proto::TransactionVariant::DeclareV1(proto::DeclareV1 { sender: Address(x.sender_address.0), max_fee: x.max_fee.0, signature: AccountSignature { @@ -132,7 +42,7 @@ impl ToProto for Transaction { class_hash: Hash(x.class_hash.0), nonce: x.nonce.0, }), - DeclareV2(x) => proto::Transaction::DeclareV2(proto::DeclareV2 { + DeclareV2(x) => proto::TransactionVariant::DeclareV2(proto::DeclareV2 { sender: Address(x.sender_address.0), max_fee: x.max_fee.0, signature: AccountSignature { @@ -142,7 +52,7 @@ impl ToProto for Transaction { nonce: x.nonce.0, compiled_class_hash: x.compiled_class_hash.0, }), - DeclareV3(x) => proto::Transaction::DeclareV3(proto::DeclareV3 { + DeclareV3(x) => proto::TransactionVariant::DeclareV3(proto::DeclareV3 { sender: Address(x.sender_address.0), signature: AccountSignature { parts: x.signature.into_iter().map(|s| s.0).collect(), @@ -151,8 +61,8 @@ impl ToProto for Transaction { nonce: x.nonce.0, compiled_class_hash: x.compiled_class_hash.0, resource_bounds: ResourceBounds { - l1_gas: x.resource_bounds.l1_gas.to_proto(), - l2_gas: x.resource_bounds.l2_gas.to_proto(), + l1_gas: x.resource_bounds.l1_gas.to_dto(), + l2_gas: x.resource_bounds.l2_gas.to_dto(), }, tip: x.tip.0.into(), paymaster_data: Address( @@ -167,50 +77,54 @@ impl ToProto for Transaction { .unwrap_or(&AccountDeploymentDataElem::ZERO) .0, ), // TODO - nonce_domain: x.nonce_data_availability_mode.to_proto(), - fee_domain: x.fee_data_availability_mode.to_proto(), + nonce_domain: x.nonce_data_availability_mode.to_dto(), + fee_domain: x.fee_data_availability_mode.to_dto(), }), - Deploy(x) => proto::Transaction::Deploy(proto::Deploy { + Deploy(x) => proto::TransactionVariant::Deploy(proto::Deploy { class_hash: Hash(x.class_hash.0), address_salt: x.contract_address_salt.0, calldata: x.constructor_calldata.into_iter().map(|c| c.0).collect(), - address: Address(x.contract_address.0), + // address: Address(x.contract_address.0), FIXME // Only these two values are allowed in storage version: if x.version.is_zero() { 0 } else { 1 }, }), - DeployAccountV0V1(x) => proto::Transaction::DeployAccountV1(proto::DeployAccountV1 { - max_fee: x.max_fee.0, - signature: AccountSignature { - parts: x.signature.into_iter().map(|s| s.0).collect(), - }, - class_hash: Hash(x.class_hash.0), - nonce: x.nonce.0, - address_salt: x.contract_address_salt.0, - constructor_calldata: x.constructor_calldata.into_iter().map(|c| c.0).collect(), - }), - DeployAccountV3(x) => proto::Transaction::DeployAccountV3(proto::DeployAccountV3 { - signature: AccountSignature { - parts: x.signature.into_iter().map(|s| s.0).collect(), - }, - class_hash: Hash(x.class_hash.0), - nonce: x.nonce.0, - address_salt: x.contract_address_salt.0, - calldata: x.constructor_calldata.into_iter().map(|c| c.0).collect(), - resource_bounds: ResourceBounds { - l1_gas: x.resource_bounds.l1_gas.to_proto(), - l2_gas: x.resource_bounds.l2_gas.to_proto(), - }, - tip: x.tip.0.into(), - paymaster_data: Address( - x.paymaster_data - .first() - .unwrap_or(&PaymasterDataElem::ZERO) - .0, - ), // TODO - nonce_domain: x.nonce_data_availability_mode.to_proto(), - fee_domain: x.fee_data_availability_mode.to_proto(), - }), - InvokeV0(x) => proto::Transaction::InvokeV0(proto::InvokeV0 { + DeployAccountV0V1(x) => { + proto::TransactionVariant::DeployAccountV1(proto::DeployAccountV1 { + max_fee: x.max_fee.0, + signature: AccountSignature { + parts: x.signature.into_iter().map(|s| s.0).collect(), + }, + class_hash: Hash(x.class_hash.0), + nonce: x.nonce.0, + address_salt: x.contract_address_salt.0, + calldata: x.constructor_calldata.into_iter().map(|c| c.0).collect(), + }) + } + DeployAccountV3(x) => { + proto::TransactionVariant::DeployAccountV3(proto::DeployAccountV3 { + signature: AccountSignature { + parts: x.signature.into_iter().map(|s| s.0).collect(), + }, + class_hash: Hash(x.class_hash.0), + nonce: x.nonce.0, + address_salt: x.contract_address_salt.0, + calldata: x.constructor_calldata.into_iter().map(|c| c.0).collect(), + resource_bounds: ResourceBounds { + l1_gas: x.resource_bounds.l1_gas.to_dto(), + l2_gas: x.resource_bounds.l2_gas.to_dto(), + }, + tip: x.tip.0.into(), + paymaster_data: Address( + x.paymaster_data + .first() + .unwrap_or(&PaymasterDataElem::ZERO) + .0, + ), // TODO + nonce_domain: x.nonce_data_availability_mode.to_dto(), + fee_domain: x.fee_data_availability_mode.to_dto(), + }) + } + InvokeV0(x) => proto::TransactionVariant::InvokeV0(proto::InvokeV0 { max_fee: x.max_fee.0, signature: AccountSignature { parts: x.signature.into_iter().map(|s| s.0).collect(), @@ -219,7 +133,7 @@ impl ToProto for Transaction { entry_point_selector: x.entry_point_selector.0, calldata: x.calldata.into_iter().map(|c| c.0).collect(), }), - InvokeV1(x) => proto::Transaction::InvokeV1(proto::InvokeV1 { + InvokeV1(x) => proto::TransactionVariant::InvokeV1(proto::InvokeV1 { sender: Address(x.sender_address.0), max_fee: x.max_fee.0, signature: AccountSignature { @@ -228,15 +142,15 @@ impl ToProto for Transaction { nonce: x.nonce.0, calldata: x.calldata.into_iter().map(|c| c.0).collect(), }), - InvokeV3(x) => proto::Transaction::InvokeV3(proto::InvokeV3 { + InvokeV3(x) => proto::TransactionVariant::InvokeV3(proto::InvokeV3 { sender: Address(x.sender_address.0), signature: AccountSignature { parts: x.signature.into_iter().map(|s| s.0).collect(), }, calldata: x.calldata.into_iter().map(|c| c.0).collect(), resource_bounds: ResourceBounds { - l1_gas: x.resource_bounds.l1_gas.to_proto(), - l2_gas: x.resource_bounds.l2_gas.to_proto(), + l1_gas: x.resource_bounds.l1_gas.to_dto(), + l2_gas: x.resource_bounds.l2_gas.to_dto(), }, tip: x.tip.0.into(), paymaster_data: Address( @@ -251,25 +165,29 @@ impl ToProto for Transaction { .unwrap_or(&AccountDeploymentDataElem::ZERO) .0, ), // TODO - nonce_domain: x.nonce_data_availability_mode.to_proto(), - fee_domain: x.fee_data_availability_mode.to_proto(), + nonce_domain: x.nonce_data_availability_mode.to_dto(), + fee_domain: x.fee_data_availability_mode.to_dto(), nonce: x.nonce.0, }), - L1Handler(x) => proto::Transaction::L1HandlerV0(proto::L1HandlerV0 { + L1Handler(x) => proto::TransactionVariant::L1HandlerV0(proto::L1HandlerV0 { nonce: x.nonce.0, address: Address(x.contract_address.0), entry_point_selector: x.entry_point_selector.0, calldata: x.calldata.into_iter().map(|c| c.0).collect(), }), + }; + + p2p_proto::transaction::Transaction { + hash: Hash(self.hash.0), + variant, } } } -impl ToProto for (CommonTransaction, CommonReceipt) { - fn to_proto(self) -> p2p_proto::receipt::Receipt { +impl ToDto for (CommonTransaction, CommonReceipt) { + fn to_dto(self) -> p2p_proto::receipt::Receipt { use p2p_proto::receipt::Receipt::{Declare, Deploy, DeployAccount, Invoke, L1Handler}; let revert_reason = self.1.revert_reason().unwrap_or_default(); - let common = ReceiptCommon { transaction_hash: Hash(self.1.transaction_hash.0), actual_fee: self.1.actual_fee.unwrap_or_default().0, @@ -320,11 +238,6 @@ impl ToProto for (CommonTransaction, CommonReceipt) .output_builtin .try_into() .unwrap(), - segment_arena: e - .builtin_instance_counter - .segment_arena_builtin - .try_into() - .unwrap(), }, steps: e.n_steps.try_into().unwrap(), memory_holes: e.n_memory_holes.try_into().unwrap(), @@ -366,10 +279,10 @@ impl ToProto for (CommonTransaction, CommonReceipt) } } -impl ToProto for (TransactionHash, Event) { - fn to_proto(self) -> p2p_proto::event::Event { +impl ToDto for (TransactionHash, Event) { + fn to_dto(self) -> p2p_proto::event::Event { p2p_proto::event::Event { - transaction_hash: Hash(self.0 .0), + transaction_hash: p2p_proto::common::Hash(self.0 .0), from_address: self.1.from_address.0, keys: self.1.keys.into_iter().map(|k| k.0).collect(), data: self.1.data.into_iter().map(|d| d.0).collect(), @@ -377,8 +290,8 @@ impl ToProto for (TransactionHash, Event) { } } -impl ToProto for ResourceBound { - fn to_proto(self) -> p2p_proto::transaction::ResourceLimits { +impl ToDto for ResourceBound { + fn to_dto(self) -> p2p_proto::transaction::ResourceLimits { p2p_proto::transaction::ResourceLimits { max_amount: self.max_amount.0.into(), max_price_per_unit: self.max_price_per_unit.0.into(), @@ -386,8 +299,8 @@ impl ToProto for ResourceBound { } } -impl ToProto for DataAvailabilityMode { - fn to_proto(self) -> String { +impl ToDto for DataAvailabilityMode { + fn to_dto(self) -> String { match self { DataAvailabilityMode::L1 => "L1".to_owned(), DataAvailabilityMode::L2 => "L2".to_owned(), diff --git a/crates/pathfinder/src/p2p_network/sync_handlers/tests.rs b/crates/pathfinder/src/p2p_network/sync_handlers/tests.rs index 4ff93b85ce..cf0983386d 100644 --- a/crates/pathfinder/src/p2p_network/sync_handlers/tests.rs +++ b/crates/pathfinder/src/p2p_network/sync_handlers/tests.rs @@ -1,848 +1,611 @@ -// use p2p_proto::common::{Direction, Step}; -// use pathfinder_common::BlockNumber; -// use rstest::rstest; - -// const I64_MAX: u64 = i64::MAX as u64; - -// #[rstest] -// #[case(0, 1, Direction::Forward, Some(1))] -// #[case(0, I64_MAX, Direction::Forward, Some(I64_MAX))] -// #[case(1, I64_MAX, Direction::Forward, None)] -// #[case(0, 1, Direction::Backward, None)] -// #[case(1, 1, Direction::Backward, Some(0))] -// #[case(I64_MAX, 1, Direction::Backward, Some(I64_MAX - 1))] -// #[case(I64_MAX, I64_MAX, Direction::Backward, Some(0))] -// #[case(I64_MAX, I64_MAX + 1, Direction::Backward, None)] -// #[test] -// fn get_next_block_number( -// #[case] start: u64, -// #[case] step: u64, -// #[case] direction: Direction, -// #[case] expected: Option, -// ) { -// assert_eq!( -// super::get_next_block_number( -// BlockNumber::new_or_panic(start), -// Step::from(Some(step)), -// direction -// ), -// expected.map(BlockNumber::new_or_panic) -// ); -// } - -// mod boundary_conditions { -// use super::I64_MAX; -// use crate::p2p_network::sync_handlers::{ -// get_bodies, get_events, get_headers, get_receipts, get_transactions, MAX_COUNT_IN_TESTS, -// }; -// use assert_matches::assert_matches; -// use fake::{Fake, Faker}; -// use futures::channel::mpsc; -// use futures::StreamExt; -// use p2p_proto::block::{ -// BlockBodiesRequest, BlockBodyMessage, BlockHeadersRequest, BlockHeadersResponse, -// BlockHeadersResponsePart, -// }; -// use p2p_proto::common::{BlockNumberOrHash, Direction, Fin, Iteration}; -// use p2p_proto::event::{EventsRequest, EventsResponseKind}; -// use p2p_proto::receipt::{ReceiptsRequest, ReceiptsResponseKind}; -// use p2p_proto::transaction::{TransactionsRequest, TransactionsResponseKind}; -// use pathfinder_storage::fake::with_n_blocks; -// use pathfinder_storage::Storage; -// use rand::{thread_rng, Rng}; -// use rstest::rstest; - -// mod zero_limit_yields_fin_ok_invalid_start_yields_fin_unknown { -// use super::*; - -// fn zero_limit() -> Iteration { -// Iteration { -// limit: 0, -// ..Faker.fake() -// } -// } - -// fn invalid_start() -> Iteration { -// Iteration { -// start: BlockNumberOrHash::Number( -// rand::thread_rng().gen_range(I64_MAX + 1..=u64::MAX), -// ), -// ..Faker.fake() -// } -// } - -// macro_rules! define_test { -// ($name:ident, $uut_name:ident, $request:tt) => { -// #[rstest] -// #[case(zero_limit(), Fin::ok())] -// #[case(invalid_start(), Fin::unknown())] -// #[tokio::test] -// async fn $name(#[case] iteration: Iteration, #[case] fin: Fin) { -// let storage = Storage::in_memory().unwrap(); -// let (tx, mut rx) = mpsc::channel(0); -// let _jh = tokio::spawn($uut_name(storage, $request { iteration }, tx)); -// assert_eq!(rx.next().await.unwrap().into_fin(), Some(fin)); -// } -// }; -// } - -// define_test!(headers, get_headers, BlockHeadersRequest); -// define_test!(bodies, get_bodies, BlockBodiesRequest); -// define_test!(transactions, get_transactions, TransactionsRequest); -// define_test!(receipts, get_receipts, ReceiptsRequest); -// define_test!(events, get_events, EventsRequest); -// } - -// mod partially_successful_requests_end_with_additional_fin_unknown { -// use super::*; - -// fn init_test( -// direction: Direction, -// ) -> (Storage, Iteration, mpsc::Sender, mpsc::Receiver) { -// let storage: Storage = Storage::in_memory().unwrap(); -// let _ = with_n_blocks(&storage, 1); -// let iteration = Iteration { -// start: BlockNumberOrHash::Number(0), -// // We want more than available, we don't care about the internal limit because -// // partial failure (`Fin::unknown()`) takes precedence over it (`Fin::too_much()`) -// limit: thread_rng().gen_range(2..=MAX_COUNT_IN_TESTS * 2), -// direction, -// ..Faker.fake() -// }; -// let (tx, rx) = mpsc::channel::(0); -// (storage, iteration, tx, rx) -// } - -// #[rstest] -// #[tokio::test] -// async fn test_get_headers( -// #[values(Direction::Backward, Direction::Forward)] direction: Direction, -// ) { -// let (storage, iteration, tx, mut rx) = init_test(direction); -// let getter_fut = get_headers(storage, BlockHeadersRequest { iteration }, tx); - -// let (_, ret) = tokio::join!(getter_fut, rx.next()); - -// let BlockHeadersResponse { parts } = ret.unwrap(); -// assert_eq!(parts.len(), 4); -// assert_matches!(&parts[0], BlockHeadersResponsePart::Header(h) => assert_eq!(h.number, 0)); -// assert_matches!(&parts[1], BlockHeadersResponsePart::Signatures(s) => assert_eq!(s.block.number, 0)); -// assert_eq!(parts[2], BlockHeadersResponsePart::Fin(Fin::ok())); -// // Expect Fin::unknown() where the first unavailable item would be -// assert_eq!(parts[3], BlockHeadersResponsePart::Fin(Fin::unknown())); -// } - -// #[rstest] -// #[tokio::test] -// async fn test_get_bodies( -// #[values(Direction::Backward, Direction::Forward)] direction: Direction, -// ) { -// let (storage, iteration, tx, mut rx) = init_test(direction); -// let _jh = tokio::spawn(get_bodies(storage, BlockBodiesRequest { iteration }, tx)); -// rx.next().await.unwrap(); // Diff -// match rx.next().await.unwrap().body_message { -// // New classes in block -// BlockBodyMessage::Classes(_) => { -// assert_eq!( -// rx.next().await.unwrap().body_message, -// BlockBodyMessage::Fin(Fin::ok()) -// ); -// } -// // No new classes in block -// BlockBodyMessage::Fin(f) => assert_eq!(f, Fin::ok()), -// _ => panic!("unexpected message type"), -// } - -// // Expect Fin::unknown() where the first unavailable item would be -// assert_eq!( -// rx.next().await.unwrap().body_message, -// BlockBodyMessage::Fin(Fin::unknown()) -// ); -// } - -// macro_rules! define_test { -// ($name:ident, $uut_name:ident, $request:tt, $reply:tt) => { -// #[rstest] -// #[tokio::test] -// async fn $name( -// #[values(Direction::Backward, Direction::Forward)] direction: Direction, -// ) { -// let (storage, iteration, tx, mut rx) = init_test(direction); -// let _jh = tokio::spawn($uut_name(storage, $request { iteration }, tx)); -// // Block data -// rx.next().await.unwrap(); -// // Properly delimited with Fin::ok() -// assert_eq!(rx.next().await.unwrap().kind, $reply::Fin(Fin::ok())); -// // Expect Fin::unknown() where the first unavailable item would be -// assert_eq!(rx.next().await.unwrap().kind, $reply::Fin(Fin::unknown())); -// } -// }; -// } - -// define_test!( -// test_get_transactions, -// get_transactions, -// TransactionsRequest, -// TransactionsResponseKind -// ); -// define_test!( -// test_get_receipts, -// get_receipts, -// ReceiptsRequest, -// ReceiptsResponseKind -// ); -// define_test!( -// test_get_events, -// get_events, -// EventsRequest, -// EventsResponseKind -// ); -// } - -// mod internally_limited_requests_end_with_additional_fin_too_much { -// use super::*; - -// const NUM_BLOCKS_IN_STORAGE: u64 = MAX_COUNT_IN_TESTS; - -// fn init_test( -// direction: Direction, -// ) -> (Storage, Iteration, mpsc::Sender, mpsc::Receiver) { -// let storage = Storage::in_memory().unwrap(); -// let _ = with_n_blocks(&storage, NUM_BLOCKS_IN_STORAGE as usize); -// let (tx, rx) = mpsc::channel::(1); -// let start = match direction { -// Direction::Forward => BlockNumberOrHash::Number(0), -// Direction::Backward => BlockNumberOrHash::Number(NUM_BLOCKS_IN_STORAGE - 1), -// }; -// let iteration = Iteration { -// start, -// // We want to trigger the internal limit -// limit: thread_rng().gen_range(NUM_BLOCKS_IN_STORAGE + 1..=u64::MAX), -// step: 1.into(), -// direction, -// }; -// (storage, iteration, tx, rx) -// } - -// #[rstest] -// #[tokio::test] -// async fn test_get_headers( -// #[values(Direction::Backward, Direction::Forward)] direction: Direction, -// ) { -// let (storage, iteration, tx, mut rx) = init_test(direction); -// get_headers(storage, BlockHeadersRequest { iteration }, tx.clone()) -// .await -// .unwrap(); - -// let BlockHeadersResponse { parts } = rx.next().await.unwrap(); -// assert_eq!(parts.len(), NUM_BLOCKS_IN_STORAGE as usize * 3 + 1); - -// let chunked = parts.chunks_exact(3); -// let remainder = chunked.remainder(); - -// chunked.for_each(|chunk| { -// assert_matches!(&chunk[0], BlockHeadersResponsePart::Header(_)); -// assert_matches!(&chunk[1], BlockHeadersResponsePart::Signatures(_)); -// assert_eq!(chunk[2], BlockHeadersResponsePart::Fin(Fin::ok())); -// }); -// // Expect Fin::too_much() if all requested items were found up to the internal limit -// assert_eq!(remainder, &[BlockHeadersResponsePart::Fin(Fin::too_much())]); -// } - -// #[rstest] -// #[tokio::test] -// async fn test_get_bodies( -// #[values(Direction::Backward, Direction::Forward)] direction: Direction, -// ) { -// let (storage, iteration, tx, mut rx) = init_test(direction); -// let _jh = tokio::spawn(get_bodies(storage, BlockBodiesRequest { iteration }, tx)); -// // 10 x [Diff, Classes*, Fin::ok()] -// for _ in 0..NUM_BLOCKS_IN_STORAGE { -// rx.next().await.unwrap(); // Diff -// match rx.next().await.unwrap().body_message { -// // New classes in block -// BlockBodyMessage::Classes(_) => { -// assert_eq!( -// rx.next().await.unwrap().body_message, -// BlockBodyMessage::Fin(Fin::ok()) -// ); -// } -// // No new classes in block -// BlockBodyMessage::Fin(f) => { -// assert_eq!(f, Fin::ok()); -// } -// _ => panic!("unexpected message type"), -// } -// } -// // Expect Fin::too_much() where the first unavailable item would be -// assert_eq!( -// rx.next().await.unwrap().body_message, -// BlockBodyMessage::Fin(Fin::too_much()) -// ); -// } - -// macro_rules! define_test { -// ($name:ident, $uut_name:ident, $request:tt, $reply:tt) => { -// #[rstest] -// #[tokio::test] -// async fn $name( -// #[values(Direction::Backward, Direction::Forward)] direction: Direction, -// ) { -// let (storage, iteration, tx, mut rx) = init_test(direction); -// let _jh = tokio::spawn($uut_name(storage, $request { iteration }, tx)); -// for _ in 0..NUM_BLOCKS_IN_STORAGE { -// rx.next().await.unwrap(); // Block data -// rx.next().await.unwrap(); // Fin::ok() -// } -// // Expect Fin::too_much() where the first unavailable item would be -// assert_eq!(rx.next().await.unwrap().kind, $reply::Fin(Fin::too_much())); -// } -// }; -// } - -// define_test!( -// test_get_transactions, -// get_transactions, -// TransactionsRequest, -// TransactionsResponseKind -// ); -// define_test!( -// test_get_receipts, -// get_receipts, -// ReceiptsRequest, -// ReceiptsResponseKind -// ); -// define_test!( -// test_get_events, -// get_events, -// EventsRequest, -// EventsResponseKind -// ); -// } -// } - -// /// Property tests, grouped to be immediately visible when executed -// mod prop { -// use crate::p2p_network::client::types as simplified; -// use crate::p2p_network::sync_handlers; -// use crate::p2p_network::sync_handlers::def_into_dto; -// use futures::channel::mpsc; -// use futures::StreamExt; -// use p2p::client::types::{self as p2p_types, RawTransactionVariant, TryFromDto}; -// use p2p_proto::block::{ -// BlockBodiesRequest, BlockBodyMessage, BlockHeadersRequest, BlockHeadersResponse, -// BlockHeadersResponsePart, -// }; -// use p2p_proto::common::{BlockId, BlockNumberOrHash, Error, Fin, Iteration}; -// use p2p_proto::event::{EventsRequest, EventsResponseKind}; -// use p2p_proto::receipt::{ReceiptsRequest, ReceiptsResponseKind}; -// use p2p_proto::state::{Cairo0Class, Cairo1Class, Class}; -// use p2p_proto::transaction::{TransactionsRequest, TransactionsResponseKind}; -// use pathfinder_common::event::Event; -// use pathfinder_common::transaction::Transaction; -// use pathfinder_common::{ -// BlockCommitmentSignature, BlockCommitmentSignatureElem, BlockHash, BlockNumber, -// TransactionHash, -// }; -// use proptest::prelude::*; -// use std::collections::{BTreeSet, HashMap}; -// use tokio::runtime::Runtime; - -// #[macro_export] -// macro_rules! prop_assert_eq_sorted { -// ($left:expr, $right:expr) => {{ -// let left = &$left; -// let right = &$right; -// let comparison_string = pretty_assertions_sorted::Comparison::new( -// &pretty_assertions_sorted::SortedDebug::new(left), -// &pretty_assertions_sorted::SortedDebug::new(right) -// ).to_string(); -// proptest::prop_assert!( -// *left == *right, -// "assertion failed: `(left == right)`\n{comparison_string}\n"); -// }}; - -// ($left:expr, $right:expr, $fmt:tt $($args:tt)*) => {{ -// let left = &$left; -// let right = &$right; -// let comparison_string = pretty_assertions_sorted::Comparison::new( -// &pretty_assertions_sorted::SortedDebug::new(left), -// &pretty_assertions_sorted::SortedDebug::new(right) -// ).to_string(); -// proptest::prop_assert!( -// *left == *right, -// concat!( -// "assertion failed: `(left == right)`\n\ -// {}: ", $fmt), -// comparison_string $($args)*); -// }}; -// } - -// proptest! { -// #[test] -// fn get_headers((num_blocks, seed, start_block, limit, step, direction) in strategy::composite()) { -// // Fake storage with a given number of blocks -// let (storage, in_db) = fixtures::storage_with_seed(seed, num_blocks); -// // Compute the overlapping set between the db and the request -// // These are the headers that we expect to be read from the db -// let expected = overlapping::get(in_db, start_block, limit, step, num_blocks, direction) -// .into_iter().map(|(h, s, _, _, _, _)| (h.into(), s)).collect::>(); -// // Run the handler -// let request = BlockHeadersRequest { iteration: Iteration { start: BlockNumberOrHash::Number(start_block), limit, step, direction, } }; -// // Reusing the runtime does not yield any performance gains -// let parts = Runtime::new().unwrap().block_on(async { -// let (tx, mut rx) = mpsc::channel(0); -// let getter_fut = sync_handlers::get_headers(storage, request, tx); - -// // Waiting for both futures to run to completion is faster than spawning the getter -// // and awaiting the receiver (almost 1s for 100 iterations on Ryzen 3700X). -// // BTW, we cannot just await the getter and then the receiver -// // as there is backpressure (channel size 0) and we would deadlock. -// let (_, ret) = tokio::join!(getter_fut, rx.next()); - -// let BlockHeadersResponse { parts } = ret.unwrap(); -// parts -// }); -// // Empty reply in the test is only possible if the request does not overlap with storage -// // Invalid start and zero limit are tested in boundary_conditions:: -// if expected.is_empty() { -// prop_assert_eq_sorted!(parts.len(), 1); -// prop_assert_eq_sorted!(parts[0].clone().into_fin().unwrap(), Fin::unknown()); -// } else { -// // Group reply parts by block: [[hdr-0, fin-0], [hdr-1, fin-1], ...] -// let actual = parts.chunks_exact(3).map(|chunk| { -// // Make sure block data is delimited -// assert_eq!(chunk[2], BlockHeadersResponsePart::Fin(Fin::ok())); -// // Extract the header -// let h = p2p_types::BlockHeader::try_from(chunk[0].clone().into_header().unwrap()).unwrap(); -// // Extract the signature -// let s = chunk[1].clone().into_signatures().unwrap(); -// assert_eq!(s.signatures.len(), 1); -// let s = s.signatures.into_iter().next().unwrap(); -// let s = BlockCommitmentSignature { -// r: BlockCommitmentSignatureElem(s.r), -// s: BlockCommitmentSignatureElem(s.s), -// }; -// (h, s) -// }).collect::>(); - -// prop_assert_eq_sorted!(actual, expected); -// } -// } -// } - -// proptest! { -// #[test] -// fn get_bodies((num_blocks, db_seed, start_block, limit, step, direction) in strategy::composite()) { -// use crate::p2p_network::sync_handlers::class_definition::{Cairo, Sierra}; - -// // Fake storage with a given number of blocks -// let (storage, in_db) = fixtures::storage_with_seed(db_seed, num_blocks); -// // Get the overlapping set between the db and the request -// let expected = overlapping::get(in_db, start_block, limit, step, num_blocks, direction); -// // Extract the expected state updates, definitions and classes from the overlapping set -// // in a form digestable for this test -// let expected = expected.into_iter() -// .map(|(header, _, _, state_update, cairo_defs, sierra_defs)| -// ( -// // block number and hash -// (header.number, header.hash), -// ( -// // "simplified" state update, without an explicit list of declared and replaced classes -// state_update.clone().into(), -// // Cairo0 class definitions, parsed into p2p DTOs -// cairo_defs.into_iter().map(|(_, d)| { -// let def = serde_json::from_slice::>(&d).unwrap(); -// def_into_dto::cairo(def) -// }).collect(), -// // Cairo1 (Sierra) class definitions, parsed into p2p DTOs -// sierra_defs.into_iter().map(|(_, s, c)| { -// let def = serde_json::from_slice::>(&s).unwrap(); -// def_into_dto::sierra(def, c) -// }).collect() -// ) -// ) -// ).collect::, BTreeSet)>>(); -// // Run the handler -// let request = BlockBodiesRequest { iteration: Iteration { start: BlockNumberOrHash::Number(start_block), limit, step, direction, } }; -// let replies = Runtime::new().unwrap().block_on(async { -// let (tx, rx) = mpsc::channel(0); -// let getter_fut = sync_handlers::get_bodies(storage, request, tx); -// let (_, replies) = tokio::join!(getter_fut, rx.collect::>()); -// replies -// }); - -// // Empty reply is only possible if the request does not overlap with storage -// // Invalid start and zero limit are tested in boundary_conditions:: -// if expected.is_empty() { -// prop_assert_eq_sorted!(replies.len(), 1); -// prop_assert_eq_sorted!(replies[0].clone().into_fin().unwrap(), Fin::unknown()); -// } else { -// // Collect replies into a set of (block_number, state_update, definitions) -// let mut actual = HashMap::new(); -// let mut block_id = None; - -// for reply in replies { -// match reply.body_message { -// BlockBodyMessage::Diff(d) => { -// let BlockId { number, hash } = reply.id.unwrap(); -// block_id = Some((BlockNumber::new(number).unwrap(), BlockHash(hash.0))); - -// let state_update = p2p_types::StateUpdate::from(d); -// actual.insert(block_id.unwrap(), (state_update, BTreeSet::new(), BTreeSet::new())); -// }, -// BlockBodyMessage::Classes(c) => { -// // Classes coming after a state diff should be for the same block -// let entry = actual.get_mut(&block_id.expect("Classes follow Diff so current block id should be set")).unwrap(); -// c.classes.into_iter().for_each(|c| { -// match c { -// Class::Cairo0(cairo) => entry.1.insert(cairo), -// Class::Cairo1(sierra) => entry.2.insert(sierra), -// }; -// }); -// }, -// BlockBodyMessage::Fin(f) => { -// match f.error { -// // We either managed to fit the entire range or we hit the internal limit -// None | Some(Error::TooMuch) => assert!(actual.contains_key(&block_id.unwrap())), -// // Either the request yielded nothing or was only partially successful -// Some(Error::Unknown) => {}, -// Some(_) => panic!("unexpected error"), -// } -// } -// _ => unimplemented!(), -// } -// } - -// prop_assert_eq_sorted!(actual, expected); -// } -// } -// } - -// mod workaround { -// use pathfinder_common::{TransactionNonce, TransactionVersion}; -// use starknet_gateway_types::reply::transaction as gw; - -// // Align with the deserialization workaround to avoid false negative mismatches -// pub fn for_legacy_l1_handlers(tx: gw::Transaction) -> gw::Transaction { -// match tx { -// gw::Transaction::Invoke(gw::InvokeTransaction::V0(tx)) -// if tx.entry_point_type == Some(gw::EntryPointType::L1Handler) => -// { -// gw::Transaction::L1Handler(gw::L1HandlerTransaction { -// contract_address: tx.sender_address, -// entry_point_selector: tx.entry_point_selector, -// nonce: TransactionNonce::ZERO, -// calldata: tx.calldata, -// transaction_hash: tx.transaction_hash, -// version: TransactionVersion::ZERO, -// }) -// } -// x => x, -// } -// } -// } - -// proptest! { -// #[test] -// fn get_transactions((num_blocks, seed, start_block, limit, step, direction) in strategy::composite()) { -// // Fake storage with a given number of blocks -// let (storage, in_db) = fixtures::storage_with_seed(seed, num_blocks); -// // Compute the overlapping set between the db and the request -// // These are the transactions that we expect to be read from the db -// let expected = overlapping::get(in_db, start_block, limit, step, num_blocks, direction).into_iter() -// .map(|(h, _, tr, _, _, _)| -// ( -// h.number, -// h.hash, -// tr.into_iter().map(|(t, _)| Transaction::from(workaround::for_legacy_l1_handlers(t.into())).variant.into()).collect::>() -// ) -// ).collect::>(); -// // Run the handler -// let request = TransactionsRequest { iteration: Iteration { start: BlockNumberOrHash::Number(start_block), limit, step, direction, } }; -// let replies = Runtime::new().unwrap().block_on(async { -// let (tx, rx) = mpsc::channel(0); -// let getter_fut = sync_handlers::get_transactions(storage, request, tx); -// let (_, replies) = tokio::join!(getter_fut, rx.collect::>()); -// replies -// }); -// // Empty reply is only possible if the request does not overlap with storage -// // Invalid start and zero limit are tested in boundary_conditions:: -// if expected.is_empty() { -// prop_assert_eq_sorted!(replies.len(), 1); -// prop_assert_eq_sorted!(replies[0].clone().into_fin().unwrap(), Fin::unknown()); -// } else { -// // Group replies by block, it is assumed that transactions per block are small enough to fit under the 1MiB limit -// // This means that there are 2 replies per block: [[transactions-0, fin-0], [transactions-1, fin-1], ...] -// let actual = replies.chunks_exact(2).map(|replies | { -// assert_eq!(replies[0].id, replies[1].id); -// // Make sure block data is delimited -// assert_eq!(replies[1].kind, TransactionsResponseKind::Fin(Fin::ok())); -// // Extract transactions -// let transactions = replies[0].kind.clone().into_transactions().unwrap().items; -// let BlockId { number, hash } = replies[0].id.unwrap(); -// ( -// BlockNumber::new(number).unwrap(), -// BlockHash(hash.0), -// transactions.into_iter().map(|t| RawTransactionVariant::try_from_dto(t).unwrap()).collect::>() -// ) -// }).collect::>(); - -// prop_assert_eq_sorted!(actual, expected); -// } -// } -// } - -// proptest! { -// #[test] -// fn get_receipts((num_blocks, seed, start_block, limit, step, direction) in strategy::composite()) { -// // Fake storage with a given number of blocks -// let (storage, in_db) = fixtures::storage_with_seed(seed, num_blocks); -// // Compute the overlapping set between the db and the request -// // These are the receipts that we expect to be read from the db -// let expected = overlapping::get(in_db, start_block, limit, step, num_blocks, direction).into_iter() -// .map(|(h, _, tr, _, _, _)| -// ( -// h.number, -// h.hash, -// tr.into_iter().map(|(_, r)| starknet_gateway_types::reply::transaction::Receipt::from(r).into()).collect::>() -// ) -// ).collect::>(); -// // Run the handler -// let request = ReceiptsRequest { iteration: Iteration { start: BlockNumberOrHash::Number(start_block), limit, step, direction, } }; -// let replies = Runtime::new().unwrap().block_on(async { -// let (tx, rx) = mpsc::channel(0); -// let getter_fut = sync_handlers::get_receipts(storage, request, tx); -// let (_, replies) = tokio::join!(getter_fut, rx.collect::>()); -// replies -// }); -// // Empty reply is only possible if the request does not overlap with storage -// // Invalid start and zero limit are tested in boundary_conditions:: -// if expected.is_empty() { -// prop_assert_eq_sorted!(replies.len(), 1); -// prop_assert_eq_sorted!(replies[0].clone().into_fin().unwrap(), Fin::unknown()); -// } else { -// // Group replies by block, it is assumed that receipts per block small enough to fit under the 1MiB limit -// // This means that there are 2 replies per block: [[receipts-0, fin-0], [receipts-1, fin-1], ...] -// let actual = replies.chunks_exact(2).map(|replies | { -// assert_eq!(replies[0].id, replies[1].id); -// // Make sure block data is delimited -// assert_eq!(replies[1].kind, ReceiptsResponseKind::Fin(Fin::ok())); -// // Extract receipts -// let receipts = replies[0].kind.clone().into_receipts().unwrap().items; -// let BlockId { number, hash } = replies[0].id.unwrap(); -// ( -// BlockNumber::new(number).unwrap(), -// BlockHash(hash.0), -// receipts.into_iter().map(|r| simplified::Receipt::try_from(r).unwrap()).collect::>() -// ) -// }).collect::>(); - -// prop_assert_eq_sorted!(actual, expected); -// } -// } -// } - -// proptest! { -// #[test] -// fn get_events((num_blocks, seed, start_block, limit, step, direction) in strategy::composite()) { -// // Fake storage with a given number of blocks -// let (storage, in_db) = fixtures::storage_with_seed(seed, num_blocks); -// // Compute the overlapping set between the db and the request -// // These are the events that we expect to be read from the db -// // Extract tuples (block_number, block_hash, [events{txn#1}, events{txn#2}, ...]) -// let expected = overlapping::get(in_db, start_block, limit, step, num_blocks, direction).into_iter() -// .map(|(h, _, tr, _, _, _)|{ -// let events = tr.into_iter().map(|(_, r)| (r.transaction_hash, r.events)).collect::>>(); -// ( -// h.number, -// h.hash, -// events -// )} -// ).collect::>(); -// // Run the handler -// let request = EventsRequest { iteration: Iteration { start: BlockNumberOrHash::Number(start_block), limit, step, direction, } }; -// let replies = Runtime::new().unwrap().block_on(async { -// let (tx, rx) = mpsc::channel(0); -// let getter_fut = sync_handlers::get_events(storage, request, tx); -// let (_, replies) = tokio::join!(getter_fut, rx.collect::>()); -// replies -// }); -// // Empty reply is only possible if the request does not overlap with storage -// // Invalid start and zero limit are tested in boundary_conditions:: -// if expected.is_empty() { -// prop_assert_eq_sorted!(replies.len(), 1); -// prop_assert_eq_sorted!(replies[0].clone().into_fin().unwrap(), Fin::unknown()); -// } else { -// // Group replies by block, it is assumed that events per block small enough to fit under the 1MiB limit -// // This means that there are 2 replies per block: [[events-0, fin-0], [events-1, fin-1], ...] -// let actual = replies.chunks_exact(2).map(|replies | { -// assert_eq!(replies[0].id, replies[1].id); -// // Make sure block data is delimited -// assert_eq!(replies[1].kind, EventsResponseKind::Fin(Fin::ok())); -// let BlockId { number, hash } = replies[0].id.unwrap(); -// // Extract events -// let mut events = HashMap::<_, Vec<_>>::new(); -// replies[0].kind.clone().into_events().unwrap().items.into_iter().for_each(|e| { -// events.entry(TransactionHash(e.transaction_hash.0)).or_default().push(Event::try_from_dto(e).unwrap()); -// }); -// ( -// BlockNumber::new(number).unwrap(), -// BlockHash(hash.0), -// events -// ) -// }).collect::>(); - -// prop_assert_eq_sorted!(actual, expected); -// } -// } -// } - -// /// Fixtures for prop tests -// mod fixtures { -// use crate::p2p_network::sync_handlers::MAX_COUNT_IN_TESTS; -// use pathfinder_storage::fake::{with_n_blocks_and_rng, StorageInitializer}; -// use pathfinder_storage::Storage; - -// pub const MAX_NUM_BLOCKS: u64 = MAX_COUNT_IN_TESTS * 2; - -// pub fn storage_with_seed(seed: u64, num_blocks: u64) -> (Storage, StorageInitializer) { -// use rand::SeedableRng; -// let storage = Storage::in_memory().unwrap(); -// // Explicitly choose RNG to make sure seeded storage is always reproducible -// let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(seed); -// let initializer = -// with_n_blocks_and_rng(&storage, num_blocks.try_into().unwrap(), &mut rng); -// (storage, initializer) -// } -// } - -// /// Find overlapping range between the DB and the request -// mod overlapping { -// use crate::p2p_network::sync_handlers::MAX_COUNT_IN_TESTS; -// use p2p_proto::common::{Direction, Step}; -// use pathfinder_storage::fake::{StorageInitializer, StorageInitializerItem}; - -// pub fn get( -// from_db: StorageInitializer, -// start_block: u64, -// limit: u64, -// step: Step, -// num_blocks: u64, -// direction: Direction, -// ) -> StorageInitializer { -// match direction { -// Direction::Forward => forward(from_db, start_block, limit, step).collect(), -// Direction::Backward => { -// backward(from_db, start_block, limit, step, num_blocks).collect() -// } -// } -// } - -// fn forward( -// from_db: StorageInitializer, -// start_block: u64, -// limit: u64, -// step: Step, -// ) -> impl Iterator { -// from_db -// .into_iter() -// .skip(start_block.try_into().unwrap()) -// .step_by(step.into_inner().try_into().unwrap()) -// .take(std::cmp::min(limit, MAX_COUNT_IN_TESTS).try_into().unwrap()) -// } - -// fn backward( -// mut from_db: StorageInitializer, -// start_block: u64, -// limit: u64, -// step: Step, -// num_blocks: u64, -// ) -> impl Iterator { -// if start_block >= num_blocks { -// // The is no overlapping range but we want to keep the iterator type in this -// // branch type-consistent -// from_db.clear(); -// } - -// from_db -// .into_iter() -// .take((start_block + 1).try_into().unwrap()) -// .rev() -// .step_by(step.into_inner().try_into().unwrap()) -// .take(std::cmp::min(limit, MAX_COUNT_IN_TESTS).try_into().unwrap()) -// } -// } - -// /// Building blocks for the ultimate composite strategy used in all property tests -// mod strategy { -// use super::fixtures::MAX_NUM_BLOCKS; -// use crate::p2p_network::sync_handlers::tests::I64_MAX; -// use p2p_proto::common::{Direction, Step}; -// use proptest::prelude::*; -// use std::ops::Range; - -// prop_compose! { -// fn inside(range: Range)(x in range) -> u64 { x } -// } - -// prop_compose! { -// fn outside_le(range: Range, max: u64)(x in range.end..=max) -> u64 { x } -// } - -// fn rarely_outside_le(range: std::ops::Range, max: u64) -> BoxedStrategy { -// // Empty range will trigger a panic in rand::distributions::Uniform -// if range.is_empty() { -// return Just(range.start).boxed(); -// } - -// prop_oneof![ -// // Occurrence 4:1 -// 4 => inside(range.clone()), -// 1 => outside_le(range, max), -// ] -// .boxed() -// } - -// fn rarely_outside(range: std::ops::Range) -> BoxedStrategy { -// rarely_outside_le(range, u64::MAX) -// } - -// prop_compose! { -// pub fn composite() -// (num_blocks in 0..MAX_NUM_BLOCKS) -// ( -// num_blocks in Just(num_blocks), -// storage_seed in any::(), -// // out of range (> i64::MAX) start values are tested in `empty_reply::` -// start in rarely_outside_le(0..num_blocks, I64_MAX), -// // limit of 0 is tested in `empty_reply::` -// limit in rarely_outside(1..num_blocks), -// // step is always >= 1 -// step in rarely_outside(1..num_blocks / 4), -// direction in prop_oneof![Just(Direction::Forward), Just(Direction::Backward)], -// ) -> (u64, u64, u64, u64, Step, Direction) { -// (num_blocks, storage_seed, start, limit, step.into(), direction) -// } -// } -// } -// } - -// mod classes { -// use crate::p2p_network::sync_handlers::classes; -// use fake::{Fake, Faker}; - -// #[test] -// fn getter_error_yields_error() { -// let mut responses = vec![]; -// assert!(classes( -// Faker.fake(), -// Faker.fake(), -// vec![Faker.fake()], -// &mut responses, -// |_, _| anyhow::bail!("getter failed"), -// ) -// .is_err()); -// assert!(responses.is_empty()); -// } -// } +use p2p_proto::common::{Direction, Step}; +use pathfinder_common::BlockNumber; +use rstest::rstest; + +const I64_MAX: u64 = i64::MAX as u64; + +#[rstest] +#[case(0, 1, Direction::Forward, Some(1))] +#[case(0, I64_MAX, Direction::Forward, Some(I64_MAX))] +#[case(1, I64_MAX, Direction::Forward, None)] +#[case(0, 1, Direction::Backward, None)] +#[case(1, 1, Direction::Backward, Some(0))] +#[case(I64_MAX, 1, Direction::Backward, Some(I64_MAX - 1))] +#[case(I64_MAX, I64_MAX, Direction::Backward, Some(0))] +#[case(I64_MAX, I64_MAX + 1, Direction::Backward, None)] +#[test] +fn get_next_block_number( + #[case] start: u64, + #[case] step: u64, + #[case] direction: Direction, + #[case] expected: Option, +) { + assert_eq!( + super::get_next_block_number( + BlockNumber::new_or_panic(start), + Step::from(Some(step)), + direction + ), + expected.map(BlockNumber::new_or_panic) + ); +} + +mod boundary_conditions { + use super::I64_MAX; + use crate::p2p_network::sync_handlers::{ + get_classes, get_events, get_headers, get_receipts, get_state_diffs, get_transactions, + }; + use fake::{Fake, Faker}; + use futures::channel::mpsc; + use futures::StreamExt; + use p2p_proto::class::ClassesRequest; + use p2p_proto::common::{BlockNumberOrHash, Iteration}; + use p2p_proto::event::EventsRequest; + use p2p_proto::header::BlockHeadersRequest; + use p2p_proto::receipt::ReceiptsRequest; + use p2p_proto::state::StateDiffsRequest; + use p2p_proto::transaction::TransactionsRequest; + use pathfinder_storage::Storage; + use rand::Rng; + use rstest::rstest; + + mod zero_limit_yields_fin_invalid_start_yields_fin { + + use super::*; + + fn zero_limit() -> Iteration { + Iteration { + limit: 0, + ..Faker.fake() + } + } + + fn invalid_start() -> Iteration { + Iteration { + start: BlockNumberOrHash::Number( + rand::thread_rng().gen_range(I64_MAX + 1..=u64::MAX), + ), + ..Faker.fake() + } + } + + macro_rules! define_test { + ($name:ident, $uut_name:ident, $request:tt) => { + #[rstest] + #[case(zero_limit())] + #[case(invalid_start())] + #[tokio::test] + async fn $name(#[case] iteration: Iteration) { + let storage = Storage::in_memory().unwrap(); + let (tx, mut rx) = mpsc::channel(0); + let _jh = tokio::spawn($uut_name(storage, $request { iteration }, tx)); + assert_eq!(rx.next().await.unwrap(), Default::default()); + } + }; + } + + define_test!(headers, get_headers, BlockHeadersRequest); + define_test!(bodies, get_classes, ClassesRequest); + define_test!(state_diffs, get_state_diffs, StateDiffsRequest); + define_test!(transactions, get_transactions, TransactionsRequest); + define_test!(receipts, get_receipts, ReceiptsRequest); + define_test!(events, get_events, EventsRequest); + } +} + +/// Property tests, grouped to be immediately visible when executed +mod prop { + use crate::p2p_network::client::conv::{ + cairo_def_from_dto, sierra_defs_from_dto, Receipt as P2PReceipt, + SignedBlockHeader as P2PSignedBlockHeader, + }; + use crate::p2p_network::sync_handlers; + use futures::channel::mpsc; + use futures::StreamExt; + use p2p::client::types::{RawTransactionVariant, TryFromDto}; + use p2p_proto::class::{Class, ClassesRequest, ClassesResponse}; + use p2p_proto::common::{BlockNumberOrHash, Iteration}; + use p2p_proto::event::{EventsRequest, EventsResponse}; + use p2p_proto::header::{BlockHeadersRequest, BlockHeadersResponse}; + use p2p_proto::receipt::{ReceiptsRequest, ReceiptsResponse}; + use p2p_proto::state::{ + ContractDiff, ContractStoredValue, StateDiffsRequest, StateDiffsResponse, + }; + use p2p_proto::transaction::{TransactionsRequest, TransactionsResponse}; + use pathfinder_common::event::Event; + use pathfinder_common::state_update::{ + ContractClassUpdate, ContractUpdate, SystemContractUpdate, + }; + use pathfinder_common::{ + ClassHash, ContractAddress, ContractNonce, SierraHash, StorageAddress, StorageValue, + TransactionHash, + }; + use pathfinder_crypto::Felt; + use proptest::prelude::*; + use std::collections::HashMap; + use tokio::runtime::Runtime; + + #[macro_export] + macro_rules! prop_assert_eq_sorted { + ($left:expr, $right:expr) => {{ + let left = &$left; + let right = &$right; + let comparison_string = pretty_assertions_sorted::Comparison::new( + &pretty_assertions_sorted::SortedDebug::new(left), + &pretty_assertions_sorted::SortedDebug::new(right) + ).to_string(); + proptest::prop_assert!( + *left == *right, + "assertion failed: `(left == right)`\n{comparison_string}\n"); + }}; + + ($left:expr, $right:expr, $fmt:tt $($args:tt)*) => {{ + let left = &$left; + let right = &$right; + let comparison_string = pretty_assertions_sorted::Comparison::new( + &pretty_assertions_sorted::SortedDebug::new(left), + &pretty_assertions_sorted::SortedDebug::new(right) + ).to_string(); + proptest::prop_assert!( + *left == *right, + concat!( + "assertion failed: `(left == right)`\n\ + {}: ", $fmt), + comparison_string $($args)*); + }}; + } + + proptest! { + #[test] + fn get_headers((num_blocks, seed, start_block, limit, step, direction) in strategy::composite()) { + // Fake storage with a given number of blocks + let (storage, in_db) = fixtures::storage_with_seed(seed, num_blocks); + // Compute the overlapping set between the db and the request + // These are the headers that we expect to be read from the db + let expected = overlapping::get(in_db, start_block, limit, step, num_blocks, direction) + .into_iter().map(|(h, s, _, _, _, _)| P2PSignedBlockHeader::from((h, s)) ).collect::>(); + // Run the handler + let request = BlockHeadersRequest { iteration: Iteration { start: BlockNumberOrHash::Number(start_block), limit, step, direction, } }; + let mut responses = Runtime::new().unwrap().block_on(async { + let (tx, rx) = mpsc::channel(0); + let getter_fut = sync_handlers::get_headers(storage, request, tx); + // Waiting for both futures to run to completion is faster than spawning the getter + // and awaiting the receiver (almost 1s for 100 iterations on Ryzen 3700X). + // BTW, we cannot just await the getter and then the receiver + // as there is backpressure (channel size 0) and we would deadlock. + let (_, response) = tokio::join!(getter_fut, rx.collect::>()); + response + }); + + // Make sure the last reply is Fin + assert_eq!(responses.pop().unwrap(), BlockHeadersResponse::Fin); + + // Check the rest + let actual = responses.into_iter().map(|response| match response { + BlockHeadersResponse::Header(hdr) => P2PSignedBlockHeader::try_from(*hdr).unwrap(), + _ => panic!("unexpected response"), + }).collect::>(); + + prop_assert_eq_sorted!(actual, expected); + } + } + + proptest! { + #[test] + fn get_state_diffs((num_blocks, seed, start_block, limit, step, direction) in strategy::composite()) { + // Fake storage with a given number of blocks + let (storage, in_db) = fixtures::storage_with_seed(seed, num_blocks); + // Compute the overlapping set between the db and the request + // These are the items that we expect to be read from the db + // Grouped by block number + let expected = overlapping::get(in_db, start_block, limit, step, num_blocks, direction).into_iter() + .map(|(h, _, _, state_update, _, _)| + ( + h.number, // Block number + state_update.contract_updates, + state_update.system_contract_updates, + ) + ).collect::>(); + // Run the handler + let request = StateDiffsRequest { iteration: Iteration { start: BlockNumberOrHash::Number(start_block), limit, step, direction, } }; + let mut responses = Runtime::new().unwrap().block_on(async { + let (tx, rx) = mpsc::channel(0); + let getter_fut = sync_handlers::get_state_diffs(storage, request, tx); + let (_, response) = tokio::join!(getter_fut, rx.collect::>()); + response + }); + + // Make sure the last reply is Fin + assert_eq!(responses.pop().unwrap(), StateDiffsResponse::Fin); + + let mut actual_contract_updates = Vec::new(); + let mut actual_system_contract_updates = Vec::new(); + + // Check the rest + responses.into_iter().for_each(|response| match response { + StateDiffsResponse::ContractDiff(ContractDiff { address, nonce, class_hash, is_replaced, values, domain: _ }) => { + if address.0 == Felt::from_u64(1) { + actual_system_contract_updates.push( + ( + ContractAddress(address.0), + SystemContractUpdate { + storage: values.into_iter().map( + |ContractStoredValue { key, value }| (StorageAddress(key), StorageValue(value))).collect()} + )); + } else { + let class = match (class_hash, is_replaced) { + (Some(hash), Some(true)) => Some(ContractClassUpdate::Replace(ClassHash(hash))), + (Some(hash), Some(false)) => Some(ContractClassUpdate::Deploy(ClassHash(hash))), + (None, None) => None, + _ => panic!("unexpected response"), + }; + actual_contract_updates.push( + ( + ContractAddress(address.0), + ContractUpdate { + storage: values.into_iter().map(|ContractStoredValue { key, value }| + (StorageAddress(key), StorageValue(value))).collect(), + class, + nonce: nonce.map(ContractNonce)} + )); + } + + }, + _ => panic!("unexpected response"), + }); + + for expected_for_block in expected { + let actual_contract_updates_for_block = actual_contract_updates.drain(..expected_for_block.1.len()).collect::>(); + let actual_system_contract_updates_for_block = actual_system_contract_updates.drain(..expected_for_block.2.len()).collect::>(); + prop_assert_eq_sorted!(expected_for_block.1, actual_contract_updates_for_block, "block number: {}", expected_for_block.0); + prop_assert_eq_sorted!(expected_for_block.2, actual_system_contract_updates_for_block, "block number: {}", expected_for_block.0); + } + } + } + + proptest! { + #[test] + fn get_classes((num_blocks, seed, start_block, limit, step, direction) in strategy::composite()) { + // Fake storage with a given number of blocks + let (storage, in_db) = fixtures::storage_with_seed(seed, num_blocks); + // Compute the overlapping set between the db and the request + // These are the items that we expect to be read from the db + // Grouped by block number + let expected = overlapping::get(in_db, start_block, limit, step, num_blocks, direction).into_iter() + .map(|(h, _, _, _, cairo_defs, sierra_defs)| + ( + // Block number + h.number, + // List of tuples (Cairo class hash, Cairo definition bytes) + cairo_defs, + // List of tuples (Sierra class hash, Sierra definition bytes, Casm definition bytes) + sierra_defs + ) + ).collect::>(); + // Run the handler + let request = ClassesRequest { iteration: Iteration { start: BlockNumberOrHash::Number(start_block), limit, step, direction, } }; + let mut responses = Runtime::new().unwrap().block_on(async { + let (tx, rx) = mpsc::channel(0); + let getter_fut = sync_handlers::get_classes(storage, request, tx); + let (_, response) = tokio::join!(getter_fut, rx.collect::>()); + response + }); + + // Make sure the last reply is Fin + assert_eq!(responses.pop().unwrap(), ClassesResponse::Fin); + + // Check the rest + let mut actual_cairo = Vec::new(); + let mut actual_sierra = Vec::new(); + + responses.into_iter().for_each(|response| match response { + ClassesResponse::Class(Class::Cairo0 { class, domain: _, class_hash }) => { + actual_cairo.push((ClassHash(class_hash.0), cairo_def_from_dto(class).unwrap())); + }, + ClassesResponse::Class(Class::Cairo1 { class, domain: _, class_hash }) => { + let (sierra_def, casm_def) = sierra_defs_from_dto(class).unwrap(); + actual_sierra.push((SierraHash(class_hash.0), sierra_def, casm_def)); + }, + _ => panic!("unexpected response"), + }); + + for expected_for_block in expected { + let actual_cairo_for_block = actual_cairo.drain(..expected_for_block.1.len()).collect::>(); + let actual_sierra_for_block = actual_sierra.drain(..expected_for_block.2.len()).collect::>(); + + prop_assert_eq_sorted!(expected_for_block.1, actual_cairo_for_block, "block number: {}", expected_for_block.0); + prop_assert_eq_sorted!(expected_for_block.2, actual_sierra_for_block, "block number: {}", expected_for_block.0); + } + } + } + + mod workaround { + use pathfinder_common::transaction::{ + EntryPointType, InvokeTransactionV0, L1HandlerTransaction, Transaction, + TransactionVariant, + }; + use pathfinder_common::TransactionNonce; + + // Align with the deserialization workaround to avoid false negative mismatches + pub fn for_legacy_l1_handlers(tx: Transaction) -> Transaction { + match tx.variant { + TransactionVariant::InvokeV0(InvokeTransactionV0 { + entry_point_type: Some(EntryPointType::L1Handler), + calldata, + sender_address, + entry_point_selector, + max_fee: _, + signature: _, + }) => Transaction { + variant: TransactionVariant::L1Handler(L1HandlerTransaction { + contract_address: sender_address, + entry_point_selector, + nonce: TransactionNonce::ZERO, + calldata, + }), + hash: tx.hash, + }, + _ => tx, + } + } + } + + proptest! { + #[test] + fn get_transactions((num_blocks, seed, start_block, limit, step, direction) in strategy::composite()) { + // Fake storage with a given number of blocks + let (storage, in_db) = fixtures::storage_with_seed(seed, num_blocks); + // Compute the overlapping set between the db and the request + // These are the transactions that we expect to be read from the db + // Grouped by block number + let expected = overlapping::get(in_db, start_block, limit, step, num_blocks, direction).into_iter() + .map(|(h, _, tr, _, _, _)| + ( + // Block number + h.number, + // List of tuples (Transaction hash, Raw transaction variant) + tr.into_iter().map(|(t, _)| { + let txn = workaround::for_legacy_l1_handlers(t); + (txn.hash, RawTransactionVariant::from(txn.variant)) + }).collect::>() + ) + ).collect::>(); + // Run the handler + let request = TransactionsRequest { iteration: Iteration { start: BlockNumberOrHash::Number(start_block), limit, step, direction, } }; + let mut responses = Runtime::new().unwrap().block_on(async { + let (tx, rx) = mpsc::channel(0); + let getter_fut = sync_handlers::get_transactions(storage, request, tx); + let (_, responses) = tokio::join!(getter_fut, rx.collect::>()); + responses + }); + + // Make sure the last reply is Fin + assert_eq!(responses.pop().unwrap(), TransactionsResponse::Fin); + + // Check the rest + let mut actual = responses.into_iter().map(|response| match response { + TransactionsResponse::Transaction(txn) => (TransactionHash(txn.hash.0), RawTransactionVariant::try_from_dto(txn.variant).unwrap()), + _ => panic!("unexpected response"), + }).collect::>(); + + for expected_for_block in expected { + let actual_for_block = actual.drain(..expected_for_block.1.len()).collect::>(); + prop_assert_eq_sorted!(expected_for_block.1, actual_for_block, "block number: {}", expected_for_block.0); + } + } + } + + proptest! { + #[test] + fn get_receipts((num_blocks, seed, start_block, limit, step, direction) in strategy::composite()) { + // Fake storage with a given number of blocks + let (storage, in_db) = fixtures::storage_with_seed(seed, num_blocks); + // Compute the overlapping set between the db and the request + // These are the receipts that we expect to be read from the db + // Grouped by block number + let expected = overlapping::get(in_db, start_block, limit, step, num_blocks, direction).into_iter() + .map(|(h, _, tr, _, _, _)| + ( + // Block number + h.number, + // List of receipts + tr.into_iter().map(|(_, r)| P2PReceipt::from(r)).collect::>() + ) + ).collect::>(); + // Run the handler + let request = ReceiptsRequest { iteration: Iteration { start: BlockNumberOrHash::Number(start_block), limit, step, direction, } }; + let mut responses = Runtime::new().unwrap().block_on(async { + let (tx, rx) = mpsc::channel(0); + let getter_fut = sync_handlers::get_receipts(storage, request, tx); + let (_, responses) = tokio::join!(getter_fut, rx.collect::>()); + responses + }); + + // Make sure the last reply is Fin + assert_eq!(responses.pop().unwrap(), ReceiptsResponse::Fin); + + // Check the rest + let mut actual = responses.into_iter().map(|response| match response { + ReceiptsResponse::Receipt(receipt) => P2PReceipt::try_from(receipt).unwrap(), + _ => panic!("unexpected response"), + }).collect::>(); + + for expected_for_block in expected { + let actual_for_block = actual.drain(..expected_for_block.1.len()).collect::>(); + prop_assert_eq_sorted!(expected_for_block.1, actual_for_block, "block number: {}", expected_for_block.0); + } + } + } + + proptest! { + #[test] + fn get_events((num_blocks, seed, start_block, limit, step, direction) in strategy::composite()) { + // Fake storage with a given number of blocks + let (storage, in_db) = fixtures::storage_with_seed(seed, num_blocks); + // Compute the overlapping set between the db and the request + // These are the items that we expect to be read from the db + // Grouped by block number + let expected = overlapping::get(in_db, start_block, limit, step, num_blocks, direction).into_iter() + .map(|(h, _, tr, _, _, _)| + ( + // Block number + h.number, + // List of tuples (Transaction hash, Event) + tr.into_iter().flat_map(|(_, r)| r.events.into_iter().map(move |event| (r.transaction_hash, event))) + .collect::>() + ) + ).collect::>(); + // Run the handler + let request = EventsRequest { iteration: Iteration { start: BlockNumberOrHash::Number(start_block), limit, step, direction, } }; + let mut responses = Runtime::new().unwrap().block_on(async { + let (tx, rx) = mpsc::channel(0); + let getter_fut = sync_handlers::get_events(storage, request, tx); + let (_, response) = tokio::join!(getter_fut, rx.collect::>()); + response + }); + + // Make sure the last reply is Fin + assert_eq!(responses.pop().unwrap(), EventsResponse::Fin); + + // Check the rest + let mut actual = responses.into_iter().map(|response| match response { + EventsResponse::Event(event) => (TransactionHash(event.transaction_hash.0), Event::try_from_dto(event).unwrap()), + _ => panic!("unexpected response"), + }).collect::>(); + + for expected_for_block in expected { + let actual_for_block = actual.drain(..expected_for_block.1.len()).collect::>(); + prop_assert_eq_sorted!(expected_for_block.1, actual_for_block, "block number: {}", expected_for_block.0); + } + } + } + + /// Fixtures for prop tests + mod fixtures { + use crate::p2p_network::sync_handlers::MAX_COUNT_IN_TESTS; + use pathfinder_storage::fake::{with_n_blocks_and_rng, StorageInitializer}; + use pathfinder_storage::Storage; + + pub const MAX_NUM_BLOCKS: u64 = MAX_COUNT_IN_TESTS * 2; + + pub fn storage_with_seed(seed: u64, num_blocks: u64) -> (Storage, StorageInitializer) { + use rand::SeedableRng; + let storage = Storage::in_memory().unwrap(); + // Explicitly choose RNG to make sure seeded storage is always reproducible + let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(seed); + let initializer = + with_n_blocks_and_rng(&storage, num_blocks.try_into().unwrap(), &mut rng); + (storage, initializer) + } + } + + /// Find overlapping range between the DB and the request + mod overlapping { + use crate::p2p_network::sync_handlers::MAX_COUNT_IN_TESTS; + use p2p_proto::common::{Direction, Step}; + use pathfinder_storage::fake::{StorageInitializer, StorageInitializerItem}; + + pub fn get( + from_db: StorageInitializer, + start_block: u64, + limit: u64, + step: Step, + num_blocks: u64, + direction: Direction, + ) -> StorageInitializer { + match direction { + Direction::Forward => forward(from_db, start_block, limit, step).collect(), + Direction::Backward => { + backward(from_db, start_block, limit, step, num_blocks).collect() + } + } + } + + fn forward( + from_db: StorageInitializer, + start_block: u64, + limit: u64, + step: Step, + ) -> impl Iterator { + from_db + .into_iter() + .skip(start_block.try_into().unwrap()) + .step_by(step.into_inner().try_into().unwrap()) + .take(std::cmp::min(limit, MAX_COUNT_IN_TESTS).try_into().unwrap()) + } + + fn backward( + mut from_db: StorageInitializer, + start_block: u64, + limit: u64, + step: Step, + num_blocks: u64, + ) -> impl Iterator { + if start_block >= num_blocks { + // The is no overlapping range but we want to keep the iterator type in this + // branch type-consistent + from_db.clear(); + } + + from_db + .into_iter() + .take((start_block + 1).try_into().unwrap()) + .rev() + .step_by(step.into_inner().try_into().unwrap()) + .take(std::cmp::min(limit, MAX_COUNT_IN_TESTS).try_into().unwrap()) + } + } + + /// Building blocks for the ultimate composite strategy used in all property tests + mod strategy { + use super::fixtures::MAX_NUM_BLOCKS; + use crate::p2p_network::sync_handlers::tests::I64_MAX; + use p2p_proto::common::{Direction, Step}; + use proptest::prelude::*; + use std::ops::Range; + + prop_compose! { + fn inside(range: Range)(x in range) -> u64 { x } + } + + prop_compose! { + fn outside_le(range: Range, max: u64)(x in range.end..=max) -> u64 { x } + } + + fn rarely_outside_le(range: std::ops::Range, max: u64) -> BoxedStrategy { + // Empty range will trigger a panic in rand::distributions::Uniform + if range.is_empty() { + return Just(range.start).boxed(); + } + + prop_oneof![ + // Occurrence 4:1 + 4 => inside(range.clone()), + 1 => outside_le(range, max), + ] + .boxed() + } + + fn rarely_outside(range: std::ops::Range) -> BoxedStrategy { + rarely_outside_le(range, u64::MAX) + } + + prop_compose! { + pub fn composite() + (num_blocks in 0..MAX_NUM_BLOCKS) + ( + num_blocks in Just(num_blocks), + storage_seed in any::(), + // out of range (> i64::MAX) start values are tested in `empty_reply::` + start in rarely_outside_le(0..num_blocks, I64_MAX), + // limit of 0 is tested in `empty_reply::` + limit in rarely_outside(1..num_blocks), + // step is always >= 1 + step in rarely_outside(1..num_blocks / 4), + direction in prop_oneof![Just(Direction::Forward), Just(Direction::Backward)], + ) -> (u64, u64, u64, u64, Step, Direction) { + (num_blocks, storage_seed, start, limit, step.into(), direction) + } + } + } +} diff --git a/crates/pathfinder/src/state.rs b/crates/pathfinder/src/state.rs index f219b34476..440a4c47e7 100644 --- a/crates/pathfinder/src/state.rs +++ b/crates/pathfinder/src/state.rs @@ -1,4 +1,4 @@ pub mod block_hash; mod sync; -pub use sync::{l1, l2, sync, SyncContext}; +pub use sync::{l1, l2, sync, Gossiper, SyncContext}; diff --git a/crates/pathfinder/src/state/sync.rs b/crates/pathfinder/src/state/sync.rs index 92e64f62de..9f598525ea 100644 --- a/crates/pathfinder/src/state/sync.rs +++ b/crates/pathfinder/src/state/sync.rs @@ -20,7 +20,7 @@ use pathfinder_rpc::{ }; use pathfinder_storage::{Connection, Storage, Transaction, TransactionBehavior}; use primitive_types::H160; -use starknet_gateway_client::{GatewayApi, GossipApi}; +use starknet_gateway_client::GatewayApi; use starknet_gateway_types::reply::Block; use starknet_gateway_types::reply::PendingBlock; @@ -80,6 +80,7 @@ pub struct SyncContext { pub block_cache_size: usize, pub restart_delay: Duration, pub verify_tree_hashes: bool, + pub gossiper: Gossiper, } impl From<&SyncContext> for L1SyncContext @@ -111,6 +112,38 @@ where } } +#[derive(Debug, Default)] +pub struct Gossiper { + #[cfg(feature = "p2p")] + p2p_client: Option, +} + +impl Gossiper { + #[cfg(feature = "p2p")] + pub fn new(p2p_client: p2p::client::peer_agnostic::Client) -> Self { + Self { + p2p_client: Some(p2p_client), + } + } + + async fn propagate_head(&self, _block_number: BlockNumber, _block_hash: BlockHash) { + #[cfg(feature = "p2p")] + { + use p2p_proto::common::{BlockId, Hash}; + + if let Some(p2p_client) = &self.p2p_client { + _ = p2p_client + .propagate_new_head(BlockId { + number: _block_number.get(), + hash: Hash(_block_hash.0), + }) + .await + .map_err(|error| tracing::warn!(%error, "Propagating head failed")); + } + } + } +} + /// Implements the main sync loop, where L1 and L2 sync results are combined. pub async fn sync( context: SyncContext, @@ -119,7 +152,7 @@ pub async fn sync( ) -> anyhow::Result<()> where Ethereum: EthereumApi + Clone + Send + 'static, - SequencerClient: GatewayApi + GossipApi + Clone + Send + Sync + 'static, + SequencerClient: GatewayApi + Clone + Send + Sync + 'static, F1: Future> + Send + 'static, F2: Future> + Send + 'static, L1Sync: FnMut(mpsc::Sender, L1SyncContext) -> F1, @@ -149,6 +182,7 @@ where block_cache_size, restart_delay, verify_tree_hashes: _, + gossiper, } = context; let mut db_conn = storage @@ -181,6 +215,7 @@ where starting_block_hash, starting_block_num, head_poll_interval, + gossiper, )); // Start L1 producer task. Clone the event sender so that the channel remains open @@ -584,10 +619,11 @@ async fn latest_n_blocks( /// propagates latest head after every change or otherwise every 2 minutes. async fn update_sync_status_latest( state: Arc, - sequencer: impl GatewayApi + GossipApi, + sequencer: impl GatewayApi, starting_block_hash: BlockHash, starting_block_num: BlockNumber, poll_interval: Duration, + gossiper: Gossiper, ) -> anyhow::Result<()> { let starting = NumberedBlock::from((starting_block_hash, starting_block_num)); let mut last_propagated = Instant::now(); @@ -608,7 +644,7 @@ async fn update_sync_status_latest( metrics::gauge!("current_block", starting.number.get() as f64); metrics::gauge!("highest_block", latest.number.get() as f64); - propagate_head(&sequencer, &mut last_propagated, latest).await; + propagate_head(&gossiper, &mut last_propagated, latest).await; tracing::debug!( status=%sync_status, @@ -621,7 +657,7 @@ async fn update_sync_status_latest( metrics::gauge!("highest_block", latest.number.get() as f64); - propagate_head(&sequencer, &mut last_propagated, latest).await; + propagate_head(&gossiper, &mut last_propagated, latest).await; tracing::debug!( %status, @@ -633,7 +669,7 @@ async fn update_sync_status_latest( // duplicate_cache_time for gossipsub defaults to 1 minute if last_propagated.elapsed() > Duration::from_secs(120) { - propagate_head(&sequencer, &mut last_propagated, latest).await; + propagate_head(&gossiper, &mut last_propagated, latest).await; } } Err(e) => { @@ -645,11 +681,7 @@ async fn update_sync_status_latest( } } -async fn propagate_head( - gossiper: &impl GossipApi, - last_propagated: &mut Instant, - head: NumberedBlock, -) { +async fn propagate_head(gossiper: &Gossiper, last_propagated: &mut Instant, head: NumberedBlock) { _ = gossiper.propagate_head(head.number, head.hash).await; *last_propagated = Instant::now(); } diff --git a/crates/pathfinder/src/sync/p2p.rs b/crates/pathfinder/src/sync/p2p.rs index d0dff8e663..d29649230e 100644 --- a/crates/pathfinder/src/sync/p2p.rs +++ b/crates/pathfinder/src/sync/p2p.rs @@ -77,58 +77,59 @@ impl Sync { /// /// No guarantees are made about any headers newer than the anchor. async fn sync_headers(&self, anchor: EthereumStateUpdate) -> anyhow::Result<()> { - while let Some(gap) = - headers::next_gap(self.storage.clone(), anchor.block_number, anchor.block_hash) - .await - .context("Finding next gap in header chain")? - { - use futures::StreamExt; - use futures::TryStreamExt; + // FIXME + todo!(); + // while let Some(gap) = + // headers::next_gap(self.storage.clone(), anchor.block_number, anchor.block_hash) + // .await + // .context("Finding next gap in header chain")? + // { + // use futures::StreamExt; + // use futures::TryStreamExt; - // TODO: create a tracing scope for this gap start, stop. + // // TODO: create a tracing scope for this gap start, stop. - tracing::info!("Syncing headers"); + // tracing::info!("Syncing headers"); - // TODO: consider .inspect_ok(tracing::trace!) for each stage. - let result = self - .p2p - .clone() - // TODO: consider buffering in the client to reduce request latency. - .header_stream(gap.head, gap.tail, true) - .scan((gap.head, gap.head_hash, false), headers::check_continuity) - // TODO: rayon scope this. - .and_then(headers::verify) - // chunk so that persisting to storage can be batched. - .try_chunks(1024) - // TODO: Pull out remaining data from try_chunks error. - // try_chunks::Error is a tuple of Err(data, error) so we - // should re-stream that as Ok(data), Err(error). Right now - // we just map to Err(error). - .map_err(|e| e.1) - .and_then(|x| headers::persist(x, self.storage.clone())) - .inspect_ok(|x| tracing::info!(tail=%x.data.header.number, "Header chunk synced")) - // Drive stream to completion. - .try_fold((), |_state, _x| std::future::ready(Ok(()))) - .await; + // // TODO: consider .inspect_ok(tracing::trace!) for each stage. + // let result = self + // .p2p + // .clone() + // // TODO: consider buffering in the client to reduce request latency. + // .header_stream(gap.head, gap.tail, true) + // .scan((gap.head, gap.head_hash, false), headers::check_continuity) + // // TODO: rayon scope this. + // .and_then(headers::verify) + // // chunk so that persisting to storage can be batched. + // .try_chunks(1024) + // // TODO: Pull out remaining data from try_chunks error. + // // try_chunks::Error is a tuple of Err(data, error) so we + // // should re-stream that as Ok(data), Err(error). Right now + // // we just map to Err(error). + // .map_err(|e| e.1) + // .and_then(|x| headers::persist(x, self.storage.clone())) + // .inspect_ok(|x| tracing::info!(tail=%x.data.header.number, "Header chunk synced")) + // // Drive stream to completion. + // .try_fold((), |_state, _x| std::future::ready(Ok(()))) + // .await; - match result { - Ok(()) => { - tracing::info!("Syncing headers complete"); - } - Err(error) => { - if let Some(peer_data) = error.peer_id_and_data() { - // TODO: punish peer. - tracing::debug!( - peer=%peer_data.peer, block=%peer_data.data.header.number, %error, - "Error while streaming headers" - ); - } else { - tracing::debug!(%error, "Error while streaming headers"); - } - } - } - } - Ok(()) + // match result { + // Ok(()) => { + // tracing::info!("Syncing headers complete"); + // } + // Err(error) => { + // if let Some(peer_data) = error.peer_id_and_data() { + // // TODO: punish peer. + // tracing::debug!( + // peer=%peer_data.peer, block=%peer_data.data.header.number, %error, + // "Error while streaming headers" + // ); + // } else { + // tracing::debug!(%error, "Error while streaming headers"); + // } + // } + // } + // } } } diff --git a/crates/storage/src/connection.rs b/crates/storage/src/connection.rs index e3d5c006dd..bb162367ae 100644 --- a/crates/storage/src/connection.rs +++ b/crates/storage/src/connection.rs @@ -386,6 +386,11 @@ impl<'inner> Transaction<'inner> { class::casm_definition_at_with_block_number(self, block_id, class_hash) } + /// Returns hashes of Cairo and Sierra classes declared at a given block. + pub fn declared_classes_at(&self, block: BlockId) -> anyhow::Result> { + state_update::declared_classes_at(self, block) + } + pub fn contract_class_hash( &self, block_id: BlockId, diff --git a/crates/storage/src/connection/state_update.rs b/crates/storage/src/connection/state_update.rs index 3333b0f4bf..80a2ac22d3 100644 --- a/crates/storage/src/connection/state_update.rs +++ b/crates/storage/src/connection/state_update.rs @@ -7,6 +7,8 @@ use pathfinder_common::{ use crate::{prelude::*, BlockId}; +use super::block::block_id; + /// Inserts a canonical [StateUpdate] into storage. pub(super) fn insert_state_update( tx: &Transaction<'_>, @@ -273,6 +275,39 @@ pub(super) fn state_update( Ok(Some(state_update)) } +pub(super) fn declared_classes_at( + tx: &Transaction<'_>, + block: BlockId, +) -> anyhow::Result> { + let Some((block_number, _)) = block_id(tx, block).context("Querying block header")? else { + return Ok(Vec::new()); + }; + + let mut stmt = tx + .inner() + .prepare_cached(r"SELECT hash FROM class_definitions WHERE block_number = ?") + .context("Preparing class declaration query statement")?; + + let mut declared_classes = stmt + .query_map(params![&block_number], |row| { + let class_hash: ClassHash = row.get_class_hash(0)?; + Ok(class_hash) + }) + .context("Querying class declarations")?; + + let mut result = Vec::new(); + + while let Some(class_hash) = declared_classes + .next() + .transpose() + .context("Iterating over class declaration query rows")? + { + result.push(class_hash); + } + + Ok(result) +} + pub(super) fn storage_value( tx: &Transaction<'_>, block: BlockId, diff --git a/crates/storage/src/connection/transaction.rs b/crates/storage/src/connection/transaction.rs index afadbd84da..14d49965d1 100644 --- a/crates/storage/src/connection/transaction.rs +++ b/crates/storage/src/connection/transaction.rs @@ -338,8 +338,9 @@ pub(super) fn transaction_block_hash( /// A copy of the gateway definitions which are currently used as the storage serde implementation. Having a copy here /// allows us to decouple this crate from the gateway types, while only exposing the common types via the storage API. pub(crate) mod dto { - use fake::Dummy; + use fake::{Dummy, Fake, Faker}; use pathfinder_common::*; + use pathfinder_crypto::Felt; use pathfinder_serde::{ CallParamAsDecimalStr, ConstructorParamAsDecimalStr, EthereumAddressAsHexStr, L2ToL1MessagePayloadElemAsDecimalStr, ResourceAmountAsHexStr, ResourcePricePerUnitAsHexStr, @@ -378,7 +379,7 @@ pub(crate) mod dto { } /// Represents execution resources for L2 transaction. - #[derive(Copy, Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq, Dummy)] + #[derive(Copy, Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub struct ExecutionResources { pub builtin_instance_counter: BuiltinCounters, @@ -406,10 +407,20 @@ pub(crate) mod dto { } } + impl Dummy for ExecutionResources { + fn dummy_with_rng(_: &T, rng: &mut R) -> Self { + Self { + builtin_instance_counter: Faker.fake_with_rng(rng), + n_steps: rng.next_u32() as u64, + n_memory_holes: rng.next_u32() as u64, + } + } + } + // This struct purposefully allows for unknown fields as it is not critical to // store these counters perfectly. Failure would be far more costly than simply // ignoring them. - #[derive(Copy, Clone, Default, Debug, Deserialize, Serialize, PartialEq, Eq, Dummy)] + #[derive(Copy, Clone, Default, Debug, Deserialize, Serialize, PartialEq, Eq)] #[serde(default)] pub struct BuiltinCounters { pub output_builtin: u64, @@ -479,6 +490,22 @@ pub(crate) mod dto { } } + impl Dummy for BuiltinCounters { + fn dummy_with_rng(_: &T, rng: &mut R) -> Self { + Self { + output_builtin: rng.next_u32() as u64, + pedersen_builtin: rng.next_u32() as u64, + range_check_builtin: rng.next_u32() as u64, + ecdsa_builtin: rng.next_u32() as u64, + bitwise_builtin: rng.next_u32() as u64, + ec_op_builtin: rng.next_u32() as u64, + keccak_builtin: rng.next_u32() as u64, + poseidon_builtin: rng.next_u32() as u64, + segment_arena_builtin: 0, // Not used in p2p + } + } + } + /// Represents deserialized L2 to L1 message. #[serde_as] #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Dummy)] @@ -532,7 +559,7 @@ pub(crate) mod dto { } /// Represents deserialized L2 transaction receipt data. - #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Dummy)] + #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub struct Receipt { #[serde(default)] @@ -613,6 +640,27 @@ pub(crate) mod dto { } } + impl Dummy for Receipt { + fn dummy_with_rng(_: &T, rng: &mut R) -> Self { + let execution_status = Faker.fake_with_rng(rng); + let revert_error = + (execution_status == ExecutionStatus::Reverted).then(|| Faker.fake_with_rng(rng)); + + // Those fields that were missing in very old receipts are always present + Self { + actual_fee: Some(Faker.fake_with_rng(rng)), + execution_resources: Some(Faker.fake_with_rng(rng)), + events: Faker.fake_with_rng(rng), + l1_to_l2_consumed_message: Faker.fake_with_rng(rng), + l2_to_l1_messages: Faker.fake_with_rng(rng), + transaction_hash: Faker.fake_with_rng(rng), + transaction_index: Faker.fake_with_rng(rng), + execution_status, + revert_error, + } + } + } + #[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Dummy)] pub enum DataAvailabilityMode { #[default] @@ -1309,7 +1357,7 @@ pub(crate) mod dto { } } - #[derive(Clone, Debug, Serialize, PartialEq, Eq, Dummy)] + #[derive(Clone, Debug, Serialize, PartialEq, Eq)] #[serde(tag = "version")] pub enum DeclareTransaction { #[serde(rename = "0x0")] @@ -1371,6 +1419,22 @@ pub(crate) mod dto { } } + impl Dummy for DeclareTransaction { + fn dummy_with_rng(_: &T, rng: &mut R) -> Self { + match rng.gen_range(0..=3) { + 0 => { + let mut v0: DeclareTransactionV0V1 = Faker.fake_with_rng(rng); + v0.nonce = TransactionNonce::ZERO; + Self::V0(v0) + } + 1 => Self::V1(Faker.fake_with_rng(rng)), + 2 => Self::V2(Faker.fake_with_rng(rng)), + 3 => Self::V3(Faker.fake_with_rng(rng)), + _ => unreachable!(), + } + } + } + /// A version 0 or 1 declare transaction. #[serde_as] #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Dummy)] @@ -1404,7 +1468,7 @@ pub(crate) mod dto { /// A version 2 declare transaction. #[serde_as] - #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Dummy)] + #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub struct DeclareTransactionV3 { pub class_hash: ClassHash, @@ -1431,9 +1495,30 @@ pub(crate) mod dto { TransactionVersion::ZERO } + impl Dummy for DeclareTransactionV3 { + fn dummy_with_rng(_: &T, rng: &mut R) -> Self { + Self { + class_hash: Faker.fake_with_rng(rng), + + nonce: Faker.fake_with_rng(rng), + nonce_data_availability_mode: Faker.fake_with_rng(rng), + fee_data_availability_mode: Faker.fake_with_rng(rng), + resource_bounds: Faker.fake_with_rng(rng), + tip: Faker.fake_with_rng(rng), + paymaster_data: vec![Faker.fake_with_rng(rng)], // TODO p2p allows 1 elem only + + sender_address: Faker.fake_with_rng(rng), + signature: Faker.fake_with_rng(rng), + transaction_hash: Faker.fake_with_rng(rng), + compiled_class_hash: Faker.fake_with_rng(rng), + account_deployment_data: vec![Faker.fake_with_rng(rng)], // TODO p2p allows 1 elem only + } + } + } + /// Represents deserialized L2 deploy transaction data. #[serde_as] - #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Dummy)] + #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub struct DeployTransaction { pub contract_address: ContractAddress, @@ -1446,6 +1531,19 @@ pub(crate) mod dto { pub version: TransactionVersion, } + impl Dummy for DeployTransaction { + fn dummy_with_rng(_: &T, rng: &mut R) -> Self { + Self { + version: TransactionVersion(Felt::from_u64(rng.gen_range(0..=1))), + contract_address: ContractAddress::ZERO, // Faker.fake_with_rng(rng), FIXME + contract_address_salt: Faker.fake_with_rng(rng), + class_hash: Faker.fake_with_rng(rng), + constructor_calldata: Faker.fake_with_rng(rng), + transaction_hash: Faker.fake_with_rng(rng), + } + } + } + /// Represents deserialized L2 deploy account transaction data. #[derive(Clone, Debug, Serialize, PartialEq, Eq, Dummy)] #[serde(untagged)] @@ -1503,7 +1601,7 @@ pub(crate) mod dto { } #[serde_as] - #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Dummy)] + #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub struct DeployAccountTransactionV0V1 { pub contract_address: ContractAddress, @@ -1519,8 +1617,32 @@ pub(crate) mod dto { pub class_hash: ClassHash, } + impl Dummy for DeployAccountTransactionV0V1 { + fn dummy_with_rng(_: &T, rng: &mut R) -> Self { + let contract_address_salt = Faker.fake_with_rng(rng); + let constructor_calldata: Vec = Faker.fake_with_rng(rng); + let class_hash = Faker.fake_with_rng(rng); + + Self { + version: TransactionVersion::ONE, + contract_address: ContractAddress::deployed_contract_address( + constructor_calldata.iter().copied(), + &contract_address_salt, + &class_hash, + ), + transaction_hash: Faker.fake_with_rng(rng), + max_fee: Faker.fake_with_rng(rng), + signature: Faker.fake_with_rng(rng), + nonce: Faker.fake_with_rng(rng), + contract_address_salt, + constructor_calldata, + class_hash, + } + } + } + #[serde_as] - #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Dummy)] + #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub struct DeployAccountTransactionV3 { pub nonce: TransactionNonce, @@ -1542,6 +1664,35 @@ pub(crate) mod dto { pub class_hash: ClassHash, } + impl Dummy for DeployAccountTransactionV3 { + fn dummy_with_rng(_: &T, rng: &mut R) -> Self { + let contract_address_salt = Faker.fake_with_rng(rng); + let constructor_calldata: Vec = Faker.fake_with_rng(rng); + let class_hash = Faker.fake_with_rng(rng); + + Self { + nonce: Faker.fake_with_rng(rng), + nonce_data_availability_mode: Faker.fake_with_rng(rng), + fee_data_availability_mode: Faker.fake_with_rng(rng), + resource_bounds: Faker.fake_with_rng(rng), + tip: Faker.fake_with_rng(rng), + paymaster_data: vec![Faker.fake_with_rng(rng)], // TODO p2p allows 1 elem only + + sender_address: ContractAddress::deployed_contract_address( + constructor_calldata.iter().copied(), + &contract_address_salt, + &class_hash, + ), + signature: Faker.fake_with_rng(rng), + transaction_hash: Faker.fake_with_rng(rng), + version: TransactionVersion::THREE, + contract_address_salt, + constructor_calldata, + class_hash, + } + } + } + #[derive(Clone, Debug, Serialize, PartialEq, Eq, Dummy)] #[serde(tag = "version")] pub enum InvokeTransaction { @@ -1600,7 +1751,7 @@ pub(crate) mod dto { /// Represents deserialized L2 invoke transaction v0 data. #[serde_as] - #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Dummy)] + #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub struct InvokeTransactionV0 { #[serde_as(as = "Vec")] @@ -1620,6 +1771,20 @@ pub(crate) mod dto { pub transaction_hash: TransactionHash, } + impl Dummy for InvokeTransactionV0 { + fn dummy_with_rng(_: &T, rng: &mut R) -> Self { + Self { + calldata: Faker.fake_with_rng(rng), + sender_address: Faker.fake_with_rng(rng), + entry_point_selector: Faker.fake_with_rng(rng), + entry_point_type: None, + max_fee: Faker.fake_with_rng(rng), + signature: Faker.fake_with_rng(rng), + transaction_hash: Faker.fake_with_rng(rng), + } + } + } + /// Represents deserialized L2 invoke transaction v1 data. #[serde_as] #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Dummy)] @@ -1642,7 +1807,7 @@ pub(crate) mod dto { /// Represents deserialized L2 invoke transaction v3 data. #[serde_as] - #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Dummy)] + #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub struct InvokeTransactionV3 { pub nonce: TransactionNonce, @@ -1663,9 +1828,28 @@ pub(crate) mod dto { pub account_deployment_data: Vec, } + impl Dummy for InvokeTransactionV3 { + fn dummy_with_rng(_: &T, rng: &mut R) -> Self { + Self { + nonce: Faker.fake_with_rng(rng), + nonce_data_availability_mode: Faker.fake_with_rng(rng), + fee_data_availability_mode: Faker.fake_with_rng(rng), + resource_bounds: Faker.fake_with_rng(rng), + tip: Faker.fake_with_rng(rng), + paymaster_data: vec![Faker.fake_with_rng(rng)], // TODO p2p allows 1 elem only + + sender_address: Faker.fake_with_rng(rng), + signature: Faker.fake_with_rng(rng), + transaction_hash: Faker.fake_with_rng(rng), + calldata: Faker.fake_with_rng(rng), + account_deployment_data: vec![Faker.fake_with_rng(rng)], // TODO p2p allows 1 elem only + } + } + } + /// Represents deserialized L2 "L1 handler" transaction data. #[serde_as] - #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Dummy)] + #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub struct L1HandlerTransaction { pub contract_address: ContractAddress, @@ -1678,6 +1862,21 @@ pub(crate) mod dto { pub version: TransactionVersion, } + impl Dummy for L1HandlerTransaction { + fn dummy_with_rng(_: &T, rng: &mut R) -> Self { + Self { + // TODO verify this is the only realistic value + version: TransactionVersion::ZERO, + + contract_address: Faker.fake_with_rng(rng), + entry_point_selector: Faker.fake_with_rng(rng), + nonce: Faker.fake_with_rng(rng), + calldata: Faker.fake_with_rng(rng), + transaction_hash: Faker.fake_with_rng(rng), + } + } + } + /// Describes L2 transaction failure details. #[derive(Clone, Debug, Deserialize, PartialEq, Eq)] #[serde(deny_unknown_fields)]