Skip to content

Commit

Permalink
Merge with remote
Browse files Browse the repository at this point in the history
  • Loading branch information
luna committed Oct 25, 2023
2 parents d7ea0ea + f095ded commit 30aa2fd
Show file tree
Hide file tree
Showing 8 changed files with 343 additions and 34 deletions.
2 changes: 2 additions & 0 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ repository = "https://github.com/GalactechsLLC/dg_xch_utils/cli"

[lib]
name = "dg_xch_cli"
path = "src/lib.rs"

[[bin]]
name = "dg_xch_cli"
path = "src/main.rs"

[dependencies]
async-trait = "0.1.74"
Expand Down
11 changes: 10 additions & 1 deletion cli/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ pub enum RootCommands {
#[arg(short, long)]
include_spent_coins: bool,
},
#[command(about = "Gets coin records for a given address or puzzlehash", long_about = None)]
#[command(about = "Migrates a PlotNFT using a mnemonic", long_about = None)]
MovePlotNFT {
#[arg(short, long)]
target_pool: String,
Expand All @@ -42,6 +42,15 @@ pub enum RootCommands {
#[arg(short, long)]
fee: Option<u64>,
},
#[command(about = "Migrates a PlotNFT using an owner_secrey_key", long_about = None)]
MovePlotNFTWithOwnerKey {
#[arg(short, long)]
target_pool: String,
#[arg(short, long)]
launcher_id: String,
#[arg(short, long)]
owner_key: String,
},
#[command(about = "Gets plotnft state for launcher_id", long_about = None)]
GetPlotnftState {
#[arg(short, long)]
Expand Down
27 changes: 26 additions & 1 deletion cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
pub mod cli;
use blst::min_pk::SecretKey;
use clap::Parser;
use cli::*;
use dg_xch_cli::wallet_commands::{create_cold_wallet, get_plotnft_ready_state, migrate_plot_nft};
use dg_xch_cli::wallet_commands::{
create_cold_wallet, get_plotnft_ready_state, migrate_plot_nft, migrate_plot_nft_with_owner_key,
};
use dg_xch_clients::rpc::full_node::FullnodeClient;
use dg_xch_core::blockchain::sized_bytes::Bytes32;
use simple_logger::SimpleLogger;
Expand Down Expand Up @@ -40,6 +43,28 @@ async fn main() -> Result<(), Error> {
)
.await?
}
RootCommands::MovePlotNFTWithOwnerKey {
target_pool,
launcher_id,
owner_key,
} => {
let host = cli.fullnode_host.unwrap_or("localhost".to_string());
let client = FullnodeClient::new(
&host,
cli.fullnode_port.unwrap_or(8444),
cli.ssl_path,
&None,
);
let owner_key = SecretKey::from_bytes(Bytes32::from(&owner_key).as_ref())
.expect("Failed to Parse Owner Secret Key");
migrate_plot_nft_with_owner_key(
&client,
&target_pool,
&Bytes32::from(launcher_id),
&owner_key,
)
.await?
}
RootCommands::GetPlotnftState { launcher_id } => {
let host = cli.fullnode_host.unwrap_or("localhost".to_string());
let client = FullnodeClient::new(
Expand Down
117 changes: 114 additions & 3 deletions cli/src/wallet_commands.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::wallets::plotnft_utils::{
get_plotnft_by_launcher_id, submit_next_state_spend_bundle, PlotNFTWallet,
get_plotnft_by_launcher_id, submit_next_state_spend_bundle,
submit_next_state_spend_bundle_with_key, PlotNFTWallet,
};
use crate::wallets::Wallet;
use bip39::Mnemonic;
Expand All @@ -21,7 +22,8 @@ use dg_xch_puzzles::p2_delegated_puzzle_or_hidden_puzzle::{
use log::{debug, error, info};
use std::collections::{HashMap, HashSet};
use std::io::{Error, ErrorKind};
use std::time::Duration;
use std::ops::Add;
use std::time::{Duration, Instant};

pub fn create_cold_wallet() -> Result<(), Error> {
let mnemonic = Mnemonic::generate(24)
Expand Down Expand Up @@ -104,7 +106,11 @@ pub async fn migrate_plot_nft(
mnemonic: &str,
fee: u64,
) -> Result<(), Error> {
let pool_url = format!("https://{}", target_pool);
let pool_url = if target_pool.starts_with("https://") {
target_pool.to_string()
} else {
format!("https://{}", target_pool)
};
let pool_info = get_pool_info(&pool_url).await?;
let pool_wallet = PlotNFTWallet::new(key_from_mnemonic(mnemonic)?, client);
info!("Searching for PlotNFT with LauncherID: {launcher_id}");
Expand Down Expand Up @@ -171,6 +177,74 @@ pub async fn migrate_plot_nft(
}
Ok(())
}
pub async fn migrate_plot_nft_with_owner_key(
client: &FullnodeClient,
target_pool: &str,
launcher_id: &Bytes32,
owner_key: &SecretKey,
) -> Result<(), Error> {
let pool_url = if target_pool.starts_with("https://") {
target_pool.to_string()
} else {
format!("https://{}", target_pool)
};
let pool_info = get_pool_info(&pool_url).await?;
info!("Searching for PlotNFT with LauncherID: {launcher_id}");
if let Some(mut plot_nft) = get_plotnft_by_launcher_id(client, launcher_id).await? {
info!("Checking if PlotNFT needs migration");
if plot_nft.pool_state.pool_url.as_ref() != Some(&pool_url) {
info!("Starting Migration");
let target_pool_state =
create_and_validate_target_state(&pool_url, pool_info, &plot_nft)?;
if plot_nft.pool_state.state == FARMING_TO_POOL {
info!("Creating Leaving Pool Spend");
submit_next_state_spend_bundle_with_key(
client,
owner_key,
&plot_nft,
&target_pool_state,
&Default::default(),
)
.await?;
info!(
"Waiting for PlotNFT State to be Buried for Leaving {}",
plot_nft
.pool_state
.pool_url
.as_ref()
.unwrap_or(&String::from("None"))
);
wait_for_plot_nft_ready_state(client, launcher_id).await;
info!("Reloading PlotNFT Info");
plot_nft = get_plotnft_by_launcher_id(client, launcher_id)
.await?
.ok_or_else(|| {
error!("Failed to reload plot_nft after first spend");
Error::new(
ErrorKind::Other,
"Failed to reload plot_nft after first spend",
)
})?;
}
info!("Creating Farming to Pool Spend");
submit_next_state_spend_bundle_with_key(
client,
owner_key,
&plot_nft,
&target_pool_state,
&Default::default(),
)
.await?;
info!("Waiting for PlotNFT State to be Buried for Joining {pool_url}");
wait_for_num_blocks(client, 20, 600).await;
} else {
info!("PlotNFT Already on Selected Pool");
}
} else {
info!("No PlotNFT Found");
}
Ok(())
}

async fn wait_for_plot_nft_ready_state(client: &FullnodeClient, launcher_id: &Bytes32) {
loop {
Expand All @@ -193,6 +267,43 @@ async fn wait_for_plot_nft_ready_state(client: &FullnodeClient, launcher_id: &By
}
}

async fn wait_for_num_blocks(client: &FullnodeClient, height: u32, timeout_seconds: u64) {
let mut start_height = None;
let end_time = Instant::now().add(Duration::from_secs(timeout_seconds));
loop {
let now = Instant::now();
if now >= end_time {
break;
}
match client.get_blockchain_state().await {
Ok(state) => {
if let Some(peak) = state.peak {
if let Some(start) = start_height {
if peak.height > start + height {
break;
} else {
info!("Waiting for {} more blocks", start + height - peak.height);
tokio::time::sleep(std::cmp::min(
end_time.duration_since(now),
Duration::from_secs(10),
))
.await;
}
} else {
start_height = Some(peak.height);
}
}
}
Err(e) => {
error!(
"Error Checking PlotNFT State, Waiting and Trying again. {:?}",
e
);
}
}
}
}

fn create_and_validate_target_state(
pool_url: &str,
pool_info: GetPoolInfoResponse,
Expand Down
153 changes: 153 additions & 0 deletions cli/src/wallets/plotnft_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use dg_xch_puzzles::clvm_puzzles::{
use dg_xch_puzzles::p2_delegated_puzzle_or_hidden_puzzle::puzzle_hash_for_pk;
use log::info;
use num_traits::cast::ToPrimitive;
use std::future::Future;
use std::io::{Error, ErrorKind};
use std::sync::Arc;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
Expand Down Expand Up @@ -277,6 +278,104 @@ impl PlotNFTWallet {
}
}

pub async fn generate_travel_transaction_without_fee<F, Fut>(
client: &FullnodeClient,
key_fn: F,
plot_nft: &PlotNft,
target_pool_state: &PoolState,
constants: &ConsensusConstants,
) -> Result<(TransactionRecord, Option<TransactionRecord>), Error>
where
F: Fn(&Bytes48) -> Fut,
Fut: Future<Output = Result<SecretKey, Error>>,
{
let launcher_coin = client
.get_coin_record_by_name(&plot_nft.launcher_id)
.await?
.ok_or_else(|| Error::new(ErrorKind::Other, "Failed to load launcher_coin"))?;
let last_record = client
.get_coin_record_by_name(&plot_nft.singleton_coin.coin.parent_coin_info)
.await?
.ok_or_else(|| Error::new(ErrorKind::Other, "Failed to load launcher_coin"))?;
let last_coin_spend = client.get_coin_spend(&last_record).await?;
let next_state = if plot_nft.pool_state.state == FARMING_TO_POOL {
PoolState {
version: POOL_PROTOCOL_VERSION,
state: LEAVING_POOL,
target_puzzle_hash: plot_nft.pool_state.target_puzzle_hash,
owner_pubkey: plot_nft.pool_state.owner_pubkey,
pool_url: plot_nft.pool_state.pool_url.clone(),
relative_lock_height: plot_nft.pool_state.relative_lock_height,
}
} else {
target_pool_state.clone()
};
let new_inner_puzzle = pool_state_to_inner_puzzle(
&next_state,
&launcher_coin.coin.name(),
&constants.genesis_challenge,
plot_nft.delay_time as u64,
&plot_nft.delay_puzzle_hash,
)?;
let new_full_puzzle = create_full_puzzle(&new_inner_puzzle, &launcher_coin.coin.name())?;
let (outgoing_coin_spend, inner_puzzle) = create_travel_spend(
&last_coin_spend,
&launcher_coin.coin,
&plot_nft.pool_state,
&next_state,
&constants.genesis_challenge,
plot_nft.delay_time as u64,
&plot_nft.delay_puzzle_hash,
)?;
let (additions, _cost) = compute_additions_with_cost(
&last_coin_spend,
constants.max_block_cost_clvm.to_u64().unwrap(),
)?;
let singleton = &additions[0];
let singleton_id = singleton.name();
assert_eq!(
outgoing_coin_spend.coin.parent_coin_info,
last_coin_spend.coin.name()
);
assert_eq!(
outgoing_coin_spend.coin.parent_coin_info,
last_coin_spend.coin.name()
);
assert_eq!(outgoing_coin_spend.coin.name(), singleton_id);
assert_ne!(new_inner_puzzle, inner_puzzle);
let signed_spend_bundle = sign_coin_spend(outgoing_coin_spend, key_fn, constants).await?;
assert_eq!(
signed_spend_bundle.removals()[0].puzzle_hash,
singleton.puzzle_hash
);
assert_eq!(signed_spend_bundle.removals()[0].name(), singleton.name());
let additions = signed_spend_bundle.additions()?;
let removals = signed_spend_bundle.removals();
let name = signed_spend_bundle.name();
let tx_record = TransactionRecord {
confirmed_at_height: 0,
created_at_time: SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs(),
to_puzzle_hash: new_full_puzzle.tree_hash(),
amount: 1,
fee_amount: 0,
confirmed: false,
sent: 0,
spend_bundle: Some(signed_spend_bundle),
additions,
removals,
wallet_id: 1,
sent_to: vec![],
trade_id: None,
memos: vec![],
transaction_type: TransactionType::OutgoingTx as u32,
name,
};
Ok((tx_record, None))
}

pub async fn get_current_pool_state(
client: &FullnodeClient,
launcher_id: &Bytes32,
Expand Down Expand Up @@ -527,3 +626,57 @@ pub async fn submit_next_state_spend_bundle(
TXStatus::FAILED => Err(Error::new(ErrorKind::Other, "Failed to submit transaction")),
}
}

pub async fn submit_next_state_spend_bundle_with_key(
client: &FullnodeClient,
secret_key: &SecretKey,
plot_nft: &PlotNft,
target_pool_state: &PoolState,
constants: &ConsensusConstants,
) -> Result<(), Error> {
let (travel_record, _) = generate_travel_transaction_without_fee(
client,
|_| async { Ok(secret_key.clone()) },
plot_nft,
target_pool_state,
constants,
)
.await?;
let coin_to_find = travel_record
.additions
.iter()
.find(|c| c.amount == 1)
.expect("Failed to find NFT coin");
match client
.push_tx(
&travel_record
.spend_bundle
.expect("Expected Transaction Record to have Spend bundle"),
)
.await?
{
TXStatus::SUCCESS => {
info!("Transaction Submitted Successfully. Waiting for coin to show as spent...");
loop {
if let Ok(Some(record)) = client.get_coin_record_by_name(&coin_to_find.name()).await
{
if let Ok(Some(record)) = client
.get_coin_record_by_name(&record.coin.parent_coin_info)
.await
{
info!(
"Found spent parent coin, Parent Coin was spent at {}",
record.spent_block_index
);
break;
}
}
tokio::time::sleep(Duration::from_secs(10)).await;
info!("Waiting for plot_nft spend to appear...");
}
Ok(())
}
TXStatus::PENDING => Err(Error::new(ErrorKind::Other, "Transaction is pending")),
TXStatus::FAILED => Err(Error::new(ErrorKind::Other, "Failed to submit transaction")),
}
}
Loading

0 comments on commit 30aa2fd

Please sign in to comment.