commit 15ded2b144f71ce971dd2a037b6d52189663350c
parent 8840d73382f1c8af915bafe3885fb91c44e34719
Author: Joris Hartog <jorishartog@hotmail.com>
Date: Fri, 7 Aug 2026 15:29:46 +0200
Increase fallback finalizer slot delay
Diffstat:
3 files changed, 12 insertions(+), 7 deletions(-)
diff --git a/docs/protocol.md b/docs/protocol.md
@@ -69,8 +69,8 @@ Fallback finalizers use more VDF work: rank `0` uses the base rounds, rank `1` u
VDF rounds alone are not the fallback gate. Faster hardware could otherwise finish a lower-ranked VDF before a slower primary finalizer. iuna therefore also uses rank time slots:
- rank `0` blocks are valid as soon as their timestamp is greater than the parent timestamp;
-- rank `1` blocks are valid from `parent timestamp + 1 * target block time`;
-- rank `2` blocks are valid from `parent timestamp + 2 * target block time`;
+- rank `1` blocks are valid from `parent timestamp + 2 * target block time`;
+- rank `2` blocks are valid from `parent timestamp + 4 * target block time`;
- and so on.
If a fallback finalizer finishes the VDF early, it must wait until its slot opens before publishing. Rank `0` does not wait on a rank slot; that keeps the primary path useful as the clean VDF-speed signal for retargeting. If a rank `0` finalizer finishes late, the block timestamp should reflect that later completion/publication time so VDF retargeting can observe slow rounds. Other nodes reject fallback blocks whose timestamp is before their rank slot.
@@ -84,7 +84,7 @@ Rank slots depend on block timestamps, so timestamps are constrained by consensu
- it must not be too far in the future relative to the validating node's network-adjusted clock;
- for fallback ticket blocks, it must be at or after the finalizer rank slot.
-The future drift limit is `2 minutes`. A finalizer can lie within that small margin, but cannot skip an entire `5 minute` rank slot by claiming a far-future timestamp. P2P treats too-early future/slot blocks as temporal errors rather than peer-banning evidence.
+The future drift limit is `2 minutes`. A finalizer can lie within that small margin, but cannot skip an entire `10 minute` fallback rank slot by claiming a far-future timestamp. P2P treats too-early future/slot blocks as temporal errors rather than peer-banning evidence.
## Recovery Blocks
diff --git a/src/app.rs b/src/app.rs
@@ -3466,7 +3466,7 @@ mod tests {
let work = plan.work.expect("fallback work should be prepared");
let vdf_output = run_vdf(work.vdf_seed(), work.vdf_rounds());
let block = node
- .complete_prepared_block_at(work, vdf_output, VDF_TARGET_BLOCK_MS)
+ .complete_prepared_block_at(work, vdf_output, VDF_TARGET_BLOCK_MS * 2)
.unwrap();
assert_eq!(block.finalizer_rank, 1);
@@ -3578,7 +3578,7 @@ mod tests {
let vdf_output = run_vdf(work.vdf_seed(), work.vdf_rounds());
let mut peer_ledger = node.clone_ledger();
let block = node
- .complete_prepared_block_at(work, vdf_output, VDF_TARGET_BLOCK_MS)
+ .complete_prepared_block_at(work, vdf_output, VDF_TARGET_BLOCK_MS * 2)
.unwrap();
assert!(block.transactions.iter().any(Transaction::is_burn));
assert!(
diff --git a/src/domain.rs b/src/domain.rs
@@ -4021,6 +4021,8 @@ fn vdf_rounds_for_finalizer_rank(base_rounds: u64, rank: u32) -> Result<u64> {
fn finalizer_rank_slot_delay_ms(rank: u32) -> Result<u64> {
VDF_TARGET_BLOCK_MS
+ .checked_mul(2)
+ .context("finalizer rank time slot overflow")?
.checked_mul(u64::from(rank))
.context("finalizer rank time slot overflow")
}
@@ -6623,7 +6625,10 @@ mod tests {
.prepare_next_block(fallback.address(), parent_timestamp + 1)
.unwrap();
- assert_eq!(work.timestamp_ms(), parent_timestamp + VDF_TARGET_BLOCK_MS);
+ assert_eq!(
+ work.timestamp_ms(),
+ parent_timestamp + VDF_TARGET_BLOCK_MS * 2
+ );
assert_eq!(work.vdf_rounds(), ledger.vdf_rounds() * 2);
}
@@ -6698,7 +6703,7 @@ mod tests {
.prepare_next_block(fallback.address(), parent_timestamp + 1)
.unwrap();
let mut block = work.finish(fallback, "preverified-vdf".to_string());
- block.timestamp_ms = parent_timestamp + VDF_TARGET_BLOCK_MS - 1;
+ block.timestamp_ms = parent_timestamp + VDF_TARGET_BLOCK_MS * 2 - 1;
block.hash = block.compute_hash();
let error = ledger