From 36c9a7145757472b1a62f9a4f6d62623ca9c3124 Mon Sep 17 00:00:00 2001 From: Boris Oncev Date: Wed, 26 Feb 2025 10:55:26 +0100 Subject: [PATCH] add support for stake pool creation from trezor and tests --- Cargo.lock | 22 ++- wallet/src/account/mod.rs | 185 +++++++++++------- wallet/src/send_request/mod.rs | 20 +- wallet/src/signer/software_signer/tests.rs | 53 +++-- wallet/src/signer/trezor_signer/tests.rs | 44 +++-- wallet/src/wallet/mod.rs | 34 +++- wallet/src/wallet/tests.rs | 36 ++++ .../wallet-controller/src/runtime_wallet.rs | 7 +- wallet/wallet-rpc-daemon/docs/RPC.md | 14 +- 9 files changed, 287 insertions(+), 128 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2c0bbec79..aad95674a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -726,9 +726,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +checksum = "c103cbbedac994e292597ab79342dbd5b306a362045095db54917d92a9fdfd92" [[package]] name = "bb8" @@ -1369,9 +1369,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.39" +version = "0.4.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" +checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" dependencies = [ "android-tzdata", "iana-time-zone", @@ -1379,7 +1379,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -8528,7 +8528,7 @@ dependencies = [ [[package]] name = "trezor-client" version = "0.1.4" -source = "git+https://github.com/mintlayer/mintlayer-trezor-firmware?branch=feature%2Fmintlayer-pk#bf2afd081d57d7e03f986acf5162f9ce71932741" +source = "git+https://github.com/mintlayer/mintlayer-trezor-firmware?branch=feature%2Fmintlayer-pk#1412336d50b0aa2c7dcbd633353a31c9645649ef" dependencies = [ "bitcoin", "byteorder", @@ -8825,9 +8825,9 @@ dependencies = [ [[package]] name = "uuid" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d59ca99a559661b96bf898d8fce28ed87935fd2bea9f05983c1464dd6c71b1" +checksum = "bd8dcafa1ca14750d8d7a05aa05988c17aab20886e1f3ae33a40223c58d92ef7" [[package]] name = "valuable" @@ -9745,6 +9745,12 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "windows-link" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3" + [[package]] name = "windows-result" version = "0.2.0" diff --git a/wallet/src/account/mod.rs b/wallet/src/account/mod.rs index af921fc35..fe7e49c37 100644 --- a/wallet/src/account/mod.rs +++ b/wallet/src/account/mod.rs @@ -433,7 +433,10 @@ impl Account { } } - let selected_inputs = selected_inputs.into_iter().flat_map(|x| x.1.into_output_pairs()); + let selected_inputs = selected_inputs + .into_iter() + .flat_map(|x| x.1.into_output_pairs()) + .map(|(inp, out)| (inp, out, None)); let pool_data_getter = |pool_id: &PoolId| self.output_cache.pool_data(*pool_id).ok(); request.with_inputs(selected_inputs, &pool_data_getter) @@ -728,7 +731,7 @@ impl Account { .ok_or(WalletError::NoUtxos)?; let mut req = SendRequest::new() - .with_inputs([(tx_input, input_utxo.clone())], &|id| { + .with_inputs([(tx_input, input_utxo.clone(), None)], &|id| { (*id == pool_id).then_some(pool_data) })? .with_outputs([output]); @@ -1324,6 +1327,107 @@ impl Account { ) } + pub fn create_stake_pool_tx_with_vrf_key( + &mut self, + db_tx: &mut impl WalletStorageWriteUnlocked, + mut stake_pool_arguments: StakePoolCreationArguments, + median_time: BlockTimestamp, + fee_rate: CurrentFeeRate, + ) -> WalletResult { + let Some(vrf_public_key) = stake_pool_arguments.vrf_public_key.take() else { + return Err(WalletError::VrfKeyMustBeProvided); + }; + + self.create_stake_pool_tx_impl( + stake_pool_arguments, + db_tx, + vrf_public_key, + median_time, + fee_rate, + ) + } + + fn create_stake_pool_tx_impl( + &mut self, + stake_pool_arguments: StakePoolCreationArguments, + db_tx: &mut impl WalletStorageWriteUnlocked, + vrf_public_key: VRFPublicKey, + median_time: BlockTimestamp, + fee_rate: CurrentFeeRate, + ) -> Result { + let staker = match stake_pool_arguments.staker_key { + Some(staker) => match staker { + Destination::PublicKey(_) => staker, + // Note: technically it's possible to create a pool with PublicKeyHash as the staker, + // the pool will seem to work and will actually try producing blocks. However, + // the produced blocks will be rejected by chainstate, see get_staking_kernel_destination + // in `consensus`. + Destination::AnyoneCanSpend + | Destination::PublicKeyHash(_) + | Destination::ScriptHash(_) + | Destination::ClassicMultisig(_) => { + return Err(WalletError::StakerDestinationMustBePublicKey) + } + }, + None => Destination::PublicKey( + self.key_chain.issue_key(db_tx, KeyPurpose::ReceiveFunds)?.into_public_key(), + ), + }; + + // the first UTXO is needed in advance to calculate pool_id, so just make a dummy one + // and then replace it with when we can calculate the pool_id + let dummy_pool_id = PoolId::new(Uint256::from_u64(0).into()); + let dummy_stake_output = make_stake_output( + dummy_pool_id, + StakePoolCreationResolvedArguments { + amount: stake_pool_arguments.amount, + margin_ratio_per_thousand: stake_pool_arguments.margin_ratio_per_thousand, + cost_per_block: stake_pool_arguments.cost_per_block, + decommission_key: stake_pool_arguments.decommission_key, + staker_key: staker, + vrf_public_key, + }, + ); + let request = SendRequest::new().with_outputs([dummy_stake_output]); + let mut request = self.select_inputs_for_send_request( + request, + SelectedInputs::Utxos(vec![]), + None, + BTreeMap::new(), + db_tx, + median_time, + fee_rate, + None, + )?; + + let input0_outpoint = crate::utils::get_first_utxo_outpoint(request.inputs())?; + let new_pool_id = pos_accounting::make_pool_id(input0_outpoint); + + // update the dummy_pool_id with the new pool_id + let old_pool_id = request + .get_outputs_mut() + .iter_mut() + .find_map(|out| match out { + TxOutput::CreateStakePool(pool_id, _) if *pool_id == dummy_pool_id => Some(pool_id), + TxOutput::CreateStakePool(_, _) + | TxOutput::Burn(_) + | TxOutput::Transfer(_, _) + | TxOutput::DelegateStaking(_, _) + | TxOutput::LockThenTransfer(_, _, _) + | TxOutput::CreateDelegationId(_, _) + | TxOutput::ProduceBlockFromStake(_, _) + | TxOutput::IssueFungibleToken(_) + | TxOutput::IssueNft(_, _, _) + | TxOutput::DataDeposit(_) + | TxOutput::Htlc(_, _) + | TxOutput::CreateOrder(_) => None, + }) + .expect("find output with dummy_pool_id"); + *old_pool_id = new_pool_id; + + Ok(request) + } + pub fn pool_exists(&self, pool_id: PoolId) -> bool { self.output_cache.pool_data(pool_id).is_ok() } @@ -2200,86 +2304,21 @@ impl Account { pub fn create_stake_pool_tx( &mut self, db_tx: &mut impl WalletStorageWriteUnlocked, - stake_pool_arguments: StakePoolCreationArguments, + mut stake_pool_arguments: StakePoolCreationArguments, median_time: BlockTimestamp, fee_rate: CurrentFeeRate, ) -> WalletResult { - // TODO: Use other accounts here - let staker = match stake_pool_arguments.staker_key { - Some(staker) => match staker { - Destination::PublicKey(_) => staker, - // Note: technically it's possible to create a pool with PublicKeyHash as the staker, - // the pool will seem to work and will actually try producing blocks. However, - // the produced blocks will be rejected by chainstate, see get_staking_kernel_destination - // in `consensus`. - Destination::AnyoneCanSpend - | Destination::PublicKeyHash(_) - | Destination::ScriptHash(_) - | Destination::ClassicMultisig(_) => { - return Err(WalletError::StakerDestinationMustBePublicKey) - } - }, - None => Destination::PublicKey( - self.key_chain.issue_key(db_tx, KeyPurpose::ReceiveFunds)?.into_public_key(), - ), - }; - let vrf_public_key = match stake_pool_arguments.vrf_public_key { + let vrf_public_key = match stake_pool_arguments.vrf_public_key.take() { Some(vrf_public_key) => vrf_public_key, None => self.get_vrf_public_key(db_tx)?, }; - - // the first UTXO is needed in advance to calculate pool_id, so just make a dummy one - // and then replace it with when we can calculate the pool_id - let dummy_pool_id = PoolId::new(Uint256::from_u64(0).into()); - let dummy_stake_output = make_stake_output( - dummy_pool_id, - StakePoolCreationResolvedArguments { - amount: stake_pool_arguments.amount, - margin_ratio_per_thousand: stake_pool_arguments.margin_ratio_per_thousand, - cost_per_block: stake_pool_arguments.cost_per_block, - decommission_key: stake_pool_arguments.decommission_key, - staker_key: staker, - vrf_public_key, - }, - ); - let request = SendRequest::new().with_outputs([dummy_stake_output]); - let mut request = self.select_inputs_for_send_request( - request, - SelectedInputs::Utxos(vec![]), - None, - BTreeMap::new(), + self.create_stake_pool_tx_impl( + stake_pool_arguments, db_tx, + vrf_public_key, median_time, fee_rate, - None, - )?; - - let input0_outpoint = crate::utils::get_first_utxo_outpoint(request.inputs())?; - let new_pool_id = pos_accounting::make_pool_id(input0_outpoint); - - // update the dummy_pool_id with the new pool_id - let old_pool_id = request - .get_outputs_mut() - .iter_mut() - .find_map(|out| match out { - TxOutput::CreateStakePool(pool_id, _) if *pool_id == dummy_pool_id => Some(pool_id), - TxOutput::CreateStakePool(_, _) - | TxOutput::Burn(_) - | TxOutput::Transfer(_, _) - | TxOutput::DelegateStaking(_, _) - | TxOutput::LockThenTransfer(_, _, _) - | TxOutput::CreateDelegationId(_, _) - | TxOutput::ProduceBlockFromStake(_, _) - | TxOutput::IssueFungibleToken(_) - | TxOutput::IssueNft(_, _, _) - | TxOutput::DataDeposit(_) - | TxOutput::Htlc(_, _) - | TxOutput::CreateOrder(_) => None, - }) - .expect("find output with dummy_pool_id"); - *old_pool_id = new_pool_id; - - Ok(request) + ) } } diff --git a/wallet/src/send_request/mod.rs b/wallet/src/send_request/mod.rs index b1acaa277..98e6f2761 100644 --- a/wallet/src/send_request/mod.rs +++ b/wallet/src/send_request/mod.rs @@ -17,6 +17,7 @@ use std::collections::BTreeMap; use std::mem::take; use common::address::Address; +use common::chain::htlc::HtlcSecret; use common::chain::output_value::OutputValue; use common::chain::stakelock::StakePoolData; use common::chain::timelock::OutputTimeLock::ForBlockCount; @@ -51,6 +52,8 @@ pub struct SendRequest { outputs: Vec, + htlc_secrets: Vec>, + fees: BTreeMap, } @@ -196,6 +199,7 @@ impl SendRequest { destinations: Vec::new(), inputs: Vec::new(), outputs: Vec::new(), + htlc_secrets: Vec::new(), fees: BTreeMap::new(), } } @@ -230,6 +234,7 @@ impl SendRequest { destinations, inputs: transaction.inputs().to_vec(), outputs: transaction.outputs().to_vec(), + htlc_secrets: vec![None; transaction.inputs().len()], fees: BTreeMap::new(), }) } @@ -258,6 +263,7 @@ impl SendRequest { self.inputs.push(outpoint); self.destinations.push(destination); self.utxos.push(None); + self.htlc_secrets.push(None); } self @@ -265,21 +271,27 @@ impl SendRequest { pub fn with_inputs<'a, PoolDataGetter>( mut self, - utxos: impl IntoIterator, + utxos: impl IntoIterator)>, pool_data_getter: &PoolDataGetter, ) -> WalletResult where PoolDataGetter: Fn(&PoolId) -> Option<&'a PoolData>, { - for (outpoint, txo) in utxos { + for (outpoint, txo, secret) in utxos { self.inputs.push(outpoint); + let htlc_spending_condition = match &secret { + Some(_) => HtlcSpendingCondition::WithSecret, + None => HtlcSpendingCondition::WithMultisig, + }; + self.destinations.push( - get_tx_output_destination(&txo, &pool_data_getter, HtlcSpendingCondition::Skip) + get_tx_output_destination(&txo, &pool_data_getter, htlc_spending_condition) .ok_or_else(|| { WalletError::UnsupportedTransactionOutput(Box::new(txo.clone())) })?, ); self.utxos.push(Some(txo)); + self.htlc_secrets.push(secret); } Ok(self) @@ -312,7 +324,7 @@ impl SendRequest { vec![None; num_inputs], utxos, destinations, - None, + Some(self.htlc_secrets), additional_info, )?; Ok(ptx) diff --git a/wallet/src/signer/software_signer/tests.rs b/wallet/src/signer/software_signer/tests.rs index 37304b39f..75f350960 100644 --- a/wallet/src/signer/software_signer/tests.rs +++ b/wallet/src/signer/software_signer/tests.rs @@ -35,6 +35,7 @@ use common::primitives::per_thousand::PerThousand; use common::primitives::{Amount, BlockHeight, Id, H256}; use crypto::key::secp256k1::Secp256k1PublicKey; use crypto::key::{KeyKind, PublicKey, Signature}; +use itertools::izip; use randomness::{Rng, RngCore}; use rstest::rstest; use serialization::Encode; @@ -172,6 +173,7 @@ fn sign_transaction(#[case] seed: Seed) { primitives::amount::UnsignedIntType, }; use crypto::vrf::VRFPrivateKey; + use itertools::izip; use serialization::extras::non_empty_vec::DataOrNoVec; let mut rng = make_seedable_rng(seed); @@ -246,21 +248,35 @@ fn sign_transaction(#[case] seed: Seed) { let challenge = ClassicMultisigChallenge::new( &chain_config, NonZeroU8::new(min_required_signatures).unwrap(), - vec![pub_key1, pub_key2, pub_key3], + vec![pub_key1.clone(), pub_key2.clone(), pub_key3], ) .unwrap(); let multisig_hash = account.add_standalone_multisig(&mut db_tx, challenge, None).unwrap(); let multisig_dest = Destination::ClassicMultisig(multisig_hash); - let source_id = if rng.gen_bool(0.5) { + let source_id: OutPointSourceId = if rng.gen_bool(0.5) { Id::::new(H256::random_using(&mut rng)).into() } else { Id::::new(H256::random_using(&mut rng)).into() }; - let multisig_input = TxInput::from_utxo(source_id, rng.next_u32()); + let multisig_input = TxInput::from_utxo(source_id.clone(), rng.next_u32()); let multisig_utxo = TxOutput::Transfer(OutputValue::Coin(Amount::from_atoms(1)), multisig_dest); + let secret = HtlcSecret::new_from_rng(&mut rng); + let hash_lock = HashedTimelockContract { + secret_hash: secret.hash(), + spend_key: Destination::PublicKey(pub_key2.clone()), + refund_timelock: OutputTimeLock::UntilHeight(BlockHeight::new(0)), + refund_key: Destination::PublicKey(pub_key1), + }; + + let htlc_input = TxInput::from_utxo(source_id, rng.next_u32()); + let htlc_utxo = TxOutput::Htlc( + OutputValue::Coin(Amount::from_atoms(rng.gen::() as u128)), + Box::new(hash_lock.clone()), + ); + let acc_inputs = vec![ TxInput::Account(AccountOutPoint::new( AccountNonce::new(0), @@ -380,13 +396,6 @@ fn sign_transaction(#[case] seed: Seed) { }); let nft_id = TokenId::new(H256::random()); - let hash_lock = HashedTimelockContract { - secret_hash: common::chain::htlc::HtlcSecretHash([1; 20]), - spend_key: Destination::PublicKey(dest_pub.clone()), - refund_timelock: OutputTimeLock::UntilHeight(BlockHeight::new(123)), - refund_key: Destination::AnyoneCanSpend, - }; - let order_data = OrderData::new( Destination::PublicKey(dest_pub.clone()), OutputValue::Coin(Amount::from_atoms(100)), @@ -426,10 +435,18 @@ fn sign_transaction(#[case] seed: Seed) { ]; let req = SendRequest::new() - .with_inputs(inputs.clone().into_iter().zip(utxos.clone()), &|_| None) + .with_inputs( + izip!(inputs.clone(), utxos.clone(), vec![None; inputs.len()]), + &|_| None, + ) + .unwrap() + .with_inputs( + [(htlc_input.clone(), htlc_utxo.clone(), Some(secret))], + &|_| None, + ) .unwrap() .with_inputs( - [multisig_input.clone()].into_iter().zip([multisig_utxo.clone()]), + [(multisig_input.clone(), multisig_utxo.clone(), None)], &|_| None, ) .unwrap() @@ -443,12 +460,15 @@ fn sign_transaction(#[case] seed: Seed) { let (ptx, _, _) = signer.sign_tx(ptx, account.key_chain(), &db_tx).unwrap(); eprintln!("num inputs in tx: {} {:?}", inputs.len(), ptx.witnesses()); + for (i, w) in ptx.witnesses().iter().enumerate() { + eprintln!("W: {i} {w:?}"); + } assert!(ptx.all_signatures_available()); let utxos_ref = utxos .iter() .map(Some) - .chain([Some(&multisig_utxo)]) + .chain([Some(&htlc_utxo), Some(&multisig_utxo)]) .chain(acc_dests.iter().map(|_| None)) .collect::>(); @@ -676,10 +696,13 @@ fn fixed_signatures() { ]; let req = SendRequest::new() - .with_inputs(inputs.clone().into_iter().zip(utxos.clone()), &|_| None) + .with_inputs( + izip!(inputs.clone(), utxos.clone(), vec![None; inputs.len()]), + &|_| None, + ) .unwrap() .with_inputs( - [multisig_input.clone()].into_iter().zip([multisig_utxo.clone()]), + [(multisig_input.clone(), multisig_utxo.clone(), None)], &|_| None, ) .unwrap() diff --git a/wallet/src/signer/trezor_signer/tests.rs b/wallet/src/signer/trezor_signer/tests.rs index fd0ee1228..84ce0064a 100644 --- a/wallet/src/signer/trezor_signer/tests.rs +++ b/wallet/src/signer/trezor_signer/tests.rs @@ -23,7 +23,7 @@ use crate::{ }; use common::chain::{ config::create_regtest, - htlc::HashedTimelockContract, + htlc::{HashedTimelockContract, HtlcSecret}, output_value::OutputValue, signature::inputsig::arbitrary_message::produce_message_challenge, stakelock::StakePoolData, @@ -34,6 +34,7 @@ use common::chain::{ }; use common::primitives::{per_thousand::PerThousand, Amount, BlockHeight, Id, H256}; use crypto::key::{KeyKind, PrivateKey}; +use itertools::izip; use randomness::{Rng, RngCore}; use rstest::rstest; use serial_test::serial; @@ -276,21 +277,35 @@ fn sign_transaction(#[case] seed: Seed) { let challenge = ClassicMultisigChallenge::new( &chain_config, NonZeroU8::new(min_required_signatures).unwrap(), - vec![pub_key1, pub_key2, pub_key3], + vec![pub_key1.clone(), pub_key2.clone(), pub_key3], ) .unwrap(); let multisig_hash = account.add_standalone_multisig(&mut db_tx, challenge, None).unwrap(); let multisig_dest = Destination::ClassicMultisig(multisig_hash); - let source_id = if rng.gen_bool(0.5) { + let source_id: OutPointSourceId = if rng.gen_bool(0.5) { Id::::new(H256::random_using(&mut rng)).into() } else { Id::::new(H256::random_using(&mut rng)).into() }; - let multisig_input = TxInput::from_utxo(source_id, rng.next_u32()); + let multisig_input = TxInput::from_utxo(source_id.clone(), rng.next_u32()); let multisig_utxo = TxOutput::Transfer(OutputValue::Coin(Amount::from_atoms(1)), multisig_dest); + let secret = HtlcSecret::new_from_rng(&mut rng); + let hash_lock = HashedTimelockContract { + secret_hash: secret.hash(), + spend_key: Destination::PublicKey(pub_key2.clone()), + refund_timelock: OutputTimeLock::UntilHeight(BlockHeight::new(0)), + refund_key: Destination::PublicKey(pub_key1), + }; + + let htlc_input = TxInput::from_utxo(source_id, rng.next_u32()); + let htlc_utxo = TxOutput::Htlc( + OutputValue::Coin(Amount::from_atoms(rng.gen::() as u128)), + Box::new(hash_lock.clone()), + ); + let token_id = TokenId::new(H256::random_using(&mut rng)); let order_id = OrderId::new(H256::random_using(&mut rng)); @@ -403,13 +418,6 @@ fn sign_transaction(#[case] seed: Seed) { }); let nft_id = TokenId::new(H256::random()); - let hash_lock = HashedTimelockContract { - secret_hash: common::chain::htlc::HtlcSecretHash([1; 20]), - spend_key: Destination::PublicKey(dest_pub.clone()), - refund_timelock: OutputTimeLock::UntilHeight(BlockHeight::new(123)), - refund_key: Destination::AnyoneCanSpend, - }; - let order_data = OrderData::new( Destination::PublicKey(dest_pub.clone()), OutputValue::Coin(Amount::from_atoms(100)), @@ -449,10 +457,18 @@ fn sign_transaction(#[case] seed: Seed) { ]; let req = SendRequest::new() - .with_inputs(inputs.clone().into_iter().zip(utxos.clone()), &|_| None) + .with_inputs( + izip!(inputs.clone(), utxos.clone(), vec![None; inputs.len()]), + &|_| None, + ) + .unwrap() + .with_inputs( + [(htlc_input.clone(), htlc_utxo.clone(), Some(secret))], + &|_| None, + ) .unwrap() .with_inputs( - [multisig_input.clone()].into_iter().zip([multisig_utxo.clone()]), + [(multisig_input.clone(), multisig_utxo.clone(), None)], &|_| None, ) .unwrap() @@ -490,7 +506,7 @@ fn sign_transaction(#[case] seed: Seed) { let utxos_ref = utxos .iter() .map(Some) - .chain([Some(&multisig_utxo)]) + .chain([Some(&htlc_utxo), Some(&multisig_utxo)]) .chain(acc_dests.iter().map(|_| None)) .collect::>(); diff --git a/wallet/src/wallet/mod.rs b/wallet/src/wallet/mod.rs index be4be2b72..14fa64b38 100644 --- a/wallet/src/wallet/mod.rs +++ b/wallet/src/wallet/mod.rs @@ -256,6 +256,10 @@ pub enum WalletError { CalculateOrderFilledAmountFailed(OrderId), #[error("Staker destination must be a public key")] StakerDestinationMustBePublicKey, + #[error( + "A VRF public key must be specified when creating a staking pool using a hardware wallet" + )] + VrfKeyMustBeProvided, #[error("Cannot change a Trezor wallet type")] CannotChangeTrezorWalletType, #[error("The file being loaded is a software wallet and does not correspond to the connected hardware wallet")] @@ -1575,7 +1579,9 @@ where additional_info: TxAdditionalInfo, ) -> WalletResult { let request = SendRequest::new().with_inputs( - inputs.into_iter().map(|(outpoint, output)| (TxInput::Utxo(outpoint), output)), + inputs + .into_iter() + .map(|(outpoint, output)| (TxInput::Utxo(outpoint), output, None)), &|_| None, )?; @@ -1915,6 +1921,31 @@ where Ok((token_id, signed_transaction)) } + pub fn create_stake_pool_tx_with_vrf_key( + &mut self, + account_index: U31, + current_fee_rate: FeeRate, + consolidate_fee_rate: FeeRate, + stake_pool_arguments: StakePoolCreationArguments, + ) -> WalletResult { + let latest_median_time = self.latest_median_time; + self.for_account_rw_unlocked_and_check_tx( + account_index, + TxAdditionalInfo::new(), + |account, db_tx| { + account.create_stake_pool_tx_with_vrf_key( + db_tx, + stake_pool_arguments, + latest_median_time, + CurrentFeeRate { + current_fee_rate, + consolidate_fee_rate, + }, + ) + }, + ) + } + pub fn decommission_stake_pool( &mut self, account_index: U31, @@ -2377,6 +2408,7 @@ where }, ) } + pub fn get_pos_gen_block_data( &self, account_index: U31, diff --git a/wallet/src/wallet/tests.rs b/wallet/src/wallet/tests.rs index ca94b706c..6e2ce0c24 100644 --- a/wallet/src/wallet/tests.rs +++ b/wallet/src/wallet/tests.rs @@ -1604,6 +1604,23 @@ fn create_stake_pool_and_list_pool_ids(#[case] seed: Seed) { .unwrap(); let decommission_key = Destination::PublicKey(stadalone_public_key); + let err = wallet + .create_stake_pool_tx_with_vrf_key( + DEFAULT_ACCOUNT_INDEX, + FeeRate::from_amount_per_kb(Amount::ZERO), + FeeRate::from_amount_per_kb(Amount::ZERO), + StakePoolCreationArguments { + amount: pool_amount, + margin_ratio_per_thousand: PerThousand::new_from_rng(&mut rng), + cost_per_block: Amount::ZERO, + decommission_key: decommission_key.clone(), + staker_key: None, + vrf_public_key: None, + }, + ) + .unwrap_err(); + assert_eq!(err, WalletError::VrfKeyMustBeProvided); + let stake_pool_transaction = wallet .create_stake_pool_tx( DEFAULT_ACCOUNT_INDEX, @@ -1829,6 +1846,25 @@ fn create_stake_pool_for_different_wallet_and_list_pool_ids(#[case] seed: Seed) ) .unwrap(); let stake_pool_transaction_id = stake_pool_transaction.transaction().get_id(); + + let stake_pool_transaction2 = wallet1 + .create_stake_pool_tx_with_vrf_key( + DEFAULT_ACCOUNT_INDEX, + FeeRate::from_amount_per_kb(Amount::ZERO), + FeeRate::from_amount_per_kb(Amount::ZERO), + StakePoolCreationArguments { + amount: pool_amount, + margin_ratio_per_thousand, + cost_per_block, + decommission_key: decommission_dest.clone(), + staker_key: Some(staker_key_dest.clone()), + vrf_public_key: Some(staker_vrf_public_key.clone()), + }, + ) + .unwrap(); + let stake_pool_transaction_id2 = stake_pool_transaction2.transaction().get_id(); + assert_eq!(stake_pool_transaction_id, stake_pool_transaction_id2); + let (_, block2) = create_block( &chain_config, &mut wallet1, diff --git a/wallet/wallet-controller/src/runtime_wallet.rs b/wallet/wallet-controller/src/runtime_wallet.rs index 42ec9dc38..d4f3577f1 100644 --- a/wallet/wallet-controller/src/runtime_wallet.rs +++ b/wallet/wallet-controller/src/runtime_wallet.rs @@ -1017,7 +1017,12 @@ impl RuntimeWallet { stake_pool_arguments, ), #[cfg(feature = "trezor")] - RuntimeWallet::Trezor(_) => Err(WalletError::UnsupportedHardwareWalletOperation), + RuntimeWallet::Trezor(w) => w.create_stake_pool_tx_with_vrf_key( + account_index, + current_fee_rate, + consolidate_fee_rate, + stake_pool_arguments, + ), } } diff --git a/wallet/wallet-rpc-daemon/docs/RPC.md b/wallet/wallet-rpc-daemon/docs/RPC.md index aa90ad7ba..80cd68652 100644 --- a/wallet/wallet-rpc-daemon/docs/RPC.md +++ b/wallet/wallet-rpc-daemon/docs/RPC.md @@ -2736,12 +2736,7 @@ Returns: 1) { "type": "UserProvided" } 2) { "type": "NewlyGenerated", - "content": { - "mnemonic": string, - "passphrase": EITHER OF - 1) string - 2) null, - }, + "content": { "mnemonic": string }, } } ``` @@ -2773,12 +2768,7 @@ Returns: 1) { "type": "UserProvided" } 2) { "type": "NewlyGenerated", - "content": { - "mnemonic": string, - "passphrase": EITHER OF - 1) string - 2) null, - }, + "content": { "mnemonic": string }, } } ```