commit c1cdd4d481b432cae99412f048c339872ecd5d15
parent 253ba778edb3c7f6eca980dfbcc8343e8dca939d
Author: Joris Hartog <jorishartog@hotmail.com>
Date: Tue, 28 Jul 2026 19:53:22 +0200
Fix PoW mine rewards and bounded search
Diffstat:
9 files changed, 322 insertions(+), 273 deletions(-)
diff --git a/README.md b/README.md
@@ -71,7 +71,7 @@ iuna combines three mechanisms:
- **Proof of Burn:** nodes burn IUNA to enter the block-finalization lottery.
- **VDF clock:** the selected finalizer must run sequential delay work before publishing a block.
-- **PoW issuance:** miners create new IUNA through mine actions and choose the fee paid to the finalizer that includes them.
+- **PoW issuance:** mine actions issue 2 IUNA: 1 IUNA to the PoW miner and 1 IUNA as a fixed fee to the finalizer that includes them.
The current devnet targets 10-minute blocks, uses local wallet encryption, stores chain state in SQLite, and includes an in-memory network test harness for protocol testing.
diff --git a/src/adapters/config_store.rs b/src/adapters/config_store.rs
@@ -9,7 +9,7 @@ use std::{
use anyhow::{Context, Result, bail};
use serde::{Deserialize, Serialize};
-use crate::domain::{Amount, DEFAULT_FEE_PER_BYTE, MICRO_IUNA};
+use crate::domain::{Amount, DEFAULT_FEE_PER_BYTE, MICRO_IUNA, MINE_FINALIZER_FEE};
const CONFIG_FILE_VERSION: u32 = 1;
const AMOUNT_UNIT_MICROIUNA: &str = "microiuna";
@@ -38,7 +38,7 @@ impl Default for UiConfig {
pow_mining_enabled: false,
burn_per_block: 0,
burn_fee: DEFAULT_BURN_FEE,
- pow_mine_fee: DEFAULT_FEE_PER_BYTE,
+ pow_mine_fee: MINE_FINALIZER_FEE,
peers: Vec::new(),
}
}
@@ -139,7 +139,7 @@ fn load(path: &Path) -> Result<UiConfig> {
pow_mine_fee: stored
.pow_mine_fee
.map(|fee| fee.saturating_mul(scale))
- .unwrap_or(DEFAULT_FEE_PER_BYTE),
+ .unwrap_or(MINE_FINALIZER_FEE),
peers: stored.peers,
})
}
@@ -160,9 +160,9 @@ mod tests {
use tempfile::tempdir;
- use crate::domain::MICRO_IUNA;
+ use crate::domain::{MICRO_IUNA, MINE_FINALIZER_FEE};
- use super::{DEFAULT_BURN_FEE, DEFAULT_FEE_PER_BYTE, UiConfig, load_or_create, save};
+ use super::{DEFAULT_BURN_FEE, UiConfig, load_or_create, save};
#[test]
fn creates_default_config_file() {
@@ -181,7 +181,7 @@ mod tests {
assert!(stored.contains("\"pow_mining_enabled\": false"));
assert!(stored.contains("\"burn_per_block\": 0"));
assert!(stored.contains("\"burn_fee\": 1"));
- assert!(stored.contains("\"pow_mine_fee\": 1"));
+ assert!(stored.contains("\"pow_mine_fee\": 1000000"));
assert!(stored.contains("\"peers\": []"));
}
@@ -250,7 +250,7 @@ mod tests {
assert!(!config.pow_mining_enabled);
assert_eq!(config.burn_per_block, 0);
assert_eq!(config.burn_fee, DEFAULT_BURN_FEE);
- assert_eq!(config.pow_mine_fee, DEFAULT_FEE_PER_BYTE);
+ assert_eq!(config.pow_mine_fee, MINE_FINALIZER_FEE);
assert_eq!(config.peers, vec!["127.0.0.1:9444"]);
}
@@ -276,7 +276,7 @@ mod tests {
assert!(config.mining_enabled);
assert_eq!(config.burn_per_block, 2 * MICRO_IUNA);
assert_eq!(config.burn_fee, MICRO_IUNA);
- assert_eq!(config.pow_mine_fee, DEFAULT_FEE_PER_BYTE);
+ assert_eq!(config.pow_mine_fee, MINE_FINALIZER_FEE);
}
#[test]
diff --git a/src/adapters/http.rs b/src/adapters/http.rs
@@ -31,7 +31,9 @@ use crate::{
app::{
FeeEstimate, NodeStatus, PeerDirection, PeerInfo, SharedNode, SharedPeerBook, StratumStatus,
},
- domain::{Amount, Block, OutPoint, Transaction, TxInput, TxOutput, hex_hash},
+ domain::{
+ Amount, Block, MINE_FINALIZER_FEE, OutPoint, Transaction, TxInput, TxOutput, hex_hash,
+ },
};
const EXPLORER_LIMIT: usize = 50;
@@ -106,7 +108,6 @@ struct BurnSettingsForm {
#[derive(Debug, Deserialize)]
struct PowMiningForm {
enabled: bool,
- fee_per_byte: Option<Amount>,
}
#[derive(Debug, Deserialize)]
@@ -610,11 +611,7 @@ async fn api_pow_mining_form(
State(state): State<HttpState>,
Form(form): Form<PowMiningForm>,
) -> Json<ActionResponse> {
- let result = match required_fee_per_byte_mine(&form) {
- Ok(fee_per_byte) => set_pow_mining(&state, form.enabled, fee_per_byte).await,
- Err(error) => Err(error),
- };
- action_json(result)
+ action_json(set_pow_mining(&state, form.enabled, MINE_FINALIZER_FEE).await)
}
async fn burn_per_block_form(
@@ -718,18 +715,17 @@ async fn set_pow_mining(state: &HttpState, enabled: bool, fee: Amount) -> Result
let mut node = state.node.lock().await;
node.set_pow_mining_settings(enabled, fee)?;
}
- persist_pow_mining_config(&state.ui_config, &state.config_path, enabled, fee).await
+ persist_pow_mining_config(&state.ui_config, &state.config_path, enabled).await
}
async fn persist_pow_mining_config(
ui_config: &Arc<Mutex<UiConfig>>,
config_path: &Path,
enabled: bool,
- fee: Amount,
) -> Result<()> {
let mut config = ui_config.lock().await;
config.pow_mining_enabled = enabled;
- config.pow_mine_fee = fee;
+ config.pow_mine_fee = MINE_FINALIZER_FEE;
config_store::save(config_path, &config)
}
@@ -1328,9 +1324,12 @@ async fn estimate_burn_fee(state: &HttpState, form: BurnSettingsForm) -> Result<
.estimate_burn_fee(form.amount, fee_per_byte)
}
-async fn estimate_mine_fee(state: &HttpState, form: PowMiningForm) -> Result<FeeEstimate> {
- let fee_per_byte = required_fee_per_byte_mine(&form)?;
- state.node.lock().await.estimate_mine_fee(fee_per_byte)
+async fn estimate_mine_fee(state: &HttpState, _form: PowMiningForm) -> Result<FeeEstimate> {
+ state
+ .node
+ .lock()
+ .await
+ .estimate_mine_fee(MINE_FINALIZER_FEE)
}
fn required_fee_per_byte_transfer(form: &TransferForm) -> Result<Amount> {
@@ -1341,10 +1340,6 @@ fn required_fee_per_byte_burn(form: &BurnSettingsForm) -> Result<Amount> {
form.fee_per_byte.context("fee per byte is required")
}
-fn required_fee_per_byte_mine(form: &PowMiningForm) -> Result<Amount> {
- form.fee_per_byte.context("fee per byte is required")
-}
-
fn fee_estimate_json(result: Result<FeeEstimate>) -> Json<FeeEstimateResponse> {
match result {
Ok(estimate) => Json(FeeEstimateResponse {
@@ -2076,7 +2071,7 @@ const INDEX_HTML: &str = r#"<!doctype html>
</div>
<div class="panel">
<h3>Mine</h3>
- <div class="panel-description">Mine with PoW to introduce new IUNA. The miner chooses the fee paid to the block finalizer; the rest of the fixed mine reward goes to this wallet.</div>
+ <div class="panel-description">Mine with PoW to introduce new IUNA. Each mine action issues 2 IUNA: 1 IUNA goes to the miner and 1 IUNA is paid to the block finalizer.</div>
<form class="mine-settings-form" @submit.prevent="savePowMining">
<div class="mine-action-row">
<div class="mine-stats" aria-label="PoW issuance settings">
@@ -2097,16 +2092,12 @@ const INDEX_HTML: &str = r#"<!doctype html>
<div class="mine-stat-value"><span x-text="status.chain?.current_mine_difficulty_bits ?? status.launch_profile?.mine_difficulty_bits ?? '-'"></span> bits</div>
</div>
</div>
- <label class="toggle-switch" :class="{ active: powMiningEnabled }" title="Automatically queue one PoW mine action per chain tip">
+ <label class="toggle-switch" :class="{ active: powMiningEnabled }" title="Continuously search for PoW mine actions with a small local work budget">
<input type="checkbox" :checked="powMiningEnabled" @change="setPowMiningEnabled($event.target.checked)">
<span class="toggle-track"><span class="toggle-thumb"></span></span>
<span class="toggle-text" x-text="powMiningEnabled ? 'On' : 'Off'"></span>
</label>
</div>
- <div class="mine-fee-fields">
- <label>Fee / byte<input x-model="powMineFeeDraft" @input="powMineFeeDirty = true; scheduleFeeEstimates()" type="number" min="0" step="0.000001" required></label>
- <button class="primary" type="submit">Save</button>
- </div>
<div class="fee-preview" x-text="feeEstimateLabel('mine')"></div>
<div class="fee-preview" x-text="autoPowStatusLabel()"></div>
</form>
@@ -2545,15 +2536,18 @@ mod tests {
use crate::{
adapters::{config_store, config_store::UiConfig, p2p::GossipNetwork, wallet_store},
app::{NodeCore, PeerBook, PeerDirection, PeerInfo, StratumStatus},
- domain::{Block, Ledger, MICRO_IUNA, MINE_REWARD, OutPoint, Transaction, Wallet},
+ domain::{
+ Block, Ledger, MICRO_IUNA, MINE_FINALIZER_FEE, MINE_REWARD, OutPoint, Transaction,
+ Wallet,
+ },
};
use super::{
AUTH_COOKIE_NAME, HttpState, PEER_STALE_AFTER_MS, TransferForm, api_auth_login_form,
api_auth_setup_form, api_auth_status, dev_seed_verify_bypass_allowed, hash_password,
hex_encode, pbkdf2_sha256, persist_burn_settings_config, persist_pow_mining_config,
- require_auth_middleware, required_fee_per_byte_burn, required_fee_per_byte_mine,
- validate_password, validate_transfer_form, verify_password, wallet_transaction_rows,
+ require_auth_middleware, required_fee_per_byte_burn, validate_password,
+ validate_transfer_form, verify_password, wallet_transaction_rows,
};
#[test]
@@ -2946,13 +2940,10 @@ mod tests {
}
#[test]
- fn mine_transaction_views_include_miner_chosen_fee() {
+ fn mine_transaction_views_include_protocol_finalizer_fee() {
let alice = Wallet::from_seed("wallet-mine-fee-alice");
let ledger = Ledger::new(BTreeMap::new(), 1);
- let mine_fee = MICRO_IUNA / 5;
- let mine = ledger
- .build_mine_with_fee(alice.address(), mine_fee)
- .unwrap();
+ let mine = ledger.build_mine(alice.address()).unwrap();
let chain = vec![fake_block(1, vec![mine.clone()])];
let outputs = super::known_output_index(&BTreeMap::new(), &chain, &[]);
@@ -2960,10 +2951,10 @@ mod tests {
let transaction = super::ui_transaction(&mine, &outputs);
assert_eq!(rows.len(), 1);
- assert_eq!(rows[0].amount, MINE_REWARD - mine_fee);
- assert_eq!(rows[0].fee, mine_fee);
- assert_eq!(transaction.amount, MINE_REWARD - mine_fee);
- assert_eq!(transaction.fee, mine_fee);
+ assert_eq!(rows[0].amount, MINE_REWARD - MINE_FINALIZER_FEE);
+ assert_eq!(rows[0].fee, MINE_FINALIZER_FEE);
+ assert_eq!(transaction.amount, MINE_REWARD - MINE_FINALIZER_FEE);
+ assert_eq!(transaction.fee, MINE_FINALIZER_FEE);
}
fn fake_block(height: u64, transactions: Vec<Transaction>) -> Block {
@@ -3111,13 +3102,13 @@ mod tests {
let initial_config = ui_config.lock().await.clone();
config_store::save(&config_path, &initial_config).expect("initial config should save");
- persist_pow_mining_config(&ui_config, &config_path, true, 2 * MICRO_IUNA)
+ persist_pow_mining_config(&ui_config, &config_path, true)
.await
.unwrap();
let config = config_store::load_or_create(&config_path).unwrap();
assert!(config.pow_mining_enabled);
- assert_eq!(config.pow_mine_fee, 2 * MICRO_IUNA);
+ assert_eq!(config.pow_mine_fee, MINE_FINALIZER_FEE);
}
#[test]
@@ -3163,13 +3154,6 @@ mod tests {
})
.unwrap_err();
assert!(burn.to_string().contains("fee per byte is required"));
-
- let mine = required_fee_per_byte_mine(&super::PowMiningForm {
- enabled: true,
- fee_per_byte: None,
- })
- .unwrap_err();
- assert!(mine.to_string().contains("fee per byte is required"));
}
#[test]
diff --git a/src/app.rs b/src/app.rs
@@ -6,12 +6,13 @@ use std::{
use anyhow::{Context, Result, bail};
use serde::{Deserialize, Serialize};
+use sha2::{Digest, Sha256};
use tokio::sync::Mutex;
use crate::domain::{
Amount, Block, ChainSnapshot, ChainStatus, DEFAULT_FEE_PER_BYTE, DEFAULT_TRANSACTION_FEE,
- Ledger, OutPoint, PreparedBlock, StratumMineShare, StratumMineTemplate, Transaction,
- TransactionSubmitOutcome, TxOutput, VDF_TARGET_BLOCK_MS, Wallet, run_vdf,
+ Ledger, MINE_FINALIZER_FEE, OutPoint, PreparedBlock, StratumMineShare, StratumMineTemplate,
+ Transaction, TransactionSubmitOutcome, VDF_TARGET_BLOCK_MS, Wallet, run_vdf,
};
pub type SharedNode = Arc<Mutex<NodeCore>>;
@@ -25,6 +26,7 @@ pub const BLOCK_REQUEST_LIMIT: usize = 128;
const IMPORT_REBROADCAST_LIMIT: usize = 128;
pub const PEER_MISBEHAVIOR_BAN_SCORE: u32 = 3;
pub const PEER_MISBEHAVIOR_BAN_MS: u64 = 10 * 60 * 1_000;
+const AUTO_POW_NONCE_ATTEMPTS_PER_TICK: u64 = 8;
#[derive(Clone, Debug)]
pub struct NodeConfig {
@@ -199,6 +201,14 @@ pub struct AutoMinePlan {
pub skipped_reason: Option<String>,
}
+#[derive(Clone, Debug, Eq, PartialEq)]
+struct AutoPowMineCursor {
+ anchor: String,
+ salt: u64,
+ next_nonce: u64,
+ searched: u64,
+}
+
#[derive(Clone, Debug)]
pub struct NodeCore {
wallet: NodeWallet,
@@ -211,6 +221,7 @@ pub struct NodeCore {
last_auto_burn_height: Option<u64>,
last_auto_pow_mine_anchor: Option<String>,
last_auto_pow_mine_status: Option<String>,
+ auto_pow_mine_cursor: Option<AutoPowMineCursor>,
outbox: Vec<GossipEnvelope>,
}
@@ -290,12 +301,13 @@ impl NodeCore {
ledger,
automatic_mining_enabled,
pow_mining_enabled: false,
- pow_mine_fee: DEFAULT_FEE_PER_BYTE,
+ pow_mine_fee: MINE_FINALIZER_FEE,
burn_per_block,
burn_fee,
last_auto_burn_height: None,
last_auto_pow_mine_anchor: None,
last_auto_pow_mine_status: None,
+ auto_pow_mine_cursor: None,
outbox: Vec::new(),
}
}
@@ -313,6 +325,7 @@ impl NodeCore {
self.last_auto_burn_height = None;
self.last_auto_pow_mine_anchor = None;
self.last_auto_pow_mine_status = None;
+ self.auto_pow_mine_cursor = None;
}
pub fn ledger(&self) -> &Ledger {
@@ -468,7 +481,7 @@ impl NodeCore {
pow_mining_enabled: self.pow_mining_enabled,
burn_per_block: self.burn_per_block,
automatic_burn_fee: self.burn_fee,
- automatic_pow_mine_fee: self.pow_mine_fee,
+ automatic_pow_mine_fee: MINE_FINALIZER_FEE,
last_auto_pow_mine_anchor: self.last_auto_pow_mine_anchor.clone(),
last_auto_pow_mine_status: if self.pow_mining_enabled && !self.has_real_chain() {
Some("waiting for a real chain before PoW mining can start".to_string())
@@ -519,6 +532,7 @@ impl NodeCore {
pub fn set_pow_mining_enabled(&mut self, enabled: bool) {
self.pow_mining_enabled = enabled;
+ self.auto_pow_mine_cursor = None;
if !enabled {
self.last_auto_pow_mine_anchor = None;
self.last_auto_pow_mine_status = None;
@@ -528,9 +542,10 @@ impl NodeCore {
}
}
- pub fn set_pow_mining_settings(&mut self, enabled: bool, fee: Amount) -> Result<()> {
+ pub fn set_pow_mining_settings(&mut self, enabled: bool, _fee: Amount) -> Result<()> {
self.pow_mining_enabled = enabled;
- self.pow_mine_fee = fee;
+ self.pow_mine_fee = MINE_FINALIZER_FEE;
+ self.auto_pow_mine_cursor = None;
if !enabled {
self.last_auto_pow_mine_anchor = None;
self.last_auto_pow_mine_status = None;
@@ -645,8 +660,8 @@ impl NodeCore {
Ok(tx)
}
- pub fn estimate_mine_fee(&self, fee_per_byte: Amount) -> Result<FeeEstimate> {
- self.build_mine_with_fee_rate(fee_per_byte)
+ pub fn estimate_mine_fee(&self, _fee_per_byte: Amount) -> Result<FeeEstimate> {
+ self.build_mine_with_fee_rate(MINE_FINALIZER_FEE)
.map(|(_, estimate)| estimate)
}
@@ -732,48 +747,40 @@ impl NodeCore {
})
}
- fn build_mine_with_fee_rate(&self, fee_per_byte: Amount) -> Result<(Transaction, FeeEstimate)> {
- converge_fee_by_byte(fee_per_byte, |fee| {
- self.ledger.build_mine_with_fee(self.wallet.address(), fee)
- })
+ fn build_mine_with_fee_rate(
+ &self,
+ _fee_per_byte: Amount,
+ ) -> Result<(Transaction, FeeEstimate)> {
+ let tx = self
+ .ledger
+ .build_mine_with_fee(self.wallet.address(), MINE_FINALIZER_FEE)?;
+ Ok((
+ tx.clone(),
+ FeeEstimate {
+ bytes: tx.economic_size_bytes(),
+ fee: tx.fee(),
+ },
+ ))
}
fn estimate_external_mine_fee(
&self,
- recipient: &str,
- anchor: &str,
- difficulty_bits: u32,
+ _recipient: &str,
+ _anchor: &str,
+ _difficulty_bits: u32,
) -> Result<Amount> {
- let fee_per_byte = self.pow_mine_fee;
- let mine_reward = self.ledger.status().mine_reward;
- let mut fee = 0;
- for _ in 0..16 {
- if fee > mine_reward {
- bail!("mine transaction fee exceeds reward");
- }
- let tx = Transaction::Mine {
- output: TxOutput {
- address: recipient.to_string(),
- amount: mine_reward - fee,
- },
- anchor: anchor.to_string(),
- salt: 1,
- nonce: u64::MAX,
- difficulty_bits,
- fee,
- proof_header: Some("00".repeat(80)),
- signature: "00".repeat(32),
- };
- let bytes = tx.economic_size_bytes();
- let next_fee = fee_per_byte
- .checked_mul(bytes as Amount)
- .context("fee per byte times transaction bytes overflows")?;
- if fee >= next_fee {
- return Ok(fee);
- }
- fee = next_fee;
- }
- Ok(fee.min(mine_reward))
+ Ok(MINE_FINALIZER_FEE)
+ }
+
+ fn estimate_local_mine_fee(
+ &self,
+ _recipient: &str,
+ _anchor: &str,
+ _salt: u64,
+ _difficulty_bits: u32,
+ _fee_per_byte: Amount,
+ ) -> Result<Amount> {
+ Ok(MINE_FINALIZER_FEE)
}
pub fn mine_one(&mut self) -> Result<Block> {
@@ -886,6 +893,7 @@ impl NodeCore {
fn prepare_automatic_pow_mine(&mut self) -> Result<Option<Transaction>> {
if !self.pow_mining_enabled {
self.last_auto_pow_mine_status = None;
+ self.auto_pow_mine_cursor = None;
return Ok(None);
}
let anchor = self
@@ -894,22 +902,58 @@ impl NodeCore {
.last()
.map(|block| block.hash.clone())
.context("ledger has no anchor block")?;
- if self.last_auto_pow_mine_anchor.as_deref() == Some(anchor.as_str()) {
- self.last_auto_pow_mine_status =
- Some("already queued a mine action for the current tip".to_string());
- return Ok(None);
+ let wallet_address = self.wallet.address().to_string();
+ let difficulty_bits = self.ledger.current_mine_difficulty_bits();
+ let needs_cursor = self
+ .auto_pow_mine_cursor
+ .as_ref()
+ .is_none_or(|cursor| cursor.anchor != anchor);
+ if needs_cursor {
+ self.auto_pow_mine_cursor = Some(AutoPowMineCursor {
+ salt: auto_pow_salt(&wallet_address, &anchor),
+ anchor: anchor.clone(),
+ next_nonce: 0,
+ searched: 0,
+ });
}
- if self.wallet_has_mine_for_anchor(&anchor) {
- self.last_auto_pow_mine_anchor = Some(anchor);
- self.last_auto_pow_mine_status =
- Some("mine action is already pending for the current tip".to_string());
- return Ok(None);
+ let cursor = self
+ .auto_pow_mine_cursor
+ .as_ref()
+ .context("automatic PoW cursor was not initialized")?
+ .clone();
+ let fee = self.estimate_local_mine_fee(
+ &wallet_address,
+ &anchor,
+ cursor.salt,
+ difficulty_bits,
+ self.pow_mine_fee,
+ )?;
+ let outcome = self.ledger.search_mine_with_fee(
+ wallet_address,
+ fee,
+ cursor.salt,
+ cursor.next_nonce,
+ AUTO_POW_NONCE_ATTEMPTS_PER_TICK,
+ )?;
+ let mut searched = outcome.attempts;
+ if let Some(cursor) = &mut self.auto_pow_mine_cursor {
+ if cursor.anchor == anchor {
+ cursor.next_nonce = outcome.next_nonce;
+ cursor.searched = cursor.searched.saturating_add(outcome.attempts);
+ searched = cursor.searched;
+ }
}
- let (tx, _) = self.build_mine_with_fee_rate(self.pow_mine_fee)?;
+ let Some(tx) = outcome.transaction else {
+ self.last_auto_pow_mine_status = Some(format!(
+ "searched {searched} PoW nonces for the current tip; no proof yet"
+ ));
+ return Ok(None);
+ };
if self.ledger.submit_transaction(tx.clone())? {
self.last_auto_pow_mine_anchor = Some(anchor);
- self.last_auto_pow_mine_status =
- Some("queued mine action for the current tip".to_string());
+ self.last_auto_pow_mine_status = Some(format!(
+ "queued mine action after {searched} PoW nonce attempts for the current tip"
+ ));
self.outbox.push(GossipEnvelope::Transaction(tx.clone()));
return Ok(Some(tx));
}
@@ -918,19 +962,6 @@ impl NodeCore {
Ok(None)
}
- fn wallet_has_mine_for_anchor(&self, anchor: &str) -> bool {
- self.ledger.pending().iter().any(|tx| {
- matches!(
- tx,
- Transaction::Mine {
- output,
- anchor: tx_anchor,
- ..
- } if tx_anchor == anchor && output.address == self.wallet.address()
- )
- })
- }
-
fn prepare_automatic_burn(&mut self) -> Result<Option<Transaction>> {
let current_height = self.ledger.status().height;
if !self.automatic_mining_enabled {
@@ -1069,6 +1100,7 @@ impl NodeCore {
self.last_auto_burn_height = None;
self.last_auto_pow_mine_anchor = None;
self.last_auto_pow_mine_status = None;
+ self.auto_pow_mine_cursor = None;
self.enqueue_imported_blocks(previous_height);
}
Ok(())
@@ -1089,6 +1121,7 @@ impl NodeCore {
self.last_auto_burn_height = None;
self.last_auto_pow_mine_anchor = None;
self.last_auto_pow_mine_status = None;
+ self.auto_pow_mine_cursor = None;
self.enqueue_imported_blocks(previous_height);
Ok(true)
}
@@ -1420,6 +1453,13 @@ pub fn now_ms() -> u64 {
.as_millis() as u64
}
+fn auto_pow_salt(wallet_address: &str, anchor: &str) -> u64 {
+ let digest = Sha256::digest(format!("iuna-auto-pow:{wallet_address}:{anchor}").as_bytes());
+ let mut bytes = [0_u8; 8];
+ bytes.copy_from_slice(&digest[..8]);
+ u64::from_be_bytes(bytes)
+}
+
fn converge_fee_by_byte(
fee_per_byte: Amount,
mut build: impl FnMut(Amount) -> Result<Transaction>,
@@ -1482,7 +1522,7 @@ fn converge_fee_by_byte(
mod tests {
use std::collections::BTreeMap;
- use crate::domain::{Ledger, MICRO_IUNA, Transaction, Wallet};
+ use crate::domain::{Ledger, MICRO_IUNA, MINE_FINALIZER_FEE, Transaction, Wallet};
use super::{NodeConfig, NodeCore};
@@ -1512,7 +1552,7 @@ mod tests {
}
#[test]
- fn automatic_pow_mining_queues_one_mine_action_per_anchor() {
+ fn automatic_pow_mining_searches_bounded_nonce_batches_per_tip() {
let wallet = Wallet::from_seed("automatic-pow-mining-wallet");
let mut node = NodeCore::new(NodeConfig {
wallet: wallet.clone(),
@@ -1531,6 +1571,11 @@ mod tests {
node.set_pow_mining_enabled(true);
let first = node.prepare_automatic_mining(2);
+ assert!(node.ledger().pending().len() <= 1);
+ let first = std::iter::once(first)
+ .chain((3..10_000).map(|timestamp| node.prepare_automatic_mining(timestamp)))
+ .find(|plan| plan.pow_mined.is_some())
+ .expect("bounded PoW search should eventually find a proof");
let first_mine = first.pow_mined.as_ref().expect("PoW should be queued");
let Transaction::Mine {
anchor,
@@ -1545,32 +1590,34 @@ mod tests {
assert_eq!(anchor, &node.chain().last().unwrap().hash);
assert_eq!(output.address, wallet.address());
let minimum_fee = first_mine.economic_size_bytes() as u64;
- let (_, expected_estimate) = node
- .build_mine_with_fee_rate(node.status().mining.automatic_pow_mine_fee)
- .unwrap();
assert!(*fee >= minimum_fee);
- assert_eq!(*fee, expected_estimate.fee);
assert_eq!(
*difficulty_bits,
node.ledger().current_mine_difficulty_bits()
);
assert_eq!(node.ledger().pending().len(), 1);
- assert_eq!(
- node.status().mining.last_auto_pow_mine_status.as_deref(),
- Some("queued mine action for the current tip")
+ assert!(
+ node.status()
+ .mining
+ .last_auto_pow_mine_status
+ .as_deref()
+ .unwrap_or_default()
+ .contains("queued mine action after")
);
- let second = node.prepare_automatic_mining(3);
- assert!(second.pow_mined.is_none());
- assert_eq!(node.ledger().pending().len(), 1);
- assert_eq!(
- node.status().mining.last_auto_pow_mine_status.as_deref(),
- Some("already queued a mine action for the current tip")
+ let second = (10_000..20_000)
+ .map(|timestamp| node.prepare_automatic_mining(timestamp))
+ .find(|plan| plan.pow_mined.is_some())
+ .expect("automatic PoW should keep searching the same tip after one proof");
+ assert_ne!(
+ second.pow_mined.as_ref().unwrap().signature(),
+ first_mine.signature()
);
+ assert_eq!(node.ledger().pending().len(), 2);
}
#[test]
- fn automatic_pow_mining_uses_configured_mine_fee() {
+ fn automatic_pow_mining_uses_protocol_finalizer_fee() {
let wallet = Wallet::from_seed("automatic-pow-mining-fee-wallet");
let mut node = NodeCore::new(NodeConfig {
wallet,
@@ -1580,25 +1627,21 @@ mod tests {
burn_fee: 0,
});
- let configured_fee_per_byte = 2;
- node.set_pow_mining_settings(true, configured_fee_per_byte)
- .unwrap();
- let plan = node.prepare_automatic_mining(1);
+ node.set_pow_mining_settings(true, 2).unwrap();
+ let plan = (1..10_000)
+ .map(|timestamp| node.prepare_automatic_mining(timestamp))
+ .find(|plan| plan.pow_mined.is_some())
+ .expect("bounded PoW search should eventually find a proof");
let mine = plan.pow_mined.expect("PoW should be queued");
- let minimum_fee = mine.economic_size_bytes() as u64 * configured_fee_per_byte;
- let (_, expected_estimate) = node
- .build_mine_with_fee_rate(configured_fee_per_byte)
- .unwrap();
- assert!(mine.fee() >= minimum_fee);
- assert_eq!(mine.fee(), expected_estimate.fee);
+ assert_eq!(mine.fee(), MINE_FINALIZER_FEE);
assert_eq!(
mine.amount(),
node.ledger().status().mine_reward - mine.fee()
);
assert_eq!(
node.status().mining.automatic_pow_mine_fee,
- configured_fee_per_byte
+ MINE_FINALIZER_FEE
);
}
@@ -1617,47 +1660,6 @@ mod tests {
}
#[test]
- fn automatic_pow_mining_reports_fee_rate_above_reward() {
- let wallet = Wallet::from_seed("automatic-pow-mining-too-high-fee-wallet");
- let mut node = NodeCore::new(NodeConfig {
- wallet: wallet.clone(),
- genesis_allocations: BTreeMap::new(),
- vdf_rounds: 10,
- burn_per_block: 0,
- burn_fee: 0,
- });
- let mut genesis_allocations = BTreeMap::new();
- genesis_allocations.insert(wallet.address().to_string(), 1);
- let ledger = Ledger::new_with_genesis_burns(
- genesis_allocations,
- vec![crate::domain::GenesisBurn::new(wallet.address(), 1)],
- 10,
- )
- .unwrap();
- node.import_verified_ledger(ledger).unwrap();
-
- node.set_pow_mining_settings(true, MICRO_IUNA).unwrap();
- let plan = node.prepare_automatic_mining(1);
-
- assert!(plan.pow_mined.is_none());
- assert!(
- plan.skipped_reason
- .as_deref()
- .unwrap_or_default()
- .contains("fee exceeds reward")
- );
- assert!(node.status().mining.pow_mining_enabled);
- assert!(
- node.status()
- .mining
- .last_auto_pow_mine_status
- .as_deref()
- .unwrap_or_default()
- .contains("fee exceeds reward")
- );
- }
-
- #[test]
fn automatic_pow_status_reports_setup_placeholder_wait() {
let wallet = Wallet::from_seed("automatic-pow-setup-placeholder-wallet");
let ledger = Ledger::new(BTreeMap::new(), 1);
diff --git a/src/domain.rs b/src/domain.rs
@@ -8,8 +8,9 @@ use sha2::{Digest, Sha256};
pub type Amount = u64;
pub const MICRO_IUNA: Amount = 1_000_000;
pub const BLOCK_REWARD: Amount = 100 * MICRO_IUNA;
-pub const MINE_REWARD: Amount = MICRO_IUNA;
-pub const DEFAULT_MINE_FEE: Amount = MINE_REWARD / 100;
+pub const MINE_REWARD: Amount = 2 * MICRO_IUNA;
+pub const MINE_FINALIZER_FEE: Amount = MICRO_IUNA;
+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;
@@ -128,6 +129,13 @@ pub enum Transaction {
},
}
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct MineSearchOutcome {
+ pub transaction: Option<Transaction>,
+ pub next_nonce: u64,
+ pub attempts: u64,
+}
+
impl Transaction {
pub fn genesis_burn(from: impl Into<String>, amount: Amount) -> Self {
let from = from.into();
@@ -669,8 +677,8 @@ fn stratum_mine_template(
let recipient = recipient.into();
validate_address(&recipient, "mine recipient")?;
validate_hash(anchor, "mine transaction anchor")?;
- if fee > mine_reward {
- bail!("mine transaction fee exceeds reward");
+ if fee != MINE_FINALIZER_FEE {
+ bail!("mine transaction fee must be exactly the protocol finalizer fee");
}
let output = TxOutput {
address: recipient.clone(),
@@ -1682,7 +1690,7 @@ impl Ledger {
}
pub fn build_mine(&self, recipient: impl Into<String>) -> Result<Transaction> {
- self.build_mine_with_fee(recipient, 0)
+ self.build_mine_with_fee(recipient, MINE_FINALIZER_FEE)
}
pub fn build_mine_with_fee(
@@ -1692,8 +1700,8 @@ impl Ledger {
) -> Result<Transaction> {
let recipient = recipient.into();
validate_address(&recipient, "mine recipient")?;
- if fee > self.mine_reward {
- bail!("mine transaction fee exceeds reward");
+ if fee != MINE_FINALIZER_FEE {
+ bail!("mine transaction fee must be exactly the protocol finalizer fee");
}
let output = TxOutput {
address: recipient,
@@ -1726,6 +1734,60 @@ impl Ledger {
bail!("could not find valid mine proof");
}
+ pub fn search_mine_with_fee(
+ &self,
+ recipient: impl Into<String>,
+ fee: Amount,
+ salt: u64,
+ start_nonce: u64,
+ max_attempts: u64,
+ ) -> Result<MineSearchOutcome> {
+ let recipient = recipient.into();
+ validate_address(&recipient, "mine recipient")?;
+ if fee != MINE_FINALIZER_FEE {
+ bail!("mine transaction fee must be exactly the protocol finalizer fee");
+ }
+ let output = TxOutput {
+ address: recipient,
+ amount: self.mine_reward - fee,
+ };
+ let anchor = self.tip().hash.clone();
+ let difficulty_bits = self.current_mine_difficulty_bits();
+ let mut attempts = 0_u64;
+ let mut nonce = start_nonce;
+ while attempts < max_attempts {
+ let signature = mine_signature(&output, &anchor, salt, nonce, difficulty_bits, fee);
+ attempts = attempts.saturating_add(1);
+ let next_nonce = nonce.checked_add(1).unwrap_or(0);
+ if hash_meets_difficulty(&signature, difficulty_bits) {
+ let transaction = Transaction::Mine {
+ output: output.clone(),
+ anchor: anchor.clone(),
+ salt,
+ nonce,
+ difficulty_bits,
+ fee,
+ proof_header: None,
+ signature,
+ };
+ if !self.has_transaction(transaction.signature()) {
+ self.validate_new_transaction(&transaction)?;
+ return Ok(MineSearchOutcome {
+ transaction: Some(transaction),
+ next_nonce,
+ attempts,
+ });
+ }
+ }
+ nonce = next_nonce;
+ }
+ Ok(MineSearchOutcome {
+ transaction: None,
+ next_nonce: nonce,
+ attempts,
+ })
+ }
+
pub fn stratum_mine_template(
&self,
recipient: impl Into<String>,
@@ -2179,6 +2241,9 @@ impl Ledger {
if let Some(proof_header) = proof_header {
validate_stratum_header(proof_header)?;
}
+ if *fee != MINE_FINALIZER_FEE {
+ bail!("mine transaction fee must be exactly the protocol finalizer fee");
+ }
if output
.amount
.checked_add(*fee)
@@ -3551,6 +3616,20 @@ mod tests {
}
#[test]
+ fn mine_search_respects_nonce_attempt_limit() {
+ let alice = Wallet::from_seed("bounded-mine-search-alice");
+ let ledger = Ledger::new(BTreeMap::new(), 1);
+
+ let outcome = ledger
+ .search_mine_with_fee(alice.address(), MINE_FINALIZER_FEE, 1, 0, 0)
+ .unwrap();
+
+ assert!(outcome.transaction.is_none());
+ assert_eq!(outcome.next_nonce, 0);
+ assert_eq!(outcome.attempts, 0);
+ }
+
+ #[test]
fn mempool_rejects_invalid_input_outpoint_id() {
let alice = Wallet::from_seed("invalid-outpoint-alice");
let bob = Wallet::from_seed("invalid-outpoint-bob");
@@ -3778,22 +3857,23 @@ mod tests {
}
#[test]
- fn mine_fee_cannot_exceed_reward() {
- let alice = Wallet::from_seed("mine-fee-too-high-alice");
+ fn mine_fee_must_match_protocol_finalizer_fee() {
+ let alice = Wallet::from_seed("mine-fee-fixed-alice");
let ledger = ledger_with_allocation(&alice, MICRO_IUNA);
let error = ledger
- .build_mine_with_fee(alice.address(), MINE_REWARD + 1)
+ .build_mine_with_fee(alice.address(), MINE_FINALIZER_FEE - 1)
.unwrap_err();
- assert!(format!("{error:#}").contains("fee exceeds reward"));
+ assert!(format!("{error:#}").contains("protocol finalizer fee"));
}
#[test]
fn mine_output_plus_fee_must_equal_reward_even_with_valid_pow() {
let alice = Wallet::from_seed("mine-invalid-split-alice");
let mut ledger = ledger_with_allocation(&alice, MICRO_IUNA);
- let forged = mine_with_output_and_fee(&ledger, alice.address(), MINE_REWARD, 1);
+ let forged =
+ mine_with_output_and_fee(&ledger, alice.address(), MINE_REWARD, MINE_FINALIZER_FEE);
let error = ledger.submit_transaction(forged).unwrap_err();
@@ -3801,53 +3881,35 @@ mod tests {
}
#[test]
- fn mine_fee_can_take_entire_reward_for_finalizer() {
- let alice = Wallet::from_seed("mine-full-fee-alice");
+ fn mine_action_uses_fixed_split_between_miner_and_finalizer() {
+ let alice = Wallet::from_seed("mine-fixed-split-alice");
let mut ledger = ledger_with_allocation(&alice, MICRO_IUNA);
- let mine = ledger
- .build_mine_with_fee(alice.address(), MINE_REWARD)
- .unwrap();
+ let mine = ledger.build_mine(alice.address()).unwrap();
- assert_eq!(mine.amount(), 0);
- assert_eq!(mine.fee(), MINE_REWARD);
+ assert_eq!(mine.amount(), MICRO_IUNA);
+ assert_eq!(mine.fee(), MINE_FINALIZER_FEE);
assert!(ledger.submit_transaction(mine).unwrap());
}
#[test]
- fn block_selection_prefers_higher_fee_mine_action_when_space_is_limited() {
- let alice = Wallet::from_seed("mine-fee-priority-alice");
+ fn block_selection_can_skip_mine_action_when_space_is_limited() {
+ let alice = Wallet::from_seed("mine-space-limit-alice");
let mut ledger = ledger_with_allocation(&alice, 10 * MICRO_IUNA);
ledger.launch_profile.max_block_transactions = 2;
- let low_fee_mine = ledger
- .build_mine_with_fee(alice.address(), MICRO_IUNA / 100)
- .unwrap();
- let high_fee_mine = ledger
- .build_mine_with_fee(alice.address(), MICRO_IUNA / 2)
- .unwrap();
let burn = ledger.build_burn(&alice, MICRO_IUNA, 0).unwrap();
- ledger.submit_transaction(low_fee_mine.clone()).unwrap();
- ledger.submit_transaction(high_fee_mine.clone()).unwrap();
ledger.submit_transaction(burn).unwrap();
+ let first_mine = ledger.build_mine(alice.address()).unwrap();
+ ledger.submit_transaction(first_mine).unwrap();
+ let second_mine = ledger.build_mine(alice.address()).unwrap();
+ ledger.submit_transaction(second_mine).unwrap();
let block = ledger.mine_next_block(&alice, 1).unwrap();
assert_eq!(block.transactions.len(), 2);
assert!(block.transactions.iter().any(Transaction::is_burn));
- assert!(
- block
- .transactions
- .iter()
- .any(|tx| tx.signature() == high_fee_mine.signature())
- );
- assert!(
- block
- .transactions
- .iter()
- .all(|tx| tx.signature() != low_fee_mine.signature())
- );
- assert_eq!(block.reward, high_fee_mine.fee());
+ assert_eq!(block.reward, MINE_FINALIZER_FEE);
}
#[test]
@@ -3857,13 +3919,9 @@ mod tests {
let burn = ledger.build_burn(&alice, MICRO_IUNA, 0).unwrap();
ledger.submit_transaction(burn).unwrap();
- let first_mine = ledger
- .build_mine_with_fee(alice.address(), MICRO_IUNA / 100)
- .unwrap();
+ let first_mine = ledger.build_mine(alice.address()).unwrap();
ledger.submit_transaction(first_mine.clone()).unwrap();
- let second_mine = ledger
- .build_mine_with_fee(alice.address(), MICRO_IUNA / 100)
- .unwrap();
+ let second_mine = ledger.build_mine(alice.address()).unwrap();
ledger.submit_transaction(second_mine.clone()).unwrap();
assert_eq!(ledger.pending().len(), 3);
@@ -3967,7 +4025,13 @@ mod tests {
let anchor = ledger.tip().hash.clone();
let difficulty_bits = ledger.current_mine_difficulty_bits();
let template = ledger
- .stratum_mine_template(alice.address(), 0, anchor, 1, difficulty_bits)
+ .stratum_mine_template(
+ alice.address(),
+ MINE_FINALIZER_FEE,
+ anchor,
+ 1,
+ difficulty_bits,
+ )
.unwrap();
let mut accepted = None;
@@ -4007,7 +4071,13 @@ mod tests {
for salt in [1, 2] {
let template = ledger
- .stratum_mine_template(alice.address(), 0, anchor.clone(), salt, difficulty_bits)
+ .stratum_mine_template(
+ alice.address(),
+ MINE_FINALIZER_FEE,
+ anchor.clone(),
+ salt,
+ difficulty_bits,
+ )
.unwrap();
let mut accepted = None;
for nonce in 0_u32..50_000 {
diff --git a/tests/iuna.rs b/tests/iuna.rs
@@ -5,8 +5,7 @@ use iuna::{
app::{DEFAULT_BURN_PER_BLOCK, InMemoryNetwork, NodeConfig, NodeCore, PeerBook, PeerDirection},
domain::{
Amount, BLOCK_REWARD, DEFAULT_FEE_PER_BYTE, GenesisBurn, Ledger, MAX_BLOCK_BYTES,
- MICRO_IUNA, MINE_REWARD, TransactionSubmitOutcome, VDF_TARGET_BLOCK_MS, Wallet, run_vdf,
- verify_vdf,
+ MICRO_IUNA, TransactionSubmitOutcome, VDF_TARGET_BLOCK_MS, Wallet, run_vdf, verify_vdf,
},
};
use tempfile::tempdir;
@@ -291,48 +290,46 @@ fn block_with_forged_transaction_is_rejected() {
}
#[test]
-fn mine_action_introduces_one_iuna_and_block_author_gets_fees_only() {
+fn mine_action_introduces_two_iuna_and_splits_one_to_miner_one_to_finalizer() {
let alice = Wallet::from_seed("alice");
let mut allocations = BTreeMap::new();
allocations.insert(alice.address().to_string(), 2 * MICRO_IUNA);
let mut ledger = Ledger::new(allocations, 10);
let mine = ledger.build_mine(alice.address()).unwrap();
- assert_eq!(mine.amount(), MINE_REWARD);
+ assert_eq!(mine.amount(), MICRO_IUNA);
+ assert_eq!(mine.fee(), MICRO_IUNA);
ledger.submit_transaction(mine.clone()).unwrap();
submit_burn(&mut ledger, &alice, MICRO_IUNA);
let block = ledger.mine_next_block(&alice, 1).unwrap();
- assert_eq!(block.reward, 0);
+ assert_eq!(block.reward, MICRO_IUNA);
ledger.apply_block(block).unwrap();
- assert_eq!(ledger.balance_of(alice.address()), 2 * MICRO_IUNA);
+ assert_eq!(ledger.balance_of(alice.address()), 3 * MICRO_IUNA);
assert!(!ledger.submit_transaction(mine).unwrap());
- assert_eq!(ledger.balance_of(alice.address()), 2 * MICRO_IUNA);
+ assert_eq!(ledger.balance_of(alice.address()), 3 * MICRO_IUNA);
}
#[test]
-fn mine_action_fee_is_chosen_by_pow_miner_and_paid_to_block_finalizer() {
+fn mine_action_protocol_fee_is_paid_to_block_finalizer() {
let alice = Wallet::from_seed("mine-fee-alice");
let bob = Wallet::from_seed("mine-fee-bob");
let mut allocations = BTreeMap::new();
allocations.insert(bob.address().to_string(), 2 * MICRO_IUNA);
let mut ledger = Ledger::new(allocations, 10);
- let mine_fee = MICRO_IUNA / 4;
- let mine = ledger
- .build_mine_with_fee(alice.address(), mine_fee)
- .unwrap();
- assert_eq!(mine.amount(), MINE_REWARD - mine_fee);
- assert_eq!(mine.fee(), mine_fee);
+ let mine = ledger.build_mine(alice.address()).unwrap();
+ assert_eq!(mine.amount(), MICRO_IUNA);
+ assert_eq!(mine.fee(), MICRO_IUNA);
ledger.submit_transaction(mine).unwrap();
submit_burn(&mut ledger, &bob, MICRO_IUNA);
let block = ledger.mine_next_block(&bob, 1).unwrap();
- assert_eq!(block.reward, mine_fee);
+ assert_eq!(block.reward, MICRO_IUNA);
ledger.apply_block(block).unwrap();
- assert_eq!(ledger.balance_of(alice.address()), MINE_REWARD - mine_fee);
- assert_eq!(ledger.balance_of(bob.address()), MICRO_IUNA + mine_fee);
+ assert_eq!(ledger.balance_of(alice.address()), MICRO_IUNA);
+ assert_eq!(ledger.balance_of(bob.address()), 2 * MICRO_IUNA);
}
#[test]
@@ -811,7 +808,10 @@ fn pow_only_node_gossips_mine_action_to_pob_only_finalizer() {
.set_pow_mining_settings(true, DEFAULT_FEE_PER_BYTE)
.unwrap();
- let bob_plan = bob_node.prepare_automatic_mining(1);
+ let bob_plan = (1..10_000)
+ .map(|timestamp| bob_node.prepare_automatic_mining(timestamp))
+ .find(|plan| plan.pow_mined.is_some())
+ .expect("B should eventually queue a mine action");
let mine = bob_plan.pow_mined.expect("B should queue a mine action");
assert_eq!(
bob_plan.skipped_reason.as_deref(),
diff --git a/tests/properties.rs b/tests/properties.rs
@@ -371,7 +371,7 @@ fn try_random_transaction(
}
2 if round % 4 == 0 => {
let wallet = &wallets[rng.index(wallets.len())];
- if let Ok(tx) = ledger.build_mine_with_fee(wallet.address(), rng.next_u64() % 3) {
+ if let Ok(tx) = ledger.build_mine(wallet.address()) {
let _ = ledger.submit_transaction(tx);
}
}
diff --git a/www/assets/iuna-ui.js b/www/assets/iuna-ui.js
@@ -38,8 +38,8 @@ window.iunaApp = function iunaApp() {
burnFeeDraft: "0.000001",
miningEnabled: false,
powMiningEnabled: false,
- powMineFee: 1,
- powMineFeeDraft: "0.000001",
+ powMineFee: 1000000,
+ powMineFeeDraft: "1",
powMineFeeDirty: false,
burnAmountDirty: false,
transferTo: "",
@@ -655,10 +655,8 @@ window.iunaApp = function iunaApp() {
},
async refreshMineFeeEstimate() {
- const feePerByte = this.parseiunaAmount(this.powMineFeeDraft);
this.feeEstimates.mine = await this.fetchFeeEstimate("/api/fee-estimate/mine", {
enabled: this.powMiningEnabled,
- fee_per_byte: feePerByte,
});
},
@@ -751,15 +749,13 @@ window.iunaApp = function iunaApp() {
async setPowMiningEnabled(enabled) {
const previous = this.powMiningEnabled;
try {
- const fee = this.parseiunaAmountRequired(this.powMineFeeDraft, "Mine fee per byte is required");
this.powMiningEnabled = enabled;
await this.postForm(
"/api/settings/pow-mining",
- { enabled, fee_per_byte: fee },
+ { enabled },
enabled ? "PoW mining turned on" : "PoW mining turned off"
);
this.powMineFeeDirty = false;
- this.powMineFee = fee;
} catch (error) {
this.powMiningEnabled = previous;
this.showFlash(error.message, "error");
@@ -768,17 +764,14 @@ window.iunaApp = function iunaApp() {
async savePowMining() {
try {
- const fee = this.parseiunaAmountRequired(this.powMineFeeDraft, "Mine fee per byte is required");
- this.powMineFeeDraft = this.amountLabel(fee);
await this.postForm(
"/api/settings/pow-mining",
- { enabled: this.powMiningEnabled, fee_per_byte: fee },
+ { enabled: this.powMiningEnabled },
this.powMiningEnabled
- ? `Mine fee rate set to ${this.amountLabel(fee)} IUNA per byte`
+ ? "PoW mining settings saved"
: `Mine settings saved while off`
);
this.powMineFeeDirty = false;
- this.powMineFee = fee;
} catch (error) {
this.showFlash(error.message, "error");
}
diff --git a/www/index.html b/www/index.html
@@ -286,7 +286,7 @@
<div class="card">
<span class="tag blue">PoW</span>
<h3>Proof Of Work Issuance</h3>
- <p>PoW mine actions introduce new IUNA. Miners create 1 IUNA at a time and choose the fee paid to the block finalizer that includes the action.</p>
+ <p>PoW mine actions introduce new IUNA. Each action issues 2 IUNA: 1 IUNA goes to the miner and 1 IUNA is paid as a fixed fee to the block finalizer.</p>
</div>
</div>
</div>