diff --git a/test/functional/test_framework/wallet_cli_controller.py b/test/functional/test_framework/wallet_cli_controller.py index 8acb89f504..5626fddc83 100644 --- a/test/functional/test_framework/wallet_cli_controller.py +++ b/test/functional/test_framework/wallet_cli_controller.py @@ -214,8 +214,9 @@ async def get_raw_signed_transaction(self, tx_id: str) -> str: async def send_to_address(self, address: str, amount: int, selected_utxos: List[UtxoOutpoint] = []) -> str: return await self._write_command(f"address-send {address} {amount} {' '.join(map(str, selected_utxos))}\n") - async def compose_transaction(self, outputs: List[TxOutput], selected_utxos: List[UtxoOutpoint]) -> str: - return await self._write_command(f"transaction-compose {' '.join(map(str, outputs))} --utxos {' --utxos '.join(map(str, selected_utxos))}\n") + async def compose_transaction(self, outputs: List[TxOutput], selected_utxos: List[UtxoOutpoint], only_transaction: bool = False) -> str: + only_tx = "--only-transaction" if only_transaction else "" + return await self._write_command(f"transaction-compose {' '.join(map(str, outputs))} --utxos {' --utxos '.join(map(str, selected_utxos))} {only_tx}\n") async def send_tokens_to_address(self, token_id: str, address: str, amount: Union[float, str]): return await self._write_command(f"token-send {token_id} {address} {amount}\n") diff --git a/test/functional/wallet_tx_compose.py b/test/functional/wallet_tx_compose.py index 7d1690afd8..7e94be7760 100644 --- a/test/functional/wallet_tx_compose.py +++ b/test/functional/wallet_tx_compose.py @@ -138,13 +138,21 @@ def make_output(pub_key_bytes): assert_equal(len(utxos), len(addresses)) # compose a transaction with all our utxos and n outputs to the other acc and 1 as change - output = await wallet.compose_transaction(outputs, utxos) - self.log.info(f"compose output: '{output}'") + output = await wallet.compose_transaction(outputs, utxos, True) assert_in("The hex encoded transaction is", output) # check the fees include the 0.1 + any extra utxos assert_in(f"Coins amount: {((len(addresses) - (num_outputs + 1))*coins_to_send)}.1", output) encoded_tx = output.split('\n')[1] + output = await wallet.compose_transaction(outputs, utxos, False) + assert_in("The hex encoded transaction is", output) + # check the fees include the 0.1 + any extra utxos + assert_in(f"Coins amount: {((len(addresses) - (num_outputs + 1))*coins_to_send)}.1", output) + encoded_ptx = output.split('\n')[1] + + # partially_signed_tx is bigger than just the tx + assert len(encoded_tx) < len(encoded_ptx) + output = await wallet.sign_raw_transaction(encoded_tx) self.log.info(f"sign output: '{output}'") assert_in("The transaction has been fully signed signed", output) diff --git a/wallet/wallet-cli-lib/src/commands/mod.rs b/wallet/wallet-cli-lib/src/commands/mod.rs index ed230c95a2..5695d4e455 100644 --- a/wallet/wallet-cli-lib/src/commands/mod.rs +++ b/wallet/wallet-cli-lib/src/commands/mod.rs @@ -36,7 +36,11 @@ use mempool::tx_options::TxOptionsOverrides; use p2p_types::{bannable_address::BannableAddress, ip_or_socket_address::IpOrSocketAddress}; use serialization::{hex::HexEncode, hex_encoded::HexEncoded}; use utils::qrcode::QrCode; -use wallet::{account::PartiallySignedTransaction, version::get_version, WalletError}; +use wallet::{ + account::{PartiallySignedTransaction, TransactionToSign}, + version::get_version, + WalletError, +}; use wallet_controller::{ControllerConfig, NodeInterface, PeerId, DEFAULT_ACCOUNT_INDEX}; use wallet_rpc_lib::{ config::WalletRpcConfig, types::NewTransaction, CreatedWallet, WalletRpc, WalletRpcServer, @@ -628,6 +632,9 @@ pub enum WalletCommand { /// block(000000000000000000059fa50103b9683e51e5aba83b8a34c9b98ce67d66136c,2) #[arg(long="utxos", default_values_t = Vec::::new())] utxos: Vec, + + #[arg(long = "only-transaction", default_value_t = false)] + only_transaction: bool, }, /// Abandon an unconfirmed transaction in the wallet database, and make the consumed inputs available to be used again @@ -773,11 +780,6 @@ where .ok_or(WalletCliError::NoWallet) } - pub fn tx_submitted_command() -> ConsoleCommand { - let status_text = "The transaction was submitted successfully"; - ConsoleCommand::Print(status_text.to_owned()) - } - pub fn new_tx_submitted_command(new_tx: NewTransaction) -> ConsoleCommand { let status_text = format!( "The transaction was submitted successfully with ID:\n{}", @@ -1209,13 +1211,18 @@ where } WalletCommand::SubmitTransaction { transaction } => { - self.wallet_rpc + let new_tx = self + .wallet_rpc .submit_raw_transaction(transaction, TxOptionsOverrides::default()) .await?; - Ok(Self::tx_submitted_command()) + Ok(Self::new_tx_submitted_command(new_tx)) } - WalletCommand::TransactionCompose { outputs, utxos } => { + WalletCommand::TransactionCompose { + outputs, + utxos, + only_transaction, + } => { eprintln!("outputs: {outputs:?}"); eprintln!("utxos: {utxos:?}"); let outputs: Vec = outputs @@ -1229,11 +1236,16 @@ where .collect::, WalletCliError>>( )?; - let (tx, fees) = self.wallet_rpc.compose_transaction(input_utxos, outputs).await?; + let (tx, fees) = self + .wallet_rpc + .compose_transaction(input_utxos, outputs, only_transaction) + .await?; let (coins, tokens) = fees.into_coins_and_tokens(); - - let mut output = - format!("The hex encoded transaction is:\n{}\n", HexEncoded::new(tx)); + let encoded_tx = match tx { + TransactionToSign::Tx(tx) => HexEncoded::new(tx).to_string(), + TransactionToSign::Partial(tx) => HexEncoded::new(tx).to_string(), + }; + let mut output = format!("The hex encoded transaction is:\n{encoded_tx}\n"); writeln!( &mut output, diff --git a/wallet/wallet-controller/src/lib.rs b/wallet/wallet-controller/src/lib.rs index 4cf19f2928..4dfd7123c3 100644 --- a/wallet/wallet-controller/src/lib.rs +++ b/wallet/wallet-controller/src/lib.rs @@ -67,7 +67,7 @@ pub use node_comm::{ use wallet::{ account::{ currency_grouper::{self, Currency}, - PartiallySignedTransaction, + PartiallySignedTransaction, TransactionToSign, }, get_tx_output_destination, wallet::WalletPoolsFilter, @@ -617,7 +617,8 @@ impl Controll &self, inputs: Vec, outputs: Vec, - ) -> Result<(PartiallySignedTransaction, Balances), ControllerError> { + only_transaction: bool, + ) -> Result<(TransactionToSign, Balances), ControllerError> { let input_utxos = self.fetch_utxos(&inputs).await?; let fees = self.get_fees(&input_utxos, &outputs)?; let fees = into_balances(&self.rpc_client, &self.chain_config, fees).await?; @@ -628,22 +629,29 @@ impl Controll let tx = Transaction::new(0, inputs, outputs) .map_err(|err| ControllerError::WalletError(WalletError::TransactionCreation(err)))?; - let destinations = input_utxos - .iter() - .map(|txo| { - get_tx_output_destination(txo, &|_| None) - .ok_or_else(|| WalletError::UnsupportedTransactionOutput(Box::new(txo.clone()))) - }) - .collect::, WalletError>>() + let tx = if only_transaction { + TransactionToSign::Tx(tx) + } else { + let destinations = input_utxos + .iter() + .map(|txo| { + get_tx_output_destination(txo, &|_| None).ok_or_else(|| { + WalletError::UnsupportedTransactionOutput(Box::new(txo.clone())) + }) + }) + .collect::, WalletError>>() + .map_err(ControllerError::WalletError)?; + + let tx = PartiallySignedTransaction::new( + tx, + vec![None; num_inputs], + input_utxos.into_iter().map(Option::Some).collect(), + destinations.into_iter().map(Option::Some).collect(), + ) .map_err(ControllerError::WalletError)?; - let tx = PartiallySignedTransaction::new( - tx, - vec![None; num_inputs], - input_utxos.into_iter().map(Option::Some).collect(), - destinations.into_iter().map(Option::Some).collect(), - ) - .map_err(ControllerError::WalletError)?; + TransactionToSign::Partial(tx) + }; Ok((tx, fees)) } diff --git a/wallet/wallet-rpc-lib/src/rpc/interface.rs b/wallet/wallet-rpc-lib/src/rpc/interface.rs index b66a1211ec..05d97c62ef 100644 --- a/wallet/wallet-rpc-lib/src/rpc/interface.rs +++ b/wallet/wallet-rpc-lib/src/rpc/interface.rs @@ -120,7 +120,7 @@ trait WalletRpc { &self, tx: HexEncoded, options: TxOptionsOverrides, - ) -> rpc::RpcResult<()>; + ) -> rpc::RpcResult; #[method(name = "address_send")] async fn send_coins( diff --git a/wallet/wallet-rpc-lib/src/rpc/mod.rs b/wallet/wallet-rpc-lib/src/rpc/mod.rs index 8b1441a792..cb24fc8f34 100644 --- a/wallet/wallet-rpc-lib/src/rpc/mod.rs +++ b/wallet/wallet-rpc-lib/src/rpc/mod.rs @@ -39,7 +39,9 @@ use common::{ Block, ChainConfig, DelegationId, Destination, GenBlock, PoolId, SignedTransaction, Transaction, TxOutput, UtxoOutPoint, }, - primitives::{id::WithId, per_thousand::PerThousand, Amount, BlockHeight, DecimalAmount, Id}, + primitives::{ + id::WithId, per_thousand::PerThousand, Amount, BlockHeight, DecimalAmount, Id, Idable, + }, }; pub use interface::WalletRpcServer; pub use rpc::{rpc_creds::RpcCreds, Rpc}; @@ -353,7 +355,7 @@ impl WalletRpc { &self, tx: HexEncoded, options: TxOptionsOverrides, - ) -> WRpcResult<(), N> { + ) -> WRpcResult { let tx = tx.take(); let block_height = self.best_block().await?.height; check_transaction(&self.chain_config, block_height, &tx).map_err(|err| { @@ -361,7 +363,9 @@ impl WalletRpc { WalletError::InvalidTransaction(err), )) })?; - self.node.submit_transaction(tx, options).await.map_err(RpcError::RpcError) + let tx_id = tx.transaction().get_id(); + self.node.submit_transaction(tx, options).await.map_err(RpcError::RpcError)?; + Ok(NewTransaction { tx_id }) } pub async fn sign_raw_transaction( @@ -695,10 +699,13 @@ impl WalletRpc { &self, inputs: Vec, outputs: Vec, - ) -> WRpcResult<(PartiallySignedTransaction, Balances), N> { + only_transaction: bool, + ) -> WRpcResult<(TransactionToSign, Balances), N> { self.wallet .call_async(move |w| { - Box::pin(async move { w.compose_transaction(inputs, outputs).await }) + Box::pin( + async move { w.compose_transaction(inputs, outputs, only_transaction).await }, + ) }) .await? } diff --git a/wallet/wallet-rpc-lib/src/rpc/server_impl.rs b/wallet/wallet-rpc-lib/src/rpc/server_impl.rs index 35103344c4..bfbe0f6fed 100644 --- a/wallet/wallet-rpc-lib/src/rpc/server_impl.rs +++ b/wallet/wallet-rpc-lib/src/rpc/server_impl.rs @@ -185,7 +185,7 @@ impl WalletRpcServer f &self, tx: HexEncoded, options: TxOptionsOverrides, - ) -> rpc::RpcResult<()> { + ) -> rpc::RpcResult { rpc::handle_result(self.submit_raw_transaction(tx, options).await) }