From ea166b749afec6e0e934df5d375fee1eb96558cc Mon Sep 17 00:00:00 2001 From: luna Date: Wed, 16 Oct 2024 19:10:25 -0700 Subject: [PATCH] Wallet Updates, Fixed clvm parsing issue, added condition utility functions --- cli/src/wallets/memory_wallet.rs | 209 ++++++++++++++++++++++++- cli/src/wallets/mod.rs | 34 ++++ cli/src/wallets/plotnft_utils.rs | 21 +++ core/src/blockchain/pending_payment.rs | 14 ++ core/src/clvm/program.rs | 2 +- 5 files changed, 276 insertions(+), 4 deletions(-) diff --git a/cli/src/wallets/memory_wallet.rs b/cli/src/wallets/memory_wallet.rs index 42ec4b0..57fc53d 100644 --- a/cli/src/wallets/memory_wallet.rs +++ b/cli/src/wallets/memory_wallet.rs @@ -1,5 +1,7 @@ -use crate::wallets::common::DerivationRecord; -use crate::wallets::{SecretKeyStore, Wallet, WalletInfo, WalletStore}; +use crate::wallets::common::{sign_coin_spends, DerivationRecord}; +use crate::wallets::{ + make_solution_from_conditions, SecretKeyStore, Wallet, WalletInfo, WalletStore, +}; use async_trait::async_trait; use blst::min_pk::SecretKey; use dashmap::DashMap; @@ -8,13 +10,21 @@ use dg_xch_clients::ClientSSLConfig; use dg_xch_core::blockchain::coin::Coin; use dg_xch_core::blockchain::coin_record::CoinRecord; use dg_xch_core::blockchain::coin_spend::CoinSpend; +use dg_xch_core::blockchain::condition_opcode::ConditionOpcode; +use dg_xch_core::blockchain::condition_with_args::ConditionWithArgs; +use dg_xch_core::blockchain::pending_payment::PendingPayment; use dg_xch_core::blockchain::sized_bytes::{Bytes32, Bytes48, SizedBytes}; +use dg_xch_core::blockchain::spend_bundle::SpendBundle; use dg_xch_core::blockchain::wallet_type::WalletType; +use dg_xch_core::clvm::program::{Program, SerializedProgram, NULL}; +use dg_xch_core::consensus::constants::ConsensusConstants; use dg_xch_keys::{master_sk_to_wallet_sk, master_sk_to_wallet_sk_unhardened}; use dg_xch_puzzles::p2_delegated_puzzle_or_hidden_puzzle::{ calculate_synthetic_secret_key, puzzle_hash_for_pk, DEFAULT_HIDDEN_PUZZLE_HASH, }; +use dg_xch_serialize::{hash_256, ChiaProtocolVersion, ChiaSerialize}; use log::{debug, error, info}; +use num_traits::ToPrimitive; use rand::rngs::StdRng; use rand::{Rng, SeedableRng}; use std::collections::{HashMap, HashSet}; @@ -426,7 +436,31 @@ pub struct MemoryWallet { pub config: MemoryWalletConfig, pub fullnode_client: FullnodeClient, } -impl MemoryWallet {} +impl MemoryWallet { + pub fn new( + master_secret_key: SecretKey, + client: &FullnodeClient, + constants: Arc, + ) -> Self { + Self::create( + WalletInfo { + id: 1, + name: "memory_wallet".to_string(), + wallet_type: WalletType::StandardWallet, + constants, + master_sk: master_secret_key.clone(), + wallet_store: Arc::new(Mutex::new(MemoryWalletStore::new(master_secret_key, 0))), + data: "".to_string(), + }, + MemoryWalletConfig { + fullnode_host: client.host.clone(), + fullnode_port: client.port, + fullnode_ssl_path: client.ssl_path.clone(), + additional_headers: client.additional_headers.clone(), + }, + ) + } +} #[async_trait] impl Wallet for MemoryWallet { fn create(info: WalletInfo, config: MemoryWalletConfig) -> Self { @@ -463,4 +497,173 @@ impl Wallet for MemoryWallet { fn wallet_store(&self) -> Arc> { self.info.wallet_store.clone() } + + async fn create_spend_bundle( + &self, + payments: &[PendingPayment], + input_coins: &[CoinRecord], + change_puzzle_hash: Option, + allow_excess: bool, + fee: i64, + surplus: i64, + origin_id: Option, + coins_to_assert: &[Bytes32], + coin_announcements_to_assert: Vec, + puzzle_announcements_to_assert: Vec, + additional_conditions: Vec, + solution_transformer: Option Program + 'static + Send + Sync>>, + ) -> Result { + let make_solution_fn = |conditions: Vec| -> Program { + let standard_solution = make_solution_from_conditions(conditions); + if let Some(solution_transformer) = &solution_transformer { + solution_transformer(standard_solution) + } else { + standard_solution + } + }; + let mut coins = input_coins.to_vec(); + let total_coin_value: u64 = coins.iter().map(|c| c.coin.amount).sum(); + let total_payment_value: u64 = payments.iter().map(|p| p.amount).sum(); + let change = total_coin_value as i64 - total_payment_value as i64 - fee - surplus; + if change_puzzle_hash.is_none() && change > 0 && !allow_excess { + return Err(Error::new( + ErrorKind::InvalidInput, + "Found change but not Change Puzzle Hash was provided.", + )); + } + let mut spends = vec![]; + let origin_index = match origin_id { + Some(origin_id) => { + match coins + .iter() + .enumerate() + .find(|(_, val)| val.coin.coin_id() == origin_id) + { + Some((index, _)) => index as i64, + None => -1i64, + } + } + None => 0i64, + }; + if origin_index == -1 { + return Err(Error::new( + ErrorKind::InvalidInput, + "Origin ID Not in Coin List", + )); + } + if origin_index != 0 { + let origin_coin = coins.remove(origin_index as usize); + coins.insert(0, origin_coin); + } + let mut primary_assert_coin_announcement: Option = None; + let mut first = true; + for coin in coins.iter() { + let mut solution = None; + // create output for origin coin + if first { + first = false; + let mut conditions = vec![]; + let mut created_coins = vec![]; + for payment in payments { + let send_create_coin_condition = payment.to_create_coin_condition(); + conditions.push(send_create_coin_condition); + created_coins.push(Coin { + parent_coin_info: coin.coin.coin_id(), + puzzle_hash: payment.puzzle_hash, + amount: payment.amount, + }); + } + if change > 0 { + if let Some(change_puzzle_hash) = change_puzzle_hash { + conditions.push(ConditionWithArgs { + opcode: ConditionOpcode::CreateCoin, + vars: vec![ + change_puzzle_hash.to_bytes(ChiaProtocolVersion::default()), + change.to_bytes(ChiaProtocolVersion::default()), + ], + }); + created_coins.push(Coin { + parent_coin_info: coin.coin.coin_id(), + puzzle_hash: change_puzzle_hash, + amount: change as u64, + }); + } + } + if fee > 0 { + conditions.push(ConditionWithArgs { + opcode: ConditionOpcode::ReserveFee, + vars: vec![fee.to_bytes(ChiaProtocolVersion::default())], + }); + } + conditions.extend(coin_announcements_to_assert.clone()); + conditions.extend(puzzle_announcements_to_assert.clone()); + conditions.extend(additional_conditions.clone()); + let existing_coins_message = coins.iter().fold(vec![], |mut v, coin| { + v.extend(coin.coin.coin_id().as_slice()); + v + }); + let created_coins_message = created_coins.iter().fold(vec![], |mut v, coin| { + v.extend(coin.coin_id().as_slice()); + v + }); + let mut to_hash = existing_coins_message.clone(); + to_hash.extend(created_coins_message.as_slice()); + let message = hash_256(to_hash); + conditions.push(ConditionWithArgs { + opcode: ConditionOpcode::CreateCoinAnnouncement, + vars: vec![message.clone()], + }); + for coin_id_to_assert in coins_to_assert.iter() { + conditions.push(ConditionWithArgs { + opcode: ConditionOpcode::AssertCoinAnnouncement, + vars: vec![ + coin_id_to_assert.to_bytes(ChiaProtocolVersion::default()), + message.clone(), + ], + }); + } + primary_assert_coin_announcement = Some(ConditionWithArgs { + opcode: ConditionOpcode::AssertCoinAnnouncement, + vars: vec![ + coin.coin.coin_id().to_bytes(ChiaProtocolVersion::default()), + message.clone(), + ], + }); + solution = Some(make_solution_fn(conditions)); + } else if let Some(primary_assert_coin_announcement) = &primary_assert_coin_announcement + { + let args = vec![primary_assert_coin_announcement.clone()]; + solution = Some(make_solution_fn(args)); + } + let puzzle = self.puzzle_for_puzzle_hash(&coin.coin.puzzle_hash).await?; + let coin_spend = CoinSpend { + coin: coin.coin.clone(), + puzzle_reveal: SerializedProgram::from(puzzle), + solution: SerializedProgram::from(solution.unwrap_or(NULL.clone())), + }; + spends.push(coin_spend); + } + let spend_bundle = sign_coin_spends( + spends, + |pub_key| { + let pub_key = *pub_key; + let wallet_store = self.wallet_store().clone(); + async move { + wallet_store + .lock() + .await + .secret_key_for_public_key(&pub_key) + .await + } + }, + &self.wallet_info().constants.agg_sig_me_additional_data, + self.wallet_info() + .constants + .max_block_cost_clvm + .to_u64() + .unwrap(), + ) + .await?; + Ok(spend_bundle) + } } diff --git a/cli/src/wallets/mod.rs b/cli/src/wallets/mod.rs index a26f255..d3c7820 100644 --- a/cli/src/wallets/mod.rs +++ b/cli/src/wallets/mod.rs @@ -5,13 +5,17 @@ use dashmap::mapref::one::Ref; use dashmap::DashMap; use dg_xch_core::blockchain::announcement::Announcement; use dg_xch_core::blockchain::coin::Coin; +use dg_xch_core::blockchain::coin_record::CoinRecord; use dg_xch_core::blockchain::coin_spend::CoinSpend; use dg_xch_core::blockchain::condition_opcode::ConditionOpcode; +use dg_xch_core::blockchain::condition_with_args::ConditionWithArgs; +use dg_xch_core::blockchain::pending_payment::PendingPayment; use dg_xch_core::blockchain::sized_bytes::{Bytes32, Bytes48}; use dg_xch_core::blockchain::spend_bundle::SpendBundle; use dg_xch_core::blockchain::transaction_record::{TransactionRecord, TransactionType}; use dg_xch_core::blockchain::wallet_type::{AmountWithPuzzlehash, WalletType}; use dg_xch_core::clvm::program::{Program, SerializedProgram}; +use dg_xch_core::clvm::sexp::NULL; use dg_xch_core::clvm::utils::INFINITE_COST; use dg_xch_core::consensus::constants::ConsensusConstants; use dg_xch_puzzles::p2_delegated_puzzle_or_hidden_puzzle::{ @@ -128,6 +132,22 @@ pub trait Wallet { dg_xch_puzzles::p2_delegated_puzzle_or_hidden_puzzle::puzzle_hash_for_pk(public_key) } #[allow(clippy::too_many_arguments)] + async fn create_spend_bundle( + &self, + payments: &[PendingPayment], + input_coins: &[CoinRecord], + change_puzzle_hash: Option, + allow_excess: bool, + fee: i64, + surplus: i64, + origin_id: Option, + coins_to_assert: &[Bytes32], + coin_announcements_to_assert: Vec, + puzzle_announcements_to_assert: Vec, + additional_conditions: Vec, + solution_transformer: Option Program + 'static + Send + Sync>>, + ) -> Result; + #[allow(clippy::too_many_arguments)] fn make_solution( &self, primaries: &[AmountWithPuzzlehash], @@ -661,3 +681,17 @@ pub fn compute_memos_for_spend( } Ok(memos) } +pub fn make_solution_from_program(program: Program) -> Program { + Program::to(vec![NULL.clone(), program.sexp.clone(), NULL.clone()]) +} +pub fn make_solution_from_conditions(conditions: Vec) -> Program { + make_solution_from_program(Program::to(vec![ + Program::to(0x01), + Program::to( + conditions + .iter() + .map(|c| Program::new(c.to_bytes(ChiaProtocolVersion::default()))) + .collect::>(), + ), + ])) +} diff --git a/cli/src/wallets/plotnft_utils.rs b/cli/src/wallets/plotnft_utils.rs index 8d9f98f..5b43d6b 100644 --- a/cli/src/wallets/plotnft_utils.rs +++ b/cli/src/wallets/plotnft_utils.rs @@ -8,11 +8,14 @@ use dg_xch_clients::rpc::full_node::FullnodeClient; use dg_xch_core::blockchain::announcement::Announcement; use dg_xch_core::blockchain::coin_record::CoinRecord; use dg_xch_core::blockchain::coin_spend::{compute_additions_with_cost, CoinSpend}; +use dg_xch_core::blockchain::condition_with_args::ConditionWithArgs; +use dg_xch_core::blockchain::pending_payment::PendingPayment; use dg_xch_core::blockchain::sized_bytes::{Bytes32, Bytes48}; use dg_xch_core::blockchain::spend_bundle::SpendBundle; use dg_xch_core::blockchain::transaction_record::{TransactionRecord, TransactionType}; use dg_xch_core::blockchain::tx_status::TXStatus; use dg_xch_core::blockchain::wallet_type::WalletType; +use dg_xch_core::clvm::program::Program; use dg_xch_core::consensus::constants::ConsensusConstants; use dg_xch_core::plots::PlotNft; use dg_xch_core::pool::PoolState; @@ -109,6 +112,24 @@ impl Wallet for PlotNFTWallet { fn wallet_store(&self) -> Arc> { self.info.wallet_store.clone() } + + async fn create_spend_bundle( + &self, + _payments: &[PendingPayment], + _input_coins: &[CoinRecord], + _change_puzzle_hash: Option, + _allow_excess: bool, + _fee: i64, + _surplus: i64, + _origin_id: Option, + _coins_to_assert: &[Bytes32], + _coin_announcements_to_assert: Vec, + _puzzle_announcements_to_assert: Vec, + _additional_conditions: Vec, + _solution_transformer: Option Program + 'static + Send + Sync>>, + ) -> Result { + todo!() + } } impl PlotNFTWallet { pub fn new( diff --git a/core/src/blockchain/pending_payment.rs b/core/src/blockchain/pending_payment.rs index 6624f46..faa5d84 100644 --- a/core/src/blockchain/pending_payment.rs +++ b/core/src/blockchain/pending_payment.rs @@ -1,5 +1,8 @@ +use crate::blockchain::condition_opcode::ConditionOpcode; +use crate::blockchain::condition_with_args::ConditionWithArgs; use crate::blockchain::sized_bytes::Bytes32; use dg_xch_macros::ChiaSerial; +use dg_xch_serialize::{ChiaProtocolVersion, ChiaSerialize}; use serde::{Deserialize, Serialize}; #[derive(ChiaSerial, Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] @@ -7,3 +10,14 @@ pub struct PendingPayment { pub puzzle_hash: Bytes32, pub amount: u64, } +impl PendingPayment { + pub fn to_create_coin_condition(&self) -> ConditionWithArgs { + ConditionWithArgs { + opcode: ConditionOpcode::CreateCoin, + vars: vec![ + self.puzzle_hash.to_bytes(ChiaProtocolVersion::default()), + self.amount.to_bytes(ChiaProtocolVersion::default()), + ], + } + } +} diff --git a/core/src/clvm/program.rs b/core/src/clvm/program.rs index d96ba30..ef70950 100644 --- a/core/src/clvm/program.rs +++ b/core/src/clvm/program.rs @@ -457,7 +457,7 @@ impl SerializedProgram { } pub fn from_hex(hex_str: &str) -> Result { Ok(SerializedProgram { - buffer: hex_to_bytes(hex_str).map_err(|_| { + buffer: hex_to_bytes(hex_str.trim()).map_err(|_| { Error::new( ErrorKind::InvalidData, "Failed to convert str to SerializedProgram",