miner rewards reporting update
This commit is contained in:
parent
05b027f30e
commit
a85e9aab4c
|
|
@ -2,7 +2,6 @@ use crate::blocks::rewards::RewardsTransaction;
|
||||||
use crate::common::network_paths_and_settings::block_extension_and_paths;
|
use crate::common::network_paths_and_settings::block_extension_and_paths;
|
||||||
use crate::decode;
|
use crate::decode;
|
||||||
use crate::records::balance_sheet::operations::balance_sheet_operation_with_db;
|
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::{
|
use crate::records::record_chain::rewards_tx::{
|
||||||
remove_immature_reward, remove_reward_credit_marker, reward_credit_applied,
|
remove_immature_reward, remove_reward_credit_marker, reward_credit_applied,
|
||||||
};
|
};
|
||||||
|
|
@ -42,7 +41,6 @@ pub async fn undo_rewards_transaction(
|
||||||
|
|
||||||
let hash = decode(transaction.unsigned.hash().await).unwrap();
|
let hash = decode(transaction.unsigned.hash().await).unwrap();
|
||||||
let _ = remove_wallet_transaction_index(db, &[mining_receiver], &hash);
|
let _ = remove_wallet_transaction_index(db, &[mining_receiver], &hash);
|
||||||
remove_finalized_miner_earnings(db, &hash);
|
|
||||||
|
|
||||||
// Remove the reward transaction lookup from the txid tree.
|
// Remove the reward transaction lookup from the txid tree.
|
||||||
let tree = db.open_tree("txid").unwrap();
|
let tree = db.open_tree("txid").unwrap();
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::records::record_chain::miner_earnings::MinerEarnings;
|
|
||||||
use crate::records::record_chain::pending_effects::{BalanceOperand, PendingEffects};
|
use crate::records::record_chain::pending_effects::{BalanceOperand, PendingEffects};
|
||||||
use crate::records::record_chain::wallet_tx_index::index_wallet_transaction;
|
use crate::records::record_chain::wallet_tx_index::index_wallet_transaction;
|
||||||
|
|
||||||
|
|
@ -348,15 +347,13 @@ pub async fn apply_selected_transaction_math(
|
||||||
block_number: u32,
|
block_number: u32,
|
||||||
start_index: usize,
|
start_index: usize,
|
||||||
pending_effects: &mut PendingEffects,
|
pending_effects: &mut PendingEffects,
|
||||||
) -> Result<MinerEarnings> {
|
) -> Result<()> {
|
||||||
// Net balance deltas are collected first so multiple changes to the same
|
// Net balance deltas are collected first so multiple changes to the same
|
||||||
// address/asset pair become one balance-sheet write.
|
// address/asset pair become one balance-sheet write.
|
||||||
let mut balance_changes: HashMap<BalanceKey, i64> = HashMap::new();
|
let mut balance_changes: HashMap<BalanceKey, i64> = HashMap::new();
|
||||||
let miner_address = address_key_bytes(db, &miner);
|
let miner_address = address_key_bytes(db, &miner);
|
||||||
let base_coin = BASECOIN.as_bytes().to_vec();
|
let base_coin = BASECOIN.as_bytes().to_vec();
|
||||||
let mut tx_index = start_index;
|
let mut tx_index = start_index;
|
||||||
let mut miner_earnings = MinerEarnings::default();
|
|
||||||
|
|
||||||
for tx in &batch.transactions {
|
for tx in &batch.transactions {
|
||||||
// Transaction index is stored with the block number for later txid
|
// Transaction index is stored with the block number for later txid
|
||||||
// lookups inside the saved block payload.
|
// lookups inside the saved block payload.
|
||||||
|
|
@ -372,9 +369,6 @@ pub async fn apply_selected_transaction_math(
|
||||||
hash,
|
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
|
// Transfers move the asset, charge the base fee, and credit
|
||||||
// that fee to the miner including this transaction.
|
// that fee to the miner including this transaction.
|
||||||
let transfer_asset = nft_asset_name(coin, *nft_series as u32);
|
let transfer_asset = nft_asset_name(coin, *nft_series as u32);
|
||||||
|
|
@ -416,9 +410,6 @@ pub async fn apply_selected_transaction_math(
|
||||||
hash,
|
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
|
// Token creation mints the initial supply and records metadata
|
||||||
// used by later issue and burn validation.
|
// used by later issue and burn validation.
|
||||||
add_balance_change(db, &mut balance_changes, creator, ticker, *number);
|
add_balance_change(db, &mut balance_changes, creator, ticker, *number);
|
||||||
|
|
@ -486,9 +477,6 @@ pub async fn apply_selected_transaction_math(
|
||||||
hash,
|
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, ticker, *number);
|
||||||
add_balance_change(db, &mut balance_changes, creator, &BASECOIN, -*fee);
|
add_balance_change(db, &mut balance_changes, creator, &BASECOIN, -*fee);
|
||||||
add_balance_change_bytes(
|
add_balance_change_bytes(
|
||||||
|
|
@ -528,9 +516,6 @@ pub async fn apply_selected_transaction_math(
|
||||||
hash,
|
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);
|
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, &burned_asset, -*value);
|
||||||
add_balance_change(db, &mut balance_changes, address, &BASECOIN, -*fee);
|
add_balance_change(db, &mut balance_changes, address, &BASECOIN, -*fee);
|
||||||
|
|
@ -566,9 +551,6 @@ pub async fn apply_selected_transaction_math(
|
||||||
hash,
|
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-one NFT creation expands into numbered assets; later
|
||||||
// series use the given NFT name as the asset key.
|
// series use the given NFT name as the asset key.
|
||||||
if *series == 1 {
|
if *series == 1 {
|
||||||
|
|
@ -639,9 +621,6 @@ pub async fn apply_selected_transaction_math(
|
||||||
hash,
|
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(db, &mut balance_changes, advertiser, &BASECOIN, -*fee);
|
||||||
add_balance_change_bytes(
|
add_balance_change_bytes(
|
||||||
&mut balance_changes,
|
&mut balance_changes,
|
||||||
|
|
@ -669,9 +648,6 @@ pub async fn apply_selected_transaction_math(
|
||||||
hash,
|
hash,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
miner_earnings
|
|
||||||
.add_fee(nonnegative_u64(*fee, "selected vanity fee")?)
|
|
||||||
.map_err(|err| anyhow!(err))?;
|
|
||||||
// Vanity transactions update the alias registry during the
|
// Vanity transactions update the alias registry during the
|
||||||
// final block-effect commit so rollback can restore it.
|
// final block-effect commit so rollback can restore it.
|
||||||
pending_effects.update_vanity(address, vanity_address, hash);
|
pending_effects.update_vanity(address, vanity_address, hash);
|
||||||
|
|
@ -711,28 +687,6 @@ pub async fn apply_selected_transaction_math(
|
||||||
hash,
|
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,
|
// Swaps debit both sides first, then credit received assets,
|
||||||
// miner fees, and optional asset tips.
|
// miner fees, and optional asset tips.
|
||||||
let asset1 = nft_asset_name(ticker1, *nft_series1 as u32);
|
let asset1 = nft_asset_name(ticker1, *nft_series1 as u32);
|
||||||
|
|
@ -809,9 +763,6 @@ pub async fn apply_selected_transaction_math(
|
||||||
hash,
|
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
|
// Loan creation moves the loan asset to the borrower and locks
|
||||||
// collateral under a contract-specific holding key.
|
// collateral under a contract-specific holding key.
|
||||||
add_balance_change(db, &mut balance_changes, lender, &BASECOIN, -*fee);
|
add_balance_change(db, &mut balance_changes, lender, &BASECOIN, -*fee);
|
||||||
|
|
@ -881,16 +832,6 @@ pub async fn apply_selected_transaction_math(
|
||||||
// Loan payments resolve the contract, then move repayment from
|
// Loan payments resolve the contract, then move repayment from
|
||||||
// borrower to lender while paying any tip to the miner.
|
// borrower to lender while paying any tip to the miner.
|
||||||
let (loan_coin, lender) = resolve_loan_details(db, contract_hash).await?;
|
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(db, &mut balance_changes, address, &BASECOIN, -*fee);
|
||||||
add_balance_change_bytes(
|
add_balance_change_bytes(
|
||||||
&mut balance_changes,
|
&mut balance_changes,
|
||||||
|
|
@ -955,9 +896,6 @@ pub async fn apply_selected_transaction_math(
|
||||||
hash,
|
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
|
// Collateral claims release the contract holding to the claimant
|
||||||
// and mark the loan as closed.
|
// and mark the loan as closed.
|
||||||
let (collateral, collateral_amount) =
|
let (collateral, collateral_amount) =
|
||||||
|
|
@ -1026,11 +964,7 @@ pub async fn apply_selected_transaction_math(
|
||||||
pending_effects.add_balance(&address, change.unsigned_abs(), &coin, operand);
|
pending_effects.add_balance(&address, change.unsigned_abs(), &coin, operand);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(miner_earnings)
|
Ok(())
|
||||||
}
|
|
||||||
|
|
||||||
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(
|
pub async fn clear_selected_transaction_sql(
|
||||||
|
|
@ -1044,7 +978,7 @@ pub async fn clear_selected_transaction_sql(
|
||||||
// selected batch into the block file afterward.
|
// selected batch into the block file afterward.
|
||||||
let batch = select_transactions_for_block(limit).await?;
|
let batch = select_transactions_for_block(limit).await?;
|
||||||
let mut pending_effects = PendingEffects::default();
|
let mut pending_effects = PendingEffects::default();
|
||||||
let _miner_earnings = apply_selected_transaction_math(
|
apply_selected_transaction_math(
|
||||||
&batch,
|
&batch,
|
||||||
db,
|
db,
|
||||||
miner,
|
miner,
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ pub mod header_number;
|
||||||
pub mod issue_token_tx;
|
pub mod issue_token_tx;
|
||||||
pub mod lender_tx;
|
pub mod lender_tx;
|
||||||
pub mod marketing_tx;
|
pub mod marketing_tx;
|
||||||
pub mod miner_earnings;
|
|
||||||
pub mod nft_provenance;
|
pub mod nft_provenance;
|
||||||
pub mod nft_tx;
|
pub mod nft_tx;
|
||||||
pub mod parse_transactions;
|
pub mod parse_transactions;
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,6 @@ use crate::lazy_static;
|
||||||
use crate::records::balance_sheet::operations::balance_sheet_operation_with_db;
|
use crate::records::balance_sheet::operations::balance_sheet_operation_with_db;
|
||||||
use crate::records::block_height::get_block_height::get_height;
|
use crate::records::block_height::get_block_height::get_height;
|
||||||
use crate::records::memory::mempool::BASECOIN;
|
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::pending_effects::PendingEffects;
|
||||||
use crate::records::record_chain::wallet_tx_index::index_wallet_transaction_direct;
|
use crate::records::record_chain::wallet_tx_index::index_wallet_transaction_direct;
|
||||||
use crate::records::unpack_block::load_by_block_number::load_block;
|
use crate::records::unpack_block::load_by_block_number::load_block;
|
||||||
|
|
@ -26,7 +23,6 @@ struct ImmatureReward {
|
||||||
amount: u64,
|
amount: u64,
|
||||||
txid: Vec<u8>,
|
txid: Vec<u8>,
|
||||||
entry_index: u32,
|
entry_index: u32,
|
||||||
earnings: MinerEarnings,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
|
|
@ -60,8 +56,6 @@ fn mark_reward_credit_applied(db: &Db, block_height: u32) -> Result<(), String>
|
||||||
}
|
}
|
||||||
|
|
||||||
fn credit_matured_reward(db: &Db, reward: ImmatureReward) -> 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) {
|
if reward_credit_applied(db, reward.block_height) {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
@ -88,7 +82,6 @@ pub async fn record_saved_reward(
|
||||||
amount: u64,
|
amount: u64,
|
||||||
txid: Vec<u8>,
|
txid: Vec<u8>,
|
||||||
entry_index: u32,
|
entry_index: u32,
|
||||||
earnings: MinerEarnings,
|
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let matured = {
|
let matured = {
|
||||||
let mut rewards = IMMATURE_REWARDS.lock().await;
|
let mut rewards = IMMATURE_REWARDS.lock().await;
|
||||||
|
|
@ -99,7 +92,6 @@ pub async fn record_saved_reward(
|
||||||
amount,
|
amount,
|
||||||
txid,
|
txid,
|
||||||
entry_index,
|
entry_index,
|
||||||
earnings,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if rewards.len() > IMMATURE_REWARD_WINDOW {
|
if rewards.len() > IMMATURE_REWARD_WINDOW {
|
||||||
|
|
@ -132,7 +124,6 @@ pub async fn rebuild_immature_reward_window(db: &Db) -> Result<(), String> {
|
||||||
Err(_) => continue,
|
Err(_) => continue,
|
||||||
};
|
};
|
||||||
let miner = block.vrf_block.unmined_block.miner;
|
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() {
|
for (position, transaction) in block.transactions.into_iter().enumerate() {
|
||||||
if let Transaction::Rewards(rewards) = transaction {
|
if let Transaction::Rewards(rewards) = transaction {
|
||||||
|
|
@ -146,7 +137,6 @@ pub async fn rebuild_immature_reward_window(db: &Db) -> Result<(), String> {
|
||||||
amount: rewards.unsigned.value,
|
amount: rewards.unsigned.value,
|
||||||
txid,
|
txid,
|
||||||
entry_index,
|
entry_index,
|
||||||
earnings: earnings.clone(),
|
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ use crate::records::memory::mempool::{
|
||||||
};
|
};
|
||||||
use crate::records::memory::network_mapping::NodeInfo;
|
use crate::records::memory::network_mapping::NodeInfo;
|
||||||
use crate::records::memory::torrent_status::prune_torrent_statuses_through_height;
|
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::parse_transactions::handle_transactions;
|
||||||
use crate::records::record_chain::pending_effects::PendingEffects;
|
use crate::records::record_chain::pending_effects::PendingEffects;
|
||||||
use crate::records::record_chain::previous_difficulty::previous_block_difficulty;
|
use crate::records::record_chain::previous_difficulty::previous_block_difficulty;
|
||||||
|
|
@ -133,8 +132,6 @@ pub async fn save_block(params: SaveBlockParams) -> Result<(), String> {
|
||||||
let mut index_counter = 0;
|
let mut index_counter = 0;
|
||||||
let index_mutex = Arc::new(Mutex::new(&mut index_counter));
|
let index_mutex = Arc::new(Mutex::new(&mut index_counter));
|
||||||
let mut pending_effects = PendingEffects::default();
|
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
|
// Append transaction-derived record data to the block binary while
|
||||||
// tracking the index offset where mempool-derived records begin.
|
// tracking the index offset where mempool-derived records begin.
|
||||||
binary_data = handle_transactions(
|
binary_data = handle_transactions(
|
||||||
|
|
@ -173,7 +170,7 @@ pub async fn save_block(params: SaveBlockParams) -> Result<(), String> {
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
let selected_earnings = apply_selected_transaction_math(
|
apply_selected_transaction_math(
|
||||||
&selected,
|
&selected,
|
||||||
&db,
|
&db,
|
||||||
miner.clone(),
|
miner.clone(),
|
||||||
|
|
@ -183,7 +180,6 @@ pub async fn save_block(params: SaveBlockParams) -> Result<(), String> {
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
miner_earnings.merge(selected_earnings)?;
|
|
||||||
|
|
||||||
save_binary_data_with_mempool_stream(SaveBinaryDataWithMempoolStreamParams {
|
save_binary_data_with_mempool_stream(SaveBinaryDataWithMempoolStreamParams {
|
||||||
data: &binary_data,
|
data: &binary_data,
|
||||||
|
|
@ -230,7 +226,6 @@ pub async fn save_block(params: SaveBlockParams) -> Result<(), String> {
|
||||||
amount,
|
amount,
|
||||||
txid,
|
txid,
|
||||||
entry_index,
|
entry_index,
|
||||||
miner_earnings,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,147 @@
|
||||||
|
use crate::blocks::loans::LoanContractTransaction;
|
||||||
|
use crate::common::types::{Transaction, COIN_LENGTH};
|
||||||
|
use crate::decode;
|
||||||
use crate::records::memory::mempool::latest_pending_txids_by_address;
|
use crate::records::memory::mempool::latest_pending_txids_by_address;
|
||||||
use crate::records::record_chain::miner_earnings::finalized_miner_earnings_bytes;
|
use crate::records::memory::mempool::BASECOIN;
|
||||||
use crate::records::record_chain::wallet_tx_index::WALLET_TX_INDEX_TREE;
|
use crate::records::record_chain::wallet_tx_index::WALLET_TX_INDEX_TREE;
|
||||||
|
use crate::records::unpack_block::load_by_block_number::load_block;
|
||||||
|
use crate::rpc::commands::transaction_by_txid::request_transaction_by_txid;
|
||||||
use crate::rpc::commands::transaction_by_txid::transaction_bytes_with_block;
|
use crate::rpc::commands::transaction_by_txid::transaction_bytes_with_block;
|
||||||
use crate::rpc::responses::RpcResponse;
|
use crate::rpc::responses::RpcResponse;
|
||||||
use crate::sled::Db;
|
use crate::sled::Db;
|
||||||
use crate::wallets::structures::Wallet;
|
use crate::wallets::structures::Wallet;
|
||||||
use std::collections::HashSet;
|
use std::collections::{BTreeMap, HashSet};
|
||||||
use std::ops::Bound;
|
use std::ops::Bound;
|
||||||
|
|
||||||
const TXID_LENGTH: usize = 32;
|
const TXID_LENGTH: usize = 32;
|
||||||
const MAX_ADDRESS_HISTORY_TXIDS: usize = 10_000;
|
const MAX_ADDRESS_HISTORY_TXIDS: usize = 10_000;
|
||||||
|
const MINER_EARNING_FEE: u8 = 0;
|
||||||
|
const MINER_EARNING_TIP: u8 = 1;
|
||||||
|
const MINER_EARNING_ENTRY_BYTES: usize = 1 + COIN_LENGTH + 4 + 8;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct MinerEarnings {
|
||||||
|
entries: BTreeMap<(u8, String, u32), u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MinerEarnings {
|
||||||
|
fn add_fee(&mut self, amount: u64) -> Result<(), String> {
|
||||||
|
self.add(MINER_EARNING_FEE, BASECOIN.clone(), 0, amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
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(());
|
||||||
|
}
|
||||||
|
if asset.as_bytes().len() != COIN_LENGTH {
|
||||||
|
return Err(format!(
|
||||||
|
"Miner earning asset must be exactly {COIN_LENGTH} bytes: {asset:?}"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let total = self
|
||||||
|
.entries
|
||||||
|
.entry((earning_type, asset, nft_series))
|
||||||
|
.or_default();
|
||||||
|
*total = total
|
||||||
|
.checked_add(amount)
|
||||||
|
.ok_or_else(|| "Miner earnings amount overflowed u64.".to_string())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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}"))?;
|
||||||
|
Ok(loan.unsigned_loan_contract.loan_coin)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn miner_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)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn miner_earnings_for_block(db: &Db, block_height: u32) -> Result<Vec<u8>, String> {
|
||||||
|
// Reward history is already delayed until maturity. Derive its associated
|
||||||
|
// fees and tips from the canonical block only when the RPC record is requested.
|
||||||
|
let block = load_block(block_height).await?;
|
||||||
|
miner_earnings_from_transactions(db, &block.transactions)
|
||||||
|
.await?
|
||||||
|
.encode()
|
||||||
|
}
|
||||||
|
|
||||||
fn signature_count(transaction_type: u8) -> usize {
|
fn signature_count(transaction_type: u8) -> usize {
|
||||||
match transaction_type {
|
match transaction_type {
|
||||||
|
|
@ -40,7 +172,7 @@ async fn transaction_records_response(
|
||||||
match transaction_bytes_with_block(db, txid).await {
|
match transaction_bytes_with_block(db, txid).await {
|
||||||
Ok((block_height, transaction_bytes)) => {
|
Ok((block_height, transaction_bytes)) => {
|
||||||
let miner_earnings = if transaction_bytes.first() == Some(&1) {
|
let miner_earnings = if transaction_bytes.first() == Some(&1) {
|
||||||
match finalized_miner_earnings_bytes(db, txid) {
|
match miner_earnings_for_block(db, block_height).await {
|
||||||
Ok(bytes) => bytes,
|
Ok(bytes) => bytes,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
return RpcResponse::Binary(format!("error: {err}").into_bytes());
|
return RpcResponse::Binary(format!("error: {err}").into_bytes());
|
||||||
|
|
@ -75,9 +207,6 @@ async fn transaction_records_response(
|
||||||
response.extend_from_slice(&block_height.to_le_bytes());
|
response.extend_from_slice(&block_height.to_le_bytes());
|
||||||
response.extend_from_slice(&tx_len.to_le_bytes());
|
response.extend_from_slice(&tx_len.to_le_bytes());
|
||||||
response.extend_from_slice(&transaction_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);
|
response.extend_from_slice(&miner_earnings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -235,3 +364,69 @@ pub async fn latest_transactions(address_bytes: Vec<u8>, limit: u32, db: &Db) ->
|
||||||
|
|
||||||
transaction_records_response(&txid_bytes, db, false).await
|
transaction_records_response(&txid_bytes, db, false).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::blocks::swap::{SwapTransaction, UnsignedSwapTransaction};
|
||||||
|
use crate::blocks::token::{CreateTokenTransaction, UnsignedCreateTokenTransaction};
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn derives_fees_and_tips_from_requested_block_transactions() {
|
||||||
|
let db = sled::Config::new().temporary(true).open().unwrap();
|
||||||
|
let token = Transaction::Token(CreateTokenTransaction {
|
||||||
|
unsigned_create_token: UnsignedCreateTokenTransaction {
|
||||||
|
txtype: 3,
|
||||||
|
time: 1,
|
||||||
|
creator: "creator".to_string(),
|
||||||
|
ticker: format!("{:<15}", "token"),
|
||||||
|
number: 1,
|
||||||
|
hard_limit: 0,
|
||||||
|
txfee: 500,
|
||||||
|
},
|
||||||
|
signature: String::new(),
|
||||||
|
});
|
||||||
|
let asset_one = format!("{:<15}", "assetone");
|
||||||
|
let asset_two = format!("{:<15}", "assettwo");
|
||||||
|
let swap = Transaction::Swap(SwapTransaction {
|
||||||
|
unsigned_swap: UnsignedSwapTransaction {
|
||||||
|
txtype: 6,
|
||||||
|
timestamp: 2,
|
||||||
|
offer_expiration: 3,
|
||||||
|
ticker1: asset_one.clone(),
|
||||||
|
nft_series1: 7,
|
||||||
|
value1: 1,
|
||||||
|
ticker2: asset_two.clone(),
|
||||||
|
nft_series2: 0,
|
||||||
|
value2: 1,
|
||||||
|
sender1: "sender1".to_string(),
|
||||||
|
sender2: "sender2".to_string(),
|
||||||
|
tip1: 11,
|
||||||
|
tip2: 13,
|
||||||
|
txfee1: 2,
|
||||||
|
txfee2: 3,
|
||||||
|
},
|
||||||
|
signature1: String::new(),
|
||||||
|
signature2: String::new(),
|
||||||
|
});
|
||||||
|
|
||||||
|
let earnings = miner_earnings_from_transactions(&db, &[token, swap])
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
earnings
|
||||||
|
.entries
|
||||||
|
.get(&(MINER_EARNING_FEE, BASECOIN.clone(), 0)),
|
||||||
|
Some(&505)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
earnings.entries.get(&(MINER_EARNING_TIP, asset_one, 7)),
|
||||||
|
Some(&11)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
earnings.entries.get(&(MINER_EARNING_TIP, asset_two, 0)),
|
||||||
|
Some(&13)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue