mirror of
https://github.com/mimblewimble/grin.git
synced 2025-01-20 19:11:08 +03:00
implement fix past fees RFC with fee shift since genesis (#3629)
This commit is contained in:
parent
a9f1dd7bcd
commit
f51b6e1376
20 changed files with 133 additions and 203 deletions
|
@ -13,7 +13,6 @@
|
|||
// limitations under the License.
|
||||
|
||||
use crate::chain;
|
||||
use crate::core::consensus::YEAR_HEIGHT;
|
||||
use crate::core::core::hash::Hashed;
|
||||
use crate::core::core::merkle_proof::MerkleProof;
|
||||
use crate::core::core::{FeeFields, KernelFeatures, TxKernel};
|
||||
|
@ -518,13 +517,10 @@ impl TxKernelPrintable {
|
|||
relative_height,
|
||||
} => (fee, relative_height.into()),
|
||||
};
|
||||
let height = 2 * YEAR_HEIGHT; // print as if post-HF4
|
||||
let fee = fee_fields.fee(height);
|
||||
let fee_shift: u8 = fee_fields.fee_shift(height);
|
||||
TxKernelPrintable {
|
||||
features,
|
||||
fee_shift,
|
||||
fee,
|
||||
fee_shift: fee_fields.fee_shift(),
|
||||
fee: fee_fields.fee(),
|
||||
lock_height,
|
||||
excess: k.excess.to_hex(),
|
||||
excess_sig: (&k.excess_sig.to_raw_data()[..]).to_hex(),
|
||||
|
|
|
@ -33,7 +33,7 @@ where
|
|||
{
|
||||
let prev = chain.head_header().unwrap();
|
||||
let next_header_info = consensus::next_difficulty(1, chain.difficulty_iter().unwrap());
|
||||
let fee = txs.iter().map(|x| x.fee(prev.height + 1)).sum();
|
||||
let fee = txs.iter().map(|x| x.fee()).sum();
|
||||
let reward =
|
||||
reward::output(keychain, &ProofBuilder::new(keychain), key_id, fee, false).unwrap();
|
||||
|
||||
|
|
|
@ -1030,8 +1030,7 @@ where
|
|||
let proof_size = global::proofsize();
|
||||
let key_id = ExtKeychainPath::new(1, key_idx, 0, 0, 0).to_identifier();
|
||||
|
||||
let height = prev.height + 1;
|
||||
let fees = txs.iter().map(|tx| tx.fee(height)).sum();
|
||||
let fees = txs.iter().map(|tx| tx.fee()).sum();
|
||||
let reward =
|
||||
libtx::reward::output(kc, &libtx::ProofBuilder::new(kc), &key_id, fees, false).unwrap();
|
||||
let mut b = match core::core::Block::new(prev, txs, Difficulty::from_num(diff), reward) {
|
||||
|
|
|
@ -54,7 +54,7 @@ where
|
|||
{
|
||||
let next_header_info =
|
||||
consensus::next_difficulty(prev.height, chain.difficulty_iter().unwrap());
|
||||
let fee = txs.iter().map(|x| x.fee(prev.height + 1)).sum();
|
||||
let fee = txs.iter().map(|x| x.fee()).sum();
|
||||
let reward =
|
||||
reward::output(keychain, &ProofBuilder::new(keychain), key_id, fee, false).unwrap();
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ where
|
|||
let prev = chain.head_header().unwrap();
|
||||
let next_height = prev.height + 1;
|
||||
let next_header_info = consensus::next_difficulty(1, chain.difficulty_iter()?);
|
||||
let fee = txs.iter().map(|x| x.fee(next_height)).sum();
|
||||
let fee = txs.iter().map(|x| x.fee()).sum();
|
||||
let key_id = ExtKeychainPath::new(1, next_height as u32, 0, 0, 0).to_identifier();
|
||||
let reward =
|
||||
reward::output(keychain, &ProofBuilder::new(keychain), &key_id, fee, false).unwrap();
|
||||
|
@ -126,9 +126,8 @@ fn process_block_cut_through() -> Result<(), chain::Error> {
|
|||
.any(|output| output.commitment() == commit));
|
||||
|
||||
// Transaction is invalid due to cut-through.
|
||||
let height = 7;
|
||||
assert_eq!(
|
||||
tx.validate(Weighting::AsTransaction, height),
|
||||
tx.validate(Weighting::AsTransaction),
|
||||
Err(transaction::Error::CutThrough),
|
||||
);
|
||||
|
||||
|
|
|
@ -106,7 +106,7 @@ fn test_coinbase_maturity() {
|
|||
.unwrap();
|
||||
|
||||
let txs = &[coinbase_txn.clone()];
|
||||
let fees = txs.iter().map(|tx| tx.fee(prev.height + 1)).sum();
|
||||
let fees = txs.iter().map(|tx| tx.fee()).sum();
|
||||
let reward = libtx::reward::output(&keychain, &builder, &key_id3, fees, false).unwrap();
|
||||
let next_header_info =
|
||||
consensus::next_difficulty(prev.height + 1, chain.difficulty_iter().unwrap());
|
||||
|
@ -192,7 +192,7 @@ fn test_coinbase_maturity() {
|
|||
.unwrap();
|
||||
|
||||
let txs = &[coinbase_txn.clone()];
|
||||
let fees = txs.iter().map(|tx| tx.fee(prev.height + 1)).sum();
|
||||
let fees = txs.iter().map(|tx| tx.fee()).sum();
|
||||
let reward = libtx::reward::output(&keychain, &builder, &key_id3, fees, false).unwrap();
|
||||
let next_header_info =
|
||||
consensus::next_difficulty(prev.height + 1, chain.difficulty_iter().unwrap());
|
||||
|
@ -261,7 +261,7 @@ fn test_coinbase_maturity() {
|
|||
.unwrap();
|
||||
|
||||
let txs = &[coinbase_txn];
|
||||
let fees = txs.iter().map(|tx| tx.fee(prev.height + 1)).sum();
|
||||
let fees = txs.iter().map(|tx| tx.fee()).sum();
|
||||
let next_header_info =
|
||||
consensus::next_difficulty(prev.height + 1, chain.difficulty_iter().unwrap());
|
||||
let reward = libtx::reward::output(&keychain, &builder, &key_id4, fees, false).unwrap();
|
||||
|
|
|
@ -694,7 +694,7 @@ impl Block {
|
|||
|
||||
/// Sum of all fees (inputs less outputs) in the block
|
||||
pub fn total_fees(&self) -> u64 {
|
||||
self.body.fee(self.header.height)
|
||||
self.body.fee()
|
||||
}
|
||||
|
||||
/// "Lightweight" validation that we can perform quickly during read/deserialization.
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
|
||||
//! Transactions
|
||||
|
||||
use crate::core::block::HeaderVersion;
|
||||
use crate::core::hash::{DefaultHashable, Hashed};
|
||||
use crate::core::{committed, Committed};
|
||||
use crate::libtx::{aggsig, secp_ser};
|
||||
|
@ -161,21 +160,13 @@ impl FeeFields {
|
|||
}
|
||||
|
||||
/// Extract fee_shift field
|
||||
pub fn fee_shift(&self, height: u64) -> u8 {
|
||||
if consensus::header_version(height) < HeaderVersion(5) {
|
||||
0
|
||||
} else {
|
||||
((self.0 >> FeeFields::FEE_BITS) & FeeFields::FEE_SHIFT_MASK) as u8
|
||||
}
|
||||
pub fn fee_shift(&self) -> u8 {
|
||||
((self.0 >> FeeFields::FEE_BITS) & FeeFields::FEE_SHIFT_MASK) as u8
|
||||
}
|
||||
|
||||
/// Extract fee field
|
||||
pub fn fee(&self, height: u64) -> u64 {
|
||||
if consensus::header_version(height) < HeaderVersion(5) {
|
||||
self.0
|
||||
} else {
|
||||
self.0 & FeeFields::FEE_MASK
|
||||
}
|
||||
pub fn fee(&self) -> u64 {
|
||||
self.0 & FeeFields::FEE_MASK
|
||||
}
|
||||
|
||||
/// Turn a zero `FeeField` into a `None`, any other value into a `Some`.
|
||||
|
@ -1031,7 +1022,7 @@ impl TransactionBody {
|
|||
}
|
||||
|
||||
/// Total fee for a TransactionBody is the sum of fees of all fee carrying kernels.
|
||||
pub fn fee(&self, height: u64) -> u64 {
|
||||
pub fn fee(&self) -> u64 {
|
||||
self.kernels
|
||||
.iter()
|
||||
.filter_map(|k| match k.features {
|
||||
|
@ -1040,13 +1031,11 @@ impl TransactionBody {
|
|||
KernelFeatures::HeightLocked { fee, .. } => Some(fee),
|
||||
KernelFeatures::NoRecentDuplicate { fee, .. } => Some(fee),
|
||||
})
|
||||
.fold(0, |acc, fee_fields| {
|
||||
acc.saturating_add(fee_fields.fee(height))
|
||||
})
|
||||
.fold(0, |acc, fee_fields| acc.saturating_add(fee_fields.fee()))
|
||||
}
|
||||
|
||||
/// fee_shift for a TransactionBody is the maximum of fee_shifts of all fee carrying kernels.
|
||||
pub fn fee_shift(&self, height: u64) -> u8 {
|
||||
pub fn fee_shift(&self) -> u8 {
|
||||
self.kernels
|
||||
.iter()
|
||||
.filter_map(|k| match k.features {
|
||||
|
@ -1055,24 +1044,24 @@ impl TransactionBody {
|
|||
KernelFeatures::HeightLocked { fee, .. } => Some(fee),
|
||||
KernelFeatures::NoRecentDuplicate { fee, .. } => Some(fee),
|
||||
})
|
||||
.fold(0, |acc, fee_fields| max(acc, fee_fields.fee_shift(height)))
|
||||
.fold(0, |acc, fee_fields| max(acc, fee_fields.fee_shift()))
|
||||
}
|
||||
|
||||
/// Shifted fee for a TransactionBody is the sum of fees shifted right by the maximum fee_shift
|
||||
/// this is used to determine whether a tx can be relayed or accepted in a mempool
|
||||
/// where transactions can specify a higher block-inclusion priority as a positive shift up to 15
|
||||
/// but are required to overpay the minimum required fees by a factor of 2^priority
|
||||
pub fn shifted_fee(&self, height: u64) -> u64 {
|
||||
self.fee(height) >> self.fee_shift(height)
|
||||
pub fn shifted_fee(&self) -> u64 {
|
||||
self.fee() >> self.fee_shift()
|
||||
}
|
||||
|
||||
/// aggregate fee_fields from all appropriate kernels in TransactionBody into one, if possible
|
||||
pub fn aggregate_fee_fields(&self, height: u64) -> Result<FeeFields, Error> {
|
||||
FeeFields::new(self.fee_shift(height) as u64, self.fee(height))
|
||||
pub fn aggregate_fee_fields(&self) -> Result<FeeFields, Error> {
|
||||
FeeFields::new(self.fee_shift() as u64, self.fee())
|
||||
}
|
||||
|
||||
fn overage(&self, height: u64) -> i64 {
|
||||
self.fee(height) as i64
|
||||
fn overage(&self) -> i64 {
|
||||
self.fee() as i64
|
||||
}
|
||||
|
||||
/// Calculate weight of transaction using block weighing
|
||||
|
@ -1413,23 +1402,23 @@ impl Transaction {
|
|||
}
|
||||
|
||||
/// Total fee for a transaction is the sum of fees of all kernels.
|
||||
pub fn fee(&self, height: u64) -> u64 {
|
||||
self.body.fee(height)
|
||||
pub fn fee(&self) -> u64 {
|
||||
self.body.fee()
|
||||
}
|
||||
|
||||
/// Shifted fee for a transaction is the sum of fees of all kernels shifted right by the maximum fee shift
|
||||
pub fn shifted_fee(&self, height: u64) -> u64 {
|
||||
self.body.shifted_fee(height)
|
||||
pub fn shifted_fee(&self) -> u64 {
|
||||
self.body.shifted_fee()
|
||||
}
|
||||
|
||||
/// aggregate fee_fields from all appropriate kernels in transaction into one
|
||||
pub fn aggregate_fee_fields(&self, height: u64) -> Result<FeeFields, Error> {
|
||||
self.body.aggregate_fee_fields(height)
|
||||
pub fn aggregate_fee_fields(&self) -> Result<FeeFields, Error> {
|
||||
self.body.aggregate_fee_fields()
|
||||
}
|
||||
|
||||
/// Total overage across all kernels.
|
||||
pub fn overage(&self, height: u64) -> i64 {
|
||||
self.body.overage(height)
|
||||
pub fn overage(&self) -> i64 {
|
||||
self.body.overage()
|
||||
}
|
||||
|
||||
/// Lock height of a transaction is the max lock height of the kernels.
|
||||
|
@ -1451,17 +1440,17 @@ impl Transaction {
|
|||
/// Validates all relevant parts of a fully built transaction. Checks the
|
||||
/// excess value against the signature as well as range proofs for each
|
||||
/// output.
|
||||
pub fn validate(&self, weighting: Weighting, height: u64) -> Result<(), Error> {
|
||||
pub fn validate(&self, weighting: Weighting) -> Result<(), Error> {
|
||||
self.body.verify_features()?;
|
||||
self.body.validate(weighting)?;
|
||||
self.verify_kernel_sums(self.overage(height), self.offset.clone())?;
|
||||
self.verify_kernel_sums(self.overage(), self.offset.clone())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Can be used to compare txs by their fee/weight ratio, aka feerate.
|
||||
/// Don't use these values for anything else though due to precision multiplier.
|
||||
pub fn fee_rate(&self, height: u64) -> u64 {
|
||||
self.fee(height) / self.weight() as u64
|
||||
pub fn fee_rate(&self) -> u64 {
|
||||
self.fee() / self.weight() as u64
|
||||
}
|
||||
|
||||
/// Calculate transaction weight
|
||||
|
@ -1470,16 +1459,8 @@ impl Transaction {
|
|||
}
|
||||
|
||||
/// Transaction minimum acceptable fee
|
||||
pub fn accept_fee(&self, height: u64) -> u64 {
|
||||
if consensus::header_version(height) < HeaderVersion(5) {
|
||||
Transaction::old_weight_by_iok(
|
||||
self.body.inputs.len() as u64,
|
||||
self.body.outputs.len() as u64,
|
||||
self.body.kernels.len() as u64,
|
||||
) * consensus::MILLI_GRIN
|
||||
} else {
|
||||
self.weight() * global::get_accept_fee_base()
|
||||
}
|
||||
pub fn accept_fee(&self) -> u64 {
|
||||
self.weight() * global::get_accept_fee_base()
|
||||
}
|
||||
|
||||
/// Old weight definition for pool acceptance
|
||||
|
|
|
@ -276,8 +276,7 @@ mod test {
|
|||
)
|
||||
.unwrap();
|
||||
|
||||
let height = 42; // arbitrary
|
||||
tx.validate(Weighting::AsTransaction, height).unwrap();
|
||||
tx.validate(Weighting::AsTransaction).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -297,8 +296,7 @@ mod test {
|
|||
)
|
||||
.unwrap();
|
||||
|
||||
let height = 42; // arbitrary
|
||||
tx.validate(Weighting::AsTransaction, height).unwrap();
|
||||
tx.validate(Weighting::AsTransaction).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -317,7 +315,6 @@ mod test {
|
|||
)
|
||||
.unwrap();
|
||||
|
||||
let height = 42; // arbitrary
|
||||
tx.validate(Weighting::AsTransaction, height).unwrap();
|
||||
tx.validate(Weighting::AsTransaction).unwrap();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,6 +41,6 @@ pub fn tx_fee(input_len: usize, output_len: usize, kernel_len: usize) -> u64 {
|
|||
}
|
||||
|
||||
/// Transaction fee calculation given transaction
|
||||
pub fn accept_fee(tx: Transaction, height: u64) -> u64 {
|
||||
tx.accept_fee(height)
|
||||
pub fn accept_fee(tx: Transaction) -> u64 {
|
||||
tx.accept_fee()
|
||||
}
|
||||
|
|
|
@ -120,10 +120,7 @@ where
|
|||
K: Keychain,
|
||||
B: ProofBuild,
|
||||
{
|
||||
let fees = txs
|
||||
.iter()
|
||||
.map(|tx| tx.fee(previous_header.height + 1))
|
||||
.sum();
|
||||
let fees = txs.iter().map(|tx| tx.fee()).sum();
|
||||
let reward_output = reward::output(keychain, builder, &key_id, fees, false).unwrap();
|
||||
Block::new(&previous_header, txs, Difficulty::min_dma(), reward_output).unwrap()
|
||||
}
|
||||
|
|
|
@ -94,8 +94,7 @@ fn simple_tx_ser_deser() {
|
|||
let mut vec = Vec::new();
|
||||
ser::serialize_default(&mut vec, &tx).expect("serialization failed");
|
||||
let dtx: Transaction = ser::deserialize_default(&mut &vec[..]).unwrap();
|
||||
let height = 42; // arbitrary
|
||||
assert_eq!(dtx.fee(height), 2);
|
||||
assert_eq!(dtx.fee(), 2);
|
||||
assert_eq!(dtx.inputs().len(), 2);
|
||||
assert_eq!(dtx.outputs().len(), 1);
|
||||
assert_eq!(tx.hash(), dtx.hash());
|
||||
|
@ -157,8 +156,7 @@ fn build_tx_kernel() {
|
|||
.unwrap();
|
||||
|
||||
// check the tx is valid
|
||||
let height = 42; // arbitrary
|
||||
tx.validate(Weighting::AsTransaction, height).unwrap();
|
||||
tx.validate(Weighting::AsTransaction).unwrap();
|
||||
|
||||
// check the kernel is also itself valid
|
||||
assert_eq!(tx.kernels().len(), 1);
|
||||
|
@ -166,7 +164,7 @@ fn build_tx_kernel() {
|
|||
kern.verify().unwrap();
|
||||
|
||||
assert_eq!(kern.features, KernelFeatures::Plain { fee: 2.into() });
|
||||
assert_eq!(2, tx.fee(height));
|
||||
assert_eq!(2, tx.fee());
|
||||
}
|
||||
|
||||
// Proof of concept demonstrating we can build two transactions that share
|
||||
|
@ -220,10 +218,9 @@ fn build_two_half_kernels() {
|
|||
)
|
||||
.unwrap();
|
||||
|
||||
let height = 42; // arbitrary
|
||||
assert_eq!(tx1.validate(Weighting::AsTransaction, height), Ok(()),);
|
||||
assert_eq!(tx1.validate(Weighting::AsTransaction), Ok(()),);
|
||||
|
||||
assert_eq!(tx2.validate(Weighting::AsTransaction, height), Ok(()),);
|
||||
assert_eq!(tx2.validate(Weighting::AsTransaction), Ok(()),);
|
||||
|
||||
// The transactions share an identical kernel.
|
||||
assert_eq!(tx1.kernels()[0], tx2.kernels()[0]);
|
||||
|
@ -247,14 +244,14 @@ fn transaction_cut_through() {
|
|||
let tx1 = tx1i2o();
|
||||
let tx2 = tx2i1o();
|
||||
|
||||
let height = 42; // arbitrary
|
||||
assert!(tx1.validate(Weighting::AsTransaction, height).is_ok());
|
||||
assert!(tx2.validate(Weighting::AsTransaction, height).is_ok());
|
||||
|
||||
assert!(tx1.validate(Weighting::AsTransaction).is_ok());
|
||||
assert!(tx2.validate(Weighting::AsTransaction).is_ok());
|
||||
|
||||
// now build a "cut_through" tx from tx1 and tx2
|
||||
let tx3 = aggregate(&[tx1, tx2]).unwrap();
|
||||
|
||||
assert!(tx3.validate(Weighting::AsTransaction, height).is_ok());
|
||||
assert!(tx3.validate(Weighting::AsTransaction).is_ok());
|
||||
}
|
||||
|
||||
// Attempt to deaggregate a multi-kernel transaction in a different way
|
||||
|
@ -266,30 +263,30 @@ fn multi_kernel_transaction_deaggregation() {
|
|||
let tx3 = tx1i1o();
|
||||
let tx4 = tx1i1o();
|
||||
|
||||
let height = 42; // arbitrary
|
||||
assert!(tx1.validate(Weighting::AsTransaction, height).is_ok());
|
||||
assert!(tx2.validate(Weighting::AsTransaction, height).is_ok());
|
||||
assert!(tx3.validate(Weighting::AsTransaction, height).is_ok());
|
||||
assert!(tx4.validate(Weighting::AsTransaction, height).is_ok());
|
||||
assert!(tx1.validate(Weighting::AsTransaction).is_ok());
|
||||
assert!(tx2.validate(Weighting::AsTransaction).is_ok());
|
||||
assert!(tx3.validate(Weighting::AsTransaction).is_ok());
|
||||
assert!(tx4.validate(Weighting::AsTransaction).is_ok());
|
||||
|
||||
let tx1234 = aggregate(&[tx1.clone(), tx2.clone(), tx3.clone(), tx4.clone()]).unwrap();
|
||||
let tx12 = aggregate(&[tx1, tx2]).unwrap();
|
||||
let tx34 = aggregate(&[tx3, tx4]).unwrap();
|
||||
|
||||
assert!(tx1234.validate(Weighting::AsTransaction, height).is_ok());
|
||||
assert!(tx12.validate(Weighting::AsTransaction, height).is_ok());
|
||||
assert!(tx34.validate(Weighting::AsTransaction, height).is_ok());
|
||||
assert!(tx1234.validate(Weighting::AsTransaction).is_ok());
|
||||
assert!(tx12.validate(Weighting::AsTransaction).is_ok());
|
||||
assert!(tx34.validate(Weighting::AsTransaction).is_ok());
|
||||
|
||||
let deaggregated_tx34 = deaggregate(tx1234.clone(), &[tx12.clone()]).unwrap();
|
||||
assert!(deaggregated_tx34
|
||||
.validate(Weighting::AsTransaction, height)
|
||||
.validate(Weighting::AsTransaction)
|
||||
|
||||
.is_ok());
|
||||
assert_eq!(tx34, deaggregated_tx34);
|
||||
|
||||
let deaggregated_tx12 = deaggregate(tx1234, &[tx34]).unwrap();
|
||||
|
||||
assert!(deaggregated_tx12
|
||||
.validate(Weighting::AsTransaction, height)
|
||||
.validate(Weighting::AsTransaction)
|
||||
.is_ok());
|
||||
assert_eq!(tx12, deaggregated_tx12);
|
||||
}
|
||||
|
@ -301,20 +298,19 @@ fn multi_kernel_transaction_deaggregation_2() {
|
|||
let tx2 = tx1i1o();
|
||||
let tx3 = tx1i1o();
|
||||
|
||||
let height = 42; // arbitrary
|
||||
assert!(tx1.validate(Weighting::AsTransaction, height).is_ok());
|
||||
assert!(tx2.validate(Weighting::AsTransaction, height).is_ok());
|
||||
assert!(tx3.validate(Weighting::AsTransaction, height).is_ok());
|
||||
assert!(tx1.validate(Weighting::AsTransaction).is_ok());
|
||||
assert!(tx2.validate(Weighting::AsTransaction).is_ok());
|
||||
assert!(tx3.validate(Weighting::AsTransaction).is_ok());
|
||||
|
||||
let tx123 = aggregate(&[tx1.clone(), tx2.clone(), tx3.clone()]).unwrap();
|
||||
let tx12 = aggregate(&[tx1, tx2]).unwrap();
|
||||
|
||||
assert!(tx123.validate(Weighting::AsTransaction, height).is_ok());
|
||||
assert!(tx12.validate(Weighting::AsTransaction, height).is_ok());
|
||||
assert!(tx123.validate(Weighting::AsTransaction).is_ok());
|
||||
assert!(tx12.validate(Weighting::AsTransaction).is_ok());
|
||||
|
||||
let deaggregated_tx3 = deaggregate(tx123, &[tx12]).unwrap();
|
||||
assert!(deaggregated_tx3
|
||||
.validate(Weighting::AsTransaction, height)
|
||||
.validate(Weighting::AsTransaction)
|
||||
.is_ok());
|
||||
assert_eq!(tx3, deaggregated_tx3);
|
||||
}
|
||||
|
@ -326,21 +322,20 @@ fn multi_kernel_transaction_deaggregation_3() {
|
|||
let tx2 = tx1i1o();
|
||||
let tx3 = tx1i1o();
|
||||
|
||||
let height = 42; // arbitrary
|
||||
assert!(tx1.validate(Weighting::AsTransaction, height).is_ok());
|
||||
assert!(tx2.validate(Weighting::AsTransaction, height).is_ok());
|
||||
assert!(tx3.validate(Weighting::AsTransaction, height).is_ok());
|
||||
assert!(tx1.validate(Weighting::AsTransaction).is_ok());
|
||||
assert!(tx2.validate(Weighting::AsTransaction).is_ok());
|
||||
assert!(tx3.validate(Weighting::AsTransaction).is_ok());
|
||||
|
||||
let tx123 = aggregate(&[tx1.clone(), tx2.clone(), tx3.clone()]).unwrap();
|
||||
let tx13 = aggregate(&[tx1, tx3]).unwrap();
|
||||
let tx2 = aggregate(&[tx2]).unwrap();
|
||||
|
||||
assert!(tx123.validate(Weighting::AsTransaction, height).is_ok());
|
||||
assert!(tx2.validate(Weighting::AsTransaction, height).is_ok());
|
||||
assert!(tx123.validate(Weighting::AsTransaction).is_ok());
|
||||
assert!(tx2.validate(Weighting::AsTransaction).is_ok());
|
||||
|
||||
let deaggregated_tx13 = deaggregate(tx123, &[tx2]).unwrap();
|
||||
assert!(deaggregated_tx13
|
||||
.validate(Weighting::AsTransaction, height)
|
||||
.validate(Weighting::AsTransaction)
|
||||
.is_ok());
|
||||
assert_eq!(tx13, deaggregated_tx13);
|
||||
}
|
||||
|
@ -354,12 +349,11 @@ fn multi_kernel_transaction_deaggregation_4() {
|
|||
let tx4 = tx1i1o();
|
||||
let tx5 = tx1i1o();
|
||||
|
||||
let height = 42; // arbitrary
|
||||
assert!(tx1.validate(Weighting::AsTransaction, height).is_ok());
|
||||
assert!(tx2.validate(Weighting::AsTransaction, height).is_ok());
|
||||
assert!(tx3.validate(Weighting::AsTransaction, height).is_ok());
|
||||
assert!(tx4.validate(Weighting::AsTransaction, height).is_ok());
|
||||
assert!(tx5.validate(Weighting::AsTransaction, height).is_ok());
|
||||
assert!(tx1.validate(Weighting::AsTransaction).is_ok());
|
||||
assert!(tx2.validate(Weighting::AsTransaction).is_ok());
|
||||
assert!(tx3.validate(Weighting::AsTransaction).is_ok());
|
||||
assert!(tx4.validate(Weighting::AsTransaction).is_ok());
|
||||
assert!(tx5.validate(Weighting::AsTransaction).is_ok());
|
||||
|
||||
let tx12345 = aggregate(&[
|
||||
tx1.clone(),
|
||||
|
@ -369,11 +363,11 @@ fn multi_kernel_transaction_deaggregation_4() {
|
|||
tx5.clone(),
|
||||
])
|
||||
.unwrap();
|
||||
assert!(tx12345.validate(Weighting::AsTransaction, height).is_ok());
|
||||
assert!(tx12345.validate(Weighting::AsTransaction).is_ok());
|
||||
|
||||
let deaggregated_tx5 = deaggregate(tx12345, &[tx1, tx2, tx3, tx4]).unwrap();
|
||||
assert!(deaggregated_tx5
|
||||
.validate(Weighting::AsTransaction, height)
|
||||
.validate(Weighting::AsTransaction)
|
||||
.is_ok());
|
||||
assert_eq!(tx5, deaggregated_tx5);
|
||||
}
|
||||
|
@ -387,12 +381,11 @@ fn multi_kernel_transaction_deaggregation_5() {
|
|||
let tx4 = tx1i1o();
|
||||
let tx5 = tx1i1o();
|
||||
|
||||
let height = 42; // arbitrary
|
||||
assert!(tx1.validate(Weighting::AsTransaction, height).is_ok());
|
||||
assert!(tx2.validate(Weighting::AsTransaction, height).is_ok());
|
||||
assert!(tx3.validate(Weighting::AsTransaction, height).is_ok());
|
||||
assert!(tx4.validate(Weighting::AsTransaction, height).is_ok());
|
||||
assert!(tx5.validate(Weighting::AsTransaction, height).is_ok());
|
||||
assert!(tx1.validate(Weighting::AsTransaction).is_ok());
|
||||
assert!(tx2.validate(Weighting::AsTransaction).is_ok());
|
||||
assert!(tx3.validate(Weighting::AsTransaction).is_ok());
|
||||
assert!(tx4.validate(Weighting::AsTransaction).is_ok());
|
||||
assert!(tx5.validate(Weighting::AsTransaction).is_ok());
|
||||
|
||||
let tx12345 = aggregate(&[
|
||||
tx1.clone(),
|
||||
|
@ -405,11 +398,11 @@ fn multi_kernel_transaction_deaggregation_5() {
|
|||
let tx12 = aggregate(&[tx1, tx2]).unwrap();
|
||||
let tx34 = aggregate(&[tx3, tx4]).unwrap();
|
||||
|
||||
assert!(tx12345.validate(Weighting::AsTransaction, height).is_ok());
|
||||
assert!(tx12345.validate(Weighting::AsTransaction).is_ok());
|
||||
|
||||
let deaggregated_tx5 = deaggregate(tx12345, &[tx12, tx34]).unwrap();
|
||||
assert!(deaggregated_tx5
|
||||
.validate(Weighting::AsTransaction, height)
|
||||
.validate(Weighting::AsTransaction)
|
||||
.is_ok());
|
||||
assert_eq!(tx5, deaggregated_tx5);
|
||||
}
|
||||
|
@ -421,26 +414,25 @@ fn basic_transaction_deaggregation() {
|
|||
let tx1 = tx1i2o();
|
||||
let tx2 = tx2i1o();
|
||||
|
||||
let height = 42; // arbitrary
|
||||
assert!(tx1.validate(Weighting::AsTransaction, height).is_ok());
|
||||
assert!(tx2.validate(Weighting::AsTransaction, height).is_ok());
|
||||
assert!(tx1.validate(Weighting::AsTransaction).is_ok());
|
||||
assert!(tx2.validate(Weighting::AsTransaction).is_ok());
|
||||
|
||||
// now build a "cut_through" tx from tx1 and tx2
|
||||
let tx3 = aggregate(&[tx1.clone(), tx2.clone()]).unwrap();
|
||||
|
||||
assert!(tx3.validate(Weighting::AsTransaction, height).is_ok());
|
||||
assert!(tx3.validate(Weighting::AsTransaction).is_ok());
|
||||
|
||||
let deaggregated_tx1 = deaggregate(tx3.clone(), &[tx2.clone()]).unwrap();
|
||||
|
||||
assert!(deaggregated_tx1
|
||||
.validate(Weighting::AsTransaction, height)
|
||||
.validate(Weighting::AsTransaction)
|
||||
.is_ok());
|
||||
assert_eq!(tx1, deaggregated_tx1);
|
||||
|
||||
let deaggregated_tx2 = deaggregate(tx3, &[tx1]).unwrap();
|
||||
|
||||
assert!(deaggregated_tx2
|
||||
.validate(Weighting::AsTransaction, height)
|
||||
.validate(Weighting::AsTransaction)
|
||||
.is_ok());
|
||||
assert_eq!(tx2, deaggregated_tx2);
|
||||
}
|
||||
|
@ -470,8 +462,7 @@ fn hash_output() {
|
|||
#[test]
|
||||
fn blind_tx() {
|
||||
let btx = tx2i1o();
|
||||
let height = 42; // arbitrary
|
||||
assert!(btx.validate(Weighting::AsTransaction, height).is_ok());
|
||||
assert!(btx.validate(Weighting::AsTransaction).is_ok());
|
||||
|
||||
// Ignored for bullet proofs, because calling range_proof_info
|
||||
// with a bullet proof causes painful errors
|
||||
|
@ -541,8 +532,7 @@ fn tx_build_exchange() {
|
|||
)
|
||||
.unwrap();
|
||||
|
||||
let height = 42; // arbitrary
|
||||
tx_final.validate(Weighting::AsTransaction, height).unwrap();
|
||||
tx_final.validate(Weighting::AsTransaction).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -568,7 +558,7 @@ fn reward_with_tx_block() {
|
|||
|
||||
let tx1 = tx2i1o();
|
||||
let previous_header = BlockHeader::default();
|
||||
tx1.validate(Weighting::AsTransaction, previous_header.height + 1)
|
||||
tx1.validate(Weighting::AsTransaction)
|
||||
.unwrap();
|
||||
|
||||
let block = new_block(&[tx1], &keychain, &builder, &previous_header, &key_id);
|
||||
|
@ -652,14 +642,12 @@ fn test_block_with_timelocked_tx() {
|
|||
pub fn test_verify_1i1o_sig() {
|
||||
test_setup();
|
||||
let tx = tx1i1o();
|
||||
let height = 42; // arbitrary
|
||||
tx.validate(Weighting::AsTransaction, height).unwrap();
|
||||
tx.validate(Weighting::AsTransaction).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_verify_2i1o_sig() {
|
||||
test_setup();
|
||||
let tx = tx2i1o();
|
||||
let height = 42; // arbitrary
|
||||
tx.validate(Weighting::AsTransaction, height).unwrap();
|
||||
tx.validate(Weighting::AsTransaction).unwrap();
|
||||
}
|
||||
|
|
|
@ -109,9 +109,8 @@ fn test_verify_cut_through_plain() -> Result<(), Error> {
|
|||
.expect("valid tx");
|
||||
|
||||
// Transaction should fail validation due to cut-through.
|
||||
let height = 42; // arbitrary
|
||||
assert_eq!(
|
||||
tx.validate(Weighting::AsTransaction, height),
|
||||
tx.validate(Weighting::AsTransaction),
|
||||
Err(Error::CutThrough),
|
||||
);
|
||||
|
||||
|
@ -129,7 +128,7 @@ fn test_verify_cut_through_plain() -> Result<(), Error> {
|
|||
.replace_outputs(outputs);
|
||||
|
||||
// Transaction validates successfully after applying cut-through.
|
||||
tx.validate(Weighting::AsTransaction, height)?;
|
||||
tx.validate(Weighting::AsTransaction)?;
|
||||
|
||||
// Transaction validates via lightweight "read" validation as well.
|
||||
tx.validate_read()?;
|
||||
|
@ -169,9 +168,8 @@ fn test_verify_cut_through_coinbase() -> Result<(), Error> {
|
|||
.expect("valid tx");
|
||||
|
||||
// Transaction should fail validation due to cut-through.
|
||||
let height = 42; // arbitrary
|
||||
assert_eq!(
|
||||
tx.validate(Weighting::AsTransaction, height),
|
||||
tx.validate(Weighting::AsTransaction),
|
||||
Err(Error::CutThrough),
|
||||
);
|
||||
|
||||
|
@ -189,7 +187,7 @@ fn test_verify_cut_through_coinbase() -> Result<(), Error> {
|
|||
.replace_outputs(outputs);
|
||||
|
||||
// Transaction validates successfully after applying cut-through.
|
||||
tx.validate(Weighting::AsTransaction, height)?;
|
||||
tx.validate(Weighting::AsTransaction)?;
|
||||
|
||||
// Transaction validates via lightweight "read" validation as well.
|
||||
tx.validate_read()?;
|
||||
|
@ -222,20 +220,9 @@ fn test_fee_fields() -> Result<(), Error> {
|
|||
)
|
||||
.expect("valid tx");
|
||||
|
||||
let hf4_height = 4 * consensus::TESTING_HARD_FORK_INTERVAL;
|
||||
assert_eq!(
|
||||
tx.accept_fee(hf4_height),
|
||||
(1 * 1 + 1 * 21 + 1 * 3) * 500_000
|
||||
);
|
||||
assert_eq!(tx.fee(hf4_height), 42);
|
||||
assert_eq!(tx.fee(hf4_height), 42);
|
||||
assert_eq!(tx.shifted_fee(hf4_height), 21);
|
||||
assert_eq!(
|
||||
tx.accept_fee(hf4_height - 1),
|
||||
(1 * 4 + 1 * 1 - 1 * 1) * 1_000_000
|
||||
);
|
||||
assert_eq!(tx.fee(hf4_height - 1), 42 + (1u64 << 40));
|
||||
assert_eq!(tx.shifted_fee(hf4_height - 1), 42 + (1u64 << 40));
|
||||
assert_eq!(tx.accept_fee(), (1 * 1 + 1 * 21 + 1 * 3) * 500_000);
|
||||
assert_eq!(tx.fee(), 42);
|
||||
assert_eq!(tx.shifted_fee(), 21);
|
||||
|
||||
tx.body.kernels.append(&mut vec![
|
||||
TxKernel::with_features(KernelFeatures::Plain {
|
||||
|
@ -244,9 +231,9 @@ fn test_fee_fields() -> Result<(), Error> {
|
|||
TxKernel::with_features(KernelFeatures::Plain { fee: 21.into() }),
|
||||
]);
|
||||
|
||||
assert_eq!(tx.fee(hf4_height), 147);
|
||||
assert_eq!(tx.shifted_fee(hf4_height), 36);
|
||||
assert_eq!(tx.aggregate_fee_fields(hf4_height), FeeFields::new(2, 147));
|
||||
assert_eq!(tx.fee(), 147);
|
||||
assert_eq!(tx.shifted_fee(), 36);
|
||||
assert_eq!(tx.aggregate_fee_fields(), FeeFields::new(2, 147));
|
||||
assert_eq!(tx_fee(1, 1, 3), 15_500_000);
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -122,13 +122,13 @@ where
|
|||
// * maintain dependency ordering
|
||||
// * maximize cut-through
|
||||
// * maximize overall fees
|
||||
let header = self.blockchain.chain_head()?;
|
||||
let txs = self.bucket_transactions(weighting);
|
||||
|
||||
// Iteratively apply the txs to the current chain state,
|
||||
// rejecting any that do not result in a valid state.
|
||||
// Verify these txs produce an aggregated tx below max_weight.
|
||||
// Return a vec of all the valid txs.
|
||||
let header = self.blockchain.chain_head()?;
|
||||
let valid_txs = self.validate_raw_txs(&txs, None, &header, weighting)?;
|
||||
Ok(valid_txs)
|
||||
}
|
||||
|
@ -155,8 +155,7 @@ where
|
|||
let tx = transaction::aggregate(&txs)?;
|
||||
|
||||
// Validate the single aggregate transaction "as pool", not subject to tx weight limits.
|
||||
let header = self.blockchain.chain_head()?;
|
||||
tx.validate(Weighting::NoLimit, header.height)?;
|
||||
tx.validate(Weighting::NoLimit)?;
|
||||
|
||||
Ok(Some(tx))
|
||||
}
|
||||
|
@ -224,7 +223,7 @@ where
|
|||
) -> Result<BlockSums, PoolError> {
|
||||
// Validate the tx, conditionally checking against weight limits,
|
||||
// based on weight verification type.
|
||||
tx.validate(weighting, header.height)?;
|
||||
tx.validate(weighting)?;
|
||||
|
||||
// Validate the tx against current chain state.
|
||||
// Check all inputs are in the current UTXO set.
|
||||
|
@ -299,7 +298,7 @@ where
|
|||
tx: &Transaction,
|
||||
header: &BlockHeader,
|
||||
) -> Result<BlockSums, PoolError> {
|
||||
let overage = tx.overage(header.height);
|
||||
let overage = tx.overage();
|
||||
|
||||
let offset = {
|
||||
let secp = static_secp_instance();
|
||||
|
@ -389,14 +388,13 @@ where
|
|||
continue;
|
||||
}
|
||||
|
||||
let height = self.blockchain.chain_head().map(|x| x.height).unwrap_or(0);
|
||||
match insert_pos {
|
||||
None => {
|
||||
// No parent tx, just add to the end in its own bucket.
|
||||
// This is the common case for non 0-conf txs in the txpool.
|
||||
// We assume the tx is valid here as we validated it on the way into the txpool.
|
||||
insert_pos = Some(tx_buckets.len());
|
||||
tx_buckets.push(Bucket::new(entry.tx.clone(), tx_buckets.len(), height));
|
||||
tx_buckets.push(Bucket::new(entry.tx.clone(), tx_buckets.len()));
|
||||
}
|
||||
Some(pos) => {
|
||||
// We found a single parent tx, so aggregate in the bucket
|
||||
|
@ -405,7 +403,7 @@ where
|
|||
let bucket = &tx_buckets[pos];
|
||||
|
||||
if let Ok(new_bucket) =
|
||||
bucket.aggregate_with_tx(entry.tx.clone(), weighting, height)
|
||||
bucket.aggregate_with_tx(entry.tx.clone(), weighting)
|
||||
{
|
||||
if new_bucket.fee_rate >= bucket.fee_rate {
|
||||
// Only aggregate if it would not reduce the fee_rate ratio.
|
||||
|
@ -414,11 +412,7 @@ where
|
|||
// Otherwise put it in its own bucket at the end.
|
||||
// Note: This bucket will have a lower fee_rate
|
||||
// than the bucket it depends on.
|
||||
tx_buckets.push(Bucket::new(
|
||||
entry.tx.clone(),
|
||||
tx_buckets.len(),
|
||||
height,
|
||||
));
|
||||
tx_buckets.push(Bucket::new(entry.tx.clone(), tx_buckets.len()));
|
||||
}
|
||||
} else {
|
||||
// Aggregation failed so discard this new tx.
|
||||
|
@ -511,9 +505,9 @@ impl Bucket {
|
|||
/// also specifies an "age_idx" so we can sort buckets by age
|
||||
/// as well as fee_rate. Txs are maintained in the pool in insert order
|
||||
/// so buckets with low age_idx contain oldest txs.
|
||||
fn new(tx: Transaction, age_idx: usize, height: u64) -> Bucket {
|
||||
fn new(tx: Transaction, age_idx: usize) -> Bucket {
|
||||
Bucket {
|
||||
fee_rate: tx.fee_rate(height),
|
||||
fee_rate: tx.fee_rate(),
|
||||
raw_txs: vec![tx],
|
||||
age_idx,
|
||||
}
|
||||
|
@ -523,14 +517,13 @@ impl Bucket {
|
|||
&self,
|
||||
new_tx: Transaction,
|
||||
weighting: Weighting,
|
||||
height: u64,
|
||||
) -> Result<Bucket, PoolError> {
|
||||
let mut raw_txs = self.raw_txs.clone();
|
||||
raw_txs.push(new_tx);
|
||||
let agg_tx = transaction::aggregate(&raw_txs)?;
|
||||
agg_tx.validate(weighting, height)?;
|
||||
agg_tx.validate(weighting)?;
|
||||
Ok(Bucket {
|
||||
fee_rate: agg_tx.fee_rate(height),
|
||||
fee_rate: agg_tx.fee_rate(),
|
||||
raw_txs: raw_txs,
|
||||
age_idx: self.age_idx,
|
||||
})
|
||||
|
|
|
@ -179,7 +179,7 @@ where
|
|||
|
||||
// Make sure the transaction is valid before anything else.
|
||||
// Validate tx accounting for max tx weight.
|
||||
tx.validate(Weighting::AsTransaction, header.height)
|
||||
tx.validate(Weighting::AsTransaction)
|
||||
.map_err(PoolError::InvalidTx)?;
|
||||
|
||||
// Check the tx lock_time is valid based on current chain state.
|
||||
|
@ -260,8 +260,7 @@ where
|
|||
};
|
||||
|
||||
// Validate the tx to ensure our converted inputs are correct.
|
||||
let header = self.chain_head()?;
|
||||
tx.validate(Weighting::AsTransaction, header.height)?;
|
||||
tx.validate(Weighting::AsTransaction)?;
|
||||
|
||||
Ok(PoolEntry::new(tx, entry.src))
|
||||
}
|
||||
|
@ -352,9 +351,8 @@ where
|
|||
// weight for a basic transaction (2 inputs, 2 outputs, 1 kernel) -
|
||||
// (2 * 1) + (2 * 21) + (1 * 3) = 47
|
||||
// minfees = 47 * 500_000 = 23_500_000
|
||||
let header = self.chain_head()?;
|
||||
if tx.shifted_fee(header.height) < tx.accept_fee(header.height) {
|
||||
return Err(PoolError::LowFeeTransaction(tx.shifted_fee(header.height)));
|
||||
if tx.shifted_fee() < tx.accept_fee() {
|
||||
return Err(PoolError::LowFeeTransaction(tx.shifted_fee()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -79,7 +79,7 @@ fn test_block_building_max_weight() {
|
|||
|
||||
// Fees and weights of our original txs in insert order.
|
||||
assert_eq!(
|
||||
txs.iter().map(|x| x.fee(header.height)).collect::<Vec<_>>(),
|
||||
txs.iter().map(|x| x.fee()).collect::<Vec<_>>(),
|
||||
[2_500_000, 90_000, 80_000, 30_000, 70_000, 60_000]
|
||||
);
|
||||
assert_eq!(
|
||||
|
@ -87,9 +87,7 @@ fn test_block_building_max_weight() {
|
|||
[88, 46, 46, 25, 46, 46]
|
||||
);
|
||||
assert_eq!(
|
||||
txs.iter()
|
||||
.map(|x| x.fee_rate(header.height))
|
||||
.collect::<Vec<_>>(),
|
||||
txs.iter().map(|x| x.fee_rate()).collect::<Vec<_>>(),
|
||||
[28409, 1956, 1739, 1200, 1521, 1304]
|
||||
);
|
||||
|
||||
|
@ -107,7 +105,7 @@ fn test_block_building_max_weight() {
|
|||
|
||||
// Fees and weights of the "mineable" txs.
|
||||
assert_eq!(
|
||||
txs.iter().map(|x| x.fee(header.height)).collect::<Vec<_>>(),
|
||||
txs.iter().map(|x| x.fee()).collect::<Vec<_>>(),
|
||||
[2_500_000, 90_000, 80_000, 70_000]
|
||||
);
|
||||
assert_eq!(
|
||||
|
@ -115,9 +113,7 @@ fn test_block_building_max_weight() {
|
|||
[88, 46, 46, 46]
|
||||
);
|
||||
assert_eq!(
|
||||
txs.iter()
|
||||
.map(|x| x.fee_rate(header.height))
|
||||
.collect::<Vec<_>>(),
|
||||
txs.iter().map(|x| x.fee_rate()).collect::<Vec<_>>(),
|
||||
[28409, 1956, 1739, 1521]
|
||||
);
|
||||
|
||||
|
|
|
@ -75,7 +75,7 @@ where
|
|||
let prev = chain.head_header().unwrap();
|
||||
let height = prev.height + 1;
|
||||
let next_header_info = consensus::next_difficulty(height, chain.difficulty_iter().unwrap());
|
||||
let fee = txs.iter().map(|x| x.fee(height)).sum();
|
||||
let fee = txs.iter().map(|x| x.fee()).sum();
|
||||
let key_id = ExtKeychainPath::new(1, height as u32, 0, 0, 0).to_identifier();
|
||||
let reward =
|
||||
reward::output(keychain, &ProofBuilder::new(keychain), &key_id, fee, false).unwrap();
|
||||
|
|
|
@ -187,8 +187,7 @@ fn test_the_transaction_pool() {
|
|||
// tx4 is the "new" part of this aggregated tx that we care about
|
||||
let agg_tx = transaction::aggregate(&[tx1.clone(), tx2.clone(), tx4]).unwrap();
|
||||
|
||||
let height = 12 + 1;
|
||||
agg_tx.validate(Weighting::AsTransaction, height).unwrap();
|
||||
agg_tx.validate(Weighting::AsTransaction).unwrap();
|
||||
|
||||
pool.add_to_pool(test_source(), agg_tx, false, &header)
|
||||
.unwrap();
|
||||
|
|
|
@ -139,7 +139,7 @@ fn process_fluff_phase(
|
|||
);
|
||||
|
||||
let agg_tx = transaction::aggregate(&fluffable_txs)?;
|
||||
agg_tx.validate(transaction::Weighting::AsTransaction, header.height)?;
|
||||
agg_tx.validate(transaction::Weighting::AsTransaction)?;
|
||||
|
||||
tx_pool.add_to_pool(TxSource::Fluff, agg_tx, false, &header)?;
|
||||
Ok(())
|
||||
|
|
|
@ -152,7 +152,7 @@ fn build_block(
|
|||
};
|
||||
|
||||
// build the coinbase and the block itself
|
||||
let fees = txs.iter().map(|tx| tx.fee(head.height)).sum();
|
||||
let fees = txs.iter().map(|tx| tx.fee()).sum();
|
||||
let height = head.height + 1;
|
||||
let block_fees = BlockFees {
|
||||
fees,
|
||||
|
|
Loading…
Reference in a new issue