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
39 changes: 2 additions & 37 deletions examples/checkpoints-eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,11 @@
//! test-utils provided helpers for creating mock instances.

use {
alloy::{
consensus::{EthereumTxEnvelope, Transaction, TxEip4844},
network::{TransactionBuilder, TxSignerSync},
primitives::{Address, U256},
signers::local::PrivateKeySigner,
},
alloy::{consensus::Transaction, primitives::U256},
rblib::{
alloy,
prelude::*,
reth,
test_utils::{BlockContextMocked, FundedAccounts},
},
reth::{
ethereum::{TransactionSigned, primitives::SignedTransaction},
primitives::Recovered,
rpc::types::TransactionRequest,
test_utils::{BlockContextMocked, FundedAccounts, transfer_tx},
},
};

Expand Down Expand Up @@ -69,27 +58,3 @@ fn main() -> eyre::Result<()> {

Ok(())
}

fn transfer_tx(
signer: &PrivateKeySigner,
nonce: u64,
value: U256,
) -> Recovered<EthereumTxEnvelope<TxEip4844>> {
let mut tx = TransactionRequest::default()
.with_nonce(nonce)
.with_to(Address::random())
.value(value)
.with_gas_price(1_000_000_000)
.with_gas_limit(21_000)
.with_max_priority_fee_per_gas(1_000_000)
.with_max_fee_per_gas(2_000_000)
.build_unsigned()
.expect("valid transaction request");

let sig = signer
.sign_transaction_sync(&mut tx)
.expect("signing should succeed");

TransactionSigned::new_unhashed(tx.into(), sig) //
.with_signer(signer.address())
}
77 changes: 77 additions & 0 deletions src/payload/checkpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -546,3 +546,80 @@ impl<P: Platform> Display for Checkpoint<P> {
}
}
}

#[cfg(test)]
mod tests {
use {
crate::{
alloy::primitives::U256,
prelude::{BlockContext, Ethereum},
test_utils::{BlockContextMocked, FundedAccounts, transfer_tx},
},
std::time::Instant,
};

#[test]
fn test_barrier_depth_and_is_barrier() {
let block = BlockContext::<Ethereum>::mocked();

let checkpoint = block.start();

// Expected: initial checkpoint is depth 0 and is barrier
assert_eq!(checkpoint.depth(), 0);
assert!(checkpoint.is_barrier());
assert!(checkpoint.prev().is_none());
}

#[test]
fn test_named_barrier_and_prev_depth() {
// Outline:
// 1. create initial checkpoint (depth 0)
// 2. create named barrier on top
// 3. verify new depth is 1, prev is initial, and is_named_barrier returns
// true
let block = BlockContext::<Ethereum>::mocked();

let root = block.start();

let named = root.barrier_with_tag("sequencer-synced");

assert_eq!(named.depth(), root.depth() + 1);
assert_eq!(named.tag(), Some("sequencer-synced"));
assert!(named.prev().is_some());
assert_eq!(named.prev().unwrap().depth(), root.depth());
}

#[test]
fn test_created_at() {
let block = BlockContext::<Ethereum>::mocked();

let before = Instant::now();
let cp = block.start();
let after = Instant::now();
assert!((before..=after).contains(&cp.created_at()));
}

#[test]
fn test_iter() {
let block = BlockContext::<Ethereum>::mocked();

let checkpoint = block.start();

let checkpoint2 = checkpoint.barrier();
let checkpoint3 = checkpoint2.barrier();

let tx = transfer_tx(&FundedAccounts::signer(0), 0, U256::from(10u64));
let checkpoint4 = checkpoint3.apply(tx).unwrap();

let history: Vec<_> = checkpoint4.into_iter().collect();
assert_eq!(history.len(), 4);
assert_eq!(history[0], checkpoint4);
assert_eq!(history[0].depth(), 3);
assert_eq!(history[1], checkpoint3);
assert_eq!(history[1].depth(), 2);
assert_eq!(history[2], checkpoint2);
assert_eq!(history[2].depth(), 1);
assert_eq!(history[3], checkpoint);
assert_eq!(history[3].depth(), 0);
}
}
234 changes: 234 additions & 0 deletions src/payload/ext/checkpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -327,3 +327,237 @@ impl<P: Platform> CheckpointExt<P> for Checkpoint<P> {
self.root().created_at()
}
}

