Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ members = ["crates/*"]
resolver = "2"

[workspace.package]
version = "0.16.0-rc.13"
version = "0.16.0-rc.14"
edition = "2021"
rust-version = "1.87"
authors = ["init4"]
Expand Down Expand Up @@ -34,19 +34,19 @@ debug = false
incremental = false

[workspace.dependencies]
signet-bundle = { version = "0.16.0-rc.13", path = "crates/bundle" }
signet-constants = { version = "0.16.0-rc.13", path = "crates/constants" }
signet-evm = { version = "0.16.0-rc.13", path = "crates/evm" }
signet-extract = { version = "0.16.0-rc.13", path = "crates/extract" }
signet-journal = { version = "0.16.0-rc.13", path = "crates/journal" }
signet-node = { version = "0.16.0-rc.13", path = "crates/node" }
signet-orders = { version = "0.16.0-rc.13", path = "crates/orders" }
signet-sim = { version = "0.16.0-rc.13", path = "crates/sim" }
signet-types = { version = "0.16.0-rc.13", path = "crates/types" }
signet-tx-cache = { version = "0.16.0-rc.13", path = "crates/tx-cache" }
signet-zenith = { version = "0.16.0-rc.13", path = "crates/zenith" }
signet-bundle = { version = "0.16.0-rc.14", path = "crates/bundle" }
signet-constants = { version = "0.16.0-rc.14", path = "crates/constants" }
signet-evm = { version = "0.16.0-rc.14", path = "crates/evm" }
signet-extract = { version = "0.16.0-rc.14", path = "crates/extract" }
signet-journal = { version = "0.16.0-rc.14", path = "crates/journal" }
signet-node = { version = "0.16.0-rc.14", path = "crates/node" }
signet-orders = { version = "0.16.0-rc.14", path = "crates/orders" }
signet-sim = { version = "0.16.0-rc.14", path = "crates/sim" }
signet-types = { version = "0.16.0-rc.14", path = "crates/types" }
signet-tx-cache = { version = "0.16.0-rc.14", path = "crates/tx-cache" }
signet-zenith = { version = "0.16.0-rc.14", path = "crates/zenith" }

signet-test-utils = { version = "0.16.0-rc.13", path = "crates/test-utils" }
signet-test-utils = { version = "0.16.0-rc.14", path = "crates/test-utils" }

# trevm
trevm = { version = "0.34.1", features = ["full_env_cfg", "asyncdb"] }
Expand Down
2 changes: 1 addition & 1 deletion crates/extract/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ mod extractor;
pub use extractor::Extractor;

mod r#trait;
pub use r#trait::{Extractable, HasTxns};
pub use r#trait::{BlockAndReceipts, Extractable, HasTxns};

mod step;
pub use step::ExtractStep;
8 changes: 3 additions & 5 deletions crates/extract/src/step.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ pub trait ExtractStep<C: Extractable> {
{
extractable
.blocks_and_receipts()
.map(|(block, receipts)| (block, self.extract_block(block, receipts)))
.map(|bar| (bar.block, self.extract_block(bar.block, bar.receipts)))
}
}

Expand Down Expand Up @@ -142,9 +142,7 @@ where
{
extractable
.blocks_and_receipts()
.filter(|(host_block, _)| host_block.number() > self.host_deploy_height())
.map(|(host_block, receipts)| {
(host_block, ExtractStep::<C>::extract_block(self, host_block, receipts))
})
.filter(|bar| bar.block.number() > self.host_deploy_height())
.map(|bar| (bar.block, ExtractStep::<C>::extract_block(self, bar.block, bar.receipts)))
}
}
59 changes: 57 additions & 2 deletions crates/extract/src/trait.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,72 @@
use alloy::{consensus::TxReceipt, primitives::Log};
use alloy::{
consensus::{BlockHeader, TxReceipt},
primitives::Log,
};
use signet_types::primitives::TransactionSigned;

/// A block with its associated receipts, yielded by
/// [`Extractable::blocks_and_receipts`].
///
/// ```
/// # use alloy::consensus::BlockHeader;
/// # use signet_extract::BlockAndReceipts;
/// # fn example(bar: BlockAndReceipts<'_, alloy::consensus::Header, alloy::consensus::ReceiptEnvelope>) {
/// let _number = bar.block.number();
/// let _count = bar.receipts.len();
/// # }
/// ```
#[derive(Debug, Clone, Copy)]
pub struct BlockAndReceipts<'a, B, R> {
/// The block.
pub block: &'a B,
/// The receipts for this block's transactions.
pub receipts: &'a [R],
}

/// A trait for types from which data can be extracted. This currently exists
/// to provide a common interface for extracting data from host chain blocks
/// and receipts which may be in alloy or reth types.
///
/// Implementors must guarantee that the segment is non-empty — i.e.,
/// [`blocks_and_receipts`] always yields at least one item. An empty
/// extractable segment is not meaningful.
///
/// [`blocks_and_receipts`]: Extractable::blocks_and_receipts
#[allow(clippy::len_without_is_empty)]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#[allow(clippy::len_without_is_empty)]
#[expect(clippy::len_without_is_empty, reason = "non-empty invariant makes is_empty() always false")]

