commit c9031f56d415327e380d8eca0b77be590e801b51
parent a38690a7a3176f64a9992faa672d3ac2acd30479
Author: Joris Hartog <jorishartog@hotmail.com>
Date: Mon, 3 Aug 2026 20:25:32 +0200
Remove burn claims
Diffstat:
9 files changed, 68 insertions(+), 1610 deletions(-)
diff --git a/docs/protocol.md b/docs/protocol.md
@@ -43,7 +43,7 @@ For each block height, eligible tickets are ranked:
The selected finalizer must prove ownership of the selected ticket, respect its rank time slot, and run the required VDF work. A block is valid only if the finalizer matches its ranked ticket, carries the correct leader proof, has a valid timestamp for its rank, includes a valid VDF output, and follows the transaction selection rules.
-Every normal block must include at least one burn transaction. Transaction fees normally go to the block finalizer, except claimed burn fees can be split between the finalizer and claim attesters.
+Every normal block must include at least one burn transaction. Transaction fees go to the block finalizer.
## VDF Timing
@@ -112,40 +112,17 @@ This keeps issuance separate from finalization. PoW miners compete to create min
The central censorship risk is simple: what if a finalizer only includes its own burns and ignores everyone else's burns?
-iuna handles this with burn claims.
+The current devnet protocol does not yet have a consensus-level burn inclusion fairness mechanism. Nodes gossip burn transactions through the normal mempool, blocks must include at least one burn, and finalizers earn the fees of the transactions they include. That gives finalizers a direct economic reason to include third-party burns, but it does not make censorship impossible.
-When recent finalizers see a valid burn in the mempool, they can sign a `BurnSeen` attestation. A burner can package the burn plus enough recent-finalizer attestations into a `BurnClaim`.
-
-A burn claim is valid only when:
-
-- it references a valid burn transaction;
-- the burn is still unconfirmed;
-- the burn can be applied to the current UTXO set;
-- the attestations are from recent finalizers;
-- the attestations match the finalizer's recent block height and hash;
-- enough unique recent finalizers signed it.
-
-The current quorum target is `3` recent finalizers, capped by however many unique recent finalizers exist. Attestations are taken from the last `10` blocks.
-
-Once a valid burn claim is included, the claimed burn becomes consensus-required. Blocks after the claim must include that burn within the inclusion window. On the current devnet, that window is `3` blocks. A block that omits a due claimed burn is invalid.
-
-Burn fees also become the incentive for escalation:
-
-- If a burn is included without an active claim and without a valid claim for it in the same block, its fee goes to the block finalizer.
-- A `BurnClaim` does not carry its own fee; it turns the original burn fee into the bounty.
-- If a burn is included while a claim for it is active, or in the same block as a valid claim for it, `50%` of its fee goes to the block finalizer and `50%` is split evenly across the unique recent finalizers that signed the claim's `BurnSeen` attestations.
-- Remainders from integer division stay deterministic: the finalizer keeps the remainder from the 50/50 split, and any attester-share remainder is assigned by sorted attester address.
-
-This does not make censorship impossible. A fully partitioned network or a cartel that controls enough recent finalizers can still cause trouble. But it changes the normal case: if independent finalizers have seen a burn, later finalizers cannot simply ignore it without producing invalid blocks.
+This is an active protocol-design area. A production-grade solution likely needs stronger mempool or transaction ordering rules, for example a blinded mempool or commit-reveal style mechanism where finalizers cannot cheaply distinguish burns from other fee-paying transactions before committing to inclusion.
## Block Selection
When a node builds a block, it selects transactions in this order:
-1. Include due claimed burns first.
-2. Ensure the block has at least one burn.
-3. For recovery blocks, ensure at least one burn is from the recovery finalizer.
-4. Fill remaining space with valid transactions ordered by fee rate.
+1. Ensure the block has at least one burn.
+2. For recovery blocks, ensure at least one burn is from the recovery finalizer.
+3. Fill remaining space with valid transactions ordered by fee rate.
Blocks are bounded by transaction count and serialized byte size. The current devnet maximum block size is `100,000` bytes.
diff --git a/src/adapters/chain_store.rs b/src/adapters/chain_store.rs
@@ -30,13 +30,10 @@ CREATE TABLE IF NOT EXISTS block_metrics (
transaction_count INTEGER NOT NULL,
transfer_count INTEGER NOT NULL,
burn_count INTEGER NOT NULL,
- burn_claim_count INTEGER NOT NULL DEFAULT 0,
mine_count INTEGER NOT NULL,
burned_amount INTEGER NOT NULL,
total_burned_amount INTEGER NOT NULL,
fees_amount INTEGER NOT NULL,
- burn_claim_fee_share_count INTEGER NOT NULL DEFAULT 0,
- burn_claim_fee_share_amount INTEGER NOT NULL DEFAULT 0,
reward_amount INTEGER NOT NULL,
vdf_rounds INTEGER NOT NULL,
finalizer_rank INTEGER NOT NULL
@@ -55,13 +52,10 @@ pub struct BlockMetricRow {
pub transaction_count: u64,
pub transfer_count: u64,
pub burn_count: u64,
- pub burn_claim_count: u64,
pub mine_count: u64,
pub burned_amount: Amount,
pub total_burned_amount: Amount,
pub fees_amount: Amount,
- pub burn_claim_fee_share_count: u64,
- pub burn_claim_fee_share_amount: Amount,
pub reward_amount: Amount,
pub vdf_rounds: u64,
pub finalizer_rank: u32,
@@ -89,7 +83,6 @@ impl SqliteChainStore {
connection
.execute_batch(SCHEMA)
.context("failed to initialize chain database schema")
- .and_then(|_| ensure_block_metric_columns(connection))
})?;
Ok(store)
}
@@ -192,9 +185,8 @@ ON CONFLICT(id) DO UPDATE SET
r#"
SELECT height, block_hash, timestamp_ms, block_time_ms, mine_difficulty_bits,
circulating_supply, transaction_count, transfer_count, burn_count,
- burn_claim_count, mine_count, burned_amount, total_burned_amount, fees_amount,
- burn_claim_fee_share_count, burn_claim_fee_share_amount, reward_amount, vdf_rounds,
- finalizer_rank
+ mine_count, burned_amount, total_burned_amount, fees_amount, reward_amount,
+ vdf_rounds, finalizer_rank
FROM block_metrics
ORDER BY height ASC
"#,
@@ -212,16 +204,13 @@ ORDER BY height ASC
transaction_count: row.get(6)?,
transfer_count: row.get(7)?,
burn_count: row.get(8)?,
- burn_claim_count: row.get(9)?,
- mine_count: row.get(10)?,
- burned_amount: row.get(11)?,
- total_burned_amount: row.get(12)?,
- fees_amount: row.get(13)?,
- burn_claim_fee_share_count: row.get(14)?,
- burn_claim_fee_share_amount: row.get(15)?,
- reward_amount: row.get(16)?,
- vdf_rounds: row.get(17)?,
- finalizer_rank: row.get(18)?,
+ mine_count: row.get(9)?,
+ burned_amount: row.get(10)?,
+ total_burned_amount: row.get(11)?,
+ fees_amount: row.get(12)?,
+ reward_amount: row.get(13)?,
+ vdf_rounds: row.get(14)?,
+ finalizer_rank: row.get(15)?,
})
})
.context("failed to load block metrics")?;
@@ -259,32 +248,6 @@ PRAGMA synchronous = NORMAL;
}
}
-fn ensure_block_metric_columns(connection: &Connection) -> Result<()> {
- let mut statement = connection
- .prepare("PRAGMA table_info(block_metrics)")
- .context("failed to inspect block metrics schema")?;
- let columns = statement
- .query_map([], |row| row.get::<_, String>(1))
- .context("failed to read block metrics schema")?
- .collect::<std::result::Result<std::collections::BTreeSet<_>, _>>()
- .context("failed to collect block metrics columns")?;
- for (name, definition) in [
- ("burn_claim_count", "INTEGER NOT NULL DEFAULT 0"),
- ("burn_claim_fee_share_count", "INTEGER NOT NULL DEFAULT 0"),
- ("burn_claim_fee_share_amount", "INTEGER NOT NULL DEFAULT 0"),
- ] {
- if !columns.contains(name) {
- connection
- .execute(
- &format!("ALTER TABLE block_metrics ADD COLUMN {name} {definition}"),
- [],
- )
- .with_context(|| format!("failed to add block metrics column {name}"))?;
- }
- }
- Ok(())
-}
-
fn replace_metrics(
transaction: &rusqlite::Transaction<'_>,
metrics: &[BlockMetricRow],
@@ -296,10 +259,9 @@ fn replace_metrics(
r#"
INSERT INTO block_metrics (
height, block_hash, timestamp_ms, block_time_ms, mine_difficulty_bits,
- circulating_supply, transaction_count, transfer_count, burn_count, burn_claim_count,
- mine_count, burned_amount, total_burned_amount, fees_amount, burn_claim_fee_share_count,
- burn_claim_fee_share_amount, reward_amount, vdf_rounds, finalizer_rank
-) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19)
+ circulating_supply, transaction_count, transfer_count, burn_count, mine_count,
+ burned_amount, total_burned_amount, fees_amount, reward_amount, vdf_rounds, finalizer_rank
+) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16)
"#,
params![
metric.height,
@@ -311,13 +273,10 @@ INSERT INTO block_metrics (
metric.transaction_count,
metric.transfer_count,
metric.burn_count,
- metric.burn_claim_count,
metric.mine_count,
metric.burned_amount,
metric.total_burned_amount,
metric.fees_amount,
- metric.burn_claim_fee_share_count,
- metric.burn_claim_fee_share_amount,
metric.reward_amount,
metric.vdf_rounds,
metric.finalizer_rank,
@@ -354,18 +313,10 @@ fn metrics_from_snapshot(snapshot: &ChainSnapshot) -> Result<Vec<BlockMetricRow>
for block in &snapshot.blocks {
let mut transfer_count = 0_u64;
let mut burn_count = 0_u64;
- let mut burn_claim_count = 0_u64;
let mut mine_count = 0_u64;
let mut burned_amount = 0_u64;
let mut mine_issued_amount = 0_u64;
let mut fees_amount = 0_u64;
- let burn_claim_fee_share_count = block.fee_shares.len() as u64;
- let burn_claim_fee_share_amount =
- block.fee_shares.iter().try_fold(0_u64, |total, share| {
- total
- .checked_add(share.amount)
- .context("block metric burn claim fee shares overflow")
- })?;
for transaction in &block.transactions {
fees_amount = fees_amount
@@ -386,7 +337,6 @@ fn metrics_from_snapshot(snapshot: &ChainSnapshot) -> Result<Vec<BlockMetricRow>
.and_then(|amount| amount.checked_add(transaction.fee()))
.context("block metric mine issuance overflow")?;
}
- Transaction::BurnClaim { .. } => burn_claim_count += 1,
}
}
@@ -427,13 +377,10 @@ fn metrics_from_snapshot(snapshot: &ChainSnapshot) -> Result<Vec<BlockMetricRow>
transaction_count: block.transactions.len() as u64,
transfer_count,
burn_count,
- burn_claim_count,
mine_count,
burned_amount,
total_burned_amount,
fees_amount,
- burn_claim_fee_share_count,
- burn_claim_fee_share_amount,
reward_amount: block.reward,
vdf_rounds: block.vdf_rounds,
finalizer_rank: block.finalizer_rank,
@@ -469,7 +416,6 @@ fn unix_ms() -> u64 {
mod tests {
use std::collections::BTreeMap;
- use rusqlite::Connection;
use tempfile::tempdir;
use crate::domain::{BLOCK_REWARD, GenesisBurn, Ledger, Wallet};
@@ -535,11 +481,8 @@ mod tests {
assert_eq!(metrics.last().unwrap().height, 1);
assert_eq!(metrics.last().unwrap().burn_count, 1);
- assert_eq!(metrics.last().unwrap().burn_claim_count, 0);
assert_eq!(metrics.last().unwrap().burned_amount, 2);
assert_eq!(metrics.last().unwrap().fees_amount, 1);
- assert_eq!(metrics.last().unwrap().burn_claim_fee_share_count, 0);
- assert_eq!(metrics.last().unwrap().burn_claim_fee_share_amount, 0);
assert_eq!(metrics.last().unwrap().circulating_supply, BLOCK_REWARD + 7);
store.clear_metrics().unwrap();
@@ -547,58 +490,6 @@ mod tests {
}
#[test]
- fn sqlite_chain_store_migrates_burn_claim_metric_columns() {
- let dir = tempdir().unwrap();
- let path = dir.path().join("chain.sqlite3");
- Connection::open(&path)
- .unwrap()
- .execute_batch(
- r#"
-CREATE TABLE chain_snapshots (
- id INTEGER PRIMARY KEY CHECK (id = 1),
- height INTEGER NOT NULL,
- tip_hash TEXT NOT NULL,
- snapshot_json TEXT NOT NULL,
- updated_at_ms INTEGER NOT NULL
-);
-CREATE TABLE block_metrics (
- height INTEGER PRIMARY KEY,
- block_hash TEXT NOT NULL,
- timestamp_ms INTEGER NOT NULL,
- block_time_ms INTEGER,
- mine_difficulty_bits INTEGER NOT NULL,
- circulating_supply INTEGER NOT NULL,
- transaction_count INTEGER NOT NULL,
- transfer_count INTEGER NOT NULL,
- burn_count INTEGER NOT NULL,
- mine_count INTEGER NOT NULL,
- burned_amount INTEGER NOT NULL,
- total_burned_amount INTEGER NOT NULL,
- fees_amount INTEGER NOT NULL,
- reward_amount INTEGER NOT NULL,
- vdf_rounds INTEGER NOT NULL,
- finalizer_rank INTEGER NOT NULL
-);
-INSERT INTO block_metrics (
- height, block_hash, timestamp_ms, block_time_ms, mine_difficulty_bits,
- circulating_supply, transaction_count, transfer_count, burn_count, mine_count,
- burned_amount, total_burned_amount, fees_amount, reward_amount, vdf_rounds,
- finalizer_rank
-) VALUES (1, 'hash', 1000, 500, 12, 100, 2, 0, 1, 0, 1, 1, 2, 2, 99, 0);
-"#,
- )
- .unwrap();
-
- let store = SqliteChainStore::open(&path).unwrap();
- let metrics = store.load_metrics().unwrap();
-
- assert_eq!(metrics.len(), 1);
- assert_eq!(metrics[0].burn_claim_count, 0);
- assert_eq!(metrics[0].burn_claim_fee_share_count, 0);
- assert_eq!(metrics[0].burn_claim_fee_share_amount, 0);
- }
-
- #[test]
fn sqlite_chain_store_roundtrips_vdf_round_metrics_above_legacy_u32_limit() {
let dir = tempdir().unwrap();
let store = SqliteChainStore::open(dir.path().join("chain.sqlite3")).unwrap();
@@ -619,13 +510,10 @@ INSERT INTO block_metrics (
transaction_count: 0,
transfer_count: 0,
burn_count: 0,
- burn_claim_count: 0,
mine_count: 0,
burned_amount: 0,
total_burned_amount: 0,
fees_amount: 0,
- burn_claim_fee_share_count: 0,
- burn_claim_fee_share_amount: 0,
reward_amount: 0,
vdf_rounds,
finalizer_rank: 0,
diff --git a/src/adapters/http.rs b/src/adapters/http.rs
@@ -33,7 +33,7 @@ use crate::{
FeeEstimate, NodeStatus, PeerDirection, PeerInfo, SharedNode, SharedPeerBook, StratumStatus,
},
domain::{
- Amount, Block, BurnLeaderRank, FeeShare, Ledger, MINE_FINALIZER_FEE, MINE_REWARD, OutPoint,
+ Amount, Block, BurnLeaderRank, Ledger, MINE_FINALIZER_FEE, MINE_REWARD, OutPoint,
Transaction, TxInput, TxOutput, hex_hash,
},
};
@@ -242,7 +242,6 @@ impl WalletTransactionFilters {
Transaction::Transfer { .. } => self.transfer,
Transaction::Mine { .. } => self.mine,
Transaction::Burn { .. } => self.burn,
- Transaction::BurnClaim { .. } => self.burn,
}
}
}
@@ -276,7 +275,6 @@ struct MetricsResponse {
#[serde(rename_all = "camelCase")]
struct MetricsChart {
id: &'static str,
- section: &'static str,
title: &'static str,
unit: &'static str,
value_kind: MetricsValueKind,
@@ -356,7 +354,6 @@ struct UiBlock {
finalizer_mode: crate::domain::FinalizerMode,
finalizer_rank: u32,
reward: Amount,
- fee_shares: Vec<FeeShare>,
total_fees: Amount,
vdf_rounds: u64,
vdf_output: String,
@@ -1250,7 +1247,6 @@ fn metrics_response(enabled: bool, rows: Vec<BlockMetricRow>) -> MetricsResponse
charts: vec![
metrics_chart(
"block-time",
- "chain",
"Time per block",
"s",
MetricsValueKind::Seconds,
@@ -1263,7 +1259,6 @@ fn metrics_response(enabled: bool, rows: Vec<BlockMetricRow>) -> MetricsResponse
),
metrics_chart(
"difficulty",
- "chain",
"Difficulty",
"bits",
MetricsValueKind::Number,
@@ -1272,7 +1267,6 @@ fn metrics_response(enabled: bool, rows: Vec<BlockMetricRow>) -> MetricsResponse
),
metrics_chart(
"supply",
- "chain",
"IUNA in circulation",
"IUNA",
MetricsValueKind::Iuna,
@@ -1281,7 +1275,6 @@ fn metrics_response(enabled: bool, rows: Vec<BlockMetricRow>) -> MetricsResponse
),
metrics_chart(
"transactions",
- "chain",
"Transactions",
"tx",
MetricsValueKind::Number,
@@ -1290,7 +1283,6 @@ fn metrics_response(enabled: bool, rows: Vec<BlockMetricRow>) -> MetricsResponse
),
metrics_chart(
"burn-count",
- "chain",
"Burn transactions",
"burns",
MetricsValueKind::Number,
@@ -1299,7 +1291,6 @@ fn metrics_response(enabled: bool, rows: Vec<BlockMetricRow>) -> MetricsResponse
),
metrics_chart(
"burn-amount",
- "chain",
"Burn amount",
"IUNA",
MetricsValueKind::Iuna,
@@ -1308,7 +1299,6 @@ fn metrics_response(enabled: bool, rows: Vec<BlockMetricRow>) -> MetricsResponse
),
metrics_chart(
"total-burn",
- "chain",
"Total burn",
"IUNA",
MetricsValueKind::Iuna,
@@ -1317,7 +1307,6 @@ fn metrics_response(enabled: bool, rows: Vec<BlockMetricRow>) -> MetricsResponse
),
metrics_chart(
"fees",
- "chain",
"Fees",
"IUNA",
MetricsValueKind::Iuna,
@@ -1326,7 +1315,6 @@ fn metrics_response(enabled: bool, rows: Vec<BlockMetricRow>) -> MetricsResponse
),
metrics_chart(
"mine-actions",
- "chain",
"Mine actions",
"mine",
MetricsValueKind::Number,
@@ -1335,47 +1323,18 @@ fn metrics_response(enabled: bool, rows: Vec<BlockMetricRow>) -> MetricsResponse
),
metrics_chart(
"vdf-rounds",
- "chain",
"VDF rounds",
"rounds",
MetricsValueKind::Number,
&rows,
|row| (row.vdf_rounds > 0).then_some(row.vdf_rounds as f64),
),
- metrics_chart(
- "burn-claims",
- "burnClaims",
- "Burn claims",
- "claims",
- MetricsValueKind::Number,
- &rows,
- |row| Some(row.burn_claim_count as f64),
- ),
- metrics_chart(
- "burn-claim-payouts",
- "burnClaims",
- "Claim payouts",
- "outputs",
- MetricsValueKind::Number,
- &rows,
- |row| Some(row.burn_claim_fee_share_count as f64),
- ),
- metrics_chart(
- "burn-claim-payout-amount",
- "burnClaims",
- "Claim payout amount",
- "IUNA",
- MetricsValueKind::Iuna,
- &rows,
- |row| Some(micro_iuna_as_iuna(row.burn_claim_fee_share_amount)),
- ),
],
}
}
fn metrics_chart(
id: &'static str,
- section: &'static str,
title: &'static str,
unit: &'static str,
value_kind: MetricsValueKind,
@@ -1384,7 +1343,6 @@ fn metrics_chart(
) -> MetricsChart {
MetricsChart {
id,
- section,
title,
unit,
value_kind,
@@ -1725,11 +1683,6 @@ fn ui_block(
.get(&block.hash)
.cloned()
.unwrap_or_default();
- let total_fees = block
- .fee_shares
- .iter()
- .try_fold(block.reward, |total, share| total.checked_add(share.amount))
- .unwrap_or(u64::MAX);
UiBlock {
height: block.height,
prev_hash: block.prev_hash,
@@ -1738,8 +1691,7 @@ fn ui_block(
finalizer_mode: block.finalizer_mode,
finalizer_rank: block.finalizer_rank,
reward: block.reward,
- fee_shares: block.fee_shares.clone(),
- total_fees,
+ total_fees: block.reward,
vdf_rounds: block.vdf_rounds,
vdf_output: block.vdf_output,
leader_proof: block.leader_proof,
@@ -1819,22 +1771,6 @@ fn ui_transaction(
proof_bits: Some(proof_bits(signature)),
proof_hash: Some(signature.clone()),
},
- Transaction::BurnClaim {
- burn, signature, ..
- } => UiTransaction {
- kind: "burn_claim",
- from: transaction.sender().to_string(),
- to: None,
- amount: burn.amount(),
- fee: 0,
- inputs: Vec::new(),
- outputs: Vec::new(),
- change: Vec::new(),
- signature: signature.clone(),
- difficulty_bits: None,
- proof_bits: None,
- proof_hash: None,
- },
}
}
@@ -1913,15 +1849,6 @@ fn known_output_index(
},
);
}
- for (index, share) in block.fee_shares.iter().enumerate() {
- outputs.insert(
- fee_share_outpoint(&block.hash, index),
- TxOutput {
- address: share.address.clone(),
- amount: share.amount,
- },
- );
- }
}
for transaction in pending {
index_transaction_outputs(&mut outputs, transaction);
@@ -1940,7 +1867,6 @@ fn index_transaction_outputs(
address: recipient.clone(),
amount: MINE_REWARD,
}],
- Transaction::BurnClaim { .. } => Vec::new(),
};
for (index, output) in created_outputs.iter().enumerate() {
outputs.insert(
@@ -1967,13 +1893,6 @@ fn reward_outpoint(block_hash: &str) -> OutPoint {
}
}
-fn fee_share_outpoint(block_hash: &str, index: usize) -> OutPoint {
- OutPoint {
- txid: block_hash.to_string(),
- index: u32::MAX - 1 - index as u32,
- }
-}
-
async fn replace_setup_wallet_with_generated_seed(
state: &HttpState,
headers: &HeaderMap,
@@ -2696,8 +2615,6 @@ const INDEX_HTML: &str = r#"<!doctype html>
.metrics-head h2 { margin: 0; }
.metrics-range { flex: 0 0 auto; }
.metrics-range button { padding: 5px 9px; font-size: 12px; white-space: nowrap; }
- .metrics-section-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding-top: 4px; border-top: 1px solid #2a3035; }
- .metrics-section-head h3 { margin: 0; color: #e8edf0; font-size: 15px; font-weight: 850; }
.metrics-summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; }
.metrics-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 430px), 1fr)); gap: 12px; }
.metric-chart-card { display: grid; gap: 10px; min-width: 0; border: 1px solid #2a3035; border-radius: 8px; padding: 12px; background: #181b1f; }
@@ -2931,7 +2848,7 @@ const INDEX_HTML: &str = r#"<!doctype html>
.block-card { flex-basis: 108px; }
}
</style>
- <script defer src="/assets/iuna-ui.js?v=77"></script>
+ <script defer src="/assets/iuna-ui.js?v=78"></script>
<script defer src="/assets/alpine.min.js"></script>
</head>
<body x-data="iunaApp()" x-init="init()" @keydown.window.escape="closeModals()" x-cloak>
@@ -3425,53 +3342,7 @@ const INDEX_HTML: &str = r#"<!doctype html>
</div>
<div class="metrics-empty" x-show="metricsCharts().length === 0">No metrics collected yet</div>
<div class="metrics-grid">
- <template x-for="chart in metricsChainCharts()" :key="chart.id">
- <article class="metric-chart-card">
- <div class="metric-chart-head">
- <h3 class="metric-chart-title" x-text="chart.title"></h3>
- <div class="metric-chart-value" x-text="metricLatestValueLabel(chart)"></div>
- </div>
- <div class="metric-chart-frame">
- <div class="metric-chart-y-axis">
- <template x-for="tick in metricYAxisTicks(chart)" :key="`${chart.id}-y-${tick}`">
- <span class="metric-chart-axis-label" :style="metricYAxisLabelStyle(chart, tick)" x-text="metricAxisValueLabel(chart, tick)"></span>
- </template>
- </div>
- <div class="metric-chart-plot" @mousemove="setMetricHoverFromPlot(chart, $event)" @mouseleave="clearMetricHover(chart)">
- <svg class="metric-chart-svg" viewBox="0 0 300 148" preserveAspectRatio="none" role="img" :aria-label="chart.title">
- <path class="metric-chart-gridline" :d="metricGridPath(chart)"></path>
- <line class="metric-chart-axis" x1="4" y1="8" x2="4" y2="132"></line>
- <line class="metric-chart-axis" x1="4" y1="132" x2="296" y2="132"></line>
- <polyline class="metric-chart-line" :points="metricChartPoints(chart)"></polyline>
- </svg>
- <div class="metric-chart-points">
- <template x-for="marker in metricChartPointMarkers(chart)" :key="`${chart.id}-point-${marker.height}`">
- <button class="metric-chart-point-hit" type="button" :class="{ 'is-active': metricHover?.chartId === chart.id && metricHover?.height === marker.height }" :style="metricPointStyle(marker)" :title="marker.label" @focus="setMetricHover(chart, marker)" @blur="clearMetricHover(chart)" :aria-label="marker.label"></button>
- </template>
- </div>
- <template x-if="metricHover?.chartId === chart.id">
- <div class="metric-chart-tooltip" :style="metricTooltipStyle(chart)" x-text="metricTooltipLabel(chart)"></div>
- </template>
- </div>
- <div class="metric-chart-x-axis">
- <template x-for="tick in metricXAxisTicks(chart)" :key="`${chart.id}-x-${tick}`">
- <span class="metric-chart-axis-label" :style="metricXAxisLabelStyle(chart, tick)" x-text="`#${tick}`"></span>
- </template>
- </div>
- </div>
- </article>
- </template>
- </div>
- <div class="metrics-section-head">
- <h3>Burn claims</h3>
- </div>
- <div class="metrics-summary">
- <div class="metric"><div class="label">Claims</div><div class="value" x-text="metricsLatest().burnClaimCount ?? '-'"></div></div>
- <div class="metric"><div class="label">Payouts</div><div class="value" x-text="metricsLatest().burnClaimFeeShareCount ?? '-'"></div></div>
- <div class="metric"><div class="label">Payout amount</div><div class="value" x-text="metricAmountLabel(metricsLatest().burnClaimFeeShareAmount)"></div></div>
- </div>
- <div class="metrics-grid">
- <template x-for="chart in metricsBurnClaimCharts()" :key="chart.id">
+ <template x-for="chart in metricsCharts()" :key="chart.id">
<article class="metric-chart-card">
<div class="metric-chart-head">
<h3 class="metric-chart-title" x-text="chart.title"></h3>
@@ -4795,7 +4666,6 @@ mod tests {
vdf_rounds: 0,
vdf_output: "vdf".to_string(),
leader_proof: None,
- fee_shares: Vec::new(),
transactions,
hash: format!("hash-{height}"),
}
@@ -4816,13 +4686,10 @@ mod tests {
transaction_count: 0,
transfer_count: 0,
burn_count: 0,
- burn_claim_count: 0,
mine_count: 0,
burned_amount: 0,
total_burned_amount: 0,
fees_amount: 0,
- burn_claim_fee_share_count: 0,
- burn_claim_fee_share_amount: 0,
reward_amount: 0,
vdf_rounds,
finalizer_rank: 0,
@@ -4871,41 +4738,8 @@ mod tests {
}
#[test]
- fn metrics_response_includes_burn_claim_charts() {
- let mut row = metric_row(7, Some(600_000), 120);
- row.burn_claim_count = 2;
- row.burn_claim_fee_share_count = 3;
- row.burn_claim_fee_share_amount = 4_500_000;
- let response = super::metrics_response(true, vec![row]);
-
- let burn_claims = response
- .charts
- .iter()
- .find(|chart| chart.id == "burn-claims")
- .expect("burn claim count chart should exist");
- assert_eq!(burn_claims.section, "burnClaims");
- assert_eq!(burn_claims.points[0].value, 2.0);
-
- let payouts = response
- .charts
- .iter()
- .find(|chart| chart.id == "burn-claim-payouts")
- .expect("burn claim payout count chart should exist");
- assert_eq!(payouts.section, "burnClaims");
- assert_eq!(payouts.points[0].value, 3.0);
-
- let payout_amount = response
- .charts
- .iter()
- .find(|chart| chart.id == "burn-claim-payout-amount")
- .expect("burn claim payout amount chart should exist");
- assert_eq!(payout_amount.section, "burnClaims");
- assert_eq!(payout_amount.points[0].value, 4.5);
- }
-
- #[test]
fn metrics_screen_includes_block_range_filter() {
- assert!(super::INDEX_HTML.contains("iuna-ui.js?v=77"));
+ assert!(super::INDEX_HTML.contains("iuna-ui.js?v=78"));
assert!(super::INDEX_HTML.contains("aria-label=\"Metrics block range\""));
assert!(super::INDEX_HTML.contains("setMetricsRange(100)"));
assert!(super::INDEX_HTML.contains("setMetricsRange(1000)"));
@@ -4913,14 +4747,6 @@ mod tests {
}
#[test]
- fn metrics_screen_includes_burn_claim_section() {
- assert!(super::INDEX_HTML.contains("metricsBurnClaimCharts()"));
- assert!(super::INDEX_HTML.contains("Burn claims</h3>"));
- assert!(super::INDEX_HTML.contains("burnClaimCount"));
- assert!(super::INDEX_HTML.contains("burnClaimFeeShareAmount"));
- }
-
- #[test]
fn initial_setup_includes_node_mode_choices() {
assert!(super::INDEX_HTML.contains("aria-label=\"Initial node mode\""));
assert!(super::INDEX_HTML.contains("selectSetupNodeMode('wallet')"));
diff --git a/src/adapters/p2p.rs b/src/adapters/p2p.rs
@@ -1821,7 +1821,6 @@ fn record_received_envelope_kind(metrics: &P2pMetricsCounters, envelope: &Gossip
}
GossipEnvelope::Transaction(_)
| GossipEnvelope::Transactions { .. }
- | GossipEnvelope::BurnSeen(_)
| GossipEnvelope::Block(_)
| GossipEnvelope::Blocks { .. }
| GossipEnvelope::ChainSnapshot(_) => {
@@ -1899,7 +1898,6 @@ fn validate_envelope_limits(envelope: &GossipEnvelope) -> Result<()> {
GossipEnvelope::Hello(_)
| GossipEnvelope::ChainSnapshotRequest
| GossipEnvelope::Transaction(_)
- | GossipEnvelope::BurnSeen(_)
| GossipEnvelope::Block(_)
| GossipEnvelope::PeerAnnouncement { .. }
| GossipEnvelope::PeerVerificationChallenge { .. }
@@ -3193,7 +3191,6 @@ mod tests {
vdf_rounds: 1,
vdf_output: "vdf".to_string(),
leader_proof: None,
- fee_shares: Vec::new(),
transactions: Vec::new(),
hash: "hash".to_string(),
};
diff --git a/src/app.rs b/src/app.rs
@@ -13,10 +13,10 @@ use sha2::{Digest, Sha256};
use tokio::sync::Mutex;
use crate::domain::{
- Amount, BURN_CLAIM_SEEN_WINDOW_BLOCKS, Block, BurnLeaderRank, BurnSeen, ChainSnapshot,
- ChainStatus, DEFAULT_FEE_PER_BYTE, DEFAULT_TRANSACTION_FEE, Ledger, MAX_PENDING_TRANSACTIONS,
- MINE_FINALIZER_FEE, OutPoint, PreparedBlock, StratumMineShare, StratumMineTemplate,
- Transaction, TransactionSubmitOutcome, VDF_TARGET_BLOCK_MS, Wallet, hex_hash, run_vdf,
+ Amount, Block, BurnLeaderRank, ChainSnapshot, ChainStatus, DEFAULT_FEE_PER_BYTE,
+ DEFAULT_TRANSACTION_FEE, Ledger, MAX_PENDING_TRANSACTIONS, MINE_FINALIZER_FEE, OutPoint,
+ PreparedBlock, StratumMineShare, StratumMineTemplate, Transaction, TransactionSubmitOutcome,
+ VDF_TARGET_BLOCK_MS, Wallet, hex_hash, run_vdf,
};
pub type SharedNode = Arc<Mutex<NodeCore>>;
@@ -130,7 +130,6 @@ pub enum GossipEnvelope {
Transactions {
transactions: Vec<Transaction>,
},
- BurnSeen(BurnSeen),
Block(Block),
Blocks {
blocks: Vec<Block>,
@@ -261,7 +260,6 @@ pub struct NodeCore {
last_auto_pow_mine_anchor: Option<String>,
last_auto_pow_mine_status: Option<String>,
auto_pow_mine_cursor: Option<AutoPowMineCursor>,
- burn_seen_pool: BTreeMap<String, BTreeMap<String, BurnSeen>>,
outbox: Vec<GossipEnvelope>,
}
@@ -347,7 +345,6 @@ impl NodeCore {
last_auto_pow_mine_anchor: None,
last_auto_pow_mine_status: None,
auto_pow_mine_cursor: None,
- burn_seen_pool: BTreeMap::new(),
outbox: Vec::new(),
}
}
@@ -773,115 +770,10 @@ impl NodeCore {
let outcome = self.ledger.submit_transaction_with_outcome(tx.clone())?;
if outcome.added() {
self.outbox.push(GossipEnvelope::Transaction(tx.clone()));
- self.maybe_attest_burn(&tx)?;
- self.try_submit_burn_claim(tx.signature())?;
}
Ok(outcome)
}
- pub fn receive_burn_seen(&mut self, seen: BurnSeen) -> Result<()> {
- seen.verify_signature()?;
- let burn_signature = seen.burn_signature.clone();
- if self.remember_burn_seen(seen.clone()) {
- self.outbox.push(GossipEnvelope::BurnSeen(seen));
- }
- self.try_submit_burn_claim(&burn_signature)
- }
-
- fn maybe_attest_burn(&mut self, tx: &Transaction) -> Result<()> {
- if !tx.is_burn() {
- return Ok(());
- }
- let Ok(wallet) = self.wallet.unlocked() else {
- return Ok(());
- };
- let Some((seen_height, seen_block_hash)) = self.recent_finalizer_block_for_wallet() else {
- return Ok(());
- };
- let seen = wallet.burn_seen(tx.signature(), seen_height, seen_block_hash);
- if !self.remember_burn_seen(seen.clone()) {
- return Ok(());
- }
- self.outbox.push(GossipEnvelope::BurnSeen(seen));
- Ok(())
- }
-
- fn remember_burn_seen(&mut self, seen: BurnSeen) -> bool {
- let entry = self
- .burn_seen_pool
- .entry(seen.burn_signature.clone())
- .or_default();
- let should_store = entry
- .get(&seen.signer)
- .is_none_or(|existing| seen.seen_height > existing.seen_height);
- if should_store {
- entry.insert(seen.signer.clone(), seen);
- }
- should_store
- }
-
- fn attest_pending_burns(&mut self) -> Result<()> {
- let burns = self
- .ledger
- .pending()
- .iter()
- .filter(|transaction| transaction.is_burn())
- .cloned()
- .collect::<Vec<_>>();
- for burn in burns {
- self.maybe_attest_burn(&burn)?;
- self.try_submit_burn_claim(burn.signature())?;
- }
- Ok(())
- }
-
- fn recent_finalizer_block_for_wallet(&self) -> Option<(u64, String)> {
- let address = self.wallet.address();
- let tip_height = self.ledger.height();
- self.ledger
- .chain()
- .iter()
- .rev()
- .find(|block| {
- block.height > 0
- && block.miner == address
- && block.height.saturating_add(BURN_CLAIM_SEEN_WINDOW_BLOCKS) > tip_height
- })
- .map(|block| (block.height, block.hash.clone()))
- }
-
- fn try_submit_burn_claim(&mut self, burn_signature: &str) -> Result<()> {
- let Some(burn) = self.ledger.transaction_by_signature(burn_signature) else {
- return Ok(());
- };
- if !burn.is_burn() {
- return Ok(());
- }
- let tip_height = self.ledger.height();
- let Some(seen_by_signer) = self.burn_seen_pool.get_mut(burn_signature) else {
- return Ok(());
- };
- seen_by_signer.retain(|_, seen| {
- seen.seen_height > 0
- && seen.seen_height <= tip_height
- && seen
- .seen_height
- .saturating_add(BURN_CLAIM_SEEN_WINDOW_BLOCKS)
- > tip_height
- });
- if seen_by_signer.is_empty() {
- return Ok(());
- }
- let seen = seen_by_signer.values().cloned().collect::<Vec<_>>();
- let Ok(claim) = self.ledger.build_burn_claim(burn, seen) else {
- return Ok(());
- };
- if self.ledger.submit_transaction(claim.clone())? {
- self.outbox.push(GossipEnvelope::Transaction(claim));
- }
- Ok(())
- }
-
fn build_burn_with_fee_rate(
&self,
amount: Amount,
@@ -1255,7 +1147,6 @@ impl NodeCore {
.mine_next_block(self.wallet.unlocked()?, timestamp_ms)?;
self.ledger.apply_locally_mined_block(block.clone())?;
self.outbox.push(GossipEnvelope::Block(block.clone()));
- self.attest_pending_burns()?;
Ok(block)
}
@@ -1276,7 +1167,6 @@ impl NodeCore {
let block = work.finish_at(self.wallet.unlocked()?, vdf_output, timestamp_ms);
self.ledger.apply_locally_mined_block(block.clone())?;
self.outbox.push(GossipEnvelope::Block(block.clone()));
- self.attest_pending_burns()?;
Ok(block)
}
@@ -1300,13 +1190,11 @@ impl NodeCore {
}
Ok(())
}
- GossipEnvelope::BurnSeen(seen) => self.receive_burn_seen(seen),
GossipEnvelope::Block(block) => {
let previous_height = self.ledger.height();
self.ledger.apply_block(block.clone())?;
if self.ledger.height() > previous_height {
self.outbox.push(GossipEnvelope::Block(block));
- self.attest_pending_burns()?;
}
Ok(())
}
@@ -1322,7 +1210,6 @@ impl NodeCore {
for block in imported {
self.outbox.push(GossipEnvelope::Block(block));
}
- self.attest_pending_burns()?;
Ok(())
}
GossipEnvelope::ChainSnapshot(snapshot) => self.import_chain_snapshot(snapshot),
@@ -1339,7 +1226,6 @@ impl NodeCore {
.apply_preverified_block_at(block.clone(), now_ms)?;
if self.ledger.height() > previous_height {
self.outbox.push(GossipEnvelope::Block(block));
- self.attest_pending_burns()?;
}
Ok(())
}
@@ -1361,9 +1247,7 @@ impl NodeCore {
self.last_auto_pow_mine_anchor = None;
self.last_auto_pow_mine_status = None;
self.auto_pow_mine_cursor = None;
- self.burn_seen_pool.clear();
self.enqueue_imported_blocks(previous_height);
- self.attest_pending_burns()?;
}
Ok(())
}
@@ -1384,9 +1268,7 @@ impl NodeCore {
self.last_auto_pow_mine_anchor = None;
self.last_auto_pow_mine_status = None;
self.auto_pow_mine_cursor = None;
- self.burn_seen_pool.clear();
self.enqueue_imported_blocks(previous_height);
- self.attest_pending_burns()?;
Ok(true)
}
@@ -1978,7 +1860,7 @@ mod tests {
RECOVERY_BLOCK_DELAY_MS, Transaction, Wallet,
};
- use super::{GossipEnvelope, NodeConfig, NodeCore};
+ use super::{NodeConfig, NodeCore};
#[test]
fn same_height_verified_import_does_not_reset_auto_burn_guard() {
@@ -2224,41 +2106,6 @@ mod tests {
let minimum_burn_fee = burn.economic_size_bytes() as u64 * 3;
assert!(burn.fee() >= minimum_burn_fee);
}
-
- #[test]
- fn recent_finalizer_gossips_burn_seen_and_claim_for_received_burn() {
- let finalizer = Wallet::from_seed("burn-seen-node-finalizer");
- let burner = Wallet::from_seed("burn-seen-node-burner");
- let mut allocations = BTreeMap::new();
- allocations.insert(finalizer.address().to_string(), 10 * MICRO_IUNA);
- allocations.insert(burner.address().to_string(), 10 * MICRO_IUNA);
- let mut ledger = Ledger::new_with_genesis_burns(
- allocations,
- vec![GenesisBurn::new(finalizer.address(), MICRO_IUNA)],
- 1,
- )
- .unwrap();
- let finalizer_burn = ledger.build_burn(&finalizer, 1, 0).unwrap();
- ledger.submit_transaction(finalizer_burn).unwrap();
- let block = ledger.mine_next_block(&finalizer, 1).unwrap();
- ledger.apply_locally_mined_block(block).unwrap();
- let burn = ledger.build_burn(&burner, 1, 0).unwrap();
- let burn_signature = burn.signature().to_string();
- let mut node = NodeCore::from_ledger(finalizer, ledger, 0);
-
- node.receive_transaction(burn).unwrap();
- let outbox = node.drain_outbox();
-
- assert!(outbox.iter().any(|envelope| matches!(
- envelope,
- GossipEnvelope::BurnSeen(seen) if seen.burn_signature == burn_signature
- )));
- assert!(outbox.iter().any(|envelope| matches!(
- envelope,
- GossipEnvelope::Transaction(Transaction::BurnClaim { burn, .. })
- if burn.signature() == burn_signature
- )));
- }
}
#[derive(Debug, Default)]
@@ -2328,9 +2175,7 @@ impl InMemoryNetwork {
fn receive_in_memory_envelope(node: &mut NodeCore, envelope: GossipEnvelope) -> Result<()> {
let transaction_like = matches!(
envelope,
- GossipEnvelope::Transaction(_)
- | GossipEnvelope::Transactions { .. }
- | GossipEnvelope::BurnSeen(_)
+ GossipEnvelope::Transaction(_) | GossipEnvelope::Transactions { .. }
);
match node.receive(envelope) {
Ok(()) => Ok(()),
diff --git a/src/domain.rs b/src/domain.rs
@@ -21,11 +21,6 @@ pub const VDF_TARGET_BLOCK_MS: u64 = 10 * 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;
-pub const BURN_CLAIM_SEEN_QUORUM: usize = 3;
-pub const BURN_CLAIM_SEEN_WINDOW_BLOCKS: u64 = 10;
-pub const BURN_CLAIM_INCLUDE_WITHIN_BLOCKS: u64 = 3;
-const CLAIMED_BURN_ATTESTER_FEE_SHARE_BPS: u64 = 5_000;
-const FEE_SHARE_BPS_DENOMINATOR: u64 = 10_000;
const MINE_RETARGET_WINDOW_BLOCKS: u64 = 10;
const MINE_TARGET_ACTIONS_PER_BLOCK: u64 = 1;
const MINE_MAX_RETARGET_STEP_BITS: u32 = 2;
@@ -91,29 +86,6 @@ impl Wallet {
signature,
}
}
-
- pub fn burn_seen(
- &self,
- burn_signature: impl Into<String>,
- seen_height: u64,
- seen_block_hash: impl Into<String>,
- ) -> BurnSeen {
- let burn_signature = burn_signature.into();
- let seen_block_hash = seen_block_hash.into();
- let payload = burn_seen_payload(
- &burn_signature,
- seen_height,
- &seen_block_hash,
- self.address(),
- );
- BurnSeen {
- burn_signature,
- seen_height,
- seen_block_hash,
- signer: self.address.clone(),
- signature: self.sign_payload(&payload),
- }
- }
}
#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
@@ -136,12 +108,6 @@ pub struct TxOutput {
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
-pub struct FeeShare {
- pub address: String,
- pub amount: Amount,
-}
-
-#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum Transaction {
Transfer {
@@ -170,35 +136,6 @@ pub enum Transaction {
proof_header: Option<String>,
signature: String,
},
- BurnClaim {
- burn: Box<Transaction>,
- seen: Vec<BurnSeen>,
- signature: String,
- },
-}
-
-#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
-pub struct BurnSeen {
- pub burn_signature: String,
- pub seen_height: u64,
- pub seen_block_hash: String,
- pub signer: String,
- pub signature: String,
-}
-
-impl BurnSeen {
- pub fn verify_signature(&self) -> Result<()> {
- verify_burn_seen_signature(self)
- }
-
- fn signing_payload(&self) -> String {
- burn_seen_payload(
- &self.burn_signature,
- self.seen_height,
- &self.seen_block_hash,
- &self.signer,
- )
- }
}
#[derive(Clone, Debug, Eq, PartialEq)]
@@ -214,18 +151,6 @@ impl Transaction {
Self::genesis_burn_with_change(from, amount, Vec::new())
}
- pub fn burn_claim(burn: Transaction, seen: Vec<BurnSeen>) -> Result<Self> {
- if !burn.is_burn() {
- bail!("burn claim must reference a burn transaction");
- }
- let signature = burn_claim_signature(&burn, &seen);
- Ok(Self::BurnClaim {
- burn: Box::new(burn),
- seen,
- signature,
- })
- }
-
fn genesis_burn_with_allocation(
from: impl Into<String>,
amount: Amount,
@@ -276,9 +201,6 @@ impl Transaction {
.map(|input| input.owner.as_str())
.unwrap_or(""),
Self::Mine { recipient, .. } => recipient.as_str(),
- Self::BurnClaim { seen, .. } => {
- seen.first().map(|seen| seen.signer.as_str()).unwrap_or("")
- }
}
}
@@ -287,7 +209,6 @@ impl Transaction {
Self::Transfer { outputs, .. } => outputs.first().map(|output| output.address.as_str()),
Self::Burn { .. } => None,
Self::Mine { recipient, .. } => Some(recipient.as_str()),
- Self::BurnClaim { .. } => None,
}
}
@@ -298,7 +219,6 @@ impl Transaction {
}
Self::Burn { amount, .. } => *amount,
Self::Mine { .. } => MINE_REWARD,
- Self::BurnClaim { .. } => 0,
}
}
@@ -306,12 +226,11 @@ impl Transaction {
match self {
Self::Transfer { fee, .. } | Self::Burn { fee, .. } => *fee,
Self::Mine { .. } => MINE_FINALIZER_FEE,
- Self::BurnClaim { .. } => 0,
}
}
pub fn total_debit(&self) -> Result<Amount> {
- if matches!(self, Self::Mine { .. } | Self::BurnClaim { .. }) {
+ if matches!(self, Self::Mine { .. }) {
return Ok(0);
}
self.amount()
@@ -323,7 +242,6 @@ impl Transaction {
match self {
Self::Transfer { signature, .. } | Self::Burn { signature, .. } => signature,
Self::Mine { signature, .. } => signature,
- Self::BurnClaim { signature, .. } => signature,
}
}
@@ -379,7 +297,6 @@ impl Transaction {
difficulty_bits,
..
} => mine_payload(recipient, anchor, *salt, *nonce, *difficulty_bits),
- Self::BurnClaim { burn, seen, .. } => burn_claim_payload(burn, seen),
}
}
@@ -413,22 +330,6 @@ impl Transaction {
}
return Ok(());
}
- if let Self::BurnClaim {
- burn,
- seen,
- signature,
- } = self
- {
- burn.verify_signature()?;
- for seen in seen {
- verify_burn_seen_signature(seen)?;
- }
- let expected = burn_claim_signature(burn, seen);
- if *signature != expected {
- bail!("burn claim signature is invalid");
- }
- return Ok(());
- }
if self.signature().starts_with("iuna-genesis-burn:") || self.inputs_are_genesis_signed() {
return Ok(());
}
@@ -455,7 +356,7 @@ impl Transaction {
fn inputs(&self) -> &[TxInput] {
match self {
Self::Transfer { inputs, .. } | Self::Burn { inputs, .. } => inputs,
- Self::Mine { .. } | Self::BurnClaim { .. } => &[],
+ Self::Mine { .. } => &[],
}
}
@@ -467,7 +368,6 @@ impl Transaction {
address: recipient.clone(),
amount: MINE_REWARD,
}],
- Self::BurnClaim { .. } => Vec::new(),
}
}
@@ -604,14 +504,6 @@ fn canonical_outputs(outputs: &[TxOutput]) -> String {
.join("|")
}
-fn canonical_fee_shares(shares: &[FeeShare]) -> String {
- shares
- .iter()
- .map(|share| format!("{}:{}", share.address, share.amount))
- .collect::<Vec<_>>()
- .join("|")
-}
-
fn mine_payload(
recipient: &str,
anchor: &str,
@@ -638,49 +530,6 @@ fn mine_signature(
))
}
-fn burn_seen_payload(
- burn_signature: &str,
- seen_height: u64,
- seen_block_hash: &str,
- signer: &str,
-) -> String {
- format!("iuna-burn-seen:{burn_signature}:{seen_height}:{seen_block_hash}:{signer}")
-}
-
-fn canonical_burn_seen(seen: &BurnSeen) -> String {
- format!(
- "{}:{}:{}:{}:{}",
- seen.burn_signature, seen.seen_height, seen.seen_block_hash, seen.signer, seen.signature
- )
-}
-
-fn burn_claim_payload(burn: &Transaction, seen: &[BurnSeen]) -> String {
- let mut seen = seen.iter().map(canonical_burn_seen).collect::<Vec<_>>();
- seen.sort_unstable();
- format!("iuna-burn-claim:{}:{}", burn.canonical(), seen.join("|"))
-}
-
-fn burn_claim_signature(burn: &Transaction, seen: &[BurnSeen]) -> String {
- hex_hash(burn_claim_payload(burn, seen))
-}
-
-fn verify_burn_seen_signature(seen: &BurnSeen) -> Result<()> {
- validate_protocol_id(&seen.burn_signature, "burn seen transaction signature")?;
- validate_hash(&seen.seen_block_hash, "burn seen block hash")?;
- validate_address(&seen.signer, "burn seen signer")?;
- validate_signature(&seen.signature, "burn seen signature")?;
- let public_key = decode_hex_array::<PUBLIC_KEY_BYTES>(&seen.signer)
- .with_context(|| format!("invalid burn seen signer {}", seen.signer))?;
- let signature = decode_hex_array::<SIGNATURE_BYTES>(&seen.signature)
- .context("invalid burn seen signature")?;
- let verifying_key =
- VerifyingKey::from_bytes(&public_key).context("invalid burn seen public key")?;
- let signature = Signature::from_bytes(&signature);
- verifying_key
- .verify(seen.signing_payload().as_bytes(), &signature)
- .context("burn seen signature is invalid")
-}
-
pub const STRATUM_EXTRANONCE1_HEX: &str = "00000000";
pub const STRATUM_EXTRANONCE2_SIZE: usize = 4;
const STRATUM_MINE_VERSION: [u8; 4] = [1, 0, 0, 0];
@@ -873,8 +722,6 @@ pub struct Block {
pub vdf_rounds: u64,
pub vdf_output: String,
pub leader_proof: Option<LeaderProof>,
- #[serde(default, skip_serializing_if = "Vec::is_empty")]
- pub fee_shares: Vec<FeeShare>,
pub transactions: Vec<Transaction>,
pub hash: String,
}
@@ -900,7 +747,6 @@ impl Block {
vdf_rounds: draft.vdf_rounds,
vdf_output: draft.vdf_output,
leader_proof: draft.leader_proof,
- fee_shares: draft.fee_shares,
transactions: draft.transactions,
hash: String::new(),
};
@@ -943,22 +789,6 @@ impl Block {
)
})
.unwrap_or_default();
- if !self.fee_shares.is_empty() {
- return hex_hash(format!(
- "block-content-v3:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}",
- self.height,
- self.prev_hash,
- self.timestamp_ms,
- self.miner,
- self.finalizer_mode.canonical(),
- self.finalizer_rank,
- self.reward,
- self.vdf_rounds,
- leader_proof,
- canonical_fee_shares(&self.fee_shares),
- txs
- ));
- }
hex_hash(format!(
"{}:{}",
self.legacy_content_hash_prefix(&leader_proof),
@@ -1030,13 +860,6 @@ impl FinalizerMode {
Self::Recovery => 1,
}
}
-
- fn canonical(self) -> &'static str {
- match self {
- Self::Ticket => "ticket",
- Self::Recovery => "recovery",
- }
- }
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
@@ -1113,16 +936,6 @@ pub struct BurnLeaderRank {
pub eligible_until_height: u64,
}
-#[derive(Clone, Debug, Eq, PartialEq)]
-struct BurnClaimState {
- burn: Transaction,
- claim_signature: String,
- claim_height: u64,
- attesters: Vec<String>,
- due_height: u64,
- expires_at_height: u64,
-}
-
#[derive(Clone, Debug)]
pub struct PreparedBlock {
height: u64,
@@ -1132,7 +945,6 @@ pub struct PreparedBlock {
finalizer_mode: FinalizerMode,
finalizer_rank: u32,
reward: Amount,
- fee_shares: Vec<FeeShare>,
vdf_rounds: u64,
vdf_seed: String,
leader_ticket: Option<BurnTicket>,
@@ -1195,7 +1007,6 @@ impl PreparedBlock {
finalizer_mode: self.finalizer_mode,
finalizer_rank: self.finalizer_rank,
reward: self.reward,
- fee_shares: self.fee_shares,
vdf_rounds: self.vdf_rounds,
vdf_output,
leader_proof,
@@ -1213,7 +1024,6 @@ struct BlockDraft {
finalizer_mode: FinalizerMode,
finalizer_rank: u32,
reward: Amount,
- fee_shares: Vec<FeeShare>,
vdf_rounds: u64,
vdf_output: String,
leader_proof: Option<LeaderProof>,
@@ -1390,7 +1200,6 @@ pub struct Ledger {
genesis_allocations: BTreeMap<String, Amount>,
utxos: BTreeMap<OutPoint, TxOutput>,
tickets: Vec<BurnTicket>,
- burn_claims: Vec<BurnClaimState>,
pending: Vec<Transaction>,
orphans: Vec<Transaction>,
mine_reward: Amount,
@@ -1438,7 +1247,6 @@ impl Ledger {
genesis_allocations: genesis_allocations.clone(),
utxos,
tickets,
- burn_claims: Vec::new(),
pending: Vec::new(),
orphans: Vec::new(),
mine_reward: MINE_REWARD,
@@ -1491,7 +1299,6 @@ impl Ledger {
genesis_allocations,
utxos,
tickets: Vec::new(),
- burn_claims: Vec::new(),
pending: Vec::new(),
orphans: Vec::new(),
mine_reward: MINE_REWARD,
@@ -1982,12 +1789,6 @@ impl Ledger {
Ok(transaction)
}
- pub fn build_burn_claim(&self, burn: Transaction, seen: Vec<BurnSeen>) -> Result<Transaction> {
- let transaction = Transaction::burn_claim(burn, seen)?;
- self.validate_new_transaction(&transaction)?;
- Ok(transaction)
- }
-
pub fn build_mine(&self, recipient: impl Into<String>) -> Result<Transaction> {
let recipient = recipient.into();
validate_address(&recipient, "mine recipient")?;
@@ -2167,15 +1968,13 @@ impl Ledger {
let prev_hash = tip.hash.clone();
let timestamp_ms = timestamp_ms.max(ticket_block_min_timestamp(tip, finalizer_rank)?);
let vdf_seed = vdf_seed_for_child(&prev_hash, height);
- let fee_distribution = self.fee_distribution(&transactions)?;
Ok(PreparedBlock {
height,
prev_hash,
timestamp_ms,
miner: miner.to_string(),
finalizer_mode: FinalizerMode::Ticket,
- reward: fee_distribution.finalizer_reward,
- fee_shares: fee_distribution.fee_shares,
+ reward: fee_reward(&transactions)?,
vdf_rounds: self.vdf_rounds_for_finalizer_rank(finalizer_rank)?,
vdf_seed,
finalizer_rank,
@@ -2209,7 +2008,6 @@ impl Ledger {
let prev_hash = tip.hash.clone();
let timestamp_ms = timestamp_ms.max(tip.timestamp_ms + 1);
let vdf_seed = recovery_vdf_seed_for_child(&prev_hash, height, timestamp_ms);
- let fee_distribution = self.fee_distribution(&transactions)?;
Ok(PreparedBlock {
height,
prev_hash,
@@ -2217,8 +2015,7 @@ impl Ledger {
miner: miner.to_string(),
finalizer_mode: FinalizerMode::Recovery,
finalizer_rank: 0,
- reward: fee_distribution.finalizer_reward,
- fee_shares: fee_distribution.fee_shares,
+ reward: fee_reward(&transactions)?,
vdf_rounds: self.recovery_vdf_rounds()?,
vdf_seed,
leader_ticket: None,
@@ -2278,10 +2075,12 @@ impl Ledger {
self.validate_transaction_terms(tx)?;
apply_transaction(tx, &mut utxos)?;
}
- self.ensure_block_fee_distribution_is_valid(&block)?;
+ if block.reward != fee_reward(&block.transactions)? {
+ bail!("block reward is invalid");
+ }
let mut tickets = self.tickets.clone();
apply_finalizer_ticket_effects(&block, &mut tickets)?;
- credit_reward_outputs(&mut utxos, &block)?;
+ credit_reward_output(&mut utxos, &block)?;
tickets.extend(tickets_created_by_block(&block, &self.launch_profile)?);
let mined_signatures = block
@@ -2291,7 +2090,6 @@ impl Ledger {
.collect::<BTreeSet<_>>();
self.utxos = utxos;
self.tickets = tickets;
- self.update_burn_claims_after_block(&block)?;
self.chain.push(block);
let available = self.utxos.clone();
let pending = std::mem::take(&mut self.pending);
@@ -2344,7 +2142,9 @@ impl Ledger {
if block.compute_hash() != block.hash {
bail!("block hash is invalid");
}
- self.ensure_block_fee_distribution_is_valid(block)?;
+ if block.reward != fee_reward(&block.transactions)? {
+ bail!("block reward is invalid");
+ }
let expected_vdf_rounds = self.expected_vdf_rounds_for_block(block)?;
if block.vdf_rounds != expected_vdf_rounds {
bail!("block VDF rounds are invalid");
@@ -2376,7 +2176,6 @@ impl Ledger {
bail!("block exceeds max block size");
}
ensure_block_has_burn(&block.transactions)?;
- self.ensure_due_burn_claims_are_included(block.height, &block.transactions)?;
match block.finalizer_mode {
FinalizerMode::Ticket => {
let selected_ticket = self
@@ -2498,73 +2297,6 @@ impl Ledger {
self.select_block_transactions_with_required_burn_owner(Some(miner))
}
- fn fee_distribution(&self, transactions: &[Transaction]) -> Result<FeeDistribution> {
- let claimed_burns = self.claimed_burn_attesters_for_fee_distribution(transactions);
- let mut finalizer_reward = 0_u64;
- let mut fee_shares = BTreeMap::new();
-
- for transaction in transactions {
- let fee = transaction.fee();
- if transaction.is_burn() {
- if let Some(attesters) = claimed_burns.get(transaction.signature()) {
- let (claimed_finalizer_reward, claimed_fee_shares) =
- split_claimed_burn_fee(fee, attesters)?;
- finalizer_reward = finalizer_reward
- .checked_add(claimed_finalizer_reward)
- .context("block finalizer reward overflows")?;
- for share in claimed_fee_shares {
- add_fee_share(&mut fee_shares, share)?;
- }
- continue;
- }
- }
-
- finalizer_reward = finalizer_reward
- .checked_add(fee)
- .context("block finalizer reward overflows")?;
- }
-
- Ok(FeeDistribution {
- finalizer_reward,
- fee_shares: fee_shares
- .into_iter()
- .map(|(address, amount)| FeeShare { address, amount })
- .collect(),
- })
- }
-
- fn claimed_burn_attesters_for_fee_distribution(
- &self,
- transactions: &[Transaction],
- ) -> BTreeMap<String, Vec<String>> {
- let mut claimed_burns = self
- .burn_claims
- .iter()
- .map(|claim| (claim.burn.signature().to_string(), claim.attesters.clone()))
- .collect::<BTreeMap<_, _>>();
- for transaction in transactions {
- let Transaction::BurnClaim { burn, seen, .. } = transaction else {
- continue;
- };
- claimed_burns
- .entry(burn.signature().to_string())
- .or_insert_with(|| burn_claim_attesters(seen));
- }
- claimed_burns
- }
-
- fn ensure_block_fee_distribution_is_valid(&self, block: &Block) -> Result<()> {
- validate_fee_shares(&block.fee_shares)?;
- let expected = self.fee_distribution(&block.transactions)?;
- if block.reward != expected.finalizer_reward {
- bail!("block reward is invalid");
- }
- if block.fee_shares != expected.fee_shares {
- bail!("block fee shares are invalid");
- }
- Ok(())
- }
-
fn select_block_transactions_with_required_burn_owner(
&self,
required_burn_owner: Option<&str>,
@@ -2573,17 +2305,6 @@ impl Ledger {
let mut remaining = self.valid_pending_transactions();
let mut selected = Vec::new();
- for tx in self.due_required_claimed_burns(self.tip().height + 1)? {
- remaining.retain(|pending| pending.signature() != tx.signature());
- let mut candidate = selected.clone();
- candidate.push(tx.clone());
- if estimated_block_size_bytes(&candidate)? > self.launch_profile.max_block_bytes {
- bail!("due claimed burns exceed max block size");
- }
- apply_transaction(&tx, &mut utxos)?;
- selected.push(tx);
- }
-
let needs_first_burn = !selected.iter().any(Transaction::is_burn);
let needs_owner_burn = required_burn_owner.is_some_and(|owner| {
!selected
@@ -2622,56 +2343,6 @@ impl Ledger {
Ok(selected)
}
- fn ensure_due_burn_claims_are_included(
- &self,
- block_height: u64,
- transactions: &[Transaction],
- ) -> Result<()> {
- let included_burns = transactions
- .iter()
- .filter(|transaction| transaction.is_burn())
- .map(|transaction| transaction.signature().to_string())
- .collect::<BTreeSet<_>>();
- for burn in self.due_required_claimed_burns(block_height)? {
- if !included_burns.contains(burn.signature()) {
- bail!(
- "block omits due claimed burn transaction {}",
- burn.signature()
- );
- }
- }
- Ok(())
- }
-
- fn due_required_claimed_burns(&self, block_height: u64) -> Result<Vec<Transaction>> {
- let mut claims = self
- .burn_claims
- .iter()
- .filter(|claim| claim.due_height <= block_height)
- .collect::<Vec<_>>();
- claims.sort_by(|left, right| {
- left.due_height
- .cmp(&right.due_height)
- .then_with(|| left.claim_height.cmp(&right.claim_height))
- .then_with(|| left.claim_signature.cmp(&right.claim_signature))
- });
-
- let mut utxos = self.utxos.clone();
- let mut required = Vec::new();
- let mut seen_burns = BTreeSet::new();
- for claim in claims {
- if !seen_burns.insert(claim.burn.signature().to_string()) {
- continue;
- }
- let mut candidate = utxos.clone();
- if apply_transaction(&claim.burn, &mut candidate).is_ok() {
- utxos = candidate;
- required.push(claim.burn.clone());
- }
- }
- Ok(required)
- }
-
fn select_inputs(
&self,
address: &str,
@@ -2821,128 +2492,6 @@ impl Ledger {
bail!("mine transaction difficulty is invalid");
}
}
- Transaction::BurnClaim {
- burn,
- seen,
- signature,
- } => {
- validate_hash(signature, "burn claim signature")?;
- self.validate_burn_claim(burn, seen)?;
- }
- }
- Ok(())
- }
-
- fn validate_burn_claim(&self, burn: &Transaction, seen: &[BurnSeen]) -> Result<()> {
- if !burn.is_burn() {
- bail!("burn claim must reference a burn transaction");
- }
- burn.verify_signature()?;
- self.validate_transaction_terms(burn)?;
- let mut burn_utxos = self.utxos.clone();
- apply_transaction(burn, &mut burn_utxos)?;
- if self
- .chain
- .iter()
- .flat_map(|block| block.transactions.iter())
- .any(|transaction| transaction.signature() == burn.signature())
- {
- bail!("burn claim references an already confirmed burn");
- }
- if burn.serialized_size_bytes()? > self.launch_profile.max_block_bytes {
- bail!("burn claim references a burn larger than max block size");
- }
-
- let recent_finalizers = self.recent_finalizers_for_burn_claim();
- let required_quorum = BURN_CLAIM_SEEN_QUORUM.min(recent_finalizers.len()).max(1);
- let mut unique_seen_signers = BTreeSet::new();
- for attestation in seen {
- if attestation.burn_signature != burn.signature() {
- bail!("burn seen attestation references a different burn");
- }
- let Some(block) = recent_finalizers.get(&attestation.signer) else {
- bail!("burn seen signer is not a recent finalizer");
- };
- if attestation.seen_height != block.height || attestation.seen_block_hash != block.hash
- {
- bail!("burn seen attestation does not match recent finalizer block");
- }
- if !unique_seen_signers.insert(attestation.signer.as_str()) {
- bail!("burn claim contains duplicate finalizer attestation");
- }
- }
- if unique_seen_signers.len() < required_quorum {
- bail!(
- "burn claim has {} finalizer attestations but requires {}",
- unique_seen_signers.len(),
- required_quorum
- );
- }
- Ok(())
- }
-
- fn recent_finalizers_for_burn_claim(&self) -> BTreeMap<String, Block> {
- let min_height = self
- .tip()
- .height
- .saturating_sub(BURN_CLAIM_SEEN_WINDOW_BLOCKS)
- .saturating_add(1);
- let mut finalizers = BTreeMap::new();
- for block in self.chain.iter().rev() {
- if block.height == 0 || block.height < min_height {
- break;
- }
- finalizers
- .entry(block.miner.clone())
- .or_insert_with(|| block.clone());
- }
- finalizers
- }
-
- fn update_burn_claims_after_block(&mut self, block: &Block) -> Result<()> {
- let included_burns = block
- .transactions
- .iter()
- .filter(|transaction| transaction.is_burn())
- .map(|transaction| transaction.signature().to_string())
- .collect::<BTreeSet<_>>();
- self.burn_claims.retain(|claim| {
- !included_burns.contains(claim.burn.signature())
- && claim.expires_at_height > block.height
- && claim.due_height > block.height
- });
-
- let mut active_burns = self
- .burn_claims
- .iter()
- .map(|claim| claim.burn.signature().to_string())
- .collect::<BTreeSet<_>>();
- for transaction in &block.transactions {
- let Transaction::BurnClaim {
- burn,
- seen,
- signature,
- } = transaction
- else {
- continue;
- };
- if included_burns.contains(burn.signature()) || active_burns.contains(burn.signature())
- {
- continue;
- }
- active_burns.insert(burn.signature().to_string());
- let due_height = block
- .height
- .checked_add(BURN_CLAIM_INCLUDE_WITHIN_BLOCKS)
- .context("burn claim due height overflows")?;
- self.burn_claims.push(BurnClaimState {
- burn: burn.as_ref().clone(),
- claim_signature: signature.clone(),
- claim_height: block.height,
- attesters: burn_claim_attesters(seen),
- due_height,
- expires_at_height: due_height,
- });
}
Ok(())
}
@@ -3435,20 +2984,6 @@ fn validate_transaction_outputs(outputs: &[TxOutput]) -> Result<()> {
Ok(())
}
-fn validate_fee_shares(shares: &[FeeShare]) -> Result<()> {
- let mut seen = BTreeSet::new();
- for share in shares {
- validate_address(&share.address, "fee share recipient")?;
- if share.amount == 0 {
- bail!("fee share amount must be positive");
- }
- if !seen.insert(share.address.as_str()) {
- bail!("duplicate fee share recipient");
- }
- }
- Ok(())
-}
-
fn validate_genesis_burn_transaction(transaction: &Transaction) -> Result<()> {
let Transaction::Burn {
inputs,
@@ -3556,27 +3091,9 @@ fn canonical_transaction_size_bytes(transaction: &Transaction) -> usize {
.unwrap_or(0)
+ hash_size_bytes(signature)
}
- Transaction::BurnClaim {
- burn,
- seen,
- signature,
- } => {
- 1 + canonical_transaction_size_bytes(burn)
- + compact_len(seen.len() as u128)
- + seen.iter().map(burn_seen_size_bytes).sum::<usize>()
- + hash_size_bytes(signature)
- }
}
}
-fn burn_seen_size_bytes(seen: &BurnSeen) -> usize {
- hash_size_bytes(&seen.burn_signature)
- + compact_len(u128::from(seen.seen_height))
- + hash_size_bytes(&seen.seen_block_hash)
- + address_size_bytes(&seen.signer)
- + signature_size_bytes(&seen.signature)
-}
-
fn compact_inputs_size_bytes(inputs: &[TxInput]) -> usize {
inputs
.iter()
@@ -3649,7 +3166,6 @@ fn estimated_block_size_bytes(transactions: &[Transaction]) -> Result<usize> {
public_key: "f".repeat(64),
signature: "f".repeat(128),
}),
- fee_shares: Vec::new(),
transactions: transactions.to_vec(),
hash: "f".repeat(64),
};
@@ -3733,7 +3249,6 @@ fn apply_transaction(
);
return Ok(());
}
- Transaction::BurnClaim { .. } => return Ok(()),
Transaction::Transfer { .. } | Transaction::Burn { .. } => {}
}
ensure_single_input_owner(transaction)?;
@@ -3749,9 +3264,7 @@ fn apply_transaction(
.context("transaction outputs plus fee overflow")?
.checked_add(match transaction {
Transaction::Burn { amount, .. } => *amount,
- Transaction::Transfer { .. }
- | Transaction::Mine { .. }
- | Transaction::BurnClaim { .. } => 0,
+ Transaction::Transfer { .. } | Transaction::Mine { .. } => 0,
})
.context("transaction outputs plus burn overflow")?;
if input_total != required {
@@ -3770,60 +3283,10 @@ fn apply_transaction(
Ok(())
}
-#[derive(Clone, Debug, Eq, PartialEq)]
-struct FeeDistribution {
- finalizer_reward: Amount,
- fee_shares: Vec<FeeShare>,
-}
-
-fn split_claimed_burn_fee(fee: Amount, attesters: &[String]) -> Result<(Amount, Vec<FeeShare>)> {
- if fee == 0 || attesters.is_empty() {
- return Ok((fee, Vec::new()));
- }
-
- let attester_total = u128::from(fee)
- .checked_mul(u128::from(CLAIMED_BURN_ATTESTER_FEE_SHARE_BPS))
- .context("claimed burn attester fee share overflows")?
- / u128::from(FEE_SHARE_BPS_DENOMINATOR);
- let attester_total = attester_total as Amount;
- let finalizer_reward = fee
- .checked_sub(attester_total)
- .context("claimed burn finalizer fee share underflows")?;
- if attester_total == 0 {
- return Ok((finalizer_reward, Vec::new()));
- }
-
- let attester_count = attesters.len() as Amount;
- let base_share = attester_total / attester_count;
- let remainder = attester_total % attester_count;
- let shares = attesters
- .iter()
- .enumerate()
- .filter_map(|(index, address)| {
- let amount = base_share + u64::from((index as Amount) < remainder);
- (amount > 0).then(|| FeeShare {
- address: address.clone(),
- amount,
- })
- })
- .collect();
- Ok((finalizer_reward, shares))
-}
-
-fn add_fee_share(shares: &mut BTreeMap<String, Amount>, share: FeeShare) -> Result<()> {
- let entry = shares.entry(share.address).or_insert(0);
- *entry = entry
- .checked_add(share.amount)
- .context("fee share amount overflows")?;
- Ok(())
-}
-
-fn burn_claim_attesters(seen: &[BurnSeen]) -> Vec<String> {
- seen.iter()
- .map(|attestation| attestation.signer.clone())
- .collect::<BTreeSet<_>>()
- .into_iter()
- .collect()
+fn fee_reward(transactions: &[Transaction]) -> Result<Amount> {
+ transactions.iter().try_fold(0_u64, |total, tx| {
+ total.checked_add(tx.fee()).context("block fees overflow")
+ })
}
fn spend_inputs(
@@ -3860,10 +3323,7 @@ fn transaction_has_missing_inputs(
}
fn ensure_single_input_owner(transaction: &Transaction) -> Result<()> {
- if matches!(
- transaction,
- Transaction::Mine { .. } | Transaction::BurnClaim { .. }
- ) {
+ if matches!(transaction, Transaction::Mine { .. }) {
return Ok(());
}
let Some(first) = transaction.inputs().first() else {
@@ -3879,34 +3339,16 @@ fn ensure_single_input_owner(transaction: &Transaction) -> Result<()> {
Ok(())
}
-fn credit_reward_outputs(utxos: &mut BTreeMap<OutPoint, TxOutput>, block: &Block) -> Result<()> {
- let mut outputs = Vec::new();
- if block.reward > 0 {
- outputs.push((
- reward_outpoint(&block.hash),
- TxOutput {
- address: block.miner.clone(),
- amount: block.reward,
- },
- ));
- }
- outputs.extend(block.fee_shares.iter().enumerate().map(|(index, share)| {
- (
- fee_share_outpoint(&block.hash, index),
- TxOutput {
- address: share.address.clone(),
- amount: share.amount,
- },
- )
- }));
- let reward_outputs = outputs
- .iter()
- .map(|(_, output)| output.clone())
- .collect::<Vec<_>>();
- ensure_outputs_do_not_overflow(utxos, &reward_outputs)?;
- for (outpoint, output) in outputs {
- utxos.insert(outpoint, output);
+fn credit_reward_output(utxos: &mut BTreeMap<OutPoint, TxOutput>, block: &Block) -> Result<()> {
+ if block.reward == 0 {
+ return Ok(());
}
+ let output = TxOutput {
+ address: block.miner.clone(),
+ amount: block.reward,
+ };
+ ensure_outputs_do_not_overflow(utxos, std::slice::from_ref(&output))?;
+ utxos.insert(reward_outpoint(&block.hash), output);
Ok(())
}
@@ -3953,7 +3395,6 @@ fn build_genesis_block(
vdf_rounds: 0,
vdf_output,
leader_proof: None,
- fee_shares: Vec::new(),
transactions,
hash: String::new(),
};
@@ -3972,14 +3413,12 @@ fn utxos_after_genesis(
validate_genesis_burn_transaction(transaction)?;
apply_transaction(transaction, &mut utxos)?;
}
- Transaction::Transfer { .. }
- | Transaction::Mine { .. }
- | Transaction::BurnClaim { .. } => {
+ Transaction::Transfer { .. } | Transaction::Mine { .. } => {
bail!("genesis only supports burn transactions")
}
}
}
- credit_reward_outputs(&mut utxos, genesis)?;
+ credit_reward_output(&mut utxos, genesis)?;
Ok(utxos)
}
@@ -4024,13 +3463,6 @@ fn reward_outpoint(block_hash: &str) -> OutPoint {
}
}
-fn fee_share_outpoint(block_hash: &str, index: usize) -> OutPoint {
- OutPoint {
- txid: block_hash.to_string(),
- index: u32::MAX - 1 - index as u32,
- }
-}
-
fn validate_genesis_block(block: &Block) -> Result<()> {
if block.height != 0 {
bail!("genesis block height must be 0");
@@ -4053,9 +3485,6 @@ fn validate_genesis_block(block: &Block) -> Result<()> {
if block.leader_proof.is_some() {
bail!("genesis block must not carry a leader proof");
}
- if !block.fee_shares.is_empty() {
- bail!("genesis block must not carry fee shares");
- }
if block.compute_hash() != block.hash {
bail!("genesis block hash is invalid");
}
@@ -4070,9 +3499,7 @@ fn genesis_miner(
.iter()
.filter_map(|transaction| match transaction {
Transaction::Burn { inputs, .. } => inputs.first().map(|input| input.owner.as_str()),
- Transaction::Transfer { .. }
- | Transaction::Mine { .. }
- | Transaction::BurnClaim { .. } => None,
+ Transaction::Transfer { .. } | Transaction::Mine { .. } => None,
})
.find(|from| genesis_allocations.contains_key(*from))
.or_else(|| genesis_allocations.keys().next().map(String::as_str))
@@ -4397,7 +3824,6 @@ mod tests {
vdf_rounds: 1,
vdf_output: String::new(),
leader_proof: None,
- fee_shares: Vec::new(),
transactions: Vec::new(),
hash: String::new(),
}
@@ -4426,22 +3852,6 @@ mod tests {
panic!("expected to find mine proof");
}
- fn ledger_with_finalizers_and_burner(finalizers: &[Wallet], burner: &Wallet) -> Ledger {
- let mut allocations = BTreeMap::new();
- for wallet in finalizers {
- allocations.insert(wallet.address().to_string(), 100 * MICRO_IUNA);
- }
- allocations.insert(burner.address().to_string(), 100 * MICRO_IUNA);
- let genesis_burns = finalizers
- .iter()
- .map(|wallet| GenesisBurn {
- from: wallet.address().to_string(),
- amount: MICRO_IUNA,
- })
- .collect::<Vec<_>>();
- Ledger::new_with_genesis_burns(allocations, genesis_burns, 1).unwrap()
- }
-
fn wallet_for_address<'a>(wallets: &'a [Wallet], address: &str) -> &'a Wallet {
wallets
.iter()
@@ -4449,88 +3859,6 @@ mod tests {
.unwrap_or_else(|| panic!("missing wallet for address {address}"))
}
- fn recent_unique_finalizer_count(ledger: &Ledger) -> usize {
- ledger.recent_finalizers_for_burn_claim().len()
- }
-
- fn mine_next_preverified_burn_block(ledger: &mut Ledger, wallets: &[Wallet]) -> Block {
- let recent_finalizers = ledger.recent_finalizers_for_burn_claim();
- let wallet = wallets
- .iter()
- .find(|wallet| {
- !recent_finalizers.contains_key(wallet.address())
- && ledger
- .finalizer_rank_for_next_block(wallet.address())
- .is_some()
- })
- .or_else(|| {
- wallets.iter().find(|wallet| {
- ledger
- .finalizer_rank_for_next_block(wallet.address())
- .is_some()
- })
- })
- .expect("expected an eligible finalizer wallet");
- let burn = ledger.build_burn(wallet, TEST_BURN_AMOUNT, 0).unwrap();
- ledger.submit_transaction(burn).unwrap();
- let prepared = ledger
- .prepare_next_block(wallet.address(), ledger.tip().timestamp_ms + 1)
- .unwrap();
- let block = prepared.finish(wallet, "preverified-vdf".to_string());
- ledger
- .apply_preverified_block_at(block.clone(), u64::MAX)
- .unwrap();
- block
- }
-
- fn mine_until_recent_finalizers(
- ledger: &mut Ledger,
- wallets: &[Wallet],
- required_unique: usize,
- ) {
- for _ in 0..30 {
- if recent_unique_finalizer_count(ledger) >= required_unique {
- return;
- }
- mine_next_preverified_burn_block(ledger, wallets);
- }
- panic!("could not mine {required_unique} recent unique finalizers");
- }
-
- fn recent_burn_seen_attestations(
- ledger: &Ledger,
- wallets: &[Wallet],
- burn: &Transaction,
- count: usize,
- ) -> Vec<BurnSeen> {
- let mut seen = Vec::new();
- let mut signers = BTreeSet::new();
- for block in ledger.chain().iter().rev().filter(|block| block.height > 0) {
- if !signers.insert(block.miner.clone()) {
- continue;
- }
- let wallet = wallet_for_address(wallets, &block.miner);
- seen.push(wallet.burn_seen(burn.signature(), block.height, block.hash.clone()));
- if seen.len() == count {
- break;
- }
- }
- assert_eq!(seen.len(), count);
- seen
- }
-
- fn confirm_burn_claim(
- ledger: &mut Ledger,
- wallets: &[Wallet],
- burn: Transaction,
- ) -> Transaction {
- let seen = recent_burn_seen_attestations(ledger, wallets, &burn, BURN_CLAIM_SEEN_QUORUM);
- let claim = ledger.build_burn_claim(burn, seen).unwrap();
- ledger.submit_transaction(claim.clone()).unwrap();
- mine_next_preverified_burn_block(ledger, wallets);
- claim
- }
-
fn transfer_with_extra_zero_outputs(
ledger: &Ledger,
wallet: &Wallet,
@@ -5318,7 +4646,6 @@ mod tests {
public_key: "alice".to_string(),
signature: "signature".to_string(),
}),
- fee_shares: Vec::new(),
transactions: Vec::new(),
});
@@ -5389,377 +4716,18 @@ mod tests {
}
#[test]
- fn burn_claim_requires_recent_finalizer_quorum() {
- let finalizers = (0..5)
- .map(|index| Wallet::from_seed(&format!("burn-claim-quorum-finalizer-{index}")))
- .collect::<Vec<_>>();
- let burner = Wallet::from_seed("burn-claim-quorum-burner");
- let mut ledger = ledger_with_finalizers_and_burner(&finalizers, &burner);
- mine_until_recent_finalizers(&mut ledger, &finalizers, BURN_CLAIM_SEEN_QUORUM);
- assert!(recent_unique_finalizer_count(&ledger) >= BURN_CLAIM_SEEN_QUORUM);
-
- let burn = ledger.build_burn(&burner, TEST_BURN_AMOUNT, 0).unwrap();
- let seen =
- recent_burn_seen_attestations(&ledger, &finalizers, &burn, BURN_CLAIM_SEEN_QUORUM - 1);
-
- let error = ledger.build_burn_claim(burn, seen).unwrap_err();
-
- assert!(format!("{error:#}").contains("finalizer attestations"));
- }
-
- #[test]
- fn burn_claim_rejects_invalid_seen_signature() {
- let finalizers = (0..5)
- .map(|index| Wallet::from_seed(&format!("burn-claim-signature-finalizer-{index}")))
- .collect::<Vec<_>>();
- let burner = Wallet::from_seed("burn-claim-signature-burner");
- let mut ledger = ledger_with_finalizers_and_burner(&finalizers, &burner);
- mine_until_recent_finalizers(&mut ledger, &finalizers, BURN_CLAIM_SEEN_QUORUM);
-
- let burn = ledger.build_burn(&burner, TEST_BURN_AMOUNT, 0).unwrap();
- let mut seen =
- recent_burn_seen_attestations(&ledger, &finalizers, &burn, BURN_CLAIM_SEEN_QUORUM);
- seen[0].signature = "00".repeat(SIGNATURE_BYTES);
- let claim = Transaction::burn_claim(burn, seen).unwrap();
-
- let error = ledger.submit_transaction(claim).unwrap_err();
-
- assert!(format!("{error:#}").contains("burn seen signature"));
- }
-
- #[test]
- fn due_burn_claim_makes_block_omitting_burn_invalid() {
- let finalizers = (0..5)
- .map(|index| Wallet::from_seed(&format!("burn-claim-due-finalizer-{index}")))
- .collect::<Vec<_>>();
- let burner = Wallet::from_seed("burn-claim-due-burner");
- let mut ledger = ledger_with_finalizers_and_burner(&finalizers, &burner);
- mine_until_recent_finalizers(&mut ledger, &finalizers, BURN_CLAIM_SEEN_QUORUM);
- let burn = ledger.build_burn(&burner, TEST_BURN_AMOUNT, 0).unwrap();
- let burn_signature = burn.signature().to_string();
-
- confirm_burn_claim(&mut ledger, &finalizers, burn);
- let claim_height = ledger.height();
- while ledger.height() + 1 < claim_height + BURN_CLAIM_INCLUDE_WITHIN_BLOCKS {
- mine_next_preverified_burn_block(&mut ledger, &finalizers);
- }
-
- let leader = ledger.expected_leader_for_next_block().unwrap();
- let wallet = wallet_for_address(&finalizers, &leader);
- let filler_burn = ledger.build_burn(wallet, TEST_BURN_AMOUNT, 0).unwrap();
- ledger.submit_transaction(filler_burn.clone()).unwrap();
- let mut prepared = ledger
- .prepare_next_block(wallet.address(), ledger.tip().timestamp_ms + 1)
- .unwrap();
- assert!(
- prepared
- .transactions
- .iter()
- .any(|transaction| transaction.signature() == burn_signature)
- );
- prepared
- .transactions
- .retain(|transaction| transaction.signature() != burn_signature);
- assert!(
- prepared
- .transactions
- .iter()
- .any(|transaction| transaction.signature() == filler_burn.signature())
- );
- let fee_distribution = ledger.fee_distribution(&prepared.transactions).unwrap();
- prepared.reward = fee_distribution.finalizer_reward;
- prepared.fee_shares = fee_distribution.fee_shares;
- let block = prepared.finish(wallet, "preverified-vdf".to_string());
-
- let error = ledger
- .apply_preverified_block_at(block, u64::MAX)
- .unwrap_err();
-
- assert!(format!("{error:#}").contains("omits due claimed burn"));
- }
-
- #[test]
- fn block_selection_includes_due_claimed_burn() {
- let finalizers = (0..5)
- .map(|index| Wallet::from_seed(&format!("burn-claim-select-finalizer-{index}")))
- .collect::<Vec<_>>();
- let burner = Wallet::from_seed("burn-claim-select-burner");
- let mut ledger = ledger_with_finalizers_and_burner(&finalizers, &burner);
- mine_until_recent_finalizers(&mut ledger, &finalizers, BURN_CLAIM_SEEN_QUORUM);
- let burn = ledger.build_burn(&burner, TEST_BURN_AMOUNT, 0).unwrap();
- let burn_signature = burn.signature().to_string();
-
- confirm_burn_claim(&mut ledger, &finalizers, burn);
- let claim_height = ledger.height();
- while ledger.height() + 1 < claim_height + BURN_CLAIM_INCLUDE_WITHIN_BLOCKS {
- mine_next_preverified_burn_block(&mut ledger, &finalizers);
- }
-
- let leader = ledger.expected_leader_for_next_block().unwrap();
- let wallet = wallet_for_address(&finalizers, &leader);
- let prepared = ledger
- .prepare_next_block(wallet.address(), ledger.tip().timestamp_ms + 1)
- .unwrap();
-
- assert!(
- prepared
- .transactions
- .iter()
- .any(|transaction| transaction.signature() == burn_signature)
- );
- }
-
- #[test]
- fn normal_burn_fee_goes_to_block_finalizer_without_fee_shares() {
- let finalizers = (0..5)
- .map(|index| Wallet::from_seed(&format!("burn-fee-normal-finalizer-{index}")))
- .collect::<Vec<_>>();
- let burner = Wallet::from_seed("burn-fee-normal-burner");
- let mut ledger = ledger_with_finalizers_and_burner(&finalizers, &burner);
- mine_until_recent_finalizers(&mut ledger, &finalizers, BURN_CLAIM_SEEN_QUORUM);
+ fn burn_fee_goes_to_block_finalizer() {
+ let alice = Wallet::from_seed("burn-fee-finalizer-alice");
+ let mut ledger = ledger_with_allocation(&alice, MICRO_IUNA);
let burn_fee = 12;
let burn = ledger
- .build_burn(&burner, TEST_BURN_AMOUNT, burn_fee)
+ .build_burn(&alice, TEST_BURN_AMOUNT, burn_fee)
.unwrap();
ledger.submit_transaction(burn).unwrap();
- let leader = ledger.expected_leader_for_next_block().unwrap();
- let wallet = wallet_for_address(&finalizers, &leader);
- let prepared = ledger
- .prepare_next_block(wallet.address(), ledger.tip().timestamp_ms + 1)
- .unwrap();
+ let prepared = ledger.prepare_next_block(alice.address(), 1).unwrap();
assert_eq!(prepared.reward, burn_fee);
- assert!(prepared.fee_shares.is_empty());
- }
-
- #[test]
- fn claimed_burn_fee_is_split_between_finalizer_and_claim_attesters() {
- let finalizers = (0..5)
- .map(|index| Wallet::from_seed(&format!("burn-fee-claim-finalizer-{index}")))
- .collect::<Vec<_>>();
- let burner = Wallet::from_seed("burn-fee-claim-burner");
- let mut ledger = ledger_with_finalizers_and_burner(&finalizers, &burner);
- mine_until_recent_finalizers(&mut ledger, &finalizers, BURN_CLAIM_SEEN_QUORUM);
- let burn_fee = 12;
- let burn = ledger
- .build_burn(&burner, TEST_BURN_AMOUNT, burn_fee)
- .unwrap();
- let burn_signature = burn.signature().to_string();
-
- let claim = confirm_burn_claim(&mut ledger, &finalizers, burn);
- let Transaction::BurnClaim { seen, .. } = &claim else {
- panic!("expected burn claim");
- };
- let expected_attesters = burn_claim_attesters(seen);
- let claim_height = ledger.height();
- while ledger.height() + 1 < claim_height + BURN_CLAIM_INCLUDE_WITHIN_BLOCKS {
- mine_next_preverified_burn_block(&mut ledger, &finalizers);
- }
-
- let leader = ledger.expected_leader_for_next_block().unwrap();
- let wallet = wallet_for_address(&finalizers, &leader);
- let prepared = ledger
- .prepare_next_block(wallet.address(), ledger.tip().timestamp_ms + 1)
- .unwrap();
- assert!(
- prepared
- .transactions
- .iter()
- .any(|transaction| transaction.signature() == burn_signature)
- );
- assert_eq!(prepared.reward, burn_fee / 2);
- assert_eq!(
- prepared.fee_shares,
- expected_attesters
- .iter()
- .map(|address| FeeShare {
- address: address.clone(),
- amount: burn_fee / 2 / BURN_CLAIM_SEEN_QUORUM as u64,
- })
- .collect::<Vec<_>>()
- );
-
- let mut expected_deltas = BTreeMap::new();
- expected_deltas.insert(wallet.address().to_string(), prepared.reward);
- for share in &prepared.fee_shares {
- let entry = expected_deltas
- .entry(share.address.clone())
- .or_insert(0_u64);
- *entry = entry.checked_add(share.amount).unwrap();
- }
- let before = expected_deltas
- .keys()
- .map(|address| (address.clone(), ledger.balance_of(address)))
- .collect::<BTreeMap<_, _>>();
- let block = prepared.finish(wallet, "preverified-vdf".to_string());
- ledger.apply_preverified_block_at(block, u64::MAX).unwrap();
-
- for (address, delta) in expected_deltas {
- assert_eq!(
- ledger.balance_of(&address),
- before.get(&address).copied().unwrap_or_default() + delta
- );
- }
- }
-
- #[test]
- fn burn_claim_in_same_block_splits_burn_fee() {
- let finalizers = (0..5)
- .map(|index| Wallet::from_seed(&format!("burn-fee-same-block-finalizer-{index}")))
- .collect::<Vec<_>>();
- let burner = Wallet::from_seed("burn-fee-same-block-burner");
- let mut ledger = ledger_with_finalizers_and_burner(&finalizers, &burner);
- mine_until_recent_finalizers(&mut ledger, &finalizers, BURN_CLAIM_SEEN_QUORUM);
- let burn_fee = 11;
- let burn = ledger
- .build_burn(&burner, TEST_BURN_AMOUNT, burn_fee)
- .unwrap();
- let burn_signature = burn.signature().to_string();
- let seen =
- recent_burn_seen_attestations(&ledger, &finalizers, &burn, BURN_CLAIM_SEEN_QUORUM);
- let expected_attesters = burn_claim_attesters(&seen);
- let claim = ledger.build_burn_claim(burn.clone(), seen).unwrap();
- let claim_signature = claim.signature().to_string();
- ledger.submit_transaction(burn).unwrap();
- ledger.submit_transaction(claim).unwrap();
-
- let leader = ledger.expected_leader_for_next_block().unwrap();
- let wallet = wallet_for_address(&finalizers, &leader);
- let prepared = ledger
- .prepare_next_block(wallet.address(), ledger.tip().timestamp_ms + 1)
- .unwrap();
-
- assert!(
- prepared
- .transactions
- .iter()
- .any(|transaction| transaction.signature() == burn_signature)
- );
- assert!(
- prepared
- .transactions
- .iter()
- .any(|transaction| transaction.signature() == claim_signature)
- );
- let (expected_reward, expected_fee_shares) =
- split_claimed_burn_fee(burn_fee, &expected_attesters).unwrap();
- assert_eq!(prepared.reward, expected_reward);
- assert_eq!(prepared.fee_shares, expected_fee_shares);
- }
-
- #[test]
- fn claimed_burn_fee_split_handles_remainders_and_tiny_fees() {
- let attesters = vec!["alice".to_string(), "bob".to_string(), "carol".to_string()];
-
- let (reward, shares) = split_claimed_burn_fee(11, &attesters).unwrap();
- assert_eq!(reward, 6);
- assert_eq!(
- shares,
- vec![
- FeeShare {
- address: "alice".to_string(),
- amount: 2,
- },
- FeeShare {
- address: "bob".to_string(),
- amount: 2,
- },
- FeeShare {
- address: "carol".to_string(),
- amount: 1,
- },
- ]
- );
-
- let (reward, shares) = split_claimed_burn_fee(1, &attesters).unwrap();
- assert_eq!(reward, 1);
- assert!(shares.is_empty());
-
- let (reward, shares) = split_claimed_burn_fee(2, &attesters).unwrap();
- assert_eq!(reward, 1);
- assert_eq!(
- shares,
- vec![FeeShare {
- address: "alice".to_string(),
- amount: 1,
- }]
- );
- }
-
- #[test]
- fn claimed_burn_block_rejects_invalid_fee_shares() {
- let finalizers = (0..5)
- .map(|index| Wallet::from_seed(&format!("burn-fee-invalid-finalizer-{index}")))
- .collect::<Vec<_>>();
- let burner = Wallet::from_seed("burn-fee-invalid-burner");
- let mut ledger = ledger_with_finalizers_and_burner(&finalizers, &burner);
- mine_until_recent_finalizers(&mut ledger, &finalizers, BURN_CLAIM_SEEN_QUORUM);
- let burn = ledger.build_burn(&burner, TEST_BURN_AMOUNT, 12).unwrap();
-
- confirm_burn_claim(&mut ledger, &finalizers, burn);
- let claim_height = ledger.height();
- while ledger.height() + 1 < claim_height + BURN_CLAIM_INCLUDE_WITHIN_BLOCKS {
- mine_next_preverified_burn_block(&mut ledger, &finalizers);
- }
-
- let leader = ledger.expected_leader_for_next_block().unwrap();
- let wallet = wallet_for_address(&finalizers, &leader);
- let prepared = ledger
- .prepare_next_block(wallet.address(), ledger.tip().timestamp_ms + 1)
- .unwrap();
- assert!(!prepared.fee_shares.is_empty());
- let mut block = prepared.finish(wallet, "preverified-vdf".to_string());
- block.fee_shares.clear();
- block.hash = block.compute_hash();
-
- let error = ledger
- .apply_preverified_block_at(block, u64::MAX)
- .unwrap_err();
-
- assert!(format!("{error:#}").contains("fee shares"));
- }
-
- #[test]
- fn claimed_burn_is_not_required_after_it_becomes_invalid() {
- let finalizers = (0..5)
- .map(|index| Wallet::from_seed(&format!("burn-claim-invalid-finalizer-{index}")))
- .collect::<Vec<_>>();
- let burner = Wallet::from_seed("burn-claim-invalid-burner");
- let recipient = Wallet::from_seed("burn-claim-invalid-recipient");
- let mut ledger = ledger_with_finalizers_and_burner(&finalizers, &burner);
- mine_until_recent_finalizers(&mut ledger, &finalizers, BURN_CLAIM_SEEN_QUORUM);
- let burn = ledger.build_burn(&burner, TEST_BURN_AMOUNT, 0).unwrap();
- let burn_signature = burn.signature().to_string();
- let burn_input = burn.inputs().first().unwrap().outpoint.clone();
-
- confirm_burn_claim(&mut ledger, &finalizers, burn);
- let claim_height = ledger.height();
- let spend = ledger
- .build_transfer_with_inputs(&burner, recipient.address(), 1, 0, &[burn_input])
- .unwrap();
- ledger.submit_transaction(spend).unwrap();
- mine_next_preverified_burn_block(&mut ledger, &finalizers);
- while ledger.height() + 1 < claim_height + BURN_CLAIM_INCLUDE_WITHIN_BLOCKS {
- mine_next_preverified_burn_block(&mut ledger, &finalizers);
- }
-
- let leader = ledger.expected_leader_for_next_block().unwrap();
- let wallet = wallet_for_address(&finalizers, &leader);
- let filler_burn = ledger.build_burn(wallet, TEST_BURN_AMOUNT, 0).unwrap();
- ledger.submit_transaction(filler_burn).unwrap();
- let block = ledger
- .prepare_next_block(wallet.address(), ledger.tip().timestamp_ms + 1)
- .unwrap()
- .finish(wallet, "preverified-vdf".to_string());
-
- assert!(
- !block
- .transactions
- .iter()
- .any(|transaction| transaction.signature() == burn_signature)
- );
- ledger.apply_preverified_block_at(block, u64::MAX).unwrap();
}
#[test]
diff --git a/tests/iuna.rs b/tests/iuna.rs
@@ -2386,8 +2386,7 @@ fn fork_choice_preflight_rejects_invalid_fork_before_vrf_scoring() {
match transaction {
iuna::domain::Transaction::Burn { signature, .. }
| iuna::domain::Transaction::Transfer { signature, .. }
- | iuna::domain::Transaction::Mine { signature, .. }
- | iuna::domain::Transaction::BurnClaim { signature, .. } => signature.push_str("00"),
+ | iuna::domain::Transaction::Mine { signature, .. } => signature.push_str("00"),
}
}
diff --git a/tests/properties.rs b/tests/properties.rs
@@ -172,17 +172,11 @@ fn expected_confirmed_supply(snapshot: &ChainSnapshot) -> Amount {
.checked_add(MINE_REWARD)
.expect("mine output does not overflow supply");
}
- Transaction::BurnClaim { .. } => {}
}
}
supply = supply
.checked_add(block.reward)
.expect("block reward does not overflow supply");
- for share in &block.fee_shares {
- supply = supply
- .checked_add(share.amount)
- .expect("fee share does not overflow supply");
- }
}
supply
@@ -221,19 +215,8 @@ fn reference_balances(snapshot: &ChainSnapshot) -> BTreeMap<String, Amount> {
.expect("reference block fees do not overflow");
}
- let fee_shares = block.fee_shares.iter().fold(0_u64, |total, share| {
- total
- .checked_add(share.amount)
- .expect("reference fee shares do not overflow")
- });
if block.height > 0 {
- assert_eq!(
- block
- .reward
- .checked_add(fee_shares)
- .expect("reference reward and fee shares do not overflow"),
- block_fees
- );
+ assert_eq!(block.reward, block_fees);
}
if block.reward > 0 {
let replaced = utxos.insert(
@@ -248,19 +231,6 @@ fn reference_balances(snapshot: &ChainSnapshot) -> BTreeMap<String, Amount> {
);
assert!(replaced.is_none(), "duplicate reference reward output");
}
- for (index, share) in block.fee_shares.iter().enumerate() {
- let replaced = utxos.insert(
- OutPoint {
- txid: block.hash.clone(),
- index: u32::MAX - 1 - index as u32,
- },
- TxOutput {
- address: share.address.clone(),
- amount: share.amount,
- },
- );
- assert!(replaced.is_none(), "duplicate reference fee share output");
- }
}
balances_from_reference_utxos(&utxos)
@@ -325,9 +295,7 @@ fn apply_reference_transaction(
});
let burn_amount = match transaction {
Transaction::Burn { amount, .. } => *amount,
- Transaction::Transfer { .. } | Transaction::Mine { .. } | Transaction::BurnClaim { .. } => {
- 0
- }
+ Transaction::Transfer { .. } | Transaction::Mine { .. } => 0,
};
let required = output_total
.checked_add(transaction.fee())
@@ -359,7 +327,7 @@ fn insert_reference_outputs(
fn reference_inputs(transaction: &Transaction) -> &[TxInput] {
match transaction {
Transaction::Transfer { inputs, .. } | Transaction::Burn { inputs, .. } => inputs,
- Transaction::Mine { .. } | Transaction::BurnClaim { .. } => &[],
+ Transaction::Mine { .. } => &[],
}
}
@@ -371,7 +339,6 @@ fn reference_outputs(transaction: &Transaction) -> Vec<TxOutput> {
address: recipient.clone(),
amount: MINE_REWARD,
}],
- Transaction::BurnClaim { .. } => Vec::new(),
}
}
@@ -911,8 +878,7 @@ fn generated_snapshot_tampering_is_rejected() {
match transaction {
Transaction::Transfer { signature, .. }
| Transaction::Burn { signature, .. }
- | Transaction::Mine { signature, .. }
- | Transaction::BurnClaim { signature, .. } => signature.push_str("00"),
+ | Transaction::Mine { signature, .. } => signature.push_str("00"),
}
}
assert!(Ledger::from_snapshot(mutated_transaction).is_err());
diff --git a/www/assets/iuna-ui.js b/www/assets/iuna-ui.js
@@ -1171,14 +1171,6 @@ window.iunaApp = function iunaApp() {
return Array.isArray(this.blockchainMetrics?.charts) ? this.blockchainMetrics.charts : [];
},
- metricsChainCharts() {
- return this.metricsCharts().filter((chart) => chart.section !== "burnClaims");
- },
-
- metricsBurnClaimCharts() {
- return this.metricsCharts().filter((chart) => chart.section === "burnClaims");
- },
-
metricsLatest() {
return this.blockchainMetrics?.latest || {};
},