miner rewards reporting update
This commit is contained in:
parent
e044f15e76
commit
05b027f30e
|
|
@ -2,6 +2,7 @@ use crate::blocks::rewards::RewardsTransaction;
|
|||
use crate::common::network_paths_and_settings::block_extension_and_paths;
|
||||
use crate::decode;
|
||||
use crate::records::balance_sheet::operations::balance_sheet_operation_with_db;
|
||||
use crate::records::record_chain::miner_earnings::remove_finalized_miner_earnings;
|
||||
use crate::records::record_chain::rewards_tx::{
|
||||
remove_immature_reward, remove_reward_credit_marker, reward_credit_applied,
|
||||
};
|
||||
|
|
@ -41,6 +42,7 @@ pub async fn undo_rewards_transaction(
|
|||
|
||||
let hash = decode(transaction.unsigned.hash().await).unwrap();
|
||||
let _ = remove_wallet_transaction_index(db, &[mining_receiver], &hash);
|
||||
remove_finalized_miner_earnings(db, &hash);
|
||||
|
||||
// Remove the reward transaction lookup from the txid tree.
|
||||
let tree = db.open_tree("txid").unwrap();
|
||||
|
|
|
|||
|
|
@ -379,11 +379,7 @@ async fn delete_processed_before_or_at(block_number: u32, limit: i64) -> Result<
|
|||
Ok(())
|
||||
}
|
||||
|
||||
async fn delete_processed_rows_limited(
|
||||
table: &str,
|
||||
block_number: i32,
|
||||
limit: i64,
|
||||
) -> Result<u64> {
|
||||
async fn delete_processed_rows_limited(table: &str, block_number: i32, limit: i64) -> Result<u64> {
|
||||
let statement = format!(
|
||||
r#"
|
||||
DELETE FROM {table}
|
||||
|
|
|
|||
|
|
@ -59,22 +59,10 @@ pub async fn restore_selected_transactions_processed(batch: &SelectedMempoolBatc
|
|||
unmark_rows_by_ids("burn", &ids_for_table(batch, "burn")).await?;
|
||||
unmark_rows_by_ids("nft", &ids_for_table(batch, "nft")).await?;
|
||||
unmark_rows_by_ids("marketing", &ids_for_table(batch, "marketing")).await?;
|
||||
unmark_rows_by_ids(
|
||||
"vanity_address",
|
||||
&ids_for_table(batch, "vanity_address"),
|
||||
)
|
||||
.await?;
|
||||
unmark_rows_by_ids("vanity_address", &ids_for_table(batch, "vanity_address")).await?;
|
||||
unmark_rows_by_ids("swap", &ids_for_table(batch, "swap")).await?;
|
||||
unmark_rows_by_ids(
|
||||
"loan_contract",
|
||||
&ids_for_table(batch, "loan_contract"),
|
||||
)
|
||||
.await?;
|
||||
unmark_rows_by_ids(
|
||||
"loan_payment",
|
||||
&ids_for_table(batch, "loan_payment"),
|
||||
)
|
||||
.await?;
|
||||
unmark_rows_by_ids("loan_contract", &ids_for_table(batch, "loan_contract")).await?;
|
||||
unmark_rows_by_ids("loan_payment", &ids_for_table(batch, "loan_payment")).await?;
|
||||
unmark_rows_by_ids(
|
||||
"collateral_claim",
|
||||
&ids_for_table(batch, "collateral_claim"),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use super::*;
|
||||
use crate::records::record_chain::miner_earnings::MinerEarnings;
|
||||
use crate::records::record_chain::pending_effects::{BalanceOperand, PendingEffects};
|
||||
use crate::records::record_chain::wallet_tx_index::index_wallet_transaction;
|
||||
|
||||
|
|
@ -347,13 +348,14 @@ pub async fn apply_selected_transaction_math(
|
|||
block_number: u32,
|
||||
start_index: usize,
|
||||
pending_effects: &mut PendingEffects,
|
||||
) -> Result<()> {
|
||||
) -> Result<MinerEarnings> {
|
||||
// Net balance deltas are collected first so multiple changes to the same
|
||||
// address/asset pair become one balance-sheet write.
|
||||
let mut balance_changes: HashMap<BalanceKey, i64> = HashMap::new();
|
||||
let miner_address = address_key_bytes(db, &miner);
|
||||
let base_coin = BASECOIN.as_bytes().to_vec();
|
||||
let mut tx_index = start_index;
|
||||
let mut miner_earnings = MinerEarnings::default();
|
||||
|
||||
for tx in &batch.transactions {
|
||||
// Transaction index is stored with the block number for later txid
|
||||
|
|
@ -370,6 +372,9 @@ pub async fn apply_selected_transaction_math(
|
|||
hash,
|
||||
..
|
||||
} => {
|
||||
miner_earnings
|
||||
.add_fee(nonnegative_u64(*fee, "selected transfer fee")?)
|
||||
.map_err(|err| anyhow!(err))?;
|
||||
// Transfers move the asset, charge the base fee, and credit
|
||||
// that fee to the miner including this transaction.
|
||||
let transfer_asset = nft_asset_name(coin, *nft_series as u32);
|
||||
|
|
@ -411,6 +416,9 @@ pub async fn apply_selected_transaction_math(
|
|||
hash,
|
||||
..
|
||||
} => {
|
||||
miner_earnings
|
||||
.add_fee(nonnegative_u64(*fee, "selected token fee")?)
|
||||
.map_err(|err| anyhow!(err))?;
|
||||
// Token creation mints the initial supply and records metadata
|
||||
// used by later issue and burn validation.
|
||||
add_balance_change(db, &mut balance_changes, creator, ticker, *number);
|
||||
|
|
@ -478,6 +486,9 @@ pub async fn apply_selected_transaction_math(
|
|||
hash,
|
||||
..
|
||||
} => {
|
||||
miner_earnings
|
||||
.add_fee(nonnegative_u64(*fee, "selected issue-token fee")?)
|
||||
.map_err(|err| anyhow!(err))?;
|
||||
add_balance_change(db, &mut balance_changes, creator, ticker, *number);
|
||||
add_balance_change(db, &mut balance_changes, creator, &BASECOIN, -*fee);
|
||||
add_balance_change_bytes(
|
||||
|
|
@ -517,6 +528,9 @@ pub async fn apply_selected_transaction_math(
|
|||
hash,
|
||||
..
|
||||
} => {
|
||||
miner_earnings
|
||||
.add_fee(nonnegative_u64(*fee, "selected burn fee")?)
|
||||
.map_err(|err| anyhow!(err))?;
|
||||
let burned_asset = nft_asset_name(coin, *nft_series as u32);
|
||||
add_balance_change(db, &mut balance_changes, address, &burned_asset, -*value);
|
||||
add_balance_change(db, &mut balance_changes, address, &BASECOIN, -*fee);
|
||||
|
|
@ -552,6 +566,9 @@ pub async fn apply_selected_transaction_math(
|
|||
hash,
|
||||
..
|
||||
} => {
|
||||
miner_earnings
|
||||
.add_fee(nonnegative_u64(*fee, "selected NFT fee")?)
|
||||
.map_err(|err| anyhow!(err))?;
|
||||
// Series-one NFT creation expands into numbered assets; later
|
||||
// series use the given NFT name as the asset key.
|
||||
if *series == 1 {
|
||||
|
|
@ -622,6 +639,9 @@ pub async fn apply_selected_transaction_math(
|
|||
hash,
|
||||
..
|
||||
} => {
|
||||
miner_earnings
|
||||
.add_fee(nonnegative_u64(*fee, "selected marketing fee")?)
|
||||
.map_err(|err| anyhow!(err))?;
|
||||
add_balance_change(db, &mut balance_changes, advertiser, &BASECOIN, -*fee);
|
||||
add_balance_change_bytes(
|
||||
&mut balance_changes,
|
||||
|
|
@ -649,6 +669,9 @@ pub async fn apply_selected_transaction_math(
|
|||
hash,
|
||||
..
|
||||
} => {
|
||||
miner_earnings
|
||||
.add_fee(nonnegative_u64(*fee, "selected vanity fee")?)
|
||||
.map_err(|err| anyhow!(err))?;
|
||||
// Vanity transactions update the alias registry during the
|
||||
// final block-effect commit so rollback can restore it.
|
||||
pending_effects.update_vanity(address, vanity_address, hash);
|
||||
|
|
@ -688,6 +711,28 @@ pub async fn apply_selected_transaction_math(
|
|||
hash,
|
||||
..
|
||||
} => {
|
||||
miner_earnings
|
||||
.add_fee(nonnegative_u64(*fee1, "selected swap fee1")?)
|
||||
.map_err(|err| anyhow!(err))?;
|
||||
miner_earnings
|
||||
.add_fee(nonnegative_u64(*fee2, "selected swap fee2")?)
|
||||
.map_err(|err| anyhow!(err))?;
|
||||
miner_earnings
|
||||
.add_tip(
|
||||
ticker1,
|
||||
u32::try_from(*nft_series1)
|
||||
.map_err(|_| anyhow!("Selected swap NFT series1 was negative"))?,
|
||||
nonnegative_u64(*tip1, "selected swap tip1")?,
|
||||
)
|
||||
.map_err(|err| anyhow!(err))?;
|
||||
miner_earnings
|
||||
.add_tip(
|
||||
ticker2,
|
||||
u32::try_from(*nft_series2)
|
||||
.map_err(|_| anyhow!("Selected swap NFT series2 was negative"))?,
|
||||
nonnegative_u64(*tip2, "selected swap tip2")?,
|
||||
)
|
||||
.map_err(|err| anyhow!(err))?;
|
||||
// Swaps debit both sides first, then credit received assets,
|
||||
// miner fees, and optional asset tips.
|
||||
let asset1 = nft_asset_name(ticker1, *nft_series1 as u32);
|
||||
|
|
@ -764,6 +809,9 @@ pub async fn apply_selected_transaction_math(
|
|||
hash,
|
||||
..
|
||||
} => {
|
||||
miner_earnings
|
||||
.add_fee(nonnegative_u64(*fee, "selected loan fee")?)
|
||||
.map_err(|err| anyhow!(err))?;
|
||||
// Loan creation moves the loan asset to the borrower and locks
|
||||
// collateral under a contract-specific holding key.
|
||||
add_balance_change(db, &mut balance_changes, lender, &BASECOIN, -*fee);
|
||||
|
|
@ -833,6 +881,16 @@ pub async fn apply_selected_transaction_math(
|
|||
// Loan payments resolve the contract, then move repayment from
|
||||
// borrower to lender while paying any tip to the miner.
|
||||
let (loan_coin, lender) = resolve_loan_details(db, contract_hash).await?;
|
||||
miner_earnings
|
||||
.add_fee(nonnegative_u64(*fee, "selected loan payment fee")?)
|
||||
.map_err(|err| anyhow!(err))?;
|
||||
miner_earnings
|
||||
.add_tip(
|
||||
&binary_to_string(loan_coin.clone()),
|
||||
0,
|
||||
nonnegative_u64(*tip, "selected loan payment tip")?,
|
||||
)
|
||||
.map_err(|err| anyhow!(err))?;
|
||||
add_balance_change(db, &mut balance_changes, address, &BASECOIN, -*fee);
|
||||
add_balance_change_bytes(
|
||||
&mut balance_changes,
|
||||
|
|
@ -897,6 +955,9 @@ pub async fn apply_selected_transaction_math(
|
|||
hash,
|
||||
..
|
||||
} => {
|
||||
miner_earnings
|
||||
.add_fee(nonnegative_u64(*fee, "selected collateral fee")?)
|
||||
.map_err(|err| anyhow!(err))?;
|
||||
// Collateral claims release the contract holding to the claimant
|
||||
// and mark the loan as closed.
|
||||
let (collateral, collateral_amount) =
|
||||
|
|
@ -965,7 +1026,11 @@ pub async fn apply_selected_transaction_math(
|
|||
pending_effects.add_balance(&address, change.unsigned_abs(), &coin, operand);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(miner_earnings)
|
||||
}
|
||||
|
||||
fn nonnegative_u64(value: i64, field: &str) -> Result<u64> {
|
||||
u64::try_from(value).map_err(|_| anyhow!("{field} cannot be negative"))
|
||||
}
|
||||
|
||||
pub async fn clear_selected_transaction_sql(
|
||||
|
|
@ -979,7 +1044,7 @@ pub async fn clear_selected_transaction_sql(
|
|||
// selected batch into the block file afterward.
|
||||
let batch = select_transactions_for_block(limit).await?;
|
||||
let mut pending_effects = PendingEffects::default();
|
||||
apply_selected_transaction_math(
|
||||
let _miner_earnings = apply_selected_transaction_math(
|
||||
&batch,
|
||||
db,
|
||||
miner,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,248 @@
|
|||
use crate::blocks::loans::LoanContractTransaction;
|
||||
use crate::common::types::Transaction;
|
||||
use crate::decode;
|
||||
use crate::records::memory::mempool::BASECOIN;
|
||||
use crate::rpc::commands::transaction_by_txid::request_transaction_by_txid;
|
||||
use crate::rpc::responses::RpcResponse;
|
||||
use crate::sled::Db;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
pub const MINER_EARNING_FEE: u8 = 0;
|
||||
pub const MINER_EARNING_TIP: u8 = 1;
|
||||
pub const MINER_EARNING_ASSET_BYTES: usize = 15;
|
||||
pub const MINER_EARNING_ENTRY_BYTES: usize = 1 + MINER_EARNING_ASSET_BYTES + 4 + 8;
|
||||
const FINALIZED_MINER_EARNINGS_TREE: &str = "finalized_miner_earnings";
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct MinerEarnings {
|
||||
entries: BTreeMap<(u8, String, u32), u64>,
|
||||
}
|
||||
|
||||
impl MinerEarnings {
|
||||
pub fn add_fee(&mut self, amount: u64) -> Result<(), String> {
|
||||
self.add(MINER_EARNING_FEE, BASECOIN.clone(), 0, amount)
|
||||
}
|
||||
|
||||
pub fn add_tip(&mut self, asset: &str, nft_series: u32, amount: u64) -> Result<(), String> {
|
||||
self.add(MINER_EARNING_TIP, asset.to_string(), nft_series, amount)
|
||||
}
|
||||
|
||||
fn add(
|
||||
&mut self,
|
||||
earning_type: u8,
|
||||
asset: String,
|
||||
nft_series: u32,
|
||||
amount: u64,
|
||||
) -> Result<(), String> {
|
||||
if amount == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
let asset = fixed_asset_name(&asset)?;
|
||||
let entry = self
|
||||
.entries
|
||||
.entry((earning_type, asset, nft_series))
|
||||
.or_default();
|
||||
*entry = entry
|
||||
.checked_add(amount)
|
||||
.ok_or_else(|| "Miner earnings amount overflowed u64.".to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn merge(&mut self, other: MinerEarnings) -> Result<(), String> {
|
||||
for ((earning_type, asset, nft_series), amount) in other.entries {
|
||||
self.add(earning_type, asset, nft_series, amount)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn encode(&self) -> Result<Vec<u8>, String> {
|
||||
let count = u32::try_from(self.entries.len())
|
||||
.map_err(|_| "Miner earnings entry count overflowed u32.".to_string())?;
|
||||
let mut bytes =
|
||||
Vec::with_capacity(4 + self.entries.len().saturating_mul(MINER_EARNING_ENTRY_BYTES));
|
||||
bytes.extend_from_slice(&count.to_le_bytes());
|
||||
|
||||
for ((earning_type, asset, nft_series), amount) in &self.entries {
|
||||
bytes.push(*earning_type);
|
||||
bytes.extend_from_slice(asset.as_bytes());
|
||||
bytes.extend_from_slice(&nft_series.to_le_bytes());
|
||||
bytes.extend_from_slice(&amount.to_le_bytes());
|
||||
}
|
||||
Ok(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
fn fixed_asset_name(asset: &str) -> Result<String, String> {
|
||||
let bytes = asset.as_bytes();
|
||||
if bytes.len() != MINER_EARNING_ASSET_BYTES {
|
||||
return Err(format!(
|
||||
"Miner earning asset must be exactly {MINER_EARNING_ASSET_BYTES} bytes: {asset:?}"
|
||||
));
|
||||
}
|
||||
Ok(asset.to_string())
|
||||
}
|
||||
|
||||
async fn loan_payment_tip_asset(db: &Db, contract_hash: &str) -> Result<String, String> {
|
||||
let contract_hash = decode(contract_hash)
|
||||
.map_err(|err| format!("Failed to decode loan contract hash for miner tip: {err}"))?;
|
||||
let RpcResponse::Binary(bytes) = request_transaction_by_txid(db, contract_hash).await;
|
||||
if bytes.is_empty() || bytes[0] != 7 {
|
||||
return Err("Loan payment miner tip references an invalid loan contract.".to_string());
|
||||
}
|
||||
let loan = LoanContractTransaction::from_bytes(7, &bytes[1..])
|
||||
.await
|
||||
.map_err(|err| format!("Failed to decode loan contract for miner tip: {err}"))?;
|
||||
fixed_asset_name(&loan.unsigned_loan_contract.loan_coin)
|
||||
}
|
||||
|
||||
pub async fn earnings_from_transactions(
|
||||
db: &Db,
|
||||
transactions: &[Transaction],
|
||||
) -> Result<MinerEarnings, String> {
|
||||
let mut earnings = MinerEarnings::default();
|
||||
|
||||
for transaction in transactions {
|
||||
match transaction {
|
||||
Transaction::Genesis(_) | Transaction::Rewards(_) => {}
|
||||
Transaction::Transfer(tx) => earnings.add_fee(tx.unsigned_transfer.txfee)?,
|
||||
Transaction::Token(tx) => earnings.add_fee(tx.unsigned_create_token.txfee)?,
|
||||
Transaction::IssueToken(tx) => earnings.add_fee(tx.unsigned_issue_token.txfee)?,
|
||||
Transaction::Burn(tx) => earnings.add_fee(tx.unsigned_burn.txfee)?,
|
||||
Transaction::Nft(tx) => earnings.add_fee(tx.unsigned_create_nft.txfee)?,
|
||||
Transaction::Marketing(tx) => earnings.add_fee(tx.unsigned_marketing.txfee)?,
|
||||
Transaction::Swap(tx) => {
|
||||
earnings.add_fee(tx.unsigned_swap.txfee1)?;
|
||||
earnings.add_fee(tx.unsigned_swap.txfee2)?;
|
||||
earnings.add_tip(
|
||||
&tx.unsigned_swap.ticker1,
|
||||
tx.unsigned_swap.nft_series1,
|
||||
tx.unsigned_swap.tip1,
|
||||
)?;
|
||||
earnings.add_tip(
|
||||
&tx.unsigned_swap.ticker2,
|
||||
tx.unsigned_swap.nft_series2,
|
||||
tx.unsigned_swap.tip2,
|
||||
)?;
|
||||
}
|
||||
Transaction::Lender(tx) => earnings.add_fee(tx.unsigned_loan_contract.txfee)?,
|
||||
Transaction::Borrower(tx) => {
|
||||
earnings.add_fee(tx.unsigned_contract_payment.txfee)?;
|
||||
if tx.unsigned_contract_payment.tip > 0 {
|
||||
let asset =
|
||||
loan_payment_tip_asset(db, &tx.unsigned_contract_payment.contract_hash)
|
||||
.await?;
|
||||
earnings.add_tip(&asset, 0, tx.unsigned_contract_payment.tip)?;
|
||||
}
|
||||
}
|
||||
Transaction::Collateral(tx) => earnings.add_fee(tx.unsigned_collateral_claim.txfee)?,
|
||||
Transaction::Vanity(tx) => earnings.add_fee(tx.unsigned_vanity_address.txfee)?,
|
||||
}
|
||||
}
|
||||
|
||||
Ok(earnings)
|
||||
}
|
||||
|
||||
pub fn store_finalized_miner_earnings(
|
||||
db: &Db,
|
||||
reward_txid: &[u8],
|
||||
earnings: &MinerEarnings,
|
||||
) -> Result<(), String> {
|
||||
let encoded = earnings.encode()?;
|
||||
db.open_tree(FINALIZED_MINER_EARNINGS_TREE)
|
||||
.map_err(|err| format!("Failed to open finalized miner earnings tree: {err}"))?
|
||||
.insert(reward_txid, encoded)
|
||||
.map_err(|err| format!("Failed to store finalized miner earnings: {err}"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn finalized_miner_earnings_bytes(db: &Db, reward_txid: &[u8]) -> Result<Vec<u8>, String> {
|
||||
let tree = db
|
||||
.open_tree(FINALIZED_MINER_EARNINGS_TREE)
|
||||
.map_err(|err| format!("Failed to open finalized miner earnings tree: {err}"))?;
|
||||
let encoded = tree
|
||||
.get(reward_txid)
|
||||
.map_err(|err| format!("Failed to read finalized miner earnings: {err}"))?
|
||||
.map(|value| value.to_vec())
|
||||
.unwrap_or_else(|| 0u32.to_le_bytes().to_vec());
|
||||
validate_encoded_miner_earnings(&encoded)?;
|
||||
Ok(encoded)
|
||||
}
|
||||
|
||||
pub fn remove_finalized_miner_earnings(db: &Db, reward_txid: &[u8]) {
|
||||
if let Ok(tree) = db.open_tree(FINALIZED_MINER_EARNINGS_TREE) {
|
||||
let _ = tree.remove(reward_txid);
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_encoded_miner_earnings(encoded: &[u8]) -> Result<(), String> {
|
||||
let count_bytes = encoded
|
||||
.get(0..4)
|
||||
.ok_or_else(|| "Finalized miner earnings record is missing its count.".to_string())?;
|
||||
let count = u32::from_le_bytes(
|
||||
count_bytes
|
||||
.try_into()
|
||||
.map_err(|_| "Finalized miner earnings count was invalid.".to_string())?,
|
||||
) as usize;
|
||||
let expected = 4usize
|
||||
.checked_add(
|
||||
count
|
||||
.checked_mul(MINER_EARNING_ENTRY_BYTES)
|
||||
.ok_or_else(|| "Finalized miner earnings length overflowed.".to_string())?,
|
||||
)
|
||||
.ok_or_else(|| "Finalized miner earnings length overflowed.".to_string())?;
|
||||
if encoded.len() != expected {
|
||||
return Err(format!(
|
||||
"Finalized miner earnings record has invalid length: expected {expected}, found {}.",
|
||||
encoded.len()
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn combines_matching_fees_and_tips() {
|
||||
let mut earnings = MinerEarnings::default();
|
||||
earnings.add_fee(10).unwrap();
|
||||
earnings.add_fee(20).unwrap();
|
||||
earnings.add_tip("example ", 0, 7).unwrap();
|
||||
earnings.add_tip("example ", 0, 8).unwrap();
|
||||
|
||||
let encoded = earnings.encode().unwrap();
|
||||
assert_eq!(u32::from_le_bytes(encoded[0..4].try_into().unwrap()), 2);
|
||||
assert_eq!(encoded.len(), 4 + (2 * MINER_EARNING_ENTRY_BYTES));
|
||||
assert!(encoded.windows(8).any(|bytes| bytes == 30u64.to_le_bytes()));
|
||||
assert!(encoded.windows(8).any(|bytes| bytes == 15u64.to_le_bytes()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn separates_nft_tip_series() {
|
||||
let mut earnings = MinerEarnings::default();
|
||||
earnings.add_tip("example ", 1, 1).unwrap();
|
||||
earnings.add_tip("example ", 2, 1).unwrap();
|
||||
|
||||
let encoded = earnings.encode().unwrap();
|
||||
assert_eq!(u32::from_le_bytes(encoded[0..4].try_into().unwrap()), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn persists_and_removes_finalized_earnings() {
|
||||
let db = crate::sled::Config::new().temporary(true).open().unwrap();
|
||||
let reward_txid = [7u8; 32];
|
||||
let mut earnings = MinerEarnings::default();
|
||||
earnings.add_fee(42).unwrap();
|
||||
|
||||
store_finalized_miner_earnings(&db, &reward_txid, &earnings).unwrap();
|
||||
let stored = finalized_miner_earnings_bytes(&db, &reward_txid).unwrap();
|
||||
assert_eq!(u32::from_le_bytes(stored[0..4].try_into().unwrap()), 1);
|
||||
|
||||
remove_finalized_miner_earnings(&db, &reward_txid);
|
||||
assert_eq!(
|
||||
finalized_miner_earnings_bytes(&db, &reward_txid).unwrap(),
|
||||
0u32.to_le_bytes()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ pub mod header_number;
|
|||
pub mod issue_token_tx;
|
||||
pub mod lender_tx;
|
||||
pub mod marketing_tx;
|
||||
pub mod miner_earnings;
|
||||
pub mod nft_provenance;
|
||||
pub mod nft_tx;
|
||||
pub mod parse_transactions;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ use crate::lazy_static;
|
|||
use crate::records::balance_sheet::operations::balance_sheet_operation_with_db;
|
||||
use crate::records::block_height::get_block_height::get_height;
|
||||
use crate::records::memory::mempool::BASECOIN;
|
||||
use crate::records::record_chain::miner_earnings::{
|
||||
earnings_from_transactions, store_finalized_miner_earnings, MinerEarnings,
|
||||
};
|
||||
use crate::records::record_chain::pending_effects::PendingEffects;
|
||||
use crate::records::record_chain::wallet_tx_index::index_wallet_transaction_direct;
|
||||
use crate::records::unpack_block::load_by_block_number::load_block;
|
||||
|
|
@ -23,6 +26,7 @@ struct ImmatureReward {
|
|||
amount: u64,
|
||||
txid: Vec<u8>,
|
||||
entry_index: u32,
|
||||
earnings: MinerEarnings,
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
|
|
@ -56,6 +60,8 @@ fn mark_reward_credit_applied(db: &Db, block_height: u32) -> Result<(), String>
|
|||
}
|
||||
|
||||
fn credit_matured_reward(db: &Db, reward: ImmatureReward) -> Result<(), String> {
|
||||
store_finalized_miner_earnings(db, &reward.txid, &reward.earnings)?;
|
||||
|
||||
if reward_credit_applied(db, reward.block_height) {
|
||||
return Ok(());
|
||||
}
|
||||
|
|
@ -82,6 +88,7 @@ pub async fn record_saved_reward(
|
|||
amount: u64,
|
||||
txid: Vec<u8>,
|
||||
entry_index: u32,
|
||||
earnings: MinerEarnings,
|
||||
) -> Result<(), String> {
|
||||
let matured = {
|
||||
let mut rewards = IMMATURE_REWARDS.lock().await;
|
||||
|
|
@ -92,6 +99,7 @@ pub async fn record_saved_reward(
|
|||
amount,
|
||||
txid,
|
||||
entry_index,
|
||||
earnings,
|
||||
});
|
||||
|
||||
if rewards.len() > IMMATURE_REWARD_WINDOW {
|
||||
|
|
@ -124,6 +132,7 @@ pub async fn rebuild_immature_reward_window(db: &Db) -> Result<(), String> {
|
|||
Err(_) => continue,
|
||||
};
|
||||
let miner = block.vrf_block.unmined_block.miner;
|
||||
let earnings = earnings_from_transactions(db, &block.transactions).await?;
|
||||
|
||||
for (position, transaction) in block.transactions.into_iter().enumerate() {
|
||||
if let Transaction::Rewards(rewards) = transaction {
|
||||
|
|
@ -137,6 +146,7 @@ pub async fn rebuild_immature_reward_window(db: &Db) -> Result<(), String> {
|
|||
amount: rewards.unsigned.value,
|
||||
txid,
|
||||
entry_index,
|
||||
earnings: earnings.clone(),
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ use crate::records::memory::mempool::{
|
|||
};
|
||||
use crate::records::memory::network_mapping::NodeInfo;
|
||||
use crate::records::memory::torrent_status::prune_torrent_statuses_through_height;
|
||||
use crate::records::record_chain::miner_earnings::earnings_from_transactions;
|
||||
use crate::records::record_chain::parse_transactions::handle_transactions;
|
||||
use crate::records::record_chain::pending_effects::PendingEffects;
|
||||
use crate::records::record_chain::previous_difficulty::previous_block_difficulty;
|
||||
|
|
@ -132,6 +133,7 @@ pub async fn save_block(params: SaveBlockParams) -> Result<(), String> {
|
|||
let mut index_counter = 0;
|
||||
let index_mutex = Arc::new(Mutex::new(&mut index_counter));
|
||||
let mut pending_effects = PendingEffects::default();
|
||||
let mut miner_earnings = earnings_from_transactions(&db, &block.transactions).await?;
|
||||
|
||||
// Append transaction-derived record data to the block binary while
|
||||
// tracking the index offset where mempool-derived records begin.
|
||||
|
|
@ -171,7 +173,7 @@ pub async fn save_block(params: SaveBlockParams) -> Result<(), String> {
|
|||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
apply_selected_transaction_math(
|
||||
let selected_earnings = apply_selected_transaction_math(
|
||||
&selected,
|
||||
&db,
|
||||
miner.clone(),
|
||||
|
|
@ -181,6 +183,7 @@ pub async fn save_block(params: SaveBlockParams) -> Result<(), String> {
|
|||
)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
miner_earnings.merge(selected_earnings)?;
|
||||
|
||||
save_binary_data_with_mempool_stream(SaveBinaryDataWithMempoolStreamParams {
|
||||
data: &binary_data,
|
||||
|
|
@ -227,6 +230,7 @@ pub async fn save_block(params: SaveBlockParams) -> Result<(), String> {
|
|||
amount,
|
||||
txid,
|
||||
entry_index,
|
||||
miner_earnings,
|
||||
)
|
||||
.await
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use crate::records::memory::mempool::latest_pending_txids_by_address;
|
||||
use crate::records::record_chain::miner_earnings::finalized_miner_earnings_bytes;
|
||||
use crate::records::record_chain::wallet_tx_index::WALLET_TX_INDEX_TREE;
|
||||
use crate::rpc::commands::transaction_by_txid::transaction_bytes_with_block;
|
||||
use crate::rpc::responses::RpcResponse;
|
||||
|
|
@ -38,6 +39,16 @@ async fn transaction_records_response(
|
|||
for txid in txid_bytes.chunks_exact(TXID_LENGTH) {
|
||||
match transaction_bytes_with_block(db, txid).await {
|
||||
Ok((block_height, transaction_bytes)) => {
|
||||
let miner_earnings = if transaction_bytes.first() == Some(&1) {
|
||||
match finalized_miner_earnings_bytes(db, txid) {
|
||||
Ok(bytes) => bytes,
|
||||
Err(err) => {
|
||||
return RpcResponse::Binary(format!("error: {err}").into_bytes());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
0u32.to_le_bytes().to_vec()
|
||||
};
|
||||
let payload = if strip_signatures {
|
||||
match without_signature_bytes(&transaction_bytes) {
|
||||
Ok(payload) => payload,
|
||||
|
|
@ -46,7 +57,7 @@ async fn transaction_records_response(
|
|||
} else {
|
||||
transaction_bytes
|
||||
};
|
||||
entries.push((txid.to_vec(), block_height, payload));
|
||||
entries.push((txid.to_vec(), block_height, payload, miner_earnings));
|
||||
}
|
||||
Err(err) => {
|
||||
return RpcResponse::Binary(err.into_bytes());
|
||||
|
|
@ -56,7 +67,7 @@ async fn transaction_records_response(
|
|||
|
||||
let mut response = Vec::new();
|
||||
response.extend_from_slice(&(entries.len() as u32).to_le_bytes());
|
||||
for (txid, block_height, transaction_bytes) in entries {
|
||||
for (txid, block_height, transaction_bytes, miner_earnings) in entries {
|
||||
let Ok(tx_len) = u32::try_from(transaction_bytes.len()) else {
|
||||
return RpcResponse::Binary(b"error: Transaction payload too large".to_vec());
|
||||
};
|
||||
|
|
@ -64,6 +75,10 @@ async fn transaction_records_response(
|
|||
response.extend_from_slice(&block_height.to_le_bytes());
|
||||
response.extend_from_slice(&tx_len.to_le_bytes());
|
||||
response.extend_from_slice(&transaction_bytes);
|
||||
// Every history record ends with a miner-earnings count. Reward
|
||||
// records may then include fixed entries of:
|
||||
// type:u8, asset:[u8;15], nft_series:u32, amount:u64.
|
||||
response.extend_from_slice(&miner_earnings);
|
||||
}
|
||||
|
||||
RpcResponse::Binary(response)
|
||||
|
|
|
|||
Loading…
Reference in New Issue