Some error improvements
This commit is contained in:
parent
4c99653933
commit
8d1a3e1f7c
|
@ -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_1ADD => { num_opcode!(stack(a): a + 1); }
|
||||||
opcodes::OP_1SUB => num_opcode!(stack(a): a - 1),
|
opcodes::OP_1SUB => { num_opcode!(stack(a): a - 1); }
|
||||||
opcodes::OP_NEGATE => num_opcode!(stack(a): -a),
|
opcodes::OP_NEGATE => { num_opcode!(stack(a): -a); }
|
||||||
opcodes::OP_ABS => num_opcode!(stack(a): a.abs()),
|
opcodes::OP_ABS => { num_opcode!(stack(a): a.abs()); }
|
||||||
opcodes::OP_NOT => 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_0NOTEQUAL => num_opcode!(stack(a): if a != 0 {1} else {0}),
|
opcodes::OP_0NOTEQUAL => { num_opcode!(stack(a): if a != 0 {1} else {0}); }
|
||||||
opcodes::OP_ADD => num_opcode!(stack(b, a): a + b),
|
opcodes::OP_ADD => { num_opcode!(stack(b, a): a + b); }
|
||||||
opcodes::OP_SUB => num_opcode!(stack(b, a): a - b),
|
opcodes::OP_SUB => { 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_BOOLAND => { num_opcode!(stack(b, a): if a != 0 && b != 0 {1} else {0}); }
|
||||||
opcodes::OP_BOOLOR => num_opcode!(stack(b, a): if a != 0 || b != 0 {1} else {0}),
|
opcodes::OP_BOOLOR => { 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_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_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));
|
||||||
|
|
|
@ -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(())
|
||||||
|
|
|
@ -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)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue