iuna

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

commit 326ef64ba79e47bed4ecb94e23b4017296cda3f1
parent 93e0aa65d53dc7986f99f0f2c4513c82b74d3d9a
Author: Joris Hartog <jorishartog@hotmail.com>
Date:   Fri,  7 Aug 2026 15:48:00 +0200

Prepare v0.2.31

Diffstat:
MCargo.lock | 2+-
MCargo.toml | 2+-
Msrc-tauri/Cargo.lock | 2+-
Msrc-tauri/Cargo.toml | 2+-
Msrc/adapters/http.rs | 50+++++++++++++++++++++++++++++++++++++++++++++++---
Msrc/app.rs | 6++++++
Msrc/domain.rs | 44++++++++++++++++++++++++++++++++++++++++++++
Mwww/assets/iuna-ui.js | 4++--
8 files changed, 103 insertions(+), 9 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -537,7 +537,7 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "iuna" -version = "0.2.30" +version = "0.2.31" dependencies = [ "anyhow", "axum", diff --git a/Cargo.toml b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iuna" -version = "0.2.30" +version = "0.2.31" edition = "2024" license = "Apache-2.0" diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock @@ -1511,7 +1511,7 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "iuna-desktop" -version = "0.2.30" +version = "0.2.31" dependencies = [ "tauri", "tauri-build", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iuna-desktop" -version = "0.2.30" +version = "0.2.31" edition = "2024" publish = false diff --git a/src/adapters/http.rs b/src/adapters/http.rs @@ -824,6 +824,11 @@ async fn api_mempool( let pending = node.pending_transactions(); let pending_blinded = node.pending_blinded_transactions(); let pending_reveals = node.pending_blinded_reveals(); + let pending_revealed = node + .pending_revealed_blinded_transactions() + .into_iter() + .map(|revealed| (revealed.commitment.clone(), revealed)) + .collect::<BTreeMap<_, _>>(); let outputs = known_output_index(&snapshot, &pending); let mut items = pending .iter() @@ -834,7 +839,12 @@ async fn api_mempool( .iter() .map(|transaction| ui_blinded_transaction(transaction, &outputs)), ); - items.extend(pending_reveals.iter().map(ui_blinded_reveal)); + items.extend(pending_reveals.iter().map(|reveal| { + pending_revealed + .get(&reveal.commitment) + .map(|revealed| ui_pending_revealed_transaction(revealed, &outputs)) + .unwrap_or_else(|| ui_blinded_reveal(reveal)) + })); Json(page_items(items, query)) } @@ -1900,6 +1910,15 @@ fn ui_revealed_transaction( row } +fn ui_pending_revealed_transaction( + revealed: &RevealedBlindedTransaction, + outputs_by_outpoint: &BTreeMap<OutPoint, TxOutput>, +) -> UiTransaction { + let mut row = ui_revealed_transaction(&revealed.transaction, outputs_by_outpoint); + row.commitment = Some(revealed.commitment.clone()); + row +} + fn ui_transaction( transaction: &Transaction, outputs_by_outpoint: &BTreeMap<OutPoint, TxOutput>, @@ -4046,7 +4065,7 @@ const INDEX_HTML: &str = r#"<!doctype html> <div class="tx-field"><span class="tx-label">Fee</span><span class="tx-value money" x-text="txFeeLabel(selectedTransaction?.tx)"></span></div> <div class="tx-field" x-show="!isBlindedMempoolItem(selectedTransaction?.tx)"><span class="tx-label">From</span><code class="tx-value hash" x-text="txFrom(selectedTransaction?.tx || {})"></code></div> <div class="tx-field" x-show="txTo(selectedTransaction?.tx || {})"><span class="tx-label">To</span><code class="tx-value hash" x-text="txTo(selectedTransaction?.tx || {})"></code></div> - <div class="tx-field" x-show="isBlindedMempoolItem(selectedTransaction?.tx)"><span class="tx-label">Commitment</span><code class="tx-value hash" x-text="selectedTransaction?.tx?.commitment || selectedTransaction?.tx?.signature || '-'"></code></div> + <div class="tx-field" x-show="isBlindedMempoolItem(selectedTransaction?.tx) || selectedTransaction?.tx?.commitment"><span class="tx-label">Commitment</span><code class="tx-value hash" x-text="selectedTransaction?.tx?.commitment || selectedTransaction?.tx?.signature || '-'"></code></div> <div class="tx-field" x-show="selectedTransaction?.tx?.encrypted_size || selectedTransaction?.tx?.encryptedSize"><span class="tx-label">Encrypted Bytes</span><span class="tx-value number" x-text="selectedTransaction?.tx?.encrypted_size || selectedTransaction?.tx?.encryptedSize"></span></div> <div class="tx-field" x-show="selectedTransaction?.tx?.expires_at_height || selectedTransaction?.tx?.expiresAtHeight"><span class="tx-label">Expires</span><span class="tx-value number" x-text="selectedTransaction?.tx?.expires_at_height || selectedTransaction?.tx?.expiresAtHeight"></span></div> <div class="tx-field" x-show="isMineTx(selectedTransaction?.tx)"><span class="tx-label">Difficulty</span><span class="tx-value number" x-text="txDifficultyBits(selectedTransaction?.tx) ?? '-'"></span></div> @@ -4278,6 +4297,21 @@ mod tests { let blinded_row = super::ui_blinded_transaction(&blinded, &outputs); let reveal_row = super::ui_blinded_reveal(&reveal); + let revealed_row = super::ui_pending_revealed_transaction( + &crate::domain::RevealedBlindedTransaction { + height: 2, + commitment: reveal.commitment.clone(), + included_by: owner.clone(), + transaction: Transaction::Burn { + inputs: blinded.inputs.clone(), + change: Vec::new(), + amount: 12, + fee: blinded.fee, + signature: "f".repeat(128), + }, + }, + &outputs, + ); assert_eq!(blinded_row.kind, "blinded"); assert_eq!(blinded_row.from, owner); @@ -4288,6 +4322,12 @@ mod tests { assert_eq!(blinded_row.expires_at_height, Some(42)); assert_eq!(reveal_row.kind, "reveal"); assert_eq!(reveal_row.commitment, blinded_row.commitment); + assert_eq!(revealed_row.kind, "burn"); + assert!(revealed_row.revealed); + assert_eq!(revealed_row.commitment, Some(reveal.commitment)); + assert_eq!(revealed_row.amount, 12); + assert_eq!(revealed_row.fee, 7); + assert_eq!(revealed_row.inputs[0].amount, Some(99)); } #[test] @@ -5505,8 +5545,12 @@ mod tests { fn reveal_mempool_items_show_unknown_fee_label() { let app_js = include_str!("../../www/assets/iuna-ui.js"); assert!(app_js.contains("txFeeLabel(tx)")); - assert!(app_js.contains("tx?.kind === \"reveal\"")); + assert!(app_js.contains("!tx?.revealed && tx?.kind === \"reveal\"")); assert!(app_js.contains("unknown until reveal")); + assert!( + app_js + .contains("!tx?.revealed && (tx?.kind === \"blinded\" || tx?.kind === \"reveal\")") + ); assert!(app_js.contains("mempoolFirstSeenHeights")); assert!(app_js.contains("syncMempoolBlockMarker")); assert!(app_js.contains("mempoolItemClass")); diff --git a/src/app.rs b/src/app.rs @@ -430,6 +430,12 @@ impl NodeCore { self.ledger.pending_blinded_reveals().to_vec() } + pub fn pending_revealed_blinded_transactions( + &self, + ) -> Vec<crate::domain::RevealedBlindedTransaction> { + self.ledger.pending_revealed_blinded_transactions() + } + pub fn owned_blinded_payloads(&self) -> Vec<Transaction> { self.owned_blinded_payloads.values().cloned().collect() } diff --git a/src/domain.rs b/src/domain.rs @@ -2164,6 +2164,22 @@ impl Ledger { &self.pending_reveals } + pub fn pending_revealed_blinded_transactions(&self) -> Vec<RevealedBlindedTransaction> { + self.pending_reveals + .iter() + .filter_map(|reveal| { + let active = self.active_blinded.get(&reveal.commitment)?; + let transaction = self.pending_reveal_transaction(reveal).ok()?; + Some(RevealedBlindedTransaction { + height: self.height().saturating_add(1), + commitment: reveal.commitment.clone(), + included_by: active.included_by.clone(), + transaction, + }) + }) + .collect() + } + pub(crate) fn drop_pending_blinded_conflicting_with_transaction( &mut self, transaction: &Transaction, @@ -7734,6 +7750,34 @@ mod tests { } #[test] + fn pending_blinded_reveals_expose_revealed_transaction_data() { + let alice = Wallet::from_seed("pending-reveal-data-finalizer-alice"); + let bob = Wallet::from_seed("pending-reveal-data-finalizer-bob"); + let carol = Wallet::from_seed("pending-reveal-data-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() + 6) + .unwrap(); + let commitment = blinded.transaction.commitment.clone(); + ledger + .submit_blinded_transaction(blinded.transaction) + .unwrap(); + queue_next_leader_burn(&mut ledger, &finalizers); + mine_preverified_as_next_leader(&mut ledger, &finalizers, 1); + + ledger.submit_blinded_reveal(blinded.reveal).unwrap(); + + let revealed = ledger.pending_revealed_blinded_transactions(); + assert_eq!(revealed.len(), 1); + assert_eq!(revealed[0].commitment, commitment); + assert_eq!(revealed[0].height, ledger.height() + 1); + assert_eq!(revealed[0].transaction.amount(), 3); + assert_eq!(revealed[0].transaction.fee(), 7); + assert!(revealed[0].transaction.is_burn()); + } + + #[test] fn abandoned_fork_blinded_transactions_return_to_mempool() { let alice = Wallet::from_seed("blinded-reorg-finalizer-alice"); let bob = Wallet::from_seed("blinded-reorg-finalizer-bob"); diff --git a/www/assets/iuna-ui.js b/www/assets/iuna-ui.js @@ -1618,11 +1618,11 @@ window.iunaApp = function iunaApp() { }, isBlindedMempoolItem(tx) { - return tx?.kind === "blinded" || tx?.kind === "reveal"; + return !tx?.revealed && (tx?.kind === "blinded" || tx?.kind === "reveal"); }, txFeeLabel(tx) { - if (tx?.kind === "reveal") return "unknown until reveal"; + if (!tx?.revealed && tx?.kind === "reveal") return "unknown until reveal"; return `IUNA ${this.amountLabel(tx?.fee ?? 0)}`; },