[BREAKING CHANGE] Make `script::Builder` implement the actual Builder pattern

Rather than having methods taking &mut self, have them consume self
and return another Builder, so that methods can be chained.

Bump major version number.
This commit is contained in:
Andrew Poelstra 2015-11-04 11:04:54 -06:00
parent 5e03adc9aa
commit 66eb08aab5
5 changed files with 65 additions and 50 deletions

View File

@ -1,7 +1,7 @@
[package] [package]
name = "bitcoin" name = "bitcoin"
version = "0.3.10" version = "0.4.0"
authors = ["Andrew Poelstra <apoelstra@wpsoftware.net>"] authors = ["Andrew Poelstra <apoelstra@wpsoftware.net>"]
license = "CC0-1.0" license = "CC0-1.0"
homepage = "https://github.com/apoelstra/rust-bitcoin/" homepage = "https://github.com/apoelstra/rust-bitcoin/"

View File

@ -65,24 +65,25 @@ fn bitcoin_genesis_tx() -> Transaction {
}; };
// Inputs // Inputs
let mut in_script = script::Builder::new(); let in_script = script::Builder::new().push_scriptint(486604799)
in_script.push_scriptint(486604799); .push_scriptint(4)
in_script.push_scriptint(4); .push_slice("The Times 03/Jan/2009 Chancellor on brink of second bailout for banks".as_bytes())
in_script.push_slice("The Times 03/Jan/2009 Chancellor on brink of second bailout for banks".as_bytes()); .into_script();
ret.input.push(TxIn { ret.input.push(TxIn {
prev_hash: Default::default(), prev_hash: Default::default(),
prev_index: 0xFFFFFFFF, prev_index: 0xFFFFFFFF,
script_sig: in_script.into_script(), script_sig: in_script,
sequence: MAX_SEQUENCE sequence: MAX_SEQUENCE
}); });
// Outputs // Outputs
let mut out_script = script::Builder::new(); let out_script = script::Builder::new()
out_script.push_slice(&hex_bytes("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f").unwrap()); .push_slice(&hex_bytes("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f").unwrap())
out_script.push_opcode(opcodes::All::OP_CHECKSIG); .push_opcode(opcodes::All::OP_CHECKSIG)
.into_script();
ret.output.push(TxOut { ret.output.push(TxOut {
value: 50 * COIN_VALUE, value: 50 * COIN_VALUE,
script_pubkey: out_script.into_script() script_pubkey: out_script
}); });
// end // end

View File

