iuna

iuna

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

commit 761bdb43dcca456dffa035736b4819755f0361c8
parent dadc1ff339eb5f21042208d4055473d833bc51bd
Author: Joris Hartog <jorishartog@hotmail.com>
Date:   Thu, 13 Aug 2026 22:35:15 +0200

Reject transactions too large for blocks

Diffstat:
Msrc/domain/ledger_mempool.rs | 9++++++++-
Msrc/domain/ledger_ops.rs | 28++++++++++++++++++++++++++++
Msrc/domain/ledger_pending.rs | 8+++++++-
Msrc/domain/tests.rs | 35+++++++++++++++++++----------------
4 files changed, 62 insertions(+), 18 deletions(-)

diff --git a/src/domain/ledger_mempool.rs b/src/domain/ledger_mempool.rs @@ -1,7 +1,9 @@ use anyhow::{Result, bail}; use super::ledger_ops::{ - apply_transaction, spend_blinded_inputs, spend_inputs, transaction_has_missing_inputs, + apply_transaction, ensure_blinded_transaction_fits_empty_block, + ensure_transaction_fits_empty_block, spend_blinded_inputs, spend_inputs, + transaction_has_missing_inputs, }; use super::transaction::{ BlindedReveal, BlindedTransaction, Transaction, blinded_transaction_inputs_spent_by, @@ -27,6 +29,10 @@ impl Ledger { return Ok(false); } self.validate_blinded_transaction(&transaction)?; + ensure_blinded_transaction_fits_empty_block( + &transaction, + self.launch_profile.max_block_bytes, + )?; if blinded_transaction_inputs_spent_by(&transaction, &self.pending_blinded) || transaction_inputs_spent_by_inputs(&transaction.inputs, &self.pending) || transaction_inputs_spent_by_inputs(&transaction.inputs, &self.orphans) @@ -64,6 +70,7 @@ impl Ledger { transaction.verify_signature()?; self.validate_transaction_terms(&transaction)?; + ensure_transaction_fits_empty_block(&transaction, self.launch_profile.max_block_bytes)?; self.validate_mine_anchor_available(&transaction)?; if transaction_inputs_spent_by(&transaction, &self.pending) { diff --git a/src/domain/ledger_ops.rs b/src/domain/ledger_ops.rs @@ -98,6 +98,34 @@ pub(super) fn estimated_block_selection_size_bytes( block.serialized_size_bytes() } +pub(super) fn ensure_transaction_fits_empty_block( + transaction: &Transaction, + max_block_bytes: usize, +) -> Result<()> { + let selection = BlockSelection { + transactions: vec![transaction.clone()], + blinded_transactions: Vec::new(), + }; + if estimated_block_selection_size_bytes(&selection, false)? > max_block_bytes { + bail!("transaction exceeds max block size"); + } + Ok(()) +} + +pub(super) fn ensure_blinded_transaction_fits_empty_block( + transaction: &BlindedTransaction, + max_block_bytes: usize, +) -> Result<()> { + let selection = BlockSelection { + transactions: Vec::new(), + blinded_transactions: vec![transaction.clone()], + }; + if estimated_block_selection_size_bytes(&selection, false)? > max_block_bytes { + bail!("blinded transaction exceeds max block size"); + } + Ok(()) +} + pub(super) fn verify_leader_proof(block: &Block, tickets: &[BurnTicket]) -> Result<()> { let Some(proof) = &block.leader_proof else { bail!("block is missing leader proof"); diff --git a/src/domain/ledger_pending.rs b/src/domain/ledger_pending.rs @@ -10,7 +10,8 @@ use super::blinded::{ use super::ledger_ops::{ apply_spendable_pending_transaction, apply_transaction, best_selectable_blinded_index, best_selectable_burn_from_index, best_selectable_transaction_index, - ensure_outputs_do_not_overflow, ensure_single_input_owner, + ensure_blinded_transaction_fits_empty_block, ensure_outputs_do_not_overflow, + ensure_single_input_owner, ensure_transaction_fits_empty_block, estimated_block_selection_size_bytes, spend_blinded_inputs, spend_spendable_blinded_inputs, transaction_has_missing_inputs, validate_transaction_inputs, validate_transaction_outputs, }; @@ -291,6 +292,7 @@ impl Ledger { pub(super) fn validate_new_transaction(&self, transaction: &Transaction) -> Result<()> { self.validate_transaction_terms(transaction)?; + ensure_transaction_fits_empty_block(transaction, self.launch_profile.max_block_bytes)?; self.validate_mine_anchor_available(transaction)?; let mut utxos = self.utxos_after_spendable_pending()?; apply_transaction(transaction, &mut utxos) @@ -445,6 +447,10 @@ impl Ledger { if transaction.commitment != expected { bail!("blinded transaction commitment is invalid"); } + ensure_blinded_transaction_fits_empty_block( + transaction, + self.launch_profile.max_block_bytes, + )?; Ok(()) } diff --git a/src/domain/tests.rs b/src/domain/tests.rs @@ -1098,14 +1098,12 @@ fn block_before_finalizer_rank_time_slot_is_rejected() { } #[test] -fn miner_skips_oversized_pending_transaction_and_keeps_fitting_fee_transaction() { +fn mempool_rejects_transaction_that_cannot_fit_in_a_block() { let alice = Wallet::from_seed("oversized-select-alice"); let bob = Wallet::from_seed("oversized-select-bob"); - let carol = Wallet::from_seed("oversized-select-carol"); let mut allocations = BTreeMap::new(); allocations.insert(alice.address().to_string(), 1); allocations.insert(bob.address().to_string(), 300_000); - allocations.insert(carol.address().to_string(), 300_000); let mut ledger = Ledger::new_with_genesis_burns(allocations, vec![GenesisBurn::new(alice.address(), 1)], 10) .unwrap(); @@ -1113,23 +1111,28 @@ fn miner_skips_oversized_pending_transaction_and_keeps_fitting_fee_transaction() ledger.submit_transaction(burn).unwrap(); let oversized = transfer_with_extra_zero_outputs(&ledger, &bob, alice.address(), 1, 100_000, 4_000); - let fitting = ledger - .build_transfer(&carol, alice.address(), 1, 5) - .unwrap(); assert!(oversized.serialized_size_bytes().unwrap() > MAX_BLOCK_BYTES); - ledger.submit_transaction(oversized.clone()).unwrap(); - ledger.submit_transaction(fitting.clone()).unwrap(); - let block = ledger.mine_next_block(&alice, 1).unwrap(); - let signatures = block - .transactions - .iter() - .map(|tx| tx.signature().to_string()) + let error = ledger.submit_transaction(oversized).unwrap_err(); + + assert!(format!("{error:#}").contains("transaction exceeds max block size")); +} + +#[test] +fn transfer_builder_rejects_selected_utxos_that_make_transaction_too_large() { + let alice = Wallet::from_seed("oversized-selected-utxos-alice"); + let bob = Wallet::from_seed("oversized-selected-utxos-bob"); + let amounts = vec![1; 2_000]; + let ledger = ledger_with_wallet_utxos(&alice, &amounts); + let selected = (0..amounts.len()) + .map(test_utxo_outpoint) .collect::<Vec<_>>(); - assert!(!signatures.contains(&oversized.signature().to_string())); - assert!(signatures.contains(&fitting.signature().to_string())); - assert!(block.serialized_size_bytes().unwrap() <= MAX_BLOCK_BYTES); + let error = ledger + .build_transfer_with_inputs(&alice, bob.address(), 1, 0, &selected) + .unwrap_err(); + + assert!(format!("{error:#}").contains("transaction exceeds max block size")); } #[test]