Skip to content

Commit 4d4bd86

Browse files
committed
fix: more detailed tests
1 parent 68fac1b commit 4d4bd86

File tree

5 files changed

+241
-97
lines changed

5 files changed

+241
-97
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

@@ -68,27 +57,3 @@ fn main() -> eyre::Result<()> {
6857

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

src/payload/checkpoint.rs

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,8 @@ impl<P: Platform> Display for Checkpoint<P> {
521521
#[cfg(test)]
522522
mod tests {
523523
use {
524+
crate::test_utils::{FundedAccounts, transfer_tx},
525+
alloy_origin::primitives::U256,
524526
rblib::{prelude::*, test_utils::BlockContextMocked},
525527
std::time::Instant,
526528
};
@@ -562,10 +564,10 @@ mod tests {
562564
fn test_created_at() {
563565
let (block, _) = BlockContext::<Ethereum>::mocked();
564566

565-
let checkpoint = block.start();
566-
567-
let now = Instant::now();
568-
assert!(checkpoint.created_at() <= now);
567+
let before = Instant::now();
568+
let cp = block.start();
569+
let after = Instant::now();
570+
assert!((before..=after).contains(&cp.created_at()));
569571
}
570572

571573
#[test]
@@ -577,10 +579,18 @@ mod tests {
577579
let checkpoint2 = checkpoint.barrier();
578580
let checkpoint3 = checkpoint2.barrier();
579581

580-
let history: Vec<_> = checkpoint3.into_iter().collect();
581-
assert_eq!(history.len(), 3);
582-
assert_eq!(history[0].depth(), 2);
583-
assert_eq!(history[1].depth(), 1);
584-
assert_eq!(history[2].depth(), 0);
582+
let tx = transfer_tx(&FundedAccounts::signer(0), 0, U256::from(10u64));
583+
let checkpoint4 = checkpoint3.apply(tx).unwrap();
584+
585+
let history: Vec<_> = checkpoint4.into_iter().collect();
586+
assert_eq!(history.len(), 4);
587+
assert_eq!(history[0], checkpoint4);
588+
assert_eq!(history[0].depth(), 3);
589+
assert_eq!(history[1], checkpoint3);
590+
assert_eq!(history[1].depth(), 2);
591+
assert_eq!(history[2], checkpoint2);
592+
assert_eq!(history[2].depth(), 1);
593+
assert_eq!(history[3], checkpoint);
594+
assert_eq!(history[3].depth(), 0);
585595
}
586596
}

src/payload/ext/checkpoint.rs

Lines changed: 181 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -315,104 +315,234 @@ impl<P: Platform> CheckpointExt<P> for Checkpoint<P> {
315315
#[cfg(test)]
316316
mod tests {
317317
use {
318-
crate::alloy::primitives::Address,
319-
alloy_origin::primitives::{TxHash, U256},
320-
rblib::{prelude::*, test_utils::BlockContextMocked},
318+
crate::{
319+
alloy::primitives::Address,
320+
payload::{Checkpoint, CheckpointExt},
321+
prelude::{BlockContext, Ethereum},
322+
test_utils::{BlockContextMocked, FundedAccounts, transfer_tx},
323+
},
324+
alloy_origin::primitives::U256,
321325
std::{
322326
thread,
323327
time::{Duration, Instant},
324328
},
325329
};
326330

327331
#[test]
328-
fn test_is_empty_and_root() {
332+
fn test_new_at_block() {
329333
let (block, _) = BlockContext::<Ethereum>::mocked();
330-
let root = block.start();
331-
let mid = root.barrier();
332-
let leaf = mid.barrier();
334+
let cp = Checkpoint::new_at_block(block);
333335

334-
assert!(root.is_empty());
335-
assert_eq!(leaf.root(), root);
336-
}
336+
let cp2 = cp.barrier();
337+
let cp3 = cp2.barrier();
338+
339+
assert!(cp.is_empty());
340+
assert_eq!(cp2.root(), cp);
341+
assert_eq!(cp3.root(), cp);
337342

338-
#[test]
339-
fn test_gas_used_and_cumulative_gas_used() {
340-
let (block, _) = BlockContext::<Ethereum>::mocked();
341-
let cp = block.start();
342343
assert_eq!(cp.gas_used(), 0);
343344
assert_eq!(cp.cumulative_gas_used(), 0);
344-
}
345345

346-
#[test]
347-
fn test_effective_tip_and_blob_gas() {
348-
let (block, _) = BlockContext::<Ethereum>::mocked();
349-
let cp = block.start();
350346
assert_eq!(cp.effective_tip_per_gas(), 0);
351347
assert!(!cp.has_blobs());
352348
assert_eq!(cp.blob_gas_used(), Some(0));
353349
assert_eq!(cp.cumulative_blob_gas_used(), 0);
354-
}
355-
356-
#[test]
357-
fn test_to_between_linear_history() {
358-
let (block, _) = BlockContext::<Ethereum>::mocked();
359-
let a = block.start();
360-
let b = a.barrier();
361-
let c = b.barrier();
362350

363-
let span1 = c.to(&a).unwrap();
364-
let span2 = a.to(&c).unwrap();
351+
let span1 = cp3.to(&cp).unwrap();
352+
let span2 = cp.to(&cp3).unwrap();
365353

354+
assert_eq!(span1.len(), span2.len());
355+
for i in 0..span2.len() {
356+
assert_eq!(span1.at(i), span2.at(i));
357+
}
366358
assert_eq!(span1.len(), 3);
367359
assert_eq!(span2.len(), 3);
368-
}
369-
370-
#[test]
371-
fn test_balance_and_nonce_defaults() {
372-
let (block, _) = BlockContext::<Ethereum>::mocked();
373-
let cp = block.start();
374360

375361
let addr = Address::ZERO;
376362
assert_eq!(cp.balance_of(addr).unwrap(), U256::ZERO);
377363
assert_eq!(cp.nonce_of(addr).unwrap(), 0);
378-
}
379-
380-
#[test]
381-
fn test_signers_and_nonces_are_empty() {
382-
let (block, _) = BlockContext::<Ethereum>::mocked();
383-
let cp = block.start();
384364

385365
assert!(cp.signers().is_empty());
386366
assert!(cp.nonces().is_empty());
387-
}
388-
389-
#[test]
390-
fn test_hash_and_is_bundle_and_has_failures_defaults() {
391-
let (block, _) = BlockContext::<Ethereum>::mocked();
392-
let cp = block.start();
393367

394368
assert_eq!(cp.hash(), None);
395369
assert!(!cp.is_bundle());
396370
assert!(!cp.has_failures());
397371
assert_eq!(cp.failed_txs().count(), 0);
372+
373+
let random_addr = Address::random();
374+
assert_eq!(
375+
cp.balance_of(random_addr).unwrap(),
376+
U256::ZERO,
377+
"Nonexistent account should have zero balance"
378+
);
379+
380+
assert_eq!(
381+
cp.nonce_of(random_addr).unwrap(),
382+
0,
383+
"Nonexistent account should have zero nonce"
384+
);
398385
}
399386

400387
#[test]
401388
fn test_contains_is_false_without_txs() {
402389
let (block, _) = BlockContext::<Ethereum>::mocked();
403-
let cp = block.start();
390+
let cp1 = Checkpoint::new_at_block(block);
391+
392+
let tx1 = transfer_tx(&FundedAccounts::signer(0), 0, U256::from(50_000u64));
393+
let tx1_hash = tx1.hash().clone();
394+
assert!(!cp1.contains(tx1_hash));
395+
let cp2 = cp1.apply(tx1).unwrap();
404396

405-
let fake_hash = TxHash::repeat_byte(0x42);
406-
assert!(!cp.contains(fake_hash));
397+
assert!(cp2.contains(tx1_hash));
407398
}
408399

409400
#[test]
410401
fn test_history_timestamps() {
411402
let (block, _) = BlockContext::<Ethereum>::mocked();
412-
let cp1 = block.start();
403+
let cp1 = Checkpoint::new_at_block(block);
404+
413405
thread::sleep(Duration::from_millis(5));
406+
414407
let cp2 = cp1.barrier();
408+
415409
assert!(cp2.building_since() <= Instant::now());
416410
assert!(cp2.building_since() >= cp1.created_at());
417411
}
412+
413+
#[test]
414+
fn test_to_self() {
415+
let (block, _) = BlockContext::<Ethereum>::mocked();
416+
let cp = Checkpoint::new_at_block(block);
417+
418+
// to(self, self) should produce a span of length 1 containing the
419+
// checkpoint itself
420+
let span = cp.to(&cp).expect("to(self,self) must succeed");
421+
assert_eq!(span.len(), 1);
422+
assert_eq!(*span.at(0).unwrap(), cp);
423+
}
424+
425+
#[test]
426+
fn test_to_non_linear_error() {
427+
let (block_a, _) = BlockContext::<Ethereum>::mocked();
428+
let (block_b, _) = BlockContext::<Ethereum>::mocked();
429+
430+
let cp_a = Checkpoint::new_at_block(block_a);
431+
let cp_b = Checkpoint::new_at_block(block_b);
432+
433+
// They are not on the same linear history, so to should return an Err.
434+
assert!(cp_a.to(&cp_b).is_err());
435+
assert!(cp_b.to(&cp_a).is_err());
436+
}
437+
438+
#[test]
439+
fn test_to_includes_all_intermediates_and_is_linear() {
440+
let (block, _) = BlockContext::<Ethereum>::mocked();
441+
let base = Checkpoint::new_at_block(block);
442+
443+
// base -> x -> y
444+
let tx_x = transfer_tx(&FundedAccounts::signer(0), 0, U256::from(10u64));
445+
let x = base.apply(tx_x).unwrap();
446+
447+
let tx_y = transfer_tx(&FundedAccounts::signer(1), 0, U256::from(20u64));
448+
let y = x.apply(tx_y).unwrap();
449+
450+
let x_barrier = x.barrier();
451+
let y_barrier = y.barrier();
452+
453+
// `to` between base and y_barrier should include `base``, `x` (or
454+
// x_barrier), `y` (or y_barrier)
455+
let span_by = y_barrier
456+
.to(&base)
457+
.expect("to should succeed for linear history");
458+
let collected: Vec<Checkpoint<Ethereum>> = (0..span_by.len())
459+
.map(|i| span_by.at(i).unwrap().clone())
460+
.collect();
461+
462+
assert!(collected.contains(&base));
463+
assert!(collected.contains(&y_barrier));
464+
assert!(collected.iter().any(|cp| *cp == x || *cp == x_barrier));
465+
}
466+
467+
#[test]
468+
fn test_to_different_roots_error() {
469+
let (block1, _) = BlockContext::<Ethereum>::mocked();
470+
let (block2, _) = BlockContext::<Ethereum>::mocked();
471+
472+
let root1 = Checkpoint::new_at_block(block1);
473+
let root2 = Checkpoint::new_at_block(block2);
474+
475+
let tx = transfer_tx(&FundedAccounts::signer(0), 0, U256::from(5u64));
476+
let root1_child = root1.apply(tx).unwrap();
477+
478+
assert!(root1_child.to(&root2).is_err());
479+
assert!(root2.to(&root1_child).is_err());
480+
}
481+
482+
#[test]
483+
fn test_effective_tip_checkpoint() {
484+
let (block, _) = BlockContext::<Ethereum>::mocked();
485+
let cp = Checkpoint::new_at_block(block);
486+
assert_eq!(
487+
cp.effective_tip_per_gas(),
488+
0,
489+
"Empty checkpoint should have zero tip"
490+
);
491+
492+
let tx = transfer_tx(&FundedAccounts::signer(0), 0, U256::from(100u64));
493+
let cp2 = cp.apply(tx.clone()).unwrap();
494+
495+
let tip = cp2.effective_tip_per_gas();
496+
assert!(tip > 0, "Transaction should have positive effective tip");
497+
}
498+
499+
#[test]
500+
fn test_history_staging_no_barrier() {
501+
let (block, _) = BlockContext::<Ethereum>::mocked();
502+
let base = Checkpoint::new_at_block(block);
503+
504+
let tx1 = transfer_tx(&FundedAccounts::signer(0), 0, U256::from(50u64));
505+
let cp1 = base.apply(tx1).unwrap();
506+
507+
let tx2 = transfer_tx(&FundedAccounts::signer(1), 0, U256::from(75u64));
508+
let cp2 = cp1.apply(tx2).unwrap();
509+
510+
let staging = cp2.history_staging();
511+
let full = cp2.history();
512+
513+
assert_eq!(
514+
staging.len(),
515+
full.len(),
516+
"Without barriers, staging should equal full history"
517+
);
518+
}
519+
520+
#[test]
521+
fn test_history_staging_with_barrier() {
522+
let (block, _) = BlockContext::<Ethereum>::mocked();
523+
let base = Checkpoint::new_at_block(block);
524+
525+
let tx1 = transfer_tx(&FundedAccounts::signer(0), 0, U256::from(50u64));
526+
let cp1 = base.apply(tx1).unwrap();
527+
528+
let barrier = cp1.barrier();
529+
530+
let tx2 = transfer_tx(&FundedAccounts::signer(1), 0, U256::from(75u64));
531+
let cp2 = barrier.apply(tx2).unwrap();
532+
533+
let staging = cp2.history_staging();
534+
535+
// Staging should only include checkpoints after the barrier
536+
assert!(
537+
staging.len() < cp2.history().len(),
538+
"Staging should be shorter than full history"
539+
);
540+
let sealed = cp2.history_sealed();
541+
542+
// Sealed should include everything up to and including the barrier
543+
assert!(
544+
sealed.len() > 0,
545+
"Sealed should include checkpoints up to barrier"
546+
);
547+
}
418548
}

0 commit comments

Comments
 (0)