Skip to content

Commit 7c444d8

Browse files
varun-doshijulio4
andauthored
feat(tests): Add tests for Checkpoint and CheckpointExt (#36)
* feat(tests): Add tests for Checkpoint and CheckpointExt * fix: more detailed tests * test: checkpoints --------- Co-authored-by: julio4 <30329843+julio4@users.noreply.github.com>
1 parent 4283e6e commit 7c444d8

File tree

5 files changed

+354
-37
lines changed

5 files changed

+354
-37
lines changed

examples/checkpoints-eth.rs

Lines changed: 2 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,11 @@
44
//! test-utils provided helpers for creating mock instances.
55
66
use {
7-
alloy::{
8-
consensus::{EthereumTxEnvelope, Transaction, TxEip4844},
9-
network::{TransactionBuilder, TxSignerSync},
10-
primitives::{Address, U256},
11-
signers::local::PrivateKeySigner,
12-
},
7+
alloy::{consensus::Transaction, primitives::U256},
138
rblib::{
149
alloy,
1510
prelude::*,
16-
reth,
17-
test_utils::{BlockContextMocked, FundedAccounts},
18-
},
19-
reth::{
20-
ethereum::{TransactionSigned, primitives::SignedTransaction},
21-
primitives::Recovered,
22-
rpc::types::TransactionRequest,
11+
test_utils::{BlockContextMocked, FundedAccounts, transfer_tx},
2312
},
2413
};
2514

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

7059
Ok(())
7160
}
72-
73-
fn transfer_tx(
74-
signer: &PrivateKeySigner,
75-
nonce: u64,
76-
value: U256,
77-
) -> Recovered<EthereumTxEnvelope<TxEip4844>> {
78-
let mut tx = TransactionRequest::default()
79-
.with_nonce(nonce)
80-
.with_to(Address::random())
81-
.value(value)
82-
.with_gas_price(1_000_000_000)
83-
.with_gas_limit(21_000)
84-
.with_max_priority_fee_per_gas(1_000_000)
85-
.with_max_fee_per_gas(2_000_000)
86-
.build_unsigned()
87-
.expect("valid transaction request");
88-
89-
let sig = signer
90-
.sign_transaction_sync(&mut tx)
91-
.expect("signing should succeed");
92-
93-
TransactionSigned::new_unhashed(tx.into(), sig) //
94-
.with_signer(signer.address())
95-
}

