From 0e6e748844b1b31bc9c06b9a5858b1ece2738142 Mon Sep 17 00:00:00 2001 From: max143672 Date: Fri, 14 Jun 2024 11:16:32 +0300 Subject: [PATCH 01/93] implement new opcodes --- crypto/txscript/src/data_stack.rs | 7 +++ crypto/txscript/src/lib.rs | 41 ++++++++++++++++- crypto/txscript/src/opcodes/mod.rs | 72 ++++++++++++++++++++++++++---- 3 files changed, 111 insertions(+), 9 deletions(-) diff --git a/crypto/txscript/src/data_stack.rs b/crypto/txscript/src/data_stack.rs index 74988042a..823653b7d 100644 --- a/crypto/txscript/src/data_stack.rs +++ b/crypto/txscript/src/data_stack.rs @@ -343,6 +343,13 @@ mod tests { for test in tests { let serialized: Vec = OpcodeData::::serialize(&test.num); assert_eq!(serialized, test.serialized); + println!( + "number: {}; serialized: {}; be: {}, le: {}", + test.num, + hex::encode(serialized), + hex::encode(&test.num.to_be_bytes()), + hex::encode(&test.num.to_le_bytes()), + ) } } diff --git a/crypto/txscript/src/lib.rs b/crypto/txscript/src/lib.rs index 77cef45bc..c6186de2c 100644 --- a/crypto/txscript/src/lib.rs +++ b/crypto/txscript/src/lib.rs @@ -505,9 +505,10 @@ impl<'a, T: VerifiableTransaction> TxScriptEngine<'a, T> { mod tests { use std::iter::once; - use crate::opcodes::codes::{OpBlake2b, OpCheckSig, OpData1, OpData2, OpData32, OpDup, OpEqual, OpPushData1, OpTrue}; + use crate::opcodes::codes::{OpBlake2b, OpCheckSig, OpData1, OpData2, OpData32, OpDup, OpEqual, OpEqualVerify, OpGreaterThanOrEqual, OpInputAmount, OpInputSPK, OpOutputAmount, OpOutputSpk, OpPushData1, OpSub, OpTrue}; use super::*; + use crate::script_builder::ScriptBuilder; use kaspa_consensus_core::tx::{ PopulatedTransaction, ScriptPublicKey, Transaction, TransactionId, TransactionOutpoint, TransactionOutput, }; @@ -912,6 +913,44 @@ mod tests { ); } } + #[test] + fn output_gt_input_test() { + let threshold: i64 = 100; + let sig_cache = Cache::new(10_000); + let mut reused_values = SigHashReusedValues::new(); + let script = ScriptBuilder::new() + .add_ops(&[OpInputSPK, OpOutputSpk, OpEqualVerify, OpOutputAmount]) + .unwrap() + .add_i64(threshold) + .unwrap() + .add_ops(&[OpSub, OpInputAmount, OpGreaterThanOrEqual]) + .unwrap() + .drain(); + let spk = pay_to_script_hash_script(&script); + let input = TransactionInput { + previous_outpoint: TransactionOutpoint { + transaction_id: TransactionId::from_bytes([ + 0xc9, 0x97, 0xa5, 0xe5, 0x6e, 0x10, 0x41, 0x02, 0xfa, 0x20, 0x9c, 0x6a, 0x85, 0x2d, 0xd9, 0x06, 0x60, 0xa2, 0x0b, + 0x2d, 0x9c, 0x35, 0x24, 0x23, 0xed, 0xce, 0x25, 0x85, 0x7f, 0xcd, 0x37, 0x04, + ]), + index: 0, + }, + signature_script: ScriptBuilder::new().add_data(&script).unwrap().drain(), + sequence: 4294967295, + sig_op_count: 0, + }; + let input_value = 1000000000; + let output = TransactionOutput { value: 1000000000 + threshold as u64, script_public_key: spk.clone() }; + + let tx = Transaction::new(1, vec![input.clone()], vec![output.clone()], 0, Default::default(), 0, vec![]); + let utxo_entry = UtxoEntry::new(input_value, spk, 0, tx.is_coinbase()); + + let populated_tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]); + + let mut vm = TxScriptEngine::from_transaction_input(&populated_tx, &input, 0, &utxo_entry, &mut reused_values, &sig_cache) + .expect("Script creation failed"); + assert_eq!(vm.execute(), Ok(())); + } } #[cfg(test)] diff --git a/crypto/txscript/src/opcodes/mod.rs b/crypto/txscript/src/opcodes/mod.rs index 5d6096b7a..fca94ec77 100644 --- a/crypto/txscript/src/opcodes/mod.rs +++ b/crypto/txscript/src/opcodes/mod.rs @@ -877,10 +877,66 @@ opcode_list! { } // Undefined opcodes. - opcode OpUnknown178<0xb2, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) - opcode OpUnknown179<0xb3, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) - opcode OpUnknown180<0xb4, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) - opcode OpUnknown181<0xb5, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) + opcode OpInputSPK<0xb2, 1>(self, vm) { + match vm.script_source { + ScriptSource::TxInput{ + utxo_entry: kaspa_consensus_core::tx::UtxoEntry{ + script_public_key: spk @ kaspa_consensus_core::tx::ScriptPublicKey{ + version, ..}, + .. + }, + .. + } => { + let version = version.to_be_bytes(); + let script = spk.script(); + let mut v = Vec::with_capacity(version.len() + script.len()); + v.extend_from_slice(&version); + v.extend_from_slice(script); + vm.dstack.push(v); + Ok(()) + }, + _ => Err(TxScriptError::InvalidSource("OpInputSPK only applies to transaction inputs".to_string())) + } + } + opcode OpInputAmount<0xb3, 1>(self, vm) { + match vm.script_source { + ScriptSource::TxInput{ + utxo_entry: kaspa_consensus_core::tx::UtxoEntry{ + amount, + .. + }, + .. + } => { + push_number(*amount as i64, vm) + }, + _ => Err(TxScriptError::InvalidSource("OpInputAmount only applies to transaction inputs".to_string())) + } + } + opcode OpOutputSpk<0xb4, 1>(self, vm) { + match vm.script_source { + ScriptSource::TxInput{tx, id , ..} => { + let v = tx.outputs().get(id).map(|output| { + let version = output.script_public_key.version.to_be_bytes(); + let script = output.script_public_key.script(); + let mut v = Vec::with_capacity(version.len() + script.len()); + v.extend_from_slice(&version); + v.extend_from_slice(script); + v + }); + vm.dstack.push(v.unwrap_or_default()); + Ok(()) + }, + _ => Err(TxScriptError::InvalidSource("OpInputAmount only applies to transaction inputs".to_string())) + } + } + opcode OpOutputAmount<0xb5, 1>(self, vm) { + match vm.script_source { + ScriptSource::TxInput{tx, id , ..} => { + push_number(tx.outputs().get(id).map(|output| output.value).unwrap_or_default() as i64, vm) + }, + _ => Err(TxScriptError::InvalidSource("OpInputAmount only applies to transaction inputs".to_string())) + } + } opcode OpUnknown182<0xb6, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) opcode OpUnknown183<0xb7, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) opcode OpUnknown184<0xb8, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) @@ -1085,10 +1141,10 @@ mod test { let tests: Vec>> = vec![ opcodes::OpUnknown166::empty().expect("Should accept empty"), opcodes::OpUnknown167::empty().expect("Should accept empty"), - opcodes::OpUnknown178::empty().expect("Should accept empty"), - opcodes::OpUnknown179::empty().expect("Should accept empty"), - opcodes::OpUnknown180::empty().expect("Should accept empty"), - opcodes::OpUnknown181::empty().expect("Should accept empty"), + // opcodes::OpUnknown178::empty().expect("Should accept empty"), + // opcodes::OpUnknown179::empty().expect("Should accept empty"), + // opcodes::OpUnknown180::empty().expect("Should accept empty"), + // opcodes::OpUnknown181::empty().expect("Should accept empty"), opcodes::OpUnknown182::empty().expect("Should accept empty"), opcodes::OpUnknown183::empty().expect("Should accept empty"), opcodes::OpUnknown184::empty().expect("Should accept empty"), From 20767572db5989b79c907715b015637239593c75 Mon Sep 17 00:00:00 2001 From: max143672 Date: Sun, 16 Jun 2024 13:54:16 +0300 Subject: [PATCH 02/93] example of mutual tx --- Cargo.lock | 23 +++++ Cargo.toml | 1 + crypto/txscript/examples/kip-10.rs | 141 +++++++++++++++++++++++++++++ 3 files changed, 165 insertions(+) create mode 100644 crypto/txscript/examples/kip-10.rs diff --git a/Cargo.lock b/Cargo.lock index 22cd64f4f..0036b6a6c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -634,6 +634,22 @@ dependencies = [ "syn 2.0.60", ] +[[package]] +name = "bitcoin-internals" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9425c3bf7089c983facbae04de54513cce73b41c7f9ff8c845b54e7bc64ebbfb" + +[[package]] +name = "bitcoin_hashes" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b" +dependencies = [ + "bitcoin-internals", + "hex-conservative", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -1997,6 +2013,12 @@ dependencies = [ "serde", ] +[[package]] +name = "hex-conservative" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ed443af458ccb6d81c1e7e661545f94d3176752fb1df2f543b902a1e0f51e2" + [[package]] name = "hex-literal" version = "0.4.1" @@ -5305,6 +5327,7 @@ version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d24b59d129cdadea20aea4fb2352fa053712e5d713eee47d700cd4b2bc002f10" dependencies = [ + "bitcoin_hashes", "rand 0.8.5", "secp256k1-sys", "serde", diff --git a/Cargo.toml b/Cargo.toml index 283981df8..5f19cc91d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -221,6 +221,7 @@ secp256k1 = { version = "0.28.2", features = [ "global-context", "rand-std", "serde", + "hashes", ] } # TODO "0.28.0" separator = "0.4.1" seqlock = "0.2.0" diff --git a/crypto/txscript/examples/kip-10.rs b/crypto/txscript/examples/kip-10.rs new file mode 100644 index 000000000..75434e990 --- /dev/null +++ b/crypto/txscript/examples/kip-10.rs @@ -0,0 +1,141 @@ +//! # Kaspa Transaction Script Example +//! +//! This example demonstrates the use of custom opcodes and script execution within the Kaspa blockchain ecosystem. +//! There are two main scenarios: +//! +//! 1. **Owner scenario:** The script checks if the input is used by the owner and verifies the owner's signature. +//! 2. **Borrower scenario:** The script allows the input to be consumed if the output with the same index has a value of input + threshold and goes to the P2SH of the script itself. This scenario also includes a check where the threshold is not reached. + +use kaspa_consensus_core::{ + hashing::sighash::{calc_schnorr_signature_hash, SigHashReusedValues}, + hashing::sighash_type::SIG_HASH_ALL, + tx::{ + MutableTransaction, PopulatedTransaction, Transaction, TransactionId, TransactionInput, TransactionOutpoint, + TransactionOutput, UtxoEntry, VerifiableTransaction, + }, +}; +use kaspa_txscript::{ + caches::Cache, + opcodes::codes::{ + OpCheckSig, OpDup, OpElse, OpEndIf, OpEqualVerify, OpFalse, OpGreaterThanOrEqual, OpIf, OpInputAmount, OpInputSPK, + OpOutputAmount, OpOutputSpk, OpSub, OpTrue, + }, + pay_to_script_hash_script, + script_builder::{ScriptBuilder, ScriptBuilderResult}, + TxScriptEngine, +}; +use kaspa_txscript_errors::TxScriptError::EvalFalse; +use rand::thread_rng; +use secp256k1::Keypair; + +/// Main function to execute the Kaspa transaction script example. +/// +/// # Returns +/// +/// * `ScriptBuilderResult<()>` - Result of script builder operations. +fn main() -> ScriptBuilderResult<()> { + // Create a new key pair for the owner + let owner = Keypair::new(secp256k1::SECP256K1, &mut thread_rng()); + + // Set a threshold value for comparison + let threshold: i64 = 100; + + // Initialize a cache for signature verification + let sig_cache = Cache::new(10_000); + + // Prepare to reuse values for signature hashing + let mut reused_values = SigHashReusedValues::new(); + + // Create the script builder + let mut builder = ScriptBuilder::new(); + let script = builder + // Owner branch + .add_op(OpIf)? + .add_op(OpDup)? + .add_data(owner.x_only_public_key().0.serialize().as_slice())? + .add_op(OpEqualVerify)? + .add_op(OpCheckSig)? + // Borrower branch + .add_op(OpElse)? + .add_ops(&[OpInputSPK, OpOutputSpk, OpEqualVerify, OpOutputAmount])? + .add_i64(threshold)? + .add_ops(&[OpSub, OpInputAmount, OpGreaterThanOrEqual])? + .add_op(OpEndIf)? + .drain(); + + // Generate the script public key + let spk = pay_to_script_hash_script(&script); + + // Define the input value + let input_value = 1000000000; + + // Create a transaction output + let output = TransactionOutput { value: 1000000000 + threshold as u64, script_public_key: spk.clone() }; + + // Create a UTXO entry for the input + let utxo_entry = UtxoEntry::new(input_value, spk, 0, false); + + // Create a transaction input + let input = TransactionInput { + previous_outpoint: TransactionOutpoint { + transaction_id: TransactionId::from_bytes([ + 0xc9, 0x97, 0xa5, 0xe5, 0x6e, 0x10, 0x42, 0x02, 0xfa, 0x20, 0x9c, 0x6a, 0x85, 0x2d, 0xd9, 0x06, 0x60, 0xa2, 0x0b, + 0x2d, 0x9c, 0x35, 0x24, 0x23, 0xed, 0xce, 0x25, 0x85, 0x7f, 0xcd, 0x37, 0x04, + ]), + index: 0, + }, + signature_script: ScriptBuilder::new().add_data(&script)?.drain(), + sequence: 4294967295, + sig_op_count: 0, + }; + + // Create a transaction with the input and output + let mut tx = Transaction::new(1, vec![input.clone()], vec![output.clone()], 0, Default::default(), 0, vec![]); + + // Check owner branch + { + let mut tx = MutableTransaction::with_entries(tx.clone(), vec![utxo_entry.clone()]); + let sig_hash = calc_schnorr_signature_hash(&tx.as_verifiable(), 0, SIG_HASH_ALL, &mut reused_values); + let msg = secp256k1::Message::from_digest_slice(sig_hash.as_bytes().as_slice()).unwrap(); + + let sig = owner.sign_schnorr(msg); + let mut signature = Vec::new(); + signature.extend_from_slice(sig.as_ref().as_slice()); + signature.push(SIG_HASH_ALL.to_u8()); + + let mut builder = ScriptBuilder::new(); + builder.add_data(&signature)?; + builder.add_data(owner.x_only_public_key().0.serialize().as_slice())?; + builder.add_op(OpTrue)?; + builder.add_data(&script)?; + { + tx.tx.inputs[0].signature_script = builder.drain(); + } + + let tx = tx.as_verifiable(); + let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &mut reused_values, &sig_cache) + .expect("Script creation failed"); + assert_eq!(vm.execute(), Ok(())); + } + + // Check borrower branch + { + tx.inputs[0].signature_script = ScriptBuilder::new().add_op(OpFalse)?.add_data(&script)?.drain(); + let tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]); + let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &mut reused_values, &sig_cache) + .expect("Script creation failed"); + assert_eq!(vm.execute(), Ok(())); + } + + // Check borrower branch with threshold not reached + { + // Less than threshold + tx.outputs[0].value -= 1; + let tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]); + let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &mut reused_values, &sig_cache) + .expect("Script creation failed"); + assert_eq!(vm.execute(), Err(EvalFalse)); + } + + Ok(()) +} From 50fd9533113e0d43ff2e4a2c60dd3534eccf4a6d Mon Sep 17 00:00:00 2001 From: max143672 Date: Sun, 16 Jun 2024 14:00:32 +0300 Subject: [PATCH 03/93] add docs describing scenario --- crypto/txscript/Cargo.toml | 8 ++++++++ crypto/txscript/examples/kip-10.rs | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/crypto/txscript/Cargo.toml b/crypto/txscript/Cargo.toml index 6084df0b2..1b92e05c0 100644 --- a/crypto/txscript/Cargo.toml +++ b/crypto/txscript/Cargo.toml @@ -9,6 +9,14 @@ include.workspace = true license.workspace = true repository.workspace = true +[[example]] +name = "kip-10" +required-features = ["kip-10-mutual-tx"] + +[features] +hf = ["kip-10-mutual-tx"] +kip-10-mutual-tx = [] + [dependencies] blake2b_simd.workspace = true borsh.workspace = true diff --git a/crypto/txscript/examples/kip-10.rs b/crypto/txscript/examples/kip-10.rs index 75434e990..08233dafe 100644 --- a/crypto/txscript/examples/kip-10.rs +++ b/crypto/txscript/examples/kip-10.rs @@ -94,6 +94,7 @@ fn main() -> ScriptBuilderResult<()> { // Check owner branch { + println!("check owner scenario"); let mut tx = MutableTransaction::with_entries(tx.clone(), vec![utxo_entry.clone()]); let sig_hash = calc_schnorr_signature_hash(&tx.as_verifiable(), 0, SIG_HASH_ALL, &mut reused_values); let msg = secp256k1::Message::from_digest_slice(sig_hash.as_bytes().as_slice()).unwrap(); @@ -116,25 +117,30 @@ fn main() -> ScriptBuilderResult<()> { let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &mut reused_values, &sig_cache) .expect("Script creation failed"); assert_eq!(vm.execute(), Ok(())); + println!("owner scenario successes"); } // Check borrower branch { + println!("check borrower scenario"); tx.inputs[0].signature_script = ScriptBuilder::new().add_op(OpFalse)?.add_data(&script)?.drain(); let tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]); let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &mut reused_values, &sig_cache) .expect("Script creation failed"); assert_eq!(vm.execute(), Ok(())); + println!("borrower scenario successes"); } // Check borrower branch with threshold not reached { + println!("check borrower scenario with underflow"); // Less than threshold tx.outputs[0].value -= 1; let tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]); let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &mut reused_values, &sig_cache) .expect("Script creation failed"); assert_eq!(vm.execute(), Err(EvalFalse)); + println!("borrower scenario with underflow failed! all good"); } Ok(()) From 7afb955b57407d21ee60f6d0dfbf9908836a489d Mon Sep 17 00:00:00 2001 From: max143672 Date: Sun, 16 Jun 2024 14:11:54 +0300 Subject: [PATCH 04/93] introduce feature gate for new features --- Cargo.lock | 1 + crypto/txscript/Cargo.toml | 1 + crypto/txscript/src/opcodes/mod.rs | 124 +++++++++++++++++------------ 3 files changed, 76 insertions(+), 50 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0036b6a6c..60fd47e3a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3365,6 +3365,7 @@ version = "0.14.1" dependencies = [ "blake2b_simd", "borsh", + "cfg-if 1.0.0", "criterion", "hex", "indexmap 2.2.6", diff --git a/crypto/txscript/Cargo.toml b/crypto/txscript/Cargo.toml index 1b92e05c0..7b536d749 100644 --- a/crypto/txscript/Cargo.toml +++ b/crypto/txscript/Cargo.toml @@ -20,6 +20,7 @@ kip-10-mutual-tx = [] [dependencies] blake2b_simd.workspace = true borsh.workspace = true +cfg-if.workspace = true indexmap.workspace = true itertools.workspace = true kaspa-addresses.workspace = true diff --git a/crypto/txscript/src/opcodes/mod.rs b/crypto/txscript/src/opcodes/mod.rs index fca94ec77..e00c2b1d8 100644 --- a/crypto/txscript/src/opcodes/mod.rs +++ b/crypto/txscript/src/opcodes/mod.rs @@ -875,68 +875,92 @@ opcode_list! { _ => Err(TxScriptError::InvalidSource("LockTimeVerify only applies to transaction inputs".to_string())) } } - - // Undefined opcodes. opcode OpInputSPK<0xb2, 1>(self, vm) { - match vm.script_source { - ScriptSource::TxInput{ - utxo_entry: kaspa_consensus_core::tx::UtxoEntry{ - script_public_key: spk @ kaspa_consensus_core::tx::ScriptPublicKey{ - version, ..}, - .. - }, - .. - } => { - let version = version.to_be_bytes(); - let script = spk.script(); - let mut v = Vec::with_capacity(version.len() + script.len()); - v.extend_from_slice(&version); - v.extend_from_slice(script); - vm.dstack.push(v); - Ok(()) - }, - _ => Err(TxScriptError::InvalidSource("OpInputSPK only applies to transaction inputs".to_string())) + cfg_if::cfg_if! { + if #[cfg(feature = "kip-10-mutual-tx")] { + match vm.script_source { + ScriptSource::TxInput{ + utxo_entry: kaspa_consensus_core::tx::UtxoEntry{ + script_public_key: spk @ kaspa_consensus_core::tx::ScriptPublicKey{ + version, ..}, + .. + }, + .. + } => { + let version = version.to_be_bytes(); + let script = spk.script(); + let mut v = Vec::with_capacity(version.len() + script.len()); + v.extend_from_slice(&version); + v.extend_from_slice(script); + vm.dstack.push(v); + Ok(()) + }, + _ => Err(TxScriptError::InvalidSource("OpInputSPK only applies to transaction inputs".to_string())) + } + } else { + Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) + } } } opcode OpInputAmount<0xb3, 1>(self, vm) { - match vm.script_source { - ScriptSource::TxInput{ - utxo_entry: kaspa_consensus_core::tx::UtxoEntry{ - amount, - .. - }, - .. - } => { - push_number(*amount as i64, vm) - }, - _ => Err(TxScriptError::InvalidSource("OpInputAmount only applies to transaction inputs".to_string())) + cfg_if::cfg_if! { + if #[cfg(feature = "kip-10-mutual-tx")] { + match vm.script_source { + ScriptSource::TxInput{ + utxo_entry: kaspa_consensus_core::tx::UtxoEntry{ + amount, + .. + }, + .. + } => { + push_number(*amount as i64, vm) + }, + _ => Err(TxScriptError::InvalidSource("OpInputAmount only applies to transaction inputs".to_string())) + } + } else { + Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) + } } } opcode OpOutputSpk<0xb4, 1>(self, vm) { - match vm.script_source { - ScriptSource::TxInput{tx, id , ..} => { - let v = tx.outputs().get(id).map(|output| { - let version = output.script_public_key.version.to_be_bytes(); - let script = output.script_public_key.script(); - let mut v = Vec::with_capacity(version.len() + script.len()); - v.extend_from_slice(&version); - v.extend_from_slice(script); - v - }); - vm.dstack.push(v.unwrap_or_default()); - Ok(()) - }, - _ => Err(TxScriptError::InvalidSource("OpInputAmount only applies to transaction inputs".to_string())) + cfg_if::cfg_if! { + if #[cfg(feature = "kip-10-mutual-tx")] { + match vm.script_source { + ScriptSource::TxInput{tx, id , ..} => { + let v = tx.outputs().get(id).map(|output| { + let version = output.script_public_key.version.to_be_bytes(); + let script = output.script_public_key.script(); + let mut v = Vec::with_capacity(version.len() + script.len()); + v.extend_from_slice(&version); + v.extend_from_slice(script); + v + }); + vm.dstack.push(v.unwrap_or_default()); + Ok(()) + }, + _ => Err(TxScriptError::InvalidSource("OpInputAmount only applies to transaction inputs".to_string())) + } + } else { + Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) + } } } opcode OpOutputAmount<0xb5, 1>(self, vm) { - match vm.script_source { - ScriptSource::TxInput{tx, id , ..} => { - push_number(tx.outputs().get(id).map(|output| output.value).unwrap_or_default() as i64, vm) - }, - _ => Err(TxScriptError::InvalidSource("OpInputAmount only applies to transaction inputs".to_string())) + cfg_if::cfg_if! { + if #[cfg(feature = "kip-10-mutual-tx")] { + match vm.script_source { + ScriptSource::TxInput{tx, id , ..} => { + push_number(tx.outputs().get(id).map(|output| output.value).unwrap_or_default() as i64, vm) + }, + _ => Err(TxScriptError::InvalidSource("OpInputAmount only applies to transaction inputs".to_string())) + } + } else { + Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) + } } } + + // Undefined opcodes. opcode OpUnknown182<0xb6, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) opcode OpUnknown183<0xb7, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) opcode OpUnknown184<0xb8, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) From 993e91e12d41e68a7c116521e48763b580f2e0c3 Mon Sep 17 00:00:00 2001 From: max143672 Date: Sun, 16 Jun 2024 14:46:17 +0300 Subject: [PATCH 05/93] introduce hf feature that enables txscript hf feature --- consensus/Cargo.toml | 1 + consensus/client/Cargo.toml | 1 + consensus/wasm/Cargo.toml | 1 + kaspad/Cargo.toml | 1 + mining/Cargo.toml | 3 +++ testing/integration/Cargo.toml | 1 + wallet/core/Cargo.toml | 1 + wallet/keys/Cargo.toml | 1 + 8 files changed, 10 insertions(+) diff --git a/consensus/Cargo.toml b/consensus/Cargo.toml index f151e404b..2a623edf2 100644 --- a/consensus/Cargo.toml +++ b/consensus/Cargo.toml @@ -61,3 +61,4 @@ harness = false [features] html_reports = [] devnet-prealloc = ["kaspa-consensus-core/devnet-prealloc"] +hf = ["kaspa-txscript/hf"] diff --git a/consensus/client/Cargo.toml b/consensus/client/Cargo.toml index 38cbed9a3..24d228f80 100644 --- a/consensus/client/Cargo.toml +++ b/consensus/client/Cargo.toml @@ -11,6 +11,7 @@ repository.workspace = true [features] wasm32-sdk = [] wasm32-types = [] +hf = ["kaspa-txscript/hf"] [dependencies] kaspa-addresses.workspace = true diff --git a/consensus/wasm/Cargo.toml b/consensus/wasm/Cargo.toml index ea211f3f9..f3d92dd13 100644 --- a/consensus/wasm/Cargo.toml +++ b/consensus/wasm/Cargo.toml @@ -12,6 +12,7 @@ repository.workspace = true [features] wasm32-sdk = [] wasm32-types = [] +hf = ["kaspa-txscript/hf"] [dependencies] kaspa-consensus-core.workspace = true diff --git a/kaspad/Cargo.toml b/kaspad/Cargo.toml index 9f3290a51..8699ebb33 100644 --- a/kaspad/Cargo.toml +++ b/kaspad/Cargo.toml @@ -60,3 +60,4 @@ serde_with = "3.7.0" [features] heap = ["dhat", "kaspa-alloc/heap"] devnet-prealloc = ["kaspa-consensus/devnet-prealloc"] +hf = ["kaspa-txscript/hf"] diff --git a/mining/Cargo.toml b/mining/Cargo.toml index facd45d6a..a0db5619b 100644 --- a/mining/Cargo.toml +++ b/mining/Cargo.toml @@ -38,3 +38,6 @@ secp256k1.workspace = true [[bench]] name = "bench" harness = false + +[features] +hf = ["kaspa-txscript/hf"] diff --git a/testing/integration/Cargo.toml b/testing/integration/Cargo.toml index 7d9dd99af..284a38e49 100644 --- a/testing/integration/Cargo.toml +++ b/testing/integration/Cargo.toml @@ -75,3 +75,4 @@ kaspa-txscript-errors.workspace = true heap = ["dhat"] html_reports = [] devnet-prealloc = ["kaspad/devnet-prealloc"] +hf = ["kaspa-txscript/hf"] diff --git a/wallet/core/Cargo.toml b/wallet/core/Cargo.toml index fb31afb31..f46ed3a2a 100644 --- a/wallet/core/Cargo.toml +++ b/wallet/core/Cargo.toml @@ -27,6 +27,7 @@ wasm32-sdk = [ ] default = ["wasm32-sdk"] # default = [] +hf = ["kaspa-txscript/hf"] [lib] crate-type = ["cdylib", "lib"] diff --git a/wallet/keys/Cargo.toml b/wallet/keys/Cargo.toml index 7300c1de5..062b1a575 100644 --- a/wallet/keys/Cargo.toml +++ b/wallet/keys/Cargo.toml @@ -11,6 +11,7 @@ repository.workspace = true [features] default = [] +hf = ["kaspa-txscript/hf"] [lib] crate-type = ["cdylib", "lib"] From a0be8af89b81bc830482d8a141fdf7ec55c42426 Mon Sep 17 00:00:00 2001 From: max143672 Date: Sun, 16 Jun 2024 14:57:34 +0300 Subject: [PATCH 06/93] style: fmt and clippy fix --- crypto/txscript/src/data_stack.rs | 4 ++-- crypto/txscript/src/lib.rs | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/crypto/txscript/src/data_stack.rs b/crypto/txscript/src/data_stack.rs index 823653b7d..3e236e472 100644 --- a/crypto/txscript/src/data_stack.rs +++ b/crypto/txscript/src/data_stack.rs @@ -347,8 +347,8 @@ mod tests { "number: {}; serialized: {}; be: {}, le: {}", test.num, hex::encode(serialized), - hex::encode(&test.num.to_be_bytes()), - hex::encode(&test.num.to_le_bytes()), + hex::encode(test.num.to_be_bytes()), + hex::encode(test.num.to_le_bytes()), ) } } diff --git a/crypto/txscript/src/lib.rs b/crypto/txscript/src/lib.rs index c6186de2c..d8f041733 100644 --- a/crypto/txscript/src/lib.rs +++ b/crypto/txscript/src/lib.rs @@ -505,7 +505,10 @@ impl<'a, T: VerifiableTransaction> TxScriptEngine<'a, T> { mod tests { use std::iter::once; - use crate::opcodes::codes::{OpBlake2b, OpCheckSig, OpData1, OpData2, OpData32, OpDup, OpEqual, OpEqualVerify, OpGreaterThanOrEqual, OpInputAmount, OpInputSPK, OpOutputAmount, OpOutputSpk, OpPushData1, OpSub, OpTrue}; + use crate::opcodes::codes::{ + OpBlake2b, OpCheckSig, OpData1, OpData2, OpData32, OpDup, OpEqual, OpEqualVerify, OpGreaterThanOrEqual, OpInputAmount, + OpInputSPK, OpOutputAmount, OpOutputSpk, OpPushData1, OpSub, OpTrue, + }; use super::*; use crate::script_builder::ScriptBuilder; From f1eed926992955997f482cb16dca81e428142909 Mon Sep 17 00:00:00 2001 From: max143672 Date: Fri, 14 Jun 2024 11:16:32 +0300 Subject: [PATCH 07/93] implement new opcodes --- crypto/txscript/src/data_stack.rs | 4 +- crypto/txscript/src/lib.rs | 5 +- crypto/txscript/src/opcodes/mod.rs | 124 ++++++++++++----------------- 3 files changed, 53 insertions(+), 80 deletions(-) diff --git a/crypto/txscript/src/data_stack.rs b/crypto/txscript/src/data_stack.rs index 3e236e472..823653b7d 100644 --- a/crypto/txscript/src/data_stack.rs +++ b/crypto/txscript/src/data_stack.rs @@ -347,8 +347,8 @@ mod tests { "number: {}; serialized: {}; be: {}, le: {}", test.num, hex::encode(serialized), - hex::encode(test.num.to_be_bytes()), - hex::encode(test.num.to_le_bytes()), + hex::encode(&test.num.to_be_bytes()), + hex::encode(&test.num.to_le_bytes()), ) } } diff --git a/crypto/txscript/src/lib.rs b/crypto/txscript/src/lib.rs index d8f041733..c6186de2c 100644 --- a/crypto/txscript/src/lib.rs +++ b/crypto/txscript/src/lib.rs @@ -505,10 +505,7 @@ impl<'a, T: VerifiableTransaction> TxScriptEngine<'a, T> { mod tests { use std::iter::once; - use crate::opcodes::codes::{ - OpBlake2b, OpCheckSig, OpData1, OpData2, OpData32, OpDup, OpEqual, OpEqualVerify, OpGreaterThanOrEqual, OpInputAmount, - OpInputSPK, OpOutputAmount, OpOutputSpk, OpPushData1, OpSub, OpTrue, - }; + use crate::opcodes::codes::{OpBlake2b, OpCheckSig, OpData1, OpData2, OpData32, OpDup, OpEqual, OpEqualVerify, OpGreaterThanOrEqual, OpInputAmount, OpInputSPK, OpOutputAmount, OpOutputSpk, OpPushData1, OpSub, OpTrue}; use super::*; use crate::script_builder::ScriptBuilder; diff --git a/crypto/txscript/src/opcodes/mod.rs b/crypto/txscript/src/opcodes/mod.rs index e00c2b1d8..fca94ec77 100644 --- a/crypto/txscript/src/opcodes/mod.rs +++ b/crypto/txscript/src/opcodes/mod.rs @@ -875,92 +875,68 @@ opcode_list! { _ => Err(TxScriptError::InvalidSource("LockTimeVerify only applies to transaction inputs".to_string())) } } + + // Undefined opcodes. opcode OpInputSPK<0xb2, 1>(self, vm) { - cfg_if::cfg_if! { - if #[cfg(feature = "kip-10-mutual-tx")] { - match vm.script_source { - ScriptSource::TxInput{ - utxo_entry: kaspa_consensus_core::tx::UtxoEntry{ - script_public_key: spk @ kaspa_consensus_core::tx::ScriptPublicKey{ - version, ..}, - .. - }, - .. - } => { - let version = version.to_be_bytes(); - let script = spk.script(); - let mut v = Vec::with_capacity(version.len() + script.len()); - v.extend_from_slice(&version); - v.extend_from_slice(script); - vm.dstack.push(v); - Ok(()) - }, - _ => Err(TxScriptError::InvalidSource("OpInputSPK only applies to transaction inputs".to_string())) - } - } else { - Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) - } + match vm.script_source { + ScriptSource::TxInput{ + utxo_entry: kaspa_consensus_core::tx::UtxoEntry{ + script_public_key: spk @ kaspa_consensus_core::tx::ScriptPublicKey{ + version, ..}, + .. + }, + .. + } => { + let version = version.to_be_bytes(); + let script = spk.script(); + let mut v = Vec::with_capacity(version.len() + script.len()); + v.extend_from_slice(&version); + v.extend_from_slice(script); + vm.dstack.push(v); + Ok(()) + }, + _ => Err(TxScriptError::InvalidSource("OpInputSPK only applies to transaction inputs".to_string())) } } opcode OpInputAmount<0xb3, 1>(self, vm) { - cfg_if::cfg_if! { - if #[cfg(feature = "kip-10-mutual-tx")] { - match vm.script_source { - ScriptSource::TxInput{ - utxo_entry: kaspa_consensus_core::tx::UtxoEntry{ - amount, - .. - }, - .. - } => { - push_number(*amount as i64, vm) - }, - _ => Err(TxScriptError::InvalidSource("OpInputAmount only applies to transaction inputs".to_string())) - } - } else { - Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) - } + match vm.script_source { + ScriptSource::TxInput{ + utxo_entry: kaspa_consensus_core::tx::UtxoEntry{ + amount, + .. + }, + .. + } => { + push_number(*amount as i64, vm) + }, + _ => Err(TxScriptError::InvalidSource("OpInputAmount only applies to transaction inputs".to_string())) } } opcode OpOutputSpk<0xb4, 1>(self, vm) { - cfg_if::cfg_if! { - if #[cfg(feature = "kip-10-mutual-tx")] { - match vm.script_source { - ScriptSource::TxInput{tx, id , ..} => { - let v = tx.outputs().get(id).map(|output| { - let version = output.script_public_key.version.to_be_bytes(); - let script = output.script_public_key.script(); - let mut v = Vec::with_capacity(version.len() + script.len()); - v.extend_from_slice(&version); - v.extend_from_slice(script); - v - }); - vm.dstack.push(v.unwrap_or_default()); - Ok(()) - }, - _ => Err(TxScriptError::InvalidSource("OpInputAmount only applies to transaction inputs".to_string())) - } - } else { - Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) - } + match vm.script_source { + ScriptSource::TxInput{tx, id , ..} => { + let v = tx.outputs().get(id).map(|output| { + let version = output.script_public_key.version.to_be_bytes(); + let script = output.script_public_key.script(); + let mut v = Vec::with_capacity(version.len() + script.len()); + v.extend_from_slice(&version); + v.extend_from_slice(script); + v + }); + vm.dstack.push(v.unwrap_or_default()); + Ok(()) + }, + _ => Err(TxScriptError::InvalidSource("OpInputAmount only applies to transaction inputs".to_string())) } } opcode OpOutputAmount<0xb5, 1>(self, vm) { - cfg_if::cfg_if! { - if #[cfg(feature = "kip-10-mutual-tx")] { - match vm.script_source { - ScriptSource::TxInput{tx, id , ..} => { - push_number(tx.outputs().get(id).map(|output| output.value).unwrap_or_default() as i64, vm) - }, - _ => Err(TxScriptError::InvalidSource("OpInputAmount only applies to transaction inputs".to_string())) - } - } else { - Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) - } + match vm.script_source { + ScriptSource::TxInput{tx, id , ..} => { + push_number(tx.outputs().get(id).map(|output| output.value).unwrap_or_default() as i64, vm) + }, + _ => Err(TxScriptError::InvalidSource("OpInputAmount only applies to transaction inputs".to_string())) } } - - // Undefined opcodes. opcode OpUnknown182<0xb6, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) opcode OpUnknown183<0xb7, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) opcode OpUnknown184<0xb8, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) From 7b02bb22857481d93e05f1ea1442cf1b977d350a Mon Sep 17 00:00:00 2001 From: max143672 Date: Sun, 16 Jun 2024 13:54:16 +0300 Subject: [PATCH 08/93] example of mutual tx --- crypto/txscript/examples/kip-10.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/crypto/txscript/examples/kip-10.rs b/crypto/txscript/examples/kip-10.rs index 08233dafe..75434e990 100644 --- a/crypto/txscript/examples/kip-10.rs +++ b/crypto/txscript/examples/kip-10.rs @@ -94,7 +94,6 @@ fn main() -> ScriptBuilderResult<()> { // Check owner branch { - println!("check owner scenario"); let mut tx = MutableTransaction::with_entries(tx.clone(), vec![utxo_entry.clone()]); let sig_hash = calc_schnorr_signature_hash(&tx.as_verifiable(), 0, SIG_HASH_ALL, &mut reused_values); let msg = secp256k1::Message::from_digest_slice(sig_hash.as_bytes().as_slice()).unwrap(); @@ -117,30 +116,25 @@ fn main() -> ScriptBuilderResult<()> { let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &mut reused_values, &sig_cache) .expect("Script creation failed"); assert_eq!(vm.execute(), Ok(())); - println!("owner scenario successes"); } // Check borrower branch { - println!("check borrower scenario"); tx.inputs[0].signature_script = ScriptBuilder::new().add_op(OpFalse)?.add_data(&script)?.drain(); let tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]); let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &mut reused_values, &sig_cache) .expect("Script creation failed"); assert_eq!(vm.execute(), Ok(())); - println!("borrower scenario successes"); } // Check borrower branch with threshold not reached { - println!("check borrower scenario with underflow"); // Less than threshold tx.outputs[0].value -= 1; let tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]); let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &mut reused_values, &sig_cache) .expect("Script creation failed"); assert_eq!(vm.execute(), Err(EvalFalse)); - println!("borrower scenario with underflow failed! all good"); } Ok(()) From a67952542c7997f0ac8fb30b9b83cac46699de2e Mon Sep 17 00:00:00 2001 From: max143672 Date: Sun, 16 Jun 2024 14:00:32 +0300 Subject: [PATCH 09/93] add docs describing scenario --- crypto/txscript/examples/kip-10.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crypto/txscript/examples/kip-10.rs b/crypto/txscript/examples/kip-10.rs index 75434e990..08233dafe 100644 --- a/crypto/txscript/examples/kip-10.rs +++ b/crypto/txscript/examples/kip-10.rs @@ -94,6 +94,7 @@ fn main() -> ScriptBuilderResult<()> { // Check owner branch { + println!("check owner scenario"); let mut tx = MutableTransaction::with_entries(tx.clone(), vec![utxo_entry.clone()]); let sig_hash = calc_schnorr_signature_hash(&tx.as_verifiable(), 0, SIG_HASH_ALL, &mut reused_values); let msg = secp256k1::Message::from_digest_slice(sig_hash.as_bytes().as_slice()).unwrap(); @@ -116,25 +117,30 @@ fn main() -> ScriptBuilderResult<()> { let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &mut reused_values, &sig_cache) .expect("Script creation failed"); assert_eq!(vm.execute(), Ok(())); + println!("owner scenario successes"); } // Check borrower branch { + println!("check borrower scenario"); tx.inputs[0].signature_script = ScriptBuilder::new().add_op(OpFalse)?.add_data(&script)?.drain(); let tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]); let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &mut reused_values, &sig_cache) .expect("Script creation failed"); assert_eq!(vm.execute(), Ok(())); + println!("borrower scenario successes"); } // Check borrower branch with threshold not reached { + println!("check borrower scenario with underflow"); // Less than threshold tx.outputs[0].value -= 1; let tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]); let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &mut reused_values, &sig_cache) .expect("Script creation failed"); assert_eq!(vm.execute(), Err(EvalFalse)); + println!("borrower scenario with underflow failed! all good"); } Ok(()) From 2e61b44774c7e21291599d5541a22ba022f9671d Mon Sep 17 00:00:00 2001 From: max143672 Date: Sun, 16 Jun 2024 14:11:54 +0300 Subject: [PATCH 10/93] introduce feature gate for new features --- crypto/txscript/src/opcodes/mod.rs | 124 +++++++++++++++++------------ 1 file changed, 74 insertions(+), 50 deletions(-) diff --git a/crypto/txscript/src/opcodes/mod.rs b/crypto/txscript/src/opcodes/mod.rs index fca94ec77..e00c2b1d8 100644 --- a/crypto/txscript/src/opcodes/mod.rs +++ b/crypto/txscript/src/opcodes/mod.rs @@ -875,68 +875,92 @@ opcode_list! { _ => Err(TxScriptError::InvalidSource("LockTimeVerify only applies to transaction inputs".to_string())) } } - - // Undefined opcodes. opcode OpInputSPK<0xb2, 1>(self, vm) { - match vm.script_source { - ScriptSource::TxInput{ - utxo_entry: kaspa_consensus_core::tx::UtxoEntry{ - script_public_key: spk @ kaspa_consensus_core::tx::ScriptPublicKey{ - version, ..}, - .. - }, - .. - } => { - let version = version.to_be_bytes(); - let script = spk.script(); - let mut v = Vec::with_capacity(version.len() + script.len()); - v.extend_from_slice(&version); - v.extend_from_slice(script); - vm.dstack.push(v); - Ok(()) - }, - _ => Err(TxScriptError::InvalidSource("OpInputSPK only applies to transaction inputs".to_string())) + cfg_if::cfg_if! { + if #[cfg(feature = "kip-10-mutual-tx")] { + match vm.script_source { + ScriptSource::TxInput{ + utxo_entry: kaspa_consensus_core::tx::UtxoEntry{ + script_public_key: spk @ kaspa_consensus_core::tx::ScriptPublicKey{ + version, ..}, + .. + }, + .. + } => { + let version = version.to_be_bytes(); + let script = spk.script(); + let mut v = Vec::with_capacity(version.len() + script.len()); + v.extend_from_slice(&version); + v.extend_from_slice(script); + vm.dstack.push(v); + Ok(()) + }, + _ => Err(TxScriptError::InvalidSource("OpInputSPK only applies to transaction inputs".to_string())) + } + } else { + Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) + } } } opcode OpInputAmount<0xb3, 1>(self, vm) { - match vm.script_source { - ScriptSource::TxInput{ - utxo_entry: kaspa_consensus_core::tx::UtxoEntry{ - amount, - .. - }, - .. - } => { - push_number(*amount as i64, vm) - }, - _ => Err(TxScriptError::InvalidSource("OpInputAmount only applies to transaction inputs".to_string())) + cfg_if::cfg_if! { + if #[cfg(feature = "kip-10-mutual-tx")] { + match vm.script_source { + ScriptSource::TxInput{ + utxo_entry: kaspa_consensus_core::tx::UtxoEntry{ + amount, + .. + }, + .. + } => { + push_number(*amount as i64, vm) + }, + _ => Err(TxScriptError::InvalidSource("OpInputAmount only applies to transaction inputs".to_string())) + } + } else { + Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) + } } } opcode OpOutputSpk<0xb4, 1>(self, vm) { - match vm.script_source { - ScriptSource::TxInput{tx, id , ..} => { - let v = tx.outputs().get(id).map(|output| { - let version = output.script_public_key.version.to_be_bytes(); - let script = output.script_public_key.script(); - let mut v = Vec::with_capacity(version.len() + script.len()); - v.extend_from_slice(&version); - v.extend_from_slice(script); - v - }); - vm.dstack.push(v.unwrap_or_default()); - Ok(()) - }, - _ => Err(TxScriptError::InvalidSource("OpInputAmount only applies to transaction inputs".to_string())) + cfg_if::cfg_if! { + if #[cfg(feature = "kip-10-mutual-tx")] { + match vm.script_source { + ScriptSource::TxInput{tx, id , ..} => { + let v = tx.outputs().get(id).map(|output| { + let version = output.script_public_key.version.to_be_bytes(); + let script = output.script_public_key.script(); + let mut v = Vec::with_capacity(version.len() + script.len()); + v.extend_from_slice(&version); + v.extend_from_slice(script); + v + }); + vm.dstack.push(v.unwrap_or_default()); + Ok(()) + }, + _ => Err(TxScriptError::InvalidSource("OpInputAmount only applies to transaction inputs".to_string())) + } + } else { + Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) + } } } opcode OpOutputAmount<0xb5, 1>(self, vm) { - match vm.script_source { - ScriptSource::TxInput{tx, id , ..} => { - push_number(tx.outputs().get(id).map(|output| output.value).unwrap_or_default() as i64, vm) - }, - _ => Err(TxScriptError::InvalidSource("OpInputAmount only applies to transaction inputs".to_string())) + cfg_if::cfg_if! { + if #[cfg(feature = "kip-10-mutual-tx")] { + match vm.script_source { + ScriptSource::TxInput{tx, id , ..} => { + push_number(tx.outputs().get(id).map(|output| output.value).unwrap_or_default() as i64, vm) + }, + _ => Err(TxScriptError::InvalidSource("OpInputAmount only applies to transaction inputs".to_string())) + } + } else { + Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) + } } } + + // Undefined opcodes. opcode OpUnknown182<0xb6, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) opcode OpUnknown183<0xb7, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) opcode OpUnknown184<0xb8, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) From f7a3f77c55252da48d8697d3e1d72f691c9cc7b7 Mon Sep 17 00:00:00 2001 From: max143672 Date: Sun, 16 Jun 2024 14:59:20 +0300 Subject: [PATCH 11/93] style: fmt and clippy fix --- crypto/txscript/src/data_stack.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crypto/txscript/src/data_stack.rs b/crypto/txscript/src/data_stack.rs index 823653b7d..3e236e472 100644 --- a/crypto/txscript/src/data_stack.rs +++ b/crypto/txscript/src/data_stack.rs @@ -347,8 +347,8 @@ mod tests { "number: {}; serialized: {}; be: {}, le: {}", test.num, hex::encode(serialized), - hex::encode(&test.num.to_be_bytes()), - hex::encode(&test.num.to_le_bytes()), + hex::encode(test.num.to_be_bytes()), + hex::encode(test.num.to_le_bytes()), ) } } From 4afb1dc47844eb3b34799a0a91f462bad7dbfdfa Mon Sep 17 00:00:00 2001 From: max143672 Date: Sun, 16 Jun 2024 15:01:16 +0300 Subject: [PATCH 12/93] remove unused feature --- Cargo.lock | 23 ----------------------- Cargo.toml | 1 - 2 files changed, 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 60fd47e3a..3daf46f3b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -634,22 +634,6 @@ dependencies = [ "syn 2.0.60", ] -[[package]] -name = "bitcoin-internals" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9425c3bf7089c983facbae04de54513cce73b41c7f9ff8c845b54e7bc64ebbfb" - -[[package]] -name = "bitcoin_hashes" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b" -dependencies = [ - "bitcoin-internals", - "hex-conservative", -] - [[package]] name = "bitflags" version = "1.3.2" @@ -2013,12 +1997,6 @@ dependencies = [ "serde", ] -[[package]] -name = "hex-conservative" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ed443af458ccb6d81c1e7e661545f94d3176752fb1df2f543b902a1e0f51e2" - [[package]] name = "hex-literal" version = "0.4.1" @@ -5328,7 +5306,6 @@ version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d24b59d129cdadea20aea4fb2352fa053712e5d713eee47d700cd4b2bc002f10" dependencies = [ - "bitcoin_hashes", "rand 0.8.5", "secp256k1-sys", "serde", diff --git a/Cargo.toml b/Cargo.toml index 5f19cc91d..283981df8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -221,7 +221,6 @@ secp256k1 = { version = "0.28.2", features = [ "global-context", "rand-std", "serde", - "hashes", ] } # TODO "0.28.0" separator = "0.4.1" seqlock = "0.2.0" From cd5c169d07f2f6d1196eb178ab886efc6e817168 Mon Sep 17 00:00:00 2001 From: max143672 Date: Sun, 16 Jun 2024 15:02:53 +0300 Subject: [PATCH 13/93] fmt --- crypto/txscript/src/lib.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crypto/txscript/src/lib.rs b/crypto/txscript/src/lib.rs index c6186de2c..d8f041733 100644 --- a/crypto/txscript/src/lib.rs +++ b/crypto/txscript/src/lib.rs @@ -505,7 +505,10 @@ impl<'a, T: VerifiableTransaction> TxScriptEngine<'a, T> { mod tests { use std::iter::once; - use crate::opcodes::codes::{OpBlake2b, OpCheckSig, OpData1, OpData2, OpData32, OpDup, OpEqual, OpEqualVerify, OpGreaterThanOrEqual, OpInputAmount, OpInputSPK, OpOutputAmount, OpOutputSpk, OpPushData1, OpSub, OpTrue}; + use crate::opcodes::codes::{ + OpBlake2b, OpCheckSig, OpData1, OpData2, OpData32, OpDup, OpEqual, OpEqualVerify, OpGreaterThanOrEqual, OpInputAmount, + OpInputSPK, OpOutputAmount, OpOutputSpk, OpPushData1, OpSub, OpTrue, + }; use super::*; use crate::script_builder::ScriptBuilder; From 96cfd54b9dc7aa7bcbe1604ff2400d0f6be862a6 Mon Sep 17 00:00:00 2001 From: max143672 Date: Sun, 16 Jun 2024 15:38:42 +0300 Subject: [PATCH 14/93] make opcode invalid in case of feature disabled --- crypto/txscript/src/opcodes/mod.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/crypto/txscript/src/opcodes/mod.rs b/crypto/txscript/src/opcodes/mod.rs index e00c2b1d8..25741e090 100644 --- a/crypto/txscript/src/opcodes/mod.rs +++ b/crypto/txscript/src/opcodes/mod.rs @@ -1162,13 +1162,9 @@ mod test { #[test] fn test_opcode_invalid() { - let tests: Vec>> = vec![ + let mut tests: Vec>> = vec![ opcodes::OpUnknown166::empty().expect("Should accept empty"), opcodes::OpUnknown167::empty().expect("Should accept empty"), - // opcodes::OpUnknown178::empty().expect("Should accept empty"), - // opcodes::OpUnknown179::empty().expect("Should accept empty"), - // opcodes::OpUnknown180::empty().expect("Should accept empty"), - // opcodes::OpUnknown181::empty().expect("Should accept empty"), opcodes::OpUnknown182::empty().expect("Should accept empty"), opcodes::OpUnknown183::empty().expect("Should accept empty"), opcodes::OpUnknown184::empty().expect("Should accept empty"), @@ -1238,7 +1234,15 @@ mod test { opcodes::OpUnknown248::empty().expect("Should accept empty"), opcodes::OpUnknown249::empty().expect("Should accept empty"), ]; - + #[cfg(not(feature = "kip-10-mutual-tx"))] + { + tests.extend(&[ + opcodes::OpInputSPK::empty().expect("Should accept empty"), + opcodes::OpInputAmount::empty().expect("Should accept empty"), + opcodes::OpOutputSpk::empty().expect("Should accept empty"), + opcodes::OpOutputAmount::empty().expect("Should accept empty"), + ]) + } let cache = Cache::new(10_000); let mut reused_values = SigHashReusedValues::new(); let mut vm = TxScriptEngine::new(&mut reused_values, &cache); From 399fcd7d5347fb8b4b301ff1e34bfcfad16450db Mon Sep 17 00:00:00 2001 From: max143672 Date: Sun, 16 Jun 2024 15:40:47 +0300 Subject: [PATCH 15/93] feature gate test --- crypto/txscript/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crypto/txscript/src/lib.rs b/crypto/txscript/src/lib.rs index d8f041733..18047de86 100644 --- a/crypto/txscript/src/lib.rs +++ b/crypto/txscript/src/lib.rs @@ -916,7 +916,8 @@ mod tests { ); } } - #[test] + #[cfg(feature = "kip-10-mutual-tx")] + #[cfg_attr(feature = "kip-10-mutual-tx", test)] fn output_gt_input_test() { let threshold: i64 = 100; let sig_cache = Cache::new(10_000); From 8cff7fe209774859bc214b6ef7239177b01dbc41 Mon Sep 17 00:00:00 2001 From: max143672 Date: Sun, 16 Jun 2024 15:57:42 +0300 Subject: [PATCH 16/93] change test set based on feature add ci cd test --- .github/workflows/ci.yaml | 3 + crypto/txscript/src/lib.rs | 25 +- crypto/txscript/src/opcodes/mod.rs | 17 +- .../txscript/test-data/script_tests-hf.json | 5270 +++++++++++++++++ 4 files changed, 5300 insertions(+), 15 deletions(-) create mode 100644 crypto/txscript/test-data/script_tests-hf.json diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 43e21e237..521c4841e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -126,6 +126,9 @@ jobs: - name: Run cargo doc tests with features=no-asm on kaspa-hashes run: cargo test --doc --release -p kaspa-hashes --features=no-asm + - name: Run txscipt tests with features=hf on kaspa-txscipt + run: cargo nextest run --release -p kaspa-txscript --features=hf + # test-release: # name: Test Suite Release # runs-on: ${{ matrix.os }} diff --git a/crypto/txscript/src/lib.rs b/crypto/txscript/src/lib.rs index 18047de86..6c2012e10 100644 --- a/crypto/txscript/src/lib.rs +++ b/crypto/txscript/src/lib.rs @@ -505,13 +505,9 @@ impl<'a, T: VerifiableTransaction> TxScriptEngine<'a, T> { mod tests { use std::iter::once; - use crate::opcodes::codes::{ - OpBlake2b, OpCheckSig, OpData1, OpData2, OpData32, OpDup, OpEqual, OpEqualVerify, OpGreaterThanOrEqual, OpInputAmount, - OpInputSPK, OpOutputAmount, OpOutputSpk, OpPushData1, OpSub, OpTrue, - }; + use crate::opcodes::codes::{OpBlake2b, OpCheckSig, OpData1, OpData2, OpData32, OpDup, OpEqual, OpPushData1, OpTrue}; use super::*; - use crate::script_builder::ScriptBuilder; use kaspa_consensus_core::tx::{ PopulatedTransaction, ScriptPublicKey, Transaction, TransactionId, TransactionOutpoint, TransactionOutput, }; @@ -919,6 +915,13 @@ mod tests { #[cfg(feature = "kip-10-mutual-tx")] #[cfg_attr(feature = "kip-10-mutual-tx", test)] fn output_gt_input_test() { + use crate::opcodes::codes::{ + OpEqualVerify, OpGreaterThanOrEqual, OpInputAmount, OpInputSPK, OpOutputAmount, OpOutputSpk, OpSub, + }; + + use super::*; + use crate::script_builder::ScriptBuilder; + let threshold: i64 = 100; let sig_cache = Cache::new(10_000); let mut reused_values = SigHashReusedValues::new(); @@ -960,6 +963,7 @@ mod tests { #[cfg(test)] mod bitcoind_tests { // Bitcoind tests + use cfg_if::cfg_if; use serde::Deserialize; use std::fs::File; use std::io::BufReader; @@ -1146,8 +1150,15 @@ mod bitcoind_tests { #[test] fn test_bitcoind_tests() { - let file = File::open(Path::new(env!("CARGO_MANIFEST_DIR")).join("test-data").join("script_tests.json")) - .expect("Could not find test file"); + cfg_if!( + if #[cfg(feature = "hf")] { + let file_name = "script_tests-hf.json"; + } else { + let file_name = "script_tests.json"; + } + ); + let file = + File::open(Path::new(env!("CARGO_MANIFEST_DIR")).join("test-data").join(file_name)).expect("Could not find test file"); let reader = BufReader::new(file); // Read the JSON contents of the file as an instance of `User`. diff --git a/crypto/txscript/src/opcodes/mod.rs b/crypto/txscript/src/opcodes/mod.rs index 25741e090..5c498293e 100644 --- a/crypto/txscript/src/opcodes/mod.rs +++ b/crypto/txscript/src/opcodes/mod.rs @@ -1162,6 +1162,7 @@ mod test { #[test] fn test_opcode_invalid() { + #[allow(unused_mut)] let mut tests: Vec>> = vec![ opcodes::OpUnknown166::empty().expect("Should accept empty"), opcodes::OpUnknown167::empty().expect("Should accept empty"), @@ -1234,15 +1235,15 @@ mod test { opcodes::OpUnknown248::empty().expect("Should accept empty"), opcodes::OpUnknown249::empty().expect("Should accept empty"), ]; + #[cfg(not(feature = "kip-10-mutual-tx"))] - { - tests.extend(&[ - opcodes::OpInputSPK::empty().expect("Should accept empty"), - opcodes::OpInputAmount::empty().expect("Should accept empty"), - opcodes::OpOutputSpk::empty().expect("Should accept empty"), - opcodes::OpOutputAmount::empty().expect("Should accept empty"), - ]) - } + tests.extend([ + opcodes::OpInputSPK::empty().expect("Should accept empty"), + opcodes::OpInputAmount::empty().expect("Should accept empty"), + opcodes::OpOutputSpk::empty().expect("Should accept empty"), + opcodes::OpOutputAmount::empty().expect("Should accept empty"), + ]); + let cache = Cache::new(10_000); let mut reused_values = SigHashReusedValues::new(); let mut vm = TxScriptEngine::new(&mut reused_values, &cache); diff --git a/crypto/txscript/test-data/script_tests-hf.json b/crypto/txscript/test-data/script_tests-hf.json new file mode 100644 index 000000000..47686397c --- /dev/null +++ b/crypto/txscript/test-data/script_tests-hf.json @@ -0,0 +1,5270 @@ +[ + [ + "Format is: [[wit..., amount]?, scriptSig, scriptPubKey, flags, expected_scripterror, ... comments]" + ], + [ + "It is evaluated as if there was a crediting coinbase transaction with two 0" + ], + [ + "pushes as scriptSig, and one output of 0 satoshi and given scriptPubKey," + ], + [ + "followed by a spending transaction which spends this output as only input (and" + ], + [ + "correct prevout hash), using the given scriptSig. All nLockTimes are 0, all" + ], + [ + "nSequences are max." + ], + [ + "", + "DEPTH 0 EQUAL", + "", + "OK", + "Test the test: we should have an empty stack after scriptSig evaluation" + ], + [ + " ", + "DEPTH 0 EQUAL", + "", + "OK", + "and multiple spaces should not change that." + ], + [ + " ", + "DEPTH 0 EQUAL", + "", + "OK" + ], + [ + " ", + "DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "1 2", + "2 EQUALVERIFY 1 EQUAL", + "", + "OK", + "Similarly whitespace around and between symbols" + ], + [ + "1 2", + "2 EQUALVERIFY 1 EQUAL", + "", + "OK" + ], + [ + " 1 2", + "2 EQUALVERIFY 1 EQUAL", + "", + "OK" + ], + [ + "1 2 ", + "2 EQUALVERIFY 1 EQUAL", + "", + "OK" + ], + [ + " 1 2 ", + "2 EQUALVERIFY 1 EQUAL", + "", + "OK" + ], + [ + "1", + "", + "", + "OK" + ], + [ + "0x02 0x01 0x00", + "", + "", + "OK", + "all bytes are significant, not only the last one" + ], + [ + "0x09 0x00000000 0x00000000 0x10", + "", + "", + "OK", + "equals zero when cast to Int64" + ], + [ + "0x01 0x11", + "17 EQUAL", + "", + "OK", + "push 1 byte" + ], + [ + "0x02 0x417a", + "'Az' EQUAL", + "", + "OK" + ], + [ + "0x4b 0x417a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a", + "'Azzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz' EQUAL", + "", + "OK", + "push 75 bytes" + ], + [ + "0x4c 0x4c 0x417a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a", + "'Azzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz' EQUAL", + "", + "OK", + "0x4c is OP_PUSHDATA1 (push 76 bytes)" + ], + [ + "0x4d 0x0001 0x417a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a", + "'Azzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz' EQUAL", + "", + "OK", + "0x4d is OP_PUSHDATA2" + ], + [ + "0x4f 1000", + "ADD 999 EQUAL", + "", + "OK" + ], + [ + "0", + "IF 0x50 ENDIF 1", + "", + "OK", + "0x50 is reserved (ok if not executed)" + ], + [ + "0x51", + "0x5f ADD 0x60 EQUAL", + "", + "OK", + "0x51 through 0x60 push 1 through 16 onto stack" + ], + [ + "1", + "NOP", + "", + "OK" + ], + [ + "0", + "IF VER ELSE 1 ENDIF", + "", + "OK", + "VER non-functional (ok if not executed)" + ], + [ + "0", + "IF RESERVED RESERVED1 RESERVED2 ELSE 1 ENDIF", + "", + "OK", + "RESERVED ok in un-executed IF" + ], + [ + "1", + "DUP IF ENDIF", + "", + "OK" + ], + [ + "1", + "IF 1 ENDIF", + "", + "OK" + ], + [ + "1", + "DUP IF ELSE ENDIF", + "", + "OK" + ], + [ + "1", + "IF 1 ELSE ENDIF", + "", + "OK" + ], + [ + "0", + "IF ELSE 1 ENDIF", + "", + "OK" + ], + [ + "1 1", + "IF IF 1 ELSE 0 ENDIF ENDIF", + "", + "OK" + ], + [ + "1 0", + "IF IF 1 ELSE 0 ENDIF ENDIF", + "", + "OK" + ], + [ + "1 1", + "IF IF 1 ELSE 0 ENDIF ELSE IF 0 ELSE 1 ENDIF ENDIF", + "", + "OK" + ], + [ + "0 0", + "IF IF 1 ELSE 0 ENDIF ELSE IF 0 ELSE 1 ENDIF ENDIF", + "", + "OK" + ], + [ + "1 0", + "NOTIF IF 1 ELSE 0 ENDIF ENDIF", + "", + "OK" + ], + [ + "1 1", + "NOTIF IF 1 ELSE 0 ENDIF ENDIF", + "", + "OK" + ], + [ + "1 0", + "NOTIF IF 1 ELSE 0 ENDIF ELSE IF 0 ELSE 1 ENDIF ENDIF", + "", + "OK" + ], + [ + "0 1", + "NOTIF IF 1 ELSE 0 ENDIF ELSE IF 0 ELSE 1 ENDIF ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0 ELSE 1 ELSE 0 ENDIF", + "", + "OK", + "Multiple ELSE's are valid and executed inverts on each ELSE encountered" + ], + [ + "1", + "IF 1 ELSE 0 ELSE ENDIF", + "", + "OK" + ], + [ + "1", + "IF ELSE 0 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "1", + "IF 1 ELSE 0 ELSE 1 ENDIF ADD 2 EQUAL", + "", + "OK" + ], + [ + "'' 1", + "IF SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ENDIF 0x20 0x2c49a55fe0ca3e7a005420c19a527865df8f17e468d234f562ef238d4236a632 EQUAL", + "", + "OK" + ], + [ + "1", + "NOTIF 0 ELSE 1 ELSE 0 ENDIF", + "", + "OK", + "Multiple ELSE's are valid and execution inverts on each ELSE encountered" + ], + [ + "0", + "NOTIF 1 ELSE 0 ELSE ENDIF", + "", + "OK" + ], + [ + "0", + "NOTIF ELSE 0 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "NOTIF 1 ELSE 0 ELSE 1 ENDIF ADD 2 EQUAL", + "", + "OK" + ], + [ + "'' 0", + "NOTIF SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ENDIF 0x20 0x2c49a55fe0ca3e7a005420c19a527865df8f17e468d234f562ef238d4236a632 EQUAL", + "", + "OK" + ], + [ + "0", + "IF 1 IF RETURN ELSE RETURN ELSE RETURN ENDIF ELSE 1 IF 1 ELSE RETURN ELSE 1 ENDIF ELSE RETURN ENDIF ADD 2 EQUAL", + "", + "OK", + "Nested ELSE ELSE" + ], + [ + "1", + "NOTIF 0 NOTIF RETURN ELSE RETURN ELSE RETURN ENDIF ELSE 0 NOTIF 1 ELSE RETURN ELSE 1 ENDIF ELSE RETURN ENDIF ADD 2 EQUAL", + "", + "OK" + ], + [ + "0", + "IF RETURN ENDIF 1", + "", + "OK", + "RETURN only works if executed" + ], + [ + "1 1", + "VERIFY", + "", + "OK" + ], + [ + "1 0x05 0x01 0x00 0x00 0x00 0x00", + "VERIFY", + "", + "OK", + "values >4 bytes can be cast to boolean" + ], + [ + "0x01 0x80", + "VERIFY TRUE", + "", + "VERIFY", + "negative 0 is false" + ], + [ + "10 0 11", + "TOALTSTACK DROP FROMALTSTACK ADD 21 EQUAL", + "", + "OK" + ], + [ + "'gavin_was_here'", + "TOALTSTACK 11 FROMALTSTACK 'gavin_was_here' EQUALVERIFY 11 EQUAL", + "", + "OK" + ], + [ + "0", + "IFDUP DEPTH 1 EQUALVERIFY 0 EQUAL", + "", + "OK" + ], + [ + "1", + "IFDUP DEPTH 2 EQUALVERIFY 1 EQUALVERIFY 1 EQUAL", + "", + "OK" + ], + [ + "0x05 0x0100000000", + "IFDUP DEPTH 2 EQUALVERIFY 0x05 0x0100000000 EQUALVERIFY DROP TRUE", + "", + "OK", + "IFDUP dups non ints" + ], + [ + "0", + "DROP DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "0", + "DUP 1 ADD 1 EQUALVERIFY 0 EQUAL", + "", + "OK" + ], + [ + "0 1", + "NIP", + "", + "OK" + ], + [ + "1 0", + "OVER DEPTH 3 EQUALVERIFY DROP DROP DROP TRUE", + "", + "OK" + ], + [ + "22 21 20", + "0 PICK 20 EQUALVERIFY DEPTH 3 EQUALVERIFY DROP DROP DROP TRUE", + "", + "OK" + ], + [ + "22 21 20", + "1 PICK 21 EQUALVERIFY DEPTH 3 EQUALVERIFY DROP DROP DROP TRUE", + "", + "OK" + ], + [ + "22 21 20", + "2 PICK 22 EQUALVERIFY DEPTH 3 EQUALVERIFY DROP DROP DROP TRUE", + "", + "OK" + ], + [ + "22 21 20", + "0 ROLL 20 EQUALVERIFY DEPTH 2 EQUALVERIFY DROP DROP TRUE", + "", + "OK" + ], + [ + "22 21 20", + "1 ROLL 21 EQUALVERIFY DEPTH 2 EQUALVERIFY DROP DROP TRUE", + "", + "OK" + ], + [ + "22 21 20", + "2 ROLL 22 EQUALVERIFY DEPTH 2 EQUALVERIFY DROP DROP TRUE", + "", + "OK" + ], + [ + "22 21 20", + "ROT 22 EQUALVERIFY DROP DROP TRUE", + "", + "OK" + ], + [ + "22 21 20", + "ROT DROP 20 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "22 21 20", + "ROT DROP DROP 21 EQUAL", + "", + "OK" + ], + [ + "22 21 20", + "ROT ROT 21 EQUAL 2DROP", + "", + "OK" + ], + [ + "22 21 20", + "ROT ROT ROT 20 EQUALVERIFY DROP DROP TRUE", + "", + "OK" + ], + [ + "25 24 23 22 21 20", + "2ROT 24 EQUALVERIFY DROP DROP DROP DROP DROP TRUE", + "", + "OK" + ], + [ + "25 24 23 22 21 20", + "2ROT DROP 25 EQUALVERIFY DROP DROP DROP DROP TRUE", + "", + "OK" + ], + [ + "25 24 23 22 21 20", + "2ROT 2DROP 20 EQUALVERIFY DROP DROP DROP TRUE", + "", + "OK" + ], + [ + "25 24 23 22 21 20", + "2ROT 2DROP DROP 21 EQUALVERIFY 2DROP TRUE", + "", + "OK" + ], + [ + "25 24 23 22 21 20", + "2ROT 2DROP 2DROP 22 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "25 24 23 22 21 20", + "2ROT 2DROP 2DROP DROP 23 EQUALVERIFY TRUE", + "", + "OK" + ], + [ + "25 24 23 22 21 20", + "2ROT 2ROT 22 EQUALVERIFY 2DROP DROP DROP DROP TRUE", + "", + "OK" + ], + [ + "25 24 23 22 21 20", + "2ROT 2ROT 2ROT 20 EQUALVERIFY DROP DROP DROP DROP DROP TRUE", + "", + "OK" + ], + [ + "1 0", + "SWAP 1 EQUALVERIFY 0 EQUAL", + "", + "OK" + ], + [ + "0 1", + "TUCK DEPTH 3 EQUALVERIFY SWAP 2DROP", + "", + "OK" + ], + [ + "13 14", + "2DUP ROT EQUALVERIFY EQUAL", + "", + "OK" + ], + [ + "-1 0 1 2", + "3DUP DEPTH 7 EQUALVERIFY ADD ADD 3 EQUALVERIFY 2DROP 0 EQUALVERIFY", + "", + "OK" + ], + [ + "1 2 3 5", + "2OVER ADD ADD 8 EQUALVERIFY ADD ADD 6 EQUAL", + "", + "OK" + ], + [ + "1 3 5 7", + "2SWAP ADD 4 EQUALVERIFY ADD 12 EQUAL", + "", + "OK" + ], + [ + "0", + "SIZE 0 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "1", + "SIZE 1 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "127", + "SIZE 1 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "128", + "SIZE 2 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "32767", + "SIZE 2 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "32768", + "SIZE 3 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "8388607", + "SIZE 3 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "8388608", + "SIZE 4 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "2147483647", + "SIZE 4 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "2147483648", + "SIZE 5 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "549755813887", + "SIZE 5 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "549755813888", + "SIZE 6 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "9223372036854775807", + "SIZE 8 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "-1", + "SIZE 1 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "-127", + "SIZE 1 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "-128", + "SIZE 2 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "-32767", + "SIZE 2 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "-32768", + "SIZE 3 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "-8388607", + "SIZE 3 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "-8388608", + "SIZE 4 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "-2147483647", + "SIZE 4 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "-2147483648", + "SIZE 5 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "-549755813887", + "SIZE 5 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "-549755813888", + "SIZE 6 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "-9223372036854775807", + "SIZE 8 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "'abcdefghijklmnopqrstuvwxyz'", + "SIZE 26 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "42", + "SIZE 1 EQUALVERIFY 42 EQUAL", + "", + "OK", + "SIZE does not consume argument" + ], + [ + "2 -2", + "ADD 0 EQUAL", + "", + "OK" + ], + [ + "2147483647 -2147483647", + "ADD 0 EQUAL", + "", + "OK" + ], + [ + "-1 -1", + "ADD -2 EQUAL", + "", + "OK" + ], + [ + "0 0", + "EQUAL", + "", + "OK" + ], + [ + "1 1", + "ADD 2 EQUAL", + "", + "OK" + ], + [ + "1", + "1ADD 2 EQUAL", + "", + "OK" + ], + [ + "111", + "1SUB 110 EQUAL", + "", + "OK" + ], + [ + "111 1", + "ADD 12 SUB 100 EQUAL", + "", + "OK" + ], + [ + "0", + "ABS 0 EQUAL", + "", + "OK" + ], + [ + "16", + "ABS 16 EQUAL", + "", + "OK" + ], + [ + "-16", + "ABS -16 NEGATE EQUAL", + "", + "OK" + ], + [ + "0", + "NOT NOP", + "", + "OK" + ], + [ + "1", + "NOT 0 EQUAL", + "", + "OK" + ], + [ + "11", + "NOT 0 EQUAL", + "", + "OK" + ], + [ + "0", + "0NOTEQUAL 0 EQUAL", + "", + "OK" + ], + [ + "1", + "0NOTEQUAL 1 EQUAL", + "", + "OK" + ], + [ + "111", + "0NOTEQUAL 1 EQUAL", + "", + "OK" + ], + [ + "-111", + "0NOTEQUAL 1 EQUAL", + "", + "OK" + ], + [ + "1 1", + "BOOLAND NOP", + "", + "OK" + ], + [ + "1 0", + "BOOLAND NOT", + "", + "OK" + ], + [ + "0 1", + "BOOLAND NOT", + "", + "OK" + ], + [ + "0 0", + "BOOLAND NOT", + "", + "OK" + ], + [ + "16 17", + "BOOLAND NOP", + "", + "OK" + ], + [ + "1 1", + "BOOLOR NOP", + "", + "OK" + ], + [ + "1 0", + "BOOLOR NOP", + "", + "OK" + ], + [ + "0 1", + "BOOLOR NOP", + "", + "OK" + ], + [ + "0 0", + "BOOLOR NOT", + "", + "OK" + ], + [ + "16 17", + "BOOLOR NOP", + "", + "OK" + ], + [ + "11 10 1", + "ADD NUMEQUAL", + "", + "OK" + ], + [ + "11 10 1", + "ADD NUMEQUALVERIFY 1", + "", + "OK" + ], + [ + "11 10 1", + "ADD NUMNOTEQUAL NOT", + "", + "OK" + ], + [ + "111 10 1", + "ADD NUMNOTEQUAL", + "", + "OK" + ], + [ + "11 10", + "LESSTHAN NOT", + "", + "OK" + ], + [ + "4 4", + "LESSTHAN NOT", + "", + "OK" + ], + [ + "10 11", + "LESSTHAN", + "", + "OK" + ], + [ + "-11 11", + "LESSTHAN", + "", + "OK" + ], + [ + "-11 -10", + "LESSTHAN", + "", + "OK" + ], + [ + "11 10", + "GREATERTHAN", + "", + "OK" + ], + [ + "4 4", + "GREATERTHAN NOT", + "", + "OK" + ], + [ + "10 11", + "GREATERTHAN NOT", + "", + "OK" + ], + [ + "-11 11", + "GREATERTHAN NOT", + "", + "OK" + ], + [ + "-11 -10", + "GREATERTHAN NOT", + "", + "OK" + ], + [ + "11 10", + "LESSTHANOREQUAL NOT", + "", + "OK" + ], + [ + "4 4", + "LESSTHANOREQUAL", + "", + "OK" + ], + [ + "10 11", + "LESSTHANOREQUAL", + "", + "OK" + ], + [ + "-11 11", + "LESSTHANOREQUAL", + "", + "OK" + ], + [ + "-11 -10", + "LESSTHANOREQUAL", + "", + "OK" + ], + [ + "11 10", + "GREATERTHANOREQUAL", + "", + "OK" + ], + [ + "4 4", + "GREATERTHANOREQUAL", + "", + "OK" + ], + [ + "10 11", + "GREATERTHANOREQUAL NOT", + "", + "OK" + ], + [ + "-11 11", + "GREATERTHANOREQUAL NOT", + "", + "OK" + ], + [ + "-11 -10", + "GREATERTHANOREQUAL NOT", + "", + "OK" + ], + [ + "1 0", + "MIN 0 NUMEQUAL", + "", + "OK" + ], + [ + "0 1", + "MIN 0 NUMEQUAL", + "", + "OK" + ], + [ + "-1 0", + "MIN -1 NUMEQUAL", + "", + "OK" + ], + [ + "0 -2147483647", + "MIN -2147483647 NUMEQUAL", + "", + "OK" + ], + [ + "2147483647 0", + "MAX 2147483647 NUMEQUAL", + "", + "OK" + ], + [ + "0 100", + "MAX 100 NUMEQUAL", + "", + "OK" + ], + [ + "-100 0", + "MAX 0 NUMEQUAL", + "", + "OK" + ], + [ + "0 -2147483647", + "MAX 0 NUMEQUAL", + "", + "OK" + ], + [ + "0 0 1", + "WITHIN", + "", + "OK" + ], + [ + "1 0 1", + "WITHIN NOT", + "", + "OK" + ], + [ + "0 -2147483647 2147483647", + "WITHIN", + "", + "OK" + ], + [ + "-1 -100 100", + "WITHIN", + "", + "OK" + ], + [ + "11 -100 100", + "WITHIN", + "", + "OK" + ], + [ + "-2147483647 -100 100", + "WITHIN NOT", + "", + "OK" + ], + [ + "2147483647 -100 100", + "WITHIN NOT", + "", + "OK" + ], + [ + "2147483647 2147483647", + "SUB 0 EQUAL", + "", + "OK" + ], + [ + "2147483647", + "DUP ADD 4294967294 EQUAL", + "", + "OK", + ">32 bit EQUAL is valid" + ], + [ + "2147483647", + "NEGATE DUP ADD -4294967294 EQUAL", + "", + "OK" + ], + [ + "''", + "SHA256 0x20 0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 EQUAL", + "", + "OK" + ], + [ + "'a'", + "SHA256 0x20 0xca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb EQUAL", + "", + "OK" + ], + [ + "'abcdefghijklmnopqrstuvwxyz'", + "SHA256 0x20 0x71c480df93d6ae2f1efad1447c66c9525e316218cf51fc8d9ed832f2daf18b73 EQUAL", + "", + "OK" + ], + [ + "''", + "SHA256 0x20 0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 EQUAL", + "", + "OK" + ], + [ + "'a'", + "SHA256 0x20 0xca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb EQUAL", + "", + "OK" + ], + [ + "'abcdefghijklmnopqrstuvwxyz'", + "SHA256 0x20 0x71c480df93d6ae2f1efad1447c66c9525e316218cf51fc8d9ed832f2daf18b73 EQUAL", + "", + "OK" + ], + [ + "''", + "SHA256 0x20 0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 EQUAL", + "", + "OK" + ], + [ + "'a'", + "SHA256 0x20 0xca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb EQUAL", + "", + "OK" + ], + [ + "'abcdefghijklmnopqrstuvwxyz'", + "SHA256 0x20 0x71c480df93d6ae2f1efad1447c66c9525e316218cf51fc8d9ed832f2daf18b73 EQUAL", + "", + "OK" + ], + [ + "''", + "NOP BLAKE2B 0x20 0x0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8 EQUAL", + "", + "OK" + ], + [ + "'a'", + "BLAKE2B NOP 0x20 0x8928aae63c84d87ea098564d1e03ad813f107add474e56aedd286349c0c03ea4 EQUAL", + "", + "OK" + ], + [ + "'abcdefghijklmnopqrstuvwxyz'", + "NOP BLAKE2B 0x20 0x117ad6b940f5e8292c007d9c7e7350cd33cf85b5887e8da71c7957830f536e7c EQUAL", + "", + "OK", + "The NOP is added so the script won't be interpreted as P2SH" + ], + [ + "'a'", + "NOP BLAKE2B 0x20 0x8928aae63c84d87ea098564d1e03ad813f107add474e56aedd286349c0c03ea4 EQUAL", + "", + "OK" + ], + [ + "0", + "IF 0xbd ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xbe ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xbf ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xc0 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xc1 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xc2 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xc3 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xc4 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xc5 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xc6 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xc7 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xc8 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xc9 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xca ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xcb ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xcc ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xcd ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xce ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xcf ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xd0 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xd1 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xd2 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xd3 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xd4 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xd5 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xd6 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xd7 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xd8 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xd9 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xda ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xdb ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xdc ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xdd ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xde ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xdf ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xe0 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xe1 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xe2 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xe3 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xe4 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xe5 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xe6 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xe7 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xe8 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xe9 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xea ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xeb ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xec ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xed ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xee ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xef ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xf0 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xf1 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xf2 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xf3 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xf4 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xf5 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xf6 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xf7 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xf8 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xf9 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xfa ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xfb ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xfc ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xfd ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xfe ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xff ELSE 1 ENDIF", + "", + "OK" + ], + [ + "", + "'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'", + "", + "OK", + "520 byte push" + ], + [ + "1", + "0x616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161", + "", + "OK", + "201 opcodes executed. 0x61 is NOP" + ], + [ + "1 2 3 4 5", + "0x6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f 1 2 3 4 5 0x6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f 0x6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d75", + "", + "OK", + "244 stack size (0x6f is 3DUP, 0x6d is 2DROP, and 0x75 is DROP)" + ], + [ + "'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'", + "'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 0x6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f 2DUP DROP 0x6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d 0x61616161", + "", + "OK", + "Max-size (10,000-byte), max-push(520 bytes), max-opcodes(201), max stack size(244 items). 0x6f is 3DUP, 0x61 is NOP, 0x6d is 2DROP" + ], + [ + "0", + "IF 0x5050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050 ENDIF 1", + "", + "OK", + ">201 opcodes, but RESERVED (0x50) doesn't count towards opcode limit." + ], + [ + "", + "1", + "", + "OK" + ], + [ + "127", + "0x01 0x7F EQUAL", + "", + "OK" + ], + [ + "128", + "0x02 0x8000 EQUAL", + "", + "OK", + "Leave room for the sign bit" + ], + [ + "32767", + "0x02 0xFF7F EQUAL", + "", + "OK" + ], + [ + "32768", + "0x03 0x008000 EQUAL", + "", + "OK" + ], + [ + "8388607", + "0x03 0xFFFF7F EQUAL", + "", + "OK" + ], + [ + "8388608", + "0x04 0x00008000 EQUAL", + "", + "OK" + ], + [ + "2147483647", + "0x04 0xFFFFFF7F EQUAL", + "", + "OK" + ], + [ + "2147483648", + "0x05 0x0000008000 EQUAL", + "", + "OK" + ], + [ + "549755813887", + "0x05 0xFFFFFFFF7F EQUAL", + "", + "OK" + ], + [ + "549755813888", + "0x06 0xFFFFFFFF7F EQUALVERIFY 2DROP TRUE", + "", + "OK" + ], + [ + "9223372036854775807", + "0x08 0xFFFFFFFFFFFFFF7F EQUAL", + "", + "OK" + ], + [ + "-2", + "0x01 0x82 EQUAL", + "", + "OK", + "Numbers are little-endian with the MSB being a sign bit" + ], + [ + "-127", + "0x01 0xFF EQUAL", + "", + "OK" + ], + [ + "-128", + "0x02 0x8080 EQUAL", + "", + "OK" + ], + [ + "-32767", + "0x02 0xFFFF EQUAL", + "", + "OK" + ], + [ + "-32768", + "0x03 0x008080 EQUAL", + "", + "OK" + ], + [ + "-8388607", + "0x03 0xFFFFFF EQUAL", + "", + "OK" + ], + [ + "-8388608", + "0x04 0x00008080 EQUAL", + "", + "OK" + ], + [ + "-2147483647", + "0x04 0xFFFFFFFF EQUAL", + "", + "OK" + ], + [ + "-2147483648", + "0x05 0x0000008080 EQUAL", + "", + "OK" + ], + [ + "-4294967295", + "0x05 0xFFFFFFFF80 EQUAL", + "", + "OK" + ], + [ + "-549755813887", + "0x05 0xFFFFFFFFFF EQUAL", + "", + "OK" + ], + [ + "-549755813888", + "0x06 0x000000008080 EQUAL", + "", + "OK" + ], + [ + "-9223372036854775807", + "0x08 0xFFFFFFFFFFFFFFFF EQUAL", + "", + "OK" + ], + [ + "2147483647", + "1ADD 2147483648 EQUAL", + "", + "OK", + "We can do math on 4-byte integers, and compare 5-byte ones" + ], + [ + "2147483647", + "1ADD DROP 1", + "", + "OK" + ], + [ + "-2147483647", + "1ADD DROP 1", + "", + "OK" + ], + [ + "1", + "0x02 0x0100 EQUAL NOT", + "", + "OK", + "Not the same byte array..." + ], + [ + "0", + "0x01 0x80 EQUAL NOT", + "", + "OK" + ], + [ + "", + "NOP 1", + "", + "OK", + "The following tests check the if(stack.size() < N) tests in each opcode" + ], + [ + "1", + "IF 1 ENDIF", + "", + "OK", + "They are here to catch copy-and-paste errors" + ], + [ + "0", + "NOTIF 1 ENDIF", + "", + "OK", + "Most of them are duplicated elsewhere," + ], + [ + "1", + "VERIFY 1", + "", + "OK", + "but, hey, more is always better, right?" + ], + [ + "0", + "TOALTSTACK 1", + "", + "OK" + ], + [ + "1", + "TOALTSTACK FROMALTSTACK", + "", + "OK" + ], + [ + "0 0", + "2DROP 1", + "", + "OK" + ], + [ + "0 1", + "2DUP VERIFY DROP DROP DROP TRUE", + "", + "OK" + ], + [ + "0 0 1", + "3DUP VERIFY DROP DROP DROP DROP DROP TRUE", + "", + "OK" + ], + [ + "0 1 0 0", + "2OVER VERIFY DROP DROP DROP DROP DROP TRUE", + "", + "OK" + ], + [ + "0 1 0 0 0 0", + "2ROT VERIFY DROP DROP DROP DROP DROP TRUE", + "", + "OK" + ], + [ + "0 1 0 0", + "2SWAP VERIFY DROP DROP DROP TRUE", + "", + "OK" + ], + [ + "1", + "IFDUP VERIFY", + "", + "OK" + ], + [ + "", + "DEPTH 1 VERIFY DROP TRUE", + "", + "OK" + ], + [ + "0", + "DROP 1", + "", + "OK" + ], + [ + "1", + "DUP VERIFY", + "", + "OK" + ], + [ + "0 1", + "NIP", + "", + "OK" + ], + [ + "1 0", + "OVER VERIFY DROP DROP TRUE", + "", + "OK" + ], + [ + "1 0 0 0 3", + "PICK VERIFY DROP DROP DROP DROP TRUE", + "", + "OK" + ], + [ + "1 0", + "PICK VERIFY DROP TRUE", + "", + "OK" + ], + [ + "1 0 0 0 3", + "ROLL VERIFY DROP DROP DROP TRUE", + "", + "OK" + ], + [ + "1 0", + "ROLL", + "", + "OK" + ], + [ + "1 0 0", + "ROT VERIFY DROP DROP TRUE", + "", + "OK" + ], + [ + "1 0", + "SWAP VERIFY DROP TRUE", + "", + "OK" + ], + [ + "0 1", + "TUCK VERIFY DROP DROP TRUE", + "", + "OK" + ], + [ + "1", + "SIZE VERIFY DROP TRUE", + "", + "OK" + ], + [ + "0 0", + "EQUAL", + "", + "OK" + ], + [ + "0 0", + "EQUALVERIFY 1", + "", + "OK" + ], + [ + "0 0 1", + "EQUAL EQUAL", + "", + "OK", + "OP_0 and bools must have identical byte representations" + ], + [ + "0", + "1ADD", + "", + "OK" + ], + [ + "2", + "1SUB", + "", + "OK" + ], + [ + "-1", + "NEGATE", + "", + "OK" + ], + [ + "-1", + "ABS", + "", + "OK" + ], + [ + "0", + "NOT", + "", + "OK" + ], + [ + "-1", + "0NOTEQUAL", + "", + "OK" + ], + [ + "1 0", + "ADD", + "", + "OK" + ], + [ + "1 0", + "SUB", + "", + "OK" + ], + [ + "-1 -1", + "BOOLAND", + "", + "OK" + ], + [ + "-1 0", + "BOOLOR", + "", + "OK" + ], + [ + "0 0", + "NUMEQUAL", + "", + "OK" + ], + [ + "5 4", + "NUMEQUAL FALSE EQUAL", + "", + "OK" + ], + [ + "0 0", + "NUMEQUALVERIFY 1", + "", + "OK" + ], + [ + "-1 0", + "NUMNOTEQUAL", + "", + "OK" + ], + [ + "-1 0", + "LESSTHAN", + "", + "OK" + ], + [ + "1 0", + "GREATERTHAN", + "", + "OK" + ], + [ + "0 0", + "LESSTHANOREQUAL", + "", + "OK" + ], + [ + "0 0", + "GREATERTHANOREQUAL", + "", + "OK" + ], + [ + "-1 0", + "MIN", + "", + "OK" + ], + [ + "1 0", + "MAX", + "", + "OK" + ], + [ + "-1 -1 0", + "WITHIN", + "", + "OK" + ], + [ + "0", + "SHA256", + "", + "OK" + ], + [ + "0", + "BLAKE2B", + "", + "OK" + ], + [ + "", + "0 0 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", + "", + "OK", + "CHECKMULTISIG is allowed to have zero keys and/or sigs" + ], + [ + "", + "0 0 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 0 1 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", + "", + "OK", + "Zero sigs means no sigs are checked" + ], + [ + "", + "0 0 1 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 0 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", + "", + "OK", + "CHECKMULTISIG is allowed to have zero keys and/or sigs" + ], + [ + "", + "0 0 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 0 1 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", + "", + "OK", + "Zero sigs means no sigs are checked" + ], + [ + "", + "0 0 1 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 2 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", + "", + "OK", + "Test from up to 20 pubkeys, all not checked" + ], + [ + "", + "0 'a' 'b' 'c' 3 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 4 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 5 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 6 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 7 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 8 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 9 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 10 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 11 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 12 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 13 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 14 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 15 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 16 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 17 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 18 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 19 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 1 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 2 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 3 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 4 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 5 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 6 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 7 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 8 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 9 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 10 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 11 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 12 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 13 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 14 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 15 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 16 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 17 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 18 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 19 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "1", + "0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY", + "", + "OK", + "nOpCount is incremented by the number of keys evaluated in addition to the usual one op per op. In this case we have zero keys, so we can execute 201 CHECKMULTISIGS" + ], + [ + "", + "NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIG 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIG 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIG 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIG 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIG 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIG 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIG 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY DROP DROP DROP DROP DROP DROP DROP TRUE", + "", + "OK", + "Even though there are no signatures being checked nOpCount is incremented by the number of keys." + ], + [ + "1", + "NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY", + "", + "OK" + ], + [ + "0x01 1", + "BLAKE2B 0x20 0xce57216285125006ec18197bd8184221cefa559bb0798410d99a5bba5b07cd1d EQUAL", + "", + "OK", + "Very basic P2SH" + ], + [ + "0x00", + "SIZE 0 EQUALVERIFY DROP TRUE", + "", + "OK", + "Basic OP_0 execution" + ], + [ + "Numeric pushes" + ], + [ + "-1", + "0x4f EQUAL", + "", + "OK", + "OP1_NEGATE pushes 0x81" + ], + [ + "1", + "0x51 EQUAL", + "", + "OK", + "OP_1 pushes 0x01" + ], + [ + "2", + "0x52 EQUAL", + "", + "OK", + "OP_2 pushes 0x02" + ], + [ + "3", + "0x53 EQUAL", + "", + "OK", + "OP_3 pushes 0x03" + ], + [ + "4", + "0x54 EQUAL", + "", + "OK", + "OP_4 pushes 0x04" + ], + [ + "5", + "0x55 EQUAL", + "", + "OK", + "OP_5 pushes 0x05" + ], + [ + "6", + "0x56 EQUAL", + "", + "OK", + "OP_6 pushes 0x06" + ], + [ + "7", + "0x57 EQUAL", + "", + "OK", + "OP_7 pushes 0x07" + ], + [ + "8", + "0x58 EQUAL", + "", + "OK", + "OP_8 pushes 0x08" + ], + [ + "9", + "0x59 EQUAL", + "", + "OK", + "OP_9 pushes 0x09" + ], + [ + "10", + "0x5a EQUAL", + "", + "OK", + "OP_10 pushes 0x0a" + ], + [ + "11", + "0x5b EQUAL", + "", + "OK", + "OP_11 pushes 0x0b" + ], + [ + "12", + "0x5c EQUAL", + "", + "OK", + "OP_12 pushes 0x0c" + ], + [ + "13", + "0x5d EQUAL", + "", + "OK", + "OP_13 pushes 0x0d" + ], + [ + "14", + "0x5e EQUAL", + "", + "OK", + "OP_14 pushes 0x0e" + ], + [ + "15", + "0x5f EQUAL", + "", + "OK", + "OP_15 pushes 0x0f" + ], + [ + "16", + "0x60 EQUAL", + "", + "OK", + "OP_16 pushes 0x10" + ], + [ + "Unevaluated non-minimal pushes are ignored" + ], + [ + "0", + "IF 0x4c 0x00 ENDIF 1 ", + "", + "OK", + "non-minimal PUSHDATA1 ignored" + ], + [ + "0", + "IF 0x4d 0x0000 ENDIF 1 ", + "", + "OK", + "non-minimal PUSHDATA2 ignored" + ], + [ + "0", + "IF 0x4c 0x00000000 ENDIF 1 ", + "", + "OK", + "non-minimal PUSHDATA4 ignored" + ], + [ + "0", + "IF 0x01 0x81 ENDIF 1 ", + "", + "OK", + "1NEGATE equiv" + ], + [ + "0", + "IF 0x01 0x01 ENDIF 1 ", + "", + "OK", + "OP_1 equiv" + ], + [ + "0", + "IF 0x01 0x02 ENDIF 1 ", + "", + "OK", + "OP_2 equiv" + ], + [ + "0", + "IF 0x01 0x03 ENDIF 1 ", + "", + "OK", + "OP_3 equiv" + ], + [ + "0", + "IF 0x01 0x04 ENDIF 1 ", + "", + "OK", + "OP_4 equiv" + ], + [ + "0", + "IF 0x01 0x05 ENDIF 1 ", + "", + "OK", + "OP_5 equiv" + ], + [ + "0", + "IF 0x01 0x06 ENDIF 1 ", + "", + "OK", + "OP_6 equiv" + ], + [ + "0", + "IF 0x01 0x07 ENDIF 1 ", + "", + "OK", + "OP_7 equiv" + ], + [ + "0", + "IF 0x01 0x08 ENDIF 1 ", + "", + "OK", + "OP_8 equiv" + ], + [ + "0", + "IF 0x01 0x09 ENDIF 1 ", + "", + "OK", + "OP_9 equiv" + ], + [ + "0", + "IF 0x01 0x0a ENDIF 1 ", + "", + "OK", + "OP_10 equiv" + ], + [ + "0", + "IF 0x01 0x0b ENDIF 1 ", + "", + "OK", + "OP_11 equiv" + ], + [ + "0", + "IF 0x01 0x0c ENDIF 1 ", + "", + "OK", + "OP_12 equiv" + ], + [ + "0", + "IF 0x01 0x0d ENDIF 1 ", + "", + "OK", + "OP_13 equiv" + ], + [ + "0", + "IF 0x01 0x0e ENDIF 1 ", + "", + "OK", + "OP_14 equiv" + ], + [ + "0", + "IF 0x01 0x0f ENDIF 1 ", + "", + "OK", + "OP_15 equiv" + ], + [ + "0", + "IF 0x01 0x10 ENDIF 1 ", + "", + "OK", + "OP_16 equiv" + ], + [ + "Numeric minimaldata rules are only applied when a stack item is numerically evaluated; the push itself is allowed" + ], + [ + "0x01 0x00", + "1 VERIFY DROP TRUE", + "", + "OK" + ], + [ + "0x01 0x80", + "1 VERIFY DROP TRUE", + "", + "OK" + ], + [ + "0x02 0x0180", + "1 VERIFY DROP TRUE", + "", + "OK" + ], + [ + "0x02 0x0100", + "1 VERIFY DROP TRUE", + "", + "OK" + ], + [ + "0x02 0x0200", + "1 VERIFY DROP TRUE", + "", + "OK" + ], + [ + "0x02 0x0300", + "1 VERIFY DROP TRUE", + "", + "OK" + ], + [ + "0x02 0x0400", + "1 VERIFY DROP TRUE", + "", + "OK" + ], + [ + "0x02 0x0500", + "1 VERIFY DROP TRUE", + "", + "OK" + ], + [ + "0x02 0x0600", + "1 VERIFY DROP TRUE", + "", + "OK" + ], + [ + "0x02 0x0700", + "1 VERIFY DROP TRUE", + "", + "OK" + ], + [ + "0x02 0x0800", + "1 VERIFY DROP TRUE", + "", + "OK" + ], + [ + "0x02 0x0900", + "1 VERIFY DROP TRUE", + "", + "OK" + ], + [ + "0x02 0x0a00", + "1 VERIFY DROP TRUE", + "", + "OK" + ], + [ + "0x02 0x0b00", + "1 VERIFY DROP TRUE", + "", + "OK" + ], + [ + "0x02 0x0c00", + "1 VERIFY DROP TRUE", + "", + "OK" + ], + [ + "0x02 0x0d00", + "1 VERIFY DROP TRUE", + "", + "OK" + ], + [ + "0x02 0x0e00", + "1 VERIFY DROP TRUE", + "", + "OK" + ], + [ + "0x02 0x0f00", + "1 VERIFY DROP TRUE", + "", + "OK" + ], + [ + "0x02 0x1000", + "1 VERIFY DROP TRUE", + "", + "OK" + ], + [ + "While not really correctly DER encoded, the empty signature is allowed" + ], + [ + "to provide a compact way to provide a delibrately invalid signature." + ], + [ + "0", + "0x21 0x02865c40293a680cb9c020e7b1e106d8c1916d3cef99aa431a56d253e69256dac0 CHECKSIG NOT", + "", + "OK" + ], + [ + "0", + "1 0x21 0x02865c40293a680cb9c020e7b1e106d8c1916d3cef99aa431a56d253e69256dac0 1 CHECKMULTISIG NOT", + "", + "OK" + ], + [ + "TRUE DATA_8 0x0000000000000080", + "CHECKSEQUENCEVERIFY", + "", + "OK", + "CSV passes if stack top bit 1 << 63 is set" + ], + [ + "", + "DEPTH", + "", + "EVAL_FALSE", + "Test the test: we should have an empty stack after scriptSig evaluation" + ], + [ + " ", + "DEPTH", + "", + "EVAL_FALSE", + "and multiple spaces should not change that." + ], + [ + " ", + "DEPTH", + "", + "EVAL_FALSE" + ], + [ + " ", + "DEPTH", + "", + "EVAL_FALSE" + ], + [ + "", + "", + "", + "EVAL_FALSE" + ], + [ + "", + "NOP", + "", + "EVAL_FALSE" + ], + [ + "", + "NOP DEPTH", + "", + "EVAL_FALSE" + ], + [ + "", + "DEPTH", + "", + "EVAL_FALSE" + ], + [ + "", + "NOP", + "", + "EVAL_FALSE" + ], + [ + "", + "NOP DEPTH", + "", + "EVAL_FALSE" + ], + [ + "0x4c01", + "0x01 NOP", + "", + "BAD_OPCODE", + "PUSHDATA1 with not enough bytes" + ], + [ + "0x4d0200ff", + "0x01 NOP", + "", + "BAD_OPCODE", + "PUSHDATA2 with not enough bytes" + ], + [ + "0x4e03000000ffff", + "0x01 NOP", + "", + "BAD_OPCODE", + "PUSHDATA4 with not enough bytes" + ], + [ + "1", + "IF 0x50 ENDIF 1", + "", + "BAD_OPCODE", + "0x50 is reserved" + ], + [ + "0x52", + "0x5f ADD 0x60 EQUAL", + "", + "EVAL_FALSE", + "0x51 through 0x60 push 1 through 16 onto stack" + ], + [ + "0", + "NOP", + "", + "EVAL_FALSE", + "" + ], + [ + "1", + "IF VER ELSE 1 ENDIF", + "", + "BAD_OPCODE", + "VER non-functional" + ], + [ + "0", + "IF VERIF ELSE 1 ENDIF", + "", + "BAD_OPCODE", + "VERIF illegal everywhere" + ], + [ + "0", + "IF ELSE 1 ELSE VERIF ENDIF", + "", + "BAD_OPCODE", + "VERIF illegal everywhere" + ], + [ + "0", + "IF VERNOTIF ELSE 1 ENDIF", + "", + "BAD_OPCODE", + "VERNOTIF illegal everywhere" + ], + [ + "0", + "IF ELSE 1 ELSE VERNOTIF ENDIF", + "", + "BAD_OPCODE", + "VERNOTIF illegal everywhere" + ], + [ + "0", + "DUP IF ENDIF", + "", + "EVAL_FALSE" + ], + [ + "0", + "IF 1 ENDIF", + "", + "EVAL_FALSE" + ], + [ + "0", + "DUP IF ELSE ENDIF", + "", + "EVAL_FALSE" + ], + [ + "0", + "IF 1 ELSE ENDIF", + "", + "EVAL_FALSE" + ], + [ + "0", + "NOTIF ELSE 1 ENDIF", + "", + "EVAL_FALSE" + ], + [ + "0 1", + "IF IF 1 ELSE 0 ENDIF ENDIF", + "", + "EVAL_FALSE" + ], + [ + "0 0", + "IF IF 1 ELSE 0 ENDIF ENDIF", + "", + "EVAL_FALSE" + ], + [ + "1 0", + "IF IF 1 ELSE 0 ENDIF ELSE IF 0 ELSE 1 ENDIF ENDIF", + "", + "EVAL_FALSE" + ], + [ + "0 1", + "IF IF 1 ELSE 0 ENDIF ELSE IF 0 ELSE 1 ENDIF ENDIF", + "", + "EVAL_FALSE" + ], + [ + "0 0", + "NOTIF IF 1 ELSE 0 ENDIF ENDIF", + "", + "EVAL_FALSE" + ], + [ + "0 1", + "NOTIF IF 1 ELSE 0 ENDIF ENDIF", + "", + "EVAL_FALSE" + ], + [ + "1 1", + "NOTIF IF 1 ELSE 0 ENDIF ELSE IF 0 ELSE 1 ENDIF ENDIF", + "", + "EVAL_FALSE" + ], + [ + "0 0", + "NOTIF IF 1 ELSE 0 ENDIF ELSE IF 0 ELSE 1 ENDIF ENDIF", + "", + "EVAL_FALSE" + ], + [ + "1", + "IF RETURN ELSE ELSE 1 ENDIF", + "", + "OP_RETURN", + "Multiple ELSEs" + ], + [ + "1", + "IF 1 ELSE ELSE RETURN ENDIF", + "", + "OP_RETURN" + ], + [ + "1", + "ENDIF", + "", + "UNBALANCED_CONDITIONAL", + "Malformed IF/ELSE/ENDIF sequence" + ], + [ + "1", + "ELSE ENDIF", + "", + "UNBALANCED_CONDITIONAL" + ], + [ + "1", + "ENDIF ELSE", + "", + "UNBALANCED_CONDITIONAL" + ], + [ + "1", + "ENDIF ELSE IF", + "", + "UNBALANCED_CONDITIONAL" + ], + [ + "1", + "IF ELSE ENDIF ELSE", + "", + "UNBALANCED_CONDITIONAL" + ], + [ + "1", + "IF ELSE ENDIF ELSE ENDIF", + "", + "UNBALANCED_CONDITIONAL" + ], + [ + "1", + "IF ENDIF ENDIF", + "", + "UNBALANCED_CONDITIONAL" + ], + [ + "1", + "IF ELSE ELSE ENDIF ENDIF", + "", + "UNBALANCED_CONDITIONAL" + ], + [ + "1", + "RETURN", + "", + "OP_RETURN" + ], + [ + "1", + "DUP IF RETURN ENDIF", + "", + "OP_RETURN" + ], + [ + "1", + "RETURN 'data'", + "", + "OP_RETURN", + "canonical prunable txout format" + ], + [ + "0", + "VERIFY 1", + "", + "VERIFY" + ], + [ + "1", + "VERIFY", + "", + "EVAL_FALSE" + ], + [ + "1", + "VERIFY 0", + "", + "EVAL_FALSE" + ], + [ + "", + "IFDUP DEPTH 0 EQUAL", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "DROP DEPTH 0 EQUAL", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "DUP DEPTH 0 EQUAL", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "DUP 1 ADD 2 EQUALVERIFY 0 EQUAL", + "", + "EVAL_FALSE" + ], + [ + "", + "NIP", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "1 NIP", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "1 0 NIP", + "", + "EVAL_FALSE" + ], + [ + "", + "OVER 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "OVER", + "", + "INVALID_STACK_OPERATION" + ], + [ + "19 20 21", + "PICK 19 EQUALVERIFY DEPTH 2 EQUAL", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "0 PICK", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "-1 PICK", + "", + "INVALID_STACK_OPERATION" + ], + [ + "19 20 21", + "0 PICK 20 EQUALVERIFY DEPTH 3 EQUAL", + "", + "EQUALVERIFY" + ], + [ + "19 20 21", + "1 PICK 21 EQUALVERIFY DEPTH 3 EQUAL", + "", + "EQUALVERIFY" + ], + [ + "19 20 21", + "2 PICK 22 EQUALVERIFY DEPTH 3 EQUAL", + "", + "EQUALVERIFY" + ], + [ + "", + "0 ROLL", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "-1 ROLL", + "", + "INVALID_STACK_OPERATION" + ], + [ + "19 20 21", + "0 ROLL 20 EQUALVERIFY DEPTH 2 EQUAL", + "", + "EQUALVERIFY" + ], + [ + "19 20 21", + "1 ROLL 21 EQUALVERIFY DEPTH 2 EQUAL", + "", + "EQUALVERIFY" + ], + [ + "19 20 21", + "2 ROLL 22 EQUALVERIFY DEPTH 2 EQUAL", + "", + "EQUALVERIFY" + ], + [ + "", + "ROT 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "1 ROT 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "1 2 ROT 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "SWAP 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "SWAP 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "0 1", + "SWAP 1 EQUALVERIFY", + "", + "EQUALVERIFY" + ], + [ + "", + "TUCK 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "TUCK 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1 0", + "TUCK DEPTH 3 EQUALVERIFY SWAP 2DROP", + "", + "EVAL_FALSE" + ], + [ + "", + "2DUP 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "2DUP 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "3DUP 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "3DUP 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1 2", + "3DUP 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "2OVER 1 VERIFY DROP DROP DROP DROP TRUE", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "2 3 2OVER 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "2SWAP 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "2 3 2SWAP 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "'a' 'b'", + "CAT", + "", + "DISABLED_OPCODE", + "CAT disabled" + ], + [ + "'a' 'b' 0", + "IF CAT ELSE 1 ENDIF", + "", + "DISABLED_OPCODE", + "CAT disabled" + ], + [ + "'abc' 1 1", + "SUBSTR", + "", + "DISABLED_OPCODE", + "SUBSTR disabled" + ], + [ + "'abc' 1 1 0", + "IF SUBSTR ELSE 1 ENDIF", + "", + "DISABLED_OPCODE", + "SUBSTR disabled" + ], + [ + "'abc' 2 0", + "IF LEFT ELSE 1 ENDIF", + "", + "DISABLED_OPCODE", + "LEFT disabled" + ], + [ + "'abc' 2 0", + "IF RIGHT ELSE 1 ENDIF", + "", + "DISABLED_OPCODE", + "RIGHT disabled" + ], + [ + "", + "SIZE 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "NOP", + "", + "EMPTY_STACK", + "Checks EMPTY_STACK error" + ], + [ + "'abc'", + "INVERT VERIFY TRUE", + "", + "DISABLED_OPCODE", + "INVERT disabled" + ], + [ + "1 2 0", + "IF AND ELSE 1 ENDIF NOP", + "", + "DISABLED_OPCODE", + "AND disabled" + ], + [ + "1 2 0", + "IF OR ELSE 1 ENDIF NOP", + "", + "DISABLED_OPCODE", + "OR disabled" + ], + [ + "1 2 0", + "IF XOR ELSE 1 ENDIF NOP", + "", + "DISABLED_OPCODE", + "XOR disabled" + ], + [ + "2 0", + "IF 2MUL ELSE 1 ENDIF NOP", + "", + "DISABLED_OPCODE", + "2MUL disabled" + ], + [ + "2 0", + "IF 2DIV ELSE 1 ENDIF NOP", + "", + "DISABLED_OPCODE", + "2DIV disabled" + ], + [ + "2 2 0", + "IF MUL ELSE 1 ENDIF NOP", + "", + "DISABLED_OPCODE", + "MUL disabled" + ], + [ + "2 2 0", + "IF DIV ELSE 1 ENDIF NOP", + "", + "DISABLED_OPCODE", + "DIV disabled" + ], + [ + "2 2 0", + "IF MOD ELSE 1 ENDIF NOP", + "", + "DISABLED_OPCODE", + "MOD disabled" + ], + [ + "2 2 0", + "IF LSHIFT ELSE 1 ENDIF NOP", + "", + "DISABLED_OPCODE", + "LSHIFT disabled" + ], + [ + "2 2 0", + "IF RSHIFT ELSE 1 ENDIF NOP", + "", + "DISABLED_OPCODE", + "RSHIFT disabled" + ], + [ + "", + "EQUAL NOT", + "", + "INVALID_STACK_OPERATION", + "EQUAL must error when there are no stack items" + ], + [ + "0", + "EQUAL NOT", + "", + "INVALID_STACK_OPERATION", + "EQUAL must error when there are not 2 stack items" + ], + [ + "0 1", + "EQUAL", + "", + "EVAL_FALSE" + ], + [ + "1 1", + "ADD 0 EQUAL", + "", + "EVAL_FALSE" + ], + [ + "11 1", + "ADD 12 SUB 11 EQUAL", + "", + "EVAL_FALSE" + ], + [ + "2147483648 0", + "ADD NOP", + "", + "UNKNOWN_ERROR", + "arithmetic operands must be in range [-2^31...2^31] " + ], + [ + "-2147483648 0", + "ADD NOP", + "", + "UNKNOWN_ERROR", + "arithmetic operands must be in range [-2^31...2^31] " + ], + [ + "2147483647", + "DUP ADD 4294967294 NUMEQUAL", + "", + "UNKNOWN_ERROR", + "NUMEQUAL must be in numeric range" + ], + [ + "'abcdef'", + "NOT 0 EQUAL", + "", + "UNKNOWN_ERROR", + "NOT is an arithmetic operand" + ], + [ + "2", + "DUP MUL 4 EQUAL", + "", + "DISABLED_OPCODE", + "disabled" + ], + [ + "2", + "DUP DIV 1 EQUAL", + "", + "DISABLED_OPCODE", + "disabled" + ], + [ + "2", + "2MUL 4 EQUAL", + "", + "DISABLED_OPCODE", + "disabled" + ], + [ + "2", + "2DIV 1 EQUAL", + "", + "DISABLED_OPCODE", + "disabled" + ], + [ + "7 3", + "MOD 1 EQUAL", + "", + "DISABLED_OPCODE", + "disabled" + ], + [ + "2 2", + "LSHIFT 8 EQUAL", + "", + "DISABLED_OPCODE", + "disabled" + ], + [ + "2 1", + "RSHIFT 1 EQUAL", + "", + "DISABLED_OPCODE", + "disabled" + ], + [ + "0x50", + "1", + "", + "BAD_OPCODE", + "opcode 0x50 is reserved" + ], + [ + "1", + "IF 0xb6 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xb7 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xb8 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xb9 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xba ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xbb ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xbc ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xbd ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xbe ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xbf ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xc0 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xc1 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xc2 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xc3 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xc4 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xc5 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xc6 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xc7 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xc8 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xc9 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xca ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xcb ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xcc ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xcd ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xce ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xcf ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xd0 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xd1 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xd2 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xd3 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xd4 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xd5 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xd6 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xd7 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xd8 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xd9 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xda ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xdb ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xdc ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xdd ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xde ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xdf ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xe0 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xe1 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xe2 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xe3 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xe4 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xe5 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xe6 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xe7 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xe8 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xe9 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xea ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xeb ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xec ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xed ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xee ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xef ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xf0 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xf1 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xf2 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xf3 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xf4 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xf5 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xf6 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xf7 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xf8 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xf9 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xfa ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xfb ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xfc ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xfd ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xfe ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xff ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "", + "SHA256", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "SHA256", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "SHA256", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "BLAKE2B", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "BLAKE2B", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'", + "", + "PUSH_SIZE", + ">520 byte push" + ], + [ + "0", + "IF 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' ENDIF 1", + "", + "PUSH_SIZE", + ">520 byte push in non-executed IF branch" + ], + [ + "1", + "0x61616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161", + "", + "OP_COUNT", + ">201 opcodes executed. 0x61 is NOP" + ], + [ + "0", + "IF 0x6161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161 ENDIF 1", + "", + "OP_COUNT", + ">201 opcodes including non-executed IF branch. 0x61 is NOP" + ], + [ + "", + "1 2 3 4 5 6 0x6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f", + "", + "STACK_SIZE", + ">244 stack size (0x6f is 3DUP)" + ], + [ + "", + "1 TOALTSTACK 2 TOALTSTACK 3 4 5 6 7 8 0x6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f", + "", + "STACK_SIZE", + ">244 stack+altstack size" + ], + [ + "", + "0 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 0x6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f 2DUP 0x616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161", + "", + "SCRIPT_SIZE", + "10,001-byte scriptPubKey" + ], + [ + "1", + "VER", + "", + "BAD_OPCODE", + "OP_VER is reserved" + ], + [ + "1", + "VERIF", + "", + "BAD_OPCODE", + "OP_VERIF is reserved" + ], + [ + "1", + "VERNOTIF", + "", + "BAD_OPCODE", + "OP_VERNOTIF is reserved" + ], + [ + "1", + "RESERVED", + "", + "BAD_OPCODE", + "OP_RESERVED is reserved" + ], + [ + "1", + "RESERVED1", + "", + "BAD_OPCODE", + "OP_RESERVED1 is reserved" + ], + [ + "1", + "RESERVED2", + "", + "BAD_OPCODE", + "OP_RESERVED2 is reserved" + ], + [ + "2147483648", + "1ADD 1", + "", + "UNKNOWN_ERROR", + "We cannot do math on 5-byte integers" + ], + [ + "2147483648", + "NEGATE 1", + "", + "UNKNOWN_ERROR", + "We cannot do math on 5-byte integers" + ], + [ + "-2147483648", + "1ADD 1", + "", + "UNKNOWN_ERROR", + "Because we use a sign bit, -2147483648 is also 5 bytes" + ], + [ + "2147483647", + "1ADD 1SUB 1", + "", + "UNKNOWN_ERROR", + "We cannot do math on 5-byte integers, even if the result is 4-bytes" + ], + [ + "2147483648", + "1SUB 1", + "", + "UNKNOWN_ERROR", + "We cannot do math on 5-byte integers, even if the result is 4-bytes" + ], + [ + "2147483648 1", + "BOOLOR 1", + "", + "UNKNOWN_ERROR", + "We cannot do BOOLOR on 5-byte integers (but we can still do IF etc)" + ], + [ + "2147483648 1", + "BOOLAND 1", + "", + "UNKNOWN_ERROR", + "We cannot do BOOLAND on 5-byte integers" + ], + [ + "1", + "1 ENDIF", + "", + "UNBALANCED_CONDITIONAL", + "ENDIF without IF" + ], + [ + "1", + "IF 1", + "", + "UNBALANCED_CONDITIONAL", + "IF without ENDIF" + ], + [ + "", + "IF 1 ENDIF", + "", + "UNBALANCED_CONDITIONAL", + "The following tests check the if(stack.size() < N) tests in each opcode" + ], + [ + "", + "NOTIF 1 ENDIF", + "", + "UNBALANCED_CONDITIONAL", + "They are here to catch copy-and-paste errors" + ], + [ + "", + "VERIFY 1", + "", + "INVALID_STACK_OPERATION", + "Most of them are duplicated elsewhere," + ], + [ + "", + "TOALTSTACK 1", + "", + "INVALID_STACK_OPERATION", + "but, hey, more is always better, right?" + ], + [ + "1", + "FROMALTSTACK", + "", + "INVALID_ALTSTACK_OPERATION" + ], + [ + "1", + "2DROP 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "2DUP", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1 1", + "3DUP", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1 1 1", + "2OVER", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1 1 1 1 1", + "2ROT", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1 1 1", + "2SWAP", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "IFDUP 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "DROP 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "DUP 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "NIP", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "OVER", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1 1 1 3", + "PICK", + "", + "INVALID_STACK_OPERATION" + ], + [ + "0", + "PICK 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1 1 1 3", + "ROLL", + "", + "INVALID_STACK_OPERATION" + ], + [ + "0", + "ROLL 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1 1", + "ROT", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "SWAP", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "TUCK", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "SIZE 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "EQUAL 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "EQUALVERIFY 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "1ADD 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "1SUB 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "NEGATE 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "ABS 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "NOT 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "0NOTEQUAL 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "ADD", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "SUB", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "BOOLAND", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "BOOLOR", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "NUMEQUAL", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "NUMEQUALVERIFY 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "NUMNOTEQUAL", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "LESSTHAN", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "GREATERTHAN", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "LESSTHANOREQUAL", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "GREATERTHANOREQUAL", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "MIN", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "MAX", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1 1", + "WITHIN", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "SHA256 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "BLAKE2B 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "BLAKE2B 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "Increase CHECKSIG and CHECKMULTISIG negative test coverage" + ], + [ + "", + "CHECKSIG NOT", + "", + "INVALID_STACK_OPERATION", + "CHECKSIG must error when there are no stack items" + ], + [ + "0", + "CHECKSIG NOT", + "", + "INVALID_STACK_OPERATION", + "CHECKSIG must error when there are not 2 stack items" + ], + [ + "", + "CHECKMULTISIG NOT", + "", + "INVALID_STACK_OPERATION", + "CHECKMULTISIG must error when there are no stack items" + ], + [ + "", + "-1 CHECKMULTISIG NOT", + "", + "PUBKEY_COUNT", + "CHECKMULTISIG must error when the specified number of pubkeys is negative" + ], + [ + "", + "1 CHECKMULTISIG NOT", + "", + "INVALID_STACK_OPERATION", + "CHECKMULTISIG must error when there are not enough pubkeys on the stack" + ], + [ + "", + "-1 0 CHECKMULTISIG NOT", + "", + "SIG_COUNT", + "CHECKMULTISIG must error when the specified number of signatures is negative" + ], + [ + "", + "1 'pk1' 1 CHECKMULTISIG NOT", + "", + "INVALID_STACK_OPERATION", + "CHECKMULTISIG must error when there are not enough signatures on the stack" + ], + [ + "", + "0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG", + "", + "OP_COUNT", + "202 CHECKMULTISIGS, fails due to 201 op limit" + ], + [ + "", + "NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIG 0 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIG 0 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIG 0 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIG 0 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIG 0 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIG 0 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIG 0 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIG 0 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIG", + "", + "OP_COUNT", + "Fails due to 201 script operation limit" + ], + [ + "1", + "NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY 0 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY 0 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY 0 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY 0 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY 0 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY 0 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY 0 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY 0 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY", + "", + "OP_COUNT", + "" + ], + [ + "0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21", + "21 CHECKMULTISIG 1", + "", + "PUBKEY_COUNT", + "nPubKeys > 20" + ], + [ + "0 'sig' 1 0", + "CHECKMULTISIG 1", + "", + "SIG_COUNT", + "nSigs > nPubKeys" + ], + [ + "NOP 0x01 1", + "BLAKE2B 0x20 0xda1745e9b549bd0bfa1a569971c77eba30cd5a4b EQUAL", + "", + "SIG_PUSHONLY", + "Tests for Script.IsPushOnly()" + ], + [ + "0 0x01 0x50", + "BLAKE2B 0x20 0xece424a6bb6ddf4db592c0faed60685047a361b1 EQUAL", + "", + "BAD_OPCODE", + "OP_RESERVED in P2SH should fail" + ], + [ + "0 0x01", + "VER BLAKE2B 0x20 0x0f4d7845db968f2a81b530b6f3c1d6246d4c7e01 EQUAL", + "", + "BAD_OPCODE", + "OP_VER in P2SH should fail" + ], + [ + "0x00", + "'00' EQUAL", + "", + "EVAL_FALSE", + "Basic OP_0 execution" + ], + [ + "MINIMALDATA enforcement for PUSHDATAs" + ], + [ + "0x4c 0x00", + "DROP 1", + "", + "MINIMALDATA", + "Empty vector minimally represented by OP_0" + ], + [ + "0x01 0x81", + "DROP 1", + "", + "MINIMALDATA", + "-1 minimally represented by OP_1NEGATE" + ], + [ + "0x01 0x01", + "DROP 1", + "", + "MINIMALDATA", + "1 to 16 minimally represented by OP_1 to OP_16" + ], + [ + "0x01 0x02", + "DROP 1", + "", + "MINIMALDATA" + ], + [ + "0x01 0x03", + "DROP 1", + "", + "MINIMALDATA" + ], + [ + "0x01 0x04", + "DROP 1", + "", + "MINIMALDATA" + ], + [ + "0x01 0x05", + "DROP 1", + "", + "MINIMALDATA" + ], + [ + "0x01 0x06", + "DROP 1", + "", + "MINIMALDATA" + ], + [ + "0x01 0x07", + "DROP 1", + "", + "MINIMALDATA" + ], + [ + "0x01 0x08", + "DROP 1", + "", + "MINIMALDATA" + ], + [ + "0x01 0x09", + "DROP 1", + "", + "MINIMALDATA" + ], + [ + "0x01 0x0a", + "DROP 1", + "", + "MINIMALDATA" + ], + [ + "0x01 0x0b", + "DROP 1", + "", + "MINIMALDATA" + ], + [ + "0x01 0x0c", + "DROP 1", + "", + "MINIMALDATA" + ], + [ + "0x01 0x0d", + "DROP 1", + "", + "MINIMALDATA" + ], + [ + "0x01 0x0e", + "DROP 1", + "", + "MINIMALDATA" + ], + [ + "0x01 0x0f", + "DROP 1", + "", + "MINIMALDATA" + ], + [ + "0x01 0x10", + "DROP 1", + "", + "MINIMALDATA" + ], + [ + "0x4c 0x48 0x111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111", + "DROP 1", + "", + "MINIMALDATA", + "PUSHDATA1 of 72 bytes minimally represented by direct push" + ], + [ + "0x4d 0xFF00 0x111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111", + "DROP 1", + "", + "MINIMALDATA", + "PUSHDATA2 of 255 bytes minimally represented by PUSHDATA1" + ], + [ + "0x4e 0x00010000 0x11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111", + "DROP 1", + "", + "MINIMALDATA", + "PUSHDATA4 of 256 bytes minimally represented by PUSHDATA2" + ], + [ + "MINIMALDATA enforcement for numeric arguments" + ], + [ + "0x01 0x00", + "NOT DROP 1", + "", + "UNKNOWN_ERROR", + "numequals 0" + ], + [ + "0x02 0x0000", + "NOT DROP 1", + "", + "UNKNOWN_ERROR", + "numequals 0" + ], + [ + "0x01 0x80", + "NOT DROP 1", + "", + "UNKNOWN_ERROR", + "0x80 (negative zero) numequals 0" + ], + [ + "0x02 0x0080", + "NOT DROP 1", + "", + "UNKNOWN_ERROR", + "numequals 0" + ], + [ + "0x02 0x0500", + "NOT DROP 1", + "", + "UNKNOWN_ERROR", + "numequals 5" + ], + [ + "0x03 0x050000", + "NOT DROP 1", + "", + "UNKNOWN_ERROR", + "numequals 5" + ], + [ + "0x02 0x0580", + "NOT DROP 1", + "", + "UNKNOWN_ERROR", + "numequals -5" + ], + [ + "0x03 0x050080", + "NOT DROP 1", + "", + "UNKNOWN_ERROR", + "numequals -5" + ], + [ + "0x03 0xff7f80", + "NOT DROP 1", + "", + "UNKNOWN_ERROR", + "Minimal encoding is 0xffff" + ], + [ + "0x03 0xff7f00", + "NOT DROP 1", + "", + "UNKNOWN_ERROR", + "Minimal encoding is 0xff7f" + ], + [ + "0x04 0xffff7f80", + "NOT DROP 1", + "", + "UNKNOWN_ERROR", + "Minimal encoding is 0xffffff" + ], + [ + "0x04 0xffff7f00", + "NOT DROP 1", + "", + "UNKNOWN_ERROR", + "Minimal encoding is 0xffff7f" + ], + [ + "Test every numeric-accepting opcode for correct handling of the numeric minimal encoding rule" + ], + [ + "1 0x02 0x0000", + "PICK DROP", + "", + "UNKNOWN_ERROR" + ], + [ + "1 0x02 0x0000", + "ROLL DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0x02 0x0000", + "1ADD DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0x02 0x0000", + "1SUB DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0x02 0x0000", + "NEGATE DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0x02 0x0000", + "ABS DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0x02 0x0000", + "NOT DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0x02 0x0000", + "0NOTEQUAL DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0 0x02 0x0000", + "ADD DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0x02 0x0000 0", + "ADD DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0 0x02 0x0000", + "SUB DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0x02 0x0000 0", + "SUB DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0 0x02 0x0000", + "BOOLAND DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0x02 0x0000 0", + "BOOLAND DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0 0x02 0x0000", + "BOOLOR DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0x02 0x0000 0", + "BOOLOR DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0 0x02 0x0000", + "NUMEQUAL DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0x02 0x0000 1", + "NUMEQUAL DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0 0x02 0x0000", + "NUMEQUALVERIFY 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0x02 0x0000 0", + "NUMEQUALVERIFY 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0 0x02 0x0000", + "NUMNOTEQUAL DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0x02 0x0000 0", + "NUMNOTEQUAL DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0 0x02 0x0000", + "LESSTHAN DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0x02 0x0000 0", + "LESSTHAN DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0 0x02 0x0000", + "GREATERTHAN DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0x02 0x0000 0", + "GREATERTHAN DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0 0x02 0x0000", + "LESSTHANOREQUAL DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0x02 0x0000 0", + "LESSTHANOREQUAL DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0 0x02 0x0000", + "GREATERTHANOREQUAL DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0x02 0x0000 0", + "GREATERTHANOREQUAL DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0 0x02 0x0000", + "MIN DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0x02 0x0000 0", + "MIN DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0 0x02 0x0000", + "MAX DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0x02 0x0000 0", + "MAX DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0x02 0x0000 0 0", + "WITHIN DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0 0x02 0x0000 0", + "WITHIN DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0 0 0x02 0x0000", + "WITHIN DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0 0x02 0x0000", + "CHECKMULTISIG DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0x02 0x0000 0", + "CHECKMULTISIG DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0x02 0x0000 0 1", + "CHECKMULTISIG DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0 0x02 0x0000", + "CHECKMULTISIGVERIFY 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0x02 0x0000 0", + "CHECKMULTISIGVERIFY 1", + "", + "UNKNOWN_ERROR" + ], + [ + "Check MINIMALIF" + ], + [ + "2", + "IF TRUE ELSE FALSE", + "", + "MINIMALIF" + ], + [ + "2", + "NOTIF TRUE ELSE FALSE", + "", + "MINIMALIF" + ], + [ + "Order of CHECKMULTISIG evaluation tests, inverted by swapping the order of" + ], + [ + "pubkeys/signatures so they fail due to the STRICTENC rules on validly encoded" + ], + [ + "signatures and pubkeys." + ], + [ + "0x41 0x833682d4f60cc916a22a2c263e658fa662c49badb1e2a8c6208987bf99b1abd740498371480069e7a7a6e7471bf78c27bd9a1fd04fb212a92017346250ac187b01 0x41 0xea4a8d20562a950f4695dc24804565482e9fa111704886179d0c348f2b8a15fe691a305cd599c59c131677146661d5b98cb935330989a85f33afc70d0a21add101", + "2 0x21 0x02865c40293a680cb9c020e7b1e106d8c1916d3cef99aa431a56d253e69256dac0 0 2 CHECKMULTISIG NOT", + "", + "PUBKEYFORMAT", + "2-of-2 CHECKMULTISIG NOT with the first pubkey invalid, and both signatures validly encoded." + ], + [ + "CHECKSEQUENCEVERIFY tests" + ], + [ + "", + "CHECKSEQUENCEVERIFY", + "", + "INVALID_STACK_OPERATION", + "CSV automatically fails on a empty stack" + ], + [ + "0", + "CHECKSEQUENCEVERIFY", + "", + "UNSATISFIED_LOCKTIME", + "CSV fails if stack top bit 1 << 31 is set and the tx version < 2" + ], + [ + "4294967296", + "CHECKSEQUENCEVERIFY", + "", + "UNSATISFIED_LOCKTIME", + "CSV fails if stack top bit 1 << 31 is not set, and tx version < 2" + ], + [ + "NULLFAIL should cover all signatures and signatures only" + ], + [ + "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0", + "0x01 0x14 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0x01 0x14 CHECKMULTISIG NOT", + "", + "OK", + "BIP66 and NULLFAIL-compliant" + ], + [ + "0x09 0x300602010102010101 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0", + "0x01 0x14 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0x01 0x14 CHECKMULTISIG NOT", + "", + "NULLFAIL", + "BIP66-compliant but not NULLFAIL-compliant 4" + ], + [ + "The End" + ] +] From c882d32a498a4479046835d1ecf81ecc8ff4d60b Mon Sep 17 00:00:00 2001 From: max143672 Date: Sun, 16 Jun 2024 16:00:27 +0300 Subject: [PATCH 17/93] rename InputSPK -> InputSpk --- crypto/txscript/examples/kip-10.rs | 4 ++-- crypto/txscript/src/lib.rs | 4 ++-- crypto/txscript/src/opcodes/mod.rs | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/crypto/txscript/examples/kip-10.rs b/crypto/txscript/examples/kip-10.rs index 08233dafe..f200d32ca 100644 --- a/crypto/txscript/examples/kip-10.rs +++ b/crypto/txscript/examples/kip-10.rs @@ -17,7 +17,7 @@ use kaspa_consensus_core::{ use kaspa_txscript::{ caches::Cache, opcodes::codes::{ - OpCheckSig, OpDup, OpElse, OpEndIf, OpEqualVerify, OpFalse, OpGreaterThanOrEqual, OpIf, OpInputAmount, OpInputSPK, + OpCheckSig, OpDup, OpElse, OpEndIf, OpEqualVerify, OpFalse, OpGreaterThanOrEqual, OpIf, OpInputAmount, OpInputSpk, OpOutputAmount, OpOutputSpk, OpSub, OpTrue, }, pay_to_script_hash_script, @@ -57,7 +57,7 @@ fn main() -> ScriptBuilderResult<()> { .add_op(OpCheckSig)? // Borrower branch .add_op(OpElse)? - .add_ops(&[OpInputSPK, OpOutputSpk, OpEqualVerify, OpOutputAmount])? + .add_ops(&[OpInputSpk, OpOutputSpk, OpEqualVerify, OpOutputAmount])? .add_i64(threshold)? .add_ops(&[OpSub, OpInputAmount, OpGreaterThanOrEqual])? .add_op(OpEndIf)? diff --git a/crypto/txscript/src/lib.rs b/crypto/txscript/src/lib.rs index 6c2012e10..69fb2eeac 100644 --- a/crypto/txscript/src/lib.rs +++ b/crypto/txscript/src/lib.rs @@ -916,7 +916,7 @@ mod tests { #[cfg_attr(feature = "kip-10-mutual-tx", test)] fn output_gt_input_test() { use crate::opcodes::codes::{ - OpEqualVerify, OpGreaterThanOrEqual, OpInputAmount, OpInputSPK, OpOutputAmount, OpOutputSpk, OpSub, + OpEqualVerify, OpGreaterThanOrEqual, OpInputAmount, OpInputSpk, OpOutputAmount, OpOutputSpk, OpSub, }; use super::*; @@ -926,7 +926,7 @@ mod tests { let sig_cache = Cache::new(10_000); let mut reused_values = SigHashReusedValues::new(); let script = ScriptBuilder::new() - .add_ops(&[OpInputSPK, OpOutputSpk, OpEqualVerify, OpOutputAmount]) + .add_ops(&[OpInputSpk, OpOutputSpk, OpEqualVerify, OpOutputAmount]) .unwrap() .add_i64(threshold) .unwrap() diff --git a/crypto/txscript/src/opcodes/mod.rs b/crypto/txscript/src/opcodes/mod.rs index 5c498293e..8eb094d2f 100644 --- a/crypto/txscript/src/opcodes/mod.rs +++ b/crypto/txscript/src/opcodes/mod.rs @@ -875,7 +875,7 @@ opcode_list! { _ => Err(TxScriptError::InvalidSource("LockTimeVerify only applies to transaction inputs".to_string())) } } - opcode OpInputSPK<0xb2, 1>(self, vm) { + opcode OpInputSpk<0xb2, 1>(self, vm) { cfg_if::cfg_if! { if #[cfg(feature = "kip-10-mutual-tx")] { match vm.script_source { @@ -895,7 +895,7 @@ opcode_list! { vm.dstack.push(v); Ok(()) }, - _ => Err(TxScriptError::InvalidSource("OpInputSPK only applies to transaction inputs".to_string())) + _ => Err(TxScriptError::InvalidSource("OpInputSpk only applies to transaction inputs".to_string())) } } else { Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) @@ -1238,7 +1238,7 @@ mod test { #[cfg(not(feature = "kip-10-mutual-tx"))] tests.extend([ - opcodes::OpInputSPK::empty().expect("Should accept empty"), + opcodes::OpInputSpk::empty().expect("Should accept empty"), opcodes::OpInputAmount::empty().expect("Should accept empty"), opcodes::OpOutputSpk::empty().expect("Should accept empty"), opcodes::OpOutputAmount::empty().expect("Should accept empty"), From 1c19ae14827897d9292142823593eb19fe9ab9fe Mon Sep 17 00:00:00 2001 From: max143672 Date: Sun, 16 Jun 2024 16:55:29 +0300 Subject: [PATCH 18/93] enable kip10 opcodes based on daa_score in runtime --- .github/workflows/ci.yaml | 3 - Cargo.lock | 1 - consensus/Cargo.toml | 1 - consensus/client/Cargo.toml | 1 - .../processes/transaction_validator/mod.rs | 5 + .../transaction_validator_populated.rs | 32 +++-- consensus/wasm/Cargo.toml | 1 - crypto/txscript/Cargo.toml | 4 - crypto/txscript/examples/kip-10.rs | 15 +- crypto/txscript/src/data_stack.rs | 7 - crypto/txscript/src/lib.rs | 63 +++++---- crypto/txscript/src/opcodes/mod.rs | 133 ++++++++---------- crypto/txscript/src/standard/multisig.rs | 2 +- ..._tests-hf.json => script_tests-kip10.json} | 0 kaspad/Cargo.toml | 1 - mining/Cargo.toml | 3 - testing/integration/Cargo.toml | 1 - wallet/core/Cargo.toml | 1 - wallet/keys/Cargo.toml | 1 - 19 files changed, 124 insertions(+), 151 deletions(-) rename crypto/txscript/test-data/{script_tests-hf.json => script_tests-kip10.json} (100%) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 521c4841e..43e21e237 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -126,9 +126,6 @@ jobs: - name: Run cargo doc tests with features=no-asm on kaspa-hashes run: cargo test --doc --release -p kaspa-hashes --features=no-asm - - name: Run txscipt tests with features=hf on kaspa-txscipt - run: cargo nextest run --release -p kaspa-txscript --features=hf - # test-release: # name: Test Suite Release # runs-on: ${{ matrix.os }} diff --git a/Cargo.lock b/Cargo.lock index 3daf46f3b..22cd64f4f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3343,7 +3343,6 @@ version = "0.14.1" dependencies = [ "blake2b_simd", "borsh", - "cfg-if 1.0.0", "criterion", "hex", "indexmap 2.2.6", diff --git a/consensus/Cargo.toml b/consensus/Cargo.toml index 2a623edf2..f151e404b 100644 --- a/consensus/Cargo.toml +++ b/consensus/Cargo.toml @@ -61,4 +61,3 @@ harness = false [features] html_reports = [] devnet-prealloc = ["kaspa-consensus-core/devnet-prealloc"] -hf = ["kaspa-txscript/hf"] diff --git a/consensus/client/Cargo.toml b/consensus/client/Cargo.toml index 24d228f80..38cbed9a3 100644 --- a/consensus/client/Cargo.toml +++ b/consensus/client/Cargo.toml @@ -11,7 +11,6 @@ repository.workspace = true [features] wasm32-sdk = [] wasm32-types = [] -hf = ["kaspa-txscript/hf"] [dependencies] kaspa-addresses.workspace = true diff --git a/consensus/src/processes/transaction_validator/mod.rs b/consensus/src/processes/transaction_validator/mod.rs index 646a20355..401961d27 100644 --- a/consensus/src/processes/transaction_validator/mod.rs +++ b/consensus/src/processes/transaction_validator/mod.rs @@ -28,6 +28,8 @@ pub struct TransactionValidator { /// Storage mass hardfork DAA score storage_mass_activation_daa_score: u64, + /// KIP-10 hardfork DAA score + kip10_activation_daa_score: u64, } impl TransactionValidator { @@ -42,6 +44,7 @@ impl TransactionValidator { counters: Arc, mass_calculator: MassCalculator, storage_mass_activation_daa_score: u64, + kip10_activation_daa_score: u64, ) -> Self { Self { max_tx_inputs, @@ -54,6 +57,7 @@ impl TransactionValidator { sig_cache: Cache::with_counters(10_000, counters), mass_calculator, storage_mass_activation_daa_score, + kip10_activation_daa_score, } } @@ -78,6 +82,7 @@ impl TransactionValidator { sig_cache: Cache::with_counters(10_000, counters), mass_calculator: MassCalculator::new(0, 0, 0, 0), storage_mass_activation_daa_score: u64::MAX, + kip10_activation_daa_score: u64::MAX, } } } diff --git a/consensus/src/processes/transaction_validator/transaction_validator_populated.rs b/consensus/src/processes/transaction_validator/transaction_validator_populated.rs index 696b9a9d4..9e5adab56 100644 --- a/consensus/src/processes/transaction_validator/transaction_validator_populated.rs +++ b/consensus/src/processes/transaction_validator/transaction_validator_populated.rs @@ -43,7 +43,7 @@ impl TransactionValidator { match flags { TxValidationFlags::Full | TxValidationFlags::SkipMassCheck => { Self::check_sig_op_counts(tx)?; - self.check_scripts(tx)?; + self.check_scripts(tx, pov_daa_score)?; } TxValidationFlags::SkipScriptChecks => {} } @@ -144,11 +144,19 @@ impl TransactionValidator { Ok(()) } - pub fn check_scripts(&self, tx: &impl VerifiableTransaction) -> TxResult<()> { + pub fn check_scripts(&self, tx: &impl VerifiableTransaction, pov_daa_score: u64) -> TxResult<()> { let mut reused_values = SigHashReusedValues::new(); for (i, (input, entry)) in tx.populated_inputs().enumerate() { - let mut engine = TxScriptEngine::from_transaction_input(tx, input, i, entry, &mut reused_values, &self.sig_cache) - .map_err(TxRuleError::SignatureInvalid)?; + let mut engine = TxScriptEngine::from_transaction_input( + tx, + input, + i, + entry, + &mut reused_values, + &self.sig_cache, + pov_daa_score > self.kip10_activation_daa_score, + ) + .map_err(TxRuleError::SignatureInvalid)?; engine.execute().map_err(TxRuleError::SignatureInvalid)?; } @@ -230,7 +238,7 @@ mod tests { }], ); - tv.check_scripts(&populated_tx).expect("Signature check failed"); + tv.check_scripts(&populated_tx, u64::MAX).expect("Signature check failed"); } #[test] @@ -292,7 +300,7 @@ mod tests { }], ); - assert!(tv.check_scripts(&populated_tx).is_err(), "Failing Signature Test Failed"); + assert!(tv.check_scripts(&populated_tx, u64::MAX).is_err(), "Failing Signature Test Failed"); } #[test] @@ -354,7 +362,7 @@ mod tests { is_coinbase: false, }], ); - tv.check_scripts(&populated_tx).expect("Signature check failed"); + tv.check_scripts(&populated_tx, u64::MAX).expect("Signature check failed"); } #[test] @@ -417,7 +425,7 @@ mod tests { }], ); - assert!(tv.check_scripts(&populated_tx) == Err(TxRuleError::SignatureInvalid(TxScriptError::NullFail))); + assert!(tv.check_scripts(&populated_tx, u64::MAX) == Err(TxRuleError::SignatureInvalid(TxScriptError::NullFail))); } #[test] @@ -480,7 +488,7 @@ mod tests { }], ); - assert!(tv.check_scripts(&populated_tx) == Err(TxRuleError::SignatureInvalid(TxScriptError::NullFail))); + assert!(tv.check_scripts(&populated_tx, u64::MAX) == Err(TxRuleError::SignatureInvalid(TxScriptError::NullFail))); } #[test] @@ -543,7 +551,7 @@ mod tests { }], ); - let result = tv.check_scripts(&populated_tx); + let result = tv.check_scripts(&populated_tx, u64::MAX); assert!(result == Err(TxRuleError::SignatureInvalid(TxScriptError::EvalFalse))); } @@ -598,7 +606,7 @@ mod tests { }], ); - let result = tv.check_scripts(&populated_tx); + let result = tv.check_scripts(&populated_tx, u64::MAX); assert!(result == Err(TxRuleError::SignatureInvalid(TxScriptError::SignatureScriptNotPushOnly))); } @@ -678,7 +686,7 @@ mod tests { let schnorr_key = secp256k1::Keypair::from_seckey_slice(secp256k1::SECP256K1, &secret_key.secret_bytes()).unwrap(); let signed_tx = sign(MutableTransaction::with_entries(unsigned_tx, entries), schnorr_key); let populated_tx = signed_tx.as_verifiable(); - assert_eq!(tv.check_scripts(&populated_tx), Ok(())); + assert_eq!(tv.check_scripts(&populated_tx, u64::MAX), Ok(())); assert_eq!(TransactionValidator::check_sig_op_counts(&populated_tx), Ok(())); } } diff --git a/consensus/wasm/Cargo.toml b/consensus/wasm/Cargo.toml index f3d92dd13..ea211f3f9 100644 --- a/consensus/wasm/Cargo.toml +++ b/consensus/wasm/Cargo.toml @@ -12,7 +12,6 @@ repository.workspace = true [features] wasm32-sdk = [] wasm32-types = [] -hf = ["kaspa-txscript/hf"] [dependencies] kaspa-consensus-core.workspace = true diff --git a/crypto/txscript/Cargo.toml b/crypto/txscript/Cargo.toml index 7b536d749..787b9a612 100644 --- a/crypto/txscript/Cargo.toml +++ b/crypto/txscript/Cargo.toml @@ -11,16 +11,12 @@ repository.workspace = true [[example]] name = "kip-10" -required-features = ["kip-10-mutual-tx"] [features] -hf = ["kip-10-mutual-tx"] -kip-10-mutual-tx = [] [dependencies] blake2b_simd.workspace = true borsh.workspace = true -cfg-if.workspace = true indexmap.workspace = true itertools.workspace = true kaspa-addresses.workspace = true diff --git a/crypto/txscript/examples/kip-10.rs b/crypto/txscript/examples/kip-10.rs index f200d32ca..d279d9036 100644 --- a/crypto/txscript/examples/kip-10.rs +++ b/crypto/txscript/examples/kip-10.rs @@ -114,8 +114,9 @@ fn main() -> ScriptBuilderResult<()> { } let tx = tx.as_verifiable(); - let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &mut reused_values, &sig_cache) - .expect("Script creation failed"); + let mut vm = + TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true) + .expect("Script creation failed"); assert_eq!(vm.execute(), Ok(())); println!("owner scenario successes"); } @@ -125,8 +126,9 @@ fn main() -> ScriptBuilderResult<()> { println!("check borrower scenario"); tx.inputs[0].signature_script = ScriptBuilder::new().add_op(OpFalse)?.add_data(&script)?.drain(); let tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]); - let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &mut reused_values, &sig_cache) - .expect("Script creation failed"); + let mut vm = + TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true) + .expect("Script creation failed"); assert_eq!(vm.execute(), Ok(())); println!("borrower scenario successes"); } @@ -137,8 +139,9 @@ fn main() -> ScriptBuilderResult<()> { // Less than threshold tx.outputs[0].value -= 1; let tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]); - let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &mut reused_values, &sig_cache) - .expect("Script creation failed"); + let mut vm = + TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true) + .expect("Script creation failed"); assert_eq!(vm.execute(), Err(EvalFalse)); println!("borrower scenario with underflow failed! all good"); } diff --git a/crypto/txscript/src/data_stack.rs b/crypto/txscript/src/data_stack.rs index 3e236e472..74988042a 100644 --- a/crypto/txscript/src/data_stack.rs +++ b/crypto/txscript/src/data_stack.rs @@ -343,13 +343,6 @@ mod tests { for test in tests { let serialized: Vec = OpcodeData::::serialize(&test.num); assert_eq!(serialized, test.serialized); - println!( - "number: {}; serialized: {}; be: {}, le: {}", - test.num, - hex::encode(serialized), - hex::encode(test.num.to_be_bytes()), - hex::encode(test.num.to_le_bytes()), - ) } } diff --git a/crypto/txscript/src/lib.rs b/crypto/txscript/src/lib.rs index 69fb2eeac..c346b4fbc 100644 --- a/crypto/txscript/src/lib.rs +++ b/crypto/txscript/src/lib.rs @@ -80,6 +80,7 @@ pub struct TxScriptEngine<'a, T: VerifiableTransaction> { cond_stack: Vec, // Following if stacks, and whether it is running num_ops: i32, + kip10_enabled: bool, } fn parse_script( @@ -152,6 +153,7 @@ impl<'a, T: VerifiableTransaction> TxScriptEngine<'a, T> { sig_cache, cond_stack: vec![], num_ops: 0, + kip10_enabled: false, } } @@ -162,6 +164,7 @@ impl<'a, T: VerifiableTransaction> TxScriptEngine<'a, T> { utxo_entry: &'a UtxoEntry, reused_values: &'a mut SigHashReusedValues, sig_cache: &'a Cache, + kip10_enabled: bool, ) -> Result { let script_public_key = utxo_entry.script_public_key.script(); // The script_public_key in P2SH is just validating the hash on the OpMultiSig script @@ -176,6 +179,7 @@ impl<'a, T: VerifiableTransaction> TxScriptEngine<'a, T> { sig_cache, cond_stack: Default::default(), num_ops: 0, + kip10_enabled, }), false => Err(TxScriptError::InvalidIndex(input_idx, tx.tx().inputs.len())), } @@ -190,6 +194,7 @@ impl<'a, T: VerifiableTransaction> TxScriptEngine<'a, T> { sig_cache, cond_stack: Default::default(), num_ops: 0, + kip10_enabled: false, } } @@ -561,8 +566,9 @@ mod tests { let populated_tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]); - let mut vm = TxScriptEngine::from_transaction_input(&populated_tx, &input, 0, &utxo_entry, &mut reused_values, &sig_cache) - .expect("Script creation failed"); + let mut vm = + TxScriptEngine::from_transaction_input(&populated_tx, &input, 0, &utxo_entry, &mut reused_values, &sig_cache, false) + .expect("Script creation failed"); assert_eq!(vm.execute(), test.expected_result); } } @@ -912,8 +918,7 @@ mod tests { ); } } - #[cfg(feature = "kip-10-mutual-tx")] - #[cfg_attr(feature = "kip-10-mutual-tx", test)] + #[test] fn output_gt_input_test() { use crate::opcodes::codes::{ OpEqualVerify, OpGreaterThanOrEqual, OpInputAmount, OpInputSpk, OpOutputAmount, OpOutputSpk, OpSub, @@ -954,8 +959,9 @@ mod tests { let populated_tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]); - let mut vm = TxScriptEngine::from_transaction_input(&populated_tx, &input, 0, &utxo_entry, &mut reused_values, &sig_cache) - .expect("Script creation failed"); + let mut vm = + TxScriptEngine::from_transaction_input(&populated_tx, &input, 0, &utxo_entry, &mut reused_values, &sig_cache, true) + .expect("Script creation failed"); assert_eq!(vm.execute(), Ok(())); } } @@ -963,7 +969,6 @@ mod tests { #[cfg(test)] mod bitcoind_tests { // Bitcoind tests - use cfg_if::cfg_if; use serde::Deserialize; use std::fs::File; use std::io::BufReader; @@ -1030,7 +1035,7 @@ mod bitcoind_tests { } impl JsonTestRow { - fn test_row(&self) -> Result<(), TestError> { + fn test_row(&self, kip10_enabled: bool) -> Result<(), TestError> { // Parse test to objects let (sig_script, script_pub_key, expected_result) = match self.clone() { JsonTestRow::Test(sig_script, sig_pub_key, _, expected_result) => (sig_script, sig_pub_key, expected_result), @@ -1042,7 +1047,7 @@ mod bitcoind_tests { } }; - let result = Self::run_test(sig_script, script_pub_key); + let result = Self::run_test(sig_script, script_pub_key, kip10_enabled); match Self::result_name(result.clone()).contains(&expected_result.as_str()) { true => Ok(()), @@ -1050,7 +1055,7 @@ mod bitcoind_tests { } } - fn run_test(sig_script: String, script_pub_key: String) -> Result<(), UnifiedError> { + fn run_test(sig_script: String, script_pub_key: String, kip10_enabled: bool) -> Result<(), UnifiedError> { let script_sig = opcodes::parse_short_form(sig_script).map_err(UnifiedError::ScriptBuilderError)?; let script_pub_key = ScriptPublicKey::from_vec(0, opcodes::parse_short_form(script_pub_key).map_err(UnifiedError::ScriptBuilderError)?); @@ -1070,6 +1075,7 @@ mod bitcoind_tests { &populated_tx.entries[0], &mut reused_values, &sig_cache, + kip10_enabled, ) .map_err(UnifiedError::TxScriptError)?; vm.execute().map_err(UnifiedError::TxScriptError) @@ -1150,29 +1156,24 @@ mod bitcoind_tests { #[test] fn test_bitcoind_tests() { - cfg_if!( - if #[cfg(feature = "hf")] { - let file_name = "script_tests-hf.json"; - } else { - let file_name = "script_tests.json"; + for (file_name, kip10_enabled) in [("script_tests.json", false), ("script_tests-kip10.json", true)] { + let file = + File::open(Path::new(env!("CARGO_MANIFEST_DIR")).join("test-data").join(file_name)).expect("Could not find test file"); + let reader = BufReader::new(file); + + // Read the JSON contents of the file as an instance of `User`. + let tests: Vec = serde_json::from_reader(reader).expect("Failed Parsing {:?}"); + let mut had_errors = 0; + let total_tests = tests.len(); + for row in tests { + if let Err(error) = row.test_row(kip10_enabled) { + println!("Test: {:?} failed: {:?}", row.clone(), error); + had_errors += 1; + } } - ); - let file = - File::open(Path::new(env!("CARGO_MANIFEST_DIR")).join("test-data").join(file_name)).expect("Could not find test file"); - let reader = BufReader::new(file); - - // Read the JSON contents of the file as an instance of `User`. - let tests: Vec = serde_json::from_reader(reader).expect("Failed Parsing {:?}"); - let mut had_errors = 0; - let total_tests = tests.len(); - for row in tests { - if let Err(error) = row.test_row() { - println!("Test: {:?} failed: {:?}", row.clone(), error); - had_errors += 1; + if had_errors > 0 { + panic!("{}/{} json tests failed", had_errors, total_tests) } } - if had_errors > 0 { - panic!("{}/{} json tests failed", had_errors, total_tests) - } } } diff --git a/crypto/txscript/src/opcodes/mod.rs b/crypto/txscript/src/opcodes/mod.rs index 8eb094d2f..6ca5a22cd 100644 --- a/crypto/txscript/src/opcodes/mod.rs +++ b/crypto/txscript/src/opcodes/mod.rs @@ -876,87 +876,77 @@ opcode_list! { } } opcode OpInputSpk<0xb2, 1>(self, vm) { - cfg_if::cfg_if! { - if #[cfg(feature = "kip-10-mutual-tx")] { - match vm.script_source { - ScriptSource::TxInput{ - utxo_entry: kaspa_consensus_core::tx::UtxoEntry{ - script_public_key: spk @ kaspa_consensus_core::tx::ScriptPublicKey{ - version, ..}, - .. - }, + if vm.kip10_enabled { + match vm.script_source { + ScriptSource::TxInput{ + utxo_entry: kaspa_consensus_core::tx::UtxoEntry{ + script_public_key: spk @ kaspa_consensus_core::tx::ScriptPublicKey{ + version, ..}, .. - } => { - let version = version.to_be_bytes(); - let script = spk.script(); - let mut v = Vec::with_capacity(version.len() + script.len()); - v.extend_from_slice(&version); - v.extend_from_slice(script); - vm.dstack.push(v); - Ok(()) }, - _ => Err(TxScriptError::InvalidSource("OpInputSpk only applies to transaction inputs".to_string())) - } - } else { - Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) + .. + } => { + let version = version.to_be_bytes(); + let script = spk.script(); + let mut v = Vec::with_capacity(version.len() + script.len()); + v.extend_from_slice(&version); + v.extend_from_slice(script); + vm.dstack.push(v); + Ok(()) + }, + _ => Err(TxScriptError::InvalidSource("OpInputSpk only applies to transaction inputs".to_string())) } + } else { + Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) } } opcode OpInputAmount<0xb3, 1>(self, vm) { - cfg_if::cfg_if! { - if #[cfg(feature = "kip-10-mutual-tx")] { - match vm.script_source { - ScriptSource::TxInput{ - utxo_entry: kaspa_consensus_core::tx::UtxoEntry{ - amount, - .. - }, + if vm.kip10_enabled { + match vm.script_source { + ScriptSource::TxInput{ + utxo_entry: kaspa_consensus_core::tx::UtxoEntry{ + amount, .. - } => { - push_number(*amount as i64, vm) }, - _ => Err(TxScriptError::InvalidSource("OpInputAmount only applies to transaction inputs".to_string())) - } - } else { - Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) + .. + } => push_number(*amount as i64, vm), + _ => Err(TxScriptError::InvalidSource("OpInputAmount only applies to transaction inputs".to_string())) } + } else { + Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) } } opcode OpOutputSpk<0xb4, 1>(self, vm) { - cfg_if::cfg_if! { - if #[cfg(feature = "kip-10-mutual-tx")] { - match vm.script_source { - ScriptSource::TxInput{tx, id , ..} => { - let v = tx.outputs().get(id).map(|output| { - let version = output.script_public_key.version.to_be_bytes(); - let script = output.script_public_key.script(); - let mut v = Vec::with_capacity(version.len() + script.len()); - v.extend_from_slice(&version); - v.extend_from_slice(script); - v - }); - vm.dstack.push(v.unwrap_or_default()); - Ok(()) - }, - _ => Err(TxScriptError::InvalidSource("OpInputAmount only applies to transaction inputs".to_string())) - } - } else { - Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) + if vm.kip10_enabled { + match vm.script_source { + ScriptSource::TxInput{tx, id , ..} => { + let v = tx.outputs().get(id).map(|output| { + let version = output.script_public_key.version.to_be_bytes(); + let script = output.script_public_key.script(); + let mut v = Vec::with_capacity(version.len() + script.len()); + v.extend_from_slice(&version); + v.extend_from_slice(script); + v + }); + vm.dstack.push(v.unwrap_or_default()); + Ok(()) + }, + _ => Err(TxScriptError::InvalidSource("OpInputAmount only applies to transaction inputs".to_string())) } + } else { + Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) } } opcode OpOutputAmount<0xb5, 1>(self, vm) { - cfg_if::cfg_if! { - if #[cfg(feature = "kip-10-mutual-tx")] { - match vm.script_source { - ScriptSource::TxInput{tx, id , ..} => { - push_number(tx.outputs().get(id).map(|output| output.value).unwrap_or_default() as i64, vm) - }, - _ => Err(TxScriptError::InvalidSource("OpInputAmount only applies to transaction inputs".to_string())) - } - } else { - Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) + if vm.kip10_enabled { + match vm.script_source { + ScriptSource::TxInput{tx, id , ..} => { + push_number(tx.outputs().get(id).map(|output| output.value).unwrap_or_default() as i64, vm) + }, + _ => Err(TxScriptError::InvalidSource("OpInputAmount only applies to transaction inputs".to_string())) } + } else { + Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) } } @@ -1162,8 +1152,7 @@ mod test { #[test] fn test_opcode_invalid() { - #[allow(unused_mut)] - let mut tests: Vec>> = vec![ + let tests: Vec>> = vec![ opcodes::OpUnknown166::empty().expect("Should accept empty"), opcodes::OpUnknown167::empty().expect("Should accept empty"), opcodes::OpUnknown182::empty().expect("Should accept empty"), @@ -1236,14 +1225,6 @@ mod test { opcodes::OpUnknown249::empty().expect("Should accept empty"), ]; - #[cfg(not(feature = "kip-10-mutual-tx"))] - tests.extend([ - opcodes::OpInputSpk::empty().expect("Should accept empty"), - opcodes::OpInputAmount::empty().expect("Should accept empty"), - opcodes::OpOutputSpk::empty().expect("Should accept empty"), - opcodes::OpOutputAmount::empty().expect("Should accept empty"), - ]); - let cache = Cache::new(10_000); let mut reused_values = SigHashReusedValues::new(); let mut vm = TxScriptEngine::new(&mut reused_values, &cache); @@ -2838,7 +2819,7 @@ mod test { ] { let mut tx = base_tx.clone(); tx.0.lock_time = tx_lock_time; - let mut vm = TxScriptEngine::from_transaction_input(&tx, &input, 0, &utxo_entry, &mut reused_values, &sig_cache) + let mut vm = TxScriptEngine::from_transaction_input(&tx, &input, 0, &utxo_entry, &mut reused_values, &sig_cache, false) .expect("Shouldn't fail"); vm.dstack = vec![lock_time.clone()]; match code.execute(&mut vm) { @@ -2881,7 +2862,7 @@ mod test { ] { let mut input = base_input.clone(); input.sequence = tx_sequence; - let mut vm = TxScriptEngine::from_transaction_input(&tx, &input, 0, &utxo_entry, &mut reused_values, &sig_cache) + let mut vm = TxScriptEngine::from_transaction_input(&tx, &input, 0, &utxo_entry, &mut reused_values, &sig_cache, false) .expect("Shouldn't fail"); vm.dstack = vec![sequence.clone()]; match code.execute(&mut vm) { diff --git a/crypto/txscript/src/standard/multisig.rs b/crypto/txscript/src/standard/multisig.rs index 79c74c7b3..ac62ad4d6 100644 --- a/crypto/txscript/src/standard/multisig.rs +++ b/crypto/txscript/src/standard/multisig.rs @@ -184,7 +184,7 @@ mod tests { let (input, entry) = tx.populated_inputs().next().unwrap(); let cache = Cache::new(10_000); - let mut engine = TxScriptEngine::from_transaction_input(&tx, input, 0, entry, &mut reused_values, &cache).unwrap(); + let mut engine = TxScriptEngine::from_transaction_input(&tx, input, 0, entry, &mut reused_values, &cache, false).unwrap(); assert_eq!(engine.execute().is_ok(), is_ok); } #[test] diff --git a/crypto/txscript/test-data/script_tests-hf.json b/crypto/txscript/test-data/script_tests-kip10.json similarity index 100% rename from crypto/txscript/test-data/script_tests-hf.json rename to crypto/txscript/test-data/script_tests-kip10.json diff --git a/kaspad/Cargo.toml b/kaspad/Cargo.toml index 8699ebb33..9f3290a51 100644 --- a/kaspad/Cargo.toml +++ b/kaspad/Cargo.toml @@ -60,4 +60,3 @@ serde_with = "3.7.0" [features] heap = ["dhat", "kaspa-alloc/heap"] devnet-prealloc = ["kaspa-consensus/devnet-prealloc"] -hf = ["kaspa-txscript/hf"] diff --git a/mining/Cargo.toml b/mining/Cargo.toml index a0db5619b..facd45d6a 100644 --- a/mining/Cargo.toml +++ b/mining/Cargo.toml @@ -38,6 +38,3 @@ secp256k1.workspace = true [[bench]] name = "bench" harness = false - -[features] -hf = ["kaspa-txscript/hf"] diff --git a/testing/integration/Cargo.toml b/testing/integration/Cargo.toml index 284a38e49..7d9dd99af 100644 --- a/testing/integration/Cargo.toml +++ b/testing/integration/Cargo.toml @@ -75,4 +75,3 @@ kaspa-txscript-errors.workspace = true heap = ["dhat"] html_reports = [] devnet-prealloc = ["kaspad/devnet-prealloc"] -hf = ["kaspa-txscript/hf"] diff --git a/wallet/core/Cargo.toml b/wallet/core/Cargo.toml index f46ed3a2a..fb31afb31 100644 --- a/wallet/core/Cargo.toml +++ b/wallet/core/Cargo.toml @@ -27,7 +27,6 @@ wasm32-sdk = [ ] default = ["wasm32-sdk"] # default = [] -hf = ["kaspa-txscript/hf"] [lib] crate-type = ["cdylib", "lib"] diff --git a/wallet/keys/Cargo.toml b/wallet/keys/Cargo.toml index 062b1a575..7300c1de5 100644 --- a/wallet/keys/Cargo.toml +++ b/wallet/keys/Cargo.toml @@ -11,7 +11,6 @@ repository.workspace = true [features] default = [] -hf = ["kaspa-txscript/hf"] [lib] crate-type = ["cdylib", "lib"] From 414747366eb6f5054e5c64b5eabd8468cb5cd319 Mon Sep 17 00:00:00 2001 From: max143672 Date: Sun, 16 Jun 2024 17:06:10 +0300 Subject: [PATCH 19/93] use dummy kip10 activation daa score in params --- consensus/core/src/config/params.rs | 9 ++++++++- consensus/src/consensus/services.rs | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/consensus/core/src/config/params.rs b/consensus/core/src/config/params.rs index e2f2639a1..1bae0f8dc 100644 --- a/consensus/core/src/config/params.rs +++ b/consensus/core/src/config/params.rs @@ -83,6 +83,9 @@ pub struct Params { /// DAA score from which storage mass calculation and transaction mass field are activated as a consensus rule pub storage_mass_activation_daa_score: u64, + /// DAA score from which tx engine supports kip10 opcodes: OpInputAmount, OpInputSpk, OpOutputAmount, OpOutputSpk + pub kip10_activation_daa_score: u64, + /// DAA score after which the pre-deflationary period switches to the deflationary period pub deflationary_phase_daa_score: u64, @@ -349,6 +352,7 @@ pub const MAINNET_PARAMS: Params = Params { storage_mass_parameter: STORAGE_MASS_PARAMETER, storage_mass_activation_daa_score: u64::MAX, + kip10_activation_daa_score: u64::MAX, // deflationary_phase_daa_score is the DAA score after which the pre-deflationary period // switches to the deflationary period. This number is calculated as follows: @@ -408,7 +412,7 @@ pub const TESTNET_PARAMS: Params = Params { storage_mass_parameter: STORAGE_MASS_PARAMETER, storage_mass_activation_daa_score: u64::MAX, - + kip10_activation_daa_score: u64::MAX, // deflationary_phase_daa_score is the DAA score after which the pre-deflationary period // switches to the deflationary period. This number is calculated as follows: // We define a year as 365.25 days @@ -474,6 +478,7 @@ pub const TESTNET11_PARAMS: Params = Params { storage_mass_parameter: STORAGE_MASS_PARAMETER, storage_mass_activation_daa_score: 0, + kip10_activation_daa_score: u64::MAX, skip_proof_of_work: false, max_block_level: 250, @@ -526,6 +531,7 @@ pub const SIMNET_PARAMS: Params = Params { storage_mass_parameter: STORAGE_MASS_PARAMETER, storage_mass_activation_daa_score: 0, + kip10_activation_daa_score: u64::MAX, skip_proof_of_work: true, // For simnet only, PoW can be simulated by default max_block_level: 250, @@ -572,6 +578,7 @@ pub const DEVNET_PARAMS: Params = Params { storage_mass_parameter: STORAGE_MASS_PARAMETER, storage_mass_activation_daa_score: u64::MAX, + kip10_activation_daa_score: u64::MAX, // deflationary_phase_daa_score is the DAA score after which the pre-deflationary period // switches to the deflationary period. This number is calculated as follows: diff --git a/consensus/src/consensus/services.rs b/consensus/src/consensus/services.rs index 4afb5938a..6c49a8e2d 100644 --- a/consensus/src/consensus/services.rs +++ b/consensus/src/consensus/services.rs @@ -158,6 +158,7 @@ impl ConsensusServices { tx_script_cache_counters, mass_calculator.clone(), params.storage_mass_activation_daa_score, + params.kip10_activation_daa_score, ); let pruning_point_manager = PruningPointManager::new( From c32c34bdebb21e07dc197e9afa931bbded1064e8 Mon Sep 17 00:00:00 2001 From: max143672 Date: Sun, 16 Jun 2024 17:16:34 +0300 Subject: [PATCH 20/93] use dummy kip10 activation daa score in params --- testing/integration/src/consensus_integration_tests.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/testing/integration/src/consensus_integration_tests.rs b/testing/integration/src/consensus_integration_tests.rs index e66baaf69..a8d2fb3ea 100644 --- a/testing/integration/src/consensus_integration_tests.rs +++ b/testing/integration/src/consensus_integration_tests.rs @@ -831,6 +831,7 @@ impl KaspadGoParams { max_block_mass: self.MaxBlockMass, storage_mass_parameter: STORAGE_MASS_PARAMETER, storage_mass_activation_daa_score: u64::MAX, + kip10_activation_daa_score: u64::MAX, deflationary_phase_daa_score: self.DeflationaryPhaseDaaScore, pre_deflationary_phase_base_subsidy: self.PreDeflationaryPhaseBaseSubsidy, coinbase_maturity: MAINNET_PARAMS.coinbase_maturity, From 915b5d3d5df06d00d2b80dbb2af69d3c091f9ae1 Mon Sep 17 00:00:00 2001 From: max143672 Date: Sun, 16 Jun 2024 17:27:56 +0300 Subject: [PATCH 21/93] suppress clippy lint --- consensus/src/processes/transaction_validator/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/consensus/src/processes/transaction_validator/mod.rs b/consensus/src/processes/transaction_validator/mod.rs index 401961d27..63f2473ca 100644 --- a/consensus/src/processes/transaction_validator/mod.rs +++ b/consensus/src/processes/transaction_validator/mod.rs @@ -33,6 +33,7 @@ pub struct TransactionValidator { } impl TransactionValidator { + #[allow(clippy::too_many_arguments)] pub fn new( max_tx_inputs: usize, max_tx_outputs: usize, From 72940207077f861f6c04b39d5bdc2a15bed6f893 Mon Sep 17 00:00:00 2001 From: max143672 Date: Mon, 17 Jun 2024 19:22:11 +0300 Subject: [PATCH 22/93] add example with shared key --- crypto/txscript/examples/kip-10.rs | 191 ++++++++++++++++++++++++++--- 1 file changed, 172 insertions(+), 19 deletions(-) diff --git a/crypto/txscript/examples/kip-10.rs b/crypto/txscript/examples/kip-10.rs index d279d9036..45702bc3c 100644 --- a/crypto/txscript/examples/kip-10.rs +++ b/crypto/txscript/examples/kip-10.rs @@ -1,14 +1,8 @@ -//! # Kaspa Transaction Script Example -//! -//! This example demonstrates the use of custom opcodes and script execution within the Kaspa blockchain ecosystem. -//! There are two main scenarios: -//! -//! 1. **Owner scenario:** The script checks if the input is used by the owner and verifies the owner's signature. -//! 2. **Borrower scenario:** The script allows the input to be consumed if the output with the same index has a value of input + threshold and goes to the P2SH of the script itself. This scenario also includes a check where the threshold is not reached. - use kaspa_consensus_core::{ - hashing::sighash::{calc_schnorr_signature_hash, SigHashReusedValues}, - hashing::sighash_type::SIG_HASH_ALL, + hashing::{ + sighash::{calc_schnorr_signature_hash, SigHashReusedValues}, + sighash_type::SIG_HASH_ALL, + }, tx::{ MutableTransaction, PopulatedTransaction, Transaction, TransactionId, TransactionInput, TransactionOutpoint, TransactionOutput, UtxoEntry, VerifiableTransaction, @@ -17,14 +11,14 @@ use kaspa_consensus_core::{ use kaspa_txscript::{ caches::Cache, opcodes::codes::{ - OpCheckSig, OpDup, OpElse, OpEndIf, OpEqualVerify, OpFalse, OpGreaterThanOrEqual, OpIf, OpInputAmount, OpInputSpk, - OpOutputAmount, OpOutputSpk, OpSub, OpTrue, + OpCheckSig, OpCheckSigVerify, OpDup, OpElse, OpEndIf, OpEqualVerify, OpFalse, OpGreaterThanOrEqual, OpIf, OpInputAmount, + OpInputSpk, OpOutputAmount, OpOutputSpk, OpSub, OpTrue, }, pay_to_script_hash_script, script_builder::{ScriptBuilder, ScriptBuilderResult}, TxScriptEngine, }; -use kaspa_txscript_errors::TxScriptError::EvalFalse; +use kaspa_txscript_errors::TxScriptError::{EvalFalse, VerifyError}; use rand::thread_rng; use secp256k1::Keypair; @@ -34,6 +28,21 @@ use secp256k1::Keypair; /// /// * `ScriptBuilderResult<()>` - Result of script builder operations. fn main() -> ScriptBuilderResult<()> { + threshold_scenario()?; + shared_secret_scenario()?; + Ok(()) +} + +/// # Kaspa Transaction Script Example +/// +/// This example demonstrates the use of custom opcodes and script execution within the Kaspa blockchain ecosystem. +/// There are two main scenarios: +/// +/// 1. **Owner scenario:** The script checks if the input is used by the owner and verifies the owner's signature. +/// 2. **Borrower scenario:** The script allows the input to be consumed if the output with the same index has a value of input + threshold and goes to the P2SH of the script itself. This scenario also includes a check where the threshold is not reached. + +fn threshold_scenario() -> ScriptBuilderResult<()> { + println!("\nrun threshold scenario"); // Create a new key pair for the owner let owner = Keypair::new(secp256k1::SECP256K1, &mut thread_rng()); @@ -94,7 +103,7 @@ fn main() -> ScriptBuilderResult<()> { // Check owner branch { - println!("check owner scenario"); + println!("check owner branch in threshold scenario"); let mut tx = MutableTransaction::with_entries(tx.clone(), vec![utxo_entry.clone()]); let sig_hash = calc_schnorr_signature_hash(&tx.as_verifiable(), 0, SIG_HASH_ALL, &mut reused_values); let msg = secp256k1::Message::from_digest_slice(sig_hash.as_bytes().as_slice()).unwrap(); @@ -118,24 +127,24 @@ fn main() -> ScriptBuilderResult<()> { TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true) .expect("Script creation failed"); assert_eq!(vm.execute(), Ok(())); - println!("owner scenario successes"); + println!("owner branch in threshold scenario successes"); } // Check borrower branch { - println!("check borrower scenario"); + println!("check borrower branch in threshold scenario"); tx.inputs[0].signature_script = ScriptBuilder::new().add_op(OpFalse)?.add_data(&script)?.drain(); let tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]); let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true) .expect("Script creation failed"); assert_eq!(vm.execute(), Ok(())); - println!("borrower scenario successes"); + println!("borrower branch in threshold scenario successes"); } // Check borrower branch with threshold not reached { - println!("check borrower scenario with underflow"); + println!("check borrower branch in threshold scenario with underflow"); // Less than threshold tx.outputs[0].value -= 1; let tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]); @@ -143,7 +152,151 @@ fn main() -> ScriptBuilderResult<()> { TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true) .expect("Script creation failed"); assert_eq!(vm.execute(), Err(EvalFalse)); - println!("borrower scenario with underflow failed! all good"); + println!("borrower branch in threshold scenario with underflow failed! all good"); + } + + Ok(()) +} + +/// # Shared Secret Scenario +/// +/// This scenario demonstrates the use of a shared secret within the Kaspa blockchain ecosystem. +/// Instead of using a threshold value, it checks the shared secret and the signature associated with it. +/// There are three main sub-scenarios: +/// +/// 1. **Owner scenario:** The script checks if the input is used by the owner and verifies the owner's signature. +/// 2. **Borrower scenario with shared secret:** The script allows the input to be consumed if the shared secret is verified. +/// 3. **Borrower scenario with incorrect secret:** The script fails if the borrower uses an incorrect secret. +fn shared_secret_scenario() -> ScriptBuilderResult<()> { + println!("\nrun shared secret scenario"); + + // Create a new key pair for the owner + let owner = Keypair::new(secp256k1::SECP256K1, &mut thread_rng()); + let shared_secret_kp = Keypair::new(secp256k1::SECP256K1, &mut thread_rng()); + let borrower_kp = Keypair::new(secp256k1::SECP256K1, &mut thread_rng()); + + // Initialize a cache for signature verification + let sig_cache = Cache::new(10_000); + + // Create the script builder + let mut builder = ScriptBuilder::new(); + let script = builder + // Owner branch + .add_op(OpIf)? + .add_op(OpDup)? + .add_data(owner.x_only_public_key().0.serialize().as_slice())? + .add_op(OpEqualVerify)? + .add_op(OpCheckSig)? + // Borrower branch + .add_op(OpElse)? + .add_op(OpDup)? + .add_data(shared_secret_kp.x_only_public_key().0.serialize().as_slice())? + .add_op(OpEqualVerify)? + .add_op(OpCheckSigVerify)? + .add_ops(&[OpInputSpk, OpOutputSpk, OpEqualVerify, OpOutputAmount, OpInputAmount, OpGreaterThanOrEqual])? + .add_op(OpEndIf)? + .drain(); + + // Generate the script public key + let spk = pay_to_script_hash_script(&script); + + // Define the input value + let input_value = 1000000000; + + // Create a transaction output + let output = TransactionOutput { value: input_value, script_public_key: spk.clone() }; + + // Create a UTXO entry for the input + let utxo_entry = UtxoEntry::new(input_value, spk, 0, false); + + // Create a transaction input + let input = TransactionInput { + previous_outpoint: TransactionOutpoint { + transaction_id: TransactionId::from_bytes([ + 0xc9, 0x97, 0xa5, 0xe5, 0x6e, 0x10, 0x42, 0x02, 0xfa, 0x20, 0x9c, 0x6a, 0x85, 0x2d, 0xd9, 0x06, 0x60, 0xa2, 0x0b, + 0x2d, 0x9c, 0x35, 0x24, 0x23, 0xed, 0xce, 0x25, 0x85, 0x7f, 0xcd, 0x37, 0x04, + ]), + index: 0, + }, + signature_script: ScriptBuilder::new().add_data(&script)?.drain(), + sequence: 4294967295, + sig_op_count: 0, + }; + + // Create a transaction with the input and output + let tx = Transaction::new(1, vec![input.clone()], vec![output.clone()], 0, Default::default(), 0, vec![]); + let sign = |pk: Keypair| { + // Prepare to reuse values for signature hashing + let mut reused_values = SigHashReusedValues::new(); + + let tx = MutableTransaction::with_entries(tx.clone(), vec![utxo_entry.clone()]); + let sig_hash = calc_schnorr_signature_hash(&tx.as_verifiable(), 0, SIG_HASH_ALL, &mut reused_values); + let msg = secp256k1::Message::from_digest_slice(sig_hash.as_bytes().as_slice()).unwrap(); + + let sig = pk.sign_schnorr(msg); + let mut signature = Vec::new(); + signature.extend_from_slice(sig.as_ref().as_slice()); + signature.push(SIG_HASH_ALL.to_u8()); + (tx, signature, reused_values) + }; + // Check owner branch + { + println!("check owner branch in shared_secret_scenario"); + let (mut tx, signature, mut reused_values) = sign(owner); + let mut builder = ScriptBuilder::new(); + builder.add_data(&signature)?; + builder.add_data(owner.x_only_public_key().0.serialize().as_slice())?; + builder.add_op(OpTrue)?; + builder.add_data(&script)?; + { + tx.tx.inputs[0].signature_script = builder.drain(); + } + + let tx = tx.as_verifiable(); + let mut vm = + TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true) + .expect("Script creation failed"); + assert_eq!(vm.execute(), Ok(())); + println!("owner scenario in shared_secret_scenario successes"); + } + + // Check borrower branch + { + println!("check borrower branch in shared_secret_scenario"); + let (mut tx, signature, mut reused_values) = sign(shared_secret_kp); + builder.add_data(&signature)?; + builder.add_data(shared_secret_kp.x_only_public_key().0.serialize().as_slice())?; + builder.add_op(OpFalse)?; + builder.add_data(&script)?; + { + tx.tx.inputs[0].signature_script = builder.drain(); + } + + let tx = tx.as_verifiable(); + let mut vm = + TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true) + .expect("Script creation failed"); + assert_eq!(vm.execute(), Ok(())); + println!("borrower scenario successes in shared_secret_scenario"); + } + + // Check borrower branch with borrower signature + { + let (mut tx, signature, mut reused_values) = sign(borrower_kp); + builder.add_data(&signature)?; + builder.add_data(borrower_kp.x_only_public_key().0.serialize().as_slice())?; + builder.add_op(OpFalse)?; + builder.add_data(&script)?; + { + tx.tx.inputs[0].signature_script = builder.drain(); + } + + let tx = tx.as_verifiable(); + let mut vm = + TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true) + .expect("Script creation failed"); + assert_eq!(vm.execute(), Err(VerifyError)); + println!("borrower scenario in shared_secret_scenario with wrong secret signature failed! all good"); } Ok(()) From 8b4d0be233045836c919b0d5dba7d15b517382c8 Mon Sep 17 00:00:00 2001 From: max143672 Date: Sat, 28 Sep 2024 17:23:26 +0300 Subject: [PATCH 23/93] fix clippy --- wallet/pskt/src/pskt.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wallet/pskt/src/pskt.rs b/wallet/pskt/src/pskt.rs index 73f87a628..65e3945d5 100644 --- a/wallet/pskt/src/pskt.rs +++ b/wallet/pskt/src/pskt.rs @@ -435,7 +435,7 @@ impl PSKT { let mut reused_values = SigHashReusedValues::new(); tx.populated_inputs().enumerate().try_for_each(|(idx, (input, entry))| { - TxScriptEngine::from_transaction_input(&tx, input, idx, entry, &mut reused_values, &cache)?.execute()?; + TxScriptEngine::from_transaction_input(&tx, input, idx, entry, &mut reused_values, &cache, false)?.execute()?; >::Ok(()) })?; } From 2892374a2b0d0fc5e6ab218ad72583fb39820c22 Mon Sep 17 00:00:00 2001 From: max143672 Date: Fri, 11 Oct 2024 22:09:02 +0400 Subject: [PATCH 24/93] remove useless check from example --- crypto/txscript/examples/kip-10.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/crypto/txscript/examples/kip-10.rs b/crypto/txscript/examples/kip-10.rs index 45702bc3c..0af35f7c0 100644 --- a/crypto/txscript/examples/kip-10.rs +++ b/crypto/txscript/examples/kip-10.rs @@ -60,9 +60,7 @@ fn threshold_scenario() -> ScriptBuilderResult<()> { let script = builder // Owner branch .add_op(OpIf)? - .add_op(OpDup)? .add_data(owner.x_only_public_key().0.serialize().as_slice())? - .add_op(OpEqualVerify)? .add_op(OpCheckSig)? // Borrower branch .add_op(OpElse)? @@ -115,7 +113,6 @@ fn threshold_scenario() -> ScriptBuilderResult<()> { let mut builder = ScriptBuilder::new(); builder.add_data(&signature)?; - builder.add_data(owner.x_only_public_key().0.serialize().as_slice())?; builder.add_op(OpTrue)?; builder.add_data(&script)?; { @@ -183,9 +180,7 @@ fn shared_secret_scenario() -> ScriptBuilderResult<()> { let script = builder // Owner branch .add_op(OpIf)? - .add_op(OpDup)? .add_data(owner.x_only_public_key().0.serialize().as_slice())? - .add_op(OpEqualVerify)? .add_op(OpCheckSig)? // Borrower branch .add_op(OpElse)? @@ -245,7 +240,6 @@ fn shared_secret_scenario() -> ScriptBuilderResult<()> { let (mut tx, signature, mut reused_values) = sign(owner); let mut builder = ScriptBuilder::new(); builder.add_data(&signature)?; - builder.add_data(owner.x_only_public_key().0.serialize().as_slice())?; builder.add_op(OpTrue)?; builder.add_data(&script)?; { From c86881f7f92c591fbefcfe016e2a9c6fbbca32c7 Mon Sep 17 00:00:00 2001 From: max143672 Date: Tue, 15 Oct 2024 14:21:28 +0400 Subject: [PATCH 25/93] add one-time borrowing example --- crypto/txscript/examples/kip-10.rs | 267 +++++++++++++++++++++++++---- 1 file changed, 238 insertions(+), 29 deletions(-) diff --git a/crypto/txscript/examples/kip-10.rs b/crypto/txscript/examples/kip-10.rs index 0af35f7c0..29dad0a32 100644 --- a/crypto/txscript/examples/kip-10.rs +++ b/crypto/txscript/examples/kip-10.rs @@ -1,3 +1,4 @@ +use kaspa_addresses::{Address, Prefix, Version}; use kaspa_consensus_core::{ hashing::{ sighash::{calc_schnorr_signature_hash, SigHashReusedValues}, @@ -14,7 +15,7 @@ use kaspa_txscript::{ OpCheckSig, OpCheckSigVerify, OpDup, OpElse, OpEndIf, OpEqualVerify, OpFalse, OpGreaterThanOrEqual, OpIf, OpInputAmount, OpInputSpk, OpOutputAmount, OpOutputSpk, OpSub, OpTrue, }, - pay_to_script_hash_script, + pay_to_address_script, pay_to_script_hash_script, script_builder::{ScriptBuilder, ScriptBuilderResult}, TxScriptEngine, }; @@ -22,27 +23,31 @@ use kaspa_txscript_errors::TxScriptError::{EvalFalse, VerifyError}; use rand::thread_rng; use secp256k1::Keypair; -/// Main function to execute the Kaspa transaction script example. +/// Main function to execute all Kaspa transaction script scenarios. /// /// # Returns /// -/// * `ScriptBuilderResult<()>` - Result of script builder operations. +/// * `ScriptBuilderResult<()>` - Result of script builder operations for all scenarios. fn main() -> ScriptBuilderResult<()> { threshold_scenario()?; + threshold_scenario_limited_one_time()?; shared_secret_scenario()?; Ok(()) } -/// # Kaspa Transaction Script Example +/// # Standard Threshold Scenario /// -/// This example demonstrates the use of custom opcodes and script execution within the Kaspa blockchain ecosystem. -/// There are two main scenarios: +/// This scenario demonstrates the use of custom opcodes and script execution within the Kaspa blockchain ecosystem. +/// There are two main cases: /// -/// 1. **Owner scenario:** The script checks if the input is used by the owner and verifies the owner's signature. -/// 2. **Borrower scenario:** The script allows the input to be consumed if the output with the same index has a value of input + threshold and goes to the P2SH of the script itself. This scenario also includes a check where the threshold is not reached. - +/// 1. **Owner case:** The script checks if the input is used by the owner and verifies the owner's signature. +/// 2. **Borrower case:** The script allows the input to be consumed if the output with the same index has a value of input + threshold and goes to the P2SH of the script itself. +/// +/// # Returns +/// +/// * `ScriptBuilderResult<()>` - Result of script builder operations for this scenario. fn threshold_scenario() -> ScriptBuilderResult<()> { - println!("\nrun threshold scenario"); + println!("\n[STANDARD] Running standard threshold scenario"); // Create a new key pair for the owner let owner = Keypair::new(secp256k1::SECP256K1, &mut thread_rng()); @@ -101,7 +106,7 @@ fn threshold_scenario() -> ScriptBuilderResult<()> { // Check owner branch { - println!("check owner branch in threshold scenario"); + println!("[STANDARD] Checking owner branch"); let mut tx = MutableTransaction::with_entries(tx.clone(), vec![utxo_entry.clone()]); let sig_hash = calc_schnorr_signature_hash(&tx.as_verifiable(), 0, SIG_HASH_ALL, &mut reused_values); let msg = secp256k1::Message::from_digest_slice(sig_hash.as_bytes().as_slice()).unwrap(); @@ -124,24 +129,24 @@ fn threshold_scenario() -> ScriptBuilderResult<()> { TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true) .expect("Script creation failed"); assert_eq!(vm.execute(), Ok(())); - println!("owner branch in threshold scenario successes"); + println!("[STANDARD] Owner branch execution successful"); } // Check borrower branch { - println!("check borrower branch in threshold scenario"); + println!("[STANDARD] Checking borrower branch"); tx.inputs[0].signature_script = ScriptBuilder::new().add_op(OpFalse)?.add_data(&script)?.drain(); let tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]); let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true) .expect("Script creation failed"); assert_eq!(vm.execute(), Ok(())); - println!("borrower branch in threshold scenario successes"); + println!("[STANDARD] Borrower branch execution successful"); } // Check borrower branch with threshold not reached { - println!("check borrower branch in threshold scenario with underflow"); + println!("[STANDARD] Checking borrower branch with threshold not reached"); // Less than threshold tx.outputs[0].value -= 1; let tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]); @@ -149,9 +154,201 @@ fn threshold_scenario() -> ScriptBuilderResult<()> { TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true) .expect("Script creation failed"); assert_eq!(vm.execute(), Err(EvalFalse)); - println!("borrower branch in threshold scenario with underflow failed! all good"); + println!("[STANDARD] Borrower branch with threshold not reached failed as expected"); } + println!("[STANDARD] Standard threshold scenario completed successfully"); + Ok(()) +} + +/// Generate a script for the one-time borrowing scenario +/// +/// This function creates a script that allows for one-time borrowing with a threshold, +/// or spending by the owner at any time. +/// +/// # Arguments +/// +/// * `owner` - The public key of the owner +/// * `threshold` - The threshold amount that must be met for borrowing +/// +/// # Returns +/// +/// * The generated script as a vector of bytes +fn generate_one_time_script(owner: &Keypair, threshold: i64) -> ScriptBuilderResult> { + let p2pk = + pay_to_address_script(&Address::new(Prefix::Mainnet, Version::PubKey, owner.x_only_public_key().0.serialize().as_slice())); + let p2pk_as_vec = { + let version = p2pk.version.to_be_bytes(); + let script = p2pk.script(); + let mut v = Vec::with_capacity(version.len() + script.len()); + v.extend_from_slice(&version); + v.extend_from_slice(script); + v + }; + + let mut builder = ScriptBuilder::new(); + let script = builder + // Owner branch + .add_op(OpIf)? + .add_data(owner.x_only_public_key().0.serialize().as_slice())? + .add_op(OpCheckSig)? + // Borrower branch + .add_op(OpElse)? + .add_data(&p2pk_as_vec)? + .add_ops(&[OpOutputSpk, OpEqualVerify, OpOutputAmount])? + .add_i64(threshold)? + .add_ops(&[OpSub, OpInputAmount, OpGreaterThanOrEqual])? + .add_op(OpEndIf)? + .drain(); + + Ok(script) +} + +/// # Threshold Scenario with Limited One-Time Borrowing +/// +/// This function demonstrates a modified version of the threshold scenario where borrowing +/// is limited to a single occurrence. The key difference from the standard threshold scenario +/// is that the output goes to a Pay-to-Public-Key (P2PK) address instead of a Pay-to-Script-Hash (P2SH) +/// address of the script itself. +/// +/// ## Key Features: +/// 1. **One-Time Borrowing:** The borrower can only use this mechanism once, as the funds are +/// sent to a regular P2PK address instead of back to the script. +/// 2. **Owner Access:** The owner retains the ability to spend the funds at any time using their private key. +/// 3. **Threshold Mechanism:** The borrower must still meet the threshold requirement to spend the funds. +/// 4. **Output Validation:** Ensures the output goes to the correct address. +/// +/// ## Scenarios Tested: +/// 1. **Owner Spending:** Verifies that the owner can spend the funds using their signature. +/// 2. **Borrower Spending:** Checks if the borrower can spend when meeting the threshold and +/// sending to the correct P2PK address. +/// 3. **Invalid Borrower Attempt (Threshold):** Ensures the script fails if the borrower doesn't meet the threshold. +/// 4. **Invalid Borrower Attempt (Wrong Output):** Ensures the script fails if the output goes to an incorrect address. +/// +/// # Returns +/// +/// * `ScriptBuilderResult<()>` - Result of script builder operations for this scenario. +fn threshold_scenario_limited_one_time() -> ScriptBuilderResult<()> { + println!("\n[ONE-TIME] Running threshold one-time scenario"); + // Create a new key pair for the owner + let owner = Keypair::new(secp256k1::SECP256K1, &mut thread_rng()); + + // Set a threshold value for comparison + let threshold: i64 = 100; + + // Generate the one-time script + let script = generate_one_time_script(&owner, threshold)?; + let p2pk = pay_to_address_script(&Address::new(Prefix::Mainnet, Version::PubKey, owner.x_only_public_key().0.serialize().as_slice())); + + // Initialize a cache for signature verification + let sig_cache = Cache::new(10_000); + + // Prepare to reuse values for signature hashing + let mut reused_values = SigHashReusedValues::new(); + + // Generate the script public key + let spk = pay_to_script_hash_script(&script); + + // Define the input value + let input_value = 1000000000; + + // Create a transaction output + let output = TransactionOutput { value: 1000000000 + threshold as u64, script_public_key: p2pk.clone() }; + + // Create a UTXO entry for the input + let utxo_entry = UtxoEntry::new(input_value, spk, 0, false); + + // Create a transaction input + let input = TransactionInput { + previous_outpoint: TransactionOutpoint { + transaction_id: TransactionId::from_bytes([ + 0xc9, 0x97, 0xa5, 0xe5, 0x6e, 0x10, 0x42, 0x02, 0xfa, 0x20, 0x9c, 0x6a, 0x85, 0x2d, 0xd9, 0x06, 0x60, 0xa2, 0x0b, + 0x2d, 0x9c, 0x35, 0x24, 0x23, 0xed, 0xce, 0x25, 0x85, 0x7f, 0xcd, 0x37, 0x04, + ]), + index: 0, + }, + signature_script: ScriptBuilder::new().add_data(&script)?.drain(), + sequence: 4294967295, + sig_op_count: 0, + }; + + // Create a transaction with the input and output + let mut tx = Transaction::new(1, vec![input.clone()], vec![output.clone()], 0, Default::default(), 0, vec![]); + + // Check owner branch + { + println!("[ONE-TIME] Checking owner branch"); + let mut tx = MutableTransaction::with_entries(tx.clone(), vec![utxo_entry.clone()]); + let sig_hash = calc_schnorr_signature_hash(&tx.as_verifiable(), 0, SIG_HASH_ALL, &mut reused_values); + let msg = secp256k1::Message::from_digest_slice(sig_hash.as_bytes().as_slice()).unwrap(); + + let sig = owner.sign_schnorr(msg); + let mut signature = Vec::new(); + signature.extend_from_slice(sig.as_ref().as_slice()); + signature.push(SIG_HASH_ALL.to_u8()); + + let mut builder = ScriptBuilder::new(); + builder.add_data(&signature)?; + builder.add_op(OpTrue)?; + builder.add_data(&script)?; + { + tx.tx.inputs[0].signature_script = builder.drain(); + } + + let tx = tx.as_verifiable(); + let mut vm = + TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true) + .expect("Script creation failed"); + assert_eq!(vm.execute(), Ok(())); + println!("[ONE-TIME] Owner branch execution successful"); + } + + // Check borrower branch + { + println!("[ONE-TIME] Checking borrower branch"); + tx.inputs[0].signature_script = ScriptBuilder::new().add_op(OpFalse)?.add_data(&script)?.drain(); + let tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]); + let mut vm = + TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true) + .expect("Script creation failed"); + assert_eq!(vm.execute(), Ok(())); + println!("[ONE-TIME] Borrower branch execution successful"); + } + + // Check borrower branch with threshold not reached + { + println!("[ONE-TIME] Checking borrower branch with threshold not reached"); + // Less than threshold + tx.outputs[0].value -= 1; + let tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]); + let mut vm = + TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true) + .expect("Script creation failed"); + assert_eq!(vm.execute(), Err(EvalFalse)); + println!("[ONE-TIME] Borrower branch with threshold not reached failed as expected"); + } + + // Check borrower branch with output going to wrong address + { + println!("[ONE-TIME] Checking borrower branch with output going to wrong address"); + // Create a new key pair for a different address + let wrong_recipient = Keypair::new(secp256k1::SECP256K1, &mut thread_rng()); + let wrong_p2pk = pay_to_address_script(&Address::new(Prefix::Mainnet, Version::PubKey, wrong_recipient.x_only_public_key().0.serialize().as_slice())); + + // Create a new transaction with the wrong output address + let mut wrong_tx = tx.clone(); + wrong_tx.outputs[0].script_public_key = wrong_p2pk; + wrong_tx.inputs[0].signature_script = ScriptBuilder::new().add_op(OpFalse)?.add_data(&script)?.drain(); + + let wrong_tx = PopulatedTransaction::new(&wrong_tx, vec![utxo_entry.clone()]); + let mut vm = + TxScriptEngine::from_transaction_input(&wrong_tx, &wrong_tx.tx.inputs[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true) + .expect("Script creation failed"); + assert_eq!(vm.execute(), Err(VerifyError)); + println!("[ONE-TIME] Borrower branch with output going to wrong address failed as expected"); + } + + println!("[ONE-TIME] Threshold one-time scenario completed successfully"); Ok(()) } @@ -159,15 +356,24 @@ fn threshold_scenario() -> ScriptBuilderResult<()> { /// /// This scenario demonstrates the use of a shared secret within the Kaspa blockchain ecosystem. /// Instead of using a threshold value, it checks the shared secret and the signature associated with it. -/// There are three main sub-scenarios: /// -/// 1. **Owner scenario:** The script checks if the input is used by the owner and verifies the owner's signature. -/// 2. **Borrower scenario with shared secret:** The script allows the input to be consumed if the shared secret is verified. -/// 3. **Borrower scenario with incorrect secret:** The script fails if the borrower uses an incorrect secret. +/// ## Key Features: +/// 1. **Owner Access:** The owner can spend funds at any time using their signature. +/// 2. **Shared Secret:** A separate keypair is used as a shared secret for borrower access. +/// 3. **Borrower Verification:** The borrower must provide the correct shared secret signature to spend. +/// +/// ## Scenarios Tested: +/// 1. **Owner Spending:** Verifies that the owner can spend the funds using their signature. +/// 2. **Borrower with Correct Secret:** Checks if the borrower can spend when providing the correct shared secret. +/// 3. **Borrower with Incorrect Secret:** Ensures the script fails if the borrower uses an incorrect secret. +/// +/// # Returns +/// +/// * `ScriptBuilderResult<()>` - Result of script builder operations for this scenario. fn shared_secret_scenario() -> ScriptBuilderResult<()> { - println!("\nrun shared secret scenario"); + println!("\n[SHARED-SECRET] Running shared secret scenario"); - // Create a new key pair for the owner + // Create key pairs for the owner, shared secret, and a potential borrower let owner = Keypair::new(secp256k1::SECP256K1, &mut thread_rng()); let shared_secret_kp = Keypair::new(secp256k1::SECP256K1, &mut thread_rng()); let borrower_kp = Keypair::new(secp256k1::SECP256K1, &mut thread_rng()); @@ -234,9 +440,10 @@ fn shared_secret_scenario() -> ScriptBuilderResult<()> { signature.push(SIG_HASH_ALL.to_u8()); (tx, signature, reused_values) }; + // Check owner branch { - println!("check owner branch in shared_secret_scenario"); + println!("[SHARED-SECRET] Checking owner branch"); let (mut tx, signature, mut reused_values) = sign(owner); let mut builder = ScriptBuilder::new(); builder.add_data(&signature)?; @@ -251,12 +458,12 @@ fn shared_secret_scenario() -> ScriptBuilderResult<()> { TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true) .expect("Script creation failed"); assert_eq!(vm.execute(), Ok(())); - println!("owner scenario in shared_secret_scenario successes"); + println!("[SHARED-SECRET] Owner branch execution successful"); } - // Check borrower branch + // Check borrower branch with correct shared secret { - println!("check borrower branch in shared_secret_scenario"); + println!("[SHARED-SECRET] Checking borrower branch with correct shared secret"); let (mut tx, signature, mut reused_values) = sign(shared_secret_kp); builder.add_data(&signature)?; builder.add_data(shared_secret_kp.x_only_public_key().0.serialize().as_slice())?; @@ -271,11 +478,12 @@ fn shared_secret_scenario() -> ScriptBuilderResult<()> { TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true) .expect("Script creation failed"); assert_eq!(vm.execute(), Ok(())); - println!("borrower scenario successes in shared_secret_scenario"); + println!("[SHARED-SECRET] Borrower branch with correct shared secret execution successful"); } - // Check borrower branch with borrower signature + // Check borrower branch with incorrect secret { + println!("[SHARED-SECRET] Checking borrower branch with incorrect secret"); let (mut tx, signature, mut reused_values) = sign(borrower_kp); builder.add_data(&signature)?; builder.add_data(borrower_kp.x_only_public_key().0.serialize().as_slice())?; @@ -290,8 +498,9 @@ fn shared_secret_scenario() -> ScriptBuilderResult<()> { TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true) .expect("Script creation failed"); assert_eq!(vm.execute(), Err(VerifyError)); - println!("borrower scenario in shared_secret_scenario with wrong secret signature failed! all good"); + println!("[SHARED-SECRET] Borrower branch with incorrect secret failed as expected"); } + println!("[SHARED-SECRET] Shared secret scenario completed successfully"); Ok(()) } From e5e8b64ef4d0fa31ad1f080ef6e5f7e6e775bf93 Mon Sep 17 00:00:00 2001 From: max143672 Date: Tue, 15 Oct 2024 14:33:32 +0400 Subject: [PATCH 26/93] Implement one-time and two-times threshold borrowing scenarios - Add threshold_scenario_limited_one_time function - Add threshold_scenario_limited_2_times function - Create generate_limited_time_script for reusable script generation - Implement nested script structure for two-times borrowing - Update documentation for both scenarios - Add tests for owner spending, borrowing, and invalid attempts in both cases - Ensure consistent error handling and logging across scenarios - Refactor to use more generic script generation approach --- crypto/txscript/examples/kip-10.rs | 229 ++++++++++++++++++++++++++--- 1 file changed, 206 insertions(+), 23 deletions(-) diff --git a/crypto/txscript/examples/kip-10.rs b/crypto/txscript/examples/kip-10.rs index 29dad0a32..b7dff7346 100644 --- a/crypto/txscript/examples/kip-10.rs +++ b/crypto/txscript/examples/kip-10.rs @@ -31,6 +31,7 @@ use secp256k1::Keypair; fn main() -> ScriptBuilderResult<()> { threshold_scenario()?; threshold_scenario_limited_one_time()?; + threshold_scenario_limited_2_times()?; shared_secret_scenario()?; Ok(()) } @@ -161,31 +162,22 @@ fn threshold_scenario() -> ScriptBuilderResult<()> { Ok(()) } -/// Generate a script for the one-time borrowing scenario +/// Generate a script for limited-time borrowing scenarios /// -/// This function creates a script that allows for one-time borrowing with a threshold, -/// or spending by the owner at any time. +/// This function creates a script that allows for limited-time borrowing with a threshold, +/// or spending by the owner at any time. It's generic enough to be used for both one-time +/// and multi-time borrowing scenarios. /// /// # Arguments /// /// * `owner` - The public key of the owner /// * `threshold` - The threshold amount that must be met for borrowing +/// * `output_spk` - The output script public key as a vector of bytes /// /// # Returns /// /// * The generated script as a vector of bytes -fn generate_one_time_script(owner: &Keypair, threshold: i64) -> ScriptBuilderResult> { - let p2pk = - pay_to_address_script(&Address::new(Prefix::Mainnet, Version::PubKey, owner.x_only_public_key().0.serialize().as_slice())); - let p2pk_as_vec = { - let version = p2pk.version.to_be_bytes(); - let script = p2pk.script(); - let mut v = Vec::with_capacity(version.len() + script.len()); - v.extend_from_slice(&version); - v.extend_from_slice(script); - v - }; - +fn generate_limited_time_script(owner: &Keypair, threshold: i64, output_spk: Vec) -> ScriptBuilderResult> { let mut builder = ScriptBuilder::new(); let script = builder // Owner branch @@ -194,7 +186,7 @@ fn generate_one_time_script(owner: &Keypair, threshold: i64) -> ScriptBuilderRes .add_op(OpCheckSig)? // Borrower branch .add_op(OpElse)? - .add_data(&p2pk_as_vec)? + .add_data(&output_spk)? .add_ops(&[OpOutputSpk, OpEqualVerify, OpOutputAmount])? .add_i64(threshold)? .add_ops(&[OpSub, OpInputAmount, OpGreaterThanOrEqual])? @@ -204,6 +196,18 @@ fn generate_one_time_script(owner: &Keypair, threshold: i64) -> ScriptBuilderRes Ok(script) } +// Helper function to create P2PK script as a vector +fn p2pk_as_vec(owner: &Keypair) -> Vec { + let p2pk = + pay_to_address_script(&Address::new(Prefix::Mainnet, Version::PubKey, owner.x_only_public_key().0.serialize().as_slice())); + let version = p2pk.version.to_be_bytes(); + let script = p2pk.script(); + let mut v = Vec::with_capacity(version.len() + script.len()); + v.extend_from_slice(&version); + v.extend_from_slice(script); + v +} + /// # Threshold Scenario with Limited One-Time Borrowing /// /// This function demonstrates a modified version of the threshold scenario where borrowing @@ -236,9 +240,10 @@ fn threshold_scenario_limited_one_time() -> ScriptBuilderResult<()> { // Set a threshold value for comparison let threshold: i64 = 100; - // Generate the one-time script - let script = generate_one_time_script(&owner, threshold)?; - let p2pk = pay_to_address_script(&Address::new(Prefix::Mainnet, Version::PubKey, owner.x_only_public_key().0.serialize().as_slice())); + let p2pk = + pay_to_address_script(&Address::new(Prefix::Mainnet, Version::PubKey, owner.x_only_public_key().0.serialize().as_slice())); + let p2pk_vec = p2pk_as_vec(&owner); + let script = generate_limited_time_script(&owner, threshold, p2pk_vec.clone())?; // Initialize a cache for signature verification let sig_cache = Cache::new(10_000); @@ -333,7 +338,11 @@ fn threshold_scenario_limited_one_time() -> ScriptBuilderResult<()> { println!("[ONE-TIME] Checking borrower branch with output going to wrong address"); // Create a new key pair for a different address let wrong_recipient = Keypair::new(secp256k1::SECP256K1, &mut thread_rng()); - let wrong_p2pk = pay_to_address_script(&Address::new(Prefix::Mainnet, Version::PubKey, wrong_recipient.x_only_public_key().0.serialize().as_slice())); + let wrong_p2pk = pay_to_address_script(&Address::new( + Prefix::Mainnet, + Version::PubKey, + wrong_recipient.x_only_public_key().0.serialize().as_slice(), + )); // Create a new transaction with the wrong output address let mut wrong_tx = tx.clone(); @@ -341,9 +350,16 @@ fn threshold_scenario_limited_one_time() -> ScriptBuilderResult<()> { wrong_tx.inputs[0].signature_script = ScriptBuilder::new().add_op(OpFalse)?.add_data(&script)?.drain(); let wrong_tx = PopulatedTransaction::new(&wrong_tx, vec![utxo_entry.clone()]); - let mut vm = - TxScriptEngine::from_transaction_input(&wrong_tx, &wrong_tx.tx.inputs[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true) - .expect("Script creation failed"); + let mut vm = TxScriptEngine::from_transaction_input( + &wrong_tx, + &wrong_tx.tx.inputs[0], + 0, + &utxo_entry, + &mut reused_values, + &sig_cache, + true, + ) + .expect("Script creation failed"); assert_eq!(vm.execute(), Err(VerifyError)); println!("[ONE-TIME] Borrower branch with output going to wrong address failed as expected"); } @@ -352,6 +368,173 @@ fn threshold_scenario_limited_one_time() -> ScriptBuilderResult<()> { Ok(()) } +/// # Threshold Scenario with Limited Two-Times Borrowing +/// +/// This function demonstrates a modified version of the threshold scenario where borrowing +/// is limited to two occurrences. The key difference from the one-time scenario is that +/// the first borrowing outputs to a P2SH of the one-time script, allowing for a second borrowing. +/// +/// ## Key Features: +/// 1. **Two-Times Borrowing:** The borrower can use this mechanism twice. +/// 2. **Owner Access:** The owner retains the ability to spend the funds at any time using their private key. +/// 3. **Threshold Mechanism:** The borrower must still meet the threshold requirement to spend the funds. +/// 4. **Output Validation:** Ensures the output goes to the correct address (P2SH of one-time script for first borrow). +/// +/// ## Scenarios Tested: +/// 1. **Owner Spending:** Verifies that the owner can spend the funds using their signature. +/// 2. **Borrower First Spending:** Checks if the borrower can spend when meeting the threshold and +/// sending to the correct P2SH address of the one-time script. +/// 3. **Invalid Borrower Attempt (Threshold):** Ensures the script fails if the borrower doesn't meet the threshold. +/// 4. **Invalid Borrower Attempt (Wrong Output):** Ensures the script fails if the output goes to an incorrect address. +/// +/// # Returns +/// +/// * `ScriptBuilderResult<()>` - Result of script builder operations for this scenario. +fn threshold_scenario_limited_2_times() -> ScriptBuilderResult<()> { + println!("\n[TWO-TIMES] Running threshold two-times scenario"); + let owner = Keypair::new(secp256k1::SECP256K1, &mut thread_rng()); + let threshold: i64 = 100; + + // First, create the one-time script + let p2pk_vec = p2pk_as_vec(&owner); + let one_time_script = generate_limited_time_script(&owner, threshold, p2pk_vec)?; + + // Now, create the two-times script using the one-time script as output + let p2sh_one_time = pay_to_script_hash_script(&one_time_script); + let p2sh_one_time_vec = { + let version = p2sh_one_time.version.to_be_bytes(); + let script = p2sh_one_time.script(); + let mut v = Vec::with_capacity(version.len() + script.len()); + v.extend_from_slice(&version); + v.extend_from_slice(script); + v + }; + + let two_times_script = generate_limited_time_script(&owner, threshold, p2sh_one_time_vec)?; + + // Initialize a cache for signature verification + let sig_cache = Cache::new(10_000); + + // Prepare to reuse values for signature hashing + let mut reused_values = SigHashReusedValues::new(); + + // Generate the script public key + let spk = pay_to_script_hash_script(&two_times_script); + + // Define the input value + let input_value = 1000000000; + + // Create a transaction output + let output = TransactionOutput { value: 1000000000 + threshold as u64, script_public_key: p2sh_one_time }; + + // Create a UTXO entry for the input + let utxo_entry = UtxoEntry::new(input_value, spk, 0, false); + + // Create a transaction input + let input = TransactionInput { + previous_outpoint: TransactionOutpoint { + transaction_id: TransactionId::from_bytes([ + 0xc9, 0x97, 0xa5, 0xe5, 0x6e, 0x10, 0x42, 0x02, 0xfa, 0x20, 0x9c, 0x6a, 0x85, 0x2d, 0xd9, 0x06, 0x60, 0xa2, 0x0b, + 0x2d, 0x9c, 0x35, 0x24, 0x23, 0xed, 0xce, 0x25, 0x85, 0x7f, 0xcd, 0x37, 0x04, + ]), + index: 0, + }, + signature_script: ScriptBuilder::new().add_data(&two_times_script)?.drain(), + sequence: 4294967295, + sig_op_count: 0, + }; + + // Create a transaction with the input and output + let mut tx = Transaction::new(1, vec![input.clone()], vec![output.clone()], 0, Default::default(), 0, vec![]); + + // Check owner branch + { + println!("[TWO-TIMES] Checking owner branch"); + let mut tx = MutableTransaction::with_entries(tx.clone(), vec![utxo_entry.clone()]); + let sig_hash = calc_schnorr_signature_hash(&tx.as_verifiable(), 0, SIG_HASH_ALL, &mut reused_values); + let msg = secp256k1::Message::from_digest_slice(sig_hash.as_bytes().as_slice()).unwrap(); + + let sig = owner.sign_schnorr(msg); + let mut signature = Vec::new(); + signature.extend_from_slice(sig.as_ref().as_slice()); + signature.push(SIG_HASH_ALL.to_u8()); + + let mut builder = ScriptBuilder::new(); + builder.add_data(&signature)?; + builder.add_op(OpTrue)?; + builder.add_data(&two_times_script)?; + { + tx.tx.inputs[0].signature_script = builder.drain(); + } + + let tx = tx.as_verifiable(); + let mut vm = + TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true) + .expect("Script creation failed"); + assert_eq!(vm.execute(), Ok(())); + println!("[TWO-TIMES] Owner branch execution successful"); + } + + // Check borrower branch (first borrowing) + { + println!("[TWO-TIMES] Checking borrower branch (first borrowing)"); + tx.inputs[0].signature_script = ScriptBuilder::new().add_op(OpFalse)?.add_data(&two_times_script)?.drain(); + let tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]); + let mut vm = + TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true) + .expect("Script creation failed"); + assert_eq!(vm.execute(), Ok(())); + println!("[TWO-TIMES] Borrower branch (first borrowing) execution successful"); + } + + // Check borrower branch with threshold not reached + { + println!("[TWO-TIMES] Checking borrower branch with threshold not reached"); + // Less than threshold + tx.outputs[0].value -= 1; + let tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]); + let mut vm = + TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true) + .expect("Script creation failed"); + assert_eq!(vm.execute(), Err(EvalFalse)); + println!("[TWO-TIMES] Borrower branch with threshold not reached failed as expected"); + } + + // Check borrower branch with output going to wrong address + { + println!("[TWO-TIMES] Checking borrower branch with output going to wrong address"); + // Create a new key pair for a different address + let wrong_recipient = Keypair::new(secp256k1::SECP256K1, &mut thread_rng()); + let wrong_p2pk = pay_to_address_script(&Address::new( + Prefix::Mainnet, + Version::PubKey, + wrong_recipient.x_only_public_key().0.serialize().as_slice(), + )); + + // Create a new transaction with the wrong output address + let mut wrong_tx = tx.clone(); + wrong_tx.outputs[0].script_public_key = wrong_p2pk; + wrong_tx.inputs[0].signature_script = ScriptBuilder::new().add_op(OpFalse)?.add_data(&two_times_script)?.drain(); + + let wrong_tx = PopulatedTransaction::new(&wrong_tx, vec![utxo_entry.clone()]); + let mut vm = TxScriptEngine::from_transaction_input( + &wrong_tx, + &wrong_tx.tx.inputs[0], + 0, + &utxo_entry, + &mut reused_values, + &sig_cache, + true, + ) + .expect("Script creation failed"); + assert_eq!(vm.execute(), Err(VerifyError)); + println!("[TWO-TIMES] Borrower branch with output going to wrong address failed as expected"); + } + + println!("[TWO-TIMES] Threshold two-times scenario completed successfully"); + Ok(()) +} + /// # Shared Secret Scenario /// /// This scenario demonstrates the use of a shared secret within the Kaspa blockchain ecosystem. From ef4e7ee415e38ac6f2fc63c2c4c1827822fe0c89 Mon Sep 17 00:00:00 2001 From: max143672 Date: Tue, 15 Oct 2024 19:58:38 +0400 Subject: [PATCH 27/93] fix: fix incorrect sig-op count --- crypto/txscript/examples/kip-10.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crypto/txscript/examples/kip-10.rs b/crypto/txscript/examples/kip-10.rs index b7dff7346..09778f940 100644 --- a/crypto/txscript/examples/kip-10.rs +++ b/crypto/txscript/examples/kip-10.rs @@ -99,7 +99,7 @@ fn threshold_scenario() -> ScriptBuilderResult<()> { }, signature_script: ScriptBuilder::new().add_data(&script)?.drain(), sequence: 4294967295, - sig_op_count: 0, + sig_op_count: 1, }; // Create a transaction with the input and output @@ -274,7 +274,7 @@ fn threshold_scenario_limited_one_time() -> ScriptBuilderResult<()> { }, signature_script: ScriptBuilder::new().add_data(&script)?.drain(), sequence: 4294967295, - sig_op_count: 0, + sig_op_count: 1, }; // Create a transaction with the input and output @@ -441,7 +441,7 @@ fn threshold_scenario_limited_2_times() -> ScriptBuilderResult<()> { }, signature_script: ScriptBuilder::new().add_data(&two_times_script)?.drain(), sequence: 4294967295, - sig_op_count: 0, + sig_op_count: 1, }; // Create a transaction with the input and output @@ -604,7 +604,7 @@ fn shared_secret_scenario() -> ScriptBuilderResult<()> { }, signature_script: ScriptBuilder::new().add_data(&script)?.drain(), sequence: 4294967295, - sig_op_count: 0, + sig_op_count: 1, }; // Create a transaction with the input and output From 7b6cf0d1a8a61ae26cdb43ef29b2f45ef804dd48 Mon Sep 17 00:00:00 2001 From: max143672 Date: Tue, 22 Oct 2024 18:34:22 +0400 Subject: [PATCH 28/93] correct error description --- crypto/txscript/src/data_stack.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto/txscript/src/data_stack.rs b/crypto/txscript/src/data_stack.rs index 5d8ea18ed..401ac1dc4 100644 --- a/crypto/txscript/src/data_stack.rs +++ b/crypto/txscript/src/data_stack.rs @@ -142,7 +142,7 @@ impl OpcodeData> for Vec { "numeric value encoded as {:x?} is {} bytes which exceeds the max allowed of {}", self, self.len(), - DEFAULT_SCRIPT_NUM_LEN + LEN ))), false => deserialize_i64(self).map(SizedEncodeInt::), } From cdb43ae1bfa838b8e0759d719bcd633222d99b01 Mon Sep 17 00:00:00 2001 From: max143672 Date: Tue, 22 Oct 2024 23:41:45 +0400 Subject: [PATCH 29/93] style: fmt --- crypto/txscript/examples/kip-10.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto/txscript/examples/kip-10.rs b/crypto/txscript/examples/kip-10.rs index 82bac1456..d3e028b9f 100644 --- a/crypto/txscript/examples/kip-10.rs +++ b/crypto/txscript/examples/kip-10.rs @@ -1,4 +1,5 @@ use kaspa_addresses::{Address, Prefix, Version}; +use kaspa_consensus_core::hashing::sighash::SigHashReusedValuesUnsync; use kaspa_consensus_core::{ hashing::{ sighash::{calc_schnorr_signature_hash, SigHashReusedValues}, @@ -22,7 +23,6 @@ use kaspa_txscript::{ use kaspa_txscript_errors::TxScriptError::{EvalFalse, VerifyError}; use rand::thread_rng; use secp256k1::Keypair; -use kaspa_consensus_core::hashing::sighash::SigHashReusedValuesUnsync; /// Main function to execute all Kaspa transaction script scenarios. /// From 730df361a3507ffe5d6b3068f90f2424c33bb31f Mon Sep 17 00:00:00 2001 From: max143672 Date: Thu, 31 Oct 2024 12:52:27 +0400 Subject: [PATCH 30/93] pass kip-10 flag in constructor params --- crypto/txscript/src/lib.rs | 13 +++++++++---- crypto/txscript/src/opcodes/mod.rs | 10 +++++----- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/crypto/txscript/src/lib.rs b/crypto/txscript/src/lib.rs index 7e7801b9f..0554b9721 100644 --- a/crypto/txscript/src/lib.rs +++ b/crypto/txscript/src/lib.rs @@ -155,7 +155,7 @@ pub fn is_unspendable(scr } impl<'a, T: VerifiableTransaction, Reused: SigHashReusedValues> TxScriptEngine<'a, T, Reused> { - pub fn new(reused_values: &'a Reused, sig_cache: &'a Cache) -> Self { + pub fn new(reused_values: &'a Reused, sig_cache: &'a Cache, kip10_enabled: bool) -> Self { Self { dstack: vec![], astack: vec![], @@ -164,7 +164,7 @@ impl<'a, T: VerifiableTransaction, Reused: SigHashReusedValues> TxScriptEngine<' sig_cache, cond_stack: vec![], num_ops: 0, - kip10_enabled: false, + kip10_enabled, } } @@ -196,7 +196,12 @@ impl<'a, T: VerifiableTransaction, Reused: SigHashReusedValues> TxScriptEngine<' } } - pub fn from_script(script: &'a [u8], reused_values: &'a Reused, sig_cache: &'a Cache) -> Self { + pub fn from_script( + script: &'a [u8], + reused_values: &'a Reused, + sig_cache: &'a Cache, + kip10_enabled: bool, + ) -> Self { Self { dstack: Default::default(), astack: Default::default(), @@ -205,7 +210,7 @@ impl<'a, T: VerifiableTransaction, Reused: SigHashReusedValues> TxScriptEngine<' sig_cache, cond_stack: Default::default(), num_ops: 0, - kip10_enabled: false, + kip10_enabled, } } diff --git a/crypto/txscript/src/opcodes/mod.rs b/crypto/txscript/src/opcodes/mod.rs index 6bfb1dcbb..200b1fb45 100644 --- a/crypto/txscript/src/opcodes/mod.rs +++ b/crypto/txscript/src/opcodes/mod.rs @@ -1079,7 +1079,7 @@ mod test { let cache = Cache::new(10_000); let reused_values = SigHashReusedValuesUnsync::new(); for TestCase { init, code, dstack } in tests { - let mut vm = TxScriptEngine::new(&reused_values, &cache); + let mut vm = TxScriptEngine::new(&reused_values, &cache, false); vm.dstack = init; code.execute(&mut vm).unwrap_or_else(|_| panic!("Opcode {} should not fail", code.value())); assert_eq!(*vm.dstack, dstack, "OpCode {} Pushed wrong value", code.value()); @@ -1090,7 +1090,7 @@ mod test { let cache = Cache::new(10_000); let reused_values = SigHashReusedValuesUnsync::new(); for ErrorTestCase { init, code, error } in tests { - let mut vm = TxScriptEngine::new(&reused_values, &cache); + let mut vm = TxScriptEngine::new(&reused_values, &cache, false); vm.dstack.clone_from(&init); assert_eq!( code.execute(&mut vm) @@ -1125,7 +1125,7 @@ mod test { let cache = Cache::new(10_000); let reused_values = SigHashReusedValuesUnsync::new(); - let mut vm = TxScriptEngine::new(&reused_values, &cache); + let mut vm = TxScriptEngine::new(&reused_values, &cache, false); for pop in tests { match pop.execute(&mut vm) { @@ -1148,7 +1148,7 @@ mod test { let cache = Cache::new(10_000); let reused_values = SigHashReusedValuesUnsync::new(); - let mut vm = TxScriptEngine::new(&reused_values, &cache); + let mut vm = TxScriptEngine::new(&reused_values, &cache, false); for pop in tests { match pop.execute(&mut vm) { @@ -1235,7 +1235,7 @@ mod test { let cache = Cache::new(10_000); let reused_values = SigHashReusedValuesUnsync::new(); - let mut vm = TxScriptEngine::new(&reused_values, &cache); + let mut vm = TxScriptEngine::new(&reused_values, &cache, false); for pop in tests { match pop.execute(&mut vm) { From a777a126a6921446f2cb3a861ab7a4e610f9b943 Mon Sep 17 00:00:00 2001 From: max143672 Date: Sat, 2 Nov 2024 19:37:30 +0400 Subject: [PATCH 31/93] remove borrow scenario from tests. run tests against both kip1- enabled/disabled engine --- crypto/txscript/src/lib.rs | 63 ++++++++------------------------------ 1 file changed, 13 insertions(+), 50 deletions(-) diff --git a/crypto/txscript/src/lib.rs b/crypto/txscript/src/lib.rs index 0554b9721..193033594 100644 --- a/crypto/txscript/src/lib.rs +++ b/crypto/txscript/src/lib.rs @@ -582,11 +582,19 @@ mod tests { let utxo_entry = UtxoEntry::new(output.value, output.script_public_key.clone(), 0, tx.is_coinbase()); let populated_tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]); - - let mut vm = - TxScriptEngine::from_transaction_input(&populated_tx, &input, 0, &utxo_entry, &reused_values, &sig_cache, false) - .expect("Script creation failed"); - assert_eq!(vm.execute(), test.expected_result); + [false, true].into_iter().for_each(|kip10_enabled| { + let mut vm = TxScriptEngine::from_transaction_input( + &populated_tx, + &input, + 0, + &utxo_entry, + &reused_values, + &sig_cache, + kip10_enabled, + ) + .expect("Script creation failed"); + assert_eq!(vm.execute(), test.expected_result); + }); } } @@ -938,51 +946,6 @@ mod tests { ); } } - #[test] - fn output_gt_input_test() { - use crate::opcodes::codes::{ - OpEqualVerify, OpGreaterThanOrEqual, OpInputAmount, OpInputSpk, OpOutputAmount, OpOutputSpk, OpSub, - }; - - use super::*; - use crate::script_builder::ScriptBuilder; - - let threshold: i64 = 100; - let sig_cache = Cache::new(10_000); - let reused_values = SigHashReusedValuesUnsync::new(); - let script = ScriptBuilder::new() - .add_ops(&[OpInputSpk, OpOutputSpk, OpEqualVerify, OpOutputAmount]) - .unwrap() - .add_i64(threshold) - .unwrap() - .add_ops(&[OpSub, OpInputAmount, OpGreaterThanOrEqual]) - .unwrap() - .drain(); - let spk = pay_to_script_hash_script(&script); - let input = TransactionInput { - previous_outpoint: TransactionOutpoint { - transaction_id: TransactionId::from_bytes([ - 0xc9, 0x97, 0xa5, 0xe5, 0x6e, 0x10, 0x41, 0x02, 0xfa, 0x20, 0x9c, 0x6a, 0x85, 0x2d, 0xd9, 0x06, 0x60, 0xa2, 0x0b, - 0x2d, 0x9c, 0x35, 0x24, 0x23, 0xed, 0xce, 0x25, 0x85, 0x7f, 0xcd, 0x37, 0x04, - ]), - index: 0, - }, - signature_script: ScriptBuilder::new().add_data(&script).unwrap().drain(), - sequence: 4294967295, - sig_op_count: 0, - }; - let input_value = 1000000000; - let output = TransactionOutput { value: 1000000000 + threshold as u64, script_public_key: spk.clone() }; - - let tx = Transaction::new(1, vec![input.clone()], vec![output.clone()], 0, Default::default(), 0, vec![]); - let utxo_entry = UtxoEntry::new(input_value, spk, 0, tx.is_coinbase()); - - let populated_tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]); - - let mut vm = TxScriptEngine::from_transaction_input(&populated_tx, &input, 0, &utxo_entry, &reused_values, &sig_cache, true) - .expect("Script creation failed"); - assert_eq!(vm.execute(), Ok(())); - } } #[cfg(test)] From c216370496094aba17befaf2284388d7e443d698 Mon Sep 17 00:00:00 2001 From: max143672 Date: Sat, 2 Nov 2024 19:39:09 +0400 Subject: [PATCH 32/93] introduce method that converts spk to bytes. add tests covering new opcodes --- consensus/core/src/tx/script_public_key.rs | 11 ++ crypto/txscript/src/data_stack.rs | 2 +- crypto/txscript/src/opcodes/mod.rs | 179 +++++++++++++++++---- 3 files changed, 162 insertions(+), 30 deletions(-) diff --git a/consensus/core/src/tx/script_public_key.rs b/consensus/core/src/tx/script_public_key.rs index dfed2ab5c..b25227b84 100644 --- a/consensus/core/src/tx/script_public_key.rs +++ b/consensus/core/src/tx/script_public_key.rs @@ -56,6 +56,17 @@ pub struct ScriptPublicKey { pub(super) script: ScriptVec, // Kept private to preserve read-only semantics } +impl ScriptPublicKey { + pub fn to_bytes(&self) -> Vec { + let version = self.version.to_be_bytes(); + let script = self.script(); + let mut v = Vec::with_capacity(version.len() + script.len()); + v.extend_from_slice(&version); + v.extend_from_slice(script); + v + } +} + impl std::fmt::Debug for ScriptPublicKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("ScriptPublicKey").field("version", &self.version).field("script", &self.script.to_hex()).finish() diff --git a/crypto/txscript/src/data_stack.rs b/crypto/txscript/src/data_stack.rs index 401ac1dc4..19053ef87 100644 --- a/crypto/txscript/src/data_stack.rs +++ b/crypto/txscript/src/data_stack.rs @@ -5,7 +5,7 @@ use core::iter; const DEFAULT_SCRIPT_NUM_LEN: usize = 4; #[derive(PartialEq, Eq, Debug, Default)] -pub(crate) struct SizedEncodeInt(i64); +pub(crate) struct SizedEncodeInt(pub(crate) i64); pub(crate) type Stack = Vec>; diff --git a/crypto/txscript/src/opcodes/mod.rs b/crypto/txscript/src/opcodes/mod.rs index 200b1fb45..4e9e43bbf 100644 --- a/crypto/txscript/src/opcodes/mod.rs +++ b/crypto/txscript/src/opcodes/mod.rs @@ -888,18 +888,12 @@ opcode_list! { match vm.script_source { ScriptSource::TxInput{ utxo_entry: kaspa_consensus_core::tx::UtxoEntry{ - script_public_key: spk @ kaspa_consensus_core::tx::ScriptPublicKey{ - version, ..}, + script_public_key: spk, .. }, .. } => { - let version = version.to_be_bytes(); - let script = spk.script(); - let mut v = Vec::with_capacity(version.len() + script.len()); - v.extend_from_slice(&version); - v.extend_from_slice(script); - vm.dstack.push(v); + vm.dstack.push(spk.to_bytes()); Ok(()) }, _ => Err(TxScriptError::InvalidSource("OpInputSpk only applies to transaction inputs".to_string())) @@ -929,12 +923,7 @@ opcode_list! { match vm.script_source { ScriptSource::TxInput{tx, id , ..} => { let v = tx.outputs().get(id).map(|output| { - let version = output.script_public_key.version.to_be_bytes(); - let script = output.script_public_key.script(); - let mut v = Vec::with_capacity(version.len() + script.len()); - v.extend_from_slice(&version); - v.extend_from_slice(script); - v + output.script_public_key.to_bytes() }); vm.dstack.push(v.unwrap_or_default()); Ok(()) @@ -1051,7 +1040,7 @@ pub fn to_small_int(opcod #[cfg(test)] mod test { use crate::caches::Cache; - use crate::data_stack::Stack; + use crate::data_stack::{OpcodeData, SizedEncodeInt, Stack}; use crate::opcodes::{OpCodeExecution, OpCodeImplementation}; use crate::{opcodes, pay_to_address_script, TxScriptEngine, TxScriptError, LOCK_TIME_THRESHOLD}; use kaspa_addresses::{Address, Prefix, Version}; @@ -1079,10 +1068,12 @@ mod test { let cache = Cache::new(10_000); let reused_values = SigHashReusedValuesUnsync::new(); for TestCase { init, code, dstack } in tests { - let mut vm = TxScriptEngine::new(&reused_values, &cache, false); - vm.dstack = init; - code.execute(&mut vm).unwrap_or_else(|_| panic!("Opcode {} should not fail", code.value())); - assert_eq!(*vm.dstack, dstack, "OpCode {} Pushed wrong value", code.value()); + [false, true].into_iter().for_each(|kip10_enabled| { + let mut vm = TxScriptEngine::new(&reused_values, &cache, kip10_enabled); + vm.dstack = init.clone(); + code.execute(&mut vm).unwrap_or_else(|_| panic!("Opcode {} should not fail", code.value())); + assert_eq!(*vm.dstack, dstack, "OpCode {} Pushed wrong value", code.value()); + }); } } @@ -1090,16 +1081,18 @@ mod test { let cache = Cache::new(10_000); let reused_values = SigHashReusedValuesUnsync::new(); for ErrorTestCase { init, code, error } in tests { - let mut vm = TxScriptEngine::new(&reused_values, &cache, false); - vm.dstack.clone_from(&init); - assert_eq!( - code.execute(&mut vm) - .expect_err(format!("Opcode {} should have errored (init: {:?})", code.value(), init.clone()).as_str()), - error, - "Opcode {} returned wrong error {:?}", - code.value(), - init - ); + [false, true].into_iter().for_each(|kip10_enabled| { + let mut vm = TxScriptEngine::new(&reused_values, &cache, kip10_enabled); + vm.dstack.clone_from(&init); + assert_eq!( + code.execute(&mut vm) + .expect_err(format!("Opcode {} should have errored (init: {:?})", code.value(), init.clone()).as_str()), + error, + "Opcode {} returned wrong error {:?}", + code.value(), + init + ); + }); } } @@ -2351,6 +2344,134 @@ mod test { ]); } + fn execute_kip_10_op_with_mock_engine( + input_spk: ScriptPublicKey, + output_spk: Option, + input_amount: u64, + output_amount: u64, + cb: impl FnOnce(TxScriptEngine), + ) { + let cache = Cache::new(10_000); + let reused_values = SigHashReusedValuesUnsync::new(); + let dummy_prev_out = TransactionOutpoint::new(kaspa_hashes::Hash::from_u64_word(1), 1); + let dummy_sig_script = vec![0u8; 65]; + let tx_input = TransactionInput::new(dummy_prev_out, dummy_sig_script, 10, 0); + + let tx_out = output_spk.map(|spk| TransactionOutput::new(output_amount, spk)); + let tx = VerifiableTransactionMock(Transaction::new( + TX_VERSION + 1, + vec![tx_input.clone()], + tx_out.map(|tx_out| vec![tx_out]).unwrap_or_default(), + 0, + SUBNETWORK_ID_NATIVE, + 0, + vec![], + )); + let utxo_entry = UtxoEntry::new(input_amount, input_spk.clone(), 0, false); + + let vm = TxScriptEngine::from_transaction_input(&tx, &tx_input, 0, &utxo_entry, &reused_values, &cache, true) + .expect("Shouldn't fail"); + cb(vm); + } + #[test] + fn test_op_input_spk() { + let input_pub_key = vec![1u8; 32]; + let input_addr = Address::new(Prefix::Testnet, Version::PubKey, &input_pub_key); + let input_script_public_key = pay_to_address_script(&input_addr); + execute_kip_10_op_with_mock_engine(input_script_public_key.clone(), None, 0, 0, |mut vm| { + let code = opcodes::OpInputSpk::empty().expect("Should accept empty"); + code.execute(&mut vm).unwrap(); + assert_eq!(vm.dstack, vec![input_script_public_key.to_bytes()]); + }); + } + + #[test] + fn test_op_output_spk() { + let input_pub_key = vec![1u8; 32]; + let input_addr = Address::new(Prefix::Testnet, Version::PubKey, &input_pub_key); + let input_script_public_key = pay_to_address_script(&input_addr); + let output_pub_key = vec![2u8; 32]; + let output_addr = Address::new(Prefix::Testnet, Version::PubKey, &output_pub_key); + let output_script_public_key = pay_to_address_script(&output_addr); + [None, Some(output_script_public_key.clone())].into_iter().for_each(|output_spk| { + execute_kip_10_op_with_mock_engine(input_script_public_key.clone(), output_spk.clone(), 0, 0, |mut vm| { + let code = opcodes::OpOutputSpk::empty().expect("Should accept empty"); + code.execute(&mut vm).unwrap(); + assert_eq!(vm.dstack, vec![output_spk.map(|output_spk| output_spk.to_bytes()).unwrap_or_default()]); + vm.dstack.clear(); + }); + }); + } + + #[test] + fn test_op_input_amount() { + let input_pub_key = vec![1u8; 32]; + let input_addr = Address::new(Prefix::Testnet, Version::PubKey, &input_pub_key); + let input_script_public_key = pay_to_address_script(&input_addr); + [0, 268_435_455].into_iter().for_each(|amount| { + execute_kip_10_op_with_mock_engine(input_script_public_key.clone(), None, amount, 0, |mut vm| { + let code = opcodes::OpInputAmount::empty().expect("Should accept empty"); + code.execute(&mut vm).unwrap(); + let actual: i64 = vm.dstack[0].deserialize().unwrap(); + assert_eq!(actual, amount as i64); + }); + }); + + const MAX_SOMPI: u64 = 29_000_000_000 * SOMPI_PER_KASPA; // works if serialized to SizedEncodeInt<8> + execute_kip_10_op_with_mock_engine(input_script_public_key.clone(), None, MAX_SOMPI, 0, |mut vm| { + let code = opcodes::OpInputAmount::empty().expect("Should accept empty"); + code.execute(&mut vm).unwrap(); + let actual: SizedEncodeInt<8> = vm.dstack[0].deserialize().unwrap(); + assert_eq!(actual.0, MAX_SOMPI as i64); + }); + } + + #[test] + fn test_op_output_amount() { + let input_pub_key = vec![1u8; 32]; + let input_addr = Address::new(Prefix::Testnet, Version::PubKey, &input_pub_key); + let input_script_public_key = pay_to_address_script(&input_addr); + let output_pub_key = vec![2u8; 32]; + let output_addr = Address::new(Prefix::Testnet, Version::PubKey, &output_pub_key); + let output_script_public_key = pay_to_address_script(&output_addr); + + execute_kip_10_op_with_mock_engine(input_script_public_key.clone(), None, 0, 0, |mut vm| { + let code = opcodes::OpOutputAmount::empty().expect("Should accept empty"); + code.execute(&mut vm).unwrap(); + let actual: i64 = vm.dstack[0].deserialize().unwrap(); + assert_eq!(actual, 0); + }); + + [0, 268_435_455].into_iter().for_each(|amount| { + execute_kip_10_op_with_mock_engine( + input_script_public_key.clone(), + Some(output_script_public_key.clone()), + 0, + amount, + |mut vm| { + let code = opcodes::OpOutputAmount::empty().expect("Should accept empty"); + code.execute(&mut vm).unwrap(); + let actual: i64 = vm.dstack[0].deserialize().unwrap(); + assert_eq!(actual, amount as i64); + }, + ); + }); + + const MAX_SOMPI: u64 = 29_000_000_000 * SOMPI_PER_KASPA; // works if serialized to SizedEncodeInt<8> + execute_kip_10_op_with_mock_engine( + input_script_public_key.clone(), + Some(output_script_public_key.clone()), + 0, + MAX_SOMPI, + |mut vm| { + let code = opcodes::OpOutputAmount::empty().expect("Should accept empty"); + code.execute(&mut vm).unwrap(); + let actual: SizedEncodeInt<8> = vm.dstack[0].deserialize().unwrap(); + assert_eq!(actual.0, MAX_SOMPI as i64); + }, + ); + } + #[test] fn test_oppick() { run_success_test_cases(vec![ From 20a8df65839e9eace1b017c0551f1d2d9a738ce3 Mon Sep 17 00:00:00 2001 From: max143672 Date: Sat, 2 Nov 2024 19:55:20 +0400 Subject: [PATCH 33/93] return comment describing where invalid opcodes starts from. add comments describing why 2 files are used. --- crypto/txscript/src/lib.rs | 15 +++++++++++++++ crypto/txscript/test-data/script_tests-kip10.json | 3 ++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/crypto/txscript/src/lib.rs b/crypto/txscript/src/lib.rs index 193033594..df17d56bf 100644 --- a/crypto/txscript/src/lib.rs +++ b/crypto/txscript/src/lib.rs @@ -1139,6 +1139,21 @@ mod bitcoind_tests { #[test] fn test_bitcoind_tests() { + // Script test files are split into two versions to test behavior before and after KIP-10: + // + // - script_tests.json: Tests basic script functionality with KIP-10 disabled (kip10_enabled=false) + // - script_tests-kip10.json: Tests expanded functionality with KIP-10 enabled (kip10_enabled=true) + // + // KIP-10 introduces four new opcodes to enhance script functionality: + // - OpInputSpk (0xb2): Retrieves the script public key of the current input + // - OpInputAmount (0xb3): Retrieves the amount of the current input + // - OpOutputSpk (0xb4): Retrieves the script public key of the output at the current input's index + // - OpOutputAmount (0xb5): Retrieves the amount of the output at the current input's index + // + // These opcodes were added to support mutual transactions and auto-compounding addresses + // as discussed in KIP-9. When KIP-10 is disabled (pre-activation), these opcodes will return + // an InvalidOpcode error. When enabled, they provide access to transaction input/output data + // within scripts. for (file_name, kip10_enabled) in [("script_tests.json", false), ("script_tests-kip10.json", true)] { let file = File::open(Path::new(env!("CARGO_MANIFEST_DIR")).join("test-data").join(file_name)).expect("Could not find test file"); diff --git a/crypto/txscript/test-data/script_tests-kip10.json b/crypto/txscript/test-data/script_tests-kip10.json index 47686397c..278c5231d 100644 --- a/crypto/txscript/test-data/script_tests-kip10.json +++ b/crypto/txscript/test-data/script_tests-kip10.json @@ -1220,7 +1220,8 @@ "0", "IF 0xbd ELSE 1 ENDIF", "", - "OK" + "OK", + "opcodes above OP_OUTPUT_AMOUNT invalid if executed" ], [ "0", From 1a9c508abaf8665df349bd1a21b7b249cea580b5 Mon Sep 17 00:00:00 2001 From: max143672 Date: Sat, 2 Nov 2024 19:56:14 +0400 Subject: [PATCH 34/93] fix wring error messages --- crypto/txscript/src/opcodes/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crypto/txscript/src/opcodes/mod.rs b/crypto/txscript/src/opcodes/mod.rs index 4e9e43bbf..4ee367c12 100644 --- a/crypto/txscript/src/opcodes/mod.rs +++ b/crypto/txscript/src/opcodes/mod.rs @@ -928,7 +928,7 @@ opcode_list! { vm.dstack.push(v.unwrap_or_default()); Ok(()) }, - _ => Err(TxScriptError::InvalidSource("OpInputAmount only applies to transaction inputs".to_string())) + _ => Err(TxScriptError::InvalidSource("OpOutputSpk only applies to transaction inputs".to_string())) } } else { Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) @@ -940,7 +940,7 @@ opcode_list! { ScriptSource::TxInput{tx, id , ..} => { push_number(tx.outputs().get(id).map(|output| output.value).unwrap_or_default() as i64, vm) }, - _ => Err(TxScriptError::InvalidSource("OpInputAmount only applies to transaction inputs".to_string())) + _ => Err(TxScriptError::InvalidSource("OpOutputAmount only applies to transaction inputs".to_string())) } } else { Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) From 164d044f919ad798f1dfee47ad858c1dc82af104 Mon Sep 17 00:00:00 2001 From: max143672 Date: Wed, 6 Nov 2024 12:41:50 +0400 Subject: [PATCH 35/93] support introspection by index --- consensus/core/src/tx.rs | 14 ++++++++++ crypto/txscript/errors/src/lib.rs | 2 ++ crypto/txscript/src/lib.rs | 4 +++ crypto/txscript/src/opcodes/mod.rs | 42 ++++++++++++++++++------------ 4 files changed, 45 insertions(+), 17 deletions(-) diff --git a/consensus/core/src/tx.rs b/consensus/core/src/tx.rs index a4dd7dd45..9f02ade4b 100644 --- a/consensus/core/src/tx.rs +++ b/consensus/core/src/tx.rs @@ -293,6 +293,8 @@ pub trait VerifiableTransaction { fn id(&self) -> TransactionId { self.tx().id() } + + fn utxo(&self, index: usize) -> Option<&UtxoEntry>; } /// A custom iterator written only so that `populated_inputs` has a known return type and can de defined on the trait level @@ -342,6 +344,10 @@ impl<'a> VerifiableTransaction for PopulatedTransaction<'a> { fn populated_input(&self, index: usize) -> (&TransactionInput, &UtxoEntry) { (&self.tx.inputs[index], &self.entries[index]) } + + fn utxo(&self, index: usize) -> Option<&UtxoEntry> { + self.entries.get(index) + } } /// Represents a validated transaction with populated UTXO entry data and a calculated fee @@ -370,6 +376,10 @@ impl<'a> VerifiableTransaction for ValidatedTransaction<'a> { fn populated_input(&self, index: usize) -> (&TransactionInput, &UtxoEntry) { (&self.tx.inputs[index], &self.entries[index]) } + + fn utxo(&self, index: usize) -> Option<&UtxoEntry> { + self.entries.get(index) + } } impl AsRef for Transaction { @@ -507,6 +517,10 @@ impl> VerifiableTransaction for MutableTransactionVerifiab self.inner.entries[index].as_ref().expect("expected to be called only following full UTXO population"), ) } + + fn utxo(&self, index: usize) -> Option<&UtxoEntry> { + self.inner.entries.get(index).and_then(Option::as_ref) + } } /// Specialized impl for `T=Arc` diff --git a/crypto/txscript/errors/src/lib.rs b/crypto/txscript/errors/src/lib.rs index 4c077dae3..9cc23ee80 100644 --- a/crypto/txscript/errors/src/lib.rs +++ b/crypto/txscript/errors/src/lib.rs @@ -69,4 +69,6 @@ pub enum TxScriptError { InvalidStackOperation(usize, usize), #[error("script of size {0} exceeded maximum allowed size of {1}")] ScriptSize(usize, usize), + #[error("transaction output index {0} >= {1}")] + InvalidOutputIndex(usize, usize), } diff --git a/crypto/txscript/src/lib.rs b/crypto/txscript/src/lib.rs index df17d56bf..f9f74b33b 100644 --- a/crypto/txscript/src/lib.rs +++ b/crypto/txscript/src/lib.rs @@ -556,6 +556,10 @@ mod tests { fn populated_input(&self, _index: usize) -> (&TransactionInput, &UtxoEntry) { unimplemented!() } + + fn utxo(&self, _index: usize) -> Option<&UtxoEntry> { + unimplemented!() + } } fn run_test_script_cases(test_cases: Vec) { diff --git a/crypto/txscript/src/opcodes/mod.rs b/crypto/txscript/src/opcodes/mod.rs index 4ee367c12..16a3e77ad 100644 --- a/crypto/txscript/src/opcodes/mod.rs +++ b/crypto/txscript/src/opcodes/mod.rs @@ -887,13 +887,13 @@ opcode_list! { if vm.kip10_enabled { match vm.script_source { ScriptSource::TxInput{ - utxo_entry: kaspa_consensus_core::tx::UtxoEntry{ - script_public_key: spk, - .. - }, + tx, .. } => { - vm.dstack.push(spk.to_bytes()); + let [idx]: [i32; 1] = vm.dstack.pop_items()?; // todo check if idx <= u8::MAX ?? + let idx = idx as usize; + let utxo = tx.utxo(idx).ok_or_else(|| TxScriptError::InvalidIndex(idx, tx.inputs().len()))?; + vm.dstack.push(utxo.script_public_key.to_bytes()); Ok(()) }, _ => Err(TxScriptError::InvalidSource("OpInputSpk only applies to transaction inputs".to_string())) @@ -906,12 +906,14 @@ opcode_list! { if vm.kip10_enabled { match vm.script_source { ScriptSource::TxInput{ - utxo_entry: kaspa_consensus_core::tx::UtxoEntry{ - amount, - .. - }, + tx, .. - } => push_number(*amount as i64, vm), + } => { + let [idx]: [i32; 1] = vm.dstack.pop_items()?; // todo check if idx <= u8::MAX ?? + let idx = idx as usize; + let utxo = tx.utxo(idx).ok_or_else(|| TxScriptError::InvalidIndex(idx, tx.inputs().len()))?; + push_number(utxo.amount as i64, vm) + }, _ => Err(TxScriptError::InvalidSource("OpInputAmount only applies to transaction inputs".to_string())) } } else { @@ -921,11 +923,11 @@ opcode_list! { opcode OpOutputSpk<0xb4, 1>(self, vm) { if vm.kip10_enabled { match vm.script_source { - ScriptSource::TxInput{tx, id , ..} => { - let v = tx.outputs().get(id).map(|output| { - output.script_public_key.to_bytes() - }); - vm.dstack.push(v.unwrap_or_default()); + ScriptSource::TxInput{tx, ..} => { + let [idx]: [i32; 1] = vm.dstack.pop_items()?; // todo check if idx <= u8::MAX ?? + let idx = idx as usize; + let output = tx.outputs().get(idx).ok_or_else(|| TxScriptError::InvalidOutputIndex(idx, tx.outputs().len()))?; + vm.dstack.push(output.script_public_key.to_bytes()); Ok(()) }, _ => Err(TxScriptError::InvalidSource("OpOutputSpk only applies to transaction inputs".to_string())) @@ -937,8 +939,11 @@ opcode_list! { opcode OpOutputAmount<0xb5, 1>(self, vm) { if vm.kip10_enabled { match vm.script_source { - ScriptSource::TxInput{tx, id , ..} => { - push_number(tx.outputs().get(id).map(|output| output.value).unwrap_or_default() as i64, vm) + ScriptSource::TxInput{tx, ..} => { + let [idx]: [i32; 1] = vm.dstack.pop_items()?; // todo check if idx <= u8::MAX ?? + let idx = idx as usize; + let output = tx.outputs().get(idx).ok_or_else(|| TxScriptError::InvalidOutputIndex(idx, tx.outputs().len()))?; + push_number(output.value as i64, vm) }, _ => Err(TxScriptError::InvalidSource("OpOutputAmount only applies to transaction inputs".to_string())) } @@ -2905,6 +2910,9 @@ mod test { fn populated_input(&self, _index: usize) -> (&TransactionInput, &UtxoEntry) { unimplemented!() } + fn utxo(&self, _index: usize) -> Option<&UtxoEntry> { + unimplemented!() + } } fn make_mock_transaction(lock_time: u64) -> (VerifiableTransactionMock, TransactionInput, UtxoEntry) { From 91298d5b1d18335af268ff136982a668d4f6a946 Mon Sep 17 00:00:00 2001 From: max143672 Date: Wed, 6 Nov 2024 14:50:08 +0400 Subject: [PATCH 36/93] test input spk --- crypto/txscript/src/opcodes/mod.rs | 315 +++++++++++++++++------------ 1 file changed, 184 insertions(+), 131 deletions(-) diff --git a/crypto/txscript/src/opcodes/mod.rs b/crypto/txscript/src/opcodes/mod.rs index 16a3e77ad..bfd7e9fd6 100644 --- a/crypto/txscript/src/opcodes/mod.rs +++ b/crypto/txscript/src/opcodes/mod.rs @@ -890,9 +890,12 @@ opcode_list! { tx, .. } => { - let [idx]: [i32; 1] = vm.dstack.pop_items()?; // todo check if idx <= u8::MAX ?? + let [idx]: [i32; 1] = vm.dstack.pop_items()?; + if !(0..=u8::MAX as i32).contains(&idx) { + return Err(TxScriptError::InvalidIndex(idx as usize, tx.inputs().len())) + } let idx = idx as usize; - let utxo = tx.utxo(idx).ok_or_else(|| TxScriptError::InvalidIndex(idx, tx.inputs().len()))?; + let utxo = tx.utxo(idx).ok_or_else(|| TxScriptError::InvalidIndex(idx, tx.inputs().len()))?; vm.dstack.push(utxo.script_public_key.to_bytes()); Ok(()) }, @@ -910,6 +913,9 @@ opcode_list! { .. } => { let [idx]: [i32; 1] = vm.dstack.pop_items()?; // todo check if idx <= u8::MAX ?? + if !(0..=u8::MAX as i32).contains(&idx) { + return Err(TxScriptError::InvalidIndex(idx as usize, tx.inputs().len())) + } let idx = idx as usize; let utxo = tx.utxo(idx).ok_or_else(|| TxScriptError::InvalidIndex(idx, tx.inputs().len()))?; push_number(utxo.amount as i64, vm) @@ -925,6 +931,9 @@ opcode_list! { match vm.script_source { ScriptSource::TxInput{tx, ..} => { let [idx]: [i32; 1] = vm.dstack.pop_items()?; // todo check if idx <= u8::MAX ?? + if !(0..=u8::MAX as i32).contains(&idx) { + return Err(TxScriptError::InvalidIndex(idx as usize, tx.inputs().len())) + } let idx = idx as usize; let output = tx.outputs().get(idx).ok_or_else(|| TxScriptError::InvalidOutputIndex(idx, tx.outputs().len()))?; vm.dstack.push(output.script_public_key.to_bytes()); @@ -941,6 +950,9 @@ opcode_list! { match vm.script_source { ScriptSource::TxInput{tx, ..} => { let [idx]: [i32; 1] = vm.dstack.pop_items()?; // todo check if idx <= u8::MAX ?? + if !(0..=u8::MAX as i32).contains(&idx) { + return Err(TxScriptError::InvalidIndex(idx as usize, tx.inputs().len())) + } let idx = idx as usize; let output = tx.outputs().get(idx).ok_or_else(|| TxScriptError::InvalidOutputIndex(idx, tx.outputs().len()))?; push_number(output.value as i64, vm) @@ -1045,7 +1057,7 @@ pub fn to_small_int(opcod #[cfg(test)] mod test { use crate::caches::Cache; - use crate::data_stack::{OpcodeData, SizedEncodeInt, Stack}; + use crate::data_stack::Stack; use crate::opcodes::{OpCodeExecution, OpCodeImplementation}; use crate::{opcodes, pay_to_address_script, TxScriptEngine, TxScriptError, LOCK_TIME_THRESHOLD}; use kaspa_addresses::{Address, Prefix, Version}; @@ -2349,134 +2361,6 @@ mod test { ]); } - fn execute_kip_10_op_with_mock_engine( - input_spk: ScriptPublicKey, - output_spk: Option, - input_amount: u64, - output_amount: u64, - cb: impl FnOnce(TxScriptEngine), - ) { - let cache = Cache::new(10_000); - let reused_values = SigHashReusedValuesUnsync::new(); - let dummy_prev_out = TransactionOutpoint::new(kaspa_hashes::Hash::from_u64_word(1), 1); - let dummy_sig_script = vec![0u8; 65]; - let tx_input = TransactionInput::new(dummy_prev_out, dummy_sig_script, 10, 0); - - let tx_out = output_spk.map(|spk| TransactionOutput::new(output_amount, spk)); - let tx = VerifiableTransactionMock(Transaction::new( - TX_VERSION + 1, - vec![tx_input.clone()], - tx_out.map(|tx_out| vec![tx_out]).unwrap_or_default(), - 0, - SUBNETWORK_ID_NATIVE, - 0, - vec![], - )); - let utxo_entry = UtxoEntry::new(input_amount, input_spk.clone(), 0, false); - - let vm = TxScriptEngine::from_transaction_input(&tx, &tx_input, 0, &utxo_entry, &reused_values, &cache, true) - .expect("Shouldn't fail"); - cb(vm); - } - #[test] - fn test_op_input_spk() { - let input_pub_key = vec![1u8; 32]; - let input_addr = Address::new(Prefix::Testnet, Version::PubKey, &input_pub_key); - let input_script_public_key = pay_to_address_script(&input_addr); - execute_kip_10_op_with_mock_engine(input_script_public_key.clone(), None, 0, 0, |mut vm| { - let code = opcodes::OpInputSpk::empty().expect("Should accept empty"); - code.execute(&mut vm).unwrap(); - assert_eq!(vm.dstack, vec![input_script_public_key.to_bytes()]); - }); - } - - #[test] - fn test_op_output_spk() { - let input_pub_key = vec![1u8; 32]; - let input_addr = Address::new(Prefix::Testnet, Version::PubKey, &input_pub_key); - let input_script_public_key = pay_to_address_script(&input_addr); - let output_pub_key = vec![2u8; 32]; - let output_addr = Address::new(Prefix::Testnet, Version::PubKey, &output_pub_key); - let output_script_public_key = pay_to_address_script(&output_addr); - [None, Some(output_script_public_key.clone())].into_iter().for_each(|output_spk| { - execute_kip_10_op_with_mock_engine(input_script_public_key.clone(), output_spk.clone(), 0, 0, |mut vm| { - let code = opcodes::OpOutputSpk::empty().expect("Should accept empty"); - code.execute(&mut vm).unwrap(); - assert_eq!(vm.dstack, vec![output_spk.map(|output_spk| output_spk.to_bytes()).unwrap_or_default()]); - vm.dstack.clear(); - }); - }); - } - - #[test] - fn test_op_input_amount() { - let input_pub_key = vec![1u8; 32]; - let input_addr = Address::new(Prefix::Testnet, Version::PubKey, &input_pub_key); - let input_script_public_key = pay_to_address_script(&input_addr); - [0, 268_435_455].into_iter().for_each(|amount| { - execute_kip_10_op_with_mock_engine(input_script_public_key.clone(), None, amount, 0, |mut vm| { - let code = opcodes::OpInputAmount::empty().expect("Should accept empty"); - code.execute(&mut vm).unwrap(); - let actual: i64 = vm.dstack[0].deserialize().unwrap(); - assert_eq!(actual, amount as i64); - }); - }); - - const MAX_SOMPI: u64 = 29_000_000_000 * SOMPI_PER_KASPA; // works if serialized to SizedEncodeInt<8> - execute_kip_10_op_with_mock_engine(input_script_public_key.clone(), None, MAX_SOMPI, 0, |mut vm| { - let code = opcodes::OpInputAmount::empty().expect("Should accept empty"); - code.execute(&mut vm).unwrap(); - let actual: SizedEncodeInt<8> = vm.dstack[0].deserialize().unwrap(); - assert_eq!(actual.0, MAX_SOMPI as i64); - }); - } - - #[test] - fn test_op_output_amount() { - let input_pub_key = vec![1u8; 32]; - let input_addr = Address::new(Prefix::Testnet, Version::PubKey, &input_pub_key); - let input_script_public_key = pay_to_address_script(&input_addr); - let output_pub_key = vec![2u8; 32]; - let output_addr = Address::new(Prefix::Testnet, Version::PubKey, &output_pub_key); - let output_script_public_key = pay_to_address_script(&output_addr); - - execute_kip_10_op_with_mock_engine(input_script_public_key.clone(), None, 0, 0, |mut vm| { - let code = opcodes::OpOutputAmount::empty().expect("Should accept empty"); - code.execute(&mut vm).unwrap(); - let actual: i64 = vm.dstack[0].deserialize().unwrap(); - assert_eq!(actual, 0); - }); - - [0, 268_435_455].into_iter().for_each(|amount| { - execute_kip_10_op_with_mock_engine( - input_script_public_key.clone(), - Some(output_script_public_key.clone()), - 0, - amount, - |mut vm| { - let code = opcodes::OpOutputAmount::empty().expect("Should accept empty"); - code.execute(&mut vm).unwrap(); - let actual: i64 = vm.dstack[0].deserialize().unwrap(); - assert_eq!(actual, amount as i64); - }, - ); - }); - - const MAX_SOMPI: u64 = 29_000_000_000 * SOMPI_PER_KASPA; // works if serialized to SizedEncodeInt<8> - execute_kip_10_op_with_mock_engine( - input_script_public_key.clone(), - Some(output_script_public_key.clone()), - 0, - MAX_SOMPI, - |mut vm| { - let code = opcodes::OpOutputAmount::empty().expect("Should accept empty"); - code.execute(&mut vm).unwrap(); - let actual: SizedEncodeInt<8> = vm.dstack[0].deserialize().unwrap(); - assert_eq!(actual.0, MAX_SOMPI as i64); - }, - ); - } - #[test] fn test_oppick() { run_success_test_cases(vec![ @@ -3102,4 +2986,173 @@ mod test { TestCase { code: opcodes::OpIfDup::empty().expect("Should accept empty"), init: vec![vec![]], dstack: vec![vec![]] }, ]) } + + mod kip10 { + use super::*; + use crate::opcodes::push_number; + + struct Kip10Mock { + spk: ScriptPublicKey, + amount: u64, + } + + fn kip_10_tx_mock(inputs: Vec, outputs: Vec) -> (Transaction, Vec) { + let dummy_prev_out = TransactionOutpoint::new(kaspa_hashes::Hash::from_u64_word(1), 1); + let dummy_sig_script = vec![0u8; 65]; + let (utxos, tx_inputs) = inputs + .into_iter() + .map(|Kip10Mock { spk, amount }| { + (UtxoEntry::new(amount, spk, 0, false), TransactionInput::new(dummy_prev_out, dummy_sig_script.clone(), 10, 0)) + }) + .unzip(); + + let tx_out = outputs.into_iter().map(|Kip10Mock { spk, amount }| TransactionOutput::new(amount, spk)); + let tx = Transaction::new(TX_VERSION + 1, tx_inputs, tx_out.collect(), 0, SUBNETWORK_ID_NATIVE, 0, vec![]); + (tx, utxos) + } + #[test] + fn test_op_input_spk() { + let spk = |pub_key: Vec<_>| { + let addr = Address::new(Prefix::Testnet, Version::PubKey, &pub_key); + pay_to_address_script(&addr) + }; + let input_spk1 = spk(vec![1u8; 32]); + let input_spk2 = spk(vec![2u8; 32]); + let (tx, utxo_entries) = kip_10_tx_mock( + vec![Kip10Mock { spk: input_spk1.clone(), amount: 0 }, Kip10Mock { spk: input_spk2.clone(), amount: 0 }], + vec![], + ); + let tx = PopulatedTransaction::new(&tx, utxo_entries); + let sig_cache = Cache::new(10_000); + let reused_values = SigHashReusedValuesUnsync::new(); + [true, false].into_iter().for_each(|kip10_enabled| { + (0..tx.inputs().len()).for_each(|current_idx| { + let mut vm = TxScriptEngine::from_transaction_input( + &tx, + &tx.inputs()[current_idx], + current_idx, + tx.utxo(current_idx).unwrap(), + &reused_values, + &sig_cache, + kip10_enabled, + ) + .unwrap(); + let code = opcodes::OpInputSpk::empty().expect("Should accept empty"); + + if !kip10_enabled { + assert!(matches!(code.execute(&mut vm), Err(TxScriptError::InvalidOpcode(_)))); + } else { + // check first input + { + push_number(0, &mut vm).unwrap(); + code.execute(&mut vm).unwrap(); + assert_eq!(vm.dstack, vec![input_spk1.to_bytes()]); + vm.dstack.clear(); + } + // check second input + { + push_number(1, &mut vm).unwrap(); + code.execute(&mut vm).unwrap(); + assert_eq!(vm.dstack, vec![input_spk2.to_bytes()]); + vm.dstack.clear(); + } + // check empty stack + { + assert!(matches!(code.execute(&mut vm), Err(TxScriptError::InvalidStackOperation(1, 0)))); + } + // check negative input + { + push_number(-1, &mut vm).unwrap(); + assert!(matches!(code.execute(&mut vm), Err(TxScriptError::InvalidIndex(_, 2)))); + } + // check big number input + { + push_number(u8::MAX as i64 + 1, &mut vm).unwrap(); + assert!(matches!(code.execute(&mut vm), Err(TxScriptError::InvalidIndex(_, 2)))); + } + // check third input + { + push_number(2, &mut vm).unwrap(); + assert!(matches!(code.execute(&mut vm), Err(TxScriptError::InvalidIndex(2, 2)))); + } + } + }) + }); + } + + // #[test] + // fn test_op_output_spk() { + // let input_pub_key = vec![1u8; 32]; + // let input_addr = Address::new(Prefix::Testnet, Version::PubKey, &input_pub_key); + // let input_script_public_key = pay_to_address_script(&input_addr); + // let output_pub_key = vec![2u8; 32]; + // let output_addr = Address::new(Prefix::Testnet, Version::PubKey, &output_pub_key); + // let output_script_public_key = pay_to_address_script(&output_addr); + // [None, Some(output_script_public_key.clone())].into_iter().for_each(|output_spk| { + // kip_10_tx_mock(input_script_public_key.clone(), output_spk.clone(), 0, 0, |mut vm| { + // let code = opcodes::OpOutputSpk::empty().expect("Should accept empty"); + // code.execute(&mut vm).unwrap(); + // assert_eq!(vm.dstack, vec![output_spk.map(|output_spk| output_spk.to_bytes()).unwrap_or_default()]); + // vm.dstack.clear(); + // }); + // }); + // } + // + // #[test] + // fn test_op_input_amount() { + // let input_pub_key = vec![1u8; 32]; + // let input_addr = Address::new(Prefix::Testnet, Version::PubKey, &input_pub_key); + // let input_script_public_key = pay_to_address_script(&input_addr); + // [0, 268_435_455].into_iter().for_each(|amount| { + // kip_10_tx_mock(input_script_public_key.clone(), None, amount, 0, |mut vm| { + // let code = opcodes::OpInputAmount::empty().expect("Should accept empty"); + // code.execute(&mut vm).unwrap(); + // let actual: i64 = vm.dstack[0].deserialize().unwrap(); + // assert_eq!(actual, amount as i64); + // }); + // }); + // + // const MAX_SOMPI: u64 = 29_000_000_000 * SOMPI_PER_KASPA; // works if serialized to SizedEncodeInt<8> + // kip_10_tx_mock(input_script_public_key.clone(), None, MAX_SOMPI, 0, |mut vm| { + // let code = opcodes::OpInputAmount::empty().expect("Should accept empty"); + // code.execute(&mut vm).unwrap(); + // let actual: SizedEncodeInt<8> = vm.dstack[0].deserialize().unwrap(); + // assert_eq!(actual.0, MAX_SOMPI as i64); + // }); + // } + // + // #[test] + // fn test_op_output_amount() { + // let input_pub_key = vec![1u8; 32]; + // let input_addr = Address::new(Prefix::Testnet, Version::PubKey, &input_pub_key); + // let input_script_public_key = pay_to_address_script(&input_addr); + // let output_pub_key = vec![2u8; 32]; + // let output_addr = Address::new(Prefix::Testnet, Version::PubKey, &output_pub_key); + // let output_script_public_key = pay_to_address_script(&output_addr); + // + // kip_10_tx_mock(input_script_public_key.clone(), None, 0, 0, |mut vm| { + // let code = opcodes::OpOutputAmount::empty().expect("Should accept empty"); + // code.execute(&mut vm).unwrap(); + // let actual: i64 = vm.dstack[0].deserialize().unwrap(); + // assert_eq!(actual, 0); + // }); + // + // [0, 268_435_455].into_iter().for_each(|amount| { + // kip_10_tx_mock(input_script_public_key.clone(), Some(output_script_public_key.clone()), 0, amount, |mut vm| { + // let code = opcodes::OpOutputAmount::empty().expect("Should accept empty"); + // code.execute(&mut vm).unwrap(); + // let actual: i64 = vm.dstack[0].deserialize().unwrap(); + // assert_eq!(actual, amount as i64); + // }); + // }); + // + // const MAX_SOMPI: u64 = 29_000_000_000 * SOMPI_PER_KASPA; // works if serialized to SizedEncodeInt<8> + // kip_10_tx_mock(input_script_public_key.clone(), Some(output_script_public_key.clone()), 0, MAX_SOMPI, |mut vm| { + // let code = opcodes::OpOutputAmount::empty().expect("Should accept empty"); + // code.execute(&mut vm).unwrap(); + // let actual: SizedEncodeInt<8> = vm.dstack[0].deserialize().unwrap(); + // assert_eq!(actual.0, MAX_SOMPI as i64); + // }); + // } + } } From a117c0e1718d51384f2e2103f90c3f277d3c8305 Mon Sep 17 00:00:00 2001 From: max143672 Date: Wed, 6 Nov 2024 20:47:35 +0400 Subject: [PATCH 37/93] test output spk --- crypto/txscript/src/opcodes/mod.rs | 62 ++++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 12 deletions(-) diff --git a/crypto/txscript/src/opcodes/mod.rs b/crypto/txscript/src/opcodes/mod.rs index bfd7e9fd6..4cee60a2f 100644 --- a/crypto/txscript/src/opcodes/mod.rs +++ b/crypto/txscript/src/opcodes/mod.rs @@ -912,7 +912,7 @@ opcode_list! { tx, .. } => { - let [idx]: [i32; 1] = vm.dstack.pop_items()?; // todo check if idx <= u8::MAX ?? + let [idx]: [i32; 1] = vm.dstack.pop_items()?; if !(0..=u8::MAX as i32).contains(&idx) { return Err(TxScriptError::InvalidIndex(idx as usize, tx.inputs().len())) } @@ -930,7 +930,7 @@ opcode_list! { if vm.kip10_enabled { match vm.script_source { ScriptSource::TxInput{tx, ..} => { - let [idx]: [i32; 1] = vm.dstack.pop_items()?; // todo check if idx <= u8::MAX ?? + let [idx]: [i32; 1] = vm.dstack.pop_items()?; if !(0..=u8::MAX as i32).contains(&idx) { return Err(TxScriptError::InvalidIndex(idx as usize, tx.inputs().len())) } @@ -949,7 +949,7 @@ opcode_list! { if vm.kip10_enabled { match vm.script_source { ScriptSource::TxInput{tx, ..} => { - let [idx]: [i32; 1] = vm.dstack.pop_items()?; // todo check if idx <= u8::MAX ?? + let [idx]: [i32; 1] = vm.dstack.pop_items()?; if !(0..=u8::MAX as i32).contains(&idx) { return Err(TxScriptError::InvalidIndex(idx as usize, tx.inputs().len())) } @@ -3018,9 +3018,11 @@ mod test { }; let input_spk1 = spk(vec![1u8; 32]); let input_spk2 = spk(vec![2u8; 32]); + let output_spk1 = spk(vec![3u8; 32]); + let output_spk2 = spk(vec![4u8; 32]); let (tx, utxo_entries) = kip_10_tx_mock( vec![Kip10Mock { spk: input_spk1.clone(), amount: 0 }, Kip10Mock { spk: input_spk2.clone(), amount: 0 }], - vec![], + vec![Kip10Mock { spk: output_spk1.clone(), amount: 0 }, Kip10Mock { spk: output_spk2.clone(), amount: 0 }], ); let tx = PopulatedTransaction::new(&tx, utxo_entries); let sig_cache = Cache::new(10_000); @@ -3037,43 +3039,79 @@ mod test { kip10_enabled, ) .unwrap(); - let code = opcodes::OpInputSpk::empty().expect("Should accept empty"); + let op_input_spk = opcodes::OpInputSpk::empty().expect("Should accept empty"); + let op_output_spk = opcodes::OpOutputSpk::empty().expect("Should accept empty"); if !kip10_enabled { - assert!(matches!(code.execute(&mut vm), Err(TxScriptError::InvalidOpcode(_)))); + assert!(matches!(op_input_spk.execute(&mut vm), Err(TxScriptError::InvalidOpcode(_)))); + assert!(matches!(op_output_spk.execute(&mut vm), Err(TxScriptError::InvalidOpcode(_)))); } else { // check first input { push_number(0, &mut vm).unwrap(); - code.execute(&mut vm).unwrap(); + op_input_spk.execute(&mut vm).unwrap(); assert_eq!(vm.dstack, vec![input_spk1.to_bytes()]); vm.dstack.clear(); } // check second input { push_number(1, &mut vm).unwrap(); - code.execute(&mut vm).unwrap(); + op_input_spk.execute(&mut vm).unwrap(); assert_eq!(vm.dstack, vec![input_spk2.to_bytes()]); vm.dstack.clear(); } // check empty stack { - assert!(matches!(code.execute(&mut vm), Err(TxScriptError::InvalidStackOperation(1, 0)))); + assert!(matches!(op_input_spk.execute(&mut vm), Err(TxScriptError::InvalidStackOperation(1, 0)))); } // check negative input { push_number(-1, &mut vm).unwrap(); - assert!(matches!(code.execute(&mut vm), Err(TxScriptError::InvalidIndex(_, 2)))); + assert!(matches!(op_input_spk.execute(&mut vm), Err(TxScriptError::InvalidIndex(_, 2)))); } // check big number input { push_number(u8::MAX as i64 + 1, &mut vm).unwrap(); - assert!(matches!(code.execute(&mut vm), Err(TxScriptError::InvalidIndex(_, 2)))); + assert!(matches!(op_input_spk.execute(&mut vm), Err(TxScriptError::InvalidIndex(_, 2)))); } // check third input { push_number(2, &mut vm).unwrap(); - assert!(matches!(code.execute(&mut vm), Err(TxScriptError::InvalidIndex(2, 2)))); + assert!(matches!(op_input_spk.execute(&mut vm), Err(TxScriptError::InvalidIndex(2, 2)))); + } + + // check first output + { + push_number(0, &mut vm).unwrap(); + op_output_spk.execute(&mut vm).unwrap(); + assert_eq!(vm.dstack, vec![output_spk1.to_bytes()]); + vm.dstack.clear(); + } + // check second output + { + push_number(1, &mut vm).unwrap(); + op_output_spk.execute(&mut vm).unwrap(); + assert_eq!(vm.dstack, vec![output_spk2.to_bytes()]); + vm.dstack.clear(); + } + // check empty stack + { + assert!(matches!(op_output_spk.execute(&mut vm), Err(TxScriptError::InvalidStackOperation(1, 0)))); + } + // check negative output + { + push_number(-1, &mut vm).unwrap(); + assert!(matches!(op_output_spk.execute(&mut vm), Err(TxScriptError::InvalidIndex(_, 2)))); + } + // check big number output + { + push_number(u8::MAX as i64 + 1, &mut vm).unwrap(); + assert!(matches!(op_output_spk.execute(&mut vm), Err(TxScriptError::InvalidIndex(_, 2)))); + } + // check third output + { + push_number(2, &mut vm).unwrap(); + assert!(matches!(op_output_spk.execute(&mut vm), Err(TxScriptError::InvalidOutputIndex(2, 2)))); } } }) From 3b2849f9a8dfc09d4afde7104d3ccc8e172d2d23 Mon Sep 17 00:00:00 2001 From: max143672 Date: Wed, 6 Nov 2024 22:39:30 +0400 Subject: [PATCH 38/93] tests refactor --- crypto/txscript/src/opcodes/mod.rs | 267 ++++++++++++++++++----------- 1 file changed, 170 insertions(+), 97 deletions(-) diff --git a/crypto/txscript/src/opcodes/mod.rs b/crypto/txscript/src/opcodes/mod.rs index 4cee60a2f..82071370a 100644 --- a/crypto/txscript/src/opcodes/mod.rs +++ b/crypto/txscript/src/opcodes/mod.rs @@ -2991,11 +2991,37 @@ mod test { use super::*; use crate::opcodes::push_number; + #[derive(Clone, Debug)] struct Kip10Mock { spk: ScriptPublicKey, amount: u64, } + #[derive(Debug)] + struct TestCase { + name: &'static str, + kip10_enabled: bool, + expected_results: Vec, + } + + #[derive(Debug)] + enum ExpectedResult { + Success { operation: Operation, input: i64, expected_spk: Vec }, + Error { operation: Operation, input: Option, expected_error: TxScriptError }, + } + + #[derive(Debug)] + enum Operation { + InputSpk, + OutputSpk, + } + + fn create_mock_spk(value: u8) -> ScriptPublicKey { + let pub_key = vec![value; 32]; + let addr = Address::new(Prefix::Testnet, Version::PubKey, &pub_key); + pay_to_address_script(&addr) + } + fn kip_10_tx_mock(inputs: Vec, outputs: Vec) -> (Transaction, Vec) { let dummy_prev_out = TransactionOutpoint::new(kaspa_hashes::Hash::from_u64_word(1), 1); let dummy_sig_script = vec![0u8; 65]; @@ -3007,115 +3033,162 @@ mod test { .unzip(); let tx_out = outputs.into_iter().map(|Kip10Mock { spk, amount }| TransactionOutput::new(amount, spk)); + let tx = Transaction::new(TX_VERSION + 1, tx_inputs, tx_out.collect(), 0, SUBNETWORK_ID_NATIVE, 0, vec![]); (tx, utxos) } - #[test] - fn test_op_input_spk() { - let spk = |pub_key: Vec<_>| { - let addr = Address::new(Prefix::Testnet, Version::PubKey, &pub_key); - pay_to_address_script(&addr) - }; - let input_spk1 = spk(vec![1u8; 32]); - let input_spk2 = spk(vec![2u8; 32]); - let output_spk1 = spk(vec![3u8; 32]); - let output_spk2 = spk(vec![4u8; 32]); - let (tx, utxo_entries) = kip_10_tx_mock( - vec![Kip10Mock { spk: input_spk1.clone(), amount: 0 }, Kip10Mock { spk: input_spk2.clone(), amount: 0 }], - vec![Kip10Mock { spk: output_spk1.clone(), amount: 0 }, Kip10Mock { spk: output_spk2.clone(), amount: 0 }], - ); + + fn execute_test_case(test_case: &TestCase) { + let input_spk1 = create_mock_spk(1); + let input_spk2 = create_mock_spk(2); + let output_spk1 = create_mock_spk(3); + let output_spk2 = create_mock_spk(4); + + let inputs = vec![Kip10Mock { spk: input_spk1.clone(), amount: 0 }, Kip10Mock { spk: input_spk2.clone(), amount: 0 }]; + let outputs = vec![Kip10Mock { spk: output_spk1.clone(), amount: 0 }, Kip10Mock { spk: output_spk2.clone(), amount: 0 }]; + + let (tx, utxo_entries) = kip_10_tx_mock(inputs, outputs); let tx = PopulatedTransaction::new(&tx, utxo_entries); let sig_cache = Cache::new(10_000); let reused_values = SigHashReusedValuesUnsync::new(); - [true, false].into_iter().for_each(|kip10_enabled| { - (0..tx.inputs().len()).for_each(|current_idx| { - let mut vm = TxScriptEngine::from_transaction_input( - &tx, - &tx.inputs()[current_idx], - current_idx, - tx.utxo(current_idx).unwrap(), - &reused_values, - &sig_cache, - kip10_enabled, - ) - .unwrap(); - let op_input_spk = opcodes::OpInputSpk::empty().expect("Should accept empty"); - let op_output_spk = opcodes::OpOutputSpk::empty().expect("Should accept empty"); - - if !kip10_enabled { - assert!(matches!(op_input_spk.execute(&mut vm), Err(TxScriptError::InvalidOpcode(_)))); - assert!(matches!(op_output_spk.execute(&mut vm), Err(TxScriptError::InvalidOpcode(_)))); - } else { - // check first input - { - push_number(0, &mut vm).unwrap(); - op_input_spk.execute(&mut vm).unwrap(); - assert_eq!(vm.dstack, vec![input_spk1.to_bytes()]); - vm.dstack.clear(); - } - // check second input - { - push_number(1, &mut vm).unwrap(); - op_input_spk.execute(&mut vm).unwrap(); - assert_eq!(vm.dstack, vec![input_spk2.to_bytes()]); - vm.dstack.clear(); - } - // check empty stack - { - assert!(matches!(op_input_spk.execute(&mut vm), Err(TxScriptError::InvalidStackOperation(1, 0)))); - } - // check negative input - { - push_number(-1, &mut vm).unwrap(); - assert!(matches!(op_input_spk.execute(&mut vm), Err(TxScriptError::InvalidIndex(_, 2)))); - } - // check big number input - { - push_number(u8::MAX as i64 + 1, &mut vm).unwrap(); - assert!(matches!(op_input_spk.execute(&mut vm), Err(TxScriptError::InvalidIndex(_, 2)))); - } - // check third input - { - push_number(2, &mut vm).unwrap(); - assert!(matches!(op_input_spk.execute(&mut vm), Err(TxScriptError::InvalidIndex(2, 2)))); - } - // check first output - { - push_number(0, &mut vm).unwrap(); - op_output_spk.execute(&mut vm).unwrap(); - assert_eq!(vm.dstack, vec![output_spk1.to_bytes()]); + for current_idx in 0..tx.inputs().len() { + let mut vm = TxScriptEngine::from_transaction_input( + &tx, + &tx.inputs()[current_idx], + current_idx, + tx.utxo(current_idx).unwrap(), + &reused_values, + &sig_cache, + test_case.kip10_enabled, + ) + .unwrap(); + + let op_input_spk = opcodes::OpInputSpk::empty().expect("Should accept empty"); + let op_output_spk = opcodes::OpOutputSpk::empty().expect("Should accept empty"); + + for expected_result in &test_case.expected_results { + match expected_result { + ExpectedResult::Success { operation, input, expected_spk } => { + push_number(*input, &mut vm).unwrap(); + match operation { + Operation::InputSpk => { + op_input_spk.execute(&mut vm).unwrap(); + assert_eq!(vm.dstack, vec![expected_spk.clone()]); + } + Operation::OutputSpk => { + op_output_spk.execute(&mut vm).unwrap(); + assert_eq!(vm.dstack, vec![expected_spk.clone()]); + } + } vm.dstack.clear(); } - // check second output - { - push_number(1, &mut vm).unwrap(); - op_output_spk.execute(&mut vm).unwrap(); - assert_eq!(vm.dstack, vec![output_spk2.to_bytes()]); + ExpectedResult::Error { operation, input, expected_error } => { + if let Some(input_value) = input { + push_number(*input_value, &mut vm).unwrap(); + } + let result = match operation { + Operation::InputSpk => op_input_spk.execute(&mut vm), + Operation::OutputSpk => op_output_spk.execute(&mut vm), + }; + assert!( + matches!(result, Err(ref e) if std::mem::discriminant(e) == std::mem::discriminant(expected_error)) + ); vm.dstack.clear(); } - // check empty stack - { - assert!(matches!(op_output_spk.execute(&mut vm), Err(TxScriptError::InvalidStackOperation(1, 0)))); - } - // check negative output - { - push_number(-1, &mut vm).unwrap(); - assert!(matches!(op_output_spk.execute(&mut vm), Err(TxScriptError::InvalidIndex(_, 2)))); - } - // check big number output - { - push_number(u8::MAX as i64 + 1, &mut vm).unwrap(); - assert!(matches!(op_output_spk.execute(&mut vm), Err(TxScriptError::InvalidIndex(_, 2)))); - } - // check third output - { - push_number(2, &mut vm).unwrap(); - assert!(matches!(op_output_spk.execute(&mut vm), Err(TxScriptError::InvalidOutputIndex(2, 2)))); - } } - }) - }); + } + } + } + + #[test] + fn test_op_spk() { + let test_cases = vec![ + TestCase { + name: "KIP-10 disabled", + kip10_enabled: false, + expected_results: vec![ + ExpectedResult::Error { + operation: Operation::InputSpk, + input: Some(0), + expected_error: TxScriptError::InvalidOpcode("Invalid opcode".to_string()), + }, + ExpectedResult::Error { + operation: Operation::OutputSpk, + input: Some(0), + expected_error: TxScriptError::InvalidOpcode("Invalid opcode".to_string()), + }, + ], + }, + TestCase { + name: "Valid input indices", + kip10_enabled: true, + expected_results: vec![ + ExpectedResult::Success { + operation: Operation::InputSpk, + input: 0, + expected_spk: create_mock_spk(1).to_bytes(), + }, + ExpectedResult::Success { + operation: Operation::InputSpk, + input: 1, + expected_spk: create_mock_spk(2).to_bytes(), + }, + ], + }, + TestCase { + name: "Valid output indices", + kip10_enabled: true, + expected_results: vec![ + ExpectedResult::Success { + operation: Operation::OutputSpk, + input: 0, + expected_spk: create_mock_spk(3).to_bytes(), + }, + ExpectedResult::Success { + operation: Operation::OutputSpk, + input: 1, + expected_spk: create_mock_spk(4).to_bytes(), + }, + ], + }, + TestCase { + name: "Error cases", + kip10_enabled: true, + expected_results: vec![ + ExpectedResult::Error { + operation: Operation::InputSpk, + input: None, // empty stack case + expected_error: TxScriptError::InvalidStackOperation(1, 0), + }, + ExpectedResult::Error { + operation: Operation::InputSpk, + input: Some(-1), + expected_error: TxScriptError::InvalidIndex(u8::MAX as usize + 1, 2), + }, + ExpectedResult::Error { + operation: Operation::InputSpk, + input: Some(u8::MAX as i64 + 1), + expected_error: TxScriptError::InvalidIndex(u8::MAX as usize + 1, 2), + }, + ExpectedResult::Error { + operation: Operation::InputSpk, + input: Some(2), + expected_error: TxScriptError::InvalidIndex(2, 2), + }, + ExpectedResult::Error { + operation: Operation::OutputSpk, + input: Some(2), + expected_error: TxScriptError::InvalidOutputIndex(2, 2), + }, + ], + }, + ]; + + for test_case in test_cases { + println!("Running test case: {}", test_case.name); + execute_test_case(&test_case); + } } // #[test] From d1cb457bd4f7f24e1d7b92af39abf4cf54574a38 Mon Sep 17 00:00:00 2001 From: max143672 Date: Thu, 7 Nov 2024 10:27:12 +0400 Subject: [PATCH 39/93] support 8-byte arithmetics --- crypto/txscript/src/data_stack.rs | 110 ++++++-------------------- crypto/txscript/src/opcodes/mod.rs | 18 +++-- crypto/txscript/src/script_builder.rs | 10 +++ 3 files changed, 48 insertions(+), 90 deletions(-) diff --git a/crypto/txscript/src/data_stack.rs b/crypto/txscript/src/data_stack.rs index 19053ef87..aa201522d 100644 --- a/crypto/txscript/src/data_stack.rs +++ b/crypto/txscript/src/data_stack.rs @@ -2,7 +2,7 @@ use crate::TxScriptError; use core::fmt::Debug; use core::iter; -const DEFAULT_SCRIPT_NUM_LEN: usize = 4; +const DEFAULT_SCRIPT_NUM_LEN: usize = 8; #[derive(PartialEq, Eq, Debug, Default)] pub(crate) struct SizedEncodeInt(pub(crate) i64); @@ -92,7 +92,7 @@ impl OpcodeData for Vec { #[inline] fn serialize(from: &i64) -> Self { let sign = from.signum(); - let mut positive = from.abs(); + let mut positive = from.unsigned_abs(); let mut last_saturated = false; let mut number_vec: Vec = iter::from_fn(move || { if positive == 0 { @@ -321,8 +321,6 @@ mod tests { TestCase { num: -8388608, serialized: hex::decode("00008080").expect("failed parsing hex") }, TestCase { num: 2147483647, serialized: hex::decode("ffffff7f").expect("failed parsing hex") }, TestCase { num: -2147483647, serialized: hex::decode("ffffffff").expect("failed parsing hex") }, - // Values that are out of range for data that is interpreted as - // numbers, but are allowed as the result of numeric operations. TestCase { num: 2147483648, serialized: hex::decode("0000008000").expect("failed parsing hex") }, TestCase { num: -2147483648, serialized: hex::decode("0000008080").expect("failed parsing hex") }, TestCase { num: 2415919104, serialized: hex::decode("0000009000").expect("failed parsing hex") }, @@ -337,6 +335,9 @@ mod tests { TestCase { num: -72057594037927935, serialized: hex::decode("ffffffffffffff80").expect("failed parsing hex") }, TestCase { num: 9223372036854775807, serialized: hex::decode("ffffffffffffff7f").expect("failed parsing hex") }, TestCase { num: -9223372036854775807, serialized: hex::decode("ffffffffffffffff").expect("failed parsing hex") }, + // Values that are out of range for data that is interpreted as + // numbers, but are allowed as the result of numeric operations. + TestCase { num: -9223372036854775808, serialized: hex::decode("000000000000008080").expect("failed parsing hex") }, ]; for test in tests { @@ -385,98 +386,39 @@ mod tests { TestCase:: { serialized: hex::decode("00008080").expect("failed parsing hex"), result: Ok(-8388608) }, TestCase:: { serialized: hex::decode("ffffff7f").expect("failed parsing hex"), result: Ok(2147483647) }, TestCase:: { serialized: hex::decode("ffffffff").expect("failed parsing hex"), result: Ok(-2147483647) }, - /* - TestCase::{serialized: hex::decode("ffffffffffffff7f").expect("failed parsing hex"), num_len: 8, result: Ok(9223372036854775807)}, - TestCase::{serialized: hex::decode("ffffffffffffffff").expect("failed parsing hex"), num_len: 8, result: Ok(-9223372036854775807)},*/ - // Minimally encoded values that are out of range for data that - // is interpreted as script numbers with the minimal encoding - // flag set. Should error and return 0. - TestCase:: { - serialized: hex::decode("0000008000").expect("failed parsing hex"), - result: Err(TxScriptError::NumberTooBig( - "numeric value encoded as [0, 0, 0, 80, 0] is 5 bytes which exceeds the max allowed of 4".to_string(), - )), - }, - TestCase:: { - serialized: hex::decode("0000008080").expect("failed parsing hex"), - result: Err(TxScriptError::NumberTooBig( - "numeric value encoded as [0, 0, 0, 80, 80] is 5 bytes which exceeds the max allowed of 4".to_string(), - )), - }, - TestCase:: { - serialized: hex::decode("0000009000").expect("failed parsing hex"), - result: Err(TxScriptError::NumberTooBig( - "numeric value encoded as [0, 0, 0, 90, 0] is 5 bytes which exceeds the max allowed of 4".to_string(), - )), - }, - TestCase:: { - serialized: hex::decode("0000009080").expect("failed parsing hex"), - result: Err(TxScriptError::NumberTooBig( - "numeric value encoded as [0, 0, 0, 90, 80] is 5 bytes which exceeds the max allowed of 4".to_string(), - )), - }, - TestCase:: { - serialized: hex::decode("ffffffff00").expect("failed parsing hex"), - result: Err(TxScriptError::NumberTooBig( - "numeric value encoded as [ff, ff, ff, ff, 0] is 5 bytes which exceeds the max allowed of 4".to_string(), - )), - }, - TestCase:: { - serialized: hex::decode("ffffffff80").expect("failed parsing hex"), - result: Err(TxScriptError::NumberTooBig( - "numeric value encoded as [ff, ff, ff, ff, 80] is 5 bytes which exceeds the max allowed of 4".to_string(), - )), - }, - TestCase:: { - serialized: hex::decode("0000000001").expect("failed parsing hex"), - result: Err(TxScriptError::NumberTooBig( - "numeric value encoded as [0, 0, 0, 0, 1] is 5 bytes which exceeds the max allowed of 4".to_string(), - )), - }, - TestCase:: { - serialized: hex::decode("0000000081").expect("failed parsing hex"), - result: Err(TxScriptError::NumberTooBig( - "numeric value encoded as [0, 0, 0, 0, 81] is 5 bytes which exceeds the max allowed of 4".to_string(), - )), - }, - TestCase:: { - serialized: hex::decode("ffffffffffff00").expect("failed parsing hex"), - result: Err(TxScriptError::NumberTooBig( - "numeric value encoded as [ff, ff, ff, ff, ff, ff, 0] is 7 bytes which exceeds the max allowed of 4".to_string(), - )), - }, - TestCase:: { - serialized: hex::decode("ffffffffffff80").expect("failed parsing hex"), - result: Err(TxScriptError::NumberTooBig( - "numeric value encoded as [ff, ff, ff, ff, ff, ff, 80] is 7 bytes which exceeds the max allowed of 4".to_string(), - )), - }, + TestCase:: { serialized: hex::decode("0000008000").expect("failed parsing hex"), result: Ok(2147483648) }, + TestCase:: { serialized: hex::decode("0000008080").expect("failed parsing hex"), result: Ok(-2147483648) }, + TestCase:: { serialized: hex::decode("0000009000").expect("failed parsing hex"), result: Ok(2415919104) }, + TestCase:: { serialized: hex::decode("0000009080").expect("failed parsing hex"), result: Ok(-2415919104) }, + TestCase:: { serialized: hex::decode("ffffffff00").expect("failed parsing hex"), result: Ok(4294967295) }, + TestCase:: { serialized: hex::decode("ffffffff80").expect("failed parsing hex"), result: Ok(-4294967295) }, + TestCase:: { serialized: hex::decode("0000000001").expect("failed parsing hex"), result: Ok(4294967296) }, + TestCase:: { serialized: hex::decode("0000000081").expect("failed parsing hex"), result: Ok(-4294967296) }, + TestCase:: { serialized: hex::decode("ffffffffffff00").expect("failed parsing hex"), result: Ok(281474976710655) }, + TestCase:: { serialized: hex::decode("ffffffffffff80").expect("failed parsing hex"), result: Ok(-281474976710655) }, TestCase:: { serialized: hex::decode("ffffffffffffff00").expect("failed parsing hex"), - result: Err(TxScriptError::NumberTooBig( - "numeric value encoded as [ff, ff, ff, ff, ff, ff, ff, 0] is 8 bytes which exceeds the max allowed of 4" - .to_string(), - )), + result: Ok(72057594037927935), }, TestCase:: { serialized: hex::decode("ffffffffffffff80").expect("failed parsing hex"), - result: Err(TxScriptError::NumberTooBig( - "numeric value encoded as [ff, ff, ff, ff, ff, ff, ff, 80] is 8 bytes which exceeds the max allowed of 4" - .to_string(), - )), + result: Ok(-72057594037927935), }, TestCase:: { serialized: hex::decode("ffffffffffffff7f").expect("failed parsing hex"), - result: Err(TxScriptError::NumberTooBig( - "numeric value encoded as [ff, ff, ff, ff, ff, ff, ff, 7f] is 8 bytes which exceeds the max allowed of 4" - .to_string(), - )), + result: Ok(9223372036854775807), }, TestCase:: { serialized: hex::decode("ffffffffffffffff").expect("failed parsing hex"), + result: Ok(-9223372036854775807), + }, + // Minimally encoded values that are out of range for data that + // is interpreted as script numbers with the minimal encoding + // flag set. Should error and return 0. + TestCase:: { + serialized: hex::decode("000000000000008080").expect("failed parsing hex"), result: Err(TxScriptError::NumberTooBig( - "numeric value encoded as [ff, ff, ff, ff, ff, ff, ff, ff] is 8 bytes which exceeds the max allowed of 4" - .to_string(), + "numeric value encoded as [0, 0, 0, 0, 0, 0, 0, 80, 80] is 9 bytes which exceeds the max allowed of 8".to_string(), )), }, // Non-minimally encoded, but otherwise valid values with diff --git a/crypto/txscript/src/opcodes/mod.rs b/crypto/txscript/src/opcodes/mod.rs index 82071370a..64175c823 100644 --- a/crypto/txscript/src/opcodes/mod.rs +++ b/crypto/txscript/src/opcodes/mod.rs @@ -567,13 +567,15 @@ opcode_list! { // Numeric related opcodes. opcode Op1Add<0x8b, 1>(self, vm) { let [value]: [i64; 1] = vm.dstack.pop_items()?; - vm.dstack.push_item(value + 1); + let r = value.checked_add(1).ok_or_else(|| TxScriptError::NumberTooBig("todo".to_string()))?; + vm.dstack.push_item(r); Ok(()) } opcode Op1Sub<0x8c, 1>(self, vm) { let [value]: [i64; 1] = vm.dstack.pop_items()?; - vm.dstack.push_item(value - 1); + let r = value.checked_sub(1).ok_or_else(|| TxScriptError::NumberTooBig("todo".to_string()))?; + vm.dstack.push_item(r); Ok(()) } @@ -582,13 +584,15 @@ opcode_list! { opcode OpNegate<0x8f, 1>(self, vm) { let [value]: [i64; 1] = vm.dstack.pop_items()?; - vm.dstack.push_item(-value); + let r = value.checked_neg().ok_or_else(|| TxScriptError::NumberTooBig("todo".to_string()))?; + vm.dstack.push_item(r); Ok(()) } opcode OpAbs<0x90, 1>(self, vm) { let [m]: [i64; 1] = vm.dstack.pop_items()?; - vm.dstack.push_item(m.abs()); + let r = m.checked_abs().ok_or_else(|| TxScriptError::NumberTooBig("todo".to_string()))?; + vm.dstack.push_item(r); Ok(()) } @@ -606,13 +610,15 @@ opcode_list! { opcode OpAdd<0x93, 1>(self, vm) { let [a,b]: [i64; 2] = vm.dstack.pop_items()?; - vm.dstack.push_item(a+b); + let r = a.checked_add(b).ok_or_else(|| TxScriptError::NumberTooBig("todo".to_string()))?; + vm.dstack.push_item(r); Ok(()) } opcode OpSub<0x94, 1>(self, vm) { let [a,b]: [i64; 2] = vm.dstack.pop_items()?; - vm.dstack.push_item(a-b); + let r = a.checked_sub(b).ok_or_else(|| TxScriptError::NumberTooBig("todo".to_string()))?; + vm.dstack.push_item(r); Ok(()) } diff --git a/crypto/txscript/src/script_builder.rs b/crypto/txscript/src/script_builder.rs index 731c47680..3409e29e1 100644 --- a/crypto/txscript/src/script_builder.rs +++ b/crypto/txscript/src/script_builder.rs @@ -355,6 +355,16 @@ mod tests { Test { name: "push -256", val: -256, expected: vec![OpData2, 0x00, 0x81] }, Test { name: "push -32767", val: -32767, expected: vec![OpData2, 0xff, 0xff] }, Test { name: "push -32768", val: -32768, expected: vec![OpData3, 0x00, 0x80, 0x80] }, + Test { + name: "push 9223372036854775807", + val: 9223372036854775807, + expected: vec![OpData8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F], + }, + Test { + name: "push -9223372036854775808", + val: -9223372036854775808, + expected: vec![OpData9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80], + }, ]; for test in tests { From 8069be50824986299ad2b74e7d2f583eee5b73dd Mon Sep 17 00:00:00 2001 From: Ori Newman Date: Tue, 5 Nov 2024 16:25:34 +0200 Subject: [PATCH 40/93] Standartize fork activation logic (#588) * Use ForkActivation for all fork activations * Avoid using negation in some ifs * Add is_within_range_from_activation * Move 'is always' check inside is_within_range_from_activation * lints --- consensus/core/src/config/params.rs | 93 ++++++++++++------- consensus/src/consensus/mod.rs | 4 +- consensus/src/consensus/services.rs | 4 +- .../body_validation_in_isolation.rs | 2 +- .../src/pipeline/body_processor/processor.rs | 8 +- .../pipeline/virtual_processor/processor.rs | 8 +- .../virtual_processor/utxo_validation.rs | 4 +- .../processes/transaction_validator/mod.rs | 10 +- .../transaction_validator_populated.rs | 4 +- consensus/src/processes/window.rs | 18 ++-- simpa/src/main.rs | 8 +- .../src/consensus_integration_tests.rs | 18 ++-- 12 files changed, 106 insertions(+), 75 deletions(-) diff --git a/consensus/core/src/config/params.rs b/consensus/core/src/config/params.rs index f4e1991e4..689c25409 100644 --- a/consensus/core/src/config/params.rs +++ b/consensus/core/src/config/params.rs @@ -15,6 +15,33 @@ use std::{ time::{SystemTime, UNIX_EPOCH}, }; +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct ForkActivation(u64); + +impl ForkActivation { + pub const fn new(daa_score: u64) -> Self { + Self(daa_score) + } + + pub const fn never() -> Self { + Self(u64::MAX) + } + + pub const fn always() -> Self { + Self(0) + } + + pub fn is_active(self, current_daa_score: u64) -> bool { + current_daa_score >= self.0 + } + + /// Checks if the fork was "recently" activated, i.e., in the time frame of the provided range. + /// This function returns false for forks that were always active, since they were never activated. + pub fn is_within_range_from_activation(self, current_daa_score: u64, range: u64) -> bool { + self != Self::always() && self.is_active(current_daa_score) && current_daa_score < self.0 + range + } +} + /// Consensus parameters. Contains settings and configurations which are consensus-sensitive. /// Changing one of these on a network node would exclude and prevent it from reaching consensus /// with the other unmodified nodes. @@ -41,7 +68,7 @@ pub struct Params { pub target_time_per_block: u64, /// DAA score from which the window sampling starts for difficulty and past median time calculation - pub sampling_activation_daa_score: u64, + pub sampling_activation: ForkActivation, /// Defines the highest allowed proof of work difficulty value for a block as a [`Uint256`] pub max_difficulty_target: Uint256, @@ -81,7 +108,7 @@ pub struct Params { pub storage_mass_parameter: u64, /// DAA score from which storage mass calculation and transaction mass field are activated as a consensus rule - pub storage_mass_activation_daa_score: u64, + pub storage_mass_activation: ForkActivation, /// DAA score from which tx engine supports kip10 opcodes: OpInputAmount, OpInputSpk, OpOutputAmount, OpOutputSpk pub kip10_activation_daa_score: u64, @@ -120,10 +147,10 @@ impl Params { #[inline] #[must_use] pub fn past_median_time_window_size(&self, selected_parent_daa_score: u64) -> usize { - if selected_parent_daa_score < self.sampling_activation_daa_score { - self.legacy_past_median_time_window_size() - } else { + if self.sampling_activation.is_active(selected_parent_daa_score) { self.sampled_past_median_time_window_size() + } else { + self.legacy_past_median_time_window_size() } } @@ -132,10 +159,10 @@ impl Params { #[inline] #[must_use] pub fn timestamp_deviation_tolerance(&self, selected_parent_daa_score: u64) -> u64 { - if selected_parent_daa_score < self.sampling_activation_daa_score { - self.legacy_timestamp_deviation_tolerance - } else { + if self.sampling_activation.is_active(selected_parent_daa_score) { self.new_timestamp_deviation_tolerance + } else { + self.legacy_timestamp_deviation_tolerance } } @@ -144,10 +171,10 @@ impl Params { #[inline] #[must_use] pub fn past_median_time_sample_rate(&self, selected_parent_daa_score: u64) -> u64 { - if selected_parent_daa_score < self.sampling_activation_daa_score { - 1 - } else { + if self.sampling_activation.is_active(selected_parent_daa_score) { self.past_median_time_sample_rate + } else { + 1 } } @@ -156,10 +183,10 @@ impl Params { #[inline] #[must_use] pub fn difficulty_window_size(&self, selected_parent_daa_score: u64) -> usize { - if selected_parent_daa_score < self.sampling_activation_daa_score { - self.legacy_difficulty_window_size - } else { + if self.sampling_activation.is_active(selected_parent_daa_score) { self.sampled_difficulty_window_size + } else { + self.legacy_difficulty_window_size } } @@ -168,10 +195,10 @@ impl Params { #[inline] #[must_use] pub fn difficulty_sample_rate(&self, selected_parent_daa_score: u64) -> u64 { - if selected_parent_daa_score < self.sampling_activation_daa_score { - 1 - } else { + if self.sampling_activation.is_active(selected_parent_daa_score) { self.difficulty_sample_rate + } else { + 1 } } @@ -191,18 +218,18 @@ impl Params { } pub fn daa_window_duration_in_blocks(&self, selected_parent_daa_score: u64) -> u64 { - if selected_parent_daa_score < self.sampling_activation_daa_score { - self.legacy_difficulty_window_size as u64 - } else { + if self.sampling_activation.is_active(selected_parent_daa_score) { self.difficulty_sample_rate * self.sampled_difficulty_window_size as u64 + } else { + self.legacy_difficulty_window_size as u64 } } fn expected_daa_window_duration_in_milliseconds(&self, selected_parent_daa_score: u64) -> u64 { - if selected_parent_daa_score < self.sampling_activation_daa_score { - self.target_time_per_block * self.legacy_difficulty_window_size as u64 - } else { + if self.sampling_activation.is_active(selected_parent_daa_score) { self.target_time_per_block * self.difficulty_sample_rate * self.sampled_difficulty_window_size as u64 + } else { + self.target_time_per_block * self.legacy_difficulty_window_size as u64 } } @@ -325,7 +352,7 @@ pub const MAINNET_PARAMS: Params = Params { past_median_time_sample_rate: Bps::<1>::past_median_time_sample_rate(), past_median_time_sampled_window_size: MEDIAN_TIME_SAMPLED_WINDOW_SIZE, target_time_per_block: 1000, - sampling_activation_daa_score: u64::MAX, + sampling_activation: ForkActivation::never(), max_difficulty_target: MAX_DIFFICULTY_TARGET, max_difficulty_target_f64: MAX_DIFFICULTY_TARGET_AS_F64, difficulty_sample_rate: Bps::<1>::difficulty_adjustment_sample_rate(), @@ -355,7 +382,7 @@ pub const MAINNET_PARAMS: Params = Params { max_block_mass: 500_000, storage_mass_parameter: STORAGE_MASS_PARAMETER, - storage_mass_activation_daa_score: u64::MAX, + storage_mass_activation: ForkActivation::never(), kip10_activation_daa_score: u64::MAX, // deflationary_phase_daa_score is the DAA score after which the pre-deflationary period @@ -389,7 +416,7 @@ pub const TESTNET_PARAMS: Params = Params { past_median_time_sample_rate: Bps::<1>::past_median_time_sample_rate(), past_median_time_sampled_window_size: MEDIAN_TIME_SAMPLED_WINDOW_SIZE, target_time_per_block: 1000, - sampling_activation_daa_score: u64::MAX, + sampling_activation: ForkActivation::never(), max_difficulty_target: MAX_DIFFICULTY_TARGET, max_difficulty_target_f64: MAX_DIFFICULTY_TARGET_AS_F64, difficulty_sample_rate: Bps::<1>::difficulty_adjustment_sample_rate(), @@ -419,7 +446,7 @@ pub const TESTNET_PARAMS: Params = Params { max_block_mass: 500_000, storage_mass_parameter: STORAGE_MASS_PARAMETER, - storage_mass_activation_daa_score: u64::MAX, + storage_mass_activation: ForkActivation::never(), kip10_activation_daa_score: u64::MAX, // deflationary_phase_daa_score is the DAA score after which the pre-deflationary period // switches to the deflationary period. This number is calculated as follows: @@ -451,7 +478,7 @@ pub const TESTNET11_PARAMS: Params = Params { legacy_timestamp_deviation_tolerance: LEGACY_TIMESTAMP_DEVIATION_TOLERANCE, new_timestamp_deviation_tolerance: NEW_TIMESTAMP_DEVIATION_TOLERANCE, past_median_time_sampled_window_size: MEDIAN_TIME_SAMPLED_WINDOW_SIZE, - sampling_activation_daa_score: 0, // Sampling is activated from network inception + sampling_activation: ForkActivation::always(), // Sampling is activated from network inception max_difficulty_target: MAX_DIFFICULTY_TARGET, max_difficulty_target_f64: MAX_DIFFICULTY_TARGET_AS_F64, sampled_difficulty_window_size: DIFFICULTY_SAMPLED_WINDOW_SIZE as usize, @@ -489,7 +516,7 @@ pub const TESTNET11_PARAMS: Params = Params { max_block_mass: 500_000, storage_mass_parameter: STORAGE_MASS_PARAMETER, - storage_mass_activation_daa_score: 0, + storage_mass_activation: ForkActivation::always(), kip10_activation_daa_score: u64::MAX, skip_proof_of_work: false, @@ -503,7 +530,7 @@ pub const SIMNET_PARAMS: Params = Params { legacy_timestamp_deviation_tolerance: LEGACY_TIMESTAMP_DEVIATION_TOLERANCE, new_timestamp_deviation_tolerance: NEW_TIMESTAMP_DEVIATION_TOLERANCE, past_median_time_sampled_window_size: MEDIAN_TIME_SAMPLED_WINDOW_SIZE, - sampling_activation_daa_score: 0, // Sampling is activated from network inception + sampling_activation: ForkActivation::always(), // Sampling is activated from network inception max_difficulty_target: MAX_DIFFICULTY_TARGET, max_difficulty_target_f64: MAX_DIFFICULTY_TARGET_AS_F64, sampled_difficulty_window_size: DIFFICULTY_SAMPLED_WINDOW_SIZE as usize, @@ -543,7 +570,7 @@ pub const SIMNET_PARAMS: Params = Params { max_block_mass: 500_000, storage_mass_parameter: STORAGE_MASS_PARAMETER, - storage_mass_activation_daa_score: 0, + storage_mass_activation: ForkActivation::always(), kip10_activation_daa_score: u64::MAX, skip_proof_of_work: true, // For simnet only, PoW can be simulated by default @@ -560,7 +587,7 @@ pub const DEVNET_PARAMS: Params = Params { past_median_time_sample_rate: Bps::<1>::past_median_time_sample_rate(), past_median_time_sampled_window_size: MEDIAN_TIME_SAMPLED_WINDOW_SIZE, target_time_per_block: 1000, - sampling_activation_daa_score: u64::MAX, + sampling_activation: ForkActivation::never(), max_difficulty_target: MAX_DIFFICULTY_TARGET, max_difficulty_target_f64: MAX_DIFFICULTY_TARGET_AS_F64, difficulty_sample_rate: Bps::<1>::difficulty_adjustment_sample_rate(), @@ -590,7 +617,7 @@ pub const DEVNET_PARAMS: Params = Params { max_block_mass: 500_000, storage_mass_parameter: STORAGE_MASS_PARAMETER, - storage_mass_activation_daa_score: u64::MAX, + storage_mass_activation: ForkActivation::never(), kip10_activation_daa_score: u64::MAX, // deflationary_phase_daa_score is the DAA score after which the pre-deflationary period diff --git a/consensus/src/consensus/mod.rs b/consensus/src/consensus/mod.rs index 6909562aa..a47b4218f 100644 --- a/consensus/src/consensus/mod.rs +++ b/consensus/src/consensus/mod.rs @@ -257,7 +257,7 @@ impl Consensus { pruning_lock.clone(), notification_root.clone(), counters.clone(), - params.storage_mass_activation_daa_score, + params.storage_mass_activation, )); let virtual_processor = Arc::new(VirtualStateProcessor::new( @@ -753,7 +753,7 @@ impl ConsensusApi for Consensus { } fn calc_transaction_hash_merkle_root(&self, txs: &[Transaction], pov_daa_score: u64) -> Hash { - let storage_mass_activated = pov_daa_score > self.config.storage_mass_activation_daa_score; + let storage_mass_activated = self.config.storage_mass_activation.is_active(pov_daa_score); calc_hash_merkle_root(txs.iter(), storage_mass_activated) } diff --git a/consensus/src/consensus/services.rs b/consensus/src/consensus/services.rs index 3aba3fb8a..5aed1c301 100644 --- a/consensus/src/consensus/services.rs +++ b/consensus/src/consensus/services.rs @@ -94,7 +94,7 @@ impl ConsensusServices { storage.block_window_cache_for_past_median_time.clone(), params.max_difficulty_target, params.target_time_per_block, - params.sampling_activation_daa_score, + params.sampling_activation, params.legacy_difficulty_window_size, params.sampled_difficulty_window_size, params.min_difficulty_window_len, @@ -146,7 +146,7 @@ impl ConsensusServices { params.coinbase_maturity, tx_script_cache_counters, mass_calculator.clone(), - params.storage_mass_activation_daa_score, + params.storage_mass_activation, params.kip10_activation_daa_score, ); diff --git a/consensus/src/pipeline/body_processor/body_validation_in_isolation.rs b/consensus/src/pipeline/body_processor/body_validation_in_isolation.rs index c413552b9..4c6139846 100644 --- a/consensus/src/pipeline/body_processor/body_validation_in_isolation.rs +++ b/consensus/src/pipeline/body_processor/body_validation_in_isolation.rs @@ -6,7 +6,7 @@ use kaspa_consensus_core::{block::Block, merkle::calc_hash_merkle_root, tx::Tran impl BlockBodyProcessor { pub fn validate_body_in_isolation(self: &Arc, block: &Block) -> BlockProcessResult { - let storage_mass_activated = block.header.daa_score > self.storage_mass_activation_daa_score; + let storage_mass_activated = self.storage_mass_activation.is_active(block.header.daa_score); Self::check_has_transactions(block)?; Self::check_hash_merkle_root(block, storage_mass_activated)?; diff --git a/consensus/src/pipeline/body_processor/processor.rs b/consensus/src/pipeline/body_processor/processor.rs index 4191a01ce..6885c78b5 100644 --- a/consensus/src/pipeline/body_processor/processor.rs +++ b/consensus/src/pipeline/body_processor/processor.rs @@ -23,7 +23,7 @@ use crossbeam_channel::{Receiver, Sender}; use kaspa_consensus_core::{ block::Block, blockstatus::BlockStatus::{self, StatusHeaderOnly, StatusInvalid}, - config::genesis::GenesisBlock, + config::{genesis::GenesisBlock, params::ForkActivation}, mass::MassCalculator, tx::Transaction, }; @@ -81,7 +81,7 @@ pub struct BlockBodyProcessor { counters: Arc, /// Storage mass hardfork DAA score - pub(crate) storage_mass_activation_daa_score: u64, + pub(crate) storage_mass_activation: ForkActivation, } impl BlockBodyProcessor { @@ -108,7 +108,7 @@ impl BlockBodyProcessor { pruning_lock: SessionLock, notification_root: Arc, counters: Arc, - storage_mass_activation_daa_score: u64, + storage_mass_activation: ForkActivation, ) -> Self { Self { receiver, @@ -131,7 +131,7 @@ impl BlockBodyProcessor { task_manager: BlockTaskDependencyManager::new(), notification_root, counters, - storage_mass_activation_daa_score, + storage_mass_activation, } } diff --git a/consensus/src/pipeline/virtual_processor/processor.rs b/consensus/src/pipeline/virtual_processor/processor.rs index 4b571dddc..c654fef43 100644 --- a/consensus/src/pipeline/virtual_processor/processor.rs +++ b/consensus/src/pipeline/virtual_processor/processor.rs @@ -52,7 +52,7 @@ use kaspa_consensus_core::{ block::{BlockTemplate, MutableBlock, TemplateBuildMode, TemplateTransactionSelector}, blockstatus::BlockStatus::{StatusDisqualifiedFromChain, StatusUTXOValid}, coinbase::MinerData, - config::genesis::GenesisBlock, + config::{genesis::GenesisBlock, params::ForkActivation}, header::Header, merkle::calc_hash_merkle_root, pruning::PruningPointsList, @@ -159,7 +159,7 @@ pub struct VirtualStateProcessor { counters: Arc, // Storage mass hardfork DAA score - pub(crate) storage_mass_activation_daa_score: u64, + pub(crate) storage_mass_activation: ForkActivation, } impl VirtualStateProcessor { @@ -220,7 +220,7 @@ impl VirtualStateProcessor { pruning_lock, notification_root, counters, - storage_mass_activation_daa_score: params.storage_mass_activation_daa_score, + storage_mass_activation: params.storage_mass_activation, } } @@ -983,7 +983,7 @@ impl VirtualStateProcessor { let parents_by_level = self.parents_manager.calc_block_parents(pruning_info.pruning_point, &virtual_state.parents); // Hash according to hardfork activation - let storage_mass_activated = virtual_state.daa_score > self.storage_mass_activation_daa_score; + let storage_mass_activated = self.storage_mass_activation.is_active(virtual_state.daa_score); let hash_merkle_root = calc_hash_merkle_root(txs.iter(), storage_mass_activated); let accepted_id_merkle_root = kaspa_merkle::calc_merkle_root(virtual_state.accepted_tx_ids.iter().copied()); diff --git a/consensus/src/pipeline/virtual_processor/utxo_validation.rs b/consensus/src/pipeline/virtual_processor/utxo_validation.rs index 6daa3deb8..4a62a4ae8 100644 --- a/consensus/src/pipeline/virtual_processor/utxo_validation.rs +++ b/consensus/src/pipeline/virtual_processor/utxo_validation.rs @@ -14,6 +14,7 @@ use kaspa_consensus_core::{ acceptance_data::{AcceptedTxEntry, MergesetBlockAcceptanceData}, api::args::TransactionValidationArgs, coinbase::*, + config::params::ForkActivation, hashing, header::Header, mass::Kip9Version, @@ -328,7 +329,8 @@ impl VirtualStateProcessor { // For non-activated nets (mainnet, TN10) we can update mempool rules to KIP9 beta asap. For // TN11 we need to hard-fork consensus first (since the new beta rules are more permissive) - let kip9_version = if self.storage_mass_activation_daa_score == u64::MAX { Kip9Version::Beta } else { Kip9Version::Alpha }; + let kip9_version = + if self.storage_mass_activation == ForkActivation::never() { Kip9Version::Beta } else { Kip9Version::Alpha }; // Calc the full contextual mass including storage mass let contextual_mass = self diff --git a/consensus/src/processes/transaction_validator/mod.rs b/consensus/src/processes/transaction_validator/mod.rs index 311cd777e..12c32bdca 100644 --- a/consensus/src/processes/transaction_validator/mod.rs +++ b/consensus/src/processes/transaction_validator/mod.rs @@ -11,7 +11,7 @@ use kaspa_txscript::{ SigCacheKey, }; -use kaspa_consensus_core::mass::MassCalculator; +use kaspa_consensus_core::{config::params::ForkActivation, mass::MassCalculator}; #[derive(Clone)] pub struct TransactionValidator { @@ -27,7 +27,7 @@ pub struct TransactionValidator { pub(crate) mass_calculator: MassCalculator, /// Storage mass hardfork DAA score - storage_mass_activation_daa_score: u64, + storage_mass_activation: ForkActivation, /// KIP-10 hardfork DAA score kip10_activation_daa_score: u64, } @@ -44,7 +44,7 @@ impl TransactionValidator { coinbase_maturity: u64, counters: Arc, mass_calculator: MassCalculator, - storage_mass_activation_daa_score: u64, + storage_mass_activation: ForkActivation, kip10_activation_daa_score: u64, ) -> Self { Self { @@ -57,7 +57,7 @@ impl TransactionValidator { coinbase_maturity, sig_cache: Cache::with_counters(10_000, counters), mass_calculator, - storage_mass_activation_daa_score, + storage_mass_activation, kip10_activation_daa_score, } } @@ -82,7 +82,7 @@ impl TransactionValidator { coinbase_maturity, sig_cache: Cache::with_counters(10_000, counters), mass_calculator: MassCalculator::new(0, 0, 0, 0), - storage_mass_activation_daa_score: u64::MAX, + storage_mass_activation: ForkActivation::never(), kip10_activation_daa_score: u64::MAX, } } diff --git a/consensus/src/processes/transaction_validator/transaction_validator_populated.rs b/consensus/src/processes/transaction_validator/transaction_validator_populated.rs index bac89c961..bd64965b3 100644 --- a/consensus/src/processes/transaction_validator/transaction_validator_populated.rs +++ b/consensus/src/processes/transaction_validator/transaction_validator_populated.rs @@ -44,11 +44,11 @@ impl TransactionValidator { let total_in = self.check_transaction_input_amounts(tx)?; let total_out = Self::check_transaction_output_values(tx, total_in)?; let fee = total_in - total_out; - if flags != TxValidationFlags::SkipMassCheck && pov_daa_score > self.storage_mass_activation_daa_score { + if flags != TxValidationFlags::SkipMassCheck && self.storage_mass_activation.is_active(pov_daa_score) { // Storage mass hardfork was activated self.check_mass_commitment(tx)?; - if pov_daa_score < self.storage_mass_activation_daa_score + 10 && self.storage_mass_activation_daa_score > 0 { + if self.storage_mass_activation.is_within_range_from_activation(pov_daa_score, 10) { warn!("--------- Storage mass hardfork was activated successfully!!! --------- (DAA score: {})", pov_daa_score); } } diff --git a/consensus/src/processes/window.rs b/consensus/src/processes/window.rs index 9c582af28..ca0f71cf2 100644 --- a/consensus/src/processes/window.rs +++ b/consensus/src/processes/window.rs @@ -9,7 +9,7 @@ use crate::{ }; use kaspa_consensus_core::{ blockhash::BlockHashExtensions, - config::genesis::GenesisBlock, + config::{genesis::GenesisBlock, params::ForkActivation}, errors::{block::RuleError, difficulty::DifficultyResult}, BlockHashSet, BlueWorkType, }; @@ -249,7 +249,7 @@ pub struct SampledWindowManager, block_window_cache_for_past_median_time: Arc, target_time_per_block: u64, - sampling_activation_daa_score: u64, + sampling_activation: ForkActivation, difficulty_window_size: usize, difficulty_sample_rate: u64, past_median_time_window_size: usize, @@ -269,7 +269,7 @@ impl, max_difficulty_target: Uint256, target_time_per_block: u64, - sampling_activation_daa_score: u64, + sampling_activation: ForkActivation, difficulty_window_size: usize, min_difficulty_window_len: usize, difficulty_sample_rate: u64, @@ -294,7 +294,7 @@ impl { ghostdag_store: Arc, headers_store: Arc, - sampling_activation_daa_score: u64, + sampling_activation: ForkActivation, full_window_manager: FullWindowManager, sampled_window_manager: SampledWindowManager, } @@ -541,7 +541,7 @@ impl, max_difficulty_target: Uint256, target_time_per_block: u64, - sampling_activation_daa_score: u64, + sampling_activation: ForkActivation, full_difficulty_window_size: usize, sampled_difficulty_window_size: usize, min_difficulty_window_len: usize, @@ -571,19 +571,19 @@ impl bool { let sp_daa_score = self.headers_store.get_daa_score(ghostdag_data.selected_parent).unwrap(); - sp_daa_score >= self.sampling_activation_daa_score + self.sampling_activation.is_active(sp_daa_score) } } diff --git a/simpa/src/main.rs b/simpa/src/main.rs index 2994b0a09..a2365e1c9 100644 --- a/simpa/src/main.rs +++ b/simpa/src/main.rs @@ -13,7 +13,7 @@ use kaspa_consensus::{ headers::HeaderStoreReader, relations::RelationsStoreReader, }, - params::{Params, Testnet11Bps, DEVNET_PARAMS, NETWORK_DELAY_BOUND, TESTNET11_PARAMS}, + params::{ForkActivation, Params, Testnet11Bps, DEVNET_PARAMS, NETWORK_DELAY_BOUND, TESTNET11_PARAMS}, }; use kaspa_consensus_core::{ api::ConsensusApi, block::Block, blockstatus::BlockStatus, config::bps::calculate_ghostdag_k, errors::block::BlockProcessResult, @@ -189,7 +189,7 @@ fn main_impl(mut args: Args) { } args.bps = if args.testnet11 { Testnet11Bps::bps() as f64 } else { args.bps }; let mut params = if args.testnet11 { TESTNET11_PARAMS } else { DEVNET_PARAMS }; - params.storage_mass_activation_daa_score = 400; + params.storage_mass_activation = ForkActivation::new(400); params.storage_mass_parameter = 10_000; let mut builder = ConfigBuilder::new(params) .apply_args(|config| apply_args_to_consensus_params(&args, &mut config.params)) @@ -306,12 +306,12 @@ fn apply_args_to_consensus_params(args: &Args, params: &mut Params) { if args.daa_legacy { // Scale DAA and median-time windows linearly with BPS - params.sampling_activation_daa_score = u64::MAX; + params.sampling_activation = ForkActivation::never(); params.legacy_timestamp_deviation_tolerance = (params.legacy_timestamp_deviation_tolerance as f64 * args.bps) as u64; params.legacy_difficulty_window_size = (params.legacy_difficulty_window_size as f64 * args.bps) as usize; } else { // Use the new sampling algorithms - params.sampling_activation_daa_score = 0; + params.sampling_activation = ForkActivation::always(); params.past_median_time_sample_rate = (10.0 * args.bps) as u64; params.new_timestamp_deviation_tolerance = (600.0 * args.bps) as u64; params.difficulty_sample_rate = (2.0 * args.bps) as u64; diff --git a/testing/integration/src/consensus_integration_tests.rs b/testing/integration/src/consensus_integration_tests.rs index a8d2fb3ea..5ac3c7132 100644 --- a/testing/integration/src/consensus_integration_tests.rs +++ b/testing/integration/src/consensus_integration_tests.rs @@ -16,7 +16,9 @@ use kaspa_consensus::model::stores::headers::HeaderStoreReader; use kaspa_consensus::model::stores::reachability::DbReachabilityStore; use kaspa_consensus::model::stores::relations::DbRelationsStore; use kaspa_consensus::model::stores::selected_chain::SelectedChainStoreReader; -use kaspa_consensus::params::{Params, DEVNET_PARAMS, MAINNET_PARAMS, MAX_DIFFICULTY_TARGET, MAX_DIFFICULTY_TARGET_AS_F64}; +use kaspa_consensus::params::{ + ForkActivation, Params, DEVNET_PARAMS, MAINNET_PARAMS, MAX_DIFFICULTY_TARGET, MAX_DIFFICULTY_TARGET_AS_F64, +}; use kaspa_consensus::pipeline::monitor::ConsensusMonitor; use kaspa_consensus::pipeline::ProcessingCounters; use kaspa_consensus::processes::reachability::tests::{DagBlock, DagBuilder, StoreValidationExtensions}; @@ -553,7 +555,7 @@ async fn median_time_test() { config: ConfigBuilder::new(MAINNET_PARAMS) .skip_proof_of_work() .edit_consensus_params(|p| { - p.sampling_activation_daa_score = u64::MAX; + p.sampling_activation = ForkActivation::never(); }) .build(), }, @@ -562,7 +564,7 @@ async fn median_time_test() { config: ConfigBuilder::new(MAINNET_PARAMS) .skip_proof_of_work() .edit_consensus_params(|p| { - p.sampling_activation_daa_score = 0; + p.sampling_activation = ForkActivation::always(); p.new_timestamp_deviation_tolerance = 120; p.past_median_time_sample_rate = 3; p.past_median_time_sampled_window_size = (2 * 120 - 1) / 3; @@ -807,7 +809,7 @@ impl KaspadGoParams { past_median_time_sample_rate: 1, past_median_time_sampled_window_size: 2 * self.TimestampDeviationTolerance - 1, target_time_per_block: self.TargetTimePerBlock / 1_000_000, - sampling_activation_daa_score: u64::MAX, + sampling_activation: ForkActivation::never(), max_block_parents: self.MaxBlockParents, max_difficulty_target: MAX_DIFFICULTY_TARGET, max_difficulty_target_f64: MAX_DIFFICULTY_TARGET_AS_F64, @@ -830,7 +832,7 @@ impl KaspadGoParams { mass_per_sig_op: self.MassPerSigOp, max_block_mass: self.MaxBlockMass, storage_mass_parameter: STORAGE_MASS_PARAMETER, - storage_mass_activation_daa_score: u64::MAX, + storage_mass_activation: ForkActivation::never(), kip10_activation_daa_score: u64::MAX, deflationary_phase_daa_score: self.DeflationaryPhaseDaaScore, pre_deflationary_phase_base_subsidy: self.PreDeflationaryPhaseBaseSubsidy, @@ -1389,7 +1391,7 @@ async fn difficulty_test() { .edit_consensus_params(|p| { p.ghostdag_k = 1; p.legacy_difficulty_window_size = FULL_WINDOW_SIZE; - p.sampling_activation_daa_score = u64::MAX; + p.sampling_activation = ForkActivation::never(); // Define past median time so that calls to add_block_with_min_time create blocks // which timestamps fit within the min-max timestamps found in the difficulty window p.legacy_timestamp_deviation_tolerance = 60; @@ -1405,7 +1407,7 @@ async fn difficulty_test() { p.ghostdag_k = 1; p.sampled_difficulty_window_size = SAMPLED_WINDOW_SIZE; p.difficulty_sample_rate = SAMPLE_RATE; - p.sampling_activation_daa_score = 0; + p.sampling_activation = ForkActivation::always(); // Define past median time so that calls to add_block_with_min_time create blocks // which timestamps fit within the min-max timestamps found in the difficulty window p.past_median_time_sample_rate = PMT_SAMPLE_RATE; @@ -1424,7 +1426,7 @@ async fn difficulty_test() { p.target_time_per_block /= HIGH_BPS; p.sampled_difficulty_window_size = HIGH_BPS_SAMPLED_WINDOW_SIZE; p.difficulty_sample_rate = SAMPLE_RATE * HIGH_BPS; - p.sampling_activation_daa_score = 0; + p.sampling_activation = ForkActivation::always(); // Define past median time so that calls to add_block_with_min_time create blocks // which timestamps fit within the min-max timestamps found in the difficulty window p.past_median_time_sample_rate = PMT_SAMPLE_RATE * HIGH_BPS; From dced99f9d9584633447de37a55669130893e1509 Mon Sep 17 00:00:00 2001 From: coderofstuff <114628839+coderofstuff@users.noreply.github.com> Date: Wed, 6 Nov 2024 01:36:34 -0700 Subject: [PATCH 41/93] Refactoring for cleaner pruning proof module (#589) * Cleanup manual block level calc There were two areas in pruning proof mod that manually calculated block level. This replaces those with a call to calc_block_level * Refactor pruning proof build functions * Refactor apply pruning proof functions * Refactor validate pruning functions * Add comments for clarity --- .../src/processes/pruning_proof/apply.rs | 236 ++++ .../src/processes/pruning_proof/build.rs | 532 ++++++++ consensus/src/processes/pruning_proof/mod.rs | 1101 +---------------- .../src/processes/pruning_proof/validate.rs | 376 ++++++ 4 files changed, 1169 insertions(+), 1076 deletions(-) create mode 100644 consensus/src/processes/pruning_proof/apply.rs create mode 100644 consensus/src/processes/pruning_proof/build.rs create mode 100644 consensus/src/processes/pruning_proof/validate.rs diff --git a/consensus/src/processes/pruning_proof/apply.rs b/consensus/src/processes/pruning_proof/apply.rs new file mode 100644 index 000000000..8463d6464 --- /dev/null +++ b/consensus/src/processes/pruning_proof/apply.rs @@ -0,0 +1,236 @@ +use std::{ + cmp::Reverse, + collections::{hash_map::Entry::Vacant, BinaryHeap, HashSet}, + sync::Arc, +}; + +use itertools::Itertools; +use kaspa_consensus_core::{ + blockhash::{BlockHashes, ORIGIN}, + errors::pruning::{PruningImportError, PruningImportResult}, + header::Header, + pruning::PruningPointProof, + trusted::TrustedBlock, + BlockHashMap, BlockHashSet, BlockLevel, HashMapCustomHasher, +}; +use kaspa_core::{debug, trace}; +use kaspa_hashes::Hash; +use kaspa_pow::calc_block_level; +use kaspa_utils::{binary_heap::BinaryHeapExtensions, vec::VecExtensions}; +use rocksdb::WriteBatch; + +use crate::{ + model::{ + services::reachability::ReachabilityService, + stores::{ + ghostdag::{GhostdagData, GhostdagStore}, + headers::HeaderStore, + reachability::StagingReachabilityStore, + relations::StagingRelationsStore, + selected_chain::SelectedChainStore, + virtual_state::{VirtualState, VirtualStateStore}, + }, + }, + processes::{ + ghostdag::{mergeset::unordered_mergeset_without_selected_parent, ordering::SortableBlock}, + reachability::inquirer as reachability, + relations::RelationsStoreExtensions, + }, +}; + +use super::PruningProofManager; + +impl PruningProofManager { + pub fn apply_proof(&self, mut proof: PruningPointProof, trusted_set: &[TrustedBlock]) -> PruningImportResult<()> { + let pruning_point_header = proof[0].last().unwrap().clone(); + let pruning_point = pruning_point_header.hash; + + // Create a copy of the proof, since we're going to be mutating the proof passed to us + let proof_sets = (0..=self.max_block_level) + .map(|level| BlockHashSet::from_iter(proof[level as usize].iter().map(|header| header.hash))) + .collect_vec(); + + let mut trusted_gd_map: BlockHashMap = BlockHashMap::new(); + for tb in trusted_set.iter() { + trusted_gd_map.insert(tb.block.hash(), tb.ghostdag.clone().into()); + let tb_block_level = calc_block_level(&tb.block.header, self.max_block_level); + + (0..=tb_block_level).for_each(|current_proof_level| { + // If this block was in the original proof, ignore it + if proof_sets[current_proof_level as usize].contains(&tb.block.hash()) { + return; + } + + proof[current_proof_level as usize].push(tb.block.header.clone()); + }); + } + + proof.iter_mut().for_each(|level_proof| { + level_proof.sort_by(|a, b| a.blue_work.cmp(&b.blue_work)); + }); + + self.populate_reachability_and_headers(&proof); + + { + let reachability_read = self.reachability_store.read(); + for tb in trusted_set.iter() { + // Header-only trusted blocks are expected to be in pruning point past + if tb.block.is_header_only() && !reachability_read.is_dag_ancestor_of(tb.block.hash(), pruning_point) { + return Err(PruningImportError::PruningPointPastMissingReachability(tb.block.hash())); + } + } + } + + for (level, headers) in proof.iter().enumerate() { + trace!("Applying level {} from the pruning point proof", level); + let mut level_ancestors: HashSet = HashSet::new(); + level_ancestors.insert(ORIGIN); + + for header in headers.iter() { + let parents = Arc::new( + self.parents_manager + .parents_at_level(header, level as BlockLevel) + .iter() + .copied() + .filter(|parent| level_ancestors.contains(parent)) + .collect_vec() + .push_if_empty(ORIGIN), + ); + + self.relations_stores.write()[level].insert(header.hash, parents.clone()).unwrap(); + + if level == 0 { + let gd = if let Some(gd) = trusted_gd_map.get(&header.hash) { + gd.clone() + } else { + let calculated_gd = self.ghostdag_manager.ghostdag(&parents); + // Override the ghostdag data with the real blue score and blue work + GhostdagData { + blue_score: header.blue_score, + blue_work: header.blue_work, + selected_parent: calculated_gd.selected_parent, + mergeset_blues: calculated_gd.mergeset_blues, + mergeset_reds: calculated_gd.mergeset_reds, + blues_anticone_sizes: calculated_gd.blues_anticone_sizes, + } + }; + self.ghostdag_store.insert(header.hash, Arc::new(gd)).unwrap(); + } + + level_ancestors.insert(header.hash); + } + } + + let virtual_parents = vec![pruning_point]; + let virtual_state = Arc::new(VirtualState { + parents: virtual_parents.clone(), + ghostdag_data: self.ghostdag_manager.ghostdag(&virtual_parents), + ..VirtualState::default() + }); + self.virtual_stores.write().state.set(virtual_state).unwrap(); + + let mut batch = WriteBatch::default(); + self.body_tips_store.write().init_batch(&mut batch, &virtual_parents).unwrap(); + self.headers_selected_tip_store + .write() + .set_batch(&mut batch, SortableBlock { hash: pruning_point, blue_work: pruning_point_header.blue_work }) + .unwrap(); + self.selected_chain_store.write().init_with_pruning_point(&mut batch, pruning_point).unwrap(); + self.depth_store.insert_batch(&mut batch, pruning_point, ORIGIN, ORIGIN).unwrap(); + self.db.write(batch).unwrap(); + + Ok(()) + } + + pub fn populate_reachability_and_headers(&self, proof: &PruningPointProof) { + let capacity_estimate = self.estimate_proof_unique_size(proof); + let mut dag = BlockHashMap::with_capacity(capacity_estimate); + let mut up_heap = BinaryHeap::with_capacity(capacity_estimate); + for header in proof.iter().flatten().cloned() { + if let Vacant(e) = dag.entry(header.hash) { + // TODO: Check if pow passes + let block_level = calc_block_level(&header, self.max_block_level); + self.headers_store.insert(header.hash, header.clone(), block_level).unwrap(); + + let mut parents = BlockHashSet::with_capacity(header.direct_parents().len() * 2); + // We collect all available parent relations in order to maximize reachability information. + // By taking into account parents from all levels we ensure that the induced DAG has valid + // reachability information for each level-specific sub-DAG -- hence a single reachability + // oracle can serve them all + for level in 0..=self.max_block_level { + for parent in self.parents_manager.parents_at_level(&header, level) { + parents.insert(*parent); + } + } + + struct DagEntry { + header: Arc
, + parents: Arc, + } + + up_heap.push(Reverse(SortableBlock { hash: header.hash, blue_work: header.blue_work })); + e.insert(DagEntry { header, parents: Arc::new(parents) }); + } + } + + debug!("Estimated proof size: {}, actual size: {}", capacity_estimate, dag.len()); + + for reverse_sortable_block in up_heap.into_sorted_iter() { + // TODO: Convert to into_iter_sorted once it gets stable + let hash = reverse_sortable_block.0.hash; + let dag_entry = dag.get(&hash).unwrap(); + + // Filter only existing parents + let parents_in_dag = BinaryHeap::from_iter( + dag_entry + .parents + .iter() + .cloned() + .filter(|parent| dag.contains_key(parent)) + .map(|parent| SortableBlock { hash: parent, blue_work: dag.get(&parent).unwrap().header.blue_work }), + ); + + let reachability_read = self.reachability_store.upgradable_read(); + + // Find the maximal parent antichain from the possibly redundant set of existing parents + let mut reachability_parents: Vec = Vec::new(); + for parent in parents_in_dag.into_sorted_iter() { + if reachability_read.is_dag_ancestor_of_any(parent.hash, &mut reachability_parents.iter().map(|parent| parent.hash)) { + continue; + } + + reachability_parents.push(parent); + } + let reachability_parents_hashes = + BlockHashes::new(reachability_parents.iter().map(|parent| parent.hash).collect_vec().push_if_empty(ORIGIN)); + let selected_parent = reachability_parents.iter().max().map(|parent| parent.hash).unwrap_or(ORIGIN); + + // Prepare batch + let mut batch = WriteBatch::default(); + let mut reachability_relations_write = self.reachability_relations_store.write(); + let mut staging_reachability = StagingReachabilityStore::new(reachability_read); + let mut staging_reachability_relations = StagingRelationsStore::new(&mut reachability_relations_write); + + // Stage + staging_reachability_relations.insert(hash, reachability_parents_hashes.clone()).unwrap(); + let mergeset = unordered_mergeset_without_selected_parent( + &staging_reachability_relations, + &staging_reachability, + selected_parent, + &reachability_parents_hashes, + ); + reachability::add_block(&mut staging_reachability, hash, selected_parent, &mut mergeset.iter().copied()).unwrap(); + + // Commit + let reachability_write = staging_reachability.commit(&mut batch).unwrap(); + staging_reachability_relations.commit(&mut batch).unwrap(); + + // Write + self.db.write(batch).unwrap(); + + // Drop + drop(reachability_write); + drop(reachability_relations_write); + } + } +} diff --git a/consensus/src/processes/pruning_proof/build.rs b/consensus/src/processes/pruning_proof/build.rs new file mode 100644 index 000000000..8ae6fb34c --- /dev/null +++ b/consensus/src/processes/pruning_proof/build.rs @@ -0,0 +1,532 @@ +use std::{cmp::Reverse, collections::BinaryHeap, sync::Arc}; + +use itertools::Itertools; +use kaspa_consensus_core::{ + blockhash::{BlockHashExtensions, BlockHashes, ORIGIN}, + header::Header, + pruning::PruningPointProof, + BlockHashSet, BlockLevel, HashMapCustomHasher, +}; +use kaspa_core::debug; +use kaspa_database::prelude::{CachePolicy, ConnBuilder, StoreError, StoreResult, StoreResultEmptyTuple, StoreResultExtensions, DB}; +use kaspa_hashes::Hash; + +use crate::{ + model::{ + services::reachability::ReachabilityService, + stores::{ + ghostdag::{DbGhostdagStore, GhostdagStore, GhostdagStoreReader}, + headers::{HeaderStoreReader, HeaderWithBlockLevel}, + relations::RelationsStoreReader, + }, + }, + processes::{ + ghostdag::{ordering::SortableBlock, protocol::GhostdagManager}, + pruning_proof::PruningProofManagerInternalError, + }, +}; + +use super::{PruningProofManager, PruningProofManagerInternalResult}; + +#[derive(Clone)] +struct RelationsStoreInFutureOfRoot { + relations_store: T, + reachability_service: U, + root: Hash, +} + +impl RelationsStoreReader for RelationsStoreInFutureOfRoot { + fn get_parents(&self, hash: Hash) -> Result { + self.relations_store.get_parents(hash).map(|hashes| { + Arc::new(hashes.iter().copied().filter(|h| self.reachability_service.is_dag_ancestor_of(self.root, *h)).collect_vec()) + }) + } + + fn get_children(&self, hash: Hash) -> StoreResult> { + // We assume hash is in future of root + assert!(self.reachability_service.is_dag_ancestor_of(self.root, hash)); + self.relations_store.get_children(hash) + } + + fn has(&self, hash: Hash) -> Result { + if self.reachability_service.is_dag_ancestor_of(self.root, hash) { + Ok(false) + } else { + self.relations_store.has(hash) + } + } + + fn counts(&self) -> Result<(usize, usize), kaspa_database::prelude::StoreError> { + unimplemented!() + } +} + +impl PruningProofManager { + pub(crate) fn build_pruning_point_proof(&self, pp: Hash) -> PruningPointProof { + if pp == self.genesis_hash { + return vec![]; + } + + let (_db_lifetime, temp_db) = kaspa_database::create_temp_db!(ConnBuilder::default().with_files_limit(10)); + let pp_header = self.headers_store.get_header_with_block_level(pp).unwrap(); + let (ghostdag_stores, selected_tip_by_level, roots_by_level) = self.calc_gd_for_all_levels(&pp_header, temp_db); + + (0..=self.max_block_level) + .map(|level| { + let level = level as usize; + let selected_tip = selected_tip_by_level[level]; + let block_at_depth_2m = self + .block_at_depth(&*ghostdag_stores[level], selected_tip, 2 * self.pruning_proof_m) + .map_err(|err| format!("level: {}, err: {}", level, err)) + .unwrap(); + + // TODO (relaxed): remove the assertion below + // (New Logic) This is the root we calculated by going through block relations + let root = roots_by_level[level]; + // (Old Logic) This is the root we can calculate given that the GD records are already filled + // The root calc logic below is the original logic before the on-demand higher level GD calculation + // We only need old_root to sanity check the new logic + let old_root = if level != self.max_block_level as usize { + let block_at_depth_m_at_next_level = self + .block_at_depth(&*ghostdag_stores[level + 1], selected_tip_by_level[level + 1], self.pruning_proof_m) + .map_err(|err| format!("level + 1: {}, err: {}", level + 1, err)) + .unwrap(); + if self.reachability_service.is_dag_ancestor_of(block_at_depth_m_at_next_level, block_at_depth_2m) { + block_at_depth_m_at_next_level + } else if self.reachability_service.is_dag_ancestor_of(block_at_depth_2m, block_at_depth_m_at_next_level) { + block_at_depth_2m + } else { + self.find_common_ancestor_in_chain_of_a( + &*ghostdag_stores[level], + block_at_depth_m_at_next_level, + block_at_depth_2m, + ) + .map_err(|err| format!("level: {}, err: {}", level, err)) + .unwrap() + } + } else { + block_at_depth_2m + }; + + // new root is expected to be always an ancestor of old_root because new root takes a safety margin + assert!(self.reachability_service.is_dag_ancestor_of(root, old_root)); + + let mut headers = Vec::with_capacity(2 * self.pruning_proof_m as usize); + let mut queue = BinaryHeap::>::new(); + let mut visited = BlockHashSet::new(); + queue.push(Reverse(SortableBlock::new(root, self.headers_store.get_header(root).unwrap().blue_work))); + while let Some(current) = queue.pop() { + let current = current.0.hash; + if !visited.insert(current) { + continue; + } + + // The second condition is always expected to be true (ghostdag store will have the entry) + // because we are traversing the exact diamond (future(root) â‹‚ past(tip)) for which we calculated + // GD for (see fill_level_proof_ghostdag_data). TODO (relaxed): remove the condition or turn into assertion + if !self.reachability_service.is_dag_ancestor_of(current, selected_tip) + || !ghostdag_stores[level].has(current).is_ok_and(|found| found) + { + continue; + } + + headers.push(self.headers_store.get_header(current).unwrap()); + for child in self.relations_stores.read()[level].get_children(current).unwrap().read().iter().copied() { + queue.push(Reverse(SortableBlock::new(child, self.headers_store.get_header(child).unwrap().blue_work))); + } + } + + // TODO (relaxed): remove the assertion below + // Temp assertion for verifying a bug fix: assert that the full 2M chain is actually contained in the composed level proof + let set = BlockHashSet::from_iter(headers.iter().map(|h| h.hash)); + let chain_2m = self + .chain_up_to_depth(&*ghostdag_stores[level], selected_tip, 2 * self.pruning_proof_m) + .map_err(|err| { + dbg!(level, selected_tip, block_at_depth_2m, root); + format!("Assert 2M chain -- level: {}, err: {}", level, err) + }) + .unwrap(); + let chain_2m_len = chain_2m.len(); + for (i, chain_hash) in chain_2m.into_iter().enumerate() { + if !set.contains(&chain_hash) { + let next_level_tip = selected_tip_by_level[level + 1]; + let next_level_chain_m = + self.chain_up_to_depth(&*ghostdag_stores[level + 1], next_level_tip, self.pruning_proof_m).unwrap(); + let next_level_block_m = next_level_chain_m.last().copied().unwrap(); + dbg!(next_level_chain_m.len()); + dbg!(ghostdag_stores[level + 1].get_compact_data(next_level_tip).unwrap().blue_score); + dbg!(ghostdag_stores[level + 1].get_compact_data(next_level_block_m).unwrap().blue_score); + dbg!(ghostdag_stores[level].get_compact_data(selected_tip).unwrap().blue_score); + dbg!(ghostdag_stores[level].get_compact_data(block_at_depth_2m).unwrap().blue_score); + dbg!(level, selected_tip, block_at_depth_2m, root); + panic!("Assert 2M chain -- missing block {} at index {} out of {} chain blocks", chain_hash, i, chain_2m_len); + } + } + + headers + }) + .collect_vec() + } + + fn calc_gd_for_all_levels( + &self, + pp_header: &HeaderWithBlockLevel, + temp_db: Arc, + ) -> (Vec>, Vec, Vec) { + let current_dag_level = self.find_current_dag_level(&pp_header.header); + let mut ghostdag_stores: Vec>> = vec![None; self.max_block_level as usize + 1]; + let mut selected_tip_by_level = vec![None; self.max_block_level as usize + 1]; + let mut root_by_level = vec![None; self.max_block_level as usize + 1]; + for level in (0..=self.max_block_level).rev() { + let level_usize = level as usize; + let required_block = if level != self.max_block_level { + let next_level_store = ghostdag_stores[level_usize + 1].as_ref().unwrap().clone(); + let block_at_depth_m_at_next_level = self + .block_at_depth(&*next_level_store, selected_tip_by_level[level_usize + 1].unwrap(), self.pruning_proof_m) + .map_err(|err| format!("level + 1: {}, err: {}", level + 1, err)) + .unwrap(); + Some(block_at_depth_m_at_next_level) + } else { + None + }; + let (store, selected_tip, root) = self + .find_sufficient_root(pp_header, level, current_dag_level, required_block, temp_db.clone()) + .unwrap_or_else(|_| panic!("find_sufficient_root failed for level {level}")); + ghostdag_stores[level_usize] = Some(store); + selected_tip_by_level[level_usize] = Some(selected_tip); + root_by_level[level_usize] = Some(root); + } + + ( + ghostdag_stores.into_iter().map(Option::unwrap).collect_vec(), + selected_tip_by_level.into_iter().map(Option::unwrap).collect_vec(), + root_by_level.into_iter().map(Option::unwrap).collect_vec(), + ) + } + + /// Find a sufficient root at a given level by going through the headers store and looking + /// for a deep enough level block + /// For each root candidate, fill in the ghostdag data to see if it actually is deep enough. + /// If the root is deep enough, it will satisfy these conditions + /// 1. block at depth 2m at this level ∈ Future(root) + /// 2. block at depth m at the next level ∈ Future(root) + /// + /// Returns: the filled ghostdag store from root to tip, the selected tip and the root + fn find_sufficient_root( + &self, + pp_header: &HeaderWithBlockLevel, + level: BlockLevel, + current_dag_level: BlockLevel, + required_block: Option, + temp_db: Arc, + ) -> PruningProofManagerInternalResult<(Arc, Hash, Hash)> { + // Step 1: Determine which selected tip to use + let selected_tip = if pp_header.block_level >= level { + pp_header.header.hash + } else { + self.find_selected_parent_header_at_level(&pp_header.header, level)?.hash + }; + + let cache_policy = CachePolicy::Count(2 * self.pruning_proof_m as usize); + let required_level_depth = 2 * self.pruning_proof_m; + + // We only have the headers store (which has level 0 blue_scores) to assemble the proof data from. + // We need to look deeper at higher levels (2x deeper every level) to find 2M (plus margin) blocks at that level + let mut required_base_level_depth = self.estimated_blue_depth_at_level_0( + level, + required_level_depth + 100, // We take a safety margin + current_dag_level, + ); + + let mut is_last_level_header; + let mut tries = 0; + + let block_at_depth_m_at_next_level = required_block.unwrap_or(selected_tip); + + loop { + // Step 2 - Find a deep enough root candidate + let block_at_depth_2m = match self.level_block_at_base_depth(level, selected_tip, required_base_level_depth) { + Ok((header, is_last_header)) => { + is_last_level_header = is_last_header; + header + } + Err(e) => return Err(e), + }; + + let root = if self.reachability_service.is_dag_ancestor_of(block_at_depth_2m, block_at_depth_m_at_next_level) { + block_at_depth_2m + } else if self.reachability_service.is_dag_ancestor_of(block_at_depth_m_at_next_level, block_at_depth_2m) { + block_at_depth_m_at_next_level + } else { + // find common ancestor of block_at_depth_m_at_next_level and block_at_depth_2m in chain of block_at_depth_m_at_next_level + let mut common_ancestor = self.headers_store.get_header(block_at_depth_m_at_next_level).unwrap(); + + while !self.reachability_service.is_dag_ancestor_of(common_ancestor.hash, block_at_depth_2m) { + common_ancestor = match self.find_selected_parent_header_at_level(&common_ancestor, level) { + Ok(header) => header, + // Try to give this last header a chance at being root + Err(PruningProofManagerInternalError::NotEnoughHeadersToBuildProof(_)) => break, + Err(e) => return Err(e), + }; + } + + common_ancestor.hash + }; + + if level == 0 { + return Ok((self.ghostdag_store.clone(), selected_tip, root)); + } + + // Step 3 - Fill the ghostdag data from root to tip + let ghostdag_store = Arc::new(DbGhostdagStore::new_temp(temp_db.clone(), level, cache_policy, cache_policy, tries)); + let has_required_block = self.fill_level_proof_ghostdag_data( + root, + pp_header.header.hash, + &ghostdag_store, + Some(block_at_depth_m_at_next_level), + level, + ); + + // Step 4 - Check if we actually have enough depth. + // Need to ensure this does the same 2M+1 depth that block_at_depth does + if has_required_block + && (root == self.genesis_hash || ghostdag_store.get_blue_score(selected_tip).unwrap() >= required_level_depth) + { + break Ok((ghostdag_store, selected_tip, root)); + } + + tries += 1; + if is_last_level_header { + if has_required_block { + // Normally this scenario doesn't occur when syncing with nodes that already have the safety margin change in place. + // However, when syncing with an older node version that doesn't have a safety margin for the proof, it's possible to + // try to find 2500 depth worth of headers at a level, but the proof only contains about 2000 headers. To be able to sync + // with such an older node. As long as we found the required block, we can still proceed. + debug!("Failed to find sufficient root for level {level} after {tries} tries. Headers below the current depth of {required_base_level_depth} are already pruned. Required block found so trying anyway."); + break Ok((ghostdag_store, selected_tip, root)); + } else { + panic!("Failed to find sufficient root for level {level} after {tries} tries. Headers below the current depth of {required_base_level_depth} are already pruned"); + } + } + + // If we don't have enough depth now, we need to look deeper + required_base_level_depth = (required_base_level_depth as f64 * 1.1) as u64; + debug!("Failed to find sufficient root for level {level} after {tries} tries. Retrying again to find with depth {required_base_level_depth}"); + } + } + + /// BFS forward iterates from root until selected tip, ignoring blocks in the antipast of selected_tip. + /// For each block along the way, insert that hash into the ghostdag_store + /// If we have a required_block to find, this will return true if that block was found along the way + fn fill_level_proof_ghostdag_data( + &self, + root: Hash, + selected_tip: Hash, + ghostdag_store: &Arc, + required_block: Option, + level: BlockLevel, + ) -> bool { + let relations_service = RelationsStoreInFutureOfRoot { + relations_store: self.level_relations_services[level as usize].clone(), + reachability_service: self.reachability_service.clone(), + root, + }; + let gd_manager = GhostdagManager::new( + root, + self.ghostdag_k, + ghostdag_store.clone(), + relations_service.clone(), + self.headers_store.clone(), + self.reachability_service.clone(), + level != 0, + ); + + ghostdag_store.insert(root, Arc::new(gd_manager.genesis_ghostdag_data())).unwrap(); + ghostdag_store.insert(ORIGIN, gd_manager.origin_ghostdag_data()).unwrap(); + + let mut topological_heap: BinaryHeap<_> = Default::default(); + let mut visited = BlockHashSet::new(); + for child in relations_service.get_children(root).unwrap().read().iter().copied() { + topological_heap.push(Reverse(SortableBlock { + hash: child, + // It's important to use here blue work and not score so we can iterate the heap in a way that respects the topology + blue_work: self.headers_store.get_header(child).unwrap().blue_work, + })); + } + + let mut has_required_block = required_block.is_some_and(|required_block| root == required_block); + loop { + let Some(current) = topological_heap.pop() else { + break; + }; + let current_hash = current.0.hash; + if !visited.insert(current_hash) { + continue; + } + + if !self.reachability_service.is_dag_ancestor_of(current_hash, selected_tip) { + // We don't care about blocks in the antipast of the selected tip + continue; + } + + if !has_required_block && required_block.is_some_and(|required_block| current_hash == required_block) { + has_required_block = true; + } + + let current_gd = gd_manager.ghostdag(&relations_service.get_parents(current_hash).unwrap()); + + ghostdag_store.insert(current_hash, Arc::new(current_gd)).unwrap_or_exists(); + + for child in relations_service.get_children(current_hash).unwrap().read().iter().copied() { + topological_heap.push(Reverse(SortableBlock { + hash: child, + // It's important to use here blue work and not score so we can iterate the heap in a way that respects the topology + blue_work: self.headers_store.get_header(child).unwrap().blue_work, + })); + } + } + + has_required_block + } + + // The "current dag level" is the level right before the level whose parents are + // not the same as our header's direct parents + // + // Find the current DAG level by going through all the parents at each level, + // starting from the bottom level and see which is the first level that has + // parents that are NOT our current pp_header's direct parents. + fn find_current_dag_level(&self, pp_header: &Header) -> BlockLevel { + let direct_parents = BlockHashSet::from_iter(pp_header.direct_parents().iter().copied()); + pp_header + .parents_by_level + .iter() + .enumerate() + .skip(1) + .find_map(|(level, parents)| { + if BlockHashSet::from_iter(parents.iter().copied()) == direct_parents { + None + } else { + Some((level - 1) as BlockLevel) + } + }) + .unwrap_or(self.max_block_level) + } + + fn estimated_blue_depth_at_level_0(&self, level: BlockLevel, level_depth: u64, current_dag_level: BlockLevel) -> u64 { + level_depth.checked_shl(level.saturating_sub(current_dag_level) as u32).unwrap_or(level_depth) + } + + /// selected parent at level = the parent of the header at the level + /// with the highest blue_work + fn find_selected_parent_header_at_level( + &self, + header: &Header, + level: BlockLevel, + ) -> PruningProofManagerInternalResult> { + // Parents manager parents_at_level may return parents that aren't in relations_service, so it's important + // to filter to include only parents that are in relations_service. + let sp = self + .parents_manager + .parents_at_level(header, level) + .iter() + .copied() + .filter(|p| self.level_relations_services[level as usize].has(*p).unwrap()) + .filter_map(|p| self.headers_store.get_header(p).unwrap_option().map(|h| SortableBlock::new(p, h.blue_work))) + .max() + .ok_or(PruningProofManagerInternalError::NotEnoughHeadersToBuildProof("no parents with header".to_string()))?; + Ok(self.headers_store.get_header(sp.hash).expect("unwrapped above")) + } + + /// Finds the block on a given level that is at base_depth deep from it. + /// Also returns if the block was the last one in the level + /// base_depth = the blue score depth at level 0 + fn level_block_at_base_depth( + &self, + level: BlockLevel, + high: Hash, + base_depth: u64, + ) -> PruningProofManagerInternalResult<(Hash, bool)> { + let high_header = self + .headers_store + .get_header(high) + .map_err(|err| PruningProofManagerInternalError::BlockAtDepth(format!("high: {high}, depth: {base_depth}, {err}")))?; + let high_header_score = high_header.blue_score; + let mut current_header = high_header; + + let mut is_last_header = false; + + while current_header.blue_score + base_depth >= high_header_score { + if current_header.direct_parents().is_empty() { + break; + } + + current_header = match self.find_selected_parent_header_at_level(¤t_header, level) { + Ok(header) => header, + Err(PruningProofManagerInternalError::NotEnoughHeadersToBuildProof(_)) => { + // We want to give this root a shot if all its past is pruned + is_last_header = true; + break; + } + Err(e) => return Err(e), + }; + } + Ok((current_header.hash, is_last_header)) + } + + /// Copy of `block_at_depth` which returns the full chain up to depth. Temporarily used for assertion purposes. + fn chain_up_to_depth( + &self, + ghostdag_store: &impl GhostdagStoreReader, + high: Hash, + depth: u64, + ) -> Result, PruningProofManagerInternalError> { + let high_gd = ghostdag_store + .get_compact_data(high) + .map_err(|err| PruningProofManagerInternalError::BlockAtDepth(format!("high: {high}, depth: {depth}, {err}")))?; + let mut current_gd = high_gd; + let mut current = high; + let mut res = vec![current]; + while current_gd.blue_score + depth >= high_gd.blue_score { + if current_gd.selected_parent.is_origin() { + break; + } + let prev = current; + current = current_gd.selected_parent; + res.push(current); + current_gd = ghostdag_store.get_compact_data(current).map_err(|err| { + PruningProofManagerInternalError::BlockAtDepth(format!( + "high: {}, depth: {}, current: {}, high blue score: {}, current blue score: {}, {}", + high, depth, prev, high_gd.blue_score, current_gd.blue_score, err + )) + })?; + } + Ok(res) + } + + fn find_common_ancestor_in_chain_of_a( + &self, + ghostdag_store: &impl GhostdagStoreReader, + a: Hash, + b: Hash, + ) -> Result { + let a_gd = ghostdag_store + .get_compact_data(a) + .map_err(|err| PruningProofManagerInternalError::FindCommonAncestor(format!("a: {a}, b: {b}, {err}")))?; + let mut current_gd = a_gd; + let mut current; + let mut loop_counter = 0; + loop { + current = current_gd.selected_parent; + loop_counter += 1; + if current.is_origin() { + break Err(PruningProofManagerInternalError::NoCommonAncestor(format!("a: {a}, b: {b} ({loop_counter} loop steps)"))); + } + if self.reachability_service.is_dag_ancestor_of(current, b) { + break Ok(current); + } + current_gd = ghostdag_store + .get_compact_data(current) + .map_err(|err| PruningProofManagerInternalError::FindCommonAncestor(format!("a: {a}, b: {b}, {err}")))?; + } + } +} diff --git a/consensus/src/processes/pruning_proof/mod.rs b/consensus/src/processes/pruning_proof/mod.rs index e9690ec38..2b3ba5f9d 100644 --- a/consensus/src/processes/pruning_proof/mod.rs +++ b/consensus/src/processes/pruning_proof/mod.rs @@ -1,40 +1,32 @@ +mod apply; +mod build; +mod validate; + use std::{ - cmp::{max, Reverse}, collections::{ - hash_map::Entry::{self, Vacant}, - BinaryHeap, HashSet, VecDeque, - }, - ops::{Deref, DerefMut}, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, + hash_map::Entry::{self}, + VecDeque, }, + ops::Deref, + sync::{atomic::AtomicBool, Arc}, }; use itertools::Itertools; -use kaspa_math::int::SignedInteger; use parking_lot::{Mutex, RwLock}; use rocksdb::WriteBatch; use kaspa_consensus_core::{ - blockhash::{self, BlockHashExtensions, BlockHashes, ORIGIN}, - errors::{ - consensus::{ConsensusError, ConsensusResult}, - pruning::{PruningImportError, PruningImportResult}, - }, + blockhash::{self, BlockHashExtensions}, + errors::consensus::{ConsensusError, ConsensusResult}, header::Header, pruning::{PruningPointProof, PruningPointTrustedData}, - trusted::{TrustedBlock, TrustedGhostdagData, TrustedHeader}, + trusted::{TrustedGhostdagData, TrustedHeader}, BlockHashMap, BlockHashSet, BlockLevel, HashMapCustomHasher, KType, }; -use kaspa_core::{debug, info, trace}; -use kaspa_database::{ - prelude::{CachePolicy, ConnBuilder, StoreError, StoreResult, StoreResultEmptyTuple, StoreResultExtensions}, - utils::DbLifetime, -}; +use kaspa_core::info; +use kaspa_database::{prelude::StoreResultExtensions, utils::DbLifetime}; use kaspa_hashes::Hash; use kaspa_pow::calc_block_level; -use kaspa_utils::{binary_heap::BinaryHeapExtensions, vec::VecExtensions}; use thiserror::Error; use crate::{ @@ -43,35 +35,26 @@ use crate::{ storage::ConsensusStorage, }, model::{ - services::{ - reachability::{MTReachabilityService, ReachabilityService}, - relations::MTRelationsService, - }, + services::{reachability::MTReachabilityService, relations::MTRelationsService}, stores::{ depth::DbDepthStore, - ghostdag::{CompactGhostdagData, DbGhostdagStore, GhostdagData, GhostdagStore, GhostdagStoreReader}, - headers::{DbHeadersStore, HeaderStore, HeaderStoreReader, HeaderWithBlockLevel}, + ghostdag::{DbGhostdagStore, GhostdagStoreReader}, + headers::{DbHeadersStore, HeaderStore, HeaderStoreReader}, headers_selected_tip::DbHeadersSelectedTipStore, past_pruning_points::{DbPastPruningPointsStore, PastPruningPointsStore}, pruning::{DbPruningStore, PruningStoreReader}, - reachability::{DbReachabilityStore, ReachabilityStoreReader, StagingReachabilityStore}, - relations::{DbRelationsStore, RelationsStoreReader, StagingRelationsStore}, - selected_chain::{DbSelectedChainStore, SelectedChainStore}, + reachability::DbReachabilityStore, + relations::{DbRelationsStore, RelationsStoreReader}, + selected_chain::DbSelectedChainStore, tips::DbTipsStore, - virtual_state::{VirtualState, VirtualStateStore, VirtualStateStoreReader, VirtualStores}, + virtual_state::{VirtualStateStoreReader, VirtualStores}, DB, }, }, - processes::{ - ghostdag::ordering::SortableBlock, reachability::inquirer as reachability, relations::RelationsStoreExtensions, - window::WindowType, - }, + processes::window::WindowType, }; -use super::{ - ghostdag::{mergeset::unordered_mergeset_without_selected_parent, protocol::GhostdagManager}, - window::WindowManager, -}; +use super::{ghostdag::protocol::GhostdagManager, window::WindowManager}; #[derive(Error, Debug)] enum PruningProofManagerInternalError { @@ -110,39 +93,6 @@ struct TempProofContext { db_lifetime: DbLifetime, } -#[derive(Clone)] -struct RelationsStoreInFutureOfRoot { - relations_store: T, - reachability_service: U, - root: Hash, -} - -impl RelationsStoreReader for RelationsStoreInFutureOfRoot { - fn get_parents(&self, hash: Hash) -> Result { - self.relations_store.get_parents(hash).map(|hashes| { - Arc::new(hashes.iter().copied().filter(|h| self.reachability_service.is_dag_ancestor_of(self.root, *h)).collect_vec()) - }) - } - - fn get_children(&self, hash: Hash) -> StoreResult> { - // We assume hash is in future of root - assert!(self.reachability_service.is_dag_ancestor_of(self.root, hash)); - self.relations_store.get_children(hash) - } - - fn has(&self, hash: Hash) -> Result { - if self.reachability_service.is_dag_ancestor_of(self.root, hash) { - Ok(false) - } else { - self.relations_store.has(hash) - } - } - - fn counts(&self) -> Result<(usize, usize), kaspa_database::prelude::StoreError> { - unimplemented!() - } -} - pub struct PruningProofManager { db: Arc, @@ -241,10 +191,7 @@ impl PruningProofManager { continue; } - let state = kaspa_pow::State::new(header); - let (_, pow) = state.check_pow(header.nonce); - let signed_block_level = self.max_block_level as i64 - pow.bits() as i64; - let block_level = max(signed_block_level, 0) as BlockLevel; + let block_level = calc_block_level(header, self.max_block_level); self.headers_store.insert(header.hash, header.clone(), block_level).unwrap(); } @@ -259,949 +206,14 @@ impl PruningProofManager { drop(pruning_point_write); } - pub fn apply_proof(&self, mut proof: PruningPointProof, trusted_set: &[TrustedBlock]) -> PruningImportResult<()> { - let pruning_point_header = proof[0].last().unwrap().clone(); - let pruning_point = pruning_point_header.hash; - - // Create a copy of the proof, since we're going to be mutating the proof passed to us - let proof_sets = (0..=self.max_block_level) - .map(|level| BlockHashSet::from_iter(proof[level as usize].iter().map(|header| header.hash))) - .collect_vec(); - - let mut trusted_gd_map: BlockHashMap = BlockHashMap::new(); - for tb in trusted_set.iter() { - trusted_gd_map.insert(tb.block.hash(), tb.ghostdag.clone().into()); - let tb_block_level = calc_block_level(&tb.block.header, self.max_block_level); - - (0..=tb_block_level).for_each(|current_proof_level| { - // If this block was in the original proof, ignore it - if proof_sets[current_proof_level as usize].contains(&tb.block.hash()) { - return; - } - - proof[current_proof_level as usize].push(tb.block.header.clone()); - }); - } - - proof.iter_mut().for_each(|level_proof| { - level_proof.sort_by(|a, b| a.blue_work.cmp(&b.blue_work)); - }); - - self.populate_reachability_and_headers(&proof); - - { - let reachability_read = self.reachability_store.read(); - for tb in trusted_set.iter() { - // Header-only trusted blocks are expected to be in pruning point past - if tb.block.is_header_only() && !reachability_read.is_dag_ancestor_of(tb.block.hash(), pruning_point) { - return Err(PruningImportError::PruningPointPastMissingReachability(tb.block.hash())); - } - } - } - - for (level, headers) in proof.iter().enumerate() { - trace!("Applying level {} from the pruning point proof", level); - let mut level_ancestors: HashSet = HashSet::new(); - level_ancestors.insert(ORIGIN); - - for header in headers.iter() { - let parents = Arc::new( - self.parents_manager - .parents_at_level(header, level as BlockLevel) - .iter() - .copied() - .filter(|parent| level_ancestors.contains(parent)) - .collect_vec() - .push_if_empty(ORIGIN), - ); - - self.relations_stores.write()[level].insert(header.hash, parents.clone()).unwrap(); - - if level == 0 { - let gd = if let Some(gd) = trusted_gd_map.get(&header.hash) { - gd.clone() - } else { - let calculated_gd = self.ghostdag_manager.ghostdag(&parents); - // Override the ghostdag data with the real blue score and blue work - GhostdagData { - blue_score: header.blue_score, - blue_work: header.blue_work, - selected_parent: calculated_gd.selected_parent, - mergeset_blues: calculated_gd.mergeset_blues, - mergeset_reds: calculated_gd.mergeset_reds, - blues_anticone_sizes: calculated_gd.blues_anticone_sizes, - } - }; - self.ghostdag_store.insert(header.hash, Arc::new(gd)).unwrap(); - } - - level_ancestors.insert(header.hash); - } - } - - let virtual_parents = vec![pruning_point]; - let virtual_state = Arc::new(VirtualState { - parents: virtual_parents.clone(), - ghostdag_data: self.ghostdag_manager.ghostdag(&virtual_parents), - ..VirtualState::default() - }); - self.virtual_stores.write().state.set(virtual_state).unwrap(); - - let mut batch = WriteBatch::default(); - self.body_tips_store.write().init_batch(&mut batch, &virtual_parents).unwrap(); - self.headers_selected_tip_store - .write() - .set_batch(&mut batch, SortableBlock { hash: pruning_point, blue_work: pruning_point_header.blue_work }) - .unwrap(); - self.selected_chain_store.write().init_with_pruning_point(&mut batch, pruning_point).unwrap(); - self.depth_store.insert_batch(&mut batch, pruning_point, ORIGIN, ORIGIN).unwrap(); - self.db.write(batch).unwrap(); - - Ok(()) - } - + // Used in apply and validate fn estimate_proof_unique_size(&self, proof: &PruningPointProof) -> usize { let approx_history_size = proof[0][0].daa_score; let approx_unique_full_levels = f64::log2(approx_history_size as f64 / self.pruning_proof_m as f64).max(0f64) as usize; proof.iter().map(|l| l.len()).sum::().min((approx_unique_full_levels + 1) * self.pruning_proof_m as usize) } - pub fn populate_reachability_and_headers(&self, proof: &PruningPointProof) { - let capacity_estimate = self.estimate_proof_unique_size(proof); - let mut dag = BlockHashMap::with_capacity(capacity_estimate); - let mut up_heap = BinaryHeap::with_capacity(capacity_estimate); - for header in proof.iter().flatten().cloned() { - if let Vacant(e) = dag.entry(header.hash) { - let state = kaspa_pow::State::new(&header); - let (_, pow) = state.check_pow(header.nonce); // TODO: Check if pow passes - let signed_block_level = self.max_block_level as i64 - pow.bits() as i64; - let block_level = max(signed_block_level, 0) as BlockLevel; - self.headers_store.insert(header.hash, header.clone(), block_level).unwrap(); - - let mut parents = BlockHashSet::with_capacity(header.direct_parents().len() * 2); - // We collect all available parent relations in order to maximize reachability information. - // By taking into account parents from all levels we ensure that the induced DAG has valid - // reachability information for each level-specific sub-DAG -- hence a single reachability - // oracle can serve them all - for level in 0..=self.max_block_level { - for parent in self.parents_manager.parents_at_level(&header, level) { - parents.insert(*parent); - } - } - - struct DagEntry { - header: Arc
, - parents: Arc, - } - - up_heap.push(Reverse(SortableBlock { hash: header.hash, blue_work: header.blue_work })); - e.insert(DagEntry { header, parents: Arc::new(parents) }); - } - } - - debug!("Estimated proof size: {}, actual size: {}", capacity_estimate, dag.len()); - - for reverse_sortable_block in up_heap.into_sorted_iter() { - // TODO: Convert to into_iter_sorted once it gets stable - let hash = reverse_sortable_block.0.hash; - let dag_entry = dag.get(&hash).unwrap(); - - // Filter only existing parents - let parents_in_dag = BinaryHeap::from_iter( - dag_entry - .parents - .iter() - .cloned() - .filter(|parent| dag.contains_key(parent)) - .map(|parent| SortableBlock { hash: parent, blue_work: dag.get(&parent).unwrap().header.blue_work }), - ); - - let reachability_read = self.reachability_store.upgradable_read(); - - // Find the maximal parent antichain from the possibly redundant set of existing parents - let mut reachability_parents: Vec = Vec::new(); - for parent in parents_in_dag.into_sorted_iter() { - if reachability_read.is_dag_ancestor_of_any(parent.hash, &mut reachability_parents.iter().map(|parent| parent.hash)) { - continue; - } - - reachability_parents.push(parent); - } - let reachability_parents_hashes = - BlockHashes::new(reachability_parents.iter().map(|parent| parent.hash).collect_vec().push_if_empty(ORIGIN)); - let selected_parent = reachability_parents.iter().max().map(|parent| parent.hash).unwrap_or(ORIGIN); - - // Prepare batch - let mut batch = WriteBatch::default(); - let mut reachability_relations_write = self.reachability_relations_store.write(); - let mut staging_reachability = StagingReachabilityStore::new(reachability_read); - let mut staging_reachability_relations = StagingRelationsStore::new(&mut reachability_relations_write); - - // Stage - staging_reachability_relations.insert(hash, reachability_parents_hashes.clone()).unwrap(); - let mergeset = unordered_mergeset_without_selected_parent( - &staging_reachability_relations, - &staging_reachability, - selected_parent, - &reachability_parents_hashes, - ); - reachability::add_block(&mut staging_reachability, hash, selected_parent, &mut mergeset.iter().copied()).unwrap(); - - // Commit - let reachability_write = staging_reachability.commit(&mut batch).unwrap(); - staging_reachability_relations.commit(&mut batch).unwrap(); - - // Write - self.db.write(batch).unwrap(); - - // Drop - drop(reachability_write); - drop(reachability_relations_write); - } - } - - fn init_validate_pruning_point_proof_stores_and_processes( - &self, - proof: &PruningPointProof, - ) -> PruningImportResult { - if proof[0].is_empty() { - return Err(PruningImportError::PruningProofNotEnoughHeaders); - } - - let headers_estimate = self.estimate_proof_unique_size(proof); - - let (db_lifetime, db) = kaspa_database::create_temp_db!(ConnBuilder::default().with_files_limit(10)); - let cache_policy = CachePolicy::Count(2 * self.pruning_proof_m as usize); - let headers_store = - Arc::new(DbHeadersStore::new(db.clone(), CachePolicy::Count(headers_estimate), CachePolicy::Count(headers_estimate))); - let ghostdag_stores = (0..=self.max_block_level) - .map(|level| Arc::new(DbGhostdagStore::new(db.clone(), level, cache_policy, cache_policy))) - .collect_vec(); - let mut relations_stores = - (0..=self.max_block_level).map(|level| DbRelationsStore::new(db.clone(), level, cache_policy, cache_policy)).collect_vec(); - let reachability_stores = (0..=self.max_block_level) - .map(|level| Arc::new(RwLock::new(DbReachabilityStore::with_block_level(db.clone(), cache_policy, cache_policy, level)))) - .collect_vec(); - - let reachability_services = (0..=self.max_block_level) - .map(|level| MTReachabilityService::new(reachability_stores[level as usize].clone())) - .collect_vec(); - - let ghostdag_managers = ghostdag_stores - .iter() - .cloned() - .enumerate() - .map(|(level, ghostdag_store)| { - GhostdagManager::new( - self.genesis_hash, - self.ghostdag_k, - ghostdag_store, - relations_stores[level].clone(), - headers_store.clone(), - reachability_services[level].clone(), - level != 0, - ) - }) - .collect_vec(); - - { - let mut batch = WriteBatch::default(); - for level in 0..=self.max_block_level { - let level = level as usize; - reachability::init(reachability_stores[level].write().deref_mut()).unwrap(); - relations_stores[level].insert_batch(&mut batch, ORIGIN, BlockHashes::new(vec![])).unwrap(); - ghostdag_stores[level].insert(ORIGIN, ghostdag_managers[level].origin_ghostdag_data()).unwrap(); - } - - db.write(batch).unwrap(); - } - - Ok(TempProofContext { db_lifetime, headers_store, ghostdag_stores, relations_stores, reachability_stores, ghostdag_managers }) - } - - fn populate_stores_for_validate_pruning_point_proof( - &self, - proof: &PruningPointProof, - ctx: &mut TempProofContext, - log_validating: bool, - ) -> PruningImportResult> { - let headers_store = &ctx.headers_store; - let ghostdag_stores = &ctx.ghostdag_stores; - let mut relations_stores = ctx.relations_stores.clone(); - let reachability_stores = &ctx.reachability_stores; - let ghostdag_managers = &ctx.ghostdag_managers; - - let proof_pp_header = proof[0].last().expect("checked if empty"); - let proof_pp = proof_pp_header.hash; - - let mut selected_tip_by_level = vec![None; self.max_block_level as usize + 1]; - for level in (0..=self.max_block_level).rev() { - // Before processing this level, check if the process is exiting so we can end early - if self.is_consensus_exiting.load(Ordering::Relaxed) { - return Err(PruningImportError::PruningValidationInterrupted); - } - - if log_validating { - info!("Validating level {level} from the pruning point proof ({} headers)", proof[level as usize].len()); - } - let level_idx = level as usize; - let mut selected_tip = None; - for (i, header) in proof[level as usize].iter().enumerate() { - let header_level = calc_block_level(header, self.max_block_level); - if header_level < level { - return Err(PruningImportError::PruningProofWrongBlockLevel(header.hash, header_level, level)); - } - - headers_store.insert(header.hash, header.clone(), header_level).unwrap_or_exists(); - - let parents = self - .parents_manager - .parents_at_level(header, level) - .iter() - .copied() - .filter(|parent| ghostdag_stores[level_idx].has(*parent).unwrap()) - .collect_vec(); - - // Only the first block at each level is allowed to have no known parents - if parents.is_empty() && i != 0 { - return Err(PruningImportError::PruningProofHeaderWithNoKnownParents(header.hash, level)); - } - - let parents: BlockHashes = parents.push_if_empty(ORIGIN).into(); - - if relations_stores[level_idx].has(header.hash).unwrap() { - return Err(PruningImportError::PruningProofDuplicateHeaderAtLevel(header.hash, level)); - } - - relations_stores[level_idx].insert(header.hash, parents.clone()).unwrap(); - let ghostdag_data = Arc::new(ghostdag_managers[level_idx].ghostdag(&parents)); - ghostdag_stores[level_idx].insert(header.hash, ghostdag_data.clone()).unwrap(); - selected_tip = Some(match selected_tip { - Some(tip) => ghostdag_managers[level_idx].find_selected_parent([tip, header.hash]), - None => header.hash, - }); - - let mut reachability_mergeset = { - let reachability_read = reachability_stores[level_idx].read(); - ghostdag_data - .unordered_mergeset_without_selected_parent() - .filter(|hash| reachability_read.has(*hash).unwrap()) - .collect_vec() // We collect to vector so reachability_read can be released and let `reachability::add_block` use a write lock. - .into_iter() - }; - reachability::add_block( - reachability_stores[level_idx].write().deref_mut(), - header.hash, - ghostdag_data.selected_parent, - &mut reachability_mergeset, - ) - .unwrap(); - - if selected_tip.unwrap() == header.hash { - reachability::hint_virtual_selected_parent(reachability_stores[level_idx].write().deref_mut(), header.hash) - .unwrap(); - } - } - - if level < self.max_block_level { - let block_at_depth_m_at_next_level = self - .block_at_depth( - &*ghostdag_stores[level_idx + 1], - selected_tip_by_level[level_idx + 1].unwrap(), - self.pruning_proof_m, - ) - .unwrap(); - if !relations_stores[level_idx].has(block_at_depth_m_at_next_level).unwrap() { - return Err(PruningImportError::PruningProofMissingBlockAtDepthMFromNextLevel(level, level + 1)); - } - } - - if selected_tip.unwrap() != proof_pp - && !self.parents_manager.parents_at_level(proof_pp_header, level).contains(&selected_tip.unwrap()) - { - return Err(PruningImportError::PruningProofMissesBlocksBelowPruningPoint(selected_tip.unwrap(), level)); - } - - selected_tip_by_level[level_idx] = selected_tip; - } - - Ok(selected_tip_by_level.into_iter().map(|selected_tip| selected_tip.unwrap()).collect()) - } - - fn validate_proof_selected_tip( - &self, - proof_selected_tip: Hash, - level: BlockLevel, - proof_pp_level: BlockLevel, - proof_pp: Hash, - proof_pp_header: &Header, - ) -> PruningImportResult<()> { - // A proof selected tip of some level has to be the proof suggested prunint point itself if its level - // is lower or equal to the pruning point level, or a parent of the pruning point on the relevant level - // otherwise. - if level <= proof_pp_level { - if proof_selected_tip != proof_pp { - return Err(PruningImportError::PruningProofSelectedTipIsNotThePruningPoint(proof_selected_tip, level)); - } - } else if !self.parents_manager.parents_at_level(proof_pp_header, level).contains(&proof_selected_tip) { - return Err(PruningImportError::PruningProofSelectedTipNotParentOfPruningPoint(proof_selected_tip, level)); - } - - Ok(()) - } - - // find_proof_and_consensus_common_chain_ancestor_ghostdag_data returns an option of a tuple - // that contains the ghostdag data of the proof and current consensus common ancestor. If no - // such ancestor exists, it returns None. - fn find_proof_and_consensus_common_ancestor_ghostdag_data( - &self, - proof_ghostdag_stores: &[Arc], - current_consensus_ghostdag_stores: &[Arc], - proof_selected_tip: Hash, - level: BlockLevel, - proof_selected_tip_gd: CompactGhostdagData, - ) -> Option<(CompactGhostdagData, CompactGhostdagData)> { - let mut proof_current = proof_selected_tip; - let mut proof_current_gd = proof_selected_tip_gd; - loop { - match current_consensus_ghostdag_stores[level as usize].get_compact_data(proof_current).unwrap_option() { - Some(current_gd) => { - break Some((proof_current_gd, current_gd)); - } - None => { - proof_current = proof_current_gd.selected_parent; - if proof_current.is_origin() { - break None; - } - proof_current_gd = proof_ghostdag_stores[level as usize].get_compact_data(proof_current).unwrap(); - } - }; - } - } - - pub fn validate_pruning_point_proof(&self, proof: &PruningPointProof) -> PruningImportResult<()> { - if proof.len() != self.max_block_level as usize + 1 { - return Err(PruningImportError::ProofNotEnoughLevels(self.max_block_level as usize + 1)); - } - - // Initialize the stores for the proof - let mut proof_stores_and_processes = self.init_validate_pruning_point_proof_stores_and_processes(proof)?; - let proof_pp_header = proof[0].last().expect("checked if empty"); - let proof_pp = proof_pp_header.hash; - let proof_pp_level = calc_block_level(proof_pp_header, self.max_block_level); - let proof_selected_tip_by_level = - self.populate_stores_for_validate_pruning_point_proof(proof, &mut proof_stores_and_processes, true)?; - let proof_ghostdag_stores = proof_stores_and_processes.ghostdag_stores; - - // Get the proof for the current consensus and recreate the stores for it - // This is expected to be fast because if a proof exists, it will be cached. - // If no proof exists, this is empty - let mut current_consensus_proof = self.get_pruning_point_proof(); - if current_consensus_proof.is_empty() { - // An empty proof can only happen if we're at genesis. We're going to create a proof for this case that contains the genesis header only - let genesis_header = self.headers_store.get_header(self.genesis_hash).unwrap(); - current_consensus_proof = Arc::new((0..=self.max_block_level).map(|_| vec![genesis_header.clone()]).collect_vec()); - } - let mut current_consensus_stores_and_processes = - self.init_validate_pruning_point_proof_stores_and_processes(¤t_consensus_proof)?; - let _ = self.populate_stores_for_validate_pruning_point_proof( - ¤t_consensus_proof, - &mut current_consensus_stores_and_processes, - false, - )?; - let current_consensus_ghostdag_stores = current_consensus_stores_and_processes.ghostdag_stores; - - let pruning_read = self.pruning_point_store.read(); - let relations_read = self.relations_stores.read(); - let current_pp = pruning_read.get().unwrap().pruning_point; - let current_pp_header = self.headers_store.get_header(current_pp).unwrap(); - - for (level_idx, selected_tip) in proof_selected_tip_by_level.iter().copied().enumerate() { - let level = level_idx as BlockLevel; - self.validate_proof_selected_tip(selected_tip, level, proof_pp_level, proof_pp, proof_pp_header)?; - - let proof_selected_tip_gd = proof_ghostdag_stores[level_idx].get_compact_data(selected_tip).unwrap(); - - // Next check is to see if this proof is "better" than what's in the current consensus - // Step 1 - look at only levels that have a full proof (least 2m blocks in the proof) - if proof_selected_tip_gd.blue_score < 2 * self.pruning_proof_m { - continue; - } - - // Step 2 - if we can find a common ancestor between the proof and current consensus - // we can determine if the proof is better. The proof is better if the blue work* difference between the - // old current consensus's tips and the common ancestor is less than the blue work difference between the - // proof's tip and the common ancestor. - // *Note: blue work is the same as blue score on levels higher than 0 - if let Some((proof_common_ancestor_gd, common_ancestor_gd)) = self.find_proof_and_consensus_common_ancestor_ghostdag_data( - &proof_ghostdag_stores, - ¤t_consensus_ghostdag_stores, - selected_tip, - level, - proof_selected_tip_gd, - ) { - let selected_tip_blue_work_diff = - SignedInteger::from(proof_selected_tip_gd.blue_work) - SignedInteger::from(proof_common_ancestor_gd.blue_work); - for parent in self.parents_manager.parents_at_level(¤t_pp_header, level).iter().copied() { - let parent_blue_work = current_consensus_ghostdag_stores[level_idx].get_blue_work(parent).unwrap(); - let parent_blue_work_diff = - SignedInteger::from(parent_blue_work) - SignedInteger::from(common_ancestor_gd.blue_work); - if parent_blue_work_diff >= selected_tip_blue_work_diff { - return Err(PruningImportError::PruningProofInsufficientBlueWork); - } - } - - return Ok(()); - } - } - - if current_pp == self.genesis_hash { - // If the proof has better tips and the current pruning point is still - // genesis, we consider the proof state to be better. - return Ok(()); - } - - // If we got here it means there's no level with shared blocks - // between the proof and the current consensus. In this case we - // consider the proof to be better if it has at least one level - // with 2*self.pruning_proof_m blue blocks where consensus doesn't. - for level in (0..=self.max_block_level).rev() { - let level_idx = level as usize; - - let proof_selected_tip = proof_selected_tip_by_level[level_idx]; - let proof_selected_tip_gd = proof_ghostdag_stores[level_idx].get_compact_data(proof_selected_tip).unwrap(); - if proof_selected_tip_gd.blue_score < 2 * self.pruning_proof_m { - continue; - } - - match relations_read[level_idx].get_parents(current_pp).unwrap_option() { - Some(parents) => { - if parents.iter().copied().any(|parent| { - current_consensus_ghostdag_stores[level_idx].get_blue_score(parent).unwrap() < 2 * self.pruning_proof_m - }) { - return Ok(()); - } - } - None => { - // If the current pruning point doesn't have a parent at this level, we consider the proof state to be better. - return Ok(()); - } - } - } - - drop(pruning_read); - drop(relations_read); - drop(proof_stores_and_processes.db_lifetime); - drop(current_consensus_stores_and_processes.db_lifetime); - - Err(PruningImportError::PruningProofNotEnoughHeaders) - } - - // The "current dag level" is the level right before the level whose parents are - // not the same as our header's direct parents - // - // Find the current DAG level by going through all the parents at each level, - // starting from the bottom level and see which is the first level that has - // parents that are NOT our current pp_header's direct parents. - fn find_current_dag_level(&self, pp_header: &Header) -> BlockLevel { - let direct_parents = BlockHashSet::from_iter(pp_header.direct_parents().iter().copied()); - pp_header - .parents_by_level - .iter() - .enumerate() - .skip(1) - .find_map(|(level, parents)| { - if BlockHashSet::from_iter(parents.iter().copied()) == direct_parents { - None - } else { - Some((level - 1) as BlockLevel) - } - }) - .unwrap_or(self.max_block_level) - } - - fn estimated_blue_depth_at_level_0(&self, level: BlockLevel, level_depth: u64, current_dag_level: BlockLevel) -> u64 { - level_depth.checked_shl(level.saturating_sub(current_dag_level) as u32).unwrap_or(level_depth) - } - - /// selected parent at level = the parent of the header at the level - /// with the highest blue_work - fn find_selected_parent_header_at_level( - &self, - header: &Header, - level: BlockLevel, - ) -> PruningProofManagerInternalResult> { - // Parents manager parents_at_level may return parents that aren't in relations_service, so it's important - // to filter to include only parents that are in relations_service. - let sp = self - .parents_manager - .parents_at_level(header, level) - .iter() - .copied() - .filter(|p| self.level_relations_services[level as usize].has(*p).unwrap()) - .filter_map(|p| self.headers_store.get_header(p).unwrap_option().map(|h| SortableBlock::new(p, h.blue_work))) - .max() - .ok_or(PruningProofManagerInternalError::NotEnoughHeadersToBuildProof("no parents with header".to_string()))?; - Ok(self.headers_store.get_header(sp.hash).expect("unwrapped above")) - } - - /// Find a sufficient root at a given level by going through the headers store and looking - /// for a deep enough level block - /// For each root candidate, fill in the ghostdag data to see if it actually is deep enough. - /// If the root is deep enough, it will satisfy these conditions - /// 1. block at depth 2m at this level ∈ Future(root) - /// 2. block at depth m at the next level ∈ Future(root) - /// - /// Returns: the filled ghostdag store from root to tip, the selected tip and the root - fn find_sufficient_root( - &self, - pp_header: &HeaderWithBlockLevel, - level: BlockLevel, - current_dag_level: BlockLevel, - required_block: Option, - temp_db: Arc, - ) -> PruningProofManagerInternalResult<(Arc, Hash, Hash)> { - // Step 1: Determine which selected tip to use - let selected_tip = if pp_header.block_level >= level { - pp_header.header.hash - } else { - self.find_selected_parent_header_at_level(&pp_header.header, level)?.hash - }; - - let cache_policy = CachePolicy::Count(2 * self.pruning_proof_m as usize); - let required_level_depth = 2 * self.pruning_proof_m; - - // We only have the headers store (which has level 0 blue_scores) to assemble the proof data from. - // We need to look deeper at higher levels (2x deeper every level) to find 2M (plus margin) blocks at that level - let mut required_base_level_depth = self.estimated_blue_depth_at_level_0( - level, - required_level_depth + 100, // We take a safety margin - current_dag_level, - ); - - let mut is_last_level_header; - let mut tries = 0; - - let block_at_depth_m_at_next_level = required_block.unwrap_or(selected_tip); - - loop { - // Step 2 - Find a deep enough root candidate - let block_at_depth_2m = match self.level_block_at_base_depth(level, selected_tip, required_base_level_depth) { - Ok((header, is_last_header)) => { - is_last_level_header = is_last_header; - header - } - Err(e) => return Err(e), - }; - - let root = if self.reachability_service.is_dag_ancestor_of(block_at_depth_2m, block_at_depth_m_at_next_level) { - block_at_depth_2m - } else if self.reachability_service.is_dag_ancestor_of(block_at_depth_m_at_next_level, block_at_depth_2m) { - block_at_depth_m_at_next_level - } else { - // find common ancestor of block_at_depth_m_at_next_level and block_at_depth_2m in chain of block_at_depth_m_at_next_level - let mut common_ancestor = self.headers_store.get_header(block_at_depth_m_at_next_level).unwrap(); - - while !self.reachability_service.is_dag_ancestor_of(common_ancestor.hash, block_at_depth_2m) { - common_ancestor = match self.find_selected_parent_header_at_level(&common_ancestor, level) { - Ok(header) => header, - // Try to give this last header a chance at being root - Err(PruningProofManagerInternalError::NotEnoughHeadersToBuildProof(_)) => break, - Err(e) => return Err(e), - }; - } - - common_ancestor.hash - }; - - if level == 0 { - return Ok((self.ghostdag_store.clone(), selected_tip, root)); - } - - // Step 3 - Fill the ghostdag data from root to tip - let ghostdag_store = Arc::new(DbGhostdagStore::new_temp(temp_db.clone(), level, cache_policy, cache_policy, tries)); - let has_required_block = self.fill_level_proof_ghostdag_data( - root, - pp_header.header.hash, - &ghostdag_store, - Some(block_at_depth_m_at_next_level), - level, - ); - - // Step 4 - Check if we actually have enough depth. - // Need to ensure this does the same 2M+1 depth that block_at_depth does - if has_required_block - && (root == self.genesis_hash || ghostdag_store.get_blue_score(selected_tip).unwrap() >= required_level_depth) - { - break Ok((ghostdag_store, selected_tip, root)); - } - - tries += 1; - if is_last_level_header { - if has_required_block { - // Normally this scenario doesn't occur when syncing with nodes that already have the safety margin change in place. - // However, when syncing with an older node version that doesn't have a safety margin for the proof, it's possible to - // try to find 2500 depth worth of headers at a level, but the proof only contains about 2000 headers. To be able to sync - // with such an older node. As long as we found the required block, we can still proceed. - debug!("Failed to find sufficient root for level {level} after {tries} tries. Headers below the current depth of {required_base_level_depth} are already pruned. Required block found so trying anyway."); - break Ok((ghostdag_store, selected_tip, root)); - } else { - panic!("Failed to find sufficient root for level {level} after {tries} tries. Headers below the current depth of {required_base_level_depth} are already pruned"); - } - } - - // If we don't have enough depth now, we need to look deeper - required_base_level_depth = (required_base_level_depth as f64 * 1.1) as u64; - debug!("Failed to find sufficient root for level {level} after {tries} tries. Retrying again to find with depth {required_base_level_depth}"); - } - } - - fn calc_gd_for_all_levels( - &self, - pp_header: &HeaderWithBlockLevel, - temp_db: Arc, - ) -> (Vec>, Vec, Vec) { - let current_dag_level = self.find_current_dag_level(&pp_header.header); - let mut ghostdag_stores: Vec>> = vec![None; self.max_block_level as usize + 1]; - let mut selected_tip_by_level = vec![None; self.max_block_level as usize + 1]; - let mut root_by_level = vec![None; self.max_block_level as usize + 1]; - for level in (0..=self.max_block_level).rev() { - let level_usize = level as usize; - let required_block = if level != self.max_block_level { - let next_level_store = ghostdag_stores[level_usize + 1].as_ref().unwrap().clone(); - let block_at_depth_m_at_next_level = self - .block_at_depth(&*next_level_store, selected_tip_by_level[level_usize + 1].unwrap(), self.pruning_proof_m) - .map_err(|err| format!("level + 1: {}, err: {}", level + 1, err)) - .unwrap(); - Some(block_at_depth_m_at_next_level) - } else { - None - }; - let (store, selected_tip, root) = self - .find_sufficient_root(pp_header, level, current_dag_level, required_block, temp_db.clone()) - .unwrap_or_else(|_| panic!("find_sufficient_root failed for level {level}")); - ghostdag_stores[level_usize] = Some(store); - selected_tip_by_level[level_usize] = Some(selected_tip); - root_by_level[level_usize] = Some(root); - } - - ( - ghostdag_stores.into_iter().map(Option::unwrap).collect_vec(), - selected_tip_by_level.into_iter().map(Option::unwrap).collect_vec(), - root_by_level.into_iter().map(Option::unwrap).collect_vec(), - ) - } - - pub(crate) fn build_pruning_point_proof(&self, pp: Hash) -> PruningPointProof { - if pp == self.genesis_hash { - return vec![]; - } - - let (_db_lifetime, temp_db) = kaspa_database::create_temp_db!(ConnBuilder::default().with_files_limit(10)); - let pp_header = self.headers_store.get_header_with_block_level(pp).unwrap(); - let (ghostdag_stores, selected_tip_by_level, roots_by_level) = self.calc_gd_for_all_levels(&pp_header, temp_db); - - (0..=self.max_block_level) - .map(|level| { - let level = level as usize; - let selected_tip = selected_tip_by_level[level]; - let block_at_depth_2m = self - .block_at_depth(&*ghostdag_stores[level], selected_tip, 2 * self.pruning_proof_m) - .map_err(|err| format!("level: {}, err: {}", level, err)) - .unwrap(); - - // TODO (relaxed): remove the assertion below - // (New Logic) This is the root we calculated by going through block relations - let root = roots_by_level[level]; - // (Old Logic) This is the root we can calculate given that the GD records are already filled - // The root calc logic below is the original logic before the on-demand higher level GD calculation - // We only need old_root to sanity check the new logic - let old_root = if level != self.max_block_level as usize { - let block_at_depth_m_at_next_level = self - .block_at_depth(&*ghostdag_stores[level + 1], selected_tip_by_level[level + 1], self.pruning_proof_m) - .map_err(|err| format!("level + 1: {}, err: {}", level + 1, err)) - .unwrap(); - if self.reachability_service.is_dag_ancestor_of(block_at_depth_m_at_next_level, block_at_depth_2m) { - block_at_depth_m_at_next_level - } else if self.reachability_service.is_dag_ancestor_of(block_at_depth_2m, block_at_depth_m_at_next_level) { - block_at_depth_2m - } else { - self.find_common_ancestor_in_chain_of_a( - &*ghostdag_stores[level], - block_at_depth_m_at_next_level, - block_at_depth_2m, - ) - .map_err(|err| format!("level: {}, err: {}", level, err)) - .unwrap() - } - } else { - block_at_depth_2m - }; - - // new root is expected to be always an ancestor of old_root because new root takes a safety margin - assert!(self.reachability_service.is_dag_ancestor_of(root, old_root)); - - let mut headers = Vec::with_capacity(2 * self.pruning_proof_m as usize); - let mut queue = BinaryHeap::>::new(); - let mut visited = BlockHashSet::new(); - queue.push(Reverse(SortableBlock::new(root, self.headers_store.get_header(root).unwrap().blue_work))); - while let Some(current) = queue.pop() { - let current = current.0.hash; - if !visited.insert(current) { - continue; - } - - // The second condition is always expected to be true (ghostdag store will have the entry) - // because we are traversing the exact diamond (future(root) â‹‚ past(tip)) for which we calculated - // GD for (see fill_level_proof_ghostdag_data). TODO (relaxed): remove the condition or turn into assertion - if !self.reachability_service.is_dag_ancestor_of(current, selected_tip) - || !ghostdag_stores[level].has(current).is_ok_and(|found| found) - { - continue; - } - - headers.push(self.headers_store.get_header(current).unwrap()); - for child in self.relations_stores.read()[level].get_children(current).unwrap().read().iter().copied() { - queue.push(Reverse(SortableBlock::new(child, self.headers_store.get_header(child).unwrap().blue_work))); - } - } - - // TODO (relaxed): remove the assertion below - // Temp assertion for verifying a bug fix: assert that the full 2M chain is actually contained in the composed level proof - let set = BlockHashSet::from_iter(headers.iter().map(|h| h.hash)); - let chain_2m = self - .chain_up_to_depth(&*ghostdag_stores[level], selected_tip, 2 * self.pruning_proof_m) - .map_err(|err| { - dbg!(level, selected_tip, block_at_depth_2m, root); - format!("Assert 2M chain -- level: {}, err: {}", level, err) - }) - .unwrap(); - let chain_2m_len = chain_2m.len(); - for (i, chain_hash) in chain_2m.into_iter().enumerate() { - if !set.contains(&chain_hash) { - let next_level_tip = selected_tip_by_level[level + 1]; - let next_level_chain_m = - self.chain_up_to_depth(&*ghostdag_stores[level + 1], next_level_tip, self.pruning_proof_m).unwrap(); - let next_level_block_m = next_level_chain_m.last().copied().unwrap(); - dbg!(next_level_chain_m.len()); - dbg!(ghostdag_stores[level + 1].get_compact_data(next_level_tip).unwrap().blue_score); - dbg!(ghostdag_stores[level + 1].get_compact_data(next_level_block_m).unwrap().blue_score); - dbg!(ghostdag_stores[level].get_compact_data(selected_tip).unwrap().blue_score); - dbg!(ghostdag_stores[level].get_compact_data(block_at_depth_2m).unwrap().blue_score); - dbg!(level, selected_tip, block_at_depth_2m, root); - panic!("Assert 2M chain -- missing block {} at index {} out of {} chain blocks", chain_hash, i, chain_2m_len); - } - } - - headers - }) - .collect_vec() - } - - /// BFS forward iterates from root until selected tip, ignoring blocks in the antipast of selected_tip. - /// For each block along the way, insert that hash into the ghostdag_store - /// If we have a required_block to find, this will return true if that block was found along the way - fn fill_level_proof_ghostdag_data( - &self, - root: Hash, - selected_tip: Hash, - ghostdag_store: &Arc, - required_block: Option, - level: BlockLevel, - ) -> bool { - let relations_service = RelationsStoreInFutureOfRoot { - relations_store: self.level_relations_services[level as usize].clone(), - reachability_service: self.reachability_service.clone(), - root, - }; - let gd_manager = GhostdagManager::new( - root, - self.ghostdag_k, - ghostdag_store.clone(), - relations_service.clone(), - self.headers_store.clone(), - self.reachability_service.clone(), - level != 0, - ); - - ghostdag_store.insert(root, Arc::new(gd_manager.genesis_ghostdag_data())).unwrap(); - ghostdag_store.insert(ORIGIN, gd_manager.origin_ghostdag_data()).unwrap(); - - let mut topological_heap: BinaryHeap<_> = Default::default(); - let mut visited = BlockHashSet::new(); - for child in relations_service.get_children(root).unwrap().read().iter().copied() { - topological_heap.push(Reverse(SortableBlock { - hash: child, - // It's important to use here blue work and not score so we can iterate the heap in a way that respects the topology - blue_work: self.headers_store.get_header(child).unwrap().blue_work, - })); - } - - let mut has_required_block = required_block.is_some_and(|required_block| root == required_block); - loop { - let Some(current) = topological_heap.pop() else { - break; - }; - let current_hash = current.0.hash; - if !visited.insert(current_hash) { - continue; - } - - if !self.reachability_service.is_dag_ancestor_of(current_hash, selected_tip) { - // We don't care about blocks in the antipast of the selected tip - continue; - } - - if !has_required_block && required_block.is_some_and(|required_block| current_hash == required_block) { - has_required_block = true; - } - - let current_gd = gd_manager.ghostdag(&relations_service.get_parents(current_hash).unwrap()); - - ghostdag_store.insert(current_hash, Arc::new(current_gd)).unwrap_or_exists(); - - for child in relations_service.get_children(current_hash).unwrap().read().iter().copied() { - topological_heap.push(Reverse(SortableBlock { - hash: child, - // It's important to use here blue work and not score so we can iterate the heap in a way that respects the topology - blue_work: self.headers_store.get_header(child).unwrap().blue_work, - })); - } - } - - has_required_block - } - - /// Copy of `block_at_depth` which returns the full chain up to depth. Temporarily used for assertion purposes. - fn chain_up_to_depth( - &self, - ghostdag_store: &impl GhostdagStoreReader, - high: Hash, - depth: u64, - ) -> Result, PruningProofManagerInternalError> { - let high_gd = ghostdag_store - .get_compact_data(high) - .map_err(|err| PruningProofManagerInternalError::BlockAtDepth(format!("high: {high}, depth: {depth}, {err}")))?; - let mut current_gd = high_gd; - let mut current = high; - let mut res = vec![current]; - while current_gd.blue_score + depth >= high_gd.blue_score { - if current_gd.selected_parent.is_origin() { - break; - } - let prev = current; - current = current_gd.selected_parent; - res.push(current); - current_gd = ghostdag_store.get_compact_data(current).map_err(|err| { - PruningProofManagerInternalError::BlockAtDepth(format!( - "high: {}, depth: {}, current: {}, high blue score: {}, current blue score: {}, {}", - high, depth, prev, high_gd.blue_score, current_gd.blue_score, err - )) - })?; - } - Ok(res) - } - + // Used in build and validate fn block_at_depth( &self, ghostdag_store: &impl GhostdagStoreReader, @@ -1229,69 +241,6 @@ impl PruningProofManager { Ok(current) } - /// Finds the block on a given level that is at base_depth deep from it. - /// Also returns if the block was the last one in the level - /// base_depth = the blue score depth at level 0 - fn level_block_at_base_depth( - &self, - level: BlockLevel, - high: Hash, - base_depth: u64, - ) -> PruningProofManagerInternalResult<(Hash, bool)> { - let high_header = self - .headers_store - .get_header(high) - .map_err(|err| PruningProofManagerInternalError::BlockAtDepth(format!("high: {high}, depth: {base_depth}, {err}")))?; - let high_header_score = high_header.blue_score; - let mut current_header = high_header; - - let mut is_last_header = false; - - while current_header.blue_score + base_depth >= high_header_score { - if current_header.direct_parents().is_empty() { - break; - } - - current_header = match self.find_selected_parent_header_at_level(¤t_header, level) { - Ok(header) => header, - Err(PruningProofManagerInternalError::NotEnoughHeadersToBuildProof(_)) => { - // We want to give this root a shot if all its past is pruned - is_last_header = true; - break; - } - Err(e) => return Err(e), - }; - } - Ok((current_header.hash, is_last_header)) - } - - fn find_common_ancestor_in_chain_of_a( - &self, - ghostdag_store: &impl GhostdagStoreReader, - a: Hash, - b: Hash, - ) -> Result { - let a_gd = ghostdag_store - .get_compact_data(a) - .map_err(|err| PruningProofManagerInternalError::FindCommonAncestor(format!("a: {a}, b: {b}, {err}")))?; - let mut current_gd = a_gd; - let mut current; - let mut loop_counter = 0; - loop { - current = current_gd.selected_parent; - loop_counter += 1; - if current.is_origin() { - break Err(PruningProofManagerInternalError::NoCommonAncestor(format!("a: {a}, b: {b} ({loop_counter} loop steps)"))); - } - if self.reachability_service.is_dag_ancestor_of(current, b) { - break Ok(current); - } - current_gd = ghostdag_store - .get_compact_data(current) - .map_err(|err| PruningProofManagerInternalError::FindCommonAncestor(format!("a: {a}, b: {b}, {err}")))?; - } - } - /// Returns the k + 1 chain blocks below this hash (inclusive). If data is missing /// the search is halted and a partial chain is returned. /// diff --git a/consensus/src/processes/pruning_proof/validate.rs b/consensus/src/processes/pruning_proof/validate.rs new file mode 100644 index 000000000..63650cdc5 --- /dev/null +++ b/consensus/src/processes/pruning_proof/validate.rs @@ -0,0 +1,376 @@ +use std::{ + ops::DerefMut, + sync::{atomic::Ordering, Arc}, +}; + +use itertools::Itertools; +use kaspa_consensus_core::{ + blockhash::{BlockHashExtensions, BlockHashes, ORIGIN}, + errors::pruning::{PruningImportError, PruningImportResult}, + header::Header, + pruning::PruningPointProof, + BlockLevel, +}; +use kaspa_core::info; +use kaspa_database::prelude::{CachePolicy, ConnBuilder, StoreResultEmptyTuple, StoreResultExtensions}; +use kaspa_hashes::Hash; +use kaspa_math::int::SignedInteger; +use kaspa_pow::calc_block_level; +use kaspa_utils::vec::VecExtensions; +use parking_lot::lock_api::RwLock; +use rocksdb::WriteBatch; + +use crate::{ + model::{ + services::reachability::MTReachabilityService, + stores::{ + ghostdag::{CompactGhostdagData, DbGhostdagStore, GhostdagStore, GhostdagStoreReader}, + headers::{DbHeadersStore, HeaderStore, HeaderStoreReader}, + pruning::PruningStoreReader, + reachability::{DbReachabilityStore, ReachabilityStoreReader}, + relations::{DbRelationsStore, RelationsStoreReader}, + }, + }, + processes::{ghostdag::protocol::GhostdagManager, reachability::inquirer as reachability, relations::RelationsStoreExtensions}, +}; + +use super::{PruningProofManager, TempProofContext}; + +impl PruningProofManager { + pub fn validate_pruning_point_proof(&self, proof: &PruningPointProof) -> PruningImportResult<()> { + if proof.len() != self.max_block_level as usize + 1 { + return Err(PruningImportError::ProofNotEnoughLevels(self.max_block_level as usize + 1)); + } + + // Initialize the stores for the proof + let mut proof_stores_and_processes = self.init_validate_pruning_point_proof_stores_and_processes(proof)?; + let proof_pp_header = proof[0].last().expect("checked if empty"); + let proof_pp = proof_pp_header.hash; + let proof_pp_level = calc_block_level(proof_pp_header, self.max_block_level); + let proof_selected_tip_by_level = + self.populate_stores_for_validate_pruning_point_proof(proof, &mut proof_stores_and_processes, true)?; + let proof_ghostdag_stores = proof_stores_and_processes.ghostdag_stores; + + // Get the proof for the current consensus and recreate the stores for it + // This is expected to be fast because if a proof exists, it will be cached. + // If no proof exists, this is empty + let mut current_consensus_proof = self.get_pruning_point_proof(); + if current_consensus_proof.is_empty() { + // An empty proof can only happen if we're at genesis. We're going to create a proof for this case that contains the genesis header only + let genesis_header = self.headers_store.get_header(self.genesis_hash).unwrap(); + current_consensus_proof = Arc::new((0..=self.max_block_level).map(|_| vec![genesis_header.clone()]).collect_vec()); + } + let mut current_consensus_stores_and_processes = + self.init_validate_pruning_point_proof_stores_and_processes(¤t_consensus_proof)?; + let _ = self.populate_stores_for_validate_pruning_point_proof( + ¤t_consensus_proof, + &mut current_consensus_stores_and_processes, + false, + )?; + let current_consensus_ghostdag_stores = current_consensus_stores_and_processes.ghostdag_stores; + + let pruning_read = self.pruning_point_store.read(); + let relations_read = self.relations_stores.read(); + let current_pp = pruning_read.get().unwrap().pruning_point; + let current_pp_header = self.headers_store.get_header(current_pp).unwrap(); + + for (level_idx, selected_tip) in proof_selected_tip_by_level.iter().copied().enumerate() { + let level = level_idx as BlockLevel; + self.validate_proof_selected_tip(selected_tip, level, proof_pp_level, proof_pp, proof_pp_header)?; + + let proof_selected_tip_gd = proof_ghostdag_stores[level_idx].get_compact_data(selected_tip).unwrap(); + + // Next check is to see if this proof is "better" than what's in the current consensus + // Step 1 - look at only levels that have a full proof (least 2m blocks in the proof) + if proof_selected_tip_gd.blue_score < 2 * self.pruning_proof_m { + continue; + } + + // Step 2 - if we can find a common ancestor between the proof and current consensus + // we can determine if the proof is better. The proof is better if the blue work* difference between the + // old current consensus's tips and the common ancestor is less than the blue work difference between the + // proof's tip and the common ancestor. + // *Note: blue work is the same as blue score on levels higher than 0 + if let Some((proof_common_ancestor_gd, common_ancestor_gd)) = self.find_proof_and_consensus_common_ancestor_ghostdag_data( + &proof_ghostdag_stores, + ¤t_consensus_ghostdag_stores, + selected_tip, + level, + proof_selected_tip_gd, + ) { + let selected_tip_blue_work_diff = + SignedInteger::from(proof_selected_tip_gd.blue_work) - SignedInteger::from(proof_common_ancestor_gd.blue_work); + for parent in self.parents_manager.parents_at_level(¤t_pp_header, level).iter().copied() { + let parent_blue_work = current_consensus_ghostdag_stores[level_idx].get_blue_work(parent).unwrap(); + let parent_blue_work_diff = + SignedInteger::from(parent_blue_work) - SignedInteger::from(common_ancestor_gd.blue_work); + if parent_blue_work_diff >= selected_tip_blue_work_diff { + return Err(PruningImportError::PruningProofInsufficientBlueWork); + } + } + + return Ok(()); + } + } + + if current_pp == self.genesis_hash { + // If the proof has better tips and the current pruning point is still + // genesis, we consider the proof state to be better. + return Ok(()); + } + + // If we got here it means there's no level with shared blocks + // between the proof and the current consensus. In this case we + // consider the proof to be better if it has at least one level + // with 2*self.pruning_proof_m blue blocks where consensus doesn't. + for level in (0..=self.max_block_level).rev() { + let level_idx = level as usize; + + let proof_selected_tip = proof_selected_tip_by_level[level_idx]; + let proof_selected_tip_gd = proof_ghostdag_stores[level_idx].get_compact_data(proof_selected_tip).unwrap(); + if proof_selected_tip_gd.blue_score < 2 * self.pruning_proof_m { + continue; + } + + match relations_read[level_idx].get_parents(current_pp).unwrap_option() { + Some(parents) => { + if parents.iter().copied().any(|parent| { + current_consensus_ghostdag_stores[level_idx].get_blue_score(parent).unwrap() < 2 * self.pruning_proof_m + }) { + return Ok(()); + } + } + None => { + // If the current pruning point doesn't have a parent at this level, we consider the proof state to be better. + return Ok(()); + } + } + } + + drop(pruning_read); + drop(relations_read); + drop(proof_stores_and_processes.db_lifetime); + drop(current_consensus_stores_and_processes.db_lifetime); + + Err(PruningImportError::PruningProofNotEnoughHeaders) + } + + fn init_validate_pruning_point_proof_stores_and_processes( + &self, + proof: &PruningPointProof, + ) -> PruningImportResult { + if proof[0].is_empty() { + return Err(PruningImportError::PruningProofNotEnoughHeaders); + } + + let headers_estimate = self.estimate_proof_unique_size(proof); + + let (db_lifetime, db) = kaspa_database::create_temp_db!(ConnBuilder::default().with_files_limit(10)); + let cache_policy = CachePolicy::Count(2 * self.pruning_proof_m as usize); + let headers_store = + Arc::new(DbHeadersStore::new(db.clone(), CachePolicy::Count(headers_estimate), CachePolicy::Count(headers_estimate))); + let ghostdag_stores = (0..=self.max_block_level) + .map(|level| Arc::new(DbGhostdagStore::new(db.clone(), level, cache_policy, cache_policy))) + .collect_vec(); + let mut relations_stores = + (0..=self.max_block_level).map(|level| DbRelationsStore::new(db.clone(), level, cache_policy, cache_policy)).collect_vec(); + let reachability_stores = (0..=self.max_block_level) + .map(|level| Arc::new(RwLock::new(DbReachabilityStore::with_block_level(db.clone(), cache_policy, cache_policy, level)))) + .collect_vec(); + + let reachability_services = (0..=self.max_block_level) + .map(|level| MTReachabilityService::new(reachability_stores[level as usize].clone())) + .collect_vec(); + + let ghostdag_managers = ghostdag_stores + .iter() + .cloned() + .enumerate() + .map(|(level, ghostdag_store)| { + GhostdagManager::new( + self.genesis_hash, + self.ghostdag_k, + ghostdag_store, + relations_stores[level].clone(), + headers_store.clone(), + reachability_services[level].clone(), + level != 0, + ) + }) + .collect_vec(); + + { + let mut batch = WriteBatch::default(); + for level in 0..=self.max_block_level { + let level = level as usize; + reachability::init(reachability_stores[level].write().deref_mut()).unwrap(); + relations_stores[level].insert_batch(&mut batch, ORIGIN, BlockHashes::new(vec![])).unwrap(); + ghostdag_stores[level].insert(ORIGIN, ghostdag_managers[level].origin_ghostdag_data()).unwrap(); + } + + db.write(batch).unwrap(); + } + + Ok(TempProofContext { db_lifetime, headers_store, ghostdag_stores, relations_stores, reachability_stores, ghostdag_managers }) + } + + fn populate_stores_for_validate_pruning_point_proof( + &self, + proof: &PruningPointProof, + ctx: &mut TempProofContext, + log_validating: bool, + ) -> PruningImportResult> { + let headers_store = &ctx.headers_store; + let ghostdag_stores = &ctx.ghostdag_stores; + let mut relations_stores = ctx.relations_stores.clone(); + let reachability_stores = &ctx.reachability_stores; + let ghostdag_managers = &ctx.ghostdag_managers; + + let proof_pp_header = proof[0].last().expect("checked if empty"); + let proof_pp = proof_pp_header.hash; + + let mut selected_tip_by_level = vec![None; self.max_block_level as usize + 1]; + for level in (0..=self.max_block_level).rev() { + // Before processing this level, check if the process is exiting so we can end early + if self.is_consensus_exiting.load(Ordering::Relaxed) { + return Err(PruningImportError::PruningValidationInterrupted); + } + + if log_validating { + info!("Validating level {level} from the pruning point proof ({} headers)", proof[level as usize].len()); + } + let level_idx = level as usize; + let mut selected_tip = None; + for (i, header) in proof[level as usize].iter().enumerate() { + let header_level = calc_block_level(header, self.max_block_level); + if header_level < level { + return Err(PruningImportError::PruningProofWrongBlockLevel(header.hash, header_level, level)); + } + + headers_store.insert(header.hash, header.clone(), header_level).unwrap_or_exists(); + + let parents = self + .parents_manager + .parents_at_level(header, level) + .iter() + .copied() + .filter(|parent| ghostdag_stores[level_idx].has(*parent).unwrap()) + .collect_vec(); + + // Only the first block at each level is allowed to have no known parents + if parents.is_empty() && i != 0 { + return Err(PruningImportError::PruningProofHeaderWithNoKnownParents(header.hash, level)); + } + + let parents: BlockHashes = parents.push_if_empty(ORIGIN).into(); + + if relations_stores[level_idx].has(header.hash).unwrap() { + return Err(PruningImportError::PruningProofDuplicateHeaderAtLevel(header.hash, level)); + } + + relations_stores[level_idx].insert(header.hash, parents.clone()).unwrap(); + let ghostdag_data = Arc::new(ghostdag_managers[level_idx].ghostdag(&parents)); + ghostdag_stores[level_idx].insert(header.hash, ghostdag_data.clone()).unwrap(); + selected_tip = Some(match selected_tip { + Some(tip) => ghostdag_managers[level_idx].find_selected_parent([tip, header.hash]), + None => header.hash, + }); + + let mut reachability_mergeset = { + let reachability_read = reachability_stores[level_idx].read(); + ghostdag_data + .unordered_mergeset_without_selected_parent() + .filter(|hash| reachability_read.has(*hash).unwrap()) + .collect_vec() // We collect to vector so reachability_read can be released and let `reachability::add_block` use a write lock. + .into_iter() + }; + reachability::add_block( + reachability_stores[level_idx].write().deref_mut(), + header.hash, + ghostdag_data.selected_parent, + &mut reachability_mergeset, + ) + .unwrap(); + + if selected_tip.unwrap() == header.hash { + reachability::hint_virtual_selected_parent(reachability_stores[level_idx].write().deref_mut(), header.hash) + .unwrap(); + } + } + + if level < self.max_block_level { + let block_at_depth_m_at_next_level = self + .block_at_depth( + &*ghostdag_stores[level_idx + 1], + selected_tip_by_level[level_idx + 1].unwrap(), + self.pruning_proof_m, + ) + .unwrap(); + if !relations_stores[level_idx].has(block_at_depth_m_at_next_level).unwrap() { + return Err(PruningImportError::PruningProofMissingBlockAtDepthMFromNextLevel(level, level + 1)); + } + } + + if selected_tip.unwrap() != proof_pp + && !self.parents_manager.parents_at_level(proof_pp_header, level).contains(&selected_tip.unwrap()) + { + return Err(PruningImportError::PruningProofMissesBlocksBelowPruningPoint(selected_tip.unwrap(), level)); + } + + selected_tip_by_level[level_idx] = selected_tip; + } + + Ok(selected_tip_by_level.into_iter().map(|selected_tip| selected_tip.unwrap()).collect()) + } + + fn validate_proof_selected_tip( + &self, + proof_selected_tip: Hash, + level: BlockLevel, + proof_pp_level: BlockLevel, + proof_pp: Hash, + proof_pp_header: &Header, + ) -> PruningImportResult<()> { + // A proof selected tip of some level has to be the proof suggested prunint point itself if its level + // is lower or equal to the pruning point level, or a parent of the pruning point on the relevant level + // otherwise. + if level <= proof_pp_level { + if proof_selected_tip != proof_pp { + return Err(PruningImportError::PruningProofSelectedTipIsNotThePruningPoint(proof_selected_tip, level)); + } + } else if !self.parents_manager.parents_at_level(proof_pp_header, level).contains(&proof_selected_tip) { + return Err(PruningImportError::PruningProofSelectedTipNotParentOfPruningPoint(proof_selected_tip, level)); + } + + Ok(()) + } + + // find_proof_and_consensus_common_chain_ancestor_ghostdag_data returns an option of a tuple + // that contains the ghostdag data of the proof and current consensus common ancestor. If no + // such ancestor exists, it returns None. + fn find_proof_and_consensus_common_ancestor_ghostdag_data( + &self, + proof_ghostdag_stores: &[Arc], + current_consensus_ghostdag_stores: &[Arc], + proof_selected_tip: Hash, + level: BlockLevel, + proof_selected_tip_gd: CompactGhostdagData, + ) -> Option<(CompactGhostdagData, CompactGhostdagData)> { + let mut proof_current = proof_selected_tip; + let mut proof_current_gd = proof_selected_tip_gd; + loop { + match current_consensus_ghostdag_stores[level as usize].get_compact_data(proof_current).unwrap_option() { + Some(current_gd) => { + break Some((proof_current_gd, current_gd)); + } + None => { + proof_current = proof_current_gd.selected_parent; + if proof_current.is_origin() { + break None; + } + proof_current_gd = proof_ghostdag_stores[level as usize].get_compact_data(proof_current).unwrap(); + } + }; + } + } +} From 2974b9786e63fcaec2ba3ba8ae97ff47f46525a7 Mon Sep 17 00:00:00 2001 From: max143672 Date: Thu, 7 Nov 2024 15:09:45 +0400 Subject: [PATCH 42/93] only enable 8 byte arithmetics for kip10 --- crypto/txscript/src/data_stack.rs | 55 +++++++- crypto/txscript/src/opcodes/mod.rs | 121 ++++++++---------- .../test-data/script_tests-kip10.json | 24 ++-- 3 files changed, 118 insertions(+), 82 deletions(-) diff --git a/crypto/txscript/src/data_stack.rs b/crypto/txscript/src/data_stack.rs index aa201522d..81367b3dd 100644 --- a/crypto/txscript/src/data_stack.rs +++ b/crypto/txscript/src/data_stack.rs @@ -1,8 +1,11 @@ use crate::TxScriptError; use core::fmt::Debug; use core::iter; +use std::cmp::Ordering; +use std::ops::Deref; -const DEFAULT_SCRIPT_NUM_LEN: usize = 8; +const DEFAULT_SCRIPT_NUM_LEN: usize = 4; +const DEFAULT_SCRIPT_NUM_LEN_KIP10: usize = 8; #[derive(PartialEq, Eq, Debug, Default)] pub(crate) struct SizedEncodeInt(pub(crate) i64); @@ -75,6 +78,56 @@ fn deserialize_i64(v: &[u8]) -> Result { } } +#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd)] +#[repr(transparent)] +pub struct Kip10I64(pub i64); + +impl From for i64 { + fn from(value: Kip10I64) -> Self { + value.0 + } +} + +impl PartialEq for Kip10I64 { + fn eq(&self, other: &i64) -> bool { + self.0.eq(other) + } +} + +impl PartialOrd for Kip10I64 { + fn partial_cmp(&self, other: &i64) -> Option { + self.0.partial_cmp(other) + } +} + +impl Deref for Kip10I64 { + type Target = i64; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl OpcodeData for Vec { + #[inline] + fn deserialize(&self) -> Result { + match self.len() > DEFAULT_SCRIPT_NUM_LEN_KIP10 { + true => Err(TxScriptError::NumberTooBig(format!( + "numeric value encoded as {:x?} is {} bytes which exceeds the max allowed of {}", + self, + self.len(), + DEFAULT_SCRIPT_NUM_LEN_KIP10 + ))), + false => deserialize_i64(self).map(Kip10I64), + } + } + + #[inline] + fn serialize(from: &Kip10I64) -> Self { + Self::serialize(&from.0) + } +} + impl OpcodeData for Vec { #[inline] fn deserialize(&self) -> Result { diff --git a/crypto/txscript/src/opcodes/mod.rs b/crypto/txscript/src/opcodes/mod.rs index 64175c823..55ad34b3b 100644 --- a/crypto/txscript/src/opcodes/mod.rs +++ b/crypto/txscript/src/opcodes/mod.rs @@ -1,13 +1,12 @@ #[macro_use] mod macros; -use crate::data_stack::{DataStack, OpcodeData}; +use crate::data_stack::{DataStack, Kip10I64, OpcodeData}; use crate::{ ScriptSource, TxScriptEngine, TxScriptError, LOCK_TIME_THRESHOLD, MAX_TX_IN_SEQUENCE_NUM, NO_COST_OPCODE, SEQUENCE_LOCK_TIME_DISABLED, SEQUENCE_LOCK_TIME_MASK, }; use blake2b_simd::Params; -use core::cmp::{max, min}; use kaspa_consensus_core::hashing::sighash::SigHashReusedValues; use kaspa_consensus_core::hashing::sighash_type::SigHashType; use kaspa_consensus_core::tx::VerifiableTransaction; @@ -214,6 +213,26 @@ fn push_number( Ok(()) } +/// This macro helps to avoid code duplication in numeric opcodes where the only difference +/// between KIP10_ENABLED and disabled states is the numeric type used (Kip10I64 vs i64). +/// KIP10I64 deserializator supports 8-byte integers +macro_rules! numeric_op { + ($vm: expr, $pattern: pat, $count: expr, $block: expr) => { + if $vm.kip10_enabled { + let $pattern: [Kip10I64; $count] = $vm.dstack.pop_items()?; + let r = $block; + $vm.dstack.push_item(r); + Ok(()) + } else { + let $pattern: [i64; $count] = $vm.dstack.pop_items()?; + #[allow(clippy::useless_conversion)] + let r = $block; + $vm.dstack.push_item(r); + Ok(()) + } + }; +} + /* The following is the implementation and metadata of all opcodes. Each opcode has unique number (and template system makes it impossible to use two opcodes), length specification, @@ -566,60 +585,38 @@ opcode_list! { // Numeric related opcodes. opcode Op1Add<0x8b, 1>(self, vm) { - let [value]: [i64; 1] = vm.dstack.pop_items()?; - let r = value.checked_add(1).ok_or_else(|| TxScriptError::NumberTooBig("todo".to_string()))?; - vm.dstack.push_item(r); - Ok(()) + numeric_op!(vm, [value], 1, value.checked_add(1).ok_or_else(|| TxScriptError::NumberTooBig("todo".to_string()))?) } opcode Op1Sub<0x8c, 1>(self, vm) { - let [value]: [i64; 1] = vm.dstack.pop_items()?; - let r = value.checked_sub(1).ok_or_else(|| TxScriptError::NumberTooBig("todo".to_string()))?; - vm.dstack.push_item(r); - Ok(()) + numeric_op!(vm, [value], 1, value.checked_sub(1).ok_or_else(|| TxScriptError::NumberTooBig("todo".to_string()))?) } opcode Op2Mul<0x8d, 1>(self, vm) Err(TxScriptError::OpcodeDisabled(format!("{self:?}"))) opcode Op2Div<0x8e, 1>(self, vm) Err(TxScriptError::OpcodeDisabled(format!("{self:?}"))) opcode OpNegate<0x8f, 1>(self, vm) { - let [value]: [i64; 1] = vm.dstack.pop_items()?; - let r = value.checked_neg().ok_or_else(|| TxScriptError::NumberTooBig("todo".to_string()))?; - vm.dstack.push_item(r); - Ok(()) + numeric_op!(vm, [value], 1, value.checked_neg().ok_or_else(|| TxScriptError::NumberTooBig("todo".to_string()))?) } opcode OpAbs<0x90, 1>(self, vm) { - let [m]: [i64; 1] = vm.dstack.pop_items()?; - let r = m.checked_abs().ok_or_else(|| TxScriptError::NumberTooBig("todo".to_string()))?; - vm.dstack.push_item(r); - Ok(()) + numeric_op!(vm, [value], 1, value.checked_abs().ok_or_else(|| TxScriptError::NumberTooBig("todo".to_string()))?) } opcode OpNot<0x91, 1>(self, vm) { - let [m]: [i64; 1] = vm.dstack.pop_items()?; - vm.dstack.push_item((m == 0) as i64); - Ok(()) + numeric_op!(vm, [m], 1, (m == 0) as i64) } opcode Op0NotEqual<0x92, 1>(self, vm) { - let [m]: [i64; 1] = vm.dstack.pop_items()?; - vm.dstack.push_item((m != 0) as i64 ); - Ok(()) + numeric_op!(vm, [m], 1, (m != 0) as i64) } opcode OpAdd<0x93, 1>(self, vm) { - let [a,b]: [i64; 2] = vm.dstack.pop_items()?; - let r = a.checked_add(b).ok_or_else(|| TxScriptError::NumberTooBig("todo".to_string()))?; - vm.dstack.push_item(r); - Ok(()) + numeric_op!(vm, [a,b], 2, a.checked_add(b.into()).ok_or_else(|| TxScriptError::NumberTooBig("todo".to_string()))?) } opcode OpSub<0x94, 1>(self, vm) { - let [a,b]: [i64; 2] = vm.dstack.pop_items()?; - let r = a.checked_sub(b).ok_or_else(|| TxScriptError::NumberTooBig("todo".to_string()))?; - vm.dstack.push_item(r); - Ok(()) + numeric_op!(vm, [a,b], 2, a.checked_sub(b.into()).ok_or_else(|| TxScriptError::NumberTooBig("todo".to_string()))?) } opcode OpMul<0x95, 1>(self, vm) Err(TxScriptError::OpcodeDisabled(format!("{self:?}"))) @@ -629,77 +626,63 @@ opcode_list! { opcode OpRShift<0x99, 1>(self, vm) Err(TxScriptError::OpcodeDisabled(format!("{self:?}"))) opcode OpBoolAnd<0x9a, 1>(self, vm) { - let [a,b]: [i64; 2] = vm.dstack.pop_items()?; - vm.dstack.push_item(((a != 0) && (b != 0)) as i64); - Ok(()) + numeric_op!(vm, [a,b], 2, ((a != 0) && (b != 0)) as i64) } opcode OpBoolOr<0x9b, 1>(self, vm) { - let [a,b]: [i64; 2] = vm.dstack.pop_items()?; - vm.dstack.push_item(((a != 0) || (b != 0)) as i64); - Ok(()) + numeric_op!(vm, [a,b], 2, ((a != 0) || (b != 0)) as i64) } opcode OpNumEqual<0x9c, 1>(self, vm) { - let [a,b]: [i64; 2] = vm.dstack.pop_items()?; - vm.dstack.push_item((a == b) as i64); - Ok(()) + numeric_op!(vm, [a,b], 2, (a == b) as i64) } opcode OpNumEqualVerify<0x9d, 1>(self, vm) { - let [a,b]: [i64; 2] = vm.dstack.pop_items()?; - match a == b { - true => Ok(()), - false => Err(TxScriptError::VerifyError) + if vm.kip10_enabled { + let [a,b]: [Kip10I64; 2] = vm.dstack.pop_items()?; + match a == b { + true => Ok(()), + false => Err(TxScriptError::VerifyError) + } + } else { + let [a,b]: [i64; 2] = vm.dstack.pop_items()?; + match a == b { + true => Ok(()), + false => Err(TxScriptError::VerifyError) + } } } opcode OpNumNotEqual<0x9e, 1>(self, vm) { - let [a,b]: [i64; 2] = vm.dstack.pop_items()?; - vm.dstack.push_item((a != b) as i64); - Ok(()) + numeric_op!(vm, [a, b], 2, (a != b) as i64) } opcode OpLessThan<0x9f, 1>(self, vm) { - let [a,b]: [i64; 2] = vm.dstack.pop_items()?; - vm.dstack.push_item((a < b) as i64); - Ok(()) + numeric_op!(vm, [a, b], 2, (a < b) as i64) } opcode OpGreaterThan<0xa0, 1>(self, vm) { - let [a,b]: [i64; 2] = vm.dstack.pop_items()?; - vm.dstack.push_item((a > b) as i64); - Ok(()) + numeric_op!(vm, [a, b], 2, (a > b) as i64) } opcode OpLessThanOrEqual<0xa1, 1>(self, vm) { - let [a,b]: [i64; 2] = vm.dstack.pop_items()?; - vm.dstack.push_item((a <= b) as i64); - Ok(()) + numeric_op!(vm, [a, b], 2, (a <= b) as i64) } opcode OpGreaterThanOrEqual<0xa2, 1>(self, vm) { - let [a,b]: [i64; 2] = vm.dstack.pop_items()?; - vm.dstack.push_item((a >= b) as i64); - Ok(()) + numeric_op!(vm, [a, b], 2, (a >= b) as i64) } opcode OpMin<0xa3, 1>(self, vm) { - let [a,b]: [i64; 2] = vm.dstack.pop_items()?; - vm.dstack.push_item(min(a,b)); - Ok(()) + numeric_op!(vm, [a, b], 2, a.min(b)) } opcode OpMax<0xa4, 1>(self, vm) { - let [a,b]: [i64; 2] = vm.dstack.pop_items()?; - vm.dstack.push_item(max(a,b)); - Ok(()) + numeric_op!(vm, [a, b], 2, a.max(b)) } opcode OpWithin<0xa5, 1>(self, vm) { - let [x,l,u]: [i64; 3] = vm.dstack.pop_items()?; - vm.dstack.push_item((x >= l && x < u) as i64); - Ok(()) + numeric_op!(vm, [x,l,u], 3, (x >= l && x < u) as i64) } // Undefined opcodes. diff --git a/crypto/txscript/test-data/script_tests-kip10.json b/crypto/txscript/test-data/script_tests-kip10.json index 278c5231d..4525da3d2 100644 --- a/crypto/txscript/test-data/script_tests-kip10.json +++ b/crypto/txscript/test-data/script_tests-kip10.json @@ -3592,18 +3592,18 @@ "EVAL_FALSE" ], [ - "2147483648 0", + "9223372036854775808 0", "ADD NOP", "", "UNKNOWN_ERROR", - "arithmetic operands must be in range [-2^31...2^31] " + "" ], [ - "-2147483648 0", + "-9223372036854775807 0", "ADD NOP", "", "UNKNOWN_ERROR", - "arithmetic operands must be in range [-2^31...2^31] " + "" ], [ "2147483647", @@ -4241,18 +4241,18 @@ "OP_RESERVED2 is reserved" ], [ - "2147483648", + "9223372036854775808", "1ADD 1", "", "UNKNOWN_ERROR", - "We cannot do math on 5-byte integers" + "We cannot do math on 9-byte integers" ], [ - "2147483648", + "9223372036854775808", "NEGATE 1", "", "UNKNOWN_ERROR", - "We cannot do math on 5-byte integers" + "We cannot do math on 9-byte integers" ], [ "-2147483648", @@ -4262,18 +4262,18 @@ "Because we use a sign bit, -2147483648 is also 5 bytes" ], [ - "2147483647", + "9223372036854775808", "1ADD 1SUB 1", "", "UNKNOWN_ERROR", - "We cannot do math on 5-byte integers, even if the result is 4-bytes" + "We cannot do math on 9-byte integers, even if the result is 8-bytes" ], [ - "2147483648", + "9223372036854775808", "1SUB 1", "", "UNKNOWN_ERROR", - "We cannot do math on 5-byte integers, even if the result is 4-bytes" + "We cannot do math on 9-byte integers, even if the result is 8-bytes" ], [ "2147483648 1", From 186db4613231262d379d6ec1807aee299e1ed841 Mon Sep 17 00:00:00 2001 From: max143672 Date: Thu, 7 Nov 2024 16:53:40 +0400 Subject: [PATCH 43/93] use i64 value in 9-byte tests --- crypto/txscript/test-data/script_tests-kip10.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crypto/txscript/test-data/script_tests-kip10.json b/crypto/txscript/test-data/script_tests-kip10.json index 4525da3d2..0d5b1761f 100644 --- a/crypto/txscript/test-data/script_tests-kip10.json +++ b/crypto/txscript/test-data/script_tests-kip10.json @@ -3592,7 +3592,7 @@ "EVAL_FALSE" ], [ - "9223372036854775808 0", + "-9223372036854775808 0", "ADD NOP", "", "UNKNOWN_ERROR", @@ -4241,14 +4241,14 @@ "OP_RESERVED2 is reserved" ], [ - "9223372036854775808", + "-9223372036854775808", "1ADD 1", "", "UNKNOWN_ERROR", "We cannot do math on 9-byte integers" ], [ - "9223372036854775808", + "-9223372036854775808", "NEGATE 1", "", "UNKNOWN_ERROR", @@ -4262,14 +4262,14 @@ "Because we use a sign bit, -2147483648 is also 5 bytes" ], [ - "9223372036854775808", + "-9223372036854775808", "1ADD 1SUB 1", "", "UNKNOWN_ERROR", "We cannot do math on 9-byte integers, even if the result is 8-bytes" ], [ - "9223372036854775808", + "-9223372036854775808", "1SUB 1", "", "UNKNOWN_ERROR", From 96fb3dfa7b3faba651a59cea982610d6c999f896 Mon Sep 17 00:00:00 2001 From: max143672 Date: Thu, 7 Nov 2024 17:06:01 +0400 Subject: [PATCH 44/93] fix tests covering kip10 and i64 deserialization --- crypto/txscript/src/data_stack.rs | 180 ++++++++++++++++++++++++++---- 1 file changed, 156 insertions(+), 24 deletions(-) diff --git a/crypto/txscript/src/data_stack.rs b/crypto/txscript/src/data_stack.rs index 81367b3dd..0ccd47627 100644 --- a/crypto/txscript/src/data_stack.rs +++ b/crypto/txscript/src/data_stack.rs @@ -336,7 +336,7 @@ impl DataStack for Stack { #[cfg(test)] mod tests { - use super::OpcodeData; + use super::{Kip10I64, OpcodeData}; use crate::data_stack::SizedEncodeInt; use kaspa_txscript_errors::TxScriptError; @@ -374,6 +374,8 @@ mod tests { TestCase { num: -8388608, serialized: hex::decode("00008080").expect("failed parsing hex") }, TestCase { num: 2147483647, serialized: hex::decode("ffffff7f").expect("failed parsing hex") }, TestCase { num: -2147483647, serialized: hex::decode("ffffffff").expect("failed parsing hex") }, + // Values that are out of range for data that is interpreted as + // numbers before KIP-10 enabled, but are allowed as the result of numeric operations. TestCase { num: 2147483648, serialized: hex::decode("0000008000").expect("failed parsing hex") }, TestCase { num: -2147483648, serialized: hex::decode("0000008080").expect("failed parsing hex") }, TestCase { num: 2415919104, serialized: hex::decode("0000009000").expect("failed parsing hex") }, @@ -389,7 +391,7 @@ mod tests { TestCase { num: 9223372036854775807, serialized: hex::decode("ffffffffffffff7f").expect("failed parsing hex") }, TestCase { num: -9223372036854775807, serialized: hex::decode("ffffffffffffffff").expect("failed parsing hex") }, // Values that are out of range for data that is interpreted as - // numbers, but are allowed as the result of numeric operations. + // numbers after KIP-10 enabled, but are allowed as the result of numeric operations. TestCase { num: -9223372036854775808, serialized: hex::decode("000000000000008080").expect("failed parsing hex") }, ]; @@ -439,39 +441,98 @@ mod tests { TestCase:: { serialized: hex::decode("00008080").expect("failed parsing hex"), result: Ok(-8388608) }, TestCase:: { serialized: hex::decode("ffffff7f").expect("failed parsing hex"), result: Ok(2147483647) }, TestCase:: { serialized: hex::decode("ffffffff").expect("failed parsing hex"), result: Ok(-2147483647) }, - TestCase:: { serialized: hex::decode("0000008000").expect("failed parsing hex"), result: Ok(2147483648) }, - TestCase:: { serialized: hex::decode("0000008080").expect("failed parsing hex"), result: Ok(-2147483648) }, - TestCase:: { serialized: hex::decode("0000009000").expect("failed parsing hex"), result: Ok(2415919104) }, - TestCase:: { serialized: hex::decode("0000009080").expect("failed parsing hex"), result: Ok(-2415919104) }, - TestCase:: { serialized: hex::decode("ffffffff00").expect("failed parsing hex"), result: Ok(4294967295) }, - TestCase:: { serialized: hex::decode("ffffffff80").expect("failed parsing hex"), result: Ok(-4294967295) }, - TestCase:: { serialized: hex::decode("0000000001").expect("failed parsing hex"), result: Ok(4294967296) }, - TestCase:: { serialized: hex::decode("0000000081").expect("failed parsing hex"), result: Ok(-4294967296) }, - TestCase:: { serialized: hex::decode("ffffffffffff00").expect("failed parsing hex"), result: Ok(281474976710655) }, - TestCase:: { serialized: hex::decode("ffffffffffff80").expect("failed parsing hex"), result: Ok(-281474976710655) }, + /* + TestCase::{serialized: hex::decode("ffffffffffffff7f").expect("failed parsing hex"), num_len: 8, result: Ok(9223372036854775807)}, + TestCase::{serialized: hex::decode("ffffffffffffffff").expect("failed parsing hex"), num_len: 8, result: Ok(-9223372036854775807)},*/ + // Minimally encoded values that are out of range for data that + // is interpreted as script numbers with the minimal encoding + // flag set. Should error and return 0. + TestCase:: { + serialized: hex::decode("0000008000").expect("failed parsing hex"), + result: Err(TxScriptError::NumberTooBig( + "numeric value encoded as [0, 0, 0, 80, 0] is 5 bytes which exceeds the max allowed of 4".to_string(), + )), + }, + TestCase:: { + serialized: hex::decode("0000008080").expect("failed parsing hex"), + result: Err(TxScriptError::NumberTooBig( + "numeric value encoded as [0, 0, 0, 80, 80] is 5 bytes which exceeds the max allowed of 4".to_string(), + )), + }, + TestCase:: { + serialized: hex::decode("0000009000").expect("failed parsing hex"), + result: Err(TxScriptError::NumberTooBig( + "numeric value encoded as [0, 0, 0, 90, 0] is 5 bytes which exceeds the max allowed of 4".to_string(), + )), + }, + TestCase:: { + serialized: hex::decode("0000009080").expect("failed parsing hex"), + result: Err(TxScriptError::NumberTooBig( + "numeric value encoded as [0, 0, 0, 90, 80] is 5 bytes which exceeds the max allowed of 4".to_string(), + )), + }, + TestCase:: { + serialized: hex::decode("ffffffff00").expect("failed parsing hex"), + result: Err(TxScriptError::NumberTooBig( + "numeric value encoded as [ff, ff, ff, ff, 0] is 5 bytes which exceeds the max allowed of 4".to_string(), + )), + }, + TestCase:: { + serialized: hex::decode("ffffffff80").expect("failed parsing hex"), + result: Err(TxScriptError::NumberTooBig( + "numeric value encoded as [ff, ff, ff, ff, 80] is 5 bytes which exceeds the max allowed of 4".to_string(), + )), + }, + TestCase:: { + serialized: hex::decode("0000000001").expect("failed parsing hex"), + result: Err(TxScriptError::NumberTooBig( + "numeric value encoded as [0, 0, 0, 0, 1] is 5 bytes which exceeds the max allowed of 4".to_string(), + )), + }, + TestCase:: { + serialized: hex::decode("0000000081").expect("failed parsing hex"), + result: Err(TxScriptError::NumberTooBig( + "numeric value encoded as [0, 0, 0, 0, 81] is 5 bytes which exceeds the max allowed of 4".to_string(), + )), + }, + TestCase:: { + serialized: hex::decode("ffffffffffff00").expect("failed parsing hex"), + result: Err(TxScriptError::NumberTooBig( + "numeric value encoded as [ff, ff, ff, ff, ff, ff, 0] is 7 bytes which exceeds the max allowed of 4".to_string(), + )), + }, + TestCase:: { + serialized: hex::decode("ffffffffffff80").expect("failed parsing hex"), + result: Err(TxScriptError::NumberTooBig( + "numeric value encoded as [ff, ff, ff, ff, ff, ff, 80] is 7 bytes which exceeds the max allowed of 4".to_string(), + )), + }, TestCase:: { serialized: hex::decode("ffffffffffffff00").expect("failed parsing hex"), - result: Ok(72057594037927935), + result: Err(TxScriptError::NumberTooBig( + "numeric value encoded as [ff, ff, ff, ff, ff, ff, ff, 0] is 8 bytes which exceeds the max allowed of 4" + .to_string(), + )), }, TestCase:: { serialized: hex::decode("ffffffffffffff80").expect("failed parsing hex"), - result: Ok(-72057594037927935), + result: Err(TxScriptError::NumberTooBig( + "numeric value encoded as [ff, ff, ff, ff, ff, ff, ff, 80] is 8 bytes which exceeds the max allowed of 4" + .to_string(), + )), }, TestCase:: { serialized: hex::decode("ffffffffffffff7f").expect("failed parsing hex"), - result: Ok(9223372036854775807), + result: Err(TxScriptError::NumberTooBig( + "numeric value encoded as [ff, ff, ff, ff, ff, ff, ff, 7f] is 8 bytes which exceeds the max allowed of 4" + .to_string(), + )), }, TestCase:: { serialized: hex::decode("ffffffffffffffff").expect("failed parsing hex"), - result: Ok(-9223372036854775807), - }, - // Minimally encoded values that are out of range for data that - // is interpreted as script numbers with the minimal encoding - // flag set. Should error and return 0. - TestCase:: { - serialized: hex::decode("000000000000008080").expect("failed parsing hex"), result: Err(TxScriptError::NumberTooBig( - "numeric value encoded as [0, 0, 0, 0, 0, 0, 0, 80, 80] is 9 bytes which exceeds the max allowed of 8".to_string(), + "numeric value encoded as [ff, ff, ff, ff, ff, ff, ff, ff] is 8 bytes which exceeds the max allowed of 4" + .to_string(), )), }, // Non-minimally encoded, but otherwise valid values with @@ -532,7 +593,73 @@ mod tests { }, // 7340032 // Values above 8 bytes should always return error ]; - + let kip10_tests = vec![ + TestCase:: { + serialized: hex::decode("0000008000").expect("failed parsing hex"), + result: Ok(Kip10I64(2147483648)), + }, + TestCase:: { + serialized: hex::decode("0000008080").expect("failed parsing hex"), + result: Ok(Kip10I64(-2147483648)), + }, + TestCase:: { + serialized: hex::decode("0000009000").expect("failed parsing hex"), + result: Ok(Kip10I64(2415919104)), + }, + TestCase:: { + serialized: hex::decode("0000009080").expect("failed parsing hex"), + result: Ok(Kip10I64(-2415919104)), + }, + TestCase:: { + serialized: hex::decode("ffffffff00").expect("failed parsing hex"), + result: Ok(Kip10I64(4294967295)), + }, + TestCase:: { + serialized: hex::decode("ffffffff80").expect("failed parsing hex"), + result: Ok(Kip10I64(-4294967295)), + }, + TestCase:: { + serialized: hex::decode("0000000001").expect("failed parsing hex"), + result: Ok(Kip10I64(4294967296)), + }, + TestCase:: { + serialized: hex::decode("0000000081").expect("failed parsing hex"), + result: Ok(Kip10I64(-4294967296)), + }, + TestCase:: { + serialized: hex::decode("ffffffffffff00").expect("failed parsing hex"), + result: Ok(Kip10I64(281474976710655)), + }, + TestCase:: { + serialized: hex::decode("ffffffffffff80").expect("failed parsing hex"), + result: Ok(Kip10I64(-281474976710655)), + }, + TestCase:: { + serialized: hex::decode("ffffffffffffff00").expect("failed parsing hex"), + result: Ok(Kip10I64(72057594037927935)), + }, + TestCase:: { + serialized: hex::decode("ffffffffffffff80").expect("failed parsing hex"), + result: Ok(Kip10I64(-72057594037927935)), + }, + TestCase:: { + serialized: hex::decode("ffffffffffffff7f").expect("failed parsing hex"), + result: Ok(Kip10I64(9223372036854775807)), + }, + TestCase:: { + serialized: hex::decode("ffffffffffffffff").expect("failed parsing hex"), + result: Ok(Kip10I64(-9223372036854775807)), + }, + // Minimally encoded values that are out of range for data that + // is interpreted as script numbers with the minimal encoding + // flag set. Should error and return 0. + TestCase:: { + serialized: hex::decode("000000000000008080").expect("failed parsing hex"), + result: Err(TxScriptError::NumberTooBig( + "numeric value encoded as [0, 0, 0, 0, 0, 0, 0, 80, 80] is 9 bytes which exceeds the max allowed of 8".to_string(), + )), + }, + ]; let test_of_size_5 = vec![ TestCase::> { serialized: hex::decode("ffffffff7f").expect("failed parsing hex"), @@ -628,5 +755,10 @@ mod tests { // code matches the value specified in the test instance. assert_eq!(test.serialized.deserialize(), test.result); } + for test in kip10_tests { + // Ensure the error code is of the expected type and the error + // code matches the value specified in the test instance. + assert_eq!(test.serialized.deserialize(), test.result); + } } } From 2810a887ac0f2c6bfae3858c65f3ddbc84606442 Mon Sep 17 00:00:00 2001 From: max143672 Date: Thu, 7 Nov 2024 17:36:21 +0400 Subject: [PATCH 45/93] fix test according to 8-byte math --- .../test-data/script_tests-kip10.json | 25 +++++++------------ 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/crypto/txscript/test-data/script_tests-kip10.json b/crypto/txscript/test-data/script_tests-kip10.json index 0d5b1761f..5c1347c51 100644 --- a/crypto/txscript/test-data/script_tests-kip10.json +++ b/crypto/txscript/test-data/script_tests-kip10.json @@ -3598,22 +3598,15 @@ "UNKNOWN_ERROR", "" ], - [ - "-9223372036854775807 0", - "ADD NOP", - "", - "UNKNOWN_ERROR", - "" - ], [ "2147483647", "DUP ADD 4294967294 NUMEQUAL", "", - "UNKNOWN_ERROR", - "NUMEQUAL must be in numeric range" + "OK", + "NUMEQUAL is in numeric range" ], [ - "'abcdef'", + "'abcdefghi'", "NOT 0 EQUAL", "", "UNKNOWN_ERROR", @@ -4255,11 +4248,11 @@ "We cannot do math on 9-byte integers" ], [ - "-2147483648", + "-9223372036854775808", "1ADD 1", "", "UNKNOWN_ERROR", - "Because we use a sign bit, -2147483648 is also 5 bytes" + "Because we use a sign bit, -9223372036854775808 is also 9 bytes" ], [ "-9223372036854775808", @@ -4276,18 +4269,18 @@ "We cannot do math on 9-byte integers, even if the result is 8-bytes" ], [ - "2147483648 1", + "-9223372036854775808 1", "BOOLOR 1", "", "UNKNOWN_ERROR", - "We cannot do BOOLOR on 5-byte integers (but we can still do IF etc)" + "We cannot do BOOLOR on 9-byte integers (but we can still do IF etc)" ], [ - "2147483648 1", + "-9223372036854775808 1", "BOOLAND 1", "", "UNKNOWN_ERROR", - "We cannot do BOOLAND on 5-byte integers" + "We cannot do BOOLAND on 9-byte integers" ], [ "1", From 246537b381ff96f20485938ee4af740c5b15f785 Mon Sep 17 00:00:00 2001 From: max143672 Date: Thu, 7 Nov 2024 17:55:13 +0400 Subject: [PATCH 46/93] finish test covering kip10 opcodes: input/output/amount/spk --- crypto/txscript/src/opcodes/mod.rs | 213 +++++++++++++---------------- 1 file changed, 98 insertions(+), 115 deletions(-) diff --git a/crypto/txscript/src/opcodes/mod.rs b/crypto/txscript/src/opcodes/mod.rs index 55ad34b3b..f800e90db 100644 --- a/crypto/txscript/src/opcodes/mod.rs +++ b/crypto/txscript/src/opcodes/mod.rs @@ -2978,8 +2978,7 @@ mod test { mod kip10 { use super::*; - use crate::opcodes::push_number; - + use crate::{data_stack::OpcodeData, opcodes::push_number}; #[derive(Clone, Debug)] struct Kip10Mock { spk: ScriptPublicKey, @@ -2993,16 +2992,18 @@ mod test { expected_results: Vec, } - #[derive(Debug)] - enum ExpectedResult { - Success { operation: Operation, input: i64, expected_spk: Vec }, - Error { operation: Operation, input: Option, expected_error: TxScriptError }, - } - #[derive(Debug)] enum Operation { InputSpk, OutputSpk, + InputAmount, + OutputAmount, + } + + #[derive(Debug)] + enum ExpectedResult { + Success { operation: Operation, index: i64, expected_spk: Option>, expected_amount: Option> }, + Error { operation: Operation, index: Option, expected_error: TxScriptError }, } fn create_mock_spk(value: u8) -> ScriptPublicKey { @@ -3033,8 +3034,10 @@ mod test { let output_spk1 = create_mock_spk(3); let output_spk2 = create_mock_spk(4); - let inputs = vec![Kip10Mock { spk: input_spk1.clone(), amount: 0 }, Kip10Mock { spk: input_spk2.clone(), amount: 0 }]; - let outputs = vec![Kip10Mock { spk: output_spk1.clone(), amount: 0 }, Kip10Mock { spk: output_spk2.clone(), amount: 0 }]; + let inputs = + vec![Kip10Mock { spk: input_spk1.clone(), amount: 1111 }, Kip10Mock { spk: input_spk2.clone(), amount: 2222 }]; + let outputs = + vec![Kip10Mock { spk: output_spk1.clone(), amount: 3333 }, Kip10Mock { spk: output_spk2.clone(), amount: 4444 }]; let (tx, utxo_entries) = kip_10_tx_mock(inputs, outputs); let tx = PopulatedTransaction::new(&tx, utxo_entries); @@ -3055,30 +3058,42 @@ mod test { let op_input_spk = opcodes::OpInputSpk::empty().expect("Should accept empty"); let op_output_spk = opcodes::OpOutputSpk::empty().expect("Should accept empty"); + let op_input_amount = opcodes::OpInputAmount::empty().expect("Should accept empty"); + let op_output_amount = opcodes::OpOutputAmount::empty().expect("Should accept empty"); for expected_result in &test_case.expected_results { match expected_result { - ExpectedResult::Success { operation, input, expected_spk } => { - push_number(*input, &mut vm).unwrap(); + ExpectedResult::Success { operation, index, expected_spk, expected_amount } => { + push_number(*index, &mut vm).unwrap(); match operation { Operation::InputSpk => { op_input_spk.execute(&mut vm).unwrap(); - assert_eq!(vm.dstack, vec![expected_spk.clone()]); + assert_eq!(vm.dstack, vec![expected_spk.clone().unwrap()]); } Operation::OutputSpk => { op_output_spk.execute(&mut vm).unwrap(); - assert_eq!(vm.dstack, vec![expected_spk.clone()]); + assert_eq!(vm.dstack, vec![expected_spk.clone().unwrap()]); + } + Operation::InputAmount => { + op_input_amount.execute(&mut vm).unwrap(); + assert_eq!(vm.dstack, vec![expected_amount.clone().unwrap()]); + } + Operation::OutputAmount => { + op_output_amount.execute(&mut vm).unwrap(); + assert_eq!(vm.dstack, vec![expected_amount.clone().unwrap()]); } } vm.dstack.clear(); } - ExpectedResult::Error { operation, input, expected_error } => { - if let Some(input_value) = input { - push_number(*input_value, &mut vm).unwrap(); + ExpectedResult::Error { operation, index, expected_error } => { + if let Some(idx) = index { + push_number(*idx, &mut vm).unwrap(); } let result = match operation { Operation::InputSpk => op_input_spk.execute(&mut vm), Operation::OutputSpk => op_output_spk.execute(&mut vm), + Operation::InputAmount => op_input_amount.execute(&mut vm), + Operation::OutputAmount => op_output_amount.execute(&mut vm), }; assert!( matches!(result, Err(ref e) if std::mem::discriminant(e) == std::mem::discriminant(expected_error)) @@ -3091,7 +3106,7 @@ mod test { } #[test] - fn test_op_spk() { + fn test_nullary_introspection_ops() { let test_cases = vec![ TestCase { name: "KIP-10 disabled", @@ -3099,12 +3114,22 @@ mod test { expected_results: vec![ ExpectedResult::Error { operation: Operation::InputSpk, - input: Some(0), + index: Some(0), expected_error: TxScriptError::InvalidOpcode("Invalid opcode".to_string()), }, ExpectedResult::Error { operation: Operation::OutputSpk, - input: Some(0), + index: Some(0), + expected_error: TxScriptError::InvalidOpcode("Invalid opcode".to_string()), + }, + ExpectedResult::Error { + operation: Operation::InputAmount, + index: Some(0), + expected_error: TxScriptError::InvalidOpcode("Invalid opcode".to_string()), + }, + ExpectedResult::Error { + operation: Operation::OutputAmount, + index: Some(0), expected_error: TxScriptError::InvalidOpcode("Invalid opcode".to_string()), }, ], @@ -3115,13 +3140,27 @@ mod test { expected_results: vec![ ExpectedResult::Success { operation: Operation::InputSpk, - input: 0, - expected_spk: create_mock_spk(1).to_bytes(), + index: 0, + expected_spk: Some(create_mock_spk(1).to_bytes()), + expected_amount: None, }, ExpectedResult::Success { operation: Operation::InputSpk, - input: 1, - expected_spk: create_mock_spk(2).to_bytes(), + index: 1, + expected_spk: Some(create_mock_spk(2).to_bytes()), + expected_amount: None, + }, + ExpectedResult::Success { + operation: Operation::InputAmount, + index: 0, + expected_spk: None, + expected_amount: Some(OpcodeData::::serialize(&1111)), + }, + ExpectedResult::Success { + operation: Operation::InputAmount, + index: 1, + expected_spk: None, + expected_amount: Some(OpcodeData::::serialize(&2222)), }, ], }, @@ -3131,13 +3170,27 @@ mod test { expected_results: vec![ ExpectedResult::Success { operation: Operation::OutputSpk, - input: 0, - expected_spk: create_mock_spk(3).to_bytes(), + index: 0, + expected_spk: Some(create_mock_spk(3).to_bytes()), + expected_amount: None, }, ExpectedResult::Success { operation: Operation::OutputSpk, - input: 1, - expected_spk: create_mock_spk(4).to_bytes(), + index: 1, + expected_spk: Some(create_mock_spk(4).to_bytes()), + expected_amount: None, + }, + ExpectedResult::Success { + operation: Operation::OutputAmount, + index: 0, + expected_spk: None, + expected_amount: Some(OpcodeData::::serialize(&3333)), + }, + ExpectedResult::Success { + operation: Operation::OutputAmount, + index: 1, + expected_spk: None, + expected_amount: Some(OpcodeData::::serialize(&4444)), }, ], }, @@ -3146,28 +3199,33 @@ mod test { kip10_enabled: true, expected_results: vec![ ExpectedResult::Error { - operation: Operation::InputSpk, - input: None, // empty stack case + operation: Operation::InputAmount, + index: None, expected_error: TxScriptError::InvalidStackOperation(1, 0), }, ExpectedResult::Error { - operation: Operation::InputSpk, - input: Some(-1), + operation: Operation::InputAmount, + index: Some(-1), expected_error: TxScriptError::InvalidIndex(u8::MAX as usize + 1, 2), }, ExpectedResult::Error { - operation: Operation::InputSpk, - input: Some(u8::MAX as i64 + 1), - expected_error: TxScriptError::InvalidIndex(u8::MAX as usize + 1, 2), + operation: Operation::InputAmount, + index: Some(2), + expected_error: TxScriptError::InvalidIndex(2, 2), }, ExpectedResult::Error { - operation: Operation::InputSpk, - input: Some(2), - expected_error: TxScriptError::InvalidIndex(2, 2), + operation: Operation::OutputAmount, + index: None, + expected_error: TxScriptError::InvalidStackOperation(1, 0), }, ExpectedResult::Error { - operation: Operation::OutputSpk, - input: Some(2), + operation: Operation::OutputAmount, + index: Some(-1), + expected_error: TxScriptError::InvalidIndex(u8::MAX as usize + 1, 2), + }, + ExpectedResult::Error { + operation: Operation::OutputAmount, + index: Some(2), expected_error: TxScriptError::InvalidOutputIndex(2, 2), }, ], @@ -3179,80 +3237,5 @@ mod test { execute_test_case(&test_case); } } - - // #[test] - // fn test_op_output_spk() { - // let input_pub_key = vec![1u8; 32]; - // let input_addr = Address::new(Prefix::Testnet, Version::PubKey, &input_pub_key); - // let input_script_public_key = pay_to_address_script(&input_addr); - // let output_pub_key = vec![2u8; 32]; - // let output_addr = Address::new(Prefix::Testnet, Version::PubKey, &output_pub_key); - // let output_script_public_key = pay_to_address_script(&output_addr); - // [None, Some(output_script_public_key.clone())].into_iter().for_each(|output_spk| { - // kip_10_tx_mock(input_script_public_key.clone(), output_spk.clone(), 0, 0, |mut vm| { - // let code = opcodes::OpOutputSpk::empty().expect("Should accept empty"); - // code.execute(&mut vm).unwrap(); - // assert_eq!(vm.dstack, vec![output_spk.map(|output_spk| output_spk.to_bytes()).unwrap_or_default()]); - // vm.dstack.clear(); - // }); - // }); - // } - // - // #[test] - // fn test_op_input_amount() { - // let input_pub_key = vec![1u8; 32]; - // let input_addr = Address::new(Prefix::Testnet, Version::PubKey, &input_pub_key); - // let input_script_public_key = pay_to_address_script(&input_addr); - // [0, 268_435_455].into_iter().for_each(|amount| { - // kip_10_tx_mock(input_script_public_key.clone(), None, amount, 0, |mut vm| { - // let code = opcodes::OpInputAmount::empty().expect("Should accept empty"); - // code.execute(&mut vm).unwrap(); - // let actual: i64 = vm.dstack[0].deserialize().unwrap(); - // assert_eq!(actual, amount as i64); - // }); - // }); - // - // const MAX_SOMPI: u64 = 29_000_000_000 * SOMPI_PER_KASPA; // works if serialized to SizedEncodeInt<8> - // kip_10_tx_mock(input_script_public_key.clone(), None, MAX_SOMPI, 0, |mut vm| { - // let code = opcodes::OpInputAmount::empty().expect("Should accept empty"); - // code.execute(&mut vm).unwrap(); - // let actual: SizedEncodeInt<8> = vm.dstack[0].deserialize().unwrap(); - // assert_eq!(actual.0, MAX_SOMPI as i64); - // }); - // } - // - // #[test] - // fn test_op_output_amount() { - // let input_pub_key = vec![1u8; 32]; - // let input_addr = Address::new(Prefix::Testnet, Version::PubKey, &input_pub_key); - // let input_script_public_key = pay_to_address_script(&input_addr); - // let output_pub_key = vec![2u8; 32]; - // let output_addr = Address::new(Prefix::Testnet, Version::PubKey, &output_pub_key); - // let output_script_public_key = pay_to_address_script(&output_addr); - // - // kip_10_tx_mock(input_script_public_key.clone(), None, 0, 0, |mut vm| { - // let code = opcodes::OpOutputAmount::empty().expect("Should accept empty"); - // code.execute(&mut vm).unwrap(); - // let actual: i64 = vm.dstack[0].deserialize().unwrap(); - // assert_eq!(actual, 0); - // }); - // - // [0, 268_435_455].into_iter().for_each(|amount| { - // kip_10_tx_mock(input_script_public_key.clone(), Some(output_script_public_key.clone()), 0, amount, |mut vm| { - // let code = opcodes::OpOutputAmount::empty().expect("Should accept empty"); - // code.execute(&mut vm).unwrap(); - // let actual: i64 = vm.dstack[0].deserialize().unwrap(); - // assert_eq!(actual, amount as i64); - // }); - // }); - // - // const MAX_SOMPI: u64 = 29_000_000_000 * SOMPI_PER_KASPA; // works if serialized to SizedEncodeInt<8> - // kip_10_tx_mock(input_script_public_key.clone(), Some(output_script_public_key.clone()), 0, MAX_SOMPI, |mut vm| { - // let code = opcodes::OpOutputAmount::empty().expect("Should accept empty"); - // code.execute(&mut vm).unwrap(); - // let actual: SizedEncodeInt<8> = vm.dstack[0].deserialize().unwrap(); - // assert_eq!(actual.0, MAX_SOMPI as i64); - // }); - // } } } From a432e968793d22f1507c663387b6470848259702 Mon Sep 17 00:00:00 2001 From: max143672 Date: Thu, 7 Nov 2024 18:03:57 +0400 Subject: [PATCH 47/93] fix kip10 examples --- crypto/txscript/examples/kip-10.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/crypto/txscript/examples/kip-10.rs b/crypto/txscript/examples/kip-10.rs index d3e028b9f..f0c857691 100644 --- a/crypto/txscript/examples/kip-10.rs +++ b/crypto/txscript/examples/kip-10.rs @@ -23,6 +23,7 @@ use kaspa_txscript::{ use kaspa_txscript_errors::TxScriptError::{EvalFalse, VerifyError}; use rand::thread_rng; use secp256k1::Keypair; +use kaspa_txscript::opcodes::codes::Op0; /// Main function to execute all Kaspa transaction script scenarios. /// @@ -71,9 +72,9 @@ fn threshold_scenario() -> ScriptBuilderResult<()> { .add_op(OpCheckSig)? // Borrower branch .add_op(OpElse)? - .add_ops(&[OpInputSpk, OpOutputSpk, OpEqualVerify, OpOutputAmount])? + .add_ops(&[Op0, OpInputSpk, Op0, OpOutputSpk, OpEqualVerify, Op0, OpOutputAmount])? .add_i64(threshold)? - .add_ops(&[OpSub, OpInputAmount, OpGreaterThanOrEqual])? + .add_ops(&[OpSub, Op0, OpInputAmount, OpGreaterThanOrEqual])? .add_op(OpEndIf)? .drain(); @@ -188,9 +189,9 @@ fn generate_limited_time_script(owner: &Keypair, threshold: i64, output_spk: Vec // Borrower branch .add_op(OpElse)? .add_data(&output_spk)? - .add_ops(&[OpOutputSpk, OpEqualVerify, OpOutputAmount])? + .add_ops(&[Op0, OpOutputSpk, OpEqualVerify, Op0, OpOutputAmount])? .add_i64(threshold)? - .add_ops(&[OpSub, OpInputAmount, OpGreaterThanOrEqual])? + .add_ops(&[OpSub, Op0, OpInputAmount, OpGreaterThanOrEqual])? .add_op(OpEndIf)? .drain(); @@ -578,7 +579,7 @@ fn shared_secret_scenario() -> ScriptBuilderResult<()> { .add_data(shared_secret_kp.x_only_public_key().0.serialize().as_slice())? .add_op(OpEqualVerify)? .add_op(OpCheckSigVerify)? - .add_ops(&[OpInputSpk, OpOutputSpk, OpEqualVerify, OpOutputAmount, OpInputAmount, OpGreaterThanOrEqual])? + .add_ops(&[Op0, OpInputSpk, Op0, OpOutputSpk, OpEqualVerify, Op0, OpOutputAmount, Op0, OpInputAmount, OpGreaterThanOrEqual])? .add_op(OpEndIf)? .drain(); From a09fdb7ab22b2a5d576a55fac206823a38e30eb3 Mon Sep 17 00:00:00 2001 From: max143672 Date: Thu, 7 Nov 2024 18:17:00 +0400 Subject: [PATCH 48/93] rename test --- crypto/txscript/src/opcodes/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto/txscript/src/opcodes/mod.rs b/crypto/txscript/src/opcodes/mod.rs index f800e90db..99f78d4b8 100644 --- a/crypto/txscript/src/opcodes/mod.rs +++ b/crypto/txscript/src/opcodes/mod.rs @@ -3106,7 +3106,7 @@ mod test { } #[test] - fn test_nullary_introspection_ops() { + fn test_unary_introspection_ops() { let test_cases = vec![ TestCase { name: "KIP-10 disabled", From 372a7ad37fab33157c1c689407422c4ebaef040f Mon Sep 17 00:00:00 2001 From: max143672 Date: Thu, 7 Nov 2024 18:30:05 +0400 Subject: [PATCH 49/93] feat: add input index op --- crypto/txscript/src/opcodes/mod.rs | 37 +++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/crypto/txscript/src/opcodes/mod.rs b/crypto/txscript/src/opcodes/mod.rs index 99f78d4b8..470124930 100644 --- a/crypto/txscript/src/opcodes/mod.rs +++ b/crypto/txscript/src/opcodes/mod.rs @@ -872,6 +872,8 @@ opcode_list! { _ => Err(TxScriptError::InvalidSource("LockTimeVerify only applies to transaction inputs".to_string())) } } + + // Introspection opcodes opcode OpInputSpk<0xb2, 1>(self, vm) { if vm.kip10_enabled { match vm.script_source { @@ -953,12 +955,23 @@ opcode_list! { } } - // Undefined opcodes. - opcode OpUnknown182<0xb6, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) - opcode OpUnknown183<0xb7, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) - opcode OpUnknown184<0xb8, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) - opcode OpUnknown185<0xb9, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) + opcode OpInputIndex<0xb6, 1>(self, vm) { + if vm.kip10_enabled { + match vm.script_source { + ScriptSource::TxInput{id, ..} => { + push_number(id as i64, vm) + }, + _ => Err(TxScriptError::InvalidSource("OpOutputAmount only applies to transaction inputs".to_string())) + } + } else { + Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) + } + } + opcode OpUnknown183<0xb7, 1>(self, vm) Err(TxScriptError::OpcodeDisabled(format!("{self:?}"))) + opcode OpUnknown184<0xb8, 1>(self, vm) Err(TxScriptError::OpcodeDisabled(format!("{self:?}"))) + opcode OpUnknown185<0xb9, 1>(self, vm) Err(TxScriptError::OpcodeDisabled(format!("{self:?}"))) opcode OpUnknown186<0xba, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) + // Undefined opcodes. opcode OpUnknown187<0xbb, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) opcode OpUnknown188<0xbc, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) opcode OpUnknown189<0xbd, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) @@ -1162,7 +1175,6 @@ mod test { let tests: Vec>> = vec![ opcodes::OpUnknown166::empty().expect("Should accept empty"), opcodes::OpUnknown167::empty().expect("Should accept empty"), - opcodes::OpUnknown182::empty().expect("Should accept empty"), opcodes::OpUnknown183::empty().expect("Should accept empty"), opcodes::OpUnknown184::empty().expect("Should accept empty"), opcodes::OpUnknown185::empty().expect("Should accept empty"), @@ -2979,6 +2991,8 @@ mod test { mod kip10 { use super::*; use crate::{data_stack::OpcodeData, opcodes::push_number}; + use crate::data_stack::DataStack; + #[derive(Clone, Debug)] struct Kip10Mock { spk: ScriptPublicKey, @@ -3055,6 +3069,17 @@ mod test { test_case.kip10_enabled, ) .unwrap(); + let op_input_idx = opcodes::OpInputIndex::empty().expect("Should accept empty"); + + if !test_case.kip10_enabled { + assert!(matches!(op_input_idx.execute(&mut vm), Err(TxScriptError::InvalidOpcode(_)))); + } else { + let mut expected = vm.dstack.clone(); + expected.push_item(current_idx as i64); + op_input_idx.execute(&mut vm).unwrap(); + assert_eq!(vm.dstack, expected); + vm.dstack.clear(); + } let op_input_spk = opcodes::OpInputSpk::empty().expect("Should accept empty"); let op_output_spk = opcodes::OpOutputSpk::empty().expect("Should accept empty"); From 7e81eb2e17b68497b3027b9c77119ab201fcc987 Mon Sep 17 00:00:00 2001 From: max143672 Date: Thu, 7 Nov 2024 18:34:36 +0400 Subject: [PATCH 50/93] feat: add input/outpiut opcodes --- crypto/txscript/src/opcodes/mod.rs | 126 +++++++++++++++++++++++++++-- 1 file changed, 121 insertions(+), 5 deletions(-) diff --git a/crypto/txscript/src/opcodes/mod.rs b/crypto/txscript/src/opcodes/mod.rs index 470124930..176d73a87 100644 --- a/crypto/txscript/src/opcodes/mod.rs +++ b/crypto/txscript/src/opcodes/mod.rs @@ -967,8 +967,30 @@ opcode_list! { Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) } } - opcode OpUnknown183<0xb7, 1>(self, vm) Err(TxScriptError::OpcodeDisabled(format!("{self:?}"))) - opcode OpUnknown184<0xb8, 1>(self, vm) Err(TxScriptError::OpcodeDisabled(format!("{self:?}"))) + opcode OpInputCount<0xb7, 1>(self, vm) { + if vm.kip10_enabled { + match vm.script_source { + ScriptSource::TxInput{tx, ..} => { + push_number(tx.inputs().len() as i64, vm) + }, + _ => Err(TxScriptError::InvalidSource("OpInputCount only applies to transaction inputs".to_string())) + } + } else { + Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) + } + } + opcode OpOutputCount<0xb8, 1>(self, vm) { + if vm.kip10_enabled { + match vm.script_source { + ScriptSource::TxInput{tx, ..} => { + push_number(tx.outputs().len() as i64, vm) + }, + _ => Err(TxScriptError::InvalidSource("OpOutputCount only applies to transaction inputs".to_string())) + } + } else { + Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) + } + } opcode OpUnknown185<0xb9, 1>(self, vm) Err(TxScriptError::OpcodeDisabled(format!("{self:?}"))) opcode OpUnknown186<0xba, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) // Undefined opcodes. @@ -1175,8 +1197,6 @@ mod test { let tests: Vec>> = vec![ opcodes::OpUnknown166::empty().expect("Should accept empty"), opcodes::OpUnknown167::empty().expect("Should accept empty"), - opcodes::OpUnknown183::empty().expect("Should accept empty"), - opcodes::OpUnknown184::empty().expect("Should accept empty"), opcodes::OpUnknown185::empty().expect("Should accept empty"), opcodes::OpUnknown186::empty().expect("Should accept empty"), opcodes::OpUnknown187::empty().expect("Should accept empty"), @@ -2990,8 +3010,8 @@ mod test { mod kip10 { use super::*; - use crate::{data_stack::OpcodeData, opcodes::push_number}; use crate::data_stack::DataStack; + use crate::{data_stack::OpcodeData, opcodes::push_number}; #[derive(Clone, Debug)] struct Kip10Mock { @@ -3262,5 +3282,101 @@ mod test { execute_test_case(&test_case); } } + fn create_mock_tx(input_count: usize, output_count: usize) -> (Transaction, Vec) { + let dummy_prev_out = TransactionOutpoint::new(kaspa_hashes::Hash::from_u64_word(1), 1); + let dummy_sig_script = vec![0u8; 65]; + + // Create inputs with different SPKs and amounts + let inputs: Vec = + (0..input_count).map(|i| Kip10Mock { spk: create_mock_spk(i as u8), amount: 1000 + i as u64 }).collect(); + + // Create outputs with different SPKs and amounts + let outputs: Vec = + (0..output_count).map(|i| Kip10Mock { spk: create_mock_spk((100 + i) as u8), amount: 2000 + i as u64 }).collect(); + + let (utxos, tx_inputs): (Vec<_>, Vec<_>) = inputs + .into_iter() + .map(|Kip10Mock { spk, amount }| { + (UtxoEntry::new(amount, spk, 0, false), TransactionInput::new(dummy_prev_out, dummy_sig_script.clone(), 10, 0)) + }) + .unzip(); + + let tx_outputs: Vec<_> = + outputs.into_iter().map(|Kip10Mock { spk, amount }| TransactionOutput::new(amount, spk)).collect(); + + let tx = Transaction::new(TX_VERSION + 1, tx_inputs, tx_outputs, 0, SUBNETWORK_ID_NATIVE, 0, vec![]); + + (tx, utxos) + } + + #[test] + fn test_op_input_output_count() { + // Test cases with different input/output combinations + let test_cases = vec![ + (1, 0), // Minimum inputs, no outputs + (1, 1), // Minimum inputs, one output + (1, 2), // Minimum inputs, multiple outputs + (2, 1), // Multiple inputs, one output + (3, 2), // Multiple inputs, multiple outputs + (5, 3), // More inputs than outputs + (2, 4), // More outputs than inputs + ]; + + for (input_count, output_count) in test_cases { + let (tx, utxo_entries) = create_mock_tx(input_count, output_count); + let tx = PopulatedTransaction::new(&tx, utxo_entries); + let sig_cache = Cache::new(10_000); + let reused_values = SigHashReusedValuesUnsync::new(); + + // Test with KIP-10 enabled and disabled + for kip10_enabled in [true, false] { + let mut vm = TxScriptEngine::from_transaction_input( + &tx, + &tx.inputs()[0], // Use first input + 0, + tx.utxo(0).unwrap(), + &reused_values, + &sig_cache, + kip10_enabled, + ) + .unwrap(); + + let op_input_count = opcodes::OpInputCount::empty().expect("Should accept empty"); + let op_output_count = opcodes::OpOutputCount::empty().expect("Should accept empty"); + + if kip10_enabled { + // Test input count + op_input_count.execute(&mut vm).unwrap(); + assert_eq!( + vm.dstack, + vec![ as OpcodeData>::serialize(&(input_count as i64))], + "Input count mismatch for {} inputs", + input_count + ); + vm.dstack.clear(); + + // Test output count + op_output_count.execute(&mut vm).unwrap(); + assert_eq!( + vm.dstack, + vec![ as OpcodeData>::serialize(&(output_count as i64))], + "Output count mismatch for {} outputs", + output_count + ); + vm.dstack.clear(); + } else { + // Test that operations fail when KIP-10 is disabled + assert!( + matches!(op_input_count.execute(&mut vm), Err(TxScriptError::InvalidOpcode(_))), + "OpInputCount should fail when KIP-10 is disabled" + ); + assert!( + matches!(op_output_count.execute(&mut vm), Err(TxScriptError::InvalidOpcode(_))), + "OpOutputCount should fail when KIP-10 is disabled" + ); + } + } + } + } } } From a30976d3368d0bcd8b0ca612fef7aa018e645253 Mon Sep 17 00:00:00 2001 From: max143672 Date: Thu, 7 Nov 2024 19:16:00 +0400 Subject: [PATCH 51/93] reseve opcodes reorder kip10 opcodes. reflect script tests --- crypto/txscript/examples/kip-10.rs | 3 +- crypto/txscript/src/opcodes/mod.rs | 165 +++++++++--------- .../test-data/script_tests-kip10.json | 34 +--- crypto/txscript/test-data/script_tests.json | 6 +- 4 files changed, 87 insertions(+), 121 deletions(-) diff --git a/crypto/txscript/examples/kip-10.rs b/crypto/txscript/examples/kip-10.rs index f0c857691..9b2de1add 100644 --- a/crypto/txscript/examples/kip-10.rs +++ b/crypto/txscript/examples/kip-10.rs @@ -13,7 +13,7 @@ use kaspa_consensus_core::{ use kaspa_txscript::{ caches::Cache, opcodes::codes::{ - OpCheckSig, OpCheckSigVerify, OpDup, OpElse, OpEndIf, OpEqualVerify, OpFalse, OpGreaterThanOrEqual, OpIf, OpInputAmount, + Op0, OpCheckSig, OpCheckSigVerify, OpDup, OpElse, OpEndIf, OpEqualVerify, OpFalse, OpGreaterThanOrEqual, OpIf, OpInputAmount, OpInputSpk, OpOutputAmount, OpOutputSpk, OpSub, OpTrue, }, pay_to_address_script, pay_to_script_hash_script, @@ -23,7 +23,6 @@ use kaspa_txscript::{ use kaspa_txscript_errors::TxScriptError::{EvalFalse, VerifyError}; use rand::thread_rng; use secp256k1::Keypair; -use kaspa_txscript::opcodes::codes::Op0; /// Main function to execute all Kaspa transaction script scenarios. /// diff --git a/crypto/txscript/src/opcodes/mod.rs b/crypto/txscript/src/opcodes/mod.rs index 176d73a87..455225821 100644 --- a/crypto/txscript/src/opcodes/mod.rs +++ b/crypto/txscript/src/opcodes/mod.rs @@ -874,135 +874,132 @@ opcode_list! { } // Introspection opcodes - opcode OpInputSpk<0xb2, 1>(self, vm) { + // Transaction level opcodes (following Transaction struct field order) + opcode OpTxVersion<0xb2, 1>(self, vm) Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) + opcode OpTxInputCount<0xb3, 1>(self, vm) { if vm.kip10_enabled { match vm.script_source { - ScriptSource::TxInput{ - tx, - .. - } => { - let [idx]: [i32; 1] = vm.dstack.pop_items()?; - if !(0..=u8::MAX as i32).contains(&idx) { - return Err(TxScriptError::InvalidIndex(idx as usize, tx.inputs().len())) - } - let idx = idx as usize; - let utxo = tx.utxo(idx).ok_or_else(|| TxScriptError::InvalidIndex(idx, tx.inputs().len()))?; - vm.dstack.push(utxo.script_public_key.to_bytes()); - Ok(()) + ScriptSource::TxInput{tx, ..} => { + push_number(tx.inputs().len() as i64, vm) }, - _ => Err(TxScriptError::InvalidSource("OpInputSpk only applies to transaction inputs".to_string())) + _ => Err(TxScriptError::InvalidSource("OpInputCount only applies to transaction inputs".to_string())) } } else { Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) } } - opcode OpInputAmount<0xb3, 1>(self, vm) { + opcode OpTxOutputCount<0xb4, 1>(self, vm) { if vm.kip10_enabled { match vm.script_source { - ScriptSource::TxInput{ - tx, - .. - } => { - let [idx]: [i32; 1] = vm.dstack.pop_items()?; - if !(0..=u8::MAX as i32).contains(&idx) { - return Err(TxScriptError::InvalidIndex(idx as usize, tx.inputs().len())) - } - let idx = idx as usize; - let utxo = tx.utxo(idx).ok_or_else(|| TxScriptError::InvalidIndex(idx, tx.inputs().len()))?; - push_number(utxo.amount as i64, vm) + ScriptSource::TxInput{tx, ..} => { + push_number(tx.outputs().len() as i64, vm) }, - _ => Err(TxScriptError::InvalidSource("OpInputAmount only applies to transaction inputs".to_string())) + _ => Err(TxScriptError::InvalidSource("OpOutputCount only applies to transaction inputs".to_string())) } } else { Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) } } - opcode OpOutputSpk<0xb4, 1>(self, vm) { + opcode OpTxLockTime<0xb5, 1>(self, vm) Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) + opcode OpTxSubnetId<0xb6, 1>(self, vm) Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) + opcode OpTxGas<0xb7, 1>(self, vm) Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) + opcode OpTxPayload<0xb8, 1>(self, vm) Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) + opcode OpTxId<0xb9, 1>(self, vm) Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) + // Input related opcodes (following TransactionInput struct field order) + opcode OpOutpointTxHash<0xba, 1>(self, vm) Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) + opcode OpOutpointTxIdx<0xbb, 1>(self, vm) Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) + opcode OpTxInputIndex<0xbc, 1>(self, vm) { if vm.kip10_enabled { match vm.script_source { - ScriptSource::TxInput{tx, ..} => { - let [idx]: [i32; 1] = vm.dstack.pop_items()?; - if !(0..=u8::MAX as i32).contains(&idx) { - return Err(TxScriptError::InvalidIndex(idx as usize, tx.inputs().len())) - } - let idx = idx as usize; - let output = tx.outputs().get(idx).ok_or_else(|| TxScriptError::InvalidOutputIndex(idx, tx.outputs().len()))?; - vm.dstack.push(output.script_public_key.to_bytes()); - Ok(()) + ScriptSource::TxInput{id, ..} => { + push_number(id as i64, vm) }, - _ => Err(TxScriptError::InvalidSource("OpOutputSpk only applies to transaction inputs".to_string())) + _ => Err(TxScriptError::InvalidSource("OpInputIndex only applies to transaction inputs".to_string())) } } else { Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) } } - opcode OpOutputAmount<0xb5, 1>(self, vm) { + opcode OpTxInputSeq<0xbd, 1>(self, vm) Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) + // UTXO related opcodes (following UtxoEntry struct field order) + opcode OpTxInputAmount<0xbe, 1>(self, vm) { if vm.kip10_enabled { match vm.script_source { ScriptSource::TxInput{tx, ..} => { let [idx]: [i32; 1] = vm.dstack.pop_items()?; - if !(0..=u8::MAX as i32).contains(&idx) { + if !(0..=u8::MAX as i32).contains(&idx) { return Err(TxScriptError::InvalidIndex(idx as usize, tx.inputs().len())) } let idx = idx as usize; - let output = tx.outputs().get(idx).ok_or_else(|| TxScriptError::InvalidOutputIndex(idx, tx.outputs().len()))?; - push_number(output.value as i64, vm) + let utxo = tx.utxo(idx).ok_or_else(|| TxScriptError::InvalidIndex(idx, tx.inputs().len()))?; + push_number(utxo.amount as i64, vm) }, - _ => Err(TxScriptError::InvalidSource("OpOutputAmount only applies to transaction inputs".to_string())) + _ => Err(TxScriptError::InvalidSource("OpInputAmount only applies to transaction inputs".to_string())) } } else { Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) } } - - opcode OpInputIndex<0xb6, 1>(self, vm) { + opcode OpTxInputSpk<0xbf, 1>(self, vm) { if vm.kip10_enabled { match vm.script_source { - ScriptSource::TxInput{id, ..} => { - push_number(id as i64, vm) + ScriptSource::TxInput{tx, ..} => { + let [idx]: [i32; 1] = vm.dstack.pop_items()?; + if !(0..=u8::MAX as i32).contains(&idx) { + return Err(TxScriptError::InvalidIndex(idx as usize, tx.inputs().len())) + } + let idx = idx as usize; + let utxo = tx.utxo(idx).ok_or_else(|| TxScriptError::InvalidIndex(idx, tx.inputs().len()))?; + vm.dstack.push(utxo.script_public_key.to_bytes()); + Ok(()) }, - _ => Err(TxScriptError::InvalidSource("OpOutputAmount only applies to transaction inputs".to_string())) + _ => Err(TxScriptError::InvalidSource("OpInputSpk only applies to transaction inputs".to_string())) } } else { Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) } } - opcode OpInputCount<0xb7, 1>(self, vm) { + opcode OpTxInputBlockDaaScore<0xc0, 1>(self, vm) Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) + opcode OpTxInputIsCoinbase<0xc1, 1>(self, vm) Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) + // Output related opcodes (following TransactionOutput struct field order) + opcode OpTxOutputAmount<0xc2, 1>(self, vm) { if vm.kip10_enabled { match vm.script_source { ScriptSource::TxInput{tx, ..} => { - push_number(tx.inputs().len() as i64, vm) + let [idx]: [i32; 1] = vm.dstack.pop_items()?; + if !(0..=u8::MAX as i32).contains(&idx) { + return Err(TxScriptError::InvalidIndex(idx as usize, tx.inputs().len())) + } + let idx = idx as usize; + let output = tx.outputs().get(idx).ok_or_else(|| TxScriptError::InvalidOutputIndex(idx, tx.outputs().len()))?; + push_number(output.value as i64, vm) }, - _ => Err(TxScriptError::InvalidSource("OpInputCount only applies to transaction inputs".to_string())) + _ => Err(TxScriptError::InvalidSource("OpOutputAmount only applies to transaction inputs".to_string())) } } else { Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) } } - opcode OpOutputCount<0xb8, 1>(self, vm) { + opcode OpTxOutputSpk<0xc3, 1>(self, vm) { if vm.kip10_enabled { match vm.script_source { ScriptSource::TxInput{tx, ..} => { - push_number(tx.outputs().len() as i64, vm) + let [idx]: [i32; 1] = vm.dstack.pop_items()?; + if !(0..=u8::MAX as i32).contains(&idx) { + return Err(TxScriptError::InvalidIndex(idx as usize, tx.inputs().len())) + } + let idx = idx as usize; + let output = tx.outputs().get(idx).ok_or_else(|| TxScriptError::InvalidOutputIndex(idx, tx.outputs().len()))?; + vm.dstack.push(output.script_public_key.to_bytes()); + Ok(()) }, - _ => Err(TxScriptError::InvalidSource("OpOutputCount only applies to transaction inputs".to_string())) + _ => Err(TxScriptError::InvalidSource("OpOutputSpk only applies to transaction inputs".to_string())) } } else { Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) } } - opcode OpUnknown185<0xb9, 1>(self, vm) Err(TxScriptError::OpcodeDisabled(format!("{self:?}"))) - opcode OpUnknown186<0xba, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) - // Undefined opcodes. - opcode OpUnknown187<0xbb, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) - opcode OpUnknown188<0xbc, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) - opcode OpUnknown189<0xbd, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) - opcode OpUnknown190<0xbe, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) - opcode OpUnknown191<0xbf, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) - opcode OpUnknown192<0xc0, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) - opcode OpUnknown193<0xc1, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) - opcode OpUnknown194<0xc2, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) - opcode OpUnknown195<0xc3, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) + // Undefined opcodes opcode OpUnknown196<0xc4, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) opcode OpUnknown197<0xc5, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) opcode OpUnknown198<0xc6, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) @@ -1155,6 +1152,17 @@ mod test { opcodes::OpMod::empty().expect("Should accept empty"), opcodes::OpLShift::empty().expect("Should accept empty"), opcodes::OpRShift::empty().expect("Should accept empty"), + opcodes::OpTxVersion::empty().expect("Should accept empty"), + opcodes::OpTxLockTime::empty().expect("Should accept empty"), + opcodes::OpTxSubnetId::empty().expect("Should accept empty"), + opcodes::OpTxGas::empty().expect("Should accept empty"), + opcodes::OpTxPayload::empty().expect("Should accept empty"), + opcodes::OpTxId::empty().expect("Should accept empty"), + opcodes::OpOutpointTxHash::empty().expect("Should accept empty"), + opcodes::OpOutpointTxIdx::empty().expect("Should accept empty"), + opcodes::OpTxInputSeq::empty().expect("Should accept empty"), + opcodes::OpTxInputBlockDaaScore::empty().expect("Should accept empty"), + opcodes::OpTxInputIsCoinbase::empty().expect("Should accept empty"), ]; let cache = Cache::new(10_000); @@ -1197,17 +1205,6 @@ mod test { let tests: Vec>> = vec![ opcodes::OpUnknown166::empty().expect("Should accept empty"), opcodes::OpUnknown167::empty().expect("Should accept empty"), - opcodes::OpUnknown185::empty().expect("Should accept empty"), - opcodes::OpUnknown186::empty().expect("Should accept empty"), - opcodes::OpUnknown187::empty().expect("Should accept empty"), - opcodes::OpUnknown188::empty().expect("Should accept empty"), - opcodes::OpUnknown189::empty().expect("Should accept empty"), - opcodes::OpUnknown190::empty().expect("Should accept empty"), - opcodes::OpUnknown191::empty().expect("Should accept empty"), - opcodes::OpUnknown192::empty().expect("Should accept empty"), - opcodes::OpUnknown193::empty().expect("Should accept empty"), - opcodes::OpUnknown194::empty().expect("Should accept empty"), - opcodes::OpUnknown195::empty().expect("Should accept empty"), opcodes::OpUnknown196::empty().expect("Should accept empty"), opcodes::OpUnknown197::empty().expect("Should accept empty"), opcodes::OpUnknown198::empty().expect("Should accept empty"), @@ -3089,7 +3086,7 @@ mod test { test_case.kip10_enabled, ) .unwrap(); - let op_input_idx = opcodes::OpInputIndex::empty().expect("Should accept empty"); + let op_input_idx = opcodes::OpTxInputIndex::empty().expect("Should accept empty"); if !test_case.kip10_enabled { assert!(matches!(op_input_idx.execute(&mut vm), Err(TxScriptError::InvalidOpcode(_)))); @@ -3101,10 +3098,10 @@ mod test { vm.dstack.clear(); } - let op_input_spk = opcodes::OpInputSpk::empty().expect("Should accept empty"); - let op_output_spk = opcodes::OpOutputSpk::empty().expect("Should accept empty"); - let op_input_amount = opcodes::OpInputAmount::empty().expect("Should accept empty"); - let op_output_amount = opcodes::OpOutputAmount::empty().expect("Should accept empty"); + let op_input_spk = opcodes::OpTxInputSpk::empty().expect("Should accept empty"); + let op_output_spk = opcodes::OpTxOutputSpk::empty().expect("Should accept empty"); + let op_input_amount = opcodes::OpTxInputAmount::empty().expect("Should accept empty"); + let op_output_amount = opcodes::OpTxOutputAmount::empty().expect("Should accept empty"); for expected_result in &test_case.expected_results { match expected_result { @@ -3341,8 +3338,8 @@ mod test { ) .unwrap(); - let op_input_count = opcodes::OpInputCount::empty().expect("Should accept empty"); - let op_output_count = opcodes::OpOutputCount::empty().expect("Should accept empty"); + let op_input_count = opcodes::OpTxInputCount::empty().expect("Should accept empty"); + let op_output_count = opcodes::OpTxOutputCount::empty().expect("Should accept empty"); if kip10_enabled { // Test input count diff --git a/crypto/txscript/test-data/script_tests-kip10.json b/crypto/txscript/test-data/script_tests-kip10.json index 5c1347c51..b0cd5808d 100644 --- a/crypto/txscript/test-data/script_tests-kip10.json +++ b/crypto/txscript/test-data/script_tests-kip10.json @@ -1218,10 +1218,10 @@ ], [ "0", - "IF 0xbd ELSE 1 ENDIF", + "IF 0xc4 ELSE 1 ENDIF", "", "OK", - "opcodes above OP_OUTPUT_AMOUNT invalid if executed" + "opcodes above OpTxOutputSpk invalid if executed" ], [ "0", @@ -3704,30 +3704,12 @@ "", "BAD_OPCODE" ], - [ - "1", - "IF 0xbc ELSE 1 ENDIF", - "", - "BAD_OPCODE" - ], [ "1", "IF 0xbd ELSE 1 ENDIF", "", "BAD_OPCODE" ], - [ - "1", - "IF 0xbe ELSE 1 ENDIF", - "", - "BAD_OPCODE" - ], - [ - "1", - "IF 0xbf ELSE 1 ENDIF", - "", - "BAD_OPCODE" - ], [ "1", "IF 0xc0 ELSE 1 ENDIF", @@ -3740,18 +3722,6 @@ "", "BAD_OPCODE" ], - [ - "1", - "IF 0xc2 ELSE 1 ENDIF", - "", - "BAD_OPCODE" - ], - [ - "1", - "IF 0xc3 ELSE 1 ENDIF", - "", - "BAD_OPCODE" - ], [ "1", "IF 0xc4 ELSE 1 ENDIF", diff --git a/crypto/txscript/test-data/script_tests.json b/crypto/txscript/test-data/script_tests.json index 5bc5e7a16..0baba0097 100644 --- a/crypto/txscript/test-data/script_tests.json +++ b/crypto/txscript/test-data/script_tests.json @@ -1218,10 +1218,10 @@ ], [ "0", - "IF 0xb2 ELSE 1 ENDIF", + "IF 0xc4 ELSE 1 ENDIF", "", "OK", - "opcodes above OP_CHECKSEQUENCEVERIFY invalid if executed" + "opcodes above OpTxOutputSpk invalid if executed" ], [ "0", @@ -3683,7 +3683,7 @@ ], [ "1", - "IF 0xb2 ELSE 1 ENDIF", + "IF 0xc4 ELSE 1 ENDIF", "", "BAD_OPCODE", "opcodes above OP_CHECKSEQUENCEVERIFY invalid if executed" From 25243f568120f0e155b5bab71268aed2de7ea03b Mon Sep 17 00:00:00 2001 From: max143672 Date: Thu, 7 Nov 2024 19:21:41 +0400 Subject: [PATCH 52/93] fix example --- crypto/txscript/examples/kip-10.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/crypto/txscript/examples/kip-10.rs b/crypto/txscript/examples/kip-10.rs index 9b2de1add..2d72b3325 100644 --- a/crypto/txscript/examples/kip-10.rs +++ b/crypto/txscript/examples/kip-10.rs @@ -1,8 +1,7 @@ use kaspa_addresses::{Address, Prefix, Version}; -use kaspa_consensus_core::hashing::sighash::SigHashReusedValuesUnsync; use kaspa_consensus_core::{ hashing::{ - sighash::{calc_schnorr_signature_hash, SigHashReusedValues}, + sighash::{calc_schnorr_signature_hash, SigHashReusedValuesUnsync}, sighash_type::SIG_HASH_ALL, }, tx::{ @@ -13,8 +12,8 @@ use kaspa_consensus_core::{ use kaspa_txscript::{ caches::Cache, opcodes::codes::{ - Op0, OpCheckSig, OpCheckSigVerify, OpDup, OpElse, OpEndIf, OpEqualVerify, OpFalse, OpGreaterThanOrEqual, OpIf, OpInputAmount, - OpInputSpk, OpOutputAmount, OpOutputSpk, OpSub, OpTrue, + Op0, OpCheckSig, OpCheckSigVerify, OpDup, OpElse, OpEndIf, OpEqualVerify, OpFalse, OpGreaterThanOrEqual, OpIf, OpSub, OpTrue, + OpTxInputAmount, OpTxInputSpk, OpTxOutputAmount, OpTxOutputSpk, }, pay_to_address_script, pay_to_script_hash_script, script_builder::{ScriptBuilder, ScriptBuilderResult}, @@ -71,9 +70,9 @@ fn threshold_scenario() -> ScriptBuilderResult<()> { .add_op(OpCheckSig)? // Borrower branch .add_op(OpElse)? - .add_ops(&[Op0, OpInputSpk, Op0, OpOutputSpk, OpEqualVerify, Op0, OpOutputAmount])? + .add_ops(&[Op0, OpTxInputSpk, Op0, OpTxOutputSpk, OpEqualVerify, Op0, OpTxOutputAmount])? .add_i64(threshold)? - .add_ops(&[OpSub, Op0, OpInputAmount, OpGreaterThanOrEqual])? + .add_ops(&[OpSub, Op0, OpTxInputAmount, OpGreaterThanOrEqual])? .add_op(OpEndIf)? .drain(); @@ -188,9 +187,9 @@ fn generate_limited_time_script(owner: &Keypair, threshold: i64, output_spk: Vec // Borrower branch .add_op(OpElse)? .add_data(&output_spk)? - .add_ops(&[Op0, OpOutputSpk, OpEqualVerify, Op0, OpOutputAmount])? + .add_ops(&[Op0, OpTxOutputSpk, OpEqualVerify, Op0, OpTxOutputAmount])? .add_i64(threshold)? - .add_ops(&[OpSub, Op0, OpInputAmount, OpGreaterThanOrEqual])? + .add_ops(&[OpSub, Op0, OpTxInputAmount, OpGreaterThanOrEqual])? .add_op(OpEndIf)? .drain(); @@ -578,7 +577,7 @@ fn shared_secret_scenario() -> ScriptBuilderResult<()> { .add_data(shared_secret_kp.x_only_public_key().0.serialize().as_slice())? .add_op(OpEqualVerify)? .add_op(OpCheckSigVerify)? - .add_ops(&[Op0, OpInputSpk, Op0, OpOutputSpk, OpEqualVerify, Op0, OpOutputAmount, Op0, OpInputAmount, OpGreaterThanOrEqual])? + .add_ops(&[Op0, OpTxInputSpk, Op0, OpTxOutputSpk, OpEqualVerify, Op0, OpTxOutputAmount, Op0, OpTxInputAmount, OpGreaterThanOrEqual])? .add_op(OpEndIf)? .drain(); From 66d6badbef894c104ec413f445fb3e21d7e1f597 Mon Sep 17 00:00:00 2001 From: max143672 Date: Thu, 7 Nov 2024 19:28:03 +0400 Subject: [PATCH 53/93] introspection opcodes are reserverd, not disables --- crypto/txscript/src/opcodes/mod.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/crypto/txscript/src/opcodes/mod.rs b/crypto/txscript/src/opcodes/mod.rs index 455225821..0db8baf71 100644 --- a/crypto/txscript/src/opcodes/mod.rs +++ b/crypto/txscript/src/opcodes/mod.rs @@ -1152,17 +1152,6 @@ mod test { opcodes::OpMod::empty().expect("Should accept empty"), opcodes::OpLShift::empty().expect("Should accept empty"), opcodes::OpRShift::empty().expect("Should accept empty"), - opcodes::OpTxVersion::empty().expect("Should accept empty"), - opcodes::OpTxLockTime::empty().expect("Should accept empty"), - opcodes::OpTxSubnetId::empty().expect("Should accept empty"), - opcodes::OpTxGas::empty().expect("Should accept empty"), - opcodes::OpTxPayload::empty().expect("Should accept empty"), - opcodes::OpTxId::empty().expect("Should accept empty"), - opcodes::OpOutpointTxHash::empty().expect("Should accept empty"), - opcodes::OpOutpointTxIdx::empty().expect("Should accept empty"), - opcodes::OpTxInputSeq::empty().expect("Should accept empty"), - opcodes::OpTxInputBlockDaaScore::empty().expect("Should accept empty"), - opcodes::OpTxInputIsCoinbase::empty().expect("Should accept empty"), ]; let cache = Cache::new(10_000); @@ -1186,6 +1175,17 @@ mod test { opcodes::OpVerNotIf::empty().expect("Should accept empty"), opcodes::OpReserved1::empty().expect("Should accept empty"), opcodes::OpReserved2::empty().expect("Should accept empty"), + opcodes::OpTxVersion::empty().expect("Should accept empty"), + opcodes::OpTxLockTime::empty().expect("Should accept empty"), + opcodes::OpTxSubnetId::empty().expect("Should accept empty"), + opcodes::OpTxGas::empty().expect("Should accept empty"), + opcodes::OpTxPayload::empty().expect("Should accept empty"), + opcodes::OpTxId::empty().expect("Should accept empty"), + opcodes::OpOutpointTxHash::empty().expect("Should accept empty"), + opcodes::OpOutpointTxIdx::empty().expect("Should accept empty"), + opcodes::OpTxInputSeq::empty().expect("Should accept empty"), + opcodes::OpTxInputBlockDaaScore::empty().expect("Should accept empty"), + opcodes::OpTxInputIsCoinbase::empty().expect("Should accept empty"), ]; let cache = Cache::new(10_000); From 2ba5d479c600656e0f573acb0d87d1f6fc6674ac Mon Sep 17 00:00:00 2001 From: max143672 Date: Thu, 7 Nov 2024 19:32:34 +0400 Subject: [PATCH 54/93] use ForkActivation type --- consensus/core/src/config/params.rs | 12 ++++++------ consensus/src/processes/transaction_validator/mod.rs | 6 +++--- .../transaction_validator_populated.rs | 2 +- .../integration/src/consensus_integration_tests.rs | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/consensus/core/src/config/params.rs b/consensus/core/src/config/params.rs index 689c25409..0ecb6f06c 100644 --- a/consensus/core/src/config/params.rs +++ b/consensus/core/src/config/params.rs @@ -111,7 +111,7 @@ pub struct Params { pub storage_mass_activation: ForkActivation, /// DAA score from which tx engine supports kip10 opcodes: OpInputAmount, OpInputSpk, OpOutputAmount, OpOutputSpk - pub kip10_activation_daa_score: u64, + pub kip10_activation_daa_score: ForkActivation, /// DAA score after which the pre-deflationary period switches to the deflationary period pub deflationary_phase_daa_score: u64, @@ -383,7 +383,7 @@ pub const MAINNET_PARAMS: Params = Params { storage_mass_parameter: STORAGE_MASS_PARAMETER, storage_mass_activation: ForkActivation::never(), - kip10_activation_daa_score: u64::MAX, + kip10_activation_daa_score: ForkActivation::never(), // deflationary_phase_daa_score is the DAA score after which the pre-deflationary period // switches to the deflationary period. This number is calculated as follows: @@ -447,7 +447,7 @@ pub const TESTNET_PARAMS: Params = Params { storage_mass_parameter: STORAGE_MASS_PARAMETER, storage_mass_activation: ForkActivation::never(), - kip10_activation_daa_score: u64::MAX, + kip10_activation_daa_score: ForkActivation::never(), // deflationary_phase_daa_score is the DAA score after which the pre-deflationary period // switches to the deflationary period. This number is calculated as follows: // We define a year as 365.25 days @@ -517,7 +517,7 @@ pub const TESTNET11_PARAMS: Params = Params { storage_mass_parameter: STORAGE_MASS_PARAMETER, storage_mass_activation: ForkActivation::always(), - kip10_activation_daa_score: u64::MAX, + kip10_activation_daa_score: ForkActivation::never(), skip_proof_of_work: false, max_block_level: 250, @@ -571,7 +571,7 @@ pub const SIMNET_PARAMS: Params = Params { storage_mass_parameter: STORAGE_MASS_PARAMETER, storage_mass_activation: ForkActivation::always(), - kip10_activation_daa_score: u64::MAX, + kip10_activation_daa_score: ForkActivation::never(), skip_proof_of_work: true, // For simnet only, PoW can be simulated by default max_block_level: 250, @@ -618,7 +618,7 @@ pub const DEVNET_PARAMS: Params = Params { storage_mass_parameter: STORAGE_MASS_PARAMETER, storage_mass_activation: ForkActivation::never(), - kip10_activation_daa_score: u64::MAX, + kip10_activation_daa_score: ForkActivation::never(), // deflationary_phase_daa_score is the DAA score after which the pre-deflationary period // switches to the deflationary period. This number is calculated as follows: diff --git a/consensus/src/processes/transaction_validator/mod.rs b/consensus/src/processes/transaction_validator/mod.rs index 12c32bdca..e471054e4 100644 --- a/consensus/src/processes/transaction_validator/mod.rs +++ b/consensus/src/processes/transaction_validator/mod.rs @@ -29,7 +29,7 @@ pub struct TransactionValidator { /// Storage mass hardfork DAA score storage_mass_activation: ForkActivation, /// KIP-10 hardfork DAA score - kip10_activation_daa_score: u64, + kip10_activation_daa_score: ForkActivation, } impl TransactionValidator { @@ -45,7 +45,7 @@ impl TransactionValidator { counters: Arc, mass_calculator: MassCalculator, storage_mass_activation: ForkActivation, - kip10_activation_daa_score: u64, + kip10_activation_daa_score: ForkActivation, ) -> Self { Self { max_tx_inputs, @@ -83,7 +83,7 @@ impl TransactionValidator { sig_cache: Cache::with_counters(10_000, counters), mass_calculator: MassCalculator::new(0, 0, 0, 0), storage_mass_activation: ForkActivation::never(), - kip10_activation_daa_score: u64::MAX, + kip10_activation_daa_score: ForkActivation::never(), } } } diff --git a/consensus/src/processes/transaction_validator/transaction_validator_populated.rs b/consensus/src/processes/transaction_validator/transaction_validator_populated.rs index bd64965b3..da0561522 100644 --- a/consensus/src/processes/transaction_validator/transaction_validator_populated.rs +++ b/consensus/src/processes/transaction_validator/transaction_validator_populated.rs @@ -173,7 +173,7 @@ impl TransactionValidator { } pub fn check_scripts(&self, tx: &(impl VerifiableTransaction + Sync), pov_daa_score: u64) -> TxResult<()> { - check_scripts(&self.sig_cache, tx, pov_daa_score > self.kip10_activation_daa_score) + check_scripts(&self.sig_cache, tx, self.kip10_activation_daa_score.is_active(pov_daa_score)) } } diff --git a/testing/integration/src/consensus_integration_tests.rs b/testing/integration/src/consensus_integration_tests.rs index 5ac3c7132..f43560311 100644 --- a/testing/integration/src/consensus_integration_tests.rs +++ b/testing/integration/src/consensus_integration_tests.rs @@ -833,7 +833,7 @@ impl KaspadGoParams { max_block_mass: self.MaxBlockMass, storage_mass_parameter: STORAGE_MASS_PARAMETER, storage_mass_activation: ForkActivation::never(), - kip10_activation_daa_score: u64::MAX, + kip10_activation_daa_score: ForkActivation::never(), deflationary_phase_daa_score: self.DeflationaryPhaseDaaScore, pre_deflationary_phase_base_subsidy: self.PreDeflationaryPhaseBaseSubsidy, coinbase_maturity: MAINNET_PARAMS.coinbase_maturity, From 3eae88a5735e2408b70dcd6d67cd01ed45e9788f Mon Sep 17 00:00:00 2001 From: max143672 Date: Fri, 8 Nov 2024 14:26:34 +0400 Subject: [PATCH 55/93] cicd: run kip-10 example --- .github/workflows/ci.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 49fe4e463..9076749e6 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -128,6 +128,9 @@ jobs: - name: Run cargo doc run: cargo doc --release --no-deps --workspace + - name: Run kip-10 example + run: cargo run --example kip-10 + # test-release: # name: Test Suite Release From 8422331af5003617be907dd84facef3b809751b5 Mon Sep 17 00:00:00 2001 From: max143672 Date: Sat, 9 Nov 2024 12:37:27 +0400 Subject: [PATCH 56/93] move spk encoding to txscript module --- consensus/core/src/tx/script_public_key.rs | 11 ----------- crypto/txscript/src/lib.rs | 15 +++++++++++++++ crypto/txscript/src/opcodes/mod.rs | 2 +- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/consensus/core/src/tx/script_public_key.rs b/consensus/core/src/tx/script_public_key.rs index b25227b84..dfed2ab5c 100644 --- a/consensus/core/src/tx/script_public_key.rs +++ b/consensus/core/src/tx/script_public_key.rs @@ -56,17 +56,6 @@ pub struct ScriptPublicKey { pub(super) script: ScriptVec, // Kept private to preserve read-only semantics } -impl ScriptPublicKey { - pub fn to_bytes(&self) -> Vec { - let version = self.version.to_be_bytes(); - let script = self.script(); - let mut v = Vec::with_capacity(version.len() + script.len()); - v.extend_from_slice(&version); - v.extend_from_slice(script); - v - } -} - impl std::fmt::Debug for ScriptPublicKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("ScriptPublicKey").field("version", &self.version).field("script", &self.script.to_hex()).finish() diff --git a/crypto/txscript/src/lib.rs b/crypto/txscript/src/lib.rs index f9f74b33b..8fcb7012c 100644 --- a/crypto/txscript/src/lib.rs +++ b/crypto/txscript/src/lib.rs @@ -522,6 +522,21 @@ impl<'a, T: VerifiableTransaction, Reused: SigHashReusedValues> TxScriptEngine<' } } +trait SpkEncoding { + fn to_bytes(&self) -> Vec; +} + +impl SpkEncoding for ScriptPublicKey { + fn to_bytes(&self) -> Vec { + let version = self.version.to_be_bytes(); + let script = self.script(); + let mut v = Vec::with_capacity(version.len() + script.len()); + v.extend_from_slice(&version); + v.extend_from_slice(script); + v + } +} + #[cfg(test)] mod tests { use std::iter::once; diff --git a/crypto/txscript/src/opcodes/mod.rs b/crypto/txscript/src/opcodes/mod.rs index 0db8baf71..f885e3af2 100644 --- a/crypto/txscript/src/opcodes/mod.rs +++ b/crypto/txscript/src/opcodes/mod.rs @@ -3,7 +3,7 @@ mod macros; use crate::data_stack::{DataStack, Kip10I64, OpcodeData}; use crate::{ - ScriptSource, TxScriptEngine, TxScriptError, LOCK_TIME_THRESHOLD, MAX_TX_IN_SEQUENCE_NUM, NO_COST_OPCODE, + ScriptSource, SpkEncoding, TxScriptEngine, TxScriptError, LOCK_TIME_THRESHOLD, MAX_TX_IN_SEQUENCE_NUM, NO_COST_OPCODE, SEQUENCE_LOCK_TIME_DISABLED, SEQUENCE_LOCK_TIME_MASK, }; use blake2b_simd::Params; From 046308f0a94592dc39e388ec33eaf90d2931c03b Mon Sep 17 00:00:00 2001 From: max143672 Date: Sat, 9 Nov 2024 12:52:38 +0400 Subject: [PATCH 57/93] rework bound check ot input/output index --- crypto/txscript/src/opcodes/mod.rs | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/crypto/txscript/src/opcodes/mod.rs b/crypto/txscript/src/opcodes/mod.rs index f885e3af2..6cc57a8f3 100644 --- a/crypto/txscript/src/opcodes/mod.rs +++ b/crypto/txscript/src/opcodes/mod.rs @@ -927,10 +927,7 @@ opcode_list! { match vm.script_source { ScriptSource::TxInput{tx, ..} => { let [idx]: [i32; 1] = vm.dstack.pop_items()?; - if !(0..=u8::MAX as i32).contains(&idx) { - return Err(TxScriptError::InvalidIndex(idx as usize, tx.inputs().len())) - } - let idx = idx as usize; + let idx = usize::try_from(idx).map_err(|_| TxScriptError::InvalidIndex(idx as usize, tx.inputs().len()))?; let utxo = tx.utxo(idx).ok_or_else(|| TxScriptError::InvalidIndex(idx, tx.inputs().len()))?; push_number(utxo.amount as i64, vm) }, @@ -945,10 +942,7 @@ opcode_list! { match vm.script_source { ScriptSource::TxInput{tx, ..} => { let [idx]: [i32; 1] = vm.dstack.pop_items()?; - if !(0..=u8::MAX as i32).contains(&idx) { - return Err(TxScriptError::InvalidIndex(idx as usize, tx.inputs().len())) - } - let idx = idx as usize; + let idx = usize::try_from(idx).map_err(|_| TxScriptError::InvalidIndex(idx as usize, tx.inputs().len()))?; let utxo = tx.utxo(idx).ok_or_else(|| TxScriptError::InvalidIndex(idx, tx.inputs().len()))?; vm.dstack.push(utxo.script_public_key.to_bytes()); Ok(()) @@ -967,10 +961,7 @@ opcode_list! { match vm.script_source { ScriptSource::TxInput{tx, ..} => { let [idx]: [i32; 1] = vm.dstack.pop_items()?; - if !(0..=u8::MAX as i32).contains(&idx) { - return Err(TxScriptError::InvalidIndex(idx as usize, tx.inputs().len())) - } - let idx = idx as usize; + let idx = usize::try_from(idx).map_err(|_| TxScriptError::InvalidIndex(idx as usize, tx.inputs().len()))?; let output = tx.outputs().get(idx).ok_or_else(|| TxScriptError::InvalidOutputIndex(idx, tx.outputs().len()))?; push_number(output.value as i64, vm) }, @@ -985,10 +976,7 @@ opcode_list! { match vm.script_source { ScriptSource::TxInput{tx, ..} => { let [idx]: [i32; 1] = vm.dstack.pop_items()?; - if !(0..=u8::MAX as i32).contains(&idx) { - return Err(TxScriptError::InvalidIndex(idx as usize, tx.inputs().len())) - } - let idx = idx as usize; + let idx = usize::try_from(idx).map_err(|_| TxScriptError::InvalidIndex(idx as usize, tx.inputs().len()))?; let output = tx.outputs().get(idx).ok_or_else(|| TxScriptError::InvalidOutputIndex(idx, tx.outputs().len()))?; vm.dstack.push(output.script_public_key.to_bytes()); Ok(()) From 828a5bc2dcf5af622558f2d835320fee60555ab1 Mon Sep 17 00:00:00 2001 From: max143672 Date: Sat, 9 Nov 2024 13:04:01 +0400 Subject: [PATCH 58/93] fix tests by importing spkencoding trait --- crypto/txscript/src/opcodes/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crypto/txscript/src/opcodes/mod.rs b/crypto/txscript/src/opcodes/mod.rs index 6cc57a8f3..91d9be860 100644 --- a/crypto/txscript/src/opcodes/mod.rs +++ b/crypto/txscript/src/opcodes/mod.rs @@ -1,8 +1,8 @@ #[macro_use] mod macros; -use crate::data_stack::{DataStack, Kip10I64, OpcodeData}; use crate::{ + data_stack::{DataStack, Kip10I64, OpcodeData}, ScriptSource, SpkEncoding, TxScriptEngine, TxScriptError, LOCK_TIME_THRESHOLD, MAX_TX_IN_SEQUENCE_NUM, NO_COST_OPCODE, SEQUENCE_LOCK_TIME_DISABLED, SEQUENCE_LOCK_TIME_MASK, }; @@ -2996,7 +2996,7 @@ mod test { mod kip10 { use super::*; use crate::data_stack::DataStack; - use crate::{data_stack::OpcodeData, opcodes::push_number}; + use crate::{data_stack::OpcodeData, opcodes::push_number, SpkEncoding}; #[derive(Clone, Debug)] struct Kip10Mock { From 3259cc61aa3082977d8b9a5094efd4895c262008 Mon Sep 17 00:00:00 2001 From: max143672 Date: Sat, 9 Nov 2024 13:13:45 +0400 Subject: [PATCH 59/93] replace todo in descripotions of over[under]flow errors --- crypto/txscript/src/opcodes/mod.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crypto/txscript/src/opcodes/mod.rs b/crypto/txscript/src/opcodes/mod.rs index 91d9be860..0858cb1fa 100644 --- a/crypto/txscript/src/opcodes/mod.rs +++ b/crypto/txscript/src/opcodes/mod.rs @@ -585,22 +585,22 @@ opcode_list! { // Numeric related opcodes. opcode Op1Add<0x8b, 1>(self, vm) { - numeric_op!(vm, [value], 1, value.checked_add(1).ok_or_else(|| TxScriptError::NumberTooBig("todo".to_string()))?) + numeric_op!(vm, [value], 1, value.checked_add(1).ok_or_else(|| TxScriptError::NumberTooBig("Result of addition exceeds 64-bit signed integer range".to_string()))?) } opcode Op1Sub<0x8c, 1>(self, vm) { - numeric_op!(vm, [value], 1, value.checked_sub(1).ok_or_else(|| TxScriptError::NumberTooBig("todo".to_string()))?) + numeric_op!(vm, [value], 1, value.checked_sub(1).ok_or_else(|| TxScriptError::NumberTooBig("Result of subtraction exceeds 64-bit signed integer range".to_string()))?) } opcode Op2Mul<0x8d, 1>(self, vm) Err(TxScriptError::OpcodeDisabled(format!("{self:?}"))) opcode Op2Div<0x8e, 1>(self, vm) Err(TxScriptError::OpcodeDisabled(format!("{self:?}"))) opcode OpNegate<0x8f, 1>(self, vm) { - numeric_op!(vm, [value], 1, value.checked_neg().ok_or_else(|| TxScriptError::NumberTooBig("todo".to_string()))?) + numeric_op!(vm, [value], 1, value.checked_neg().ok_or_else(|| TxScriptError::NumberTooBig("Negation result exceeds 64-bit signed integer range".to_string()))?) } opcode OpAbs<0x90, 1>(self, vm) { - numeric_op!(vm, [value], 1, value.checked_abs().ok_or_else(|| TxScriptError::NumberTooBig("todo".to_string()))?) + numeric_op!(vm, [value], 1, value.checked_abs().ok_or_else(|| TxScriptError::NumberTooBig("Absolute value exceeds 64-bit signed integer range".to_string()))?) } opcode OpNot<0x91, 1>(self, vm) { @@ -612,11 +612,11 @@ opcode_list! { } opcode OpAdd<0x93, 1>(self, vm) { - numeric_op!(vm, [a,b], 2, a.checked_add(b.into()).ok_or_else(|| TxScriptError::NumberTooBig("todo".to_string()))?) + numeric_op!(vm, [a,b], 2, a.checked_add(b.into()).ok_or_else(|| TxScriptError::NumberTooBig("Sum exceeds 64-bit signed integer range".to_string()))?) } opcode OpSub<0x94, 1>(self, vm) { - numeric_op!(vm, [a,b], 2, a.checked_sub(b.into()).ok_or_else(|| TxScriptError::NumberTooBig("todo".to_string()))?) + numeric_op!(vm, [a,b], 2, a.checked_sub(b.into()).ok_or_else(|| TxScriptError::NumberTooBig("Difference exceeds 64-bit signed integer range".to_string()))?) } opcode OpMul<0x95, 1>(self, vm) Err(TxScriptError::OpcodeDisabled(format!("{self:?}"))) From 5f8351db44a51ea72c2744f582d0d8461e0a8bf4 Mon Sep 17 00:00:00 2001 From: max143672 Date: Sat, 9 Nov 2024 13:25:54 +0400 Subject: [PATCH 60/93] reorder new opcodes, reserve script sig opcode, remove txid --- crypto/txscript/src/opcodes/mod.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crypto/txscript/src/opcodes/mod.rs b/crypto/txscript/src/opcodes/mod.rs index 0858cb1fa..3f8c75855 100644 --- a/crypto/txscript/src/opcodes/mod.rs +++ b/crypto/txscript/src/opcodes/mod.rs @@ -904,11 +904,8 @@ opcode_list! { opcode OpTxSubnetId<0xb6, 1>(self, vm) Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) opcode OpTxGas<0xb7, 1>(self, vm) Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) opcode OpTxPayload<0xb8, 1>(self, vm) Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) - opcode OpTxId<0xb9, 1>(self, vm) Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) // Input related opcodes (following TransactionInput struct field order) - opcode OpOutpointTxHash<0xba, 1>(self, vm) Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) - opcode OpOutpointTxIdx<0xbb, 1>(self, vm) Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) - opcode OpTxInputIndex<0xbc, 1>(self, vm) { + opcode OpTxInputIndex<0xb9, 1>(self, vm) { if vm.kip10_enabled { match vm.script_source { ScriptSource::TxInput{id, ..} => { @@ -920,6 +917,9 @@ opcode_list! { Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) } } + opcode OpOutpointTxId<0xba, 1>(self, vm) Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) + opcode OpOutpointOutputIdx<0xbb, 1>(self, vm) Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) + opcode OpTxInputScriptSig<0xbc, 1>(self, vm) Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) opcode OpTxInputSeq<0xbd, 1>(self, vm) Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) // UTXO related opcodes (following UtxoEntry struct field order) opcode OpTxInputAmount<0xbe, 1>(self, vm) { @@ -1168,9 +1168,9 @@ mod test { opcodes::OpTxSubnetId::empty().expect("Should accept empty"), opcodes::OpTxGas::empty().expect("Should accept empty"), opcodes::OpTxPayload::empty().expect("Should accept empty"), - opcodes::OpTxId::empty().expect("Should accept empty"), - opcodes::OpOutpointTxHash::empty().expect("Should accept empty"), - opcodes::OpOutpointTxIdx::empty().expect("Should accept empty"), + opcodes::OpOutpointTxId::empty().expect("Should accept empty"), + opcodes::OpOutpointOutputIdx::empty().expect("Should accept empty"), + opcodes::OpTxInputScriptSig::empty().expect("Should accept empty"), opcodes::OpTxInputSeq::empty().expect("Should accept empty"), opcodes::OpTxInputBlockDaaScore::empty().expect("Should accept empty"), opcodes::OpTxInputIsCoinbase::empty().expect("Should accept empty"), From 955424cd4c1dd72f4c1028b3bcdd3dd017bb6309 Mon Sep 17 00:00:00 2001 From: max143672 Date: Sat, 9 Nov 2024 16:10:32 +0400 Subject: [PATCH 61/93] fix bitcoin script tests --- .../test-data/script_tests-kip10.json | 184 ++++++++++++++++-- crypto/txscript/test-data/script_tests.json | 6 +- 2 files changed, 173 insertions(+), 17 deletions(-) diff --git a/crypto/txscript/test-data/script_tests-kip10.json b/crypto/txscript/test-data/script_tests-kip10.json index b0cd5808d..c0d1c5e69 100644 --- a/crypto/txscript/test-data/script_tests-kip10.json +++ b/crypto/txscript/test-data/script_tests-kip10.json @@ -1218,10 +1218,16 @@ ], [ "0", - "IF 0xc4 ELSE 1 ENDIF", + "IF 0xb2 ELSE 1 ENDIF", "", "OK", - "opcodes above OpTxOutputSpk invalid if executed" + "opcodes above OP_CHECKSEQUENCEVERIFY invalid if executed" + ], + [ + "0", + "IF 0xbd ELSE 1 ENDIF", + "", + "OK" ], [ "0", @@ -3591,6 +3597,20 @@ "", "EVAL_FALSE" ], + [ + "2147483648 0", + "ADD NOP", + "", + "OK", + "numbers up to 8 bytes are supported since kip10" + ], + [ + "-2147483648 0", + "ADD NOP", + "", + "OK", + "numbers up to 8 bytes are supported since kip10" + ], [ "-9223372036854775808 0", "ADD NOP", @@ -3603,7 +3623,14 @@ "DUP ADD 4294967294 NUMEQUAL", "", "OK", - "NUMEQUAL is in numeric range" + "NUMEQUAL is in numeric range since kip10" + ], + [ + "'abcdef'", + "NOT 0 EQUAL", + "", + "OK", + "numbers up to 8 bytes are supported since kip10" ], [ "'abcdefghi'", @@ -3668,65 +3695,138 @@ "BAD_OPCODE", "opcode 0x50 is reserved" ], + [ + "1", + "IF 0xb2 ELSE 1 ENDIF", + "", + "BAD_OPCODE", + "OpTxVersion is reserved" + ], + [ + "1", + "IF 0xb3 ELSE 1 ENDIF", + "", + "OK", + "OpTxInputCount is enabled since kip10" + ], + [ + "1", + "IF 0xb4 ELSE 1 ENDIF", + "", + "OK", + "OpTxOutputCount is enabled since kip10" + ], + [ + "1", + "IF 0xb5 ELSE 1 ENDIF", + "", + "BAD_OPCODE", + "OpTxLockTime is reserved" + ], [ "1", "IF 0xb6 ELSE 1 ENDIF", "", - "BAD_OPCODE" + "BAD_OPCODE", + "OpTxSubnetId is reserved" ], [ "1", "IF 0xb7 ELSE 1 ENDIF", "", - "BAD_OPCODE" + "BAD_OPCODE", + "OpTxGas is reserved" ], [ "1", "IF 0xb8 ELSE 1 ENDIF", "", - "BAD_OPCODE" + "BAD_OPCODE", + "OpTxPayload is reserved" ], [ "1", - "IF 0xb9 ELSE 1 ENDIF", + "IF 0xb9 0 NUMEQUAL ELSE 1 ENDIF", "", - "BAD_OPCODE" + "OK", + "OpTxInputIndex is enabled since kip10" ], [ "1", "IF 0xba ELSE 1 ENDIF", "", - "BAD_OPCODE" + "BAD_OPCODE", + "OpOutpointTxId is reserved" ], [ "1", "IF 0xbb ELSE 1 ENDIF", "", - "BAD_OPCODE" + "BAD_OPCODE", + "OpOutpointOutputIdx is reserved" + ], + [ + "1", + "IF 0xbc ELSE 1 ENDIF", + "", + "BAD_OPCODE", + "OpTxInputScriptSig is reserved" ], [ "1", "IF 0xbd ELSE 1 ENDIF", "", - "BAD_OPCODE" + "BAD_OPCODE", + "OpTxInputSeq is reserved" + ], + [ + "0 1", + "IF 0xbe 0 NUMEQUAL ELSE 1 ENDIF", + "", + "OK", + "OpTxInputAmount is enabled since kip10" + ], + [ + "0 1", + "IF 0xbf ELSE 1 ENDIF", + "", + "OK", + "OpTxInputSpk is enabled since kip10" ], [ "1", "IF 0xc0 ELSE 1 ENDIF", "", - "BAD_OPCODE" + "BAD_OPCODE", + "OpTxInputBlockDaaScore is reserved" ], [ "1", "IF 0xc1 ELSE 1 ENDIF", "", - "BAD_OPCODE" + "BAD_OPCODE", + "OpTxInputIsCoinbase is reserved" + ], + [ + "0 1", + "IF 0xc2 0 NUMEQUAL ELSE 1 ENDIF", + "", + "OK", + "OpTxOutputAmount is enabled since kip10" + ], + [ + "0 1", + "IF 0xc3 0x02 0x0000 EQUAL ELSE 1 ENDIF", + "", + "OK", + "OpTxOutputSpk is enabled since kip10" ], [ "1", "IF 0xc4 ELSE 1 ENDIF", "", - "BAD_OPCODE" + "BAD_OPCODE", + "opcodes above OpTxOutputSpk invalid if executed" ], [ "1", @@ -4203,6 +4303,62 @@ "BAD_OPCODE", "OP_RESERVED2 is reserved" ], + [ + "1", + "0xb2", + "", + "BAD_OPCODE", + "0xb2 == OP_CHECKSEQUENCEVERIFY + 1" + ], + [ + "2147483648", + "1ADD 2147483649 NUMEQUAL", + "", + "OK", + "We can do math on 5-byte integers since kip10" + ], + [ + "2147483648", + "NEGATE -2147483648 NUMEQUAL", + "", + "OK", + "We can do math on 5-byte integers since kip10" + ], + [ + "-2147483648", + "1ADD -2147483647 NUMEQUAL", + "", + "OK", + "We can do math on 5-byte integers since kip10" + ], + [ + "2147483647", + "DUP 1ADD 1SUB NUMEQUAL", + "", + "OK", + "We can do math on 5-byte integers since kip10" + ], + [ + "2147483648", + "1SUB 2147483647 NUMEQUAL", + "", + "OK", + "We can do math on 5-byte integers since kip10" + ], + [ + "2147483648 1", + "BOOLOR 1 EQUAL", + "", + "OK", + "We can do math on 5-byte integers since kip10" + ], + [ + "2147483648 1", + "BOOLAND 1 EQUAL", + "", + "OK", + "We can do math on 5-byte integers since kip10" + ], [ "-9223372036854775808", "1ADD 1", diff --git a/crypto/txscript/test-data/script_tests.json b/crypto/txscript/test-data/script_tests.json index 0baba0097..5bc5e7a16 100644 --- a/crypto/txscript/test-data/script_tests.json +++ b/crypto/txscript/test-data/script_tests.json @@ -1218,10 +1218,10 @@ ], [ "0", - "IF 0xc4 ELSE 1 ENDIF", + "IF 0xb2 ELSE 1 ENDIF", "", "OK", - "opcodes above OpTxOutputSpk invalid if executed" + "opcodes above OP_CHECKSEQUENCEVERIFY invalid if executed" ], [ "0", @@ -3683,7 +3683,7 @@ ], [ "1", - "IF 0xc4 ELSE 1 ENDIF", + "IF 0xb2 ELSE 1 ENDIF", "", "BAD_OPCODE", "opcodes above OP_CHECKSEQUENCEVERIFY invalid if executed" From 5689ed5d01212719910e90d369806313aca8ab78 Mon Sep 17 00:00:00 2001 From: max143672 Date: Sat, 9 Nov 2024 17:39:26 +0400 Subject: [PATCH 62/93] add simple opcode tests --- crypto/txscript/src/opcodes/mod.rs | 396 ++++++++++++++++++++++++++++- 1 file changed, 394 insertions(+), 2 deletions(-) diff --git a/crypto/txscript/src/opcodes/mod.rs b/crypto/txscript/src/opcodes/mod.rs index 3f8c75855..2f9651636 100644 --- a/crypto/txscript/src/opcodes/mod.rs +++ b/crypto/txscript/src/opcodes/mod.rs @@ -2995,8 +2995,14 @@ mod test { mod kip10 { use super::*; - use crate::data_stack::DataStack; - use crate::{data_stack::OpcodeData, opcodes::push_number, SpkEncoding}; + use crate::{ + data_stack::{DataStack, OpcodeData}, + opcodes::{codes::*, push_number}, + pay_to_script_hash_script, + script_builder::ScriptBuilder, + SpkEncoding, + }; + use kaspa_consensus_core::tx::MutableTransaction; #[derive(Clone, Debug)] struct Kip10Mock { @@ -3363,5 +3369,391 @@ mod test { } } } + + #[test] + fn test_output_amount() { + // Create script: 0 OP_OUTPUTAMOUNT 100 EQUAL + let redeem_script = ScriptBuilder::new() + .add_op(Op0) + .unwrap() + .add_op(OpTxOutputAmount) + .unwrap() + .add_i64(100) + .unwrap() + .add_op(OpEqual) + .unwrap() + .drain(); + + let spk = pay_to_script_hash_script(&redeem_script); + + // Create transaction with output amount 100 + let input_mock = Kip10Mock { spk: spk.clone(), amount: 200 }; + let output_mock = Kip10Mock { spk: create_mock_spk(1), amount: 100 }; + + let (tx, utxo_entries) = kip_10_tx_mock(vec![input_mock.clone()], vec![output_mock]); + let mut tx = MutableTransaction::with_entries(tx, utxo_entries); + + // Set signature script to push redeem script + tx.tx.inputs[0].signature_script = ScriptBuilder::new().add_data(&redeem_script).unwrap().drain(); + + let tx = tx.as_verifiable(); + let sig_cache = Cache::new(10_000); + let reused_values = SigHashReusedValuesUnsync::new(); + + // Test success case + { + let mut vm = TxScriptEngine::from_transaction_input( + &tx, + &tx.inputs()[0], + 0, + tx.utxo(0).unwrap(), + &reused_values, + &sig_cache, + true, + ) + .unwrap(); + + assert_eq!(vm.execute(), Ok(())); + } + + // Test failure case with wrong amount + { + let output_mock = Kip10Mock { + spk: create_mock_spk(1), + amount: 99, // Wrong amount + }; + let (tx, utxo_entries) = kip_10_tx_mock(vec![input_mock.clone()], vec![output_mock]); + let mut tx = MutableTransaction::with_entries(tx, utxo_entries); + tx.tx.inputs[0].signature_script = ScriptBuilder::new().add_data(&redeem_script).unwrap().drain(); + + let tx = tx.as_verifiable(); + let mut vm = TxScriptEngine::from_transaction_input( + &tx, + &tx.inputs()[0], + 0, + tx.utxo(0).unwrap(), + &reused_values, + &sig_cache, + true, + ) + .unwrap(); + + assert_eq!(vm.execute(), Err(TxScriptError::EvalFalse)); + } + } + + #[test] + fn test_input_amount() { + // Create script: 0 OP_INPUTAMOUNT 200 EQUAL + let redeem_script = ScriptBuilder::new() + .add_op(Op0) + .unwrap() + .add_op(OpTxInputAmount) + .unwrap() + .add_i64(200) + .unwrap() + .add_op(OpEqual) + .unwrap() + .drain(); + let sig_cache = Cache::new(10_000); + let reused_values = SigHashReusedValuesUnsync::new(); + let spk = pay_to_script_hash_script(&redeem_script); + + // Test success case + { + let input_mock = Kip10Mock { spk: spk.clone(), amount: 200 }; + let output_mock = Kip10Mock { spk: create_mock_spk(1), amount: 100 }; + + let (tx, utxo_entries) = kip_10_tx_mock(vec![input_mock], vec![output_mock]); + let mut tx = MutableTransaction::with_entries(tx, utxo_entries); + tx.tx.inputs[0].signature_script = ScriptBuilder::new().add_data(&redeem_script).unwrap().drain(); + let tx = tx.as_verifiable(); + let mut vm = TxScriptEngine::from_transaction_input( + &tx, + &tx.inputs()[0], + 0, + tx.utxo(0).unwrap(), + &reused_values, + &sig_cache, + true, + ) + .unwrap(); + + assert_eq!(vm.execute(), Ok(())); + } + + // Test failure case + { + let input_mock = Kip10Mock { + spk: spk.clone(), + amount: 199, // Wrong amount + }; + let output_mock = Kip10Mock { spk: create_mock_spk(1), amount: 100 }; + + let (tx, utxo_entries) = kip_10_tx_mock(vec![input_mock], vec![output_mock]); + let mut tx = MutableTransaction::with_entries(tx, utxo_entries); + tx.tx.inputs[0].signature_script = ScriptBuilder::new().add_data(&redeem_script).unwrap().drain(); + + let tx = tx.as_verifiable(); + let mut vm = TxScriptEngine::from_transaction_input( + &tx, + &tx.inputs()[0], + 0, + tx.utxo(0).unwrap(), + &reused_values, + &sig_cache, + true, + ) + .unwrap(); + + assert_eq!(vm.execute(), Err(TxScriptError::EvalFalse)); + } + } + + #[test] + fn test_output_spk() { + // Create unique SPK to check + let expected_spk = create_mock_spk(42); + let expected_spk_bytes = expected_spk.to_bytes(); + let sig_cache = Cache::new(10_000); + let reused_values = SigHashReusedValuesUnsync::new(); + // Create script: 0 OP_OUTPUTSPK EQUAL + let redeem_script = ScriptBuilder::new() + .add_op(Op0) + .unwrap() + .add_op(OpTxOutputSpk) + .unwrap() + .add_data(&expected_spk_bytes) + .unwrap() + .add_op(OpEqual) + .unwrap() + .drain(); + + let spk = pay_to_script_hash_script(&redeem_script); + + // Test success case + { + let input_mock = Kip10Mock { spk: spk.clone(), amount: 200 }; + let output_mock = Kip10Mock { spk: expected_spk.clone(), amount: 100 }; + + let (tx, utxo_entries) = kip_10_tx_mock(vec![input_mock], vec![output_mock]); + let mut tx = MutableTransaction::with_entries(tx, utxo_entries); + tx.tx.inputs[0].signature_script = ScriptBuilder::new().add_data(&redeem_script).unwrap().drain(); + + let tx = tx.as_verifiable(); + let mut vm = TxScriptEngine::from_transaction_input( + &tx, + &tx.inputs()[0], + 0, + tx.utxo(0).unwrap(), + &reused_values, + &sig_cache, + true, + ) + .unwrap(); + + assert_eq!(vm.execute(), Ok(())); + } + + // Test failure case + { + let input_mock = Kip10Mock { spk: spk.clone(), amount: 200 }; + let output_mock = Kip10Mock { + spk: create_mock_spk(43), // Different SPK + amount: 100, + }; + + let (tx, utxo_entries) = kip_10_tx_mock(vec![input_mock], vec![output_mock]); + let mut tx = MutableTransaction::with_entries(tx, utxo_entries); + tx.tx.inputs[0].signature_script = ScriptBuilder::new().add_data(&redeem_script).unwrap().drain(); + + let tx = tx.as_verifiable(); + let mut vm = TxScriptEngine::from_transaction_input( + &tx, + &tx.inputs()[0], + 0, + tx.utxo(0).unwrap(), + &reused_values, + &sig_cache, + true, + ) + .unwrap(); + + assert_eq!(vm.execute(), Err(TxScriptError::EvalFalse)); + } + } + + #[test] + fn test_input_index() { + // Create script: OP_INPUTINDEX 0 EQUAL + let redeem_script = + ScriptBuilder::new().add_op(OpTxInputIndex).unwrap().add_i64(0).unwrap().add_op(OpEqual).unwrap().drain(); + + let spk = pay_to_script_hash_script(&redeem_script); + let sig_cache = Cache::new(10_000); + let reused_values = SigHashReusedValuesUnsync::new(); + // Test first input (success case) + { + let input_mock = Kip10Mock { spk: spk.clone(), amount: 200 }; + let output_mock = Kip10Mock { spk: create_mock_spk(1), amount: 100 }; + + let (tx, utxo_entries) = kip_10_tx_mock(vec![input_mock], vec![output_mock]); + let mut tx = MutableTransaction::with_entries(tx, utxo_entries); + tx.tx.inputs[0].signature_script = ScriptBuilder::new().add_data(&redeem_script).unwrap().drain(); + + let tx = tx.as_verifiable(); + let mut vm = TxScriptEngine::from_transaction_input( + &tx, + &tx.inputs()[0], + 0, + tx.utxo(0).unwrap(), + &reused_values, + &sig_cache, + true, + ) + .unwrap(); + + assert_eq!(vm.execute(), Ok(())); + } + + // Test second input (failure case) + { + let input_mock1 = Kip10Mock { spk: create_mock_spk(1), amount: 100 }; + let input_mock2 = Kip10Mock { spk: spk.clone(), amount: 200 }; + let output_mock = Kip10Mock { spk: create_mock_spk(2), amount: 100 }; + + let (tx, utxo_entries) = kip_10_tx_mock(vec![input_mock1, input_mock2], vec![output_mock]); + let mut tx = MutableTransaction::with_entries(tx, utxo_entries); + tx.tx.inputs[1].signature_script = ScriptBuilder::new().add_data(&redeem_script).unwrap().drain(); + + let tx = tx.as_verifiable(); + let mut vm = TxScriptEngine::from_transaction_input( + &tx, + &tx.inputs()[1], + 1, + tx.utxo(1).unwrap(), + &reused_values, + &sig_cache, + true, + ) + .unwrap(); + + // Should fail because script expects index 0 but we're at index 1 + assert_eq!(vm.execute(), Err(TxScriptError::EvalFalse)); + } + } + + #[test] + fn test_counts() { + let sig_cache = Cache::new(10_000); + let reused_values = SigHashReusedValuesUnsync::new(); + // Test OpInputCount: "OP_INPUTCOUNT 2 EQUAL" + let input_count_script = + ScriptBuilder::new().add_op(OpTxInputCount).unwrap().add_i64(2).unwrap().add_op(OpEqual).unwrap().drain(); + + // Test OpOutputCount: "OP_OUTPUTCOUNT 3 EQUAL" + let output_count_script = + ScriptBuilder::new().add_op(OpTxOutputCount).unwrap().add_i64(3).unwrap().add_op(OpEqual).unwrap().drain(); + + let input_spk = pay_to_script_hash_script(&input_count_script); + let output_spk = pay_to_script_hash_script(&output_count_script); + + // Create transaction with 2 inputs and 3 outputs + let input_mock1 = Kip10Mock { spk: input_spk.clone(), amount: 100 }; + let input_mock2 = Kip10Mock { spk: output_spk.clone(), amount: 200 }; + let output_mock1 = Kip10Mock { spk: create_mock_spk(1), amount: 50 }; + let output_mock2 = Kip10Mock { spk: create_mock_spk(2), amount: 100 }; + let output_mock3 = Kip10Mock { spk: create_mock_spk(3), amount: 150 }; + + let (tx, utxo_entries) = + kip_10_tx_mock(vec![input_mock1.clone(), input_mock2.clone()], vec![output_mock1, output_mock2, output_mock3]); + + // Test InputCount + { + let mut tx = MutableTransaction::with_entries(tx.clone(), utxo_entries.clone()); + tx.tx.inputs[0].signature_script = ScriptBuilder::new().add_data(&input_count_script).unwrap().drain(); + + let tx = tx.as_verifiable(); + let mut vm = TxScriptEngine::from_transaction_input( + &tx, + &tx.inputs()[0], + 0, + tx.utxo(0).unwrap(), + &reused_values, + &sig_cache, + true, + ) + .unwrap(); + + assert_eq!(vm.execute(), Ok(())); + } + + // Test OutputCount + { + let mut tx = MutableTransaction::with_entries(tx.clone(), utxo_entries.clone()); + tx.tx.inputs[1].signature_script = ScriptBuilder::new().add_data(&output_count_script).unwrap().drain(); + + let tx = tx.as_verifiable(); + let mut vm = TxScriptEngine::from_transaction_input( + &tx, + &tx.inputs()[1], + 1, + tx.utxo(1).unwrap(), + &reused_values, + &sig_cache, + true, + ) + .unwrap(); + + assert_eq!(vm.execute(), Ok(())); + } + + // Test failure cases with wrong counts + { + // Wrong input count script: "OP_INPUTCOUNT 3 EQUAL" + let wrong_input_count_script = + ScriptBuilder::new().add_op(OpTxInputCount).unwrap().add_i64(3).unwrap().add_op(OpEqual).unwrap().drain(); + + let mut tx = MutableTransaction::with_entries(tx.clone(), utxo_entries.clone()); + tx.tx.inputs[0].signature_script = ScriptBuilder::new().add_data(&wrong_input_count_script).unwrap().drain(); + + let tx = tx.as_verifiable(); + let mut vm = TxScriptEngine::from_transaction_input( + &tx, + &tx.inputs()[0], + 0, + tx.utxo(0).unwrap(), + &reused_values, + &sig_cache, + true, + ) + .unwrap(); + + assert_eq!(vm.execute(), Err(TxScriptError::EvalFalse)); + } + + { + // Wrong output count script: "OP_OUTPUTCOUNT 2 EQUAL" + let wrong_output_count_script = + ScriptBuilder::new().add_op(OpTxOutputCount).unwrap().add_i64(2).unwrap().add_op(OpEqual).unwrap().drain(); + + let mut tx = MutableTransaction::with_entries(tx.clone(), utxo_entries.clone()); + tx.tx.inputs[1].signature_script = ScriptBuilder::new().add_data(&wrong_output_count_script).unwrap().drain(); + + let tx = tx.as_verifiable(); + let mut vm = TxScriptEngine::from_transaction_input( + &tx, + &tx.inputs()[1], + 1, + tx.utxo(1).unwrap(), + &reused_values, + &sig_cache, + true, + ) + .unwrap(); + + assert_eq!(vm.execute(), Err(TxScriptError::EvalFalse)); + } + } } } From e9829e2cbc05efa19e12e5da0e46180859b2a699 Mon Sep 17 00:00:00 2001 From: max143672 Date: Sat, 9 Nov 2024 17:43:42 +0400 Subject: [PATCH 63/93] rename id(which represents input index) to idx --- crypto/txscript/src/lib.rs | 10 +++++----- crypto/txscript/src/opcodes/mod.rs | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/crypto/txscript/src/lib.rs b/crypto/txscript/src/lib.rs index 8fcb7012c..01188ea1e 100644 --- a/crypto/txscript/src/lib.rs +++ b/crypto/txscript/src/lib.rs @@ -68,7 +68,7 @@ pub struct SigCacheKey { } enum ScriptSource<'a, T: VerifiableTransaction> { - TxInput { tx: &'a T, input: &'a TransactionInput, id: usize, utxo_entry: &'a UtxoEntry, is_p2sh: bool }, + TxInput { tx: &'a T, input: &'a TransactionInput, idx: usize, utxo_entry: &'a UtxoEntry, is_p2sh: bool }, StandAloneScripts(Vec<&'a [u8]>), } @@ -185,7 +185,7 @@ impl<'a, T: VerifiableTransaction, Reused: SigHashReusedValues> TxScriptEngine<' true => Ok(Self { dstack: Default::default(), astack: Default::default(), - script_source: ScriptSource::TxInput { tx, input, id: input_idx, utxo_entry, is_p2sh }, + script_source: ScriptSource::TxInput { tx, input, idx: input_idx, utxo_entry, is_p2sh }, reused_values, sig_cache, cond_stack: Default::default(), @@ -310,7 +310,7 @@ impl<'a, T: VerifiableTransaction, Reused: SigHashReusedValues> TxScriptEngine<' // each is successful scripts.iter().enumerate().filter(|(_, s)| !s.is_empty()).try_for_each(|(idx, s)| { let verify_only_push = - idx == 0 && matches!(self.script_source, ScriptSource::TxInput { tx: _, input: _, id: _, utxo_entry: _, is_p2sh: _ }); + idx == 0 && matches!(self.script_source, ScriptSource::TxInput { tx: _, input: _, idx: _, utxo_entry: _, is_p2sh: _ }); // Save script in p2sh if is_p2sh && idx == 1 { saved_stack = Some(self.dstack.clone()); @@ -454,7 +454,7 @@ impl<'a, T: VerifiableTransaction, Reused: SigHashReusedValues> TxScriptEngine<' #[inline] fn check_schnorr_signature(&mut self, hash_type: SigHashType, key: &[u8], sig: &[u8]) -> Result { match self.script_source { - ScriptSource::TxInput { tx, id, .. } => { + ScriptSource::TxInput { tx, idx: id, .. } => { if sig.len() != 64 { return Err(TxScriptError::SigLength(sig.len())); } @@ -489,7 +489,7 @@ impl<'a, T: VerifiableTransaction, Reused: SigHashReusedValues> TxScriptEngine<' fn check_ecdsa_signature(&mut self, hash_type: SigHashType, key: &[u8], sig: &[u8]) -> Result { match self.script_source { - ScriptSource::TxInput { tx, id, .. } => { + ScriptSource::TxInput { tx, idx: id, .. } => { if sig.len() != 64 { return Err(TxScriptError::SigLength(sig.len())); } diff --git a/crypto/txscript/src/opcodes/mod.rs b/crypto/txscript/src/opcodes/mod.rs index 2f9651636..894cf00a9 100644 --- a/crypto/txscript/src/opcodes/mod.rs +++ b/crypto/txscript/src/opcodes/mod.rs @@ -908,8 +908,8 @@ opcode_list! { opcode OpTxInputIndex<0xb9, 1>(self, vm) { if vm.kip10_enabled { match vm.script_source { - ScriptSource::TxInput{id, ..} => { - push_number(id as i64, vm) + ScriptSource::TxInput{idx, ..} => { + push_number(idx as i64, vm) }, _ => Err(TxScriptError::InvalidSource("OpInputIndex only applies to transaction inputs".to_string())) } From 7705ba38a30592048db897cd316aaa46346b432e Mon Sep 17 00:00:00 2001 From: max143672 Date: Sat, 9 Nov 2024 21:15:18 +0400 Subject: [PATCH 64/93] fix comments --- consensus/core/src/config/params.rs | 11 ++++++++++- crypto/txscript/src/lib.rs | 26 +++++++++++++++++--------- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/consensus/core/src/config/params.rs b/consensus/core/src/config/params.rs index 0ecb6f06c..001f2c6e4 100644 --- a/consensus/core/src/config/params.rs +++ b/consensus/core/src/config/params.rs @@ -110,7 +110,16 @@ pub struct Params { /// DAA score from which storage mass calculation and transaction mass field are activated as a consensus rule pub storage_mass_activation: ForkActivation, - /// DAA score from which tx engine supports kip10 opcodes: OpInputAmount, OpInputSpk, OpOutputAmount, OpOutputSpk + /// DAA score from which tx engine: + /// 1. Supports 8-byte integer arithmetic operations (previously limited to 4 bytes) + /// 2. Supports transaction introspection opcodes: + /// - OpTxInputCount (0xb3): Get number of inputs + /// - OpTxOutputCount (0xb4): Get number of outputs + /// - OpTxInputIndex (0xb9): Get current input index + /// - OpTxInputAmount (0xbe): Get input amount + /// - OpTxInputSpk (0xbf): Get input script public key + /// - OpTxOutputAmount (0xc2): Get output amount + /// - OpTxOutputSpk (0xc3): Get output script public key pub kip10_activation_daa_score: ForkActivation, /// DAA score after which the pre-deflationary period switches to the deflationary period diff --git a/crypto/txscript/src/lib.rs b/crypto/txscript/src/lib.rs index 01188ea1e..f65f962ed 100644 --- a/crypto/txscript/src/lib.rs +++ b/crypto/txscript/src/lib.rs @@ -1163,16 +1163,24 @@ mod bitcoind_tests { // - script_tests.json: Tests basic script functionality with KIP-10 disabled (kip10_enabled=false) // - script_tests-kip10.json: Tests expanded functionality with KIP-10 enabled (kip10_enabled=true) // - // KIP-10 introduces four new opcodes to enhance script functionality: - // - OpInputSpk (0xb2): Retrieves the script public key of the current input - // - OpInputAmount (0xb3): Retrieves the amount of the current input - // - OpOutputSpk (0xb4): Retrieves the script public key of the output at the current input's index - // - OpOutputAmount (0xb5): Retrieves the amount of the output at the current input's index + // KIP-10 introduces two major changes: // - // These opcodes were added to support mutual transactions and auto-compounding addresses - // as discussed in KIP-9. When KIP-10 is disabled (pre-activation), these opcodes will return - // an InvalidOpcode error. When enabled, they provide access to transaction input/output data - // within scripts. + // 1. Support for 8-byte integer arithmetic (previously limited to 4 bytes) + // This enables working with larger numbers in scripts and reduces artificial constraints + // + // 2. Transaction introspection opcodes: + // - OpTxInputCount (0xb3): Get number of inputs + // - OpTxOutputCount (0xb4): Get number of outputs + // - OpTxInputIndex (0xb9): Get current input index + // - OpTxInputAmount (0xbe): Get input amount + // - OpTxInputSpk (0xbf): Get input script public key + // - OpTxOutputAmount (0xc2): Get output amount + // - OpTxOutputSpk (0xc3): Get output script public key + // + // These changes were added to support mutual transactions and auto-compounding addresses. + // When KIP-10 is disabled (pre-activation), the new opcodes will return an InvalidOpcode error + // and arithmetic is limited to 4 bytes. When enabled, scripts gain full access to transaction + // data and 8-byte arithmetic capabilities. for (file_name, kip10_enabled) in [("script_tests.json", false), ("script_tests-kip10.json", true)] { let file = File::open(Path::new(env!("CARGO_MANIFEST_DIR")).join("test-data").join(file_name)).expect("Could not find test file"); From 1f38b6459a04f331672fa6e4bcd1b55e2352795d Mon Sep 17 00:00:00 2001 From: max143672 Date: Sat, 9 Nov 2024 21:58:23 +0400 Subject: [PATCH 65/93] add input spk tests --- crypto/txscript/src/opcodes/mod.rs | 74 ++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/crypto/txscript/src/opcodes/mod.rs b/crypto/txscript/src/opcodes/mod.rs index 894cf00a9..56ef2ff7c 100644 --- a/crypto/txscript/src/opcodes/mod.rs +++ b/crypto/txscript/src/opcodes/mod.rs @@ -3510,6 +3510,80 @@ mod test { } } + #[test] + fn test_input_spk_basic() { + let sig_cache = Cache::new(10_000); + let reused_values = SigHashReusedValuesUnsync::new(); + + // Create script: 0 OP_INPUTSPK OpNop + // Just verify that OpInputSpk pushes something onto stack + let redeem_script = ScriptBuilder::new().add_ops(&[Op0, OpTxInputSpk, OpNop]).unwrap().drain(); + let spk = pay_to_script_hash_script(&redeem_script); + + let (tx, utxo_entries) = kip_10_tx_mock(vec![Kip10Mock { spk, amount: 100 }], vec![]); + let mut tx = MutableTransaction::with_entries(tx, utxo_entries); + tx.tx.inputs[0].signature_script = ScriptBuilder::new().add_data(&redeem_script).unwrap().drain(); + + let tx = tx.as_verifiable(); + let mut vm = + TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, tx.utxo(0).unwrap(), &reused_values, &sig_cache, true) + .unwrap(); + + // OpInputSpk should push input's SPK onto stack, making it non-empty + assert_eq!(vm.execute(), Ok(())); + } + + #[test] + fn test_input_spk_different() { + let sig_cache = Cache::new(10_000); + let reused_values = SigHashReusedValuesUnsync::new(); + + // Create script: 0 OP_INPUTSPK 1 OP_INPUTSPK OP_EQUAL OP_NOT + // Verifies that two different inputs have different SPKs + let redeem_script = ScriptBuilder::new().add_ops(&[Op0, OpTxInputSpk, Op1, OpTxInputSpk, OpEqual, OpNot]).unwrap().drain(); + let spk = pay_to_script_hash_script(&redeem_script); + let input_mock1 = Kip10Mock { spk, amount: 100 }; + let input_mock2 = Kip10Mock { spk: create_mock_spk(2), amount: 100 }; // Different SPK + + let (tx, utxo_entries) = kip_10_tx_mock(vec![input_mock1, input_mock2], vec![]); + let mut tx = MutableTransaction::with_entries(tx, utxo_entries); + tx.tx.inputs[0].signature_script = ScriptBuilder::new().add_data(&redeem_script).unwrap().drain(); + + let tx = tx.as_verifiable(); + let mut vm = + TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, tx.utxo(0).unwrap(), &reused_values, &sig_cache, true) + .unwrap(); + + // Should succeed because the SPKs are different + assert_eq!(vm.execute(), Ok(())); + } + + #[test] + fn test_input_spk_same() { + let sig_cache = Cache::new(10_000); + let reused_values = SigHashReusedValuesUnsync::new(); + + // Create script: 0 OP_INPUTSPK 1 OP_INPUTSPK OP_EQUAL + // Verifies that two inputs with same SPK are equal + let redeem_script = ScriptBuilder::new().add_ops(&[Op0, OpTxInputSpk, Op1, OpTxInputSpk, OpEqual]).unwrap().drain(); + + let spk = pay_to_script_hash_script(&redeem_script); + let input_mock1 = Kip10Mock { spk: spk.clone(), amount: 100 }; + let input_mock2 = Kip10Mock { spk, amount: 100 }; + + let (tx, utxo_entries) = kip_10_tx_mock(vec![input_mock1, input_mock2], vec![]); + let mut tx = MutableTransaction::with_entries(tx, utxo_entries); + tx.tx.inputs[0].signature_script = ScriptBuilder::new().add_data(&redeem_script).unwrap().drain(); + + let tx = tx.as_verifiable(); + let mut vm = + TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, tx.utxo(0).unwrap(), &reused_values, &sig_cache, true) + .unwrap(); + + // Should succeed because both SPKs are identical + assert_eq!(vm.execute(), Ok(())); + } + #[test] fn test_output_spk() { // Create unique SPK to check From 4a3a44bdee467019e21a0a86bcb8790874b7754c Mon Sep 17 00:00:00 2001 From: max143672 Date: Sat, 9 Nov 2024 22:32:42 +0400 Subject: [PATCH 66/93] refactor test cases --- crypto/txscript/src/opcodes/mod.rs | 208 ++++++++++++++++------------- 1 file changed, 116 insertions(+), 92 deletions(-) diff --git a/crypto/txscript/src/opcodes/mod.rs b/crypto/txscript/src/opcodes/mod.rs index 56ef2ff7c..c98d2c185 100644 --- a/crypto/txscript/src/opcodes/mod.rs +++ b/crypto/txscript/src/opcodes/mod.rs @@ -3010,27 +3010,6 @@ mod test { amount: u64, } - #[derive(Debug)] - struct TestCase { - name: &'static str, - kip10_enabled: bool, - expected_results: Vec, - } - - #[derive(Debug)] - enum Operation { - InputSpk, - OutputSpk, - InputAmount, - OutputAmount, - } - - #[derive(Debug)] - enum ExpectedResult { - Success { operation: Operation, index: i64, expected_spk: Option>, expected_amount: Option> }, - Error { operation: Operation, index: Option, expected_error: TxScriptError }, - } - fn create_mock_spk(value: u8) -> ScriptPublicKey { let pub_key = vec![value; 32]; let addr = Address::new(Prefix::Testnet, Version::PubKey, &pub_key); @@ -3053,7 +3032,34 @@ mod test { (tx, utxos) } - fn execute_test_case(test_case: &TestCase) { + #[derive(Debug)] + struct TestGroup { + name: &'static str, + kip10_enabled: bool, + test_cases: Vec, + } + + #[derive(Debug)] + enum Operation { + InputSpk, + OutputSpk, + InputAmount, + OutputAmount, + } + + #[derive(Debug)] + enum TestCase { + Successful { operation: Operation, index: i64, expected_result: ExpectedResult }, + Incorrect { operation: Operation, index: Option, expected_error: TxScriptError }, + } + + #[derive(Debug)] + struct ExpectedResult { + expected_spk: Option>, + expected_amount: Option>, + } + + fn execute_test_group(group: &TestGroup) { let input_spk1 = create_mock_spk(1); let input_spk2 = create_mock_spk(2); let output_spk1 = create_mock_spk(3); @@ -3077,12 +3083,13 @@ mod test { tx.utxo(current_idx).unwrap(), &reused_values, &sig_cache, - test_case.kip10_enabled, + group.kip10_enabled, ) .unwrap(); - let op_input_idx = opcodes::OpTxInputIndex::empty().expect("Should accept empty"); - if !test_case.kip10_enabled { + // Check input index opcode first + let op_input_idx = opcodes::OpTxInputIndex::empty().expect("Should accept empty"); + if !group.kip10_enabled { assert!(matches!(op_input_idx.execute(&mut vm), Err(TxScriptError::InvalidOpcode(_)))); } else { let mut expected = vm.dstack.clone(); @@ -3092,45 +3099,46 @@ mod test { vm.dstack.clear(); } + // Prepare opcodes let op_input_spk = opcodes::OpTxInputSpk::empty().expect("Should accept empty"); let op_output_spk = opcodes::OpTxOutputSpk::empty().expect("Should accept empty"); let op_input_amount = opcodes::OpTxInputAmount::empty().expect("Should accept empty"); let op_output_amount = opcodes::OpTxOutputAmount::empty().expect("Should accept empty"); - for expected_result in &test_case.expected_results { - match expected_result { - ExpectedResult::Success { operation, index, expected_spk, expected_amount } => { + // Execute each test case + for test_case in &group.test_cases { + match test_case { + TestCase::Successful { operation, index, expected_result } => { push_number(*index, &mut vm).unwrap(); - match operation { - Operation::InputSpk => { - op_input_spk.execute(&mut vm).unwrap(); - assert_eq!(vm.dstack, vec![expected_spk.clone().unwrap()]); - } - Operation::OutputSpk => { - op_output_spk.execute(&mut vm).unwrap(); - assert_eq!(vm.dstack, vec![expected_spk.clone().unwrap()]); - } - Operation::InputAmount => { - op_input_amount.execute(&mut vm).unwrap(); - assert_eq!(vm.dstack, vec![expected_amount.clone().unwrap()]); - } - Operation::OutputAmount => { - op_output_amount.execute(&mut vm).unwrap(); - assert_eq!(vm.dstack, vec![expected_amount.clone().unwrap()]); - } + let result = match operation { + Operation::InputSpk => op_input_spk.execute(&mut vm), + Operation::OutputSpk => op_output_spk.execute(&mut vm), + Operation::InputAmount => op_input_amount.execute(&mut vm), + Operation::OutputAmount => op_output_amount.execute(&mut vm), + }; + assert!(result.is_ok()); + + // Check the result matches expectations + if let Some(ref expected_spk) = expected_result.expected_spk { + assert_eq!(vm.dstack, vec![expected_spk.clone()]); + } + if let Some(ref expected_amount) = expected_result.expected_amount { + assert_eq!(vm.dstack, vec![expected_amount.clone()]); } vm.dstack.clear(); } - ExpectedResult::Error { operation, index, expected_error } => { + TestCase::Incorrect { operation, index, expected_error } => { if let Some(idx) = index { push_number(*idx, &mut vm).unwrap(); } + let result = match operation { Operation::InputSpk => op_input_spk.execute(&mut vm), Operation::OutputSpk => op_output_spk.execute(&mut vm), Operation::InputAmount => op_input_amount.execute(&mut vm), Operation::OutputAmount => op_output_amount.execute(&mut vm), }; + assert!( matches!(result, Err(ref e) if std::mem::discriminant(e) == std::mem::discriminant(expected_error)) ); @@ -3143,123 +3151,139 @@ mod test { #[test] fn test_unary_introspection_ops() { - let test_cases = vec![ - TestCase { + let test_groups = vec![ + TestGroup { name: "KIP-10 disabled", kip10_enabled: false, - expected_results: vec![ - ExpectedResult::Error { + test_cases: vec![ + TestCase::Incorrect { operation: Operation::InputSpk, index: Some(0), expected_error: TxScriptError::InvalidOpcode("Invalid opcode".to_string()), }, - ExpectedResult::Error { + TestCase::Incorrect { operation: Operation::OutputSpk, index: Some(0), expected_error: TxScriptError::InvalidOpcode("Invalid opcode".to_string()), }, - ExpectedResult::Error { + TestCase::Incorrect { operation: Operation::InputAmount, index: Some(0), expected_error: TxScriptError::InvalidOpcode("Invalid opcode".to_string()), }, - ExpectedResult::Error { + TestCase::Incorrect { operation: Operation::OutputAmount, index: Some(0), expected_error: TxScriptError::InvalidOpcode("Invalid opcode".to_string()), }, ], }, - TestCase { + TestGroup { name: "Valid input indices", kip10_enabled: true, - expected_results: vec![ - ExpectedResult::Success { + test_cases: vec![ + TestCase::Successful { operation: Operation::InputSpk, index: 0, - expected_spk: Some(create_mock_spk(1).to_bytes()), - expected_amount: None, + expected_result: ExpectedResult { + expected_spk: Some(create_mock_spk(1).to_bytes()), + expected_amount: None, + }, }, - ExpectedResult::Success { + TestCase::Successful { operation: Operation::InputSpk, index: 1, - expected_spk: Some(create_mock_spk(2).to_bytes()), - expected_amount: None, + expected_result: ExpectedResult { + expected_spk: Some(create_mock_spk(2).to_bytes()), + expected_amount: None, + }, }, - ExpectedResult::Success { + TestCase::Successful { operation: Operation::InputAmount, index: 0, - expected_spk: None, - expected_amount: Some(OpcodeData::::serialize(&1111)), + expected_result: ExpectedResult { + expected_spk: None, + expected_amount: Some(OpcodeData::::serialize(&1111)), + }, }, - ExpectedResult::Success { + TestCase::Successful { operation: Operation::InputAmount, index: 1, - expected_spk: None, - expected_amount: Some(OpcodeData::::serialize(&2222)), + expected_result: ExpectedResult { + expected_spk: None, + expected_amount: Some(OpcodeData::::serialize(&2222)), + }, }, ], }, - TestCase { + TestGroup { name: "Valid output indices", kip10_enabled: true, - expected_results: vec![ - ExpectedResult::Success { + test_cases: vec![ + TestCase::Successful { operation: Operation::OutputSpk, index: 0, - expected_spk: Some(create_mock_spk(3).to_bytes()), - expected_amount: None, + expected_result: ExpectedResult { + expected_spk: Some(create_mock_spk(3).to_bytes()), + expected_amount: None, + }, }, - ExpectedResult::Success { + TestCase::Successful { operation: Operation::OutputSpk, index: 1, - expected_spk: Some(create_mock_spk(4).to_bytes()), - expected_amount: None, + expected_result: ExpectedResult { + expected_spk: Some(create_mock_spk(4).to_bytes()), + expected_amount: None, + }, }, - ExpectedResult::Success { + TestCase::Successful { operation: Operation::OutputAmount, index: 0, - expected_spk: None, - expected_amount: Some(OpcodeData::::serialize(&3333)), + expected_result: ExpectedResult { + expected_spk: None, + expected_amount: Some(OpcodeData::::serialize(&3333)), + }, }, - ExpectedResult::Success { + TestCase::Successful { operation: Operation::OutputAmount, index: 1, - expected_spk: None, - expected_amount: Some(OpcodeData::::serialize(&4444)), + expected_result: ExpectedResult { + expected_spk: None, + expected_amount: Some(OpcodeData::::serialize(&4444)), + }, }, ], }, - TestCase { + TestGroup { name: "Error cases", kip10_enabled: true, - expected_results: vec![ - ExpectedResult::Error { + test_cases: vec![ + TestCase::Incorrect { operation: Operation::InputAmount, index: None, expected_error: TxScriptError::InvalidStackOperation(1, 0), }, - ExpectedResult::Error { + TestCase::Incorrect { operation: Operation::InputAmount, index: Some(-1), expected_error: TxScriptError::InvalidIndex(u8::MAX as usize + 1, 2), }, - ExpectedResult::Error { + TestCase::Incorrect { operation: Operation::InputAmount, index: Some(2), expected_error: TxScriptError::InvalidIndex(2, 2), }, - ExpectedResult::Error { + TestCase::Incorrect { operation: Operation::OutputAmount, index: None, expected_error: TxScriptError::InvalidStackOperation(1, 0), }, - ExpectedResult::Error { + TestCase::Incorrect { operation: Operation::OutputAmount, index: Some(-1), expected_error: TxScriptError::InvalidIndex(u8::MAX as usize + 1, 2), }, - ExpectedResult::Error { + TestCase::Incorrect { operation: Operation::OutputAmount, index: Some(2), expected_error: TxScriptError::InvalidOutputIndex(2, 2), @@ -3268,9 +3292,9 @@ mod test { }, ]; - for test_case in test_cases { - println!("Running test case: {}", test_case.name); - execute_test_case(&test_case); + for group in test_groups { + println!("Running test group: {}", group.name); + execute_test_group(&group); } } fn create_mock_tx(input_count: usize, output_count: usize) -> (Transaction, Vec) { From 5a00231e4e519092f274c9e09527fd6a62757491 Mon Sep 17 00:00:00 2001 From: max143672 Date: Sat, 9 Nov 2024 22:52:23 +0400 Subject: [PATCH 67/93] refactor(txscript): Enforce input index invariant via assertion Change TxScriptEngine::from_transaction_input to assert valid input index instead of returning Result. This better reflects that an invalid index is a caller's (transaction validation) error rather than a script engine error, since the input must be part of the transaction being validated. An invalid index signifies a mismatch between the transaction and the input being validated - this is a programming error in the transaction validator layer, not a script engine concern. The script engine should be able to assume it receives valid inputs from its caller. The change simplifies error handling by enforcing this invariant early, while maintaining identical behavior for valid inputs. The function is now documented to panic on malformed inputs. This is a breaking change for code that previously handled InvalidIndex errors, though such handling was likely incorrect as it indicated an inconsistency in transaction validation. --- crypto/txscript/src/lib.rs | 40 +++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/crypto/txscript/src/lib.rs b/crypto/txscript/src/lib.rs index f65f962ed..9cfdaf1ae 100644 --- a/crypto/txscript/src/lib.rs +++ b/crypto/txscript/src/lib.rs @@ -168,6 +168,22 @@ impl<'a, T: VerifiableTransaction, Reused: SigHashReusedValues> TxScriptEngine<' } } + /// Creates a new Script Engine for validating transaction input. + /// + /// # Arguments + /// * `tx` - The transaction being validated + /// * `input` - The input being validated + /// * `input_idx` - Index of the input in the transaction + /// * `utxo_entry` - UTXO entry being spent + /// * `reused_values` - Reused values for signature hashing + /// * `sig_cache` - Cache for signature verification + /// * `kip10_enabled` - Whether KIP-10 transaction introspection opcodes are enabled + /// + /// # Panics + /// * When input_idx >= number of inputs in transaction (malformed input) + /// + /// # Returns + /// Script engine instance configured for the given input pub fn from_transaction_input( tx: &'a T, input: &'a TransactionInput, @@ -181,19 +197,17 @@ impl<'a, T: VerifiableTransaction, Reused: SigHashReusedValues> TxScriptEngine<' // The script_public_key in P2SH is just validating the hash on the OpMultiSig script // the user provides let is_p2sh = ScriptClass::is_pay_to_script_hash(script_public_key); - match input_idx < tx.tx().inputs.len() { - true => Ok(Self { - dstack: Default::default(), - astack: Default::default(), - script_source: ScriptSource::TxInput { tx, input, idx: input_idx, utxo_entry, is_p2sh }, - reused_values, - sig_cache, - cond_stack: Default::default(), - num_ops: 0, - kip10_enabled, - }), - false => Err(TxScriptError::InvalidIndex(input_idx, tx.tx().inputs.len())), - } + assert!(input_idx < tx.tx().inputs.len()); + Ok(Self { + dstack: Default::default(), + astack: Default::default(), + script_source: ScriptSource::TxInput { tx, input, idx: input_idx, utxo_entry, is_p2sh }, + reused_values, + sig_cache, + cond_stack: Default::default(), + num_ops: 0, + kip10_enabled, + }) } pub fn from_script( From df7c2430b2c771c99fbb5776bc300d2a7fc788b1 Mon Sep 17 00:00:00 2001 From: max143672 Date: Sat, 9 Nov 2024 22:53:28 +0400 Subject: [PATCH 68/93] refactor error types to contain correct info --- crypto/txscript/errors/src/lib.rs | 8 ++++---- crypto/txscript/src/opcodes/mod.rs | 24 +++++++++++++----------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/crypto/txscript/errors/src/lib.rs b/crypto/txscript/errors/src/lib.rs index 9cc23ee80..9384a066f 100644 --- a/crypto/txscript/errors/src/lib.rs +++ b/crypto/txscript/errors/src/lib.rs @@ -6,8 +6,8 @@ pub enum TxScriptError { MalformedPushSize(Vec), #[error("opcode requires {0} bytes, but script only has {1} remaining")] MalformedPush(usize, usize), - #[error("transaction input index {0} >= {1}")] - InvalidIndex(usize, usize), + #[error("transaction input {0} is out of bounds, should be non-negative below {1}")] + InvalidInputIndex(i32, usize), #[error("combined stack size {0} > max allowed {1}")] StackSizeExceeded(usize, usize), #[error("attempt to execute invalid opcode {0}")] @@ -69,6 +69,6 @@ pub enum TxScriptError { InvalidStackOperation(usize, usize), #[error("script of size {0} exceeded maximum allowed size of {1}")] ScriptSize(usize, usize), - #[error("transaction output index {0} >= {1}")] - InvalidOutputIndex(usize, usize), + #[error("transaction output {0} is out of bounds, should be non-negative below {1}")] + InvalidOutputIndex(i32, usize), } diff --git a/crypto/txscript/src/opcodes/mod.rs b/crypto/txscript/src/opcodes/mod.rs index c98d2c185..0a4a6eb13 100644 --- a/crypto/txscript/src/opcodes/mod.rs +++ b/crypto/txscript/src/opcodes/mod.rs @@ -927,8 +927,7 @@ opcode_list! { match vm.script_source { ScriptSource::TxInput{tx, ..} => { let [idx]: [i32; 1] = vm.dstack.pop_items()?; - let idx = usize::try_from(idx).map_err(|_| TxScriptError::InvalidIndex(idx as usize, tx.inputs().len()))?; - let utxo = tx.utxo(idx).ok_or_else(|| TxScriptError::InvalidIndex(idx, tx.inputs().len()))?; + let utxo = usize::try_from(idx).ok().and_then(|idx| tx.utxo(idx)).ok_or_else(|| TxScriptError::InvalidInputIndex(idx, tx.inputs().len()))?; push_number(utxo.amount as i64, vm) }, _ => Err(TxScriptError::InvalidSource("OpInputAmount only applies to transaction inputs".to_string())) @@ -942,8 +941,9 @@ opcode_list! { match vm.script_source { ScriptSource::TxInput{tx, ..} => { let [idx]: [i32; 1] = vm.dstack.pop_items()?; - let idx = usize::try_from(idx).map_err(|_| TxScriptError::InvalidIndex(idx as usize, tx.inputs().len()))?; - let utxo = tx.utxo(idx).ok_or_else(|| TxScriptError::InvalidIndex(idx, tx.inputs().len()))?; + let utxo = usize::try_from(idx).ok(). + and_then(|idx| tx.utxo(idx)). + ok_or_else(|| TxScriptError::InvalidInputIndex(idx, tx.inputs().len()))?; vm.dstack.push(utxo.script_public_key.to_bytes()); Ok(()) }, @@ -961,8 +961,9 @@ opcode_list! { match vm.script_source { ScriptSource::TxInput{tx, ..} => { let [idx]: [i32; 1] = vm.dstack.pop_items()?; - let idx = usize::try_from(idx).map_err(|_| TxScriptError::InvalidIndex(idx as usize, tx.inputs().len()))?; - let output = tx.outputs().get(idx).ok_or_else(|| TxScriptError::InvalidOutputIndex(idx, tx.outputs().len()))?; + let output = usize::try_from(idx).ok(). + and_then(|idx| tx.outputs().get(idx)). + ok_or_else(|| TxScriptError::InvalidOutputIndex(idx, tx.inputs().len()))?; push_number(output.value as i64, vm) }, _ => Err(TxScriptError::InvalidSource("OpOutputAmount only applies to transaction inputs".to_string())) @@ -976,8 +977,9 @@ opcode_list! { match vm.script_source { ScriptSource::TxInput{tx, ..} => { let [idx]: [i32; 1] = vm.dstack.pop_items()?; - let idx = usize::try_from(idx).map_err(|_| TxScriptError::InvalidIndex(idx as usize, tx.inputs().len()))?; - let output = tx.outputs().get(idx).ok_or_else(|| TxScriptError::InvalidOutputIndex(idx, tx.outputs().len()))?; + let output = usize::try_from(idx).ok(). + and_then(|idx| tx.outputs().get(idx)). + ok_or_else(|| TxScriptError::InvalidOutputIndex(idx, tx.inputs().len()))?; vm.dstack.push(output.script_public_key.to_bytes()); Ok(()) }, @@ -3266,12 +3268,12 @@ mod test { TestCase::Incorrect { operation: Operation::InputAmount, index: Some(-1), - expected_error: TxScriptError::InvalidIndex(u8::MAX as usize + 1, 2), + expected_error: TxScriptError::InvalidInputIndex(-1, 2), }, TestCase::Incorrect { operation: Operation::InputAmount, index: Some(2), - expected_error: TxScriptError::InvalidIndex(2, 2), + expected_error: TxScriptError::InvalidInputIndex(2, 2), }, TestCase::Incorrect { operation: Operation::OutputAmount, @@ -3281,7 +3283,7 @@ mod test { TestCase::Incorrect { operation: Operation::OutputAmount, index: Some(-1), - expected_error: TxScriptError::InvalidIndex(u8::MAX as usize + 1, 2), + expected_error: TxScriptError::InvalidOutputIndex(-1, 2), }, TestCase::Incorrect { operation: Operation::OutputAmount, From 523ee9a1d3ee3306cb7a4d8dd702ec3cac71193e Mon Sep 17 00:00:00 2001 From: max143672 Date: Sat, 9 Nov 2024 22:54:16 +0400 Subject: [PATCH 69/93] rename id to idx --- crypto/txscript/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crypto/txscript/src/lib.rs b/crypto/txscript/src/lib.rs index 9cfdaf1ae..4f80f7dde 100644 --- a/crypto/txscript/src/lib.rs +++ b/crypto/txscript/src/lib.rs @@ -468,14 +468,14 @@ impl<'a, T: VerifiableTransaction, Reused: SigHashReusedValues> TxScriptEngine<' #[inline] fn check_schnorr_signature(&mut self, hash_type: SigHashType, key: &[u8], sig: &[u8]) -> Result { match self.script_source { - ScriptSource::TxInput { tx, idx: id, .. } => { + ScriptSource::TxInput { tx, idx, .. } => { if sig.len() != 64 { return Err(TxScriptError::SigLength(sig.len())); } Self::check_pub_key_encoding(key)?; let pk = secp256k1::XOnlyPublicKey::from_slice(key).map_err(TxScriptError::InvalidSignature)?; let sig = secp256k1::schnorr::Signature::from_slice(sig).map_err(TxScriptError::InvalidSignature)?; - let sig_hash = calc_schnorr_signature_hash(tx, id, hash_type, self.reused_values); + let sig_hash = calc_schnorr_signature_hash(tx, idx, hash_type, self.reused_values); let msg = secp256k1::Message::from_digest_slice(sig_hash.as_bytes().as_slice()).unwrap(); let sig_cache_key = SigCacheKey { signature: Signature::Secp256k1(sig), pub_key: PublicKey::Schnorr(pk), message: msg }; @@ -503,14 +503,14 @@ impl<'a, T: VerifiableTransaction, Reused: SigHashReusedValues> TxScriptEngine<' fn check_ecdsa_signature(&mut self, hash_type: SigHashType, key: &[u8], sig: &[u8]) -> Result { match self.script_source { - ScriptSource::TxInput { tx, idx: id, .. } => { + ScriptSource::TxInput { tx, idx, .. } => { if sig.len() != 64 { return Err(TxScriptError::SigLength(sig.len())); } Self::check_pub_key_encoding_ecdsa(key)?; let pk = secp256k1::PublicKey::from_slice(key).map_err(TxScriptError::InvalidSignature)?; let sig = secp256k1::ecdsa::Signature::from_compact(sig).map_err(TxScriptError::InvalidSignature)?; - let sig_hash = calc_ecdsa_signature_hash(tx, id, hash_type, self.reused_values); + let sig_hash = calc_ecdsa_signature_hash(tx, idx, hash_type, self.reused_values); let msg = secp256k1::Message::from_digest_slice(sig_hash.as_bytes().as_slice()).unwrap(); let sig_cache_key = SigCacheKey { signature: Signature::Ecdsa(sig), pub_key: PublicKey::Ecdsa(pk), message: msg }; From a5c607712e79bae0942758428274dda96dd0a123 Mon Sep 17 00:00:00 2001 From: max143672 Date: Sat, 9 Nov 2024 22:56:39 +0400 Subject: [PATCH 70/93] rename opcode --- crypto/txscript/src/opcodes/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crypto/txscript/src/opcodes/mod.rs b/crypto/txscript/src/opcodes/mod.rs index 0a4a6eb13..d98678955 100644 --- a/crypto/txscript/src/opcodes/mod.rs +++ b/crypto/txscript/src/opcodes/mod.rs @@ -918,7 +918,7 @@ opcode_list! { } } opcode OpOutpointTxId<0xba, 1>(self, vm) Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) - opcode OpOutpointOutputIdx<0xbb, 1>(self, vm) Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) + opcode OpOutpointIndex<0xbb, 1>(self, vm) Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) opcode OpTxInputScriptSig<0xbc, 1>(self, vm) Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) opcode OpTxInputSeq<0xbd, 1>(self, vm) Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) // UTXO related opcodes (following UtxoEntry struct field order) @@ -1171,7 +1171,7 @@ mod test { opcodes::OpTxGas::empty().expect("Should accept empty"), opcodes::OpTxPayload::empty().expect("Should accept empty"), opcodes::OpOutpointTxId::empty().expect("Should accept empty"), - opcodes::OpOutpointOutputIdx::empty().expect("Should accept empty"), + opcodes::OpOutpointIndex::empty().expect("Should accept empty"), opcodes::OpTxInputScriptSig::empty().expect("Should accept empty"), opcodes::OpTxInputSeq::empty().expect("Should accept empty"), opcodes::OpTxInputBlockDaaScore::empty().expect("Should accept empty"), From da7d81b8e292314b7d481135b292192181216cfc Mon Sep 17 00:00:00 2001 From: max143672 Date: Sun, 10 Nov 2024 10:14:44 +0400 Subject: [PATCH 71/93] make construction of TxScriptEngine from transaction input infallible --- .../transaction_validator_populated.rs | 4 +- crypto/txscript/examples/kip-10.rs | 42 +++++--------- crypto/txscript/src/lib.rs | 12 ++-- crypto/txscript/src/opcodes/mod.rs | 57 +++++++------------ crypto/txscript/src/standard/multisig.rs | 2 +- wallet/pskt/src/pskt.rs | 2 +- 6 files changed, 42 insertions(+), 77 deletions(-) diff --git a/consensus/src/processes/transaction_validator/transaction_validator_populated.rs b/consensus/src/processes/transaction_validator/transaction_validator_populated.rs index da0561522..2d0e6b8d2 100644 --- a/consensus/src/processes/transaction_validator/transaction_validator_populated.rs +++ b/consensus/src/processes/transaction_validator/transaction_validator_populated.rs @@ -197,7 +197,7 @@ pub fn check_scripts_sequential( let reused_values = SigHashReusedValuesUnsync::new(); for (i, (input, entry)) in tx.populated_inputs().enumerate() { TxScriptEngine::from_transaction_input(tx, input, i, entry, &reused_values, sig_cache, kip10_enabled) - .and_then(|mut e| e.execute()) + .execute() .map_err(|err| map_script_err(err, input))?; } Ok(()) @@ -212,7 +212,7 @@ pub fn check_scripts_par_iter( (0..tx.inputs().len()).into_par_iter().try_for_each(|idx| { let (input, utxo) = tx.populated_input(idx); TxScriptEngine::from_transaction_input(tx, input, idx, utxo, &reused_values, sig_cache, kip10_enabled) - .and_then(|mut e| e.execute()) + .execute() .map_err(|err| map_script_err(err, input)) }) } diff --git a/crypto/txscript/examples/kip-10.rs b/crypto/txscript/examples/kip-10.rs index 2d72b3325..5fa635619 100644 --- a/crypto/txscript/examples/kip-10.rs +++ b/crypto/txscript/examples/kip-10.rs @@ -127,8 +127,7 @@ fn threshold_scenario() -> ScriptBuilderResult<()> { let tx = tx.as_verifiable(); let mut vm = - TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true) - .expect("Script creation failed"); + TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true); assert_eq!(vm.execute(), Ok(())); println!("[STANDARD] Owner branch execution successful"); } @@ -139,8 +138,7 @@ fn threshold_scenario() -> ScriptBuilderResult<()> { tx.inputs[0].signature_script = ScriptBuilder::new().add_op(OpFalse)?.add_data(&script)?.drain(); let tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]); let mut vm = - TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true) - .expect("Script creation failed"); + TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true); assert_eq!(vm.execute(), Ok(())); println!("[STANDARD] Borrower branch execution successful"); } @@ -152,8 +150,7 @@ fn threshold_scenario() -> ScriptBuilderResult<()> { tx.outputs[0].value -= 1; let tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]); let mut vm = - TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true) - .expect("Script creation failed"); + TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true); assert_eq!(vm.execute(), Err(EvalFalse)); println!("[STANDARD] Borrower branch with threshold not reached failed as expected"); } @@ -302,8 +299,7 @@ fn threshold_scenario_limited_one_time() -> ScriptBuilderResult<()> { let tx = tx.as_verifiable(); let mut vm = - TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true) - .expect("Script creation failed"); + TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true); assert_eq!(vm.execute(), Ok(())); println!("[ONE-TIME] Owner branch execution successful"); } @@ -314,8 +310,7 @@ fn threshold_scenario_limited_one_time() -> ScriptBuilderResult<()> { tx.inputs[0].signature_script = ScriptBuilder::new().add_op(OpFalse)?.add_data(&script)?.drain(); let tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]); let mut vm = - TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true) - .expect("Script creation failed"); + TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true); assert_eq!(vm.execute(), Ok(())); println!("[ONE-TIME] Borrower branch execution successful"); } @@ -327,8 +322,7 @@ fn threshold_scenario_limited_one_time() -> ScriptBuilderResult<()> { tx.outputs[0].value -= 1; let tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]); let mut vm = - TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true) - .expect("Script creation failed"); + TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true); assert_eq!(vm.execute(), Err(EvalFalse)); println!("[ONE-TIME] Borrower branch with threshold not reached failed as expected"); } @@ -358,8 +352,7 @@ fn threshold_scenario_limited_one_time() -> ScriptBuilderResult<()> { &mut reused_values, &sig_cache, true, - ) - .expect("Script creation failed"); + ); assert_eq!(vm.execute(), Err(VerifyError)); println!("[ONE-TIME] Borrower branch with output going to wrong address failed as expected"); } @@ -469,8 +462,7 @@ fn threshold_scenario_limited_2_times() -> ScriptBuilderResult<()> { let tx = tx.as_verifiable(); let mut vm = - TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true) - .expect("Script creation failed"); + TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true); assert_eq!(vm.execute(), Ok(())); println!("[TWO-TIMES] Owner branch execution successful"); } @@ -481,8 +473,7 @@ fn threshold_scenario_limited_2_times() -> ScriptBuilderResult<()> { tx.inputs[0].signature_script = ScriptBuilder::new().add_op(OpFalse)?.add_data(&two_times_script)?.drain(); let tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]); let mut vm = - TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true) - .expect("Script creation failed"); + TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true); assert_eq!(vm.execute(), Ok(())); println!("[TWO-TIMES] Borrower branch (first borrowing) execution successful"); } @@ -494,8 +485,7 @@ fn threshold_scenario_limited_2_times() -> ScriptBuilderResult<()> { tx.outputs[0].value -= 1; let tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]); let mut vm = - TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true) - .expect("Script creation failed"); + TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true); assert_eq!(vm.execute(), Err(EvalFalse)); println!("[TWO-TIMES] Borrower branch with threshold not reached failed as expected"); } @@ -525,8 +515,7 @@ fn threshold_scenario_limited_2_times() -> ScriptBuilderResult<()> { &mut reused_values, &sig_cache, true, - ) - .expect("Script creation failed"); + ); assert_eq!(vm.execute(), Err(VerifyError)); println!("[TWO-TIMES] Borrower branch with output going to wrong address failed as expected"); } @@ -638,8 +627,7 @@ fn shared_secret_scenario() -> ScriptBuilderResult<()> { let tx = tx.as_verifiable(); let mut vm = - TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true) - .expect("Script creation failed"); + TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true); assert_eq!(vm.execute(), Ok(())); println!("[SHARED-SECRET] Owner branch execution successful"); } @@ -658,8 +646,7 @@ fn shared_secret_scenario() -> ScriptBuilderResult<()> { let tx = tx.as_verifiable(); let mut vm = - TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true) - .expect("Script creation failed"); + TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true); assert_eq!(vm.execute(), Ok(())); println!("[SHARED-SECRET] Borrower branch with correct shared secret execution successful"); } @@ -678,8 +665,7 @@ fn shared_secret_scenario() -> ScriptBuilderResult<()> { let tx = tx.as_verifiable(); let mut vm = - TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true) - .expect("Script creation failed"); + TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true); assert_eq!(vm.execute(), Err(VerifyError)); println!("[SHARED-SECRET] Borrower branch with incorrect secret failed as expected"); } diff --git a/crypto/txscript/src/lib.rs b/crypto/txscript/src/lib.rs index 4f80f7dde..2d016d6e9 100644 --- a/crypto/txscript/src/lib.rs +++ b/crypto/txscript/src/lib.rs @@ -192,13 +192,13 @@ impl<'a, T: VerifiableTransaction, Reused: SigHashReusedValues> TxScriptEngine<' reused_values: &'a Reused, sig_cache: &'a Cache, kip10_enabled: bool, - ) -> Result { + ) -> Self { let script_public_key = utxo_entry.script_public_key.script(); // The script_public_key in P2SH is just validating the hash on the OpMultiSig script // the user provides let is_p2sh = ScriptClass::is_pay_to_script_hash(script_public_key); assert!(input_idx < tx.tx().inputs.len()); - Ok(Self { + Self { dstack: Default::default(), astack: Default::default(), script_source: ScriptSource::TxInput { tx, input, idx: input_idx, utxo_entry, is_p2sh }, @@ -207,7 +207,7 @@ impl<'a, T: VerifiableTransaction, Reused: SigHashReusedValues> TxScriptEngine<' cond_stack: Default::default(), num_ops: 0, kip10_enabled, - }) + } } pub fn from_script( @@ -624,8 +624,7 @@ mod tests { &reused_values, &sig_cache, kip10_enabled, - ) - .expect("Script creation failed"); + ); assert_eq!(vm.execute(), test.expected_result); }); } @@ -1092,8 +1091,7 @@ mod bitcoind_tests { &reused_values, &sig_cache, kip10_enabled, - ) - .map_err(UnifiedError::TxScriptError)?; + ); vm.execute().map_err(UnifiedError::TxScriptError) } diff --git a/crypto/txscript/src/opcodes/mod.rs b/crypto/txscript/src/opcodes/mod.rs index d98678955..27eecdc16 100644 --- a/crypto/txscript/src/opcodes/mod.rs +++ b/crypto/txscript/src/opcodes/mod.rs @@ -2848,8 +2848,7 @@ mod test { ] { let mut tx = base_tx.clone(); tx.0.lock_time = tx_lock_time; - let mut vm = TxScriptEngine::from_transaction_input(&tx, &input, 0, &utxo_entry, &reused_values, &sig_cache, false) - .expect("Shouldn't fail"); + let mut vm = TxScriptEngine::from_transaction_input(&tx, &input, 0, &utxo_entry, &reused_values, &sig_cache, false); vm.dstack = vec![lock_time.clone()]; match code.execute(&mut vm) { // Message is based on the should_fail values @@ -2891,8 +2890,7 @@ mod test { ] { let mut input = base_input.clone(); input.sequence = tx_sequence; - let mut vm = TxScriptEngine::from_transaction_input(&tx, &input, 0, &utxo_entry, &reused_values, &sig_cache, false) - .expect("Shouldn't fail"); + let mut vm = TxScriptEngine::from_transaction_input(&tx, &input, 0, &utxo_entry, &reused_values, &sig_cache, false); vm.dstack = vec![sequence.clone()]; match code.execute(&mut vm) { // Message is based on the should_fail values @@ -3086,8 +3084,7 @@ mod test { &reused_values, &sig_cache, group.kip10_enabled, - ) - .unwrap(); + ); // Check input index opcode first let op_input_idx = opcodes::OpTxInputIndex::empty().expect("Should accept empty"); @@ -3355,8 +3352,7 @@ mod test { &reused_values, &sig_cache, kip10_enabled, - ) - .unwrap(); + ); let op_input_count = opcodes::OpTxInputCount::empty().expect("Should accept empty"); let op_output_count = opcodes::OpTxOutputCount::empty().expect("Should accept empty"); @@ -3436,8 +3432,7 @@ mod test { &reused_values, &sig_cache, true, - ) - .unwrap(); + ); assert_eq!(vm.execute(), Ok(())); } @@ -3461,8 +3456,7 @@ mod test { &reused_values, &sig_cache, true, - ) - .unwrap(); + ); assert_eq!(vm.execute(), Err(TxScriptError::EvalFalse)); } @@ -3502,8 +3496,7 @@ mod test { &reused_values, &sig_cache, true, - ) - .unwrap(); + ); assert_eq!(vm.execute(), Ok(())); } @@ -3529,8 +3522,7 @@ mod test { &reused_values, &sig_cache, true, - ) - .unwrap(); + ); assert_eq!(vm.execute(), Err(TxScriptError::EvalFalse)); } @@ -3552,8 +3544,7 @@ mod test { let tx = tx.as_verifiable(); let mut vm = - TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, tx.utxo(0).unwrap(), &reused_values, &sig_cache, true) - .unwrap(); + TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, tx.utxo(0).unwrap(), &reused_values, &sig_cache, true); // OpInputSpk should push input's SPK onto stack, making it non-empty assert_eq!(vm.execute(), Ok(())); @@ -3577,8 +3568,7 @@ mod test { let tx = tx.as_verifiable(); let mut vm = - TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, tx.utxo(0).unwrap(), &reused_values, &sig_cache, true) - .unwrap(); + TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, tx.utxo(0).unwrap(), &reused_values, &sig_cache, true); // Should succeed because the SPKs are different assert_eq!(vm.execute(), Ok(())); @@ -3603,8 +3593,7 @@ mod test { let tx = tx.as_verifiable(); let mut vm = - TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, tx.utxo(0).unwrap(), &reused_values, &sig_cache, true) - .unwrap(); + TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, tx.utxo(0).unwrap(), &reused_values, &sig_cache, true); // Should succeed because both SPKs are identical assert_eq!(vm.execute(), Ok(())); @@ -3649,8 +3638,7 @@ mod test { &reused_values, &sig_cache, true, - ) - .unwrap(); + ); assert_eq!(vm.execute(), Ok(())); } @@ -3676,8 +3664,7 @@ mod test { &reused_values, &sig_cache, true, - ) - .unwrap(); + ); assert_eq!(vm.execute(), Err(TxScriptError::EvalFalse)); } @@ -3710,8 +3697,7 @@ mod test { &reused_values, &sig_cache, true, - ) - .unwrap(); + ); assert_eq!(vm.execute(), Ok(())); } @@ -3735,8 +3721,7 @@ mod test { &reused_values, &sig_cache, true, - ) - .unwrap(); + ); // Should fail because script expects index 0 but we're at index 1 assert_eq!(vm.execute(), Err(TxScriptError::EvalFalse)); @@ -3782,8 +3767,7 @@ mod test { &reused_values, &sig_cache, true, - ) - .unwrap(); + ); assert_eq!(vm.execute(), Ok(())); } @@ -3802,8 +3786,7 @@ mod test { &reused_values, &sig_cache, true, - ) - .unwrap(); + ); assert_eq!(vm.execute(), Ok(())); } @@ -3826,8 +3809,7 @@ mod test { &reused_values, &sig_cache, true, - ) - .unwrap(); + ); assert_eq!(vm.execute(), Err(TxScriptError::EvalFalse)); } @@ -3849,8 +3831,7 @@ mod test { &reused_values, &sig_cache, true, - ) - .unwrap(); + ); assert_eq!(vm.execute(), Err(TxScriptError::EvalFalse)); } diff --git a/crypto/txscript/src/standard/multisig.rs b/crypto/txscript/src/standard/multisig.rs index b792cf4b5..6c51fd361 100644 --- a/crypto/txscript/src/standard/multisig.rs +++ b/crypto/txscript/src/standard/multisig.rs @@ -184,7 +184,7 @@ mod tests { let (input, entry) = tx.populated_inputs().next().unwrap(); let cache = Cache::new(10_000); - let mut engine = TxScriptEngine::from_transaction_input(&tx, input, 0, entry, &reused_values, &cache, false).unwrap(); + let mut engine = TxScriptEngine::from_transaction_input(&tx, input, 0, entry, &reused_values, &cache, false); assert_eq!(engine.execute().is_ok(), is_ok); } #[test] diff --git a/wallet/pskt/src/pskt.rs b/wallet/pskt/src/pskt.rs index 3db6a60e9..abae18f2d 100644 --- a/wallet/pskt/src/pskt.rs +++ b/wallet/pskt/src/pskt.rs @@ -436,7 +436,7 @@ impl PSKT { let reused_values = SigHashReusedValuesUnsync::new(); tx.populated_inputs().enumerate().try_for_each(|(idx, (input, entry))| { - TxScriptEngine::from_transaction_input(&tx, input, idx, entry, &reused_values, &cache, false)?.execute()?; + TxScriptEngine::from_transaction_input(&tx, input, idx, entry, &reused_values, &cache, false).execute()?; >::Ok(()) })?; } From 46441795fbf72e0c66a9efb9fd714ea2ac4618bb Mon Sep 17 00:00:00 2001 From: max143672 Date: Sun, 10 Nov 2024 10:50:54 +0400 Subject: [PATCH 72/93] style: format combinators chain --- crypto/txscript/src/opcodes/mod.rs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/crypto/txscript/src/opcodes/mod.rs b/crypto/txscript/src/opcodes/mod.rs index 27eecdc16..762eb2c46 100644 --- a/crypto/txscript/src/opcodes/mod.rs +++ b/crypto/txscript/src/opcodes/mod.rs @@ -927,7 +927,9 @@ opcode_list! { match vm.script_source { ScriptSource::TxInput{tx, ..} => { let [idx]: [i32; 1] = vm.dstack.pop_items()?; - let utxo = usize::try_from(idx).ok().and_then(|idx| tx.utxo(idx)).ok_or_else(|| TxScriptError::InvalidInputIndex(idx, tx.inputs().len()))?; + let utxo = usize::try_from(idx).ok() + .and_then(|idx| tx.utxo(idx)) + .ok_or_else(|| TxScriptError::InvalidInputIndex(idx, tx.inputs().len()))?; push_number(utxo.amount as i64, vm) }, _ => Err(TxScriptError::InvalidSource("OpInputAmount only applies to transaction inputs".to_string())) @@ -941,9 +943,9 @@ opcode_list! { match vm.script_source { ScriptSource::TxInput{tx, ..} => { let [idx]: [i32; 1] = vm.dstack.pop_items()?; - let utxo = usize::try_from(idx).ok(). - and_then(|idx| tx.utxo(idx)). - ok_or_else(|| TxScriptError::InvalidInputIndex(idx, tx.inputs().len()))?; + let utxo = usize::try_from(idx).ok() + .and_then(|idx| tx.utxo(idx)) + .ok_or_else(|| TxScriptError::InvalidInputIndex(idx, tx.inputs().len()))?; vm.dstack.push(utxo.script_public_key.to_bytes()); Ok(()) }, @@ -961,9 +963,9 @@ opcode_list! { match vm.script_source { ScriptSource::TxInput{tx, ..} => { let [idx]: [i32; 1] = vm.dstack.pop_items()?; - let output = usize::try_from(idx).ok(). - and_then(|idx| tx.outputs().get(idx)). - ok_or_else(|| TxScriptError::InvalidOutputIndex(idx, tx.inputs().len()))?; + let output = usize::try_from(idx).ok() + .and_then(|idx| tx.outputs().get(idx)) + .ok_or_else(|| TxScriptError::InvalidOutputIndex(idx, tx.inputs().len()))?; push_number(output.value as i64, vm) }, _ => Err(TxScriptError::InvalidSource("OpOutputAmount only applies to transaction inputs".to_string())) @@ -977,9 +979,9 @@ opcode_list! { match vm.script_source { ScriptSource::TxInput{tx, ..} => { let [idx]: [i32; 1] = vm.dstack.pop_items()?; - let output = usize::try_from(idx).ok(). - and_then(|idx| tx.outputs().get(idx)). - ok_or_else(|| TxScriptError::InvalidOutputIndex(idx, tx.inputs().len()))?; + let output = usize::try_from(idx).ok() + .and_then(|idx| tx.outputs().get(idx)) + .ok_or_else(|| TxScriptError::InvalidOutputIndex(idx, tx.inputs().len()))?; vm.dstack.push(output.script_public_key.to_bytes()); Ok(()) }, From 0fa920e4be78ea7b861ff4526442e3bfdfa4078c Mon Sep 17 00:00:00 2001 From: max143672 Date: Sun, 10 Nov 2024 15:57:50 +0400 Subject: [PATCH 73/93] add integration test covering activation of kip10 --- consensus/src/consensus/test_consensus.rs | 23 ++-- .../src/consensus_integration_tests.rs | 115 ++++++++++++++++-- 2 files changed, 117 insertions(+), 21 deletions(-) diff --git a/consensus/src/consensus/test_consensus.rs b/consensus/src/consensus/test_consensus.rs index 472bdbd83..3a511c411 100644 --- a/consensus/src/consensus/test_consensus.rs +++ b/consensus/src/consensus/test_consensus.rs @@ -13,11 +13,6 @@ use kaspa_hashes::Hash; use kaspa_notify::subscription::context::SubscriptionContext; use parking_lot::RwLock; -use kaspa_database::create_temp_db; -use kaspa_database::prelude::ConnBuilder; -use std::future::Future; -use std::{sync::Arc, thread::JoinHandle}; - use crate::pipeline::virtual_processor::test_block_builder::TestBlockBuilder; use crate::processes::window::WindowManager; use crate::{ @@ -35,6 +30,11 @@ use crate::{ pipeline::{body_processor::BlockBodyProcessor, virtual_processor::VirtualStateProcessor, ProcessingCounters}, test_helpers::header_from_precomputed_hash, }; +use kaspa_consensus_core::errors::block::RuleError; +use kaspa_database::create_temp_db; +use kaspa_database::prelude::ConnBuilder; +use std::future::Future; +use std::{sync::Arc, thread::JoinHandle}; use super::services::{DbDagTraversalManager, DbGhostdagManager, DbWindowManager}; use super::Consensus; @@ -143,10 +143,11 @@ impl TestConsensus { hash: Hash, parents: Vec, txs: Vec, - ) -> impl Future> { + ) -> Result>, RuleError> { let miner_data = MinerData::new(ScriptPublicKey::from_vec(0, vec![]), vec![]); - self.validate_and_insert_block(self.build_utxo_valid_block_with_parents(hash, parents, miner_data, txs).to_immutable()) - .virtual_state_task + Ok(self + .validate_and_insert_block(self.build_utxo_valid_block_with_parents(hash, parents, miner_data, txs)?.to_immutable()) + .virtual_state_task) } pub fn build_utxo_valid_block_with_parents( @@ -155,10 +156,10 @@ impl TestConsensus { parents: Vec, miner_data: MinerData, txs: Vec, - ) -> MutableBlock { - let mut template = self.block_builder.build_block_template_with_parents(parents, miner_data, txs).unwrap(); + ) -> Result { + let mut template = self.block_builder.build_block_template_with_parents(parents, miner_data, txs)?; template.block.header.hash = hash; - template.block + Ok(template.block) } pub fn build_block_with_parents_and_transactions( diff --git a/testing/integration/src/consensus_integration_tests.rs b/testing/integration/src/consensus_integration_tests.rs index f43560311..ff2582cc9 100644 --- a/testing/integration/src/consensus_integration_tests.rs +++ b/testing/integration/src/consensus_integration_tests.rs @@ -27,7 +27,7 @@ use kaspa_consensus_core::api::{BlockValidationFutures, ConsensusApi}; use kaspa_consensus_core::block::Block; use kaspa_consensus_core::blockhash::new_unique; use kaspa_consensus_core::blockstatus::BlockStatus; -use kaspa_consensus_core::constants::{BLOCK_VERSION, STORAGE_MASS_PARAMETER}; +use kaspa_consensus_core::constants::{BLOCK_VERSION, SOMPI_PER_KASPA, STORAGE_MASS_PARAMETER}; use kaspa_consensus_core::errors::block::{BlockProcessResult, RuleError}; use kaspa_consensus_core::header::Header; use kaspa_consensus_core::network::{NetworkId, NetworkType::Mainnet}; @@ -43,9 +43,11 @@ use kaspa_core::time::unix_now; use kaspa_database::utils::get_kaspa_tempdir; use kaspa_hashes::Hash; +use crate::common; use flate2::read::GzDecoder; use futures_util::future::try_join_all; use itertools::Itertools; +use kaspa_consensus_core::muhash::MuHashExtensions; use kaspa_core::core::Core; use kaspa_core::signals::Shutdown; use kaspa_core::task::runtime::AsyncRuntime; @@ -72,8 +74,6 @@ use std::{ str::{from_utf8, FromStr}, }; -use crate::common; - #[derive(Serialize, Deserialize, Debug)] struct JsonBlock { id: String, @@ -1664,12 +1664,12 @@ async fn selected_chain_test() { let consensus = TestConsensus::new(&config); let wait_handles = consensus.init(); - consensus.add_utxo_valid_block_with_parents(1.into(), vec![config.genesis.hash], vec![]).await.unwrap(); + consensus.add_utxo_valid_block_with_parents(1.into(), vec![config.genesis.hash], vec![]).unwrap().await.unwrap(); for i in 2..7 { let hash = i.into(); - consensus.add_utxo_valid_block_with_parents(hash, vec![(i - 1).into()], vec![]).await.unwrap(); + consensus.add_utxo_valid_block_with_parents(hash, vec![(i - 1).into()], vec![]).unwrap().await.unwrap(); } - consensus.add_utxo_valid_block_with_parents(7.into(), vec![1.into()], vec![]).await.unwrap(); // Adding a non chain block shouldn't affect the selected chain store. + consensus.add_utxo_valid_block_with_parents(7.into(), vec![1.into()], vec![]).unwrap().await.unwrap(); // Adding a non chain block shouldn't affect the selected chain store. assert_eq!(consensus.selected_chain_store.read().get_by_index(0).unwrap(), config.genesis.hash); for i in 1..7 { @@ -1677,10 +1677,10 @@ async fn selected_chain_test() { } assert!(consensus.selected_chain_store.read().get_by_index(7).is_err()); - consensus.add_utxo_valid_block_with_parents(8.into(), vec![config.genesis.hash], vec![]).await.unwrap(); + consensus.add_utxo_valid_block_with_parents(8.into(), vec![config.genesis.hash], vec![]).unwrap().await.unwrap(); for i in 9..15 { let hash = i.into(); - consensus.add_utxo_valid_block_with_parents(hash, vec![(i - 1).into()], vec![]).await.unwrap(); + consensus.add_utxo_valid_block_with_parents(hash, vec![(i - 1).into()], vec![]).unwrap().await.unwrap(); } assert_eq!(consensus.selected_chain_store.read().get_by_index(0).unwrap(), config.genesis.hash); @@ -1691,9 +1691,9 @@ async fn selected_chain_test() { // We now check a situation where there's a shorter selected chain (3 blocks) with more blue work for i in 15..23 { - consensus.add_utxo_valid_block_with_parents(i.into(), vec![config.genesis.hash], vec![]).await.unwrap(); + consensus.add_utxo_valid_block_with_parents(i.into(), vec![config.genesis.hash], vec![]).unwrap().await.unwrap(); } - consensus.add_utxo_valid_block_with_parents(23.into(), (15..23).map(|i| i.into()).collect_vec(), vec![]).await.unwrap(); + consensus.add_utxo_valid_block_with_parents(23.into(), (15..23).map(|i| i.into()).collect_vec(), vec![]).unwrap().await.unwrap(); assert_eq!(consensus.selected_chain_store.read().get_by_index(0).unwrap(), config.genesis.hash); assert_eq!(consensus.selected_chain_store.read().get_by_index(1).unwrap(), 22.into()); // We expect 23's selected parent to be 22 because of GHOSTDAG tie-breaking rules. @@ -1758,3 +1758,98 @@ async fn staging_consensus_test() { core.shutdown(); core.join(joins); } + +/// Tests the KIP-10 transaction introspection opcode activation by verifying that: +/// 1. Transactions using these opcodes are rejected before the activation DAA score +/// 2. The same transactions are accepted at and after the activation score +/// Uses OpInputSpk opcode as an example +#[tokio::test] +async fn run_kip10_activation_test() { + use kaspa_consensus_core::subnets::SUBNETWORK_ID_NATIVE; + use kaspa_txscript::opcodes::codes::{Op0, OpTxInputSpk}; + use kaspa_txscript::pay_to_script_hash_script; + use kaspa_txscript::script_builder::ScriptBuilder; + + // KIP-10 activates at DAA score 3 in this test + const KIP10_ACTIVATION_DAA_SCORE: u64 = 3; + + init_allocator_with_default_settings(); + + // Create P2SH script that attempts to use OpInputSpk - this will be our test subject + // The script should fail before KIP-10 activation and succeed after + let redeem_script = ScriptBuilder::new() + .add_op(Op0).unwrap() // Push 0 for input index + .add_op(OpTxInputSpk).unwrap() // Get the input's script pubkey + .drain(); + let spk = pay_to_script_hash_script(&redeem_script); + + // Set up initial UTXO with our test script + let initial_utxo_collection = [( + TransactionOutpoint::new(1.into(), 0), + UtxoEntry { amount: SOMPI_PER_KASPA, script_public_key: spk.clone(), block_daa_score: 0, is_coinbase: false }, + )]; + + // Initialize consensus with KIP-10 activation point + let config = ConfigBuilder::new(DEVNET_PARAMS) + .skip_proof_of_work() + .apply_args(|cfg| { + let mut genesis_multiset = MuHash::new(); + initial_utxo_collection.iter().for_each(|(outpoint, utxo)| { + genesis_multiset.add_utxo(outpoint, utxo); + }); + cfg.params.genesis.utxo_commitment = genesis_multiset.finalize(); + let genesis_header: Header = (&cfg.params.genesis).into(); + cfg.params.genesis.hash = genesis_header.hash; + }) + .edit_consensus_params(|p| { + p.kip10_activation_daa_score = ForkActivation::new(KIP10_ACTIVATION_DAA_SCORE); + }) + .build(); + + let consensus = TestConsensus::new(&config); + let mut genesis_multiset = MuHash::new(); + consensus.append_imported_pruning_point_utxos(&initial_utxo_collection, &mut genesis_multiset); + consensus.import_pruning_point_utxo_set(config.genesis.hash, genesis_multiset).unwrap(); + consensus.init(); + + // Build blockchain up to one block before activation + let mut daa = 0; + for _ in 0..KIP10_ACTIVATION_DAA_SCORE - 1 { + let parent = if daa == 0 { config.genesis.hash } else { daa.into() }; + consensus.add_utxo_valid_block_with_parents((daa + 1).into(), vec![parent], vec![]).unwrap().await.unwrap(); + daa += 1; + } + assert_eq!(consensus.get_virtual_daa_score(), daa); + + // Create transaction that attempts to use the KIP-10 opcode + let mut spending_tx = Transaction::new( + 0, + vec![TransactionInput::new( + initial_utxo_collection[0].0, + ScriptBuilder::new().add_data(&redeem_script).unwrap().drain(), + 0, + 0, + )], + vec![TransactionOutput::new(initial_utxo_collection[0].1.amount - 5000, spk)], + 0, + SUBNETWORK_ID_NATIVE, + 0, + vec![], + ); + + // Test 1: Verify transaction is rejected before activation + assert!(matches!( + consensus.add_utxo_valid_block_with_parents((daa + 1).into(), vec![daa.into()], vec![spending_tx.clone()]), + Err(RuleError::InvalidTransactionsInNewBlock(_)) + )); + + // Add one more block to reach activation score + consensus.add_utxo_valid_block_with_parents((daa + 1).into(), vec![daa.into()], vec![]).unwrap().await.unwrap(); + daa += 1; + + // Test 2: Verify the same transaction is accepted after activation + _ = consensus.add_utxo_valid_block_with_parents((daa + 1).into(), vec![daa.into()], vec![spending_tx.clone()]).unwrap().await; + spending_tx.finalize(); + let tx_id = spending_tx.id(); + assert!(consensus.lkg_virtual_state.load().accepted_tx_ids.contains(&tx_id)); +} From 83ad7617d47333c2fb011ad4002b045a8315997e Mon Sep 17 00:00:00 2001 From: max143672 Date: Sun, 10 Nov 2024 15:59:24 +0400 Subject: [PATCH 74/93] rename kip10_activation_daa_score to kip10_activation --- consensus/core/src/config/params.rs | 12 ++++++------ consensus/src/consensus/services.rs | 2 +- consensus/src/processes/transaction_validator/mod.rs | 8 ++++---- .../transaction_validator_populated.rs | 2 +- .../integration/src/consensus_integration_tests.rs | 4 ++-- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/consensus/core/src/config/params.rs b/consensus/core/src/config/params.rs index 001f2c6e4..8cab11c92 100644 --- a/consensus/core/src/config/params.rs +++ b/consensus/core/src/config/params.rs @@ -120,7 +120,7 @@ pub struct Params { /// - OpTxInputSpk (0xbf): Get input script public key /// - OpTxOutputAmount (0xc2): Get output amount /// - OpTxOutputSpk (0xc3): Get output script public key - pub kip10_activation_daa_score: ForkActivation, + pub kip10_activation: ForkActivation, /// DAA score after which the pre-deflationary period switches to the deflationary period pub deflationary_phase_daa_score: u64, @@ -392,7 +392,7 @@ pub const MAINNET_PARAMS: Params = Params { storage_mass_parameter: STORAGE_MASS_PARAMETER, storage_mass_activation: ForkActivation::never(), - kip10_activation_daa_score: ForkActivation::never(), + kip10_activation: ForkActivation::never(), // deflationary_phase_daa_score is the DAA score after which the pre-deflationary period // switches to the deflationary period. This number is calculated as follows: @@ -456,7 +456,7 @@ pub const TESTNET_PARAMS: Params = Params { storage_mass_parameter: STORAGE_MASS_PARAMETER, storage_mass_activation: ForkActivation::never(), - kip10_activation_daa_score: ForkActivation::never(), + kip10_activation: ForkActivation::never(), // deflationary_phase_daa_score is the DAA score after which the pre-deflationary period // switches to the deflationary period. This number is calculated as follows: // We define a year as 365.25 days @@ -526,7 +526,7 @@ pub const TESTNET11_PARAMS: Params = Params { storage_mass_parameter: STORAGE_MASS_PARAMETER, storage_mass_activation: ForkActivation::always(), - kip10_activation_daa_score: ForkActivation::never(), + kip10_activation: ForkActivation::never(), skip_proof_of_work: false, max_block_level: 250, @@ -580,7 +580,7 @@ pub const SIMNET_PARAMS: Params = Params { storage_mass_parameter: STORAGE_MASS_PARAMETER, storage_mass_activation: ForkActivation::always(), - kip10_activation_daa_score: ForkActivation::never(), + kip10_activation: ForkActivation::never(), skip_proof_of_work: true, // For simnet only, PoW can be simulated by default max_block_level: 250, @@ -627,7 +627,7 @@ pub const DEVNET_PARAMS: Params = Params { storage_mass_parameter: STORAGE_MASS_PARAMETER, storage_mass_activation: ForkActivation::never(), - kip10_activation_daa_score: ForkActivation::never(), + kip10_activation: ForkActivation::never(), // deflationary_phase_daa_score is the DAA score after which the pre-deflationary period // switches to the deflationary period. This number is calculated as follows: diff --git a/consensus/src/consensus/services.rs b/consensus/src/consensus/services.rs index 5aed1c301..6a4d7c663 100644 --- a/consensus/src/consensus/services.rs +++ b/consensus/src/consensus/services.rs @@ -147,7 +147,7 @@ impl ConsensusServices { tx_script_cache_counters, mass_calculator.clone(), params.storage_mass_activation, - params.kip10_activation_daa_score, + params.kip10_activation, ); let pruning_point_manager = PruningPointManager::new( diff --git a/consensus/src/processes/transaction_validator/mod.rs b/consensus/src/processes/transaction_validator/mod.rs index e471054e4..7d007a335 100644 --- a/consensus/src/processes/transaction_validator/mod.rs +++ b/consensus/src/processes/transaction_validator/mod.rs @@ -29,7 +29,7 @@ pub struct TransactionValidator { /// Storage mass hardfork DAA score storage_mass_activation: ForkActivation, /// KIP-10 hardfork DAA score - kip10_activation_daa_score: ForkActivation, + kip10_activation: ForkActivation, } impl TransactionValidator { @@ -45,7 +45,7 @@ impl TransactionValidator { counters: Arc, mass_calculator: MassCalculator, storage_mass_activation: ForkActivation, - kip10_activation_daa_score: ForkActivation, + kip10_activation: ForkActivation, ) -> Self { Self { max_tx_inputs, @@ -58,7 +58,7 @@ impl TransactionValidator { sig_cache: Cache::with_counters(10_000, counters), mass_calculator, storage_mass_activation, - kip10_activation_daa_score, + kip10_activation, } } @@ -83,7 +83,7 @@ impl TransactionValidator { sig_cache: Cache::with_counters(10_000, counters), mass_calculator: MassCalculator::new(0, 0, 0, 0), storage_mass_activation: ForkActivation::never(), - kip10_activation_daa_score: ForkActivation::never(), + kip10_activation: ForkActivation::never(), } } } diff --git a/consensus/src/processes/transaction_validator/transaction_validator_populated.rs b/consensus/src/processes/transaction_validator/transaction_validator_populated.rs index 2d0e6b8d2..cff13d9fb 100644 --- a/consensus/src/processes/transaction_validator/transaction_validator_populated.rs +++ b/consensus/src/processes/transaction_validator/transaction_validator_populated.rs @@ -173,7 +173,7 @@ impl TransactionValidator { } pub fn check_scripts(&self, tx: &(impl VerifiableTransaction + Sync), pov_daa_score: u64) -> TxResult<()> { - check_scripts(&self.sig_cache, tx, self.kip10_activation_daa_score.is_active(pov_daa_score)) + check_scripts(&self.sig_cache, tx, self.kip10_activation.is_active(pov_daa_score)) } } diff --git a/testing/integration/src/consensus_integration_tests.rs b/testing/integration/src/consensus_integration_tests.rs index ff2582cc9..42b487ab8 100644 --- a/testing/integration/src/consensus_integration_tests.rs +++ b/testing/integration/src/consensus_integration_tests.rs @@ -833,7 +833,7 @@ impl KaspadGoParams { max_block_mass: self.MaxBlockMass, storage_mass_parameter: STORAGE_MASS_PARAMETER, storage_mass_activation: ForkActivation::never(), - kip10_activation_daa_score: ForkActivation::never(), + kip10_activation: ForkActivation::never(), deflationary_phase_daa_score: self.DeflationaryPhaseDaaScore, pre_deflationary_phase_base_subsidy: self.PreDeflationaryPhaseBaseSubsidy, coinbase_maturity: MAINNET_PARAMS.coinbase_maturity, @@ -1802,7 +1802,7 @@ async fn run_kip10_activation_test() { cfg.params.genesis.hash = genesis_header.hash; }) .edit_consensus_params(|p| { - p.kip10_activation_daa_score = ForkActivation::new(KIP10_ACTIVATION_DAA_SCORE); + p.kip10_activation = ForkActivation::new(KIP10_ACTIVATION_DAA_SCORE); }) .build(); From 07b80dd057e9500f14d9ff6c7fe0d8f0bec32c47 Mon Sep 17 00:00:00 2001 From: Maxim <59533214+biryukovmaxim@users.noreply.github.com> Date: Sun, 10 Nov 2024 15:01:03 +0300 Subject: [PATCH 75/93] Update crypto/txscript/src/lib.rs refactor vector filling Co-authored-by: Michael Sutton --- crypto/txscript/src/lib.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/crypto/txscript/src/lib.rs b/crypto/txscript/src/lib.rs index 2d016d6e9..f2fb7048b 100644 --- a/crypto/txscript/src/lib.rs +++ b/crypto/txscript/src/lib.rs @@ -542,12 +542,7 @@ trait SpkEncoding { impl SpkEncoding for ScriptPublicKey { fn to_bytes(&self) -> Vec { - let version = self.version.to_be_bytes(); - let script = self.script(); - let mut v = Vec::with_capacity(version.len() + script.len()); - v.extend_from_slice(&version); - v.extend_from_slice(script); - v + self.version.to_be_bytes().into_iter().chain(self.script().iter().copied()).collect() } } From b50b7b3e873c643e31ac5891ea7bd15e615e8795 Mon Sep 17 00:00:00 2001 From: max143672 Date: Sun, 10 Nov 2024 16:30:14 +0400 Subject: [PATCH 76/93] rework assert --- .../src/consensus_integration_tests.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/testing/integration/src/consensus_integration_tests.rs b/testing/integration/src/consensus_integration_tests.rs index 42b487ab8..2c3b2299a 100644 --- a/testing/integration/src/consensus_integration_tests.rs +++ b/testing/integration/src/consensus_integration_tests.rs @@ -47,6 +47,7 @@ use crate::common; use flate2::read::GzDecoder; use futures_util::future::try_join_all; use itertools::Itertools; +use kaspa_consensus_core::errors::tx::TxRuleError; use kaspa_consensus_core::muhash::MuHashExtensions; use kaspa_core::core::Core; use kaspa_core::signals::Shutdown; @@ -59,6 +60,7 @@ use kaspa_math::Uint256; use kaspa_muhash::MuHash; use kaspa_notify::subscription::context::SubscriptionContext; use kaspa_txscript::caches::TxScriptCacheCounters; +use kaspa_txscript_errors::TxScriptError; use kaspa_utxoindex::api::{UtxoIndexApi, UtxoIndexProxy}; use kaspa_utxoindex::UtxoIndex; use serde::{Deserialize, Serialize}; @@ -1836,11 +1838,18 @@ async fn run_kip10_activation_test() { 0, vec![], ); + spending_tx.finalize(); + let tx_id = spending_tx.id(); + let result = consensus.add_utxo_valid_block_with_parents((daa + 1).into(), vec![daa.into()], vec![spending_tx.clone()]); // Test 1: Verify transaction is rejected before activation assert!(matches!( - consensus.add_utxo_valid_block_with_parents((daa + 1).into(), vec![daa.into()], vec![spending_tx.clone()]), - Err(RuleError::InvalidTransactionsInNewBlock(_)) + result, + Err(RuleError::InvalidTransactionsInNewBlock(map)) if matches!( + map.get(&tx_id).unwrap(), + TxRuleError::SignatureInvalid(TxScriptError::InvalidOpcode(s)) + if s.contains(&format!("{:#01x}", OpTxInputSpk)) + ) )); // Add one more block to reach activation score @@ -1849,7 +1858,6 @@ async fn run_kip10_activation_test() { // Test 2: Verify the same transaction is accepted after activation _ = consensus.add_utxo_valid_block_with_parents((daa + 1).into(), vec![daa.into()], vec![spending_tx.clone()]).unwrap().await; - spending_tx.finalize(); - let tx_id = spending_tx.id(); + assert!(consensus.lkg_virtual_state.load().accepted_tx_ids.contains(&tx_id)); } From 7fc9828cbb4df842ef659773391b53696652212d Mon Sep 17 00:00:00 2001 From: max143672 Date: Sun, 10 Nov 2024 20:31:53 +0400 Subject: [PATCH 77/93] verify that block is disqualified in case of it has tx which requires block that contains the tx with kip10 opcode is accepted after daa score has being reached --- .../src/consensus_integration_tests.rs | 53 ++++++++++--------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/testing/integration/src/consensus_integration_tests.rs b/testing/integration/src/consensus_integration_tests.rs index 2c3b2299a..a845efa99 100644 --- a/testing/integration/src/consensus_integration_tests.rs +++ b/testing/integration/src/consensus_integration_tests.rs @@ -47,7 +47,8 @@ use crate::common; use flate2::read::GzDecoder; use futures_util::future::try_join_all; use itertools::Itertools; -use kaspa_consensus_core::errors::tx::TxRuleError; +use kaspa_consensus_core::coinbase::MinerData; +use kaspa_consensus_core::merkle::calc_hash_merkle_root; use kaspa_consensus_core::muhash::MuHashExtensions; use kaspa_core::core::Core; use kaspa_core::signals::Shutdown; @@ -60,7 +61,6 @@ use kaspa_math::Uint256; use kaspa_muhash::MuHash; use kaspa_notify::subscription::context::SubscriptionContext; use kaspa_txscript::caches::TxScriptCacheCounters; -use kaspa_txscript_errors::TxScriptError; use kaspa_utxoindex::api::{UtxoIndexApi, UtxoIndexProxy}; use kaspa_utxoindex::UtxoIndex; use serde::{Deserialize, Serialize}; @@ -1815,13 +1815,13 @@ async fn run_kip10_activation_test() { consensus.init(); // Build blockchain up to one block before activation - let mut daa = 0; + let mut index = 0; for _ in 0..KIP10_ACTIVATION_DAA_SCORE - 1 { - let parent = if daa == 0 { config.genesis.hash } else { daa.into() }; - consensus.add_utxo_valid_block_with_parents((daa + 1).into(), vec![parent], vec![]).unwrap().await.unwrap(); - daa += 1; + let parent = if index == 0 { config.genesis.hash } else { index.into() }; + consensus.add_utxo_valid_block_with_parents((index + 1).into(), vec![parent], vec![]).unwrap().await.unwrap(); + index += 1; } - assert_eq!(consensus.get_virtual_daa_score(), daa); + assert_eq!(consensus.get_virtual_daa_score(), index); // Create transaction that attempts to use the KIP-10 opcode let mut spending_tx = Transaction::new( @@ -1840,24 +1840,29 @@ async fn run_kip10_activation_test() { ); spending_tx.finalize(); let tx_id = spending_tx.id(); - - let result = consensus.add_utxo_valid_block_with_parents((daa + 1).into(), vec![daa.into()], vec![spending_tx.clone()]); - // Test 1: Verify transaction is rejected before activation - assert!(matches!( - result, - Err(RuleError::InvalidTransactionsInNewBlock(map)) if matches!( - map.get(&tx_id).unwrap(), - TxRuleError::SignatureInvalid(TxScriptError::InvalidOpcode(s)) - if s.contains(&format!("{:#01x}", OpTxInputSpk)) - ) - )); - - // Add one more block to reach activation score - consensus.add_utxo_valid_block_with_parents((daa + 1).into(), vec![daa.into()], vec![]).unwrap().await.unwrap(); - daa += 1; + // Test 1: Build empty block, then manually insert invalid tx and verify consensus rejects it + { + let miner_data = MinerData::new(ScriptPublicKey::from_vec(0, vec![]), vec![]); + + // First build block without transactions + let mut block = + consensus.build_utxo_valid_block_with_parents((index + 1).into(), vec![index.into()], miner_data.clone(), vec![]).unwrap(); + + // Insert our test transaction and recalculate block hashes + block.transactions.push(spending_tx.clone()); + block.header.hash_merkle_root = calc_hash_merkle_root(block.transactions.iter(), false); + let block_status = consensus.validate_and_insert_block(block.to_immutable()).virtual_state_task.await; + assert!(matches!(block_status, Ok(BlockStatus::StatusDisqualifiedFromChain))); + assert_eq!(consensus.lkg_virtual_state.load().daa_score, 2); + index += 1; + } + // // Add one more block to reach activation score + consensus.add_utxo_valid_block_with_parents((index + 1).into(), vec![(index - 1).into()], vec![]).unwrap().await.unwrap(); + index += 1; // Test 2: Verify the same transaction is accepted after activation - _ = consensus.add_utxo_valid_block_with_parents((daa + 1).into(), vec![daa.into()], vec![spending_tx.clone()]).unwrap().await; - + let status = + consensus.add_utxo_valid_block_with_parents((index + 1).into(), vec![index.into()], vec![spending_tx.clone()]).unwrap().await; + assert!(matches!(status, Ok(BlockStatus::StatusUTXOValid))); assert!(consensus.lkg_virtual_state.load().accepted_tx_ids.contains(&tx_id)); } From 4d79485a71e83b9f196c5649a1539f4bb3ad6f2c Mon Sep 17 00:00:00 2001 From: max143672 Date: Sun, 10 Nov 2024 21:02:15 +0400 Subject: [PATCH 78/93] revert changer to infallible api --- consensus/src/consensus/test_consensus.rs | 14 +++++------ .../src/consensus_integration_tests.rs | 23 +++++++++---------- 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/consensus/src/consensus/test_consensus.rs b/consensus/src/consensus/test_consensus.rs index 3a511c411..309674b3c 100644 --- a/consensus/src/consensus/test_consensus.rs +++ b/consensus/src/consensus/test_consensus.rs @@ -30,7 +30,6 @@ use crate::{ pipeline::{body_processor::BlockBodyProcessor, virtual_processor::VirtualStateProcessor, ProcessingCounters}, test_helpers::header_from_precomputed_hash, }; -use kaspa_consensus_core::errors::block::RuleError; use kaspa_database::create_temp_db; use kaspa_database::prelude::ConnBuilder; use std::future::Future; @@ -143,11 +142,10 @@ impl TestConsensus { hash: Hash, parents: Vec, txs: Vec, - ) -> Result>, RuleError> { + ) -> impl Future> { let miner_data = MinerData::new(ScriptPublicKey::from_vec(0, vec![]), vec![]); - Ok(self - .validate_and_insert_block(self.build_utxo_valid_block_with_parents(hash, parents, miner_data, txs)?.to_immutable()) - .virtual_state_task) + self.validate_and_insert_block(self.build_utxo_valid_block_with_parents(hash, parents, miner_data, txs).to_immutable()) + .virtual_state_task } pub fn build_utxo_valid_block_with_parents( @@ -156,10 +154,10 @@ impl TestConsensus { parents: Vec, miner_data: MinerData, txs: Vec, - ) -> Result { - let mut template = self.block_builder.build_block_template_with_parents(parents, miner_data, txs)?; + ) -> MutableBlock { + let mut template = self.block_builder.build_block_template_with_parents(parents, miner_data, txs).unwrap(); template.block.header.hash = hash; - Ok(template.block) + template.block } pub fn build_block_with_parents_and_transactions( diff --git a/testing/integration/src/consensus_integration_tests.rs b/testing/integration/src/consensus_integration_tests.rs index a845efa99..3db614dc4 100644 --- a/testing/integration/src/consensus_integration_tests.rs +++ b/testing/integration/src/consensus_integration_tests.rs @@ -1666,12 +1666,12 @@ async fn selected_chain_test() { let consensus = TestConsensus::new(&config); let wait_handles = consensus.init(); - consensus.add_utxo_valid_block_with_parents(1.into(), vec![config.genesis.hash], vec![]).unwrap().await.unwrap(); + consensus.add_utxo_valid_block_with_parents(1.into(), vec![config.genesis.hash], vec![]).await.unwrap(); for i in 2..7 { let hash = i.into(); - consensus.add_utxo_valid_block_with_parents(hash, vec![(i - 1).into()], vec![]).unwrap().await.unwrap(); + consensus.add_utxo_valid_block_with_parents(hash, vec![(i - 1).into()], vec![]).await.unwrap(); } - consensus.add_utxo_valid_block_with_parents(7.into(), vec![1.into()], vec![]).unwrap().await.unwrap(); // Adding a non chain block shouldn't affect the selected chain store. + consensus.add_utxo_valid_block_with_parents(7.into(), vec![1.into()], vec![]).await.unwrap(); // Adding a non chain block shouldn't affect the selected chain store. assert_eq!(consensus.selected_chain_store.read().get_by_index(0).unwrap(), config.genesis.hash); for i in 1..7 { @@ -1679,10 +1679,10 @@ async fn selected_chain_test() { } assert!(consensus.selected_chain_store.read().get_by_index(7).is_err()); - consensus.add_utxo_valid_block_with_parents(8.into(), vec![config.genesis.hash], vec![]).unwrap().await.unwrap(); + consensus.add_utxo_valid_block_with_parents(8.into(), vec![config.genesis.hash], vec![]).await.unwrap(); for i in 9..15 { let hash = i.into(); - consensus.add_utxo_valid_block_with_parents(hash, vec![(i - 1).into()], vec![]).unwrap().await.unwrap(); + consensus.add_utxo_valid_block_with_parents(hash, vec![(i - 1).into()], vec![]).await.unwrap(); } assert_eq!(consensus.selected_chain_store.read().get_by_index(0).unwrap(), config.genesis.hash); @@ -1693,9 +1693,9 @@ async fn selected_chain_test() { // We now check a situation where there's a shorter selected chain (3 blocks) with more blue work for i in 15..23 { - consensus.add_utxo_valid_block_with_parents(i.into(), vec![config.genesis.hash], vec![]).unwrap().await.unwrap(); + consensus.add_utxo_valid_block_with_parents(i.into(), vec![config.genesis.hash], vec![]).await.unwrap(); } - consensus.add_utxo_valid_block_with_parents(23.into(), (15..23).map(|i| i.into()).collect_vec(), vec![]).unwrap().await.unwrap(); + consensus.add_utxo_valid_block_with_parents(23.into(), (15..23).map(|i| i.into()).collect_vec(), vec![]).await.unwrap(); assert_eq!(consensus.selected_chain_store.read().get_by_index(0).unwrap(), config.genesis.hash); assert_eq!(consensus.selected_chain_store.read().get_by_index(1).unwrap(), 22.into()); // We expect 23's selected parent to be 22 because of GHOSTDAG tie-breaking rules. @@ -1818,7 +1818,7 @@ async fn run_kip10_activation_test() { let mut index = 0; for _ in 0..KIP10_ACTIVATION_DAA_SCORE - 1 { let parent = if index == 0 { config.genesis.hash } else { index.into() }; - consensus.add_utxo_valid_block_with_parents((index + 1).into(), vec![parent], vec![]).unwrap().await.unwrap(); + consensus.add_utxo_valid_block_with_parents((index + 1).into(), vec![parent], vec![]).await.unwrap(); index += 1; } assert_eq!(consensus.get_virtual_daa_score(), index); @@ -1846,7 +1846,7 @@ async fn run_kip10_activation_test() { // First build block without transactions let mut block = - consensus.build_utxo_valid_block_with_parents((index + 1).into(), vec![index.into()], miner_data.clone(), vec![]).unwrap(); + consensus.build_utxo_valid_block_with_parents((index + 1).into(), vec![index.into()], miner_data.clone(), vec![]); // Insert our test transaction and recalculate block hashes block.transactions.push(spending_tx.clone()); @@ -1857,12 +1857,11 @@ async fn run_kip10_activation_test() { index += 1; } // // Add one more block to reach activation score - consensus.add_utxo_valid_block_with_parents((index + 1).into(), vec![(index - 1).into()], vec![]).unwrap().await.unwrap(); + consensus.add_utxo_valid_block_with_parents((index + 1).into(), vec![(index - 1).into()], vec![]).await.unwrap(); index += 1; // Test 2: Verify the same transaction is accepted after activation - let status = - consensus.add_utxo_valid_block_with_parents((index + 1).into(), vec![index.into()], vec![spending_tx.clone()]).unwrap().await; + let status = consensus.add_utxo_valid_block_with_parents((index + 1).into(), vec![index.into()], vec![spending_tx.clone()]).await; assert!(matches!(status, Ok(BlockStatus::StatusUTXOValid))); assert!(consensus.lkg_virtual_state.load().accepted_tx_ids.contains(&tx_id)); } From 4fe40f8cbf2c23220e5d3b05a355a2e92ec069a3 Mon Sep 17 00:00:00 2001 From: max143672 Date: Sun, 10 Nov 2024 21:09:23 +0400 Subject: [PATCH 79/93] add doc comments --- consensus/src/consensus/test_consensus.rs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/consensus/src/consensus/test_consensus.rs b/consensus/src/consensus/test_consensus.rs index 309674b3c..87790d093 100644 --- a/consensus/src/consensus/test_consensus.rs +++ b/consensus/src/consensus/test_consensus.rs @@ -13,6 +13,8 @@ use kaspa_hashes::Hash; use kaspa_notify::subscription::context::SubscriptionContext; use parking_lot::RwLock; +use super::services::{DbDagTraversalManager, DbGhostdagManager, DbWindowManager}; +use super::Consensus; use crate::pipeline::virtual_processor::test_block_builder::TestBlockBuilder; use crate::processes::window::WindowManager; use crate::{ @@ -35,9 +37,6 @@ use kaspa_database::prelude::ConnBuilder; use std::future::Future; use std::{sync::Arc, thread::JoinHandle}; -use super::services::{DbDagTraversalManager, DbGhostdagManager, DbWindowManager}; -use super::Consensus; - pub struct TestConsensus { params: Params, consensus: Arc, @@ -137,6 +136,12 @@ impl TestConsensus { self.validate_and_insert_block(self.build_block_with_parents(hash, parents).to_immutable()).virtual_state_task } + /// Adds a valid block with the given transactions and parents to the consensus. + /// + /// # Panics + /// + /// Panics if block builder validation rules are violated. + /// See `kaspa_consensus_core::errors::block::RuleError` for the complete list of possible validation rules. pub fn add_utxo_valid_block_with_parents( &self, hash: Hash, @@ -148,6 +153,12 @@ impl TestConsensus { .virtual_state_task } + /// Builds a valid block with the given transactions, parents, and miner data. + /// + /// # Panics + /// + /// Panics if block builder validation rules are violated. + /// See `kaspa_consensus_core::errors::block::RuleError` for the complete list of possible validation rules. pub fn build_utxo_valid_block_with_parents( &self, hash: Hash, From 898a56a6f291b6304e879afef6324dfc1b6241a6 Mon Sep 17 00:00:00 2001 From: Maxim <59533214+biryukovmaxim@users.noreply.github.com> Date: Mon, 11 Nov 2024 09:39:55 +0400 Subject: [PATCH 80/93] Update crypto/txscript/src/opcodes/mod.rs Fallible conversion of output amount (usize -> i64) Co-authored-by: Michael Sutton --- crypto/txscript/src/opcodes/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto/txscript/src/opcodes/mod.rs b/crypto/txscript/src/opcodes/mod.rs index 762eb2c46..0db6ebe2c 100644 --- a/crypto/txscript/src/opcodes/mod.rs +++ b/crypto/txscript/src/opcodes/mod.rs @@ -966,7 +966,7 @@ opcode_list! { let output = usize::try_from(idx).ok() .and_then(|idx| tx.outputs().get(idx)) .ok_or_else(|| TxScriptError::InvalidOutputIndex(idx, tx.inputs().len()))?; - push_number(output.value as i64, vm) + push_number(output.value.try_into().map_err(|e: TryFromIntError| TxScriptError::NumberTooBig(e.to_string()))?, vm) }, _ => Err(TxScriptError::InvalidSource("OpOutputAmount only applies to transaction inputs".to_string())) } From 3af28ed6eab2ad217cbec604abb928042a7d90f1 Mon Sep 17 00:00:00 2001 From: Maxim <59533214+biryukovmaxim@users.noreply.github.com> Date: Mon, 11 Nov 2024 09:40:15 +0400 Subject: [PATCH 81/93] Update crypto/txscript/src/opcodes/mod.rs Fallible conversion of input amount (usize -> i64) Co-authored-by: Michael Sutton --- crypto/txscript/src/opcodes/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto/txscript/src/opcodes/mod.rs b/crypto/txscript/src/opcodes/mod.rs index 0db6ebe2c..67db364ae 100644 --- a/crypto/txscript/src/opcodes/mod.rs +++ b/crypto/txscript/src/opcodes/mod.rs @@ -930,7 +930,7 @@ opcode_list! { let utxo = usize::try_from(idx).ok() .and_then(|idx| tx.utxo(idx)) .ok_or_else(|| TxScriptError::InvalidInputIndex(idx, tx.inputs().len()))?; - push_number(utxo.amount as i64, vm) + push_number(utxo.amount.try_into().map_err(|e: TryFromIntError| TxScriptError::NumberTooBig(e.to_string()))?, vm) }, _ => Err(TxScriptError::InvalidSource("OpInputAmount only applies to transaction inputs".to_string())) } From 88399daec79d529a05342b78ee450f3e1f960bf9 Mon Sep 17 00:00:00 2001 From: max143672 Date: Mon, 11 Nov 2024 10:19:39 +0400 Subject: [PATCH 82/93] add required import --- crypto/txscript/src/opcodes/mod.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crypto/txscript/src/opcodes/mod.rs b/crypto/txscript/src/opcodes/mod.rs index 67db364ae..3da4e8790 100644 --- a/crypto/txscript/src/opcodes/mod.rs +++ b/crypto/txscript/src/opcodes/mod.rs @@ -11,7 +11,10 @@ use kaspa_consensus_core::hashing::sighash::SigHashReusedValues; use kaspa_consensus_core::hashing::sighash_type::SigHashType; use kaspa_consensus_core::tx::VerifiableTransaction; use sha2::{Digest, Sha256}; -use std::fmt::{Debug, Formatter}; +use std::{ + fmt::{Debug, Formatter}, + num::TryFromIntError, +}; /// First value in the range formed by the "small integer" Op# opcodes pub const OP_SMALL_INT_MIN_VAL: u8 = 1; From 863a9a142044392f1724aa36650a90ae8be15551 Mon Sep 17 00:00:00 2001 From: max143672 Date: Mon, 11 Nov 2024 10:35:37 +0400 Subject: [PATCH 83/93] refactor: SigHashReusedValuesUnsync doesnt neet to be mutable --- consensus/client/src/signing.rs | 2 +- crypto/txscript/examples/kip-10.rs | 56 ++++++++++++------------------ 2 files changed, 23 insertions(+), 35 deletions(-) diff --git a/consensus/client/src/signing.rs b/consensus/client/src/signing.rs index dac657aa9..cd8604677 100644 --- a/consensus/client/src/signing.rs +++ b/consensus/client/src/signing.rs @@ -178,7 +178,7 @@ pub fn calc_schnorr_signature_hash( let utxo = cctx::UtxoEntry::from(utxo.as_ref()); let hash_type = SIG_HASH_ALL; - let mut reused_values = SigHashReusedValuesUnsync::new(); + let reused_values = SigHashReusedValuesUnsync::new(); // let input = verifiable_tx.populated_input(input_index); // let tx = verifiable_tx.tx(); diff --git a/crypto/txscript/examples/kip-10.rs b/crypto/txscript/examples/kip-10.rs index 5fa635619..9662d46bb 100644 --- a/crypto/txscript/examples/kip-10.rs +++ b/crypto/txscript/examples/kip-10.rs @@ -59,7 +59,7 @@ fn threshold_scenario() -> ScriptBuilderResult<()> { let sig_cache = Cache::new(10_000); // Prepare to reuse values for signature hashing - let mut reused_values = SigHashReusedValuesUnsync::new(); + let reused_values = SigHashReusedValuesUnsync::new(); // Create the script builder let mut builder = ScriptBuilder::new(); @@ -109,7 +109,7 @@ fn threshold_scenario() -> ScriptBuilderResult<()> { { println!("[STANDARD] Checking owner branch"); let mut tx = MutableTransaction::with_entries(tx.clone(), vec![utxo_entry.clone()]); - let sig_hash = calc_schnorr_signature_hash(&tx.as_verifiable(), 0, SIG_HASH_ALL, &mut reused_values); + let sig_hash = calc_schnorr_signature_hash(&tx.as_verifiable(), 0, SIG_HASH_ALL, &reused_values); let msg = secp256k1::Message::from_digest_slice(sig_hash.as_bytes().as_slice()).unwrap(); let sig = owner.sign_schnorr(msg); @@ -126,8 +126,7 @@ fn threshold_scenario() -> ScriptBuilderResult<()> { } let tx = tx.as_verifiable(); - let mut vm = - TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true); + let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &reused_values, &sig_cache, true); assert_eq!(vm.execute(), Ok(())); println!("[STANDARD] Owner branch execution successful"); } @@ -137,8 +136,7 @@ fn threshold_scenario() -> ScriptBuilderResult<()> { println!("[STANDARD] Checking borrower branch"); tx.inputs[0].signature_script = ScriptBuilder::new().add_op(OpFalse)?.add_data(&script)?.drain(); let tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]); - let mut vm = - TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true); + let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &reused_values, &sig_cache, true); assert_eq!(vm.execute(), Ok(())); println!("[STANDARD] Borrower branch execution successful"); } @@ -149,8 +147,7 @@ fn threshold_scenario() -> ScriptBuilderResult<()> { // Less than threshold tx.outputs[0].value -= 1; let tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]); - let mut vm = - TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true); + let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &reused_values, &sig_cache, true); assert_eq!(vm.execute(), Err(EvalFalse)); println!("[STANDARD] Borrower branch with threshold not reached failed as expected"); } @@ -246,7 +243,7 @@ fn threshold_scenario_limited_one_time() -> ScriptBuilderResult<()> { let sig_cache = Cache::new(10_000); // Prepare to reuse values for signature hashing - let mut reused_values = SigHashReusedValuesUnsync::new(); + let reused_values = SigHashReusedValuesUnsync::new(); // Generate the script public key let spk = pay_to_script_hash_script(&script); @@ -281,7 +278,7 @@ fn threshold_scenario_limited_one_time() -> ScriptBuilderResult<()> { { println!("[ONE-TIME] Checking owner branch"); let mut tx = MutableTransaction::with_entries(tx.clone(), vec![utxo_entry.clone()]); - let sig_hash = calc_schnorr_signature_hash(&tx.as_verifiable(), 0, SIG_HASH_ALL, &mut reused_values); + let sig_hash = calc_schnorr_signature_hash(&tx.as_verifiable(), 0, SIG_HASH_ALL, &reused_values); let msg = secp256k1::Message::from_digest_slice(sig_hash.as_bytes().as_slice()).unwrap(); let sig = owner.sign_schnorr(msg); @@ -298,8 +295,7 @@ fn threshold_scenario_limited_one_time() -> ScriptBuilderResult<()> { } let tx = tx.as_verifiable(); - let mut vm = - TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true); + let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &reused_values, &sig_cache, true); assert_eq!(vm.execute(), Ok(())); println!("[ONE-TIME] Owner branch execution successful"); } @@ -309,8 +305,7 @@ fn threshold_scenario_limited_one_time() -> ScriptBuilderResult<()> { println!("[ONE-TIME] Checking borrower branch"); tx.inputs[0].signature_script = ScriptBuilder::new().add_op(OpFalse)?.add_data(&script)?.drain(); let tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]); - let mut vm = - TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true); + let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &reused_values, &sig_cache, true); assert_eq!(vm.execute(), Ok(())); println!("[ONE-TIME] Borrower branch execution successful"); } @@ -321,8 +316,7 @@ fn threshold_scenario_limited_one_time() -> ScriptBuilderResult<()> { // Less than threshold tx.outputs[0].value -= 1; let tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]); - let mut vm = - TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true); + let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &reused_values, &sig_cache, true); assert_eq!(vm.execute(), Err(EvalFalse)); println!("[ONE-TIME] Borrower branch with threshold not reached failed as expected"); } @@ -349,7 +343,7 @@ fn threshold_scenario_limited_one_time() -> ScriptBuilderResult<()> { &wrong_tx.tx.inputs[0], 0, &utxo_entry, - &mut reused_values, + &reused_values, &sig_cache, true, ); @@ -409,7 +403,7 @@ fn threshold_scenario_limited_2_times() -> ScriptBuilderResult<()> { let sig_cache = Cache::new(10_000); // Prepare to reuse values for signature hashing - let mut reused_values = SigHashReusedValuesUnsync::new(); + let reused_values = SigHashReusedValuesUnsync::new(); // Generate the script public key let spk = pay_to_script_hash_script(&two_times_script); @@ -444,7 +438,7 @@ fn threshold_scenario_limited_2_times() -> ScriptBuilderResult<()> { { println!("[TWO-TIMES] Checking owner branch"); let mut tx = MutableTransaction::with_entries(tx.clone(), vec![utxo_entry.clone()]); - let sig_hash = calc_schnorr_signature_hash(&tx.as_verifiable(), 0, SIG_HASH_ALL, &mut reused_values); + let sig_hash = calc_schnorr_signature_hash(&tx.as_verifiable(), 0, SIG_HASH_ALL, &reused_values); let msg = secp256k1::Message::from_digest_slice(sig_hash.as_bytes().as_slice()).unwrap(); let sig = owner.sign_schnorr(msg); @@ -461,8 +455,7 @@ fn threshold_scenario_limited_2_times() -> ScriptBuilderResult<()> { } let tx = tx.as_verifiable(); - let mut vm = - TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true); + let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &reused_values, &sig_cache, true); assert_eq!(vm.execute(), Ok(())); println!("[TWO-TIMES] Owner branch execution successful"); } @@ -472,8 +465,7 @@ fn threshold_scenario_limited_2_times() -> ScriptBuilderResult<()> { println!("[TWO-TIMES] Checking borrower branch (first borrowing)"); tx.inputs[0].signature_script = ScriptBuilder::new().add_op(OpFalse)?.add_data(&two_times_script)?.drain(); let tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]); - let mut vm = - TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true); + let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &reused_values, &sig_cache, true); assert_eq!(vm.execute(), Ok(())); println!("[TWO-TIMES] Borrower branch (first borrowing) execution successful"); } @@ -484,8 +476,7 @@ fn threshold_scenario_limited_2_times() -> ScriptBuilderResult<()> { // Less than threshold tx.outputs[0].value -= 1; let tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]); - let mut vm = - TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true); + let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &reused_values, &sig_cache, true); assert_eq!(vm.execute(), Err(EvalFalse)); println!("[TWO-TIMES] Borrower branch with threshold not reached failed as expected"); } @@ -512,7 +503,7 @@ fn threshold_scenario_limited_2_times() -> ScriptBuilderResult<()> { &wrong_tx.tx.inputs[0], 0, &utxo_entry, - &mut reused_values, + &reused_values, &sig_cache, true, ); @@ -600,10 +591,10 @@ fn shared_secret_scenario() -> ScriptBuilderResult<()> { let tx = Transaction::new(1, vec![input.clone()], vec![output.clone()], 0, Default::default(), 0, vec![]); let sign = |pk: Keypair| { // Prepare to reuse values for signature hashing - let mut reused_values = SigHashReusedValuesUnsync::new(); + let reused_values = SigHashReusedValuesUnsync::new(); let tx = MutableTransaction::with_entries(tx.clone(), vec![utxo_entry.clone()]); - let sig_hash = calc_schnorr_signature_hash(&tx.as_verifiable(), 0, SIG_HASH_ALL, &mut reused_values); + let sig_hash = calc_schnorr_signature_hash(&tx.as_verifiable(), 0, SIG_HASH_ALL, &reused_values); let msg = secp256k1::Message::from_digest_slice(sig_hash.as_bytes().as_slice()).unwrap(); let sig = pk.sign_schnorr(msg); @@ -626,8 +617,7 @@ fn shared_secret_scenario() -> ScriptBuilderResult<()> { } let tx = tx.as_verifiable(); - let mut vm = - TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true); + let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &reused_values, &sig_cache, true); assert_eq!(vm.execute(), Ok(())); println!("[SHARED-SECRET] Owner branch execution successful"); } @@ -645,8 +635,7 @@ fn shared_secret_scenario() -> ScriptBuilderResult<()> { } let tx = tx.as_verifiable(); - let mut vm = - TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true); + let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &reused_values, &sig_cache, true); assert_eq!(vm.execute(), Ok(())); println!("[SHARED-SECRET] Borrower branch with correct shared secret execution successful"); } @@ -664,8 +653,7 @@ fn shared_secret_scenario() -> ScriptBuilderResult<()> { } let tx = tx.as_verifiable(); - let mut vm = - TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &mut reused_values, &sig_cache, true); + let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &reused_values, &sig_cache, true); assert_eq!(vm.execute(), Err(VerifyError)); println!("[SHARED-SECRET] Borrower branch with incorrect secret failed as expected"); } From 103684e527a18a819d6cde06b9587903cd355692 Mon Sep 17 00:00:00 2001 From: max143672 Date: Mon, 11 Nov 2024 12:22:18 +0400 Subject: [PATCH 84/93] fix test description --- crypto/txscript/test-data/script_tests-kip10.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crypto/txscript/test-data/script_tests-kip10.json b/crypto/txscript/test-data/script_tests-kip10.json index c0d1c5e69..947c8810d 100644 --- a/crypto/txscript/test-data/script_tests-kip10.json +++ b/crypto/txscript/test-data/script_tests-kip10.json @@ -4408,6 +4408,13 @@ "UNKNOWN_ERROR", "We cannot do BOOLAND on 9-byte integers" ], + [ + "-9223372036854775807", + "1SUB", + "", + "UNKNOWN_ERROR", + "result of math operation can't exceed 8 bytes" + ], [ "1", "1 ENDIF", From 2db1df54f591025009fb62a65df85cee7705f111 Mon Sep 17 00:00:00 2001 From: max143672 Date: Mon, 11 Nov 2024 12:36:08 +0400 Subject: [PATCH 85/93] rework example --- crypto/txscript/examples/kip-10.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crypto/txscript/examples/kip-10.rs b/crypto/txscript/examples/kip-10.rs index 9662d46bb..cbb67ec14 100644 --- a/crypto/txscript/examples/kip-10.rs +++ b/crypto/txscript/examples/kip-10.rs @@ -12,8 +12,8 @@ use kaspa_consensus_core::{ use kaspa_txscript::{ caches::Cache, opcodes::codes::{ - Op0, OpCheckSig, OpCheckSigVerify, OpDup, OpElse, OpEndIf, OpEqualVerify, OpFalse, OpGreaterThanOrEqual, OpIf, OpSub, OpTrue, - OpTxInputAmount, OpTxInputSpk, OpTxOutputAmount, OpTxOutputSpk, + OpCheckSig, OpCheckSigVerify, OpDup, OpElse, OpEndIf, OpEqualVerify, OpFalse, OpGreaterThanOrEqual, OpIf, OpSub, OpTrue, + OpTxInputAmount, OpTxInputIndex, OpTxInputSpk, OpTxOutputAmount, OpTxOutputSpk, }, pay_to_address_script, pay_to_script_hash_script, script_builder::{ScriptBuilder, ScriptBuilderResult}, @@ -70,9 +70,9 @@ fn threshold_scenario() -> ScriptBuilderResult<()> { .add_op(OpCheckSig)? // Borrower branch .add_op(OpElse)? - .add_ops(&[Op0, OpTxInputSpk, Op0, OpTxOutputSpk, OpEqualVerify, Op0, OpTxOutputAmount])? + .add_ops(&[OpTxInputIndex, OpTxInputSpk, OpTxInputIndex, OpTxOutputSpk, OpEqualVerify, OpTxInputIndex, OpTxOutputAmount])? .add_i64(threshold)? - .add_ops(&[OpSub, Op0, OpTxInputAmount, OpGreaterThanOrEqual])? + .add_ops(&[OpSub, OpTxInputIndex, OpTxInputAmount, OpGreaterThanOrEqual])? .add_op(OpEndIf)? .drain(); @@ -181,9 +181,9 @@ fn generate_limited_time_script(owner: &Keypair, threshold: i64, output_spk: Vec // Borrower branch .add_op(OpElse)? .add_data(&output_spk)? - .add_ops(&[Op0, OpTxOutputSpk, OpEqualVerify, Op0, OpTxOutputAmount])? + .add_ops(&[OpTxInputIndex, OpTxOutputSpk, OpEqualVerify, OpTxInputIndex, OpTxOutputAmount])? .add_i64(threshold)? - .add_ops(&[OpSub, Op0, OpTxInputAmount, OpGreaterThanOrEqual])? + .add_ops(&[OpSub, OpTxInputIndex, OpTxInputAmount, OpGreaterThanOrEqual])? .add_op(OpEndIf)? .drain(); @@ -557,7 +557,7 @@ fn shared_secret_scenario() -> ScriptBuilderResult<()> { .add_data(shared_secret_kp.x_only_public_key().0.serialize().as_slice())? .add_op(OpEqualVerify)? .add_op(OpCheckSigVerify)? - .add_ops(&[Op0, OpTxInputSpk, Op0, OpTxOutputSpk, OpEqualVerify, Op0, OpTxOutputAmount, Op0, OpTxInputAmount, OpGreaterThanOrEqual])? + .add_ops(&[OpTxInputIndex, OpTxInputSpk, OpTxInputIndex, OpTxOutputSpk, OpEqualVerify, OpTxInputIndex, OpTxOutputAmount, OpTxInputIndex, OpTxInputAmount, OpGreaterThanOrEqual])? .add_op(OpEndIf)? .drain(); From 1e48ab46ea269ac8cf3b551192e52a0f806329a7 Mon Sep 17 00:00:00 2001 From: max143672 Date: Mon, 11 Nov 2024 11:23:20 +0400 Subject: [PATCH 86/93] 9 byte integers must fail to serialize --- crypto/txscript/errors/src/lib.rs | 8 ++ crypto/txscript/src/data_stack.rs | 110 ++++++++++-------- crypto/txscript/src/lib.rs | 3 +- crypto/txscript/src/opcodes/macros.rs | 6 +- crypto/txscript/src/opcodes/mod.rs | 30 ++--- crypto/txscript/src/script_builder.rs | 44 ++++++- .../test-data/script_tests-kip10.json | 7 ++ 7 files changed, 138 insertions(+), 70 deletions(-) diff --git a/crypto/txscript/errors/src/lib.rs b/crypto/txscript/errors/src/lib.rs index 9384a066f..b16ec4cea 100644 --- a/crypto/txscript/errors/src/lib.rs +++ b/crypto/txscript/errors/src/lib.rs @@ -71,4 +71,12 @@ pub enum TxScriptError { ScriptSize(usize, usize), #[error("transaction output {0} is out of bounds, should be non-negative below {1}")] InvalidOutputIndex(i32, usize), + #[error(transparent)] + Serialization(#[from] SerializationError), +} + +#[derive(Error, PartialEq, Eq, Debug, Clone, Copy)] +pub enum SerializationError { + #[error("Number exceeds 8 bytes: {0}")] + NumberTooLong(i64), } diff --git a/crypto/txscript/src/data_stack.rs b/crypto/txscript/src/data_stack.rs index 0ccd47627..0b374cfb5 100644 --- a/crypto/txscript/src/data_stack.rs +++ b/crypto/txscript/src/data_stack.rs @@ -1,6 +1,7 @@ use crate::TxScriptError; use core::fmt::Debug; use core::iter; +use kaspa_txscript_errors::SerializationError; use std::cmp::Ordering; use std::ops::Deref; @@ -22,7 +23,7 @@ pub(crate) trait DataStack { Vec: OpcodeData; fn pop_raw(&mut self) -> Result<[Vec; SIZE], TxScriptError>; fn peek_raw(&self) -> Result<[Vec; SIZE], TxScriptError>; - fn push_item(&mut self, item: T) + fn push_item(&mut self, item: T) -> Result<(), TxScriptError> where Vec: OpcodeData; fn drop_items(&mut self) -> Result<(), TxScriptError>; @@ -34,7 +35,9 @@ pub(crate) trait DataStack { pub(crate) trait OpcodeData { fn deserialize(&self) -> Result; - fn serialize(from: &T) -> Self; + fn serialize(from: &T) -> Result + where + Self: Sized; } fn check_minimal_data_encoding(v: &[u8]) -> Result<(), TxScriptError> { @@ -62,6 +65,36 @@ fn check_minimal_data_encoding(v: &[u8]) -> Result<(), TxScriptError> { Ok(()) } +#[inline] +fn serialize_i64(from: &i64) -> Vec { + let sign = from.signum(); + let mut positive = from.unsigned_abs(); + let mut last_saturated = false; + let mut number_vec: Vec = iter::from_fn(move || { + if positive == 0 { + if last_saturated { + last_saturated = false; + Some(0) + } else { + None + } + } else { + let value = positive & 0xff; + last_saturated = (value & 0x80) != 0; + positive >>= 8; + Some(value as u8) + } + }) + .collect(); + if sign == -1 { + match number_vec.last_mut() { + Some(num) => *num |= 0x80, + _ => unreachable!(), + } + } + number_vec +} + fn deserialize_i64(v: &[u8]) -> Result { match v.len() { l if l > size_of::() => { @@ -123,8 +156,11 @@ impl OpcodeData for Vec { } #[inline] - fn serialize(from: &Kip10I64) -> Self { - Self::serialize(&from.0) + fn serialize(from: &Kip10I64) -> Result { + if from.0 == i64::MIN { + return Err(SerializationError::NumberTooLong(from.0)); + } + Ok(serialize_i64(&from.0)) } } @@ -143,33 +179,11 @@ impl OpcodeData for Vec { } #[inline] - fn serialize(from: &i64) -> Self { - let sign = from.signum(); - let mut positive = from.unsigned_abs(); - let mut last_saturated = false; - let mut number_vec: Vec = iter::from_fn(move || { - if positive == 0 { - if last_saturated { - last_saturated = false; - Some(0) - } else { - None - } - } else { - let value = positive & 0xff; - last_saturated = (value & 0x80) != 0; - positive >>= 8; - Some(value as u8) - } - }) - .collect(); - if sign == -1 { - match number_vec.last_mut() { - Some(num) => *num |= 0x80, - _ => unreachable!(), - } + fn serialize(from: &i64) -> Result { + if from == &i64::MIN { + return Err(SerializationError::NumberTooLong(*from)); } - number_vec + Ok(serialize_i64(from)) } } @@ -177,13 +191,12 @@ impl OpcodeData for Vec { #[inline] fn deserialize(&self) -> Result { let res = OpcodeData::::deserialize(self)?; - i32::try_from(res.clamp(i32::MIN as i64, i32::MAX as i64)) - .map_err(|e| TxScriptError::InvalidState(format!("data is too big for `i32`: {e}"))) + Ok(res.clamp(i32::MIN as i64, i32::MAX as i64) as i32) } #[inline] - fn serialize(from: &i32) -> Self { - OpcodeData::::serialize(&(*from as i64)) + fn serialize(from: &i32) -> Result { + Ok(OpcodeData::::serialize(&(*from as i64)).expect("should never happen")) } } @@ -202,8 +215,8 @@ impl OpcodeData> for Vec { } #[inline] - fn serialize(from: &SizedEncodeInt) -> Self { - OpcodeData::::serialize(&from.0) + fn serialize(from: &SizedEncodeInt) -> Result { + Ok(serialize_i64(&from.0)) } } @@ -219,11 +232,11 @@ impl OpcodeData for Vec { } #[inline] - fn serialize(from: &bool) -> Self { - match from { + fn serialize(from: &bool) -> Result { + Ok(match from { true => vec![1], false => vec![], - } + }) } } @@ -269,11 +282,13 @@ impl DataStack for Stack { } #[inline] - fn push_item(&mut self, item: T) + fn push_item(&mut self, item: T) -> Result<(), TxScriptError> where Vec: OpcodeData, { - Vec::push(self, OpcodeData::serialize(&item)); + let v = OpcodeData::serialize(&item)?; + Vec::push(self, v); + Ok(()) } #[inline] @@ -338,7 +353,7 @@ impl DataStack for Stack { mod tests { use super::{Kip10I64, OpcodeData}; use crate::data_stack::SizedEncodeInt; - use kaspa_txscript_errors::TxScriptError; + use kaspa_txscript_errors::{SerializationError, TxScriptError}; // TestScriptNumBytes #[test] @@ -390,15 +405,16 @@ mod tests { TestCase { num: -72057594037927935, serialized: hex::decode("ffffffffffffff80").expect("failed parsing hex") }, TestCase { num: 9223372036854775807, serialized: hex::decode("ffffffffffffff7f").expect("failed parsing hex") }, TestCase { num: -9223372036854775807, serialized: hex::decode("ffffffffffffffff").expect("failed parsing hex") }, - // Values that are out of range for data that is interpreted as - // numbers after KIP-10 enabled, but are allowed as the result of numeric operations. - TestCase { num: -9223372036854775808, serialized: hex::decode("000000000000008080").expect("failed parsing hex") }, ]; for test in tests { - let serialized: Vec = OpcodeData::::serialize(&test.num); + let serialized: Vec = OpcodeData::::serialize(&test.num).unwrap(); assert_eq!(serialized, test.serialized); } + + // special case 9-byte i64 + let r: Result, _> = OpcodeData::::serialize(&-9223372036854775808); + assert_eq!(r, Err(SerializationError::NumberTooLong(-9223372036854775808))); } // TestMakeScriptNum @@ -723,7 +739,7 @@ mod tests { for test in tests { // Ensure the error code is of the expected type and the error // code matches the value specified in the test instance. - assert_eq!(test.serialized.deserialize(), test.result); + assert_eq!( as OpcodeData>::deserialize(&test.serialized), test.result); } for test in test_of_size_5 { diff --git a/crypto/txscript/src/lib.rs b/crypto/txscript/src/lib.rs index f2fb7048b..f36307a60 100644 --- a/crypto/txscript/src/lib.rs +++ b/crypto/txscript/src/lib.rs @@ -461,7 +461,7 @@ impl<'a, T: VerifiableTransaction, Reused: SigHashReusedValues> TxScriptEngine<' return Err(TxScriptError::NullFail); } - self.dstack.push_item(!failed); + self.dstack.push_item(!failed)?; Ok(()) } @@ -1119,6 +1119,7 @@ mod bitcoind_tests { Err(ue) => match ue { UnifiedError::TxScriptError(e) => match e { TxScriptError::NumberTooBig(_) => vec!["UNKNOWN_ERROR"], + TxScriptError::Serialization(_) => vec!["UNKNOWN_ERROR"], TxScriptError::PubKeyFormat => vec!["PUBKEYFORMAT"], TxScriptError::EvalFalse => vec!["EVAL_FALSE"], TxScriptError::EmptyStack => { diff --git a/crypto/txscript/src/opcodes/macros.rs b/crypto/txscript/src/opcodes/macros.rs index c4d161d40..a4b3bfbbf 100644 --- a/crypto/txscript/src/opcodes/macros.rs +++ b/crypto/txscript/src/opcodes/macros.rs @@ -132,7 +132,11 @@ macro_rules! opcode_list { let mut builder = ScriptBuilder::new(); for token in script.split_whitespace() { if let Ok(value) = token.parse::() { - builder.add_i64(value)?; + if value == i64::MIN { + builder.add_i64_min()?; + } else { + builder.add_i64(value)?; + } } else if let Some(Ok(value)) = token.strip_prefix("0x").and_then(|trimmed| Some(hex::decode(trimmed))) { builder.extend(&value); diff --git a/crypto/txscript/src/opcodes/mod.rs b/crypto/txscript/src/opcodes/mod.rs index 3da4e8790..c59bc27d9 100644 --- a/crypto/txscript/src/opcodes/mod.rs +++ b/crypto/txscript/src/opcodes/mod.rs @@ -212,7 +212,7 @@ fn push_number( number: i64, vm: &mut TxScriptEngine, ) -> OpCodeResult { - vm.dstack.push_item(number); + vm.dstack.push_item(number)?; Ok(()) } @@ -224,13 +224,13 @@ macro_rules! numeric_op { if $vm.kip10_enabled { let $pattern: [Kip10I64; $count] = $vm.dstack.pop_items()?; let r = $block; - $vm.dstack.push_item(r); + $vm.dstack.push_item(r)?; Ok(()) } else { let $pattern: [i64; $count] = $vm.dstack.pop_items()?; #[allow(clippy::useless_conversion)] let r = $block; - $vm.dstack.push_item(r); + $vm.dstack.push_item(r)?; Ok(()) } }; @@ -543,7 +543,7 @@ opcode_list! { opcode OpSize<0x82, 1>(self, vm) { match vm.dstack.last() { Some(last) => { - vm.dstack.push_item(i64::try_from(last.len()).map_err(|e| TxScriptError::NumberTooBig(e.to_string()))?); + vm.dstack.push_item(i64::try_from(last.len()).map_err(|e| TxScriptError::NumberTooBig(e.to_string()))?)?; Ok(()) }, None => Err(TxScriptError::InvalidStackOperation(1, 0)) @@ -721,7 +721,7 @@ opcode_list! { let hash_type = SigHashType::from_u8(typ).map_err(|e| TxScriptError::InvalidSigHashType(typ))?; match vm.check_ecdsa_signature(hash_type, key.as_slice(), sig.as_slice()) { Ok(valid) => { - vm.dstack.push_item(valid); + vm.dstack.push_item(valid)?; Ok(()) }, Err(e) => { @@ -730,7 +730,7 @@ opcode_list! { } } None => { - vm.dstack.push_item(false); + vm.dstack.push_item(false)?; Ok(()) } } @@ -744,7 +744,7 @@ opcode_list! { let hash_type = SigHashType::from_u8(typ).map_err(|e| TxScriptError::InvalidSigHashType(typ))?; match vm.check_schnorr_signature(hash_type, key.as_slice(), sig.as_slice()) { Ok(valid) => { - vm.dstack.push_item(valid); + vm.dstack.push_item(valid)?; Ok(()) }, Err(e) => { @@ -753,7 +753,7 @@ opcode_list! { } } None => { - vm.dstack.push_item(false); + vm.dstack.push_item(false)?; Ok(()) } } @@ -3097,7 +3097,7 @@ mod test { assert!(matches!(op_input_idx.execute(&mut vm), Err(TxScriptError::InvalidOpcode(_)))); } else { let mut expected = vm.dstack.clone(); - expected.push_item(current_idx as i64); + expected.push_item(current_idx as i64).unwrap(); op_input_idx.execute(&mut vm).unwrap(); assert_eq!(vm.dstack, expected); vm.dstack.clear(); @@ -3207,7 +3207,7 @@ mod test { index: 0, expected_result: ExpectedResult { expected_spk: None, - expected_amount: Some(OpcodeData::::serialize(&1111)), + expected_amount: Some(OpcodeData::::serialize(&1111).unwrap()), }, }, TestCase::Successful { @@ -3215,7 +3215,7 @@ mod test { index: 1, expected_result: ExpectedResult { expected_spk: None, - expected_amount: Some(OpcodeData::::serialize(&2222)), + expected_amount: Some(OpcodeData::::serialize(&2222).unwrap()), }, }, ], @@ -3245,7 +3245,7 @@ mod test { index: 0, expected_result: ExpectedResult { expected_spk: None, - expected_amount: Some(OpcodeData::::serialize(&3333)), + expected_amount: Some(OpcodeData::::serialize(&3333).unwrap()), }, }, TestCase::Successful { @@ -3253,7 +3253,7 @@ mod test { index: 1, expected_result: ExpectedResult { expected_spk: None, - expected_amount: Some(OpcodeData::::serialize(&4444)), + expected_amount: Some(OpcodeData::::serialize(&4444).unwrap()), }, }, ], @@ -3367,7 +3367,7 @@ mod test { op_input_count.execute(&mut vm).unwrap(); assert_eq!( vm.dstack, - vec![ as OpcodeData>::serialize(&(input_count as i64))], + vec![ as OpcodeData>::serialize(&(input_count as i64)).unwrap()], "Input count mismatch for {} inputs", input_count ); @@ -3377,7 +3377,7 @@ mod test { op_output_count.execute(&mut vm).unwrap(); assert_eq!( vm.dstack, - vec![ as OpcodeData>::serialize(&(output_count as i64))], + vec![ as OpcodeData>::serialize(&(output_count as i64)).unwrap()], "Output count mismatch for {} outputs", output_count ); diff --git a/crypto/txscript/src/script_builder.rs b/crypto/txscript/src/script_builder.rs index 3409e29e1..af1b1b948 100644 --- a/crypto/txscript/src/script_builder.rs +++ b/crypto/txscript/src/script_builder.rs @@ -6,6 +6,7 @@ use crate::{ MAX_SCRIPTS_SIZE, MAX_SCRIPT_ELEMENT_SIZE, }; use hexplay::{HexView, HexViewBuilder}; +use kaspa_txscript_errors::SerializationError; use thiserror::Error; /// DEFAULT_SCRIPT_ALLOC is the default size used for the backing array @@ -31,6 +32,9 @@ pub enum ScriptBuilderError { #[error("adding integer {0} would exceed the maximum allowed canonical script length of {MAX_SCRIPTS_SIZE}")] IntegerRejected(i64), + + #[error(transparent)] + Serialization(#[from] SerializationError), } pub type ScriptBuilderResult = std::result::Result; @@ -228,7 +232,31 @@ impl ScriptBuilder { return Ok(self); } - let bytes: Vec<_> = OpcodeData::serialize(&val); + let bytes: Vec<_> = OpcodeData::::serialize(&val)?; + self.add_data(&bytes) + } + + // Bitcoind tests utilizes this function + #[cfg(test)] + pub fn add_i64_min(&mut self) -> ScriptBuilderResult<&mut Self> { + let val = i64::MIN; + // Pushes that would cause the script to exceed the largest allowed + // script size would result in a non-canonical script. + if self.script.len() + 1 > MAX_SCRIPTS_SIZE { + return Err(ScriptBuilderError::IntegerRejected(val)); + } + + // Fast path for small integers and Op1Negate. + if val == 0 { + self.script.push(Op0); + return Ok(self); + } + if val == -1 || (1..=16).contains(&val) { + self.script.push(((Op1 as i64 - 1) + val) as u8); + return Ok(self); + } + + let bytes: Vec<_> = OpcodeData::serialize(&crate::data_stack::SizedEncodeInt::<9>(val)).expect("infallible"); self.add_data(&bytes) } @@ -360,11 +388,6 @@ mod tests { val: 9223372036854775807, expected: vec![OpData8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F], }, - Test { - name: "push -9223372036854775808", - val: -9223372036854775808, - expected: vec![OpData9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80], - }, ]; for test in tests { @@ -372,6 +395,15 @@ mod tests { let result = builder.add_i64(test.val).expect("the script is canonical").script(); assert_eq!(result, test.expected, "{} wrong result", test.name); } + + // special case that used in bitcoind test + let mut builder = ScriptBuilder::new(); + let result = builder.add_i64_min().expect("the script is canonical").script(); + assert_eq!( + result, + vec![OpData9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80], + "push -9223372036854775808 wrong result" + ); } /// Tests that pushing data to a script via the ScriptBuilder API works as expected and conforms to BIP0062. diff --git a/crypto/txscript/test-data/script_tests-kip10.json b/crypto/txscript/test-data/script_tests-kip10.json index c0d1c5e69..947c8810d 100644 --- a/crypto/txscript/test-data/script_tests-kip10.json +++ b/crypto/txscript/test-data/script_tests-kip10.json @@ -4408,6 +4408,13 @@ "UNKNOWN_ERROR", "We cannot do BOOLAND on 9-byte integers" ], + [ + "-9223372036854775807", + "1SUB", + "", + "UNKNOWN_ERROR", + "result of math operation can't exceed 8 bytes" + ], [ "1", "1 ENDIF", From aaeed6d920b51ef9fc7279c19fefd93ef2a06981 Mon Sep 17 00:00:00 2001 From: max143672 Date: Mon, 11 Nov 2024 21:20:23 +0400 Subject: [PATCH 87/93] add todo --- crypto/txscript/src/data_stack.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto/txscript/src/data_stack.rs b/crypto/txscript/src/data_stack.rs index 0b374cfb5..8f2dd492f 100644 --- a/crypto/txscript/src/data_stack.rs +++ b/crypto/txscript/src/data_stack.rs @@ -191,7 +191,7 @@ impl OpcodeData for Vec { #[inline] fn deserialize(&self) -> Result { let res = OpcodeData::::deserialize(self)?; - Ok(res.clamp(i32::MIN as i64, i32::MAX as i64) as i32) + Ok(res.clamp(i32::MIN as i64, i32::MAX as i64) as i32) // todo rid of clamp? } #[inline] From 0befcaab5a9342c68fe5aa53e8ebd529a4306837 Mon Sep 17 00:00:00 2001 From: max143672 Date: Tue, 12 Nov 2024 00:31:05 +0400 Subject: [PATCH 88/93] rewrite todo --- crypto/txscript/src/data_stack.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crypto/txscript/src/data_stack.rs b/crypto/txscript/src/data_stack.rs index 8f2dd492f..cb5935bbb 100644 --- a/crypto/txscript/src/data_stack.rs +++ b/crypto/txscript/src/data_stack.rs @@ -191,7 +191,9 @@ impl OpcodeData for Vec { #[inline] fn deserialize(&self) -> Result { let res = OpcodeData::::deserialize(self)?; - Ok(res.clamp(i32::MIN as i64, i32::MAX as i64) as i32) // todo rid of clamp? + // TODO: Consider getting rid of clamp, since the call to deserialize should return an error + // if the number is not in the i32 range (this should be done with proper testing)? + Ok(res.clamp(i32::MIN as i64, i32::MAX as i64) as i32) } #[inline] @@ -739,7 +741,7 @@ mod tests { for test in tests { // Ensure the error code is of the expected type and the error // code matches the value specified in the test instance. - assert_eq!( as OpcodeData>::deserialize(&test.serialized), test.result); + assert_eq!(test.serialized.deserialize(), test.result); } for test in test_of_size_5 { From 19a8c52226dbe88d759168bb5362002791623921 Mon Sep 17 00:00:00 2001 From: max143672 Date: Tue, 12 Nov 2024 00:32:45 +0400 Subject: [PATCH 89/93] remove redundant code --- crypto/txscript/src/script_builder.rs | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/crypto/txscript/src/script_builder.rs b/crypto/txscript/src/script_builder.rs index af1b1b948..466b8b408 100644 --- a/crypto/txscript/src/script_builder.rs +++ b/crypto/txscript/src/script_builder.rs @@ -239,24 +239,7 @@ impl ScriptBuilder { // Bitcoind tests utilizes this function #[cfg(test)] pub fn add_i64_min(&mut self) -> ScriptBuilderResult<&mut Self> { - let val = i64::MIN; - // Pushes that would cause the script to exceed the largest allowed - // script size would result in a non-canonical script. - if self.script.len() + 1 > MAX_SCRIPTS_SIZE { - return Err(ScriptBuilderError::IntegerRejected(val)); - } - - // Fast path for small integers and Op1Negate. - if val == 0 { - self.script.push(Op0); - return Ok(self); - } - if val == -1 || (1..=16).contains(&val) { - self.script.push(((Op1 as i64 - 1) + val) as u8); - return Ok(self); - } - - let bytes: Vec<_> = OpcodeData::serialize(&crate::data_stack::SizedEncodeInt::<9>(val)).expect("infallible"); + let bytes: Vec<_> = OpcodeData::serialize(&crate::data_stack::SizedEncodeInt::<9>(i64::MIN)).expect("infallible"); self.add_data(&bytes) } From ca02b61184417bff351beeebc1c22a25fd1e2985 Mon Sep 17 00:00:00 2001 From: Maxim <59533214+biryukovmaxim@users.noreply.github.com> Date: Tue, 12 Nov 2024 12:25:44 +0300 Subject: [PATCH 90/93] remove redundant mut in example Co-authored-by: Michael Sutton --- crypto/txscript/examples/kip-10.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto/txscript/examples/kip-10.rs b/crypto/txscript/examples/kip-10.rs index cbb67ec14..ebf40b162 100644 --- a/crypto/txscript/examples/kip-10.rs +++ b/crypto/txscript/examples/kip-10.rs @@ -607,7 +607,7 @@ fn shared_secret_scenario() -> ScriptBuilderResult<()> { // Check owner branch { println!("[SHARED-SECRET] Checking owner branch"); - let (mut tx, signature, mut reused_values) = sign(owner); + let (mut tx, signature, reused_values) = sign(owner); let mut builder = ScriptBuilder::new(); builder.add_data(&signature)?; builder.add_op(OpTrue)?; From 3323e2447f394ffe9bbeaf34a4a03860223d0627 Mon Sep 17 00:00:00 2001 From: Maxim <59533214+biryukovmaxim@users.noreply.github.com> Date: Tue, 12 Nov 2024 12:25:53 +0300 Subject: [PATCH 91/93] remove redundant mut in example Co-authored-by: Michael Sutton --- crypto/txscript/examples/kip-10.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto/txscript/examples/kip-10.rs b/crypto/txscript/examples/kip-10.rs index ebf40b162..e7129849b 100644 --- a/crypto/txscript/examples/kip-10.rs +++ b/crypto/txscript/examples/kip-10.rs @@ -643,7 +643,7 @@ fn shared_secret_scenario() -> ScriptBuilderResult<()> { // Check borrower branch with incorrect secret { println!("[SHARED-SECRET] Checking borrower branch with incorrect secret"); - let (mut tx, signature, mut reused_values) = sign(borrower_kp); + let (mut tx, signature, reused_values) = sign(borrower_kp); builder.add_data(&signature)?; builder.add_data(borrower_kp.x_only_public_key().0.serialize().as_slice())?; builder.add_op(OpFalse)?; From 83711f43611059e8ecab3662b3f156000e36f936 Mon Sep 17 00:00:00 2001 From: Maxim <59533214+biryukovmaxim@users.noreply.github.com> Date: Tue, 12 Nov 2024 12:26:01 +0300 Subject: [PATCH 92/93] remove redundant mut in example Co-authored-by: Michael Sutton --- crypto/txscript/examples/kip-10.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto/txscript/examples/kip-10.rs b/crypto/txscript/examples/kip-10.rs index e7129849b..4077385a7 100644 --- a/crypto/txscript/examples/kip-10.rs +++ b/crypto/txscript/examples/kip-10.rs @@ -625,7 +625,7 @@ fn shared_secret_scenario() -> ScriptBuilderResult<()> { // Check borrower branch with correct shared secret { println!("[SHARED-SECRET] Checking borrower branch with correct shared secret"); - let (mut tx, signature, mut reused_values) = sign(shared_secret_kp); + let (mut tx, signature, reused_values) = sign(shared_secret_kp); builder.add_data(&signature)?; builder.add_data(shared_secret_kp.x_only_public_key().0.serialize().as_slice())?; builder.add_op(OpFalse)?; From 74468e586ce1e783cfe5b6da65803e923fcc0f40 Mon Sep 17 00:00:00 2001 From: max143672 Date: Tue, 12 Nov 2024 13:32:02 +0400 Subject: [PATCH 93/93] cicd: apply lint to examples --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 9076749e6..c9aa2a267 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -213,7 +213,7 @@ jobs: run: cargo fmt --all -- --check - name: Run cargo clippy - run: cargo clippy --workspace --tests --benches -- -D warnings + run: cargo clippy --workspace --tests --benches --examples -- -D warnings check-wasm32: