Some error improvements

This commit is contained in:
Andrew Poelstra 2014-08-16 13:33:13 -07:00
parent 4c99653933
commit 8d1a3e1f7c
3 changed files with 64 additions and 52 deletions

View File

@ -27,6 +27,7 @@
use std::char::from_digit; use std::char::from_digit;
use std::default::Default; use std::default::Default;
use serialize::json; use serialize::json;
use serialize::hex::ToHex;
use crypto::digest::Digest; use crypto::digest::Digest;
use crypto::ripemd160::Ripemd160; use crypto::ripemd160::Ripemd160;
@ -65,6 +66,8 @@ pub enum ScriptError {
ElseWithoutIf, ElseWithoutIf,
/// An OP_ENDIF happened while not in an OP_IF tree /// An OP_ENDIF happened while not in an OP_IF tree
EndifWithoutIf, EndifWithoutIf,
/// An OP_EQUALVERIFY failed (expected, gotten)
EqualVerifyFailed(String, String),
/// An OP_IF happened with an empty stack /// An OP_IF happened with an empty stack
IfEmptyStack, IfEmptyStack,
/// An illegal opcode appeared in the script (does not need to be executed) /// An illegal opcode appeared in the script (does not need to be executed)
@ -83,6 +86,8 @@ pub enum ScriptError {
NegativeRoll, NegativeRoll,
/// Tried to execute a signature operation but no transaction context was provided /// Tried to execute a signature operation but no transaction context was provided
NoTransaction, NoTransaction,
/// An OP_NUMEQUALVERIFY failed (expected, gotten)
NumEqualVerifyFailed(i64, i64),
/// Tried to read an array off the stack as a number when it was more than 4 bytes /// Tried to read an array off the stack as a number when it was more than 4 bytes
NumericOverflow, NumericOverflow,
/// Some stack operation was done with an empty stack /// Some stack operation was done with an empty stack
@ -401,6 +406,8 @@ macro_rules! num_opcode(
}.as_slice())); }.as_slice()));
)* )*
$stack.push(Owned(build_scriptint($op))); $stack.push(Owned(build_scriptint($op)));
// Return a tuple of all the variables
($( $var ),*)
}); });
) )
@ -463,10 +470,10 @@ macro_rules! hash_opcode_provable(
// OP_VERIFY macro // OP_VERIFY macro
macro_rules! op_verify ( macro_rules! op_verify (
($stack:expr) => ( ($stack:expr, $err:expr) => (
match $stack.last().map(|v| read_scriptbool(v.as_slice())) { match $stack.last().map(|v| read_scriptbool(v.as_slice())) {
None => { return Err(VerifyEmptyStack); } None => { return Err(VerifyEmptyStack); }
Some(false) => { return Err(VerifyFailed); } Some(false) => { return Err($err); }
Some(true) => { $stack.pop(); } Some(true) => { $stack.pop(); }
} }
) )
@ -651,7 +658,7 @@ impl Script {
return Err(EndifWithoutIf); return Err(EndifWithoutIf);
} }
} }
opcodes::OP_VERIFY => op_verify!(stack), opcodes::OP_VERIFY => op_verify!(stack, VerifyFailed),
opcodes::OP_TOALTSTACK => { opcodes::OP_TOALTSTACK => {
match stack.pop() { match stack.pop() {
None => { return Err(PopEmptyStack); } None => { return Err(PopEmptyStack); }
@ -718,31 +725,34 @@ impl Script {
let a = stack.pop().unwrap(); let a = stack.pop().unwrap();
let b = stack.pop().unwrap(); let b = stack.pop().unwrap();
stack.push(Slice(if a == b { script_true } else { script_false })); stack.push(Slice(if a == b { script_true } else { script_false }));
if op == opcodes::OP_EQUALVERIFY { op_verify!(stack); } if op == opcodes::OP_EQUALVERIFY {
op_verify!(stack, EqualVerifyFailed(a.as_slice().to_hex(),
b.as_slice().to_hex()));
} }
opcodes::OP_1ADD => num_opcode!(stack(a): a + 1), }
opcodes::OP_1SUB => num_opcode!(stack(a): a - 1), opcodes::OP_1ADD => { num_opcode!(stack(a): a + 1); }
opcodes::OP_NEGATE => num_opcode!(stack(a): -a), opcodes::OP_1SUB => { num_opcode!(stack(a): a - 1); }
opcodes::OP_ABS => num_opcode!(stack(a): a.abs()), opcodes::OP_NEGATE => { num_opcode!(stack(a): -a); }
opcodes::OP_NOT => num_opcode!(stack(a): if a == 0 {1} else {0}), opcodes::OP_ABS => { num_opcode!(stack(a): a.abs()); }
opcodes::OP_0NOTEQUAL => num_opcode!(stack(a): if a != 0 {1} else {0}), opcodes::OP_NOT => { num_opcode!(stack(a): if a == 0 {1} else {0}); }
opcodes::OP_ADD => num_opcode!(stack(b, a): a + b), opcodes::OP_0NOTEQUAL => { num_opcode!(stack(a): if a != 0 {1} else {0}); }
opcodes::OP_SUB => num_opcode!(stack(b, a): a - b), opcodes::OP_ADD => { num_opcode!(stack(b, a): a + b); }
opcodes::OP_BOOLAND => num_opcode!(stack(b, a): if a != 0 && b != 0 {1} else {0}), opcodes::OP_SUB => { num_opcode!(stack(b, a): a - b); }
opcodes::OP_BOOLOR => num_opcode!(stack(b, a): if a != 0 || b != 0 {1} else {0}), opcodes::OP_BOOLAND => { num_opcode!(stack(b, a): if a != 0 && b != 0 {1} else {0}); }
opcodes::OP_NUMEQUAL => num_opcode!(stack(b, a): if a == b {1} else {0}), opcodes::OP_BOOLOR => { num_opcode!(stack(b, a): if a != 0 || b != 0 {1} else {0}); }
opcodes::OP_NUMNOTEQUAL => num_opcode!(stack(b, a): if a != b {1} else {0}), opcodes::OP_NUMEQUAL => { num_opcode!(stack(b, a): if a == b {1} else {0}); }
opcodes::OP_NUMNOTEQUAL => { num_opcode!(stack(b, a): if a != b {1} else {0}); }
opcodes::OP_NUMEQUALVERIFY => { opcodes::OP_NUMEQUALVERIFY => {
num_opcode!(stack(b, a): if a == b {1} else {0}); let (b, a) = num_opcode!(stack(b, a): if a == b {1} else {0});
op_verify!(stack); op_verify!(stack, NumEqualVerifyFailed(a, b));
} }
opcodes::OP_LESSTHAN => num_opcode!(stack(b, a): if a < b {1} else {0}), opcodes::OP_LESSTHAN => { num_opcode!(stack(b, a): if a < b {1} else {0}); }
opcodes::OP_GREATERTHAN => num_opcode!(stack(b, a): if a > b {1} else {0}), opcodes::OP_GREATERTHAN => { num_opcode!(stack(b, a): if a > b {1} else {0}); }
opcodes::OP_LESSTHANOREQUAL => num_opcode!(stack(b, a): if a <= b {1} else {0}), opcodes::OP_LESSTHANOREQUAL => { num_opcode!(stack(b, a): if a <= b {1} else {0}); }
opcodes::OP_GREATERTHANOREQUAL => num_opcode!(stack(b, a): if a >= b {1} else {0}), opcodes::OP_GREATERTHANOREQUAL => { num_opcode!(stack(b, a): if a >= b {1} else {0}); }
opcodes::OP_MIN => num_opcode!(stack(b, a): if a < b {a} else {b}), opcodes::OP_MIN => { num_opcode!(stack(b, a): if a < b {a} else {b}); }
opcodes::OP_MAX => num_opcode!(stack(b, a): if a > b {a} else {b}), opcodes::OP_MAX => { num_opcode!(stack(b, a): if a > b {a} else {b}); }
opcodes::OP_WITHIN => num_opcode!(stack(c, b, a): if b <= a && a < c {1} else {0}), opcodes::OP_WITHIN => { num_opcode!(stack(c, b, a): if b <= a && a < c {1} else {0}); }
opcodes::OP_RIPEMD160 => hash_opcode!(stack, Ripemd160), opcodes::OP_RIPEMD160 => hash_opcode!(stack, Ripemd160),
opcodes::OP_SHA1 => hash_opcode!(stack, Sha1), opcodes::OP_SHA1 => hash_opcode!(stack, Sha1),
opcodes::OP_SHA256 => hash_opcode!(stack, Sha256), opcodes::OP_SHA256 => hash_opcode!(stack, Sha256),
@ -780,7 +790,7 @@ impl Script {
Ok(()) => stack.push(Slice(script_true)), Ok(()) => stack.push(Slice(script_true)),
_ => stack.push(Slice(script_false)), _ => stack.push(Slice(script_false)),
} }
if op == opcodes::OP_CHECKSIGVERIFY { op_verify!(stack); } if op == opcodes::OP_CHECKSIGVERIFY { op_verify!(stack, VerifyFailed); }
} }
opcodes::OP_CHECKMULTISIG | opcodes::OP_CHECKMULTISIGVERIFY => { opcodes::OP_CHECKMULTISIG | opcodes::OP_CHECKMULTISIGVERIFY => {
// Read all the keys // Read all the keys
@ -856,7 +866,7 @@ impl Script {
} }
} }
} }
if op == opcodes::OP_CHECKMULTISIGVERIFY { op_verify!(stack); } if op == opcodes::OP_CHECKMULTISIGVERIFY { op_verify!(stack, VerifyFailed); }
} }
} }
} }
@ -1205,7 +1215,7 @@ mod test {
use serialize::hex::FromHex; use serialize::hex::FromHex;
use super::{Script, build_scriptint, read_scriptint, read_scriptbool}; use super::{Script, build_scriptint, read_scriptint, read_scriptbool};
use super::{NoTransaction, PopEmptyStack, VerifyFailed}; use super::{EqualVerifyFailed, NoTransaction, PopEmptyStack};
use super::Owned; use super::Owned;
use network::serialize::{deserialize, serialize}; use network::serialize::{deserialize, serialize};
@ -1302,7 +1312,9 @@ mod test {
// Should be able to check that the sig is there and pk correct // Should be able to check that the sig is there and pk correct
// before needing a transaction // before needing a transaction
assert_eq!(script_pk.evaluate(&mut vec![], None), Err(PopEmptyStack)); assert_eq!(script_pk.evaluate(&mut vec![], None), Err(PopEmptyStack));
assert_eq!(script_pk.evaluate(&mut vec![Owned(vec![]), Owned(vec![])], None), Err(VerifyFailed)); assert_eq!(script_pk.evaluate(&mut vec![Owned(vec![]), Owned(vec![])], None),
Err(EqualVerifyFailed("e729dea4a3a81108e16376d1cc329c91db589994".to_string(),
"b472a266d0bd89c13706a4132ccfb16f7c3b9fcb".to_string())));
// But if the signature is there, we need a tx to check it // But if the signature is there, we need a tx to check it
assert_eq!(script_pk.evaluate(&mut vec![Owned(vec![]), Owned("026d5d4cfef5f3d97d2263941b4d8e7aaa82910bf8e6f7c6cf1d8f0d755b9d2d1a".from_hex().unwrap())], None), Err(NoTransaction)); assert_eq!(script_pk.evaluate(&mut vec![Owned(vec![]), Owned("026d5d4cfef5f3d97d2263941b4d8e7aaa82910bf8e6f7c6cf1d8f0d755b9d2d1a".from_hex().unwrap())], None), Err(NoTransaction));
assert_eq!(script_pk.evaluate(&mut vec![Owned(vec![0]), Owned("026d5d4cfef5f3d97d2263941b4d8e7aaa82910bf8e6f7c6cf1d8f0d755b9d2d1a".from_hex().unwrap())], None), Err(NoTransaction)); assert_eq!(script_pk.evaluate(&mut vec![Owned(vec![0]), Owned("026d5d4cfef5f3d97d2263941b4d8e7aaa82910bf8e6f7c6cf1d8f0d755b9d2d1a".from_hex().unwrap())], None), Err(NoTransaction));

View File

@ -81,25 +81,24 @@ pub struct Transaction {
/// A transaction error /// A transaction error
#[deriving(PartialEq, Eq, Clone, Show)] #[deriving(PartialEq, Eq, Clone, Show)]
pub enum TransactionError { pub enum TransactionError {
/// Concatenated script failed in the input half (txid, script error) /// Concatenated script failed in the input half (script error)
InputScriptFailure(Sha256dHash, ScriptError), InputScriptFailure(ScriptError),
/// Concatenated script failed in the output half (txid, script error) /// Concatenated script failed in the output half (script error)
OutputScriptFailure(Sha256dHash, ScriptError), OutputScriptFailure(ScriptError),
/// P2SH serialized script failed (txid, script error) /// P2SH serialized script failed (script error)
P2shScriptFailure(Sha256dHash, ScriptError), P2shScriptFailure(ScriptError),
/// Script ended with false at the top of the stock (txid) /// Script ended with false at the top of the stack
ScriptReturnedFalse(Sha256dHash), ScriptReturnedFalse,
/// Script ended with nothing in the stack (txid) /// Script ended with nothing in the stack
ScriptReturnedEmptyStack(Sha256dHash), ScriptReturnedEmptyStack,
/// Script ended with nothing in the stack (txid, input txid, input vout) /// Script ended with nothing in the stack (input txid, input vout)
InputNotFound(Sha256dHash, Sha256dHash, u32), InputNotFound(Sha256dHash, u32),
} }
impl Transaction { impl Transaction {
/// Check a transaction for validity /// Check a transaction for validity
pub fn validate(&self, utxoset: &UtxoSet) -> Result<(), TransactionError> { pub fn validate(&self, utxoset: &UtxoSet) -> Result<(), TransactionError> {
let txid = self.bitcoin_hash();
for (n, input) in self.input.iter().enumerate() { for (n, input) in self.input.iter().enumerate() {
let txo = utxoset.get_utxo(input.prev_hash, input.prev_index); let txo = utxoset.get_utxo(input.prev_hash, input.prev_index);
match txo { match txo {
@ -110,7 +109,7 @@ impl Transaction {
let mut stack = Vec::with_capacity(6); let mut stack = Vec::with_capacity(6);
match input.script_sig.evaluate(&mut stack, Some((self, n))) { match input.script_sig.evaluate(&mut stack, Some((self, n))) {
Ok(_) => {} Ok(_) => {}
Err(e) => { return Err(InputScriptFailure(txid, e)); } Err(e) => { return Err(InputScriptFailure(e)); }
} }
if txo.script_pubkey.is_p2sh() && stack.len() > 0 { if txo.script_pubkey.is_p2sh() && stack.len() > 0 {
p2sh_stack = stack.clone(); p2sh_stack = stack.clone();
@ -122,24 +121,24 @@ impl Transaction {
} }
match txo.script_pubkey.evaluate(&mut stack, Some((self, n))) { match txo.script_pubkey.evaluate(&mut stack, Some((self, n))) {
Ok(_) => {} Ok(_) => {}
Err(e) => { return Err(OutputScriptFailure(txid, e)); } Err(e) => { return Err(OutputScriptFailure(e)); }
} }
match stack.pop() { match stack.pop() {
Some(v) => { Some(v) => {
if !read_scriptbool(v.as_slice()) { if !read_scriptbool(v.as_slice()) {
return Err(ScriptReturnedFalse(txid)); return Err(ScriptReturnedFalse);
} }
} }
None => { return Err(ScriptReturnedEmptyStack(txid)); } None => { return Err(ScriptReturnedEmptyStack); }
} }
if txo.script_pubkey.is_p2sh() { if txo.script_pubkey.is_p2sh() {
match p2sh_script.evaluate(&mut p2sh_stack, Some((self, n))) { match p2sh_script.evaluate(&mut p2sh_stack, Some((self, n))) {
Ok(_) => {} Ok(_) => {}
Err(e) => { return Err(P2shScriptFailure(txid, e)); } Err(e) => { return Err(P2shScriptFailure(e)); }
} }
} }
} }
None => { return Err(InputNotFound(txid, input.prev_hash, input.prev_index)); } None => { return Err(InputNotFound(input.prev_hash, input.prev_index)); }
} }
} }
Ok(()) Ok(())

View File

@ -54,8 +54,8 @@ pub enum UtxoSetError {
BadPrevHash(Sha256dHash, Sha256dHash), BadPrevHash(Sha256dHash, Sha256dHash),
/// A TXID was duplicated /// A TXID was duplicated
DuplicatedTxid(Sha256dHash), DuplicatedTxid(Sha256dHash),
/// A tx was invalid /// A tx was invalid (txid, error)
InvalidTx(TransactionError), InvalidTx(Sha256dHash, TransactionError),
} }
/// Vector of outputs; None indicates a nonexistent or already spent output /// Vector of outputs; None indicates a nonexistent or already spent output
@ -234,7 +234,7 @@ impl UtxoSet {
for tx in txes.slice(start, end).iter() { for tx in txes.slice(start, end).iter() {
match tx.validate(unsafe {&*s}) { match tx.validate(unsafe {&*s}) {
Ok(_) => {}, Ok(_) => {},
Err(e) => { return Err(InvalidTx(e)); } Err(e) => { return Err(InvalidTx(tx.bitcoin_hash(), e)); }
} }
} }
Ok(()) Ok(())
@ -266,7 +266,8 @@ impl UtxoSet {
None => { None => {
if validation >= TxoValidation { if validation >= TxoValidation {
self.rewind(block); self.rewind(block);
return Err(InvalidTx(InputNotFound(txid, input.prev_hash, input.prev_index))); return Err(InvalidTx(txid,
InputNotFound(input.prev_hash, input.prev_index)));
} }
} }
} }