src/payload/checkpoint.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,3 +546,80 @@ impl<P: Platform> Display for Checkpoint<P> {
546546
}
547547
}
548548
}
549+
550+
#[cfg(test)]
551+
mod tests {
552+
use {
553+
crate::{
554+
alloy::primitives::U256,
555+
prelude::{BlockContext, Ethereum},
556+
test_utils::{BlockContextMocked, FundedAccounts, transfer_tx},
557+
},
558+
std::time::Instant,
559+
};
560+
561+
#[test]
562+
fn test_barrier_depth_and_is_barrier() {
563+
let block = BlockContext::<Ethereum>::mocked();
564+
565+
let checkpoint = block.start();
566+
567+
// Expected: initial checkpoint is depth 0 and is barrier
568+
assert_eq!(checkpoint.depth(), 0);
569+
assert!(checkpoint.is_barrier());
570+
assert!(checkpoint.prev().is_none());
571+
}
572+
573+
#[test]
574+
fn test_named_barrier_and_prev_depth() {
575+
// Outline:
576+
// 1. create initial checkpoint (depth 0)
577+
// 2. create named barrier on top
578+
// 3. verify new depth is 1, prev is initial, and is_named_barrier returns
579+
// true
580+
let block = BlockContext::<Ethereum>::mocked();
581+
582+
let root = block.start();
583+
584+
let named = root.barrier_with_tag("sequencer-synced");
585+
586+
assert_eq!(named.depth(), root.depth() + 1);
587+
assert_eq!(named.tag(), Some("sequencer-synced"));
588+
assert!(named.prev().is_some());
589+
assert_eq!(named.prev().unwrap().depth(), root.depth());
590+
}
591+
592+
#[test]
593+
fn test_created_at() {
594+
let block = BlockContext::<Ethereum>::mocked();
595+
596+
let before = Instant::now();
597+
let cp = block.start();
598+
let after = Instant::now();
599+
assert!((before..=after).contains(&cp.created_at()));
600+
}
601+
602+
#[test]
603+
fn test_iter() {
604+
let block = BlockContext::<Ethereum>::mocked();
605+
606+
let checkpoint = block.start();
607+
608+
let checkpoint2 = checkpoint.barrier();
609+
let checkpoint3 = checkpoint2.barrier();
610+
611+
let tx = transfer_tx(&FundedAccounts::signer(0), 0, U256::from(10u64));
612+
let checkpoint4 = checkpoint3.apply(tx).unwrap();
613+
614+
let history: Vec<_> = checkpoint4.into_iter().collect();
615+
assert_eq!(history.len(), 4);
616+
assert_eq!(history[0], checkpoint4);
617+
assert_eq!(history[0].depth(), 3);
618+
assert_eq!(history[1], checkpoint3);
619+
assert_eq!(history[1].depth(), 2);
620+
assert_eq!(history[2], checkpoint2);
621+
assert_eq!(history[2].depth(), 1);
622+
assert_eq!(history[3], checkpoint);
623+
assert_eq!(history[3].depth(), 0);
624+
}
625+
}

src/payload/ext/checkpoint.rs

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,3 +327,237 @@ impl<P: Platform> CheckpointExt<P> for Checkpoint<P> {
327327
self.root().created_at()
328328
}
329329
}
330+
331+
#[cfg(test)]
332+
mod tests {
333+
use {
334+
crate::{
335+
alloy::primitives::{Address, U256},
336+
payload::{Checkpoint, CheckpointExt},
337+
prelude::{BlockContext, Ethereum},
338+
test_utils::{BlockContextMocked, FundedAccounts, transfer_tx},
339+
},
340+
std::{
341+
thread,
342+
time::{Duration, Instant},
343+
},
344+
};
345+
346+
#[test]
347+
fn test_new_at_block() {
348+
let block = BlockContext::<Ethereum>::mocked();
349+
let cp = Checkpoint::new_at_block(block);
350+
351+
let cp2 = cp.barrier();
352+
let cp3 = cp2.barrier();
353+
354+
assert!(cp.is_empty());
355+
assert_eq!(cp2.root(), cp);
356+
assert_eq!(cp3.root(), cp);
357+
358+
assert_eq!(cp.gas_used(), 0);
359+
assert_eq!(cp.cumulative_gas_used(), 0);
360+
361+
assert_eq!(cp.effective_tip_per_gas(), 0);
362+
assert!(!cp.has_blobs());
363+
assert_eq!(cp.blob_gas_used(), Some(0));
364+
assert_eq!(cp.cumulative_blob_gas_used(), 0);
365+
366+
let span1 = cp3.to(&cp).unwrap();
367+
let span2 = cp.to(&cp3).unwrap();
368+
369+
assert_eq!(span1.len(), span2.len());
370+
for i in 0..span2.len() {
371+
assert_eq!(span1.at(i), span2.at(i));
372+
}
373+
assert_eq!(span1.len(), 3);
374+
assert_eq!(span2.len(), 3);
375+
376+
let addr = Address::ZERO;
377+
assert_eq!(cp.balance_of(addr).unwrap(), U256::ZERO);
378+
assert_eq!(cp.nonce_of(addr).unwrap(), 0);
379+
380+
assert!(cp.signers().is_empty());
381+
assert!(cp.nonces().is_empty());
382+
383+
assert_eq!(cp.hash(), None);
384+
assert!(!cp.is_bundle());
385+
assert!(!cp.has_failures());
386+
assert_eq!(cp.failed_txs().count(), 0);
387+
388+
let random_addr = Address::random();
389+
assert_eq!(
390+
cp.balance_of(random_addr).unwrap(),
391+
U256::ZERO,
392+
"Nonexistent account should have zero balance"
393+
);
394+
395+
assert_eq!(
396+
cp.nonce_of(random_addr).unwrap(),
397+
0,
398+
"Nonexistent account should have zero nonce"
399+
);
400+
}
401+
402+
#[test]
403+
fn test_contains_is_false_without_txs() {
404+
let block = BlockContext::<Ethereum>::mocked();
405+
let cp1 = Checkpoint::new_at_block(block);
406+
407+
let tx1 = transfer_tx(&FundedAccounts::signer(0), 0, U256::from(50_000u64));
408+
let tx1_hash = *tx1.hash();
409+
assert!(!cp1.contains(tx1_hash));
410+
let cp2 = cp1.apply(tx1).unwrap();
411+
412+
assert!(cp2.contains(tx1_hash));
413+
}
414+
415+
#[test]
416+
fn test_history_timestamps() {
417+
let block = BlockContext::<Ethereum>::mocked();
418+
let cp1 = Checkpoint::new_at_block(block);
419+
420+
thread::sleep(Duration::from_millis(5));
421+
422+
let cp2 = cp1.barrier();
423+
424+
assert!(cp2.building_since() <= Instant::now());
425+
assert!(cp2.building_since() >= cp1.created_at());
426+
}
427+
428+
#[test]
429+
fn test_to_self() {
430+
let block = BlockContext::<Ethereum>::mocked();
431+
let cp = Checkpoint::new_at_block(block);
432+
433+
// to(self, self) should produce a span of length 1 containing the
434+
// checkpoint itself
435+
let span = cp.to(&cp).expect("to(self,self) must succeed");
436+
assert_eq!(span.len(), 1);
437+
assert_eq!(*span.at(0).unwrap(), cp);
438+
}
439+
440+
#[test]
441+
fn test_to_non_linear_error() {
442+
let block_a = BlockContext::<Ethereum>::mocked();
443+
let block_b = BlockContext::<Ethereum>::mocked();
444+
445+
let cp_a = Checkpoint::new_at_block(block_a);
446+
let cp_b = Checkpoint::new_at_block(block_b);
447+
448+
// They are not on the same linear history, so to should return an Err.
449+
assert!(cp_a.to(&cp_b).is_err());
450+
assert!(cp_b.to(&cp_a).is_err());
451+
}
452+
453+
#[test]
454+
fn test_to_includes_all_intermediates_and_is_linear() {
455+
let block = BlockContext::<Ethereum>::mocked();
456+
let base = Checkpoint::new_at_block(block);
457+
458+
// base -> x -> y
459+
let tx_x = transfer_tx(&FundedAccounts::signer(0), 0, U256::from(10u64));
460+
let x = base.apply(tx_x).unwrap();
461+
462+
let tx_y = transfer_tx(&FundedAccounts::signer(1), 0, U256::from(20u64));
463+
let y = x.apply(tx_y).unwrap();
464+
465+
let x_barrier = x.barrier();
466+
let y_barrier = y.barrier();
467+
468+
// `to` between base and y_barrier should include `base``, `x` (or
469+
// x_barrier), `y` (or y_barrier)
470+
let span_by = y_barrier
471+
.to(&base)
472+
.expect("to should succeed for linear history");
473+
let collected: Vec<Checkpoint<Ethereum>> = (0..span_by.len())
474+
.map(|i| span_by.at(i).unwrap().clone())
475+
.collect();
476+
477+
assert!(collected.contains(&base));
478+
assert!(collected.contains(&y_barrier));
479+
assert!(collected.iter().any(|cp| *cp == x || *cp == x_barrier));
480+
}
481+
482+
#[test]
483+
fn test_to_different_roots_error() {
484+
let block1 = BlockContext::<Ethereum>::mocked();
485+
let block2 = BlockContext::<Ethereum>::mocked();
486+
487+
let root1 = Checkpoint::new_at_block(block1);
488+
let root2 = Checkpoint::new_at_block(block2);
489+
490+
let tx = transfer_tx(&FundedAccounts::signer(0), 0, U256::from(5u64));
491+
let root1_child = root1.apply(tx).unwrap();
492+
493+
assert!(root1_child.to(&root2).is_err());
494+
assert!(root2.to(&root1_child).is_err());
495+
}
496+
497+
#[test]
498+
fn test_effective_tip_checkpoint() {
499+
let block = BlockContext::<Ethereum>::mocked();
500+
let cp = Checkpoint::new_at_block(block);
501+
assert_eq!(
502+
cp.effective_tip_per_gas(),
503+
0,
504+
"Empty checkpoint should have zero tip"
505+
);
506+
507+
let tx = transfer_tx(&FundedAccounts::signer(0), 0, U256::from(100u64));
508+
let cp2 = cp.apply(tx.clone()).unwrap();
509+
510+
let tip = cp2.effective_tip_per_gas();
511+
assert!(tip > 0, "Transaction should have positive effective tip");
512+
}
513+
514+
#[test]
515+
fn test_history_staging_no_barrier() {
516+
let block = BlockContext::<Ethereum>::mocked();
517+
let base = Checkpoint::new_at_block(block);
518+
519+
let tx1 = transfer_tx(&FundedAccounts::signer(0), 0, U256::from(50u64));
520+
let cp1 = base.apply(tx1).unwrap();
521+
522+
let tx2 = transfer_tx(&FundedAccounts::signer(1), 0, U256::from(75u64));
523+
let cp2 = cp1.apply(tx2).unwrap();
524+
525+
let staging = cp2.history_staging();
526+
let full = cp2.history();
527+
528+
assert_eq!(
529+
staging.len(),
530+
full.len(),
531+
"Without barriers, staging should equal full history"
532+
);
533+
}
534+
535+
#[test]
536+
fn test_history_staging_with_barrier() {
537+
let block = BlockContext::<Ethereum>::mocked();
538+
let base = Checkpoint::new_at_block(block);
539+
540+
let tx1 = transfer_tx(&FundedAccounts::signer(0), 0, U256::from(50u64));
541+
let cp1 = base.apply(tx1).unwrap();
542+
543+
let barrier = cp1.barrier();
544+
545+
let tx2 = transfer_tx(&FundedAccounts::signer(1), 0, U256::from(75u64));
546+
let cp2 = barrier.apply(tx2).unwrap();
547+
548+
let staging = cp2.history_staging();
549+
550+
// Staging should only include checkpoints after the barrier
551+
assert!(
552+
staging.len() < cp2.history().len(),
553+
"Staging should be shorter than full history"
554+
);
555+
let sealed = cp2.history_sealed();
556+
557+
// Sealed should include everything up to and including the barrier
558+
assert!(
559+
!sealed.is_empty(),
560+
"Sealed should include checkpoints up to barrier"
561+
);
562+
}
563+
}

src/test_utils/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ mod mock;
2020
mod node;
2121
mod platform;
2222
mod step;
23+
mod transactions;
2324

2425
#[allow(unused_imports)]
2526
pub(crate) use step::fake_step;
@@ -32,6 +33,7 @@ pub use {
3233
platform::{TestNodeFactory, TestablePlatform},
3334
rblib_tests_macros::{assert_is_dyn_safe, if_platform, rblib_test},
3435
step::{AlwaysBreakStep, AlwaysFailStep, AlwaysOkStep, OneStep},
36+
transactions::transfer_tx,
3537
};
3638

3739
#[cfg(feature = "optimism")]

0 commit comments

Comments
 (0)