diff --git a/Cargo.lock b/Cargo.lock index f73b185..f958ce5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -774,7 +774,9 @@ name = "chainscript" version = "0.1.0" dependencies = [ "displaydoc", + "flate2", "frame-support", + "hex", "hex-literal 0.3.3", "parity-scale-codec", "proptest", @@ -1486,9 +1488,9 @@ checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" [[package]] name = "flate2" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80edafed416a46fb378521624fab1cfa2eb514784fd8921adbe8a8d8321da811" +checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" dependencies = [ "cfg-if 1.0.0", "crc32fast", diff --git a/libs/chainscript/Cargo.toml b/libs/chainscript/Cargo.toml index 7dd8c78..f764305 100644 --- a/libs/chainscript/Cargo.toml +++ b/libs/chainscript/Cargo.toml @@ -27,6 +27,8 @@ frame-support = { default-features = false, version = '4.0.0-dev', git = 'https: [dev-dependencies] # serde = '1.0.119' +flate2 = "1.0.22" +hex = '0.4.3' hex-literal = "0.3.1" proptest = "1.0.0" diff --git a/libs/chainscript/src/lib.rs b/libs/chainscript/src/lib.rs index 5b4d783..d02410f 100644 --- a/libs/chainscript/src/lib.rs +++ b/libs/chainscript/src/lib.rs @@ -57,6 +57,8 @@ mod interpreter; pub mod opcodes; pub mod script; pub mod sighash; +#[cfg(test)] +mod test; #[cfg(feature = "testcontext")] pub use context::testcontext::TestContext; diff --git a/libs/chainscript/src/test/mod.rs b/libs/chainscript/src/test/mod.rs new file mode 100644 index 0000000..d424800 --- /dev/null +++ b/libs/chainscript/src/test/mod.rs @@ -0,0 +1,56 @@ +use crate::*; + +// Test the interpreter on all 4-byte combinations of non-trivial opcodes. +#[test] +//#[ignore = "This test is too expensive to run by default"] +fn test_4opc_sequences() { + use hex::FromHex; + use std::io::{BufRead, BufReader}; + + // The test vectors are encoded in a gzipped CSV file. + // Each line in the file is has the following comma-separated filelds: + // 1) The hex-encoded bitcoin script + // 2) The expected outcome, which is either 0 (script should fail) or 1 (script should succceed) + // 3) If the expected outcome is 1 (success), then a sequence of comma-separated hex-encoded + // stack items after the execution of the script follows. + // + // The test vectors were obtained obtained by running the script interpreter in Tapscript mode + // on all 4-opcode sequences of a subset of opcodes. Notable omissions include: + // * `OP_NOP_N`, `OP_SUCCESS_N`: These are trivial and including them would make the file much + // larger (and test run time much longer) with little benefit. + // * Opcodes dealing with checking signatures: These behave differently in Bitcoin. + // * `OP_PUSHDATA_N`: Some these should be included in the future here or in a separate test. + let test_vectors_raw = include_bytes!("test_vectors_4opc.csv.gz").as_ref(); + let test_vectors = BufReader::new(flate2::bufread::GzDecoder::new(test_vectors_raw)); + + let mut fails = 0u32; + for line in test_vectors.lines().map(|l| l.expect("can't get a line")) { + let mut parts = line.split(','); + // Load the script. + let script: Script = Vec::from_hex(parts.next().expect("no script")) + .expect("script not in hex format") + .into(); + + // Load the expected outcome. It should be either 0, or 1 followed by stack items. + let expected: Option = match parts.next().expect("no desired outcome") { + "0" => None, + "1" => { + // For successful outcome, read the expected sequence of items on the stack + let stack: Vec<_> = + parts.map(|s| Vec::from_hex(s).expect("hex item").into()).collect(); + Some(stack.into()) + } + _ => unreachable!("bad format"), + }; + + // Run the script and record mismatches between expected and actual outputs. + let result = run_script(&TestContext::default(), &script, vec![].into()).ok(); + if expected != result { + eprintln!("FAIL {:?}: {:?} vs. {:?}", script, result, expected); + fails += 1; + } + } + + // Let the test fail if we have at least one mismatch. + assert!(fails == 0, "{} tests failed", fails); +} diff --git a/libs/chainscript/src/test/test_vectors_4opc.csv.gz b/libs/chainscript/src/test/test_vectors_4opc.csv.gz new file mode 100644 index 0000000..c46c032 Binary files /dev/null and b/libs/chainscript/src/test/test_vectors_4opc.csv.gz differ diff --git a/pallets/utxo/src/script.rs b/pallets/utxo/src/script.rs index 4b06e8a..106e3fe 100644 --- a/pallets/utxo/src/script.rs +++ b/pallets/utxo/src/script.rs @@ -166,8 +166,8 @@ pub(crate) mod test { use super::*; use chainscript::Context; use core::time::Duration; - use sp_core::sr25519; use proptest::prelude::*; + use sp_core::sr25519; // Generate block time in seconds pub fn gen_block_time_real() -> impl Strategy { diff --git a/pallets/utxo/src/tests.rs b/pallets/utxo/src/tests.rs index 50c64cd..e22ba13 100644 --- a/pallets/utxo/src/tests.rs +++ b/pallets/utxo/src/tests.rs @@ -26,7 +26,6 @@ use frame_support::{ sp_io::crypto, sp_runtime::traits::{BlakeTwo256, Hash}, }; - use crate::script::test::gen_block_time_real; use crate::tokens::OutputData; use proptest::prelude::*;