commit 99aac16025652612dc304d505c41aa4ae20c2068
parent da0d834384b507da38af0c37e677e45db2a6734b
Author: Joris Hartog <jorishartog@hotmail.com>
Date: Mon, 17 Aug 2026 18:51:42 +0200
Fix automatic burn fees after activation
Diffstat:
2 files changed, 82 insertions(+), 1 deletion(-)
diff --git a/src/app/helpers.rs b/src/app/helpers.rs
@@ -18,7 +18,7 @@ pub(super) fn converge_fee_by_byte(
fee_per_byte: Amount,
mut build: impl FnMut(Amount) -> Result<BuiltBlindedTransaction>,
) -> Result<(BuiltBlindedTransaction, FeeEstimate)> {
- let mut fee = 0;
+ let mut fee = if fee_per_byte == 0 { 0 } else { 1 };
let mut best = None;
for _ in 0..64 {
let built = build(fee)?;
@@ -129,3 +129,61 @@ pub(super) fn recovery_vdf_sample_percent(address: &str, tip_hash: &str) -> u8 {
let digest = Sha256::digest(format!("iuna-recovery-vdf-sample:{tip_hash}:{address}"));
digest[0] % 100
}
+
+#[cfg(test)]
+mod tests {
+ use anyhow::bail;
+
+ use crate::domain::{BlindedReveal, BlindedTransaction, BuiltBlindedTransaction, Transaction};
+
+ use super::converge_fee_by_byte;
+
+ fn built_with_fee(fee: u64) -> BuiltBlindedTransaction {
+ BuiltBlindedTransaction {
+ payload: Transaction::Burn {
+ inputs: Vec::new(),
+ change: Vec::new(),
+ amount: 1,
+ fee,
+ signature: "payload".to_string(),
+ },
+ transaction: BlindedTransaction {
+ commitment: "0".repeat(64),
+ inputs: Vec::new(),
+ fee,
+ encrypted_size: 1,
+ expires_at_height: 1,
+ nonce: "0".repeat(24),
+ ciphertext: "00".to_string(),
+ payload_hash: "1".repeat(64),
+ },
+ reveal: BlindedReveal {
+ commitment: "0".repeat(64),
+ key: "2".repeat(64),
+ },
+ }
+ }
+
+ #[test]
+ fn fee_convergence_does_not_probe_zero_when_fee_rate_is_positive() {
+ let (built, estimate) = converge_fee_by_byte(1, |fee| {
+ if fee == 0 {
+ bail!("zero fee rejected after activation");
+ }
+ Ok(built_with_fee(fee))
+ })
+ .expect("positive fee-rate convergence should skip invalid zero fee");
+
+ assert!(estimate.fee > 0);
+ assert_eq!(built.transaction.fee, estimate.fee);
+ }
+
+ #[test]
+ fn fee_convergence_allows_zero_when_fee_rate_is_zero() {
+ let (built, estimate) =
+ converge_fee_by_byte(0, |fee| Ok(built_with_fee(fee))).expect("zero fee rate works");
+
+ assert_eq!(estimate.fee, 0);
+ assert_eq!(built.transaction.fee, 0);
+ }
+}
diff --git a/src/domain/tests.rs b/src/domain/tests.rs
@@ -2290,6 +2290,29 @@ fn zero_fee_blinded_transaction_creation_is_rejected_from_height_750() {
assert!(format!("{error:#}").contains("must pay a fee from height 750"));
}
+#[test]
+fn automatic_burn_builds_paid_blinded_transaction_from_height_750() {
+ let alice = Wallet::from_seed("auto-burn-after-fee-activation-alice");
+ let mut ledger = ledger_with_allocation(&alice, 10 * MICRO_IUNA);
+ set_tip_height_for_validation(&mut ledger, BLOCK_ITEM_FEES_REQUIRED_HEIGHT - 1);
+ let mut node =
+ crate::app::NodeCore::from_ledger_with_burn_fee_and_enabled(alice, ledger, true, 50, 1);
+
+ let plan = node.prepare_automatic_mining(1);
+
+ let burned = plan
+ .burned
+ .as_ref()
+ .expect("automatic burn should be built at fee activation");
+ assert_eq!(burned.amount(), 50);
+ assert!(burned.fee() > 0);
+ assert_eq!(node.ledger().pending_blinded_transactions().len(), 1);
+ assert_eq!(
+ node.ledger().pending_blinded_transactions()[0].fee,
+ burned.fee()
+ );
+}
+
fn block_for_fee_policy(miner: &Wallet, height: u64, transactions: Vec<Transaction>) -> Block {
Block {
height,