commit 04a13b097eb8f7cf39df34b5e52743b4768ab324
parent 1bb3f739123f3f1c0b38230fc029e2a6371f4212
Author: Joris Hartog <jorishartog@hotmail.com>
Date: Wed, 5 Aug 2026 23:39:31 +0200
Prepare v0.2.25
Diffstat:
4 files changed, 60 insertions(+), 12 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -537,7 +537,7 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "iuna"
-version = "0.2.24"
+version = "0.2.25"
dependencies = [
"anyhow",
"axum",
diff --git a/Cargo.toml b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "iuna"
-version = "0.2.24"
+version = "0.2.25"
edition = "2024"
license = "Apache-2.0"
diff --git a/src/adapters/http.rs b/src/adapters/http.rs
@@ -3252,6 +3252,8 @@ const INDEX_HTML: &str = r#"<!doctype html>
.tx-section { display: grid; gap: 8px; }
.tx-section + .tx-section { margin-top: 10px; padding-top: 10px; border-top: 1px solid #2f363c; }
.tx-section-title { display: flex; align-items: center; justify-content: space-between; gap: 8px; color: #f4f7f8; font-size: 13px; font-weight: 800; }
+ summary.tx-section-title { cursor: pointer; }
+ details.tx-section:not([open]) { gap: 0; }
.tx-section-meta { color: #8e979e; font-size: 12px; font-weight: 600; }
.tx-card, .mempool-item { position: relative; display: grid; gap: 6px; border: 1px solid #2f363c; border-radius: 8px; padding: 12px; background: #111316; cursor: pointer; text-align: left; }
.tx-card .pill, .mempool-item .pill { position: absolute; top: 10px; right: 10px; }
@@ -3747,8 +3749,8 @@ const INDEX_HTML: &str = r#"<!doctype html>
<div class="muted" x-show="selectedBlock.transactions.length === 0">No envelope transactions</div>
</div>
<template x-for="bundle in selectedBlock.reveal_bundles || selectedBlock.revealBundles || []" :key="bundle.hash">
- <div class="tx-section">
- <div class="tx-section-title"><span x-text="`Reveal bundle ${bundle.slot}`"></span><span class="tx-section-meta"><span x-text="short(bundle.member)"></span> · <span x-text="bundle.byte_size || bundle.byteSize || 0"></span>B</span></div>
+ <details class="tx-section">
+ <summary class="tx-section-title"><span x-text="`Reveal bundle ${bundle.slot}`"></span><span class="tx-section-meta"><span x-text="short(bundle.member)"></span> · <span x-text="bundle.byte_size || bundle.byteSize || 0"></span>B</span></summary>
<template x-for="tx in bundle.reveals" :key="tx.signature">
<div class="tx-card" role="button" tabindex="0" @click="openTransactionModal(tx, { source: 'Reveal bundle', blockHeight: selectedBlock.height, blockFinalizer: bundle.member })" @keydown.enter.prevent="openTransactionModal(tx, { source: 'Reveal bundle', blockHeight: selectedBlock.height, blockFinalizer: bundle.member })" @keydown.space.prevent="openTransactionModal(tx, { source: 'Reveal bundle', blockHeight: selectedBlock.height, blockFinalizer: bundle.member })">
<span class="pill" :class="txPillClass(tx)" x-text="txPillLabel(tx)"></span>
@@ -3761,7 +3763,7 @@ const INDEX_HTML: &str = r#"<!doctype html>
</div>
</template>
<div class="muted" x-show="bundle.reveals.length === 0">No reveals in bundle</div>
- </div>
+ </details>
</template>
<div class="muted" x-show="selectedBlock.transactions.length === 0 && !(selectedBlock.reveal_bundles || selectedBlock.revealBundles || []).length">No transactions</div>
</div>
@@ -4371,6 +4373,8 @@ mod tests {
assert!(super::INDEX_HTML.contains("commitCountLabel(block)"));
assert!(super::INDEX_HTML.contains(".pill.blinded"));
assert!(super::INDEX_HTML.contains(".pill.reveal, .pill.revealed"));
+ assert!(super::INDEX_HTML.contains("<details class=\"tx-section\">"));
+ assert!(super::INDEX_HTML.contains("<summary class=\"tx-section-title\">"));
assert!(super::INDEX_HTML.contains("Commitment"));
assert!(!super::INDEX_HTML.contains("<h3>Revealed</h3>"));
assert!(app_js.contains("tx?.revealed ? \"revealed\""));
diff --git a/src/app.rs b/src/app.rs
@@ -474,14 +474,17 @@ impl NodeCore {
{
continue;
}
- if !self.ledger.has_blinded_transaction(&commitment)
- && self
+ if !self.ledger.has_blinded_transaction(&commitment) {
+ match self
.ledger
- .submit_blinded_transaction(owned.transaction.clone())?
- {
- self.outbox.push(GossipEnvelope::BlindedTransaction(
- owned.transaction.clone(),
- ));
+ .submit_blinded_transaction(owned.transaction.clone())
+ {
+ Ok(true) => self.outbox.push(GossipEnvelope::BlindedTransaction(
+ owned.transaction.clone(),
+ )),
+ Ok(false) => {}
+ Err(_) => continue,
+ }
}
if !self.ledger.has_unrevealed_blinded_transaction(&commitment) {
continue;
@@ -2852,6 +2855,47 @@ mod tests {
}
#[test]
+ fn owned_blinded_transaction_restore_skips_stale_commit_with_spent_input() {
+ let alice = Wallet::from_seed("owned-blinded-restore-stale-alice");
+ let bob = Wallet::from_seed("owned-blinded-restore-stale-bob");
+ let finalizers = [bob.clone()];
+ let mut allocations = BTreeMap::new();
+ allocations.insert(alice.address().to_string(), MICRO_IUNA);
+ allocations.insert(bob.address().to_string(), MICRO_IUNA);
+ let ledger = Ledger::new_with_genesis_burns(
+ allocations,
+ finalizers
+ .iter()
+ .map(|wallet| GenesisBurn::new(wallet.address(), MICRO_IUNA))
+ .collect(),
+ 1,
+ )
+ .unwrap();
+ let mut wallet_node = NodeCore::from_ledger(alice.clone(), ledger.clone(), 0);
+
+ wallet_node
+ .blinded_burn_with_fee(MICRO_IUNA / 10, 7, wallet_node.chain_height() + 4)
+ .unwrap();
+ let owned = wallet_node.owned_blinded_transactions();
+ let stale_conflict = ledger.build_burn(&alice, MICRO_IUNA / 10, 7).unwrap();
+ let mut advanced_ledger = ledger;
+ advanced_ledger.submit_transaction(stale_conflict).unwrap();
+ let leader = advanced_ledger.expected_leader_for_next_block().unwrap();
+ assert_eq!(leader, bob.address());
+ let anchor_burn = advanced_ledger.build_burn(&bob, 1, 0).unwrap();
+ advanced_ledger.submit_transaction(anchor_burn).unwrap();
+ let block = advanced_ledger.mine_next_block(&bob, 1).unwrap();
+ advanced_ledger.apply_block(block).unwrap();
+ let mut restarted = NodeCore::from_ledger(alice, advanced_ledger, 0);
+
+ restarted.restore_owned_blinded_transactions(owned).unwrap();
+
+ assert!(restarted.owned_blinded_transactions().is_empty());
+ assert!(restarted.ledger().pending_blinded_transactions().is_empty());
+ assert!(restarted.drain_outbox().is_empty());
+ }
+
+ #[test]
fn owned_blinded_transaction_restore_publishes_reveal_after_commit() {
let alice = Wallet::from_seed("owned-blinded-restore-reveal-alice");
let bob = Wallet::from_seed("owned-blinded-restore-reveal-bob");