commit 4903b2414cc055dad32a8727f8f01205aed634b4
parent 6bc7084835e9319af8f8715e844611f00f9f1e9e
Author: Joris Hartog <jorishartog@hotmail.com>
Date: Tue, 4 Aug 2026 13:40:34 +0200
Release v0.2.17
Diffstat:
8 files changed, 222 insertions(+), 38 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -537,7 +537,7 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "iuna"
-version = "0.2.16"
+version = "0.2.17"
dependencies = [
"anyhow",
"axum",
diff --git a/Cargo.toml b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "iuna"
-version = "0.2.16"
+version = "0.2.17"
edition = "2024"
license = "Apache-2.0"
diff --git a/docs/protocol.md b/docs/protocol.md
@@ -56,7 +56,7 @@ Plaintext transaction fees go to the block finalizer immediately. Blinded transa
The VDF is there to make block production sequential and time-based. It cannot be parallelized in the same way as normal hashing work.
-The target block time is `10 minutes`. The protocol retargets VDF rounds from recent observed block times:
+The target block time is `5 minutes`. The protocol retargets VDF rounds from recent observed block times:
- It uses a `20` block observation window.
- It uses rank `0` ticket blocks for retargeting.
@@ -84,7 +84,7 @@ Rank slots depend on block timestamps, so timestamps are constrained by consensu
- it must not be too far in the future relative to the validating node's network-adjusted clock;
- for fallback ticket blocks, it must be at or after the finalizer rank slot.
-The future drift limit is `2 minutes`. A finalizer can lie within that small margin, but cannot skip an entire `10 minute` rank slot by claiming a far-future timestamp. P2P treats too-early future/slot blocks as temporal errors rather than peer-banning evidence.
+The future drift limit is `2 minutes`. A finalizer can lie within that small margin, but cannot skip an entire `5 minute` rank slot by claiming a far-future timestamp. P2P treats too-early future/slot blocks as temporal errors rather than peer-banning evidence.
## Recovery Blocks
diff --git a/src/adapters/http.rs b/src/adapters/http.rs
@@ -339,6 +339,7 @@ struct WalletTransactionRow {
block_height: Option<u64>,
block_finalizer: Option<String>,
direction: &'static str,
+ blinded: bool,
difficulty_bits: Option<u32>,
proof_bits: Option<u32>,
proof_hash: Option<String>,
@@ -389,6 +390,7 @@ struct UiTransaction {
commitment: Option<String>,
encrypted_size: Option<u32>,
expires_at_height: Option<u64>,
+ revealed: bool,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
@@ -780,6 +782,7 @@ async fn api_wallet_transactions(
let node = state.node.lock().await;
let snapshot = node.chain_snapshot();
let pending = node.pending_transactions();
+ let owned_blinded = node.owned_blinded_payloads();
let outputs = known_output_index(&snapshot, &pending);
let page_query = query.page();
let filters = WalletTransactionFilters::from_query(query);
@@ -787,6 +790,7 @@ async fn api_wallet_transactions(
wallet_transaction_rows(
node.wallet_address(),
pending,
+ owned_blinded,
&snapshot.blocks,
&outputs,
filters,
@@ -1538,6 +1542,7 @@ async fn setup_requires_peer(state: &HttpState) -> bool {
fn wallet_transaction_rows(
wallet: &str,
pending: Vec<Transaction>,
+ owned_blinded: Vec<Transaction>,
chain: &[Block],
outputs: &BTreeMap<OutPoint, TxOutput>,
filters: WalletTransactionFilters,
@@ -1548,11 +1553,22 @@ fn wallet_transaction_rows(
if !filters.allows(tx) {
continue;
}
- if let Some(row) = wallet_transaction_row(wallet, tx, outputs, "pending", None, None) {
+ if let Some(row) = wallet_transaction_row(wallet, tx, outputs, "pending", None, None, false)
+ {
rows.push((u128::MAX - index as u128, row));
}
}
+ for (index, tx) in owned_blinded.iter().enumerate() {
+ if !filters.allows(tx) {
+ continue;
+ }
+ if let Some(row) = wallet_transaction_row(wallet, tx, outputs, "pending", None, None, true)
+ {
+ rows.push((u128::MAX - 10_000 - index as u128, row));
+ }
+ }
+
for block in chain {
for (index, tx) in block.transactions.iter().rev().enumerate() {
if !filters.allows(tx) {
@@ -1565,6 +1581,7 @@ fn wallet_transaction_rows(
"confirmed",
Some(block.height),
Some(block.miner.clone()),
+ false,
) {
rows.push((block.height as u128 * 10_000 + index as u128, row));
}
@@ -1582,6 +1599,7 @@ fn wallet_transaction_row(
status: &'static str,
block_height: Option<u64>,
block_finalizer: Option<String>,
+ blinded: bool,
) -> Option<WalletTransactionRow> {
match tx {
Transaction::Transfer {
@@ -1607,6 +1625,7 @@ fn wallet_transaction_row(
} else {
"sent"
},
+ blinded,
difficulty_bits: None,
proof_bits: None,
proof_hash: None,
@@ -1631,6 +1650,7 @@ fn wallet_transaction_row(
block_height,
block_finalizer,
direction: "burned",
+ blinded,
difficulty_bits: None,
proof_bits: None,
proof_hash: None,
@@ -1657,6 +1677,7 @@ fn wallet_transaction_row(
block_height,
block_finalizer,
direction: "received",
+ blinded,
difficulty_bits: Some(*difficulty_bits),
proof_bits: Some(proof_bits(signature)),
proof_hash: Some(signature.clone()),
@@ -1707,6 +1728,22 @@ fn ui_block(
let revealed_fees = revealed_transactions
.iter()
.fold(0_u64, |total, tx| total.saturating_add(tx.fee()));
+ let mut transactions = block
+ .transactions
+ .iter()
+ .map(|tx| ui_transaction(tx, outputs))
+ .collect::<Vec<_>>();
+ transactions.extend(
+ block
+ .blinded_transactions
+ .iter()
+ .map(ui_blinded_transaction),
+ );
+ transactions.extend(
+ revealed_transactions
+ .iter()
+ .map(|tx| ui_revealed_transaction(tx, outputs)),
+ );
UiBlock {
height: block.height,
prev_hash: block.prev_hash,
@@ -1720,19 +1757,24 @@ fn ui_block(
vdf_output: block.vdf_output,
leader_proof: block.leader_proof,
burn_leader_ranks: ranks,
- transactions: block
- .transactions
- .iter()
- .map(|tx| ui_transaction(tx, outputs))
- .collect(),
+ transactions,
revealed_transactions: revealed_transactions
.iter()
- .map(|tx| ui_transaction(tx, outputs))
+ .map(|tx| ui_revealed_transaction(tx, outputs))
.collect(),
hash: block.hash,
}
}
+fn ui_revealed_transaction(
+ transaction: &Transaction,
+ outputs_by_outpoint: &BTreeMap<OutPoint, TxOutput>,
+) -> UiTransaction {
+ let mut row = ui_transaction(transaction, outputs_by_outpoint);
+ row.revealed = true;
+ row
+}
+
fn ui_transaction(
transaction: &Transaction,
outputs_by_outpoint: &BTreeMap<OutPoint, TxOutput>,
@@ -1759,6 +1801,7 @@ fn ui_transaction(
commitment: None,
encrypted_size: None,
expires_at_height: None,
+ revealed: false,
},
Transaction::Burn {
inputs,
@@ -1782,6 +1825,7 @@ fn ui_transaction(
commitment: None,
encrypted_size: None,
expires_at_height: None,
+ revealed: false,
},
Transaction::Mine {
recipient,
@@ -1807,6 +1851,7 @@ fn ui_transaction(
commitment: None,
encrypted_size: None,
expires_at_height: None,
+ revealed: false,
},
}
}
@@ -1828,6 +1873,7 @@ fn ui_blinded_transaction(transaction: &BlindedTransaction) -> UiTransaction {
commitment: Some(transaction.commitment.clone()),
encrypted_size: Some(transaction.encrypted_size),
expires_at_height: Some(transaction.expires_at_height),
+ revealed: false,
}
}
@@ -1848,6 +1894,7 @@ fn ui_blinded_reveal(reveal: &BlindedReveal) -> UiTransaction {
commitment: Some(reveal.commitment.clone()),
encrypted_size: None,
expires_at_height: None,
+ revealed: false,
}
}
@@ -2896,6 +2943,8 @@ const INDEX_HTML: &str = r#"<!doctype html>
.pill.burn { background: #332918; color: #ffd070; }
.pill.transfer { background: #17312a; color: #8de9cd; }
.pill.mine { background: #172a34; color: #8bdcff; }
+ .pill.blinded { background: #272433; color: #c8b8ff; }
+ .pill.reveal, .pill.revealed { background: #2b2f20; color: #d5f55f; }
.mempool-panel { min-width: 0; overflow: hidden; }
.mempool-strip { width: 100%; min-width: 0; display: flex; gap: 8px; overflow-x: auto; overscroll-behavior-x: contain; padding: 1px 0 10px; scroll-snap-type: x proximity; }
.mempool-item { flex: 0 0 220px; scroll-snap-align: start; }
@@ -2944,7 +2993,7 @@ const INDEX_HTML: &str = r#"<!doctype html>
.block-card { flex-basis: 108px; }
}
</style>
- <script defer src="/assets/iuna-ui.js?v=80"></script>
+ <script defer src="/assets/iuna-ui.js?v=82"></script>
<script defer src="/assets/alpine.min.js"></script>
</head>
<body x-data="iunaApp()" x-init="init()" @keydown.window.escape="closeModals()" x-cloak>
@@ -3363,32 +3412,20 @@ const INDEX_HTML: &str = r#"<!doctype html>
<h3>Transactions</h3>
<template x-for="tx in selectedBlock.transactions" :key="tx.signature">
<div class="tx-card" role="button" tabindex="0" @click="openTransactionModal(tx, { source: 'Block', blockHeight: selectedBlock.height, blockFinalizer: selectedBlock.miner })" @keydown.enter.prevent="openTransactionModal(tx, { source: 'Block', blockHeight: selectedBlock.height, blockFinalizer: selectedBlock.miner })" @keydown.space.prevent="openTransactionModal(tx, { source: 'Block', blockHeight: selectedBlock.height, blockFinalizer: selectedBlock.miner })">
- <span class="pill" :class="tx.kind" x-text="tx.kind"></span>
- <div class="tx-field"><span class="tx-label">Amount</span><span class="tx-value money">IUNA <span x-text="amountLabel(txAmount(tx))"></span></span></div>
+ <span class="pill" :class="txPillClass(tx)" x-text="txPillLabel(tx)"></span>
+ <div class="tx-field" x-show="!isBlindedMempoolItem(tx)"><span class="tx-label">Amount</span><span class="tx-value money">IUNA <span x-text="amountLabel(txAmount(tx))"></span></span></div>
<div class="tx-field"><span class="tx-label">Fee</span><span class="tx-value money">IUNA <span x-text="amountLabel(tx.fee ?? 0)"></span></span></div>
- <div class="tx-field"><span class="tx-label">From</span><code class="tx-value hash" x-text="short(txFrom(tx))"></code></div>
+ <div class="tx-field" x-show="!isBlindedMempoolItem(tx)"><span class="tx-label">From</span><code class="tx-value hash" x-text="short(txFrom(tx))"></code></div>
<div class="tx-field" x-show="txTo(tx)"><span class="tx-label">To</span><code class="tx-value hash" x-text="short(txTo(tx))"></code></div>
+ <div class="tx-field" x-show="isBlindedMempoolItem(tx)"><span class="tx-label">Commitment</span><code class="tx-value hash" x-text="short(tx.commitment || tx.signature)"></code></div>
+ <div class="tx-field" x-show="tx.encrypted_size || tx.encryptedSize"><span class="tx-label">Bytes</span><span class="tx-value number" x-text="tx.encrypted_size || tx.encryptedSize"></span></div>
+ <div class="tx-field" x-show="tx.expires_at_height || tx.expiresAtHeight"><span class="tx-label">Expires</span><span class="tx-value number" x-text="tx.expires_at_height || tx.expiresAtHeight"></span></div>
<div class="tx-field" x-show="isMineTx(tx)"><span class="tx-label">Proof Bits</span><span class="tx-value number"><span x-text="txProofBits(tx) ?? '-'"></span> / <span x-text="txDifficultyBits(tx) ?? '-'"></span></span></div>
<div class="tx-field" x-show="isMineTx(tx)"><span class="tx-label">Proof Hash</span><code class="tx-value hash" x-text="short(txProofHash(tx))"></code></div>
- <div class="tx-field"><span class="tx-label">Signature</span><code class="tx-value hash" x-text="short(tx.signature)"></code></div>
+ <div class="tx-field" x-show="!isBlindedMempoolItem(tx)"><span class="tx-label">Signature</span><code class="tx-value hash" x-text="short(tx.signature)"></code></div>
</div>
</template>
<div class="muted" x-show="selectedBlock.transactions.length === 0">No transactions</div>
- <template x-if="selectedBlock.revealed_transactions?.length">
- <div class="tx-list">
- <h3>Revealed</h3>
- <template x-for="tx in selectedBlock.revealed_transactions" :key="tx.signature">
- <div class="tx-card" role="button" tabindex="0" @click="openTransactionModal(tx, { source: 'Revealed', blockHeight: selectedBlock.height, blockFinalizer: selectedBlock.miner })" @keydown.enter.prevent="openTransactionModal(tx, { source: 'Revealed', blockHeight: selectedBlock.height, blockFinalizer: selectedBlock.miner })" @keydown.space.prevent="openTransactionModal(tx, { source: 'Revealed', blockHeight: selectedBlock.height, blockFinalizer: selectedBlock.miner })">
- <span class="pill" :class="tx.kind" x-text="tx.kind"></span>
- <div class="tx-field"><span class="tx-label">Amount</span><span class="tx-value money">IUNA <span x-text="amountLabel(txAmount(tx))"></span></span></div>
- <div class="tx-field"><span class="tx-label">Fee</span><span class="tx-value money">IUNA <span x-text="amountLabel(tx.fee ?? 0)"></span></span></div>
- <div class="tx-field"><span class="tx-label">From</span><code class="tx-value hash" x-text="short(txFrom(tx))"></code></div>
- <div class="tx-field" x-show="txTo(tx)"><span class="tx-label">To</span><code class="tx-value hash" x-text="short(txTo(tx))"></code></div>
- <div class="tx-field"><span class="tx-label">Signature</span><code class="tx-value hash" x-text="short(tx.signature)"></code></div>
- </div>
- </template>
- </div>
- </template>
</div>
</div>
</template>
@@ -3644,7 +3681,7 @@ const INDEX_HTML: &str = r#"<!doctype html>
<section class="tx-modal">
<div class="tx-modal-head">
<div class="tx-modal-title">
- <span class="pill" :class="selectedTransaction?.tx?.kind" x-text="selectedTransaction?.tx?.kind"></span>
+ <span class="pill" :class="txPillClass(selectedTransaction?.tx)" x-text="txPillLabel(selectedTransaction?.tx)"></span>
<h2 id="tx-modal-title">Transaction</h2>
<code class="tx-value hash" x-text="selectedTransaction?.tx?.signature || '-'"></code>
</div>
@@ -3880,6 +3917,91 @@ mod tests {
}
#[test]
+ fn block_detail_transactions_include_blinded_and_revealed_items() {
+ let alice = Wallet::from_seed("block-detail-blind-alice");
+ let bob = Wallet::from_seed("block-detail-blind-bob");
+ let mut allocations = BTreeMap::new();
+ allocations.insert(alice.address().to_string(), 100);
+ allocations.insert(bob.address().to_string(), 100);
+ let ledger = Ledger::new(allocations.clone(), 1);
+ let transfer = ledger.build_transfer(&alice, bob.address(), 12, 3).unwrap();
+ let built = ledger
+ .build_blinded_transaction(transfer.clone(), 20)
+ .unwrap();
+ let mut block = fake_block(7, Vec::new());
+ block.blinded_transactions = vec![built.transaction.clone()];
+
+ let ui_block = super::ui_block(block, &BTreeMap::new(), &BTreeMap::new(), &[transfer]);
+
+ assert_eq!(ui_block.transactions.len(), 2);
+ assert_eq!(ui_block.transactions[0].kind, "blinded");
+ assert_eq!(
+ ui_block.transactions[0].commitment.as_deref(),
+ Some(built.transaction.commitment.as_str())
+ );
+ assert!(!ui_block.transactions[0].revealed);
+ assert_eq!(ui_block.transactions[1].kind, "transfer");
+ assert!(ui_block.transactions[1].revealed);
+ assert_eq!(ui_block.revealed_transactions.len(), 1);
+ assert!(ui_block.revealed_transactions[0].revealed);
+ }
+
+ #[test]
+ fn block_detail_reconstructs_revealed_items_from_snapshot_blocks() {
+ let alice = Wallet::from_seed("block-detail-reveal-alice");
+ let bob = Wallet::from_seed("block-detail-reveal-bob");
+ let mut allocations = BTreeMap::new();
+ allocations.insert(alice.address().to_string(), 100);
+ allocations.insert(bob.address().to_string(), 100);
+ let ledger = Ledger::new(allocations.clone(), 1);
+ let transfer = ledger.build_transfer(&alice, bob.address(), 12, 3).unwrap();
+ let built = ledger
+ .build_blinded_transaction(transfer.clone(), 20)
+ .unwrap();
+ let mut commit_block = fake_block(7, Vec::new());
+ commit_block.blinded_transactions = vec![built.transaction.clone()];
+ let mut reveal_block = fake_block(8, Vec::new());
+ reveal_block.blinded_reveals = vec![built.reveal.clone()];
+ let snapshot = fake_snapshot(
+ allocations,
+ vec![commit_block.clone(), reveal_block.clone()],
+ );
+
+ let blocks = super::ui_blocks(
+ vec![commit_block, reveal_block],
+ &snapshot,
+ &[],
+ &BTreeMap::new(),
+ );
+
+ assert_eq!(blocks[0].transactions.len(), 1);
+ assert_eq!(blocks[0].transactions[0].kind, "blinded");
+ assert_eq!(
+ blocks[0].transactions[0].commitment.as_deref(),
+ Some(built.transaction.commitment.as_str())
+ );
+ assert_eq!(blocks[1].transactions.len(), 1);
+ assert_eq!(blocks[1].transactions[0].kind, "transfer");
+ assert!(blocks[1].transactions[0].revealed);
+ assert_eq!(blocks[1].transactions[0].amount, transfer.amount());
+ assert_eq!(blocks[1].transactions[0].to.as_deref(), Some(bob.address()));
+ assert_eq!(blocks[1].revealed_transactions.len(), 1);
+ }
+
+ #[test]
+ fn block_detail_markup_uses_blinded_and_revealed_labels() {
+ let app_js = include_str!("../../www/assets/iuna-ui.js");
+
+ assert!(super::INDEX_HTML.contains("txPillLabel(tx)"));
+ assert!(super::INDEX_HTML.contains(".pill.blinded"));
+ assert!(super::INDEX_HTML.contains(".pill.reveal, .pill.revealed"));
+ assert!(super::INDEX_HTML.contains("Commitment"));
+ assert!(!super::INDEX_HTML.contains("<h3>Revealed</h3>"));
+ assert!(app_js.contains("tx?.revealed ? \"revealed\""));
+ assert!(app_js.contains("transactions.some((tx) => tx?.revealed)"));
+ }
+
+ #[test]
fn password_policy_rejects_short_or_excessive_passwords() {
let short = validate_password("too-short").unwrap_err();
assert!(short.to_string().contains("at least 12"));
@@ -4548,6 +4670,7 @@ mod tests {
let rows = wallet_transaction_rows(
alice.address(),
vec![pending_burn.clone()],
+ Vec::new(),
&chain,
&outputs,
WalletTransactionFilters::default(),
@@ -4564,6 +4687,38 @@ mod tests {
}
#[test]
+ fn wallet_transactions_show_owned_blinded_payloads_as_pending_blind() {
+ let alice = Wallet::from_seed("wallet-blind-pending-alice");
+ let bob = Wallet::from_seed("wallet-blind-pending-bob");
+ let mut allocations = BTreeMap::new();
+ allocations.insert(alice.address().to_string(), 100);
+ allocations.insert(bob.address().to_string(), 100);
+ let ledger = Ledger::new(allocations.clone(), 1);
+ let pending_blind = ledger.build_transfer(&alice, bob.address(), 12, 3).unwrap();
+ let snapshot = fake_snapshot(allocations, Vec::new());
+ let outputs = super::known_output_index(&snapshot, &[]);
+
+ let rows = wallet_transaction_rows(
+ alice.address(),
+ Vec::new(),
+ vec![pending_blind.clone()],
+ &[],
+ &outputs,
+ WalletTransactionFilters::default(),
+ );
+
+ assert_eq!(rows.len(), 1);
+ assert_eq!(rows[0].kind, "transfer");
+ assert_eq!(rows[0].status, "pending");
+ assert!(rows[0].blinded);
+ assert_eq!(rows[0].direction, "sent");
+ assert_eq!(rows[0].to.as_deref(), Some(bob.address()));
+ assert_eq!(rows[0].amount, 12);
+ assert_eq!(rows[0].fee, 3);
+ assert_eq!(rows[0].signature, pending_blind.signature());
+ }
+
+ #[test]
fn wallet_transaction_query_defaults_to_tx_only() {
assert_eq!(
WalletTransactionFilters::from_query(WalletTransactionsQuery::default()),
@@ -4633,6 +4788,7 @@ mod tests {
let rows = wallet_transaction_rows(
alice.address(),
Vec::new(),
+ Vec::new(),
&chain,
&outputs,
WalletTransactionFilters {
@@ -4663,6 +4819,7 @@ mod tests {
let default_rows = wallet_transaction_rows(
alice.address(),
vec![burn.clone()],
+ Vec::new(),
&[],
&outputs,
WalletTransactionFilters::default(),
@@ -4670,6 +4827,7 @@ mod tests {
let burn_rows = wallet_transaction_rows(
alice.address(),
vec![burn.clone()],
+ Vec::new(),
&[],
&outputs,
WalletTransactionFilters {
@@ -4829,7 +4987,7 @@ mod tests {
#[test]
fn metrics_screen_includes_block_range_filter() {
- assert!(super::INDEX_HTML.contains("iuna-ui.js?v=80"));
+ assert!(super::INDEX_HTML.contains("iuna-ui.js?v=82"));
assert!(super::INDEX_HTML.contains("aria-label=\"Metrics block range\""));
assert!(super::INDEX_HTML.contains("setMetricsRange(100)"));
assert!(super::INDEX_HTML.contains("setMetricsRange(1000)"));
@@ -4873,6 +5031,13 @@ mod tests {
}
#[test]
+ fn wallet_pending_blinded_transactions_are_labeled() {
+ let app_js = include_str!("../../www/assets/iuna-ui.js");
+ assert!(app_js.contains("Pending blind"));
+ assert!(app_js.contains("tx.blinded"));
+ }
+
+ #[test]
fn mine_screen_shows_fixed_pow_reward_without_burn_slider() {
let app_js = include_str!("../../www/assets/iuna-ui.js");
assert!(app_js.contains("powMineReward()"));
diff --git a/src/app.rs b/src/app.rs
@@ -403,6 +403,10 @@ impl NodeCore {
self.ledger.pending_blinded_reveals().to_vec()
}
+ pub fn owned_blinded_payloads(&self) -> Vec<Transaction> {
+ self.owned_blinded_payloads.values().cloned().collect()
+ }
+
pub fn mempool_gossip(&self) -> Vec<GossipEnvelope> {
let mut gossip = Vec::new();
gossip.extend(
diff --git a/src/domain.rs b/src/domain.rs
@@ -22,7 +22,7 @@ pub const DEFAULT_MINE_FEE: Amount = MINE_FINALIZER_FEE;
pub const DEFAULT_TRANSACTION_FEE: Amount = MICRO_IUNA;
pub const DEFAULT_FEE_PER_BYTE: Amount = 1;
pub const MAX_BLOCK_BYTES: usize = 100_000;
-pub const VDF_TARGET_BLOCK_MS: u64 = 10 * 60 * 1_000;
+pub const VDF_TARGET_BLOCK_MS: u64 = 5 * 60 * 1_000;
pub const RECOVERY_BLOCK_DELAY_MS: u64 = VDF_TARGET_BLOCK_MS * 6;
pub const MAX_VDF_ROUNDS: u64 = i64::MAX as u64;
pub const MINE_DIFFICULTY_BITS: u32 = 12;
@@ -4612,6 +4612,11 @@ fn hex_encode(bytes: impl AsRef<[u8]>) -> String {
mod tests {
use super::*;
+ #[test]
+ fn target_block_time_is_five_minutes() {
+ assert_eq!(VDF_TARGET_BLOCK_MS, 5 * 60 * 1_000);
+ }
+
fn test_utxo_outpoint(index: usize) -> OutPoint {
OutPoint {
txid: format!("{index:064x}"),
diff --git a/src/main.rs b/src/main.rs
@@ -1183,7 +1183,7 @@ mod tests {
Duration::from_secs(1),
Duration::from_millis(VDF_TARGET_BLOCK_MS),
),
- 6_000_000
+ 3_000_000
);
assert_eq!(
extrapolate_vdf_rounds(
@@ -1191,7 +1191,7 @@ mod tests {
Duration::from_secs(0),
Duration::from_millis(VDF_TARGET_BLOCK_MS),
),
- 6_000_000_000_000_000
+ 3_000_000_000_000_000
);
}
diff --git a/www/assets/iuna-ui.js b/www/assets/iuna-ui.js
@@ -1552,6 +1552,14 @@ window.iunaApp = function iunaApp() {
return tx?.kind === "blinded" || tx?.kind === "reveal";
},
+ txPillLabel(tx) {
+ return tx?.revealed ? "revealed" : (tx?.kind || "-");
+ },
+
+ txPillClass(tx) {
+ return tx?.revealed ? "revealed" : (tx?.kind || "");
+ },
+
txDifficultyBits(tx) {
return tx?.difficulty_bits ?? tx?.difficultyBits ?? null;
},
@@ -1730,8 +1738,10 @@ window.iunaApp = function iunaApp() {
},
blockTransactions(block) {
+ const transactions = block?.transactions || [];
+ if (transactions.some((tx) => tx?.revealed)) return transactions;
return [
- ...(block?.transactions || []),
+ ...transactions,
...(block?.revealedTransactions || block?.revealed_transactions || []),
];
},
@@ -1783,7 +1793,7 @@ window.iunaApp = function iunaApp() {
},
txTitle(tx) {
- if (tx.status === "pending") return "Pending";
+ if (tx.status === "pending") return tx.blinded ? "Pending blind" : "Pending";
return tx.blockHeight === null ? "Confirmed" : `Block ${tx.blockHeight}`;
},