mirror of
https://github.com/mimblewimble/grin.git
synced 2025-02-01 08:51:08 +03:00
Fixfees (#3481)
* add FeeFields type * use FeeFields with ::zero and try_into().unwrap() * fixed tests * avoid 0 accept_base_fee * add aggregate_fee_fields method for transaction * implement std::fmt::Display trait for FeeFields * make base_fee argument non-optional in libtx::mod::tx_fee * add global and thread local accept_fee_base; use to simplify tests * set unusually high fee base for a change * revert to optional base fee argument; default coming from either grin-{server,wallet}.toml * remove optional base fee argument; can be set with global::set_local_accept_fee_base instead * define constant global::DEFAULT_ACCEPT_FEE_BASE and set global value * add Transaction::accept_fee() method and use * Manual (de)ser impl on FeeFields * fix comment bug * Serialize FeeFields as int in tx * allow feefields: u32:into() for tests * try adding height args everywhere * make FeeFields shift/fee methods height dependent * prior to hf4 feefield testing * rename selected fee_fields back to fee for serialization compatibility * fix test_fee_fields test, merge conflict, and doctest use of obsolete fee_fields * make accept_fee height dependent * Accept any u64 in FeeFields deser Co-authored-by: Jasper van der Maarel <j@sper.dev>
This commit is contained in:
parent
14f4683ca1
commit
48efb693e2
35 changed files with 866 additions and 369 deletions
|
@ -13,9 +13,10 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use crate::chain;
|
use crate::chain;
|
||||||
|
use crate::core::consensus::YEAR_HEIGHT;
|
||||||
use crate::core::core::hash::Hashed;
|
use crate::core::core::hash::Hashed;
|
||||||
use crate::core::core::merkle_proof::MerkleProof;
|
use crate::core::core::merkle_proof::MerkleProof;
|
||||||
use crate::core::core::{KernelFeatures, TxKernel};
|
use crate::core::core::{FeeFields, KernelFeatures, TxKernel};
|
||||||
use crate::core::{core, ser};
|
use crate::core::{core, ser};
|
||||||
use crate::p2p;
|
use crate::p2p;
|
||||||
use crate::util::secp::pedersen;
|
use crate::util::secp::pedersen;
|
||||||
|
@ -499,6 +500,7 @@ impl<'de> serde::de::Deserialize<'de> for OutputPrintable {
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct TxKernelPrintable {
|
pub struct TxKernelPrintable {
|
||||||
pub features: String,
|
pub features: String,
|
||||||
|
pub fee_shift: u8,
|
||||||
pub fee: u64,
|
pub fee: u64,
|
||||||
pub lock_height: u64,
|
pub lock_height: u64,
|
||||||
pub excess: String,
|
pub excess: String,
|
||||||
|
@ -508,17 +510,21 @@ pub struct TxKernelPrintable {
|
||||||
impl TxKernelPrintable {
|
impl TxKernelPrintable {
|
||||||
pub fn from_txkernel(k: &core::TxKernel) -> TxKernelPrintable {
|
pub fn from_txkernel(k: &core::TxKernel) -> TxKernelPrintable {
|
||||||
let features = k.features.as_string();
|
let features = k.features.as_string();
|
||||||
let (fee, lock_height) = match k.features {
|
let (fee_fields, lock_height) = match k.features {
|
||||||
KernelFeatures::Plain { fee } => (fee, 0),
|
KernelFeatures::Plain { fee } => (fee, 0),
|
||||||
KernelFeatures::Coinbase => (0, 0),
|
KernelFeatures::Coinbase => (FeeFields::zero(), 0),
|
||||||
KernelFeatures::HeightLocked { fee, lock_height } => (fee, lock_height),
|
KernelFeatures::HeightLocked { fee, lock_height } => (fee, lock_height),
|
||||||
KernelFeatures::NoRecentDuplicate {
|
KernelFeatures::NoRecentDuplicate {
|
||||||
fee,
|
fee,
|
||||||
relative_height,
|
relative_height,
|
||||||
} => (fee, relative_height.into()),
|
} => (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 {
|
TxKernelPrintable {
|
||||||
features,
|
features,
|
||||||
|
fee_shift,
|
||||||
fee,
|
fee,
|
||||||
lock_height,
|
lock_height,
|
||||||
excess: k.excess.to_hex(),
|
excess: k.excess.to_hex(),
|
||||||
|
|
|
@ -355,7 +355,7 @@ fn validate_header(header: &BlockHeader, ctx: &mut BlockContext<'_>) -> Result<(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Block header is invalid (and block is invalid) if this lower bound is too heavy for a full block.
|
// Block header is invalid (and block is invalid) if this lower bound is too heavy for a full block.
|
||||||
let weight = TransactionBody::weight_as_block(0, num_outputs, num_kernels);
|
let weight = TransactionBody::weight_by_iok(0, num_outputs, num_kernels);
|
||||||
if weight > global::max_block_weight() {
|
if weight > global::max_block_weight() {
|
||||||
return Err(ErrorKind::Block(block::Error::TooHeavy).into());
|
return Err(ErrorKind::Block(block::Error::TooHeavy).into());
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ where
|
||||||
{
|
{
|
||||||
let prev = chain.head_header().unwrap();
|
let prev = chain.head_header().unwrap();
|
||||||
let next_header_info = consensus::next_difficulty(1, chain.difficulty_iter().unwrap());
|
let next_header_info = consensus::next_difficulty(1, chain.difficulty_iter().unwrap());
|
||||||
let fee = txs.iter().map(|x| x.fee()).sum();
|
let fee = txs.iter().map(|x| x.fee(prev.height + 1)).sum();
|
||||||
let reward =
|
let reward =
|
||||||
reward::output(keychain, &ProofBuilder::new(keychain), key_id, fee, false).unwrap();
|
reward::output(keychain, &ProofBuilder::new(keychain), key_id, fee, false).unwrap();
|
||||||
|
|
||||||
|
@ -84,7 +84,7 @@ fn mine_block_with_nrd_kernel_and_nrd_feature_enabled() {
|
||||||
let key_id2 = ExtKeychainPath::new(1, 2, 0, 0, 0).to_identifier();
|
let key_id2 = ExtKeychainPath::new(1, 2, 0, 0, 0).to_identifier();
|
||||||
let tx = build::transaction(
|
let tx = build::transaction(
|
||||||
KernelFeatures::NoRecentDuplicate {
|
KernelFeatures::NoRecentDuplicate {
|
||||||
fee: 20000,
|
fee: 20000.into(),
|
||||||
relative_height: NRDRelativeHeight::new(1440).unwrap(),
|
relative_height: NRDRelativeHeight::new(1440).unwrap(),
|
||||||
},
|
},
|
||||||
&[
|
&[
|
||||||
|
@ -131,7 +131,7 @@ fn mine_invalid_block_with_nrd_kernel_and_nrd_feature_enabled_before_hf() {
|
||||||
let key_id2 = ExtKeychainPath::new(1, 2, 0, 0, 0).to_identifier();
|
let key_id2 = ExtKeychainPath::new(1, 2, 0, 0, 0).to_identifier();
|
||||||
let tx = build::transaction(
|
let tx = build::transaction(
|
||||||
KernelFeatures::NoRecentDuplicate {
|
KernelFeatures::NoRecentDuplicate {
|
||||||
fee: 20000,
|
fee: 20000.into(),
|
||||||
relative_height: NRDRelativeHeight::new(1440).unwrap(),
|
relative_height: NRDRelativeHeight::new(1440).unwrap(),
|
||||||
},
|
},
|
||||||
&[
|
&[
|
||||||
|
|
|
@ -569,7 +569,7 @@ fn spend_rewind_spend() {
|
||||||
let key_id30 = ExtKeychainPath::new(1, 30, 0, 0, 0).to_identifier();
|
let key_id30 = ExtKeychainPath::new(1, 30, 0, 0, 0).to_identifier();
|
||||||
|
|
||||||
let tx1 = build::transaction(
|
let tx1 = build::transaction(
|
||||||
KernelFeatures::Plain { fee: 20000 },
|
KernelFeatures::Plain { fee: 20000.into() },
|
||||||
&[
|
&[
|
||||||
build::coinbase_input(consensus::REWARD, key_id_coinbase.clone()),
|
build::coinbase_input(consensus::REWARD, key_id_coinbase.clone()),
|
||||||
build::output(consensus::REWARD - 20000, key_id30.clone()),
|
build::output(consensus::REWARD - 20000, key_id30.clone()),
|
||||||
|
@ -642,7 +642,7 @@ fn spend_in_fork_and_compact() {
|
||||||
let key_id31 = ExtKeychainPath::new(1, 31, 0, 0, 0).to_identifier();
|
let key_id31 = ExtKeychainPath::new(1, 31, 0, 0, 0).to_identifier();
|
||||||
|
|
||||||
let tx1 = build::transaction(
|
let tx1 = build::transaction(
|
||||||
KernelFeatures::Plain { fee: 20000 },
|
KernelFeatures::Plain { fee: 20000.into() },
|
||||||
&[
|
&[
|
||||||
build::coinbase_input(consensus::REWARD, key_id2.clone()),
|
build::coinbase_input(consensus::REWARD, key_id2.clone()),
|
||||||
build::output(consensus::REWARD - 20000, key_id30.clone()),
|
build::output(consensus::REWARD - 20000, key_id30.clone()),
|
||||||
|
@ -660,7 +660,7 @@ fn spend_in_fork_and_compact() {
|
||||||
chain.validate(false).unwrap();
|
chain.validate(false).unwrap();
|
||||||
|
|
||||||
let tx2 = build::transaction(
|
let tx2 = build::transaction(
|
||||||
KernelFeatures::Plain { fee: 20000 },
|
KernelFeatures::Plain { fee: 20000.into() },
|
||||||
&[
|
&[
|
||||||
build::input(consensus::REWARD - 20000, key_id30.clone()),
|
build::input(consensus::REWARD - 20000, key_id30.clone()),
|
||||||
build::output(consensus::REWARD - 40000, key_id31.clone()),
|
build::output(consensus::REWARD - 40000, key_id31.clone()),
|
||||||
|
@ -885,7 +885,8 @@ where
|
||||||
let proof_size = global::proofsize();
|
let proof_size = global::proofsize();
|
||||||
let key_id = ExtKeychainPath::new(1, key_idx, 0, 0, 0).to_identifier();
|
let key_id = ExtKeychainPath::new(1, key_idx, 0, 0, 0).to_identifier();
|
||||||
|
|
||||||
let fees = txs.iter().map(|tx| tx.fee()).sum();
|
let height = prev.height + 1;
|
||||||
|
let fees = txs.iter().map(|tx| tx.fee(height)).sum();
|
||||||
let reward =
|
let reward =
|
||||||
libtx::reward::output(kc, &libtx::ProofBuilder::new(kc), &key_id, fees, false).unwrap();
|
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) {
|
let mut b = match core::core::Block::new(prev, txs, Difficulty::from_num(diff), reward) {
|
||||||
|
|
|
@ -54,7 +54,7 @@ where
|
||||||
{
|
{
|
||||||
let next_header_info =
|
let next_header_info =
|
||||||
consensus::next_difficulty(prev.height, chain.difficulty_iter().unwrap());
|
consensus::next_difficulty(prev.height, chain.difficulty_iter().unwrap());
|
||||||
let fee = txs.iter().map(|x| x.fee()).sum();
|
let fee = txs.iter().map(|x| x.fee(prev.height + 1)).sum();
|
||||||
let reward =
|
let reward =
|
||||||
reward::output(keychain, &ProofBuilder::new(keychain), key_id, fee, false).unwrap();
|
reward::output(keychain, &ProofBuilder::new(keychain), key_id, fee, false).unwrap();
|
||||||
|
|
||||||
|
@ -100,7 +100,7 @@ fn process_block_nrd_validation() -> Result<(), Error> {
|
||||||
assert_eq!(chain.head()?.height, 8);
|
assert_eq!(chain.head()?.height, 8);
|
||||||
|
|
||||||
let mut kernel = TxKernel::with_features(KernelFeatures::NoRecentDuplicate {
|
let mut kernel = TxKernel::with_features(KernelFeatures::NoRecentDuplicate {
|
||||||
fee: 20000,
|
fee: 20000.into(),
|
||||||
relative_height: NRDRelativeHeight::new(2)?,
|
relative_height: NRDRelativeHeight::new(2)?,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -216,7 +216,7 @@ fn process_block_nrd_validation_relative_height_1() -> Result<(), Error> {
|
||||||
assert_eq!(chain.head()?.height, 8);
|
assert_eq!(chain.head()?.height, 8);
|
||||||
|
|
||||||
let mut kernel = TxKernel::with_features(KernelFeatures::NoRecentDuplicate {
|
let mut kernel = TxKernel::with_features(KernelFeatures::NoRecentDuplicate {
|
||||||
fee: 20000,
|
fee: 20000.into(),
|
||||||
relative_height: NRDRelativeHeight::new(1)?,
|
relative_height: NRDRelativeHeight::new(1)?,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -315,7 +315,7 @@ fn process_block_nrd_validation_fork() -> Result<(), Error> {
|
||||||
assert_eq!(header_8.height, 8);
|
assert_eq!(header_8.height, 8);
|
||||||
|
|
||||||
let mut kernel = TxKernel::with_features(KernelFeatures::NoRecentDuplicate {
|
let mut kernel = TxKernel::with_features(KernelFeatures::NoRecentDuplicate {
|
||||||
fee: 20000,
|
fee: 20000.into(),
|
||||||
relative_height: NRDRelativeHeight::new(2)?,
|
relative_height: NRDRelativeHeight::new(2)?,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ use self::chain_test_helper::{clean_output_dir, genesis_block, init_chain};
|
||||||
use crate::chain::{pipe, Chain, Options};
|
use crate::chain::{pipe, Chain, Options};
|
||||||
use crate::core::core::verifier_cache::LruVerifierCache;
|
use crate::core::core::verifier_cache::LruVerifierCache;
|
||||||
use crate::core::core::{block, pmmr, transaction};
|
use crate::core::core::{block, pmmr, transaction};
|
||||||
use crate::core::core::{Block, KernelFeatures, Transaction, Weighting};
|
use crate::core::core::{Block, FeeFields, KernelFeatures, Transaction, Weighting};
|
||||||
use crate::core::libtx::{build, reward, ProofBuilder};
|
use crate::core::libtx::{build, reward, ProofBuilder};
|
||||||
use crate::core::{consensus, global, pow};
|
use crate::core::{consensus, global, pow};
|
||||||
use crate::keychain::{ExtKeychain, ExtKeychainPath, Keychain, SwitchCommitmentType};
|
use crate::keychain::{ExtKeychain, ExtKeychainPath, Keychain, SwitchCommitmentType};
|
||||||
|
@ -43,7 +43,7 @@ where
|
||||||
let prev = chain.head_header().unwrap();
|
let prev = chain.head_header().unwrap();
|
||||||
let next_height = prev.height + 1;
|
let next_height = prev.height + 1;
|
||||||
let next_header_info = consensus::next_difficulty(1, chain.difficulty_iter()?);
|
let next_header_info = consensus::next_difficulty(1, chain.difficulty_iter()?);
|
||||||
let fee = txs.iter().map(|x| x.fee()).sum();
|
let fee = txs.iter().map(|x| x.fee(next_height)).sum();
|
||||||
let key_id = ExtKeychainPath::new(1, next_height as u32, 0, 0, 0).to_identifier();
|
let key_id = ExtKeychainPath::new(1, next_height as u32, 0, 0, 0).to_identifier();
|
||||||
let reward =
|
let reward =
|
||||||
reward::output(keychain, &ProofBuilder::new(keychain), &key_id, fee, false).unwrap();
|
reward::output(keychain, &ProofBuilder::new(keychain), &key_id, fee, false).unwrap();
|
||||||
|
@ -104,7 +104,9 @@ fn process_block_cut_through() -> Result<(), chain::Error> {
|
||||||
// Note: We reuse key_ids resulting in an input and an output sharing the same commitment.
|
// Note: We reuse key_ids resulting in an input and an output sharing the same commitment.
|
||||||
// The input is coinbase and the output is plain.
|
// The input is coinbase and the output is plain.
|
||||||
let tx = build::transaction(
|
let tx = build::transaction(
|
||||||
KernelFeatures::Plain { fee: 0 },
|
KernelFeatures::Plain {
|
||||||
|
fee: FeeFields::zero(),
|
||||||
|
},
|
||||||
&[
|
&[
|
||||||
build::coinbase_input(consensus::REWARD, key_id1.clone()),
|
build::coinbase_input(consensus::REWARD, key_id1.clone()),
|
||||||
build::coinbase_input(consensus::REWARD, key_id2.clone()),
|
build::coinbase_input(consensus::REWARD, key_id2.clone()),
|
||||||
|
@ -129,8 +131,9 @@ fn process_block_cut_through() -> Result<(), chain::Error> {
|
||||||
let verifier_cache = Arc::new(RwLock::new(LruVerifierCache::new()));
|
let verifier_cache = Arc::new(RwLock::new(LruVerifierCache::new()));
|
||||||
|
|
||||||
// Transaction is invalid due to cut-through.
|
// Transaction is invalid due to cut-through.
|
||||||
|
let height = 7;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
tx.validate(Weighting::AsTransaction, verifier_cache.clone()),
|
tx.validate(Weighting::AsTransaction, verifier_cache.clone(), height),
|
||||||
Err(transaction::Error::CutThrough),
|
Err(transaction::Error::CutThrough),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -100,7 +100,7 @@ fn test_coinbase_maturity() {
|
||||||
// here we build a tx that attempts to spend the earlier coinbase output
|
// here we build a tx that attempts to spend the earlier coinbase output
|
||||||
// this is not a valid tx as the coinbase output cannot be spent yet
|
// this is not a valid tx as the coinbase output cannot be spent yet
|
||||||
let coinbase_txn = build::transaction(
|
let coinbase_txn = build::transaction(
|
||||||
KernelFeatures::Plain { fee: 2 },
|
KernelFeatures::Plain { fee: 2.into() },
|
||||||
&[
|
&[
|
||||||
build::coinbase_input(amount, key_id1.clone()),
|
build::coinbase_input(amount, key_id1.clone()),
|
||||||
build::output(amount - 2, key_id2.clone()),
|
build::output(amount - 2, key_id2.clone()),
|
||||||
|
@ -111,7 +111,7 @@ fn test_coinbase_maturity() {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let txs = &[coinbase_txn.clone()];
|
let txs = &[coinbase_txn.clone()];
|
||||||
let fees = txs.iter().map(|tx| tx.fee()).sum();
|
let fees = txs.iter().map(|tx| tx.fee(prev.height + 1)).sum();
|
||||||
let reward = libtx::reward::output(&keychain, &builder, &key_id3, fees, false).unwrap();
|
let reward = libtx::reward::output(&keychain, &builder, &key_id3, fees, false).unwrap();
|
||||||
let next_header_info =
|
let next_header_info =
|
||||||
consensus::next_difficulty(prev.height + 1, chain.difficulty_iter().unwrap());
|
consensus::next_difficulty(prev.height + 1, chain.difficulty_iter().unwrap());
|
||||||
|
@ -186,7 +186,7 @@ fn test_coinbase_maturity() {
|
||||||
// here we build a tx that attempts to spend the earlier coinbase output
|
// here we build a tx that attempts to spend the earlier coinbase output
|
||||||
// this is not a valid tx as the coinbase output cannot be spent yet
|
// this is not a valid tx as the coinbase output cannot be spent yet
|
||||||
let coinbase_txn = build::transaction(
|
let coinbase_txn = build::transaction(
|
||||||
KernelFeatures::Plain { fee: 2 },
|
KernelFeatures::Plain { fee: 2.into() },
|
||||||
&[
|
&[
|
||||||
build::coinbase_input(amount, key_id1.clone()),
|
build::coinbase_input(amount, key_id1.clone()),
|
||||||
build::output(amount - 2, key_id2.clone()),
|
build::output(amount - 2, key_id2.clone()),
|
||||||
|
@ -197,7 +197,7 @@ fn test_coinbase_maturity() {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let txs = &[coinbase_txn.clone()];
|
let txs = &[coinbase_txn.clone()];
|
||||||
let fees = txs.iter().map(|tx| tx.fee()).sum();
|
let fees = txs.iter().map(|tx| tx.fee(prev.height + 1)).sum();
|
||||||
let reward = libtx::reward::output(&keychain, &builder, &key_id3, fees, false).unwrap();
|
let reward = libtx::reward::output(&keychain, &builder, &key_id3, fees, false).unwrap();
|
||||||
let next_header_info =
|
let next_header_info =
|
||||||
consensus::next_difficulty(prev.height + 1, chain.difficulty_iter().unwrap());
|
consensus::next_difficulty(prev.height + 1, chain.difficulty_iter().unwrap());
|
||||||
|
@ -266,7 +266,7 @@ fn test_coinbase_maturity() {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let txs = &[coinbase_txn];
|
let txs = &[coinbase_txn];
|
||||||
let fees = txs.iter().map(|tx| tx.fee()).sum();
|
let fees = txs.iter().map(|tx| tx.fee(prev.height + 1)).sum();
|
||||||
let next_header_info =
|
let next_header_info =
|
||||||
consensus::next_difficulty(prev.height + 1, chain.difficulty_iter().unwrap());
|
consensus::next_difficulty(prev.height + 1, chain.difficulty_iter().unwrap());
|
||||||
let reward = libtx::reward::output(&keychain, &builder, &key_id4, fees, false).unwrap();
|
let reward = libtx::reward::output(&keychain, &builder, &key_id4, fees, false).unwrap();
|
||||||
|
|
|
@ -99,13 +99,13 @@ pub const CUT_THROUGH_HORIZON: u32 = WEEK_HEIGHT as u32;
|
||||||
pub const STATE_SYNC_THRESHOLD: u32 = 2 * DAY_HEIGHT as u32;
|
pub const STATE_SYNC_THRESHOLD: u32 = 2 * DAY_HEIGHT as u32;
|
||||||
|
|
||||||
/// Weight of an input when counted against the max block weight capacity
|
/// Weight of an input when counted against the max block weight capacity
|
||||||
pub const BLOCK_INPUT_WEIGHT: u64 = 1;
|
pub const INPUT_WEIGHT: u64 = 1;
|
||||||
|
|
||||||
/// Weight of an output when counted against the max block weight capacity
|
/// Weight of an output when counted against the max block weight capacity
|
||||||
pub const BLOCK_OUTPUT_WEIGHT: u64 = 21;
|
pub const OUTPUT_WEIGHT: u64 = 21;
|
||||||
|
|
||||||
/// Weight of a kernel when counted against the max block weight capacity
|
/// Weight of a kernel when counted against the max block weight capacity
|
||||||
pub const BLOCK_KERNEL_WEIGHT: u64 = 3;
|
pub const KERNEL_WEIGHT: u64 = 3;
|
||||||
|
|
||||||
/// Total maximum block weight. At current sizes, this means a maximum
|
/// Total maximum block weight. At current sizes, this means a maximum
|
||||||
/// theoretical size of:
|
/// theoretical size of:
|
||||||
|
|
|
@ -469,11 +469,8 @@ impl Readable for UntrustedBlockHeader {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate global output and kernel MMR sizes against upper bounds based on block height.
|
// Validate global output and kernel MMR sizes against upper bounds based on block height.
|
||||||
let global_weight = TransactionBody::weight_as_block(
|
let global_weight =
|
||||||
0,
|
TransactionBody::weight_by_iok(0, header.output_mmr_count(), header.kernel_mmr_count());
|
||||||
header.output_mmr_count(),
|
|
||||||
header.kernel_mmr_count(),
|
|
||||||
);
|
|
||||||
if global_weight > global::max_block_weight() * (header.height + 1) {
|
if global_weight > global::max_block_weight() * (header.height + 1) {
|
||||||
return Err(ser::Error::CorruptedData);
|
return Err(ser::Error::CorruptedData);
|
||||||
}
|
}
|
||||||
|
@ -700,7 +697,7 @@ impl Block {
|
||||||
|
|
||||||
/// Sum of all fees (inputs less outputs) in the block
|
/// Sum of all fees (inputs less outputs) in the block
|
||||||
pub fn total_fees(&self) -> u64 {
|
pub fn total_fees(&self) -> u64 {
|
||||||
self.body.fee()
|
self.body.fee(self.header.height)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// "Lightweight" validation that we can perform quickly during read/deserialization.
|
/// "Lightweight" validation that we can perform quickly during read/deserialization.
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
|
|
||||||
//! Transactions
|
//! Transactions
|
||||||
|
|
||||||
|
use crate::core::block::HeaderVersion;
|
||||||
use crate::core::hash::{DefaultHashable, Hashed};
|
use crate::core::hash::{DefaultHashable, Hashed};
|
||||||
use crate::core::verifier_cache::VerifierCache;
|
use crate::core::verifier_cache::VerifierCache;
|
||||||
use crate::core::{committed, Committed};
|
use crate::core::{committed, Committed};
|
||||||
|
@ -25,9 +26,12 @@ use crate::ser::{
|
||||||
use crate::{consensus, global};
|
use crate::{consensus, global};
|
||||||
use enum_primitive::FromPrimitive;
|
use enum_primitive::FromPrimitive;
|
||||||
use keychain::{self, BlindingFactor};
|
use keychain::{self, BlindingFactor};
|
||||||
|
use serde::de;
|
||||||
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::cmp::{max, min};
|
use std::cmp::{max, min};
|
||||||
use std::convert::{TryFrom, TryInto};
|
use std::convert::{TryFrom, TryInto};
|
||||||
|
use std::fmt::Display;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::{error, fmt};
|
use std::{error, fmt};
|
||||||
use util::secp;
|
use util::secp;
|
||||||
|
@ -36,6 +40,184 @@ use util::static_secp_instance;
|
||||||
use util::RwLock;
|
use util::RwLock;
|
||||||
use util::ToHex;
|
use util::ToHex;
|
||||||
|
|
||||||
|
/// Fee fields as in fix-fees RFC: { future_use: 20, fee_shift: 4, fee: 40 }
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub struct FeeFields(u64);
|
||||||
|
|
||||||
|
impl DefaultHashable for FeeFields {}
|
||||||
|
|
||||||
|
impl Writeable for FeeFields {
|
||||||
|
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
|
||||||
|
writer.write_u64(self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Readable for FeeFields {
|
||||||
|
fn read<R: Reader>(reader: &mut R) -> Result<Self, ser::Error> {
|
||||||
|
let fee_fields = reader.read_u64()?;
|
||||||
|
Ok(Self(fee_fields))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for FeeFields {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for FeeFields {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
serializer.collect_str(&self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for FeeFields {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<FeeFields, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
struct FeeFieldsVisitor;
|
||||||
|
impl<'de> de::Visitor<'de> for FeeFieldsVisitor {
|
||||||
|
type Value = FeeFields;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
formatter.write_str("an 64-bit integer")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: de::Error,
|
||||||
|
{
|
||||||
|
let value = value
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| E::custom(format!("invalid fee field")))?;
|
||||||
|
self.visit_u64(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: de::Error,
|
||||||
|
{
|
||||||
|
Ok(FeeFields(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deserializer.deserialize_any(FeeFieldsVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Conversion from a valid fee to a FeeFields with 0 fee_shift
|
||||||
|
/// The valid fee range is 1..FEE_MASK
|
||||||
|
impl TryFrom<u64> for FeeFields {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(fee: u64) -> Result<Self, Self::Error> {
|
||||||
|
if fee == 0 {
|
||||||
|
Err(Error::InvalidFeeFields)
|
||||||
|
} else if fee > FeeFields::FEE_MASK {
|
||||||
|
Err(Error::InvalidFeeFields)
|
||||||
|
} else {
|
||||||
|
Ok(Self(fee))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Conversion from a 32-bit fee to a FeeFields with 0 fee_shift
|
||||||
|
/// For use exclusively in tests with constant fees
|
||||||
|
impl From<u32> for FeeFields {
|
||||||
|
fn from(fee: u32) -> Self {
|
||||||
|
Self(fee as u64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<FeeFields> for u64 {
|
||||||
|
fn from(fee_fields: FeeFields) -> Self {
|
||||||
|
fee_fields.0 as u64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FeeFields {
|
||||||
|
/// Fees are limited to 40 bits
|
||||||
|
const FEE_BITS: u32 = 40;
|
||||||
|
/// Used to extract fee field
|
||||||
|
const FEE_MASK: u64 = (1u64 << FeeFields::FEE_BITS) - 1;
|
||||||
|
|
||||||
|
/// Fee shifts are limited to 4 bits
|
||||||
|
pub const FEE_SHIFT_BITS: u32 = 4;
|
||||||
|
/// Used to extract fee_shift field
|
||||||
|
pub const FEE_SHIFT_MASK: u64 = (1u64 << FeeFields::FEE_SHIFT_BITS) - 1;
|
||||||
|
|
||||||
|
/// Create a zero FeeFields with 0 fee and 0 fee_shift
|
||||||
|
pub fn zero() -> Self {
|
||||||
|
Self(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new FeeFields from the provided shift and fee
|
||||||
|
/// Checks both are valid (in range)
|
||||||
|
pub fn new(fee_shift: u64, fee: u64) -> Result<Self, Error> {
|
||||||
|
if fee == 0 {
|
||||||
|
Err(Error::InvalidFeeFields)
|
||||||
|
} else if fee > FeeFields::FEE_MASK {
|
||||||
|
Err(Error::InvalidFeeFields)
|
||||||
|
} else if fee_shift > FeeFields::FEE_SHIFT_MASK {
|
||||||
|
Err(Error::InvalidFeeFields)
|
||||||
|
} else {
|
||||||
|
Ok(Self((fee_shift << FeeFields::FEE_BITS) | fee))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract bitfields fee_shift and fee into tuple
|
||||||
|
/// ignore upper 64-FEE_BITS-FEE_SHIFT_BITS bits
|
||||||
|
pub fn as_tuple(&self) -> (u64, u64) {
|
||||||
|
let fee = self.0 & FeeFields::FEE_MASK;
|
||||||
|
let fee_shift = (self.0 >> FeeFields::FEE_BITS) & FeeFields::FEE_SHIFT_MASK;
|
||||||
|
(fee, fee_shift)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Turn a zero `FeeField` into a `None`, any other value into a `Some`.
|
||||||
|
/// We need this because a zero `FeeField` cannot be deserialized.
|
||||||
|
pub fn as_opt(&self) -> Option<Self> {
|
||||||
|
if self.is_zero() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(*self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if the `FeeFields` is set to zero
|
||||||
|
pub fn is_zero(&self) -> bool {
|
||||||
|
self.0 == 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fee_fields_as_int<S>(fee_fields: &FeeFields, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
serializer.serialize_u64(fee_fields.0)
|
||||||
|
}
|
||||||
|
|
||||||
/// Relative height field on NRD kernel variant.
|
/// Relative height field on NRD kernel variant.
|
||||||
/// u16 representing a height between 1 and MAX (consensus::WEEK_HEIGHT).
|
/// u16 representing a height between 1 and MAX (consensus::WEEK_HEIGHT).
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||||
|
@ -106,21 +288,24 @@ pub enum KernelFeatures {
|
||||||
/// Plain kernel (the default for Grin txs).
|
/// Plain kernel (the default for Grin txs).
|
||||||
Plain {
|
Plain {
|
||||||
/// Plain kernels have fees.
|
/// Plain kernels have fees.
|
||||||
fee: u64,
|
#[serde(serialize_with = "fee_fields_as_int")]
|
||||||
|
fee: FeeFields,
|
||||||
},
|
},
|
||||||
/// A coinbase kernel.
|
/// A coinbase kernel.
|
||||||
Coinbase,
|
Coinbase,
|
||||||
/// A kernel with an explicit lock height (and fee).
|
/// A kernel with an explicit lock height (and fee).
|
||||||
HeightLocked {
|
HeightLocked {
|
||||||
/// Height locked kernels have fees.
|
/// Height locked kernels have fees.
|
||||||
fee: u64,
|
#[serde(serialize_with = "fee_fields_as_int")]
|
||||||
|
fee: FeeFields,
|
||||||
/// Height locked kernels have lock heights.
|
/// Height locked kernels have lock heights.
|
||||||
lock_height: u64,
|
lock_height: u64,
|
||||||
},
|
},
|
||||||
/// "No Recent Duplicate" (NRD) kernels enforcing relative lock height between instances.
|
/// "No Recent Duplicate" (NRD) kernels enforcing relative lock height between instances.
|
||||||
NoRecentDuplicate {
|
NoRecentDuplicate {
|
||||||
/// These have fees.
|
/// These have fees.
|
||||||
fee: u64,
|
#[serde(serialize_with = "fee_fields_as_int")]
|
||||||
|
fee: FeeFields,
|
||||||
/// Relative lock height.
|
/// Relative lock height.
|
||||||
relative_height: NRDRelativeHeight,
|
relative_height: NRDRelativeHeight,
|
||||||
},
|
},
|
||||||
|
@ -153,10 +338,10 @@ impl KernelFeatures {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// msg = hash(features) for coinbase kernels
|
/// msg = hash(features) for coinbase kernels
|
||||||
/// hash(features || fee) for plain kernels
|
/// hash(features || fee_fields) for plain kernels
|
||||||
/// hash(features || fee || lock_height) for height locked kernels
|
/// hash(features || fee_fields || lock_height) for height locked kernels
|
||||||
/// hash(features || fee || relative_height) for NRD kernels
|
/// hash(features || fee_fields || relative_height) for NRD kernels
|
||||||
pub fn kernel_sig_msg(&self) -> Result<secp::Message, Error> {
|
pub fn kernel_sig_msg(&self) -> Result<secp::Message, Error> {
|
||||||
let x = self.as_u8();
|
let x = self.as_u8();
|
||||||
let hash = match self {
|
let hash = match self {
|
||||||
|
@ -174,21 +359,21 @@ impl KernelFeatures {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write tx kernel features out in v1 protocol format.
|
/// Write tx kernel features out in v1 protocol format.
|
||||||
/// Always include the fee and lock_height, writing 0 value if unused.
|
/// Always include the fee_fields and lock_height, writing 0 value if unused.
|
||||||
fn write_v1<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
|
fn write_v1<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
|
||||||
writer.write_u8(self.as_u8())?;
|
writer.write_u8(self.as_u8())?;
|
||||||
match self {
|
match self {
|
||||||
KernelFeatures::Plain { fee } => {
|
KernelFeatures::Plain { fee } => {
|
||||||
writer.write_u64(*fee)?;
|
fee.write(writer)?;
|
||||||
// Write "empty" bytes for feature specific data (8 bytes).
|
// Write "empty" bytes for feature specific data (8 bytes).
|
||||||
writer.write_empty_bytes(8)?;
|
writer.write_empty_bytes(8)?;
|
||||||
}
|
}
|
||||||
KernelFeatures::Coinbase => {
|
KernelFeatures::Coinbase => {
|
||||||
// Write "empty" bytes for fee (8 bytes) and feature specific data (8 bytes).
|
// Write "empty" bytes for fee_fields (8 bytes) and feature specific data (8 bytes).
|
||||||
writer.write_empty_bytes(16)?;
|
writer.write_empty_bytes(16)?;
|
||||||
}
|
}
|
||||||
KernelFeatures::HeightLocked { fee, lock_height } => {
|
KernelFeatures::HeightLocked { fee, lock_height } => {
|
||||||
writer.write_u64(*fee)?;
|
fee.write(writer)?;
|
||||||
// 8 bytes of feature specific data containing the lock height as big-endian u64.
|
// 8 bytes of feature specific data containing the lock height as big-endian u64.
|
||||||
writer.write_u64(*lock_height)?;
|
writer.write_u64(*lock_height)?;
|
||||||
}
|
}
|
||||||
|
@ -196,7 +381,7 @@ impl KernelFeatures {
|
||||||
fee,
|
fee,
|
||||||
relative_height,
|
relative_height,
|
||||||
} => {
|
} => {
|
||||||
writer.write_u64(*fee)?;
|
fee.write(writer)?;
|
||||||
|
|
||||||
// 8 bytes of feature specific data. First 6 bytes are empty.
|
// 8 bytes of feature specific data. First 6 bytes are empty.
|
||||||
// Last 2 bytes contain the relative lock height as big-endian u16.
|
// Last 2 bytes contain the relative lock height as big-endian u16.
|
||||||
|
@ -211,20 +396,20 @@ impl KernelFeatures {
|
||||||
|
|
||||||
/// Write tx kernel features out in v2 protocol format.
|
/// Write tx kernel features out in v2 protocol format.
|
||||||
/// These are variable sized based on feature variant.
|
/// These are variable sized based on feature variant.
|
||||||
/// Only write fee out for feature variants that support it.
|
/// Only write fee_fields out for feature variants that support it.
|
||||||
/// Only write lock_height out for feature variants that support it.
|
/// Only write lock_height out for feature variants that support it.
|
||||||
fn write_v2<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
|
fn write_v2<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
|
||||||
writer.write_u8(self.as_u8())?;
|
writer.write_u8(self.as_u8())?;
|
||||||
match self {
|
match self {
|
||||||
KernelFeatures::Plain { fee } => {
|
KernelFeatures::Plain { fee } => {
|
||||||
// Fee only, no additional data on plain kernels.
|
// Fee only, no additional data on plain kernels.
|
||||||
writer.write_u64(*fee)?;
|
fee.write(writer)?;
|
||||||
}
|
}
|
||||||
KernelFeatures::Coinbase => {
|
KernelFeatures::Coinbase => {
|
||||||
// No additional data.
|
// No additional data.
|
||||||
}
|
}
|
||||||
KernelFeatures::HeightLocked { fee, lock_height } => {
|
KernelFeatures::HeightLocked { fee, lock_height } => {
|
||||||
writer.write_u64(*fee)?;
|
fee.write(writer)?;
|
||||||
// V2 height locked kernels use 8 bytes for the lock height.
|
// V2 height locked kernels use 8 bytes for the lock height.
|
||||||
writer.write_u64(*lock_height)?;
|
writer.write_u64(*lock_height)?;
|
||||||
}
|
}
|
||||||
|
@ -232,7 +417,7 @@ impl KernelFeatures {
|
||||||
fee,
|
fee,
|
||||||
relative_height,
|
relative_height,
|
||||||
} => {
|
} => {
|
||||||
writer.write_u64(*fee)?;
|
fee.write(writer)?;
|
||||||
// V2 NRD kernels use 2 bytes for the relative lock height.
|
// V2 NRD kernels use 2 bytes for the relative lock height.
|
||||||
relative_height.write(writer)?;
|
relative_height.write(writer)?;
|
||||||
}
|
}
|
||||||
|
@ -240,7 +425,7 @@ impl KernelFeatures {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always read feature byte, 8 bytes for fee and 8 bytes for additional data
|
// Always read feature byte, 8 bytes for fee_fields and 8 bytes for additional data
|
||||||
// representing lock height or relative height.
|
// representing lock height or relative height.
|
||||||
// Fee and additional data may be unused for some kernel variants but we need
|
// Fee and additional data may be unused for some kernel variants but we need
|
||||||
// to read these bytes and verify they are 0 if unused.
|
// to read these bytes and verify they are 0 if unused.
|
||||||
|
@ -248,19 +433,19 @@ impl KernelFeatures {
|
||||||
let feature_byte = reader.read_u8()?;
|
let feature_byte = reader.read_u8()?;
|
||||||
let features = match feature_byte {
|
let features = match feature_byte {
|
||||||
KernelFeatures::PLAIN_U8 => {
|
KernelFeatures::PLAIN_U8 => {
|
||||||
let fee = reader.read_u64()?;
|
let fee = FeeFields::read(reader)?;
|
||||||
// 8 "empty" bytes as additional data is not used.
|
// 8 "empty" bytes as additional data is not used.
|
||||||
reader.read_empty_bytes(8)?;
|
reader.read_empty_bytes(8)?;
|
||||||
KernelFeatures::Plain { fee }
|
KernelFeatures::Plain { fee }
|
||||||
}
|
}
|
||||||
KernelFeatures::COINBASE_U8 => {
|
KernelFeatures::COINBASE_U8 => {
|
||||||
// 8 "empty" bytes as fee is not used.
|
// 8 "empty" bytes as fee_fields is not used.
|
||||||
// 8 "empty" bytes as additional data is not used.
|
// 8 "empty" bytes as additional data is not used.
|
||||||
reader.read_empty_bytes(16)?;
|
reader.read_empty_bytes(16)?;
|
||||||
KernelFeatures::Coinbase
|
KernelFeatures::Coinbase
|
||||||
}
|
}
|
||||||
KernelFeatures::HEIGHT_LOCKED_U8 => {
|
KernelFeatures::HEIGHT_LOCKED_U8 => {
|
||||||
let fee = reader.read_u64()?;
|
let fee = FeeFields::read(reader)?;
|
||||||
// 8 bytes of feature specific data, lock height as big-endian u64.
|
// 8 bytes of feature specific data, lock height as big-endian u64.
|
||||||
let lock_height = reader.read_u64()?;
|
let lock_height = reader.read_u64()?;
|
||||||
KernelFeatures::HeightLocked { fee, lock_height }
|
KernelFeatures::HeightLocked { fee, lock_height }
|
||||||
|
@ -271,7 +456,7 @@ impl KernelFeatures {
|
||||||
return Err(ser::Error::CorruptedData);
|
return Err(ser::Error::CorruptedData);
|
||||||
}
|
}
|
||||||
|
|
||||||
let fee = reader.read_u64()?;
|
let fee = FeeFields::read(reader)?;
|
||||||
|
|
||||||
// 8 bytes of feature specific data.
|
// 8 bytes of feature specific data.
|
||||||
// The first 6 bytes must be "empty".
|
// The first 6 bytes must be "empty".
|
||||||
|
@ -295,12 +480,12 @@ impl KernelFeatures {
|
||||||
fn read_v2<R: Reader>(reader: &mut R) -> Result<KernelFeatures, ser::Error> {
|
fn read_v2<R: Reader>(reader: &mut R) -> Result<KernelFeatures, ser::Error> {
|
||||||
let features = match reader.read_u8()? {
|
let features = match reader.read_u8()? {
|
||||||
KernelFeatures::PLAIN_U8 => {
|
KernelFeatures::PLAIN_U8 => {
|
||||||
let fee = reader.read_u64()?;
|
let fee = FeeFields::read(reader)?;
|
||||||
KernelFeatures::Plain { fee }
|
KernelFeatures::Plain { fee }
|
||||||
}
|
}
|
||||||
KernelFeatures::COINBASE_U8 => KernelFeatures::Coinbase,
|
KernelFeatures::COINBASE_U8 => KernelFeatures::Coinbase,
|
||||||
KernelFeatures::HEIGHT_LOCKED_U8 => {
|
KernelFeatures::HEIGHT_LOCKED_U8 => {
|
||||||
let fee = reader.read_u64()?;
|
let fee = FeeFields::read(reader)?;
|
||||||
let lock_height = reader.read_u64()?;
|
let lock_height = reader.read_u64()?;
|
||||||
KernelFeatures::HeightLocked { fee, lock_height }
|
KernelFeatures::HeightLocked { fee, lock_height }
|
||||||
}
|
}
|
||||||
|
@ -310,7 +495,7 @@ impl KernelFeatures {
|
||||||
return Err(ser::Error::CorruptedData);
|
return Err(ser::Error::CorruptedData);
|
||||||
}
|
}
|
||||||
|
|
||||||
let fee = reader.read_u64()?;
|
let fee = FeeFields::read(reader)?;
|
||||||
let relative_height = NRDRelativeHeight::read(reader)?;
|
let relative_height = NRDRelativeHeight::read(reader)?;
|
||||||
KernelFeatures::NoRecentDuplicate {
|
KernelFeatures::NoRecentDuplicate {
|
||||||
fee,
|
fee,
|
||||||
|
@ -384,6 +569,8 @@ pub enum Error {
|
||||||
/// Validation error relating to kernel features.
|
/// Validation error relating to kernel features.
|
||||||
/// It is invalid for a transaction to contain a coinbase kernel, for example.
|
/// It is invalid for a transaction to contain a coinbase kernel, for example.
|
||||||
InvalidKernelFeatures,
|
InvalidKernelFeatures,
|
||||||
|
/// feeshift is limited to 4 bits and fee must be positive and fit in 40 bits.
|
||||||
|
InvalidFeeFields,
|
||||||
/// NRD kernel relative height is limited to 1 week duration and must be greater than 0.
|
/// NRD kernel relative height is limited to 1 week duration and must be greater than 0.
|
||||||
InvalidNRDRelativeHeight,
|
InvalidNRDRelativeHeight,
|
||||||
/// Signature verification error.
|
/// Signature verification error.
|
||||||
|
@ -435,7 +622,7 @@ impl From<committed::Error> for Error {
|
||||||
/// A proof that a transaction sums to zero. Includes both the transaction's
|
/// A proof that a transaction sums to zero. Includes both the transaction's
|
||||||
/// Pedersen commitment and the signature, that guarantees that the commitments
|
/// Pedersen commitment and the signature, that guarantees that the commitments
|
||||||
/// amount to zero.
|
/// amount to zero.
|
||||||
/// The signature signs the fee and the lock_height, which are retained for
|
/// The signature signs the fee_fields and the lock_height, which are retained for
|
||||||
/// signature validation.
|
/// signature validation.
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
|
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
|
||||||
pub struct TxKernel {
|
pub struct TxKernel {
|
||||||
|
@ -450,7 +637,7 @@ pub struct TxKernel {
|
||||||
)]
|
)]
|
||||||
pub excess: Commitment,
|
pub excess: Commitment,
|
||||||
/// The signature proving the excess is a valid public key, which signs
|
/// The signature proving the excess is a valid public key, which signs
|
||||||
/// the transaction fee.
|
/// the transaction fee_fields.
|
||||||
#[serde(with = "secp_ser::sig_serde")]
|
#[serde(with = "secp_ser::sig_serde")]
|
||||||
pub excess_sig: secp::Signature,
|
pub excess_sig: secp::Signature,
|
||||||
}
|
}
|
||||||
|
@ -562,14 +749,14 @@ impl TxKernel {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The msg signed as part of the tx kernel.
|
/// The msg signed as part of the tx kernel.
|
||||||
/// Based on kernel features and associated fields (fee and lock_height).
|
/// Based on kernel features and associated fields (fee_fields and lock_height).
|
||||||
pub fn msg_to_sign(&self) -> Result<secp::Message, Error> {
|
pub fn msg_to_sign(&self) -> Result<secp::Message, Error> {
|
||||||
let msg = self.features.kernel_sig_msg()?;
|
let msg = self.features.kernel_sig_msg()?;
|
||||||
Ok(msg)
|
Ok(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verify the transaction proof validity. Entails handling the commitment
|
/// Verify the transaction proof validity. Entails handling the commitment
|
||||||
/// as a public key and checking the signature verifies with the fee as
|
/// as a public key and checking the signature verifies with the fee_fields as
|
||||||
/// message.
|
/// message.
|
||||||
pub fn verify(&self) -> Result<(), Error> {
|
pub fn verify(&self) -> Result<(), Error> {
|
||||||
let secp = static_secp_instance();
|
let secp = static_secp_instance();
|
||||||
|
@ -616,7 +803,9 @@ impl TxKernel {
|
||||||
|
|
||||||
/// Build an empty tx kernel with zero values.
|
/// Build an empty tx kernel with zero values.
|
||||||
pub fn empty() -> TxKernel {
|
pub fn empty() -> TxKernel {
|
||||||
TxKernel::with_features(KernelFeatures::Plain { fee: 0 })
|
TxKernel::with_features(KernelFeatures::Plain {
|
||||||
|
fee: FeeFields::zero(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build an empty tx kernel with the provided kernel features.
|
/// Build an empty tx kernel with the provided kernel features.
|
||||||
|
@ -687,8 +876,7 @@ impl Readable for TransactionBody {
|
||||||
|
|
||||||
// Quick block weight check before proceeding.
|
// Quick block weight check before proceeding.
|
||||||
// Note: We use weight_as_block here (inputs have weight).
|
// Note: We use weight_as_block here (inputs have weight).
|
||||||
let tx_block_weight =
|
let tx_block_weight = TransactionBody::weight_by_iok(num_inputs, num_outputs, num_kernels);
|
||||||
TransactionBody::weight_as_block(num_inputs, num_outputs, num_kernels);
|
|
||||||
if tx_block_weight > global::max_block_weight() {
|
if tx_block_weight > global::max_block_weight() {
|
||||||
return Err(ser::Error::TooLargeReadErr);
|
return Err(ser::Error::TooLargeReadErr);
|
||||||
}
|
}
|
||||||
|
@ -861,7 +1049,7 @@ impl TransactionBody {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Total fee for a TransactionBody is the sum of fees of all fee carrying kernels.
|
/// Total fee for a TransactionBody is the sum of fees of all fee carrying kernels.
|
||||||
pub fn fee(&self) -> u64 {
|
pub fn fee(&self, height: u64) -> u64 {
|
||||||
self.kernels
|
self.kernels
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|k| match k.features {
|
.filter_map(|k| match k.features {
|
||||||
|
@ -870,49 +1058,57 @@ impl TransactionBody {
|
||||||
KernelFeatures::HeightLocked { fee, .. } => Some(fee),
|
KernelFeatures::HeightLocked { fee, .. } => Some(fee),
|
||||||
KernelFeatures::NoRecentDuplicate { fee, .. } => Some(fee),
|
KernelFeatures::NoRecentDuplicate { fee, .. } => Some(fee),
|
||||||
})
|
})
|
||||||
.fold(0, |acc, fee| acc.saturating_add(fee))
|
.fold(0, |acc, fee_fields| {
|
||||||
|
acc.saturating_add(fee_fields.fee(height))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn overage(&self) -> i64 {
|
/// fee_shift for a TransactionBody is the maximum of fee_shifts of all fee carrying kernels.
|
||||||
self.fee() as i64
|
pub fn fee_shift(&self, height: u64) -> u8 {
|
||||||
|
self.kernels
|
||||||
|
.iter()
|
||||||
|
.filter_map(|k| match k.features {
|
||||||
|
KernelFeatures::Coinbase => None,
|
||||||
|
KernelFeatures::Plain { fee } => Some(fee),
|
||||||
|
KernelFeatures::HeightLocked { fee, .. } => Some(fee),
|
||||||
|
KernelFeatures::NoRecentDuplicate { fee, .. } => Some(fee),
|
||||||
|
})
|
||||||
|
.fold(0, |acc, fee_fields| max(acc, fee_fields.fee_shift(height)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate transaction weight
|
/// Shifted fee for a TransactionBody is the sum of fees shifted right by the maximum fee_shift
|
||||||
pub fn body_weight(&self) -> u64 {
|
/// this is used to determine whether a tx can be relayed or accepted in a mempool
|
||||||
TransactionBody::weight(
|
/// where transactions can specify a higher block-inclusion priority as a positive shift up to 15
|
||||||
self.inputs.len() as u64,
|
/// but are required to overpay the minimum required fees by a factor of 2^priority
|
||||||
self.outputs.len() as u64,
|
pub fn shifted_fee(&self, height: u64) -> u64 {
|
||||||
self.kernels.len() as u64,
|
self.fee(height) >> self.fee_shift(height)
|
||||||
)
|
}
|
||||||
|
|
||||||
|
/// 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))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn overage(&self, height: u64) -> i64 {
|
||||||
|
self.fee(height) as i64
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate weight of transaction using block weighing
|
/// Calculate weight of transaction using block weighing
|
||||||
pub fn body_weight_as_block(&self) -> u64 {
|
pub fn weight(&self) -> u64 {
|
||||||
TransactionBody::weight_as_block(
|
TransactionBody::weight_by_iok(
|
||||||
self.inputs.len() as u64,
|
self.inputs.len() as u64,
|
||||||
self.outputs.len() as u64,
|
self.outputs.len() as u64,
|
||||||
self.kernels.len() as u64,
|
self.kernels.len() as u64,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate transaction weight from transaction details. This is non
|
|
||||||
/// consensus critical and compared to block weight, incentivizes spending
|
|
||||||
/// more outputs (to lower the fee).
|
|
||||||
pub fn weight(num_inputs: u64, num_outputs: u64, num_kernels: u64) -> u64 {
|
|
||||||
let body_weight = num_outputs
|
|
||||||
.saturating_mul(4)
|
|
||||||
.saturating_add(num_kernels)
|
|
||||||
.saturating_sub(num_inputs);
|
|
||||||
max(body_weight, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calculate transaction weight using block weighing from transaction
|
/// Calculate transaction weight using block weighing from transaction
|
||||||
/// details. Consensus critical and uses consensus weight values.
|
/// details. Consensus critical and uses consensus weight values.
|
||||||
pub fn weight_as_block(num_inputs: u64, num_outputs: u64, num_kernels: u64) -> u64 {
|
pub fn weight_by_iok(num_inputs: u64, num_outputs: u64, num_kernels: u64) -> u64 {
|
||||||
num_inputs
|
num_inputs
|
||||||
.saturating_mul(consensus::BLOCK_INPUT_WEIGHT as u64)
|
.saturating_mul(consensus::INPUT_WEIGHT as u64)
|
||||||
.saturating_add(num_outputs.saturating_mul(consensus::BLOCK_OUTPUT_WEIGHT as u64))
|
.saturating_add(num_outputs.saturating_mul(consensus::OUTPUT_WEIGHT as u64))
|
||||||
.saturating_add(num_kernels.saturating_mul(consensus::BLOCK_KERNEL_WEIGHT as u64))
|
.saturating_add(num_kernels.saturating_mul(consensus::KERNEL_WEIGHT as u64))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Lock height of a body is the max lock height of the kernels.
|
/// Lock height of a body is the max lock height of the kernels.
|
||||||
|
@ -932,7 +1128,7 @@ impl TransactionBody {
|
||||||
fn verify_weight(&self, weighting: Weighting) -> Result<(), Error> {
|
fn verify_weight(&self, weighting: Weighting) -> Result<(), Error> {
|
||||||
// A coinbase reward is a single output and a single kernel (for now).
|
// A coinbase reward is a single output and a single kernel (for now).
|
||||||
// We need to account for this when verifying max tx weights.
|
// We need to account for this when verifying max tx weights.
|
||||||
let coinbase_weight = consensus::BLOCK_OUTPUT_WEIGHT + consensus::BLOCK_KERNEL_WEIGHT;
|
let coinbase_weight = consensus::OUTPUT_WEIGHT + consensus::KERNEL_WEIGHT;
|
||||||
|
|
||||||
// If "tx" body then remember to reduce the max_block_weight by the weight of a kernel.
|
// If "tx" body then remember to reduce the max_block_weight by the weight of a kernel.
|
||||||
// If "limited tx" then compare against the provided max_weight.
|
// If "limited tx" then compare against the provided max_weight.
|
||||||
|
@ -943,7 +1139,7 @@ impl TransactionBody {
|
||||||
// for the additional coinbase reward (1 output + 1 kernel).
|
// for the additional coinbase reward (1 output + 1 kernel).
|
||||||
//
|
//
|
||||||
let max_weight = match weighting {
|
let max_weight = match weighting {
|
||||||
Weighting::AsTransaction => global::max_block_weight().saturating_sub(coinbase_weight),
|
Weighting::AsTransaction => global::max_tx_weight(),
|
||||||
Weighting::AsLimitedTransaction(max_weight) => {
|
Weighting::AsLimitedTransaction(max_weight) => {
|
||||||
min(global::max_block_weight(), max_weight).saturating_sub(coinbase_weight)
|
min(global::max_block_weight(), max_weight).saturating_sub(coinbase_weight)
|
||||||
}
|
}
|
||||||
|
@ -954,7 +1150,7 @@ impl TransactionBody {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if self.body_weight_as_block() > max_weight {
|
if self.weight() > max_weight {
|
||||||
return Err(Error::TooHeavy);
|
return Err(Error::TooHeavy);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -1258,13 +1454,23 @@ impl Transaction {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Total fee for a transaction is the sum of fees of all kernels.
|
/// Total fee for a transaction is the sum of fees of all kernels.
|
||||||
pub fn fee(&self) -> u64 {
|
pub fn fee(&self, height: u64) -> u64 {
|
||||||
self.body.fee()
|
self.body.fee(height)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Total overage across all kernels.
|
/// Total overage across all kernels.
|
||||||
pub fn overage(&self) -> i64 {
|
pub fn overage(&self, height: u64) -> i64 {
|
||||||
self.body.overage()
|
self.body.overage(height)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Lock height of a transaction is the max lock height of the kernels.
|
/// Lock height of a transaction is the max lock height of the kernels.
|
||||||
|
@ -1290,32 +1496,50 @@ impl Transaction {
|
||||||
&self,
|
&self,
|
||||||
weighting: Weighting,
|
weighting: Weighting,
|
||||||
verifier: Arc<RwLock<dyn VerifierCache>>,
|
verifier: Arc<RwLock<dyn VerifierCache>>,
|
||||||
|
height: u64,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
self.body.verify_features()?;
|
self.body.verify_features()?;
|
||||||
self.body.validate(weighting, verifier)?;
|
self.body.validate(weighting, verifier)?;
|
||||||
self.verify_kernel_sums(self.overage(), self.offset.clone())?;
|
self.verify_kernel_sums(self.overage(height), self.offset.clone())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Can be used to compare txs by their fee/weight ratio.
|
/// 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.
|
/// Don't use these values for anything else though due to precision multiplier.
|
||||||
pub fn fee_to_weight(&self) -> u64 {
|
pub fn fee_rate(&self, height: u64) -> u64 {
|
||||||
self.fee() * 1_000 / self.tx_weight() as u64
|
self.fee(height) / self.weight() as u64
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate transaction weight
|
/// Calculate transaction weight
|
||||||
pub fn tx_weight(&self) -> u64 {
|
pub fn weight(&self) -> u64 {
|
||||||
self.body.body_weight()
|
self.body.weight()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate transaction weight as a block
|
/// Transaction minimum acceptable fee
|
||||||
pub fn tx_weight_as_block(&self) -> u64 {
|
pub fn accept_fee(&self, height: u64) -> u64 {
|
||||||
self.body.body_weight_as_block()
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Old weight definition for pool acceptance
|
||||||
|
pub fn old_weight_by_iok(num_inputs: u64, num_outputs: u64, num_kernels: u64) -> u64 {
|
||||||
|
let body_weight = num_outputs
|
||||||
|
.saturating_mul(4)
|
||||||
|
.saturating_add(num_kernels)
|
||||||
|
.saturating_sub(num_inputs);
|
||||||
|
max(body_weight, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate transaction weight from transaction details
|
/// Calculate transaction weight from transaction details
|
||||||
pub fn weight(num_inputs: u64, num_outputs: u64, num_kernels: u64) -> u64 {
|
pub fn weight_by_iok(num_inputs: u64, num_outputs: u64, num_kernels: u64) -> u64 {
|
||||||
TransactionBody::weight(num_inputs, num_outputs, num_kernels)
|
TransactionBody::weight_by_iok(num_inputs, num_outputs, num_kernels)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2113,7 +2337,7 @@ mod test {
|
||||||
let sig = secp::Signature::from_raw_data(&[0; 64]).unwrap();
|
let sig = secp::Signature::from_raw_data(&[0; 64]).unwrap();
|
||||||
|
|
||||||
let kernel = TxKernel {
|
let kernel = TxKernel {
|
||||||
features: KernelFeatures::Plain { fee: 10 },
|
features: KernelFeatures::Plain { fee: 10.into() },
|
||||||
excess: commit,
|
excess: commit,
|
||||||
excess_sig: sig.clone(),
|
excess_sig: sig.clone(),
|
||||||
};
|
};
|
||||||
|
@ -2123,7 +2347,7 @@ mod test {
|
||||||
let mut vec = vec![];
|
let mut vec = vec![];
|
||||||
ser::serialize(&mut vec, version, &kernel).expect("serialized failed");
|
ser::serialize(&mut vec, version, &kernel).expect("serialized failed");
|
||||||
let kernel2: TxKernel = ser::deserialize(&mut &vec[..], version).unwrap();
|
let kernel2: TxKernel = ser::deserialize(&mut &vec[..], version).unwrap();
|
||||||
assert_eq!(kernel2.features, KernelFeatures::Plain { fee: 10 });
|
assert_eq!(kernel2.features, KernelFeatures::Plain { fee: 10.into() });
|
||||||
assert_eq!(kernel2.excess, commit);
|
assert_eq!(kernel2.excess, commit);
|
||||||
assert_eq!(kernel2.excess_sig, sig.clone());
|
assert_eq!(kernel2.excess_sig, sig.clone());
|
||||||
}
|
}
|
||||||
|
@ -2132,7 +2356,7 @@ mod test {
|
||||||
let mut vec = vec![];
|
let mut vec = vec![];
|
||||||
ser::serialize_default(&mut vec, &kernel).expect("serialized failed");
|
ser::serialize_default(&mut vec, &kernel).expect("serialized failed");
|
||||||
let kernel2: TxKernel = ser::deserialize_default(&mut &vec[..]).unwrap();
|
let kernel2: TxKernel = ser::deserialize_default(&mut &vec[..]).unwrap();
|
||||||
assert_eq!(kernel2.features, KernelFeatures::Plain { fee: 10 });
|
assert_eq!(kernel2.features, KernelFeatures::Plain { fee: 10.into() });
|
||||||
assert_eq!(kernel2.excess, commit);
|
assert_eq!(kernel2.excess, commit);
|
||||||
assert_eq!(kernel2.excess_sig, sig.clone());
|
assert_eq!(kernel2.excess_sig, sig.clone());
|
||||||
}
|
}
|
||||||
|
@ -2151,7 +2375,7 @@ mod test {
|
||||||
// now check a kernel with lock_height serialize/deserialize correctly
|
// now check a kernel with lock_height serialize/deserialize correctly
|
||||||
let kernel = TxKernel {
|
let kernel = TxKernel {
|
||||||
features: KernelFeatures::HeightLocked {
|
features: KernelFeatures::HeightLocked {
|
||||||
fee: 10,
|
fee: 10.into(),
|
||||||
lock_height: 100,
|
lock_height: 100,
|
||||||
},
|
},
|
||||||
excess: commit,
|
excess: commit,
|
||||||
|
@ -2193,7 +2417,7 @@ mod test {
|
||||||
// now check an NRD kernel will serialize/deserialize correctly
|
// now check an NRD kernel will serialize/deserialize correctly
|
||||||
let kernel = TxKernel {
|
let kernel = TxKernel {
|
||||||
features: KernelFeatures::NoRecentDuplicate {
|
features: KernelFeatures::NoRecentDuplicate {
|
||||||
fee: 10,
|
fee: 10.into(),
|
||||||
relative_height: NRDRelativeHeight(100),
|
relative_height: NRDRelativeHeight(100),
|
||||||
},
|
},
|
||||||
excess: commit,
|
excess: commit,
|
||||||
|
@ -2225,7 +2449,7 @@ mod test {
|
||||||
let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0);
|
let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0);
|
||||||
|
|
||||||
let mut kernel = TxKernel::with_features(KernelFeatures::NoRecentDuplicate {
|
let mut kernel = TxKernel::with_features(KernelFeatures::NoRecentDuplicate {
|
||||||
fee: 10,
|
fee: 10.into(),
|
||||||
relative_height: NRDRelativeHeight(100),
|
relative_height: NRDRelativeHeight(100),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -2251,25 +2475,25 @@ mod test {
|
||||||
|
|
||||||
// Modify the fee and check signature no longer verifies.
|
// Modify the fee and check signature no longer verifies.
|
||||||
kernel.features = KernelFeatures::NoRecentDuplicate {
|
kernel.features = KernelFeatures::NoRecentDuplicate {
|
||||||
fee: 9,
|
fee: 9.into(),
|
||||||
relative_height: NRDRelativeHeight(100),
|
relative_height: NRDRelativeHeight(100),
|
||||||
};
|
};
|
||||||
assert_eq!(kernel.verify(), Err(Error::IncorrectSignature));
|
assert_eq!(kernel.verify(), Err(Error::IncorrectSignature));
|
||||||
|
|
||||||
// Modify the relative_height and check signature no longer verifies.
|
// Modify the relative_height and check signature no longer verifies.
|
||||||
kernel.features = KernelFeatures::NoRecentDuplicate {
|
kernel.features = KernelFeatures::NoRecentDuplicate {
|
||||||
fee: 10,
|
fee: 10.into(),
|
||||||
relative_height: NRDRelativeHeight(101),
|
relative_height: NRDRelativeHeight(101),
|
||||||
};
|
};
|
||||||
assert_eq!(kernel.verify(), Err(Error::IncorrectSignature));
|
assert_eq!(kernel.verify(), Err(Error::IncorrectSignature));
|
||||||
|
|
||||||
// Swap the features out for something different and check signature no longer verifies.
|
// Swap the features out for something different and check signature no longer verifies.
|
||||||
kernel.features = KernelFeatures::Plain { fee: 10 };
|
kernel.features = KernelFeatures::Plain { fee: 10.into() };
|
||||||
assert_eq!(kernel.verify(), Err(Error::IncorrectSignature));
|
assert_eq!(kernel.verify(), Err(Error::IncorrectSignature));
|
||||||
|
|
||||||
// Check signature verifies if we use the original features.
|
// Check signature verifies if we use the original features.
|
||||||
kernel.features = KernelFeatures::NoRecentDuplicate {
|
kernel.features = KernelFeatures::NoRecentDuplicate {
|
||||||
fee: 10,
|
fee: 10.into(),
|
||||||
relative_height: NRDRelativeHeight(100),
|
relative_height: NRDRelativeHeight(100),
|
||||||
};
|
};
|
||||||
assert_eq!(kernel.verify(), Ok(()));
|
assert_eq!(kernel.verify(), Ok(()));
|
||||||
|
@ -2330,7 +2554,7 @@ mod test {
|
||||||
let mut vec = vec![];
|
let mut vec = vec![];
|
||||||
ser::serialize_default(&mut vec, &(0u8, 10u64, 0u64))?;
|
ser::serialize_default(&mut vec, &(0u8, 10u64, 0u64))?;
|
||||||
let features: KernelFeatures = ser::deserialize_default(&mut &vec[..])?;
|
let features: KernelFeatures = ser::deserialize_default(&mut &vec[..])?;
|
||||||
assert_eq!(features, KernelFeatures::Plain { fee: 10 });
|
assert_eq!(features, KernelFeatures::Plain { fee: 10.into() });
|
||||||
|
|
||||||
let mut vec = vec![];
|
let mut vec = vec![];
|
||||||
ser::serialize_default(&mut vec, &(1u8, 0u64, 0u64))?;
|
ser::serialize_default(&mut vec, &(1u8, 0u64, 0u64))?;
|
||||||
|
@ -2343,7 +2567,7 @@ mod test {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
features,
|
features,
|
||||||
KernelFeatures::HeightLocked {
|
KernelFeatures::HeightLocked {
|
||||||
fee: 10,
|
fee: 10.into(),
|
||||||
lock_height: 100
|
lock_height: 100
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -2373,7 +2597,7 @@ mod test {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
features,
|
features,
|
||||||
KernelFeatures::NoRecentDuplicate {
|
KernelFeatures::NoRecentDuplicate {
|
||||||
fee: 10,
|
fee: 10.into(),
|
||||||
relative_height: NRDRelativeHeight(100)
|
relative_height: NRDRelativeHeight(100)
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -17,10 +17,10 @@
|
||||||
//! should be used sparingly.
|
//! should be used sparingly.
|
||||||
|
|
||||||
use crate::consensus::{
|
use crate::consensus::{
|
||||||
graph_weight, header_version, HeaderInfo, BASE_EDGE_BITS, BLOCK_KERNEL_WEIGHT,
|
graph_weight, header_version, HeaderInfo, BASE_EDGE_BITS, BLOCK_TIME_SEC,
|
||||||
BLOCK_OUTPUT_WEIGHT, BLOCK_TIME_SEC, C32_GRAPH_WEIGHT, COINBASE_MATURITY, CUT_THROUGH_HORIZON,
|
C32_GRAPH_WEIGHT, COINBASE_MATURITY, CUT_THROUGH_HORIZON, DAY_HEIGHT, DEFAULT_MIN_EDGE_BITS,
|
||||||
DAY_HEIGHT, DEFAULT_MIN_EDGE_BITS, DMA_WINDOW, INITIAL_DIFFICULTY, MAX_BLOCK_WEIGHT, PROOFSIZE,
|
DMA_WINDOW, GRIN_BASE, INITIAL_DIFFICULTY, KERNEL_WEIGHT, MAX_BLOCK_WEIGHT,
|
||||||
SECOND_POW_EDGE_BITS, STATE_SYNC_THRESHOLD,
|
OUTPUT_WEIGHT, PROOFSIZE, SECOND_POW_EDGE_BITS, STATE_SYNC_THRESHOLD,
|
||||||
};
|
};
|
||||||
use crate::core::block::HeaderVersion;
|
use crate::core::block::HeaderVersion;
|
||||||
use crate::pow::{
|
use crate::pow::{
|
||||||
|
@ -78,6 +78,9 @@ pub const TESTING_INITIAL_DIFFICULTY: u64 = 1;
|
||||||
/// Testing max_block_weight (artifically low, just enough to support a few txs).
|
/// Testing max_block_weight (artifically low, just enough to support a few txs).
|
||||||
pub const TESTING_MAX_BLOCK_WEIGHT: u64 = 250;
|
pub const TESTING_MAX_BLOCK_WEIGHT: u64 = 250;
|
||||||
|
|
||||||
|
/// Default unit of fee per tx weight, making each output cost about a Grincent
|
||||||
|
pub const DEFAULT_ACCEPT_FEE_BASE: u64 = GRIN_BASE / 100 / 20; // 500_000
|
||||||
|
|
||||||
/// default Future Time Limit (FTL) of 5 minutes
|
/// default Future Time Limit (FTL) of 5 minutes
|
||||||
pub const DEFAULT_FUTURE_TIME_LIMIT: u64 = 5 * 60;
|
pub const DEFAULT_FUTURE_TIME_LIMIT: u64 = 5 * 60;
|
||||||
|
|
||||||
|
@ -142,6 +145,11 @@ lazy_static! {
|
||||||
/// to be overridden on a per-thread basis (for testing).
|
/// to be overridden on a per-thread basis (for testing).
|
||||||
pub static ref GLOBAL_CHAIN_TYPE: OneTime<ChainTypes> = OneTime::new();
|
pub static ref GLOBAL_CHAIN_TYPE: OneTime<ChainTypes> = OneTime::new();
|
||||||
|
|
||||||
|
/// Global acccept fee base that must be initialized once on node startup.
|
||||||
|
/// This is accessed via get_acccept_fee_base() which allows the global value
|
||||||
|
/// to be overridden on a per-thread basis (for testing).
|
||||||
|
pub static ref GLOBAL_ACCEPT_FEE_BASE: OneTime<u64> = OneTime::new();
|
||||||
|
|
||||||
/// Global future time limit that must be initialized once on node startup.
|
/// Global future time limit that must be initialized once on node startup.
|
||||||
/// This is accessed via get_future_time_limit() which allows the global value
|
/// This is accessed via get_future_time_limit() which allows the global value
|
||||||
/// to be overridden on a per-thread basis (for testing).
|
/// to be overridden on a per-thread basis (for testing).
|
||||||
|
@ -157,6 +165,9 @@ thread_local! {
|
||||||
/// Mainnet|Testnet|UserTesting|AutomatedTesting
|
/// Mainnet|Testnet|UserTesting|AutomatedTesting
|
||||||
pub static CHAIN_TYPE: Cell<Option<ChainTypes>> = Cell::new(None);
|
pub static CHAIN_TYPE: Cell<Option<ChainTypes>> = Cell::new(None);
|
||||||
|
|
||||||
|
/// minimum transaction fee per unit of transaction weight for mempool acceptance
|
||||||
|
pub static ACCEPT_FEE_BASE: Cell<Option<u64>> = Cell::new(None);
|
||||||
|
|
||||||
/// maximum number of seconds into future for timestamp of block to be acceptable
|
/// maximum number of seconds into future for timestamp of block to be acceptable
|
||||||
pub static FUTURE_TIME_LIMIT: Cell<Option<u64>> = Cell::new(None);
|
pub static FUTURE_TIME_LIMIT: Cell<Option<u64>> = Cell::new(None);
|
||||||
|
|
||||||
|
@ -196,6 +207,35 @@ pub fn init_global_future_time_limit(new_ftl: u64) {
|
||||||
GLOBAL_FUTURE_TIME_LIMIT.init(new_ftl)
|
GLOBAL_FUTURE_TIME_LIMIT.init(new_ftl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// One time initialization of the global accept fee base
|
||||||
|
/// Will panic if we attempt to re-initialize this (via OneTime).
|
||||||
|
pub fn init_global_accept_fee_base(new_base: u64) {
|
||||||
|
GLOBAL_ACCEPT_FEE_BASE.init(new_base)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the accept fee base on a per-thread basis via thread_local storage.
|
||||||
|
pub fn set_local_accept_fee_base(new_base: u64) {
|
||||||
|
ACCEPT_FEE_BASE.with(|base| base.set(Some(new_base)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Accept Fee Base
|
||||||
|
/// Look at thread local config first. If not set fallback to global config.
|
||||||
|
/// Default to grin-cent/20 if global config unset.
|
||||||
|
pub fn get_accept_fee_base() -> u64 {
|
||||||
|
ACCEPT_FEE_BASE.with(|base| match base.get() {
|
||||||
|
None => {
|
||||||
|
let base = if GLOBAL_ACCEPT_FEE_BASE.is_init() {
|
||||||
|
GLOBAL_ACCEPT_FEE_BASE.borrow()
|
||||||
|
} else {
|
||||||
|
DEFAULT_ACCEPT_FEE_BASE
|
||||||
|
};
|
||||||
|
set_local_accept_fee_base(base);
|
||||||
|
base
|
||||||
|
}
|
||||||
|
Some(base) => base,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Set the future time limit on a per-thread basis via thread_local storage.
|
/// Set the future time limit on a per-thread basis via thread_local storage.
|
||||||
pub fn set_local_future_time_limit(new_ftl: u64) {
|
pub fn set_local_future_time_limit(new_ftl: u64) {
|
||||||
FUTURE_TIME_LIMIT.with(|ftl| ftl.set(Some(new_ftl)))
|
FUTURE_TIME_LIMIT.with(|ftl| ftl.set(Some(new_ftl)))
|
||||||
|
@ -357,7 +397,7 @@ pub fn max_block_weight() -> u64 {
|
||||||
|
|
||||||
/// Maximum allowed transaction weight (1 weight unit ~= 32 bytes)
|
/// Maximum allowed transaction weight (1 weight unit ~= 32 bytes)
|
||||||
pub fn max_tx_weight() -> u64 {
|
pub fn max_tx_weight() -> u64 {
|
||||||
let coinbase_weight = BLOCK_OUTPUT_WEIGHT + BLOCK_KERNEL_WEIGHT;
|
let coinbase_weight = OUTPUT_WEIGHT + KERNEL_WEIGHT;
|
||||||
max_block_weight().saturating_sub(coinbase_weight) as u64
|
max_block_weight().saturating_sub(coinbase_weight) as u64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -225,6 +225,7 @@ pub fn verify_partial_sig(
|
||||||
/// use core::core::transaction::KernelFeatures;
|
/// use core::core::transaction::KernelFeatures;
|
||||||
/// use core::core::{Output, OutputFeatures};
|
/// use core::core::{Output, OutputFeatures};
|
||||||
/// use keychain::{Keychain, ExtKeychain, SwitchCommitmentType};
|
/// use keychain::{Keychain, ExtKeychain, SwitchCommitmentType};
|
||||||
|
/// use std::convert::TryInto;
|
||||||
///
|
///
|
||||||
/// let secp = Secp256k1::with_caps(ContextFlag::Commit);
|
/// let secp = Secp256k1::with_caps(ContextFlag::Commit);
|
||||||
/// let keychain = ExtKeychain::from_random_seed(false).unwrap();
|
/// let keychain = ExtKeychain::from_random_seed(false).unwrap();
|
||||||
|
@ -239,7 +240,7 @@ pub fn verify_partial_sig(
|
||||||
/// let height = 20;
|
/// let height = 20;
|
||||||
/// let over_commit = secp.commit_value(reward(fees)).unwrap();
|
/// let over_commit = secp.commit_value(reward(fees)).unwrap();
|
||||||
/// let out_commit = output.commitment();
|
/// let out_commit = output.commitment();
|
||||||
/// let features = KernelFeatures::HeightLocked{fee: 0, lock_height: height};
|
/// let features = KernelFeatures::HeightLocked{fee: 1.into(), lock_height: height};
|
||||||
/// let msg = features.kernel_sig_msg().unwrap();
|
/// let msg = features.kernel_sig_msg().unwrap();
|
||||||
/// let excess = secp.commit_sum(vec![out_commit], vec![over_commit]).unwrap();
|
/// let excess = secp.commit_sum(vec![out_commit], vec![over_commit]).unwrap();
|
||||||
/// let pubkey = excess.to_pubkey(&secp).unwrap();
|
/// let pubkey = excess.to_pubkey(&secp).unwrap();
|
||||||
|
@ -287,6 +288,7 @@ where
|
||||||
/// use core::core::transaction::KernelFeatures;
|
/// use core::core::transaction::KernelFeatures;
|
||||||
/// use core::core::{Output, OutputFeatures};
|
/// use core::core::{Output, OutputFeatures};
|
||||||
/// use keychain::{Keychain, ExtKeychain, SwitchCommitmentType};
|
/// use keychain::{Keychain, ExtKeychain, SwitchCommitmentType};
|
||||||
|
/// use std::convert::TryInto;
|
||||||
///
|
///
|
||||||
/// // Create signature
|
/// // Create signature
|
||||||
/// let secp = Secp256k1::with_caps(ContextFlag::Commit);
|
/// let secp = Secp256k1::with_caps(ContextFlag::Commit);
|
||||||
|
@ -302,7 +304,7 @@ where
|
||||||
/// let height = 20;
|
/// let height = 20;
|
||||||
/// let over_commit = secp.commit_value(reward(fees)).unwrap();
|
/// let over_commit = secp.commit_value(reward(fees)).unwrap();
|
||||||
/// let out_commit = output.commitment();
|
/// let out_commit = output.commitment();
|
||||||
/// let features = KernelFeatures::HeightLocked{fee: 0, lock_height: height};
|
/// let features = KernelFeatures::HeightLocked{fee: 1.into(), lock_height: height};
|
||||||
/// let msg = features.kernel_sig_msg().unwrap();
|
/// let msg = features.kernel_sig_msg().unwrap();
|
||||||
/// let excess = secp.commit_sum(vec![out_commit], vec![over_commit]).unwrap();
|
/// let excess = secp.commit_sum(vec![out_commit], vec![over_commit]).unwrap();
|
||||||
/// let pubkey = excess.to_pubkey(&secp).unwrap();
|
/// let pubkey = excess.to_pubkey(&secp).unwrap();
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
//!
|
//!
|
||||||
//! Example:
|
//! Example:
|
||||||
//! build::transaction(
|
//! build::transaction(
|
||||||
//! KernelFeatures::Plain{ fee: 2 },
|
//! KernelFeatures::Plain{ fee: 2.try_into().unwrap() },
|
||||||
//! vec![
|
//! vec![
|
||||||
//! input_rand(75),
|
//! input_rand(75),
|
||||||
//! output_rand(42),
|
//! output_rand(42),
|
||||||
|
@ -279,14 +279,16 @@ mod test {
|
||||||
let vc = verifier_cache();
|
let vc = verifier_cache();
|
||||||
|
|
||||||
let tx = transaction(
|
let tx = transaction(
|
||||||
KernelFeatures::Plain { fee: 2 },
|
KernelFeatures::Plain { fee: 2.into() },
|
||||||
&[input(10, key_id1), input(12, key_id2), output(20, key_id3)],
|
&[input(10, key_id1), input(12, key_id2), output(20, key_id3)],
|
||||||
&keychain,
|
&keychain,
|
||||||
&builder,
|
&builder,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
tx.validate(Weighting::AsTransaction, vc.clone()).unwrap();
|
let height = 42; // arbitrary
|
||||||
|
tx.validate(Weighting::AsTransaction, vc.clone(), height)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -301,14 +303,16 @@ mod test {
|
||||||
let vc = verifier_cache();
|
let vc = verifier_cache();
|
||||||
|
|
||||||
let tx = transaction(
|
let tx = transaction(
|
||||||
KernelFeatures::Plain { fee: 2 },
|
KernelFeatures::Plain { fee: 2.into() },
|
||||||
&[input(10, key_id1), input(12, key_id2), output(20, key_id3)],
|
&[input(10, key_id1), input(12, key_id2), output(20, key_id3)],
|
||||||
&keychain,
|
&keychain,
|
||||||
&builder,
|
&builder,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
tx.validate(Weighting::AsTransaction, vc.clone()).unwrap();
|
let height = 42; // arbitrary
|
||||||
|
tx.validate(Weighting::AsTransaction, vc.clone(), height)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -322,13 +326,15 @@ mod test {
|
||||||
let vc = verifier_cache();
|
let vc = verifier_cache();
|
||||||
|
|
||||||
let tx = transaction(
|
let tx = transaction(
|
||||||
KernelFeatures::Plain { fee: 4 },
|
KernelFeatures::Plain { fee: 4.into() },
|
||||||
&[input(6, key_id1), output(2, key_id2)],
|
&[input(6, key_id1), output(2, key_id2)],
|
||||||
&keychain,
|
&keychain,
|
||||||
&builder,
|
&builder,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
tx.validate(Weighting::AsTransaction, vc.clone()).unwrap();
|
let height = 42; // arbitrary
|
||||||
|
tx.validate(Weighting::AsTransaction, vc.clone(), height)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,25 +28,19 @@ pub mod proof;
|
||||||
pub mod reward;
|
pub mod reward;
|
||||||
pub mod secp_ser;
|
pub mod secp_ser;
|
||||||
|
|
||||||
use crate::consensus;
|
|
||||||
use crate::core::Transaction;
|
use crate::core::Transaction;
|
||||||
|
use crate::global::get_accept_fee_base;
|
||||||
|
|
||||||
pub use self::proof::ProofBuilder;
|
pub use self::proof::ProofBuilder;
|
||||||
pub use crate::libtx::error::{Error, ErrorKind};
|
pub use crate::libtx::error::{Error, ErrorKind};
|
||||||
|
|
||||||
const DEFAULT_BASE_FEE: u64 = consensus::MILLI_GRIN;
|
/// Transaction fee calculation given numbers of inputs, outputs, and kernels
|
||||||
|
pub fn tx_fee(input_len: usize, output_len: usize, kernel_len: usize) -> u64 {
|
||||||
/// Transaction fee calculation
|
Transaction::weight_by_iok(input_len as u64, output_len as u64, kernel_len as u64)
|
||||||
pub fn tx_fee(
|
* get_accept_fee_base()
|
||||||
input_len: usize,
|
}
|
||||||
output_len: usize,
|
|
||||||
kernel_len: usize,
|
/// Transaction fee calculation given transaction
|
||||||
base_fee: Option<u64>,
|
pub fn accept_fee(tx: Transaction, height: u64) -> u64 {
|
||||||
) -> u64 {
|
tx.accept_fee(height)
|
||||||
let use_base_fee = match base_fee {
|
|
||||||
Some(bf) => bf,
|
|
||||||
None => DEFAULT_BASE_FEE,
|
|
||||||
};
|
|
||||||
|
|
||||||
Transaction::weight(input_len as u64, output_len as u64, kernel_len as u64) * use_base_fee
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,12 +14,13 @@
|
||||||
|
|
||||||
mod common;
|
mod common;
|
||||||
use crate::common::{new_block, tx1i2o, tx2i1o, txspend1i1o};
|
use crate::common::{new_block, tx1i2o, tx2i1o, txspend1i1o};
|
||||||
use crate::core::consensus::{self, BLOCK_OUTPUT_WEIGHT, TESTING_HARD_FORK_INTERVAL};
|
use crate::core::consensus::{self, OUTPUT_WEIGHT, TESTING_HARD_FORK_INTERVAL};
|
||||||
use crate::core::core::block::{Block, BlockHeader, Error, HeaderVersion, UntrustedBlockHeader};
|
use crate::core::core::block::{Block, BlockHeader, Error, HeaderVersion, UntrustedBlockHeader};
|
||||||
use crate::core::core::hash::Hashed;
|
use crate::core::core::hash::Hashed;
|
||||||
use crate::core::core::id::ShortIdentifiable;
|
use crate::core::core::id::ShortIdentifiable;
|
||||||
use crate::core::core::transaction::{
|
use crate::core::core::transaction::{
|
||||||
self, KernelFeatures, NRDRelativeHeight, Output, OutputFeatures, OutputIdentifier, Transaction,
|
self, FeeFields, KernelFeatures, NRDRelativeHeight, Output, OutputFeatures, OutputIdentifier,
|
||||||
|
Transaction,
|
||||||
};
|
};
|
||||||
use crate::core::core::verifier_cache::{LruVerifierCache, VerifierCache};
|
use crate::core::core::verifier_cache::{LruVerifierCache, VerifierCache};
|
||||||
use crate::core::core::{Committed, CompactBlock};
|
use crate::core::core::{Committed, CompactBlock};
|
||||||
|
@ -47,7 +48,7 @@ fn too_large_block() {
|
||||||
test_setup();
|
test_setup();
|
||||||
let keychain = ExtKeychain::from_random_seed(false).unwrap();
|
let keychain = ExtKeychain::from_random_seed(false).unwrap();
|
||||||
let builder = ProofBuilder::new(&keychain);
|
let builder = ProofBuilder::new(&keychain);
|
||||||
let max_out = global::max_block_weight() / BLOCK_OUTPUT_WEIGHT;
|
let max_out = global::max_block_weight() / OUTPUT_WEIGHT;
|
||||||
|
|
||||||
let mut pks = vec![];
|
let mut pks = vec![];
|
||||||
for n in 0..(max_out + 1) {
|
for n in 0..(max_out + 1) {
|
||||||
|
@ -61,7 +62,7 @@ fn too_large_block() {
|
||||||
|
|
||||||
parts.append(&mut vec![input(500000, pks.pop().unwrap())]);
|
parts.append(&mut vec![input(500000, pks.pop().unwrap())]);
|
||||||
let tx = build::transaction(
|
let tx = build::transaction(
|
||||||
KernelFeatures::Plain { fee: 2 },
|
KernelFeatures::Plain { fee: 2.into() },
|
||||||
&parts,
|
&parts,
|
||||||
&keychain,
|
&keychain,
|
||||||
&builder,
|
&builder,
|
||||||
|
@ -102,7 +103,7 @@ fn block_with_nrd_kernel_pre_post_hf3() {
|
||||||
|
|
||||||
let tx = build::transaction(
|
let tx = build::transaction(
|
||||||
KernelFeatures::NoRecentDuplicate {
|
KernelFeatures::NoRecentDuplicate {
|
||||||
fee: 2,
|
fee: 2.into(),
|
||||||
relative_height: NRDRelativeHeight::new(1440).unwrap(),
|
relative_height: NRDRelativeHeight::new(1440).unwrap(),
|
||||||
},
|
},
|
||||||
&[input(7, key_id1), output(5, key_id2)],
|
&[input(7, key_id1), output(5, key_id2)],
|
||||||
|
@ -187,7 +188,7 @@ fn block_with_nrd_kernel_nrd_not_enabled() {
|
||||||
|
|
||||||
let tx = build::transaction(
|
let tx = build::transaction(
|
||||||
KernelFeatures::NoRecentDuplicate {
|
KernelFeatures::NoRecentDuplicate {
|
||||||
fee: 2,
|
fee: 2.into(),
|
||||||
relative_height: NRDRelativeHeight::new(1440).unwrap(),
|
relative_height: NRDRelativeHeight::new(1440).unwrap(),
|
||||||
},
|
},
|
||||||
&[input(7, key_id1), output(5, key_id2)],
|
&[input(7, key_id1), output(5, key_id2)],
|
||||||
|
@ -275,7 +276,7 @@ fn block_with_cut_through() {
|
||||||
|
|
||||||
let btx1 = tx2i1o();
|
let btx1 = tx2i1o();
|
||||||
let btx2 = build::transaction(
|
let btx2 = build::transaction(
|
||||||
KernelFeatures::Plain { fee: 2 },
|
KernelFeatures::Plain { fee: 2.into() },
|
||||||
&[input(7, key_id1), output(5, key_id2.clone())],
|
&[input(7, key_id1), output(5, key_id2.clone())],
|
||||||
&keychain,
|
&keychain,
|
||||||
&builder,
|
&builder,
|
||||||
|
@ -373,7 +374,9 @@ fn remove_coinbase_kernel_flag() {
|
||||||
let mut b = new_block(&[], &keychain, &builder, &prev, &key_id);
|
let mut b = new_block(&[], &keychain, &builder, &prev, &key_id);
|
||||||
|
|
||||||
let mut kernel = b.kernels()[0].clone();
|
let mut kernel = b.kernels()[0].clone();
|
||||||
kernel.features = KernelFeatures::Plain { fee: 0 };
|
kernel.features = KernelFeatures::Plain {
|
||||||
|
fee: FeeFields::zero(),
|
||||||
|
};
|
||||||
b.body = b.body.replace_kernel(kernel);
|
b.body = b.body.replace_kernel(kernel);
|
||||||
|
|
||||||
// Flipping the coinbase flag results in kernels not summing correctly.
|
// Flipping the coinbase flag results in kernels not summing correctly.
|
||||||
|
@ -751,7 +754,7 @@ fn same_amount_outputs_copy_range_proof() {
|
||||||
let key_id3 = keychain::ExtKeychain::derive_key_id(1, 3, 0, 0, 0);
|
let key_id3 = keychain::ExtKeychain::derive_key_id(1, 3, 0, 0, 0);
|
||||||
|
|
||||||
let tx = build::transaction(
|
let tx = build::transaction(
|
||||||
KernelFeatures::Plain { fee: 1 },
|
KernelFeatures::Plain { fee: 1.into() },
|
||||||
&[input(7, key_id1), output(3, key_id2), output(3, key_id3)],
|
&[input(7, key_id1), output(3, key_id2), output(3, key_id3)],
|
||||||
&keychain,
|
&keychain,
|
||||||
&builder,
|
&builder,
|
||||||
|
@ -792,7 +795,7 @@ fn wrong_amount_range_proof() {
|
||||||
let key_id3 = keychain::ExtKeychain::derive_key_id(1, 3, 0, 0, 0);
|
let key_id3 = keychain::ExtKeychain::derive_key_id(1, 3, 0, 0, 0);
|
||||||
|
|
||||||
let tx1 = build::transaction(
|
let tx1 = build::transaction(
|
||||||
KernelFeatures::Plain { fee: 1 },
|
KernelFeatures::Plain { fee: 1.into() },
|
||||||
&[
|
&[
|
||||||
input(7, key_id1.clone()),
|
input(7, key_id1.clone()),
|
||||||
output(3, key_id2.clone()),
|
output(3, key_id2.clone()),
|
||||||
|
@ -803,7 +806,7 @@ fn wrong_amount_range_proof() {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let tx2 = build::transaction(
|
let tx2 = build::transaction(
|
||||||
KernelFeatures::Plain { fee: 1 },
|
KernelFeatures::Plain { fee: 1.into() },
|
||||||
&[input(7, key_id1), output(2, key_id2), output(4, key_id3)],
|
&[input(7, key_id1), output(2, key_id2), output(4, key_id3)],
|
||||||
&keychain,
|
&keychain,
|
||||||
&builder,
|
&builder,
|
||||||
|
@ -883,7 +886,9 @@ fn test_verify_cut_through_plain() -> Result<(), Error> {
|
||||||
let builder = ProofBuilder::new(&keychain);
|
let builder = ProofBuilder::new(&keychain);
|
||||||
|
|
||||||
let tx = build::transaction(
|
let tx = build::transaction(
|
||||||
KernelFeatures::Plain { fee: 0 },
|
KernelFeatures::Plain {
|
||||||
|
fee: FeeFields::zero(),
|
||||||
|
},
|
||||||
&[
|
&[
|
||||||
build::input(10, key_id1.clone()),
|
build::input(10, key_id1.clone()),
|
||||||
build::input(10, key_id2.clone()),
|
build::input(10, key_id2.clone()),
|
||||||
|
@ -947,7 +952,9 @@ fn test_verify_cut_through_coinbase() -> Result<(), Error> {
|
||||||
let builder = ProofBuilder::new(&keychain);
|
let builder = ProofBuilder::new(&keychain);
|
||||||
|
|
||||||
let tx = build::transaction(
|
let tx = build::transaction(
|
||||||
KernelFeatures::Plain { fee: 0 },
|
KernelFeatures::Plain {
|
||||||
|
fee: FeeFields::zero(),
|
||||||
|
},
|
||||||
&[
|
&[
|
||||||
build::coinbase_input(consensus::REWARD, key_id1.clone()),
|
build::coinbase_input(consensus::REWARD, key_id1.clone()),
|
||||||
build::coinbase_input(consensus::REWARD, key_id2.clone()),
|
build::coinbase_input(consensus::REWARD, key_id2.clone()),
|
||||||
|
|
|
@ -37,7 +37,7 @@ pub fn tx2i1o() -> Transaction {
|
||||||
let key_id3 = keychain::ExtKeychain::derive_key_id(1, 3, 0, 0, 0);
|
let key_id3 = keychain::ExtKeychain::derive_key_id(1, 3, 0, 0, 0);
|
||||||
|
|
||||||
let tx = build::transaction(
|
let tx = build::transaction(
|
||||||
KernelFeatures::Plain { fee: 2 },
|
KernelFeatures::Plain { fee: 2.into() },
|
||||||
&[input(10, key_id1), input(11, key_id2), output(19, key_id3)],
|
&[input(10, key_id1), input(11, key_id2), output(19, key_id3)],
|
||||||
&keychain,
|
&keychain,
|
||||||
&builder,
|
&builder,
|
||||||
|
@ -56,7 +56,7 @@ pub fn tx1i1o() -> Transaction {
|
||||||
let key_id2 = keychain::ExtKeychain::derive_key_id(1, 2, 0, 0, 0);
|
let key_id2 = keychain::ExtKeychain::derive_key_id(1, 2, 0, 0, 0);
|
||||||
|
|
||||||
let tx = build::transaction(
|
let tx = build::transaction(
|
||||||
KernelFeatures::Plain { fee: 2 },
|
KernelFeatures::Plain { fee: 2.into() },
|
||||||
&[input(5, key_id1), output(3, key_id2)],
|
&[input(5, key_id1), output(3, key_id2)],
|
||||||
&keychain,
|
&keychain,
|
||||||
&builder,
|
&builder,
|
||||||
|
@ -96,7 +96,7 @@ pub fn tx1i2o() -> Transaction {
|
||||||
let key_id3 = keychain::ExtKeychain::derive_key_id(1, 3, 0, 0, 0);
|
let key_id3 = keychain::ExtKeychain::derive_key_id(1, 3, 0, 0, 0);
|
||||||
|
|
||||||
let tx = build::transaction(
|
let tx = build::transaction(
|
||||||
KernelFeatures::Plain { fee: 2 },
|
KernelFeatures::Plain { fee: 2.into() },
|
||||||
&[input(6, key_id1), output(3, key_id2), output(1, key_id3)],
|
&[input(6, key_id1), output(3, key_id2), output(1, key_id3)],
|
||||||
&keychain,
|
&keychain,
|
||||||
&builder,
|
&builder,
|
||||||
|
@ -120,7 +120,10 @@ where
|
||||||
K: Keychain,
|
K: Keychain,
|
||||||
B: ProofBuild,
|
B: ProofBuild,
|
||||||
{
|
{
|
||||||
let fees = txs.iter().map(|tx| tx.fee()).sum();
|
let fees = txs
|
||||||
|
.iter()
|
||||||
|
.map(|tx| tx.fee(previous_header.height + 1))
|
||||||
|
.sum();
|
||||||
let reward_output = reward::output(keychain, builder, &key_id, fees, false).unwrap();
|
let reward_output = reward::output(keychain, builder, &key_id, fees, false).unwrap();
|
||||||
Block::new(&previous_header, txs, Difficulty::min_dma(), reward_output).unwrap()
|
Block::new(&previous_header, txs, Difficulty::min_dma(), reward_output).unwrap()
|
||||||
}
|
}
|
||||||
|
@ -140,7 +143,7 @@ where
|
||||||
B: ProofBuild,
|
B: ProofBuild,
|
||||||
{
|
{
|
||||||
build::transaction(
|
build::transaction(
|
||||||
KernelFeatures::Plain { fee: 2 },
|
KernelFeatures::Plain { fee: 2.into() },
|
||||||
&[input(v, key_id1), output(3, key_id2)],
|
&[input(v, key_id1), output(3, key_id2)],
|
||||||
keychain,
|
keychain,
|
||||||
builder,
|
builder,
|
||||||
|
|
|
@ -21,8 +21,8 @@ use self::core::core::block::Error::KernelLockHeight;
|
||||||
use self::core::core::hash::{Hashed, ZERO_HASH};
|
use self::core::core::hash::{Hashed, ZERO_HASH};
|
||||||
use self::core::core::verifier_cache::{LruVerifierCache, VerifierCache};
|
use self::core::core::verifier_cache::{LruVerifierCache, VerifierCache};
|
||||||
use self::core::core::{
|
use self::core::core::{
|
||||||
aggregate, deaggregate, KernelFeatures, Output, OutputFeatures, OutputIdentifier, Transaction,
|
aggregate, deaggregate, FeeFields, KernelFeatures, Output, OutputFeatures, OutputIdentifier,
|
||||||
TxKernel, Weighting,
|
Transaction, TxKernel, Weighting,
|
||||||
};
|
};
|
||||||
use self::core::libtx::build::{self, initial_tx, input, output, with_excess};
|
use self::core::libtx::build::{self, initial_tx, input, output, with_excess};
|
||||||
use self::core::libtx::{aggsig, ProofBuilder};
|
use self::core::libtx::{aggsig, ProofBuilder};
|
||||||
|
@ -97,7 +97,8 @@ fn simple_tx_ser_deser() {
|
||||||
let mut vec = Vec::new();
|
let mut vec = Vec::new();
|
||||||
ser::serialize_default(&mut vec, &tx).expect("serialization failed");
|
ser::serialize_default(&mut vec, &tx).expect("serialization failed");
|
||||||
let dtx: Transaction = ser::deserialize_default(&mut &vec[..]).unwrap();
|
let dtx: Transaction = ser::deserialize_default(&mut &vec[..]).unwrap();
|
||||||
assert_eq!(dtx.fee(), 2);
|
let height = 42; // arbitrary
|
||||||
|
assert_eq!(dtx.fee(height), 2);
|
||||||
assert_eq!(dtx.inputs().len(), 2);
|
assert_eq!(dtx.inputs().len(), 2);
|
||||||
assert_eq!(dtx.outputs().len(), 1);
|
assert_eq!(dtx.outputs().len(), 1);
|
||||||
assert_eq!(tx.hash(), dtx.hash());
|
assert_eq!(tx.hash(), dtx.hash());
|
||||||
|
@ -130,7 +131,9 @@ fn test_zero_commit_fails() {
|
||||||
|
|
||||||
// blinding should fail as signing with a zero r*G shouldn't work
|
// blinding should fail as signing with a zero r*G shouldn't work
|
||||||
let res = build::transaction(
|
let res = build::transaction(
|
||||||
KernelFeatures::Plain { fee: 0 },
|
KernelFeatures::Plain {
|
||||||
|
fee: FeeFields::zero(),
|
||||||
|
},
|
||||||
&[input(10, key_id1.clone()), output(10, key_id1)],
|
&[input(10, key_id1.clone()), output(10, key_id1)],
|
||||||
&keychain,
|
&keychain,
|
||||||
&builder,
|
&builder,
|
||||||
|
@ -153,7 +156,7 @@ fn build_tx_kernel() {
|
||||||
|
|
||||||
// first build a valid tx with corresponding blinding factor
|
// first build a valid tx with corresponding blinding factor
|
||||||
let tx = build::transaction(
|
let tx = build::transaction(
|
||||||
KernelFeatures::Plain { fee: 2 },
|
KernelFeatures::Plain { fee: 2.into() },
|
||||||
&[input(10, key_id1), output(5, key_id2), output(3, key_id3)],
|
&[input(10, key_id1), output(5, key_id2), output(3, key_id3)],
|
||||||
&keychain,
|
&keychain,
|
||||||
&builder,
|
&builder,
|
||||||
|
@ -161,7 +164,8 @@ fn build_tx_kernel() {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// check the tx is valid
|
// check the tx is valid
|
||||||
tx.validate(Weighting::AsTransaction, verifier_cache())
|
let height = 42; // arbitrary
|
||||||
|
tx.validate(Weighting::AsTransaction, verifier_cache(), height)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// check the kernel is also itself valid
|
// check the kernel is also itself valid
|
||||||
|
@ -169,8 +173,8 @@ fn build_tx_kernel() {
|
||||||
let kern = &tx.kernels()[0];
|
let kern = &tx.kernels()[0];
|
||||||
kern.verify().unwrap();
|
kern.verify().unwrap();
|
||||||
|
|
||||||
assert_eq!(kern.features, KernelFeatures::Plain { fee: 2 });
|
assert_eq!(kern.features, KernelFeatures::Plain { fee: 2.into() });
|
||||||
assert_eq!(2, tx.fee());
|
assert_eq!(2, tx.fee(height));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Proof of concept demonstrating we can build two transactions that share
|
// Proof of concept demonstrating we can build two transactions that share
|
||||||
|
@ -192,7 +196,7 @@ fn build_two_half_kernels() {
|
||||||
let key_id3 = ExtKeychain::derive_key_id(1, 3, 0, 0, 0);
|
let key_id3 = ExtKeychain::derive_key_id(1, 3, 0, 0, 0);
|
||||||
|
|
||||||
// build kernel with associated private excess
|
// build kernel with associated private excess
|
||||||
let mut kernel = TxKernel::with_features(KernelFeatures::Plain { fee: 2 });
|
let mut kernel = TxKernel::with_features(KernelFeatures::Plain { fee: 2.into() });
|
||||||
|
|
||||||
// Construct the message to be signed.
|
// Construct the message to be signed.
|
||||||
let msg = kernel.msg_to_sign().unwrap();
|
let msg = kernel.msg_to_sign().unwrap();
|
||||||
|
@ -224,13 +228,14 @@ fn build_two_half_kernels() {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
let height = 42; // arbitrary
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
tx1.validate(Weighting::AsTransaction, verifier_cache()),
|
tx1.validate(Weighting::AsTransaction, verifier_cache(), height),
|
||||||
Ok(()),
|
Ok(()),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
tx2.validate(Weighting::AsTransaction, verifier_cache()),
|
tx2.validate(Weighting::AsTransaction, verifier_cache(), height),
|
||||||
Ok(()),
|
Ok(()),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -256,11 +261,12 @@ fn transaction_cut_through() {
|
||||||
let tx1 = tx1i2o();
|
let tx1 = tx1i2o();
|
||||||
let tx2 = tx2i1o();
|
let tx2 = tx2i1o();
|
||||||
|
|
||||||
|
let height = 42; // arbitrary
|
||||||
assert!(tx1
|
assert!(tx1
|
||||||
.validate(Weighting::AsTransaction, verifier_cache())
|
.validate(Weighting::AsTransaction, verifier_cache(), height)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
assert!(tx2
|
assert!(tx2
|
||||||
.validate(Weighting::AsTransaction, verifier_cache())
|
.validate(Weighting::AsTransaction, verifier_cache(), height)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
|
|
||||||
let vc = verifier_cache();
|
let vc = verifier_cache();
|
||||||
|
@ -268,7 +274,9 @@ fn transaction_cut_through() {
|
||||||
// now build a "cut_through" tx from tx1 and tx2
|
// now build a "cut_through" tx from tx1 and tx2
|
||||||
let tx3 = aggregate(&[tx1, tx2]).unwrap();
|
let tx3 = aggregate(&[tx1, tx2]).unwrap();
|
||||||
|
|
||||||
assert!(tx3.validate(Weighting::AsTransaction, vc.clone()).is_ok());
|
assert!(tx3
|
||||||
|
.validate(Weighting::AsTransaction, vc.clone(), height)
|
||||||
|
.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt to deaggregate a multi-kernel transaction in a different way
|
// Attempt to deaggregate a multi-kernel transaction in a different way
|
||||||
|
@ -282,31 +290,44 @@ fn multi_kernel_transaction_deaggregation() {
|
||||||
|
|
||||||
let vc = verifier_cache();
|
let vc = verifier_cache();
|
||||||
|
|
||||||
assert!(tx1.validate(Weighting::AsTransaction, vc.clone()).is_ok());
|
let height = 42; // arbitrary
|
||||||
assert!(tx2.validate(Weighting::AsTransaction, vc.clone()).is_ok());
|
assert!(tx1
|
||||||
assert!(tx3.validate(Weighting::AsTransaction, vc.clone()).is_ok());
|
.validate(Weighting::AsTransaction, vc.clone(), height)
|
||||||
assert!(tx4.validate(Weighting::AsTransaction, vc.clone()).is_ok());
|
.is_ok());
|
||||||
|
assert!(tx2
|
||||||
|
.validate(Weighting::AsTransaction, vc.clone(), height)
|
||||||
|
.is_ok());
|
||||||
|
assert!(tx3
|
||||||
|
.validate(Weighting::AsTransaction, vc.clone(), height)
|
||||||
|
.is_ok());
|
||||||
|
assert!(tx4
|
||||||
|
.validate(Weighting::AsTransaction, vc.clone(), height)
|
||||||
|
.is_ok());
|
||||||
|
|
||||||
let tx1234 = aggregate(&[tx1.clone(), tx2.clone(), tx3.clone(), tx4.clone()]).unwrap();
|
let tx1234 = aggregate(&[tx1.clone(), tx2.clone(), tx3.clone(), tx4.clone()]).unwrap();
|
||||||
let tx12 = aggregate(&[tx1, tx2]).unwrap();
|
let tx12 = aggregate(&[tx1, tx2]).unwrap();
|
||||||
let tx34 = aggregate(&[tx3, tx4]).unwrap();
|
let tx34 = aggregate(&[tx3, tx4]).unwrap();
|
||||||
|
|
||||||
assert!(tx1234
|
assert!(tx1234
|
||||||
.validate(Weighting::AsTransaction, vc.clone())
|
.validate(Weighting::AsTransaction, vc.clone(), height)
|
||||||
|
.is_ok());
|
||||||
|
assert!(tx12
|
||||||
|
.validate(Weighting::AsTransaction, vc.clone(), height)
|
||||||
|
.is_ok());
|
||||||
|
assert!(tx34
|
||||||
|
.validate(Weighting::AsTransaction, vc.clone(), height)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
assert!(tx12.validate(Weighting::AsTransaction, vc.clone()).is_ok());
|
|
||||||
assert!(tx34.validate(Weighting::AsTransaction, vc.clone()).is_ok());
|
|
||||||
|
|
||||||
let deaggregated_tx34 = deaggregate(tx1234.clone(), &[tx12.clone()]).unwrap();
|
let deaggregated_tx34 = deaggregate(tx1234.clone(), &[tx12.clone()]).unwrap();
|
||||||
assert!(deaggregated_tx34
|
assert!(deaggregated_tx34
|
||||||
.validate(Weighting::AsTransaction, vc.clone())
|
.validate(Weighting::AsTransaction, vc.clone(), height)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
assert_eq!(tx34, deaggregated_tx34);
|
assert_eq!(tx34, deaggregated_tx34);
|
||||||
|
|
||||||
let deaggregated_tx12 = deaggregate(tx1234, &[tx34]).unwrap();
|
let deaggregated_tx12 = deaggregate(tx1234, &[tx34]).unwrap();
|
||||||
|
|
||||||
assert!(deaggregated_tx12
|
assert!(deaggregated_tx12
|
||||||
.validate(Weighting::AsTransaction, vc.clone())
|
.validate(Weighting::AsTransaction, vc.clone(), height)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
assert_eq!(tx12, deaggregated_tx12);
|
assert_eq!(tx12, deaggregated_tx12);
|
||||||
}
|
}
|
||||||
|
@ -320,19 +341,30 @@ fn multi_kernel_transaction_deaggregation_2() {
|
||||||
|
|
||||||
let vc = verifier_cache();
|
let vc = verifier_cache();
|
||||||
|
|
||||||
assert!(tx1.validate(Weighting::AsTransaction, vc.clone()).is_ok());
|
let height = 42; // arbitrary
|
||||||
assert!(tx2.validate(Weighting::AsTransaction, vc.clone()).is_ok());
|
assert!(tx1
|
||||||
assert!(tx3.validate(Weighting::AsTransaction, vc.clone()).is_ok());
|
.validate(Weighting::AsTransaction, vc.clone(), height)
|
||||||
|
.is_ok());
|
||||||
|
assert!(tx2
|
||||||
|
.validate(Weighting::AsTransaction, vc.clone(), height)
|
||||||
|
.is_ok());
|
||||||
|
assert!(tx3
|
||||||
|
.validate(Weighting::AsTransaction, vc.clone(), height)
|
||||||
|
.is_ok());
|
||||||
|
|
||||||
let tx123 = aggregate(&[tx1.clone(), tx2.clone(), tx3.clone()]).unwrap();
|
let tx123 = aggregate(&[tx1.clone(), tx2.clone(), tx3.clone()]).unwrap();
|
||||||
let tx12 = aggregate(&[tx1, tx2]).unwrap();
|
let tx12 = aggregate(&[tx1, tx2]).unwrap();
|
||||||
|
|
||||||
assert!(tx123.validate(Weighting::AsTransaction, vc.clone()).is_ok());
|
assert!(tx123
|
||||||
assert!(tx12.validate(Weighting::AsTransaction, vc.clone()).is_ok());
|
.validate(Weighting::AsTransaction, vc.clone(), height)
|
||||||
|
.is_ok());
|
||||||
|
assert!(tx12
|
||||||
|
.validate(Weighting::AsTransaction, vc.clone(), height)
|
||||||
|
.is_ok());
|
||||||
|
|
||||||
let deaggregated_tx3 = deaggregate(tx123, &[tx12]).unwrap();
|
let deaggregated_tx3 = deaggregate(tx123, &[tx12]).unwrap();
|
||||||
assert!(deaggregated_tx3
|
assert!(deaggregated_tx3
|
||||||
.validate(Weighting::AsTransaction, vc.clone())
|
.validate(Weighting::AsTransaction, vc.clone(), height)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
assert_eq!(tx3, deaggregated_tx3);
|
assert_eq!(tx3, deaggregated_tx3);
|
||||||
}
|
}
|
||||||
|
@ -346,20 +378,31 @@ fn multi_kernel_transaction_deaggregation_3() {
|
||||||
|
|
||||||
let vc = verifier_cache();
|
let vc = verifier_cache();
|
||||||
|
|
||||||
assert!(tx1.validate(Weighting::AsTransaction, vc.clone()).is_ok());
|
let height = 42; // arbitrary
|
||||||
assert!(tx2.validate(Weighting::AsTransaction, vc.clone()).is_ok());
|
assert!(tx1
|
||||||
assert!(tx3.validate(Weighting::AsTransaction, vc.clone()).is_ok());
|
.validate(Weighting::AsTransaction, vc.clone(), height)
|
||||||
|
.is_ok());
|
||||||
|
assert!(tx2
|
||||||
|
.validate(Weighting::AsTransaction, vc.clone(), height)
|
||||||
|
.is_ok());
|
||||||
|
assert!(tx3
|
||||||
|
.validate(Weighting::AsTransaction, vc.clone(), height)
|
||||||
|
.is_ok());
|
||||||
|
|
||||||
let tx123 = aggregate(&[tx1.clone(), tx2.clone(), tx3.clone()]).unwrap();
|
let tx123 = aggregate(&[tx1.clone(), tx2.clone(), tx3.clone()]).unwrap();
|
||||||
let tx13 = aggregate(&[tx1, tx3]).unwrap();
|
let tx13 = aggregate(&[tx1, tx3]).unwrap();
|
||||||
let tx2 = aggregate(&[tx2]).unwrap();
|
let tx2 = aggregate(&[tx2]).unwrap();
|
||||||
|
|
||||||
assert!(tx123.validate(Weighting::AsTransaction, vc.clone()).is_ok());
|
assert!(tx123
|
||||||
assert!(tx2.validate(Weighting::AsTransaction, vc.clone()).is_ok());
|
.validate(Weighting::AsTransaction, vc.clone(), height)
|
||||||
|
.is_ok());
|
||||||
|
assert!(tx2
|
||||||
|
.validate(Weighting::AsTransaction, vc.clone(), height)
|
||||||
|
.is_ok());
|
||||||
|
|
||||||
let deaggregated_tx13 = deaggregate(tx123, &[tx2]).unwrap();
|
let deaggregated_tx13 = deaggregate(tx123, &[tx2]).unwrap();
|
||||||
assert!(deaggregated_tx13
|
assert!(deaggregated_tx13
|
||||||
.validate(Weighting::AsTransaction, vc.clone())
|
.validate(Weighting::AsTransaction, vc.clone(), height)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
assert_eq!(tx13, deaggregated_tx13);
|
assert_eq!(tx13, deaggregated_tx13);
|
||||||
}
|
}
|
||||||
|
@ -375,11 +418,22 @@ fn multi_kernel_transaction_deaggregation_4() {
|
||||||
|
|
||||||
let vc = verifier_cache();
|
let vc = verifier_cache();
|
||||||
|
|
||||||
assert!(tx1.validate(Weighting::AsTransaction, vc.clone()).is_ok());
|
let height = 42; // arbitrary
|
||||||
assert!(tx2.validate(Weighting::AsTransaction, vc.clone()).is_ok());
|
assert!(tx1
|
||||||
assert!(tx3.validate(Weighting::AsTransaction, vc.clone()).is_ok());
|
.validate(Weighting::AsTransaction, vc.clone(), height)
|
||||||
assert!(tx4.validate(Weighting::AsTransaction, vc.clone()).is_ok());
|
.is_ok());
|
||||||
assert!(tx5.validate(Weighting::AsTransaction, vc.clone()).is_ok());
|
assert!(tx2
|
||||||
|
.validate(Weighting::AsTransaction, vc.clone(), height)
|
||||||
|
.is_ok());
|
||||||
|
assert!(tx3
|
||||||
|
.validate(Weighting::AsTransaction, vc.clone(), height)
|
||||||
|
.is_ok());
|
||||||
|
assert!(tx4
|
||||||
|
.validate(Weighting::AsTransaction, vc.clone(), height)
|
||||||
|
.is_ok());
|
||||||
|
assert!(tx5
|
||||||
|
.validate(Weighting::AsTransaction, vc.clone(), height)
|
||||||
|
.is_ok());
|
||||||
|
|
||||||
let tx12345 = aggregate(&[
|
let tx12345 = aggregate(&[
|
||||||
tx1.clone(),
|
tx1.clone(),
|
||||||
|
@ -390,12 +444,12 @@ fn multi_kernel_transaction_deaggregation_4() {
|
||||||
])
|
])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(tx12345
|
assert!(tx12345
|
||||||
.validate(Weighting::AsTransaction, vc.clone())
|
.validate(Weighting::AsTransaction, vc.clone(), height)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
|
|
||||||
let deaggregated_tx5 = deaggregate(tx12345, &[tx1, tx2, tx3, tx4]).unwrap();
|
let deaggregated_tx5 = deaggregate(tx12345, &[tx1, tx2, tx3, tx4]).unwrap();
|
||||||
assert!(deaggregated_tx5
|
assert!(deaggregated_tx5
|
||||||
.validate(Weighting::AsTransaction, vc.clone())
|
.validate(Weighting::AsTransaction, vc.clone(), height)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
assert_eq!(tx5, deaggregated_tx5);
|
assert_eq!(tx5, deaggregated_tx5);
|
||||||
}
|
}
|
||||||
|
@ -411,11 +465,22 @@ fn multi_kernel_transaction_deaggregation_5() {
|
||||||
|
|
||||||
let vc = verifier_cache();
|
let vc = verifier_cache();
|
||||||
|
|
||||||
assert!(tx1.validate(Weighting::AsTransaction, vc.clone()).is_ok());
|
let height = 42; // arbitrary
|
||||||
assert!(tx2.validate(Weighting::AsTransaction, vc.clone()).is_ok());
|
assert!(tx1
|
||||||
assert!(tx3.validate(Weighting::AsTransaction, vc.clone()).is_ok());
|
.validate(Weighting::AsTransaction, vc.clone(), height)
|
||||||
assert!(tx4.validate(Weighting::AsTransaction, vc.clone()).is_ok());
|
.is_ok());
|
||||||
assert!(tx5.validate(Weighting::AsTransaction, vc.clone()).is_ok());
|
assert!(tx2
|
||||||
|
.validate(Weighting::AsTransaction, vc.clone(), height)
|
||||||
|
.is_ok());
|
||||||
|
assert!(tx3
|
||||||
|
.validate(Weighting::AsTransaction, vc.clone(), height)
|
||||||
|
.is_ok());
|
||||||
|
assert!(tx4
|
||||||
|
.validate(Weighting::AsTransaction, vc.clone(), height)
|
||||||
|
.is_ok());
|
||||||
|
assert!(tx5
|
||||||
|
.validate(Weighting::AsTransaction, vc.clone(), height)
|
||||||
|
.is_ok());
|
||||||
|
|
||||||
let tx12345 = aggregate(&[
|
let tx12345 = aggregate(&[
|
||||||
tx1.clone(),
|
tx1.clone(),
|
||||||
|
@ -429,12 +494,12 @@ fn multi_kernel_transaction_deaggregation_5() {
|
||||||
let tx34 = aggregate(&[tx3, tx4]).unwrap();
|
let tx34 = aggregate(&[tx3, tx4]).unwrap();
|
||||||
|
|
||||||
assert!(tx12345
|
assert!(tx12345
|
||||||
.validate(Weighting::AsTransaction, vc.clone())
|
.validate(Weighting::AsTransaction, vc.clone(), height)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
|
|
||||||
let deaggregated_tx5 = deaggregate(tx12345, &[tx12, tx34]).unwrap();
|
let deaggregated_tx5 = deaggregate(tx12345, &[tx12, tx34]).unwrap();
|
||||||
assert!(deaggregated_tx5
|
assert!(deaggregated_tx5
|
||||||
.validate(Weighting::AsTransaction, vc.clone())
|
.validate(Weighting::AsTransaction, vc.clone(), height)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
assert_eq!(tx5, deaggregated_tx5);
|
assert_eq!(tx5, deaggregated_tx5);
|
||||||
}
|
}
|
||||||
|
@ -448,25 +513,32 @@ fn basic_transaction_deaggregation() {
|
||||||
|
|
||||||
let vc = verifier_cache();
|
let vc = verifier_cache();
|
||||||
|
|
||||||
assert!(tx1.validate(Weighting::AsTransaction, vc.clone()).is_ok());
|
let height = 42; // arbitrary
|
||||||
assert!(tx2.validate(Weighting::AsTransaction, vc.clone()).is_ok());
|
assert!(tx1
|
||||||
|
.validate(Weighting::AsTransaction, vc.clone(), height)
|
||||||
|
.is_ok());
|
||||||
|
assert!(tx2
|
||||||
|
.validate(Weighting::AsTransaction, vc.clone(), height)
|
||||||
|
.is_ok());
|
||||||
|
|
||||||
// now build a "cut_through" tx from tx1 and tx2
|
// now build a "cut_through" tx from tx1 and tx2
|
||||||
let tx3 = aggregate(&[tx1.clone(), tx2.clone()]).unwrap();
|
let tx3 = aggregate(&[tx1.clone(), tx2.clone()]).unwrap();
|
||||||
|
|
||||||
assert!(tx3.validate(Weighting::AsTransaction, vc.clone()).is_ok());
|
assert!(tx3
|
||||||
|
.validate(Weighting::AsTransaction, vc.clone(), height)
|
||||||
|
.is_ok());
|
||||||
|
|
||||||
let deaggregated_tx1 = deaggregate(tx3.clone(), &[tx2.clone()]).unwrap();
|
let deaggregated_tx1 = deaggregate(tx3.clone(), &[tx2.clone()]).unwrap();
|
||||||
|
|
||||||
assert!(deaggregated_tx1
|
assert!(deaggregated_tx1
|
||||||
.validate(Weighting::AsTransaction, vc.clone())
|
.validate(Weighting::AsTransaction, vc.clone(), height)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
assert_eq!(tx1, deaggregated_tx1);
|
assert_eq!(tx1, deaggregated_tx1);
|
||||||
|
|
||||||
let deaggregated_tx2 = deaggregate(tx3, &[tx1]).unwrap();
|
let deaggregated_tx2 = deaggregate(tx3, &[tx1]).unwrap();
|
||||||
|
|
||||||
assert!(deaggregated_tx2
|
assert!(deaggregated_tx2
|
||||||
.validate(Weighting::AsTransaction, vc.clone())
|
.validate(Weighting::AsTransaction, vc.clone(), height)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
assert_eq!(tx2, deaggregated_tx2);
|
assert_eq!(tx2, deaggregated_tx2);
|
||||||
}
|
}
|
||||||
|
@ -480,7 +552,7 @@ fn hash_output() {
|
||||||
let key_id3 = ExtKeychain::derive_key_id(1, 3, 0, 0, 0);
|
let key_id3 = ExtKeychain::derive_key_id(1, 3, 0, 0, 0);
|
||||||
|
|
||||||
let tx = build::transaction(
|
let tx = build::transaction(
|
||||||
KernelFeatures::Plain { fee: 1 },
|
KernelFeatures::Plain { fee: 1.into() },
|
||||||
&[input(75, key_id1), output(42, key_id2), output(32, key_id3)],
|
&[input(75, key_id1), output(42, key_id2), output(32, key_id3)],
|
||||||
&keychain,
|
&keychain,
|
||||||
&builder,
|
&builder,
|
||||||
|
@ -496,8 +568,9 @@ fn hash_output() {
|
||||||
#[test]
|
#[test]
|
||||||
fn blind_tx() {
|
fn blind_tx() {
|
||||||
let btx = tx2i1o();
|
let btx = tx2i1o();
|
||||||
|
let height = 42; // arbitrary
|
||||||
assert!(btx
|
assert!(btx
|
||||||
.validate(Weighting::AsTransaction, verifier_cache())
|
.validate(Weighting::AsTransaction, verifier_cache(), height)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
|
|
||||||
// Ignored for bullet proofs, because calling range_proof_info
|
// Ignored for bullet proofs, because calling range_proof_info
|
||||||
|
@ -543,8 +616,9 @@ fn tx_build_exchange() {
|
||||||
|
|
||||||
// Alice builds her transaction, with change, which also produces the sum
|
// Alice builds her transaction, with change, which also produces the sum
|
||||||
// of blinding factors before they're obscured.
|
// of blinding factors before they're obscured.
|
||||||
let tx = Transaction::empty()
|
let tx = Transaction::empty().with_kernel(TxKernel::with_features(KernelFeatures::Plain {
|
||||||
.with_kernel(TxKernel::with_features(KernelFeatures::Plain { fee: 2 }));
|
fee: 2.into(),
|
||||||
|
}));
|
||||||
let (tx, sum) =
|
let (tx, sum) =
|
||||||
build::partial_transaction(tx, &[in1, in2, output(1, key_id3)], &keychain, &builder)
|
build::partial_transaction(tx, &[in1, in2, output(1, key_id3)], &keychain, &builder)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -556,7 +630,7 @@ fn tx_build_exchange() {
|
||||||
// blinding factors. He adds his output, finalizes the transaction so it's
|
// blinding factors. He adds his output, finalizes the transaction so it's
|
||||||
// ready for broadcast.
|
// ready for broadcast.
|
||||||
let tx_final = build::transaction(
|
let tx_final = build::transaction(
|
||||||
KernelFeatures::Plain { fee: 2 },
|
KernelFeatures::Plain { fee: 2.into() },
|
||||||
&[
|
&[
|
||||||
initial_tx(tx_alice),
|
initial_tx(tx_alice),
|
||||||
with_excess(blind_sum),
|
with_excess(blind_sum),
|
||||||
|
@ -567,8 +641,9 @@ fn tx_build_exchange() {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
let height = 42; // arbitrary
|
||||||
tx_final
|
tx_final
|
||||||
.validate(Weighting::AsTransaction, verifier_cache())
|
.validate(Weighting::AsTransaction, verifier_cache(), height)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -597,9 +672,13 @@ fn reward_with_tx_block() {
|
||||||
let vc = verifier_cache();
|
let vc = verifier_cache();
|
||||||
|
|
||||||
let tx1 = tx2i1o();
|
let tx1 = tx2i1o();
|
||||||
tx1.validate(Weighting::AsTransaction, vc.clone()).unwrap();
|
|
||||||
|
|
||||||
let previous_header = BlockHeader::default();
|
let previous_header = BlockHeader::default();
|
||||||
|
tx1.validate(
|
||||||
|
Weighting::AsTransaction,
|
||||||
|
vc.clone(),
|
||||||
|
previous_header.height + 1,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let block = new_block(&[tx1], &keychain, &builder, &previous_header, &key_id);
|
let block = new_block(&[tx1], &keychain, &builder, &previous_header, &key_id);
|
||||||
block.validate(&BlindingFactor::zero(), vc.clone()).unwrap();
|
block.validate(&BlindingFactor::zero(), vc.clone()).unwrap();
|
||||||
|
@ -638,7 +717,7 @@ fn test_block_with_timelocked_tx() {
|
||||||
// block height and that the resulting block is valid
|
// block height and that the resulting block is valid
|
||||||
let tx1 = build::transaction(
|
let tx1 = build::transaction(
|
||||||
KernelFeatures::HeightLocked {
|
KernelFeatures::HeightLocked {
|
||||||
fee: 2,
|
fee: 2.into(),
|
||||||
lock_height: 1,
|
lock_height: 1,
|
||||||
},
|
},
|
||||||
&[input(5, key_id1.clone()), output(3, key_id2.clone())],
|
&[input(5, key_id1.clone()), output(3, key_id2.clone())],
|
||||||
|
@ -662,7 +741,7 @@ fn test_block_with_timelocked_tx() {
|
||||||
// block height
|
// block height
|
||||||
let tx1 = build::transaction(
|
let tx1 = build::transaction(
|
||||||
KernelFeatures::HeightLocked {
|
KernelFeatures::HeightLocked {
|
||||||
fee: 2,
|
fee: 2.into(),
|
||||||
lock_height: 2,
|
lock_height: 2,
|
||||||
},
|
},
|
||||||
&[input(5, key_id1), output(3, key_id2)],
|
&[input(5, key_id1), output(3, key_id2)],
|
||||||
|
@ -686,7 +765,8 @@ fn test_block_with_timelocked_tx() {
|
||||||
pub fn test_verify_1i1o_sig() {
|
pub fn test_verify_1i1o_sig() {
|
||||||
test_setup();
|
test_setup();
|
||||||
let tx = tx1i1o();
|
let tx = tx1i1o();
|
||||||
tx.validate(Weighting::AsTransaction, verifier_cache())
|
let height = 42; // arbitrary
|
||||||
|
tx.validate(Weighting::AsTransaction, verifier_cache(), height)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -694,6 +774,7 @@ pub fn test_verify_1i1o_sig() {
|
||||||
pub fn test_verify_2i1o_sig() {
|
pub fn test_verify_2i1o_sig() {
|
||||||
test_setup();
|
test_setup();
|
||||||
let tx = tx2i1o();
|
let tx = tx2i1o();
|
||||||
tx.validate(Weighting::AsTransaction, verifier_cache())
|
let height = 42; // arbitrary
|
||||||
|
tx.validate(Weighting::AsTransaction, verifier_cache(), height)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,10 +18,12 @@ pub mod common;
|
||||||
use crate::common::tx1i10_v2_compatible;
|
use crate::common::tx1i10_v2_compatible;
|
||||||
use crate::core::core::transaction::{self, Error};
|
use crate::core::core::transaction::{self, Error};
|
||||||
use crate::core::core::verifier_cache::LruVerifierCache;
|
use crate::core::core::verifier_cache::LruVerifierCache;
|
||||||
use crate::core::core::{KernelFeatures, Output, OutputFeatures, Transaction, Weighting};
|
use crate::core::core::{
|
||||||
|
FeeFields, KernelFeatures, Output, OutputFeatures, Transaction, TxKernel, Weighting,
|
||||||
|
};
|
||||||
use crate::core::global;
|
use crate::core::global;
|
||||||
use crate::core::libtx::build;
|
|
||||||
use crate::core::libtx::proof::{self, ProofBuilder};
|
use crate::core::libtx::proof::{self, ProofBuilder};
|
||||||
|
use crate::core::libtx::{build, tx_fee};
|
||||||
use crate::core::{consensus, ser};
|
use crate::core::{consensus, ser};
|
||||||
use grin_core as core;
|
use grin_core as core;
|
||||||
use keychain::{ExtKeychain, Keychain};
|
use keychain::{ExtKeychain, Keychain};
|
||||||
|
@ -94,7 +96,9 @@ fn test_verify_cut_through_plain() -> Result<(), Error> {
|
||||||
let builder = proof::ProofBuilder::new(&keychain);
|
let builder = proof::ProofBuilder::new(&keychain);
|
||||||
|
|
||||||
let mut tx = build::transaction(
|
let mut tx = build::transaction(
|
||||||
KernelFeatures::Plain { fee: 0 },
|
KernelFeatures::Plain {
|
||||||
|
fee: FeeFields::zero(),
|
||||||
|
},
|
||||||
&[
|
&[
|
||||||
build::input(10, key_id1.clone()),
|
build::input(10, key_id1.clone()),
|
||||||
build::input(10, key_id2.clone()),
|
build::input(10, key_id2.clone()),
|
||||||
|
@ -110,8 +114,9 @@ fn test_verify_cut_through_plain() -> Result<(), Error> {
|
||||||
let verifier_cache = Arc::new(RwLock::new(LruVerifierCache::new()));
|
let verifier_cache = Arc::new(RwLock::new(LruVerifierCache::new()));
|
||||||
|
|
||||||
// Transaction should fail validation due to cut-through.
|
// Transaction should fail validation due to cut-through.
|
||||||
|
let height = 42; // arbitrary
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
tx.validate(Weighting::AsTransaction, verifier_cache.clone()),
|
tx.validate(Weighting::AsTransaction, verifier_cache.clone(), height),
|
||||||
Err(Error::CutThrough),
|
Err(Error::CutThrough),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -129,7 +134,7 @@ fn test_verify_cut_through_plain() -> Result<(), Error> {
|
||||||
.replace_outputs(outputs);
|
.replace_outputs(outputs);
|
||||||
|
|
||||||
// Transaction validates successfully after applying cut-through.
|
// Transaction validates successfully after applying cut-through.
|
||||||
tx.validate(Weighting::AsTransaction, verifier_cache.clone())?;
|
tx.validate(Weighting::AsTransaction, verifier_cache.clone(), height)?;
|
||||||
|
|
||||||
// Transaction validates via lightweight "read" validation as well.
|
// Transaction validates via lightweight "read" validation as well.
|
||||||
tx.validate_read()?;
|
tx.validate_read()?;
|
||||||
|
@ -153,7 +158,9 @@ fn test_verify_cut_through_coinbase() -> Result<(), Error> {
|
||||||
let builder = ProofBuilder::new(&keychain);
|
let builder = ProofBuilder::new(&keychain);
|
||||||
|
|
||||||
let mut tx = build::transaction(
|
let mut tx = build::transaction(
|
||||||
KernelFeatures::Plain { fee: 0 },
|
KernelFeatures::Plain {
|
||||||
|
fee: FeeFields::zero(),
|
||||||
|
},
|
||||||
&[
|
&[
|
||||||
build::coinbase_input(consensus::REWARD, key_id1.clone()),
|
build::coinbase_input(consensus::REWARD, key_id1.clone()),
|
||||||
build::coinbase_input(consensus::REWARD, key_id2.clone()),
|
build::coinbase_input(consensus::REWARD, key_id2.clone()),
|
||||||
|
@ -169,8 +176,9 @@ fn test_verify_cut_through_coinbase() -> Result<(), Error> {
|
||||||
let verifier_cache = Arc::new(RwLock::new(LruVerifierCache::new()));
|
let verifier_cache = Arc::new(RwLock::new(LruVerifierCache::new()));
|
||||||
|
|
||||||
// Transaction should fail validation due to cut-through.
|
// Transaction should fail validation due to cut-through.
|
||||||
|
let height = 42; // arbitrary
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
tx.validate(Weighting::AsTransaction, verifier_cache.clone()),
|
tx.validate(Weighting::AsTransaction, verifier_cache.clone(), height),
|
||||||
Err(Error::CutThrough),
|
Err(Error::CutThrough),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -188,10 +196,65 @@ fn test_verify_cut_through_coinbase() -> Result<(), Error> {
|
||||||
.replace_outputs(outputs);
|
.replace_outputs(outputs);
|
||||||
|
|
||||||
// Transaction validates successfully after applying cut-through.
|
// Transaction validates successfully after applying cut-through.
|
||||||
tx.validate(Weighting::AsTransaction, verifier_cache.clone())?;
|
tx.validate(Weighting::AsTransaction, verifier_cache.clone(), height)?;
|
||||||
|
|
||||||
// Transaction validates via lightweight "read" validation as well.
|
// Transaction validates via lightweight "read" validation as well.
|
||||||
tx.validate_read()?;
|
tx.validate_read()?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test coverage for FeeFields
|
||||||
|
#[test]
|
||||||
|
fn test_fee_fields() -> Result<(), Error> {
|
||||||
|
global::set_local_chain_type(global::ChainTypes::UserTesting);
|
||||||
|
global::set_local_accept_fee_base(500_000);
|
||||||
|
|
||||||
|
let keychain = ExtKeychain::from_random_seed(false)?;
|
||||||
|
|
||||||
|
let key_id1 = ExtKeychain::derive_key_id(1, 1, 0, 0, 0);
|
||||||
|
|
||||||
|
let builder = ProofBuilder::new(&keychain);
|
||||||
|
|
||||||
|
let mut tx = build::transaction(
|
||||||
|
KernelFeatures::Plain {
|
||||||
|
fee: FeeFields::new(1, 42).unwrap(),
|
||||||
|
},
|
||||||
|
&[
|
||||||
|
build::coinbase_input(consensus::REWARD, key_id1.clone()),
|
||||||
|
build::output(60_000_000_000 - 84 - 42 - 21, key_id1.clone()),
|
||||||
|
],
|
||||||
|
&keychain,
|
||||||
|
&builder,
|
||||||
|
)
|
||||||
|
.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));
|
||||||
|
|
||||||
|
tx.body.kernels.append(&mut vec![
|
||||||
|
TxKernel::with_features(KernelFeatures::Plain {
|
||||||
|
fee: FeeFields::new(2, 84).unwrap(),
|
||||||
|
}),
|
||||||
|
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(1, 1, 3), 15_500_000);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -87,7 +87,7 @@ enum_from_primitive! {
|
||||||
|
|
||||||
/// Max theoretical size of a block filled with outputs.
|
/// Max theoretical size of a block filled with outputs.
|
||||||
fn max_block_size() -> u64 {
|
fn max_block_size() -> u64 {
|
||||||
(global::max_block_weight() / consensus::BLOCK_OUTPUT_WEIGHT * 708) as u64
|
(global::max_block_weight() / consensus::OUTPUT_WEIGHT * 708) as u64
|
||||||
}
|
}
|
||||||
|
|
||||||
// Max msg size when msg type is unknown.
|
// Max msg size when msg type is unknown.
|
||||||
|
|
|
@ -22,7 +22,7 @@ use self::core::core::verifier_cache::{LruVerifierCache, VerifierCache};
|
||||||
use self::core::core::{Block, BlockHeader, BlockSums, KernelFeatures, Transaction};
|
use self::core::core::{Block, BlockHeader, BlockSums, KernelFeatures, Transaction};
|
||||||
use self::core::genesis;
|
use self::core::genesis;
|
||||||
use self::core::global;
|
use self::core::global;
|
||||||
use self::core::libtx::{build, reward, ProofBuilder};
|
use self::core::libtx::{build, reward, ProofBuilder, DEFAULT_BASE_FEE};
|
||||||
use self::core::pow;
|
use self::core::pow;
|
||||||
use self::keychain::{ExtKeychain, ExtKeychainPath, Keychain};
|
use self::keychain::{ExtKeychain, ExtKeychainPath, Keychain};
|
||||||
use self::pool::types::*;
|
use self::pool::types::*;
|
||||||
|
@ -262,7 +262,7 @@ impl PoolFuzzer {
|
||||||
{
|
{
|
||||||
TransactionPool::new(
|
TransactionPool::new(
|
||||||
PoolConfig {
|
PoolConfig {
|
||||||
accept_fee_base: 0,
|
accept_fee_base: DEFAULT_BASE_FEE,
|
||||||
max_pool_size: 50,
|
max_pool_size: 50,
|
||||||
max_stempool_size: 50,
|
max_stempool_size: 50,
|
||||||
mineable_max_weight: 10_000,
|
mineable_max_weight: 10_000,
|
||||||
|
|
|
@ -128,13 +128,13 @@ where
|
||||||
// * maintain dependency ordering
|
// * maintain dependency ordering
|
||||||
// * maximize cut-through
|
// * maximize cut-through
|
||||||
// * maximize overall fees
|
// * maximize overall fees
|
||||||
|
let header = self.blockchain.chain_head()?;
|
||||||
let txs = self.bucket_transactions(weighting);
|
let txs = self.bucket_transactions(weighting);
|
||||||
|
|
||||||
// Iteratively apply the txs to the current chain state,
|
// Iteratively apply the txs to the current chain state,
|
||||||
// rejecting any that do not result in a valid state.
|
// rejecting any that do not result in a valid state.
|
||||||
// Verify these txs produce an aggregated tx below max_weight.
|
// Verify these txs produce an aggregated tx below max_weight.
|
||||||
// Return a vec of all the valid txs.
|
// 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)?;
|
let valid_txs = self.validate_raw_txs(&txs, None, &header, weighting)?;
|
||||||
Ok(valid_txs)
|
Ok(valid_txs)
|
||||||
}
|
}
|
||||||
|
@ -161,7 +161,12 @@ where
|
||||||
let tx = transaction::aggregate(&txs)?;
|
let tx = transaction::aggregate(&txs)?;
|
||||||
|
|
||||||
// Validate the single aggregate transaction "as pool", not subject to tx weight limits.
|
// Validate the single aggregate transaction "as pool", not subject to tx weight limits.
|
||||||
tx.validate(Weighting::NoLimit, self.verifier_cache.clone())?;
|
let header = self.blockchain.chain_head()?;
|
||||||
|
tx.validate(
|
||||||
|
Weighting::NoLimit,
|
||||||
|
self.verifier_cache.clone(),
|
||||||
|
header.height,
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(Some(tx))
|
Ok(Some(tx))
|
||||||
}
|
}
|
||||||
|
@ -229,7 +234,7 @@ where
|
||||||
) -> Result<BlockSums, PoolError> {
|
) -> Result<BlockSums, PoolError> {
|
||||||
// Validate the tx, conditionally checking against weight limits,
|
// Validate the tx, conditionally checking against weight limits,
|
||||||
// based on weight verification type.
|
// based on weight verification type.
|
||||||
tx.validate(weighting, self.verifier_cache.clone())?;
|
tx.validate(weighting, self.verifier_cache.clone(), header.height)?;
|
||||||
|
|
||||||
// Validate the tx against current chain state.
|
// Validate the tx against current chain state.
|
||||||
// Check all inputs are in the current UTXO set.
|
// Check all inputs are in the current UTXO set.
|
||||||
|
@ -304,7 +309,7 @@ where
|
||||||
tx: &Transaction,
|
tx: &Transaction,
|
||||||
header: &BlockHeader,
|
header: &BlockHeader,
|
||||||
) -> Result<BlockSums, PoolError> {
|
) -> Result<BlockSums, PoolError> {
|
||||||
let overage = tx.overage();
|
let overage = tx.overage(header.height);
|
||||||
|
|
||||||
let offset = {
|
let offset = {
|
||||||
let secp = static_secp_instance();
|
let secp = static_secp_instance();
|
||||||
|
@ -340,19 +345,19 @@ where
|
||||||
|
|
||||||
// Use our bucket logic to identify the best transaction for eviction and evict it.
|
// Use our bucket logic to identify the best transaction for eviction and evict it.
|
||||||
// We want to avoid evicting a transaction where another transaction depends on it.
|
// We want to avoid evicting a transaction where another transaction depends on it.
|
||||||
// We want to evict a transaction with low fee_to_weight.
|
// We want to evict a transaction with low fee_rate.
|
||||||
pub fn evict_transaction(&mut self) {
|
pub fn evict_transaction(&mut self) {
|
||||||
if let Some(evictable_transaction) = self.bucket_transactions(Weighting::NoLimit).last() {
|
if let Some(evictable_transaction) = self.bucket_transactions(Weighting::NoLimit).last() {
|
||||||
self.entries.retain(|x| x.tx != *evictable_transaction);
|
self.entries.retain(|x| x.tx != *evictable_transaction);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Buckets consist of a vec of txs and track the aggregate fee_to_weight.
|
/// Buckets consist of a vec of txs and track the aggregate fee_rate.
|
||||||
/// We aggregate (cut-through) dependent transactions within a bucket *unless* adding a tx
|
/// We aggregate (cut-through) dependent transactions within a bucket *unless* adding a tx
|
||||||
/// would reduce the aggregate fee_to_weight, in which case we start a new bucket.
|
/// would reduce the aggregate fee_rate, in which case we start a new bucket.
|
||||||
/// Note this new bucket will by definition have a lower fee_to_weight than the bucket
|
/// Note this new bucket will by definition have a lower fee_rate than the bucket
|
||||||
/// containing the tx it depends on.
|
/// containing the tx it depends on.
|
||||||
/// Sorting the buckets by fee_to_weight will therefore preserve dependency ordering,
|
/// Sorting the buckets by fee_rate will therefore preserve dependency ordering,
|
||||||
/// maximizing both cut-through and overall fees.
|
/// maximizing both cut-through and overall fees.
|
||||||
fn bucket_transactions(&self, weighting: Weighting) -> Vec<Transaction> {
|
fn bucket_transactions(&self, weighting: Weighting) -> Vec<Transaction> {
|
||||||
let mut tx_buckets: Vec<Bucket> = Vec::new();
|
let mut tx_buckets: Vec<Bucket> = Vec::new();
|
||||||
|
@ -394,13 +399,14 @@ where
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let height = self.blockchain.chain_head().map(|x| x.height).unwrap_or(0);
|
||||||
match insert_pos {
|
match insert_pos {
|
||||||
None => {
|
None => {
|
||||||
// No parent tx, just add to the end in its own bucket.
|
// 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.
|
// 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.
|
// We assume the tx is valid here as we validated it on the way into the txpool.
|
||||||
insert_pos = Some(tx_buckets.len());
|
insert_pos = Some(tx_buckets.len());
|
||||||
tx_buckets.push(Bucket::new(entry.tx.clone(), tx_buckets.len()));
|
tx_buckets.push(Bucket::new(entry.tx.clone(), tx_buckets.len(), height));
|
||||||
}
|
}
|
||||||
Some(pos) => {
|
Some(pos) => {
|
||||||
// We found a single parent tx, so aggregate in the bucket
|
// We found a single parent tx, so aggregate in the bucket
|
||||||
|
@ -412,15 +418,20 @@ where
|
||||||
entry.tx.clone(),
|
entry.tx.clone(),
|
||||||
weighting,
|
weighting,
|
||||||
self.verifier_cache.clone(),
|
self.verifier_cache.clone(),
|
||||||
|
height,
|
||||||
) {
|
) {
|
||||||
if new_bucket.fee_to_weight >= bucket.fee_to_weight {
|
if new_bucket.fee_rate >= bucket.fee_rate {
|
||||||
// Only aggregate if it would not reduce the fee_to_weight ratio.
|
// Only aggregate if it would not reduce the fee_rate ratio.
|
||||||
tx_buckets[pos] = new_bucket;
|
tx_buckets[pos] = new_bucket;
|
||||||
} else {
|
} else {
|
||||||
// Otherwise put it in its own bucket at the end.
|
// Otherwise put it in its own bucket at the end.
|
||||||
// Note: This bucket will have a lower fee_to_weight
|
// Note: This bucket will have a lower fee_rate
|
||||||
// than the bucket it depends on.
|
// than the bucket it depends on.
|
||||||
tx_buckets.push(Bucket::new(entry.tx.clone(), tx_buckets.len()));
|
tx_buckets.push(Bucket::new(
|
||||||
|
entry.tx.clone(),
|
||||||
|
tx_buckets.len(),
|
||||||
|
height,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Aggregation failed so discard this new tx.
|
// Aggregation failed so discard this new tx.
|
||||||
|
@ -442,11 +453,11 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort buckets by fee_to_weight (descending) and age (oldest first).
|
// Sort buckets by fee_rate (descending) and age (oldest first).
|
||||||
// Txs with highest fee_to_weight will be prioritied.
|
// Txs with highest fee_rate will be prioritied.
|
||||||
// Aggregation that increases the fee_to_weight of a bucket will prioritize the bucket.
|
// Aggregation that increases the fee_rate of a bucket will prioritize the bucket.
|
||||||
// Oldest (based on pool insertion time) will then be prioritized.
|
// Oldest (based on pool insertion time) will then be prioritized.
|
||||||
tx_buckets.sort_unstable_by_key(|x| (Reverse(x.fee_to_weight), x.age_idx));
|
tx_buckets.sort_unstable_by_key(|x| (Reverse(x.fee_rate), x.age_idx));
|
||||||
|
|
||||||
tx_buckets.into_iter().flat_map(|x| x.raw_txs).collect()
|
tx_buckets.into_iter().flat_map(|x| x.raw_txs).collect()
|
||||||
}
|
}
|
||||||
|
@ -504,18 +515,18 @@ where
|
||||||
|
|
||||||
struct Bucket {
|
struct Bucket {
|
||||||
raw_txs: Vec<Transaction>,
|
raw_txs: Vec<Transaction>,
|
||||||
fee_to_weight: u64,
|
fee_rate: u64,
|
||||||
age_idx: usize,
|
age_idx: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Bucket {
|
impl Bucket {
|
||||||
/// Construct a new bucket with the given tx.
|
/// Construct a new bucket with the given tx.
|
||||||
/// also specifies an "age_idx" so we can sort buckets by age
|
/// also specifies an "age_idx" so we can sort buckets by age
|
||||||
/// as well as fee_to_weight. Txs are maintainedin the pool in insert order
|
/// as well as fee_rate. Txs are maintained in the pool in insert order
|
||||||
/// so buckets with low age_idx contain oldest txs.
|
/// so buckets with low age_idx contain oldest txs.
|
||||||
fn new(tx: Transaction, age_idx: usize) -> Bucket {
|
fn new(tx: Transaction, age_idx: usize, height: u64) -> Bucket {
|
||||||
Bucket {
|
Bucket {
|
||||||
fee_to_weight: tx.fee_to_weight(),
|
fee_rate: tx.fee_rate(height),
|
||||||
raw_txs: vec![tx],
|
raw_txs: vec![tx],
|
||||||
age_idx,
|
age_idx,
|
||||||
}
|
}
|
||||||
|
@ -526,13 +537,14 @@ impl Bucket {
|
||||||
new_tx: Transaction,
|
new_tx: Transaction,
|
||||||
weighting: Weighting,
|
weighting: Weighting,
|
||||||
verifier_cache: Arc<RwLock<dyn VerifierCache>>,
|
verifier_cache: Arc<RwLock<dyn VerifierCache>>,
|
||||||
|
height: u64,
|
||||||
) -> Result<Bucket, PoolError> {
|
) -> Result<Bucket, PoolError> {
|
||||||
let mut raw_txs = self.raw_txs.clone();
|
let mut raw_txs = self.raw_txs.clone();
|
||||||
raw_txs.push(new_tx);
|
raw_txs.push(new_tx);
|
||||||
let agg_tx = transaction::aggregate(&raw_txs)?;
|
let agg_tx = transaction::aggregate(&raw_txs)?;
|
||||||
agg_tx.validate(weighting, verifier_cache)?;
|
agg_tx.validate(weighting, verifier_cache, height)?;
|
||||||
Ok(Bucket {
|
Ok(Bucket {
|
||||||
fee_to_weight: agg_tx.fee_to_weight(),
|
fee_rate: agg_tx.fee_rate(height),
|
||||||
raw_txs: raw_txs,
|
raw_txs: raw_txs,
|
||||||
age_idx: self.age_idx,
|
age_idx: self.age_idx,
|
||||||
})
|
})
|
||||||
|
|
|
@ -182,7 +182,7 @@ where
|
||||||
// NRD kernels only valid post HF3 and if NRD feature enabled.
|
// NRD kernels only valid post HF3 and if NRD feature enabled.
|
||||||
self.verify_kernel_variants(tx, header)?;
|
self.verify_kernel_variants(tx, header)?;
|
||||||
|
|
||||||
// Do we have the capacity to accept this transaction?
|
// Does this transaction pay the required fees and fit within the pool capacity?
|
||||||
let acceptability = self.is_acceptable(tx, stem);
|
let acceptability = self.is_acceptable(tx, stem);
|
||||||
let mut evict = false;
|
let mut evict = false;
|
||||||
if !stem && acceptability.as_ref().err() == Some(&PoolError::OverCapacity) {
|
if !stem && acceptability.as_ref().err() == Some(&PoolError::OverCapacity) {
|
||||||
|
@ -193,8 +193,12 @@ where
|
||||||
|
|
||||||
// Make sure the transaction is valid before anything else.
|
// Make sure the transaction is valid before anything else.
|
||||||
// Validate tx accounting for max tx weight.
|
// Validate tx accounting for max tx weight.
|
||||||
tx.validate(Weighting::AsTransaction, self.verifier_cache.clone())
|
tx.validate(
|
||||||
.map_err(PoolError::InvalidTx)?;
|
Weighting::AsTransaction,
|
||||||
|
self.verifier_cache.clone(),
|
||||||
|
header.height,
|
||||||
|
)
|
||||||
|
.map_err(PoolError::InvalidTx)?;
|
||||||
|
|
||||||
// Check the tx lock_time is valid based on current chain state.
|
// Check the tx lock_time is valid based on current chain state.
|
||||||
self.blockchain.verify_tx_lock_height(tx)?;
|
self.blockchain.verify_tx_lock_height(tx)?;
|
||||||
|
@ -274,14 +278,19 @@ where
|
||||||
};
|
};
|
||||||
|
|
||||||
// Validate the tx to ensure our converted inputs are correct.
|
// Validate the tx to ensure our converted inputs are correct.
|
||||||
tx.validate(Weighting::AsTransaction, self.verifier_cache.clone())?;
|
let header = self.chain_head()?;
|
||||||
|
tx.validate(
|
||||||
|
Weighting::AsTransaction,
|
||||||
|
self.verifier_cache.clone(),
|
||||||
|
header.height,
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(PoolEntry::new(tx, entry.src))
|
Ok(PoolEntry::new(tx, entry.src))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evict a transaction from the txpool.
|
// Evict a transaction from the txpool.
|
||||||
// Uses bucket logic to identify the "last" transaction.
|
// Uses bucket logic to identify the "last" transaction.
|
||||||
// No other tx depends on it and it has low fee_to_weight.
|
// No other tx depends on it and it has low fee_rate
|
||||||
pub fn evict_from_txpool(&mut self) {
|
pub fn evict_from_txpool(&mut self) {
|
||||||
self.txpool.evict_transaction()
|
self.txpool.evict_transaction()
|
||||||
}
|
}
|
||||||
|
@ -362,14 +371,12 @@ where
|
||||||
return Err(PoolError::OverCapacity);
|
return Err(PoolError::OverCapacity);
|
||||||
}
|
}
|
||||||
|
|
||||||
// for a basic transaction (1 input, 2 outputs) -
|
// weight for a basic transaction (2 inputs, 2 outputs, 1 kernel) -
|
||||||
// (-1 * 1) + (4 * 2) + 1 = 8
|
// (2 * 1) + (2 * 21) + (1 * 3) = 47
|
||||||
// 8 * 10 = 80
|
// minfees = 47 * 500_000 = 23_500_000
|
||||||
if self.config.accept_fee_base > 0 {
|
let header = self.chain_head()?;
|
||||||
let threshold = (tx.tx_weight() as u64) * self.config.accept_fee_base;
|
if tx.shifted_fee(header.height) < tx.accept_fee(header.height) {
|
||||||
if tx.fee() < threshold {
|
return Err(PoolError::LowFeeTransaction(tx.shifted_fee(header.height)));
|
||||||
return Err(PoolError::LowFeeTransaction(threshold));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ use self::core::core::committed;
|
||||||
use self::core::core::hash::Hash;
|
use self::core::core::hash::Hash;
|
||||||
use self::core::core::transaction::{self, Transaction};
|
use self::core::core::transaction::{self, Transaction};
|
||||||
use self::core::core::{BlockHeader, BlockSums, Inputs, OutputIdentifier};
|
use self::core::core::{BlockHeader, BlockSums, Inputs, OutputIdentifier};
|
||||||
|
use self::core::global::DEFAULT_ACCEPT_FEE_BASE;
|
||||||
use chrono::prelude::*;
|
use chrono::prelude::*;
|
||||||
use failure::Fail;
|
use failure::Fail;
|
||||||
use grin_core as core;
|
use grin_core as core;
|
||||||
|
@ -139,8 +140,9 @@ impl Default for PoolConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_accept_fee_base() -> u64 {
|
/// make output (of weight 21) cost about 1 Grin-cent by default, keeping a round number
|
||||||
consensus::MILLI_GRIN
|
pub fn default_accept_fee_base() -> u64 {
|
||||||
|
DEFAULT_ACCEPT_FEE_BASE
|
||||||
}
|
}
|
||||||
fn default_reorg_cache_period() -> u32 {
|
fn default_reorg_cache_period() -> u32 {
|
||||||
30
|
30
|
||||||
|
|
|
@ -31,6 +31,7 @@ use std::sync::Arc;
|
||||||
fn test_transaction_pool_block_building() -> Result<(), PoolError> {
|
fn test_transaction_pool_block_building() -> Result<(), PoolError> {
|
||||||
util::init_test_logger();
|
util::init_test_logger();
|
||||||
global::set_local_chain_type(global::ChainTypes::AutomatedTesting);
|
global::set_local_chain_type(global::ChainTypes::AutomatedTesting);
|
||||||
|
global::set_local_accept_fee_base(1);
|
||||||
let keychain: ExtKeychain = Keychain::from_random_seed(false).unwrap();
|
let keychain: ExtKeychain = Keychain::from_random_seed(false).unwrap();
|
||||||
|
|
||||||
let db_root = "target/.block_building";
|
let db_root = "target/.block_building";
|
||||||
|
@ -48,25 +49,27 @@ fn test_transaction_pool_block_building() -> Result<(), PoolError> {
|
||||||
verifier_cache,
|
verifier_cache,
|
||||||
);
|
);
|
||||||
|
|
||||||
add_some_blocks(&chain, 3, &keychain);
|
// mine enough blocks to get past HF4
|
||||||
|
add_some_blocks(&chain, 4 * 3, &keychain);
|
||||||
|
|
||||||
let header_1 = chain.get_header_by_height(1).unwrap();
|
let header_1 = chain.get_header_by_height(1).unwrap();
|
||||||
|
|
||||||
// Now create tx to spend an early coinbase (now matured).
|
// Now create tx to spend an early coinbase (now matured).
|
||||||
// Provides us with some useful outputs to test with.
|
// Provides us with some useful outputs to test with.
|
||||||
let initial_tx = test_transaction_spending_coinbase(&keychain, &header_1, vec![10, 20, 30, 40]);
|
let initial_tx =
|
||||||
|
test_transaction_spending_coinbase(&keychain, &header_1, vec![100, 200, 300, 400]);
|
||||||
|
|
||||||
// Mine that initial tx so we can spend it with multiple txs.
|
// Mine that initial tx so we can spend it with multiple txs.
|
||||||
add_block(&chain, &[initial_tx], &keychain);
|
add_block(&chain, &[initial_tx], &keychain);
|
||||||
|
|
||||||
let header = chain.head_header().unwrap();
|
let header = chain.head_header().unwrap();
|
||||||
|
|
||||||
let root_tx_1 = test_transaction(&keychain, vec![10, 20], vec![24]);
|
let root_tx_1 = test_transaction(&keychain, vec![100, 200], vec![240]);
|
||||||
let root_tx_2 = test_transaction(&keychain, vec![30], vec![28]);
|
let root_tx_2 = test_transaction(&keychain, vec![300], vec![270]);
|
||||||
let root_tx_3 = test_transaction(&keychain, vec![40], vec![38]);
|
let root_tx_3 = test_transaction(&keychain, vec![400], vec![370]);
|
||||||
|
|
||||||
let child_tx_1 = test_transaction(&keychain, vec![24], vec![22]);
|
let child_tx_1 = test_transaction(&keychain, vec![240], vec![210]);
|
||||||
let child_tx_2 = test_transaction(&keychain, vec![38], vec![32]);
|
let child_tx_2 = test_transaction(&keychain, vec![370], vec![320]);
|
||||||
|
|
||||||
{
|
{
|
||||||
// Add the three root txs to the pool.
|
// Add the three root txs to the pool.
|
||||||
|
|
|
@ -30,6 +30,7 @@ use std::sync::Arc;
|
||||||
fn test_block_building_max_weight() {
|
fn test_block_building_max_weight() {
|
||||||
util::init_test_logger();
|
util::init_test_logger();
|
||||||
global::set_local_chain_type(global::ChainTypes::AutomatedTesting);
|
global::set_local_chain_type(global::ChainTypes::AutomatedTesting);
|
||||||
|
global::set_local_accept_fee_base(1);
|
||||||
|
|
||||||
let keychain: ExtKeychain = Keychain::from_random_seed(false).unwrap();
|
let keychain: ExtKeychain = Keychain::from_random_seed(false).unwrap();
|
||||||
|
|
||||||
|
@ -48,14 +49,18 @@ fn test_block_building_max_weight() {
|
||||||
verifier_cache,
|
verifier_cache,
|
||||||
);
|
);
|
||||||
|
|
||||||
add_some_blocks(&chain, 3, &keychain);
|
// mine past HF4 to see effect of set_local_accept_fee_base
|
||||||
|
add_some_blocks(&chain, 4 * 3, &keychain);
|
||||||
|
|
||||||
let header_1 = chain.get_header_by_height(1).unwrap();
|
let header_1 = chain.get_header_by_height(1).unwrap();
|
||||||
|
|
||||||
// Now create tx to spend an early coinbase (now matured).
|
// Now create tx to spend an early coinbase (now matured).
|
||||||
// Provides us with some useful outputs to test with.
|
// Provides us with some useful outputs to test with.
|
||||||
let initial_tx =
|
let initial_tx = test_transaction_spending_coinbase(
|
||||||
test_transaction_spending_coinbase(&keychain, &header_1, vec![100, 200, 300, 1000]);
|
&keychain,
|
||||||
|
&header_1,
|
||||||
|
vec![1_000_000, 2_000_000, 3_000_000, 10_000_000],
|
||||||
|
);
|
||||||
|
|
||||||
// Mine that initial tx so we can spend it with multiple txs.
|
// Mine that initial tx so we can spend it with multiple txs.
|
||||||
add_block(&chain, &[initial_tx], &keychain);
|
add_block(&chain, &[initial_tx], &keychain);
|
||||||
|
@ -65,26 +70,32 @@ fn test_block_building_max_weight() {
|
||||||
// Build some dependent txs to add to the txpool.
|
// Build some dependent txs to add to the txpool.
|
||||||
// We will build a block from a subset of these.
|
// We will build a block from a subset of these.
|
||||||
let txs = vec![
|
let txs = vec![
|
||||||
test_transaction(&keychain, vec![1000], vec![390, 130, 120, 110]),
|
test_transaction(
|
||||||
test_transaction(&keychain, vec![100], vec![90, 1]),
|
&keychain,
|
||||||
test_transaction(&keychain, vec![90], vec![80, 2]),
|
vec![10_000_000],
|
||||||
test_transaction(&keychain, vec![200], vec![199]),
|
vec![3_900_000, 1_300_000, 1_200_000, 1_100_000],
|
||||||
test_transaction(&keychain, vec![300], vec![290, 3]),
|
),
|
||||||
test_transaction(&keychain, vec![290], vec![280, 4]),
|
test_transaction(&keychain, vec![1_000_000], vec![900_000, 10_000]),
|
||||||
|
test_transaction(&keychain, vec![900_000], vec![800_000, 20_000]),
|
||||||
|
test_transaction(&keychain, vec![2_000_000], vec![1_970_000]),
|
||||||
|
test_transaction(&keychain, vec![3_000_000], vec![2_900_000, 30_000]),
|
||||||
|
test_transaction(&keychain, vec![2_900_000], vec![2_800_000, 40_000]),
|
||||||
];
|
];
|
||||||
|
|
||||||
// Fees and weights of our original txs in insert order.
|
// Fees and weights of our original txs in insert order.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
txs.iter().map(|x| x.fee()).collect::<Vec<_>>(),
|
txs.iter().map(|x| x.fee(header.height)).collect::<Vec<_>>(),
|
||||||
[250, 9, 8, 1, 7, 6]
|
[2_500_000, 90_000, 80_000, 30_000, 70_000, 60_000]
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
txs.iter().map(|x| x.tx_weight()).collect::<Vec<_>>(),
|
txs.iter().map(|x| x.weight()).collect::<Vec<_>>(),
|
||||||
[16, 8, 8, 4, 8, 8]
|
[88, 46, 46, 25, 46, 46]
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
txs.iter().map(|x| x.fee_to_weight()).collect::<Vec<_>>(),
|
txs.iter()
|
||||||
[15625, 1125, 1000, 250, 875, 750]
|
.map(|x| x.fee_rate(header.height))
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
[28409, 1956, 1739, 1200, 1521, 1304]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Populate our txpool with the txs.
|
// Populate our txpool with the txs.
|
||||||
|
@ -101,16 +112,18 @@ fn test_block_building_max_weight() {
|
||||||
|
|
||||||
// Fees and weights of the "mineable" txs.
|
// Fees and weights of the "mineable" txs.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
txs.iter().map(|x| x.fee()).collect::<Vec<_>>(),
|
txs.iter().map(|x| x.fee(header.height)).collect::<Vec<_>>(),
|
||||||
[250, 9, 8, 7]
|
[2_500_000, 90_000, 80_000, 70_000]
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
txs.iter().map(|x| x.tx_weight()).collect::<Vec<_>>(),
|
txs.iter().map(|x| x.weight()).collect::<Vec<_>>(),
|
||||||
[16, 8, 8, 8]
|
[88, 46, 46, 46]
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
txs.iter().map(|x| x.fee_to_weight()).collect::<Vec<_>>(),
|
txs.iter()
|
||||||
[15625, 1125, 1000, 875]
|
.map(|x| x.fee_rate(header.height))
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
[28409, 1956, 1739, 1521]
|
||||||
);
|
);
|
||||||
|
|
||||||
add_block(&chain, &txs, &keychain);
|
add_block(&chain, &txs, &keychain);
|
||||||
|
|
|
@ -30,6 +30,7 @@ use std::sync::Arc;
|
||||||
fn test_transaction_pool_block_reconciliation() {
|
fn test_transaction_pool_block_reconciliation() {
|
||||||
util::init_test_logger();
|
util::init_test_logger();
|
||||||
global::set_local_chain_type(global::ChainTypes::AutomatedTesting);
|
global::set_local_chain_type(global::ChainTypes::AutomatedTesting);
|
||||||
|
global::set_local_accept_fee_base(1);
|
||||||
let keychain: ExtKeychain = Keychain::from_random_seed(false).unwrap();
|
let keychain: ExtKeychain = Keychain::from_random_seed(false).unwrap();
|
||||||
|
|
||||||
let db_root = "target/.block_reconciliation";
|
let db_root = "target/.block_reconciliation";
|
||||||
|
@ -47,13 +48,15 @@ fn test_transaction_pool_block_reconciliation() {
|
||||||
verifier_cache,
|
verifier_cache,
|
||||||
);
|
);
|
||||||
|
|
||||||
add_some_blocks(&chain, 3, &keychain);
|
// mine past HF4 to see effect of set_local_accept_fee_base
|
||||||
|
add_some_blocks(&chain, 4 * 3, &keychain);
|
||||||
|
|
||||||
let header_1 = chain.get_header_by_height(1).unwrap();
|
let header_1 = chain.get_header_by_height(1).unwrap();
|
||||||
|
|
||||||
// Now create tx to spend an early coinbase (now matured).
|
// Now create tx to spend an early coinbase (now matured).
|
||||||
// Provides us with some useful outputs to test with.
|
// Provides us with some useful outputs to test with.
|
||||||
let initial_tx = test_transaction_spending_coinbase(&keychain, &header_1, vec![10, 20, 30, 40]);
|
let initial_tx =
|
||||||
|
test_transaction_spending_coinbase(&keychain, &header_1, vec![1_000, 2_000, 3_000, 4_000]);
|
||||||
|
|
||||||
// Mine that initial tx so we can spend it with multiple txs.
|
// Mine that initial tx so we can spend it with multiple txs.
|
||||||
add_block(&chain, &[initial_tx], &keychain);
|
add_block(&chain, &[initial_tx], &keychain);
|
||||||
|
@ -66,34 +69,34 @@ fn test_transaction_pool_block_reconciliation() {
|
||||||
// 2. A transaction that should be invalidated because the input is
|
// 2. A transaction that should be invalidated because the input is
|
||||||
// consumed in the block, although it is not exactly consumed.
|
// consumed in the block, although it is not exactly consumed.
|
||||||
// 3. A transaction that should remain after block reconciliation.
|
// 3. A transaction that should remain after block reconciliation.
|
||||||
let block_transaction = test_transaction(&keychain, vec![10], vec![8]);
|
let block_transaction = test_transaction(&keychain, vec![1_000], vec![800]);
|
||||||
let conflict_transaction = test_transaction(&keychain, vec![20], vec![12, 6]);
|
let conflict_transaction = test_transaction(&keychain, vec![2_000], vec![1_200, 600]);
|
||||||
let valid_transaction = test_transaction(&keychain, vec![30], vec![13, 15]);
|
let valid_transaction = test_transaction(&keychain, vec![3_000], vec![1_300, 1_500]);
|
||||||
|
|
||||||
// We will also introduce a few children:
|
// We will also introduce a few children:
|
||||||
// 4. A transaction that descends from transaction 1, that is in
|
// 4. A transaction that descends from transaction 1, that is in
|
||||||
// turn exactly contained in the block.
|
// turn exactly contained in the block.
|
||||||
let block_child = test_transaction(&keychain, vec![8], vec![5, 1]);
|
let block_child = test_transaction(&keychain, vec![800], vec![500, 100]);
|
||||||
// 5. A transaction that descends from transaction 4, that is not
|
// 5. A transaction that descends from transaction 4, that is not
|
||||||
// contained in the block at all and should be valid after
|
// contained in the block at all and should be valid after
|
||||||
// reconciliation.
|
// reconciliation.
|
||||||
let pool_child = test_transaction(&keychain, vec![5], vec![3]);
|
let pool_child = test_transaction(&keychain, vec![500], vec![300]);
|
||||||
// 6. A transaction that descends from transaction 2 that does not
|
// 6. A transaction that descends from transaction 2 that does not
|
||||||
// conflict with anything in the block in any way, but should be
|
// conflict with anything in the block in any way, but should be
|
||||||
// invalidated (orphaned).
|
// invalidated (orphaned).
|
||||||
let conflict_child = test_transaction(&keychain, vec![12], vec![2]);
|
let conflict_child = test_transaction(&keychain, vec![1_200], vec![200]);
|
||||||
// 7. A transaction that descends from transaction 2 that should be
|
// 7. A transaction that descends from transaction 2 that should be
|
||||||
// valid due to its inputs being satisfied by the block.
|
// valid due to its inputs being satisfied by the block.
|
||||||
let conflict_valid_child = test_transaction(&keychain, vec![6], vec![4]);
|
let conflict_valid_child = test_transaction(&keychain, vec![600], vec![400]);
|
||||||
// 8. A transaction that descends from transaction 3 that should be
|
// 8. A transaction that descends from transaction 3 that should be
|
||||||
// invalidated due to an output conflict.
|
// invalidated due to an output conflict.
|
||||||
let valid_child_conflict = test_transaction(&keychain, vec![13], vec![9]);
|
let valid_child_conflict = test_transaction(&keychain, vec![1_300], vec![900]);
|
||||||
// 9. A transaction that descends from transaction 3 that should remain
|
// 9. A transaction that descends from transaction 3 that should remain
|
||||||
// valid after reconciliation.
|
// valid after reconciliation.
|
||||||
let valid_child_valid = test_transaction(&keychain, vec![15], vec![11]);
|
let valid_child_valid = test_transaction(&keychain, vec![1_500], vec![1_100]);
|
||||||
// 10. A transaction that descends from both transaction 6 and
|
// 10. A transaction that descends from both transaction 6 and
|
||||||
// transaction 9
|
// transaction 9
|
||||||
let mixed_child = test_transaction(&keychain, vec![2, 11], vec![7]);
|
let mixed_child = test_transaction(&keychain, vec![200, 1_100], vec![700]);
|
||||||
|
|
||||||
let txs_to_add = vec![
|
let txs_to_add = vec![
|
||||||
block_transaction,
|
block_transaction,
|
||||||
|
@ -122,13 +125,13 @@ fn test_transaction_pool_block_reconciliation() {
|
||||||
// Now we prepare the block that will cause the above conditions to be met.
|
// Now we prepare the block that will cause the above conditions to be met.
|
||||||
// First, the transactions we want in the block:
|
// First, the transactions we want in the block:
|
||||||
// - Copy of 1
|
// - Copy of 1
|
||||||
let block_tx_1 = test_transaction(&keychain, vec![10], vec![8]);
|
let block_tx_1 = test_transaction(&keychain, vec![1_000], vec![800]);
|
||||||
// - Conflict w/ 2, satisfies 7
|
// - Conflict w/ 2, satisfies 7
|
||||||
let block_tx_2 = test_transaction(&keychain, vec![20], vec![6]);
|
let block_tx_2 = test_transaction(&keychain, vec![2_000], vec![600]);
|
||||||
// - Copy of 4
|
// - Copy of 4
|
||||||
let block_tx_3 = test_transaction(&keychain, vec![8], vec![5, 1]);
|
let block_tx_3 = test_transaction(&keychain, vec![800], vec![500, 100]);
|
||||||
// - Output conflict w/ 8
|
// - Output conflict w/ 8
|
||||||
let block_tx_4 = test_transaction(&keychain, vec![40], vec![9, 31]);
|
let block_tx_4 = test_transaction(&keychain, vec![4_000], vec![900, 2_900]);
|
||||||
|
|
||||||
let block_txs = &[block_tx_1, block_tx_2, block_tx_3, block_tx_4];
|
let block_txs = &[block_tx_1, block_tx_2, block_tx_3, block_tx_4];
|
||||||
add_block(&chain, block_txs, &keychain);
|
add_block(&chain, block_txs, &keychain);
|
||||||
|
|
|
@ -31,6 +31,7 @@ use std::sync::Arc;
|
||||||
fn test_coinbase_maturity() {
|
fn test_coinbase_maturity() {
|
||||||
util::init_test_logger();
|
util::init_test_logger();
|
||||||
global::set_local_chain_type(global::ChainTypes::AutomatedTesting);
|
global::set_local_chain_type(global::ChainTypes::AutomatedTesting);
|
||||||
|
global::set_local_accept_fee_base(50_000_000);
|
||||||
let keychain: ExtKeychain = Keychain::from_random_seed(false).unwrap();
|
let keychain: ExtKeychain = Keychain::from_random_seed(false).unwrap();
|
||||||
|
|
||||||
let db_root = "target/.coinbase_maturity";
|
let db_root = "target/.coinbase_maturity";
|
||||||
|
|
|
@ -36,6 +36,7 @@ use grin_core as core;
|
||||||
use grin_keychain as keychain;
|
use grin_keychain as keychain;
|
||||||
use grin_pool as pool;
|
use grin_pool as pool;
|
||||||
use grin_util as util;
|
use grin_util as util;
|
||||||
|
use std::convert::TryInto;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
@ -79,7 +80,7 @@ where
|
||||||
let prev = chain.head_header().unwrap();
|
let prev = chain.head_header().unwrap();
|
||||||
let height = prev.height + 1;
|
let height = prev.height + 1;
|
||||||
let next_header_info = consensus::next_difficulty(height, chain.difficulty_iter().unwrap());
|
let next_header_info = consensus::next_difficulty(height, chain.difficulty_iter().unwrap());
|
||||||
let fee = txs.iter().map(|x| x.fee()).sum();
|
let fee = txs.iter().map(|x| x.fee(height)).sum();
|
||||||
let key_id = ExtKeychainPath::new(1, height as u32, 0, 0, 0).to_identifier();
|
let key_id = ExtKeychainPath::new(1, height as u32, 0, 0, 0).to_identifier();
|
||||||
let reward =
|
let reward =
|
||||||
reward::output(keychain, &ProofBuilder::new(keychain), &key_id, fee, false).unwrap();
|
reward::output(keychain, &ProofBuilder::new(keychain), &key_id, fee, false).unwrap();
|
||||||
|
@ -166,7 +167,7 @@ where
|
||||||
{
|
{
|
||||||
TransactionPool::new(
|
TransactionPool::new(
|
||||||
PoolConfig {
|
PoolConfig {
|
||||||
accept_fee_base: 0,
|
accept_fee_base: default_accept_fee_base(),
|
||||||
reorg_cache_period: 30,
|
reorg_cache_period: 30,
|
||||||
max_pool_size: 50,
|
max_pool_size: 50,
|
||||||
max_stempool_size: 50,
|
max_stempool_size: 50,
|
||||||
|
@ -207,7 +208,9 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
build::transaction(
|
build::transaction(
|
||||||
KernelFeatures::Plain { fee: fees as u64 },
|
KernelFeatures::Plain {
|
||||||
|
fee: (fees as u64).try_into().unwrap(),
|
||||||
|
},
|
||||||
&tx_elements,
|
&tx_elements,
|
||||||
keychain,
|
keychain,
|
||||||
&ProofBuilder::new(keychain),
|
&ProofBuilder::new(keychain),
|
||||||
|
@ -232,7 +235,9 @@ where
|
||||||
keychain,
|
keychain,
|
||||||
input_values,
|
input_values,
|
||||||
output_values,
|
output_values,
|
||||||
KernelFeatures::Plain { fee: fees as u64 },
|
KernelFeatures::Plain {
|
||||||
|
fee: (fees as u64).try_into().unwrap(),
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ use std::sync::Arc;
|
||||||
fn test_nrd_kernel_relative_height() -> Result<(), PoolError> {
|
fn test_nrd_kernel_relative_height() -> Result<(), PoolError> {
|
||||||
util::init_test_logger();
|
util::init_test_logger();
|
||||||
global::set_local_chain_type(global::ChainTypes::AutomatedTesting);
|
global::set_local_chain_type(global::ChainTypes::AutomatedTesting);
|
||||||
|
global::set_local_accept_fee_base(10);
|
||||||
global::set_local_nrd_enabled(true);
|
global::set_local_nrd_enabled(true);
|
||||||
|
|
||||||
let keychain: ExtKeychain = Keychain::from_random_seed(false).unwrap();
|
let keychain: ExtKeychain = Keychain::from_random_seed(false).unwrap();
|
||||||
|
@ -59,21 +60,23 @@ fn test_nrd_kernel_relative_height() -> Result<(), PoolError> {
|
||||||
|
|
||||||
// Now create tx to spend an early coinbase (now matured).
|
// Now create tx to spend an early coinbase (now matured).
|
||||||
// Provides us with some useful outputs to test with.
|
// Provides us with some useful outputs to test with.
|
||||||
let initial_tx = test_transaction_spending_coinbase(&keychain, &header_1, vec![10, 20, 30, 40]);
|
let initial_tx =
|
||||||
|
test_transaction_spending_coinbase(&keychain, &header_1, vec![1_000, 2_000, 3_000, 4_000]);
|
||||||
|
|
||||||
// Mine that initial tx so we can spend it with multiple txs.
|
// Mine that initial tx so we can spend it with multiple txs.
|
||||||
add_block(&chain, &[initial_tx], &keychain);
|
add_block(&chain, &[initial_tx], &keychain);
|
||||||
|
|
||||||
add_some_blocks(&chain, 5, &keychain);
|
// mine past HF4 to see effect of set_local_accept_fee_base
|
||||||
|
add_some_blocks(&chain, 8, &keychain);
|
||||||
|
|
||||||
let header = chain.head_header().unwrap();
|
let header = chain.head_header().unwrap();
|
||||||
|
|
||||||
assert_eq!(header.height, 3 * consensus::TESTING_HARD_FORK_INTERVAL);
|
assert_eq!(header.height, 4 * consensus::TESTING_HARD_FORK_INTERVAL);
|
||||||
assert_eq!(header.version, HeaderVersion(4));
|
assert_eq!(header.version, HeaderVersion(5));
|
||||||
|
|
||||||
let (tx1, tx2, tx3) = {
|
let (tx1, tx2, tx3) = {
|
||||||
let mut kernel = TxKernel::with_features(KernelFeatures::NoRecentDuplicate {
|
let mut kernel = TxKernel::with_features(KernelFeatures::NoRecentDuplicate {
|
||||||
fee: 6,
|
fee: 600.into(),
|
||||||
relative_height: NRDRelativeHeight::new(2)?,
|
relative_height: NRDRelativeHeight::new(2)?,
|
||||||
});
|
});
|
||||||
let msg = kernel.msg_to_sign().unwrap();
|
let msg = kernel.msg_to_sign().unwrap();
|
||||||
|
@ -95,23 +98,23 @@ fn test_nrd_kernel_relative_height() -> Result<(), PoolError> {
|
||||||
|
|
||||||
let tx1 = test_transaction_with_kernel(
|
let tx1 = test_transaction_with_kernel(
|
||||||
&keychain,
|
&keychain,
|
||||||
vec![10, 20],
|
vec![1_000, 2_000],
|
||||||
vec![24],
|
vec![2_400],
|
||||||
kernel.clone(),
|
kernel.clone(),
|
||||||
excess.clone(),
|
excess.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let tx2 = test_transaction_with_kernel(
|
let tx2 = test_transaction_with_kernel(
|
||||||
&keychain,
|
&keychain,
|
||||||
vec![24],
|
vec![2_400],
|
||||||
vec![18],
|
vec![1_800],
|
||||||
kernel2.clone(),
|
kernel2.clone(),
|
||||||
excess.clone(),
|
excess.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Now reuse kernel excess for tx3 but with NRD relative_height=1 (and different fee).
|
// Now reuse kernel excess for tx3 but with NRD relative_height=1 (and different fee).
|
||||||
let mut kernel_short = TxKernel::with_features(KernelFeatures::NoRecentDuplicate {
|
let mut kernel_short = TxKernel::with_features(KernelFeatures::NoRecentDuplicate {
|
||||||
fee: 3,
|
fee: 300.into(),
|
||||||
relative_height: NRDRelativeHeight::new(1)?,
|
relative_height: NRDRelativeHeight::new(1)?,
|
||||||
});
|
});
|
||||||
let msg_short = kernel_short.msg_to_sign().unwrap();
|
let msg_short = kernel_short.msg_to_sign().unwrap();
|
||||||
|
@ -123,8 +126,8 @@ fn test_nrd_kernel_relative_height() -> Result<(), PoolError> {
|
||||||
|
|
||||||
let tx3 = test_transaction_with_kernel(
|
let tx3 = test_transaction_with_kernel(
|
||||||
&keychain,
|
&keychain,
|
||||||
vec![18],
|
vec![1_800],
|
||||||
vec![15],
|
vec![1_500],
|
||||||
kernel_short.clone(),
|
kernel_short.clone(),
|
||||||
excess.clone(),
|
excess.clone(),
|
||||||
);
|
);
|
||||||
|
|
|
@ -56,15 +56,16 @@ fn test_nrd_kernels_disabled() {
|
||||||
|
|
||||||
// Spend the initial coinbase.
|
// Spend the initial coinbase.
|
||||||
let header_1 = chain.get_header_by_height(1).unwrap();
|
let header_1 = chain.get_header_by_height(1).unwrap();
|
||||||
let tx = test_transaction_spending_coinbase(&keychain, &header_1, vec![10, 20, 30, 40]);
|
let tx =
|
||||||
|
test_transaction_spending_coinbase(&keychain, &header_1, vec![1_000, 2_000, 3_000, 4_000]);
|
||||||
add_block(&chain, &[tx], &keychain);
|
add_block(&chain, &[tx], &keychain);
|
||||||
|
|
||||||
let tx_1 = test_transaction_with_kernel_features(
|
let tx_1 = test_transaction_with_kernel_features(
|
||||||
&keychain,
|
&keychain,
|
||||||
vec![10, 20],
|
vec![1_000, 2_000],
|
||||||
vec![24],
|
vec![2_400],
|
||||||
KernelFeatures::NoRecentDuplicate {
|
KernelFeatures::NoRecentDuplicate {
|
||||||
fee: 6,
|
fee: 600.into(),
|
||||||
relative_height: NRDRelativeHeight::new(1440).unwrap(),
|
relative_height: NRDRelativeHeight::new(1440).unwrap(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -32,6 +32,7 @@ use std::sync::Arc;
|
||||||
fn test_nrd_kernels_enabled() {
|
fn test_nrd_kernels_enabled() {
|
||||||
util::init_test_logger();
|
util::init_test_logger();
|
||||||
global::set_local_chain_type(global::ChainTypes::AutomatedTesting);
|
global::set_local_chain_type(global::ChainTypes::AutomatedTesting);
|
||||||
|
global::set_local_accept_fee_base(10);
|
||||||
global::set_local_nrd_enabled(true);
|
global::set_local_nrd_enabled(true);
|
||||||
|
|
||||||
let keychain: ExtKeychain = Keychain::from_random_seed(false).unwrap();
|
let keychain: ExtKeychain = Keychain::from_random_seed(false).unwrap();
|
||||||
|
@ -56,15 +57,20 @@ fn test_nrd_kernels_enabled() {
|
||||||
|
|
||||||
// Spend the initial coinbase.
|
// Spend the initial coinbase.
|
||||||
let header_1 = chain.get_header_by_height(1).unwrap();
|
let header_1 = chain.get_header_by_height(1).unwrap();
|
||||||
let tx = test_transaction_spending_coinbase(&keychain, &header_1, vec![10, 20, 30, 40]);
|
let mg = consensus::MILLI_GRIN;
|
||||||
|
let tx = test_transaction_spending_coinbase(
|
||||||
|
&keychain,
|
||||||
|
&header_1,
|
||||||
|
vec![1_000 * mg, 2_000 * mg, 3_000 * mg, 4_000 * mg],
|
||||||
|
);
|
||||||
add_block(&chain, &[tx], &keychain);
|
add_block(&chain, &[tx], &keychain);
|
||||||
|
|
||||||
let tx_1 = test_transaction_with_kernel_features(
|
let tx_1 = test_transaction_with_kernel_features(
|
||||||
&keychain,
|
&keychain,
|
||||||
vec![10, 20],
|
vec![1_000 * mg, 2_000 * mg],
|
||||||
vec![24],
|
vec![2_400 * mg],
|
||||||
KernelFeatures::NoRecentDuplicate {
|
KernelFeatures::NoRecentDuplicate {
|
||||||
fee: 6,
|
fee: (600 * mg as u32).into(),
|
||||||
relative_height: NRDRelativeHeight::new(1440).unwrap(),
|
relative_height: NRDRelativeHeight::new(1440).unwrap(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -83,7 +89,7 @@ fn test_nrd_kernels_enabled() {
|
||||||
assert_eq!(header.height, 3 * consensus::TESTING_HARD_FORK_INTERVAL);
|
assert_eq!(header.height, 3 * consensus::TESTING_HARD_FORK_INTERVAL);
|
||||||
assert_eq!(header.version, HeaderVersion(4));
|
assert_eq!(header.version, HeaderVersion(4));
|
||||||
|
|
||||||
// NRD kernel support not enabled via feature flag, so not valid.
|
// NRD kernel support enabled via feature flag, so valid.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
pool.add_to_pool(test_source(), tx_1.clone(), false, &header),
|
pool.add_to_pool(test_source(), tx_1.clone(), false, &header),
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -32,6 +32,7 @@ use std::sync::Arc;
|
||||||
fn test_the_transaction_pool() {
|
fn test_the_transaction_pool() {
|
||||||
util::init_test_logger();
|
util::init_test_logger();
|
||||||
global::set_local_chain_type(global::ChainTypes::AutomatedTesting);
|
global::set_local_chain_type(global::ChainTypes::AutomatedTesting);
|
||||||
|
global::set_local_accept_fee_base(1);
|
||||||
let keychain: ExtKeychain = Keychain::from_random_seed(false).unwrap();
|
let keychain: ExtKeychain = Keychain::from_random_seed(false).unwrap();
|
||||||
|
|
||||||
let db_root = "target/.transaction_pool";
|
let db_root = "target/.transaction_pool";
|
||||||
|
@ -49,7 +50,8 @@ fn test_the_transaction_pool() {
|
||||||
verifier_cache.clone(),
|
verifier_cache.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
add_some_blocks(&chain, 3, &keychain);
|
// mine past HF4 to see effect of set_local_accept_fee_base
|
||||||
|
add_some_blocks(&chain, 4 * 3, &keychain);
|
||||||
let header = chain.head_header().unwrap();
|
let header = chain.head_header().unwrap();
|
||||||
|
|
||||||
let header_1 = chain.get_header_by_height(1).unwrap();
|
let header_1 = chain.get_header_by_height(1).unwrap();
|
||||||
|
@ -74,9 +76,9 @@ fn test_the_transaction_pool() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// tx1 spends some outputs from the initial test tx.
|
// tx1 spends some outputs from the initial test tx.
|
||||||
let tx1 = test_transaction(&keychain, vec![500, 600], vec![499, 599]);
|
let tx1 = test_transaction(&keychain, vec![500, 600], vec![469, 569]);
|
||||||
// tx2 spends some outputs from both tx1 and the initial test tx.
|
// tx2 spends some outputs from both tx1 and the initial test tx.
|
||||||
let tx2 = test_transaction(&keychain, vec![499, 700], vec![498]);
|
let tx2 = test_transaction(&keychain, vec![469, 700], vec![498]);
|
||||||
|
|
||||||
{
|
{
|
||||||
// Check we have a single initial tx in the pool.
|
// Check we have a single initial tx in the pool.
|
||||||
|
@ -105,7 +107,7 @@ fn test_the_transaction_pool() {
|
||||||
// Test adding a duplicate tx with the same input and outputs.
|
// Test adding a duplicate tx with the same input and outputs.
|
||||||
// Note: not the *same* tx, just same underlying inputs/outputs.
|
// Note: not the *same* tx, just same underlying inputs/outputs.
|
||||||
{
|
{
|
||||||
let tx1a = test_transaction(&keychain, vec![500, 600], vec![499, 599]);
|
let tx1a = test_transaction(&keychain, vec![500, 600], vec![469, 569]);
|
||||||
assert!(pool
|
assert!(pool
|
||||||
.add_to_pool(test_source(), tx1a, false, &header)
|
.add_to_pool(test_source(), tx1a, false, &header)
|
||||||
.is_err());
|
.is_err());
|
||||||
|
@ -113,7 +115,7 @@ fn test_the_transaction_pool() {
|
||||||
|
|
||||||
// Test adding a tx attempting to spend a non-existent output.
|
// Test adding a tx attempting to spend a non-existent output.
|
||||||
{
|
{
|
||||||
let bad_tx = test_transaction(&keychain, vec![10_001], vec![10_000]);
|
let bad_tx = test_transaction(&keychain, vec![10_001], vec![9_900]);
|
||||||
assert!(pool
|
assert!(pool
|
||||||
.add_to_pool(test_source(), bad_tx, false, &header)
|
.add_to_pool(test_source(), bad_tx, false, &header)
|
||||||
.is_err());
|
.is_err());
|
||||||
|
@ -130,7 +132,7 @@ fn test_the_transaction_pool() {
|
||||||
|
|
||||||
// Confirm the tx pool correctly identifies an invalid tx (already spent).
|
// Confirm the tx pool correctly identifies an invalid tx (already spent).
|
||||||
{
|
{
|
||||||
let tx3 = test_transaction(&keychain, vec![500], vec![497]);
|
let tx3 = test_transaction(&keychain, vec![500], vec![467]);
|
||||||
assert!(pool
|
assert!(pool
|
||||||
.add_to_pool(test_source(), tx3, false, &header)
|
.add_to_pool(test_source(), tx3, false, &header)
|
||||||
.is_err());
|
.is_err());
|
||||||
|
@ -139,9 +141,9 @@ fn test_the_transaction_pool() {
|
||||||
|
|
||||||
// Now add a couple of txs to the stempool (stem = true).
|
// Now add a couple of txs to the stempool (stem = true).
|
||||||
{
|
{
|
||||||
let tx = test_transaction(&keychain, vec![599], vec![598]);
|
let tx = test_transaction(&keychain, vec![569], vec![538]);
|
||||||
pool.add_to_pool(test_source(), tx, true, &header).unwrap();
|
pool.add_to_pool(test_source(), tx, true, &header).unwrap();
|
||||||
let tx2 = test_transaction(&keychain, vec![598], vec![597]);
|
let tx2 = test_transaction(&keychain, vec![538], vec![507]);
|
||||||
pool.add_to_pool(test_source(), tx2, true, &header).unwrap();
|
pool.add_to_pool(test_source(), tx2, true, &header).unwrap();
|
||||||
assert_eq!(pool.total_size(), 3);
|
assert_eq!(pool.total_size(), 3);
|
||||||
assert_eq!(pool.stempool.size(), 2);
|
assert_eq!(pool.stempool.size(), 2);
|
||||||
|
@ -165,7 +167,7 @@ fn test_the_transaction_pool() {
|
||||||
// Adding a duplicate tx to the stempool will result in it being fluffed.
|
// Adding a duplicate tx to the stempool will result in it being fluffed.
|
||||||
// This handles the case of the stem path having a cycle in it.
|
// This handles the case of the stem path having a cycle in it.
|
||||||
{
|
{
|
||||||
let tx = test_transaction(&keychain, vec![597], vec![596]);
|
let tx = test_transaction(&keychain, vec![507], vec![476]);
|
||||||
pool.add_to_pool(test_source(), tx.clone(), true, &header)
|
pool.add_to_pool(test_source(), tx.clone(), true, &header)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(pool.total_size(), 4);
|
assert_eq!(pool.total_size(), 4);
|
||||||
|
@ -185,14 +187,15 @@ fn test_the_transaction_pool() {
|
||||||
// We will do this be adding a new tx to the pool
|
// We will do this be adding a new tx to the pool
|
||||||
// that is a superset of a tx already in the pool.
|
// that is a superset of a tx already in the pool.
|
||||||
{
|
{
|
||||||
let tx4 = test_transaction(&keychain, vec![800], vec![799]);
|
let tx4 = test_transaction(&keychain, vec![800], vec![769]);
|
||||||
|
|
||||||
// tx1 and tx2 are already in the txpool (in aggregated form)
|
// tx1 and tx2 are already in the txpool (in aggregated form)
|
||||||
// tx4 is the "new" part of this aggregated tx that we care about
|
// 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 agg_tx = transaction::aggregate(&[tx1.clone(), tx2.clone(), tx4]).unwrap();
|
||||||
|
|
||||||
|
let height = 12 + 1;
|
||||||
agg_tx
|
agg_tx
|
||||||
.validate(Weighting::AsTransaction, verifier_cache.clone())
|
.validate(Weighting::AsTransaction, verifier_cache.clone(), height)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
pool.add_to_pool(test_source(), agg_tx, false, &header)
|
pool.add_to_pool(test_source(), agg_tx, false, &header)
|
||||||
|
|
|
@ -150,6 +150,7 @@ fn process_fluff_phase(
|
||||||
agg_tx.validate(
|
agg_tx.validate(
|
||||||
transaction::Weighting::AsTransaction,
|
transaction::Weighting::AsTransaction,
|
||||||
verifier_cache.clone(),
|
verifier_cache.clone(),
|
||||||
|
header.height,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
tx_pool.add_to_pool(TxSource::Fluff, agg_tx, false, &header)?;
|
tx_pool.add_to_pool(TxSource::Fluff, agg_tx, false, &header)?;
|
||||||
|
|
|
@ -166,7 +166,7 @@ fn build_block(
|
||||||
};
|
};
|
||||||
|
|
||||||
// build the coinbase and the block itself
|
// build the coinbase and the block itself
|
||||||
let fees = txs.iter().map(|tx| tx.fee()).sum();
|
let fees = txs.iter().map(|tx| tx.fee(head.height)).sum();
|
||||||
let height = head.height + 1;
|
let height = head.height + 1;
|
||||||
let block_fees = BlockFees {
|
let block_fees = BlockFees {
|
||||||
fees,
|
fees,
|
||||||
|
|
|
@ -146,7 +146,7 @@ fn real_main() -> i32 {
|
||||||
|
|
||||||
log_build_info();
|
log_build_info();
|
||||||
|
|
||||||
// Initialize our global chain_type, feature flags (NRD kernel support currently), and future_time_limit.
|
// Initialize our global chain_type, feature flags (NRD kernel support currently), accept_fee_base, and future_time_limit.
|
||||||
// These are read via global and not read from config beyond this point.
|
// These are read via global and not read from config beyond this point.
|
||||||
global::init_global_chain_type(config.members.as_ref().unwrap().server.chain_type);
|
global::init_global_chain_type(config.members.as_ref().unwrap().server.chain_type);
|
||||||
info!("Chain: {:?}", global::get_chain_type());
|
info!("Chain: {:?}", global::get_chain_type());
|
||||||
|
@ -160,6 +160,16 @@ fn real_main() -> i32 {
|
||||||
global::init_global_nrd_enabled(true);
|
global::init_global_nrd_enabled(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
global::init_global_accept_fee_base(
|
||||||
|
config
|
||||||
|
.members
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.server
|
||||||
|
.pool_config
|
||||||
|
.accept_fee_base,
|
||||||
|
);
|
||||||
|
info!("Accept Fee Base: {:?}", global::get_accept_fee_base());
|
||||||
global::init_global_future_time_limit(config.members.unwrap().server.future_time_limit);
|
global::init_global_future_time_limit(config.members.unwrap().server.future_time_limit);
|
||||||
info!("Future Time Limit: {:?}", global::get_future_time_limit());
|
info!("Future Time Limit: {:?}", global::get_future_time_limit());
|
||||||
log_feature_flags();
|
log_feature_flags();
|
||||||
|
|
Loading…
Reference in a new issue