iuna

iuna

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

commit 42931365e2b8ac38588cfbf8fea1c692f83da5c5
parent 8ace0ea013212d4821599617c0e8433cb18cca1a
Author: Joris Hartog <jorishartog@hotmail.com>
Date:   Tue, 18 Aug 2026 08:46:35 +0200

Validate reveal bundles before conflict handling

Diffstat:
Msrc/app/receive.rs | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 66 insertions(+), 2 deletions(-)

diff --git a/src/app/receive.rs b/src/app/receive.rs @@ -77,6 +77,8 @@ impl NodeCore { return Ok(()); } let key = (bundle.height, bundle.slot); + self.ledger + .validate_next_block_reveal_bundles(vec![bundle.clone()])?; if self.equivocated_reveal_bundle_slots.contains(&key) { return Ok(()); } @@ -87,8 +89,6 @@ impl NodeCore { } return Ok(()); } - self.ledger - .validate_next_block_reveal_bundles(vec![bundle.clone()])?; self.reveal_bundles.insert(key, bundle.clone()); self.outbox.push(GossipEnvelope::RevealBundle(bundle)); Ok(()) @@ -391,6 +391,70 @@ mod tests { } #[test] + fn invalid_conflicting_reveal_bundle_does_not_poison_stored_slot() { + const TEST_SIGNATURE_BYTES: usize = 64; + + let alice = Wallet::from_seed("invalid-conflict-bundle-alice"); + let bob = Wallet::from_seed("invalid-conflict-bundle-bob"); + let carol = Wallet::from_seed("invalid-conflict-bundle-carol"); + let finalizers = [alice.clone(), bob.clone()]; + let mut allocations = BTreeMap::new(); + allocations.insert(alice.address().to_string(), 10 * MICRO_IUNA); + allocations.insert(bob.address().to_string(), 10 * MICRO_IUNA); + allocations.insert(carol.address().to_string(), 10 * MICRO_IUNA); + let mut ledger = Ledger::new_with_genesis_burns( + allocations, + finalizers + .iter() + .map(|wallet| GenesisBurn::new(wallet.address(), MICRO_IUNA)) + .collect(), + 1, + ) + .unwrap(); + let blinded = ledger + .build_blinded_burn(&carol, 3, 100, ledger.height() + 4) + .unwrap(); + ledger + .submit_blinded_transaction(blinded.transaction.clone()) + .unwrap(); + let leader = ledger.expected_leader_for_next_block().unwrap(); + let leader_wallet = wallet_for_address(&finalizers, &leader); + let burn = ledger.build_burn(leader_wallet, 1, 0).unwrap(); + ledger.submit_transaction(burn).unwrap(); + let commit_block = ledger.mine_next_block(leader_wallet, 1).unwrap(); + ledger.apply_locally_mined_block(commit_block).unwrap(); + ledger.submit_blinded_reveal(blinded.reveal).unwrap(); + + let committee_member = ledger.reveal_committee_for_next_block()[0].clone(); + let committee_wallet = wallet_for_address(&finalizers, &committee_member.owner); + let valid_bundle = ledger + .build_reveal_bundle(committee_wallet) + .unwrap() + .unwrap(); + let mut node = NodeCore::from_ledger_with_burn_fee_and_enabled( + committee_wallet.clone(), + ledger, + true, + 0, + 0, + ); + + node.receive_reveal_bundle(valid_bundle.clone()).unwrap(); + node.drain_outbox(); + + let mut invalid_conflict = valid_bundle.clone(); + invalid_conflict.signature = "00".repeat(TEST_SIGNATURE_BYTES); + let error = node.receive_reveal_bundle(invalid_conflict).unwrap_err(); + + assert!(format!("{error:#}").contains("reveal bundle signature is invalid")); + let key = (valid_bundle.height, valid_bundle.slot); + assert_eq!(node.reveal_bundles.get(&key), Some(&valid_bundle)); + assert!(!node.equivocated_reveal_bundle_slots.contains(&key)); + assert_eq!(node.usable_reveal_bundles(), vec![valid_bundle]); + assert!(node.drain_outbox().is_empty()); + } + + #[test] fn inbound_blinded_transaction_conflicting_with_local_anchor_is_not_queued() { let alice = Wallet::from_seed("local-anchor-inbound-alice"); let bob = Wallet::from_seed("local-anchor-inbound-bob");