Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.lock

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

3 changes: 3 additions & 0 deletions minotari/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@ path = "src/bin/generate_openapi.rs"
[dependencies]
anyhow = "1.0.99"
argon2 = { version = "0.6.0-rc.8", features = ["alloc"] }
blake2 = "0.10"
digest = "0.10"
phc = "0.6.1"
axum = { version = "0.8.6", features = ["default", "http2", "macros"] }
chacha20poly1305 = { version = "0.11.0-rc.3", features = ["rand_core"] }
chrono = "0.4.42"
zeroize = "1.8"
clap = { version = "4.5.47", features = ["derive"] }
dirs-next = "2"
config = { version = "0.14.0", default-features = false, features = ["toml"] }
Expand Down
47 changes: 47 additions & 0 deletions minotari/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -696,6 +696,53 @@ pub enum Commands {
#[arg(long, default_value_t = 86400)]
seconds_to_lock: u64,
},

/// Migrate an already-synced legacy console wallet database into this
/// wallet's database format.
///
/// Reads the legacy console wallet's SQLite file (read-only), recovers the
/// master cipher seed using the provided source passphrase, and copies the
/// outputs and transaction history into a new account in the destination
/// database. The migrated account preserves the legacy random transaction
/// IDs as the user-facing display IDs, the same balance, and the same set
/// of unspent outputs - and sets the scan tip to the source's last
/// scanned block so the next scan does not re-process the entire chain.
///
/// # Example
///
/// ```bash
/// tari migrate-from-console-wallet \
/// --source-db /path/to/console_wallet/console_wallet.sqlite3 \
/// --source-password "old wallet password" \
/// --account-name imported \
/// --password "new wallet password"
/// ```
MigrateFromConsoleWallet {
/// Path to the legacy console wallet's SQLite database.
#[arg(long, help = "Path to the source console wallet SQLite database")]
source_db: PathBuf,

/// Passphrase that unlocks the source console wallet.
#[arg(long, help = "Source console wallet passphrase")]
source_password: String,

#[command(flatten)]
security: SecurityArgs,
#[command(flatten)]
db: DatabaseArgs,

/// Friendly name to give the new account in this wallet.
#[arg(short = 'a', long, help = "Account name for the migrated wallet")]
account_name: String,

/// Run the migration against an in-flight transaction but roll it
/// back instead of committing. Useful for checking the source DB
/// would migrate cleanly (balance match, no schema conflicts, no
/// tx_id collisions that need fallback ids) before touching the
/// destination wallet.
#[arg(long, help = "Validate the migration without writing the destination DB")]
dry_run: bool,
},
}

#[derive(Args, Debug)]
Expand Down
1 change: 1 addition & 0 deletions minotari/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ pub mod daemon;
pub mod db;
pub mod http;
pub mod log;
pub mod migrate;
pub mod models;
pub mod scan;
pub mod tasks;
Expand Down
71 changes: 71 additions & 0 deletions minotari/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ use minotari::{
daemon,
db::{self, WalletDbError, get_accounts, get_balance, init_db},
log::{init_logging, mask_string},
migrate::{MigrationOptions, run_migration},
models::WalletEvent,
scan::{self, reorg::rollback_from_height},
transactions::{
Expand Down Expand Up @@ -565,6 +566,76 @@ async fn main() -> Result<(), anyhow::Error> {
Ok(())
},

Commands::MigrateFromConsoleWallet {
source_db,
source_password,
security,
db,
account_name,
dry_run,
} => {
info!(
target: "audit",
source = source_db.display().to_string().as_str(),
account = account_name.as_str(),
dry_run = dry_run;
"Migrating from console wallet"
);

wallet_config.apply_database(&db);

let report = tokio::task::spawn_blocking(move || {
run_migration(MigrationOptions {
source_db_path: source_db,
source_passphrase: source_password,
destination_db_path: wallet_config.database_path,
destination_passphrase: security.password,
account_name,
dry_run,
})
})
.await
.map_err(|e| anyhow!("Migration task join error: {}", e))??;

println!("---------------------------------------------------------");
if report.dry_run {
println!("Migration DRY-RUN complete (nothing was written):");
} else {
println!("Migration complete:");
}
println!(" Account name : {}", report.account_name);
println!(" Outputs migrated : {}", report.outputs_migrated);
println!(" Unspent : {}", report.unspent_outputs_count);
println!(" Spent : {}", report.spent_outputs_count);
println!(" Outputs skipped : {}", report.outputs_skipped);
println!(" Transactions migrated : {}", report.displayed_transactions_migrated);
println!(
" With matched outputs : {}",
report.displayed_transactions_with_matched_outputs
);
println!(" Source balance : {} uT", report.source_balance.as_u64());
println!(" Imported balance : {} uT", report.net_balance().as_u64());
println!(
" Balance match : {}",
if report.balance_match { "YES" } else { "NO" }
);
if let Some(h) = report.scan_tip_height {
println!(" Resumed scan tip : block {}", h);
} else {
println!(" Resumed scan tip : none (full scan will be required)");
}
println!("---------------------------------------------------------");
if !report.balance_match && !report.dry_run {
return Err(anyhow!(
"Migration balance mismatch: source = {} uT, imported = {} uT. \
Re-run with --dry-run to investigate.",
report.source_balance.as_u64(),
report.net_balance().as_u64()
));
}
Ok(())
},

Commands::BurnFunds {
security,
db,
Expand Down
Loading
Loading