diff --git a/applications/minotari_app_grpc/proto/wallet.proto b/applications/minotari_app_grpc/proto/wallet.proto index cd936a6884..6d87cd46da 100644 --- a/applications/minotari_app_grpc/proto/wallet.proto +++ b/applications/minotari_app_grpc/proto/wallet.proto @@ -211,16 +211,16 @@ enum TransactionStatus { TRANSACTION_STATUS_COINBASE = 5; // This transaction is mined and confirmed at the current base node's height TRANSACTION_STATUS_MINED_CONFIRMED = 6; - // The transaction was not found by the wallet its in transaction database - TRANSACTION_STATUS_NOT_FOUND = 7; // The transaction was rejected by the mempool - TRANSACTION_STATUS_REJECTED = 8; + TRANSACTION_STATUS_REJECTED = 7; // This is faux transaction mainly for one-sided transaction outputs or wallet recovery outputs have been found - TRANSACTION_STATUS_FAUX_UNCONFIRMED = 9; + TRANSACTION_STATUS_FAUX_UNCONFIRMED = 8; // All Imported and FauxUnconfirmed transactions will end up with this status when the outputs have been confirmed - TRANSACTION_STATUS_FAUX_CONFIRMED = 10; + TRANSACTION_STATUS_FAUX_CONFIRMED = 9; // This transaction is still being queued for sending - TRANSACTION_STATUS_QUEUED = 11; + TRANSACTION_STATUS_QUEUED = 10; + // The transaction was not found by the wallet its in transaction database + TRANSACTION_STATUS_NOT_FOUND = 11; } message GetCompletedTransactionsRequest { } diff --git a/applications/minotari_app_grpc/src/conversions/transaction.rs b/applications/minotari_app_grpc/src/conversions/transaction.rs index 329e8ba570..77c9bb5176 100644 --- a/applications/minotari_app_grpc/src/conversions/transaction.rs +++ b/applications/minotari_app_grpc/src/conversions/transaction.rs @@ -95,10 +95,10 @@ impl From for grpc::TransactionStatus { Completed => grpc::TransactionStatus::Completed, Broadcast => grpc::TransactionStatus::Broadcast, MinedUnconfirmed => grpc::TransactionStatus::MinedUnconfirmed, - MinedConfirmed => grpc::TransactionStatus::MinedConfirmed, Imported => grpc::TransactionStatus::Imported, Pending => grpc::TransactionStatus::Pending, Coinbase => grpc::TransactionStatus::Coinbase, + MinedConfirmed => grpc::TransactionStatus::MinedConfirmed, Rejected => grpc::TransactionStatus::Rejected, FauxUnconfirmed => grpc::TransactionStatus::FauxUnconfirmed, FauxConfirmed => grpc::TransactionStatus::FauxConfirmed, diff --git a/applications/minotari_merge_mining_proxy/src/block_template_protocol.rs b/applications/minotari_merge_mining_proxy/src/block_template_protocol.rs index 46a361db68..74ff665d47 100644 --- a/applications/minotari_merge_mining_proxy/src/block_template_protocol.rs +++ b/applications/minotari_merge_mining_proxy/src/block_template_protocol.rs @@ -70,10 +70,15 @@ impl<'a> BlockTemplateProtocol<'a> { let wallet_private_key = PrivateKey::random(&mut OsRng); let miner_node_script_key_id = key_manager.import_key(wallet_private_key).await?; let wallet_payment_address = TariAddress::from_str(&config.wallet_payment_address) - .map_err(|err| MmProxyError::ConversionError(err.to_string()))?; + .map_err(|err| MmProxyError::WalletPaymentAddress(err.to_string()))?; if wallet_payment_address == TariAddress::default() { - return Err(MmProxyError::PaymentWalletAddressMissing( - "Has default value".to_string(), + return Err(MmProxyError::WalletPaymentAddress( + "May not have the default value".to_string(), + )); + } + if wallet_payment_address.network() != config.network { + return Err(MmProxyError::WalletPaymentAddress( + "Wallet address network does not match miner network".to_string(), )); } let consensus_manager = ConsensusManager::builder(config.network).build()?; diff --git a/applications/minotari_merge_mining_proxy/src/config.rs b/applications/minotari_merge_mining_proxy/src/config.rs index 5d56de6ee3..3d356b2d26 100644 --- a/applications/minotari_merge_mining_proxy/src/config.rs +++ b/applications/minotari_merge_mining_proxy/src/config.rs @@ -68,7 +68,7 @@ pub struct MergeMiningProxyConfig { pub coinbase_extra: String, /// Selected network pub network: Network, - /// The Tari wallet address where the mining funds will be sent to + /// The Tari wallet address (valid address in hex) where the mining funds will be sent to - must be assigned pub wallet_payment_address: String, /// Stealth payment yes or no pub stealth_payment: bool, diff --git a/applications/minotari_merge_mining_proxy/src/error.rs b/applications/minotari_merge_mining_proxy/src/error.rs index 85a8ca291c..04d585aea6 100644 --- a/applications/minotari_merge_mining_proxy/src/error.rs +++ b/applications/minotari_merge_mining_proxy/src/error.rs @@ -105,7 +105,7 @@ pub enum MmProxyError { #[error("Consensus build error: {0}")] ConsensusBuilderError(#[from] ConsensusBuilderError), #[error("Could not convert data:{0}")] - PaymentWalletAddressMissing(String), + WalletPaymentAddress(String), } impl From for MmProxyError { diff --git a/applications/minotari_miner/src/config.rs b/applications/minotari_miner/src/config.rs index 29e114ea3e..1a2d43b723 100644 --- a/applications/minotari_miner/src/config.rs +++ b/applications/minotari_miner/src/config.rs @@ -60,9 +60,9 @@ pub struct MinerConfig { /// `mine_on_tip_only` is set to true pub validate_tip_timeout_sec: u64, /// Stratum Mode configuration - mining pool address - pub mining_pool_address: String, + pub stratum_mining_pool_address: String, /// Stratum Mode configuration - mining wallet address/public key - pub mining_wallet_address: String, + pub stratum_mining_wallet_address: String, /// Stratum Mode configuration - mining worker name pub mining_worker_name: String, /// The extra data to store in the coinbase, usually some data about the mining pool. @@ -73,7 +73,7 @@ pub struct MinerConfig { pub network: Network, /// Base node reconnect timeout after any gRPC or miner error pub wait_timeout_on_error: u64, - /// The Tari wallet address where the mining funds will be sent to + /// The Tari wallet address (valid address in hex) where the mining funds will be sent to - must be assigned pub wallet_payment_address: String, /// Stealth payment yes or no pub stealth_payment: bool, @@ -102,8 +102,8 @@ impl Default for MinerConfig { mine_on_tip_only: true, proof_of_work_algo: ProofOfWork::Sha3x, validate_tip_timeout_sec: 30, - mining_pool_address: String::new(), - mining_wallet_address: String::new(), + stratum_mining_pool_address: String::new(), + stratum_mining_wallet_address: String::new(), mining_worker_name: String::new(), coinbase_extra: "minotari_miner".to_string(), network: Default::default(), diff --git a/applications/minotari_miner/src/run_miner.rs b/applications/minotari_miner/src/run_miner.rs index 68af4f340f..dbf0608489 100644 --- a/applications/minotari_miner/src/run_miner.rs +++ b/applications/minotari_miner/src/run_miner.rs @@ -82,21 +82,27 @@ pub async fn start_miner(cli: Cli) -> Result<(), ExitError> { .await .map_err(|err| ExitError::new(ExitCode::KeyManagerServiceError, err.to_string()))?; let wallet_payment_address = TariAddress::from_str(&config.wallet_payment_address) - .map_err(|err| ExitError::new(ExitCode::ConversionError, err.to_string()))?; + .map_err(|err| ExitError::new(ExitCode::WalletPaymentAddress, err.to_string()))?; debug!(target: LOG_TARGET_FILE, "wallet_payment_address: {}", wallet_payment_address); if wallet_payment_address == TariAddress::default() { return Err(ExitError::new( - ExitCode::PaymentWalletAddressMissing, - "Has default value".to_string(), + ExitCode::WalletPaymentAddress, + "May not have the default value".to_string(), + )); + } + if wallet_payment_address.network() != config.network { + return Err(ExitError::new( + ExitCode::WalletPaymentAddress, + "Wallet address network does not match miner network".to_string(), )); } let consensus_manager = ConsensusManager::builder(config.network) .build() .map_err(|err| ExitError::new(ExitCode::ConsensusManagerBuilderError, err.to_string()))?; - if !config.mining_wallet_address.is_empty() && !config.mining_pool_address.is_empty() { - let url = config.mining_pool_address.clone(); - let mut miner_address = config.mining_wallet_address.clone(); + if !config.stratum_mining_wallet_address.is_empty() && !config.stratum_mining_pool_address.is_empty() { + let url = config.stratum_mining_pool_address.clone(); + let mut miner_address = config.stratum_mining_wallet_address.clone(); let _ = RistrettoPublicKey::from_hex(&miner_address).map_err(|_| { ExitError::new( ExitCode::ConfigError, diff --git a/base_layer/core/src/transactions/coinbase_builder.rs b/base_layer/core/src/transactions/coinbase_builder.rs index 33738cf4d8..253d59cee4 100644 --- a/base_layer/core/src/transactions/coinbase_builder.rs +++ b/base_layer/core/src/transactions/coinbase_builder.rs @@ -30,7 +30,7 @@ use tari_common_types::{ use tari_crypto::keys::PublicKey as PK; use tari_key_manager::key_manager_service::{KeyManagerInterface, KeyManagerServiceError}; use tari_script::{one_sided_payment_script, stealth_payment_script, ExecutionStack, TariScript}; -use tari_utilities::{hex::Hex, ByteArrayError}; +use tari_utilities::ByteArrayError; use thiserror::Error; use crate::{ @@ -258,12 +258,6 @@ where TKeyManagerInterface: TransactionKeyManagerInterface let covenant = self.covenant; let script = self.script.ok_or(CoinbaseBuildError::MissingScript)?; - debug!(target: LOG_TARGET, - "Getting coinbase - height: {}, reward: {}, spending_key_id: {}, script_key_id: {}, encryption_key_id: {}, \ - sender_offset_key_id: {}, script: {}", - height, total_reward, spending_key_id, script_key_id, encryption_key_id, sender_offset_key_id, script - ); - let kernel_features = KernelFeatures::create_coinbase(); let metadata = TransactionMetadata::new_with_features(0.into(), 0, kernel_features); // generate kernel signature @@ -398,12 +392,7 @@ pub async fn generate_coinbase( let encryption_private_key = shared_secret_to_output_encryption_key(&shared_secret)?; let encryption_key_id = key_manager.import_key(encryption_private_key).await?; - let spending_key_hex = spending_key.to_hex(); let spending_key_id = key_manager.import_key(spending_key).await?; - debug!(target: LOG_TARGET, - "generate coinbase - height: {}, spending_key: {}, spending_key_id: {}, sender_offset_key_id: {}, shared_secret: {}", - height, spending_key_hex, spending_key_id, sender_offset_key_id, shared_secret.as_bytes().to_vec().to_hex() - ); let script = if stealth_payment { let (nonce_private_key, nonce_public_key) = PublicKey::random_keypair(&mut OsRng); @@ -439,7 +428,6 @@ pub async fn generate_coinbase( debug!(target: LOG_TARGET, "Coinbase kernel: {}", kernel.clone()); debug!(target: LOG_TARGET, "Coinbase output: {}", output.clone()); - debug!(target: LOG_TARGET, "Coinbase wallet output: {:?}", wallet_output); Ok((transaction.clone(), output.clone(), kernel.clone(), wallet_output)) } diff --git a/base_layer/core/src/validation/aggregate_body/aggregate_body_chain_validator.rs b/base_layer/core/src/validation/aggregate_body/aggregate_body_chain_validator.rs index 81b1e836b6..67c0824c20 100644 --- a/base_layer/core/src/validation/aggregate_body/aggregate_body_chain_validator.rs +++ b/base_layer/core/src/validation/aggregate_body/aggregate_body_chain_validator.rs @@ -140,7 +140,6 @@ fn validate_input_not_pruned( fn validate_input_maturity(body: &AggregateBody, height: u64) -> Result<(), ValidationError> { for input in body.inputs() { - println!("input: {:?}", input); if !input.is_mature_at(height)? { return Err(TransactionError::InputMaturity.into()); } diff --git a/base_layer/wallet/migrations/2023-11-14-131400_coinbase/up.sql b/base_layer/wallet/migrations/2023-11-14-131400_coinbase/up.sql index 129b3be052..737e3284c6 100644 --- a/base_layer/wallet/migrations/2023-11-14-131400_coinbase/up.sql +++ b/base_layer/wallet/migrations/2023-11-14-131400_coinbase/up.sql @@ -1,4 +1,4 @@ --- Any old 'outputs' will not be valid due to the removal of 'coinbase_block_height' and 'coinbase_extra', +-- Any old 'outputs' will not be valid due to the removal of 'coinbase_block_height', -- so we drop and recreate the table. DROP TABLE outputs; diff --git a/base_layer/wallet/src/output_manager_service/handle.rs b/base_layer/wallet/src/output_manager_service/handle.rs index cd873af1b3..127f301012 100644 --- a/base_layer/wallet/src/output_manager_service/handle.rs +++ b/base_layer/wallet/src/output_manager_service/handle.rs @@ -764,7 +764,7 @@ impl OutputManagerHandle { } } - pub async fn get_output_statuses_by_tx_id( + pub async fn get_output_statuses_for_tx_id( &mut self, tx_id: TxId, ) -> Result { diff --git a/base_layer/wallet/src/transaction_service/tasks/check_faux_transaction_status.rs b/base_layer/wallet/src/transaction_service/tasks/check_faux_transaction_status.rs index 2b0846a59e..0991bd17f3 100644 --- a/base_layer/wallet/src/transaction_service/tasks/check_faux_transaction_status.rs +++ b/base_layer/wallet/src/transaction_service/tasks/check_faux_transaction_status.rs @@ -97,83 +97,82 @@ pub async fn check_faux_transactions( all_faux_transactions.len() ); for tx in all_faux_transactions { - let output_statuses_by_tx_id = match output_manager.get_output_statuses_by_tx_id(tx.tx_id).await { + let output_statuses_for_tx_id = match output_manager.get_output_statuses_for_tx_id(tx.tx_id).await { Ok(s) => s, Err(e) => { error!(target: LOG_TARGET, "Problem retrieving output statuses: {}", e); return; }, }; - if !output_statuses_by_tx_id + let some_outputs_spent = !output_statuses_for_tx_id .statuses .iter() - .any(|s| s != &OutputStatus::Unspent) - { - let mined_height = if let Some(height) = output_statuses_by_tx_id.mined_height { - height - } else { - tip_height - }; - let mined_in_block: BlockHash = if let Some(hash) = output_statuses_by_tx_id.block_hash { - hash - } else { - FixedHash::zero() - }; - let is_valid = tip_height >= mined_height; - let was_confirmed = tx.status == TransactionStatus::FauxConfirmed; - let is_confirmed = tip_height.saturating_sub(mined_height) >= - TransactionServiceConfig::default().num_confirmations_required; - let num_confirmations = tip_height - mined_height; - debug!( + .any(|s| s != &OutputStatus::Unspent); + let mined_height = if let Some(height) = output_statuses_for_tx_id.mined_height { + height + } else { + tip_height + }; + let mined_in_block: BlockHash = if let Some(hash) = output_statuses_for_tx_id.block_hash { + hash + } else { + FixedHash::zero() + }; + let is_valid = tip_height >= mined_height; + let was_confirmed = tx.status == TransactionStatus::FauxConfirmed; + let is_confirmed = + tip_height.saturating_sub(mined_height) >= TransactionServiceConfig::default().num_confirmations_required; + let num_confirmations = tip_height - mined_height; + debug!( + target: LOG_TARGET, + "Updating faux transaction: TxId({}), mined_height({}), is_confirmed({}), num_confirmations({}), \ + no_outputs_spent({}), is_valid({})", + tx.tx_id, + mined_height, + is_confirmed, + num_confirmations, + some_outputs_spent, + is_valid, + ); + let result = db.set_transaction_mined_height( + tx.tx_id, + mined_height, + mined_in_block, + tx.mined_timestamp + .map_or(0, |mined_timestamp| mined_timestamp.timestamp() as u64), + num_confirmations, + is_confirmed, + true, + ); + if let Err(e) = result { + error!( target: LOG_TARGET, - "Updating faux transaction: TxId({}), mined_height({}), is_confirmed({}), num_confirmations({}), \ - is_valid({})", - tx.tx_id, - mined_height, - is_confirmed, - num_confirmations, - is_valid, - ); - let result = db.set_transaction_mined_height( - tx.tx_id, - mined_height, - mined_in_block, - tx.mined_timestamp - .map_or(0, |mined_timestamp| mined_timestamp.timestamp() as u64), - num_confirmations, - is_confirmed, - true, + "Error setting faux transaction to mined confirmed: {}", e ); - if let Err(e) = result { - error!( - target: LOG_TARGET, - "Error setting faux transaction to mined confirmed: {}", e - ); - } else { - // Only send an event if the transaction was not previously confirmed OR was previously confirmed and is - // now not confirmed (i.e. confirmation changed) - if !(was_confirmed && is_confirmed) { - let transaction_event = if is_confirmed { - TransactionEvent::FauxTransactionConfirmed { - tx_id: tx.tx_id, - is_valid, - } - } else { - TransactionEvent::FauxTransactionUnconfirmed { - tx_id: tx.tx_id, - num_confirmations: 0, - is_valid, - } - }; - let _size = event_publisher.send(Arc::new(transaction_event)).map_err(|e| { - trace!( - target: LOG_TARGET, - "Error sending event, usually because there are no subscribers: {:?}", - e - ); + } else { + // Only send an event if the transaction was not previously confirmed OR was previously confirmed and is + // now not confirmed (i.e. confirmation changed) + if !(was_confirmed && is_confirmed) { + let transaction_event = if is_confirmed { + TransactionEvent::FauxTransactionConfirmed { + tx_id: tx.tx_id, + is_valid, + } + } else { + TransactionEvent::FauxTransactionUnconfirmed { + tx_id: tx.tx_id, + num_confirmations: 0, + is_valid, + } + }; + let _size = event_publisher.send(Arc::new(transaction_event)).map_err(|e| { + trace!( + target: LOG_TARGET, + "Error sending event, usually because there are no subscribers: {:?}", e - }); - } + ); + e + }); } } } 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 567cdd9083..9d668677cb 100644 --- a/base_layer/wallet/tests/output_manager_service_tests/service.rs +++ b/base_layer/wallet/tests/output_manager_service_tests/service.rs @@ -2095,7 +2095,7 @@ async fn test_get_status_by_tx_id() { let output_statuses_by_tx_id = oms .output_manager_handle - .get_output_statuses_by_tx_id(TxId::from(1u64)) + .get_output_statuses_for_tx_id(TxId::from(1u64)) .await .unwrap(); diff --git a/common/config/presets/f_merge_mining_proxy.toml b/common/config/presets/f_merge_mining_proxy.toml index 8d65e75ec4..ea5b4ad2ff 100644 --- a/common/config/presets/f_merge_mining_proxy.toml +++ b/common/config/presets/f_merge_mining_proxy.toml @@ -63,3 +63,9 @@ monerod_url = [# stagenet # The maximum amount of VMs that RandomX will be use (default = 5) #max_randomx_vms = 5 + +# The Tari wallet address (valid address in hex) where the mining funds will be sent to - must be assigned +# e.g. "78e724f466d202abdee0f23c261289074e4a2fc9eb61e83e0179eead76ce2d3f17" +#wallet_payment_address = "xxx" +# Stealth payment yes or no (default: true) +#stealth_payment = true diff --git a/common/config/presets/g_miner.toml b/common/config/presets/g_miner.toml index 35170b0c16..ad8ade469d 100644 --- a/common/config/presets/g_miner.toml +++ b/common/config/presets/g_miner.toml @@ -23,14 +23,25 @@ #validate_tip_timeout_sec = 30 # Stratum Mode configuration - mining pool address (e.g. "miningcore.tari.com:3052") -# mining_pool_address = "miningcore.tari.com:3052" +#mining_pool_address = "miningcore.tari.com:3052" # Stratum Mode configuration - mining wallet address/public key # (e.g. "20B19870ABEE8ABC6ACC77AE4E6CA169057645B27C35334B74446B4D3EE52150") -# mining_wallet_address = "YOUR_WALLET_PUBLIC_KEY" +#stratum_mining_wallet_address = "YOUR_WALLET_TARI_ADDRESS" -# Stratum Mode configuration - mining worker name (e.g. "worker1") -# mining_worker_name = "worker1" +# Stratum Mode configuration - mining worker name (e.g. "worker1") (default = "") +#stratum_mining_worker_name = "worker1" + +# The extra data to store in the coinbase, usually some data about the mining pool. +# Note that this data is publicly readable, but it is suggested you populate it so that +# pool dominance can be seen before any one party has more than 51%. +#coinbase_extra = "minotari_miner" # Base node reconnect timeout after any GRPC or miner error (default: 10 s) -# wait_timeout_on_error = 10 +#wait_timeout_on_error = 10 + +# The Tari wallet address (valid address in hex) where the mining funds will be sent to - must be assigned +# e.g. "78e724f466d202abdee0f23c261289074e4a2fc9eb61e83e0179eead76ce2d3f17" +#wallet_payment_address = "YOUR_WALLET_TARI_ADDRESS" +# Stealth payment yes or no (default: true) +#stealth_payment = true diff --git a/common/src/exit_codes.rs b/common/src/exit_codes.rs index 6abd7e9574..6d90d49d5b 100644 --- a/common/src/exit_codes.rs +++ b/common/src/exit_codes.rs @@ -126,8 +126,8 @@ pub enum ExitCode { KeyManagerServiceError = 121, #[error("Consensus manager builder error")] ConsensusManagerBuilderError = 122, - #[error("Payment wallet address is not defined")] - PaymentWalletAddressMissing = 123, + #[error("Payment wallet address error")] + WalletPaymentAddress = 123, } impl From for ExitError { diff --git a/integration_tests/tests/features/TransactionInfo.feature b/integration_tests/tests/features/TransactionInfo.feature index 6d9e41ebef..0093e6e000 100644 --- a/integration_tests/tests/features/TransactionInfo.feature +++ b/integration_tests/tests/features/TransactionInfo.feature @@ -4,7 +4,7 @@ @transaction-info @wallet Feature: Transaction Info -@long-running +@critical @long-running Scenario: Get Transaction Info Given I have a seed node NODE When I have a stealth SHA3 miner MINER connected to all seed nodes @@ -24,14 +24,10 @@ Scenario: Get Transaction Info Then wallet WALLET_B detects all transactions are at least Completed Then wallet WALLET_A detects all transactions are at least Broadcast Then wallet WALLET_B detects all transactions are at least Broadcast - # This wait is needed to stop next merge mining task from continuing - When I wait 1 seconds When mining node MINER2 mines 1 blocks Then all nodes are at height 5 Then wallet WALLET_A detects all transactions are at least Mined_or_Faux_Unconfirmed Then wallet WALLET_B detects all transactions are at least Mined_or_Faux_Unconfirmed - # This wait is needed to stop base nodes from shutting down - When I wait 1 seconds When mining node MINER2 mines 10 blocks Then all nodes are at height 15 Then wallet WALLET_A detects all transactions as Mined_or_Faux_Confirmed diff --git a/integration_tests/tests/steps/wallet_steps.rs b/integration_tests/tests/steps/wallet_steps.rs index a588698257..b60642d3d6 100644 --- a/integration_tests/tests/steps/wallet_steps.rs +++ b/integration_tests/tests/steps/wallet_steps.rs @@ -521,11 +521,11 @@ async fn wallet_has_at_least_num_txs(world: &mut TariWorld, wallet: String, num_ "TRANSACTION_STATUS_PENDING" => 4, "TRANSACTION_STATUS_COINBASE" => 5, "TRANSACTION_STATUS_MINED_CONFIRMED" => 6, - "TRANSACTION_STATUS_NOT_FOUND" => 7, - "TRANSACTION_STATUS_REJECTED" => 8, - "TRANSACTION_STATUS_FAUX_UNCONFIRMED" => 9, - "TRANSACTION_STATUS_FAUX_CONFIRMED" => 10, - "TRANSACTION_STATUS_QUEUED" => 11, + "TRANSACTION_STATUS_REJECTED" => 7, + "TRANSACTION_STATUS_FAUX_UNCONFIRMED" => 8, + "TRANSACTION_STATUS_FAUX_CONFIRMED" => 9, + "TRANSACTION_STATUS_QUEUED" => 10, + "TRANSACTION_STATUS_NOT_FOUND" => 11, _ => panic!("Invalid transaction status {}", transaction_status), };