#[cfg(test)]
mod tests {
use {
crate::{
alloy::primitives::{Address, U256},
payload::{Checkpoint, CheckpointExt},
prelude::{BlockContext, Ethereum},
test_utils::{BlockContextMocked, FundedAccounts, transfer_tx},
},
std::{
thread,
time::{Duration, Instant},
},
};

#[test]
fn test_new_at_block() {
let block = BlockContext::<Ethereum>::mocked();
let cp = Checkpoint::new_at_block(block);

let cp2 = cp.barrier();
let cp3 = cp2.barrier();

assert!(cp.is_empty());
assert_eq!(cp2.root(), cp);
assert_eq!(cp3.root(), cp);

assert_eq!(cp.gas_used(), 0);
assert_eq!(cp.cumulative_gas_used(), 0);

assert_eq!(cp.effective_tip_per_gas(), 0);
assert!(!cp.has_blobs());
assert_eq!(cp.blob_gas_used(), Some(0));
assert_eq!(cp.cumulative_blob_gas_used(), 0);

let span1 = cp3.to(&cp).unwrap();
let span2 = cp.to(&cp3).unwrap();

assert_eq!(span1.len(), span2.len());
for i in 0..span2.len() {
assert_eq!(span1.at(i), span2.at(i));
}
assert_eq!(span1.len(), 3);
assert_eq!(span2.len(), 3);

let addr = Address::ZERO;
assert_eq!(cp.balance_of(addr).unwrap(), U256::ZERO);
assert_eq!(cp.nonce_of(addr).unwrap(), 0);

assert!(cp.signers().is_empty());
assert!(cp.nonces().is_empty());

assert_eq!(cp.hash(), None);
assert!(!cp.is_bundle());
assert!(!cp.has_failures());
assert_eq!(cp.failed_txs().count(), 0);

let random_addr = Address::random();
assert_eq!(
cp.balance_of(random_addr).unwrap(),
U256::ZERO,
"Nonexistent account should have zero balance"
);

assert_eq!(
cp.nonce_of(random_addr).unwrap(),
0,
"Nonexistent account should have zero nonce"
);
}

#[test]
fn test_contains_is_false_without_txs() {
let block = BlockContext::<Ethereum>::mocked();
let cp1 = Checkpoint::new_at_block(block);

let tx1 = transfer_tx(&FundedAccounts::signer(0), 0, U256::from(50_000u64));
let tx1_hash = *tx1.hash();
assert!(!cp1.contains(tx1_hash));
let cp2 = cp1.apply(tx1).unwrap();

assert!(cp2.contains(tx1_hash));
}

#[test]
fn test_history_timestamps() {
let block = BlockContext::<Ethereum>::mocked();
let cp1 = Checkpoint::new_at_block(block);

thread::sleep(Duration::from_millis(5));

let cp2 = cp1.barrier();

assert!(cp2.building_since() <= Instant::now());
assert!(cp2.building_since() >= cp1.created_at());
}

#[test]
fn test_to_self() {
let block = BlockContext::<Ethereum>::mocked();
let cp = Checkpoint::new_at_block(block);

// to(self, self) should produce a span of length 1 containing the
// checkpoint itself
let span = cp.to(&cp).expect("to(self,self) must succeed");
assert_eq!(span.len(), 1);
assert_eq!(*span.at(0).unwrap(), cp);
}

#[test]
fn test_to_non_linear_error() {
let block_a = BlockContext::<Ethereum>::mocked();
let block_b = BlockContext::<Ethereum>::mocked();

let cp_a = Checkpoint::new_at_block(block_a);
let cp_b = Checkpoint::new_at_block(block_b);

// They are not on the same linear history, so to should return an Err.
assert!(cp_a.to(&cp_b).is_err());
assert!(cp_b.to(&cp_a).is_err());
}

#[test]
fn test_to_includes_all_intermediates_and_is_linear() {
let block = BlockContext::<Ethereum>::mocked();
let base = Checkpoint::new_at_block(block);

// base -> x -> y
let tx_x = transfer_tx(&FundedAccounts::signer(0), 0, U256::from(10u64));
let x = base.apply(tx_x).unwrap();

let tx_y = transfer_tx(&FundedAccounts::signer(1), 0, U256::from(20u64));
let y = x.apply(tx_y).unwrap();

let x_barrier = x.barrier();
let y_barrier = y.barrier();

// `to` between base and y_barrier should include `base``, `x` (or
// x_barrier), `y` (or y_barrier)
let span_by = y_barrier
.to(&base)
.expect("to should succeed for linear history");
let collected: Vec<Checkpoint<Ethereum>> = (0..span_by.len())
.map(|i| span_by.at(i).unwrap().clone())
.collect();

assert!(collected.contains(&base));
assert!(collected.contains(&y_barrier));
assert!(collected.iter().any(|cp| *cp == x || *cp == x_barrier));
}

#[test]
fn test_to_different_roots_error() {
let block1 = BlockContext::<Ethereum>::mocked();
let block2 = BlockContext::<Ethereum>::mocked();

let root1 = Checkpoint::new_at_block(block1);
let root2 = Checkpoint::new_at_block(block2);

let tx = transfer_tx(&FundedAccounts::signer(0), 0, U256::from(5u64));
let root1_child = root1.apply(tx).unwrap();

assert!(root1_child.to(&root2).is_err());
assert!(root2.to(&root1_child).is_err());
}

#[test]
fn test_effective_tip_checkpoint() {
let block = BlockContext::<Ethereum>::mocked();
let cp = Checkpoint::new_at_block(block);
assert_eq!(
cp.effective_tip_per_gas(),
0,
"Empty checkpoint should have zero tip"
);

let tx = transfer_tx(&FundedAccounts::signer(0), 0, U256::from(100u64));
let cp2 = cp.apply(tx.clone()).unwrap();

let tip = cp2.effective_tip_per_gas();
assert!(tip > 0, "Transaction should have positive effective tip");
}

#[test]
fn test_history_staging_no_barrier() {
let block = BlockContext::<Ethereum>::mocked();
let base = Checkpoint::new_at_block(block);

let tx1 = transfer_tx(&FundedAccounts::signer(0), 0, U256::from(50u64));
let cp1 = base.apply(tx1).unwrap();

let tx2 = transfer_tx(&FundedAccounts::signer(1), 0, U256::from(75u64));
let cp2 = cp1.apply(tx2).unwrap();

let staging = cp2.history_staging();
let full = cp2.history();

assert_eq!(
staging.len(),
full.len(),
"Without barriers, staging should equal full history"
);
}

#[test]
fn test_history_staging_with_barrier() {
let block = BlockContext::<Ethereum>::mocked();
let base = Checkpoint::new_at_block(block);

let tx1 = transfer_tx(&FundedAccounts::signer(0), 0, U256::from(50u64));
let cp1 = base.apply(tx1).unwrap();

let barrier = cp1.barrier();

let tx2 = transfer_tx(&FundedAccounts::signer(1), 0, U256::from(75u64));
let cp2 = barrier.apply(tx2).unwrap();

let staging = cp2.history_staging();

// Staging should only include checkpoints after the barrier
assert!(
staging.len() < cp2.history().len(),
"Staging should be shorter than full history"
);
let sealed = cp2.history_sealed();

// Sealed should include everything up to and including the barrier
assert!(
!sealed.is_empty(),
"Sealed should include checkpoints up to barrier"
);
}
}
2 changes: 2 additions & 0 deletions src/test_utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ mod mock;
mod node;
mod platform;
mod step;
mod transactions;

#[allow(unused_imports)]
pub(crate) use step::fake_step;
Expand All @@ -32,6 +33,7 @@ pub use {
platform::{TestNodeFactory, TestablePlatform},
rblib_tests_macros::{assert_is_dyn_safe, if_platform, rblib_test},
step::{AlwaysBreakStep, AlwaysFailStep, AlwaysOkStep, OneStep},
transactions::transfer_tx,
};

#[cfg(feature = "optimism")]
Expand Down
Loading
Loading