iuna

iuna

iuna - experimental mainnet-candidate protocol
git clone https://getiuna.org/git/iuna.git
Log | Files | Refs | README | LICENSE

commit 46bd7da82ad2a1e81b97359653dc55d75847f261
parent 6ee9944c0bee65c5683797edfd61958f50b07ade
Author: Joris Hartog <jorishartog@hotmail.com>
Date:   Thu,  3 Sep 2026 18:44:47 +0200

Fix ticket block size estimation

Diffstat:
Msrc/domain/ledger_ops.rs | 13+++++--------
Msrc/domain/ledger_pending.rs | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
2 files changed, 68 insertions(+), 12 deletions(-)

diff --git a/src/domain/ledger_ops.rs b/src/domain/ledger_ops.rs @@ -83,24 +83,21 @@ pub(super) fn validate_genesis_burn_transaction(transaction: &Transaction) -> Re pub(super) fn estimated_block_selection_size_bytes( context: &CompactBlockContext, selection: &BlockSelection, - recovery: bool, + finalizer_mode: FinalizerMode, burn_bundle_section: &BurnBundleSection, ) -> Result<usize> { + let has_leader_proof = finalizer_mode == FinalizerMode::Ticket; let block = Block { height: u64::MAX, prev_hash: "f".repeat(64), timestamp_ms: u64::MAX, miner: "f".repeat(64), - finalizer_mode: if recovery { - FinalizerMode::Recovery - } else { - FinalizerMode::Ticket - }, + finalizer_mode, finalizer_rank: 0, reward: u64::MAX, vdf_rounds: u64::MAX, vdf_output: vdf_solution_placeholder(), - leader_proof: (!recovery).then(|| LeaderProof { + leader_proof: has_leader_proof.then(|| LeaderProof { ticket_id: "f".repeat(64), public_key: "f".repeat(64), signature: "f".repeat(128), @@ -123,7 +120,7 @@ pub(super) fn ensure_transaction_fits_empty_block( if estimated_block_selection_size_bytes( context, &selection, - false, + FinalizerMode::Ticket, &BurnBundleSection::default(), )? > max_block_bytes { diff --git a/src/domain/ledger_pending.rs b/src/domain/ledger_pending.rs @@ -20,8 +20,8 @@ use super::validation::{ validate_address, validate_hash, validate_signature, validate_stratum_header, }; use super::{ - Amount, BurnBundleSection, Ledger, MAX_PENDING_POOL_BYTES, MAX_PENDING_TRANSACTIONS, - MINE_ACTIONS_PER_ANCHOR_LIMIT, OutPoint, Transaction, TxOutput, + Amount, BurnBundleSection, FinalizerMode, Ledger, MAX_PENDING_POOL_BYTES, + MAX_PENDING_TRANSACTIONS, MINE_ACTIONS_PER_ANCHOR_LIMIT, OutPoint, Transaction, TxOutput, }; pub(crate) const MINE_ANCHOR_LIMIT_REACHED: &str = "mine transaction anchor limit reached"; @@ -92,6 +92,7 @@ impl Ledger { self.select_block_transactions_with_required_burn_owner( Some(miner), required_burn_signature, + FinalizerMode::Ticket, burn_bundle_section, ) } @@ -105,6 +106,7 @@ impl Ledger { self.select_block_transactions_with_required_burn_owner( Some(miner), required_burn_signature, + FinalizerMode::Recovery, burn_bundle_section, ) } @@ -113,6 +115,7 @@ impl Ledger { &self, required_burn_owner: Option<&str>, required_burn_signature: Option<&str>, + finalizer_mode: FinalizerMode, burn_bundle_section: &BurnBundleSection, ) -> Result<BlockSelection> { let block_context = compact_block_context(self); @@ -193,7 +196,7 @@ impl Ledger { if estimated_block_selection_size_bytes( block_context, &required_selection, - required_burn_owner.is_some(), + finalizer_mode, burn_bundle_section, )? > self.launch_profile.max_block_bytes { @@ -214,7 +217,7 @@ impl Ledger { if estimated_block_selection_size_bytes( block_context, &candidate, - required_burn_owner.is_some(), + finalizer_mode, burn_bundle_section, )? <= self.launch_profile.max_block_bytes { @@ -654,6 +657,7 @@ mod tests { .select_block_transactions_with_required_burn_owner( None, None, + FinalizerMode::Ticket, &BurnBundleSection::default(), ) .unwrap() @@ -674,6 +678,7 @@ mod tests { .select_block_transactions_with_required_burn_owner( None, None, + FinalizerMode::Ticket, &BurnBundleSection::default(), ) .unwrap() @@ -755,6 +760,7 @@ mod tests { .select_block_transactions_with_required_burn_owner( Some(finalizer.address()), Some(anchor.signature()), + FinalizerMode::Ticket, &BurnBundleSection::default(), ) .unwrap(); @@ -774,6 +780,58 @@ mod tests { } #[test] + fn ticket_block_selection_reserves_space_for_leader_proof() { + let finalizer = Wallet::from_seed("ticket-selection-leader-proof-finalizer"); + let mut ledger = Ledger::new(BTreeMap::from([(finalizer.address().to_string(), 10)]), 1); + let anchor = ledger.build_burn(&finalizer, 1, 1).unwrap(); + ledger.submit_transaction(anchor.clone()).unwrap(); + + let required_selection = BlockSelection { + transactions: vec![anchor.clone()], + }; + let ticket_bytes = estimated_block_selection_size_bytes( + compact_block_context(&ledger), + &required_selection, + FinalizerMode::Ticket, + &BurnBundleSection::default(), + ) + .unwrap(); + let recovery_bytes = estimated_block_selection_size_bytes( + compact_block_context(&ledger), + &required_selection, + FinalizerMode::Recovery, + &BurnBundleSection::default(), + ) + .unwrap(); + assert!(ticket_bytes > recovery_bytes); + + ledger.launch_profile.max_block_bytes = ticket_bytes - 1; + assert!(recovery_bytes <= ledger.launch_profile.max_block_bytes); + + let error = ledger + .select_block_transactions_with_burn_section( + finalizer.address(), + Some(anchor.signature()), + &BurnBundleSection::default(), + ) + .unwrap_err(); + assert!( + error + .to_string() + .contains("required block content does not fit in the block") + ); + + let recovery_selection = ledger + .select_recovery_block_transactions_with_burn_section( + finalizer.address(), + Some(anchor.signature()), + &BurnBundleSection::default(), + ) + .unwrap(); + assert_eq!(recovery_selection.transactions, vec![anchor]); + } + + #[test] fn public_transfers_and_burns_require_nonzero_fees() { let alice = Wallet::from_seed("zero-fee-alice"); let bob = Wallet::from_seed("zero-fee-bob"); @@ -824,6 +882,7 @@ mod tests { .select_block_transactions_with_required_burn_owner( Some(finalizer.address()), Some(anchor.signature()), + FinalizerMode::Ticket, &section, ) .unwrap_err()