Skip to content

Commit

Permalink
Wallet accept encoded transaction as well as PartaillySigned ones
Browse files Browse the repository at this point in the history
  • Loading branch information
OBorce committed Jan 26, 2024
1 parent ece8800 commit cb872b0
Show file tree
Hide file tree
Showing 10 changed files with 145 additions and 23 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

96 changes: 89 additions & 7 deletions wallet/src/account/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ use crate::send_request::{
};
use crate::wallet::WalletPoolsFilter;
use crate::wallet_events::{WalletEvents, WalletEventsNoOp};
use crate::{SendRequest, WalletError, WalletResult};
use crate::{get_tx_output_destination, SendRequest, WalletError, WalletResult};
use common::address::Address;
use common::chain::output_value::OutputValue;
use common::chain::signature::inputsig::standard_signature::StandardInputSignature;
Expand Down Expand Up @@ -90,6 +90,11 @@ pub struct CurrentFeeRate {
pub consolidate_fee_rate: FeeRate,
}

pub enum TransactionToSign {
Tx(Transaction),
Partial(PartiallySignedTransaction),
}

#[derive(Debug, Eq, PartialEq, Clone, Encode, Decode)]
pub struct PartiallySignedTransaction {
tx: Transaction,
Expand Down Expand Up @@ -1135,23 +1140,100 @@ impl Account {
PartiallySignedTransaction::new(tx, witnesses, input_utxos, destinations)
}

fn tx_to_partially_signed_tx(
&self,
tx: Transaction,
median_time: BlockTimestamp,
) -> WalletResult<PartiallySignedTransaction> {
let current_block_info = BlockInfo {
height: self.account_info.best_block_height(),
timestamp: median_time,
};

let (input_utxos, destinations) = tx
.inputs()
.iter()
.map(|tx_inp| match tx_inp {
TxInput::Utxo(outpoint) => {
// find utxo from cache
self.find_unspent_utxo_with_destination(outpoint, current_block_info)
}
TxInput::Account(acc_outpoint) => {
// find delegation destination
match acc_outpoint.account() {
AccountSpending::DelegationBalance(delegation_id, _) => self
.output_cache
.delegation_data(delegation_id)
.map(|data| (None, Some(data.destination.clone())))
.ok_or(WalletError::DelegationNotFound(*delegation_id)),
}
}
TxInput::AccountCommand(_, cmd) => {
// find authority of the token
match cmd {
AccountCommand::MintTokens(token_id, _)
| AccountCommand::UnmintTokens(token_id)
| AccountCommand::LockTokenSupply(token_id)
| AccountCommand::ChangeTokenAuthority(token_id, _)
| AccountCommand::FreezeToken(token_id, _)
| AccountCommand::UnfreezeToken(token_id) => self
.output_cache
.token_data(token_id)
.map(|data| (None, Some(data.authority.clone())))
.ok_or(WalletError::UnknownTokenId(*token_id)),
}
}
})
.collect::<WalletResult<Vec<_>>>()?
.into_iter()
.unzip();

let num_inputs = tx.inputs().len();
PartiallySignedTransaction::new(tx, vec![None; num_inputs], input_utxos, destinations)
}

fn find_unspent_utxo_with_destination(
&self,
outpoint: &UtxoOutPoint,
current_block_info: BlockInfo,
) -> WalletResult<(Option<TxOutput>, Option<Destination>)> {
let (txo, _) =
self.output_cache.find_unspent_unlocked_utxo(outpoint, current_block_info)?;

Ok((
Some(txo.clone()),
Some(
get_tx_output_destination(txo, &|pool_id| {
self.output_cache.pool_data(*pool_id).ok()
})
.ok_or(WalletError::InputCannotBeSpent(txo.clone()))?,
),
))
}

pub fn sign_raw_transaction(
&self,
tx: PartiallySignedTransaction,
tx: TransactionToSign,
median_time: BlockTimestamp,
db_tx: &impl WalletStorageReadUnlocked,
) -> WalletResult<PartiallySignedTransaction> {
let inputs_utxo_refs: Vec<_> = tx.input_utxos().iter().map(|u| u.as_ref()).collect();
let ptx = match tx {
TransactionToSign::Partial(ptx) => ptx,
TransactionToSign::Tx(tx) => self.tx_to_partially_signed_tx(tx, median_time)?,
};

let inputs_utxo_refs: Vec<_> = ptx.input_utxos().iter().map(|u| u.as_ref()).collect();

let witnesses = tx
let witnesses = ptx
.witnesses()
.iter()
.enumerate()
.map(|(i, witness)| match witness {
Some(w) => Ok(Some(w.clone())),
None => match tx.destinations().get(i).expect("cannot fail") {
None => match ptx.destinations().get(i).expect("cannot fail") {
Some(destination) => {
let s = self
.sign_input(tx.tx(), destination, i, &inputs_utxo_refs, db_tx)?
.sign_input(ptx.tx(), destination, i, &inputs_utxo_refs, db_tx)?
.ok_or(WalletError::InputCannotBeSigned)?;
Ok(Some(s.clone()))
}
Expand All @@ -1160,7 +1242,7 @@ impl Account {
})
.collect::<Result<Vec<_>, WalletError>>()?;

Ok(tx.new_witnesses(witnesses))
Ok(ptx.new_witnesses(witnesses))
}

pub fn account_index(&self) -> U31 {
Expand Down
2 changes: 1 addition & 1 deletion wallet/src/account/output_cache/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1034,7 +1034,7 @@ impl OutputCache {
})
}

fn find_unspent_unlocked_utxo(
pub fn find_unspent_unlocked_utxo(
&self,
utxo: &UtxoOutPoint,
current_block_info: BlockInfo,
Expand Down
9 changes: 6 additions & 3 deletions wallet/src/wallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use std::sync::Arc;
use crate::account::transaction_list::TransactionList;
use crate::account::{
Currency, CurrentFeeRate, DelegationData, PartiallySignedTransaction, PoolData,
UnconfirmedTokenInfo, UtxoSelectorError,
TransactionToSign, UnconfirmedTokenInfo, UtxoSelectorError,
};
use crate::key_chain::{
make_account_path, make_path_to_vrf_key, KeyChainError, MasterKeyChain, LOOKAHEAD_SIZE,
Expand Down Expand Up @@ -199,6 +199,8 @@ pub enum WalletError {
FullySignedTransactionInDecommissionReq,
#[error("Input cannot be signed")]
InputCannotBeSigned,
#[error("Input cannot be spent {0:?}")]
InputCannotBeSpent(TxOutput),
#[error("Failed to convert partially signed tx to signed")]
FailedToConvertPartiallySignedTx(PartiallySignedTransaction),
#[error("The specified address is not found in this wallet")]
Expand Down Expand Up @@ -1340,10 +1342,11 @@ impl<B: storage::Backend> Wallet<B> {
pub fn sign_raw_transaction(
&mut self,
account_index: U31,
tx: PartiallySignedTransaction,
tx: TransactionToSign,
) -> WalletResult<PartiallySignedTransaction> {
let latest_median_time = self.latest_median_time;
self.for_account_rw_unlocked(account_index, |account, db_tx, _| {
account.sign_raw_transaction(tx, db_tx)
account.sign_raw_transaction(tx, latest_median_time, db_tx)
})
}

Expand Down
25 changes: 21 additions & 4 deletions wallet/src/wallet/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3866,6 +3866,15 @@ fn sign_decommission_pool_request_between_accounts(#[case] seed: Seed) {
},
)
.unwrap();

// remove the signatures and try to sign it again
let tx = stake_pool_transaction.transaction().clone();
let stake_pool_transaction = wallet
.sign_raw_transaction(acc_0_index, TransactionToSign::Tx(tx))
.unwrap()
.into_signed_tx()
.unwrap();

let _ = create_block(
&chain_config,
&mut wallet,
Expand All @@ -3888,15 +3897,20 @@ fn sign_decommission_pool_request_between_accounts(#[case] seed: Seed) {
.unwrap();

// Try to sign decommission request with wrong account
let sign_from_acc0_res =
wallet.sign_raw_transaction(acc_0_index, decommission_partial_tx.clone());
let sign_from_acc0_res = wallet.sign_raw_transaction(
acc_0_index,
TransactionToSign::Partial(decommission_partial_tx.clone()),
);
assert_eq!(
sign_from_acc0_res.unwrap_err(),
WalletError::InputCannotBeSigned
);

let signed_tx = wallet
.sign_raw_transaction(acc_1_index, decommission_partial_tx)
.sign_raw_transaction(
acc_1_index,
TransactionToSign::Partial(decommission_partial_tx),
)
.unwrap()
.into_signed_tx()
.unwrap();
Expand Down Expand Up @@ -3986,7 +4000,10 @@ fn sign_decommission_pool_request_cold_wallet(#[case] seed: Seed) {

// sign the tx with cold wallet
let signed_tx = cold_wallet
.sign_raw_transaction(DEFAULT_ACCOUNT_INDEX, decommission_partial_tx)
.sign_raw_transaction(
DEFAULT_ACCOUNT_INDEX,
TransactionToSign::Partial(decommission_partial_tx),
)
.unwrap()
.into_signed_tx()
.unwrap();
Expand Down
4 changes: 2 additions & 2 deletions wallet/wallet-cli-lib/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,8 @@ pub enum ColdWalletCommand {
/// to the network.
#[clap(name = "account-sign-raw-transaction")]
SignRawTransaction {
/// Hex encoded transaction.
transaction: HexEncoded<PartiallySignedTransaction>,
/// Hex encoded transaction or PartiallySignedTransaction.
transaction: String,
},

/// Print command history in the wallet for this execution
Expand Down
4 changes: 2 additions & 2 deletions wallet/wallet-controller/src/synced_controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ use logging::log;
use mempool::FeeRate;
use node_comm::node_traits::NodeInterface;
use wallet::{
account::{PartiallySignedTransaction, UnconfirmedTokenInfo},
account::{PartiallySignedTransaction, TransactionToSign, UnconfirmedTokenInfo},
send_request::{
make_address_output, make_address_output_token, make_create_delegation_output,
make_data_deposit_output, StakePoolDataArguments,
Expand Down Expand Up @@ -618,7 +618,7 @@ impl<'a, T: NodeInterface, W: WalletEvents> SyncedController<'a, T, W> {

pub fn sign_raw_transaction(
&mut self,
tx: PartiallySignedTransaction,
tx: TransactionToSign,
) -> Result<PartiallySignedTransaction, ControllerError<T>> {
self.wallet
.sign_raw_transaction(self.account_index, tx)
Expand Down
1 change: 1 addition & 0 deletions wallet/wallet-rpc-lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ serde.workspace = true
serde_json.workspace = true
thiserror.workspace = true
tokio.workspace = true
hex.workspace = true

[dev-dependencies]

Expand Down
20 changes: 16 additions & 4 deletions wallet/wallet-rpc-lib/src/rpc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ use mempool_types::tx_options::TxOptionsOverrides;
use p2p_types::{
bannable_address::BannableAddress, ip_or_socket_address::IpOrSocketAddress, PeerId,
};
use serialization::hex_encoded::HexEncoded;
use serialization::{hex_encoded::HexEncoded, Decode, DecodeAll};
use std::{collections::BTreeMap, fmt::Debug, path::PathBuf, sync::Arc};
use utils::{ensure, shallow_clone::ShallowClone};
use wallet::{
account::{PartiallySignedTransaction, PoolData},
account::{PartiallySignedTransaction, PoolData, TransactionToSign},
WalletError,
};

Expand Down Expand Up @@ -349,16 +349,28 @@ impl<N: NodeInterface + Clone + Send + Sync + 'static> WalletRpc<N> {
pub async fn sign_raw_transaction(
&self,
account_index: U31,
tx: HexEncoded<PartiallySignedTransaction>,
raw_tx: String,
config: ControllerConfig,
) -> WRpcResult<PartiallySignedTransaction, N> {
let hex_bytes = hex::decode(raw_tx).map_err(|_| RpcError::InvalidRawTransaction)?;
let mut bytes = hex_bytes.as_slice();
let tx = Transaction::decode(&mut bytes).map_err(|_| RpcError::InvalidRawTransaction)?;
let tx_to_sign = if bytes.is_empty() {
TransactionToSign::Tx(tx)
} else {
let mut bytes = hex_bytes.as_slice();
let ptx = PartiallySignedTransaction::decode_all(&mut bytes)
.map_err(|_| RpcError::InvalidPartialTransaction)?;
TransactionToSign::Partial(ptx)
};

self.wallet
.call_async(move |controller| {
Box::pin(async move {
controller
.synced_controller(account_index, config)
.await?
.sign_raw_transaction(tx.take())
.sign_raw_transaction(tx_to_sign)
.map_err(RpcError::Controller)
})
})
Expand Down
6 changes: 6 additions & 0 deletions wallet/wallet-rpc-lib/src/rpc/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ pub enum RpcError<N: NodeInterface> {

#[error("{0}")]
SubmitError(#[from] SubmitError),

#[error("Invalid hex encoded transaction")]
InvalidRawTransaction,

#[error("Invalid hex encoded partially signed transaction")]
InvalidPartialTransaction,
}

impl<N: NodeInterface> From<RpcError<N>> for rpc::Error {
Expand Down

0 comments on commit cb872b0

Please sign in to comment.