Skip to content

Commit

Permalink
miniscript: implement from_tree nonrecursively
Browse files Browse the repository at this point in the history
This also drops the FromTree impls for Terminal and Arc<Terminal>. As
argued elsewhere, the Terminal types are not first-class types and it
doesn't make sense to allow them to be constructed in elaborate ways.

The benchmarks are pretty noisy but appear to show a pretty big speedup.

On master:
test expression::benches::parse_tree         ... bench:         885.43 ns/iter (+/- 412.97)
test expression::benches::parse_tree_deep    ... bench:       1,951.10 ns/iter (+/- 845.66)
test miniscript::benches::parse_segwit0      ... bench:       9,571.35 ns/iter (+/- 565.98)
test miniscript::benches::parse_segwit0_deep ... bench:      25,571.58 ns/iter (+/- 468.93)

On this commit:

test expression::benches::parse_tree         ... bench:       1,020.52 ns/iter (+/- 139.58)
test expression::benches::parse_tree_deep    ... bench:       1,632.75 ns/iter (+/- 176.35)
test miniscript::benches::parse_segwit0      ... bench:      10,747.28 ns/iter (+/- 2,338.26)
test miniscript::benches::parse_segwit0_deep ... bench:      19,090.65 ns/iter (+/- 1,040.62)

So if you take the numbers at face value, that's a 33% speedup on the
parse_segwit0_deep benchmark. Nice.
  • Loading branch information
apoelstra committed Dec 17, 2024
1 parent 2c422af commit 26c6dc3
Show file tree
Hide file tree
Showing 2 changed files with 193 additions and 181 deletions.
138 changes: 1 addition & 137 deletions src/miniscript/astelem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,147 +9,11 @@
use bitcoin::hashes::Hash;
use bitcoin::{absolute, opcodes, script};
use sync::Arc;

use crate::miniscript::context::SigType;
use crate::miniscript::ScriptContext;
use crate::prelude::*;
use crate::util::MsKeyBuilder;
use crate::{expression, Error, FromStrKey, Miniscript, MiniscriptKey, Terminal, ToPublicKey};

impl<Pk: FromStrKey, Ctx: ScriptContext> crate::expression::FromTree for Arc<Terminal<Pk, Ctx>> {
fn from_tree(root: expression::TreeIterItem) -> Result<Arc<Terminal<Pk, Ctx>>, Error> {
Ok(Arc::new(expression::FromTree::from_tree(root)?))
}
}

impl<Pk: FromStrKey, Ctx: ScriptContext> crate::expression::FromTree for Terminal<Pk, Ctx> {
fn from_tree(top: expression::TreeIterItem) -> Result<Terminal<Pk, Ctx>, Error> {
let binary = |node: expression::TreeIterItem,
name,
termfn: fn(_, _) -> Self|
-> Result<Self, Error> {
node.verify_binary(name)
.map_err(From::from)
.map_err(Error::Parse)
.and_then(|(x, y)| {
let x = Arc::<Miniscript<Pk, Ctx>>::from_tree(x)?;
let y = Arc::<Miniscript<Pk, Ctx>>::from_tree(y)?;
Ok(termfn(x, y))
})
};

let (frag_wrap, frag_name) = top
.name_separated(':')
.map_err(From::from)
.map_err(Error::Parse)?;
// "pk" and "pkh" are aliases for "c:pk_k" and "c:pk_h" respectively.
let unwrapped = match frag_name {
"expr_raw_pkh" => top
.verify_terminal_parent("expr_raw_pkh", "public key hash")
.map(Terminal::RawPkH)
.map_err(Error::Parse),
"pk" => top
.verify_terminal_parent("pk", "public key")
.map(Terminal::PkK)
.map_err(Error::Parse)
.and_then(|term| Miniscript::from_ast(term))
.map(|ms| Terminal::Check(Arc::new(ms))),
"pkh" => top
.verify_terminal_parent("pkh", "public key")
.map(Terminal::PkH)
.map_err(Error::Parse)
.and_then(|term| Miniscript::from_ast(term))
.map(|ms| Terminal::Check(Arc::new(ms))),
"pk_k" => top
.verify_terminal_parent("pk_k", "public key")
.map(Terminal::PkK)
.map_err(Error::Parse),
"pk_h" => top
.verify_terminal_parent("pk_h", "public key")
.map(Terminal::PkH)
.map_err(Error::Parse),
"after" => top
.verify_after()
.map_err(Error::Parse)
.map(Terminal::After),
"older" => top
.verify_older()
.map_err(Error::Parse)
.map(Terminal::Older),
"sha256" => top
.verify_terminal_parent("sha256", "hash")
.map(Terminal::Sha256)
.map_err(Error::Parse),
"hash256" => top
.verify_terminal_parent("hash256", "hash")
.map(Terminal::Hash256)
.map_err(Error::Parse),
"ripemd160" => top
.verify_terminal_parent("ripemd160", "hash")
.map(Terminal::Ripemd160)
.map_err(Error::Parse),
"hash160" => top
.verify_terminal_parent("hash160", "hash")
.map(Terminal::Hash160)
.map_err(Error::Parse),
"1" => {
top.verify_n_children("1", 0..=0)
.map_err(From::from)
.map_err(Error::Parse)?;
Ok(Terminal::True)
}
"0" => {
top.verify_n_children("0", 0..=0)
.map_err(From::from)
.map_err(Error::Parse)?;
Ok(Terminal::False)
}
"and_v" => binary(top, "and_v", Terminal::AndV),
"and_b" => binary(top, "and_b", Terminal::AndB),
"and_n" => {
binary(top, "and_n", |x, y| Terminal::AndOr(x, y, Arc::new(Miniscript::FALSE)))
}
"andor" => {
top.verify_n_children("andor", 3..=3)
.map_err(From::from)
.map_err(Error::Parse)?;
let mut child_iter = top
.children()
.map(|x| Arc::<Miniscript<Pk, Ctx>>::from_tree(x));
Ok(Terminal::AndOr(
child_iter.next().unwrap()?,
child_iter.next().unwrap()?,
child_iter.next().unwrap()?,
))
}
"or_b" => binary(top, "or_b", Terminal::OrB),
"or_d" => binary(top, "or_d", Terminal::OrD),
"or_c" => binary(top, "or_c", Terminal::OrC),
"or_i" => binary(top, "or_i", Terminal::OrI),
"thresh" => top
.verify_threshold(|sub| Miniscript::from_tree(sub).map(Arc::new))
.map(Terminal::Thresh),
"multi" => top
.verify_threshold(|sub| sub.verify_terminal("public_key").map_err(Error::Parse))
.map(Terminal::Multi),
"multi_a" => top
.verify_threshold(|sub| sub.verify_terminal("public_key").map_err(Error::Parse))
.map(Terminal::MultiA),
x => Err(Error::Parse(crate::ParseError::Tree(crate::ParseTreeError::UnknownName {
name: x.to_owned(),
}))),
}?;

if frag_wrap == Some("") {
return Err(Error::Parse(crate::ParseError::Tree(
crate::ParseTreeError::UnknownName { name: top.name().to_owned() },
)));
}
let ms = super::wrap_into_miniscript(unwrapped, frag_wrap.unwrap_or(""))?;
Ok(ms.node)
}
}
use crate::{Miniscript, MiniscriptKey, Terminal, ToPublicKey};

/// Helper trait to add a `push_astelem` method to `script::Builder`
trait PushAstElem<Pk: MiniscriptKey, Ctx: ScriptContext> {
Expand Down
Loading

0 comments on commit 26c6dc3

Please sign in to comment.