Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add 'compile' sub-command, minor cleanup #21

Merged
merged 1 commit into from
Mar 5, 2021
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
3 changes: 2 additions & 1 deletion .github/workflows/cont_integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ jobs:
- repl
- electrum
- esplora
- repl,electrum,esplora
- compiler
- repl,electrum,esplora,compiler
steps:
- name: Checkout
uses: actions/checkout@v2
Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 9 additions & 1 deletion src/bdk_cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
101 changes: 94 additions & 7 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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<serde_json::Value, Error> {
let policy = Concrete::<String>::from_str(policy.as_str())?;
let legacy_policy: Miniscript<String, Legacy> = policy
.compile()
.map_err(|e| Error::Generic(e.to_string()))?;
let segwit_policy: Miniscript<String, Segwitv0> = 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")]
Expand All @@ -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/*)",
Expand Down Expand Up @@ -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/*)",
Expand Down Expand Up @@ -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/*)",
Expand Down Expand Up @@ -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"];
Expand Down Expand Up @@ -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/*)",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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"];
Expand Down Expand Up @@ -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"
);
}
}