Skip to content

Commit

Permalink
Allow arbitrary wallet names (#1207)
Browse files Browse the repository at this point in the history
  • Loading branch information
raphjaph authored Jan 12, 2023
1 parent 96a96a5 commit 5b052f8
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 82 deletions.
2 changes: 1 addition & 1 deletion src/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ impl<T> BitcoinCoreRpcResultExt<T> for Result<T, bitcoincore_rpc::Error> {

impl Index {
pub(crate) fn open(options: &Options) -> Result<Self> {
let rpc_url = options.rpc_url();
let rpc_url = options.rpc_url(false);
let cookie_file = options.cookie_file()?;

log::info!(
Expand Down
58 changes: 40 additions & 18 deletions src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ pub(crate) struct Options {
pub(crate) signet: bool,
#[clap(long, short, help = "Use testnet. Equivalent to `--chain testnet`.")]
pub(crate) testnet: bool,
#[clap(long, default_value = "ord", help = "Use wallet named <WALLET>.")]
pub(crate) wallet: String,
}

impl Options {
Expand Down Expand Up @@ -66,12 +68,17 @@ impl Options {
}
}

pub(crate) fn rpc_url(&self) -> String {
self
pub(crate) fn rpc_url(&self, with_wallet: bool) -> String {
let mut rpc_url = self
.rpc_url
.as_ref()
.unwrap_or(&format!("127.0.0.1:{}", self.chain().default_rpc_port(),))
.into()
.clone()
.unwrap_or_else(|| format!("127.0.0.1:{}", self.chain().default_rpc_port()));

if with_wallet {
rpc_url.push_str(&format!("/wallet/{}", self.wallet));
}

rpc_url.to_string()
}

pub(crate) fn cookie_file(&self) -> Result<PathBuf> {
Expand Down Expand Up @@ -116,9 +123,11 @@ impl Options {
)
}

pub(crate) fn bitcoin_rpc_client(&self) -> Result<Client> {
pub(crate) fn bitcoin_rpc_client(&self, with_wallet: bool) -> Result<Client> {
let cookie_file = self.cookie_file()?;
let rpc_url = self.rpc_url();

let rpc_url = self.rpc_url(with_wallet);

log::info!(
"Connecting to Bitcoin Core RPC server at {rpc_url} using credentials from `{}`",
cookie_file.display()
Expand All @@ -145,7 +154,7 @@ impl Options {
}

pub(crate) fn bitcoin_rpc_client_for_wallet_command(&self, create: bool) -> Result<Client> {
let client = self.bitcoin_rpc_client()?;
let client = self.bitcoin_rpc_client(true)?;

const MIN_VERSION: usize = 240000;

Expand All @@ -159,12 +168,6 @@ impl Options {
}

if !create {
let wallet_info = client.get_wallet_info()?;

if !(wallet_info.wallet_name == "ord" || wallet_info.wallet_name.starts_with("ord-")) {
bail!("wallet commands may only be used on mainnet with a wallet named `ord` or whose name starts with `ord-`");
}

let descriptors = client.list_descriptors(None)?.descriptors;

let tr = descriptors
Expand Down Expand Up @@ -196,7 +199,7 @@ mod tests {
Arguments::try_parse_from(["ord", "--rpc-url=127.0.0.1:1234", "--chain=signet", "index"])
.unwrap()
.options
.rpc_url(),
.rpc_url(false),
"127.0.0.1:1234"
);
}
Expand All @@ -217,7 +220,7 @@ mod tests {
fn use_default_network() {
let arguments = Arguments::try_parse_from(["ord", "index"]).unwrap();

assert_eq!(arguments.options.rpc_url(), "127.0.0.1:8332");
assert_eq!(arguments.options.rpc_url(false), "127.0.0.1:8332");

assert!(arguments
.options
Expand All @@ -230,7 +233,7 @@ mod tests {
fn uses_network_defaults() {
let arguments = Arguments::try_parse_from(["ord", "--chain=signet", "index"]).unwrap();

assert_eq!(arguments.options.rpc_url(), "127.0.0.1:38332");
assert_eq!(arguments.options.rpc_url(false), "127.0.0.1:38332");

assert!(arguments
.options
Expand Down Expand Up @@ -429,7 +432,7 @@ mod tests {
.unwrap();

assert_eq!(
options.bitcoin_rpc_client().unwrap_err().to_string(),
options.bitcoin_rpc_client(false).unwrap_err().to_string(),
"Bitcoin RPC server is on testnet but ord is on mainnet"
);
}
Expand Down Expand Up @@ -484,4 +487,23 @@ mod tests {
Chain::Testnet
);
}

#[test]
fn wallet_flag_overrides_default_name() {
assert_eq!(
Arguments::try_parse_from(["ord", "wallet", "create"])
.unwrap()
.options
.wallet,
"ord"
);

assert_eq!(
Arguments::try_parse_from(["ord", "--wallet", "foo", "wallet", "create"])
.unwrap()
.options
.wallet,
"foo"
)
}
}
9 changes: 3 additions & 6 deletions src/subcommand/preview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ impl Preview {
};

for attempt in 0.. {
if options.bitcoin_rpc_client().is_ok() {
if options.bitcoin_rpc_client(false).is_ok() {
break;
}

Expand All @@ -60,12 +60,9 @@ impl Preview {
thread::sleep(Duration::from_millis(50));
}

let rpc_client = options.bitcoin_rpc_client()?;
let rpc_client = options.bitcoin_rpc_client(false)?;

super::wallet::create::Create::run(
&super::wallet::create::Create { name: "ord".into() },
options.clone(),
)?;
super::wallet::create::run(options.clone())?;

let address =
rpc_client.get_new_address(None, Some(bitcoincore_rpc::json::AddressType::Bech32m))?;
Expand Down
4 changes: 2 additions & 2 deletions src/subcommand/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub(crate) enum Wallet {
#[clap(about = "Get wallet balance")]
Balance,
#[clap(about = "Create a new wallet")]
Create(create::Create),
Create,
#[clap(about = "Create an inscription")]
Inscribe(inscribe::Inscribe),
#[clap(about = "List wallet inscriptions")]
Expand All @@ -37,7 +37,7 @@ impl Wallet {
pub(crate) fn run(self, options: Options) -> Result {
match self {
Self::Balance => balance::run(options),
Self::Create(create) => create.run(options),
Self::Create => create::run(options),
Self::Inscribe(inscribe) => inscribe.run(options),
Self::Inscriptions => inscriptions::run(options),
Self::Receive => receive::run(options),
Expand Down
72 changes: 30 additions & 42 deletions src/subcommand/wallet/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,57 +9,45 @@ use {
miniscript::descriptor::{Descriptor, DescriptorSecretKey, DescriptorXKey, Wildcard},
};

#[derive(Debug, Parser)]
pub(crate) struct Create {
#[clap(long, default_value = "ord", help = "Create wallet with <NAME>")]
pub(crate) name: String,
}

impl Create {
pub(crate) fn run(&self, options: Options) -> Result {
if !(self.name == "ord" || self.name.starts_with("ord-")) {
bail!("`ord wallet create` may only be used with a wallet named `ord` or whose name starts with `ord-`");
}
pub(crate) fn run(options: Options) -> Result {
let client = options.bitcoin_rpc_client_for_wallet_command(true)?;

let client = options.bitcoin_rpc_client_for_wallet_command(true)?;
client.create_wallet(&options.wallet, None, Some(true), None, None)?;

client.create_wallet(&self.name, None, Some(true), None, None)?;
let secp = bitcoin::secp256k1::Secp256k1::new();
let mut seed = [0; 32];
bitcoin::secp256k1::rand::thread_rng().fill_bytes(&mut seed);

let secp = bitcoin::secp256k1::Secp256k1::new();
let mut seed = [0; 32];
bitcoin::secp256k1::rand::thread_rng().fill_bytes(&mut seed);
let master_private_key = ExtendedPrivKey::new_master(options.chain().network(), &seed)?;

let master_private_key = ExtendedPrivKey::new_master(options.chain().network(), &seed)?;
let fingerprint = master_private_key.fingerprint(&secp);

let fingerprint = master_private_key.fingerprint(&secp);
let derivation_path = DerivationPath::master()
.child(ChildNumber::Hardened { index: 86 })
.child(ChildNumber::Hardened {
index: u32::from(options.chain().network() != Network::Bitcoin),
})
.child(ChildNumber::Hardened { index: 0 });

let derivation_path = DerivationPath::master()
.child(ChildNumber::Hardened { index: 86 })
.child(ChildNumber::Hardened {
index: u32::from(options.chain().network() != Network::Bitcoin),
})
.child(ChildNumber::Hardened { index: 0 });
let derived_private_key = master_private_key.derive_priv(&secp, &derivation_path)?;

let derived_private_key = master_private_key.derive_priv(&secp, &derivation_path)?;
derive_and_import_descriptor(
&client,
&secp,
(fingerprint, derivation_path.clone()),
derived_private_key,
false,
)?;

derive_and_import_descriptor(
&client,
&secp,
(fingerprint, derivation_path.clone()),
derived_private_key,
false,
)?;
derive_and_import_descriptor(
&client,
&secp,
(fingerprint, derivation_path),
derived_private_key,
true,
)?;

derive_and_import_descriptor(
&client,
&secp,
(fingerprint, derivation_path),
derived_private_key,
true,
)?;

Ok(())
}
Ok(())
}

fn derive_and_import_descriptor(
Expand Down
12 changes: 6 additions & 6 deletions tests/wallet/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,14 @@ fn detect_wrong_descriptors() {
}

#[test]
fn create_wallet_with_wrong_name() {
fn create_with_different_name() {
let rpc_server = test_bitcoincore_rpc::spawn();

CommandBuilder::new("wallet create --name nft-wallet")
assert!(!rpc_server.wallets().contains("inscription-wallet"));

CommandBuilder::new("--wallet inscription-wallet wallet create")
.rpc_server(&rpc_server)
.expected_stderr(
"error: `ord wallet create` may only be used with a wallet named `ord` or whose name starts with `ord-`\n",
)
.expected_exit_code(1)
.run();

assert!(rpc_server.wallets().contains("inscription-wallet"));
}
17 changes: 17 additions & 0 deletions tests/wallet/inscribe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,3 +292,20 @@ fn inscribe_with_fee_rate() {

pretty_assert_eq!(fee_rate, 2.0);
}

#[test]
fn inscribe_with_wallet_named_foo() {
let rpc_server = test_bitcoincore_rpc::spawn();

CommandBuilder::new("--wallet foo wallet create")
.rpc_server(&rpc_server)
.run();

rpc_server.mine_blocks(1);

CommandBuilder::new("--wallet foo wallet inscribe degenerate.png")
.write("degenerate.png", [1; 520])
.rpc_server(&rpc_server)
.stdout_regex("commit\t[[:xdigit:]]{64}\nreveal\t[[:xdigit:]]{64}\n")
.run();
}
17 changes: 10 additions & 7 deletions tests/wallet/send.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,16 +90,19 @@ fn send_inscribed_sat() {
}

#[test]
fn send_on_mainnnet_refuses_to_work_with_wallet_name_foo() {
let rpc_server = test_bitcoincore_rpc::builder().wallet_name("foo").build();
fn send_on_mainnnet_works_with_wallet_named_foo() {
let rpc_server = test_bitcoincore_rpc::spawn();
let txid = rpc_server.mine_blocks(1)[0].txdata[0].txid();

CommandBuilder::new(
format!("wallet send bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 {txid}:0:0")
)
CommandBuilder::new("--wallet foo wallet create")
.rpc_server(&rpc_server)
.run();

CommandBuilder::new(format!(
"--wallet foo wallet send bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 {txid}:0:0"
))
.rpc_server(&rpc_server)
.expected_stderr("error: wallet commands may only be used on mainnet with a wallet named `ord` or whose name starts with `ord-`\n")
.expected_exit_code(1)
.stdout_regex(r"[[:xdigit:]]{64}\n")
.run();
}

Expand Down

0 comments on commit 5b052f8

Please sign in to comment.