From 5bef3fdf6c3771620b5286605faeb83f9b2152e7 Mon Sep 17 00:00:00 2001 From: Stan Bondi Date: Tue, 19 Oct 2021 11:52:20 +0400 Subject: [PATCH] feat: tx weight takes tariscript and output features into account [igor] (#3411) Description --- - adds consensus encoding for tari script and output features - igor: tx weight takes TariScript and OutputFeatures metadata into account as per #3368 - igor: weights updated as per #3368 - add `TransactionWeight` parameters for `weatherwax` and `igor` networks and set them in consensus constants - set max block weight for `igor` (+95% adjust to allow similar number of max inputs/outputs/kernels ~2Mb) - decouple chain_storage from `consensus_contstants` module to allow wallet to use it - wallet: minor simplification/dedup code of coin split logic - wallet: default fee per gram depending on network - wallet and base node tests updated as needed Motivation and Context --- See #3368 How Has This Been Tested? --- Existing tests updated, manually checked normal and one-sided transaction sending between console wallets/mobile on watherwax and igor --- Cargo.lock | 9 +- .../src/conversions/consensus_constants.rs | 12 +- .../src/conversions/historical_block.rs | 2 +- applications/tari_base_node/src/builder.rs | 2 +- .../tari_base_node/src/command_handler.rs | 13 +- .../tari_base_node/src/grpc/blocks.rs | 5 +- .../tari_console_wallet/src/ui/app.rs | 2 +- .../src/ui/components/send_tab.rs | 17 +- .../src/ui/state/app_state.rs | 93 ++++--- applications/test_faucet/src/main.rs | 6 +- base_layer/core/Cargo.toml | 15 +- .../comms_interface/comms_response.rs | 3 +- .../src/base_node/comms_interface/error.rs | 4 +- .../comms_interface/inbound_handlers.rs | 4 +- .../comms_interface/local_interface.rs | 3 +- .../comms_interface/outbound_interface.rs | 3 +- .../core/src/base_node/proto/response.rs | 3 +- .../base_node/sync/block_sync/synchronizer.rs | 4 +- .../src/base_node/sync/header_sync/error.rs | 5 +- .../sync/header_sync/synchronizer.rs | 4 +- .../base_node/sync/header_sync/validator.rs | 15 +- base_layer/core/src/base_node/sync/hooks.rs | 2 +- .../accumulated_data.rs | 38 +-- base_layer/core/src/blocks/block.rs | 24 +- base_layer/core/src/blocks/error.rs | 33 +++ base_layer/core/src/blocks/genesis_block.rs | 20 +- .../historical_block.rs | 51 ++-- base_layer/core/src/blocks/mod.rs | 33 ++- base_layer/core/src/chain_storage/async_db.rs | 20 +- .../src/chain_storage/block_add_result.rs | 2 +- .../src/chain_storage/blockchain_backend.rs | 13 +- .../src/chain_storage/blockchain_database.rs | 18 +- .../core/src/chain_storage/db_transaction.rs | 4 +- base_layer/core/src/chain_storage/error.rs | 6 +- .../core/src/chain_storage/lmdb_db/lmdb_db.rs | 13 +- base_layer/core/src/chain_storage/mod.rs | 14 - .../tests/blockchain_database.rs | 6 +- base_layer/core/src/common/byte_counter.rs | 50 ++++ base_layer/core/src/common/mod.rs | 1 + .../src/consensus/chain_strength_comparer.rs | 2 +- .../core/src/consensus/consensus_constants.rs | 33 ++- .../core/src/consensus/consensus_encoding.rs | 78 ++++++ .../core/src/consensus/consensus_manager.rs | 49 ++-- base_layer/core/src/consensus/mod.rs | 26 +- base_layer/core/src/lib.rs | 3 - base_layer/core/src/mempool/mempool.rs | 13 +- .../core/src/mempool/mempool_storage.rs | 38 ++- base_layer/core/src/mempool/mod.rs | 5 +- base_layer/core/src/mempool/priority/mod.rs | 7 +- .../priority/prioritized_transaction.rs | 23 +- .../priority/timelocked_transaction.rs | 74 ----- .../core/src/mempool/reorg_pool/error.rs | 1 - .../core/src/mempool/reorg_pool/reorg_pool.rs | 61 +--- .../core/src/mempool/sync_protocol/test.rs | 12 +- .../unconfirmed_pool/unconfirmed_pool.rs | 178 ++++++------ base_layer/core/src/proof_of_work/mod.rs | 8 +- .../src/proof_of_work/target_difficulty.rs | 83 +----- .../proof_of_work/target_difficulty_window.rs | 99 +++++++ base_layer/core/src/proto/block.rs | 3 +- .../core/src/test_helpers/blockchain.rs | 21 +- base_layer/core/src/test_helpers/mod.rs | 14 +- .../core/src/transactions/aggregated_body.rs | 41 ++- .../core/src/transactions/coinbase_builder.rs | 10 +- base_layer/core/src/transactions/fee.rs | 49 ++-- .../core/src/transactions/format_currency.rs | 67 +++++ base_layer/core/src/transactions/mod.rs | 22 +- .../core/src/transactions/tari_amount.rs | 17 +- .../{helpers.rs => test_helpers.rs} | 138 +++++----- .../core/src/transactions/transaction.rs | 245 +++++++++++++---- .../transactions/transaction_protocol/mod.rs | 2 + .../transaction_protocol/recipient.rs | 2 +- .../transaction_protocol/sender.rs | 121 ++++---- .../transaction_initializer.rs | 217 ++++++++++----- base_layer/core/src/transactions/weight.rs | 114 ++++++++ .../validation/block_validators/body_only.rs | 3 +- .../src/validation/block_validators/test.rs | 2 +- base_layer/core/src/validation/error.rs | 2 +- base_layer/core/src/validation/helpers.rs | 16 +- base_layer/core/src/validation/mocks.rs | 4 +- base_layer/core/src/validation/test.rs | 10 +- base_layer/core/src/validation/traits.rs | 4 +- .../src/validation/transaction_validators.rs | 8 +- base_layer/core/tests/async_db.rs | 2 +- base_layer/core/tests/base_node_rpc.rs | 4 +- base_layer/core/tests/block_validation.rs | 13 +- .../chain_storage_tests/chain_backend.rs | 4 +- .../chain_storage_tests/chain_storage.rs | 6 +- .../core/tests/helpers/block_builders.rs | 16 +- base_layer/core/tests/helpers/block_proxy.rs | 2 +- base_layer/core/tests/helpers/nodes.rs | 6 +- .../core/tests/helpers/sample_blockchains.rs | 3 +- base_layer/core/tests/mempool.rs | 88 ++++-- base_layer/core/tests/node_comms_interface.rs | 18 +- base_layer/core/tests/node_service.rs | 5 +- .../src/output_manager_service/handle.rs | 26 +- .../wallet/src/output_manager_service/mod.rs | 8 +- .../src/output_manager_service/service.rs | 260 ++++++++++++------ .../storage/sqlite_db.rs | 2 +- base_layer/wallet/src/test_utils.rs | 10 + .../transaction_service/storage/sqlite_db.rs | 8 +- base_layer/wallet/src/types.rs | 5 - base_layer/wallet/src/wallet.rs | 15 +- .../tests/output_manager_service/service.rs | 141 ++++++---- base_layer/wallet/tests/support/utils.rs | 2 +- .../tests/transaction_service/service.rs | 36 ++- .../tests/transaction_service/storage.rs | 24 +- .../transaction_protocols.rs | 2 +- base_layer/wallet/tests/wallet/mod.rs | 2 +- base_layer/wallet_ffi/src/lib.rs | 6 +- integration_tests/features/Mempool.feature | 42 +-- integration_tests/features/Reorgs.feature | 12 +- integration_tests/features/StressTest.feature | 8 +- .../features/TransactionInfo.feature | 2 +- integration_tests/features/WalletFFI.feature | 10 +- .../features/WalletMonitoring.feature | 4 +- .../features/WalletRecovery.feature | 10 +- .../features/WalletRoutingMechanism.feature | 2 +- .../features/WalletTransactions.feature | 42 +-- .../features/WalletTransfer.feature | 4 +- .../features/support/ffi_steps.js | 6 +- 120 files changed, 2106 insertions(+), 1247 deletions(-) rename base_layer/core/src/{chain_storage => blocks}/accumulated_data.rs (94%) create mode 100644 base_layer/core/src/blocks/error.rs rename base_layer/core/src/{chain_storage => blocks}/historical_block.rs (59%) create mode 100644 base_layer/core/src/common/byte_counter.rs create mode 100644 base_layer/core/src/consensus/consensus_encoding.rs delete mode 100644 base_layer/core/src/mempool/priority/timelocked_transaction.rs create mode 100644 base_layer/core/src/proof_of_work/target_difficulty_window.rs create mode 100644 base_layer/core/src/transactions/format_currency.rs rename base_layer/core/src/transactions/{helpers.rs => test_helpers.rs} (89%) create mode 100644 base_layer/core/src/transactions/weight.rs diff --git a/Cargo.lock b/Cargo.lock index 92f0f03fe3..331b369d1e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1955,6 +1955,12 @@ version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48dc51180a9b377fd75814d0cc02199c20f8e99433d6762f650d39cdbbd3b56f" +[[package]] +name = "integer-encoding" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90c11140ffea82edce8dcd74137ce9324ec24b3cf0175fc9d7e29164da9915b8" + [[package]] name = "ipnet" version = "2.3.1" @@ -4487,6 +4493,7 @@ dependencies = [ "fs2", "futures 0.3.16", "hex", + "integer-encoding 3.0.2", "lazy_static 1.4.0", "lmdb-zero", "log 0.4.14", @@ -5001,7 +5008,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c6d965454947cc7266d22716ebfd07b18d84ebaf35eec558586bbb2a8cb6b5b" dependencies = [ "byteorder", - "integer-encoding", + "integer-encoding 1.1.7", "log 0.4.14", "ordered-float 1.1.1", "threadpool", diff --git a/applications/tari_app_grpc/src/conversions/consensus_constants.rs b/applications/tari_app_grpc/src/conversions/consensus_constants.rs index 7f46baee01..7a3648b426 100644 --- a/applications/tari_app_grpc/src/conversions/consensus_constants.rs +++ b/applications/tari_app_grpc/src/conversions/consensus_constants.rs @@ -22,14 +22,12 @@ use crate::tari_rpc as grpc; use std::convert::TryFrom; -use tari_core::{ - consensus::{ConsensusConstants, KERNEL_WEIGHT, WEIGHT_PER_INPUT, WEIGHT_PER_OUTPUT}, - proof_of_work::PowAlgorithm, -}; +use tari_core::{consensus::ConsensusConstants, proof_of_work::PowAlgorithm}; impl From for grpc::ConsensusConstants { fn from(cc: ConsensusConstants) -> Self { let (emission_initial, emission_decay, emission_tail) = cc.emission_amounts(); + let weight_params = cc.transaction_weight().params(); Self { coinbase_lock_height: cc.coinbase_lock_height(), blockchain_version: cc.blockchain_version().into(), @@ -43,9 +41,9 @@ impl From for grpc::ConsensusConstants { emission_decay: emission_decay.to_vec(), emission_tail: emission_tail.into(), min_blake_pow_difficulty: cc.min_pow_difficulty(PowAlgorithm::Sha3).into(), - block_weight_inputs: WEIGHT_PER_INPUT, - block_weight_outputs: WEIGHT_PER_OUTPUT, - block_weight_kernels: KERNEL_WEIGHT, + block_weight_inputs: weight_params.input_weight, + block_weight_outputs: weight_params.output_weight, + block_weight_kernels: weight_params.kernel_weight, } } } diff --git a/applications/tari_app_grpc/src/conversions/historical_block.rs b/applications/tari_app_grpc/src/conversions/historical_block.rs index bf2022703a..8d8bbf9da9 100644 --- a/applications/tari_app_grpc/src/conversions/historical_block.rs +++ b/applications/tari_app_grpc/src/conversions/historical_block.rs @@ -22,7 +22,7 @@ use crate::tari_rpc as grpc; use std::convert::TryFrom; -use tari_core::chain_storage::{ChainStorageError, HistoricalBlock}; +use tari_core::{blocks::HistoricalBlock, chain_storage::ChainStorageError}; impl TryFrom for grpc::HistoricalBlock { type Error = ChainStorageError; diff --git a/applications/tari_base_node/src/builder.rs b/applications/tari_base_node/src/builder.rs index ee374b339e..ca7d6d2cab 100644 --- a/applications/tari_base_node/src/builder.rs +++ b/applications/tari_base_node/src/builder.rs @@ -250,7 +250,7 @@ async fn build_node_context( Box::new(TxInputAndMaturityValidator::new(blockchain_db.clone())), Box::new(TxConsensusValidator::new(blockchain_db.clone())), ]); - let mempool = Mempool::new(MempoolConfig::default(), Arc::new(mempool_validator)); + let mempool = Mempool::new(MempoolConfig::default(), rules.clone(), Arc::new(mempool_validator)); //---------------------------------- Base Node --------------------------------------------// debug!(target: LOG_TARGET, "Creating base node state machine."); diff --git a/applications/tari_base_node/src/command_handler.rs b/applications/tari_base_node/src/command_handler.rs index fc99c70990..103da488a7 100644 --- a/applications/tari_base_node/src/command_handler.rs +++ b/applications/tari_base_node/src/command_handler.rs @@ -51,8 +51,8 @@ use tari_core::{ state_machine_service::states::{PeerMetadata, StatusInfo}, LocalNodeCommsInterface, }, - blocks::BlockHeader, - chain_storage::{async_db::AsyncBlockchainDb, ChainHeader, LMDBDatabase}, + blocks::{BlockHeader, ChainHeader}, + chain_storage::{async_db::AsyncBlockchainDb, LMDBDatabase}, consensus::ConsensusManager, mempool::service::LocalMempoolService, proof_of_work::PowAlgorithm, @@ -77,6 +77,7 @@ pub enum StatusOutput { pub struct CommandHandler { executor: runtime::Handle, config: Arc, + consensus_rules: ConsensusManager, blockchain_db: AsyncBlockchainDb, discovery_service: DhtDiscoveryRequester, dht_metrics_collector: MetricsCollectorHandle, @@ -96,6 +97,7 @@ impl CommandHandler { Self { executor, config: ctx.config(), + consensus_rules: ctx.consensus_rules().clone(), blockchain_db: ctx.blockchain_db().into(), discovery_service: ctx.base_node_dht().discovery_service_requester(), dht_metrics_collector: ctx.base_node_dht().metrics_collector(), @@ -120,6 +122,7 @@ impl CommandHandler { let mut metrics = self.dht_metrics_collector.clone(); let mut rpc_server = self.rpc_server.clone(); let config = self.config.clone(); + let consensus_rules = self.consensus_rules.clone(); self.executor.spawn(async move { let mut status_line = StatusLine::new(); @@ -145,6 +148,7 @@ impl CommandHandler { ), ); + let constants = consensus_rules.consensus_constants(metadata.height_of_longest_chain()); let mempool_stats = mempool.get_mempool_stats().await.unwrap(); status_line.add_field( "Mempool", @@ -155,7 +159,7 @@ impl CommandHandler { if mempool_stats.total_weight == 0 { 0 } else { - 1 + mempool_stats.total_weight / 19500 + 1 + mempool_stats.total_weight / constants.get_max_block_transaction_weight() }, ), ); @@ -1000,7 +1004,7 @@ impl CommandHandler { pow_algo: Option, ) { let db = self.blockchain_db.clone(); - let network = self.config.network; + let consensus_rules = self.consensus_rules.clone(); self.executor.spawn(async move { let mut output = try_or_print!(File::create(&filename)); @@ -1016,7 +1020,6 @@ impl CommandHandler { let start_height = cmp::max(start_height, 1); let mut prev_header = try_or_print!(db.fetch_chain_header(start_height - 1).await); - let consensus_rules = ConsensusManager::builder(network).build(); writeln!( output, diff --git a/applications/tari_base_node/src/grpc/blocks.rs b/applications/tari_base_node/src/grpc/blocks.rs index 0fe5a735c9..2baa015dd2 100644 --- a/applications/tari_base_node/src/grpc/blocks.rs +++ b/applications/tari_base_node/src/grpc/blocks.rs @@ -21,7 +21,10 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use std::cmp; -use tari_core::{base_node::LocalNodeCommsInterface, blocks::BlockHeader, chain_storage::HistoricalBlock}; +use tari_core::{ + base_node::LocalNodeCommsInterface, + blocks::{BlockHeader, HistoricalBlock}, +}; use tonic::Status; // The maximum number of blocks that can be requested at a time. These will be streamed to the diff --git a/applications/tari_console_wallet/src/ui/app.rs b/applications/tari_console_wallet/src/ui/app.rs index 3cc3a80075..01ab0bf701 100644 --- a/applications/tari_console_wallet/src/ui/app.rs +++ b/applications/tari_console_wallet/src/ui/app.rs @@ -85,7 +85,7 @@ impl App { let tabs = TabsContainer::::new(title.clone()) .add("Transactions".into(), Box::new(TransactionsTab::new())) - .add("Send".into(), Box::new(SendTab::new())) + .add("Send".into(), Box::new(SendTab::new(&app_state))) .add("Receive".into(), Box::new(ReceiveTab::new())) .add("Network".into(), Box::new(NetworkTab::new(base_node_selected))) .add("Log".into(), Box::new(LogTab::new())) diff --git a/applications/tari_console_wallet/src/ui/components/send_tab.rs b/applications/tari_console_wallet/src/ui/components/send_tab.rs index df98d2f6fd..d731fd4788 100644 --- a/applications/tari_console_wallet/src/ui/components/send_tab.rs +++ b/applications/tari_console_wallet/src/ui/components/send_tab.rs @@ -8,7 +8,6 @@ use crate::{ utils::formatting::display_compressed_string, }; use tari_core::transactions::tari_amount::MicroTari; -use tari_wallet::types::DEFAULT_FEE_PER_GRAM; use tokio::{runtime::Handle, sync::watch}; use tui::{ backend::Backend, @@ -40,19 +39,19 @@ pub struct SendTab { } impl SendTab { - pub fn new() -> Self { + pub fn new(app_state: &AppState) -> Self { Self { balance: Balance::new(), send_input_mode: SendInputMode::None, edit_contact_mode: ContactInputMode::None, show_contacts: false, show_edit_contact: false, - to_field: "".to_string(), - amount_field: "".to_string(), - fee_field: u64::from(DEFAULT_FEE_PER_GRAM).to_string(), - message_field: "".to_string(), - alias_field: "".to_string(), - public_key_field: "".to_string(), + to_field: String::new(), + amount_field: String::new(), + fee_field: app_state.get_default_fee_per_gram().as_u64().to_string(), + message_field: String::new(), + alias_field: String::new(), + public_key_field: String::new(), error_message: None, success_message: None, contacts_list_state: WindowedListState::new(), @@ -368,7 +367,7 @@ impl SendTab { if reset_fields { self.to_field = "".to_string(); self.amount_field = "".to_string(); - self.fee_field = u64::from(DEFAULT_FEE_PER_GRAM).to_string(); + self.fee_field = app_state.get_default_fee_per_gram().as_u64().to_string(); self.message_field = "".to_string(); self.send_input_mode = SendInputMode::None; self.send_result_watch = Some(rx); diff --git a/applications/tari_console_wallet/src/ui/state/app_state.rs b/applications/tari_console_wallet/src/ui/state/app_state.rs index 8bd616df4d..52522d0b1d 100644 --- a/applications/tari_console_wallet/src/ui/state/app_state.rs +++ b/applications/tari_console_wallet/src/ui/state/app_state.rs @@ -50,7 +50,10 @@ use tari_comms::{ types::CommsPublicKey, NodeIdentity, }; -use tari_core::transactions::tari_amount::{uT, MicroTari}; +use tari_core::transactions::{ + tari_amount::{uT, MicroTari}, + weight::TransactionWeight, +}; use tari_shutdown::ShutdownSignal; use tari_wallet::{ base_node_service::{handle::BaseNodeEventReceiver, service::BaseNodeState}, @@ -460,6 +463,19 @@ impl AppState { self.update_cache().await; } } + + pub fn get_default_fee_per_gram(&self) -> MicroTari { + use Network::*; + // TODO: TBD + match self.node_config.network { + MainNet => MicroTari(5), + LocalNet => MicroTari(5), + Ridcully => MicroTari(25), + Stibbons => MicroTari(25), + Weatherwax => MicroTari(25), + Igor => MicroTari(5), + } + } } pub struct AppStateInner { updated: bool, @@ -495,6 +511,16 @@ impl AppStateInner { } } + pub fn get_transaction_weight(&self) -> TransactionWeight { + *self + .wallet + .network + .create_consensus_constants() + .last() + .unwrap() + .transaction_weight() + } + pub async fn refresh_full_transaction_state(&mut self) -> Result<(), UiError> { let mut pending_transactions: Vec = Vec::new(); pending_transactions.extend( @@ -521,7 +547,7 @@ impl AppStateInner { }); self.data.pending_txs = pending_transactions .iter() - .map(|tx| CompletedTransactionInfo::from(tx.clone())) + .map(|tx| CompletedTransactionInfo::from_completed_transaction(tx.clone(), &self.get_transaction_weight())) .collect(); let mut completed_transactions: Vec = Vec::new(); @@ -553,7 +579,7 @@ impl AppStateInner { self.data.completed_txs = completed_transactions .iter() - .map(|tx| CompletedTransactionInfo::from(tx.clone())) + .map(|tx| CompletedTransactionInfo::from_completed_transaction(tx.clone(), &self.get_transaction_weight())) .collect(); self.updated = true; Ok(()) @@ -596,7 +622,8 @@ impl AppStateInner { }); }, Some(tx) => { - let tx = CompletedTransactionInfo::from(CompletedTransaction::from(tx)); + let tx = + CompletedTransactionInfo::from_completed_transaction(tx.into(), &self.get_transaction_weight()); if let Some(index) = self.data.pending_txs.iter().position(|i| i.tx_id == tx_id) { if tx.status == TransactionStatus::Pending && !tx.cancelled { self.data.pending_txs[index] = tx; @@ -860,42 +887,42 @@ pub struct CompletedTransactionInfo { pub outputs_count: usize, } -impl From for CompletedTransactionInfo { - fn from(completed_transaction: CompletedTransaction) -> Self { - let excess_signature = if completed_transaction.transaction.body.kernels().is_empty() { - "".to_string() - } else { - completed_transaction.transaction.body.kernels()[0] - .excess_sig - .get_signature() - .to_hex() - }; - +impl CompletedTransactionInfo { + pub fn from_completed_transaction(tx: CompletedTransaction, transaction_weighting: &TransactionWeight) -> Self { + let excess_signature = tx + .transaction + .first_kernel_excess_sig() + .map(|s| s.get_signature().to_hex()) + .unwrap_or_default(); + let is_coinbase = tx.is_coinbase(); + let weight = tx.transaction.calculate_weight(transaction_weighting); + let inputs_count = tx.transaction.body.inputs().len(); + let outputs_count = tx.transaction.body.outputs().len(); Self { - tx_id: completed_transaction.tx_id, - source_public_key: completed_transaction.source_public_key.clone(), - destination_public_key: completed_transaction.destination_public_key.clone(), - amount: completed_transaction.amount, - fee: completed_transaction.fee, + tx_id: tx.tx_id, + source_public_key: tx.source_public_key.clone(), + destination_public_key: tx.destination_public_key.clone(), + amount: tx.amount, + fee: tx.fee, excess_signature, - maturity: completed_transaction + maturity: tx .transaction .body .outputs() .first() .map(|o| o.features.maturity) - .unwrap_or_else(|| 0), - status: completed_transaction.status.clone(), - message: completed_transaction.message.clone(), - timestamp: completed_transaction.timestamp, - cancelled: completed_transaction.cancelled, - direction: completed_transaction.direction.clone(), - valid: completed_transaction.valid, - mined_height: completed_transaction.mined_height, - is_coinbase: completed_transaction.is_coinbase(), - weight: completed_transaction.transaction.calculate_weight(), - inputs_count: completed_transaction.transaction.body.inputs().len(), - outputs_count: completed_transaction.transaction.body.outputs().len(), + .unwrap_or(0), + status: tx.status, + message: tx.message, + timestamp: tx.timestamp, + cancelled: tx.cancelled, + direction: tx.direction, + valid: tx.valid, + mined_height: tx.mined_height, + is_coinbase, + weight, + inputs_count, + outputs_count, } } } diff --git a/applications/test_faucet/src/main.rs b/applications/test_faucet/src/main.rs index 65f75a4b5c..3b463d7d22 100644 --- a/applications/test_faucet/src/main.rs +++ b/applications/test_faucet/src/main.rs @@ -14,8 +14,8 @@ use tokio::{sync::mpsc, task}; use tari_common_types::types::{Commitment, PrivateKey}; use tari_core::transactions::{ - helpers, tari_amount::{MicroTari, T}, + test_helpers, transaction::{KernelFeatures, OutputFeatures, TransactionKernel, TransactionOutput}, CryptoFactories, }; @@ -65,7 +65,7 @@ async fn main() -> Result<(), Box> { task::spawn(async move { let result = task::spawn_blocking(move || { let script = script!(Nop); - let (utxo, key, _) = helpers::create_utxo(value, &fc, feature, &script); + let (utxo, key, _) = test_helpers::create_utxo(value, &fc, feature, &script); print!("."); (utxo, key, value) }) @@ -110,7 +110,7 @@ async fn write_keys(mut rx: mpsc::Receiver<(TransactionOutput, PrivateKey, Micro Err(e) => println!("{}", e.to_string()), } } - let (pk, sig) = helpers::create_random_signature_from_s_key(key_sum, 0.into(), 0); + let (pk, sig) = test_helpers::create_random_signature_from_s_key(key_sum, 0.into(), 0); let excess = Commitment::from_public_key(&pk); let kernel = TransactionKernel { features: KernelFeatures::empty(), diff --git a/base_layer/core/Cargo.toml b/base_layer/core/Cargo.toml index b0217ecca8..77257bf83a 100644 --- a/base_layer/core/Cargo.toml +++ b/base_layer/core/Cargo.toml @@ -35,37 +35,38 @@ async-trait = "0.1.50" bincode = "1.1.4" bitflags = "1.0.4" blake2 = "^0.9.0" -sha3 = "0.9" bytes = "0.5" chrono = { version = "0.4.6", features = ["serde"] } croaring = { version = "=0.4.5", optional = true } decimal-rs = "0.1.20" +derive_more = "0.99.16" digest = "0.9.0" -futures = { version = "^0.3.16", features = ["async-await"] } fs2 = "0.3.0" +futures = { version = "^0.3.16", features = ["async-await"] } hex = "0.4.2" +integer-encoding = "3.0.2" lazy_static = "1.4.0" lmdb-zero = "0.4.4" log = "0.4" monero = { version = "^0.13.0", features = ["serde_support"], optional = true } newtype-ops = "0.1.4" num = "0.3" +num-format = "0.4.0" prost = "0.8.0" prost-types = "0.8.0" rand = "0.8" randomx-rs = { version = "1.1.9", optional = true } serde = { version = "1.0.106", features = ["derive"] } serde_json = "1.0" +sha3 = "0.9" strum_macros = "0.17.1" thiserror = "1.0.26" tokio = { version = "1.11", features = ["time", "sync", "macros"] } -ttl_cache = "0.5.1" -uint = { version = "0.9", default-features = false } -num-format = "0.4.0" tracing = "0.1.26" -tracing-futures = "*" tracing-attributes = "*" -derive_more = "0.99.16" +tracing-futures = "*" +ttl_cache = "0.5.1" +uint = { version = "0.9", default-features = false } [dev-dependencies] tari_p2p = { version = "^0.11", path = "../../base_layer/p2p", features = ["test-mocks"] } diff --git a/base_layer/core/src/base_node/comms_interface/comms_response.rs b/base_layer/core/src/base_node/comms_interface/comms_response.rs index 8f7ec1b9e5..1d0fa7f53d 100644 --- a/base_layer/core/src/base_node/comms_interface/comms_response.rs +++ b/base_layer/core/src/base_node/comms_interface/comms_response.rs @@ -21,8 +21,7 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use crate::{ - blocks::{block_header::BlockHeader, Block, NewBlockTemplate}, - chain_storage::HistoricalBlock, + blocks::{Block, BlockHeader, HistoricalBlock, NewBlockTemplate}, proof_of_work::Difficulty, transactions::transaction::{TransactionKernel, TransactionOutput}, }; diff --git a/base_layer/core/src/base_node/comms_interface/error.rs b/base_layer/core/src/base_node/comms_interface/error.rs index b50f34b758..b42eca6277 100644 --- a/base_layer/core/src/base_node/comms_interface/error.rs +++ b/base_layer/core/src/base_node/comms_interface/error.rs @@ -21,7 +21,7 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use crate::{ - blocks::BlockHeaderValidationError, + blocks::{BlockError, BlockHeaderValidationError}, chain_storage::ChainStorageError, consensus::ConsensusManagerError, mempool::MempoolError, @@ -62,4 +62,6 @@ pub enum CommsInterfaceError { ApiError(String), #[error("Header not found at {0}")] BlockHeaderNotFound(u64), + #[error("Block error: {0}")] + BlockError(#[from] BlockError), } diff --git a/base_layer/core/src/base_node/comms_interface/inbound_handlers.rs b/base_layer/core/src/base_node/comms_interface/inbound_handlers.rs index f760c5146c..84e0da17a3 100644 --- a/base_layer/core/src/base_node/comms_interface/inbound_handlers.rs +++ b/base_layer/core/src/base_node/comms_interface/inbound_handlers.rs @@ -29,8 +29,8 @@ use crate::{ }, OutboundNodeCommsInterface, }, - blocks::{block_header::BlockHeader, Block, NewBlock, NewBlockTemplate}, - chain_storage::{async_db::AsyncBlockchainDb, BlockAddResult, BlockchainBackend, ChainBlock, PrunedOutput}, + blocks::{Block, BlockHeader, ChainBlock, NewBlock, NewBlockTemplate}, + chain_storage::{async_db::AsyncBlockchainDb, BlockAddResult, BlockchainBackend, PrunedOutput}, consensus::{ConsensusConstants, ConsensusManager}, mempool::{async_mempool, Mempool}, proof_of_work::{Difficulty, PowAlgorithm}, diff --git a/base_layer/core/src/base_node/comms_interface/local_interface.rs b/base_layer/core/src/base_node/comms_interface/local_interface.rs index 0a270a78e2..b5ec655633 100644 --- a/base_layer/core/src/base_node/comms_interface/local_interface.rs +++ b/base_layer/core/src/base_node/comms_interface/local_interface.rs @@ -28,8 +28,7 @@ use crate::{ NodeCommsRequest, NodeCommsResponse, }, - blocks::{Block, BlockHeader, NewBlockTemplate}, - chain_storage::HistoricalBlock, + blocks::{Block, BlockHeader, HistoricalBlock, NewBlockTemplate}, proof_of_work::PowAlgorithm, transactions::transaction::TransactionKernel, }; diff --git a/base_layer/core/src/base_node/comms_interface/outbound_interface.rs b/base_layer/core/src/base_node/comms_interface/outbound_interface.rs index 433b5325de..7f8a9d0c33 100644 --- a/base_layer/core/src/base_node/comms_interface/outbound_interface.rs +++ b/base_layer/core/src/base_node/comms_interface/outbound_interface.rs @@ -22,8 +22,7 @@ use crate::{ base_node::comms_interface::{error::CommsInterfaceError, NodeCommsRequest, NodeCommsResponse}, - blocks::{block_header::BlockHeader, NewBlock}, - chain_storage::HistoricalBlock, + blocks::{BlockHeader, HistoricalBlock, NewBlock}, transactions::transaction::TransactionOutput, }; use log::*; diff --git a/base_layer/core/src/base_node/proto/response.rs b/base_layer/core/src/base_node/proto/response.rs index ef88be684f..459bed3b11 100644 --- a/base_layer/core/src/base_node/proto/response.rs +++ b/base_layer/core/src/base_node/proto/response.rs @@ -23,8 +23,7 @@ pub use crate::proto::base_node::base_node_service_response::Response as ProtoNodeCommsResponse; use crate::{ base_node::comms_interface as ci, - blocks::BlockHeader, - chain_storage::HistoricalBlock, + blocks::{BlockHeader, HistoricalBlock}, proof_of_work::Difficulty, proto, proto::{ diff --git a/base_layer/core/src/base_node/sync/block_sync/synchronizer.rs b/base_layer/core/src/base_node/sync/block_sync/synchronizer.rs index b47e8b8945..d31866a872 100644 --- a/base_layer/core/src/base_node/sync/block_sync/synchronizer.rs +++ b/base_layer/core/src/base_node/sync/block_sync/synchronizer.rs @@ -26,8 +26,8 @@ use crate::{ sync::{hooks::Hooks, rpc}, BlockSyncConfig, }, - blocks::Block, - chain_storage::{async_db::AsyncBlockchainDb, BlockchainBackend, ChainBlock}, + blocks::{Block, ChainBlock}, + chain_storage::{async_db::AsyncBlockchainDb, BlockchainBackend}, proto::base_node::SyncBlocksRequest, tari_utilities::{hex::Hex, Hashable}, transactions::aggregated_body::AggregateBody, diff --git a/base_layer/core/src/base_node/sync/header_sync/error.rs b/base_layer/core/src/base_node/sync/header_sync/error.rs index fc39debac4..22e99905bd 100644 --- a/base_layer/core/src/base_node/sync/header_sync/error.rs +++ b/base_layer/core/src/base_node/sync/header_sync/error.rs @@ -20,7 +20,7 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::{chain_storage::ChainStorageError, validation::ValidationError}; +use crate::{blocks::BlockError, chain_storage::ChainStorageError, validation::ValidationError}; use tari_comms::{ connectivity::ConnectivityError, peer_manager::NodeId, @@ -63,4 +63,7 @@ pub enum BlockHeaderSyncError { InvalidProtocolResponse(String), #[error("Headers did not form a chain. Expected {actual} to equal the previous hash {expected}")] ChainLinkBroken { actual: String, expected: String }, + + #[error("Block error: {0}")] + BlockError(#[from] BlockError), } diff --git a/base_layer/core/src/base_node/sync/header_sync/synchronizer.rs b/base_layer/core/src/base_node/sync/header_sync/synchronizer.rs index b75124cabf..b54ed291cd 100644 --- a/base_layer/core/src/base_node/sync/header_sync/synchronizer.rs +++ b/base_layer/core/src/base_node/sync/header_sync/synchronizer.rs @@ -23,8 +23,8 @@ use super::{validator::BlockHeaderSyncValidator, BlockHeaderSyncError}; use crate::{ base_node::sync::{hooks::Hooks, rpc, BlockSyncConfig}, - blocks::BlockHeader, - chain_storage::{async_db::AsyncBlockchainDb, BlockchainBackend, ChainBlock, ChainHeader}, + blocks::{BlockHeader, ChainBlock, ChainHeader}, + chain_storage::{async_db::AsyncBlockchainDb, BlockchainBackend}, consensus::ConsensusManager, proof_of_work::randomx_factory::RandomXFactory, proto::{ diff --git a/base_layer/core/src/base_node/sync/header_sync/validator.rs b/base_layer/core/src/base_node/sync/header_sync/validator.rs index a591f831b0..29155886ba 100644 --- a/base_layer/core/src/base_node/sync/header_sync/validator.rs +++ b/base_layer/core/src/base_node/sync/header_sync/validator.rs @@ -22,15 +22,8 @@ use crate::{ base_node::sync::BlockHeaderSyncError, - blocks::BlockHeader, - chain_storage::{ - async_db::AsyncBlockchainDb, - BlockHeaderAccumulatedData, - BlockchainBackend, - ChainHeader, - ChainStorageError, - TargetDifficulties, - }, + blocks::{BlockHeader, BlockHeaderAccumulatedData, ChainHeader}, + chain_storage::{async_db::AsyncBlockchainDb, BlockchainBackend, ChainStorageError, TargetDifficulties}, common::rolling_vec::RollingVec, consensus::ConsensusManager, proof_of_work::{randomx_factory::RandomXFactory, PowAlgorithm}, @@ -237,8 +230,8 @@ impl BlockHeaderSyncValidator { mod test { use super::*; use crate::{ - blocks::BlockHeader, - chain_storage::{async_db::AsyncBlockchainDb, BlockHeaderAccumulatedData}, + blocks::{BlockHeader, BlockHeaderAccumulatedData}, + chain_storage::async_db::AsyncBlockchainDb, consensus::ConsensusManager, crypto::tari_utilities::{hex::Hex, Hashable}, proof_of_work::{randomx_factory::RandomXFactory, PowAlgorithm}, diff --git a/base_layer/core/src/base_node/sync/hooks.rs b/base_layer/core/src/base_node/sync/hooks.rs index d1e2628822..debe6a273c 100644 --- a/base_layer/core/src/base_node/sync/hooks.rs +++ b/base_layer/core/src/base_node/sync/hooks.rs @@ -22,7 +22,7 @@ #![allow(clippy::type_complexity)] -use crate::chain_storage::ChainBlock; +use crate::blocks::ChainBlock; use std::sync::Arc; use tari_comms::peer_manager::NodeId; diff --git a/base_layer/core/src/chain_storage/accumulated_data.rs b/base_layer/core/src/blocks/accumulated_data.rs similarity index 94% rename from base_layer/core/src/chain_storage/accumulated_data.rs rename to base_layer/core/src/blocks/accumulated_data.rs index c7eef86bd0..c14f494c2e 100644 --- a/base_layer/core/src/chain_storage/accumulated_data.rs +++ b/base_layer/core/src/blocks/accumulated_data.rs @@ -1,4 +1,4 @@ -// Copyright 2020, The Tari Project +// Copyright 2021, The Tari Project // // Redistribution and use in source and binary forms, with or without modification, are permitted provided that the // following conditions are met: @@ -21,8 +21,7 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use crate::{ - blocks::{Block, BlockHeader}, - chain_storage::ChainStorageError, + blocks::{error::BlockError, Block, BlockHeader}, proof_of_work::{AchievedTargetDifficulty, Difficulty, PowAlgorithm}, tari_utilities::Hashable, transactions::aggregated_body::AggregateBody, @@ -52,11 +51,11 @@ const LOG_TARGET: &str = "c::bn::acc_data"; #[derive(Debug, Serialize, Deserialize)] pub struct BlockAccumulatedData { - pub(super) kernels: PrunedHashSet, - pub(super) outputs: PrunedHashSet, - pub(super) deleted: DeletedBitmap, - pub(super) range_proofs: PrunedHashSet, - pub(super) kernel_sum: Commitment, + pub(crate) kernels: PrunedHashSet, + pub(crate) outputs: PrunedHashSet, + pub(crate) deleted: DeletedBitmap, + pub(crate) range_proofs: PrunedHashSet, + pub(crate) kernel_sum: Commitment, } impl BlockAccumulatedData { @@ -131,7 +130,7 @@ impl DeletedBitmap { &self.deleted } - pub(super) fn bitmap_mut(&mut self) -> &mut Bitmap { + pub(crate) fn bitmap_mut(&mut self) -> &mut Bitmap { &mut self.deleted } } @@ -267,20 +266,19 @@ impl BlockHeaderAccumulatedDataBuilder<'_> { self } - pub fn build(self) -> Result { + pub fn build(self) -> Result { let previous_accum = self.previous_accum; - let hash = self - .hash - .ok_or_else(|| ChainStorageError::InvalidOperation("hash not provided".to_string()))?; + let hash = self.hash.ok_or(BlockError::BuilderMissingField { field: "hash" })?; if hash == previous_accum.hash { - return Err(ChainStorageError::InvalidOperation( - "Hash was set to the same hash that is contained in previous accumulated data".to_string(), - )); + return Err(BlockError::BuilderInvalidValue { + field: "hash", + details: "Hash was set to the same hash that is contained in previous accumulated data".to_string(), + }); } - let achieved_target = self.current_achieved_target.ok_or_else(|| { - ChainStorageError::InvalidOperation("Current achieved difficulty not provided".to_string()) + let achieved_target = self.current_achieved_target.ok_or(BlockError::BuilderMissingField { + field: "Current achieved difficulty", })?; let (monero_diff, blake_diff) = match achieved_target.pow_algo() { @@ -297,7 +295,9 @@ impl BlockHeaderAccumulatedDataBuilder<'_> { let total_kernel_offset = self .current_total_kernel_offset .map(|offset| &previous_accum.total_kernel_offset + offset) - .ok_or_else(|| ChainStorageError::InvalidOperation("total_kernel_offset not provided".to_string()))?; + .ok_or(BlockError::BuilderMissingField { + field: "total_kernel_offset", + })?; let result = BlockHeaderAccumulatedData { hash, diff --git a/base_layer/core/src/blocks/block.rs b/base_layer/core/src/blocks/block.rs index 15c4d9c8ef..2c6457566e 100644 --- a/base_layer/core/src/blocks/block.rs +++ b/base_layer/core/src/blocks/block.rs @@ -23,21 +23,8 @@ // Portions of this file were originally copyrighted (c) 2018 The Grin Developers, issued under the Apache License, // Version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0. -use std::{ - fmt, - fmt::{Display, Formatter}, -}; - -use log::*; -use serde::{Deserialize, Serialize}; -use tari_crypto::tari_utilities::Hashable; -use thiserror::Error; - -use tari_common_types::types::BlockHash; - use crate::{ blocks::BlockHeader, - chain_storage::MmrTree, consensus::ConsensusConstants, proof_of_work::ProofOfWork, tari_utilities::hex::Hex, @@ -48,6 +35,15 @@ use crate::{ CryptoFactories, }, }; +use log::*; +use serde::{Deserialize, Serialize}; +use std::{ + fmt, + fmt::{Display, Formatter}, +}; +use tari_common_types::types::BlockHash; +use tari_crypto::tari_utilities::Hashable; +use thiserror::Error; #[derive(Clone, Debug, PartialEq, Error)] pub enum BlockValidationError { @@ -61,7 +57,7 @@ pub enum BlockValidationError { MismatchedMmrRoots, #[error("MMR size for {mmr_tree} does not match. Expected: {expected}, received: {actual}")] MismatchedMmrSize { - mmr_tree: MmrTree, + mmr_tree: String, expected: u64, actual: u64, }, diff --git a/base_layer/core/src/blocks/error.rs b/base_layer/core/src/blocks/error.rs new file mode 100644 index 0000000000..acc9650e23 --- /dev/null +++ b/base_layer/core/src/blocks/error.rs @@ -0,0 +1,33 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#[derive(Debug, thiserror::Error)] +pub enum BlockError { + #[error("{field} not provided")] + BuilderMissingField { field: &'static str }, + #[error("{field} contained an invalid value: {details}")] + BuilderInvalidValue { field: &'static str, details: String }, + #[error("A full block cannot be constructed from the historical block because it contains pruned TXOs")] + HistoricalBlockContainsPrunedTxos, + #[error("Chain block invariant error: {0}")] + ChainBlockInvariantError(String), +} diff --git a/base_layer/core/src/blocks/genesis_block.rs b/base_layer/core/src/blocks/genesis_block.rs index 9f03977a4b..d892ba41ff 100644 --- a/base_layer/core/src/blocks/genesis_block.rs +++ b/base_layer/core/src/blocks/genesis_block.rs @@ -22,12 +22,8 @@ // This file is used to store the genesis block use crate::{ - blocks::{block::Block, BlockHeader}, + blocks::{block::Block, BlockHeader, BlockHeaderAccumulatedData, ChainBlock}, proof_of_work::{PowAlgorithm, ProofOfWork}, -}; - -use crate::{ - chain_storage::{BlockHeaderAccumulatedData, ChainBlock}, transactions::{ aggregated_body::AggregateBody, tari_amount::MicroTari, @@ -36,12 +32,26 @@ use crate::{ }; use chrono::DateTime; use std::sync::Arc; +use tari_common::configuration::Network; use tari_common_types::types::{BulletRangeProof, Commitment, PrivateKey, PublicKey, Signature, BLOCK_HASH_LENGTH}; use tari_crypto::{ script::TariScript, tari_utilities::{hash::Hashable, hex::*}, }; +/// Returns the genesis block for the selected network. +pub fn get_genesis_block(network: Network) -> ChainBlock { + use Network::*; + match network { + MainNet => get_mainnet_genesis_block(), + Ridcully => get_ridcully_genesis_block(), + Stibbons => get_stibbons_genesis_block(), + Weatherwax => get_weatherwax_genesis_block(), + LocalNet => get_weatherwax_genesis_block(), + Igor => get_igor_genesis_block(), + } +} + pub fn get_mainnet_genesis_block() -> ChainBlock { unimplemented!() } diff --git a/base_layer/core/src/chain_storage/historical_block.rs b/base_layer/core/src/blocks/historical_block.rs similarity index 59% rename from base_layer/core/src/chain_storage/historical_block.rs rename to base_layer/core/src/blocks/historical_block.rs index 99fd45335f..4d8de7223e 100644 --- a/base_layer/core/src/chain_storage/historical_block.rs +++ b/base_layer/core/src/blocks/historical_block.rs @@ -1,29 +1,26 @@ -// Copyright 2019. The Tari Project +// Copyright 2021, The Tari Project // -// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -// following conditions are met: +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: // -// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following -// disclaimer. +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. // -// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the -// following disclaimer in the documentation and/or other materials provided with the distribution. +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. // -// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote -// products derived from this software without specific prior written permission. +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. // -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, -// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -use crate::{ - blocks::{Block, BlockHeader}, - chain_storage::{BlockHeaderAccumulatedData, ChainBlock, ChainStorageError}, -}; +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::blocks::{error::BlockError, Block, BlockHeader, BlockHeaderAccumulatedData, ChainBlock}; use serde::{Deserialize, Serialize}; use std::{fmt, fmt::Display, sync::Arc}; use tari_common_types::types::HashOutput; @@ -82,21 +79,23 @@ impl HistoricalBlock { !self.pruned_outputs.is_empty() || self.pruned_input_count > 0 } - pub fn try_into_block(self) -> Result { + pub fn try_into_block(self) -> Result { if self.contains_pruned_txos() { - Err(ChainStorageError::HistoricalBlockContainsPrunedTxos) + Err(BlockError::HistoricalBlockContainsPrunedTxos) } else { Ok(self.block) } } - pub fn try_into_chain_block(self) -> Result { + pub fn try_into_chain_block(self) -> Result { if self.contains_pruned_txos() { - return Err(ChainStorageError::HistoricalBlockContainsPrunedTxos); + return Err(BlockError::HistoricalBlockContainsPrunedTxos); } let chain_block = ChainBlock::try_construct(Arc::new(self.block), self.accumulated_data).ok_or_else(|| { - ChainStorageError::InvalidOperation("Unable to construct ChainBlock because of a hash mismatch".to_string()) + BlockError::ChainBlockInvariantError( + "Unable to construct ChainBlock because of a hash mismatch".to_string(), + ) })?; Ok(chain_block) diff --git a/base_layer/core/src/blocks/mod.rs b/base_layer/core/src/blocks/mod.rs index 509d91f405..19a49c3071 100644 --- a/base_layer/core/src/blocks/mod.rs +++ b/base_layer/core/src/blocks/mod.rs @@ -21,21 +21,42 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #[cfg(feature = "base_node")] +mod accumulated_data; +#[cfg(feature = "base_node")] +pub use accumulated_data::{ + BlockAccumulatedData, + BlockHeaderAccumulatedData, + ChainBlock, + ChainHeader, + CompleteDeletedBitmap, + DeletedBitmap, +}; + +mod error; +pub use error::BlockError; + mod block; +pub use block::{Block, BlockBuilder, BlockValidationError, NewBlock}; + #[cfg(any(feature = "base_node", feature = "base_node_proto"))] -pub mod block_header; +mod block_header; +#[cfg(any(feature = "base_node", feature = "base_node_proto"))] +pub use block_header::{BlockHeader, BlockHeaderValidationError}; #[cfg(feature = "base_node")] pub mod genesis_block; + #[cfg(feature = "base_node")] -mod new_block_template; +mod historical_block; #[cfg(feature = "base_node")] -mod new_blockheader_template; +pub use historical_block::HistoricalBlock; + #[cfg(feature = "base_node")] -pub use block::{Block, BlockBuilder, BlockValidationError, NewBlock}; -#[cfg(any(feature = "base_node", feature = "base_node_proto"))] -pub use block_header::{BlockHeader, BlockHeaderValidationError}; +mod new_block_template; #[cfg(feature = "base_node")] pub use new_block_template::NewBlockTemplate; + +#[cfg(feature = "base_node")] +mod new_blockheader_template; #[cfg(feature = "base_node")] pub use new_blockheader_template::NewBlockHeaderTemplate; diff --git a/base_layer/core/src/chain_storage/async_db.rs b/base_layer/core/src/chain_storage/async_db.rs index 7ec8c153e2..0c1e743842 100644 --- a/base_layer/core/src/chain_storage/async_db.rs +++ b/base_layer/core/src/chain_storage/async_db.rs @@ -21,24 +21,28 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use crate::{ - blocks::{Block, BlockHeader, NewBlockTemplate}, + blocks::{ + Block, + BlockAccumulatedData, + BlockHeader, + BlockHeaderAccumulatedData, + ChainBlock, + ChainHeader, + CompleteDeletedBitmap, + DeletedBitmap, + HistoricalBlock, + NewBlockTemplate, + }, chain_storage::{ - accumulated_data::BlockHeaderAccumulatedData, blockchain_database::MmrRoots, utxo_mined_info::UtxoMinedInfo, - BlockAccumulatedData, BlockAddResult, BlockchainBackend, BlockchainDatabase, - ChainBlock, - ChainHeader, ChainStorageError, - CompleteDeletedBitmap, DbBasicStats, DbTotalSizeStats, DbTransaction, - DeletedBitmap, - HistoricalBlock, HorizonData, MmrTree, PrunedOutput, diff --git a/base_layer/core/src/chain_storage/block_add_result.rs b/base_layer/core/src/chain_storage/block_add_result.rs index 4c9a783f17..ed49f9da49 100644 --- a/base_layer/core/src/chain_storage/block_add_result.rs +++ b/base_layer/core/src/chain_storage/block_add_result.rs @@ -20,7 +20,7 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::chain_storage::ChainBlock; +use crate::blocks::ChainBlock; use std::{fmt, sync::Arc}; use tari_crypto::tari_utilities::hex::Hex; diff --git a/base_layer/core/src/chain_storage/blockchain_backend.rs b/base_layer/core/src/chain_storage/blockchain_backend.rs index 86794ab4d3..8c4612806a 100644 --- a/base_layer/core/src/chain_storage/blockchain_backend.rs +++ b/base_layer/core/src/chain_storage/blockchain_backend.rs @@ -1,13 +1,15 @@ use crate::{ - blocks::{Block, BlockHeader}, - chain_storage::{ - accumulated_data::DeletedBitmap, - pruned_output::PrunedOutput, - utxo_mined_info::UtxoMinedInfo, + blocks::{ + Block, BlockAccumulatedData, + BlockHeader, BlockHeaderAccumulatedData, ChainBlock, ChainHeader, + DeletedBitmap, + }, + chain_storage::{ + pruned_output::PrunedOutput, ChainStorageError, DbBasicStats, DbKey, @@ -16,6 +18,7 @@ use crate::{ DbValue, HorizonData, MmrTree, + UtxoMinedInfo, }, transactions::transaction::{TransactionInput, TransactionKernel}, }; diff --git a/base_layer/core/src/chain_storage/blockchain_database.rs b/base_layer/core/src/chain_storage/blockchain_database.rs index 34a4cce5da..7dac26615f 100644 --- a/base_layer/core/src/chain_storage/blockchain_database.rs +++ b/base_layer/core/src/chain_storage/blockchain_database.rs @@ -20,9 +20,19 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use crate::{ - blocks::{Block, BlockHeader, NewBlockTemplate}, + blocks::{ + Block, + BlockAccumulatedData, + BlockHeader, + BlockHeaderAccumulatedData, + ChainBlock, + ChainHeader, + CompleteDeletedBitmap, + DeletedBitmap, + HistoricalBlock, + NewBlockTemplate, + }, chain_storage::{ - accumulated_data::{BlockAccumulatedData, BlockHeaderAccumulatedData, CompleteDeletedBitmap}, consts::{ BLOCKCHAIN_DATABASE_ORPHAN_STORAGE_CAPACITY, BLOCKCHAIN_DATABASE_PRUNED_MODE_PRUNING_INTERVAL, @@ -34,12 +44,8 @@ use crate::{ utxo_mined_info::UtxoMinedInfo, BlockAddResult, BlockchainBackend, - ChainBlock, - ChainHeader, DbBasicStats, DbTotalSizeStats, - DeletedBitmap, - HistoricalBlock, HorizonData, MmrTree, Optional, diff --git a/base_layer/core/src/chain_storage/db_transaction.rs b/base_layer/core/src/chain_storage/db_transaction.rs index e3bd5fc645..970988d655 100644 --- a/base_layer/core/src/chain_storage/db_transaction.rs +++ b/base_layer/core/src/chain_storage/db_transaction.rs @@ -20,8 +20,8 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use crate::{ - blocks::{Block, BlockHeader}, - chain_storage::{error::ChainStorageError, BlockHeaderAccumulatedData, ChainBlock, ChainHeader, MmrTree}, + blocks::{Block, BlockHeader, BlockHeaderAccumulatedData, ChainBlock, ChainHeader}, + chain_storage::{error::ChainStorageError, MmrTree}, transactions::transaction::{TransactionKernel, TransactionOutput}, }; use croaring::Bitmap; diff --git a/base_layer/core/src/chain_storage/error.rs b/base_layer/core/src/chain_storage/error.rs index ac9208025e..14cbd7a53f 100644 --- a/base_layer/core/src/chain_storage/error.rs +++ b/base_layer/core/src/chain_storage/error.rs @@ -20,7 +20,7 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::{chain_storage::MmrTree, proof_of_work::PowError, validation::ValidationError}; +use crate::{blocks::BlockError, chain_storage::MmrTree, proof_of_work::PowError, validation::ValidationError}; use lmdb_zero::error; use tari_mmr::{error::MerkleMountainRangeError, MerkleProofError}; use tari_storage::lmdb_store::LMDBError; @@ -51,8 +51,6 @@ pub enum ChainStorageError { DataInconsistencyDetected { function: &'static str, details: String }, #[error("There appears to be a critical error on the back end: {0}. Check the logs for more information.")] CriticalError(String), - #[error("A full block cannot be constructed from the historical block because it contains pruned TXOs")] - HistoricalBlockContainsPrunedTxos, #[error("Could not insert {table}: {error}")] InsertError { table: &'static str, error: String }, #[error("An invalid query was attempted: {0}")] @@ -116,6 +114,8 @@ pub enum ChainStorageError { DbTransactionTooLarge(usize), #[error("DB needs to be resynced: {0}")] DatabaseResyncRequired(&'static str), + #[error("Block error: {0}")] + BlockError(#[from] BlockError), } impl ChainStorageError { diff --git a/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs b/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs index 1bae3c9ab2..a4e3d4cea8 100644 --- a/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs +++ b/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs @@ -25,9 +25,16 @@ #![allow(clippy::ptr_arg)] use crate::{ - blocks::{block_header::BlockHeader, Block}, + blocks::{ + Block, + BlockAccumulatedData, + BlockHeader, + BlockHeaderAccumulatedData, + ChainBlock, + ChainHeader, + DeletedBitmap, + }, chain_storage::{ - accumulated_data::{BlockAccumulatedData, BlockHeaderAccumulatedData, DeletedBitmap}, db_transaction::{DbKey, DbTransaction, DbValue, WriteOperation}, error::{ChainStorageError, OrNotFound}, lmdb_db::{ @@ -55,8 +62,6 @@ use crate::{ stats::DbTotalSizeStats, utxo_mined_info::UtxoMinedInfo, BlockchainBackend, - ChainBlock, - ChainHeader, DbBasicStats, DbSize, HorizonData, diff --git a/base_layer/core/src/chain_storage/mod.rs b/base_layer/core/src/chain_storage/mod.rs index 6e616e9640..204e0a7092 100644 --- a/base_layer/core/src/chain_storage/mod.rs +++ b/base_layer/core/src/chain_storage/mod.rs @@ -29,17 +29,6 @@ #[cfg(test)] mod tests; -mod accumulated_data; -pub use accumulated_data::{ - BlockAccumulatedData, - BlockHeaderAccumulatedData, - BlockHeaderAccumulatedDataBuilder, - ChainBlock, - ChainHeader, - CompleteDeletedBitmap, - DeletedBitmap, -}; - pub mod async_db; mod block_add_result; @@ -70,9 +59,6 @@ pub use mmr_tree::*; mod error; pub use error::{ChainStorageError, Optional, OrNotFound}; -mod historical_block; -pub use historical_block::HistoricalBlock; - mod horizon_data; pub use horizon_data::HorizonData; diff --git a/base_layer/core/src/chain_storage/tests/blockchain_database.rs b/base_layer/core/src/chain_storage/tests/blockchain_database.rs index f5cb1b9802..b7716373b8 100644 --- a/base_layer/core/src/chain_storage/tests/blockchain_database.rs +++ b/base_layer/core/src/chain_storage/tests/blockchain_database.rs @@ -355,8 +355,8 @@ mod add_block { chain_storage::ChainStorageError, crypto::tari_utilities::hex::Hex, transactions::{ - helpers::{schema_to_transaction, TransactionSchema}, tari_amount::T, + test_helpers::{schema_to_transaction, TransactionSchema}, transaction::OutputFeatures, }, txn_schema, @@ -383,7 +383,7 @@ mod add_block { from: vec![outputs[1].clone()], to: vec![], to_outputs: vec![prev_utxo.clone()], - fee: 25.into(), + fee: 5.into(), lock_height: 0, features: Default::default(), script: tari_crypto::script![Nop], @@ -408,7 +408,7 @@ mod add_block { from: vec![outputs[1].clone()], to: vec![], to_outputs: vec![prev_utxo], - fee: 25.into(), + fee: 5.into(), lock_height: 0, features: Default::default(), script: tari_crypto::script![Nop], diff --git a/base_layer/core/src/common/byte_counter.rs b/base_layer/core/src/common/byte_counter.rs new file mode 100644 index 0000000000..44b2ca084d --- /dev/null +++ b/base_layer/core/src/common/byte_counter.rs @@ -0,0 +1,50 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::io; + +#[derive(Debug, Clone, Default)] +pub struct ByteCounter { + count: usize, +} + +impl ByteCounter { + pub fn new() -> Self { + Default::default() + } + + pub fn get(&self) -> usize { + self.count + } +} + +impl io::Write for ByteCounter { + fn write(&mut self, buf: &[u8]) -> io::Result { + let len = buf.len(); + self.count += len; + Ok(len) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} diff --git a/base_layer/core/src/common/mod.rs b/base_layer/core/src/common/mod.rs index 67261433b8..4da315a60e 100644 --- a/base_layer/core/src/common/mod.rs +++ b/base_layer/core/src/common/mod.rs @@ -20,5 +20,6 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +pub mod byte_counter; #[cfg(feature = "base_node")] pub mod rolling_vec; diff --git a/base_layer/core/src/consensus/chain_strength_comparer.rs b/base_layer/core/src/consensus/chain_strength_comparer.rs index 54b5e4ae75..00bcb5521b 100644 --- a/base_layer/core/src/consensus/chain_strength_comparer.rs +++ b/base_layer/core/src/consensus/chain_strength_comparer.rs @@ -1,4 +1,4 @@ -use crate::chain_storage::ChainHeader; +use crate::blocks::ChainHeader; use std::{cmp::Ordering, fmt::Debug}; pub trait ChainStrengthComparer: Debug { diff --git a/base_layer/core/src/consensus/consensus_constants.rs b/base_layer/core/src/consensus/consensus_constants.rs index e06c1f6ee2..40fc48480a 100644 --- a/base_layer/core/src/consensus/consensus_constants.rs +++ b/base_layer/core/src/consensus/consensus_constants.rs @@ -21,9 +21,12 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use crate::{ - consensus::{network::NetworkConsensus, KERNEL_WEIGHT, WEIGHT_PER_OUTPUT}, + consensus::network::NetworkConsensus, proof_of_work::{Difficulty, PowAlgorithm}, - transactions::tari_amount::{uT, MicroTari, T}, + transactions::{ + tari_amount::{uT, MicroTari, T}, + weight::TransactionWeight, + }, }; use chrono::{DateTime, Duration, Utc}; use std::{collections::HashMap, ops::Add}; @@ -62,6 +65,8 @@ pub struct ConsensusConstants { proof_of_work: HashMap, /// This is to keep track of the value inside of the genesis block faucet_value: MicroTari, + /// Transaction Weight params + transaction_weight: TransactionWeight, } /// This is just a convenience wrapper to put all the info into a hashmap per diff algo @@ -127,7 +132,11 @@ impl ConsensusConstants { /// Maximum transaction weight used for the construction of new blocks. It leaves place for 1 kernel and 1 output pub fn get_max_block_weight_excluding_coinbase(&self) -> u64 { - self.max_block_transaction_weight - WEIGHT_PER_OUTPUT - KERNEL_WEIGHT + self.max_block_transaction_weight - self.coinbase_weight() + } + + pub fn coinbase_weight(&self) -> u64 { + self.transaction_weight.calculate(1, 0, 1, 0) } /// The amount of PoW algorithms used by the Tari chain. @@ -178,11 +187,15 @@ impl ConsensusConstants { } } - // This is the maximum age a monero merge mined seed can be reused + /// The maximum age a monero merge mined seed can be reused pub fn max_randomx_seed_height(&self) -> u64 { self.max_randomx_seed_height } + pub fn transaction_weight(&self) -> &TransactionWeight { + &self.transaction_weight + } + pub fn localnet() -> Vec { let difficulty_block_window = 90; let mut algos = HashMap::new(); @@ -212,6 +225,7 @@ impl ConsensusConstants { max_randomx_seed_height: u64::MAX, proof_of_work: algos, faucet_value: (5000 * 4000) * T, + transaction_weight: TransactionWeight::v2(), }] } @@ -245,6 +259,7 @@ impl ConsensusConstants { max_randomx_seed_height: u64::MAX, proof_of_work: algos, faucet_value: (5000 * 4000) * T, + transaction_weight: TransactionWeight::v1(), }] } @@ -305,6 +320,7 @@ impl ConsensusConstants { max_randomx_seed_height: u64::MAX, proof_of_work: algos, faucet_value: (5000 * 4000) * T, + transaction_weight: TransactionWeight::v1(), }, ConsensusConstants { effective_from_height: 1400, @@ -320,6 +336,7 @@ impl ConsensusConstants { max_randomx_seed_height: u64::MAX, proof_of_work: algos2, faucet_value: (5000 * 4000) * T, + transaction_weight: TransactionWeight::v1(), }, ] } @@ -353,6 +370,7 @@ impl ConsensusConstants { max_randomx_seed_height: u64::MAX, proof_of_work: algos, faucet_value: (5000 * 4000) * T, + transaction_weight: TransactionWeight::v1(), }] } @@ -377,7 +395,10 @@ impl ConsensusConstants { blockchain_version: 2, future_time_limit: 540, difficulty_block_window: 90, - max_block_transaction_weight: 19500, + // 65536 = target_block_size / bytes_per_gram = (1024*1024) / 16 + // adj. + 95% = 127,795 - this effectively targets ~2Mb blocks closely matching the previous 19500 + // weightings + max_block_transaction_weight: 127_795, median_timestamp_count: 11, emission_initial: 5_538_846_115 * uT, emission_decay: &EMISSION_DECAY, @@ -385,6 +406,7 @@ impl ConsensusConstants { max_randomx_seed_height: u64::MAX, proof_of_work: algos, faucet_value: (5000 * 4000) * T, + transaction_weight: TransactionWeight::v2(), }] } @@ -418,6 +440,7 @@ impl ConsensusConstants { max_randomx_seed_height: u64::MAX, proof_of_work: algos, faucet_value: MicroTari::from(0), + transaction_weight: TransactionWeight::v2(), }] } } diff --git a/base_layer/core/src/consensus/consensus_encoding.rs b/base_layer/core/src/consensus/consensus_encoding.rs new file mode 100644 index 0000000000..9cbbd8b67e --- /dev/null +++ b/base_layer/core/src/consensus/consensus_encoding.rs @@ -0,0 +1,78 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::io; + +/// Abstracts the ability of a type to canonically encode itself for the purposes of consensus +pub trait ConsensusEncoding { + /// Encode to the given writer returning the number of bytes writter. + /// If writing to this Writer is infallible, this implementation must always succeed. + fn consensus_encode(&self, writer: &mut W) -> Result; +} + +pub trait ConsensusEncodingSized: ConsensusEncoding { + /// The return value MUST be the exact byte size of the implementing type + /// and SHOULD be implemented without allocations. + fn consensus_encode_exact_size(&self) -> usize; +} + +/// Abstracts the ability of a type to be decoded from canonical consensus bytes +pub trait ConsensusDecoding: Sized { + /// Attempt to decode this type from the given reader + fn consensus_decode(reader: &mut R) -> Result; +} + +pub struct ConsensusEncodingWrapper<'a, T> { + inner: &'a T, +} + +impl<'a, T> ConsensusEncodingWrapper<'a, T> { + pub fn wrap(inner: &'a T) -> Self { + Self { inner } + } +} + +// TODO: move traits and implement consensus encoding for TariScript +// for now, this wrapper will do that job +mod tariscript_impl { + use super::*; + use crate::common::byte_counter::ByteCounter; + use tari_crypto::script::TariScript; + + impl<'a> ConsensusEncoding for ConsensusEncodingWrapper<'a, TariScript> { + fn consensus_encode(&self, writer: &mut W) -> Result { + let bytes = self.inner.as_bytes(); + writer.write_all(&bytes)?; + Ok(bytes.len()) + } + } + + impl<'a> ConsensusEncodingSized for ConsensusEncodingWrapper<'a, TariScript> { + fn consensus_encode_exact_size(&self) -> usize { + let mut counter = ByteCounter::new(); + // TODO: consensus_encode_exact_size must be cheap to run + // unreachable panic: ByteCounter is infallible + self.consensus_encode(&mut counter).expect("unreachable"); + counter.get() + } + } +} diff --git a/base_layer/core/src/consensus/consensus_manager.rs b/base_layer/core/src/consensus/consensus_manager.rs index 3c653ea15b..925bfbe0bd 100644 --- a/base_layer/core/src/consensus/consensus_manager.rs +++ b/base_layer/core/src/consensus/consensus_manager.rs @@ -20,25 +20,24 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#[cfg(feature = "base_node")] +use crate::{ + blocks::ChainBlock, + consensus::chain_strength_comparer::{strongest_chain, ChainStrengthComparer}, + proof_of_work::PowAlgorithm, + proof_of_work::TargetDifficultyWindow, +}; + use crate::{ - blocks::genesis_block::{ - get_igor_genesis_block, - get_mainnet_genesis_block, - get_ridcully_genesis_block, - get_stibbons_genesis_block, - get_weatherwax_genesis_block, - }, - chain_storage::{ChainBlock, ChainStorageError}, consensus::{ - chain_strength_comparer::{strongest_chain, ChainStrengthComparer}, emission::{Emission, EmissionSchedule}, ConsensusConstants, NetworkConsensus, }, - proof_of_work::{DifficultyAdjustmentError, PowAlgorithm, TargetDifficultyWindow}, + proof_of_work::DifficultyAdjustmentError, transactions::{tari_amount::MicroTari, transaction::TransactionKernel}, }; -use std::{convert::TryFrom, sync::Arc}; +use std::sync::Arc; use tari_common::configuration::Network; use thiserror::Error; @@ -47,8 +46,6 @@ use thiserror::Error; pub enum ConsensusManagerError { #[error("Difficulty adjustment encountered an error: `{0}`")] DifficultyAdjustmentError(#[from] DifficultyAdjustmentError), - #[error("Problem with the DB backend storage: `{0}`")] - ChainStorageError(#[from] ChainStorageError), #[error("There is no blockchain to query")] EmptyBlockchain, #[error("RwLock access broken: `{0}`")] @@ -69,18 +66,17 @@ impl ConsensusManager { } /// Returns the genesis block for the selected network. + #[cfg(feature = "base_node")] pub fn get_genesis_block(&self) -> ChainBlock { - match self.inner.network.as_network() { - Network::MainNet => get_mainnet_genesis_block(), - Network::Ridcully => get_ridcully_genesis_block(), - Network::Stibbons => get_stibbons_genesis_block(), - Network::Weatherwax => get_weatherwax_genesis_block(), + use crate::blocks::genesis_block::get_genesis_block; + let network = self.inner.network.as_network(); + match network { Network::LocalNet => self .inner .gen_block .clone() - .unwrap_or_else(get_weatherwax_genesis_block), - Network::Igor => get_igor_genesis_block(), + .unwrap_or_else(|| get_genesis_block(network)), + _ => get_genesis_block(network), } } @@ -115,7 +111,9 @@ impl ConsensusManager { /// Create a new TargetDifficulty for the given proof of work using constants that are effective from the given /// height + #[cfg(feature = "base_node")] pub(crate) fn new_target_difficulty(&self, pow_algo: PowAlgorithm, height: u64) -> TargetDifficultyWindow { + use std::convert::TryFrom; let constants = self.consensus_constants(height); let block_window = constants.get_difficulty_block_window(); @@ -132,6 +130,7 @@ impl ConsensusManager { kernels.iter().fold(coinbase, |total, k| total + k.fee) } + #[cfg(feature = "base_node")] pub fn chain_strength_comparer(&self) -> &dyn ChainStrengthComparer { self.inner.chain_strength_comparer.as_ref() } @@ -152,7 +151,9 @@ struct ConsensusManagerInner { /// The configuration for the emission schedule for integer only. pub emission: EmissionSchedule, /// This allows the user to set a custom Genesis block + #[cfg(feature = "base_node")] pub gen_block: Option, + #[cfg(feature = "base_node")] /// The comparer used to determine which chain is stronger for reorgs. pub chain_strength_comparer: Box, } @@ -161,7 +162,9 @@ struct ConsensusManagerInner { pub struct ConsensusManagerBuilder { consensus_constants: Vec, network: NetworkConsensus, + #[cfg(feature = "base_node")] gen_block: Option, + #[cfg(feature = "base_node")] chain_strength_comparer: Option>, } @@ -171,7 +174,9 @@ impl ConsensusManagerBuilder { ConsensusManagerBuilder { consensus_constants: vec![], network: network.into(), + #[cfg(feature = "base_node")] gen_block: None, + #[cfg(feature = "base_node")] chain_strength_comparer: None, } } @@ -183,11 +188,13 @@ impl ConsensusManagerBuilder { } /// Adds in a custom block to be used. This will be overwritten if the network is anything else than localnet + #[cfg(feature = "base_node")] pub fn with_block(mut self, block: ChainBlock) -> Self { self.gen_block = Some(block); self } + #[cfg(feature = "base_node")] pub fn on_ties(mut self, chain_strength_comparer: Box) -> Self { self.chain_strength_comparer = Some(chain_strength_comparer); self @@ -209,7 +216,9 @@ impl ConsensusManagerBuilder { consensus_constants: self.consensus_constants, network: self.network, emission, + #[cfg(feature = "base_node")] gen_block: self.gen_block, + #[cfg(feature = "base_node")] chain_strength_comparer: self.chain_strength_comparer.unwrap_or_else(|| { strongest_chain() .by_accumulated_difficulty() diff --git a/base_layer/core/src/consensus/mod.rs b/base_layer/core/src/consensus/mod.rs index f0432fbf29..e1a2e95e84 100644 --- a/base_layer/core/src/consensus/mod.rs +++ b/base_layer/core/src/consensus/mod.rs @@ -22,25 +22,17 @@ #[cfg(feature = "base_node")] pub(crate) mod chain_strength_comparer; -#[cfg(any(feature = "base_node", feature = "transactions"))] + pub mod consensus_constants; -#[cfg(feature = "base_node")] +pub use consensus_constants::{ConsensusConstants, ConsensusConstantsBuilder}; + mod consensus_manager; -#[cfg(any(feature = "base_node", feature = "transactions"))] -pub mod emission; -#[cfg(any(feature = "base_node", feature = "transactions"))] -mod network; +pub use consensus_manager::{ConsensusManager, ConsensusManagerBuilder, ConsensusManagerError}; -#[cfg(any(feature = "base_node", feature = "transactions"))] -pub const WEIGHT_PER_INPUT: u64 = 1; -#[cfg(any(feature = "base_node", feature = "transactions"))] -pub const WEIGHT_PER_OUTPUT: u64 = 13; -#[cfg(any(feature = "base_node", feature = "transactions"))] -pub const KERNEL_WEIGHT: u64 = 3; // Constant weight per transaction; covers kernel and part of header. +mod consensus_encoding; +pub use consensus_encoding::{ConsensusDecoding, ConsensusEncoding, ConsensusEncodingSized, ConsensusEncodingWrapper}; -#[cfg(any(feature = "base_node", feature = "transactions"))] -pub use consensus_constants::{ConsensusConstants, ConsensusConstantsBuilder}; -#[cfg(feature = "base_node")] -pub use consensus_manager::{ConsensusManager, ConsensusManagerBuilder, ConsensusManagerError}; -#[cfg(any(feature = "base_node", feature = "transactions"))] +mod network; pub use network::NetworkConsensus; + +pub mod emission; diff --git a/base_layer/core/src/lib.rs b/base_layer/core/src/lib.rs index 8d6b4fc3b5..1522d8420b 100644 --- a/base_layer/core/src/lib.rs +++ b/base_layer/core/src/lib.rs @@ -31,15 +31,12 @@ #[macro_use] extern crate bitflags; -#[cfg(any(feature = "base_node", feature = "base_node_proto"))] pub mod blocks; #[cfg(feature = "base_node")] pub mod chain_storage; -#[cfg(any(feature = "base_node", feature = "transactions"))] pub mod consensus; #[cfg(feature = "base_node")] pub mod iterators; -#[cfg(any(feature = "base_node", feature = "transactions"))] pub mod proof_of_work; #[cfg(feature = "base_node")] pub mod validation; diff --git a/base_layer/core/src/mempool/mempool.rs b/base_layer/core/src/mempool/mempool.rs index 865ca7b980..444bf491b6 100644 --- a/base_layer/core/src/mempool/mempool.rs +++ b/base_layer/core/src/mempool/mempool.rs @@ -22,6 +22,7 @@ use crate::{ blocks::Block, + consensus::ConsensusManager, mempool::{ error::MempoolError, mempool_storage::MempoolStorage, @@ -46,9 +47,13 @@ pub struct Mempool { impl Mempool { /// Create a new Mempool with an UnconfirmedPool, OrphanPool, PendingPool and ReOrgPool. - pub fn new(config: MempoolConfig, validator: Arc) -> Self { + pub fn new( + config: MempoolConfig, + rules: ConsensusManager, + validator: Arc, + ) -> Self { Self { - pool_storage: Arc::new(RwLock::new(MempoolStorage::new(config, validator))), + pool_storage: Arc::new(RwLock::new(MempoolStorage::new(config, rules, validator))), } } @@ -111,7 +116,7 @@ impl Mempool { /// Gathers and returns the stats of the Mempool. pub fn stats(&self) -> Result { self.pool_storage - .read() + .write() .map_err(|e| MempoolError::BackendError(e.to_string()))? .stats() } @@ -119,7 +124,7 @@ impl Mempool { /// Gathers and returns a breakdown of all the transaction in the Mempool. pub fn state(&self) -> Result { self.pool_storage - .read() + .write() .map_err(|e| MempoolError::BackendError(e.to_string()))? .state() } diff --git a/base_layer/core/src/mempool/mempool_storage.rs b/base_layer/core/src/mempool/mempool_storage.rs index 87d09c0695..a50e25a7ae 100644 --- a/base_layer/core/src/mempool/mempool_storage.rs +++ b/base_layer/core/src/mempool/mempool_storage.rs @@ -22,6 +22,7 @@ use crate::{ blocks::Block, + consensus::ConsensusManager, mempool::{ error::MempoolError, reorg_pool::ReorgPool, @@ -31,7 +32,7 @@ use crate::{ StatsResponse, TxStorageResponse, }, - transactions::transaction::Transaction, + transactions::{transaction::Transaction, weight::TransactionWeight}, validation::{MempoolTransactionValidation, ValidationError}, }; use log::*; @@ -48,15 +49,21 @@ pub struct MempoolStorage { unconfirmed_pool: UnconfirmedPool, reorg_pool: ReorgPool, validator: Arc, + rules: ConsensusManager, } impl MempoolStorage { /// Create a new Mempool with an UnconfirmedPool and ReOrgPool. - pub fn new(config: MempoolConfig, validators: Arc) -> Self { + pub fn new( + config: MempoolConfig, + rules: ConsensusManager, + validator: Arc, + ) -> Self { Self { unconfirmed_pool: UnconfirmedPool::new(config.unconfirmed_pool), reorg_pool: ReorgPool::new(config.reorg_pool), - validator: validators, + validator, + rules, } } @@ -74,12 +81,14 @@ impl MempoolStorage { ); match self.validator.validate(&tx) { Ok(()) => { - self.unconfirmed_pool.insert(tx, None)?; + let weight = *self.get_transaction_weight(0); + self.unconfirmed_pool.insert(tx, None, &weight)?; Ok(TxStorageResponse::UnconfirmedPool) }, Err(ValidationError::UnknownInputs(dependent_outputs)) => { if self.unconfirmed_pool.verify_outputs_exist(&dependent_outputs) { - self.unconfirmed_pool.insert(tx, Some(dependent_outputs))?; + let weight = *self.get_transaction_weight(0); + self.unconfirmed_pool.insert(tx, Some(dependent_outputs), &weight)?; Ok(TxStorageResponse::UnconfirmedPool) } else { warn!(target: LOG_TARGET, "Validation failed due to unknown inputs"); @@ -101,6 +110,10 @@ impl MempoolStorage { } } + fn get_transaction_weight(&self, height: u64) -> &TransactionWeight { + self.rules.consensus_constants(height).transaction_weight() + } + // Insert a set of new transactions into the UTxPool. fn insert_txs(&mut self, txs: Vec>) -> Result<(), MempoolError> { for tx in txs { @@ -155,10 +168,10 @@ impl MempoolStorage { let removed_txs = self.unconfirmed_pool.drain_all_mempool_transactions(); self.insert_txs(removed_txs)?; // Remove re-orged transactions from reorg pool and re-submit them to the unconfirmed mempool - self.insert_txs( - self.reorg_pool - .remove_reorged_txs_and_discard_double_spends(removed_blocks, &new_blocks)?, - )?; + let removed_txs = self + .reorg_pool + .remove_reorged_txs_and_discard_double_spends(removed_blocks, &new_blocks)?; + self.insert_txs(removed_txs)?; // Update the Mempool based on the received set of new blocks. for block in new_blocks { self.process_published_block(block)?; @@ -220,17 +233,18 @@ impl MempoolStorage { } /// Gathers and returns the stats of the Mempool. - pub fn stats(&self) -> Result { + pub fn stats(&mut self) -> Result { + let weight = *self.get_transaction_weight(0); Ok(StatsResponse { total_txs: self.len()?, unconfirmed_txs: self.unconfirmed_pool.len(), reorg_txs: self.reorg_pool.len()?, - total_weight: self.unconfirmed_pool.calculate_weight(), + total_weight: self.unconfirmed_pool.calculate_weight(&weight), }) } /// Gathers and returns a breakdown of all the transaction in the Mempool. - pub fn state(&self) -> Result { + pub fn state(&mut self) -> Result { let unconfirmed_pool = self .unconfirmed_pool .snapshot() diff --git a/base_layer/core/src/mempool/mod.rs b/base_layer/core/src/mempool/mod.rs index 52af3df5f6..4e12849f71 100644 --- a/base_layer/core/src/mempool/mod.rs +++ b/base_layer/core/src/mempool/mod.rs @@ -108,14 +108,15 @@ impl Display for StateResponse { fmt.write_str("--- Unconfirmed Pool ---\n")?; for tx in &self.unconfirmed_pool { fmt.write_str(&format!( - " {} Fee:{}, Outputs:{}, Kernels:{}, Inputs:{}\n", + " {} Fee: {}, Outputs: {}, Kernels: {}, Inputs: {}, metadata: {} bytes\n", tx.first_kernel_excess_sig() .map(|sig| sig.get_signature().to_hex()) .unwrap_or_else(|| "N/A".to_string()), tx.body.get_total_fee(), tx.body.outputs().len(), tx.body.kernels().len(), - tx.body.inputs().len() + tx.body.inputs().len(), + tx.body.sum_metadata_size() ))?; } fmt.write_str("--- Reorg Pool ---\n")?; diff --git a/base_layer/core/src/mempool/priority/mod.rs b/base_layer/core/src/mempool/priority/mod.rs index cbcaf3a75f..5e8ef8fd1a 100644 --- a/base_layer/core/src/mempool/priority/mod.rs +++ b/base_layer/core/src/mempool/priority/mod.rs @@ -21,10 +21,7 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. mod error; -mod prioritized_transaction; -mod timelocked_transaction; - -// Public re-exports pub use error::PriorityError; + +mod prioritized_transaction; pub use prioritized_transaction::{FeePriority, PrioritizedTransaction}; -pub use timelocked_transaction::{TimelockPriority, TimelockedTransaction}; diff --git a/base_layer/core/src/mempool/priority/prioritized_transaction.rs b/base_layer/core/src/mempool/priority/prioritized_transaction.rs index cc82531461..7051db9858 100644 --- a/base_layer/core/src/mempool/priority/prioritized_transaction.rs +++ b/base_layer/core/src/mempool/priority/prioritized_transaction.rs @@ -20,7 +20,10 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::{mempool::priority::PriorityError, transactions::transaction::Transaction}; +use crate::{ + mempool::priority::PriorityError, + transactions::{transaction::Transaction, weight::TransactionWeight}, +}; use std::sync::Arc; use tari_common_types::types::HashOutput; use tari_crypto::tari_utilities::message_format::MessageFormat; @@ -32,13 +35,13 @@ use tari_crypto::tari_utilities::message_format::MessageFormat; pub struct FeePriority(Vec); impl FeePriority { - pub fn try_from(transaction: &Transaction) -> Result { + pub fn try_construct(transaction: &Transaction, weight: u64) -> Result { // The weights have been normalised, so the fee priority is now equal to the fee per gram ± a few pct points - let fee_per_byte = (transaction.calculate_ave_fee_per_gram() * 1000.0) as usize; // Include 3 decimal places before flooring + let fee_per_byte = ((transaction.body.get_total_fee().as_u64() as f64 / weight as f64) * 1000.0) as usize; // Include 3 decimal places before flooring let mut fee_priority = fee_per_byte.to_binary()?; fee_priority.reverse(); // Requires Big-endian for BtreeMap sorting - let mut maturity_priority = (std::u64::MAX - transaction.min_input_maturity()).to_binary()?; + let mut maturity_priority = (u64::MAX - transaction.min_input_maturity()).to_binary()?; maturity_priority.reverse(); // Requires Big-endian for BtreeMap sorting let mut priority = fee_priority; @@ -64,18 +67,20 @@ pub struct PrioritizedTransaction { } impl PrioritizedTransaction { - pub fn convert_from_transaction( - transaction: Transaction, + pub fn try_construct( + weighting: &TransactionWeight, + transaction: Arc, dependent_outputs: Option>, ) -> Result { let depended_output_hashes = match dependent_outputs { Some(v) => v, None => Vec::new(), }; + let weight = transaction.calculate_weight(weighting); Ok(Self { - priority: FeePriority::try_from(&transaction)?, - weight: transaction.calculate_weight(), - transaction: Arc::new(transaction), + priority: FeePriority::try_construct(&transaction, weight)?, + weight, + transaction, depended_output_hashes, }) } diff --git a/base_layer/core/src/mempool/priority/timelocked_transaction.rs b/base_layer/core/src/mempool/priority/timelocked_transaction.rs deleted file mode 100644 index db01c4904d..0000000000 --- a/base_layer/core/src/mempool/priority/timelocked_transaction.rs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2019 The Tari Project -// -// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -// following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following -// disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the -// following disclaimer in the documentation and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote -// products derived from this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, -// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -use crate::{ - mempool::priority::{FeePriority, PriorityError}, - transactions::transaction::Transaction, -}; -use std::{convert::TryFrom, sync::Arc}; -use tari_crypto::tari_utilities::message_format::MessageFormat; - -/// Create a unique transaction priority based on the maximum time-lock (lock_height or input UTXO maturity) and the -/// excess_sig, allowing transactions to be sorted according to their time-lock expiry. The excess_sig is included to -/// ensure the priority key is unique so it can be used with a BTreeMap. -#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)] -pub struct TimelockPriority(Vec); - -impl TimelockPriority { - pub fn try_from(transaction: &Transaction) -> Result { - let mut priority = transaction.min_spendable_height().to_binary()?; - priority.reverse(); // Requires Big-endian for BtreeMap sorting - priority.append(&mut transaction.body.kernels()[0].excess_sig.to_binary()?); - Ok(Self(priority)) - } -} - -impl Clone for TimelockPriority { - fn clone(&self) -> Self { - TimelockPriority(self.0.clone()) - } -} - -/// A Timelocked prioritized transaction includes a transaction and the calculated FeePriority and TimelockPriority of -/// the transaction. -pub struct TimelockedTransaction { - pub transaction: Arc, - pub fee_priority: FeePriority, - pub timelock_priority: TimelockPriority, - pub max_timelock_height: u64, -} - -impl TryFrom for TimelockedTransaction { - type Error = PriorityError; - - fn try_from(transaction: Transaction) -> Result { - Ok(Self { - fee_priority: FeePriority::try_from(&transaction)?, - timelock_priority: TimelockPriority::try_from(&transaction)?, - max_timelock_height: match transaction.min_spendable_height() { - 0 => 0, - v => v - 1, - }, - transaction: Arc::new(transaction), - }) - } -} diff --git a/base_layer/core/src/mempool/reorg_pool/error.rs b/base_layer/core/src/mempool/reorg_pool/error.rs index c5bbd18302..7fee0de031 100644 --- a/base_layer/core/src/mempool/reorg_pool/error.rs +++ b/base_layer/core/src/mempool/reorg_pool/error.rs @@ -21,7 +21,6 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use thiserror::Error; - #[derive(Debug, Error)] pub enum ReorgPoolError { #[error("A problem has been encountered with the storage backend: `{0}`")] diff --git a/base_layer/core/src/mempool/reorg_pool/reorg_pool.rs b/base_layer/core/src/mempool/reorg_pool/reorg_pool.rs index f7e803e220..f86e04d7ed 100644 --- a/base_layer/core/src/mempool/reorg_pool/reorg_pool.rs +++ b/base_layer/core/src/mempool/reorg_pool/reorg_pool.rs @@ -29,10 +29,7 @@ use crate::{ transactions::transaction::Transaction, }; use serde::{Deserialize, Serialize}; -use std::{ - sync::{Arc, RwLock}, - time::Duration, -}; +use std::{sync::Arc, time::Duration}; use tari_common::configuration::seconds; use tari_common_types::types::Signature; @@ -61,84 +58,56 @@ impl Default for ReorgPoolConfig { /// from the pool when the Time-to-live thresholds is reached. Also, when the capacity of the pool has been reached, the /// oldest transactions will be removed to make space for incoming transactions. pub struct ReorgPool { - pool_storage: Arc>, + pool_storage: ReorgPoolStorage, } impl ReorgPool { /// Create a new ReorgPool with the specified configuration pub fn new(config: ReorgPoolConfig) -> Self { Self { - pool_storage: Arc::new(RwLock::new(ReorgPoolStorage::new(config))), + pool_storage: ReorgPoolStorage::new(config), } } /// Insert a set of new transactions into the ReorgPool. Published transactions will have a limited Time-to-live in /// the ReorgPool and will be discarded once the Time-to-live threshold has been reached. - pub fn insert_txs(&self, transactions: Vec>) -> Result<(), ReorgPoolError> { - self.pool_storage - .write() - .map_err(|e| ReorgPoolError::BackendError(e.to_string()))? - .insert_txs(transactions); + pub fn insert_txs(&mut self, transactions: Vec>) -> Result<(), ReorgPoolError> { + self.pool_storage.insert_txs(transactions); Ok(()) } /// Insert a new transaction into the ReorgPool. Published transactions will have a limited Time-to-live in /// the ReorgPool and will be discarded once the Time-to-live threshold has been reached. - pub fn _insert(&self, transaction: Arc) -> Result<(), ReorgPoolError> { - self.pool_storage - .write() - .map_err(|e| ReorgPoolError::BackendError(e.to_string()))? - .insert(transaction); + pub fn _insert(&mut self, transaction: Arc) -> Result<(), ReorgPoolError> { + self.pool_storage.insert(transaction); Ok(()) } /// Check if a transaction is stored in the ReorgPool pub fn has_tx_with_excess_sig(&self, excess_sig: &Signature) -> Result { - Ok(self - .pool_storage - .read() - .map_err(|e| ReorgPoolError::BackendError(e.to_string()))? - .has_tx_with_excess_sig(excess_sig)) + Ok(self.pool_storage.has_tx_with_excess_sig(excess_sig)) } /// Remove the transactions from the ReorgPool that were used in provided removed blocks. The transactions can be /// resubmitted to the Unconfirmed Pool. pub fn remove_reorged_txs_and_discard_double_spends( - &self, + &mut self, removed_blocks: Vec>, new_blocks: &[Arc], ) -> Result>, ReorgPoolError> { Ok(self .pool_storage - .write() - .map_err(|e| ReorgPoolError::BackendError(e.to_string()))? .remove_reorged_txs_and_discard_double_spends(removed_blocks, new_blocks)) } /// Returns the total number of published transactions stored in the ReorgPool - pub fn len(&self) -> Result { - Ok(self - .pool_storage - .write() - .map_err(|e| ReorgPoolError::BackendError(e.to_string()))? - .len()) + pub fn len(&mut self) -> Result { + Ok(self.pool_storage.len()) } /// Returns all transaction stored in the ReorgPool. - pub fn snapshot(&self) -> Result>, ReorgPoolError> { - Ok(self - .pool_storage - .write() - .map_err(|e| ReorgPoolError::BackendError(e.to_string()))? - .snapshot()) - } -} - -impl Clone for ReorgPool { - fn clone(&self) -> Self { - ReorgPool { - pool_storage: self.pool_storage.clone(), - } + pub fn snapshot(&mut self) -> Result>, ReorgPoolError> { + Ok(self.pool_storage.snapshot()) } } @@ -163,7 +132,7 @@ mod test { let tx5 = Arc::new(tx!(MicroTari(100_000), fee: MicroTari(500), lock: 2000, inputs: 2, outputs: 1).0); let tx6 = Arc::new(tx!(MicroTari(100_000), fee: MicroTari(600), lock: 5500, inputs: 2, outputs: 1).0); - let reorg_pool = ReorgPool::new(ReorgPoolConfig { + let mut reorg_pool = ReorgPool::new(ReorgPoolConfig { storage_capacity: 3, tx_ttl: Duration::from_millis(50), }); @@ -219,7 +188,7 @@ mod test { let tx5 = Arc::new(tx!(MicroTari(10_000), fee: MicroTari(50), lock: 2000, inputs: 2, outputs: 1).0); let tx6 = Arc::new(tx!(MicroTari(10_000), fee: MicroTari(60), lock: 5500, inputs: 2, outputs: 1).0); - let reorg_pool = ReorgPool::new(ReorgPoolConfig { + let mut reorg_pool = ReorgPool::new(ReorgPoolConfig { storage_capacity: 5, tx_ttl: Duration::from_millis(50), }); diff --git a/base_layer/core/src/mempool/sync_protocol/test.rs b/base_layer/core/src/mempool/sync_protocol/test.rs index f5d88d5f40..8f094e635f 100644 --- a/base_layer/core/src/mempool/sync_protocol/test.rs +++ b/base_layer/core/src/mempool/sync_protocol/test.rs @@ -21,17 +21,19 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use crate::{ + consensus::ConsensusManager, mempool::{ async_mempool, proto, sync_protocol::{MempoolPeerProtocol, MempoolSyncProtocol, MAX_FRAME_SIZE, MEMPOOL_SYNC_PROTOCOL}, Mempool, }, - transactions::{helpers::create_tx, tari_amount::uT, transaction::Transaction}, + transactions::{tari_amount::uT, test_helpers::create_tx, transaction::Transaction}, validation::mocks::MockValidator, }; use futures::{Sink, SinkExt, Stream, StreamExt}; use std::{fmt, io, iter::repeat_with, sync::Arc}; +use tari_common::configuration::Network; use tari_comms::{ connectivity::{ConnectivityEvent, ConnectivityEventTx}, framing, @@ -51,7 +53,7 @@ use tokio::{ pub fn create_transactions(n: usize) -> Vec { repeat_with(|| { - let (transaction, _, _) = create_tx(5000 * uT, 15 * uT, 1, 2, 1, 4); + let (transaction, _, _) = create_tx(5000 * uT, 3 * uT, 1, 2, 1, 4); transaction }) .take(n) @@ -59,7 +61,11 @@ pub fn create_transactions(n: usize) -> Vec { } fn new_mempool_with_transactions(n: usize) -> (Mempool, Vec) { - let mempool = Mempool::new(Default::default(), Arc::new(MockValidator::new(true))); + let mempool = Mempool::new( + Default::default(), + ConsensusManager::builder(Network::LocalNet).build(), + Arc::new(MockValidator::new(true)), + ); let transactions = create_transactions(n); for txn in &transactions { diff --git a/base_layer/core/src/mempool/unconfirmed_pool/unconfirmed_pool.rs b/base_layer/core/src/mempool/unconfirmed_pool/unconfirmed_pool.rs index 6b5208672a..a97d1e5773 100644 --- a/base_layer/core/src/mempool/unconfirmed_pool/unconfirmed_pool.rs +++ b/base_layer/core/src/mempool/unconfirmed_pool/unconfirmed_pool.rs @@ -20,15 +20,6 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use std::{ - collections::{BTreeMap, HashMap}, - sync::Arc, -}; - -use log::*; -use serde::{Deserialize, Serialize}; -use tari_crypto::tari_utilities::{hex::Hex, Hashable}; - use crate::{ blocks::Block, mempool::{ @@ -36,9 +27,16 @@ use crate::{ priority::{FeePriority, PrioritizedTransaction}, unconfirmed_pool::UnconfirmedPoolError, }, - transactions::transaction::Transaction, + transactions::{transaction::Transaction, weight::TransactionWeight}, +}; +use log::*; +use serde::{Deserialize, Serialize}; +use std::{ + collections::{BTreeMap, HashMap}, + sync::Arc, }; use tari_common_types::types::{HashOutput, Signature}; +use tari_crypto::tari_utilities::{hex::Hex, Hashable}; pub const LOG_TARGET: &str = "c::mp::unconfirmed_pool::unconfirmed_pool_storage"; @@ -107,40 +105,43 @@ impl UnconfirmedPool { /// Insert a new transaction into the UnconfirmedPool. Low priority transactions will be removed to make space for /// higher priority transactions. The lowest priority transactions will be removed when the maximum capacity is /// reached and the new transaction has a higher priority than the currently stored lowest priority transaction. - #[allow(clippy::map_entry)] pub fn insert( &mut self, tx: Arc, dependent_outputs: Option>, + transaction_weight: &TransactionWeight, ) -> Result<(), UnconfirmedPoolError> { let tx_key = tx .first_kernel_excess_sig() .ok_or(UnconfirmedPoolError::TransactionNoKernels)?; - if !self.txs_by_signature.contains_key(tx_key) { - let prioritized_tx = PrioritizedTransaction::convert_from_transaction((*tx).clone(), dependent_outputs)?; - if self.txs_by_signature.len() >= self.config.storage_capacity { - if prioritized_tx.priority < *self.lowest_priority() { - return Ok(()); - } - self.remove_lowest_priority_tx(); - } - self.txs_by_priority - .insert(prioritized_tx.priority.clone(), tx_key.clone()); - self.txs_by_signature.insert(tx_key.clone(), prioritized_tx); - for output in tx.body.outputs().clone() { - self.txs_by_output - .entry(output.hash()) - .or_default() - .push(tx_key.clone()); - } - debug!( - target: LOG_TARGET, - "Inserted transaction with signature {} into unconfirmed pool:", - tx_key.get_signature().to_hex() - ); - trace!(target: LOG_TARGET, "{}", tx); + if self.txs_by_signature.contains_key(tx_key) { + return Ok(()); } + + let prioritized_tx = PrioritizedTransaction::try_construct(transaction_weight, tx.clone(), dependent_outputs)?; + if self.txs_by_signature.len() >= self.config.storage_capacity { + if prioritized_tx.priority < *self.lowest_priority() { + return Ok(()); + } + self.remove_lowest_priority_tx(); + } + self.txs_by_priority + .insert(prioritized_tx.priority.clone(), tx_key.clone()); + self.txs_by_signature.insert(tx_key.clone(), prioritized_tx); + for output in tx.body.outputs().clone() { + self.txs_by_output + .entry(output.hash()) + .or_default() + .push(tx_key.clone()); + } + debug!( + target: LOG_TARGET, + "Inserted transaction with signature {} into unconfirmed pool:", + tx_key.get_signature().to_hex() + ); + + trace!(target: LOG_TARGET, "insert: {}", tx); Ok(()) } @@ -156,9 +157,13 @@ impl UnconfirmedPool { /// Insert a set of new transactions into the UnconfirmedPool #[cfg(test)] - pub fn insert_txs(&mut self, txs: Vec>) -> Result<(), UnconfirmedPoolError> { + pub fn insert_many>>( + &mut self, + txs: I, + transaction_weight: &TransactionWeight, + ) -> Result<(), UnconfirmedPoolError> { for tx in txs.into_iter() { - self.insert(tx, None)?; + self.insert(tx, None, transaction_weight)?; } Ok(()) } @@ -454,10 +459,10 @@ impl UnconfirmedPool { } /// Returns the total weight of all transactions stored in the pool. - pub fn calculate_weight(&self) -> u64 { - self.txs_by_signature - .iter() - .fold(0, |weight, (_, ptx)| weight + ptx.transaction.calculate_weight()) + pub fn calculate_weight(&self, transaction_weight: &TransactionWeight) -> u64 { + self.txs_by_signature.iter().fold(0, |weight, (_, ptx)| { + weight + ptx.transaction.calculate_weight(transaction_weight) + }) } #[cfg(test)] @@ -474,40 +479,40 @@ impl UnconfirmedPool { #[cfg(test)] mod test { - use tari_common::configuration::Network; - + use super::*; use crate::{ consensus::ConsensusManagerBuilder, - test_helpers::create_orphan_block, + test_helpers::{create_consensus_constants, create_consensus_rules, create_orphan_block}, transactions::{ fee::Fee, - helpers::{TestParams, UtxoTestParams}, tari_amount::MicroTari, + test_helpers::{TestParams, UtxoTestParams}, transaction::KernelFeatures, + weight::TransactionWeight, CryptoFactories, SenderTransactionProtocol, }, tx, }; + use tari_common::configuration::Network; use tari_common_types::types::HashDigest; - use super::*; - #[test] fn test_find_duplicate_input() { let tx1 = Arc::new(tx!(MicroTari(5000), fee: MicroTari(50), inputs: 2, outputs: 1).0); let tx2 = Arc::new(tx!(MicroTari(5000), fee: MicroTari(50), inputs: 2, outputs: 1).0); + let tx_weight = TransactionWeight::latest(); let mut tx_pool = HashMap::new(); let mut tx1_pool = HashMap::new(); let mut tx2_pool = HashMap::new(); tx_pool.insert(tx1.first_kernel_excess_sig().unwrap().clone(), tx1.clone()); tx1_pool.insert( tx1.first_kernel_excess_sig().unwrap().clone(), - PrioritizedTransaction::convert_from_transaction((*tx1).clone(), None).unwrap(), + PrioritizedTransaction::try_construct(&tx_weight, tx1.clone(), None).unwrap(), ); tx2_pool.insert( tx2.first_kernel_excess_sig().unwrap().clone(), - PrioritizedTransaction::convert_from_transaction((*tx2).clone(), None).unwrap(), + PrioritizedTransaction::try_construct(&tx_weight, tx2.clone(), None).unwrap(), ); assert!( UnconfirmedPool::find_duplicate_input(&tx_pool, &tx1_pool), @@ -521,18 +526,23 @@ mod test { #[test] fn test_insert_and_retrieve_highest_priority_txs() { - let tx1 = Arc::new(tx!(MicroTari(5_000), fee: MicroTari(50), inputs: 2, outputs: 1).0); - let tx2 = Arc::new(tx!(MicroTari(5_000), fee: MicroTari(20), inputs: 4, outputs: 1).0); - let tx3 = Arc::new(tx!(MicroTari(5_000), fee: MicroTari(100), inputs: 5, outputs: 1).0); - let tx4 = Arc::new(tx!(MicroTari(5_000), fee: MicroTari(30), inputs: 3, outputs: 1).0); - let tx5 = Arc::new(tx!(MicroTari(5_000), fee: MicroTari(55), inputs: 5, outputs: 1).0); + let tx1 = Arc::new(tx!(MicroTari(5_000), fee: MicroTari(5), inputs: 2, outputs: 1).0); + let tx2 = Arc::new(tx!(MicroTari(5_000), fee: MicroTari(4), inputs: 4, outputs: 1).0); + let tx3 = Arc::new(tx!(MicroTari(5_000), fee: MicroTari(20), inputs: 5, outputs: 1).0); + let tx4 = Arc::new(tx!(MicroTari(5_000), fee: MicroTari(6), inputs: 3, outputs: 1).0); + let tx5 = Arc::new(tx!(MicroTari(5_000), fee: MicroTari(11), inputs: 5, outputs: 1).0); let mut unconfirmed_pool = UnconfirmedPool::new(UnconfirmedPoolConfig { storage_capacity: 4, weight_tx_skip_count: 3, }); + + let tx_weight = TransactionWeight::latest(); unconfirmed_pool - .insert_txs(vec![tx1.clone(), tx2.clone(), tx3.clone(), tx4.clone(), tx5.clone()]) + .insert_many( + [tx1.clone(), tx2.clone(), tx3.clone(), tx4.clone(), tx5.clone()], + &tx_weight, + ) .unwrap(); // Check that lowest priority tx was removed to make room for new incoming transactions assert!(unconfirmed_pool.has_tx_with_excess_sig(&tx1.body.kernels()[0].excess_sig),); @@ -541,7 +551,8 @@ mod test { assert!(unconfirmed_pool.has_tx_with_excess_sig(&tx4.body.kernels()[0].excess_sig),); assert!(unconfirmed_pool.has_tx_with_excess_sig(&tx5.body.kernels()[0].excess_sig),); // Retrieve the set of highest priority unspent transactions - let desired_weight = tx1.calculate_weight() + tx3.calculate_weight() + tx5.calculate_weight(); + let desired_weight = + tx1.calculate_weight(&tx_weight) + tx3.calculate_weight(&tx_weight) + tx5.calculate_weight(&tx_weight); let results = unconfirmed_pool.highest_priority_txs(desired_weight).unwrap(); assert_eq!(results.retrieved_transactions.len(), 3); assert!(results.retrieved_transactions.contains(&tx1)); @@ -561,7 +572,7 @@ mod test { let test_params = TestParams::new(); - let mut stx_builder = SenderTransactionProtocol::builder(0); + let mut stx_builder = SenderTransactionProtocol::builder(0, create_consensus_constants(0)); stx_builder .with_lock_height(0) .with_fee_per_gram(20.into()) @@ -573,7 +584,7 @@ mod test { let double_spend_utxo = tx2.body.inputs().first().unwrap().clone(); let double_spend_input = inputs.first().unwrap().clone(); - let estimated_fee = Fee::calculate(20.into(), 1, 1, 1); + let estimated_fee = Fee::new(TransactionWeight::latest()).calculate(20.into(), 1, 1, 1, 0); let utxo = test_params.create_unblinded_output(UtxoTestParams { value: INPUT_AMOUNT - estimated_fee, @@ -599,12 +610,16 @@ mod test { weight_tx_skip_count: 3, }); + let tx_weight = TransactionWeight::latest(); unconfirmed_pool - .insert_txs(vec![tx1.clone(), tx2.clone(), tx3.clone()]) + .insert_many(vec![tx1.clone(), tx2.clone(), tx3.clone()], &tx_weight) .unwrap(); assert_eq!(unconfirmed_pool.len(), 3); - let desired_weight = tx1.calculate_weight() + tx2.calculate_weight() + tx3.calculate_weight() + 1000; + let desired_weight = tx1.calculate_weight(&tx_weight) + + tx2.calculate_weight(&tx_weight) + + tx3.calculate_weight(&tx_weight) + + 1000; let results = unconfirmed_pool.highest_priority_txs(desired_weight).unwrap(); assert!(results.retrieved_transactions.contains(&tx1)); // Whether tx2 or tx3 is selected is non-deterministic @@ -623,12 +638,16 @@ mod test { let tx5 = Arc::new(tx!(MicroTari(10_000), fee: MicroTari(50), inputs:3, outputs: 1).0); let tx6 = Arc::new(tx!(MicroTari(10_000), fee: MicroTari(75), inputs:2, outputs: 1).0); + let tx_weight = TransactionWeight::latest(); let mut unconfirmed_pool = UnconfirmedPool::new(UnconfirmedPoolConfig { storage_capacity: 10, weight_tx_skip_count: 3, }); unconfirmed_pool - .insert_txs(vec![tx1.clone(), tx2.clone(), tx3.clone(), tx4.clone(), tx5.clone()]) + .insert_many( + vec![tx1.clone(), tx2.clone(), tx3.clone(), tx4.clone(), tx5.clone()], + &tx_weight, + ) .unwrap(); // utx6 should not be added to unconfirmed_pool as it is an unknown transactions that was included in the block // by another node @@ -656,33 +675,36 @@ mod test { #[test] fn test_discard_double_spend_txs() { - let network = Network::LocalNet; - let consensus = ConsensusManagerBuilder::new(network).build(); - let tx1 = Arc::new(tx!(MicroTari(5_000), fee: MicroTari(50), inputs:2, outputs:1).0); - let tx2 = Arc::new(tx!(MicroTari(5_000), fee: MicroTari(20), inputs:3, outputs:1).0); - let tx3 = Arc::new(tx!(MicroTari(5_000), fee: MicroTari(100), inputs:2, outputs:1).0); - let tx4 = Arc::new(tx!(MicroTari(5_000), fee: MicroTari(30), inputs:2, outputs:1).0); - let mut tx5 = tx!(MicroTari(5_000), fee:MicroTari(50), inputs:3, outputs:1).0; - let mut tx6 = tx!(MicroTari(5_000), fee:MicroTari(75), inputs: 2, outputs: 1).0; + let consensus = create_consensus_rules(); + let tx1 = Arc::new(tx!(MicroTari(5_000), fee: MicroTari(5), inputs:2, outputs:1).0); + let tx2 = Arc::new(tx!(MicroTari(5_000), fee: MicroTari(4), inputs:3, outputs:1).0); + let tx3 = Arc::new(tx!(MicroTari(5_000), fee: MicroTari(20), inputs:2, outputs:1).0); + let tx4 = Arc::new(tx!(MicroTari(5_000), fee: MicroTari(6), inputs:2, outputs:1).0); + let mut tx5 = tx!(MicroTari(5_000), fee:MicroTari(5), inputs:3, outputs:1).0; + let mut tx6 = tx!(MicroTari(5_000), fee:MicroTari(13), inputs: 2, outputs: 1).0; // tx1 and tx5 have a shared input. Also, tx3 and tx6 have a shared input tx5.body.inputs_mut()[0] = tx1.body.inputs()[0].clone(); tx6.body.inputs_mut()[1] = tx3.body.inputs()[1].clone(); let tx5 = Arc::new(tx5); let tx6 = Arc::new(tx6); + let tx_weight = TransactionWeight::latest(); let mut unconfirmed_pool = UnconfirmedPool::new(UnconfirmedPoolConfig { storage_capacity: 10, weight_tx_skip_count: 3, }); unconfirmed_pool - .insert_txs(vec![ - tx1.clone(), - tx2.clone(), - tx3.clone(), - tx4.clone(), - tx5.clone(), - tx6.clone(), - ]) + .insert_many( + vec![ + tx1.clone(), + tx2.clone(), + tx3.clone(), + tx4.clone(), + tx5.clone(), + tx6.clone(), + ], + &tx_weight, + ) .unwrap(); // The publishing of tx1 and tx3 will be double-spends and orphan tx5 and tx6 @@ -714,6 +736,8 @@ mod test { tx4.body.set_kernel(tx6.body.kernels()[0].clone()); // Insert multiple transactions with the same outputs into the mempool + + let tx_weight = TransactionWeight::latest(); let mut unconfirmed_pool = UnconfirmedPool::new(UnconfirmedPoolConfig { storage_capacity: 10, weight_tx_skip_count: 3, @@ -725,7 +749,7 @@ mod test { Arc::new(tx3.clone()), Arc::new(tx4.clone()), ]; - unconfirmed_pool.insert_txs(txns.clone()).unwrap(); + unconfirmed_pool.insert_many(txns.clone(), &tx_weight).unwrap(); for txn in txns { for output in txn.as_ref().body.outputs() { diff --git a/base_layer/core/src/proof_of_work/mod.rs b/base_layer/core/src/proof_of_work/mod.rs index 6792e81822..7caaa8bc1a 100644 --- a/base_layer/core/src/proof_of_work/mod.rs +++ b/base_layer/core/src/proof_of_work/mod.rs @@ -53,12 +53,14 @@ pub use sha3_pow::sha3_difficulty; #[cfg(all(test, feature = "base_node"))] pub use sha3_pow::test as sha3_test; -#[cfg(feature = "base_node")] mod target_difficulty; -#[cfg(feature = "base_node")] -pub use target_difficulty::{AchievedTargetDifficulty, TargetDifficultyWindow}; +pub use target_difficulty::AchievedTargetDifficulty; #[cfg(feature = "base_node")] +mod target_difficulty_window; +#[cfg(feature = "base_node")] +pub use target_difficulty_window::TargetDifficultyWindow; + pub mod lwma_diff; #[cfg(feature = "base_node")] diff --git a/base_layer/core/src/proof_of_work/target_difficulty.rs b/base_layer/core/src/proof_of_work/target_difficulty.rs index e454e29aaa..4727c90b39 100644 --- a/base_layer/core/src/proof_of_work/target_difficulty.rs +++ b/base_layer/core/src/proof_of_work/target_difficulty.rs @@ -20,68 +20,7 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::proof_of_work::{ - difficulty::DifficultyAdjustment, - lwma_diff::LinearWeightedMovingAverage, - Difficulty, - PowAlgorithm, -}; -use std::cmp; -use tari_crypto::tari_utilities::epoch_time::EpochTime; - -#[derive(Debug, Clone)] -pub struct TargetDifficultyWindow { - lwma: LinearWeightedMovingAverage, -} - -impl TargetDifficultyWindow { - /// Initialize a new `TargetDifficultyWindow` - /// - /// # Panics - /// - /// Panics if block_window is 0 - pub(crate) fn new(block_window: usize, target_time: u64, max_block_time: u64) -> Self { - assert!( - block_window > 0, - "TargetDifficulty::new expected block_window to be greater than 0, but 0 was given" - ); - Self { - lwma: LinearWeightedMovingAverage::new(block_window, target_time, max_block_time), - } - } - - /// Appends a target difficulty. If the number of stored difficulties exceeds the block window, the oldest block - /// window is removed keeping the size of the stored difficulties equal to the block window. - #[inline] - pub fn add_back(&mut self, time: EpochTime, difficulty: Difficulty) { - self.lwma.add_back(time, difficulty); - } - - #[inline] - pub fn add_front(&mut self, time: EpochTime, difficulty: Difficulty) { - self.lwma.add_front(time, difficulty); - } - - /// Returns true of the TargetDifficulty has `block_window` data points, otherwise false - #[inline] - pub fn is_full(&self) -> bool { - self.lwma.is_full() - } - - pub fn len(&self) -> usize { - self.lwma.num_samples() - } - - #[inline] - pub fn is_empty(&self) -> bool { - self.lwma.num_samples() == 0 - } - - /// Calculates the target difficulty for the current set of target difficulties. - pub fn calculate(&self, min: Difficulty, max: Difficulty) -> Difficulty { - cmp::max(min, cmp::min(max, self.lwma.get_difficulty().unwrap_or(min))) - } -} +use crate::proof_of_work::{Difficulty, PowAlgorithm}; /// Immutable struct that is guaranteed to have achieved the target difficulty pub struct AchievedTargetDifficulty { @@ -116,23 +55,3 @@ impl AchievedTargetDifficulty { self.pow_algo } } - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn it_calculates_the_target_difficulty() { - let mut target_difficulties = TargetDifficultyWindow::new(5, 60, 60 * 6); - let mut time = 60.into(); - target_difficulties.add_back(time, 100.into()); - time += 60.into(); - target_difficulties.add_back(time, 100.into()); - time += 60.into(); - target_difficulties.add_back(time, 100.into()); - time += 60.into(); - target_difficulties.add_back(time, 100.into()); - - assert_eq!(target_difficulties.calculate(1.into(), 400.into()), 100.into()); - } -} diff --git a/base_layer/core/src/proof_of_work/target_difficulty_window.rs b/base_layer/core/src/proof_of_work/target_difficulty_window.rs new file mode 100644 index 0000000000..0e56aa94c7 --- /dev/null +++ b/base_layer/core/src/proof_of_work/target_difficulty_window.rs @@ -0,0 +1,99 @@ +// Copyright 2019. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::proof_of_work::{difficulty::DifficultyAdjustment, lwma_diff::LinearWeightedMovingAverage, Difficulty}; +use std::cmp; +use tari_crypto::tari_utilities::epoch_time::EpochTime; + +#[derive(Debug, Clone)] +pub struct TargetDifficultyWindow { + lwma: LinearWeightedMovingAverage, +} + +impl TargetDifficultyWindow { + /// Initialize a new `TargetDifficultyWindow` + /// + /// # Panics + /// + /// Panics if block_window is 0 + pub(crate) fn new(block_window: usize, target_time: u64, max_block_time: u64) -> Self { + assert!( + block_window > 0, + "TargetDifficulty::new expected block_window to be greater than 0, but 0 was given" + ); + Self { + lwma: LinearWeightedMovingAverage::new(block_window, target_time, max_block_time), + } + } + + /// Appends a target difficulty. If the number of stored difficulties exceeds the block window, the oldest block + /// window is removed keeping the size of the stored difficulties equal to the block window. + #[inline] + pub fn add_back(&mut self, time: EpochTime, difficulty: Difficulty) { + self.lwma.add_back(time, difficulty); + } + + #[inline] + pub fn add_front(&mut self, time: EpochTime, difficulty: Difficulty) { + self.lwma.add_front(time, difficulty); + } + + /// Returns true of the TargetDifficulty has `block_window` data points, otherwise false + #[inline] + pub fn is_full(&self) -> bool { + self.lwma.is_full() + } + + pub fn len(&self) -> usize { + self.lwma.num_samples() + } + + #[inline] + pub fn is_empty(&self) -> bool { + self.lwma.num_samples() == 0 + } + + /// Calculates the target difficulty for the current set of target difficulties. + pub fn calculate(&self, min: Difficulty, max: Difficulty) -> Difficulty { + cmp::max(min, cmp::min(max, self.lwma.get_difficulty().unwrap_or(min))) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn it_calculates_the_target_difficulty() { + let mut target_difficulties = TargetDifficultyWindow::new(5, 60, 60 * 6); + let mut time = 60.into(); + target_difficulties.add_back(time, 100.into()); + time += 60.into(); + target_difficulties.add_back(time, 100.into()); + time += 60.into(); + target_difficulties.add_back(time, 100.into()); + time += 60.into(); + target_difficulties.add_back(time, 100.into()); + + assert_eq!(target_difficulties.calculate(1.into(), 400.into()), 100.into()); + } +} diff --git a/base_layer/core/src/proto/block.rs b/base_layer/core/src/proto/block.rs index 50778f5a92..e1cd1d46dc 100644 --- a/base_layer/core/src/proto/block.rs +++ b/base_layer/core/src/proto/block.rs @@ -22,8 +22,7 @@ use super::core as proto; use crate::{ - blocks::{Block, NewBlock, NewBlockHeaderTemplate, NewBlockTemplate}, - chain_storage::{BlockHeaderAccumulatedData, HistoricalBlock}, + blocks::{Block, BlockHeaderAccumulatedData, HistoricalBlock, NewBlock, NewBlockHeaderTemplate, NewBlockTemplate}, proof_of_work::ProofOfWork, }; use std::convert::{TryFrom, TryInto}; diff --git a/base_layer/core/src/test_helpers/blockchain.rs b/base_layer/core/src/test_helpers/blockchain.rs index a98a318947..30c838cbf2 100644 --- a/base_layer/core/src/test_helpers/blockchain.rs +++ b/base_layer/core/src/test_helpers/blockchain.rs @@ -22,23 +22,27 @@ use super::{create_block, mine_to_difficulty}; use crate::{ - blocks::{genesis_block::get_weatherwax_genesis_block, Block, BlockHeader}, - chain_storage::{ - create_lmdb_database, + blocks::{ + genesis_block::get_weatherwax_genesis_block, + Block, BlockAccumulatedData, + BlockHeader, BlockHeaderAccumulatedData, + ChainBlock, + ChainHeader, + DeletedBitmap, + }, + chain_storage::{ + create_lmdb_database, BlockchainBackend, BlockchainDatabase, BlockchainDatabaseConfig, - ChainBlock, - ChainHeader, ChainStorageError, DbBasicStats, DbKey, DbTotalSizeStats, DbTransaction, DbValue, - DeletedBitmap, HorizonData, LMDBDatabase, MmrTree, @@ -49,7 +53,7 @@ use crate::{ consensus::{chain_strength_comparer::ChainStrengthComparerBuilder, ConsensusConstantsBuilder, ConsensusManager}, crypto::tari_utilities::Hashable, proof_of_work::{AchievedTargetDifficulty, Difficulty, PowAlgorithm}, - test_helpers::{block_spec::BlockSpecs, BlockSpec}, + test_helpers::{block_spec::BlockSpecs, create_consensus_rules, BlockSpec}, transactions::{ transaction::{TransactionInput, TransactionKernel, UnblindedOutput}, CryptoFactories, @@ -133,8 +137,7 @@ pub fn create_store_with_consensus(rules: ConsensusManager) -> BlockchainDatabas create_store_with_consensus_and_validators(rules, validators) } pub fn create_test_blockchain_db() -> BlockchainDatabase { - let network = Network::Weatherwax; - let rules = ConsensusManager::builder(network).build(); + let rules = create_consensus_rules(); create_store_with_consensus(rules) } diff --git a/base_layer/core/src/test_helpers/mod.rs b/base_layer/core/src/test_helpers/mod.rs index 278d9791b8..9523fe0a3e 100644 --- a/base_layer/core/src/test_helpers/mod.rs +++ b/base_layer/core/src/test_helpers/mod.rs @@ -30,9 +30,8 @@ pub use block_spec::{BlockSpec, BlockSpecs}; pub mod blockchain; use crate::{ - blocks::{Block, BlockHeader}, - chain_storage::{BlockHeaderAccumulatedData, ChainHeader}, - consensus::ConsensusManager, + blocks::{Block, BlockHeader, BlockHeaderAccumulatedData, ChainHeader}, + consensus::{ConsensusConstants, ConsensusManager}, crypto::tari_utilities::Hashable, proof_of_work::{sha3_difficulty, AchievedTargetDifficulty, Difficulty}, transactions::{ @@ -43,9 +42,18 @@ use crate::{ }; use rand::{distributions::Alphanumeric, Rng}; use std::{iter, path::Path, sync::Arc}; +use tari_common::configuration::Network; use tari_comms::PeerManager; use tari_storage::{lmdb_store::LMDBBuilder, LMDBWrapper}; +pub fn create_consensus_rules() -> ConsensusManager { + ConsensusManager::builder(Network::LocalNet).build() +} + +pub fn create_consensus_constants(height: u64) -> ConsensusConstants { + create_consensus_rules().consensus_constants(height).clone() +} + /// Create a partially constructed block using the provided set of transactions /// is chain_block, or rename it to `create_orphan_block` and drop the prev_block argument pub fn create_orphan_block(block_height: u64, transactions: Vec, consensus: &ConsensusManager) -> Block { diff --git a/base_layer/core/src/transactions/aggregated_body.rs b/base_layer/core/src/transactions/aggregated_body.rs index 96df03e91a..a7f3134678 100644 --- a/base_layer/core/src/transactions/aggregated_body.rs +++ b/base_layer/core/src/transactions/aggregated_body.rs @@ -19,7 +19,24 @@ // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::transactions::{crypto_factories::CryptoFactories, fee::Fee, tari_amount::*, transaction::*}; +use crate::{ + consensus::{ConsensusEncodingSized, ConsensusEncodingWrapper}, + transactions::{ + crypto_factories::CryptoFactories, + tari_amount::MicroTari, + transaction::{ + KernelFeatures, + KernelSum, + OutputFlags, + Transaction, + TransactionError, + TransactionInput, + TransactionKernel, + TransactionOutput, + }, + weight::TransactionWeight, + }, +}; use log::*; use serde::{Deserialize, Serialize}; use std::{ @@ -451,9 +468,25 @@ impl AggregateBody { Ok(()) } - /// Returns the byte size or weight of a body - pub fn calculate_weight(&self) -> u64 { - Fee::calculate_weight(self.kernels().len(), self.inputs().len(), self.outputs().len()) + /// Returns the weight in grams of a body + pub fn calculate_weight(&self, transaction_weight: &TransactionWeight) -> u64 { + let metadata_byte_size = self.sum_metadata_size(); + transaction_weight.calculate( + self.kernels().len(), + self.inputs().len(), + self.outputs().len(), + metadata_byte_size, + ) + } + + pub fn sum_metadata_size(&self) -> usize { + self.outputs + .iter() + .map(|o| { + o.features.consensus_encode_exact_size() + + ConsensusEncodingWrapper::wrap(&o.script).consensus_encode_exact_size() + }) + .sum() } pub fn is_sorted(&self) -> bool { diff --git a/base_layer/core/src/transactions/coinbase_builder.rs b/base_layer/core/src/transactions/coinbase_builder.rs index 5091844e6a..3c75d7fa84 100644 --- a/base_layer/core/src/transactions/coinbase_builder.rs +++ b/base_layer/core/src/transactions/coinbase_builder.rs @@ -243,24 +243,22 @@ impl CoinbaseBuilder { #[cfg(test)] mod test { - use rand::rngs::OsRng; - use tari_crypto::{commitment::HomomorphicCommitmentFactory, keys::SecretKey as SecretKeyTrait}; - - use tari_common::configuration::Network; - use crate::{ consensus::{emission::Emission, ConsensusManager, ConsensusManagerBuilder}, transactions::{ coinbase_builder::CoinbaseBuildError, crypto_factories::CryptoFactories, - helpers::TestParams, tari_amount::uT, + test_helpers::TestParams, transaction::{KernelFeatures, OutputFeatures, OutputFlags, TransactionError}, transaction_protocol::RewindData, CoinbaseBuilder, }, }; + use rand::rngs::OsRng; + use tari_common::configuration::Network; use tari_common_types::types::{BlindingFactor, PrivateKey}; + use tari_crypto::{commitment::HomomorphicCommitmentFactory, keys::SecretKey as SecretKeyTrait}; fn get_builder() -> (CoinbaseBuilder, ConsensusManager, CryptoFactories) { let network = Network::LocalNet; diff --git a/base_layer/core/src/transactions/fee.rs b/base_layer/core/src/transactions/fee.rs index 85e0ee96cb..dce769232a 100644 --- a/base_layer/core/src/transactions/fee.rs +++ b/base_layer/core/src/transactions/fee.rs @@ -20,39 +20,44 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::{ - consensus::{KERNEL_WEIGHT, WEIGHT_PER_INPUT, WEIGHT_PER_OUTPUT}, - transactions::{tari_amount::*, transaction::MINIMUM_TRANSACTION_FEE}, -}; +use super::{tari_amount::MicroTari, weight::TransactionWeight}; +use std::cmp::max; -pub struct Fee {} +#[derive(Debug, Clone, Copy)] +pub struct Fee(TransactionWeight); impl Fee { - /// Computes the absolute transaction fee given the fee-per-gram, and the size of the transaction - pub fn calculate(fee_per_gram: MicroTari, num_kernels: usize, num_inputs: usize, num_outputs: usize) -> MicroTari { - (Fee::calculate_weight(num_kernels, num_inputs, num_outputs) * u64::from(fee_per_gram)).into() + pub(crate) const MINIMUM_TRANSACTION_FEE: MicroTari = MicroTari(101); + + pub fn new(weight: TransactionWeight) -> Self { + Self(weight) } - /// Computes the absolute transaction fee using `calculate`, but the resulting fee will always be at least the - /// minimum network transaction fee. - pub fn calculate_with_minimum( + /// Computes the absolute transaction fee given the fee-per-gram, and the size of the transaction + /// NB: Each fee calculation should be done independently. No commutative, associative or distributive properties + /// are guaranteed to hold between calculations. for e.g. fee(1,1,1,4) + fee(1,1,1,12) != fee(1,1,1,16) + pub fn calculate( + &self, fee_per_gram: MicroTari, num_kernels: usize, num_inputs: usize, num_outputs: usize, + total_metadata_byte_size: usize, ) -> MicroTari { - let fee = Fee::calculate(fee_per_gram, num_kernels, num_inputs, num_outputs); - if fee < MINIMUM_TRANSACTION_FEE { - MINIMUM_TRANSACTION_FEE - } else { - fee - } + let weight = self + .0 + .calculate(num_kernels, num_inputs, num_outputs, total_metadata_byte_size); + MicroTari::from(weight) * fee_per_gram } - /// Calculate the weight of a transaction based on the number of inputs and outputs - pub fn calculate_weight(num_kernels: usize, num_inputs: usize, num_outputs: usize) -> u64 { - KERNEL_WEIGHT * num_kernels as u64 + - WEIGHT_PER_INPUT * num_inputs as u64 + - WEIGHT_PER_OUTPUT * num_outputs as u64 + /// Normalizes the given fee returning a fee that is equal to or above the minimum fee + pub fn normalize(fee: MicroTari) -> MicroTari { + max(Self::MINIMUM_TRANSACTION_FEE, fee) + } +} + +impl From for Fee { + fn from(weight: TransactionWeight) -> Self { + Self(weight) } } diff --git a/base_layer/core/src/transactions/format_currency.rs b/base_layer/core/src/transactions/format_currency.rs new file mode 100644 index 0000000000..74047a4ab2 --- /dev/null +++ b/base_layer/core/src/transactions/format_currency.rs @@ -0,0 +1,67 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +/// Return a currency styled `String` +/// # Examples +/// +/// ```rust +/// use tari_core::transactions::format_currency; +/// assert_eq!("12,345.12", format_currency("12345.12", ',')); +/// assert_eq!("12,345", format_currency("12345", ',')); +/// ``` +pub fn format_currency(value: &str, separator: char) -> String { + let full_len = value.len(); + let mut buffer = String::with_capacity(full_len / 3 + full_len); + let mut iter = value.splitn(2, '.'); + let whole = iter.next().unwrap_or(""); + let mut idx = whole.len() as isize - 1; + for c in whole.chars() { + buffer.push(c); + if idx > 0 && idx % 3 == 0 { + buffer.push(separator); + } + idx -= 1; + } + if let Some(decimal) = iter.next() { + buffer.push('.'); + buffer.push_str(decimal); + } + buffer +} + +#[cfg(test)] +#[allow(clippy::excessive_precision)] +mod test { + use super::format_currency; + + #[test] + fn test_format_currency() { + assert_eq!("0.00", format_currency("0.00", ',')); + assert_eq!("0.000000000000", format_currency("0.000000000000", ',')); + assert_eq!("123,456.123456789", format_currency("123456.123456789", ',')); + assert_eq!("123,456", format_currency("123456", ',')); + assert_eq!("123", format_currency("123", ',')); + assert_eq!("7,123", format_currency("7123", ',')); + assert_eq!(".00", format_currency(".00", ',')); + assert_eq!("00.", format_currency("00.", ',')); + } +} diff --git a/base_layer/core/src/transactions/mod.rs b/base_layer/core/src/transactions/mod.rs index 5653939bd4..ee63b708c2 100644 --- a/base_layer/core/src/transactions/mod.rs +++ b/base_layer/core/src/transactions/mod.rs @@ -1,19 +1,23 @@ pub mod aggregated_body; + mod crypto_factories; +pub use crypto_factories::CryptoFactories; + +mod coinbase_builder; +pub use coinbase_builder::{CoinbaseBuildError, CoinbaseBuilder}; + pub mod fee; pub mod tari_amount; pub mod transaction; -#[allow(clippy::op_ref)] -pub mod transaction_protocol; -pub use crypto_factories::*; +mod format_currency; +pub use format_currency::format_currency; -pub mod types; -// Re-export commonly used structs +pub mod transaction_protocol; pub use transaction_protocol::{recipient::ReceiverTransactionProtocol, sender::SenderTransactionProtocol}; -#[macro_use] -pub mod helpers; +pub mod types; +pub mod weight; -mod coinbase_builder; -pub use crate::transactions::coinbase_builder::{CoinbaseBuildError, CoinbaseBuilder}; +#[macro_use] +pub mod test_helpers; diff --git a/base_layer/core/src/transactions/tari_amount.rs b/base_layer/core/src/transactions/tari_amount.rs index db78ac6999..78189211c0 100644 --- a/base_layer/core/src/transactions/tari_amount.rs +++ b/base_layer/core/src/transactions/tari_amount.rs @@ -20,12 +20,11 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use newtype_ops::newtype_ops; -use serde::{Deserialize, Serialize}; - -use crate::transactions::helpers; +use super::format_currency; use decimal_rs::{Decimal, DecimalConvertError}; use derive_more::{Add, AddAssign, Div, From, FromStr, Into, Mul, Rem, Sub, SubAssign}; +use newtype_ops::newtype_ops; +use serde::{Deserialize, Serialize}; use std::{ convert::{TryFrom, TryInto}, fmt::{Display, Error, Formatter}, @@ -67,9 +66,9 @@ pub const uT: MicroTari = MicroTari(1); pub const T: MicroTari = MicroTari(1_000_000); // You can only add or subtract µT from µT -newtype_ops! { [MicroTari] {add sub} {:=} Self Self } -newtype_ops! { [MicroTari] {add sub} {:=} &Self &Self } -newtype_ops! { [MicroTari] {add sub} {:=} Self &Self } +newtype_ops! { [MicroTari] {add sub mul div} {:=} Self Self } +newtype_ops! { [MicroTari] {add sub mul div} {:=} &Self &Self } +newtype_ops! { [MicroTari] {add sub mul div} {:=} Self &Self } // Multiplication and division only makes sense when µT is multiplied/divided by a scalar newtype_ops! { [MicroTari] {mul div rem} {:=} Self u64 } @@ -214,7 +213,7 @@ impl From for FormattedMicroTari { impl Display for FormattedMicroTari { fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { let value = format!("{}", self.0); - let formatted = helpers::format_currency(&value, ','); + let formatted = format_currency(&value, ','); f.write_str(&formatted)?; f.write_str(" µT")?; Ok(()) @@ -233,7 +232,7 @@ impl From for FormattedTari { impl Display for FormattedTari { fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { let value = format!("{:.2}", self.0); - let formatted = helpers::format_currency(&value, ','); + let formatted = format_currency(&value, ','); f.write_str(&formatted)?; f.write_str(" T")?; Ok(()) diff --git a/base_layer/core/src/transactions/helpers.rs b/base_layer/core/src/transactions/test_helpers.rs similarity index 89% rename from base_layer/core/src/transactions/helpers.rs rename to base_layer/core/src/transactions/test_helpers.rs index 3738a40667..4fe0be260f 100644 --- a/base_layer/core/src/transactions/helpers.rs +++ b/base_layer/core/src/transactions/test_helpers.rs @@ -20,9 +20,31 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use std::sync::Arc; - +use crate::{ + consensus::{ConsensusEncodingSized, ConsensusEncodingWrapper, ConsensusManager}, + transactions::{ + crypto_factories::CryptoFactories, + fee::Fee, + tari_amount::MicroTari, + transaction::{ + KernelBuilder, + KernelFeatures, + OutputFeatures, + Transaction, + TransactionInput, + TransactionKernel, + TransactionOutput, + UnblindedOutput, + }, + transaction_protocol::{build_challenge, TransactionMetadata}, + weight::TransactionWeight, + SenderTransactionProtocol, + }, +}; use rand::rngs::OsRng; +use std::sync::Arc; +use tari_common::configuration::Network; +use tari_common_types::types::{Commitment, CommitmentFactory, PrivateKey, PublicKey, Signature}; use tari_crypto::{ commitment::HomomorphicCommitmentFactory, common::Blake256, @@ -33,25 +55,6 @@ use tari_crypto::{ script::{ExecutionStack, TariScript}, }; -use crate::transactions::{ - crypto_factories::CryptoFactories, - fee::Fee, - tari_amount::MicroTari, - transaction::{ - KernelBuilder, - KernelFeatures, - OutputFeatures, - Transaction, - TransactionInput, - TransactionKernel, - TransactionOutput, - UnblindedOutput, - }, - transaction_protocol::{build_challenge, TransactionMetadata}, - SenderTransactionProtocol, -}; -use tari_common_types::types::{Commitment, CommitmentFactory, PrivateKey, PublicKey, Signature}; - pub fn create_test_input( amount: MicroTari, maturity: u64, @@ -66,7 +69,7 @@ pub fn create_test_input( }) } -#[derive(Default, Clone)] +#[derive(Clone)] pub struct TestParams { pub spend_key: PrivateKey, pub change_spend_key: PrivateKey, @@ -81,6 +84,7 @@ pub struct TestParams { pub sender_private_commitment_nonce: PrivateKey, pub sender_public_commitment_nonce: PublicKey, pub commitment_factory: CommitmentFactory, + pub transaction_weight: TransactionWeight, } #[derive(Clone)] @@ -108,7 +112,7 @@ impl TestParams { let sender_offset_private_key = PrivateKey::random(&mut OsRng); let sender_sig_pvt_nonce = PrivateKey::random(&mut OsRng); let script_private_key = PrivateKey::random(&mut OsRng); - TestParams { + Self { spend_key: PrivateKey::random(&mut OsRng), change_spend_key: PrivateKey::random(&mut OsRng), offset: PrivateKey::random(&mut OsRng), @@ -122,9 +126,14 @@ impl TestParams { sender_private_commitment_nonce: sender_sig_pvt_nonce.clone(), sender_public_commitment_nonce: PublicKey::from_secret_key(&sender_sig_pvt_nonce), commitment_factory: CommitmentFactory::default(), + transaction_weight: TransactionWeight::v2(), } } + pub fn fee(&self) -> Fee { + Fee::new(self.transaction_weight) + } + pub fn create_unblinded_output(&self, params: UtxoTestParams) -> UnblindedOutput { let metadata_signature = TransactionOutput::create_final_metadata_signature( ¶ms.value, @@ -166,6 +175,11 @@ impl TestParams { } } +impl Default for TestParams { + fn default() -> Self { + Self::new() + } +} /// A convenience struct for a set of public-private keys and a public-private nonce pub struct TestKeySet { pub k: PrivateKey, @@ -231,7 +245,7 @@ pub fn create_unblinded_output( #[macro_export] macro_rules! tx { ($amount:expr, fee: $fee:expr, lock: $lock:expr, inputs: $n_in:expr, maturity: $mat:expr, outputs: $n_out:expr) => {{ - use $crate::transactions::helpers::create_tx; + use $crate::transactions::test_helpers::create_tx; create_tx($amount, $fee, $lock, $n_in, $mat, $n_out) }}; @@ -262,7 +276,7 @@ macro_rules! tx { #[macro_export] macro_rules! txn_schema { (from: $input:expr, to: $outputs:expr, fee: $fee:expr, lock: $lock:expr, features: $features:expr) => {{ - $crate::transactions::helpers::TransactionSchema { + $crate::transactions::test_helpers::TransactionSchema { from: $input.clone(), to: $outputs.clone(), to_outputs: vec![], @@ -295,7 +309,7 @@ macro_rules! txn_schema { }; (from: $input:expr, to: $outputs:expr) => { - txn_schema!(from: $input, to:$outputs, fee: 25.into()) + txn_schema!(from: $input, to:$outputs, fee: 5.into()) }; // Spend inputs to ± half the first input value, with default fee and lock height @@ -318,6 +332,11 @@ pub struct TransactionSchema { pub input_data: Option, } +fn default_metadata_byte_size() -> usize { + OutputFeatures::default().consensus_encode_exact_size() + + ConsensusEncodingWrapper::wrap(&script![Nop]).consensus_encode_exact_size() +} + /// Create an unconfirmed transaction for testing with a valid fee, unique access_sig, random inputs and outputs, the /// transaction is only partially constructed pub fn create_tx( @@ -340,7 +359,14 @@ pub fn create_unblinded_txos( output_count: usize, fee_per_gram: MicroTari, ) -> (Vec, Vec<(UnblindedOutput, PrivateKey)>) { - let estimated_fee = Fee::calculate(fee_per_gram, 1, input_count, output_count); + let output_metadata_size = default_metadata_byte_size() * output_count; + let estimated_fee = Fee::new(TransactionWeight::latest()).calculate( + fee_per_gram, + 1, + input_count, + output_count, + output_metadata_size, + ); let amount_per_output = (amount - estimated_fee) / output_count as u64; let amount_for_last_output = (amount - estimated_fee) - amount_per_output * (output_count as u64 - 1); @@ -395,7 +421,11 @@ pub fn create_transaction_with( ) -> Transaction { let factories = CryptoFactories::default(); let test_params = TestParams::new(); - let mut stx_builder = SenderTransactionProtocol::builder(0); + let constants = ConsensusManager::builder(Network::LocalNet) + .build() + .consensus_constants(0) + .clone(); + let mut stx_builder = SenderTransactionProtocol::builder(0, constants); stx_builder .with_lock_height(lock_height) .with_fee_per_gram(fee_per_gram) @@ -426,7 +456,11 @@ pub fn create_transaction_with( pub fn spend_utxos(schema: TransactionSchema) -> (Transaction, Vec, TestParams) { let factories = CryptoFactories::default(); let test_params_change_and_txn = TestParams::new(); - let mut stx_builder = SenderTransactionProtocol::builder(0); + let constants = ConsensusManager::builder(Network::LocalNet) + .build() + .consensus_constants(0) + .clone(); + let mut stx_builder = SenderTransactionProtocol::builder(0, constants); stx_builder .with_lock_height(schema.lock_height) .with_fee_per_gram(schema.fee) @@ -559,49 +593,3 @@ pub fn schema_to_transaction(txns: &[TransactionSchema]) -> (Vec String { - let full_len = value.len(); - let mut buffer = String::with_capacity(full_len / 3 + full_len); - let mut iter = value.splitn(2, '.'); - let whole = iter.next().unwrap_or(""); - let mut idx = whole.len() as isize - 1; - for c in whole.chars() { - buffer.push(c); - if idx > 0 && idx % 3 == 0 { - buffer.push(separator); - } - idx -= 1; - } - if let Some(decimal) = iter.next() { - buffer.push('.'); - buffer.push_str(decimal); - } - buffer -} - -#[cfg(test)] -#[allow(clippy::excessive_precision)] -mod test { - use super::format_currency; - - #[test] - fn test_format_currency() { - assert_eq!("0.00", format_currency("0.00", ',')); - assert_eq!("0.000000000000", format_currency("0.000000000000", ',')); - assert_eq!("123,456.123456789", format_currency("123456.123456789", ',')); - assert_eq!("123,456", format_currency("123456", ',')); - assert_eq!("123", format_currency("123", ',')); - assert_eq!("7,123", format_currency("7123", ',')); - assert_eq!(".00", format_currency(".00", ',')); - assert_eq!("00.", format_currency("00.", ',')); - } -} diff --git a/base_layer/core/src/transactions/transaction.rs b/base_layer/core/src/transactions/transaction.rs index 0de8edba7a..dc968a917e 100644 --- a/base_layer/core/src/transactions/transaction.rs +++ b/base_layer/core/src/transactions/transaction.rs @@ -23,17 +23,43 @@ // Portions of this file were originally copyrighted (c) 2018 The Grin Developers, issued under the Apache License, // Version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0. +use crate::{ + consensus::{ConsensusDecoding, ConsensusEncoding, ConsensusEncodingSized, ConsensusEncodingWrapper}, + transactions::{ + aggregated_body::AggregateBody, + crypto_factories::CryptoFactories, + tari_amount::{uT, MicroTari}, + transaction_protocol::{build_challenge, RewindData, TransactionMetadata}, + weight::TransactionWeight, + }, +}; +use blake2::Digest; +use integer_encoding::{VarInt, VarIntReader, VarIntWriter}; +use rand::rngs::OsRng; +use serde::{Deserialize, Serialize}; use std::{ cmp::{max, min, Ordering}, fmt, fmt::{Display, Formatter}, hash::{Hash, Hasher}, - ops::Add, + io, + io::{Read, Write}, + ops::{Add, Shl}, +}; +use tari_common_types::types::{ + BlindingFactor, + Challenge, + ComSignature, + Commitment, + CommitmentFactory, + HashDigest, + MessageHash, + PrivateKey, + PublicKey, + RangeProof, + RangeProofService, + Signature, }; - -use blake2::Digest; -use rand::rngs::OsRng; -use serde::{Deserialize, Serialize}; use tari_crypto::{ commitment::HomomorphicCommitmentFactory, keys::{PublicKey as PublicKeyTrait, SecretKey}, @@ -51,33 +77,10 @@ use tari_crypto::{ }; use thiserror::Error; -use crate::transactions::{ - aggregated_body::AggregateBody, - crypto_factories::CryptoFactories, - tari_amount::{uT, MicroTari}, - transaction_protocol::{build_challenge, RewindData, TransactionMetadata}, -}; -use std::ops::Shl; -use tari_common_types::types::{ - BlindingFactor, - Challenge, - ComSignature, - Commitment, - CommitmentFactory, - HashDigest, - MessageHash, - PrivateKey, - PublicKey, - RangeProof, - RangeProofService, - Signature, -}; - -// Tx_weight(inputs(12,500), outputs(500), kernels(1)) = 19,003, still well enough below block weight of 19,500 +// Tx_weight(inputs(12,500), outputs(500), kernels(1)) = 126,510 still well enough below block weight of 127,795 pub const MAX_TRANSACTION_INPUTS: usize = 12_500; pub const MAX_TRANSACTION_OUTPUTS: usize = 500; pub const MAX_TRANSACTION_RECIPIENTS: usize = 15; -pub const MINIMUM_TRANSACTION_FEE: MicroTari = MicroTari(100); //-------------------------------------- Output features --------------------------------------------------// @@ -108,9 +111,24 @@ pub struct OutputFeatures { } impl OutputFeatures { - pub fn to_bytes(&self) -> Vec { - let mut buf = Vec::new(); - bincode::serialize_into(&mut buf, self).unwrap(); // this should not fail + /// The version number to use in consensus encoding. In future, this value could be dynamic. + const CONSENSUS_ENCODING_VERSION: u8 = 0; + + /// Encodes output features using deprecated bincode encoding + pub fn to_v1_bytes(&self) -> Vec { + // unreachable panic: serialized_size is infallible because it uses DefaultOptions + let encode_size = bincode::serialized_size(self).expect("unreachable"); + let mut buf = Vec::with_capacity(encode_size as usize); + // unreachable panic: Vec's Write impl is infallible + bincode::serialize_into(&mut buf, self).expect("unreachable"); + buf + } + + /// Encodes output features using consensus encoding + pub fn to_consensus_bytes(&self) -> Vec { + let mut buf = Vec::with_capacity(self.consensus_encode_exact_size()); + // unreachable panic: Vec's Write impl is infallible + self.consensus_encode(&mut buf).expect("unreachable"); buf } @@ -130,6 +148,43 @@ impl OutputFeatures { } } +impl ConsensusEncoding for OutputFeatures { + fn consensus_encode(&self, writer: &mut W) -> Result { + let mut written = writer.write_varint(Self::CONSENSUS_ENCODING_VERSION)?; + written += writer.write_varint(self.maturity)?; + written += self.flags.consensus_encode(writer)?; + Ok(written) + } +} +impl ConsensusEncodingSized for OutputFeatures { + fn consensus_encode_exact_size(&self) -> usize { + Self::CONSENSUS_ENCODING_VERSION.required_space() + + self.flags.consensus_encode_exact_size() + + self.maturity.required_space() + } +} + +impl ConsensusDecoding for OutputFeatures { + fn consensus_decode(reader: &mut R) -> Result { + // Changing the order of these operations is consensus breaking + let version = reader.read_varint::()?; + if version != Self::CONSENSUS_ENCODING_VERSION { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + format!( + "Invalid version. Expected {} but got {}", + Self::CONSENSUS_ENCODING_VERSION, + version + ), + )); + } + // Decode safety: read_varint will stop reading the varint after 10 bytes + let maturity = reader.read_varint()?; + let flags = OutputFlags::consensus_decode(reader)?; + Ok(Self { flags, maturity }) + } +} + impl Default for OutputFeatures { fn default() -> Self { OutputFeatures { @@ -169,6 +224,33 @@ bitflags! { } } +impl ConsensusEncoding for OutputFlags { + fn consensus_encode(&self, writer: &mut W) -> Result { + writer.write(&self.bits.to_le_bytes()) + } +} + +impl ConsensusEncodingSized for OutputFlags { + fn consensus_encode_exact_size(&self) -> usize { + 1 + } +} + +impl ConsensusDecoding for OutputFlags { + fn consensus_decode(reader: &mut R) -> Result { + let mut buf = [0u8; 1]; + reader.read_exact(&mut buf)?; + // SAFETY: we have 3 options here: + // 1. error if unsupported flags are used, meaning that every new flag will be a hard fork + // 2. truncate unsupported flags, means different hashes will be produced for the same block + // 3. ignore unsupported flags, which could be set at any time and persisted to the blockchain. + // Once those flags are defined at some point in the future, depending on the functionality of the flag, + // a consensus rule may be needed that ignores flags prior to a given block height. + // Option 3 is used here + Ok(unsafe { OutputFlags::from_bits_unchecked(u8::from_le_bytes(buf)) }) + } +} + //---------------------------------------- TransactionError ----------------------------------------------------// #[derive(Clone, Debug, PartialEq, Error, Deserialize, Serialize)] @@ -341,6 +423,11 @@ impl UnblindedOutput { Ok(output) } + + pub fn metadata_byte_size(&self) -> usize { + self.features.consensus_encode_exact_size() + + ConsensusEncodingWrapper::wrap(&self.script).consensus_encode_exact_size() + } } // These implementations are used for order these outputs for UTXO selection which will be done by comparing the values @@ -497,7 +584,7 @@ impl TransactionInput { /// This hash matches the hash of a transaction output that this input spends. pub fn output_hash(&self) -> Vec { HashDigest::new() - .chain(self.features.to_bytes()) + .chain(self.features.to_v1_bytes()) .chain(self.commitment.as_bytes()) .chain(self.script.as_bytes()) .finalize() @@ -509,7 +596,7 @@ impl TransactionInput { impl Hashable for TransactionInput { fn hash(&self) -> Vec { HashDigest::new() - .chain(self.features.to_bytes()) + .chain(self.features.to_consensus_bytes()) .chain(self.commitment.as_bytes()) .chain(self.script.as_bytes()) .chain(self.sender_offset_public_key.as_bytes()) @@ -692,7 +779,8 @@ impl TransactionOutput { Challenge::new() .chain(public_commitment_nonce.as_bytes()) .chain(script.as_bytes()) - .chain(features.to_bytes()) + // TODO: Use consensus encoded bytes #testnet reset + .chain(features.to_v1_bytes()) .chain(sender_offset_public_key.as_bytes()) .chain(commitment.as_bytes()) .finalize() @@ -799,7 +887,8 @@ impl TransactionOutput { impl Hashable for TransactionOutput { fn hash(&self) -> Vec { HashDigest::new() - .chain(self.features.to_bytes()) + // TODO: use consensus encoding #testnetreset + .chain(self.features.to_v1_bytes()) .chain(self.commitment.as_bytes()) // .chain(range proof) // See docs as to why we exclude this .chain(self.script.as_bytes()) @@ -994,7 +1083,8 @@ impl Hashable for TransactionKernel { impl Display for TransactionKernel { fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { - let msg = format!( + write!( + fmt, "Fee: {}\nLock height: {}\nFeatures: {:?}\nExcess: {}\nExcess signature: {}\n", self.fee, self.lock_height, @@ -1003,8 +1093,7 @@ impl Display for TransactionKernel { self.excess_sig .to_json() .unwrap_or_else(|_| "Failed to serialize signature".into()), - ); - fmt.write_str(&msg) + ) } } @@ -1158,23 +1247,18 @@ impl Transaction { ) } - pub fn get_body(&self) -> &AggregateBody { + pub fn body(&self) -> &AggregateBody { &self.body } /// Returns the byte size or weight of a transaction - pub fn calculate_weight(&self) -> u64 { - self.body.calculate_weight() - } - - /// Returns the total fee allocated to each byte of the transaction - pub fn calculate_ave_fee_per_gram(&self) -> f64 { - (self.body.get_total_fee().0 as f64) / self.calculate_weight() as f64 + pub fn calculate_weight(&self, transaction_weight: &TransactionWeight) -> u64 { + self.body.calculate_weight(transaction_weight) } /// Returns the minimum maturity of the input UTXOs pub fn min_input_maturity(&self) -> u64 { - self.body.inputs().iter().fold(std::u64::MAX, |min_maturity, input| { + self.body.inputs().iter().fold(u64::MAX, |min_maturity, input| { min(min_maturity, input.features.maturity) }) } @@ -1329,9 +1413,9 @@ impl Default for TransactionBuilder { mod test { use crate::{ transactions::{ - helpers, - helpers::{TestParams, UtxoTestParams}, tari_amount::T, + test_helpers, + test_helpers::{TestParams, UtxoTestParams}, transaction::OutputFeatures, }, txn_schema, @@ -1516,7 +1600,7 @@ mod test { offset_pub_key, ); - let mut kernel = helpers::create_test_kernel(0.into(), 0); + let mut kernel = test_helpers::create_test_kernel(0.into(), 0); let mut tx = Transaction::new(Vec::new(), Vec::new(), Vec::new(), 0.into(), 0.into()); // lets add time locks @@ -1552,7 +1636,7 @@ mod test { #[test] fn test_validate_internal_consistency() { - let (tx, _, _) = helpers::create_tx(5000.into(), 15.into(), 1, 2, 1, 4); + let (tx, _, _) = test_helpers::create_tx(5000.into(), 3.into(), 1, 2, 1, 4); let factories = CryptoFactories::default(); assert!(tx.validate_internal_consistency(false, &factories, None).is_ok()); @@ -1561,7 +1645,7 @@ mod test { #[test] #[allow(clippy::identity_op)] fn check_cut_through() { - let (tx, _, outputs) = helpers::create_tx(50000000.into(), 15.into(), 1, 2, 1, 2); + let (tx, _, outputs) = test_helpers::create_tx(50000000.into(), 3.into(), 1, 2, 1, 2); assert_eq!(tx.body.inputs().len(), 2); assert_eq!(tx.body.outputs().len(), 2); @@ -1571,7 +1655,7 @@ mod test { assert!(tx.validate_internal_consistency(false, &factories, None).is_ok()); let schema = txn_schema!(from: vec![outputs[1].clone()], to: vec![1 * T, 2 * T]); - let (tx2, _outputs, _) = helpers::spend_utxos(schema); + let (tx2, _outputs, _) = test_helpers::spend_utxos(schema); assert_eq!(tx2.body.inputs().len(), 1); assert_eq!(tx2.body.outputs().len(), 3); @@ -1609,7 +1693,7 @@ mod test { #[test] fn check_duplicate_inputs_outputs() { - let (tx, _, _outputs) = helpers::create_tx(50000000.into(), 15.into(), 1, 2, 1, 2); + let (tx, _, _outputs) = test_helpers::create_tx(50000000.into(), 3.into(), 1, 2, 1, 2); assert!(!tx.body.contains_duplicated_outputs()); assert!(!tx.body.contains_duplicated_inputs()); @@ -1628,11 +1712,11 @@ mod test { #[test] fn inputs_not_malleable() { - let (mut inputs, outputs) = helpers::create_unblinded_txos(5000.into(), 1, 1, 2, 15.into()); + let (mut inputs, outputs) = test_helpers::create_unblinded_txos(5000.into(), 1, 1, 2, 15.into()); let mut stack = inputs[0].input_data.clone(); inputs[0].script = script!(Drop Nop); inputs[0].input_data.push(StackItem::Hash([0; 32])).unwrap(); - let mut tx = helpers::create_transaction_with(1, 15.into(), inputs, outputs); + let mut tx = test_helpers::create_transaction_with(1, 15.into(), inputs, outputs); stack .push(StackItem::Hash(*b"Pls put this on tha tari network")) @@ -1708,4 +1792,51 @@ mod test { assert_eq!(&full_rewind_result.proof_message, proof_message); assert_eq!(full_rewind_result.blinding_factor, test_params.spend_key); } + mod output_features { + use super::*; + + #[test] + fn consensus_encode_minimal() { + let features = OutputFeatures::with_maturity(0); + let mut buf = Vec::new(); + let written = features.consensus_encode(&mut buf).unwrap(); + assert_eq!(buf.len(), 3); + assert_eq!(written, 3); + } + + #[test] + fn consensus_encode_decode() { + let features = OutputFeatures::create_coinbase(u64::MAX); + let known_size = features.consensus_encode_exact_size(); + let mut buf = Vec::with_capacity(known_size); + assert_eq!(known_size, 12); + let written = features.consensus_encode(&mut buf).unwrap(); + assert_eq!(buf.len(), 12); + assert_eq!(written, 12); + let decoded_features = OutputFeatures::consensus_decode(&mut &buf[..]).unwrap(); + assert_eq!(features, decoded_features); + } + + #[test] + fn consensus_decode_bad_flags() { + let data = [0x00u8, 0x00, 0x02]; + let features = OutputFeatures::consensus_decode(&mut &data[..]).unwrap(); + // Assert the flag data is preserved + assert_eq!(features.flags.bits & 0x02, 0x02); + } + + #[test] + fn consensus_decode_bad_maturity() { + let data = [0x00u8, 0xFF]; + let err = OutputFeatures::consensus_decode(&mut &data[..]).unwrap_err(); + assert_eq!(err.kind(), io::ErrorKind::UnexpectedEof); + } + + #[test] + fn consensus_decode_attempt_maturity_overflow() { + let data = [0x00u8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]; + let err = OutputFeatures::consensus_decode(&mut &data[..]).unwrap_err(); + assert_eq!(err.kind(), io::ErrorKind::InvalidData); + } + } } diff --git a/base_layer/core/src/transactions/transaction_protocol/mod.rs b/base_layer/core/src/transactions/transaction_protocol/mod.rs index 5e140c4388..62907deb92 100644 --- a/base_layer/core/src/transactions/transaction_protocol/mod.rs +++ b/base_layer/core/src/transactions/transaction_protocol/mod.rs @@ -80,6 +80,8 @@ //! end //! +// #![allow(clippy::op_ref)] + pub mod proto; pub mod recipient; pub mod sender; diff --git a/base_layer/core/src/transactions/transaction_protocol/recipient.rs b/base_layer/core/src/transactions/transaction_protocol/recipient.rs index 2518f8f1de..c3c6f99ac1 100644 --- a/base_layer/core/src/transactions/transaction_protocol/recipient.rs +++ b/base_layer/core/src/transactions/transaction_protocol/recipient.rs @@ -215,8 +215,8 @@ mod test { crypto::script::TariScript, transactions::{ crypto_factories::CryptoFactories, - helpers::TestParams, tari_amount::*, + test_helpers::TestParams, transaction::OutputFeatures, transaction_protocol::{ build_challenge, diff --git a/base_layer/core/src/transactions/transaction_protocol/sender.rs b/base_layer/core/src/transactions/transaction_protocol/sender.rs index bc932801a5..b38577a2ad 100644 --- a/base_layer/core/src/transactions/transaction_protocol/sender.rs +++ b/base_layer/core/src/transactions/transaction_protocol/sender.rs @@ -20,28 +20,31 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::transactions::{ - crypto_factories::CryptoFactories, - tari_amount::*, - transaction::{ - KernelBuilder, - KernelFeatures, - OutputFeatures, - Transaction, - TransactionBuilder, - TransactionInput, - TransactionOutput, - UnblindedOutput, - MAX_TRANSACTION_INPUTS, - MAX_TRANSACTION_OUTPUTS, - MINIMUM_TRANSACTION_FEE, - }, - transaction_protocol::{ - build_challenge, - recipient::{RecipientInfo, RecipientSignedMessage}, - transaction_initializer::SenderTransactionInitializer, - TransactionMetadata, - TransactionProtocolError as TPE, +use crate::{ + consensus::ConsensusConstants, + transactions::{ + crypto_factories::CryptoFactories, + fee::Fee, + tari_amount::*, + transaction::{ + KernelBuilder, + KernelFeatures, + OutputFeatures, + Transaction, + TransactionBuilder, + TransactionInput, + TransactionOutput, + UnblindedOutput, + MAX_TRANSACTION_INPUTS, + MAX_TRANSACTION_OUTPUTS, + }, + transaction_protocol::{ + build_challenge, + recipient::{RecipientInfo, RecipientSignedMessage}, + transaction_initializer::SenderTransactionInitializer, + TransactionMetadata, + TransactionProtocolError as TPE, + }, }, }; use digest::Digest; @@ -145,14 +148,14 @@ impl TransactionSenderMessage { //---------------------------------------- Sender State Protocol ----------------------------------------------------// #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct SenderTransactionProtocol { - pub(super) state: SenderState, + state: SenderState, } impl SenderTransactionProtocol { /// Begin constructing a new transaction. All the up-front data is collected via the `SenderTransactionInitializer` /// builder function - pub fn builder(num_recipients: usize) -> SenderTransactionInitializer { - SenderTransactionInitializer::new(num_recipients) + pub fn builder(num_recipients: usize, consensus_constants: ConsensusConstants) -> SenderTransactionInitializer { + SenderTransactionInitializer::new(num_recipients, consensus_constants) } /// Convenience method to check whether we're receiving recipient data @@ -492,7 +495,7 @@ impl SenderTransactionProtocol { if let SenderState::Finalizing(info) = &self.state { let fee = info.metadata.fee; // The fee must be greater than MIN_FEE to prevent spam attacks - if fee < MINIMUM_TRANSACTION_FEE { + if fee < Fee::MINIMUM_TRANSACTION_FEE { return Err(TPE::ValidationError("Fee is less than the minimum".into())); } // Prevent overflow attacks by imposing sane limits on some key parameters @@ -608,6 +611,17 @@ impl SenderTransactionProtocol { state: SenderState::Failed(TPE::IncompleteStateError("This is a placeholder protocol".to_string())), } } + + #[cfg(test)] + pub(super) fn into_state(self) -> SenderState { + self.state + } +} + +impl From for SenderTransactionProtocol { + fn from(state: SenderState) -> Self { + Self { state } + } } impl fmt::Display for SenderTransactionProtocol { @@ -718,17 +732,19 @@ mod test { tari_utilities::{hex::Hex, ByteArray}, }; - use crate::transactions::{ - crypto_factories::CryptoFactories, - fee::Fee, - helpers::{create_test_input, create_unblinded_output, TestParams}, - tari_amount::*, - transaction::{KernelFeatures, OutputFeatures, TransactionOutput}, - transaction_protocol::{ - sender::SenderTransactionProtocol, - single_receiver::SingleReceiverTransactionProtocol, - RewindData, - TransactionProtocolError, + use crate::{ + test_helpers::create_consensus_constants, + transactions::{ + crypto_factories::CryptoFactories, + tari_amount::*, + test_helpers::{create_test_input, create_unblinded_output, TestParams}, + transaction::{KernelFeatures, OutputFeatures, TransactionOutput}, + transaction_protocol::{ + sender::SenderTransactionProtocol, + single_receiver::SingleReceiverTransactionProtocol, + RewindData, + TransactionProtocolError, + }, }, }; use tari_common_types::types::{PrivateKey, PublicKey, RangeProof}; @@ -803,13 +819,13 @@ mod test { let p1 = TestParams::new(); let p2 = TestParams::new(); let (utxo, input) = create_test_input(MicroTari(1200), 0, &factories.commitment); - let mut builder = SenderTransactionProtocol::builder(0); + let mut builder = SenderTransactionProtocol::builder(0, create_consensus_constants(0)); let script = TariScript::default(); let output_features = OutputFeatures::default(); builder .with_lock_height(0) - .with_fee_per_gram(MicroTari(10)) + .with_fee_per_gram(MicroTari(2)) .with_offset(p1.offset.clone() + p2.offset.clone()) .with_private_nonce(p1.nonce.clone()) .with_change_secret(p1.change_spend_key.clone()) @@ -844,12 +860,13 @@ mod test { let b = TestParams::new(); let (utxo, input) = create_test_input(MicroTari(1200), 0, &factories.commitment); let script = script!(Nop); - let mut builder = SenderTransactionProtocol::builder(1); - let fee = Fee::calculate(MicroTari(20), 1, 1, 1); + let mut builder = SenderTransactionProtocol::builder(1, create_consensus_constants(0)); + let fee_per_gram = MicroTari(4); + let fee = builder.fee().calculate(fee_per_gram, 1, 1, 1, 0); let features = OutputFeatures::default(); builder .with_lock_height(0) - .with_fee_per_gram(MicroTari(20)) + .with_fee_per_gram(fee_per_gram) .with_offset(a.offset.clone()) .with_private_nonce(a.nonce.clone()) .with_input(utxo.clone(), input) @@ -902,9 +919,9 @@ mod test { // Bob's parameters let b = TestParams::new(); let (utxo, input) = create_test_input(MicroTari(25000), 0, &factories.commitment); - let mut builder = SenderTransactionProtocol::builder(1); + let mut builder = SenderTransactionProtocol::builder(1, create_consensus_constants(0)); let script = script!(Nop); - let fee = Fee::calculate(MicroTari(20), 1, 1, 2); + let fee = builder.fee().calculate(MicroTari(20), 1, 1, 2, 0); let features = OutputFeatures::default(); builder .with_lock_height(0) @@ -982,7 +999,7 @@ mod test { // Bob's parameters let b = TestParams::new(); let (utxo, input) = create_test_input((2u64.pow(32) + 2001).into(), 0, &factories.commitment); - let mut builder = SenderTransactionProtocol::builder(1); + let mut builder = SenderTransactionProtocol::builder(1, create_consensus_constants(0)); let script = script!(Nop); let features = OutputFeatures::default(); @@ -1019,19 +1036,15 @@ mod test { } } - fn get_fee_larger_than_amount_values() -> (MicroTari, MicroTari, MicroTari) { - (MicroTari(2500), MicroTari(51), MicroTari(500)) - } - #[test] fn disallow_fee_larger_than_amount() { let factories = CryptoFactories::default(); // Alice's parameters let alice = TestParams::new(); - let (utxo_amount, fee_per_gram, amount) = get_fee_larger_than_amount_values(); + let (utxo_amount, fee_per_gram, amount) = (MicroTari(2500), MicroTari(10), MicroTari(500)); let (utxo, input) = create_test_input(utxo_amount, 0, &factories.commitment); let script = script!(Nop); - let mut builder = SenderTransactionProtocol::builder(1); + let mut builder = SenderTransactionProtocol::builder(1, create_consensus_constants(0)); builder .with_lock_height(0) .with_fee_per_gram(fee_per_gram) @@ -1060,10 +1073,10 @@ mod test { let factories = CryptoFactories::default(); // Alice's parameters let alice = TestParams::new(); - let (utxo_amount, fee_per_gram, amount) = get_fee_larger_than_amount_values(); + let (utxo_amount, fee_per_gram, amount) = (MicroTari(2500), MicroTari(10), MicroTari(500)); let (utxo, input) = create_test_input(utxo_amount, 0, &factories.commitment); let script = script!(Nop); - let mut builder = SenderTransactionProtocol::builder(1); + let mut builder = SenderTransactionProtocol::builder(1, create_consensus_constants(0)); builder .with_lock_height(0) .with_fee_per_gram(fee_per_gram) @@ -1114,7 +1127,7 @@ mod test { let script = script!(Nop); let features = OutputFeatures::default(); - let mut builder = SenderTransactionProtocol::builder(1); + let mut builder = SenderTransactionProtocol::builder(1, create_consensus_constants(0)); builder .with_lock_height(0) .with_fee_per_gram(MicroTari(20)) diff --git a/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs b/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs index 0a9da558ae..ef45b0a449 100644 --- a/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs +++ b/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs @@ -20,14 +20,36 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use std::{ - collections::HashMap, - fmt::{Debug, Error, Formatter}, +use crate::{ + consensus::{ConsensusConstants, ConsensusEncodingSized, ConsensusEncodingWrapper}, + transactions::{ + crypto_factories::CryptoFactories, + fee::Fee, + tari_amount::*, + transaction::{ + OutputFeatures, + TransactionInput, + TransactionOutput, + UnblindedOutput, + MAX_TRANSACTION_INPUTS, + MAX_TRANSACTION_OUTPUTS, + }, + transaction_protocol::{ + recipient::RecipientInfo, + sender::{calculate_tx_id, RawTransactionInfo, SenderState, SenderTransactionProtocol}, + RewindData, + TransactionMetadata, + }, + }, }; - use digest::Digest; use log::*; use rand::rngs::OsRng; +use std::{ + collections::HashMap, + fmt::{Debug, Error, Formatter}, +}; +use tari_common_types::types::{BlindingFactor, PrivateKey, PublicKey}; use tari_crypto::{ commitment::HomomorphicCommitmentFactory, keys::{PublicKey as PublicKeyTrait, SecretKey}, @@ -36,28 +58,6 @@ use tari_crypto::{ tari_utilities::fixed_set::FixedSet, }; -use crate::transactions::{ - crypto_factories::CryptoFactories, - fee::Fee, - tari_amount::*, - transaction::{ - OutputFeatures, - TransactionInput, - TransactionOutput, - UnblindedOutput, - MAX_TRANSACTION_INPUTS, - MAX_TRANSACTION_OUTPUTS, - MINIMUM_TRANSACTION_FEE, - }, - transaction_protocol::{ - recipient::RecipientInfo, - sender::{calculate_tx_id, RawTransactionInfo, SenderState, SenderTransactionProtocol}, - RewindData, - TransactionMetadata, - }, -}; -use tari_common_types::types::{BlindingFactor, PrivateKey, PublicKey}; - pub const LOG_TARGET: &str = "c::tx::tx_protocol::tx_initializer"; /// The SenderTransactionInitializer is a Builder that helps set up the initial state for the Sender party of a new @@ -70,6 +70,7 @@ pub const LOG_TARGET: &str = "c::tx::tx_protocol::tx_initializer"; /// methods, you can call `build()` which will return a #[derive(Debug, Clone)] pub struct SenderTransactionInitializer { + consensus_constants: ConsensusConstants, num_recipients: usize, amounts: FixedSet, lock_height: Option, @@ -94,6 +95,7 @@ pub struct SenderTransactionInitializer { recipient_sender_offset_private_keys: FixedSet, private_commitment_nonces: FixedSet, tx_id: Option, + fee: Fee, } pub struct BuildError { @@ -108,8 +110,10 @@ impl Debug for BuildError { } impl SenderTransactionInitializer { - pub fn new(num_recipients: usize) -> Self { + pub fn new(num_recipients: usize, consensus_constants: ConsensusConstants) -> Self { Self { + fee: Fee::new(*consensus_constants.transaction_weight()), + consensus_constants, num_recipients, amounts: FixedSet::new(num_recipients), lock_height: None, @@ -267,6 +271,33 @@ impl SenderTransactionInitializer { self } + fn get_total_metadata_size_for_outputs(&self) -> usize { + let mut size = 0; + size += self.get_output_features().consensus_encode_exact_size() * self.num_recipients; + size += self + .sender_custom_outputs + .iter() + .map(|o| { + o.features.consensus_encode_exact_size() + + ConsensusEncodingWrapper::wrap(&o.script).consensus_encode_exact_size() + }) + .sum::(); + // TODO: implement iter for FixedSet to avoid the clone + size += self + .recipient_scripts + .clone() + .into_vec() + .iter() + .map(|script| ConsensusEncodingWrapper::wrap(script).consensus_encode_exact_size()) + .sum::(); + + size + } + + fn get_output_features(&self) -> OutputFeatures { + Default::default() + } + /// Tries to make a change output with the given transaction parameters and add it to the set of outputs. The total /// fee, including the additional change output (if any) is returned along with the amount of change. /// The change output **always has default output features**. @@ -278,13 +309,36 @@ impl SenderTransactionInitializer { let total_to_self = self.sender_custom_outputs.iter().map(|o| o.value).sum::(); let total_amount = self.amounts.sum().ok_or("Not all amounts have been provided")?; let fee_per_gram = self.fee_per_gram.ok_or("Fee per gram was not provided")?; - let fee_without_change = Fee::calculate(fee_per_gram, 1, num_inputs, num_outputs); - let fee_with_change = Fee::calculate(fee_per_gram, 1, num_inputs, num_outputs + 1); + + let metadata_size_without_change = self.get_total_metadata_size_for_outputs(); + let fee_without_change = + self.fee() + .calculate(fee_per_gram, 1, num_inputs, num_outputs, metadata_size_without_change); + + let output_features = self.get_output_features(); + let change_metadata_size = self + .change_script + .as_ref() + .map(|script| ConsensusEncodingWrapper::wrap(script).consensus_encode_exact_size()) + .unwrap_or(0) + + output_features.consensus_encode_exact_size(); + + let fee_with_change = self.fee().calculate( + fee_per_gram, + 1, + num_inputs, + num_outputs + 1, + metadata_size_without_change + change_metadata_size, + ); let extra_fee = fee_with_change - fee_without_change; // Subtract with a check on going negative - let change_amount = total_being_spent.checked_sub(total_to_self + total_amount + fee_without_change); + let total_input_value = total_to_self + total_amount + fee_without_change; + let change_amount = total_being_spent.checked_sub(total_input_value); match change_amount { - None => Err("You are spending more than you're providing".into()), + None => Err(format!( + "You are spending ({}) more than you're providing ({}).", + total_input_value, total_being_spent + )), Some(MicroTari(0)) => Ok((fee_without_change, MicroTari(0), None)), Some(v) => { let change_amount = v.checked_sub(extra_fee); @@ -302,7 +356,6 @@ impl SenderTransactionInitializer { .as_ref() .ok_or("Change script was not provided")? .clone(); - let output_features = OutputFeatures::default(); let change_key = self .change_secret .as_ref() @@ -361,6 +414,10 @@ impl SenderTransactionInitializer { self.amounts.clone().into_vec().iter().sum() } + pub(super) fn fee(&self) -> &Fee { + &self.fee + } + /// Construct a `SenderTransactionProtocol` instance in and appropriate state. The data stored /// in the struct is _moved_ into the new struct. If any data is missing, the `self` instance is returned in the /// error (so that you can continue building) along with a string listing the missing fields. @@ -412,7 +469,7 @@ impl SenderTransactionInitializer { "Build transaction with Fee: {}. Change: {}. Output: {:?}", total_fee, change, change_output, ); // Some checks on the fee - if total_fee < MINIMUM_TRANSACTION_FEE { + if total_fee < Fee::MINIMUM_TRANSACTION_FEE { return self.build_err("Fee is less than the minimum"); } // Create transaction outputs @@ -566,7 +623,7 @@ impl SenderTransactionInitializer { let state = state .initialize() .expect("It should be possible to call initialize from Initializing state"); - Ok(SenderTransactionProtocol { state }) + Ok(state.into()) } } @@ -583,12 +640,12 @@ mod test { }; use crate::{ - consensus::{KERNEL_WEIGHT, WEIGHT_PER_INPUT, WEIGHT_PER_OUTPUT}, + test_helpers::create_consensus_constants, transactions::{ crypto_factories::CryptoFactories, fee::Fee, - helpers::{create_test_input, create_unblinded_output, TestParams, UtxoTestParams}, tari_amount::*, + test_helpers::{create_test_input, create_unblinded_output, TestParams, UtxoTestParams}, transaction::{OutputFeatures, MAX_TRANSACTION_INPUTS}, transaction_protocol::{ sender::SenderState, @@ -606,7 +663,7 @@ mod test { let factories = CryptoFactories::default(); let p = TestParams::new(); // Start the builder - let builder = SenderTransactionInitializer::new(0); + let builder = SenderTransactionInitializer::new(0, create_consensus_constants(0)); let err = builder.build::(&factories).unwrap_err(); let script = script!(Nop); // We should have a bunch of fields missing still, but we can recover and continue @@ -642,7 +699,7 @@ mod test { PrivateKey::random(&mut OsRng), ) .with_change_script(script, ExecutionStack::default(), PrivateKey::default()); - let expected_fee = Fee::calculate(MicroTari(20), 1, 1, 2); + let expected_fee = builder.fee().calculate(MicroTari(20), 1, 1, 2, 0); // We needed a change input, so this should fail let err = builder.build::(&factories).unwrap_err(); assert_eq!(err.message, "Change spending key was not provided"); @@ -651,7 +708,7 @@ mod test { builder.with_change_secret(p.change_spend_key); let result = builder.build::(&factories).unwrap(); // Peek inside and check the results - if let SenderState::Finalizing(info) = result.state { + if let SenderState::Finalizing(info) = result.into_state() { assert_eq!(info.num_recipients, 0, "Number of receivers"); assert_eq!(info.signatures.len(), 0, "Number of signatures"); assert_eq!(info.amounts.len(), 0, "Number of external payment amounts"); @@ -671,7 +728,8 @@ mod test { let factories = CryptoFactories::default(); let p = TestParams::new(); let (utxo, input) = create_test_input(MicroTari(500), 0, &factories.commitment); - let expected_fee = Fee::calculate(MicroTari(20), 1, 1, 1); + let constants = create_consensus_constants(0); + let expected_fee = Fee::from(*constants.transaction_weight()).calculate(MicroTari(4), 1, 1, 1, 0); let output = create_unblinded_output( TariScript::default(), @@ -680,7 +738,7 @@ mod test { MicroTari(500) - expected_fee, ); // Start the builder - let mut builder = SenderTransactionInitializer::new(0); + let mut builder = SenderTransactionInitializer::new(0, constants); builder .with_lock_height(0) .with_offset(p.offset) @@ -688,11 +746,11 @@ mod test { .with_output(output, p.sender_offset_private_key) .unwrap() .with_input(utxo, input) - .with_fee_per_gram(MicroTari(20)) + .with_fee_per_gram(MicroTari(4)) .with_prevent_fee_gt_amount(false); let result = builder.build::(&factories).unwrap(); // Peek inside and check the results - if let SenderState::Finalizing(info) = result.state { + if let SenderState::Finalizing(info) = result.into_state() { assert_eq!(info.num_recipients, 0, "Number of receivers"); assert_eq!(info.signatures.len(), 0, "Number of signatures"); assert_eq!(info.amounts.len(), 0, "Number of external payment amounts"); @@ -712,17 +770,26 @@ mod test { // Create some inputs let factories = CryptoFactories::default(); let p = TestParams::new(); - let (utxo, input) = create_test_input(MicroTari(500), 0, &factories.commitment); - let expected_fee = MicroTari::from((KERNEL_WEIGHT + WEIGHT_PER_INPUT + 1 * WEIGHT_PER_OUTPUT) * 20); + let constants = create_consensus_constants(0); + let weighting = constants.transaction_weight(); + let tx_fee = Fee::new(*weighting).calculate(1.into(), 1, 1, 1, 0); + let fee_for_change_output = weighting.params().output_weight * uT; // fee == 340, output = 80 + // outputs weight: 1060, kernel weight: 10, input weight: 9, output weight: 53, // Pay out so that I should get change, but not enough to pay for the output + let (utxo, input) = create_test_input( + // one under the amount required to pay the fee for a change output + 2000 * uT + tx_fee + fee_for_change_output - 1 * uT, + 0, + &factories.commitment, + ); let output = p.create_unblinded_output(UtxoTestParams { - value: MicroTari(500) - expected_fee - MicroTari(50), + value: 2000 * uT, ..Default::default() }); // Start the builder - let mut builder = SenderTransactionInitializer::new(0); + let mut builder = SenderTransactionInitializer::new(0, constants); builder .with_lock_height(0) .with_offset(p.offset) @@ -730,16 +797,16 @@ mod test { .with_output(output, p.sender_offset_private_key) .unwrap() .with_input(utxo, input) - .with_fee_per_gram(MicroTari(20)) + .with_fee_per_gram(MicroTari(1)) .with_prevent_fee_gt_amount(false); let result = builder.build::(&factories).unwrap(); // Peek inside and check the results - if let SenderState::Finalizing(info) = result.state { + if let SenderState::Finalizing(info) = result.into_state() { assert_eq!(info.num_recipients, 0, "Number of receivers"); assert_eq!(info.signatures.len(), 0, "Number of signatures"); assert_eq!(info.amounts.len(), 0, "Number of external payment amounts"); assert_eq!(info.metadata.lock_height, 0, "Lock height"); - assert_eq!(info.metadata.fee, expected_fee + MicroTari(50), "Fee"); + assert_eq!(info.metadata.fee, tx_fee + fee_for_change_output - 1 * uT, "Fee"); assert_eq!(info.outputs.len(), 1, "There should be 1 output"); assert_eq!(info.inputs.len(), 1, "There should be 1 input"); } else { @@ -759,8 +826,9 @@ mod test { p.clone(), MicroTari(500), ); + let constants = create_consensus_constants(0); // Start the builder - let mut builder = SenderTransactionInitializer::new(0); + let mut builder = SenderTransactionInitializer::new(0, constants); builder .with_lock_height(0) .with_offset(p.offset) @@ -782,11 +850,13 @@ mod test { // Create some inputs let factories = CryptoFactories::default(); let p = TestParams::new(); - let (utxo, input) = create_test_input(MicroTari(500), 0, &factories.commitment); + let tx_fee = p.fee().calculate(MicroTari(1), 1, 1, 1, 0); + let (utxo, input) = create_test_input(500 * uT + tx_fee, 0, &factories.commitment); let script = script!(Nop); - let output = create_unblinded_output(script.clone(), OutputFeatures::default(), p.clone(), MicroTari(400)); + let output = create_unblinded_output(script.clone(), OutputFeatures::default(), p.clone(), MicroTari(500)); // Start the builder - let mut builder = SenderTransactionInitializer::new(0); + let constants = create_consensus_constants(0); + let mut builder = SenderTransactionInitializer::new(0, constants); builder .with_lock_height(0) .with_offset(p.offset) @@ -798,12 +868,12 @@ mod test { .with_fee_per_gram(MicroTari(1)) .with_recipient_data( 0, - script.clone(), + script, PrivateKey::random(&mut OsRng), Default::default(), PrivateKey::random(&mut OsRng), - ) - .with_change_script(script, ExecutionStack::default(), PrivateKey::default()); + ); + // .with_change_script(script, ExecutionStack::default(), PrivateKey::default()); let err = builder.build::(&factories).unwrap_err(); assert_eq!(err.message, "Fee is less than the minimum"); } @@ -817,7 +887,8 @@ mod test { let script = script!(Nop); let output = create_unblinded_output(script.clone(), OutputFeatures::default(), p.clone(), MicroTari(400)); // Start the builder - let mut builder = SenderTransactionInitializer::new(0); + let constants = create_consensus_constants(0); + let mut builder = SenderTransactionInitializer::new(0, constants); builder .with_lock_height(0) .with_offset(p.offset) @@ -836,7 +907,10 @@ mod test { ) .with_change_script(script, ExecutionStack::default(), PrivateKey::default()); let err = builder.build::(&factories).unwrap_err(); - assert_eq!(err.message, "You are spending more than you're providing"); + assert_eq!( + err.message, + "You are spending (471 µT) more than you're providing (400 µT)." + ); } #[test] @@ -848,7 +922,8 @@ mod test { let script = script!(Nop); let output = create_unblinded_output(script.clone(), OutputFeatures::default(), p.clone(), MicroTari(15000)); // Start the builder - let mut builder = SenderTransactionInitializer::new(2); + let constants = create_consensus_constants(0); + let mut builder = SenderTransactionInitializer::new(2, constants); builder .with_lock_height(0) .with_offset(p.offset) @@ -859,7 +934,7 @@ mod test { .with_output(output, p.sender_offset_private_key.clone()) .unwrap() .with_change_secret(p.change_spend_key) - .with_fee_per_gram(MicroTari(20)) + .with_fee_per_gram(MicroTari(4)) .with_recipient_data( 0, script.clone(), @@ -877,7 +952,7 @@ mod test { .with_change_script(script, ExecutionStack::default(), PrivateKey::default()); let result = builder.build::(&factories).unwrap(); // Peek inside and check the results - if let SenderState::Failed(TransactionProtocolError::UnsupportedError(s)) = result.state { + if let SenderState::Failed(TransactionProtocolError::UnsupportedError(s)) = result.into_state() { assert_eq!(s, "Multiple recipients are not supported yet") } else { panic!("We should not allow multiple recipients at this time"); @@ -891,10 +966,11 @@ mod test { let p = TestParams::new(); let (utxo1, input1) = create_test_input(MicroTari(2000), 0, &factories.commitment); let (utxo2, input2) = create_test_input(MicroTari(3000), 0, &factories.commitment); - let weight = MicroTari(30); + let fee_per_gram = MicroTari(6); let script = script!(Nop); - let expected_fee = Fee::calculate(weight, 1, 2, 3); + let constants = create_consensus_constants(0); + let expected_fee = Fee::from(*constants.transaction_weight()).calculate(fee_per_gram, 1, 2, 3, 0); let output = create_unblinded_output( script.clone(), OutputFeatures::default(), @@ -902,7 +978,7 @@ mod test { MicroTari(1500) - expected_fee, ); // Start the builder - let mut builder = SenderTransactionInitializer::new(1); + let mut builder = SenderTransactionInitializer::new(1, constants); builder .with_lock_height(1234) .with_offset(p.offset) @@ -913,7 +989,7 @@ mod test { .with_input(utxo2, input2) .with_amount(0, MicroTari(2500)) .with_change_secret(p.change_spend_key) - .with_fee_per_gram(weight) + .with_fee_per_gram(fee_per_gram) .with_recipient_data( 0, script.clone(), @@ -924,7 +1000,7 @@ mod test { .with_change_script(script, ExecutionStack::default(), PrivateKey::default()); let result = builder.build::(&factories).unwrap(); // Peek inside and check the results - if let SenderState::SingleRoundMessageReady(info) = result.state { + if let SenderState::SingleRoundMessageReady(info) = result.into_state() { assert_eq!(info.num_recipients, 1, "Number of receivers"); assert_eq!(info.signatures.len(), 0, "Number of signatures"); assert_eq!(info.amounts.len(), 1, "Number of external payment amounts"); @@ -952,8 +1028,9 @@ mod test { ); // Start the builder let (utxo1, input1) = create_test_input((2u64.pow(32) + 20000u64).into(), 0, &factories.commitment); - let weight = MicroTari(30); - let mut builder = SenderTransactionInitializer::new(1); + let fee_per_gram = MicroTari(6); + let constants = create_consensus_constants(0); + let mut builder = SenderTransactionInitializer::new(1, constants); builder .with_lock_height(1234) .with_offset(p.offset) @@ -963,7 +1040,7 @@ mod test { .with_input(utxo1, input1) .with_amount(0, MicroTari(9800)) .with_change_secret(p.change_spend_key) - .with_fee_per_gram(weight) + .with_fee_per_gram(fee_per_gram) .with_recipient_data( 0, script.clone(), diff --git a/base_layer/core/src/transactions/weight.rs b/base_layer/core/src/transactions/weight.rs new file mode 100644 index 0000000000..2cadfe22de --- /dev/null +++ b/base_layer/core/src/transactions/weight.rs @@ -0,0 +1,114 @@ +// Copyright 2019. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::transactions::aggregated_body::AggregateBody; +use std::num::NonZeroU64; + +#[derive(Debug, Clone, Copy)] +pub struct WeightParams { + /// Weight in grams per kernel + pub kernel_weight: u64, + /// Weight in grams per input + pub input_weight: u64, + /// Weight in grams per output, excl. TariScript and OutputFeatures + pub output_weight: u64, + /// Metadata per byte weight + pub metadata_bytes_per_gram: Option, +} + +impl WeightParams { + pub const fn v1() -> Self { + Self { + kernel_weight: 3, + input_weight: 1, + output_weight: 13, + metadata_bytes_per_gram: None, + } + } + + pub const fn v2() -> Self { + Self { + kernel_weight: 10, // ajd. +2 + input_weight: 8, // ajd. -3 + output_weight: 53, + // SAFETY: the value isn't 0. NonZeroU64::new(x).expect(...) is not const so cannot be used in const fn + metadata_bytes_per_gram: Some(unsafe { NonZeroU64::new_unchecked(16) }), + } + } +} + +#[derive(Debug, Clone, Copy)] +pub struct TransactionWeight(WeightParams); + +impl TransactionWeight { + /// Creates a new `TransactionWeight` with latest weight params + pub fn latest() -> Self { + Self(WeightParams::v2()) + } + + /// Creates a new `TransactionWeight` with v1 weight params + pub fn v1() -> Self { + Self(WeightParams::v1()) + } + + /// Creates a new `TransactionWeight` with v2 weight params + pub fn v2() -> Self { + Self(WeightParams::v2()) + } + + /// Calculate the weight of a transaction based on the number of inputs and outputs + pub fn calculate( + &self, + num_kernels: usize, + num_inputs: usize, + num_outputs: usize, + metadata_byte_size: usize, + ) -> u64 { + let params = self.params(); + params.kernel_weight * num_kernels as u64 + + params.input_weight * num_inputs as u64 + + params.output_weight * num_outputs as u64 + + params + .metadata_bytes_per_gram + .map(|per_gram| metadata_byte_size as u64 / per_gram.get()) + .unwrap_or(0) + } + + pub fn calculate_body(&self, body: &AggregateBody) -> u64 { + self.calculate( + body.kernels().len(), + body.inputs().len(), + body.outputs().len(), + body.sum_metadata_size(), + ) + } + + pub fn params(&self) -> &WeightParams { + &self.0 + } +} + +impl From for TransactionWeight { + fn from(params: WeightParams) -> Self { + Self(params) + } +} diff --git a/base_layer/core/src/validation/block_validators/body_only.rs b/base_layer/core/src/validation/block_validators/body_only.rs index 2e722654f7..c6dfc9228f 100644 --- a/base_layer/core/src/validation/block_validators/body_only.rs +++ b/base_layer/core/src/validation/block_validators/body_only.rs @@ -22,8 +22,9 @@ use super::LOG_TARGET; use crate::{ + blocks::ChainBlock, chain_storage, - chain_storage::{BlockchainBackend, ChainBlock}, + chain_storage::BlockchainBackend, crypto::tari_utilities::hex::Hex, validation::{helpers, PostOrphanBodyValidation, ValidationError}, }; diff --git a/base_layer/core/src/validation/block_validators/test.rs b/base_layer/core/src/validation/block_validators/test.rs index 5b72434e70..2f59a67c11 100644 --- a/base_layer/core/src/validation/block_validators/test.rs +++ b/base_layer/core/src/validation/block_validators/test.rs @@ -28,8 +28,8 @@ use crate::{ }, transactions::{ aggregated_body::AggregateBody, - helpers::schema_to_transaction, tari_amount::T, + test_helpers::schema_to_transaction, transaction::TransactionError, CoinbaseBuilder, CryptoFactories, diff --git a/base_layer/core/src/validation/error.rs b/base_layer/core/src/validation/error.rs index 10f5225d04..ff31cded40 100644 --- a/base_layer/core/src/validation/error.rs +++ b/base_layer/core/src/validation/error.rs @@ -21,7 +21,7 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use crate::{ - blocks::{block_header::BlockHeaderValidationError, BlockValidationError}, + blocks::{BlockHeaderValidationError, BlockValidationError}, chain_storage::ChainStorageError, proof_of_work::{monero_rx::MergeMineError, PowError}, transactions::transaction::TransactionError, diff --git a/base_layer/core/src/validation/helpers.rs b/base_layer/core/src/validation/helpers.rs index ef6bb8e0db..ef099efe4a 100644 --- a/base_layer/core/src/validation/helpers.rs +++ b/base_layer/core/src/validation/helpers.rs @@ -25,11 +25,7 @@ use log::*; use tari_crypto::tari_utilities::{epoch_time::EpochTime, hash::Hashable, hex::Hex}; use crate::{ - blocks::{ - block_header::{BlockHeader, BlockHeaderValidationError}, - Block, - BlockValidationError, - }, + blocks::{Block, BlockHeader, BlockHeaderValidationError, BlockValidationError}, chain_storage::{BlockchainBackend, MmrRoots, MmrTree}, consensus::{emission::Emission, ConsensusConstants, ConsensusManager}, crypto::commitment::HomomorphicCommitmentFactory, @@ -200,7 +196,7 @@ pub fn check_target_difficulty( pub fn check_block_weight(block: &Block, consensus_constants: &ConsensusConstants) -> Result<(), ValidationError> { // The genesis block has a larger weight than other blocks may have so we have to exclude it here - let block_weight = block.body.calculate_weight(); + let block_weight = block.body.calculate_weight(consensus_constants.transaction_weight()); if block_weight <= consensus_constants.get_max_block_transaction_weight() || block.header.height == 0 { trace!( target: LOG_TARGET, @@ -465,7 +461,7 @@ pub fn check_mmr_roots(header: &BlockHeader, mmr_roots: &MmrRoots) -> Result<(), mmr_roots.kernel_mmr_size ); return Err(ValidationError::BlockError(BlockValidationError::MismatchedMmrSize { - mmr_tree: MmrTree::Kernel, + mmr_tree: MmrTree::Kernel.to_string(), expected: mmr_roots.kernel_mmr_size, actual: header.kernel_mmr_size, })); @@ -497,7 +493,7 @@ pub fn check_mmr_roots(header: &BlockHeader, mmr_roots: &MmrRoots) -> Result<(), mmr_roots.output_mmr_size ); return Err(ValidationError::BlockError(BlockValidationError::MismatchedMmrSize { - mmr_tree: MmrTree::Utxo, + mmr_tree: MmrTree::Utxo.to_string(), expected: mmr_roots.output_mmr_size, actual: header.output_mmr_size, })); @@ -647,11 +643,11 @@ mod test { mod check_lock_height { use super::*; - use crate::transactions::helpers; + use crate::transactions::test_helpers; #[test] fn it_checks_the_kernel_timelock() { - let mut kernel = helpers::create_test_kernel(0.into(), 0); + let mut kernel = test_helpers::create_test_kernel(0.into(), 0); kernel.lock_height = 2; assert_eq!( check_kernel_lock_height(1, &[kernel.clone()]), diff --git a/base_layer/core/src/validation/mocks.rs b/base_layer/core/src/validation/mocks.rs index d5d347133b..7e9543dc11 100644 --- a/base_layer/core/src/validation/mocks.rs +++ b/base_layer/core/src/validation/mocks.rs @@ -21,8 +21,8 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use crate::{ - blocks::{Block, BlockHeader}, - chain_storage::{BlockchainBackend, ChainBlock}, + blocks::{Block, BlockHeader, ChainBlock}, + chain_storage::BlockchainBackend, proof_of_work::{sha3_difficulty, AchievedTargetDifficulty, Difficulty, PowAlgorithm}, transactions::transaction::Transaction, validation::{ diff --git a/base_layer/core/src/validation/test.rs b/base_layer/core/src/validation/test.rs index 886a878dcb..65d66da23c 100644 --- a/base_layer/core/src/validation/test.rs +++ b/base_layer/core/src/validation/test.rs @@ -27,15 +27,15 @@ use tari_crypto::{commitment::HomomorphicCommitment, script}; use tari_common::configuration::Network; use crate::{ - blocks::BlockHeader, - chain_storage::{BlockHeaderAccumulatedData, ChainBlock, ChainHeader, DbTransaction}, - consensus::{ConsensusConstantsBuilder, ConsensusManagerBuilder}, + blocks::{BlockHeader, BlockHeaderAccumulatedData, ChainBlock, ChainHeader}, + chain_storage::DbTransaction, + consensus::{ConsensusConstantsBuilder, ConsensusManager, ConsensusManagerBuilder}, crypto::tari_utilities::Hashable, proof_of_work::AchievedTargetDifficulty, test_helpers::{blockchain::create_store_with_consensus, create_chain_header}, transactions::{ - helpers::{create_random_signature_from_s_key, create_utxo}, tari_amount::{uT, MicroTari}, + test_helpers::{create_random_signature_from_s_key, create_utxo}, transaction::{KernelBuilder, KernelFeatures, OutputFeatures, TransactionKernel}, CryptoFactories, }, @@ -45,7 +45,7 @@ use tari_common_types::types::Commitment; #[test] fn header_iter_empty_and_invalid_height() { - let consensus_manager = ConsensusManagerBuilder::new(Network::LocalNet).build(); + let consensus_manager = ConsensusManager::builder(Network::LocalNet).build(); let genesis = consensus_manager.get_genesis_block(); let db = create_store_with_consensus(consensus_manager); diff --git a/base_layer/core/src/validation/traits.rs b/base_layer/core/src/validation/traits.rs index 684e68600a..dde06df434 100644 --- a/base_layer/core/src/validation/traits.rs +++ b/base_layer/core/src/validation/traits.rs @@ -21,8 +21,8 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use crate::{ - blocks::{Block, BlockHeader}, - chain_storage::{BlockchainBackend, ChainBlock}, + blocks::{Block, BlockHeader, ChainBlock}, + chain_storage::BlockchainBackend, proof_of_work::AchievedTargetDifficulty, transactions::transaction::Transaction, validation::{error::ValidationError, DifficultyCalculator}, diff --git a/base_layer/core/src/validation/transaction_validators.rs b/base_layer/core/src/validation/transaction_validators.rs index 9ea8bdb2d0..169b29c923 100644 --- a/base_layer/core/src/validation/transaction_validators.rs +++ b/base_layer/core/src/validation/transaction_validators.rs @@ -81,7 +81,9 @@ impl MempoolTransactionValidation for TxConsensusValidator fn validate(&self, tx: &Transaction) -> Result<(), ValidationError> { let consensus_constants = self.db.consensus_constants()?; // validate maximum tx weight - if tx.calculate_weight() > consensus_constants.get_max_block_weight_excluding_coinbase() { + if tx.calculate_weight(consensus_constants.transaction_weight()) > + consensus_constants.get_max_block_weight_excluding_coinbase() + { return Err(ValidationError::MaxTransactionWeightExceeded); } @@ -105,8 +107,8 @@ impl TxInputAndMaturityValidator { impl MempoolTransactionValidation for TxInputAndMaturityValidator { fn validate(&self, tx: &Transaction) -> Result<(), ValidationError> { let db = self.db.db_read_access()?; - check_inputs_are_utxos(&*db, tx.get_body())?; - check_not_duplicate_txos(&*db, tx.get_body())?; + check_inputs_are_utxos(&*db, tx.body())?; + check_not_duplicate_txos(&*db, tx.body())?; let tip_height = db.fetch_chain_metadata()?.height_of_longest_chain(); verify_timelocks(tx, tip_height)?; diff --git a/base_layer/core/tests/async_db.rs b/base_layer/core/tests/async_db.rs index 832093d779..380db7f73a 100644 --- a/base_layer/core/tests/async_db.rs +++ b/base_layer/core/tests/async_db.rs @@ -36,8 +36,8 @@ use tari_core::{ blocks::Block, chain_storage::{async_db::AsyncBlockchainDb, BlockAddResult, PrunedOutput}, transactions::{ - helpers::schema_to_transaction, tari_amount::T, + test_helpers::schema_to_transaction, transaction::{TransactionOutput, UnblindedOutput}, CryptoFactories, }, diff --git a/base_layer/core/tests/base_node_rpc.rs b/base_layer/core/tests/base_node_rpc.rs index 3b1523d143..d89e5d2b30 100644 --- a/base_layer/core/tests/base_node_rpc.rs +++ b/base_layer/core/tests/base_node_rpc.rs @@ -62,7 +62,6 @@ use tari_core::{ rpc::{BaseNodeWalletRpcService, BaseNodeWalletService}, state_machine_service::states::{ListeningInfo, StateInfo, StatusInfo}, }, - chain_storage::ChainBlock, consensus::{ConsensusManager, ConsensusManagerBuilder, NetworkConsensus}, crypto::tari_utilities::Hashable, proto::{ @@ -71,8 +70,8 @@ use tari_core::{ }, test_helpers::blockchain::TempDatabase, transactions::{ - helpers::schema_to_transaction, tari_amount::{uT, T}, + test_helpers::schema_to_transaction, transaction::{TransactionOutput, UnblindedOutput}, CryptoFactories, }, @@ -83,6 +82,7 @@ use crate::helpers::{ block_builders::{chain_block, create_genesis_block_with_coinbase_value}, nodes::{BaseNodeBuilder, NodeInterfaces}, }; +use tari_core::blocks::ChainBlock; mod helpers; diff --git a/base_layer/core/tests/block_validation.rs b/base_layer/core/tests/block_validation.rs index a05688f5ef..c838e88aa4 100644 --- a/base_layer/core/tests/block_validation.rs +++ b/base_layer/core/tests/block_validation.rs @@ -35,15 +35,8 @@ use rand::{rngs::OsRng, RngCore}; use std::sync::Arc; use tari_common::configuration::Network; use tari_core::{ - blocks::{Block, BlockHeaderValidationError, BlockValidationError}, - chain_storage::{ - BlockHeaderAccumulatedData, - BlockchainDatabase, - BlockchainDatabaseConfig, - ChainBlock, - ChainStorageError, - Validators, - }, + blocks::{Block, BlockHeaderAccumulatedData, BlockHeaderValidationError, BlockValidationError, ChainBlock}, + chain_storage::{BlockchainDatabase, BlockchainDatabaseConfig, ChainStorageError, Validators}, consensus::{consensus_constants::PowAlgorithmConstants, ConsensusConstantsBuilder, ConsensusManager}, crypto::tari_utilities::hex::Hex, proof_of_work::{ @@ -56,8 +49,8 @@ use tari_core::{ test_helpers::blockchain::{create_store_with_consensus_and_validators, create_test_db}, transactions::{ aggregated_body::AggregateBody, - helpers::{create_unblinded_output, schema_to_transaction, spend_utxos, TestParams, UtxoTestParams}, tari_amount::{uT, T}, + test_helpers::{create_unblinded_output, schema_to_transaction, spend_utxos, TestParams, UtxoTestParams}, transaction::OutputFeatures, CryptoFactories, }, diff --git a/base_layer/core/tests/chain_storage_tests/chain_backend.rs b/base_layer/core/tests/chain_storage_tests/chain_backend.rs index 16c00d725c..c60ac4f066 100644 --- a/base_layer/core/tests/chain_storage_tests/chain_backend.rs +++ b/base_layer/core/tests/chain_storage_tests/chain_backend.rs @@ -38,8 +38,8 @@ fn lmdb_insert_contains_delete_and_fetch_orphan() { let consensus = ConsensusManagerBuilder::new(network).build(); let mut db = create_test_db(); let txs = vec![ - (tx!(1000.into(), fee: 20.into(), inputs: 2, outputs: 1)).0, - (tx!(2000.into(), fee: 30.into(), inputs: 1, outputs: 1)).0, + (tx!(1000.into(), fee: 4.into(), inputs: 2, outputs: 1)).0, + (tx!(2000.into(), fee: 6.into(), inputs: 1, outputs: 1)).0, ]; let orphan = create_orphan_block(10, txs, &consensus); let hash = orphan.hash(); diff --git a/base_layer/core/tests/chain_storage_tests/chain_storage.rs b/base_layer/core/tests/chain_storage_tests/chain_storage.rs index a7c16f4dd3..ef6a257488 100644 --- a/base_layer/core/tests/chain_storage_tests/chain_storage.rs +++ b/base_layer/core/tests/chain_storage_tests/chain_storage.rs @@ -46,8 +46,8 @@ use tari_core::{ create_test_db, }, transactions::{ - helpers::{schema_to_transaction, spend_utxos}, tari_amount::{uT, MicroTari, T}, + test_helpers::{schema_to_transaction, spend_utxos}, CryptoFactories, }, tx, @@ -117,8 +117,8 @@ fn insert_and_fetch_orphan() { let consensus_manager = ConsensusManagerBuilder::new(network).build(); let store = create_test_blockchain_db(); let txs = vec![ - (tx!(1000.into(), fee: 20.into(), inputs: 2, outputs: 1)).0, - (tx!(2000.into(), fee: 30.into(), inputs: 1, outputs: 1)).0, + (tx!(1000.into(), fee: 4.into(), inputs: 2, outputs: 1)).0, + (tx!(2000.into(), fee: 6.into(), inputs: 1, outputs: 1)).0, ]; let orphan = create_orphan_block(10, txs, &consensus_manager); let orphan_hash = orphan.hash(); diff --git a/base_layer/core/tests/helpers/block_builders.rs b/base_layer/core/tests/helpers/block_builders.rs index b6468b4011..66347d7918 100644 --- a/base_layer/core/tests/helpers/block_builders.rs +++ b/base_layer/core/tests/helpers/block_builders.rs @@ -33,20 +33,13 @@ use tari_crypto::{ use tari_common::configuration::Network; use tari_common_types::types::{Commitment, HashDigest, HashOutput, PublicKey}; use tari_core::{ - blocks::{Block, BlockHeader, NewBlockTemplate}, - chain_storage::{ - BlockAddResult, - BlockHeaderAccumulatedData, - BlockchainBackend, - BlockchainDatabase, - ChainBlock, - ChainHeader, - ChainStorageError, - }, + blocks::{Block, BlockHeader, BlockHeaderAccumulatedData, ChainBlock, ChainHeader, NewBlockTemplate}, + chain_storage::{BlockAddResult, BlockchainBackend, BlockchainDatabase, ChainStorageError}, consensus::{emission::Emission, ConsensusConstants, ConsensusManager, ConsensusManagerBuilder}, proof_of_work::{sha3_difficulty, AchievedTargetDifficulty, Difficulty}, transactions::{ - helpers::{ + tari_amount::MicroTari, + test_helpers::{ create_random_signature_from_s_key, create_signature, create_unblinded_output, @@ -55,7 +48,6 @@ use tari_core::{ TestParams, TransactionSchema, }, - tari_amount::MicroTari, transaction::{ KernelBuilder, KernelFeatures, diff --git a/base_layer/core/tests/helpers/block_proxy.rs b/base_layer/core/tests/helpers/block_proxy.rs index 0e469549b8..eea8a97e42 100644 --- a/base_layer/core/tests/helpers/block_proxy.rs +++ b/base_layer/core/tests/helpers/block_proxy.rs @@ -21,7 +21,7 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // -use tari_core::chain_storage::ChainBlock; +use tari_core::blocks::ChainBlock; #[derive(Debug, Clone)] pub struct BlockProxy { diff --git a/base_layer/core/tests/helpers/nodes.rs b/base_layer/core/tests/helpers/nodes.rs index 06f2d5f8e0..2850c90470 100644 --- a/base_layer/core/tests/helpers/nodes.rs +++ b/base_layer/core/tests/helpers/nodes.rs @@ -194,7 +194,11 @@ impl BaseNodeBuilder { .unwrap_or_else(|| ConsensusManagerBuilder::new(network).build()); let blockchain_db = create_store_with_consensus_and_validators(consensus_manager.clone(), validators); let mempool_validator = TxInputAndMaturityValidator::new(blockchain_db.clone()); - let mempool = Mempool::new(self.mempool_config.unwrap_or_default(), Arc::new(mempool_validator)); + let mempool = Mempool::new( + self.mempool_config.unwrap_or_default(), + consensus_manager.clone(), + Arc::new(mempool_validator), + ); let node_identity = self.node_identity.unwrap_or_else(|| random_node_identity()); let node_interfaces = setup_base_node_services( node_identity, diff --git a/base_layer/core/tests/helpers/sample_blockchains.rs b/base_layer/core/tests/helpers/sample_blockchains.rs index d97aba9883..10f0d7baae 100644 --- a/base_layer/core/tests/helpers/sample_blockchains.rs +++ b/base_layer/core/tests/helpers/sample_blockchains.rs @@ -23,7 +23,8 @@ use tari_common::configuration::Network; use tari_core::{ - chain_storage::{BlockchainDatabase, BlockchainDatabaseConfig, ChainBlock, Validators}, + blocks::ChainBlock, + chain_storage::{BlockchainDatabase, BlockchainDatabaseConfig, Validators}, consensus::{ConsensusConstants, ConsensusConstantsBuilder, ConsensusManager, ConsensusManagerBuilder}, test_helpers::blockchain::{create_store_with_consensus, TempDatabase}, transactions::{ diff --git a/base_layer/core/tests/mempool.rs b/base_layer/core/tests/mempool.rs index 011fbf794e..eeed7ad193 100644 --- a/base_layer/core/tests/mempool.rs +++ b/base_layer/core/tests/mempool.rs @@ -20,12 +20,6 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// use crate::helpers::database::create_store; -use std::{ops::Deref, sync::Arc, time::Duration}; - -use tari_crypto::{keys::PublicKey as PublicKeyTrait, script}; -use tempfile::tempdir; - use helpers::{ block_builders::{ chain_block, @@ -39,6 +33,7 @@ use helpers::{ sample_blockchains::{create_new_blockchain, create_new_blockchain_with_constants}, }; use randomx_rs::RandomXFlag; +use std::{ops::Deref, sync::Arc, time::Duration}; use tari_common::configuration::Network; use tari_common_types::types::{Commitment, PrivateKey, PublicKey, Signature}; use tari_comms_dht::domain_message::OutboundDomainMessage; @@ -54,8 +49,8 @@ use tari_core::{ proto, transactions::{ fee::Fee, - helpers::{create_unblinded_output, schema_to_transaction, spend_utxos, TestParams}, tari_amount::{uT, MicroTari, T}, + test_helpers::{create_unblinded_output, schema_to_transaction, spend_utxos, TestParams}, transaction::{KernelBuilder, OutputFeatures, Transaction, TransactionOutput}, transaction_protocol::{build_challenge, TransactionMetadata}, CryptoFactories, @@ -64,22 +59,29 @@ use tari_core::{ txn_schema, validation::transaction_validators::{TxConsensusValidator, TxInputAndMaturityValidator}, }; +use tari_crypto::{keys::PublicKey as PublicKeyTrait, script}; use tari_p2p::{services::liveness::LivenessConfig, tari_message::TariMessageType}; use tari_test_utils::async_assert_eventually; +use tempfile::tempdir; + #[allow(dead_code)] mod helpers; -#[tokio::test] +#[test] #[allow(clippy::identity_op)] -async fn test_insert_and_process_published_block() { +fn test_insert_and_process_published_block() { let network = Network::LocalNet; let (mut store, mut blocks, mut outputs, consensus_manager) = create_new_blockchain(network); let mempool_validator = TxInputAndMaturityValidator::new(store.clone()); - let mempool = Mempool::new(MempoolConfig::default(), Arc::new(mempool_validator)); + let mempool = Mempool::new( + MempoolConfig::default(), + consensus_manager.clone(), + Arc::new(mempool_validator), + ); // Create a block with 4 outputs let txs = vec![txn_schema!( from: vec![outputs[0][0].clone()], - to: vec![2 * T, 2 * T, 2 * T, 2 * T],fee: 25.into(), lock: 0, features: OutputFeatures::default() + to: vec![2 * T, 2 * T, 2 * T, 2 * T],fee: 5.into(), lock: 0, features: OutputFeatures::default() )]; generate_new_block(&mut store, &mut blocks, &mut outputs, txs, &consensus_manager).unwrap(); // Create 6 new transactions to add to the mempool @@ -155,7 +157,13 @@ async fn test_insert_and_process_published_block() { assert_eq!(stats.total_txs, 1); assert_eq!(stats.unconfirmed_txs, 1); assert_eq!(stats.reorg_txs, 0); - assert_eq!(stats.total_weight, 30); + assert_eq!( + stats.total_weight, + consensus_manager + .consensus_constants(0) + .transaction_weight() + .calculate(1, 1, 2, 0) + ); // Spend tx2, so it goes in Reorg pool generate_block(&store, &mut blocks, vec![tx2.deref().clone()], &consensus_manager).unwrap(); @@ -208,11 +216,15 @@ async fn test_time_locked() { let network = Network::LocalNet; let (mut store, mut blocks, mut outputs, consensus_manager) = create_new_blockchain(network); let mempool_validator = TxInputAndMaturityValidator::new(store.clone()); - let mempool = Mempool::new(MempoolConfig::default(), Arc::new(mempool_validator)); + let mempool = Mempool::new( + MempoolConfig::default(), + consensus_manager.clone(), + Arc::new(mempool_validator), + ); // Create a block with 4 outputs let txs = vec![txn_schema!( from: vec![outputs[0][0].clone()], - to: vec![2 * T, 2 * T, 2 * T, 2 * T], fee: 25*uT, lock: 0, features: OutputFeatures::default() + to: vec![2 * T, 2 * T, 2 * T, 2 * T], fee: 5*uT, lock: 0, features: OutputFeatures::default() )]; generate_new_block(&mut store, &mut blocks, &mut outputs, txs, &consensus_manager).unwrap(); mempool.process_published_block(blocks[1].to_arc_block()).unwrap(); @@ -224,7 +236,7 @@ async fn test_time_locked() { let mut tx3 = txn_schema!( from: vec![outputs[1][1].clone()], to: vec![1*T], - fee: 20*uT, + fee: 4*uT, lock: 4, features: OutputFeatures::with_maturity(1) ); @@ -252,7 +264,11 @@ async fn test_retrieve() { let network = Network::LocalNet; let (mut store, mut blocks, mut outputs, consensus_manager) = create_new_blockchain(network); let mempool_validator = TxInputAndMaturityValidator::new(store.clone()); - let mempool = Mempool::new(MempoolConfig::default(), Arc::new(mempool_validator)); + let mempool = Mempool::new( + MempoolConfig::default(), + consensus_manager.clone(), + Arc::new(mempool_validator), + ); let txs = vec![txn_schema!( from: vec![outputs[0][0].clone()], to: vec![1 * T, 1 * T, 1 * T, 1 * T, 1 * T, 1 * T, 1 * T] @@ -280,7 +296,9 @@ async fn test_retrieve() { mempool.insert(t.clone()).unwrap(); }); // 1-block, 8 UTXOs, 8 txs in mempool - let weight = tx[6].calculate_weight() + tx[2].calculate_weight() + tx[3].calculate_weight(); + let weighting = consensus_manager.consensus_constants(0).transaction_weight(); + let weight = + tx[6].calculate_weight(weighting) + tx[2].calculate_weight(weighting) + tx[3].calculate_weight(weighting); let retrieved_txs = mempool.retrieve(weight).unwrap(); assert_eq!(retrieved_txs.len(), 3); assert!(retrieved_txs.contains(&tx[6])); @@ -320,7 +338,7 @@ async fn test_retrieve() { // 2 blocks, 3 unconfirmed txs in mempool, 2 time locked // Top 2 txs are tx[3] (fee/g = 50) and tx2[1] (fee/g = 40). tx2[0] (fee/g = 80) is still not matured. - let weight = tx[3].calculate_weight() + tx2[1].calculate_weight(); + let weight = tx[3].calculate_weight(weighting) + tx2[1].calculate_weight(weighting); let retrieved_txs = mempool.retrieve(weight).unwrap(); let stats = mempool.stats().unwrap(); @@ -338,7 +356,11 @@ async fn test_zero_conf() { let network = Network::LocalNet; let (mut store, mut blocks, mut outputs, consensus_manager) = create_new_blockchain(network); let mempool_validator = TxInputAndMaturityValidator::new(store.clone()); - let mempool = Mempool::new(MempoolConfig::default(), Arc::new(mempool_validator)); + let mempool = Mempool::new( + MempoolConfig::default(), + consensus_manager.clone(), + Arc::new(mempool_validator), + ); let txs = vec![txn_schema!( from: vec![outputs[0][0].clone()], to: vec![21 * T, 11 * T, 11 * T, 16 * T] @@ -638,7 +660,11 @@ async fn test_reorg() { let network = Network::LocalNet; let (mut db, mut blocks, mut outputs, consensus_manager) = create_new_blockchain(network); let mempool_validator = TxInputAndMaturityValidator::new(db.clone()); - let mempool = Mempool::new(MempoolConfig::default(), Arc::new(mempool_validator)); + let mempool = Mempool::new( + MempoolConfig::default(), + consensus_manager.clone(), + Arc::new(mempool_validator), + ); // "Mine" Block 1 let txs = vec![ @@ -949,7 +975,11 @@ async fn consensus_validation_large_tx() { let (mut store, mut blocks, mut outputs, consensus_manager) = create_new_blockchain_with_constants(network, consensus_constants); let mempool_validator = TxConsensusValidator::new(store.clone()); - let mempool = Mempool::new(MempoolConfig::default(), Arc::new(mempool_validator)); + let mempool = Mempool::new( + MempoolConfig::default(), + consensus_manager.clone(), + Arc::new(mempool_validator), + ); // Create a block with 1 output let txs = vec![txn_schema!(from: vec![outputs[0][0].clone()], to: vec![5 * T])]; generate_new_block(&mut store, &mut blocks, &mut outputs, txs, &consensus_manager).unwrap(); @@ -966,7 +996,13 @@ async fn consensus_validation_large_tx() { let mut script_offset_pvt = outputs[1][0].script_private_key.clone(); let inputs = vec![input.as_transaction_input(&factories.commitment).unwrap()]; - let fee = Fee::calculate(fee_per_gram.into(), 1, input_count, output_count); + let fee = Fee::new(*consensus_manager.consensus_constants(0).transaction_weight()).calculate( + fee_per_gram.into(), + 1, + input_count, + output_count, + 0, + ); let amount_per_output = (amount - fee) / output_count as u64; let amount_for_last_output = (amount - fee) - amount_per_output * (output_count as u64 - 1); let mut unblinded_outputs = Vec::with_capacity(output_count); @@ -1025,13 +1061,15 @@ async fn consensus_validation_large_tx() { let kernels = vec![kernel]; let tx = Transaction::new(inputs, outputs, kernels, offset, script_offset_pvt); + let height = blocks.len() as u64; + let constants = consensus_manager.consensus_constants(height); + // make sure the tx was correctly made and is valid let factories = CryptoFactories::default(); assert!(tx.validate_internal_consistency(true, &factories, None).is_ok()); - let weight = tx.calculate_weight(); + let weighting = constants.transaction_weight(); + let weight = tx.calculate_weight(weighting); - let height = blocks.len() as u64; - let constants = consensus_manager.consensus_constants(height); // check the tx weight is more than the max for 1 block assert!(weight > constants.get_max_block_transaction_weight()); diff --git a/base_layer/core/tests/node_comms_interface.rs b/base_layer/core/tests/node_comms_interface.rs index a6096b8dc9..759e1fd646 100644 --- a/base_layer/core/tests/node_comms_interface.rs +++ b/base_layer/core/tests/node_comms_interface.rs @@ -32,14 +32,14 @@ use tari_core::{ comms_interface::{CommsInterfaceError, InboundNodeCommsHandlers, NodeCommsRequest, NodeCommsResponse}, OutboundNodeCommsInterface, }, - blocks::{BlockBuilder, BlockHeader}, - chain_storage::{BlockchainDatabaseConfig, DbTransaction, HistoricalBlock, Validators}, + blocks::{BlockBuilder, BlockHeader, HistoricalBlock}, + chain_storage::{BlockchainDatabaseConfig, DbTransaction, Validators}, consensus::{ConsensusManager, NetworkConsensus}, mempool::{Mempool, MempoolConfig}, test_helpers::blockchain::{create_store_with_consensus_and_validators_and_config, create_test_blockchain_db}, transactions::{ - helpers::{create_utxo, spend_utxos}, tari_amount::MicroTari, + test_helpers::{create_utxo, spend_utxos}, transaction::{OutputFeatures, TransactionOutput, UnblindedOutput}, CryptoFactories, }, @@ -56,7 +56,9 @@ use tari_crypto::{ use tari_service_framework::{reply_channel, reply_channel::Receiver}; use tokio::sync::broadcast; +use tari_core::test_helpers::create_consensus_rules; use tokio::sync::mpsc; + #[allow(dead_code)] mod helpers; // use crate::helpers::database::create_test_db; @@ -70,8 +72,9 @@ async fn test_request_responder( } fn new_mempool() -> Mempool { + let rules = create_consensus_rules(); let mempool_validator = MockValidator::new(true); - Mempool::new(MempoolConfig::default(), Arc::new(mempool_validator)) + Mempool::new(MempoolConfig::default(), rules, Arc::new(mempool_validator)) } #[tokio::test] @@ -419,7 +422,6 @@ async fn inbound_fetch_blocks() { } #[tokio::test] -// Test needs to be updated to new pruned structure. async fn inbound_fetch_blocks_before_horizon_height() { let factories = CryptoFactories::default(); let network = Network::LocalNet; @@ -437,7 +439,11 @@ async fn inbound_fetch_blocks_before_horizon_height() { }; let store = create_store_with_consensus_and_validators_and_config(consensus_manager.clone(), validators, config); let mempool_validator = TxInputAndMaturityValidator::new(store.clone()); - let mempool = Mempool::new(MempoolConfig::default(), Arc::new(mempool_validator)); + let mempool = Mempool::new( + MempoolConfig::default(), + consensus_manager.clone(), + Arc::new(mempool_validator), + ); let (block_event_sender, _) = broadcast::channel(50); let (request_sender, _) = reply_channel::unbounded(); let (block_sender, _) = mpsc::unbounded_channel(); diff --git a/base_layer/core/tests/node_service.rs b/base_layer/core/tests/node_service.rs index c064fe9518..2333cb8ebf 100644 --- a/base_layer/core/tests/node_service.rs +++ b/base_layer/core/tests/node_service.rs @@ -50,14 +50,13 @@ use tari_core::{ service::BaseNodeServiceConfig, state_machine_service::states::{ListeningInfo, StateInfo, StatusInfo}, }, - blocks::NewBlock, - chain_storage::ChainBlock, + blocks::{ChainBlock, NewBlock}, consensus::{ConsensusConstantsBuilder, ConsensusManager, ConsensusManagerBuilder, NetworkConsensus}, mempool::{MempoolServiceConfig, TxStorageResponse}, proof_of_work::PowAlgorithm, transactions::{ - helpers::{schema_to_transaction, spend_utxos}, tari_amount::{uT, T}, + test_helpers::{schema_to_transaction, spend_utxos}, transaction::OutputFeatures, CryptoFactories, }, diff --git a/base_layer/wallet/src/output_manager_service/handle.rs b/base_layer/wallet/src/output_manager_service/handle.rs index 39f31aefea..65976f983d 100644 --- a/base_layer/wallet/src/output_manager_service/handle.rs +++ b/base_layer/wallet/src/output_manager_service/handle.rs @@ -61,7 +61,12 @@ pub enum OutputManagerRequest { ApplyEncryption(Box), RemoveEncryption, GetPublicRewindKeys, - FeeEstimate((MicroTari, MicroTari, u64, u64)), + FeeEstimate { + amount: MicroTari, + fee_per_gram: MicroTari, + num_kernels: usize, + num_outputs: usize, + }, ScanForRecoverableOutputs(Vec), ScanOutputs(Vec), AddKnownOneSidedPaymentScript(KnownOneSidedPaymentScript), @@ -98,7 +103,16 @@ impl fmt::Display for OutputManagerRequest { RemoveEncryption => write!(f, "RemoveEncryption"), GetCoinbaseTransaction(_) => write!(f, "GetCoinbaseTransaction"), GetPublicRewindKeys => write!(f, "GetPublicRewindKeys"), - FeeEstimate(_) => write!(f, "FeeEstimate"), + FeeEstimate { + amount, + fee_per_gram, + num_kernels, + num_outputs, + } => write!( + f, + "FeeEstimate(amount: {}, fee_per_gram: {}, num_kernels: {}, num_outputs: {})", + amount, fee_per_gram, num_kernels, num_outputs + ), ScanForRecoverableOutputs(_) => write!(f, "ScanForRecoverableOutputs"), ScanOutputs(_) => write!(f, "ScanOutputs"), AddKnownOneSidedPaymentScript(_) => write!(f, "AddKnownOneSidedPaymentScript"), @@ -295,17 +309,17 @@ impl OutputManagerHandle { &mut self, amount: MicroTari, fee_per_gram: MicroTari, - num_kernels: u64, - num_outputs: u64, + num_kernels: usize, + num_outputs: usize, ) -> Result { match self .handle - .call(OutputManagerRequest::FeeEstimate(( + .call(OutputManagerRequest::FeeEstimate { amount, fee_per_gram, num_kernels, num_outputs, - ))) + }) .await?? { OutputManagerResponse::FeeEstimate(fee) => Ok(fee), diff --git a/base_layer/wallet/src/output_manager_service/mod.rs b/base_layer/wallet/src/output_manager_service/mod.rs index 0858229578..57110822e6 100644 --- a/base_layer/wallet/src/output_manager_service/mod.rs +++ b/base_layer/wallet/src/output_manager_service/mod.rs @@ -35,10 +35,7 @@ use futures::future; use log::*; pub(crate) use master_key_manager::MasterKeyManager; use tari_comms::types::CommsSecretKey; -use tari_core::{ - consensus::{ConsensusConstantsBuilder, NetworkConsensus}, - transactions::CryptoFactories, -}; +use tari_core::{consensus::NetworkConsensus, transactions::CryptoFactories}; use tari_service_framework::{ async_trait, reply_channel, @@ -54,7 +51,6 @@ pub mod handle; mod master_key_manager; mod recovery; pub mod resources; -#[allow(unused_assignments)] pub mod service; pub mod storage; mod tasks; @@ -115,7 +111,7 @@ where T: OutputManagerBackend + 'static .expect("Cannot start Output Manager Service without setting a storage backend"); let factories = self.factories.clone(); let config = self.config.clone(); - let constants = ConsensusConstantsBuilder::new(self.network.as_network()).build(); + let constants = self.network.create_consensus_constants().pop().unwrap(); let master_secret_key = self.master_secret_key.clone(); context.spawn_when_ready(move |handles| async move { let transaction_service = handles.expect_handle::(); diff --git a/base_layer/wallet/src/output_manager_service/service.rs b/base_layer/wallet/src/output_manager_service/service.rs index 242aa0f0d2..c75899701d 100644 --- a/base_layer/wallet/src/output_manager_service/service.rs +++ b/base_layer/wallet/src/output_manager_service/service.rs @@ -44,18 +44,14 @@ use diesel::result::{DatabaseErrorKind, Error as DieselError}; use futures::{pin_mut, StreamExt}; use log::*; use rand::{rngs::OsRng, RngCore}; -use std::{ - cmp::Ordering, - fmt::{self, Display}, - sync::Arc, -}; +use std::{cmp::Ordering, fmt, fmt::Display, sync::Arc}; use tari_common_types::{ transaction::TxId, types::{PrivateKey, PublicKey}, }; use tari_comms::types::{CommsPublicKey, CommsSecretKey}; use tari_core::{ - consensus::ConsensusConstants, + consensus::{ConsensusConstants, ConsensusEncodingSized, ConsensusEncodingWrapper}, transactions::{ fee::Fee, tari_amount::MicroTari, @@ -78,7 +74,6 @@ use tari_service_framework::reply_channel; use tari_shutdown::ShutdownSignal; const LOG_TARGET: &str = "wallet::output_manager_service"; -const LOG_TARGET_STRESS: &str = "stress_test::output_manager_service"; /// This service will manage a wallet's available outputs and the key manager that produces the keys for these outputs. /// The service will assemble transactions to be sent from the wallets available outputs and provide keys to receive @@ -235,7 +230,12 @@ where .await .map(OutputManagerResponse::PayToSelfTransaction) }, - OutputManagerRequest::FeeEstimate((amount, fee_per_gram, num_kernels, num_outputs)) => self + OutputManagerRequest::FeeEstimate { + amount, + fee_per_gram, + num_kernels, + num_outputs, + } => self .fee_estimate(amount, fee_per_gram, num_kernels, num_outputs) .await .map(OutputManagerResponse::FeeEstimate), @@ -490,8 +490,8 @@ where &mut self, amount: MicroTari, fee_per_gram: MicroTari, - num_kernels: u64, - num_outputs: u64, + num_kernels: usize, + num_outputs: usize, ) -> Result { debug!( target: LOG_TARGET, @@ -501,13 +501,22 @@ where num_kernels, num_outputs ); + // We assume that default OutputFeatures and Nop TariScript is used + let metadata_byte_size = OutputFeatures::default().consensus_encode_exact_size() + + ConsensusEncodingWrapper::wrap(&script![Nop]).consensus_encode_exact_size(); - let (utxos, _, _) = self - .select_utxos(amount, fee_per_gram, num_outputs as usize, None) + let utxo_selection = self + .select_utxos( + amount, + fee_per_gram, + num_outputs, + metadata_byte_size * num_outputs, + None, + ) .await?; - debug!(target: LOG_TARGET, "{} utxos selected.", utxos.len()); + debug!(target: LOG_TARGET, "{} utxos selected.", utxo_selection.utxos.len()); - let fee = Fee::calculate_with_minimum(fee_per_gram, num_kernels as usize, utxos.len(), num_outputs as usize); + let fee = Fee::normalize(utxo_selection.as_final_fee()); debug!(target: LOG_TARGET, "Fee calculated: {}", fee); Ok(fee) @@ -528,12 +537,18 @@ where target: LOG_TARGET, "Preparing to send transaction. Amount: {}. Fee per gram: {}. ", amount, fee_per_gram, ); - let (outputs, _, total) = self.select_utxos(amount, fee_per_gram, 1, None).await?; + let output_features = OutputFeatures::default(); + let metadata_byte_size = output_features.consensus_encode_exact_size() + + ConsensusEncodingWrapper::wrap(&recipient_script).consensus_encode_exact_size(); + + let input_selection = self + .select_utxos(amount, fee_per_gram, 1, metadata_byte_size, None) + .await?; let offset = PrivateKey::random(&mut OsRng); let nonce = PrivateKey::random(&mut OsRng); - let mut builder = SenderTransactionProtocol::builder(1); + let mut builder = SenderTransactionProtocol::builder(1, self.resources.consensus_constants.clone()); builder .with_lock_height(lock_height.unwrap_or(0)) .with_fee_per_gram(fee_per_gram) @@ -544,14 +559,14 @@ where 0, recipient_script, PrivateKey::random(&mut OsRng), - Default::default(), + output_features, PrivateKey::random(&mut OsRng), ) .with_message(message) .with_prevent_fee_gt_amount(self.resources.config.prevent_fee_gt_amount) .with_tx_id(tx_id); - for uo in outputs.iter() { + for uo in input_selection.iter() { builder.with_input( uo.unblinded_output .as_transaction_input(&self.resources.factories.commitment)?, @@ -560,14 +575,12 @@ where } debug!( target: LOG_TARGET, - "Calculating fee for tx with: Fee per gram: {}. Num outputs: {}", + "Calculating fee for tx with: Fee per gram: {}. Num selected inputs: {}", amount, - outputs.len() + input_selection.num_selected() ); - let fee_without_change = Fee::calculate(fee_per_gram, 1, outputs.len(), 1); - // If the input values > the amount to be sent + fee_without_change then we will need to include a change - // output - if total > amount + fee_without_change { + + if input_selection.requires_change_output() { let (spending_key, script_private_key) = self .resources .master_key_manager @@ -588,7 +601,7 @@ where // If a change output was created add it to the pending_outputs list. let mut change_output = Vec::::new(); - if total > amount + fee_without_change { + if input_selection.requires_change_output() { let unblinded_output = stp.get_change_unblinded_output()?.ok_or_else(|| { OutputManagerError::BuildError( "There should be a change output metadata signature available".to_string(), @@ -604,14 +617,10 @@ where // store them until the transaction times out OR is confirmed self.resources .db - .encumber_outputs(tx_id, outputs, change_output) + .encumber_outputs(tx_id, input_selection.into_selected(), change_output) .await?; debug!(target: LOG_TARGET, "Prepared transaction (TxId: {}) to send", tx_id); - debug!( - target: LOG_TARGET_STRESS, - "Prepared transaction (TxId: {}) to send", tx_id - ); Ok(stp) } @@ -700,14 +709,21 @@ where lock_height: Option, message: String, ) -> Result<(MicroTari, Transaction), OutputManagerError> { - let (inputs, _, total) = self.select_utxos(amount, fee_per_gram, 1, None).await?; + let script = script!(Nop); + let output_features = OutputFeatures::default(); + let metadata_byte_size = output_features.consensus_encode_exact_size() + + ConsensusEncodingWrapper::wrap(&script).consensus_encode_exact_size(); + + let input_selection = self + .select_utxos(amount, fee_per_gram, 1, metadata_byte_size, None) + .await?; let offset = PrivateKey::random(&mut OsRng); let nonce = PrivateKey::random(&mut OsRng); let sender_offset_private_key = PrivateKey::random(&mut OsRng); // Create builder with no recipients (other than ourselves) - let mut builder = SenderTransactionProtocol::builder(0); + let mut builder = SenderTransactionProtocol::builder(0, self.resources.consensus_constants.clone()); builder .with_lock_height(lock_height.unwrap_or(0)) .with_fee_per_gram(fee_per_gram) @@ -717,7 +733,7 @@ where .with_prevent_fee_gt_amount(self.resources.config.prevent_fee_gt_amount) .with_tx_id(tx_id); - for uo in &inputs { + for uo in input_selection.iter() { builder.with_input( uo.unblinded_output .as_transaction_input(&self.resources.factories.commitment)?, @@ -725,8 +741,6 @@ where ); } - let script = script!(Nop); - let output_features = OutputFeatures::default(); let (spending_key, script_private_key) = self .resources .master_key_manager @@ -758,9 +772,7 @@ where let mut outputs = vec![utxo]; - let fee = Fee::calculate(fee_per_gram, 1, inputs.len(), 1); - let change_value = total.saturating_sub(amount).saturating_sub(fee); - if change_value > 0.into() { + if input_selection.requires_change_output() { let (spending_key, script_private_key) = self .resources .master_key_manager @@ -780,14 +792,13 @@ where .build::(&self.resources.factories) .map_err(|e| OutputManagerError::BuildError(e.message))?; - if change_value > 0.into() { + if input_selection.requires_change_output() { let unblinded_output = stp.get_change_unblinded_output()?.ok_or_else(|| { OutputManagerError::BuildError( "There should be a change output metadata signature available".to_string(), ) })?; let change_output = DbUnblindedOutput::from_unblinded_output(unblinded_output, &self.resources.factories)?; - outputs.push(change_output); } @@ -796,7 +807,10 @@ where "Encumber send to self transaction ({}) outputs.", tx_id ); - self.resources.db.encumber_outputs(tx_id, inputs, outputs).await?; + self.resources + .db + .encumber_outputs(tx_id, input_selection.into_selected(), outputs) + .await?; self.confirm_encumberance(tx_id).await?; let fee = stp.get_fee_amount()?; trace!(target: LOG_TARGET, "Finalize send-to-self transaction ({}).", tx_id); @@ -837,21 +851,24 @@ where &mut self, amount: MicroTari, fee_per_gram: MicroTari, - output_count: usize, + num_outputs: usize, + output_metadata_byte_size: usize, strategy: Option, - ) -> Result<(Vec, bool, MicroTari), OutputManagerError> { + ) -> Result { debug!( target: LOG_TARGET, - "select_utxos amount: {}, fee_per_gram: {}, output_count: {}, strategy: {:?}", + "select_utxos amount: {}, fee_per_gram: {}, num_outputs: {}, output_metadata_byte_size: {}, strategy: {:?}", amount, fee_per_gram, - output_count, + num_outputs, + output_metadata_byte_size, strategy ); let mut utxos = Vec::new(); let mut utxos_total_value = MicroTari::from(0); let mut fee_without_change = MicroTari::from(0); let mut fee_with_change = MicroTari::from(0); + let fee_calc = self.get_fee_calc(); let uo = self.resources.db.fetch_sorted_unspent_outputs().await?; @@ -927,18 +944,28 @@ where }; trace!(target: LOG_TARGET, "We found {} UTXOs to select from", uo.len()); - let mut require_change_output = false; - for o in uo.iter() { - utxos.push(o.clone()); + // Assumes that default Outputfeatures are used for change utxo + let default_metadata_size = OutputFeatures::default().consensus_encode_exact_size() + + ConsensusEncodingWrapper::wrap(&script![Nop]).consensus_encode_exact_size(); + let mut requires_change_output = false; + for o in uo { utxos_total_value += o.unblinded_output.value; + utxos.push(o); // The assumption here is that the only output will be the payment output and change if required - fee_without_change = Fee::calculate(fee_per_gram, 1, utxos.len(), output_count); + fee_without_change = + fee_calc.calculate(fee_per_gram, 1, utxos.len(), num_outputs, output_metadata_byte_size); if utxos_total_value == amount + fee_without_change { break; } - fee_with_change = Fee::calculate(fee_per_gram, 1, utxos.len(), output_count + 1); + fee_with_change = fee_calc.calculate( + fee_per_gram, + 1, + utxos.len(), + num_outputs + 1, + output_metadata_byte_size + default_metadata_size, + ); if utxos_total_value > amount + fee_with_change { - require_change_output = true; + requires_change_output = true; break; } } @@ -957,7 +984,13 @@ where } } - Ok((utxos, require_change_output, utxos_total_value)) + Ok(UtxoSelection { + utxos, + requires_change_output, + total_value: utxos_total_value, + fee_without_change, + fee_with_change, + }) } pub async fn fetch_spent_outputs(&self) -> Result, OutputManagerError> { @@ -989,27 +1022,28 @@ where target: LOG_TARGET, "Select UTXOs and estimate coin split transaction fee." ); - let mut output_count = split_count; + let output_count = split_count; + let script = script!(Nop); + let output_features = OutputFeatures::default(); + let metadata_byte_size = output_features.consensus_encode_exact_size() + + ConsensusEncodingWrapper::wrap(&script).consensus_encode_exact_size(); + let total_split_amount = amount_per_split * split_count as u64; - let (inputs, require_change_output, utxos_total_value) = self + let input_selection = self .select_utxos( total_split_amount, fee_per_gram, output_count, + output_count * metadata_byte_size, Some(UTXOSelectionStrategy::Largest), ) .await?; - let input_count = inputs.len(); - if require_change_output { - output_count = split_count + 1 - }; - let fee = Fee::calculate(fee_per_gram, 1, input_count, output_count); trace!(target: LOG_TARGET, "Construct coin split transaction."); let offset = PrivateKey::random(&mut OsRng); let nonce = PrivateKey::random(&mut OsRng); - let mut builder = SenderTransactionProtocol::builder(0); + let mut builder = SenderTransactionProtocol::builder(0, self.resources.consensus_constants.clone()); builder .with_lock_height(lock_height.unwrap_or(0)) .with_fee_per_gram(fee_per_gram) @@ -1018,26 +1052,19 @@ where .with_rewindable_outputs(self.resources.master_key_manager.rewind_data().clone()); trace!(target: LOG_TARGET, "Add inputs to coin split transaction."); - for uo in inputs.iter() { + for uo in input_selection.iter() { builder.with_input( uo.unblinded_output .as_transaction_input(&self.resources.factories.commitment)?, uo.unblinded_output.clone(), ); } + + let utxos_total_value = input_selection.total_value(); trace!(target: LOG_TARGET, "Add outputs to coin split transaction."); let mut outputs: Vec = Vec::with_capacity(output_count); - let change_output = utxos_total_value - .checked_sub(fee) - .ok_or(OutputManagerError::NotEnoughFunds)? - .checked_sub(total_split_amount) - .ok_or(OutputManagerError::NotEnoughFunds)?; - for i in 0..output_count { - let output_amount = if i < split_count { - amount_per_split - } else { - change_output - }; + for _ in 0..output_count { + let output_amount = amount_per_split; let (spending_key, script_private_key) = self .resources @@ -1046,8 +1073,6 @@ where .await?; let sender_offset_private_key = PrivateKey::random(&mut OsRng); - let script = script!(Nop); - let output_features = OutputFeatures::default(); let sender_offset_public_key = PublicKey::from_secret_key(&sender_offset_private_key); let metadata_signature = TransactionOutput::create_final_metadata_signature( &output_amount, @@ -1060,8 +1085,8 @@ where UnblindedOutput::new( output_amount, spending_key.clone(), - output_features, - script, + output_features.clone(), + script.clone(), inputs!(PublicKey::from_secret_key(&script_private_key)), script_private_key, sender_offset_public_key, @@ -1069,12 +1094,27 @@ where ), &self.resources.factories, )?; - outputs.push(utxo.clone()); builder - .with_output(utxo.unblinded_output, sender_offset_private_key) + .with_output(utxo.unblinded_output.clone(), sender_offset_private_key) .map_err(|e| OutputManagerError::BuildError(e.message))?; + outputs.push(utxo); } - trace!(target: LOG_TARGET, "Build coin split transaction."); + + if input_selection.requires_change_output() { + let (spending_key, script_private_key) = self + .resources + .master_key_manager + .get_next_spend_and_script_key() + .await?; + builder.with_change_secret(spending_key); + builder.with_rewindable_outputs(self.resources.master_key_manager.rewind_data().clone()); + builder.with_change_script( + script!(Nop), + inputs!(PublicKey::from_secret_key(&script_private_key)), + script_private_key, + ); + } + let factories = CryptoFactories::default(); let mut stp = builder .build::(&self.resources.factories) @@ -1087,10 +1127,27 @@ where "Encumber coin split transaction ({}) outputs.", tx_id ); - self.resources.db.encumber_outputs(tx_id, inputs, outputs).await?; + + if input_selection.requires_change_output() { + let unblinded_output = stp.get_change_unblinded_output()?.ok_or_else(|| { + OutputManagerError::BuildError( + "There should be a change output metadata signature available".to_string(), + ) + })?; + outputs.push(DbUnblindedOutput::from_unblinded_output( + unblinded_output, + &self.resources.factories, + )?); + } + + self.resources + .db + .encumber_outputs(tx_id, input_selection.into_selected(), outputs) + .await?; self.confirm_encumberance(tx_id).await?; trace!(target: LOG_TARGET, "Finalize coin split transaction ({}).", tx_id); stp.finalize(KernelFeatures::empty(), &factories)?; + let fee = stp.get_fee_amount()?; let tx = stp.take_transaction()?; Ok((tx_id, tx, fee, utxos_total_value)) } @@ -1180,6 +1237,10 @@ where Ok(rewound_outputs) } + + fn get_fee_calc(&self) -> Fee { + Fee::new(*self.resources.consensus_constants.transaction_weight()) + } } /// Different UTXO selection strategies for choosing which UTXO's are used to fulfill a transaction @@ -1244,3 +1305,42 @@ impl fmt::Display for Balance { fn hash_secret_key(key: &PrivateKey) -> Vec { HashDigest::new().chain(key.as_bytes()).finalize().to_vec() } + +#[derive(Debug, Clone)] +struct UtxoSelection { + utxos: Vec, + requires_change_output: bool, + total_value: MicroTari, + fee_without_change: MicroTari, + fee_with_change: MicroTari, +} + +impl UtxoSelection { + pub fn as_final_fee(&self) -> MicroTari { + if self.requires_change_output { + return self.fee_with_change; + } + self.fee_without_change + } + + pub fn requires_change_output(&self) -> bool { + self.requires_change_output + } + + /// Total value of the selected inputs + pub fn total_value(&self) -> MicroTari { + self.total_value + } + + pub fn num_selected(&self) -> usize { + self.utxos.len() + } + + pub fn into_selected(self) -> Vec { + self.utxos + } + + pub fn iter(&self) -> impl Iterator + '_ { + self.utxos.iter() + } +} diff --git a/base_layer/wallet/src/output_manager_service/storage/sqlite_db.rs b/base_layer/wallet/src/output_manager_service/storage/sqlite_db.rs index f74966522e..b3b9c872a2 100644 --- a/base_layer/wallet/src/output_manager_service/storage/sqlite_db.rs +++ b/base_layer/wallet/src/output_manager_service/storage/sqlite_db.rs @@ -2006,8 +2006,8 @@ mod test { use tari_common_types::types::{CommitmentFactory, PrivateKey}; use tari_core::transactions::{ - helpers::{create_unblinded_output, TestParams as TestParamsHelpers}, tari_amount::MicroTari, + test_helpers::{create_unblinded_output, TestParams as TestParamsHelpers}, transaction::{OutputFeatures, TransactionInput, UnblindedOutput}, CryptoFactories, }; diff --git a/base_layer/wallet/src/test_utils.rs b/base_layer/wallet/src/test_utils.rs index 32da53b26c..ef9c83f0ca 100644 --- a/base_layer/wallet/src/test_utils.rs +++ b/base_layer/wallet/src/test_utils.rs @@ -24,6 +24,8 @@ use crate::storage::sqlite_utilities::{run_migration_and_create_sqlite_connectio use core::iter; use rand::{distributions::Alphanumeric, rngs::OsRng, Rng}; use std::path::Path; +use tari_common::configuration::Network; +use tari_core::consensus::{ConsensusConstants, ConsensusManager}; use tempfile::{tempdir, TempDir}; pub fn random_string(len: usize) -> String { @@ -50,3 +52,11 @@ pub fn make_wallet_database_connection(path: Option) -> (WalletDbConnect run_migration_and_create_sqlite_connection(&db_path.to_str().expect("Should be able to make path")).unwrap(); (connection, temp_dir) } + +pub fn create_consensus_rules() -> ConsensusManager { + ConsensusManager::builder(Network::LocalNet).build() +} + +pub fn create_consensus_constants(height: u64) -> ConsensusConstants { + create_consensus_rules().consensus_constants(height).clone() +} diff --git a/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs b/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs index 83c30f0c04..0e539823f6 100644 --- a/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs +++ b/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs @@ -1720,8 +1720,8 @@ mod test { types::{HashDigest, PrivateKey, PublicKey}, }; use tari_core::transactions::{ - helpers::{create_unblinded_output, TestParams}, tari_amount::MicroTari, + test_helpers::{create_unblinded_output, TestParams}, transaction::{OutputFeatures, Transaction}, transaction_protocol::sender::TransactionSenderMessage, CryptoFactories, @@ -1732,6 +1732,7 @@ mod test { use crate::{ storage::sqlite_utilities::WalletDbConnection, + test_utils::create_consensus_constants, transaction_service::storage::{ database::{DbKey, TransactionBackend}, models::{CompletedTransaction, InboundTransaction, OutboundTransaction}, @@ -1760,7 +1761,8 @@ mod test { conn.execute("PRAGMA foreign_keys = ON").unwrap(); - let mut builder = SenderTransactionProtocol::builder(1); + let constants = create_consensus_constants(0); + let mut builder = SenderTransactionProtocol::builder(1, constants); let test_params = TestParams::new(); let input = create_unblinded_output( TariScript::default(), @@ -1771,7 +1773,7 @@ mod test { let amount = MicroTari::from(10_000); builder .with_lock_height(0) - .with_fee_per_gram(MicroTari::from(177)) + .with_fee_per_gram(MicroTari::from(177 / 5)) .with_offset(PrivateKey::random(&mut OsRng)) .with_private_nonce(PrivateKey::random(&mut OsRng)) .with_amount(0, amount) diff --git a/base_layer/wallet/src/types.rs b/base_layer/wallet/src/types.rs index c2b8641453..8a515a6dad 100644 --- a/base_layer/wallet/src/types.rs +++ b/base_layer/wallet/src/types.rs @@ -20,13 +20,8 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use tari_core::transactions::tari_amount::MicroTari; use tari_crypto::common::Blake256; -/// The default fee per gram that the wallet will use to build transactions. -/// TODO discuss what the default fee value should actually be -pub const DEFAULT_FEE_PER_GRAM: MicroTari = MicroTari(25); - /// Specify the Hash function used by the key manager pub type KeyDigest = Blake256; diff --git a/base_layer/wallet/src/wallet.rs b/base_layer/wallet/src/wallet.rs index ab34f91a9d..f90a83515c 100644 --- a/base_layer/wallet/src/wallet.rs +++ b/base_layer/wallet/src/wallet.rs @@ -50,10 +50,13 @@ use tari_comms::{ UnspawnedCommsNode, }; use tari_comms_dht::{store_forward::StoreAndForwardRequester, Dht}; -use tari_core::transactions::{ - tari_amount::MicroTari, - transaction::{OutputFeatures, UnblindedOutput}, - CryptoFactories, +use tari_core::{ + consensus::NetworkConsensus, + transactions::{ + tari_amount::MicroTari, + transaction::{OutputFeatures, UnblindedOutput}, + CryptoFactories, + }, }; use tari_key_manager::key_manager::KeyManager; use tari_p2p::{ @@ -94,6 +97,7 @@ const LOG_TARGET: &str = "wallet"; /// the services and provide the APIs that applications will use to interact with the services #[derive(Clone)] pub struct Wallet { + pub network: NetworkConsensus, pub comms: CommsNode, pub dht_service: Dht, pub store_and_forward_requester: StoreAndForwardRequester, @@ -142,7 +146,7 @@ where let bn_service_db = wallet_database.clone(); - let factories = config.clone().factories; + let factories = config.factories.clone(); let (publisher, subscription_factory) = pubsub_connector(config.buffer_size, config.rate_limit); let peer_message_subscription_factory = Arc::new(subscription_factory); let transport_type = config.comms_config.transport_type.clone(); @@ -247,6 +251,7 @@ where .await?; Ok(Self { + network: config.network, comms, dht_service: dht, store_and_forward_requester, diff --git a/base_layer/wallet/tests/output_manager_service/service.rs b/base_layer/wallet/tests/output_manager_service/service.rs index 4fec5916c5..0bd38fda78 100644 --- a/base_layer/wallet/tests/output_manager_service/service.rs +++ b/base_layer/wallet/tests/output_manager_service/service.rs @@ -39,13 +39,13 @@ use tari_comms::{ use tari_core::{ base_node::rpc::BaseNodeWalletRpcServer, blocks::BlockHeader, - consensus::ConsensusConstantsBuilder, + consensus::{ConsensusConstantsBuilder, ConsensusEncodingSized, ConsensusEncodingWrapper}, crypto::tari_utilities::Hashable, proto::base_node::{QueryDeletedResponse, UtxoQueryResponse, UtxoQueryResponses}, transactions::{ fee::Fee, - helpers::{create_unblinded_output, TestParams as TestParamsHelpers}, tari_amount::{uT, MicroTari}, + test_helpers::{create_unblinded_output, TestParams as TestParamsHelpers}, transaction::OutputFeatures, transaction_protocol::sender::TransactionSenderMessage, CryptoFactories, @@ -79,6 +79,7 @@ use tari_wallet::{ sqlite_db::OutputManagerSqliteDatabase, }, }, + test_utils::create_consensus_constants, transaction_service::handle::TransactionServiceHandle, }; use tokio::{ @@ -87,6 +88,11 @@ use tokio::{ time::sleep, }; +fn default_metadata_byte_size() -> usize { + OutputFeatures::default().consensus_encode_exact_size() + + ConsensusEncodingWrapper::wrap(&script![Nop]).consensus_encode_exact_size() +} + #[allow(clippy::type_complexity)] async fn setup_output_manager_service( backend: T, @@ -111,7 +117,7 @@ async fn setup_output_manager_service( let (event_publisher, _) = channel(100); let ts_handle = TransactionServiceHandle::new(ts_request_sender, event_publisher); - let constants = ConsensusConstantsBuilder::new(Network::Weatherwax).build(); + let constants = create_consensus_constants(0); let (sender, receiver_bns) = reply_channel::unbounded(); let (event_publisher_bns, _) = broadcast::channel(100); @@ -199,7 +205,7 @@ pub async fn setup_oms_with_bn_state( let (event_publisher, _) = channel(100); let ts_handle = TransactionServiceHandle::new(ts_request_sender, event_publisher); - let constants = ConsensusConstantsBuilder::new(Network::Weatherwax).build(); + let constants = create_consensus_constants(0); let (sender, receiver_bns) = reply_channel::unbounded(); let (event_publisher_bns, _) = broadcast::channel(100); @@ -249,7 +255,7 @@ fn generate_sender_transaction_message(amount: MicroTari) -> (TxId, TransactionS let alice = TestParams::new(&mut OsRng); let (utxo, input) = make_input(&mut OsRng, 2 * amount, &factories.commitment); - let mut builder = SenderTransactionProtocol::builder(1); + let mut builder = SenderTransactionProtocol::builder(1, create_consensus_constants(0)); let script_private_key = PrivateKey::random(&mut OsRng); builder .with_lock_height(0) @@ -290,22 +296,35 @@ async fn fee_estimate() { let (_, uo) = make_input(&mut OsRng.clone(), MicroTari::from(3000), &factories.commitment); oms.add_output(uo).await.unwrap(); - - // minimum fee + let fee_calc = Fee::new(*create_consensus_constants(0).transaction_weight()); + // minimum fpg let fee_per_gram = MicroTari::from(1); let fee = oms .fee_estimate(MicroTari::from(100), fee_per_gram, 1, 1) .await .unwrap(); - assert_eq!(fee, MicroTari::from(100)); + assert_eq!( + fee, + fee_calc.calculate(fee_per_gram, 1, 1, 2, 2 * default_metadata_byte_size()) + ); - let fee_per_gram = MicroTari::from(25); + let fee_per_gram = MicroTari::from(5); for outputs in 1..5 { let fee = oms .fee_estimate(MicroTari::from(100), fee_per_gram, 1, outputs) .await .unwrap(); - assert_eq!(fee, Fee::calculate(fee_per_gram, 1, 1, outputs as usize)); + + assert_eq!( + fee, + fee_calc.calculate( + fee_per_gram, + 1, + 1, + outputs + 1, + default_metadata_byte_size() * (outputs + 1) + ) + ); } // not enough funds @@ -326,9 +345,10 @@ async fn test_utxo_selection_no_chain_metadata() { let (mut oms, _shutdown, _, _, _) = setup_oms_with_bn_state(OutputManagerSqliteDatabase::new(connection, None), None).await; + let fee_calc = Fee::new(*create_consensus_constants(0).transaction_weight()); // no utxos - not enough funds let amount = MicroTari::from(1000); - let fee_per_gram = MicroTari::from(10); + let fee_per_gram = MicroTari::from(2); let err = oms .prepare_transaction_to_send( OsRng.next_u64(), @@ -378,7 +398,8 @@ async fn test_utxo_selection_no_chain_metadata() { // test that we can get a fee estimate with no chain metadata let fee = oms.fee_estimate(amount, fee_per_gram, 1, 2).await.unwrap(); - assert_eq!(fee, MicroTari::from(300)); + let expected_fee = fee_calc.calculate(fee_per_gram, 1, 1, 3, default_metadata_byte_size() * 3); + assert_eq!(fee, expected_fee); // test if a fee estimate would be possible with pending funds included // at this point 52000 uT is still spendable, with pending change incoming of 1690 uT @@ -397,7 +418,8 @@ async fn test_utxo_selection_no_chain_metadata() { // coin split uses the "Largest" selection strategy let (_, _, fee, utxos_total_value) = oms.create_coin_split(amount, 5, fee_per_gram, None).await.unwrap(); - assert_eq!(fee, MicroTari::from(820)); + let expected_fee = fee_calc.calculate(fee_per_gram, 1, 1, 6, default_metadata_byte_size() * 6); + assert_eq!(fee, expected_fee); assert_eq!(utxos_total_value, MicroTari::from(10_000)); // test that largest utxo was encumbered @@ -419,10 +441,11 @@ async fn test_utxo_selection_with_chain_metadata() { // setup with chain metadata at a height of 6 let (mut oms, _shutdown, _, _, _) = setup_oms_with_bn_state(OutputManagerSqliteDatabase::new(connection, None), Some(6)).await; + let fee_calc = Fee::new(*create_consensus_constants(0).transaction_weight()); // no utxos - not enough funds let amount = MicroTari::from(1000); - let fee_per_gram = MicroTari::from(10); + let fee_per_gram = MicroTari::from(2); let err = oms .prepare_transaction_to_send( OsRng.next_u64(), @@ -452,7 +475,8 @@ async fn test_utxo_selection_with_chain_metadata() { // test fee estimates let fee = oms.fee_estimate(amount, fee_per_gram, 1, 2).await.unwrap(); - assert_eq!(fee, MicroTari::from(310)); + let expected_fee = fee_calc.calculate(fee_per_gram, 1, 2, 3, default_metadata_byte_size() * 3); + assert_eq!(fee, expected_fee); // test fee estimates are maturity aware // even though we have utxos for the fee, they can't be spent because they are not mature yet @@ -466,7 +490,8 @@ async fn test_utxo_selection_with_chain_metadata() { // test coin split is maturity aware let (_, _, fee, utxos_total_value) = oms.create_coin_split(amount, 5, fee_per_gram, None).await.unwrap(); assert_eq!(utxos_total_value, MicroTari::from(6_000)); - assert_eq!(fee, MicroTari::from(820)); + let expected_fee = fee_calc.calculate(fee_per_gram, 1, 1, 6, default_metadata_byte_size() * 6); + assert_eq!(fee, expected_fee); // test that largest spendable utxo was encumbered let utxos = oms.get_unspent_outputs().await.unwrap(); @@ -535,7 +560,7 @@ async fn send_not_enough_funds() { for _i in 0..num_outputs { let (_ti, uo) = make_input( &mut OsRng.clone(), - MicroTari::from(100 + OsRng.next_u64() % 1000), + MicroTari::from(200 + OsRng.next_u64() % 1000), &factories.commitment, ); oms.add_output(uo).await.unwrap(); @@ -545,7 +570,7 @@ async fn send_not_enough_funds() { .prepare_transaction_to_send( OsRng.next_u64(), MicroTari::from(num_outputs * 2000), - MicroTari::from(20), + MicroTari::from(4), None, "".to_string(), script!(Nop), @@ -564,8 +589,9 @@ async fn send_no_change() { let (mut oms, _, _shutdown, _, _, _, _, _) = setup_output_manager_service(backend, true).await; - let fee_per_gram = MicroTari::from(20); - let fee_without_change = Fee::calculate(fee_per_gram, 1, 2, 1); + let fee_per_gram = MicroTari::from(4); + let constants = create_consensus_constants(0); + let fee_without_change = Fee::new(*constants.transaction_weight()).calculate(fee_per_gram, 1, 2, 1, 0); let value1 = 500; oms.add_output(create_unblinded_output( script!(Nop), @@ -610,23 +636,24 @@ async fn send_not_enough_for_change() { let (mut oms, _, _shutdown, _, _, _, _, _) = setup_output_manager_service(backend, true).await; - let fee_per_gram = MicroTari::from(20); - let fee_without_change = Fee::calculate(fee_per_gram, 1, 2, 1); - let value1 = 500; + let fee_per_gram = MicroTari::from(4); + let constants = create_consensus_constants(0); + let fee_without_change = Fee::new(*constants.transaction_weight()).calculate(fee_per_gram, 1, 2, 1, 0); + let value1 = MicroTari(500); oms.add_output(create_unblinded_output( TariScript::default(), OutputFeatures::default(), TestParamsHelpers::new(), - MicroTari::from(value1), + value1, )) .await .unwrap(); - let value2 = 800; + let value2 = MicroTari(800); oms.add_output(create_unblinded_output( TariScript::default(), OutputFeatures::default(), TestParamsHelpers::new(), - MicroTari::from(value2), + value2, )) .await .unwrap(); @@ -634,8 +661,8 @@ async fn send_not_enough_for_change() { match oms .prepare_transaction_to_send( OsRng.next_u64(), - MicroTari::from(value1 + value2 + 1) - fee_without_change, - MicroTari::from(20), + value1 + value2 + uT - fee_without_change, + fee_per_gram, None, "".to_string(), script!(Nop), @@ -669,7 +696,7 @@ async fn cancel_transaction() { .prepare_transaction_to_send( OsRng.next_u64(), MicroTari::from(1000), - MicroTari::from(20), + MicroTari::from(4), None, "".to_string(), script!(Nop), @@ -744,7 +771,7 @@ async fn test_get_balance() { .prepare_transaction_to_send( OsRng.next_u64(), send_value, - MicroTari::from(20), + MicroTari::from(4), None, "".to_string(), script!(Nop), @@ -788,7 +815,7 @@ async fn sending_transaction_with_short_term_clear() { .prepare_transaction_to_send( OsRng.next_u64(), MicroTari::from(1000), - MicroTari::from(20), + MicroTari::from(4), None, "".to_string(), script!(Nop), @@ -813,7 +840,7 @@ async fn sending_transaction_with_short_term_clear() { .prepare_transaction_to_send( OsRng.next_u64(), MicroTari::from(1000), - MicroTari::from(20), + MicroTari::from(4), None, "".to_string(), script!(Nop), @@ -842,14 +869,14 @@ async fn coin_split_with_change() { let val1 = 6_000 * uT; let val2 = 7_000 * uT; let val3 = 8_000 * uT; - let (_ti, uo1) = make_input(&mut OsRng.clone(), val1, &factories.commitment); - let (_ti, uo2) = make_input(&mut OsRng.clone(), val2, &factories.commitment); - let (_ti, uo3) = make_input(&mut OsRng.clone(), val3, &factories.commitment); + let (_ti, uo1) = make_input(&mut OsRng, val1, &factories.commitment); + let (_ti, uo2) = make_input(&mut OsRng, val2, &factories.commitment); + let (_ti, uo3) = make_input(&mut OsRng, val3, &factories.commitment); assert!(oms.add_output(uo1).await.is_ok()); assert!(oms.add_output(uo2).await.is_ok()); assert!(oms.add_output(uo3).await.is_ok()); - let fee_per_gram = MicroTari::from(25); + let fee_per_gram = MicroTari::from(5); let split_count = 8; let (_tx_id, coin_split_tx, fee, amount) = oms .create_coin_split(1000.into(), split_count, fee_per_gram, None) @@ -857,7 +884,15 @@ async fn coin_split_with_change() { .unwrap(); assert_eq!(coin_split_tx.body.inputs().len(), 2); assert_eq!(coin_split_tx.body.outputs().len(), split_count + 1); - assert_eq!(fee, Fee::calculate(fee_per_gram, 1, 2, split_count + 1)); + let fee_calc = Fee::new(*create_consensus_constants(0).transaction_weight()); + let expected_fee = fee_calc.calculate( + fee_per_gram, + 1, + 2, + split_count + 1, + (split_count + 1) * default_metadata_byte_size(), + ); + assert_eq!(fee, expected_fee); assert_eq!(amount, val2 + val3); } @@ -868,15 +903,23 @@ async fn coin_split_no_change() { let backend = OutputManagerSqliteDatabase::new(connection, None); let (mut oms, _, _shutdown, _, _, _, _, _) = setup_output_manager_service(backend, true).await; - let fee_per_gram = MicroTari::from(25); + let fee_per_gram = MicroTari::from(4); let split_count = 15; - let fee = Fee::calculate(fee_per_gram, 1, 3, 15); + let constants = create_consensus_constants(0); + let fee_calc = Fee::new(*constants.transaction_weight()); + let expected_fee = fee_calc.calculate( + fee_per_gram, + 1, + 3, + split_count, + split_count * default_metadata_byte_size(), + ); let val1 = 4_000 * uT; let val2 = 5_000 * uT; - let val3 = 6_000 * uT + fee; - let (_ti, uo1) = make_input(&mut OsRng.clone(), val1, &factories.commitment); - let (_ti, uo2) = make_input(&mut OsRng.clone(), val2, &factories.commitment); - let (_ti, uo3) = make_input(&mut OsRng.clone(), val3, &factories.commitment); + let val3 = 6_000 * uT + expected_fee; + let (_ti, uo1) = make_input(&mut OsRng, val1, &factories.commitment); + let (_ti, uo2) = make_input(&mut OsRng, val2, &factories.commitment); + let (_ti, uo3) = make_input(&mut OsRng, val3, &factories.commitment); assert!(oms.add_output(uo1).await.is_ok()); assert!(oms.add_output(uo2).await.is_ok()); assert!(oms.add_output(uo3).await.is_ok()); @@ -887,7 +930,7 @@ async fn coin_split_no_change() { .unwrap(); assert_eq!(coin_split_tx.body.inputs().len(), 3); assert_eq!(coin_split_tx.body.outputs().len(), split_count); - assert_eq!(fee, Fee::calculate(fee_per_gram, 1, 3, split_count)); + assert_eq!(fee, expected_fee); assert_eq!(amount, val1 + val2 + val3); } @@ -1097,7 +1140,8 @@ async fn test_txo_validation() { assert_eq!( balance.pending_incoming_balance, MicroTari::from(output1_value) - - MicroTari::from(900_300) + //Output4 = output 1 -900_000 and 300 for fees + MicroTari::from(900_000) - + MicroTari::from(1240) + //Output4 = output 1 -900_000 and 1240 for fees MicroTari::from(8_000_000) + MicroTari::from(16_000_000) ); @@ -1234,7 +1278,8 @@ async fn test_txo_validation() { assert_eq!( balance.available_balance, MicroTari::from(output2_value) + MicroTari::from(output3_value) + MicroTari::from(output1_value) - - MicroTari::from(900_300) + //spent 900_000 and 300 for fees + MicroTari::from(900_000) - + MicroTari::from(1240) + //spent 900_000 and 1240 for fees MicroTari::from(8_000_000) + //output 5 MicroTari::from(16_000_000) // output 6 ); @@ -1344,7 +1389,7 @@ async fn test_txo_validation() { assert_eq!(balance.pending_outgoing_balance, MicroTari::from(output1_value)); assert_eq!( balance.pending_incoming_balance, - MicroTari::from(output1_value) - MicroTari::from(900_300) + MicroTari::from(output1_value) - MicroTari::from(901_240) ); assert_eq!(balance.available_balance, balance.time_locked_balance.unwrap()); @@ -1402,7 +1447,7 @@ async fn test_txo_validation() { assert_eq!( balance.available_balance, MicroTari::from(output2_value) + MicroTari::from(output3_value) + MicroTari::from(output1_value) - - MicroTari::from(900_300) + MicroTari::from(901_240) ); assert_eq!(balance.pending_outgoing_balance, MicroTari::from(0)); assert_eq!(balance.pending_incoming_balance, MicroTari::from(0)); diff --git a/base_layer/wallet/tests/support/utils.rs b/base_layer/wallet/tests/support/utils.rs index d2fa99c33c..e0eb6e7312 100644 --- a/base_layer/wallet/tests/support/utils.rs +++ b/base_layer/wallet/tests/support/utils.rs @@ -24,8 +24,8 @@ use rand::{CryptoRng, Rng}; use std::{fmt::Debug, thread, time::Duration}; use tari_common_types::types::{CommitmentFactory, PrivateKey, PublicKey}; use tari_core::transactions::{ - helpers::{create_unblinded_output, TestParams as TestParamsHelpers}, tari_amount::MicroTari, + test_helpers::{create_unblinded_output, TestParams as TestParamsHelpers}, transaction::{OutputFeatures, TransactionInput, UnblindedOutput}, }; use tari_crypto::{ diff --git a/base_layer/wallet/tests/transaction_service/service.rs b/base_layer/wallet/tests/transaction_service/service.rs index 64b8254189..13fb9174e0 100644 --- a/base_layer/wallet/tests/transaction_service/service.rs +++ b/base_layer/wallet/tests/transaction_service/service.rs @@ -79,8 +79,8 @@ use tari_core::{ }, transactions::{ fee::Fee, - helpers::{create_unblinded_output, TestParams as TestParamsHelpers}, tari_amount::*, + test_helpers::{create_unblinded_output, TestParams as TestParamsHelpers}, transaction::{KernelBuilder, KernelFeatures, OutputFeatures, Transaction}, transaction_protocol::{proto, recipient::RecipientSignedMessage, sender::TransactionSenderMessage}, CryptoFactories, @@ -130,7 +130,7 @@ use tari_wallet::{ sqlite_db::WalletSqliteDatabase, sqlite_utilities::{run_migration_and_create_sqlite_connection, WalletDbConnection}, }, - test_utils::make_wallet_database_connection, + test_utils::{create_consensus_constants, make_wallet_database_connection}, transaction_service::{ config::TransactionServiceConfig, error::TransactionServiceError, @@ -2078,7 +2078,8 @@ fn test_transaction_cancellation() { MicroTari::from(100_000), ); - let mut builder = SenderTransactionProtocol::builder(1); + let constants = create_consensus_constants(0); + let mut builder = SenderTransactionProtocol::builder(1, constants); let amount = MicroTari::from(10_000); builder .with_lock_height(0) @@ -2149,7 +2150,8 @@ fn test_transaction_cancellation() { TestParamsHelpers::new(), MicroTari::from(100_000), ); - let mut builder = SenderTransactionProtocol::builder(1); + let constants = create_consensus_constants(0); + let mut builder = SenderTransactionProtocol::builder(1, constants); let amount = MicroTari::from(10_000); builder .with_lock_height(0) @@ -2769,12 +2771,14 @@ fn test_restarting_transaction_protocols() { let alice = TestParams::new(&mut OsRng); let bob = TestParams::new(&mut OsRng); let (utxo, input) = make_input(&mut OsRng, MicroTari(2000), &factories.commitment); - let mut builder = SenderTransactionProtocol::builder(1); - let fee = Fee::calculate(MicroTari(20), 1, 1, 1); + let constants = create_consensus_constants(0); + let fee_calc = Fee::new(*constants.transaction_weight()); + let mut builder = SenderTransactionProtocol::builder(1, constants); + let fee = fee_calc.calculate(MicroTari(4), 1, 1, 1, 0); let script_private_key = PrivateKey::random(&mut OsRng); builder .with_lock_height(0) - .with_fee_per_gram(MicroTari(20)) + .with_fee_per_gram(MicroTari(4)) .with_offset(bob.offset.clone()) .with_private_nonce(bob.nonce) .with_input(utxo, input) @@ -3938,11 +3942,12 @@ fn test_resend_on_startup() { TestParamsHelpers::new(), MicroTari::from(100_000), ); - let mut builder = SenderTransactionProtocol::builder(1); + let constants = create_consensus_constants(0); + let mut builder = SenderTransactionProtocol::builder(1, constants); let amount = MicroTari::from(10_000); builder .with_lock_height(0) - .with_fee_per_gram(MicroTari::from(177)) + .with_fee_per_gram(MicroTari::from(177 / 5)) .with_offset(PrivateKey::random(&mut OsRng)) .with_private_nonce(PrivateKey::random(&mut OsRng)) .with_amount(0, amount) @@ -4403,7 +4408,7 @@ fn test_transaction_timeout_cancellation() { .block_on(alice_ts.send_transaction( bob_node_identity.public_key().clone(), amount_sent, - 100 * uT, + 20 * uT, "Testing Message".to_string(), )) .unwrap(); @@ -4444,11 +4449,12 @@ fn test_transaction_timeout_cancellation() { TestParamsHelpers::new(), MicroTari::from(100_000), ); - let mut builder = SenderTransactionProtocol::builder(1); + let constants = create_consensus_constants(0); + let mut builder = SenderTransactionProtocol::builder(1, constants); let amount = MicroTari::from(10_000); builder .with_lock_height(0) - .with_fee_per_gram(MicroTari::from(177)) + .with_fee_per_gram(MicroTari::from(177 / 5)) .with_offset(PrivateKey::random(&mut OsRng)) .with_private_nonce(PrivateKey::random(&mut OsRng)) .with_amount(0, amount) @@ -4741,7 +4747,7 @@ fn transaction_service_tx_broadcast() { .block_on(alice_ts.send_transaction( bob_node_identity.public_key().clone(), amount_sent2, - 100 * uT, + 20 * uT, "Testing Message2".to_string(), )) .unwrap(); @@ -4974,7 +4980,7 @@ fn broadcast_all_completed_transactions_on_startup() { source_public_key: PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), destination_public_key: PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), amount: 5000 * uT, - fee: MicroTari::from(100), + fee: MicroTari::from(20), transaction: tx, status: TransactionStatus::Completed, message: "Yo!".to_string(), @@ -5119,7 +5125,7 @@ fn dont_broadcast_invalid_transactions() { source_public_key: PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), destination_public_key: PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), amount: 5000 * uT, - fee: MicroTari::from(100), + fee: MicroTari::from(20), transaction: tx, status: TransactionStatus::Completed, message: "Yo!".to_string(), diff --git a/base_layer/wallet/tests/transaction_service/storage.rs b/base_layer/wallet/tests/transaction_service/storage.rs index b0262ef810..b042343791 100644 --- a/base_layer/wallet/tests/transaction_service/storage.rs +++ b/base_layer/wallet/tests/transaction_service/storage.rs @@ -26,36 +26,37 @@ use aes_gcm::{ }; use chrono::Utc; use rand::rngs::OsRng; -use tari_crypto::{ - keys::{PublicKey as PublicKeyTrait, SecretKey as SecretKeyTrait}, - script, - script::{ExecutionStack, TariScript}, -}; -use tempfile::tempdir; -use tokio::runtime::Runtime; - use tari_common_types::{ transaction::{TransactionDirection, TransactionStatus}, types::{HashDigest, PrivateKey, PublicKey}, }; use tari_core::transactions::{ - helpers::{create_unblinded_output, TestParams}, tari_amount::{uT, MicroTari}, + test_helpers::{create_unblinded_output, TestParams}, transaction::{OutputFeatures, Transaction}, transaction_protocol::sender::TransactionSenderMessage, CryptoFactories, ReceiverTransactionProtocol, SenderTransactionProtocol, }; +use tari_crypto::{ + keys::{PublicKey as PublicKeyTrait, SecretKey as SecretKeyTrait}, + script, + script::{ExecutionStack, TariScript}, +}; use tari_test_utils::random; use tari_wallet::{ storage::sqlite_utilities::run_migration_and_create_sqlite_connection, + test_utils::create_consensus_constants, transaction_service::storage::{ database::{TransactionBackend, TransactionDatabase}, models::{CompletedTransaction, InboundTransaction, OutboundTransaction, WalletTransaction}, sqlite_db::TransactionServiceSqliteDatabase, }, }; +use tempfile::tempdir; +use tokio::runtime::Runtime; + pub fn test_db_backend(backend: T) { let runtime = Runtime::new().unwrap(); let mut db = TransactionDatabase::new(backend); @@ -66,11 +67,12 @@ pub fn test_db_backend(backend: T) { TestParams::new(), MicroTari::from(100_000), ); - let mut builder = SenderTransactionProtocol::builder(1); + let constants = create_consensus_constants(0); + let mut builder = SenderTransactionProtocol::builder(1, constants); let amount = MicroTari::from(10_000); builder .with_lock_height(0) - .with_fee_per_gram(MicroTari::from(177)) + .with_fee_per_gram(MicroTari::from(177 / 5)) .with_offset(PrivateKey::random(&mut OsRng)) .with_private_nonce(PrivateKey::random(&mut OsRng)) .with_amount(0, amount) diff --git a/base_layer/wallet/tests/transaction_service/transaction_protocols.rs b/base_layer/wallet/tests/transaction_service/transaction_protocols.rs index cf6aca4ccc..8dc532ce81 100644 --- a/base_layer/wallet/tests/transaction_service/transaction_protocols.rs +++ b/base_layer/wallet/tests/transaction_service/transaction_protocols.rs @@ -53,8 +53,8 @@ use tari_core::{ types::Signature as SignatureProto, }, transactions::{ - helpers::schema_to_transaction, tari_amount::{uT, MicroTari, T}, + test_helpers::schema_to_transaction, CryptoFactories, }, txn_schema, diff --git a/base_layer/wallet/tests/wallet/mod.rs b/base_layer/wallet/tests/wallet/mod.rs index efa6afc42f..fde22fc9fc 100644 --- a/base_layer/wallet/tests/wallet/mod.rs +++ b/base_layer/wallet/tests/wallet/mod.rs @@ -48,8 +48,8 @@ use tari_comms::{ }; use tari_comms_dht::DhtConfig; use tari_core::transactions::{ - helpers::{create_unblinded_output, TestParams}, tari_amount::{uT, MicroTari}, + test_helpers::{create_unblinded_output, TestParams}, transaction::OutputFeatures, CryptoFactories, }; diff --git a/base_layer/wallet_ffi/src/lib.rs b/base_layer/wallet_ffi/src/lib.rs index dd9c52c7c4..673148b164 100644 --- a/base_layer/wallet_ffi/src/lib.rs +++ b/base_layer/wallet_ffi/src/lib.rs @@ -1560,7 +1560,7 @@ pub unsafe extern "C" fn completed_transaction_get_transaction_kernel( return ptr::null_mut(); } - let kernels = (*transaction).transaction.get_body().kernels(); + let kernels = (*transaction).transaction.body().kernels(); // currently we presume that each CompletedTransaction only has 1 kernel // if that changes this will need to be accounted for @@ -3438,8 +3438,8 @@ pub unsafe extern "C" fn wallet_get_fee_estimate( .block_on((*wallet).wallet.output_manager_service.fee_estimate( MicroTari::from(amount), MicroTari::from(fee_per_gram), - num_kernels, - num_outputs, + num_kernels as usize, + num_outputs as usize, )) { Ok(fee) => fee.into(), Err(e) => { diff --git a/integration_tests/features/Mempool.feature b/integration_tests/features/Mempool.feature index 2365be2ae8..92df079d7c 100644 --- a/integration_tests/features/Mempool.feature +++ b/integration_tests/features/Mempool.feature @@ -43,9 +43,9 @@ Feature: Mempool When I mine a block on SENDER with coinbase CB2 When I mine a block on SENDER with coinbase CB3 When I mine 4 blocks on SENDER - When I create a custom fee transaction TX1 spending CB1 to UTX1 with fee 80 - When I create a custom fee transaction TX2 spending CB2 to UTX2 with fee 100 - When I create a custom fee transaction TX3 spending CB3 to UTX3 with fee 90 + When I create a custom fee transaction TX1 spending CB1 to UTX1 with fee 16 + When I create a custom fee transaction TX2 spending CB2 to UTX2 with fee 20 + When I create a custom fee transaction TX3 spending CB3 to UTX3 with fee 18 When I submit transaction TX1 to SENDER When I submit transaction TX2 to SENDER When I submit transaction TX3 to SENDER @@ -68,8 +68,8 @@ Feature: Mempool And I have a base node SENDER connected to all seed nodes When I mine a block on SENDER with coinbase CB1 When I mine 4 blocks on SENDER - When I create a custom fee transaction TX1 spending CB1 to UTX1 with fee 80 - When I create a custom fee transaction TX2 spending CB1 to UTX2 with fee 100 + When I create a custom fee transaction TX1 spending CB1 to UTX1 with fee 16 + When I create a custom fee transaction TX2 spending CB1 to UTX2 with fee 20 When I submit transaction TX1 to SENDER When I submit transaction TX2 to SENDER Then SENDER has TX1 in MEMPOOL state @@ -85,8 +85,8 @@ Feature: Mempool And I have a base node SENDER connected to all seed nodes When I mine a block on SENDER with coinbase CB1 When I mine 4 blocks on SENDER - When I create a custom fee transaction TX1 spending CB1 to UTX1 with fee 80 - When I create a custom fee transaction TX2 spending CB1 to UTX2 with fee 100 + When I create a custom fee transaction TX1 spending CB1 to UTX1 with fee 16 + When I create a custom fee transaction TX2 spending CB1 to UTX2 with fee 20 When I submit transaction TX1 to SENDER When I submit transaction TX2 to SENDER Then SENDER has TX1 in MEMPOOL state @@ -106,8 +106,8 @@ Feature: Mempool And I have a base node NODE_B connected to seed SEED_B When I mine a block on NODE_B with coinbase CB_B When I mine 10 blocks on NODE_B - When I create a custom fee transaction TXA spending CB_A to UTX1 with fee 80 - When I create a custom fee transaction TXB spending CB_B to UTX1 with fee 80 + When I create a custom fee transaction TXA spending CB_A to UTX1 with fee 16 + When I create a custom fee transaction TXB spending CB_B to UTX1 with fee 16 When I submit transaction TXA to NODE_A When I submit transaction TXB to NODE_B Then NODE_A has TXA in MEMPOOL state @@ -126,12 +126,12 @@ Feature: Mempool When I mine a block on SENDER with coinbase CB1 When I mine a block on SENDER with coinbase CB2 When I mine 4 blocks on SENDER - When I create a custom fee transaction TX01 spending CB1 to UTX01 with fee 100 - When I create a custom fee transaction TX02 spending UTX01 to UTX02 with fee 100 - When I create a custom fee transaction TX03 spending UTX02 to UTX03 with fee 100 - When I create a custom fee transaction TX11 spending CB2 to UTX11 with fee 100 - When I create a custom fee transaction TX12 spending UTX11 to UTX12 with fee 100 - When I create a custom fee transaction TX13 spending UTX12 to UTX13 with fee 100 + When I create a custom fee transaction TX01 spending CB1 to UTX01 with fee 20 + When I create a custom fee transaction TX02 spending UTX01 to UTX02 with fee 20 + When I create a custom fee transaction TX03 spending UTX02 to UTX03 with fee 20 + When I create a custom fee transaction TX11 spending CB2 to UTX11 with fee 20 + When I create a custom fee transaction TX12 spending UTX11 to UTX12 with fee 20 + When I create a custom fee transaction TX13 spending UTX12 to UTX13 with fee 20 When I submit transaction TX01 to SENDER When I submit transaction TX02 to SENDER When I submit transaction TX03 to SENDER @@ -157,11 +157,11 @@ Feature: Mempool And I have a base node BN1 connected to all seed nodes When I mine a block on BN1 with coinbase CB1 When I mine 5 blocks on BN1 - When I create a custom fee transaction TX1 spending CB1 to UTX1 with fee 80 - When I create a custom fee transaction TX2 spending CB1 to UTX1 with fee 80 - When I create a custom fee transaction TX3 spending CB1 to UTX1 with fee 80 - When I create a custom fee transaction TX4 spending CB1 to UTX1 with fee 80 - When I create a custom fee transaction TX5 spending CB1 to UTX1 with fee 80 + When I create a custom fee transaction TX1 spending CB1 to UTX1 with fee 16 + When I create a custom fee transaction TX2 spending CB1 to UTX1 with fee 16 + When I create a custom fee transaction TX3 spending CB1 to UTX1 with fee 16 + When I create a custom fee transaction TX4 spending CB1 to UTX1 with fee 16 + When I create a custom fee transaction TX5 spending CB1 to UTX1 with fee 16 When I submit transaction TX1 to BN1 When I submit transaction TX2 to BN1 When I submit transaction TX3 to BN1 @@ -174,7 +174,7 @@ Feature: Mempool And I have a base node BN1 connected to all seed nodes When I mine a block on BN1 with coinbase CB1 When I mine 2 blocks on BN1 - When I create a custom fee transaction TX1 spending CB1 to UTX1 with fee 80 + When I create a custom fee transaction TX1 spending CB1 to UTX1 with fee 16 When I submit transaction TX1 to BN1 Then I wait until base node BN1 has 1 unconfirmed transactions in its mempool When I mine 1 blocks on BN1 diff --git a/integration_tests/features/Reorgs.feature b/integration_tests/features/Reorgs.feature index 34338bc6d2..f5527f92f5 100644 --- a/integration_tests/features/Reorgs.feature +++ b/integration_tests/features/Reorgs.feature @@ -101,8 +101,8 @@ Feature: Reorgs When I mine 14 blocks on NODE1 When I mine a block on NODE1 with coinbase CB1 When I mine 4 blocks on NODE1 - When I create a custom fee transaction TX1 spending CB1 to UTX1 with fee 100 - When I create a custom fee transaction TX11 spending UTX1 to UTX11 with fee 100 + When I create a custom fee transaction TX1 spending CB1 to UTX1 with fee 20 + When I create a custom fee transaction TX11 spending UTX1 to UTX11 with fee 20 When I submit transaction TX1 to NODE1 When I submit transaction TX11 to NODE1 When I mine 1 blocks on NODE1 @@ -113,8 +113,8 @@ Feature: Reorgs And node NODE2 is at height 20 When I mine a block on NODE2 with coinbase CB2 When I mine 3 blocks on NODE2 - When I create a custom fee transaction TX2 spending CB2 to UTX2 with fee 100 - When I create a custom fee transaction TX21 spending UTX2 to UTX21 with fee 100 + When I create a custom fee transaction TX2 spending CB2 to UTX2 with fee 20 + When I create a custom fee transaction TX21 spending UTX2 to UTX21 with fee 20 When I submit transaction TX2 to NODE2 When I submit transaction TX21 to NODE2 When I mine 1 blocks on NODE2 @@ -126,8 +126,8 @@ Feature: Reorgs And node NODE1 is at height 20 When I mine a block on NODE1 with coinbase CB3 When I mine 3 blocks on NODE1 - When I create a custom fee transaction TX3 spending CB3 to UTX3 with fee 100 - When I create a custom fee transaction TX31 spending UTX3 to UTX31 with fee 100 + When I create a custom fee transaction TX3 spending CB3 to UTX3 with fee 20 + When I create a custom fee transaction TX31 spending UTX3 to UTX31 with fee 20 When I submit transaction TX3 to NODE1 When I submit transaction TX31 to NODE1 When I mine 1 blocks on NODE1 diff --git a/integration_tests/features/StressTest.feature b/integration_tests/features/StressTest.feature index 559dc712be..f36ea91a50 100644 --- a/integration_tests/features/StressTest.feature +++ b/integration_tests/features/StressTest.feature @@ -15,13 +15,13 @@ Feature: Stress Test Then all nodes are on the same chain tip When I wait for wallet WALLET_A to have at least 5100000000 uT - Then I coin split tari in wallet WALLET_A to produce UTXOs of 5000 uT each with fee_per_gram 20 uT + Then I coin split tari in wallet WALLET_A to produce UTXOs of 5000 uT each with fee_per_gram 4 uT When I wait 30 seconds When mining node MINER mines 3 blocks When mining node MINER mines blocks Then all nodes are on the same chain tip Then wallet WALLET_A detects all transactions as Mined_Confirmed - When I send transactions of 1111 uT each from wallet WALLET_A to wallet WALLET_B at fee_per_gram 20 + When I send transactions of 1111 uT each from wallet WALLET_A to wallet WALLET_B at fee_per_gram 4 # Mine enough blocks for the first block of transactions to be confirmed. When mining node MINER mines 4 blocks Then all nodes are on the same chain tip @@ -63,14 +63,14 @@ Feature: Stress Test Then all nodes are on the same chain tip When I wait for wallet WALLET_A to have at least 15100000000 uT - Then I coin split tari in wallet WALLET_A to produce 2000 UTXOs of 5000 uT each with fee_per_gram 20 uT + Then I coin split tari in wallet WALLET_A to produce 2000 UTXOs of 5000 uT each with fee_per_gram 4 uT # Make sure enough blocks are mined for the coin split transaction to be confirmed When mining node MINER mines 8 blocks Then all nodes are on the same chain tip Then wallet WALLET_A detects all transactions as Mined_Confirmed - When I send 2000 transactions of 1111 uT each from wallet WALLET_A to wallet WALLET_B at fee_per_gram 20 + When I send 2000 transactions of 1111 uT each from wallet WALLET_A to wallet WALLET_B at fee_per_gram 4 # Mine enough blocks for the first block of transactions to be confirmed. When mining node MINER mines 4 blocks Then all nodes are on the same chain tip diff --git a/integration_tests/features/TransactionInfo.feature b/integration_tests/features/TransactionInfo.feature index c7d00db5ec..13bf015b30 100644 --- a/integration_tests/features/TransactionInfo.feature +++ b/integration_tests/features/TransactionInfo.feature @@ -14,7 +14,7 @@ Scenario: Get Transaction Info Then all nodes are at height 4 Then I list all COINBASE transactions for wallet WALLET_A When I wait for wallet WALLET_A to have at least 1002000 uT - And I send 1000000 uT from wallet WALLET_A to wallet WALLET_B at fee 100 + And I send 1000000 uT from wallet WALLET_A to wallet WALLET_B at fee 20 Then wallet WALLET_A detects all transactions are at least Pending Then wallet WALLET_B detects all transactions are at least Pending Then wallet WALLET_A detects all transactions are at least Completed diff --git a/integration_tests/features/WalletFFI.feature b/integration_tests/features/WalletFFI.feature index 9248974871..0988fe2c93 100644 --- a/integration_tests/features/WalletFFI.feature +++ b/integration_tests/features/WalletFFI.feature @@ -53,13 +53,13 @@ Feature: Wallet FFI And mining node MINER mines 10 blocks Then I wait for wallet SENDER to have at least 1000000 uT And I have a ffi wallet FFI_WALLET connected to base node BASE - And I send 2000000 uT from wallet SENDER to wallet FFI_WALLET at fee 100 + And I send 2000000 uT from wallet SENDER to wallet FFI_WALLET at fee 20 And wallet SENDER detects all transactions are at least Broadcast And mining node MINER mines 10 blocks Then I wait for ffi wallet FFI_WALLET to have at least 1000000 uT And I have wallet RECEIVER connected to base node BASE And I stop wallet RECEIVER - And I send 1000000 uT from ffi wallet FFI_WALLET to wallet RECEIVER at fee 100 + And I send 1000000 uT from ffi wallet FFI_WALLET to wallet RECEIVER at fee 20 Then I wait for ffi wallet FFI_WALLET to have 1 pending outbound transaction Then I cancel all outbound transactions on ffi wallet FFI_WALLET and it will cancel 1 transaction And I stop ffi wallet FFI_WALLET @@ -84,11 +84,11 @@ Feature: Wallet FFI And mining node MINER mines 10 blocks Then I wait for wallet SENDER to have at least 1000000 uT And I have a ffi wallet FFI_WALLET connected to base node BASE - And I send 2000000 uT from wallet SENDER to wallet FFI_WALLET at fee 100 + And I send 2000000 uT from wallet SENDER to wallet FFI_WALLET at fee 20 And mining node MINER mines 10 blocks Then I wait for ffi wallet FFI_WALLET to have at least 1000000 uT And I have wallet RECEIVER connected to base node BASE - And I send 1000000 uT from ffi wallet FFI_WALLET to wallet RECEIVER at fee 100 + And I send 1000000 uT from ffi wallet FFI_WALLET to wallet RECEIVER at fee 20 And mining node MINER mines 10 blocks Then I wait for wallet RECEIVER to have at least 1000000 uT And I have 1 received and 1 send transaction in ffi wallet FFI_WALLET @@ -107,7 +107,7 @@ Feature: Wallet FFI And I have a ffi wallet FFI_WALLET connected to base node BASE And I stop ffi wallet FFI_WALLET And I wait 10 seconds - And I send 2000000 uT from wallet SENDER to wallet FFI_WALLET at fee 100 + And I send 2000000 uT from wallet SENDER to wallet FFI_WALLET at fee 20 And I wait 5 seconds # Broken step with reason base node is not persisted # Log: diff --git a/integration_tests/features/WalletMonitoring.feature b/integration_tests/features/WalletMonitoring.feature index 48c664fd34..7d9120755f 100644 --- a/integration_tests/features/WalletMonitoring.feature +++ b/integration_tests/features/WalletMonitoring.feature @@ -66,7 +66,7 @@ Feature: Wallet Monitoring Then node NODE_A1 is at height 10 Then wallet WALLET_A1 detects at least 7 coinbase transactions as Mined_Confirmed # Use 7 of the 10 coinbase UTXOs in transactions (others require 3 confirmations) - And I multi-send 7 transactions of 1000000 uT from wallet WALLET_A1 to wallet WALLET_A2 at fee 100 + And I multi-send 7 transactions of 1000000 uT from wallet WALLET_A1 to wallet WALLET_A2 at fee 20 Then wallet WALLET_A1 detects all transactions are at least Broadcast When I mine 100 blocks on SEED_A Then node SEED_A is at height 110 @@ -88,7 +88,7 @@ Feature: Wallet Monitoring Then node NODE_B1 is at height 10 Then wallet WALLET_B1 detects at least 7 coinbase transactions as Mined_Confirmed # Use 7 of the 10 coinbase UTXOs in transactions (others require 3 confirmations) - And I multi-send 7 transactions of 1000000 uT from wallet WALLET_B1 to wallet WALLET_B2 at fee 100 + And I multi-send 7 transactions of 1000000 uT from wallet WALLET_B1 to wallet WALLET_B2 at fee 20 Then wallet WALLET_B1 detects all transactions are at least Broadcast When I mine 100 blocks on SEED_B Then node SEED_B is at height 110 diff --git a/integration_tests/features/WalletRecovery.feature b/integration_tests/features/WalletRecovery.feature index a499661740..17b3a7ffb3 100644 --- a/integration_tests/features/WalletRecovery.feature +++ b/integration_tests/features/WalletRecovery.feature @@ -14,7 +14,7 @@ Feature: Wallet Recovery When I recover wallet WALLET_A into wallet WALLET_B connected to all seed nodes Then wallet WALLET_A and wallet WALLET_B have the same balance And I have wallet WALLET_C connected to all seed nodes - And I send 100000 uT from wallet WALLET_B to wallet WALLET_C at fee 100 + And I send 100000 uT from wallet WALLET_B to wallet WALLET_C at fee 20 Then wallet WALLET_B detects all transactions are at least Broadcast When I mine 5 blocks on NODE Then all nodes are at height 20 @@ -54,19 +54,19 @@ Feature: Wallet Recovery And I have wallet WALLET_B connected to all seed nodes And I stop wallet WALLET_B # Send 2 one-sided payments to WALLET_B so it can spend them in two cases - Then I send a one-sided transaction of 1000000 uT from WALLET_A to WALLET_B at fee 100 - Then I send a one-sided transaction of 1000000 uT from WALLET_A to WALLET_B at fee 100 + Then I send a one-sided transaction of 1000000 uT from WALLET_A to WALLET_B at fee 20 + Then I send a one-sided transaction of 1000000 uT from WALLET_A to WALLET_B at fee 20 When mining node MINER mines 5 blocks Then all nodes are at height 15 When I recover wallet WALLET_B into wallet WALLET_C connected to all seed nodes Then I wait for wallet WALLET_C to have at least 2000000 uT # Send one of the recovered outputs back to Wallet A as a one-sided transactions - Then I send a one-sided transaction of 900000 uT from WALLET_C to WALLET_A at fee 100 + Then I send a one-sided transaction of 900000 uT from WALLET_C to WALLET_A at fee 20 When mining node MINER mines 5 blocks Then all nodes are at height 20 Then I wait for wallet WALLET_C to have less than 1100000 uT # Send the remaining recovered UTXO to self in standard MW transaction - Then I send 1000000 uT from wallet WALLET_C to wallet WALLET_C at fee 100 + Then I send 1000000 uT from wallet WALLET_C to wallet WALLET_C at fee 20 Then I wait for wallet WALLET_C to have less than 100000 uT When mining node MINER mines 5 blocks Then all nodes are at height 25 diff --git a/integration_tests/features/WalletRoutingMechanism.feature b/integration_tests/features/WalletRoutingMechanism.feature index e93212fb44..b3007d91f9 100644 --- a/integration_tests/features/WalletRoutingMechanism.feature +++ b/integration_tests/features/WalletRoutingMechanism.feature @@ -16,7 +16,7 @@ Scenario Outline: Wallets transacting via specified routing mechanism only When I wait 1 seconds When I wait for wallet WALLET_A to have at least 100000000 uT #When I print the world - And I multi-send 1000000 uT from wallet WALLET_A to all wallets at fee 100 + And I multi-send 1000000 uT from wallet WALLET_A to all wallets at fee 20 Then all wallets detect all transactions are at least Pending Then all wallets detect all transactions are at least Completed Then all wallets detect all transactions are at least Broadcast diff --git a/integration_tests/features/WalletTransactions.feature b/integration_tests/features/WalletTransactions.feature index fc75f4bc1a..51bbc878b0 100644 --- a/integration_tests/features/WalletTransactions.feature +++ b/integration_tests/features/WalletTransactions.feature @@ -11,21 +11,21 @@ Feature: Wallet Transactions Then all nodes are at height 15 When I wait for wallet WALLET_A to have at least 55000000000 uT And I have wallet WALLET_B connected to all seed nodes - Then I send a one-sided transaction of 1000000 uT from WALLET_A to WALLET_B at fee 100 - Then I send a one-sided transaction of 1000000 uT from WALLET_A to WALLET_B at fee 100 + Then I send a one-sided transaction of 1000000 uT from WALLET_A to WALLET_B at fee 20 + Then I send a one-sided transaction of 1000000 uT from WALLET_A to WALLET_B at fee 20 Then wallet WALLET_A detects all transactions are at least Broadcast When mining node MINER mines 5 blocks Then all nodes are at height 20 Then I wait for wallet WALLET_B to have at least 2000000 uT # Spend one of the recovered UTXOs to self in a standard MW transaction - Then I send 900000 uT from wallet WALLET_B to wallet WALLET_B at fee 100 + Then I send 900000 uT from wallet WALLET_B to wallet WALLET_B at fee 20 Then I wait for wallet WALLET_B to have less than 1100000 uT When mining node MINER mines 5 blocks Then all nodes are at height 25 Then I wait for wallet WALLET_B to have at least 1900000 uT # Make a one-sided payment to a new wallet that is big enough to ensure the second recovered output is spent And I have wallet WALLET_C connected to all seed nodes - Then I send a one-sided transaction of 1500000 uT from WALLET_B to WALLET_C at fee 100 + Then I send a one-sided transaction of 1500000 uT from WALLET_B to WALLET_C at fee 20 Then I wait for wallet WALLET_B to have less than 1000000 uT When mining node MINER mines 5 blocks Then all nodes are at height 30 @@ -40,7 +40,7 @@ Feature: Wallet Transactions Then all nodes are at height 5 Then I wait for wallet WALLET_A to have at least 10000000000 uT Then I have wallet WALLET_B connected to all seed nodes - And I send 1000000 uT from wallet WALLET_A to wallet WALLET_B at fee 100 + And I send 1000000 uT from wallet WALLET_A to wallet WALLET_B at fee 20 When wallet WALLET_A detects all transactions are at least Broadcast Then mining node MINER mines 5 blocks Then all nodes are at height 10 @@ -63,12 +63,12 @@ Feature: Wallet Transactions Then all nodes are at height 5 Then I wait for wallet WALLET_A to have at least 10000000000 uT Then I have wallet WALLET_B connected to all seed nodes - And I send 1000000 uT from wallet WALLET_A to wallet WALLET_B at fee 100 + And I send 1000000 uT from wallet WALLET_A to wallet WALLET_B at fee 20 When wallet WALLET_A detects all transactions are at least Broadcast Then mining node MINER mines 5 blocks Then all nodes are at height 10 Then I wait for wallet WALLET_B to have at least 1000000 uT - When I send 900000 uT from wallet WALLET_B to wallet WALLET_A at fee 100 + When I send 900000 uT from wallet WALLET_B to wallet WALLET_A at fee 20 And wallet WALLET_B detects all transactions are at least Broadcast Then mining node MINER mines 5 blocks Then all nodes are at height 15 @@ -94,7 +94,7 @@ Feature: Wallet Transactions And mining node BM mines 4 blocks with min difficulty 1 and max difficulty 50 Then I wait for wallet WB to have at least 1000000 uT And I have wallet WALLET_RECEIVE_TX connected to base node B - And I send 1000000 uT from wallet WB to wallet WALLET_RECEIVE_TX at fee 100 + And I send 1000000 uT from wallet WB to wallet WALLET_RECEIVE_TX at fee 20 And wallet WB detects all transactions are at least Broadcast Then mining node BM mines 4 blocks with min difficulty 50 and max difficulty 100 When node B is at height 8 @@ -138,7 +138,7 @@ Feature: Wallet Transactions Then all nodes are at height 5 Then I wait for wallet WALLET_A to have at least 10000000000 uT When I have wallet WALLET_B connected to all seed nodes - And I send 1000000 uT from wallet WALLET_A to wallet WALLET_B at fee 100 + And I send 1000000 uT from wallet WALLET_A to wallet WALLET_B at fee 20 When I wait 10 seconds When mining node MINER mines 6 blocks Then all nodes are at height 11 @@ -147,7 +147,7 @@ Feature: Wallet Transactions When I have wallet WALLET_C connected to all seed nodes Then I import WALLET_B unspent outputs as faucet outputs to WALLET_C Then I wait for wallet WALLET_C to have at least 1000000 uT - And I send 500000 uT from wallet WALLET_C to wallet WALLET_A at fee 100 + And I send 500000 uT from wallet WALLET_C to wallet WALLET_A at fee 20 When I wait 10 seconds Then wallet WALLET_C detects all transactions are at least Broadcast When mining node MINER mines 6 blocks @@ -163,11 +163,11 @@ Feature: Wallet Transactions Then all nodes are at height 10 Then I wait for wallet WALLET_A to have at least 10000000000 uT Then I have wallet WALLET_B connected to all seed nodes - And I send 100000 uT from wallet WALLET_A to wallet WALLET_B at fee 100 - And I send 100000 uT from wallet WALLET_A to wallet WALLET_B at fee 100 - And I send 100000 uT from wallet WALLET_A to wallet WALLET_B at fee 100 - And I send 100000 uT from wallet WALLET_A to wallet WALLET_B at fee 100 - And I send 100000 uT from wallet WALLET_A to wallet WALLET_B at fee 100 + And I send 100000 uT from wallet WALLET_A to wallet WALLET_B at fee 20 + And I send 100000 uT from wallet WALLET_A to wallet WALLET_B at fee 20 + And I send 100000 uT from wallet WALLET_A to wallet WALLET_B at fee 20 + And I send 100000 uT from wallet WALLET_A to wallet WALLET_B at fee 20 + And I send 100000 uT from wallet WALLET_A to wallet WALLET_B at fee 20 When I wait 30 seconds When wallet WALLET_A detects all transactions are at least Broadcast Then mining node MINER mines 5 blocks @@ -195,7 +195,7 @@ Feature: Wallet Transactions Then wallet WALLET_A1 detects at least 7 coinbase transactions as Mined_Confirmed Then node SEED_A is at height 10 Then node NODE_A1 is at height 10 - And I multi-send 7 transactions of 1000000 uT from wallet WALLET_A1 to wallet WALLET_A2 at fee 100 + And I multi-send 7 transactions of 1000000 uT from wallet WALLET_A1 to wallet WALLET_A2 at fee 20 Then wallet WALLET_A1 detects all transactions are at least Broadcast # # Chain 2: @@ -214,7 +214,7 @@ Feature: Wallet Transactions Then wallet WALLET_B1 detects at least 7 coinbase transactions as Mined_Confirmed Then node SEED_B is at height 12 Then node NODE_B1 is at height 12 - And I multi-send 7 transactions of 1000000 uT from wallet WALLET_B1 to wallet WALLET_B2 at fee 100 + And I multi-send 7 transactions of 1000000 uT from wallet WALLET_B1 to wallet WALLET_B2 at fee 20 Then wallet WALLET_B1 detects all transactions are at least Broadcast # # Connect Chain 1 and 2 in stages @@ -249,7 +249,7 @@ Feature: Wallet Transactions Then wallet WALLET_A1 detects at least 1 coinbase transactions as Mined_Confirmed Then node SEED_A is at height 4 Then node NODE_A1 is at height 4 - And I multi-send 1 transactions of 10000 uT from wallet WALLET_A1 to wallet WALLET_A2 at fee 100 + And I multi-send 1 transactions of 10000 uT from wallet WALLET_A1 to wallet WALLET_A2 at fee 20 Then wallet WALLET_A1 detects all transactions are at least Broadcast # # Chain 2: @@ -268,7 +268,7 @@ Feature: Wallet Transactions Then wallet WALLET_B1 detects at least 2 coinbase transactions as Mined_Confirmed Then node SEED_B is at height 5 Then node NODE_B1 is at height 5 - And I multi-send 2 transactions of 10000 uT from wallet WALLET_B1 to wallet WALLET_B2 at fee 100 + And I multi-send 2 transactions of 10000 uT from wallet WALLET_B1 to wallet WALLET_B2 at fee 20 Then wallet WALLET_B1 detects all transactions are at least Broadcast # # Connect Chain 1 and 2 in stages @@ -295,14 +295,14 @@ Feature: Wallet Transactions Then all nodes are at height 5 Then I wait for wallet WALLET_A to have at least 10000000000 uT And I have non-default wallet WALLET_SENDER connected to all seed nodes using StoreAndForwardOnly - And I send 100000000 uT from wallet WALLET_A to wallet WALLET_SENDER at fee 100 + And I send 100000000 uT from wallet WALLET_A to wallet WALLET_SENDER at fee 20 When wallet WALLET_SENDER detects all transactions are at least Broadcast And mining node MINER mines 5 blocks Then all nodes are at height 10 Then I wait for wallet WALLET_SENDER to have at least 100000000 uT And I have wallet WALLET_RECV connected to all seed nodes And I stop wallet WALLET_RECV - And I send 1000000 uT from wallet WALLET_SENDER to wallet WALLET_RECV at fee 100 + And I send 1000000 uT from wallet WALLET_SENDER to wallet WALLET_RECV at fee 20 When wallet WALLET_SENDER detects last transaction is Pending Then I stop wallet WALLET_SENDER And I start wallet WALLET_RECV diff --git a/integration_tests/features/WalletTransfer.feature b/integration_tests/features/WalletTransfer.feature index 33f8b2b172..1fbade662a 100644 --- a/integration_tests/features/WalletTransfer.feature +++ b/integration_tests/features/WalletTransfer.feature @@ -18,7 +18,7 @@ Feature: Wallet Transfer # Ensure the coinbase lock heights have expired And mining node MINER mines 5 blocks Then all nodes are at height 10 - When I transfer 50000 uT from Wallet_A to Wallet_B and Wallet_C at fee 100 + When I transfer 50000 uT from Wallet_A to Wallet_B and Wallet_C at fee 20 And mining node MINER mines 10 blocks Then all nodes are at height 20 Then all wallets detect all transactions as Mined_Confirmed @@ -34,7 +34,7 @@ Feature: Wallet Transfer Then all nodes are at height 5 # Ensure the coinbase lock heights have expired When I mine 5 blocks on NODE - When I transfer 50000 uT to self from wallet Wallet_A at fee 25 + When I transfer 50000 uT to self from wallet Wallet_A at fee 5 And I mine 5 blocks on NODE Then all nodes are at height 15 Then all wallets detect all transactions as Mined_Confirmed diff --git a/integration_tests/features/support/ffi_steps.js b/integration_tests/features/support/ffi_steps.js index 7a599c9fe7..da58633e7d 100644 --- a/integration_tests/features/support/ffi_steps.js +++ b/integration_tests/features/support/ffi_steps.js @@ -30,13 +30,13 @@ Then( When( "I send {int} uT from ffi wallet {word} to wallet {word} at fee {int}", { timeout: 20 * 1000 }, - function (amount, sender, receiver, fee) { + function (amount, sender, receiver, feePerGram) { let ffi_wallet = this.getWallet(sender); let result = ffi_wallet.sendTransaction( this.getWalletPubkey(receiver), amount, - fee, - `Send from ffi ${sender} to ${receiver} at fee ${fee}` + feePerGram, + `Send from ffi ${sender} to ${receiver} at fee ${feePerGram}` ); console.log(result); }