iuna

iuna

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

commit 388dc361137e4e46d39e8ceab2199860852ad24a
parent b6dfdeebb3a318e423a6e2092cef2ae4cb8a250e
Author: Joris Hartog <jorishartog@hotmail.com>
Date:   Fri, 14 Aug 2026 14:38:27 +0200

Add adversarial reveal and p2p regression tests

Diffstat:
Msrc/adapters/p2p/line_codec.rs | 15++++++++++++++-
Msrc/adapters/p2p/tests.rs | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/domain/ledger_mempool.rs | 17++++++++++++++++-
Msrc/domain/tests.rs | 43+++++++++++++++++++++++++++++++++++++++++++
4 files changed, 125 insertions(+), 2 deletions(-)

diff --git a/src/adapters/p2p/line_codec.rs b/src/adapters/p2p/line_codec.rs @@ -257,7 +257,10 @@ mod tests { use tokio::io::AsyncWriteExt; use crate::{ - adapters::p2p::{MAX_INVENTORY_ITEMS, MAX_OBJECT_REQUESTS, metrics::P2pMetricsCounters}, + adapters::p2p::{ + MAX_GOSSIP_LINE_BYTES, MAX_INVENTORY_ITEMS, MAX_OBJECT_REQUESTS, + metrics::P2pMetricsCounters, + }, app::{BlockInventory, GossipEnvelope}, domain::{BlindedReveal, BlindedTransaction}, }; @@ -398,4 +401,14 @@ mod tests { Some(line.as_str()) ); } + + #[tokio::test] + async fn limited_line_reader_rejects_oversized_partial_frame_without_newline() { + let bytes = vec![b'a'; MAX_GOSSIP_LINE_BYTES + 1]; + let mut reader = LimitedLineReader::new(bytes.as_slice()); + + let error = reader.read_line().await.unwrap_err(); + + assert!(error.to_string().contains("p2p message exceeds")); + } } diff --git a/src/adapters/p2p/tests.rs b/src/adapters/p2p/tests.rs @@ -136,6 +136,58 @@ async fn single_block_fork_error_requests_chain_snapshot() { } #[tokio::test] +async fn chain_snapshot_request_only_writes_snapshot_without_mutating_local_state() { + let alice = Wallet::from_seed("snapshot-request-spam-alice"); + let allocations = allocations(std::slice::from_ref(&alice), 1_000); + let mut local_node = node("snapshot-request-spam", alice.clone(), allocations); + queue_plaintext_burn(&mut local_node, &alice, 1); + local_node.drain_outbox(); + local_node.mine_one_at(1).unwrap(); + local_node.drain_outbox(); + let before = local_node.chain_snapshot(); + + let peers = Arc::new(tokio::sync::Mutex::new(PeerBook::default())); + let network = gossip_network( + Arc::new(tokio::sync::Mutex::new(local_node)), + Arc::clone(&peers), + "127.0.0.1:9544".parse().unwrap(), + None, + ); + let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); + let client = tokio::net::TcpStream::connect(listener.local_addr().unwrap()) + .await + .unwrap(); + let (server, remote_addr) = listener.accept().await.unwrap(); + let (_server_reader, mut server_writer) = server.into_split(); + let (client_reader, _client_writer) = client.into_split(); + let mut client_reader = super::LimitedLineReader::new(client_reader); + let mut known_peer = None; + + super::process_envelope( + &network, + &mut server_writer, + remote_addr, + &mut known_peer, + GossipEnvelope::ChainSnapshotRequest, + ) + .await + .unwrap(); + + let line = tokio::time::timeout(std::time::Duration::from_secs(1), client_reader.read_line()) + .await + .unwrap() + .unwrap() + .unwrap(); + assert_eq!( + super::parse_envelope(&line).unwrap(), + GossipEnvelope::ChainSnapshot(before.clone()) + ); + assert_eq!(network.inner.node.lock().await.chain_snapshot(), before); + assert!(peers.lock().await.list().is_empty()); + assert!(known_peer.is_none()); +} + +#[tokio::test] async fn hello_rejects_wrong_network_or_genesis_without_banning() { let alice = Wallet::from_seed("hello-alice"); let allocations = allocations(std::slice::from_ref(&alice), 1_000); diff --git a/src/domain/ledger_mempool.rs b/src/domain/ledger_mempool.rs @@ -53,13 +53,28 @@ impl Ledger { return Ok(false); } self.validate_blinded_reveal_terms(&reveal)?; - if self.pending_reveals.len() >= MAX_PENDING_TRANSACTIONS { + if self.pending_reveals.len() >= MAX_PENDING_TRANSACTIONS + && (!self.has_active_blinded_transaction(&reveal.commitment) + || !self.drop_one_invalid_pending_blinded_reveal()) + { bail!("blinded reveal pool is full"); } self.pending_reveals.push(reveal); Ok(true) } + fn drop_one_invalid_pending_blinded_reveal(&mut self) -> bool { + let Some(index) = self + .pending_reveals + .iter() + .position(|reveal| self.pending_reveal_transaction(reveal).is_err()) + else { + return false; + }; + self.pending_reveals.remove(index); + true + } + pub fn submit_transaction_with_outcome( &mut self, transaction: Transaction, diff --git a/src/domain/tests.rs b/src/domain/tests.rs @@ -2264,6 +2264,49 @@ fn expired_blinded_reveal_is_not_selected() { } #[test] +fn active_blinded_reveal_displaces_invalid_reveal_spam_when_pool_is_full() { + let alice = Wallet::from_seed("blinded-spam-finalizer-alice"); + let bob = Wallet::from_seed("blinded-spam-finalizer-bob"); + let carol = Wallet::from_seed("blinded-spam-carol"); + let finalizers = [alice.clone(), bob.clone()]; + let mut ledger = ledger_with_finalizers(&finalizers, &[(&carol, 10 * MICRO_IUNA)]); + let blinded = ledger + .build_blinded_burn(&carol, 3, 7, ledger.height() + 4) + .unwrap(); + ledger + .submit_blinded_transaction(blinded.transaction.clone()) + .unwrap(); + queue_next_leader_burn(&mut ledger, &finalizers); + mine_preverified_as_next_leader(&mut ledger, &finalizers, 1); + + for index in 0..MAX_PENDING_TRANSACTIONS { + ledger + .submit_blinded_reveal(BlindedReveal { + commitment: hex_hash(format!("unknown-blinded-reveal-spam:{index}")), + key: "00".repeat(BLINDED_KEY_BYTES), + }) + .unwrap(); + } + assert_eq!( + ledger.pending_blinded_reveals().len(), + MAX_PENDING_TRANSACTIONS + ); + assert!(ledger.valid_pending_blinded_reveals().is_empty()); + + assert!( + ledger + .submit_blinded_reveal(blinded.reveal.clone()) + .unwrap() + ); + + assert_eq!( + ledger.pending_blinded_reveals().len(), + MAX_PENDING_TRANSACTIONS + ); + assert_eq!(ledger.valid_pending_blinded_reveals(), vec![blinded.reveal]); +} + +#[test] fn recovery_block_includes_pending_blinded_transactions_when_space_allows() { let alice = Wallet::from_seed("recovery-blinded-commit-alice"); let bob = Wallet::from_seed("recovery-blinded-commit-bob");