diff --git a/src/blockdata/opcodes.rs b/src/blockdata/opcodes.rs index 7111315..e922b1e 100644 --- a/src/blockdata/opcodes.rs +++ b/src/blockdata/opcodes.rs @@ -20,11 +20,13 @@ #![allow(non_camel_case_types)] +use serialize::json; /// Submodule to handle -all- opcodes. Outside of this module we use /// a restricted set where the push/return/noop/illegal opcodes have /// a more convienient representation. pub mod all { + use serialize::json; // Heavy stick to translate between opcode types use std::mem::transmute; @@ -617,6 +619,12 @@ pub mod all { } } + impl json::ToJson for Opcode { + fn to_json(&self) -> json::Json { + json::String(self.to_string()) + } + } + /// Empty stack is also FALSE pub static OP_FALSE: Opcode = OP_PUSHBYTES_0; /// Number 1 is also TRUE @@ -624,7 +632,7 @@ pub mod all { } /// Broad categories of opcodes with similar behavior -#[deriving(PartialEq, Eq, Show)] +#[deriving(Clone, PartialEq, Eq, Show)] pub enum OpcodeClass { /// Pushes the given number onto the stack PushNum(int), @@ -640,6 +648,12 @@ pub enum OpcodeClass { Ordinary(Opcode) } +impl json::ToJson for OpcodeClass { + fn to_json(&self) -> json::Json { + json::String(self.to_string()) + } +} + macro_rules! ordinary_opcode( ($($op:ident),*) => ( #[repr(u8)] diff --git a/src/blockdata/script.rs b/src/blockdata/script.rs index 107130f..501a45b 100644 --- a/src/blockdata/script.rs +++ b/src/blockdata/script.rs @@ -98,6 +98,23 @@ pub enum ScriptError { VerifyFailed, } +impl json::ToJson for ScriptError { + fn to_json(&self) -> json::Json { + json::String(self.to_string()) + } +} + +/// A single iteration of a script execution +#[deriving(PartialEq, Eq, Show, Clone)] +pub struct TraceIteration { + index: uint, + opcode: allops::Opcode, + effect: opcodes::OpcodeClass, + stack: Vec +} + +impl_json!(TraceIteration, index, opcode, effect, stack) + /// Hashtype of a transaction, encoded in the last byte of a signature, /// specifically in the last 5 bits `byte & 31` #[deriving(PartialEq, Eq, Show, Clone)] @@ -563,8 +580,10 @@ impl Script { } /// Evaluate the script, modifying the stack in place - pub fn evaluate<'a>(&'a self, stack: &mut Vec>, input_context: Option<(&Transaction, uint)>) - -> Result<(), ScriptError> { + pub fn evaluate<'a>(&'a self, stack: &mut Vec>, + input_context: Option<(&Transaction, uint)>, + mut trace: Option<&mut Vec>) + -> Result<(), ScriptError> { let &Script(ref raw) = self; let secp = Secp256k1::new(); @@ -868,8 +887,20 @@ impl Script { } if op == opcodes::OP_CHECKMULTISIGVERIFY { op_verify!(stack, VerifyFailed); } } - } + } // end opcode match + } // end classification match + } // end loop + match trace { + Some(ref mut t) => { + let opcode = allops::Opcode::from_u8(byte); + t.push(TraceIteration { + index: index - 1, + opcode: opcode, + effect: opcode.classify(), + stack: stack.iter().map(|elem| elem.as_slice().to_hex()).collect() + }); } + None => {} } } Ok(()) @@ -1237,8 +1268,8 @@ mod test { for (n, script) in script_pk.iter().enumerate() { let mut stack = vec![]; - assert_eq!(tx.input[n].script_sig.evaluate(&mut stack, Some((&tx, n))), Ok(())); - assert_eq!(script.evaluate(&mut stack, Some((&tx, n))), Ok(())); + assert_eq!(tx.input[n].script_sig.evaluate(&mut stack, Some((&tx, n)), None), Ok(())); + assert_eq!(script.evaluate(&mut stack, Some((&tx, n)), None), Ok(())); assert!(stack.len() >= 1); assert_eq!(read_scriptbool(stack.pop().unwrap().as_slice()), true); } @@ -1299,10 +1330,10 @@ mod test { #[test] fn script_eval_simple() { let mut script = Script::new(); - assert!(script.evaluate(&mut vec![], None).is_ok()); + assert!(script.evaluate(&mut vec![], None, None).is_ok()); script.push_opcode(opcodes::all::OP_RETURN); - assert!(script.evaluate(&mut vec![], None).is_err()); + assert!(script.evaluate(&mut vec![], None, None).is_err()); } #[test] @@ -1311,13 +1342,13 @@ mod test { let script_pk: Script = deserialize(hex_pk.clone()).ok().expect("scriptpk"); // Should be able to check that the sig is there and pk correct // before needing a transaction - assert_eq!(script_pk.evaluate(&mut vec![], None), Err(PopEmptyStack)); - assert_eq!(script_pk.evaluate(&mut vec![Owned(vec![]), Owned(vec![])], None), + assert_eq!(script_pk.evaluate(&mut vec![], None, None), Err(PopEmptyStack)); + assert_eq!(script_pk.evaluate(&mut vec![Owned(vec![]), Owned(vec![])], None, None), Err(EqualVerifyFailed("e729dea4a3a81108e16376d1cc329c91db589994".to_string(), "b472a266d0bd89c13706a4132ccfb16f7c3b9fcb".to_string()))); // 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![0]), Owned("026d5d4cfef5f3d97d2263941b4d8e7aaa82910bf8e6f7c6cf1d8f0d755b9d2d1a".from_hex().unwrap())], None), Err(NoTransaction)); + assert_eq!(script_pk.evaluate(&mut vec![Owned(vec![]), Owned("026d5d4cfef5f3d97d2263941b4d8e7aaa82910bf8e6f7c6cf1d8f0d755b9d2d1a".from_hex().unwrap())], None, None), Err(NoTransaction)); + assert_eq!(script_pk.evaluate(&mut vec![Owned(vec![0]), Owned("026d5d4cfef5f3d97d2263941b4d8e7aaa82910bf8e6f7c6cf1d8f0d755b9d2d1a".from_hex().unwrap())], None, None), Err(NoTransaction)); } #[test] @@ -1331,8 +1362,8 @@ mod test { let script_pk: Script = deserialize(output_hex.clone()).ok().expect("scriptpk"); let mut stack = vec![]; - assert_eq!(tx.input[0].script_sig.evaluate(&mut stack, None), Ok(())); - assert_eq!(script_pk.evaluate(&mut stack, Some((&tx, 0))), Ok(())); + assert_eq!(tx.input[0].script_sig.evaluate(&mut stack, None, None), Ok(())); + assert_eq!(script_pk.evaluate(&mut stack, Some((&tx, 0)), None), Ok(())); assert_eq!(stack.len(), 1); assert_eq!(read_scriptbool(stack.pop().unwrap().as_slice()), true); } diff --git a/src/blockdata/transaction.rs b/src/blockdata/transaction.rs index e2c30da..f4a2c1e 100644 --- a/src/blockdata/transaction.rs +++ b/src/blockdata/transaction.rs @@ -24,9 +24,11 @@ //! use std::default::Default; +use serialize::hex::ToHex; +use serialize::json; use util::hash::Sha256dHash; -use blockdata::script::{mod, Script, ScriptError, read_scriptbool}; +use blockdata::script::{mod, Script, ScriptError, TraceIteration, read_scriptbool}; use blockdata::utxoset::UtxoSet; use network::encodable::ConsensusEncodable; use network::serialize::BitcoinHash; @@ -87,6 +89,10 @@ pub enum TransactionError { OutputScriptFailure(ScriptError), /// P2SH serialized script failed (script error) P2shScriptFailure(ScriptError), + /// P2SH serialized script ended with false at the top of the stack + P2shScriptReturnedFalse, + /// P2SH serialized script ended with nothing in the stack + P2shScriptReturnedEmptyStack, /// Script ended with false at the top of the stack ScriptReturnedFalse, /// Script ended with nothing in the stack @@ -95,6 +101,43 @@ pub enum TransactionError { InputNotFound(Sha256dHash, u32), } +impl json::ToJson for TransactionError { + fn to_json(&self) -> json::Json { + json::String(self.to_string()) + } +} + +/// A trace of a script execution +#[deriving(PartialEq, Eq, Show, Clone)] +pub struct ScriptTrace { + initial_stack: Vec, + iterations: Vec, + error: Option +} + +/// A trace of a transaction input's script execution +#[deriving(PartialEq, Eq, Clone, Show)] +pub struct InputTrace { + sig_trace: ScriptTrace, + pubkey_trace: Option, + p2sh_trace: Option, + error: Option +} + +impl_json!(ScriptTrace, initial_stack, iterations, error) +impl_json!(InputTrace, sig_trace, pubkey_trace, p2sh_trace, error) + +/// A trace of a transaction's execution +#[deriving(PartialEq, Eq, Clone, Show)] +pub struct TransactionTrace { + input_trace: Vec +} + +impl json::ToJson for TransactionTrace { + fn to_json(&self) -> json::Json { + self.input_trace.to_json() + } +} impl Transaction { /// Check a transaction for validity @@ -107,7 +150,7 @@ impl Transaction { let mut p2sh_script = Script::new(); 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)), None) { Ok(_) => {} Err(e) => { return Err(InputScriptFailure(e)); } } @@ -119,7 +162,7 @@ impl Transaction { None => unreachable!() }; } - match txo.script_pubkey.evaluate(&mut stack, Some((self, n))) { + match txo.script_pubkey.evaluate(&mut stack, Some((self, n)), None) { Ok(_) => {} Err(e) => { return Err(OutputScriptFailure(e)); } } @@ -132,10 +175,18 @@ impl Transaction { None => { return Err(ScriptReturnedEmptyStack); } } 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)), None) { Ok(_) => {} Err(e) => { return Err(P2shScriptFailure(e)); } } + match p2sh_stack.pop() { + Some(v) => { + if !read_scriptbool(v.as_slice()) { + return Err(P2shScriptReturnedFalse); + } + } + None => { return Err(P2shScriptReturnedEmptyStack); } + } } } None => { return Err(InputNotFound(input.prev_hash, input.prev_index)); } @@ -143,6 +194,106 @@ impl Transaction { } Ok(()) } + + /// Produce a trace of a transaction's execution + pub fn trace(&self, utxoset: &UtxoSet) -> TransactionTrace { + let mut ret = TransactionTrace { input_trace: Vec::with_capacity(self.input.len()) }; + for (n, input) in self.input.iter().enumerate() { + // Setup trace + let mut trace = InputTrace { + sig_trace: ScriptTrace { + initial_stack: vec![], + iterations: vec![], + error: None + }, + pubkey_trace: None, + p2sh_trace: None, + error: None + }; + // Run through the input + let txo = utxoset.get_utxo(input.prev_hash, input.prev_index); + match txo { + Some(txo) => { + let mut p2sh_stack = Vec::new(); + let mut p2sh_script = Script::new(); + + let mut stack = Vec::with_capacity(6); + match input.script_sig.evaluate(&mut stack, + Some((self, n)), + Some(&mut trace.sig_trace.iterations)) { + Ok(_) => {} + Err(e) => { + trace.sig_trace.error = Some(e.clone()); + trace.error = Some(InputScriptFailure(e)); + } + } + if txo.script_pubkey.is_p2sh() && stack.len() > 0 { + p2sh_stack = stack.clone(); + p2sh_script = match p2sh_stack.pop() { + Some(script::Owned(v)) => Script::from_vec(v), + Some(script::Slice(s)) => Script::from_vec(Vec::from_slice(s)), + None => unreachable!() + }; + } + if trace.error.is_none() { + let mut pk_trace = ScriptTrace { + initial_stack: stack.iter().map(|elem| elem.as_slice().to_hex()).collect(), + iterations: vec![], + error: None + }; + match txo.script_pubkey.evaluate(&mut stack, + Some((self, n)), + Some(&mut pk_trace.iterations)) { + Ok(_) => {} + Err(e) => { + pk_trace.error = Some(e.clone()); + trace.error = Some(OutputScriptFailure(e)); + } + } + match stack.pop() { + Some(v) => { + if !read_scriptbool(v.as_slice()) { + trace.error = Some(ScriptReturnedFalse); + } + } + None => { trace.error = Some(ScriptReturnedEmptyStack); } + } + trace.pubkey_trace = Some(pk_trace); + if trace.error.is_none() && txo.script_pubkey.is_p2sh() { + let mut p2sh_trace = ScriptTrace { + initial_stack: p2sh_stack.iter().map(|elem| elem.as_slice().to_hex()).collect(), + iterations: vec![], + error: None + }; + match p2sh_script.evaluate(&mut p2sh_stack, + Some((self, n)), + Some(&mut p2sh_trace.iterations)) { + Ok(_) => {} + Err(e) => { + p2sh_trace.error = Some(e.clone()); + trace.error = Some(P2shScriptFailure(e)); + } + } + match p2sh_stack.pop() { + Some(v) => { + if !read_scriptbool(v.as_slice()) { + trace.error = Some(P2shScriptReturnedFalse); + } + } + None => { trace.error = Some(P2shScriptReturnedEmptyStack); } + } + trace.p2sh_trace = Some(p2sh_trace); + } + } + } + None => { + trace.error = Some(InputNotFound(input.prev_hash, input.prev_index)); + } + } + ret.input_trace.push(trace); + } + ret + } } impl BitcoinHash for Transaction {