pub trait Extractable: core::fmt::Debug + Sync {
/// The block type that this extractor works with.
type Block: alloy::consensus::BlockHeader + HasTxns + core::fmt::Debug + Sync;
/// The receipt type that this extractor works with.
type Receipt: TxReceipt<Log = Log> + core::fmt::Debug + Sync;

/// An iterator over the blocks and their receipts.
fn blocks_and_receipts(&self) -> impl Iterator<Item = (&Self::Block, &Vec<Self::Receipt>)>;
///
/// Blocks must be yielded in ascending order by block number. The
/// iterator must yield at least one item.
fn blocks_and_receipts(
&self,
) -> impl Iterator<Item = BlockAndReceipts<'_, Self::Block, Self::Receipt>>;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could maybe provide something like

pub struct NonEmptyIterator<T, I> {
    first: T
    remainder: I
}

where this guarantees the iterator has at least one item. We'd probably still have an expect when we construct this iterator (when we create it inside blocks_and_receipts impl), but from there on, we don't need to do any more expects.

Maybe overkill though - I'm not strongly in favour of this idea :)


/// Block number of the first block in the segment.
fn first_number(&self) -> u64 {
self.blocks_and_receipts().next().expect("Extractable must be non-empty").block.number()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be helpful to use different strings for the two expect cases (here and in tip_number) to help disambiguate in case one gets hit.

}

/// Block number of the tip (last block) in the segment.
///
/// The default implementation consumes the entire iterator. Implementors
/// with indexed access should override this.
fn tip_number(&self) -> u64 {
self.blocks_and_receipts().last().expect("Extractable must be non-empty").block.number()
}

/// Number of blocks in the segment. Always `>= 1`.
///
/// The default implementation consumes the entire iterator. Implementors
/// that know their length should override this.
fn len(&self) -> usize {
self.blocks_and_receipts().count()
}
}

/// A trait for types that contain transactions. This currently exists to
Expand Down
89 changes: 78 additions & 11 deletions crates/test-utils/src/chain.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
use alloy::{
consensus::{Header, ReceiptEnvelope},
consensus::{BlockHeader, Header, ReceiptEnvelope},
primitives::{B256, B64, U256},
};
pub use signet_constants::test_utils::*;
use signet_evm::ExecutionOutcome;
use signet_extract::Extractable;
use signet_extract::{BlockAndReceipts, Extractable};
use signet_types::primitives::{RecoveredBlock, SealedBlock, SealedHeader};

/// A simple chain of blocks with receipts.
#[derive(Clone, Default, PartialEq, Eq)]
/// A simple, non-empty chain of blocks with receipts.
#[derive(Clone, PartialEq, Eq)]
pub struct Chain {
/// The blocks
pub blocks: Vec<RecoveredBlock>,

pub execution_outcome: ExecutionOutcome,
/// The blocks. Invariant: always non-empty.
blocks: Vec<RecoveredBlock>,
execution_outcome: ExecutionOutcome,
}

impl core::fmt::Debug for Chain {
Expand All @@ -23,26 +22,67 @@ impl core::fmt::Debug for Chain {
}

impl Chain {
/// Create a new chain from a block
/// Create a new chain from a single block.
pub fn from_block(block: RecoveredBlock, execution_outcome: ExecutionOutcome) -> Self {
Self { blocks: vec![block], execution_outcome }
}

/// Append a block to the chain.
pub fn append_block(&mut self, block: RecoveredBlock, outcome: ExecutionOutcome) {
self.blocks.push(block);
self.execution_outcome.append(outcome);
}

/// Get the blocks in the chain.
pub fn blocks(&self) -> &[RecoveredBlock] {
&self.blocks
}

/// Get the execution outcome.
pub fn execution_outcome(&self) -> &ExecutionOutcome {
&self.execution_outcome
}
}

impl Extractable for Chain {
type Block = RecoveredBlock;
type Receipt = ReceiptEnvelope;

fn blocks_and_receipts(&self) -> impl Iterator<Item = (&Self::Block, &Vec<Self::Receipt>)> {
self.blocks.iter().zip(self.execution_outcome.receipts().iter())
fn blocks_and_receipts(
&self,
) -> impl Iterator<Item = BlockAndReceipts<'_, Self::Block, Self::Receipt>> {
self.blocks
.iter()
.zip(self.execution_outcome.receipts().iter())
.map(|(block, receipts)| BlockAndReceipts { block, receipts })
}

fn first_number(&self) -> u64 {
self.blocks.first().expect("Chain must be non-empty").number()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing here re disambiguating the expect strings (although less important as this is test code)

}

fn tip_number(&self) -> u64 {
self.blocks.last().expect("Chain must be non-empty").number()
}

fn len(&self) -> usize {
self.blocks.len()
}
}

/// Make a chain with `count` fake blocks numbered `0..count`.
///
/// # Panics
///
/// Panics if `count` is 0, as an empty chain is not valid.
pub fn fake_chain(count: u64) -> Chain {
assert!(count > 0, "fake_chain requires at least one block");
let blocks: Vec<_> = (0..count).map(fake_block).collect();
let receipts = vec![vec![]; count as usize];
let execution_outcome = ExecutionOutcome::new(Default::default(), receipts, 0);
Chain { blocks, execution_outcome }
}

/// Make a fake block with a specific number.
pub fn fake_block(number: u64) -> RecoveredBlock {
let header = Header {
Expand All @@ -57,3 +97,30 @@ pub fn fake_block(number: u64) -> RecoveredBlock {
let sealed = SealedHeader::new(header);
SealedBlock::new(sealed, vec![]).recover_unchecked(vec![])
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
#[should_panic(expected = "fake_chain requires at least one block")]
fn fake_chain_rejects_zero() {
fake_chain(0);
}

#[test]
fn single_block_metadata() {
let chain = fake_chain(1);
assert_eq!(chain.len(), 1);
assert_eq!(chain.first_number(), 0);
assert_eq!(chain.tip_number(), 0);
}

#[test]
fn multi_block_metadata() {
let chain = fake_chain(5);
assert_eq!(chain.len(), 5);
assert_eq!(chain.first_number(), 0);
assert_eq!(chain.tip_number(), 4);
}
}