use super::*;
use crate::store::{MemoryPoolStateStore, PoolStateStore, StoreError};
use crate::voucher::{Voucher, voucher_domain};
use alloy::primitives::{address, b256};
use alloy::signers::local::PrivateKeySigner;
use std::sync::atomic::{AtomicUsize, Ordering};
#[test]
fn registered_until_defaults_zero_and_survives_stage_voucher_clone() {
let st = LaneState::hydrate(
B256::ZERO,
Address::ZERO,
Address::ZERO,
U256::MAX,
0,
U256::ZERO,
U256::ZERO,
None,
LaneChain::NONE,
);
assert_eq!(st.registered_until, 0, "hydrate seeds unknown");
let mut with_reg = st.clone();
with_reg.registered_until = 1_800_000_000;
let cloned = with_reg.clone();
assert_eq!(cloned.registered_until, 1_800_000_000);
}
fn lane_with_terms(cap: u64, expiry: u64) -> LaneState {
LaneState::hydrate(
B256::ZERO,
Address::ZERO,
Address::ZERO,
U256::from(cap),
expiry,
U256::ZERO,
U256::ZERO,
None,
LaneChain::NONE,
)
}
#[test]
fn clamp_to_registration_holds_the_lower_terms() {
let mut wider = lane_with_terms(5_000_000, 2_000);
assert!(wider.clamp_to_registration(40, 1_000));
assert_eq!(wider.cap, U256::from(40u64));
assert_eq!(wider.expiry, 1_000);
assert_eq!(wider.registered_until, 1_000);
let mut narrower = lane_with_terms(10, 500);
assert!(narrower.clamp_to_registration(40, 1_000));
assert_eq!(narrower.cap, U256::from(10u64));
assert_eq!(narrower.expiry, 500);
assert_eq!(
narrower.registered_until, 1_000,
"the registration is recorded"
);
assert!(
!narrower.clamp_to_registration(40, 1_000),
"the same registration again changes nothing"
);
}
#[test]
fn clamp_to_registration_maps_the_zero_expiries() {
let mut untracked = lane_with_terms(100, 0);
untracked.clamp_to_registration(100, 1_000);
assert_eq!(untracked.expiry, 1_000);
let mut expired = lane_with_terms(100, 0);
expired.clamp_to_registration(100, 0);
assert_eq!(expired.expiry, 1, "a zero registered expiry holds as past");
assert_eq!(expired.registered_until, 0);
}
struct FailingStore {
record_calls: AtomicUsize,
}
impl FailingStore {
fn new() -> Self {
Self {
record_calls: AtomicUsize::new(0),
}
}
fn record_calls(&self) -> usize {
self.record_calls.load(Ordering::SeqCst)
}
}
impl PoolStateStore for FailingStore {
fn load_all(&self) -> Result<Vec<LaneState>, StoreError> {
Ok(Vec::new())
}
fn record(&self, _state: &LaneState) -> Result<(), StoreError> {
self.record_calls.fetch_add(1, Ordering::SeqCst);
Err(StoreError::Io(std::io::Error::other(
"simulated fsync failure",
)))
}
fn forget(&self, _key: LaneKey) -> Result<(), StoreError> {
Ok(())
}
fn get(&self, _key: LaneKey) -> Result<Option<LaneState>, StoreError> {
Ok(None)
}
}
const CHAIN_ID: u64 = 421_614;
const VERIFYING: Address = address!("0000000000000000000000000000000000001234");
const PROVIDER: Address = address!("00000000000000000000000000000000000000b2");
const POOL_ID: B256 = b256!("11223344556677889900aabbccddeeff00112233445566778899aabbccddeeff");
fn fixture() -> (
PrivateKeySigner,
LaneState,
Eip712Domain,
MemoryPoolStateStore,
) {
let signer = PrivateKeySigner::random();
let state = LaneState::hydrate(
POOL_ID,
signer.address(),
PROVIDER,
U256::from(10_000_000u64), 0,
U256::ZERO,
U256::ZERO,
None,
LaneChain::NONE,
);
let domain = voucher_domain(CHAIN_ID, VERIFYING);
let store = MemoryPoolStateStore::new();
(signer, state, domain, store)
}
fn build(
pool_id: B256,
signer: Address,
provider: Address,
amount: u64,
bytes_delivered: u64,
) -> Voucher {
Voucher {
pool_id,
signer,
provider,
amount: U256::from(amount),
bytes_delivered: U256::from(bytes_delivered),
chain_root: B256::ZERO,
chunk_price: U256::ZERO,
}
}
fn build_metering(
signer: Address,
amount: u64,
bytes_delivered: u64,
chain_root: B256,
chunk_price: u64,
) -> Voucher {
Voucher {
pool_id: POOL_ID,
signer,
provider: PROVIDER,
amount: U256::from(amount),
bytes_delivered: U256::from(bytes_delivered),
chain_root,
chunk_price: U256::from(chunk_price),
}
}
const PRICE: u64 = 10;
const SEED: B256 = B256::repeat_byte(0x5E);
fn root() -> B256 {
crate::chain::root_from_seed(SEED)
}
fn reveal(index: u8) -> B256 {
crate::chain::preimage_at(SEED, index)
}
fn err_of<T: std::fmt::Debug, E>(r: Result<T, E>) -> anyhow::Result<E> {
r.err()
.ok_or_else(|| anyhow::anyhow!("expected error, got Ok"))
}
#[test]
fn first_voucher_advances_state() -> anyhow::Result<()> {
let (signer, mut state, domain, store) = fixture();
let signed =
build(POOL_ID, signer.address(), PROVIDER, 1_000, 1_048_576).sign(&signer, &domain)?;
let applied = state.apply_voucher(&signed, &domain, &store)?;
anyhow::ensure!(state.last_amount == U256::from(1_000u64));
anyhow::ensure!(state.last_bytes_delivered == U256::from(1_048_576u64));
anyhow::ensure!(applied.amount_delta() == U256::from(1_000u64));
anyhow::ensure!(applied.bytes_delta() == U256::from(1_048_576u64));
Ok(())
}
#[test]
fn monotonic_progression_accepted() -> anyhow::Result<()> {
let (signer, mut state, domain, store) = fixture();
for (amount, bytes) in [(1_000u64, 1_048_576u64), (2_500, 2_621_440)] {
let signed =
build(POOL_ID, signer.address(), PROVIDER, amount, bytes).sign(&signer, &domain)?;
state.apply_voucher(&signed, &domain, &store)?;
}
anyhow::ensure!(state.last_amount == U256::from(2_500u64));
Ok(())
}
#[test]
fn wrong_pool_rejected() -> anyhow::Result<()> {
let (signer, mut state, domain, store) = fixture();
let signed = build(B256::ZERO, signer.address(), PROVIDER, 1_000, 1).sign(&signer, &domain)?;
let err = err_of(state.apply_voucher(&signed, &domain, &store))?;
anyhow::ensure!(matches!(err, PoolError::WrongPool { .. }), "{err:?}");
anyhow::ensure!(state.last_amount == U256::ZERO, "state must be unchanged");
Ok(())
}
#[test]
fn wrong_provider_rejected() -> anyhow::Result<()> {
let (signer, mut state, domain, store) = fixture();
let other_provider = address!("000000000000000000000000000000000000cccc");
let signed =
build(POOL_ID, signer.address(), other_provider, 1_000, 1).sign(&signer, &domain)?;
let err = err_of(state.apply_voucher(&signed, &domain, &store))?;
anyhow::ensure!(matches!(err, PoolError::WrongProvider { .. }), "{err:?}");
Ok(())
}
#[test]
fn equal_amount_rejected() -> anyhow::Result<()> {
let (signer, mut state, domain, store) = fixture();
let v1 = build(POOL_ID, signer.address(), PROVIDER, 1_000, 1).sign(&signer, &domain)?;
state.apply_voucher(&v1, &domain, &store)?;
let v2 = build(POOL_ID, signer.address(), PROVIDER, 1_000, 2).sign(&signer, &domain)?;
let err = err_of(state.apply_voucher(&v2, &domain, &store))?;
anyhow::ensure!(matches!(err, PoolError::AmountRegression { .. }), "{err:?}");
Ok(())
}
#[test]
fn lower_amount_rejected() -> anyhow::Result<()> {
let (signer, mut state, domain, store) = fixture();
let v1 = build(POOL_ID, signer.address(), PROVIDER, 5_000, 1).sign(&signer, &domain)?;
state.apply_voucher(&v1, &domain, &store)?;
let v2 = build(POOL_ID, signer.address(), PROVIDER, 4_000, 2).sign(&signer, &domain)?;
let err = err_of(state.apply_voucher(&v2, &domain, &store))?;
anyhow::ensure!(matches!(err, PoolError::AmountRegression { .. }), "{err:?}");
Ok(())
}
#[test]
fn bytes_decrease_rejected() -> anyhow::Result<()> {
let (signer, mut state, domain, store) = fixture();
let v1 = build(POOL_ID, signer.address(), PROVIDER, 1_000, 1_048_576).sign(&signer, &domain)?;
state.apply_voucher(&v1, &domain, &store)?;
let v2 = build(POOL_ID, signer.address(), PROVIDER, 2_000, 524_288).sign(&signer, &domain)?;
let err = err_of(state.apply_voucher(&v2, &domain, &store))?;
anyhow::ensure!(matches!(err, PoolError::BytesRegression { .. }), "{err:?}");
Ok(())
}
#[test]
fn amount_exceeds_cap_rejected() -> anyhow::Result<()> {
let (signer, mut state, domain, store) = fixture();
let signed =
build(POOL_ID, signer.address(), PROVIDER, 11_000_000, 1).sign(&signer, &domain)?;
let err = err_of(state.apply_voucher(&signed, &domain, &store))?;
anyhow::ensure!(matches!(err, PoolError::CapExceeded { .. }), "{err:?}");
Ok(())
}
#[test]
fn wrong_signer_rejected() -> anyhow::Result<()> {
let (signer, mut state, domain, store) = fixture();
let interloper = PrivateKeySigner::random();
let signed = build(POOL_ID, signer.address(), PROVIDER, 1_000, 1).sign(&interloper, &domain)?;
let err = err_of(state.apply_voucher(&signed, &domain, &store))?;
anyhow::ensure!(
matches!(err, PoolError::Signature(VoucherError::WrongSigner { .. })),
"{err:?}"
);
anyhow::ensure!(
state.last_amount == U256::ZERO,
"rejection must not advance"
);
Ok(())
}
#[test]
fn high_s_voucher_rejected_at_apply() -> anyhow::Result<()> {
let (signer, mut state, domain, store) = fixture();
let signed =
build(POOL_ID, signer.address(), PROVIDER, 1_000, 1_048_576).sign(&signer, &domain)?;
let twin = SignedVoucher {
signature: crate::sig_canon::high_s_twin(&signed.signature),
..signed
};
let err = err_of(state.apply_voucher(&twin, &domain, &store))?;
anyhow::ensure!(
matches!(err, PoolError::Signature(VoucherError::InvalidSignature)),
"{err:?}"
);
anyhow::ensure!(
state.last_amount == U256::ZERO,
"rejection must not advance"
);
Ok(())
}
#[test]
fn store_failure_leaves_in_memory_state_unchanged() -> anyhow::Result<()> {
let (signer, mut state, domain, _mem_store) = fixture();
let snapshot = state.clone();
let failing = FailingStore::new();
let signed =
build(POOL_ID, signer.address(), PROVIDER, 1_000, 1_048_576).sign(&signer, &domain)?;
let err = state
.apply_voucher(&signed, &domain, &failing)
.err()
.ok_or_else(|| anyhow::anyhow!("store failure must surface to caller"))?;
anyhow::ensure!(
matches!(err, PoolError::Store(_)),
"expected PoolError::Store, got {err:?}",
);
anyhow::ensure!(
state == snapshot,
"in-memory state must NOT advance when store.record fails",
);
anyhow::ensure!(
failing.record_calls() == 1,
"expected exactly one `record` call, got {}",
failing.record_calls(),
);
Ok(())
}
#[test]
fn store_failure_does_not_poison_subsequent_retries() -> anyhow::Result<()> {
let (signer, mut state, domain, store) = fixture();
let failing = FailingStore::new();
let signed =
build(POOL_ID, signer.address(), PROVIDER, 1_000, 1_048_576).sign(&signer, &domain)?;
let _err = state
.apply_voucher(&signed, &domain, &failing)
.err()
.ok_or_else(|| anyhow::anyhow!("expected store failure"))?;
anyhow::ensure!(state.last_amount == U256::ZERO);
state.apply_voucher(&signed, &domain, &store)?;
anyhow::ensure!(state.last_amount == U256::from(1_000u64));
Ok(())
}
#[test]
fn rejected_voucher_does_not_advance_state() -> anyhow::Result<()> {
let (signer, mut state, domain, store) = fixture();
let v1 = build(POOL_ID, signer.address(), PROVIDER, 5_000, 1_000).sign(&signer, &domain)?;
state.apply_voucher(&v1, &domain, &store)?;
let snapshot = state.clone();
let interloper = PrivateKeySigner::random();
let other_provider = address!("000000000000000000000000000000000000cccc");
let cases: [(SignedVoucher, &str); 6] = [
(
build(B256::ZERO, signer.address(), PROVIDER, 6_000, 2_000).sign(&signer, &domain)?,
"wrong pool",
),
(
build(POOL_ID, signer.address(), other_provider, 6_000, 2_000)
.sign(&signer, &domain)?,
"wrong provider",
),
(
build(POOL_ID, signer.address(), PROVIDER, 5_000, 2_000).sign(&signer, &domain)?,
"equal amount",
),
(
build(POOL_ID, signer.address(), PROVIDER, 6_000, 500).sign(&signer, &domain)?,
"bytes drop",
),
(
build(POOL_ID, signer.address(), PROVIDER, 11_000_000, 2_000).sign(&signer, &domain)?,
"amount over cap",
),
(
build(POOL_ID, signer.address(), PROVIDER, 6_000, 2_000).sign(&interloper, &domain)?,
"wrong signer",
),
];
for (voucher, reason) in &cases {
let _ = state.apply_voucher(voucher, &domain, &store);
anyhow::ensure!(state == snapshot, "{reason} must not advance state");
}
let persisted = store.load_all()?;
anyhow::ensure!(persisted.len() == 1, "exactly one lane persisted");
let only = persisted
.first()
.ok_or_else(|| anyhow::anyhow!("expected one persisted entry"))?;
anyhow::ensure!(
*only == snapshot,
"rejected voucher path must not overwrite stored lane state",
);
Ok(())
}
#[test]
fn stage_batch_then_record_once_equals_sequential_apply() -> anyhow::Result<()> {
let (signer, base, domain, batch_store) = fixture();
let mut seq_state = base.clone();
let seq_store = MemoryPoolStateStore::new();
let vouchers = [
(1_000u64, 1_048_576u64),
(2_000, 2_097_152),
(3_000, 3_145_728),
];
for (amount, bytes) in vouchers {
let signed =
build(POOL_ID, signer.address(), PROVIDER, amount, bytes).sign(&signer, &domain)?;
seq_state.apply_voucher(&signed, &domain, &seq_store)?;
}
let mut candidate = base.clone();
for (amount, bytes) in vouchers {
let signed =
build(POOL_ID, signer.address(), PROVIDER, amount, bytes).sign(&signer, &domain)?;
let (next, _applied) = candidate.stage_voucher(&signed, &domain)?;
candidate = next;
}
batch_store.record(&candidate)?;
anyhow::ensure!(
candidate == seq_state,
"batched state must equal sequential"
);
anyhow::ensure!(candidate.last_amount() == U256::from(3_000u64));
anyhow::ensure!(batch_store.len() == 1, "batch persists exactly one row");
let persisted = batch_store.load_all()?;
let only = persisted.first().ok_or_else(|| anyhow::anyhow!("no row"))?;
anyhow::ensure!(*only == seq_state, "one record commits the whole batch");
Ok(())
}
#[test]
fn stage_voucher_rejects_without_advancing_candidate() -> anyhow::Result<()> {
let (signer, base, domain, _store) = fixture();
let v1 = build(POOL_ID, signer.address(), PROVIDER, 1_000, 1_048_576).sign(&signer, &domain)?;
let (after_v1, _) = base.stage_voucher(&v1, &domain)?;
let bad =
build(POOL_ID, signer.address(), PROVIDER, 1_000, 2_097_152).sign(&signer, &domain)?;
let err = err_of(after_v1.stage_voucher(&bad, &domain))?;
anyhow::ensure!(matches!(err, PoolError::AmountRegression { .. }), "{err:?}");
anyhow::ensure!(after_v1.last_amount() == U256::from(1_000u64));
Ok(())
}
#[test]
fn a_metering_voucher_opens_an_epoch_at_index_zero() -> anyhow::Result<()> {
let (signer, mut state, domain, store) = fixture();
let signed =
build_metering(signer.address(), 1_000, 1_048_576, root(), PRICE).sign(&signer, &domain)?;
state.apply_voucher(&signed, &domain, &store)?;
let chain = state.chain();
anyhow::ensure!(chain.chain_root == root());
anyhow::ensure!(chain.chunk_price == U256::from(PRICE));
anyhow::ensure!(chain.verified_index == 0);
anyhow::ensure!(chain.tip == root(), "the root is its own tip at index 0");
anyhow::ensure!(state.owed() == U256::from(1_000u64));
Ok(())
}
#[test]
fn one_reveal_adds_one_chunk_price_over_the_anchor() -> anyhow::Result<()> {
let (signer, mut state, domain, store) = fixture();
state.apply_voucher(
&build_metering(signer.address(), 1_000, 1_048_576, root(), PRICE)
.sign(&signer, &domain)?,
&domain,
&store,
)?;
let (next, applied) = state.advance_preimage(root(), 1, reveal(1))?;
anyhow::ensure!(applied.advanced());
anyhow::ensure!(applied.amount_delta() == U256::from(PRICE));
anyhow::ensure!(applied.bytes_delta() == U256::from(crate::chain::CHUNK_BYTES));
anyhow::ensure!(next.chain().verified_index == 1);
anyhow::ensure!(next.chain().tip == reveal(1));
anyhow::ensure!(next.owed() == U256::from(1_000 + PRICE));
Ok(())
}
#[test]
fn a_skipped_gap_credits_every_step_it_crossed() -> anyhow::Result<()> {
let (signer, mut state, domain, store) = fixture();
state.apply_voucher(
&build_metering(signer.address(), 1_000, 0, root(), PRICE).sign(&signer, &domain)?,
&domain,
&store,
)?;
let (next, applied) = state.advance_preimage(root(), 7, reveal(7))?;
anyhow::ensure!(applied.amount_delta() == U256::from(7 * PRICE));
anyhow::ensure!(next.owed() == U256::from(1_000 + 7 * PRICE));
Ok(())
}
#[test]
fn preimage_frontier_names_the_walk_target_or_none() -> anyhow::Result<()> {
let (signer, mut state, domain, store) = fixture();
state.apply_voucher(
&build_metering(signer.address(), 1_000, 0, root(), PRICE).sign(&signer, &domain)?,
&domain,
&store,
)?;
anyhow::ensure!(state.preimage_frontier(root(), 3) == Some((0, root())));
anyhow::ensure!(
state
.preimage_frontier(B256::repeat_byte(0xEE), 3)
.is_none()
);
let (state, _) = state.advance_preimage(root(), 5, reveal(5))?;
anyhow::ensure!(state.preimage_frontier(root(), 5).is_none());
anyhow::ensure!(state.preimage_frontier(root(), 3).is_none());
anyhow::ensure!(state.preimage_frontier(root(), 9) == Some((5, reveal(5))));
Ok(())
}
#[test]
fn a_verified_walk_against_the_live_frontier_matches_the_single_lock_form() -> anyhow::Result<()> {
let (signer, mut state, domain, store) = fixture();
state.apply_voucher(
&build_metering(signer.address(), 1_000, 0, root(), PRICE).sign(&signer, &domain)?,
&domain,
&store,
)?;
let walked = state.preimage_frontier(root(), 4);
let Some((wv, wtip)) = walked else {
anyhow::bail!("index 4 needs a walk on a fresh epoch");
};
let ok = crate::chain::verify_forward(reveal(4), 4 - wv, wtip);
anyhow::ensure!(ok, "the honest reveal must verify");
let (opt_next, opt_applied) =
state.advance_preimage_verified(root(), 4, reveal(4), walked, ok)?;
let (ref_next, ref_applied) = state.advance_preimage(root(), 4, reveal(4))?;
anyhow::ensure!(
opt_next == ref_next,
"optimistic apply diverged from the single-lock form"
);
anyhow::ensure!(opt_applied.amount_delta() == ref_applied.amount_delta());
anyhow::ensure!(opt_next.chain().verified_index == 4);
Ok(())
}
#[test]
fn a_stale_walk_is_re_hashed_against_the_live_frontier() -> anyhow::Result<()> {
let (signer, mut state, domain, store) = fixture();
state.apply_voucher(
&build_metering(signer.address(), 1_000, 0, root(), PRICE).sign(&signer, &domain)?,
&domain,
&store,
)?;
let stale = state.preimage_frontier(root(), 5);
anyhow::ensure!(stale == Some((0, root())));
let stale_ok = crate::chain::verify_forward(reveal(5), 5, root());
let (advanced, _) = state.advance_preimage(root(), 3, reveal(3))?;
let (next, applied) =
advanced.advance_preimage_verified(root(), 5, reveal(5), stale, stale_ok)?;
anyhow::ensure!(next.chain().verified_index == 5);
anyhow::ensure!(next.chain().tip == reveal(5));
anyhow::ensure!(applied.amount_delta() == U256::from(2 * PRICE));
Ok(())
}
#[test]
fn a_true_verdict_on_a_stale_frontier_cannot_bypass_the_walk() -> anyhow::Result<()> {
let (signer, mut state, domain, store) = fixture();
state.apply_voucher(
&build_metering(signer.address(), 1_000, 0, root(), PRICE).sign(&signer, &domain)?,
&domain,
&store,
)?;
let stale = state.preimage_frontier(root(), 5);
let (advanced, _) = state.advance_preimage(root(), 3, reveal(3))?;
let foreign = B256::repeat_byte(0xAB);
let err = err_of(advanced.advance_preimage_verified(root(), 5, foreign, stale, true))?;
anyhow::ensure!(
matches!(err, PoolError::BadPreimage { .. }),
"a stale true verdict must not bypass the walk: {err:?}"
);
Ok(())
}
#[test]
fn a_covered_reveal_ignores_the_walk_verdict() -> anyhow::Result<()> {
let (signer, mut state, domain, store) = fixture();
state.apply_voucher(
&build_metering(signer.address(), 1_000, 0, root(), PRICE).sign(&signer, &domain)?,
&domain,
&store,
)?;
let (advanced, _) = state.advance_preimage(root(), 5, reveal(5))?;
let (next, applied) = advanced.advance_preimage_verified(root(), 3, reveal(3), None, false)?;
anyhow::ensure!(!applied.advanced(), "a covered reveal folds nothing");
anyhow::ensure!(
next.chain().verified_index == 5,
"the frontier is untouched"
);
Ok(())
}
#[test]
fn a_reveal_at_or_below_the_frontier_is_already_covered() -> anyhow::Result<()> {
let (signer, mut state, domain, store) = fixture();
state.apply_voucher(
&build_metering(signer.address(), 1_000, 0, root(), PRICE).sign(&signer, &domain)?,
&domain,
&store,
)?;
let (state, _) = state.advance_preimage(root(), 5, reveal(5))?;
for index in [1u8, 4, 5] {
let (next, applied) = state.advance_preimage(root(), index, reveal(index))?;
anyhow::ensure!(!applied.advanced(), "index {index} must advance nothing");
anyhow::ensure!(next.chain().verified_index == 5, "frontier must hold");
}
Ok(())
}
#[test]
fn a_foreign_preimage_is_rejected() -> anyhow::Result<()> {
let (signer, mut state, domain, store) = fixture();
state.apply_voucher(
&build_metering(signer.address(), 1_000, 0, root(), PRICE).sign(&signer, &domain)?,
&domain,
&store,
)?;
let foreign = crate::chain::preimage_at(B256::repeat_byte(0x5F), 3);
let err = err_of(state.advance_preimage(root(), 3, foreign))?;
anyhow::ensure!(
matches!(
err,
PoolError::BadPreimage {
index: 3,
verified: 0
}
),
"expected BadPreimage, got: {err:?}"
);
anyhow::ensure!(
state.chain().verified_index == 0,
"a rejection advances nothing"
);
Ok(())
}
#[test]
fn a_sealed_voucher_cannot_be_extended() -> anyhow::Result<()> {
let (signer, mut state, domain, store) = fixture();
state.apply_voucher(
&build(POOL_ID, signer.address(), PROVIDER, 1_000, 0).sign(&signer, &domain)?,
&domain,
&store,
)?;
anyhow::ensure!(state.chain().chain_root.is_zero());
for index in [1u8, 255] {
let (next, applied) = state.advance_preimage(B256::ZERO, index, reveal(index))?;
anyhow::ensure!(!applied.advanced());
anyhow::ensure!(next.owed() == U256::from(1_000u64));
}
Ok(())
}
#[test]
fn a_correctly_folded_rollover_preserves_the_total() -> anyhow::Result<()> {
let (signer, mut state, domain, store) = fixture();
state.apply_voucher(
&build_metering(signer.address(), 1_000, 0, root(), PRICE).sign(&signer, &domain)?,
&domain,
&store,
)?;
let (state_at_3, _) = state.advance_preimage(root(), 3, reveal(3))?;
let owed_before = state_at_3.owed();
anyhow::ensure!(owed_before == U256::from(1_000 + 3 * PRICE));
let next_seed = B256::repeat_byte(0x6E);
let next_root = crate::chain::root_from_seed(next_seed);
let folded = build_metering(
signer.address(),
1_000 + 3 * PRICE,
3 * crate::chain::CHUNK_BYTES,
next_root,
PRICE,
)
.sign(&signer, &domain)?;
let (rolled, _) = state_at_3.advance_presigned(&folded)?;
anyhow::ensure!(rolled.owed() == owed_before, "the fold must lose nothing");
anyhow::ensure!(rolled.chain().chain_root == next_root);
anyhow::ensure!(rolled.chain().verified_index == 0);
Ok(())
}
#[test]
fn an_under_folded_rollover_is_refused() -> anyhow::Result<()> {
let (signer, mut state, domain, store) = fixture();
state.apply_voucher(
&build_metering(signer.address(), 1_000, 0, root(), PRICE).sign(&signer, &domain)?,
&domain,
&store,
)?;
let (state_at_9, _) = state.advance_preimage(root(), 9, reveal(9))?;
let owed_before = state_at_9.owed();
let next_root = crate::chain::root_from_seed(B256::repeat_byte(0x6E));
let stingy = build_metering(
signer.address(),
1_000 + 2 * PRICE,
2 * crate::chain::CHUNK_BYTES,
next_root,
PRICE,
)
.sign(&signer, &domain)?;
anyhow::ensure!(
matches!(
state_at_9.advance_presigned(&stingy),
Err(PoolError::UnderFold { axis: FoldAxis::Amount, owed, got })
if owed == owed_before && got == U256::from(1_000 + 2 * PRICE)
),
"an under-folding rollover must be refused, naming the fold it owed"
);
anyhow::ensure!(
state_at_9.owed() == owed_before,
"and the lane must be untouched by the refusal"
);
anyhow::ensure!(state_at_9.chain().chain_root == root());
anyhow::ensure!(state_at_9.chain().verified_index == 9);
Ok(())
}
#[test]
fn a_rollover_at_the_anchor_over_a_proved_frontier_is_an_under_fold() -> anyhow::Result<()> {
let (signer, mut state, domain, store) = fixture();
state.apply_voucher(
&build_metering(signer.address(), 1_000, 0, root(), PRICE).sign(&signer, &domain)?,
&domain,
&store,
)?;
let (state_at_1, _) = state.advance_preimage(root(), 1, reveal(1))?;
let next_root = crate::chain::root_from_seed(B256::repeat_byte(0x6E));
let at_anchor =
build_metering(signer.address(), 1_000, 0, next_root, PRICE).sign(&signer, &domain)?;
anyhow::ensure!(
matches!(
state_at_1.advance_presigned(&at_anchor),
Err(PoolError::UnderFold { axis: FoldAxis::Amount, owed, got })
if owed == state_at_1.owed() && got == U256::from(1_000u64)
),
"a rollover at the anchor over a proved frontier must be an under-fold"
);
let at_zero =
build_metering(signer.address(), 1_000, 0, next_root, PRICE).sign(&signer, &domain)?;
anyhow::ensure!(
matches!(
state.advance_presigned(&at_zero),
Err(PoolError::AmountRegression { .. })
),
"with nothing proved, the anchor voucher stays an ordering regression"
);
anyhow::ensure!(state.adopt_chain(&at_zero).is_some());
Ok(())
}
#[test]
fn a_late_sealed_close_at_the_anchor_is_not_an_under_fold() -> anyhow::Result<()> {
let (signer, mut state, domain, store) = fixture();
let sealed_close =
build_metering(signer.address(), 1_000, 0, B256::ZERO, 0).sign(&signer, &domain)?;
state.apply_voucher(
&build_metering(signer.address(), 1_000, 0, root(), PRICE).sign(&signer, &domain)?,
&domain,
&store,
)?;
let (state_at_1, _) = state.advance_preimage(root(), 1, reveal(1))?;
anyhow::ensure!(
matches!(
state_at_1.advance_presigned(&sealed_close),
Err(PoolError::AmountRegression { .. })
),
"a late sealed close at the anchor must stay an ordering regression"
);
Ok(())
}
#[test]
fn a_rollover_folding_short_on_bytes_is_refused() -> anyhow::Result<()> {
let (signer, mut state, domain, store) = fixture();
state.apply_voucher(
&build_metering(signer.address(), 1_000, 0, root(), PRICE).sign(&signer, &domain)?,
&domain,
&store,
)?;
let (state_at_9, _) = state.advance_preimage(root(), 9, reveal(9))?;
let bytes_before = state_at_9.owed_bytes();
let next_root = crate::chain::root_from_seed(B256::repeat_byte(0x6E));
let short_bytes = build_metering(
signer.address(),
1_000 + 9 * PRICE,
2 * crate::chain::CHUNK_BYTES,
next_root,
PRICE,
)
.sign(&signer, &domain)?;
anyhow::ensure!(
matches!(
state_at_9.advance_presigned(&short_bytes),
Err(PoolError::UnderFold { axis: FoldAxis::Bytes, owed, got })
if owed == bytes_before && got == U256::from(2 * crate::chain::CHUNK_BYTES)
),
"a rollover short on bytes must be refused, naming the bytes it owed"
);
anyhow::ensure!(state_at_9.owed_bytes() == bytes_before);
anyhow::ensure!(state_at_9.chain().chain_root == root());
Ok(())
}
#[test]
fn the_under_fold_refusal_carries_a_resume_bundle() {
let reason = crate::client_bridge::voucher_reject_reason(&PoolError::UnderFold {
axis: FoldAxis::Amount,
owed: U256::from(1_090u64),
got: U256::from(1_020u64),
});
assert_eq!(
reason,
Ok(decdn_protocol::client::VoucherRejectReason::UnderFold)
);
assert!(
decdn_protocol::client::VoucherRejectReason::UnderFold.is_watermark_gated(),
"the payer cannot fold correctly without the bundle that states the frontier"
);
}
#[test]
fn a_reveal_for_a_superseded_epoch_folds_nothing() -> anyhow::Result<()> {
let (signer, mut state, domain, store) = fixture();
state.apply_voucher(
&build_metering(signer.address(), 1_000, 0, root(), PRICE).sign(&signer, &domain)?,
&domain,
&store,
)?;
let (state_at_3, _) = state.advance_preimage(root(), 3, reveal(3))?;
let next_root = crate::chain::root_from_seed(B256::repeat_byte(0x6E));
let (rolled, _) = state_at_3.advance_presigned(
&build_metering(
signer.address(),
1_000 + 3 * PRICE,
3 * crate::chain::CHUNK_BYTES,
next_root,
PRICE,
)
.sign(&signer, &domain)?,
)?;
let (after, applied) = rolled.advance_preimage(root(), 4, reveal(4))?;
anyhow::ensure!(!applied.advanced());
anyhow::ensure!(after.owed() == rolled.owed());
Ok(())
}
#[test]
fn re_asserting_the_same_root_holds_the_frontier() -> anyhow::Result<()> {
let (signer, mut state, domain, store) = fixture();
state.apply_voucher(
&build_metering(signer.address(), 1_000, 0, root(), PRICE).sign(&signer, &domain)?,
&domain,
&store,
)?;
let (state_at_6, _) = state.advance_preimage(root(), 6, reveal(6))?;
let same_epoch =
build_metering(signer.address(), 1_500, 0, root(), PRICE).sign(&signer, &domain)?;
let (after, _) = state_at_6.advance_presigned(&same_epoch)?;
anyhow::ensure!(after.chain().verified_index == 6, "frontier must survive");
anyhow::ensure!(after.chain().tip == reveal(6));
anyhow::ensure!(after.owed() == U256::from(1_500 + 6 * PRICE));
Ok(())
}
#[test]
fn a_full_depth_chain_is_claimable() -> anyhow::Result<()> {
let (signer, mut state, domain, store) = fixture();
state.apply_voucher(
&build_metering(signer.address(), 1_000, 0, root(), PRICE).sign(&signer, &domain)?,
&domain,
&store,
)?;
let (deep, applied) = state.advance_preimage(
root(),
crate::chain::MAX_CHAIN_LENGTH,
reveal(crate::chain::MAX_CHAIN_LENGTH),
)?;
anyhow::ensure!(applied.amount_delta() == U256::from(255 * PRICE));
anyhow::ensure!(deep.owed() == U256::from(1_000 + 255 * PRICE));
let claim = deep
.live_claim()
.ok_or_else(|| anyhow::anyhow!("expected a claim"))?;
anyhow::ensure!(claim.value() == deep.owed());
anyhow::ensure!(
claim.bytes_value() == U256::from(255u64) * U256::from(crate::chain::CHUNK_BYTES)
);
Ok(())
}
#[test]
fn a_non_advancing_voucher_replaces_a_chain_that_proved_nothing() -> anyhow::Result<()> {
let (signer, mut state, domain, store) = fixture();
state.apply_voucher(
&build_metering(signer.address(), 1_000, 0, root(), PRICE).sign(&signer, &domain)?,
&domain,
&store,
)?;
let next_root = crate::chain::root_from_seed(B256::repeat_byte(0x6E));
let opening =
build_metering(signer.address(), 1_000, 0, next_root, PRICE).sign(&signer, &domain)?;
let adopted = state
.adopt_chain(&opening)
.ok_or_else(|| anyhow::anyhow!("an unproved chain must yield to a fresh root"))?;
anyhow::ensure!(adopted.chain().chain_root == next_root);
anyhow::ensure!(adopted.chain().verified_index == 0);
let claim = adopted
.live_claim()
.ok_or_else(|| anyhow::anyhow!("an adopted anchor is a claim"))?;
let rebuilt = SignedVoucher {
voucher: Voucher {
pool_id: POOL_ID,
signer: signer.address(),
provider: PROVIDER,
amount: claim.amount,
bytes_delivered: claim.bytes_delivered,
chain_root: claim.chain.chain_root,
chunk_price: claim.chain.chunk_price,
},
signature: alloy::primitives::Signature::from_raw(&claim.signature)
.map_err(|e| anyhow::anyhow!("claim signature is malformed: {e}"))?,
};
rebuilt.verify_signer(signer.address(), &domain)?;
let (next, applied) = adopted.advance_preimage(
next_root,
1,
crate::chain::preimage_at(B256::repeat_byte(0x6E), 1),
)?;
anyhow::ensure!(applied.advanced());
anyhow::ensure!(next.owed() == U256::from(1_000 + PRICE));
Ok(())
}
#[test]
fn a_re_asserting_voucher_cannot_reprice_the_lane() -> anyhow::Result<()> {
let (signer, mut state, domain, store) = fixture();
state.apply_voucher(
&build_metering(signer.address(), 1_000, 0, root(), PRICE).sign(&signer, &domain)?,
&domain,
&store,
)?;
let (proved, _) = state.advance_preimage(root(), 4, reveal(4))?;
let repriced =
build_metering(signer.address(), 1_000, 0, root(), PRICE * 2).sign(&signer, &domain)?;
anyhow::ensure!(
proved.adopt_chain(&repriced).is_none(),
"an already-satisfied voucher pays for nothing and may not reprice the frontier"
);
let claim = proved
.live_claim()
.ok_or_else(|| anyhow::anyhow!("a lane with a signature has a claim"))?;
let rebuilt = SignedVoucher {
voucher: Voucher {
pool_id: POOL_ID,
signer: signer.address(),
provider: PROVIDER,
amount: claim.amount,
bytes_delivered: claim.bytes_delivered,
chain_root: claim.chain.chain_root,
chunk_price: claim.chain.chunk_price,
},
signature: alloy::primitives::Signature::from_raw(&claim.signature)
.map_err(|e| anyhow::anyhow!("claim signature is malformed: {e}"))?,
};
rebuilt.verify_signer(signer.address(), &domain)?;
Ok(())
}
#[test]
fn a_non_advancing_voucher_cannot_retire_a_proved_chain() -> anyhow::Result<()> {
let (signer, mut state, domain, store) = fixture();
state.apply_voucher(
&build_metering(signer.address(), 1_000, 0, root(), PRICE).sign(&signer, &domain)?,
&domain,
&store,
)?;
let (proved, _) = state.advance_preimage(root(), 4, reveal(4))?;
let next_root = crate::chain::root_from_seed(B256::repeat_byte(0x6E));
let stale =
build_metering(signer.address(), 1_000, 0, next_root, PRICE).sign(&signer, &domain)?;
anyhow::ensure!(
proved.adopt_chain(&stale).is_none(),
"a proved frontier must survive a voucher that paid nothing for it"
);
anyhow::ensure!(proved.owed() == U256::from(1_000 + 4 * PRICE));
Ok(())
}
#[test]
fn a_voucher_that_folds_the_proved_frontier_may_open_a_fresh_chain() -> anyhow::Result<()> {
let (signer, mut state, domain, store) = fixture();
state.apply_voucher(
&build_metering(signer.address(), 1_000, 0, root(), PRICE).sign(&signer, &domain)?,
&domain,
&store,
)?;
let (proved, _) = state.advance_preimage(root(), 4, reveal(4))?;
let folded_amount = 1_000 + 4 * PRICE;
anyhow::ensure!(proved.owed() == U256::from(folded_amount));
let next_root = crate::chain::root_from_seed(B256::repeat_byte(0x6E));
let resumed = build_metering(
signer.address(),
folded_amount,
4 * crate::chain::CHUNK_BYTES,
next_root,
PRICE,
)
.sign(&signer, &domain)?;
let (healed, _) = proved.advance_presigned(&resumed)?;
anyhow::ensure!(
healed.chain().chain_root == next_root,
"a folding voucher installs the chain it names"
);
anyhow::ensure!(
healed.chain().verified_index == 0,
"the fresh chain starts at its own root, with nothing proved under it"
);
anyhow::ensure!(
healed.owed() == U256::from(folded_amount),
"the fold is exact: retiring the old chain strands none of its value \
and duplicates none of it either"
);
Ok(())
}
#[test]
fn a_reveal_past_the_spending_cap_is_refused() -> anyhow::Result<()> {
let (signer, _, domain, store) = fixture();
let anchor = 1_000u64;
let cap = U256::from(anchor + 2 * PRICE);
let mut state = LaneState::hydrate(
POOL_ID,
signer.address(),
PROVIDER,
cap,
0,
U256::ZERO,
U256::ZERO,
None,
LaneChain::NONE,
);
state.apply_voucher(
&build_metering(signer.address(), anchor, 0, root(), PRICE).sign(&signer, &domain)?,
&domain,
&store,
)?;
let (at_cap, _) = state.advance_preimage(root(), 2, reveal(2))?;
anyhow::ensure!(
at_cap.owed() == cap,
"a reveal landing exactly ON the cap is still payable"
);
anyhow::ensure!(
matches!(
at_cap.advance_preimage(root(), 3, reveal(3)),
Err(PoolError::CapExceeded { .. })
),
"the reveal that would cross the cap must be refused, not credited"
);
Ok(())
}
#[test]
fn a_lane_with_no_voucher_holds_no_claim() {
let (_, state, _, _) = fixture();
assert!(state.live_claim().is_none());
assert_eq!(state.owed(), U256::ZERO);
}