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
232 changes: 186 additions & 46 deletions src/payload/checkpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,7 @@ impl<P: Platform> DatabaseRef for Checkpoint<P> {
address: Address,
index: StorageKey,
) -> Result<StorageValue, Self::Error> {
// traverse checkpoints history looking for the first checkpoint that
// traverse checkpoint history looking for the first checkpoint that
// has touched the given address.

if let Some(value) = self.iter().find_map(|checkpoint| {
Expand Down Expand Up @@ -476,6 +476,7 @@ impl<P: Platform> PartialEq for Checkpoint<P> {
Arc::ptr_eq(&self.inner, &other.inner)
}
}

impl<P: Platform> Eq for Checkpoint<P> {}

impl<P: Platform> Debug for Checkpoint<P> {
Expand Down Expand Up @@ -552,76 +553,215 @@ impl<P: Platform> Display for Checkpoint<P> {
mod tests {
use {
crate::{
alloy::primitives::U256,
prelude::{BlockContext, Ethereum},
test_utils::{BlockContextMocked, FundedAccounts, transfer_tx},
payload::checkpoint::{Checkpoint, IntoExecutable, Mutation},
prelude::*,
reth::primitives::Recovered,
test_utils::{BlockContextMocked, test_bundle, test_tx, test_txs},
},
std::time::Instant,
};

/// Helper test function to apply multiple transactions on a checkpoint
fn apply_multiple<P: PlatformWithRpcTypes>(
root: Checkpoint<P>,
txs: &[Recovered<types::Transaction<P>>],
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe txs: impl Iterator<Item=types::Transaction<P>>>?

Copy link
Collaborator

Choose a reason for hiding this comment

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

also, this can be written as something along those lines:

let c1 = block.start();
let many_txs = vec![100 txs];
let c100 = many_txs.into_iter().fold(c1, Checkpoint::apply)

Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: also maybe apply_many, or we can make apply itself work with individual or collections of txs.

Copy link
Collaborator

Choose a reason for hiding this comment

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

sorry, was thinking that this is at the Checkpoint type, not in a test helper. Forget everything I said ;-)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yep I'm not sure if it's a good idea to add a apply_many at the checkpoint type level but we can discuss this separately

) -> Vec<Checkpoint<P>> {
let mut cur = root;
txs
.iter()
.map(|tx| {
cur = cur.apply(tx.clone()).unwrap();
cur.clone()
})
.collect()
}

mod internal {
use super::*;
#[test]
fn test_new_at_block() {
// test the initial checkpoint with private `Checkpoint::new_at_block`
let block = BlockContext::<Ethereum>::mocked();

let before = Instant::now();
let checkpoint = Checkpoint::new_at_block(block.clone());
let after = Instant::now();

assert_eq!(checkpoint.block(), &block);
assert!(checkpoint.prev().is_none());
assert_eq!(checkpoint.depth(), 0);
assert!(checkpoint.is_barrier());
assert!((before..=after).contains(&checkpoint.created_at()));
}

#[test]
fn test_apply_with() {
// test the checkpoint obtained by application with private
// `Checkpoint::apply_with`
let block = BlockContext::<Ethereum>::mocked();
let root = block.start();

let before = Instant::now();
let checkpoint = root.apply_with(Mutation::Barrier, None);
let after = Instant::now();

assert_eq!(checkpoint.block(), &block);
assert_eq!(checkpoint.prev(), Some(root.clone()));
assert_eq!(checkpoint.depth(), root.depth() + 1);
assert!(checkpoint.is_barrier());
assert!(checkpoint.tag().is_none());
assert!((before..=after).contains(&checkpoint.created_at()));
}
}

#[test]
fn test_barrier_depth_and_is_barrier() {
fn test_apply_barrier() {
let block = BlockContext::<Ethereum>::mocked();
let root = block.start();
let barrier = root.barrier();

assert!(barrier.result().is_none());
assert!(barrier.state().is_none());
assert!(barrier.as_transaction().is_none());
assert!(barrier.as_bundle().is_none());
assert!(barrier.transactions().is_empty());
}

let checkpoint = block.start();
#[test]
fn test_apply_tx() {
// test the checkpoint obtained by application with `Checkpoint::apply`
let block = BlockContext::<Ethereum>::mocked();
let root = 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());
let tx = test_tx::<Ethereum>(0, 0);
let checkpoint = root.apply(tx.clone()).unwrap();
assert_eq!(checkpoint.as_transaction(), Some(&tx));
assert_eq!(checkpoint.transactions(), std::slice::from_ref(&tx));
assert!(checkpoint.as_bundle().is_none());
assert!(!checkpoint.is_barrier());

// expected mutation result
let res = tx
.try_into_executable()
.unwrap()
.execute(&block, &root)
.unwrap();
assert_eq!(checkpoint.result(), Some(&res));
assert_eq!(checkpoint.state(), Some(res.state()));
assert_eq!(checkpoint.transactions(), res.transactions());
assert_eq!(checkpoint.inner.mutation, Mutation::Executable(res));

let tx = test_tx::<Ethereum>(0, 1);
let checkpoint = checkpoint.apply(tx.clone()).unwrap();
assert_eq!(checkpoint.depth(), 2);

// This tx is supposed to fail
let fail_tx = tx;
let checkpoint_res = checkpoint.apply(fail_tx);
assert!(checkpoint_res.is_err());
}

#[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
fn test_apply_bundle() {
let block = BlockContext::<Ethereum>::mocked();
let root = block.start();

let (bundle, txs) = test_bundle::<Ethereum>(0, 0);
let checkpoint = root.apply(bundle.clone()).unwrap();
assert_eq!(checkpoint.as_bundle(), Some(&bundle));
assert_eq!(checkpoint.transactions(), txs.as_slice());
assert!(checkpoint.as_transaction().is_none());
assert!(!checkpoint.is_barrier());

// expected mutation result
let res = bundle
.try_into_executable()
.unwrap()
.execute(&block, &root)
.unwrap();
assert_eq!(checkpoint.result(), Some(&res));
assert_eq!(checkpoint.state(), Some(res.state()));
assert_eq!(checkpoint.transactions(), res.transactions());
assert_eq!(checkpoint.inner.mutation, Mutation::Executable(res));

let (bundle, _) = test_bundle::<Ethereum>(0, 3);
let checkpoint = checkpoint.apply(bundle.clone()).unwrap();
assert_eq!(checkpoint.depth(), 2);

// This bundle is supposed to fail
let fail_bundle = bundle;
let checkpoint_res = checkpoint.apply(fail_bundle);
assert!(checkpoint_res.is_err());
}

#[test]
fn test_apply_with_tag() {
let tag = "tagged";
let block = BlockContext::<Ethereum>::mocked();
let root = block.start();
assert!(root.tag().is_none());
assert!(!root.is_tagged("x"));

let named = root.barrier_with_tag("sequencer-synced");
let checkpoint = root.barrier_with_tag(tag);
assert!(checkpoint.is_tagged(tag));
assert_eq!(checkpoint.tag(), Some(tag));

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());
let checkpoint =
root.apply_with_tag(test_tx::<Ethereum>(0, 0), tag).unwrap();
assert_eq!(checkpoint.tag(), Some(tag));
}

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

let before = Instant::now();
let cp = block.start();
let after = Instant::now();
assert!((before..=after).contains(&cp.created_at()));
let txs = test_txs::<Ethereum>(0, 0, 10);
let checkpoints = apply_multiple(root, &txs);

let latest = checkpoints.last().unwrap();
let iter = latest.into_iter();

assert!(
checkpoints
.into_iter()
.rev()
.zip(iter)
.all(|(expected_cp, cp)| expected_cp == cp)
);
}

#[test]
fn test_iter() {
fn test_checkpoint_to_txs() {
let block = BlockContext::<Ethereum>::mocked();
let root = block.start();
let txs = test_txs::<Ethereum>(0, 0, 10);
let checkpoint = apply_multiple(root, &txs).last().unwrap().to_owned();

let extracted_txs = Vec::<types::Transaction<Ethereum>>::from(checkpoint);

assert!(
txs
.into_iter()
.map(|tx| tx.inner().clone())
.zip(extracted_txs)
.all(|(expected_tx, tx)| expected_tx == tx)
);
}

#[test]
fn test_build_payload() {
let block = BlockContext::<Ethereum>::mocked();
let provider = block.base_state();

let root = block.start();
let txs = test_txs::<Ethereum>(0, 0, 10);
let checkpoint = apply_multiple(root, &txs).last().unwrap().to_owned();

let built_payload = checkpoint.build_payload().unwrap();
let payload = Ethereum::build_payload(checkpoint, provider).unwrap();

let checkpoint = block.start();

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

let tx =
transfer_tx::<Ethereum>(&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);
assert_eq!(built_payload.id(), payload.id());
assert_eq!(built_payload.block(), payload.block());
}
}
8 changes: 4 additions & 4 deletions src/test_utils/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -461,25 +461,25 @@ impl StorageRootProvider for GenesisStateProvider {

impl StateRootProvider for GenesisStateProvider {
fn state_root(&self, _hashed_state: HashedPostState) -> ProviderResult<B256> {
Ok(B256::random())
Ok(keccak256([0]))
}

fn state_root_from_nodes(&self, _input: TrieInput) -> ProviderResult<B256> {
Ok(B256::random())
Ok(keccak256([0]))
}

fn state_root_with_updates(
&self,
_hashed_state: HashedPostState,
) -> ProviderResult<(B256, TrieUpdates)> {
Ok((B256::random(), TrieUpdates::default()))
Ok((keccak256([0]), TrieUpdates::default()))
}

fn state_root_from_nodes_with_updates(
&self,
_input: TrieInput,
) -> ProviderResult<(B256, TrieUpdates)> {
Ok((B256::random(), TrieUpdates::default()))
Ok((keccak256([0]), TrieUpdates::default()))
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/test_utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ pub use {
platform::{TestNodeFactory, TestablePlatform},
rblib_tests_macros::{assert_is_dyn_safe, if_platform, rblib_test},
step::{AlwaysBreakStep, AlwaysFailStep, AlwaysOkStep, OneStep},
transactions::{mock_tx, transfer_tx},
transactions::{test_bundle, test_tx, test_txs, transfer_tx},
};

#[cfg(feature = "optimism")]
Expand Down
36 changes: 32 additions & 4 deletions src/test_utils/transactions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,41 @@ use {
test_utils::FundedAccounts,
};

/// A mock tx to easily get a valid tx from `FundedAccounts` with `key` and
/// `nonce`
pub fn mock_tx<P: PlatformWithRpcTypes>(
/// A tx from `FundedAccounts` with `key` and `nonce`
/// Will create a transfer tx with value 1
pub fn test_tx<P: PlatformWithRpcTypes>(
key: u32,
nonce: u64,
) -> Recovered<types::Transaction<P>> {
transfer_tx::<P>(&FundedAccounts::signer(key), nonce, U256::from(10u64))
transfer_tx::<P>(&FundedAccounts::signer(key), nonce, U256::from(1u64))
}

/// `count` txs from `FundedAccounts` with `key` and `initial_nonce`
/// Will consume `count` nonce for `key`
pub fn test_txs<P: PlatformWithRpcTypes>(
key: u32,
initial_nonce: u64,
count: u64,
) -> Vec<Recovered<types::Transaction<P>>> {
(initial_nonce..initial_nonce + count)
.map(|nonce| test_tx::<P>(key, nonce))
.collect()
}

/// A mock `FlashbotsBundle` from `FundedAccounts` with `key` and `nonce`
/// Will insert 3 txs using [`test_tx`] and consume 3 nonces for `key`
pub fn test_bundle<P: PlatformWithRpcTypes<Bundle = FlashbotsBundle<P>>>(
key: u32,
nonce: u64,
) -> (FlashbotsBundle<P>, Vec<Recovered<types::Transaction<P>>>) {
let txs = test_txs::<P>(key, nonce, 3);
(
FlashbotsBundle::<P>::default()
.with_transaction(txs[0].clone())
.with_transaction(txs[1].clone())
.with_transaction(txs[2].clone()),
txs,
)
}

#[allow(clippy::missing_panics_doc)]
Expand Down
Loading