From: Steve Myers Date: Wed, 3 Mar 2021 04:09:59 +0000 (-0800) Subject: Add 'compile' sub-command, minor cleanup X-Git-Tag: v0.3.0~67 X-Git-Url: http://internal-gitweb-vhost/script/%22https:/database/struct.CommandStringError.html?a=commitdiff_plain;h=e73215dac065729cd94298d0e81ed4c6324d1584;p=bdk-cli Add 'compile' sub-command, minor cleanup --- diff --git a/.github/workflows/cont_integration.yml b/.github/workflows/cont_integration.yml index 3de3efe..3931db1 100644 --- a/.github/workflows/cont_integration.yml +++ b/.github/workflows/cont_integration.yml @@ -17,7 +17,8 @@ jobs: - repl - electrum - esplora - - repl,electrum,esplora + - compiler + - repl,electrum,esplora,compiler steps: - name: Checkout uses: actions/checkout@v2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e27ffc..6645b0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Project + +#### Added +- `CliSubCommand::Compile` enum variant and `handle_compile_subcommand()` function + +### `bdk-cli` bin + +#### Added +- New top level command "Compile" which compiles a miniscript policy to an output descriptor + ## [0.2.0] ### Project diff --git a/src/bdk_cli.rs b/src/bdk_cli.rs index 17ed3aa..3d5ce29 100644 --- a/src/bdk_cli.rs +++ b/src/bdk_cli.rs @@ -182,9 +182,17 @@ fn main() { let result = bdk_cli::handle_key_subcommand(network, key_subcommand); serde_json::to_string_pretty(&result.unwrap()).unwrap() } + #[cfg(feature = "compiler")] + CliSubCommand::Compile { + policy, + script_type, + } => { + let result = bdk_cli::handle_compile_subcommand(network, policy, script_type); + serde_json::to_string_pretty(&result.unwrap()).unwrap() + } CliSubCommand::Repl { wallet_opts } => { let database = open_database(&wallet_opts); - let online_wallet = new_online_wallet(network, &wallet_opts, database.clone()).unwrap(); + let online_wallet = new_online_wallet(network, &wallet_opts, database).unwrap(); let mut rl = Editor::<()>::new(); diff --git a/src/lib.rs b/src/lib.rs index c114501..ece77bd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -117,11 +117,15 @@ use bdk::bitcoin::{Address, Network, OutPoint, Script, Txid}; use bdk::blockchain::{log_progress, Blockchain}; use bdk::database::BatchDatabase; use bdk::descriptor::Segwitv0; +#[cfg(feature = "compiler")] +use bdk::descriptor::{Descriptor, Legacy, Miniscript}; use bdk::keys::bip39::{Language, Mnemonic, MnemonicType}; use bdk::keys::DescriptorKey::Secret; use bdk::keys::KeyError::{InvalidNetwork, Message}; use bdk::keys::{DerivableKey, DescriptorKey, ExtendedKey, GeneratableKey, GeneratedKey}; use bdk::miniscript::miniscript; +#[cfg(feature = "compiler")] +use bdk::miniscript::policy::Concrete; use bdk::Error; use bdk::{FeeRate, KeychainKind, Wallet}; @@ -222,6 +226,17 @@ pub enum CliSubCommand { #[structopt(subcommand)] subcommand: KeySubCommand, }, + /// Compile a miniscript policy to an output descriptor + #[cfg(feature = "compiler")] + #[structopt(long_about = "Miniscript policy compiler")] + Compile { + /// Sets the spending policy to compile + #[structopt(name = "POLICY", required = true, index = 1)] + policy: String, + /// Sets the script type used to embed the compiled policy + #[structopt(name = "TYPE", short = "t", long = "type", default_value = "wsh", possible_values = &["sh","wsh", "sh-wsh"])] + script_type: String, + }, /// Enter REPL command loop mode #[structopt(long_about = "REPL command loop mode")] Repl { @@ -887,9 +902,39 @@ pub fn handle_key_subcommand( } } +/// Execute the miniscript compiler sub-command +/// +/// Compiler options are described in [`CliSubCommand::Compile`]. +#[cfg(feature = "compiler")] +pub fn handle_compile_subcommand( + _network: Network, + policy: String, + script_type: String, +) -> Result { + let policy = Concrete::::from_str(policy.as_str())?; + let legacy_policy: Miniscript = policy + .compile() + .map_err(|e| Error::Generic(e.to_string()))?; + let segwit_policy: Miniscript = policy + .compile() + .map_err(|e| Error::Generic(e.to_string()))?; + + let descriptor = match script_type.as_str() { + "sh" => Descriptor::new_sh(legacy_policy), + "wsh" => Descriptor::new_wsh(segwit_policy), + "sh-wsh" => Descriptor::new_sh_wsh(segwit_policy), + _ => panic!("Invalid type"), + } + .map_err(Error::Miniscript)?; + + Ok(json!({"descriptor": descriptor.to_string()})) +} + #[cfg(test)] mod test { use super::{CliOpts, WalletOpts}; + #[cfg(feature = "compiler")] + use crate::handle_compile_subcommand; #[cfg(feature = "electrum")] use crate::ElectrumOpts; #[cfg(feature = "esplora")] @@ -904,7 +949,7 @@ mod test { use structopt::StructOpt; #[test] - fn test_wallet_get_new_address() { + fn test_parse_wallet_get_new_address() { let cli_args = vec!["bdk-cli", "--network", "bitcoin", "wallet", "--descriptor", "wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)", "--change_descriptor", "wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/1/*)", @@ -941,7 +986,7 @@ mod test { #[cfg(feature = "electrum")] #[test] - fn test_wallet_electrum() { + fn test_parse_wallet_electrum() { let cli_args = vec!["bdk-cli", "--network", "testnet", "wallet", "--proxy", "127.0.0.1:9150", "--retries", "3", "--timeout", "10", "--descriptor", "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)", @@ -980,7 +1025,7 @@ mod test { #[cfg(feature = "esplora")] #[test] - fn test_wallet_esplora() { + fn test_parse_wallet_esplora() { let cli_args = vec!["bdk-cli", "--network", "bitcoin", "wallet", "--descriptor", "wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)", "--change_descriptor", "wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/1/*)", @@ -1018,7 +1063,7 @@ mod test { } #[test] - fn test_wallet_sync() { + fn test_parse_wallet_sync() { let cli_args = vec!["bdk-cli", "--network", "testnet", "wallet", "--descriptor", "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)", "sync", "--max_addresses", "50"]; @@ -1055,7 +1100,7 @@ mod test { } #[test] - fn test_wallet_create_tx() { + fn test_parse_wallet_create_tx() { let cli_args = vec!["bdk-cli", "--network", "testnet", "wallet", "--descriptor", "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)", "--change_descriptor", "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/1/*)", @@ -1118,7 +1163,7 @@ mod test { } #[test] - fn test_wallet_broadcast() { + fn test_parse_wallet_broadcast() { let cli_args = vec!["bdk-cli", "--network", "testnet", "wallet", "--descriptor", "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)", "broadcast", @@ -1157,7 +1202,7 @@ mod test { } #[test] - fn test_wrong_network() { + fn test_parse_wrong_network() { let cli_args = vec!["repl", "--network", "badnet", "wallet", "--descriptor", "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)", "sync", "--max_addresses", "50"]; @@ -1220,4 +1265,46 @@ mod test { assert_eq!(&xpub, &"[566844c5/84'/1'/0']tpubDDrcq8JNTLVwaGnXp7KqZZxFJoic49ikEotWnpm6hpMY2hL2F1hpgbBBkEdsFJHU1iAx9DzMUKYr5mrphBMoXxhsBPhnVdmaoSCaeh6QWgj/0/*"); } + + #[cfg(feature = "compiler")] + #[test] + fn test_parse_compile() { + let cli_args = vec![ + "bdk-cli", + "compile", + "thresh(3,pk(Alice),pk(Bob),pk(Carol),older(2))", + "--type", + "sh-wsh", + ]; + + let cli_opts = CliOpts::from_iter(&cli_args); + + let expected_cli_opts = CliOpts { + network: Network::Testnet, + subcommand: CliSubCommand::Compile { + policy: "thresh(3,pk(Alice),pk(Bob),pk(Carol),older(2))".to_string(), + script_type: "sh-wsh".to_string(), + }, + }; + + assert_eq!(expected_cli_opts, cli_opts); + } + + #[cfg(feature = "compiler")] + #[test] + fn test_compile() { + let result = handle_compile_subcommand( + Network::Testnet, + "thresh(3,pk(Alice),pk(Bob),pk(Carol),older(2))".to_string(), + "sh-wsh".to_string(), + ) + .unwrap(); + let result_obj = result.as_object().unwrap(); + + let descriptor = result_obj.get("descriptor").unwrap().as_str().unwrap(); + assert_eq!( + &descriptor, + &"sh(wsh(thresh(3,pk(Alice),s:pk(Bob),s:pk(Carol),sdv:older(2))))#l4qaawgv" + ); + } }