@ -2059,8 +2059,7 @@ impl Script {
// Compute the section of script that needs to be hashed: everything // Compute the section of script that needs to be hashed: everything
// from the last CODESEPARATOR, except the signature itself. // from the last CODESEPARATOR, except the signature itself.
let mut script = (&self.0[codeseparator_index..]).to_vec(); let mut script = (&self.0[codeseparator_index..]).to_vec();
let mut remove = Builder::new(); let remove = Builder::new().push_slice(sig_slice);
remove.push_slice(sig_slice);
script_find_and_remove(&mut script, &remove[..]); script_find_and_remove(&mut script, &remove[..]);
// Also all of the OP_CODESEPARATORS, even the unevaluated ones // Also all of the OP_CODESEPARATORS, even the unevaluated ones
script_find_and_remove(&mut script, &[opcodes::Ordinary::OP_CODESEPARATOR as u8]); script_find_and_remove(&mut script, &[opcodes::Ordinary::OP_CODESEPARATOR as u8]);
@ -2110,8 +2109,7 @@ impl Script {
// from the last CODESEPARATOR, except the signatures themselves. // from the last CODESEPARATOR, except the signatures themselves.
let mut script = (&self.0[codeseparator_index..]).to_vec(); let mut script = (&self.0[codeseparator_index..]).to_vec();
for sig in &sigs { for sig in &sigs {
let mut remove = Builder::new(); let remove = Builder::new().push_slice(&sig[..]);
remove.push_slice(&sig[..]);
script_find_and_remove(&mut script, &remove[..]); script_find_and_remove(&mut script, &remove[..]);
script_find_and_remove(&mut script, &[opcodes::Ordinary::OP_CODESEPARATOR as u8]); script_find_and_remove(&mut script, &[opcodes::Ordinary::OP_CODESEPARATOR as u8]);
} }
@ -2629,27 +2627,29 @@ impl Builder {
/// Adds instructions to push an integer onto the stack. Integers are /// Adds instructions to push an integer onto the stack. Integers are
/// encoded as little-endian signed-magnitude numbers, but there are /// encoded as little-endian signed-magnitude numbers, but there are
/// dedicated opcodes to push some small integers. /// dedicated opcodes to push some small integers.
pub fn push_int(&mut self, data: i64) { pub fn push_int(mut self, data: i64) -> Builder {
// We can special-case -1, 1-16 // We can special-case -1, 1-16
if data == -1 || (data >= 1 && data <= 16) { if data == -1 || (data >= 1 && data <= 16) {
self.0.push((data + opcodes::OP_TRUE as i64) as u8); self.0.push((data + opcodes::OP_TRUE as i64) as u8);
self
} }
// We can also special-case zero // We can also special-case zero
else if data == 0 { else if data == 0 {
self.0.push(opcodes::OP_FALSE as u8); self.0.push(opcodes::OP_FALSE as u8);
self
} }
// Otherwise encode it as data // Otherwise encode it as data
else { self.push_scriptint(data); } else { self.push_scriptint(data) }
} }
/// Adds instructions to push an integer onto the stack, using the explicit /// Adds instructions to push an integer onto the stack, using the explicit
/// encoding regardless of the availability of dedicated opcodes. /// encoding regardless of the availability of dedicated opcodes.
pub fn push_scriptint(&mut self, data: i64) { pub fn push_scriptint(self, data: i64) -> Builder {
self.push_slice(&build_scriptint(data)); self.push_slice(&build_scriptint(data))
} }
/// Adds instructions to push some arbitrary data onto the stack /// Adds instructions to push some arbitrary data onto the stack
pub fn push_slice(&mut self, data: &[u8]) { pub fn push_slice(mut self, data: &[u8]) -> Builder {
// Start with a PUSH opcode // Start with a PUSH opcode
match data.len() { match data.len() {
n if n < opcodes::Ordinary::OP_PUSHDATA1 as usize => { self.0.push(n as u8); }, n if n < opcodes::Ordinary::OP_PUSHDATA1 as usize => { self.0.push(n as u8); },
@ -2673,11 +2673,13 @@ impl Builder {
} }
// Then push the acraw // Then push the acraw
self.0.extend(data.iter().cloned()); self.0.extend(data.iter().cloned());
self
} }
/// Adds a single opcode to the script /// Adds a single opcode to the script
pub fn push_opcode(&mut self, data: opcodes::All) { pub fn push_opcode(mut self, data: opcodes::All) -> Builder {
self.0.push(data as u8); self.0.push(data as u8);
self
} }
/// Converts the `Builder` into an unmodifiable `Script` /// Converts the `Builder` into an unmodifiable `Script`
@ -2773,25 +2775,37 @@ mod test {
assert_eq!(&script[..], &comp[..]); assert_eq!(&script[..], &comp[..]);
// small ints // small ints
script.push_int(1); comp.push(82u8); assert_eq!(&script[..], &comp[..]); script = script.push_int(1); comp.push(82u8); assert_eq!(&script[..], &comp[..]);
script.push_int(0); comp.push(0u8); assert_eq!(&script[..], &comp[..]); script = script.push_int(0); comp.push(0u8); assert_eq!(&script[..], &comp[..]);
script.push_int(4); comp.push(85u8); assert_eq!(&script[..], &comp[..]); script = script.push_int(4); comp.push(85u8); assert_eq!(&script[..], &comp[..]);
script.push_int(-1); comp.push(80u8); assert_eq!(&script[..], &comp[..]); script = script.push_int(-1); comp.push(80u8); assert_eq!(&script[..], &comp[..]);
// forced scriptint // forced scriptint
script.push_scriptint(4); comp.extend([1u8, 4].iter().cloned()); assert_eq!(&script[..], &comp[..]); script = script.push_scriptint(4); comp.extend([1u8, 4].iter().cloned()); assert_eq!(&script[..], &comp[..]);
// big ints // big ints
script.push_int(17); comp.extend([1u8, 17].iter().cloned()); assert_eq!(&script[..], &comp[..]); script = script.push_int(17); comp.extend([1u8, 17].iter().cloned()); assert_eq!(&script[..], &comp[..]);
script.push_int(10000); comp.extend([2u8, 16, 39].iter().cloned()); assert_eq!(&script[..], &comp[..]); script = script.push_int(10000); comp.extend([2u8, 16, 39].iter().cloned()); assert_eq!(&script[..], &comp[..]);
// notice the sign bit set here, hence the extra zero/128 at the end // notice the sign bit set here, hence the extra zero/128 at the end
script.push_int(10000000); comp.extend([4u8, 128, 150, 152, 0].iter().cloned()); assert_eq!(&script[..], &comp[..]); script = script.push_int(10000000); comp.extend([4u8, 128, 150, 152, 0].iter().cloned()); assert_eq!(&script[..], &comp[..]);
script.push_int(-10000000); comp.extend([4u8, 128, 150, 152, 128].iter().cloned()); assert_eq!(&script[..], &comp[..]); script = script.push_int(-10000000); comp.extend([4u8, 128, 150, 152, 128].iter().cloned()); assert_eq!(&script[..], &comp[..]);
// data // data
script.push_slice("NRA4VR".as_bytes()); comp.extend([6u8, 78, 82, 65, 52, 86, 82].iter().cloned()); assert_eq!(&script[..], &comp[..]); script = script.push_slice("NRA4VR".as_bytes()); comp.extend([6u8, 78, 82, 65, 52, 86, 82].iter().cloned()); assert_eq!(&script[..], &comp[..]);
// opcodes // opcodes
script.push_opcode(opcodes::All::OP_CHECKSIG); comp.push(0xACu8); assert_eq!(&script[..], &comp[..]); script = script.push_opcode(opcodes::All::OP_CHECKSIG); comp.push(0xACu8); assert_eq!(&script[..], &comp[..]);
script.push_opcode(opcodes::All::OP_CHECKSIG); comp.push(0xACu8); assert_eq!(&script[..], &comp[..]); script = script.push_opcode(opcodes::All::OP_CHECKSIG); comp.push(0xACu8); assert_eq!(&script[..], &comp[..]);
}
#[test]
fn script_builder() {
// from txid 3bb5e6434c11fb93f64574af5d116736510717f2c595eb45b52c28e31622dfff which was in my mempool when I wrote the test
let script = Builder::new().push_opcode(opcodes::All::OP_DUP)
.push_opcode(opcodes::All::OP_HASH160)
.push_slice(&"16e1ae70ff0fa102905d4af297f6912bda6cce19".from_hex().unwrap())
.push_opcode(opcodes::All::OP_EQUALVERIFY)
.push_opcode(opcodes::All::OP_CHECKSIG)
.into_script();
assert_eq!(&format!("{:x}", script), "76a91416e1ae70ff0fa102905d4af297f6912bda6cce1988ac");
} }
#[test] #[test]
@ -2824,7 +2838,7 @@ mod test {
let mut script = Builder::new(); let mut script = Builder::new();
assert!(script.clone().into_script().evaluate(&s, &mut vec![], None, None).is_ok()); assert!(script.clone().into_script().evaluate(&s, &mut vec![], None, None).is_ok());
script.push_opcode(opcodes::All::OP_RETURN); script = script.push_opcode(opcodes::All::OP_RETURN);
assert!(script.clone().into_script().evaluate(&s, &mut vec![], None, None).is_err()); assert!(script.clone().into_script().evaluate(&s, &mut vec![], None, None).is_err());
} }

View File

@ -76,22 +76,22 @@ impl Address {
/// Generates a script pubkey spending to this address /// Generates a script pubkey spending to this address
#[inline] #[inline]
pub fn script_pubkey(&self) -> script::Script { pub fn script_pubkey(&self) -> script::Script {
let mut script = script::Builder::new();
match self.ty { match self.ty {
Type::PubkeyHash => { Type::PubkeyHash => {
script.push_opcode(opcodes::All::OP_DUP); script::Builder::new()
script.push_opcode(opcodes::All::OP_HASH160); .push_opcode(opcodes::All::OP_DUP)
script.push_slice(&self.hash[..]); .push_opcode(opcodes::All::OP_HASH160)
script.push_opcode(opcodes::All::OP_EQUALVERIFY); .push_slice(&self.hash[..])
script.push_opcode(opcodes::All::OP_CHECKSIG); .push_opcode(opcodes::All::OP_EQUALVERIFY)
.push_opcode(opcodes::All::OP_CHECKSIG)
} }
Type::ScriptHash => { Type::ScriptHash => {
script.push_opcode(opcodes::All::OP_HASH160); script::Builder::new()
script.push_slice(&self.hash[..]); .push_opcode(opcodes::All::OP_HASH160)
script.push_opcode(opcodes::All::OP_EQUAL); .push_slice(&self.hash[..])
.push_opcode(opcodes::All::OP_EQUAL)
} }
} }.into_script()
script.into_script()
} }
} }

View File

@ -113,14 +113,14 @@ impl Template {
let mut key_index = 0; let mut key_index = 0;
let mut ret = script::Builder::new(); let mut ret = script::Builder::new();
for elem in &self.0 { for elem in &self.0 {
match *elem { ret = match *elem {
TemplateElement::Op(opcode) => ret.push_opcode(opcode), TemplateElement::Op(opcode) => ret.push_opcode(opcode),
TemplateElement::Key => { TemplateElement::Key => {
if key_index == keys.len() { if key_index == keys.len() {
return Err(Error::TooFewKeys(key_index)); return Err(Error::TooFewKeys(key_index));
} }
ret.push_slice(&keys[key_index].serialize_vec(&secp, true)[..]);
key_index += 1; key_index += 1;
ret.push_slice(&keys[key_index - 1].serialize_vec(&secp, true)[..])
} }
} }
} }
@ -215,19 +215,19 @@ pub fn untemplate(script: &script::Script) -> Result<(Template, Vec<PublicKey>),
match instruction { match instruction {
script::Instruction::PushBytes(data) => { script::Instruction::PushBytes(data) => {
let n = data.len(); let n = data.len();
match PublicKey::from_slice(&secp, data) { ret = match PublicKey::from_slice(&secp, data) {
Ok(key) => { Ok(key) => {
if n == 65 { return Err(Error::UncompressedKey); } if n == 65 { return Err(Error::UncompressedKey); }
if mode == Mode::SeekingCheckMulti { return Err(Error::ExpectedChecksig); } if mode == Mode::SeekingCheckMulti { return Err(Error::ExpectedChecksig); }
retkeys.push(key); retkeys.push(key);
ret.push_opcode(opcodes::All::from(PUBKEY));
mode = Mode::CopyingKeys; mode = Mode::CopyingKeys;
ret.push_opcode(opcodes::All::from(PUBKEY))
} }
Err(_) => { Err(_) => {
// Arbitrary pushes are only allowed before we've found any keys. // Arbitrary pushes are only allowed before we've found any keys.
// Otherwise we have to wait for a N CHECKSIG pair. // Otherwise we have to wait for a N CHECKSIG pair.
match mode { match mode {
Mode::SeekingKeys => { ret.push_slice(data); } Mode::SeekingKeys => { ret.push_slice(data) }
Mode::CopyingKeys => { return Err(Error::ExpectedKey); }, Mode::CopyingKeys => { return Err(Error::ExpectedKey); },
Mode::SeekingCheckMulti => { return Err(Error::ExpectedChecksig); } Mode::SeekingCheckMulti => { return Err(Error::ExpectedChecksig); }
} }
@ -257,7 +257,7 @@ pub fn untemplate(script: &script::Script) -> Result<(Template, Vec<PublicKey>),
// All other opcodes do nothing // All other opcodes do nothing
_ => {} _ => {}
} }
ret.push_opcode(op); ret = ret.push_opcode(op);
} }
script::Instruction::Error(e) => { return Err(Error::Script(e)); } script::Instruction::Error(e) => { return Err(Error::Script(e)); }
} }