Skip to content

Commit

Permalink
pcli: add balance migration command (#4842)
Browse files Browse the repository at this point in the history
initial attempt at implementing a balance migration function for pcli -
does not currently handle non-zero fees correctly, but that seems to be
the only remaining thing to correctly implement

- [x] If this code contains consensus-breaking changes, I have added the
"consensus-breaking" label. Otherwise, I declare my belief that there
are not consensus-breaking changes, for the following reason:

  > client/planner only changes

Co-authored-by: Lucas Meier <[email protected]>
Co-authored-by: Conor Schaefer <[email protected]>
  • Loading branch information
3 people committed Sep 5, 2024
1 parent 7601c8c commit 9443d98
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 6 deletions.
18 changes: 12 additions & 6 deletions crates/bin/pcli/src/command.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub use debug::DebugCmd;
pub use init::InitCmd;
pub use migrate::MigrateCmd;
pub use query::QueryCmd;
pub use threshold::ThresholdCmd;
pub use tx::TxCmd;
Expand All @@ -11,6 +12,7 @@ use self::{ceremony::CeremonyCmd, tx::TxCmdWithOptions};
mod ceremony;
mod debug;
mod init;
mod migrate;
mod query;
mod threshold;
mod tx;
Expand Down Expand Up @@ -53,18 +55,21 @@ pub enum Command {
/// Create and broadcast a transaction.
#[clap(display_order = 400, visible_alias = "tx")]
Transaction(TxCmdWithOptions),
/// Follow the threshold signing protocol.
#[clap(subcommand, display_order = 500)]
Threshold(ThresholdCmd),
/// Migrate your balance to another wallet.
#[clap(subcommand, display_order = 600)]
Migrate(MigrateCmd),
/// Manage a validator.
#[clap(subcommand, display_order = 900)]
Validator(ValidatorCmd),
/// Display information related to diagnosing problems running Penumbra
#[clap(subcommand, display_order = 999)]
Debug(DebugCmd),
/// Contribute to the summoning ceremony.
#[clap(subcommand, display_order = 990)]
Ceremony(CeremonyCmd),
/// Follow the threshold signing protocol.
#[clap(subcommand, display_order = 500)]
Threshold(ThresholdCmd),
/// Display information related to diagnosing problems running Penumbra
#[clap(subcommand, display_order = 999)]
Debug(DebugCmd),
}

impl Command {
Expand All @@ -79,6 +84,7 @@ impl Command {
Command::Debug(cmd) => cmd.offline(),
Command::Ceremony(_) => false,
Command::Threshold(cmd) => cmd.offline(),
Command::Migrate(_) => false,
}
}
}
98 changes: 98 additions & 0 deletions crates/bin/pcli/src/command/migrate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
use crate::App;
use anyhow::{Context, Result};
use penumbra_keys::FullViewingKey;
use penumbra_proto::view::v1::GasPricesRequest;
use penumbra_view::ViewClient;
use penumbra_wallet::plan::Planner;
use rand_core::OsRng;
use std::{io::Write, str::FromStr};
use termion::input::TermRead;

#[derive(Debug, clap::Parser)]
pub enum MigrateCmd {
/// Migrate your entire balance to another wallet.
///
/// All assets from all accounts in the source wallet will be sent to the destination wallet.
/// A FullViewingKey must be provided for the destination wallet.
/// All funds will be deposited in the account 0 of the destination wallet,
/// minus any gas prices for the migration transaction.
#[clap(name = "balance")]
Balance,
}

impl MigrateCmd {
#[tracing::instrument(skip(self, app))]
pub async fn exec(&self, app: &mut App) -> Result<()> {
let gas_prices = app
.view
.as_mut()
.context("view service must be initialized")?
.gas_prices(GasPricesRequest {})
.await?
.into_inner()
.gas_prices
.expect("gas prices must be available")
.try_into()?;

print!("Enter FVK: ");
std::io::stdout().flush()?;
let to: String = std::io::stdin().lock().read_line()?.unwrap_or_default();

match self {
MigrateCmd::Balance => {
let source_fvk = app.config.full_viewing_key.clone();

let dest_fvk = to.parse::<FullViewingKey>().map_err(|_| {
anyhow::anyhow!("The provided string is not a valid FullViewingKey.")
})?;

let mut planner = Planner::new(OsRng);

let (dest_address, _) = FullViewingKey::payment_address(
&FullViewingKey::from_str(&to[..])?,
Default::default(),
);

planner
.set_gas_prices(gas_prices)
.set_fee_tier(Default::default())
.change_address(dest_address);

// Return all unspent notes from the view service
let notes = app
.view
.as_mut()
.context("view service must be initialized")?
.unspent_notes_by_account_and_asset()
.await?;

for notes in notes.into_values() {
for notes in notes.into_values() {
for note in notes {
planner.spend(note.note, note.position);
}
}
}

let memo = format!("Migrating balance from {} to {}", source_fvk, dest_fvk);
let plan = planner
.memo(memo)
.plan(
app.view
.as_mut()
.context("view service must be initialized")?,
Default::default(),
)
.await
.context("can't build send transaction")?;

if plan.actions.is_empty() {
anyhow::bail!("migration plan contained zero actions: is the source wallet already empty?");
}
app.build_and_submit_transaction(plan).await?;

Result::Ok(())
}
}
}
}
1 change: 1 addition & 0 deletions crates/bin/pcli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ async fn main() -> Result<()> {
Command::Query(cmd) => cmd.exec(&mut app).await?,
Command::Ceremony(cmd) => cmd.exec(&mut app).await?,
Command::Threshold(cmd) => cmd.exec(&mut app).await?,
Command::Migrate(cmd) => cmd.exec(&mut app).await?,
}

Ok(())
Expand Down

0 comments on commit 9443d98

Please sign in to comment.