From cb4310e114ec385a2f327bc1a6304722ac63c4c1 Mon Sep 17 00:00:00 2001 From: Stanimal Date: Tue, 28 Jun 2022 11:31:56 +0200 Subject: [PATCH 1/5] feat(wallet): add ContractOutput selection filter --- .../output_manager_service/input_selection.rs | 44 ++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/base_layer/wallet/src/output_manager_service/input_selection.rs b/base_layer/wallet/src/output_manager_service/input_selection.rs index 026ebe5616..aa24e5bd9d 100644 --- a/base_layer/wallet/src/output_manager_service/input_selection.rs +++ b/base_layer/wallet/src/output_manager_service/input_selection.rs @@ -25,7 +25,8 @@ use std::{ fmt::{Display, Formatter}, }; -use tari_common_types::types::PublicKey; +use tari_common_types::types::{Commitment, FixedHash, PublicKey}; +use tari_core::transactions::transaction_components::OutputType; use crate::output_manager_service::storage::models::DbUnblindedOutput; @@ -33,13 +34,23 @@ use crate::output_manager_service::storage::models::DbUnblindedOutput; pub struct UtxoSelectionCriteria { pub filter: UtxoSelectionFilter, pub ordering: UtxoSelectionOrdering, + pub excluding: Vec, } impl UtxoSelectionCriteria { + pub fn smallest_first() -> Self { + Self { + filter: UtxoSelectionFilter::Standard, + ordering: UtxoSelectionOrdering::SmallestFirst, + ..Default::default() + } + } + pub fn largest_first() -> Self { Self { filter: UtxoSelectionFilter::Standard, ordering: UtxoSelectionOrdering::LargestFirst, + ..Default::default() } } @@ -49,7 +60,17 @@ impl UtxoSelectionCriteria { unique_id, parent_public_key, }, - ordering: UtxoSelectionOrdering::Default, + ..Default::default() + } + } + + pub fn for_contract(contract_id: FixedHash, output_type: OutputType) -> Self { + Self { + filter: UtxoSelectionFilter::ContractOutput { + contract_id, + output_type, + }, + ..Default::default() } } } @@ -99,9 +120,25 @@ pub enum UtxoSelectionFilter { unique_id: Vec, parent_public_key: Option, }, + /// Select matching contract outputs. Additional Standard outputs may be included if necessary. + ContractOutput { + /// Contract ID to select + contract_id: FixedHash, + /// Type of contract output to select. + output_type: OutputType, + }, /// Selects specific outputs. All outputs must be exist and be spendable. SpecificOutputs { outputs: Vec }, } +impl UtxoSelectionFilter { + pub fn is_standard(&self) -> bool { + matches!(self, UtxoSelectionFilter::Standard) + } + + pub fn is_contract_output(&self) -> bool { + matches!(self, UtxoSelectionFilter::ContractOutput { .. }) + } +} impl Default for UtxoSelectionFilter { fn default() -> Self { @@ -121,6 +158,9 @@ impl Display for UtxoSelectionFilter { UtxoSelectionFilter::SpecificOutputs { outputs } => { write!(f, "Specific({} output(s))", outputs.len()) }, + UtxoSelectionFilter::ContractOutput { contract_id, .. } => { + write!(f, "ContractOutput({})", contract_id) + }, } } } From 4691ca0967331c41b9c1f5e3ac2d6f8f300f63e0 Mon Sep 17 00:00:00 2001 From: Stanimal Date: Tue, 28 Jun 2022 11:32:58 +0200 Subject: [PATCH 2/5] feat(wallet): spend contract utxos and allow output features to be specified --- .../src/automation/commands.rs | 17 ++- .../src/grpc/wallet_grpc_server.rs | 20 ++- .../src/ui/state/app_state.rs | 19 ++- .../tari_console_wallet/src/ui/state/tasks.rs | 13 +- base_layer/core/src/proto/transaction.rs | 1 + .../side_chain/committee_signatures.rs | 7 + .../unblinded_output_builder.rs | 4 + base_layer/wallet/src/assets/asset_manager.rs | 33 ++-- .../infrastructure/asset_manager_service.rs | 4 +- .../src/output_manager_service/error.rs | 5 +- .../src/output_manager_service/handle.rs | 34 ++--- .../src/output_manager_service/service.rs | 144 +++++++++--------- .../storage/database/backend.rs | 2 +- .../storage/database/mod.rs | 2 +- .../storage/sqlite_db/mod.rs | 2 +- .../storage/sqlite_db/output_sql.rs | 57 +++---- .../wallet/src/transaction_service/handle.rs | 68 +-------- .../protocols/transaction_send_protocol.rs | 15 +- .../wallet/src/transaction_service/service.rs | 47 +++--- .../output_manager_service_tests/service.rs | 142 +++++++++++++---- .../transaction_service_tests/service.rs | 34 ++++- base_layer/wallet/tests/wallet.rs | 2 + base_layer/wallet_ffi/src/lib.rs | 3 + 23 files changed, 376 insertions(+), 299 deletions(-) diff --git a/applications/tari_console_wallet/src/automation/commands.rs b/applications/tari_console_wallet/src/automation/commands.rs index 8537517a77..eec187243d 100644 --- a/applications/tari_console_wallet/src/automation/commands.rs +++ b/applications/tari_console_wallet/src/automation/commands.rs @@ -55,6 +55,7 @@ use tari_core::transactions::{ ContractAmendment, ContractDefinition, ContractUpdateProposal, + OutputFeatures, SideChainConsensus, SideChainFeatures, TransactionOutput, @@ -141,7 +142,13 @@ pub async fn send_tari( message: String, ) -> Result { wallet_transaction_service - .send_transaction(dest_pubkey, amount, fee_per_gram * uT, message) + .send_transaction( + dest_pubkey, + amount, + OutputFeatures::default(), + fee_per_gram * uT, + message, + ) .await .map_err(CommandError::TransactionServiceError) } @@ -205,7 +212,13 @@ pub async fn send_one_sided( message: String, ) -> Result { wallet_transaction_service - .send_one_sided_transaction(dest_pubkey, amount, fee_per_gram * uT, message) + .send_one_sided_transaction( + dest_pubkey, + amount, + OutputFeatures::default(), + fee_per_gram * uT, + message, + ) .await .map_err(CommandError::TransactionServiceError) } diff --git a/applications/tari_console_wallet/src/grpc/wallet_grpc_server.rs b/applications/tari_console_wallet/src/grpc/wallet_grpc_server.rs index 3eb41a80f0..8bc09b6235 100644 --- a/applications/tari_console_wallet/src/grpc/wallet_grpc_server.rs +++ b/applications/tari_console_wallet/src/grpc/wallet_grpc_server.rs @@ -458,7 +458,13 @@ impl wallet_server::Wallet for WalletGrpcServer { ( address, transaction_service - .send_transaction(pk, amount.into(), fee_per_gram.into(), message) + .send_transaction( + pk, + amount.into(), + OutputFeatures::default(), + fee_per_gram.into(), + message, + ) .await, ) }); @@ -467,7 +473,13 @@ impl wallet_server::Wallet for WalletGrpcServer { ( address, transaction_service - .send_one_sided_transaction(pk, amount.into(), fee_per_gram.into(), message) + .send_one_sided_transaction( + pk, + amount.into(), + OutputFeatures::default(), + fee_per_gram.into(), + message, + ) .await, ) }); @@ -857,8 +869,8 @@ impl wallet_server::Wallet for WalletGrpcServer { .map_err(|e| Status::internal(e.to_string()))?; let message = format!("Sidechain state checkpoint for {}", contract_id); - let _ = transaction_service - .submit_transaction(tx_id, transaction, 0.into(), message) + transaction_service + .submit_transaction(tx_id, transaction, 10.into(), message) .await .map_err(|e| Status::internal(e.to_string()))?; 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 e11c551d7e..63d84f8deb 100644 --- a/applications/tari_console_wallet/src/ui/state/app_state.rs +++ b/applications/tari_console_wallet/src/ui/state/app_state.rs @@ -45,6 +45,7 @@ use tari_comms::{ }; use tari_core::transactions::{ tari_amount::{uT, MicroTari}, + transaction_components::OutputFeatures, weight::TransactionWeight, }; use tari_crypto::ristretto::RistrettoPublicKey; @@ -295,13 +296,18 @@ impl AppState { Err(_) => EmojiId::str_to_pubkey(public_key.as_str()).map_err(|_| UiError::PublicKeyParseError)?, }; + let output_features = OutputFeatures { + unique_id, + parent_public_key, + ..Default::default() + }; + let fee_per_gram = fee_per_gram * uT; let tx_service_handle = inner.wallet.transaction_service.clone(); tokio::spawn(send_transaction_task( public_key, MicroTari::from(amount), - unique_id, - parent_public_key, + output_features, message, fee_per_gram, tx_service_handle, @@ -327,13 +333,18 @@ impl AppState { Err(_) => EmojiId::str_to_pubkey(public_key.as_str()).map_err(|_| UiError::PublicKeyParseError)?, }; + let output_features = OutputFeatures { + unique_id, + parent_public_key, + ..Default::default() + }; + let fee_per_gram = fee_per_gram * uT; let tx_service_handle = inner.wallet.transaction_service.clone(); tokio::spawn(send_one_sided_transaction_task( public_key, MicroTari::from(amount), - unique_id, - parent_public_key, + output_features, message, fee_per_gram, tx_service_handle, diff --git a/applications/tari_console_wallet/src/ui/state/tasks.rs b/applications/tari_console_wallet/src/ui/state/tasks.rs index 8dce160d5e..0a69ca97f5 100644 --- a/applications/tari_console_wallet/src/ui/state/tasks.rs +++ b/applications/tari_console_wallet/src/ui/state/tasks.rs @@ -20,9 +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_common_types::types::PublicKey; use tari_comms::types::CommsPublicKey; -use tari_core::transactions::tari_amount::MicroTari; +use tari_core::transactions::{tari_amount::MicroTari, transaction_components::OutputFeatures}; use tari_wallet::transaction_service::handle::{TransactionEvent, TransactionSendStatus, TransactionServiceHandle}; use tokio::sync::{broadcast, watch}; @@ -33,8 +32,7 @@ const LOG_TARGET: &str = "wallet::console_wallet::tasks "; pub async fn send_transaction_task( public_key: CommsPublicKey, amount: MicroTari, - unique_id: Option>, - parent_public_key: Option, + output_features: OutputFeatures, message: String, fee_per_gram: MicroTari, mut transaction_service_handle: TransactionServiceHandle, @@ -44,7 +42,7 @@ pub async fn send_transaction_task( let mut event_stream = transaction_service_handle.get_event_stream(); let mut send_status = TransactionSendStatus::default(); match transaction_service_handle - .send_transaction_or_token(public_key, amount, unique_id, parent_public_key, fee_per_gram, message) + .send_transaction(public_key, amount, output_features, fee_per_gram, message) .await { Err(e) => { @@ -102,8 +100,7 @@ pub async fn send_transaction_task( pub async fn send_one_sided_transaction_task( public_key: CommsPublicKey, amount: MicroTari, - unique_id: Option>, - parent_public_key: Option, + output_features: OutputFeatures, message: String, fee_per_gram: MicroTari, mut transaction_service_handle: TransactionServiceHandle, @@ -112,7 +109,7 @@ pub async fn send_one_sided_transaction_task( let _result = result_tx.send(UiTransactionSendStatus::Initiated); let mut event_stream = transaction_service_handle.get_event_stream(); match transaction_service_handle - .send_one_sided_transaction_or_token(public_key, amount, unique_id, parent_public_key, fee_per_gram, message) + .send_one_sided_transaction(public_key, amount, output_features, fee_per_gram, message) .await { Err(e) => { diff --git a/base_layer/core/src/proto/transaction.rs b/base_layer/core/src/proto/transaction.rs index fcaa457301..75d02a2d08 100644 --- a/base_layer/core/src/proto/transaction.rs +++ b/base_layer/core/src/proto/transaction.rs @@ -216,6 +216,7 @@ impl TryFrom for proto::types::TransactionInput { .map_err(|_| "Non-compact Transaction input should contain sender_offset_public_key".to_string())? .as_bytes() .to_vec(), + // Output hash is only used in compact form output_hash: Vec::new(), covenant: input .covenant() diff --git a/base_layer/core/src/transactions/transaction_components/side_chain/committee_signatures.rs b/base_layer/core/src/transactions/transaction_components/side_chain/committee_signatures.rs index 3cbbf0c4f2..96c6365c47 100644 --- a/base_layer/core/src/transactions/transaction_components/side_chain/committee_signatures.rs +++ b/base_layer/core/src/transactions/transaction_components/side_chain/committee_signatures.rs @@ -45,6 +45,13 @@ impl CommitteeSignatures { Self { signatures } } + pub fn empty() -> Self { + Self { + // Panic: vec is size 0 < 512 + signatures: vec![].try_into().unwrap(), + } + } + pub fn signatures(&self) -> Vec { self.signatures.to_vec() } diff --git a/base_layer/core/src/transactions/transaction_components/unblinded_output_builder.rs b/base_layer/core/src/transactions/transaction_components/unblinded_output_builder.rs index 3e8eb93d7d..865af8f821 100644 --- a/base_layer/core/src/transactions/transaction_components/unblinded_output_builder.rs +++ b/base_layer/core/src/transactions/transaction_components/unblinded_output_builder.rs @@ -183,6 +183,10 @@ impl UnblindedOutputBuilder { self.script_private_key = Some(script_private_key); self } + + pub fn covenant(&self) -> &Covenant { + &self.covenant + } } #[cfg(test)] diff --git a/base_layer/wallet/src/assets/asset_manager.rs b/base_layer/wallet/src/assets/asset_manager.rs index ef635d2753..6bdbfdb4be 100644 --- a/base_layer/wallet/src/assets/asset_manager.rs +++ b/base_layer/wallet/src/assets/asset_manager.rs @@ -46,6 +46,7 @@ use crate::{ database::{OutputManagerBackend, OutputManagerDatabase}, models::DbUnblindedOutput, }, + UtxoSelectionCriteria, }, }; @@ -122,7 +123,7 @@ impl AssetManager { debug!(target: LOG_TARGET, "Created output: {:?}", output); let (tx_id, transaction) = self .output_manager - .create_send_to_self_with_output(vec![output], ASSET_FPG.into(), None, None) + .create_send_to_self_with_output(vec![output], ASSET_FPG.into(), UtxoSelectionCriteria::default()) .await?; Ok((tx_id, transaction)) } @@ -153,7 +154,7 @@ impl AssetManager { let (tx_id, transaction) = self .output_manager - .create_send_to_self_with_output(outputs, ASSET_FPG.into(), None, None) + .create_send_to_self_with_output(outputs, ASSET_FPG.into(), UtxoSelectionCriteria::default()) .await?; Ok((tx_id, transaction)) } @@ -166,7 +167,7 @@ impl AssetManager { let output = self .output_manager .create_output_with_features( - 0.into(), + 10.into(), OutputFeatures::for_checkpoint( contract_id, merkle_root, @@ -203,15 +204,13 @@ impl AssetManager { // TODO: Fee is proportional to tx weight, so does not need to be different for contract // transactions - should be chosen by the user ASSET_FPG.into(), - // TODO: Spend previous checkpoint - None, - None, + UtxoSelectionCriteria::default(), ) .await?; Ok((tx_id, transaction)) } - pub async fn create_follow_on_asset_checkpoint( + pub async fn create_follow_on_contract_checkpoint( &mut self, contract_id: FixedHash, merkle_root: FixedHash, @@ -219,7 +218,7 @@ impl AssetManager { let output = self .output_manager .create_output_with_features( - 0.into(), + 10.into(), OutputFeatures::for_checkpoint( contract_id, merkle_root, @@ -254,15 +253,13 @@ impl AssetManager { .create_send_to_self_with_output( vec![output], ASSET_FPG.into(), - // TODO: Spend previous checkpoint - None, - None, + UtxoSelectionCriteria::for_contract(contract_id, OutputType::ContractCheckpoint), ) .await?; Ok((tx_id, transaction)) } - pub async fn create_constitution_definition( + pub async fn create_contract_constitution( &mut self, constitution_definition: &SideChainFeatures, ) -> Result<(TxId, Transaction), WalletError> { @@ -277,7 +274,7 @@ impl AssetManager { let (tx_id, transaction) = self .output_manager - .create_send_to_self_with_output(vec![output], ASSET_FPG.into(), None, None) + .create_send_to_self_with_output(vec![output], ASSET_FPG.into(), UtxoSelectionCriteria::default()) .await?; Ok((tx_id, transaction)) @@ -294,7 +291,7 @@ impl AssetManager { let (tx_id, transaction) = self .output_manager - .create_send_to_self_with_output(vec![output], ASSET_FPG.into(), None, None) + .create_send_to_self_with_output(vec![output], ASSET_FPG.into(), UtxoSelectionCriteria::default()) .await?; Ok((tx_id, transaction)) @@ -316,7 +313,7 @@ impl AssetManager { let (tx_id, transaction) = self .output_manager - .create_send_to_self_with_output(vec![output], ASSET_FPG.into(), None, None) + .create_send_to_self_with_output(vec![output], ASSET_FPG.into(), UtxoSelectionCriteria::default()) .await?; Ok((tx_id, transaction)) @@ -344,7 +341,7 @@ impl AssetManager { let (tx_id, transaction) = self .output_manager - .create_send_to_self_with_output(vec![output], ASSET_FPG.into(), None, None) + .create_send_to_self_with_output(vec![output], ASSET_FPG.into(), UtxoSelectionCriteria::default()) .await?; Ok((tx_id, transaction)) @@ -365,7 +362,7 @@ impl AssetManager { let (tx_id, transaction) = self .output_manager - .create_send_to_self_with_output(vec![output], ASSET_FPG.into(), None, None) + .create_send_to_self_with_output(vec![output], ASSET_FPG.into(), UtxoSelectionCriteria::default()) .await?; Ok((tx_id, transaction)) @@ -383,7 +380,7 @@ impl AssetManager { let (tx_id, transaction) = self .output_manager - .create_send_to_self_with_output(vec![output], ASSET_FPG.into(), None, None) + .create_send_to_self_with_output(vec![output], ASSET_FPG.into(), UtxoSelectionCriteria::default()) .await?; Ok((tx_id, transaction)) diff --git a/base_layer/wallet/src/assets/infrastructure/asset_manager_service.rs b/base_layer/wallet/src/assets/infrastructure/asset_manager_service.rs index 9f753f240c..eec7235f2b 100644 --- a/base_layer/wallet/src/assets/infrastructure/asset_manager_service.rs +++ b/base_layer/wallet/src/assets/infrastructure/asset_manager_service.rs @@ -152,7 +152,7 @@ impl AssetManagerService { } => { let (tx_id, transaction) = self .manager - .create_follow_on_asset_checkpoint(contract_id, merkle_root) + .create_follow_on_contract_checkpoint(contract_id, merkle_root) .await?; Ok(AssetManagerResponse::CreateFollowOnCheckpoint { transaction: Box::new(transaction), @@ -164,7 +164,7 @@ impl AssetManagerService { } => { let (tx_id, transaction) = self .manager - .create_constitution_definition(&constitution_definition) + .create_contract_constitution(&constitution_definition) .await?; Ok(AssetManagerResponse::CreateConstitutionDefinition { transaction: Box::new(transaction), diff --git a/base_layer/wallet/src/output_manager_service/error.rs b/base_layer/wallet/src/output_manager_service/error.rs index 0cb6ab98cf..adfee9bc0b 100644 --- a/base_layer/wallet/src/output_manager_service/error.rs +++ b/base_layer/wallet/src/output_manager_service/error.rs @@ -39,6 +39,7 @@ use crate::{ base_node_service::error::BaseNodeServiceError, error::WalletStorageError, key_manager_service::KeyManagerServiceError, + output_manager_service::UtxoSelectionCriteria, }; #[derive(Debug, Error)] @@ -115,8 +116,8 @@ pub enum OutputManagerError { MasterSeedMismatch, #[error("Private Key is not found in the current Key Chain")] KeyNotFoundInKeyChain, - #[error("Token with unique id not found")] - TokenUniqueIdNotFound, + #[error("No UTXOs selected as inputs for {criteria}")] + NoUtxosSelected { criteria: UtxoSelectionCriteria }, #[error("Connectivity error: {source}")] ConnectivityError { #[from] diff --git a/base_layer/wallet/src/output_manager_service/handle.rs b/base_layer/wallet/src/output_manager_service/handle.rs index a49209c517..325592cf56 100644 --- a/base_layer/wallet/src/output_manager_service/handle.rs +++ b/base_layer/wallet/src/output_manager_service/handle.rs @@ -57,6 +57,7 @@ use crate::output_manager_service::{ database::OutputBackendQuery, models::{KnownOneSidedPaymentScript, SpendingPriority}, }, + UtxoSelectionCriteria, }; /// API Request enum @@ -78,8 +79,8 @@ pub enum OutputManagerRequest { PrepareToSendTransaction { tx_id: TxId, amount: MicroTari, - unique_id: Option>, - parent_public_key: Option, + utxo_selection: UtxoSelectionCriteria, + output_features: OutputFeatures, fee_per_gram: MicroTari, lock_height: Option, message: String, @@ -89,8 +90,8 @@ pub enum OutputManagerRequest { CreatePayToSelfTransaction { tx_id: TxId, amount: MicroTari, - unique_id: Option>, - parent_public_key: Option, + utxo_selection: UtxoSelectionCriteria, + output_features: OutputFeatures, fee_per_gram: MicroTari, lock_height: Option, message: String, @@ -98,8 +99,7 @@ pub enum OutputManagerRequest { CreatePayToSelfWithOutputs { outputs: Vec, fee_per_gram: MicroTari, - spending_unique_id: Option>, - spending_parent_public_key: Option, + input_selection: UtxoSelectionCriteria, }, CancelTransaction(TxId), GetSpentOutputs, @@ -518,8 +518,8 @@ impl OutputManagerHandle { &mut self, tx_id: TxId, amount: MicroTari, - unique_id: Option>, - parent_public_key: Option, + utxo_selection: UtxoSelectionCriteria, + output_features: OutputFeatures, fee_per_gram: MicroTari, lock_height: Option, message: String, @@ -531,8 +531,8 @@ impl OutputManagerHandle { .call(OutputManagerRequest::PrepareToSendTransaction { tx_id, amount, - unique_id, - parent_public_key, + utxo_selection, + output_features, fee_per_gram, lock_height, message, @@ -759,16 +759,14 @@ impl OutputManagerHandle { &mut self, outputs: Vec, fee_per_gram: MicroTari, - spending_unique_id: Option>, - spending_parent_public_key: Option, + input_selection: UtxoSelectionCriteria, ) -> Result<(TxId, Transaction), OutputManagerError> { match self .handle .call(OutputManagerRequest::CreatePayToSelfWithOutputs { outputs, fee_per_gram, - spending_unique_id, - spending_parent_public_key, + input_selection, }) .await?? { @@ -781,8 +779,8 @@ impl OutputManagerHandle { &mut self, tx_id: TxId, amount: MicroTari, - unique_id: Option>, - parent_public_key: Option, + utxo_selection: UtxoSelectionCriteria, + output_features: OutputFeatures, fee_per_gram: MicroTari, lock_height: Option, message: String, @@ -792,11 +790,11 @@ impl OutputManagerHandle { .call(OutputManagerRequest::CreatePayToSelfTransaction { tx_id, amount, + utxo_selection, + output_features, fee_per_gram, lock_height, message, - unique_id, - parent_public_key, }) .await?? { diff --git a/base_layer/wallet/src/output_manager_service/service.rs b/base_layer/wallet/src/output_manager_service/service.rs index d95780cb8f..fc86a236d2 100644 --- a/base_layer/wallet/src/output_manager_service/service.rs +++ b/base_layer/wallet/src/output_manager_service/service.rs @@ -283,8 +283,8 @@ where OutputManagerRequest::PrepareToSendTransaction { tx_id, amount, - unique_id, - parent_public_key, + utxo_selection, + output_features, fee_per_gram, lock_height, message, @@ -294,11 +294,11 @@ where .prepare_transaction_to_send( tx_id, amount, - unique_id, - parent_public_key, + utxo_selection, fee_per_gram, lock_height, message, + output_features, script, covenant, ) @@ -307,8 +307,8 @@ where OutputManagerRequest::CreatePayToSelfTransaction { tx_id, amount, - unique_id, - parent_public_key, + utxo_selection, + output_features, fee_per_gram, lock_height, message, @@ -316,8 +316,8 @@ where .create_pay_to_self_transaction( tx_id, amount, - unique_id, - parent_public_key, + utxo_selection, + output_features, fee_per_gram, lock_height, message, @@ -414,16 +414,10 @@ where OutputManagerRequest::CreatePayToSelfWithOutputs { outputs, fee_per_gram, - spending_unique_id, - spending_parent_public_key, + input_selection, } => { let (tx_id, transaction) = self - .create_pay_to_self_containing_outputs( - outputs, - fee_per_gram, - spending_unique_id.as_ref(), - spending_parent_public_key.as_ref(), - ) + .create_pay_to_self_containing_outputs(outputs, fee_per_gram, input_selection) .await?; Ok(OutputManagerResponse::CreatePayToSelfWithOutputs { transaction: Box::new(transaction), @@ -844,56 +838,35 @@ where &mut self, tx_id: TxId, amount: MicroTari, - unique_id: Option>, - parent_public_key: Option, + utxo_selection: UtxoSelectionCriteria, fee_per_gram: MicroTari, lock_height: Option, message: String, + recipient_output_features: OutputFeatures, recipient_script: TariScript, recipient_covenant: Covenant, ) -> Result { debug!( target: LOG_TARGET, - "Preparing to send transaction. Amount: {}. Unique id : {:?}. Fee per gram: {}. ", + "Preparing to send transaction. Amount: {}. UTXO Selection: {}. Fee per gram: {}. ", amount, - unique_id, + utxo_selection, fee_per_gram, ); - let output_features_estimate = OutputFeatures::default(); let metadata_byte_size = self .resources .consensus_constants .transaction_weight() .round_up_metadata_size( - output_features_estimate.consensus_encode_exact_size() + + recipient_output_features.consensus_encode_exact_size() + recipient_script.consensus_encode_exact_size() + recipient_covenant.consensus_encode_exact_size(), ); - // TODO: Some(unique_id) means select the unique_id AND use the features of UTXOs with the unique_id. These - // should be able to be specified independently. - let selection_criteria = match unique_id.as_ref() { - Some(unique_id) => UtxoSelectionCriteria::for_token(unique_id.clone(), parent_public_key.as_ref().cloned()), - None => UtxoSelectionCriteria::default(), - }; - let input_selection = self - .select_utxos(amount, fee_per_gram, 1, metadata_byte_size, selection_criteria) + .select_utxos(amount, fee_per_gram, 1, metadata_byte_size, utxo_selection) .await?; - // TODO: improve this logic #LOGGED - let recipient_output_features = match unique_id { - Some(ref _unique_id) => match input_selection - .utxos - .iter() - .find(|output| output.unblinded_output.features.unique_id.is_some()) - { - Some(output) => output.unblinded_output.features.clone(), - _ => OutputFeatures::default(), - }, - _ => OutputFeatures::default(), - }; - let offset = PrivateKey::random(&mut OsRng); let nonce = PrivateKey::random(&mut OsRng); @@ -1065,8 +1038,7 @@ where &mut self, outputs: Vec, fee_per_gram: MicroTari, - spending_unique_id: Option<&Vec>, - spending_parent_public_key: Option<&PublicKey>, + selection_criteria: UtxoSelectionCriteria, ) -> Result<(TxId, Transaction), OutputManagerError> { let total_value = MicroTari(outputs.iter().fold(0u64, |running, out| running + out.value.as_u64())); let nop_script = script![Nop]; @@ -1075,6 +1047,7 @@ where total + weighting.round_up_metadata_size({ output.features.consensus_encode_exact_size() + + output.covenant().consensus_encode_exact_size() + output .script .as_ref() @@ -1083,11 +1056,6 @@ where }) }); - let selection_criteria = match spending_unique_id { - Some(unique_id) => UtxoSelectionCriteria::for_token(unique_id.clone(), spending_parent_public_key.cloned()), - None => UtxoSelectionCriteria::default(), - }; - let input_selection = self .select_utxos( total_value, @@ -1229,35 +1197,26 @@ where &mut self, tx_id: TxId, amount: MicroTari, - unique_id: Option>, - parent_public_key: Option, + utxo_selection: UtxoSelectionCriteria, + mut output_features: OutputFeatures, fee_per_gram: MicroTari, lock_height: Option, message: String, ) -> Result<(MicroTari, Transaction), OutputManagerError> { let script = script!(Nop); let covenant = Covenant::default(); - let output_features_estimate = OutputFeatures { - unique_id: unique_id.clone(), - ..Default::default() - }; let metadata_byte_size = self .resources .consensus_constants .transaction_weight() .round_up_metadata_size( - output_features_estimate.consensus_encode_exact_size() + + output_features.consensus_encode_exact_size() + script.consensus_encode_exact_size() + covenant.consensus_encode_exact_size(), ); - let selection_criteria = match unique_id { - Some(ref unique_id) => UtxoSelectionCriteria::for_token(unique_id.clone(), parent_public_key), - None => UtxoSelectionCriteria::default(), - }; - let input_selection = self - .select_utxos(amount, fee_per_gram, 1, metadata_byte_size, selection_criteria) + .select_utxos(amount, fee_per_gram, 1, metadata_byte_size, utxo_selection) .await?; let offset = PrivateKey::random(&mut OsRng); @@ -1286,11 +1245,7 @@ where let (spending_key, script_private_key) = self.get_spend_and_script_keys().await?; let recovery_byte = self.calculate_recovery_byte(spending_key.clone(), amount.as_u64(), true)?; - let output_features = OutputFeatures { - recovery_byte, - unique_id: unique_id.clone(), - ..Default::default() - }; + output_features.set_recovery_byte(recovery_byte); let encrypted_value = EncryptedValue::todo_encrypt_from(amount); let metadata_signature = TransactionOutput::create_final_metadata_signature( TransactionOutputVersion::get_current_version(), @@ -1412,6 +1367,7 @@ where /// Select which unspent transaction outputs to use to send a transaction of the specified amount. Use the specified /// selection strategy to choose the outputs. It also determines if a change output is required. + #[allow(clippy::too_many_lines)] async fn select_utxos( &mut self, amount: MicroTari, @@ -1432,9 +1388,6 @@ where ); 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(); // Attempt to get the chain tip height @@ -1445,20 +1398,59 @@ where "select_utxos selection criteria: {}", selection_criteria ); let tip_height = chain_metadata.as_ref().map(|m| m.height_of_longest_chain()); - let uo = self + let mut uo = self .resources .db - .fetch_unspent_outputs_for_spending(selection_criteria, amount, tip_height)?; - trace!(target: LOG_TARGET, "We found {} UTXOs to select from", uo.len()); + .fetch_unspent_outputs_for_spending(&selection_criteria, amount, tip_height)?; + + // For non-standard queries, we want to ensure that the intended UTXOs are selected + if !selection_criteria.filter.is_standard() && uo.is_empty() { + return Err(OutputManagerError::NoUtxosSelected { + criteria: selection_criteria, + }); + } // Assumes that default Outputfeatures are used for change utxo let output_features_estimate = OutputFeatures::default(); let default_metadata_size = fee_calc.weighting().round_up_metadata_size( - output_features_estimate.consensus_encode_exact_size() + script![Nop].consensus_encode_exact_size(), + output_features_estimate.consensus_encode_exact_size() + + Covenant::new().consensus_encode_exact_size() + + script![Nop].consensus_encode_exact_size(), ); + + if selection_criteria.filter.is_contract_output() { + let fee_with_change = fee_calc.calculate( + fee_per_gram, + 1, + uo.len(), + num_outputs + 1, + output_metadata_byte_size + default_metadata_size, + ); + + // If the initial selection was not able to select enough UTXOs, fill in the difference with standard UTXOs + let total_utxo_value = uo.iter().map(|uo| uo.unblinded_output.value).sum::(); + if total_utxo_value < amount + fee_with_change { + let mut query = UtxoSelectionCriteria::smallest_first(); + query.excluding = uo.iter().map(|o| o.commitment.clone()).collect(); + let additional = self.resources.db.fetch_unspent_outputs_for_spending( + &query, + amount + fee_with_change - total_utxo_value, + tip_height, + )?; + uo.extend(additional); + } + } + + trace!(target: LOG_TARGET, "We found {} UTXOs to select from", uo.len()); + let mut requires_change_output = false; + let mut utxos_total_value = MicroTari::from(0); + let mut fee_without_change = MicroTari::from(0); + let mut fee_with_change = MicroTari::from(0); for o in uo { utxos_total_value += o.unblinded_output.value; + + error!(target: LOG_TARGET, "-- utxos_total_value = {:?}", utxos_total_value); utxos.push(o); // The assumption here is that the only output will be the payment output and change if required fee_without_change = @@ -1473,6 +1465,8 @@ where num_outputs + 1, output_metadata_byte_size + default_metadata_size, ); + + error!(target: LOG_TARGET, "-- amt+fee = {:?} {}", amount, fee_with_change); if utxos_total_value > amount + fee_with_change { requires_change_output = true; break; diff --git a/base_layer/wallet/src/output_manager_service/storage/database/backend.rs b/base_layer/wallet/src/output_manager_service/storage/database/backend.rs index 0c925a6e01..70bd6e3ada 100644 --- a/base_layer/wallet/src/output_manager_service/storage/database/backend.rs +++ b/base_layer/wallet/src/output_manager_service/storage/database/backend.rs @@ -110,7 +110,7 @@ pub trait OutputManagerBackend: Send + Sync + Clone { fn add_unvalidated_output(&self, output: DbUnblindedOutput, tx_id: TxId) -> Result<(), OutputManagerStorageError>; fn fetch_unspent_outputs_for_spending( &self, - selection_criteria: UtxoSelectionCriteria, + selection_criteria: &UtxoSelectionCriteria, amount: u64, current_tip_height: Option, ) -> Result, OutputManagerStorageError>; diff --git a/base_layer/wallet/src/output_manager_service/storage/database/mod.rs b/base_layer/wallet/src/output_manager_service/storage/database/mod.rs index a3586d204f..ed37283cd7 100644 --- a/base_layer/wallet/src/output_manager_service/storage/database/mod.rs +++ b/base_layer/wallet/src/output_manager_service/storage/database/mod.rs @@ -251,7 +251,7 @@ where T: OutputManagerBackend + 'static /// Retrieves UTXOs than can be spent, sorted by priority, then value from smallest to largest. pub fn fetch_unspent_outputs_for_spending( &self, - selection_criteria: UtxoSelectionCriteria, + selection_criteria: &UtxoSelectionCriteria, amount: MicroTari, tip_height: Option, ) -> Result, OutputManagerStorageError> { diff --git a/base_layer/wallet/src/output_manager_service/storage/sqlite_db/mod.rs b/base_layer/wallet/src/output_manager_service/storage/sqlite_db/mod.rs index a90351314e..a74cd432c6 100644 --- a/base_layer/wallet/src/output_manager_service/storage/sqlite_db/mod.rs +++ b/base_layer/wallet/src/output_manager_service/storage/sqlite_db/mod.rs @@ -1192,7 +1192,7 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { /// Retrieves UTXOs than can be spent, sorted by priority, then value from smallest to largest. fn fetch_unspent_outputs_for_spending( &self, - selection_criteria: UtxoSelectionCriteria, + selection_criteria: &UtxoSelectionCriteria, amount: u64, tip_height: Option, ) -> Result, OutputManagerStorageError> { diff --git a/base_layer/wallet/src/output_manager_service/storage/sqlite_db/output_sql.rs b/base_layer/wallet/src/output_manager_service/storage/sqlite_db/output_sql.rs index 47ad656505..046cbdf3d1 100644 --- a/base_layer/wallet/src/output_manager_service/storage/sqlite_db/output_sql.rs +++ b/base_layer/wallet/src/output_manager_service/storage/sqlite_db/output_sql.rs @@ -28,13 +28,13 @@ use diesel::{prelude::*, sql_query, SqliteConnection}; use log::*; use tari_common_types::{ transaction::TxId, - types::{ComSignature, Commitment, FixedHash, PrivateKey, PublicKey}, + types::{ComSignature, Commitment, PrivateKey, PublicKey}, }; use tari_core::{ covenants::Covenant, transactions::{ tari_amount::MicroTari, - transaction_components::{EncryptedValue, OutputFeatures, OutputType, SideChainFeatures, UnblindedOutput}, + transaction_components::{EncryptedValue, OutputFeatures, OutputType, UnblindedOutput}, CryptoFactories, }, }; @@ -183,7 +183,7 @@ impl OutputSql { /// Retrieves UTXOs than can be spent, sorted by priority, then value from smallest to largest. #[allow(clippy::cast_sign_loss)] pub fn fetch_unspent_outputs_for_spending( - selection_criteria: UtxoSelectionCriteria, + selection_criteria: &UtxoSelectionCriteria, amount: u64, tip_height: Option, conn: &SqliteConnection, @@ -193,7 +193,7 @@ impl OutputSql { .filter(outputs::status.eq(OutputStatus::Unspent as i32)) .order_by(outputs::spending_priority.desc()); - match selection_criteria.filter { + match &selection_criteria.filter { UtxoSelectionFilter::Standard => { query = query.filter( outputs::output_type @@ -209,11 +209,23 @@ impl OutputSql { .filter(outputs::features_unique_id.eq(unique_id)) .filter(outputs::features_parent_public_key.eq(parent_public_key.as_ref().map(|pk| pk.to_vec()))); }, + UtxoSelectionFilter::ContractOutput { + contract_id, + output_type, + } => { + query = query + .filter(outputs::contract_id.eq(contract_id.as_slice())) + .filter(outputs::output_type.eq(i32::from(output_type.as_byte()))); + }, UtxoSelectionFilter::SpecificOutputs { outputs } => { - query = query.filter(outputs::hash.eq_any(outputs.into_iter().map(|o| o.hash))) + query = query.filter(outputs::hash.eq_any(outputs.iter().map(|o| &o.hash))) }, } + for exclude in &selection_criteria.excluding { + query = query.filter(outputs::commitment.ne(exclude.as_bytes())); + } + match selection_criteria.ordering { UtxoSelectionOrdering::SmallestFirst => { query = query.then_order_by(outputs::value.asc()); @@ -250,6 +262,7 @@ impl OutputSql { } }, }; + match tip_height { Some(tip_height) => { let i64_tip_height = i64::try_from(tip_height).unwrap_or(i64::MAX); @@ -263,6 +276,11 @@ impl OutputSql { query = query.then_order_by(outputs::maturity.asc()); }, } + // debug!( + // target: LOG_TARGET, + // "Executing UTXO select query: {}", + // diesel::debug_query(&query) + // ); Ok(query.load(conn)?) } @@ -594,38 +612,11 @@ impl TryFrom for DbUnblindedOutput { #[allow(clippy::too_many_lines)] fn try_from(o: OutputSql) -> Result { - let mut features: OutputFeatures = + let features: OutputFeatures = serde_json::from_str(&o.features_json).map_err(|s| OutputManagerStorageError::ConversionError { reason: format!("Could not convert json into OutputFeatures:{}", s), })?; - let output_type = o - .output_type - .try_into() - .map_err(|_| OutputManagerStorageError::ConversionError { - reason: format!("Unable to convert flag bits with value {} to OutputType", o.output_type), - })?; - features.output_type = - OutputType::from_byte(output_type).ok_or(OutputManagerStorageError::ConversionError { - reason: "Flags could not be converted from bits".to_string(), - })?; - features.maturity = o.maturity as u64; - features.metadata = o.metadata.unwrap_or_default(); - features.sidechain_features = o - .features_unique_id - .as_ref() - .map(|v| FixedHash::try_from(v.as_slice())) - .transpose() - .map_err(|_| OutputManagerStorageError::ConversionError { - reason: "Invalid contract ID".to_string(), - })? - // TODO: Add side chain features to wallet db - .map(SideChainFeatures::new); - features.parent_public_key = o - .features_parent_public_key - .map(|p| PublicKey::from_bytes(&p)) - .transpose()?; - features.recovery_byte = u8::try_from(o.recovery_byte).unwrap(); let encrypted_value = EncryptedValue::from_bytes(&o.encrypted_value)?; let unblinded_output = UnblindedOutput::new_current_version( MicroTari::from(o.value as u64), diff --git a/base_layer/wallet/src/transaction_service/handle.rs b/base_layer/wallet/src/transaction_service/handle.rs index 141f094c85..71cd6ebab0 100644 --- a/base_layer/wallet/src/transaction_service/handle.rs +++ b/base_layer/wallet/src/transaction_service/handle.rs @@ -38,7 +38,7 @@ use tari_core::{ proto, transactions::{ tari_amount::MicroTari, - transaction_components::{Transaction, TransactionOutput}, + transaction_components::{OutputFeatures, Transaction, TransactionOutput}, }, }; use tari_service_framework::reply_channel::SenderService; @@ -75,16 +75,14 @@ pub enum TransactionServiceRequest { SendTransaction { dest_pubkey: CommsPublicKey, amount: MicroTari, - unique_id: Option>, - parent_public_key: Option, + output_features: OutputFeatures, fee_per_gram: MicroTari, message: String, }, SendOneSidedTransaction { dest_pubkey: CommsPublicKey, amount: MicroTari, - unique_id: Option>, - parent_public_key: Option, + output_features: OutputFeatures, fee_per_gram: MicroTari, message: String, }, @@ -398,6 +396,7 @@ impl TransactionServiceHandle { &mut self, dest_pubkey: CommsPublicKey, amount: MicroTari, + output_features: OutputFeatures, fee_per_gram: MicroTari, message: String, ) -> Result { @@ -406,34 +405,7 @@ impl TransactionServiceHandle { .call(TransactionServiceRequest::SendTransaction { dest_pubkey, amount, - unique_id: None, - parent_public_key: None, - fee_per_gram, - message, - }) - .await?? - { - TransactionServiceResponse::TransactionSent(tx_id) => Ok(tx_id), - _ => Err(TransactionServiceError::UnexpectedApiResponse), - } - } - - pub async fn send_transaction_or_token( - &mut self, - dest_pubkey: CommsPublicKey, - amount: MicroTari, - unique_id: Option>, - parent_public_key: Option, - fee_per_gram: MicroTari, - message: String, - ) -> Result { - match self - .handle - .call(TransactionServiceRequest::SendTransaction { - dest_pubkey, - amount, - unique_id, - parent_public_key, + output_features, fee_per_gram, message, }) @@ -448,6 +420,7 @@ impl TransactionServiceHandle { &mut self, dest_pubkey: CommsPublicKey, amount: MicroTari, + output_features: OutputFeatures, fee_per_gram: MicroTari, message: String, ) -> Result { @@ -456,34 +429,7 @@ impl TransactionServiceHandle { .call(TransactionServiceRequest::SendOneSidedTransaction { dest_pubkey, amount, - unique_id: None, - parent_public_key: None, - fee_per_gram, - message, - }) - .await?? - { - TransactionServiceResponse::TransactionSent(tx_id) => Ok(tx_id), - _ => Err(TransactionServiceError::UnexpectedApiResponse), - } - } - - pub async fn send_one_sided_transaction_or_token( - &mut self, - dest_pubkey: CommsPublicKey, - amount: MicroTari, - unique_id: Option>, - parent_public_key: Option, - fee_per_gram: MicroTari, - message: String, - ) -> Result { - match self - .handle - .call(TransactionServiceRequest::SendOneSidedTransaction { - dest_pubkey, - amount, - unique_id, - parent_public_key, + output_features, fee_per_gram, message, }) diff --git a/base_layer/wallet/src/transaction_service/protocols/transaction_send_protocol.rs b/base_layer/wallet/src/transaction_service/protocols/transaction_send_protocol.rs index 35badf317a..556df671ad 100644 --- a/base_layer/wallet/src/transaction_service/protocols/transaction_send_protocol.rs +++ b/base_layer/wallet/src/transaction_service/protocols/transaction_send_protocol.rs @@ -27,7 +27,7 @@ use futures::FutureExt; use log::*; use tari_common_types::{ transaction::{TransactionDirection, TransactionStatus, TxId}, - types::{HashOutput, PublicKey}, + types::HashOutput, }; use tari_comms::{peer_manager::NodeId, types::CommsPublicKey}; use tari_comms_dht::{ @@ -38,7 +38,7 @@ use tari_core::{ covenants::Covenant, transactions::{ tari_amount::MicroTari, - transaction_components::KernelFeatures, + transaction_components::{KernelFeatures, OutputFeatures}, transaction_protocol::{ proto::protocol as proto, recipient::RecipientSignedMessage, @@ -56,6 +56,7 @@ use tokio::{ use crate::{ connectivity_service::WalletConnectivityInterface, + output_manager_service::UtxoSelectionCriteria, transaction_service::{ config::TransactionRoutingMechanism, error::{TransactionServiceError, TransactionServiceProtocolError}, @@ -87,8 +88,6 @@ pub struct TransactionSendProtocol { id: TxId, dest_pubkey: CommsPublicKey, amount: MicroTari, - unique_id: Option>, - parent_public_key: Option, fee_per_gram: MicroTari, message: String, service_request_reply_channel: Option>>, @@ -113,8 +112,6 @@ where cancellation_receiver: oneshot::Receiver<()>, dest_pubkey: CommsPublicKey, amount: MicroTari, - unique_id: Option>, - parent_public_key: Option, fee_per_gram: MicroTari, message: String, service_request_reply_channel: Option< @@ -132,8 +129,6 @@ where cancellation_receiver: Some(cancellation_receiver), dest_pubkey, amount, - unique_id, - parent_public_key, fee_per_gram, message, service_request_reply_channel, @@ -223,8 +218,8 @@ where .prepare_transaction_to_send( self.id, self.amount, - self.unique_id.clone(), - self.parent_public_key.clone(), + UtxoSelectionCriteria::default(), + OutputFeatures::default(), self.fee_per_gram, None, self.message.clone(), diff --git a/base_layer/wallet/src/transaction_service/service.rs b/base_layer/wallet/src/transaction_service/service.rs index 53ed6bf64f..cd1d6954ce 100644 --- a/base_layer/wallet/src/transaction_service/service.rs +++ b/base_layer/wallet/src/transaction_service/service.rs @@ -45,7 +45,14 @@ use tari_core::{ proto::base_node as base_node_proto, transactions::{ tari_amount::MicroTari, - transaction_components::{EncryptedValue, KernelFeatures, Transaction, TransactionOutput, UnblindedOutput}, + transaction_components::{ + EncryptedValue, + KernelFeatures, + OutputFeatures, + Transaction, + TransactionOutput, + UnblindedOutput, + }, transaction_protocol::{ proto::protocol as proto, recipient::RecipientSignedMessage, @@ -75,6 +82,7 @@ use crate::{ output_manager_service::{ handle::{OutputManagerEvent, OutputManagerHandle}, storage::models::SpendingPriority, + UtxoSelectionCriteria, }, storage::database::{WalletBackend, WalletDatabase}, transaction_service::{ @@ -562,8 +570,7 @@ where TransactionServiceRequest::SendTransaction { dest_pubkey, amount, - unique_id, - parent_public_key, + output_features, fee_per_gram, message, } => { @@ -571,8 +578,7 @@ where self.send_transaction( dest_pubkey, amount, - unique_id, - parent_public_key, + output_features, fee_per_gram, message, send_transaction_join_handles, @@ -585,16 +591,14 @@ where TransactionServiceRequest::SendOneSidedTransaction { dest_pubkey, amount, - unique_id, - parent_public_key, + output_features, fee_per_gram, message, } => self .send_one_sided_transaction( dest_pubkey, amount, - unique_id, - parent_public_key, + output_features, fee_per_gram, message, transaction_broadcast_join_handles, @@ -839,7 +843,7 @@ where } } - /// Sends a new transaction to a recipient + /// Sends a new transaction to a single recipient /// # Arguments /// 'dest_pubkey': The Comms pubkey of the recipient node /// 'amount': The amount of Tari to send to the recipient @@ -848,8 +852,7 @@ where &mut self, dest_pubkey: CommsPublicKey, amount: MicroTari, - unique_id: Option>, - parent_public_key: Option, + output_features: OutputFeatures, fee_per_gram: MicroTari, message: String, join_handles: &mut FuturesUnordered< @@ -874,8 +877,9 @@ where .create_pay_to_self_transaction( tx_id, amount, - unique_id.clone(), - parent_public_key.clone(), + // TODO: allow customization of selected inputs and outputs + UtxoSelectionCriteria::default(), + output_features, fee_per_gram, None, message.clone(), @@ -929,8 +933,6 @@ where cancellation_receiver, dest_pubkey, amount, - unique_id, - parent_public_key, fee_per_gram, message, Some(reply_channel), @@ -987,8 +989,8 @@ where .prepare_transaction_to_send( tx_id, amount, - None, - None, + UtxoSelectionCriteria::default(), + OutputFeatures::default(), fee_per_gram, None, message.clone(), @@ -1129,8 +1131,7 @@ where &mut self, dest_pubkey: CommsPublicKey, amount: MicroTari, - unique_id: Option>, - parent_public_key: Option, + output_features: OutputFeatures, fee_per_gram: MicroTari, message: String, transaction_broadcast_join_handles: &mut FuturesUnordered< @@ -1152,8 +1153,8 @@ where .prepare_transaction_to_send( tx_id, amount, - unique_id.clone(), - parent_public_key.clone(), + UtxoSelectionCriteria::default(), + output_features, fee_per_gram, None, message.clone(), @@ -1569,8 +1570,6 @@ where cancellation_receiver, tx.destination_public_key, tx.amount, - None, - None, tx.fee, tx.message, None, diff --git a/base_layer/wallet/tests/output_manager_service_tests/service.rs b/base_layer/wallet/tests/output_manager_service_tests/service.rs index 87c4c9ec68..9a90cf01ce 100644 --- a/base_layer/wallet/tests/output_manager_service_tests/service.rs +++ b/base_layer/wallet/tests/output_manager_service_tests/service.rs @@ -24,7 +24,7 @@ use std::{collections::HashMap, sync::Arc, time::Duration}; use rand::{rngs::OsRng, Rng, RngCore}; use tari_common_types::{ transaction::TxId, - types::{ComSignature, PrivateKey, PublicKey}, + types::{ComSignature, FixedHash, PrivateKey, PublicKey}, }; use tari_comms::{ peer_manager::{NodeIdentity, PeerFeatures}, @@ -41,7 +41,14 @@ use tari_core::{ fee::Fee, tari_amount::{uT, MicroTari}, test_helpers::{create_unblinded_output, TestParams as TestParamsHelpers}, - transaction_components::{EncryptedValue, OutputFeatures, OutputType, TransactionOutput, UnblindedOutput}, + transaction_components::{ + CommitteeSignatures, + EncryptedValue, + OutputFeatures, + OutputType, + TransactionOutput, + UnblindedOutput, + }, transaction_protocol::{sender::TransactionSenderMessage, RewindData}, weight::TransactionWeight, CryptoFactories, @@ -86,6 +93,7 @@ use tari_wallet::{ sqlite_db::OutputManagerSqliteDatabase, OutputStatus, }, + UtxoSelectionCriteria, }, test_utils::create_consensus_constants, transaction_service::handle::TransactionServiceHandle, @@ -437,8 +445,8 @@ async fn test_utxo_selection_no_chain_metadata() { .prepare_transaction_to_send( TxId::new_random(), amount, - None, - None, + UtxoSelectionCriteria::default(), + OutputFeatures::default(), fee_per_gram, None, "".to_string(), @@ -470,11 +478,11 @@ async fn test_utxo_selection_no_chain_metadata() { .prepare_transaction_to_send( TxId::new_random(), amount, - None, - None, + UtxoSelectionCriteria::default(), + OutputFeatures::default(), fee_per_gram, None, - "".to_string(), + String::new(), script!(Nop), Covenant::default(), ) @@ -551,8 +559,8 @@ async fn test_utxo_selection_with_chain_metadata() { .prepare_transaction_to_send( TxId::new_random(), amount, - None, - None, + UtxoSelectionCriteria::default(), + OutputFeatures::default(), fee_per_gram, None, "".to_string(), @@ -613,8 +621,8 @@ async fn test_utxo_selection_with_chain_metadata() { .prepare_transaction_to_send( TxId::new_random(), amount, - None, - None, + UtxoSelectionCriteria::default(), + OutputFeatures::default(), fee_per_gram, None, "".to_string(), @@ -640,8 +648,8 @@ async fn test_utxo_selection_with_chain_metadata() { .prepare_transaction_to_send( TxId::new_random(), 6 * amount, - None, - None, + UtxoSelectionCriteria::default(), + OutputFeatures::default(), fee_per_gram, None, "".to_string(), @@ -713,8 +721,8 @@ async fn test_utxo_selection_with_tx_priority() { .prepare_transaction_to_send( TxId::new_random(), MicroTari::from(1000), - None, - None, + UtxoSelectionCriteria::default(), + OutputFeatures::default(), fee_per_gram, None, "".to_string(), @@ -732,6 +740,77 @@ async fn test_utxo_selection_with_tx_priority() { assert_ne!(utxos[0].features.output_type, OutputType::Coinbase); } +#[tokio::test] +async fn utxo_selection_for_contract_checkpoint() { + let factories = CryptoFactories::default(); + let (connection, _tempdir) = get_temp_sqlite_database_connection(); + let contract_id = FixedHash::hash_bytes(b"test_utxo_selection_for_contract_checkpoint"); + + let server_node_identity = build_node_identity(PeerFeatures::COMMUNICATION_NODE); + // setup with chain metadata at a height of 6 + let (mut oms, _shutdown, _, _, _) = setup_oms_with_bn_state( + OutputManagerSqliteDatabase::new(connection, None), + Some(6), + server_node_identity, + ) + .await; + + let amount = MicroTari::from(2000); + let fee_per_gram = MicroTari::from(2); + + // we create two outputs, one as coinbase-high priority one as normal so we can track them + let (_, uo) = make_input_with_features( + &mut OsRng.clone(), + amount, + &factories.commitment, + Some(OutputFeatures::for_checkpoint( + contract_id, + FixedHash::zero(), + CommitteeSignatures::empty(), + )), + oms.clone(), + ) + .await; + oms.add_rewindable_output(uo, None, None).await.unwrap(); + let (_, uo) = make_input_with_features( + &mut OsRng.clone(), + amount, + &factories.commitment, + Some(OutputFeatures { + maturity: 1, + ..Default::default() + }), + oms.clone(), + ) + .await; + oms.add_rewindable_output(uo, None, None).await.unwrap(); + + let utxos = oms.get_unspent_outputs().await.unwrap(); + assert_eq!(utxos.len(), 2); + + // test transactions + let stp = oms + .prepare_transaction_to_send( + TxId::new_random(), + // Spend more than the selected contract output, this will cause the other UTXO to be included + MicroTari::from(2500), + UtxoSelectionCriteria::for_contract(contract_id, OutputType::ContractCheckpoint), + OutputFeatures::for_checkpoint(contract_id, FixedHash::zero(), CommitteeSignatures::empty()), + fee_per_gram, + None, + String::new(), + script!(Nop), + Covenant::default(), + ) + .await + .unwrap(); + assert!(stp.get_tx_id().is_ok()); + + // test that the utxo with the lowest priority was left + let utxos = oms.get_unspent_outputs().await.unwrap(); + assert_eq!(utxos.len(), 0); +} + #[tokio::test] async fn send_not_enough_funds() { let factories = CryptoFactories::default(); @@ -758,8 +837,8 @@ async fn send_not_enough_funds() { .prepare_transaction_to_send( TxId::new_random(), MicroTari::from(num_outputs * 2000), - None, - None, + UtxoSelectionCriteria::default(), + OutputFeatures::default(), MicroTari::from(4), None, "".to_string(), @@ -817,8 +896,8 @@ async fn send_no_change() { .prepare_transaction_to_send( TxId::new_random(), MicroTari::from(value1 + value2) - fee_without_change, - None, - None, + UtxoSelectionCriteria::default(), + OutputFeatures::default(), fee_per_gram, None, "".to_string(), @@ -838,6 +917,7 @@ async fn send_no_change() { MicroTari::from(0) ); } + #[tokio::test] async fn send_not_enough_for_change() { let (connection, _tempdir) = get_temp_sqlite_database_connection(); @@ -881,8 +961,8 @@ async fn send_not_enough_for_change() { .prepare_transaction_to_send( TxId::new_random(), value1 + value2 + uT - fee_without_change, - None, - None, + UtxoSelectionCriteria::default(), + OutputFeatures::default(), fee_per_gram, None, "".to_string(), @@ -922,8 +1002,8 @@ async fn cancel_transaction() { .prepare_transaction_to_send( TxId::new_random(), MicroTari::from(1000), - None, - None, + UtxoSelectionCriteria::default(), + OutputFeatures::default(), MicroTari::from(4), None, "".to_string(), @@ -1015,8 +1095,8 @@ async fn test_get_balance() { .prepare_transaction_to_send( TxId::new_random(), send_value, - None, - None, + UtxoSelectionCriteria::default(), + OutputFeatures::default(), MicroTari::from(4), None, "".to_string(), @@ -1071,8 +1151,8 @@ async fn sending_transaction_persisted_while_offline() { .prepare_transaction_to_send( TxId::new_random(), MicroTari::from(1000), - None, - None, + UtxoSelectionCriteria::default(), + OutputFeatures::default(), MicroTari::from(4), None, "".to_string(), @@ -1102,8 +1182,8 @@ async fn sending_transaction_persisted_while_offline() { .prepare_transaction_to_send( TxId::new_random(), MicroTari::from(1000), - None, - None, + UtxoSelectionCriteria::default(), + OutputFeatures::default(), MicroTari::from(4), None, "".to_string(), @@ -1409,8 +1489,8 @@ async fn test_txo_validation() { .prepare_transaction_to_send( 4u64.into(), MicroTari::from(900_000), - None, - None, + UtxoSelectionCriteria::default(), + OutputFeatures::default(), MicroTari::from(10), None, "".to_string(), diff --git a/base_layer/wallet/tests/transaction_service_tests/service.rs b/base_layer/wallet/tests/transaction_service_tests/service.rs index b69477231c..4b6edcb385 100644 --- a/base_layer/wallet/tests/transaction_service_tests/service.rs +++ b/base_layer/wallet/tests/transaction_service_tests/service.rs @@ -119,6 +119,7 @@ use tari_wallet::{ sqlite_db::OutputManagerSqliteDatabase, }, OutputManagerServiceInitializer, + UtxoSelectionCriteria, }, storage::{ database::WalletDatabase, @@ -546,6 +547,7 @@ fn manage_single_transaction() { .block_on(alice_ts.send_transaction( bob_node_identity.public_key().clone(), value, + OutputFeatures::default(), MicroTari::from(4), "".to_string() )) @@ -557,6 +559,7 @@ fn manage_single_transaction() { .block_on(alice_ts.send_transaction( bob_node_identity.public_key().clone(), value, + OutputFeatures::default(), MicroTari::from(4), message, )) @@ -681,6 +684,7 @@ fn single_transaction_to_self() { .send_transaction( alice_node_identity.public_key().clone(), value, + OutputFeatures::default(), 20.into(), message.clone(), ) @@ -772,6 +776,7 @@ fn send_one_sided_transaction_to_other() { .send_one_sided_transaction( bob_node_identity.public_key().clone(), value, + OutputFeatures::default(), 20.into(), message.clone(), ) @@ -911,6 +916,7 @@ fn recover_one_sided_transaction() { .send_one_sided_transaction( bob_node_identity.public_key().clone(), value, + OutputFeatures::default(), 20.into(), message.clone(), ) @@ -1137,6 +1143,7 @@ fn send_one_sided_transaction_to_self() { .send_one_sided_transaction( alice_node_identity.public_key().clone(), value, + OutputFeatures::default(), 20.into(), message.clone(), ) @@ -1275,6 +1282,7 @@ fn manage_multiple_transactions() { .block_on(alice_ts.send_transaction( bob_node_identity.public_key().clone(), value_a_to_b_1, + OutputFeatures::default(), MicroTari::from(20), "a to b 1".to_string(), )) @@ -1285,6 +1293,7 @@ fn manage_multiple_transactions() { .block_on(alice_ts.send_transaction( carol_node_identity.public_key().clone(), value_a_to_c_1, + OutputFeatures::default(), MicroTari::from(20), "a to c 1".to_string(), )) @@ -1297,6 +1306,7 @@ fn manage_multiple_transactions() { .block_on(bob_ts.send_transaction( alice_node_identity.public_key().clone(), value_b_to_a_1, + OutputFeatures::default(), MicroTari::from(20), "b to a 1".to_string(), )) @@ -1305,6 +1315,7 @@ fn manage_multiple_transactions() { .block_on(alice_ts.send_transaction( bob_node_identity.public_key().clone(), value_a_to_b_2, + OutputFeatures::default(), MicroTari::from(20), "a to b 2".to_string(), )) @@ -1436,6 +1447,7 @@ fn test_accepting_unknown_tx_id_and_malformed_reply() { .block_on(alice_ts_interface.transaction_service_handle.send_transaction( bob_node_identity.public_key().clone(), MicroTari::from(5000), + OutputFeatures::default(), MicroTari::from(20), "".to_string(), )) @@ -1543,8 +1555,8 @@ fn finalize_tx_with_incorrect_pubkey() { .prepare_transaction_to_send( TxId::new_random(), MicroTari::from(5000), - None, - None, + UtxoSelectionCriteria::default(), + OutputFeatures::default(), MicroTari::from(25), None, "".to_string(), @@ -1664,8 +1676,8 @@ fn finalize_tx_with_missing_output() { .prepare_transaction_to_send( TxId::new_random(), MicroTari::from(5000), - None, - None, + UtxoSelectionCriteria::default(), + OutputFeatures::default(), MicroTari::from(20), None, "".to_string(), @@ -1840,6 +1852,7 @@ fn discovery_async_return_test() { .block_on(alice_ts.send_transaction( bob_node_identity.public_key().clone(), value_a_to_c_1, + OutputFeatures::default(), MicroTari::from(20), "Discovery Tx!".to_string(), )) @@ -1876,6 +1889,7 @@ fn discovery_async_return_test() { .block_on(alice_ts.send_transaction( carol_node_identity.public_key().clone(), value_a_to_c_1, + OutputFeatures::default(), MicroTari::from(20), "Discovery Tx2!".to_string(), )) @@ -2150,6 +2164,7 @@ fn test_transaction_cancellation() { .block_on(alice_ts_interface.transaction_service_handle.send_transaction( bob_node_identity.public_key().clone(), amount_sent, + OutputFeatures::default(), 100 * uT, "Testing Message".to_string(), )) @@ -2497,6 +2512,7 @@ fn test_direct_vs_saf_send_of_tx_reply_and_finalize() { .block_on(alice_ts_interface.transaction_service_handle.send_transaction( bob_node_identity.public_key().clone(), amount_sent, + OutputFeatures::default(), 100 * uT, "Testing Message".to_string(), )) @@ -2679,6 +2695,7 @@ fn test_direct_vs_saf_send_of_tx_reply_and_finalize() { .block_on(alice_ts_interface.transaction_service_handle.send_transaction( bob_node_identity.public_key().clone(), amount_sent, + OutputFeatures::default(), 100 * uT, "Testing Message".to_string(), )) @@ -2800,6 +2817,7 @@ fn test_tx_direct_send_behaviour() { .block_on(alice_ts_interface.transaction_service_handle.send_transaction( bob_node_identity.public_key().clone(), amount_sent, + OutputFeatures::default(), 100 * uT, "Testing Message1".to_string(), )) @@ -2841,6 +2859,7 @@ fn test_tx_direct_send_behaviour() { .block_on(alice_ts_interface.transaction_service_handle.send_transaction( bob_node_identity.public_key().clone(), amount_sent, + OutputFeatures::default(), 100 * uT, "Testing Message2".to_string(), )) @@ -2886,6 +2905,7 @@ fn test_tx_direct_send_behaviour() { .block_on(alice_ts_interface.transaction_service_handle.send_transaction( bob_node_identity.public_key().clone(), amount_sent, + OutputFeatures::default(), 100 * uT, "Testing Message3".to_string(), )) @@ -2931,6 +2951,7 @@ fn test_tx_direct_send_behaviour() { .block_on(alice_ts_interface.transaction_service_handle.send_transaction( bob_node_identity.public_key().clone(), amount_sent, + OutputFeatures::default(), 100 * uT, "Testing Message4".to_string(), )) @@ -4152,6 +4173,7 @@ fn test_transaction_resending() { .block_on(alice_ts_interface.transaction_service_handle.send_transaction( bob_node_identity.public_key().clone(), amount_sent, + OutputFeatures::default(), 100 * uT, "Testing Message".to_string(), )) @@ -4653,6 +4675,7 @@ fn test_replying_to_cancelled_tx() { .block_on(alice_ts_interface.transaction_service_handle.send_transaction( bob_node_identity.public_key().clone(), amount_sent, + OutputFeatures::default(), 100 * uT, "Testing Message".to_string(), )) @@ -4775,6 +4798,7 @@ fn test_transaction_timeout_cancellation() { .block_on(alice_ts_interface.transaction_service_handle.send_transaction( bob_node_identity.public_key().clone(), amount_sent, + OutputFeatures::default(), 20 * uT, "Testing Message".to_string(), )) @@ -5027,6 +5051,7 @@ fn transaction_service_tx_broadcast() { .block_on(alice_ts_interface.transaction_service_handle.send_transaction( bob_node_identity.public_key().clone(), amount_sent1, + OutputFeatures::default(), 100 * uT, "Testing Message".to_string(), )) @@ -5084,6 +5109,7 @@ fn transaction_service_tx_broadcast() { .block_on(alice_ts_interface.transaction_service_handle.send_transaction( bob_node_identity.public_key().clone(), amount_sent2, + OutputFeatures::default(), 20 * uT, "Testing Message2".to_string(), )) diff --git a/base_layer/wallet/tests/wallet.rs b/base_layer/wallet/tests/wallet.rs index ba6957d187..cadb77a9dc 100644 --- a/base_layer/wallet/tests/wallet.rs +++ b/base_layer/wallet/tests/wallet.rs @@ -293,6 +293,7 @@ async fn test_wallet() { .send_transaction( bob_identity.public_key().clone(), value, + OutputFeatures::default(), MicroTari::from(5), "".to_string(), ) @@ -609,6 +610,7 @@ async fn test_store_and_forward_send_tx() { .send_transaction( carol_identity.public_key().clone(), value, + OutputFeatures::default(), MicroTari::from(3), "Store and Forward!".to_string(), ) diff --git a/base_layer/wallet_ffi/src/lib.rs b/base_layer/wallet_ffi/src/lib.rs index 80fea4d457..a3b45d894f 100644 --- a/base_layer/wallet_ffi/src/lib.rs +++ b/base_layer/wallet_ffi/src/lib.rs @@ -102,6 +102,7 @@ use tari_core::transactions::{ AssetOutputFeatures, CommitteeDefinitionFeatures, MintNonFungibleFeatures, + OutputFeatures, OutputFeaturesVersion, OutputType, SideChainCheckpointFeatures, @@ -4893,6 +4894,7 @@ pub unsafe extern "C" fn wallet_send_transaction( .block_on((*wallet).wallet.transaction_service.send_one_sided_transaction( (*dest_public_key).clone(), MicroTari::from(amount), + OutputFeatures::default(), MicroTari::from(fee_per_gram), message_string, )) { @@ -4909,6 +4911,7 @@ pub unsafe extern "C" fn wallet_send_transaction( .block_on((*wallet).wallet.transaction_service.send_transaction( (*dest_public_key).clone(), MicroTari::from(amount), + OutputFeatures::default(), MicroTari::from(fee_per_gram), message_string, )) { From 9f9013b9f5b5ef77671775b9ced29aa184b4878a Mon Sep 17 00:00:00 2001 From: Stanimal Date: Tue, 28 Jun 2022 11:33:51 +0200 Subject: [PATCH 3/5] chore(lmdb-db): add more debug info to contract index --- .../chain_storage/lmdb_db/contract_index.rs | 32 ++++++++++++++++++- .../core/src/chain_storage/lmdb_db/lmdb_db.rs | 1 + 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/base_layer/core/src/chain_storage/lmdb_db/contract_index.rs b/base_layer/core/src/chain_storage/lmdb_db/contract_index.rs index 5f07a0fff4..dbfdf7e821 100644 --- a/base_layer/core/src/chain_storage/lmdb_db/contract_index.rs +++ b/base_layer/core/src/chain_storage/lmdb_db/contract_index.rs @@ -23,7 +23,7 @@ use std::{ collections::{hash_map::DefaultHasher, HashSet}, convert::{TryFrom, TryInto}, - fmt::Debug, + fmt::{Debug, Display, Formatter}, hash::{BuildHasherDefault, Hash}, ops::Deref, }; @@ -211,6 +211,7 @@ impl<'a> ContractIndex<'a, WriteTransaction<'a>> { output_hash: FixedHash, ) -> Result<(), ChainStorageError> { let contract_key = ContractIndexKey::new(contract_id, output_type); + debug!(target: LOG_TARGET, "Adding contract key {} to index", contract_key,); let block_key = BlockContractIndexKey::new(block_hash, output_type, contract_id); match output_type { OutputType::ContractDefinition => { @@ -264,6 +265,7 @@ impl<'a> ContractIndex<'a, WriteTransaction<'a>> { ) -> Result<(), ChainStorageError> { let contract_key = ContractIndexKey::new(contract_id, output_type); + debug!(target: LOG_TARGET, "Removing contract key {} from index", contract_key,); match output_type { OutputType::ContractDefinition => { if self.has_dependent_outputs(&contract_key)? { @@ -485,6 +487,22 @@ impl ContractIndexKey { key.key[FixedHash::byte_size() + 1] = output_type.as_byte(); key } + + pub fn key_type(&self) -> KeyType { + match self.key[0] { + 0 => KeyType::PerContract, + 1 => KeyType::PerBlock, + _ => unreachable!(), + } + } + + pub fn contract_id(&self) -> FixedHash { + FixedHash::try_from(&self.key[1..=32]).expect("32 bytes cannot fail") + } + + pub fn output_type(&self) -> OutputType { + OutputType::from_byte(self.key[33]).expect("Contract key set with invalid OutputType") + } } impl Deref for ContractIndexKey { @@ -501,6 +519,18 @@ impl AsLmdbBytes for ContractIndexKey { } } +impl Display for ContractIndexKey { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{:?}, {}, {}", + self.key_type(), + self.contract_id(), + self.output_type() + ) + } +} + #[cfg(test)] mod tests { use digest::Digest; 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 70e0de0c2f..2a0bd8e876 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 @@ -762,6 +762,7 @@ impl LMDBDatabase { } if input.features()?.is_sidechain_contract() { + // TODO: 0-conf not supported for contract outputs self.get_contract_index(txn).spend(input)?; } From 39e9f05bbe8c284b95fc35c61d87c62513d387af Mon Sep 17 00:00:00 2001 From: Stanimal Date: Tue, 28 Jun 2022 11:36:54 +0200 Subject: [PATCH 4/5] fix(dan): use new contract output metadata for checkpoints --- dan_layer/core/src/models/base_layer_output.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/dan_layer/core/src/models/base_layer_output.rs b/dan_layer/core/src/models/base_layer_output.rs index 35a4de8bdb..3bae121e89 100644 --- a/dan_layer/core/src/models/base_layer_output.rs +++ b/dan_layer/core/src/models/base_layer_output.rs @@ -42,7 +42,11 @@ impl BaseLayerOutput { } pub fn get_checkpoint_merkle_root(&self) -> Option { - self.features.sidechain_checkpoint.as_ref().map(|cp| cp.merkle_root) + self.features + .sidechain_features + .as_ref() + .and_then(|cp| cp.checkpoint.as_ref()) + .map(|cp| cp.merkle_root) } pub fn get_parent_public_key(&self) -> Option<&PublicKey> { @@ -65,7 +69,7 @@ impl TryFrom for CheckpointOutput { type Error = ModelError; fn try_from(output: BaseLayerOutput) -> Result { - if output.features.output_type != OutputType::SidechainCheckpoint { + if output.features.output_type != OutputType::ContractCheckpoint { return Err(ModelError::NotCheckpointOutput); } From 873741f14af2e409155829034476c2ea206f564d Mon Sep 17 00:00:00 2001 From: Stanimal Date: Tue, 28 Jun 2022 11:37:26 +0200 Subject: [PATCH 5/5] fix(dan): create initial checkpoint --- .../src/grpc/services/wallet_client.rs | 33 ++++++++++++++----- .../core/src/services/checkpoint_manager.rs | 10 +++++- dan_layer/core/src/services/mocks/mod.rs | 1 + dan_layer/core/src/services/wallet_client.rs | 1 + .../core/src/workers/consensus_worker.rs | 1 + 5 files changed, 36 insertions(+), 10 deletions(-) diff --git a/applications/tari_validator_node/src/grpc/services/wallet_client.rs b/applications/tari_validator_node/src/grpc/services/wallet_client.rs index dac42263bb..208fdc7077 100644 --- a/applications/tari_validator_node/src/grpc/services/wallet_client.rs +++ b/applications/tari_validator_node/src/grpc/services/wallet_client.rs @@ -27,6 +27,7 @@ use tari_app_grpc::{ tari_rpc as grpc, tari_rpc::{ CreateFollowOnAssetCheckpointRequest, + CreateInitialAssetCheckpointRequest, SubmitContractAcceptanceRequest, SubmitContractUpdateProposalAcceptanceRequest, }, @@ -66,18 +67,32 @@ impl WalletClient for GrpcWalletClient { &mut self, contract_id: &FixedHash, state_root: &StateRoot, + is_initial: bool, ) -> Result<(), DigitalAssetError> { let inner = self.connection().await?; - let request = CreateFollowOnAssetCheckpointRequest { - contract_id: contract_id.to_vec(), - merkle_root: state_root.as_bytes().to_vec(), - }; - - let _res = inner - .create_follow_on_asset_checkpoint(request) - .await - .map_err(|e| DigitalAssetError::FatalError(format!("Could not create checkpoint:{}", e)))?; + if is_initial { + let request = CreateInitialAssetCheckpointRequest { + contract_id: contract_id.to_vec(), + merkle_root: state_root.as_bytes().to_vec(), + committee: vec![], + }; + + let _res = inner + .create_initial_asset_checkpoint(request) + .await + .map_err(|e| DigitalAssetError::FatalError(format!("Could not create checkpoint:{}", e)))?; + } else { + let request = CreateFollowOnAssetCheckpointRequest { + contract_id: contract_id.to_vec(), + merkle_root: state_root.as_bytes().to_vec(), + }; + + let _res = inner + .create_follow_on_asset_checkpoint(request) + .await + .map_err(|e| DigitalAssetError::FatalError(format!("Could not create checkpoint:{}", e)))?; + } Ok(()) } diff --git a/dan_layer/core/src/services/checkpoint_manager.rs b/dan_layer/core/src/services/checkpoint_manager.rs index a1ae232c8e..bd5b7a3c04 100644 --- a/dan_layer/core/src/services/checkpoint_manager.rs +++ b/dan_layer/core/src/services/checkpoint_manager.rs @@ -41,6 +41,7 @@ pub struct ConcreteCheckpointManager { asset_definition: AssetDefinition, wallet: TWallet, num_calls: u32, + is_initial_checkpoint: bool, checkpoint_interval: u32, } @@ -50,6 +51,8 @@ impl ConcreteCheckpointManager { asset_definition, wallet, num_calls: 0, + // TODO: VNs need to be aware of the checkpoint state + is_initial_checkpoint: true, checkpoint_interval: 100, } } @@ -65,8 +68,13 @@ impl CheckpointManager for ConcreteCheckpoi "Creating checkpoint for contract {}", self.asset_definition.contract_id ); self.wallet - .create_new_checkpoint(&self.asset_definition.contract_id, &state_root) + .create_new_checkpoint( + &self.asset_definition.contract_id, + &state_root, + self.is_initial_checkpoint, + ) .await?; + self.is_initial_checkpoint = false; self.num_calls = 0; } Ok(()) diff --git a/dan_layer/core/src/services/mocks/mod.rs b/dan_layer/core/src/services/mocks/mod.rs index a5f71d57b4..4b3131e446 100644 --- a/dan_layer/core/src/services/mocks/mod.rs +++ b/dan_layer/core/src/services/mocks/mod.rs @@ -340,6 +340,7 @@ impl WalletClient for MockWalletClient { &mut self, _contract_id: &FixedHash, _state_root: &StateRoot, + _is_initial: bool, ) -> Result<(), DigitalAssetError> { Ok(()) } diff --git a/dan_layer/core/src/services/wallet_client.rs b/dan_layer/core/src/services/wallet_client.rs index f2aeb55915..42d9537a83 100644 --- a/dan_layer/core/src/services/wallet_client.rs +++ b/dan_layer/core/src/services/wallet_client.rs @@ -31,6 +31,7 @@ pub trait WalletClient: Send + Sync { &mut self, contract_id: &FixedHash, state_root: &StateRoot, + is_initial: bool, ) -> Result<(), DigitalAssetError>; async fn submit_contract_acceptance( diff --git a/dan_layer/core/src/workers/consensus_worker.rs b/dan_layer/core/src/workers/consensus_worker.rs index 260420afbd..487e0ca585 100644 --- a/dan_layer/core/src/workers/consensus_worker.rs +++ b/dan_layer/core/src/workers/consensus_worker.rs @@ -308,6 +308,7 @@ impl<'a, T: ServiceSpecification> ConsensusWorkerProcessor<'a, &mut self.worker.payload_provider, ) .await?; + unit_of_work.commit()?; if let Some(mut state_tx) = self.worker.state_db_unit_of_work.take() { state_tx.commit()?;