From 12fe0ae9f86087dd5ae9d0d9cb59de10faeb6622 Mon Sep 17 00:00:00 2001 From: Fess Date: Mon, 27 Nov 2023 15:58:47 +0400 Subject: [PATCH 01/41] feat: order independent multisig hash modes supported, gating logic added, deserialize with epoch trait implemented --- stacks-common/src/codec/mod.rs | 70 + stackslib/src/blockstack_cli.rs | 17 +- stackslib/src/chainstate/coordinator/tests.rs | 10 +- stackslib/src/chainstate/nakamoto/mod.rs | 38 +- .../src/chainstate/nakamoto/tests/mod.rs | 17 +- stackslib/src/chainstate/stacks/auth.rs | 2690 +++++------------ stackslib/src/chainstate/stacks/block.rs | 538 +++- stackslib/src/chainstate/stacks/db/blocks.rs | 76 +- .../src/chainstate/stacks/db/transactions.rs | 563 +++- stackslib/src/chainstate/stacks/mod.rs | 97 +- stackslib/src/chainstate/stacks/tests/mod.rs | 102 + .../src/chainstate/stacks/transaction.rs | 1961 +++++++++++- stackslib/src/core/mempool.rs | 26 +- stackslib/src/core/mod.rs | 4 +- stackslib/src/main.rs | 66 +- stackslib/src/net/api/getblock.rs | 8 +- stackslib/src/net/api/postblock.rs | 22 +- stackslib/src/net/api/posttransaction.rs | 28 +- stackslib/src/net/api/tests/getblock.rs | 16 +- .../net/api/tests/getmicroblocks_confirmed.rs | 5 +- .../net/api/tests/getmicroblocks_indexed.rs | 5 +- stackslib/src/net/api/tests/postblock.rs | 3 +- stackslib/src/net/codec.rs | 59 +- stackslib/src/net/relay.rs | 329 +- stackslib/src/net/server.rs | 11 +- stackslib/src/net/tests/httpcore.rs | 3 +- testnet/stacks-node/src/config.rs | 6 + testnet/stacks-node/src/mockamoto.rs | 2 +- testnet/stacks-node/src/node.rs | 2 +- testnet/stacks-node/src/tests/epoch_205.rs | 25 +- testnet/stacks-node/src/tests/epoch_21.rs | 41 +- testnet/stacks-node/src/tests/epoch_22.rs | 15 +- testnet/stacks-node/src/tests/epoch_23.rs | 8 +- testnet/stacks-node/src/tests/epoch_24.rs | 10 +- testnet/stacks-node/src/tests/integrations.rs | 49 +- testnet/stacks-node/src/tests/mempool.rs | 184 +- testnet/stacks-node/src/tests/mod.rs | 9 +- .../src/tests/neon_integrations.rs | 372 ++- 38 files changed, 5072 insertions(+), 2415 deletions(-) diff --git a/stacks-common/src/codec/mod.rs b/stacks-common/src/codec/mod.rs index c2d9fd9610..1cf30146f1 100644 --- a/stacks-common/src/codec/mod.rs +++ b/stacks-common/src/codec/mod.rs @@ -3,6 +3,7 @@ use std::{error, fmt, io, mem}; // use crate::types::chainstate::MARFValue; use crate::types::chainstate::SortitionId; +use crate::types::StacksEpochId; use crate::util::hash::HASH160_ENCODED_SIZE; use crate::util::secp256k1::MESSAGE_SIGNATURE_ENCODED_SIZE; @@ -86,6 +87,15 @@ pub trait StacksMessageCodec { } } +pub trait DeserializeWithEpoch { + fn consensus_deserialize_with_epoch( + fd: &mut R, + epoch_id: StacksEpochId, + ) -> Result + where + Self: Sized; +} + // impl_byte_array_message_codec!(MARFValue, 40); impl_byte_array_message_codec!(SortitionId, 32); @@ -183,6 +193,66 @@ pub fn read_next_exact( read_next_vec::(fd, num_items, 0) } +pub fn read_next_with_epoch( + fd: &mut R, + epoch_id: StacksEpochId, +) -> Result { + let item: T = T::consensus_deserialize_with_epoch(fd, epoch_id)?; + Ok(item) +} + +fn read_next_vec_with_epoch( + fd: &mut R, + num_items: u32, + max_items: u32, + epoch_id: StacksEpochId, +) -> Result, Error> { + let len = u32::consensus_deserialize(fd)?; + + if max_items > 0 { + if len > max_items { + // too many items + return Err(Error::DeserializeError(format!( + "Array has too many items ({} > {}", + len, max_items + ))); + } + } else { + if len != num_items { + // inexact item count + return Err(Error::DeserializeError(format!( + "Array has incorrect number of items ({} != {})", + len, num_items + ))); + } + } + + if (mem::size_of::() as u128) * (len as u128) > MAX_MESSAGE_LEN as u128 { + return Err(Error::DeserializeError(format!( + "Message occupies too many bytes (tried to allocate {}*{}={})", + mem::size_of::() as u128, + len, + (mem::size_of::() as u128) * (len as u128) + ))); + } + + let mut ret = Vec::with_capacity(len as usize); + for _i in 0..len { + let next_item = T::consensus_deserialize_with_epoch(fd, epoch_id)?; + ret.push(next_item); + } + + Ok(ret) +} + +pub fn read_next_at_most_with_epoch( + fd: &mut R, + max_items: u32, + epoch_id: StacksEpochId, +) -> Result, Error> { + read_next_vec_with_epoch::(fd, 0, max_items, epoch_id) +} + impl StacksMessageCodec for Vec where T: StacksMessageCodec + Sized, diff --git a/stackslib/src/blockstack_cli.rs b/stackslib/src/blockstack_cli.rs index e85d02bc7f..8f39c8c1a7 100644 --- a/stackslib/src/blockstack_cli.rs +++ b/stackslib/src/blockstack_cli.rs @@ -47,8 +47,9 @@ use clarity::vm::errors::{Error as ClarityError, RuntimeErrorType}; use clarity::vm::types::PrincipalData; use clarity::vm::{ClarityName, ClarityVersion, ContractName, Value}; use stacks_common::address::{b58, AddressHashMode}; -use stacks_common::codec::{Error as CodecError, StacksMessageCodec}; +use stacks_common::codec::{DeserializeWithEpoch, Error as CodecError, StacksMessageCodec}; use stacks_common::types::chainstate::StacksAddress; +use stacks_common::types::StacksEpochId; use stacks_common::util::hash::{hex_bytes, to_hex}; use stacks_common::util::retry::LogReader; @@ -315,8 +316,10 @@ fn sign_transaction_single_sig_standard( transaction: &str, secret_key: &StacksPrivateKey, ) -> Result { - let transaction = - StacksTransaction::consensus_deserialize(&mut io::Cursor::new(&hex_bytes(transaction)?))?; + let transaction = StacksTransaction::consensus_deserialize_with_epoch( + &mut io::Cursor::new(&hex_bytes(transaction)?), + StacksEpochId::latest(), + )?; let mut tx_signer = StacksTransactionSigner::new(&transaction); tx_signer.sign_origin(secret_key)?; @@ -663,7 +666,10 @@ fn decode_transaction(args: &[String], _version: TransactionVersion) -> Result Ok(serde_json::to_string(&tx).expect("Failed to serialize transaction to JSON")), Err(e) => { let mut ret = String::new(); @@ -739,7 +745,8 @@ fn decode_block(args: &[String], _version: TransactionVersion) -> Result Ok(serde_json::to_string(&block).expect("Failed to serialize block to JSON")), Err(e) => { let mut ret = String::new(); diff --git a/stackslib/src/chainstate/coordinator/tests.rs b/stackslib/src/chainstate/coordinator/tests.rs index 7e94c4bbc3..8bc0ddd58e 100644 --- a/stackslib/src/chainstate/coordinator/tests.rs +++ b/stackslib/src/chainstate/coordinator/tests.rs @@ -674,7 +674,7 @@ fn make_genesis_block_with_recipients( ), key_block_ptr: 1, // all registers happen in block height 1 key_vtxindex: (1 + key_index) as u16, - memo: vec![STACKS_EPOCH_2_4_MARKER], + memo: vec![STACKS_EPOCH_3_0_MARKER], new_seed: VRFSeed::from_proof(&proof), commit_outs, @@ -945,7 +945,7 @@ fn make_stacks_block_with_input( ), key_block_ptr: 1, // all registers happen in block height 1 key_vtxindex: (1 + key_index) as u16, - memo: vec![STACKS_EPOCH_2_4_MARKER], + memo: vec![STACKS_EPOCH_3_0_MARKER], new_seed: VRFSeed::from_proof(&proof), commit_outs, @@ -4663,7 +4663,7 @@ fn test_epoch_switch_pox_3_contract_instantiation() { &committers, pox_consts.clone(), None, - StacksEpochId::Epoch24, + StacksEpochId::Epoch30, ); let mut coord = make_coordinator(path, Some(burnchain_conf)); @@ -4777,7 +4777,9 @@ fn test_epoch_switch_pox_3_contract_instantiation() { x if x >= 8 && x < 12 => StacksEpochId::Epoch21, x if x >= 12 && x < 16 => StacksEpochId::Epoch22, x if x >= 16 && x < 20 => StacksEpochId::Epoch23, - _ => StacksEpochId::Epoch24, + x if x >= 20 && x < 24 => StacksEpochId::Epoch24, + x if x >= 24 && x < 28 => StacksEpochId::Epoch25, + _ => StacksEpochId::Epoch30, }; assert_eq!( chainstate diff --git a/stackslib/src/chainstate/nakamoto/mod.rs b/stackslib/src/chainstate/nakamoto/mod.rs index 3d55999b16..5d2da1a9d3 100644 --- a/stackslib/src/chainstate/nakamoto/mod.rs +++ b/stackslib/src/chainstate/nakamoto/mod.rs @@ -27,7 +27,7 @@ use rusqlite::types::{FromSql, FromSqlError}; use rusqlite::{params, Connection, OptionalExtension, ToSql, NO_PARAMS}; use sha2::{Digest as Sha2Digest, Sha512_256}; use stacks_common::codec::{ - read_next, write_next, Error as CodecError, StacksMessageCodec, MAX_MESSAGE_LEN, + read_next, write_next, Error as CodecError, StacksMessageCodec, MAX_MESSAGE_LEN, DeserializeWithEpoch, read_next_at_most_with_epoch }; use stacks_common::consts::{ FIRST_BURNCHAIN_CONSENSUS_HASH, FIRST_STACKS_BLOCK_HASH, MINER_REWARD_MATURITY, @@ -134,7 +134,7 @@ lazy_static! { -- block data data BLOB NOT NULL, - + PRIMARY KEY(block_hash,consensus_hash) );"#.into(), r#" @@ -144,7 +144,7 @@ lazy_static! { block_height INTEGER NOT NULL, -- root hash of the internal, not-consensus-critical MARF that allows us to track chainstate/fork metadata index_root TEXT NOT NULL, - -- burn header hash corresponding to the consensus hash (NOT guaranteed to be unique, since we can + -- burn header hash corresponding to the consensus hash (NOT guaranteed to be unique, since we can -- have 2+ blocks per burn block if there's a PoX fork) burn_header_hash TEXT NOT NULL, -- height of the burnchain block header that generated this consensus hash @@ -178,7 +178,7 @@ lazy_static! { header_type TEXT NOT NULL, -- hash of the block block_hash TEXT NOT NULL, - -- index_block_hash is the hash of the block hash and consensus hash of the burn block that selected it, + -- index_block_hash is the hash of the block hash and consensus hash of the burn block that selected it, -- and is guaranteed to be globally unique (across all Stacks forks and across all PoX forks). -- index_block_hash is the block hash fed into the MARF index. index_block_hash TEXT NOT NULL, @@ -831,7 +831,7 @@ impl NakamotoBlock { return false; } } - if !StacksBlock::validate_transactions_static_epoch(&self.txs, epoch_id) { + if !StacksBlock::validate_transactions_static_epoch(&self.txs, epoch_id, true) { return false; } match self.is_wellformed_first_tenure_block() { @@ -927,6 +927,7 @@ impl NakamotoChainState { /// Returns (the block, the size of the block) pub fn next_ready_nakamoto_block( staging_db_conn: &Connection, + epoch_id: StacksEpochId, ) -> Result, ChainstateError> { let query = "SELECT data FROM nakamoto_staging_blocks WHERE burn_attachable = 1 @@ -937,7 +938,7 @@ impl NakamotoChainState { staging_db_conn .query_row_and_then(query, NO_PARAMS, |row| { let data: Vec = row.get("data")?; - let block = NakamotoBlock::consensus_deserialize(&mut data.as_slice())?; + let block = NakamotoBlock::consensus_deserialize_with_epoch(&mut data.as_slice(), epoch_id)?; Ok(Some(( block, u64::try_from(data.len()).expect("FATAL: block is bigger than a u64"), @@ -960,6 +961,7 @@ impl NakamotoChainState { staging_db_conn: &Connection, consensus_hash: &ConsensusHash, block_hash: &BlockHeaderHash, + epoch_id: StacksEpochId, ) -> Result, ChainstateError> { let query = "SELECT data FROM nakamoto_staging_blocks WHERE consensus_hash = ?1 AND block_hash = ?2"; staging_db_conn @@ -968,7 +970,7 @@ impl NakamotoChainState { rusqlite::params![consensus_hash, block_hash], |row| { let data: Vec = row.get("data")?; - let block = NakamotoBlock::consensus_deserialize(&mut data.as_slice()) + let block = NakamotoBlock::consensus_deserialize_with_epoch(&mut data.as_slice(), epoch_id) .map_err(|_| DBError::ParseError)?; if &block.header.block_hash() != block_hash { error!( @@ -1006,7 +1008,7 @@ impl NakamotoChainState { ) -> Result, ChainstateError> { let (mut chainstate_tx, clarity_instance) = stacks_chain_state.chainstate_tx_begin()?; let Some((next_ready_block, block_size)) = - Self::next_ready_nakamoto_block(&chainstate_tx.tx)? + Self::next_ready_nakamoto_block(&chainstate_tx.tx, StacksEpochId::latest())? else { // no more blocks return Ok(None); @@ -2714,12 +2716,22 @@ impl StacksMessageCodec for NakamotoBlock { } fn consensus_deserialize(fd: &mut R) -> Result { - let (header, txs) = { + panic!("NakamotoBlock should be deserialized with consensus_deserialize_with_epoch instead") + } +} + +impl DeserializeWithEpoch for NakamotoBlock { + fn consensus_deserialize_with_epoch( + fd: &mut R, + epoch_id: StacksEpochId, + ) -> Result { + let header: NakamotoBlockHeader = read_next(fd)?; + + let txs: Vec = { let mut bound_read = BoundReader::from_reader(fd, u64::from(MAX_MESSAGE_LEN)); - let header: NakamotoBlockHeader = read_next(&mut bound_read)?; - let txs: Vec<_> = read_next(&mut bound_read)?; - (header, txs) - }; + // The latest epoch where StacksMicroblock exist is Epoch25 + read_next_at_most_with_epoch(&mut bound_read, u32::MAX, epoch_id) + }?; // all transactions are unique if !StacksBlock::validate_transactions_unique(&txs) { diff --git a/stackslib/src/chainstate/nakamoto/tests/mod.rs b/stackslib/src/chainstate/nakamoto/tests/mod.rs index a7d299b9ca..09f9289408 100644 --- a/stackslib/src/chainstate/nakamoto/tests/mod.rs +++ b/stackslib/src/chainstate/nakamoto/tests/mod.rs @@ -570,12 +570,18 @@ pub fn test_load_store_update_nakamoto_blocks() { tx.commit().unwrap(); } + let epoch_id = SortitionDB::get_stacks_epoch(chainstate.db(), nakamoto_header.chain_length) + .unwrap() + .unwrap() + .epoch_id; + // can load Nakamoto block, but only the Nakamoto block assert_eq!( NakamotoChainState::load_nakamoto_block( chainstate.db(), &nakamoto_header.consensus_hash, - &nakamoto_header.block_hash() + &nakamoto_header.block_hash(), + epoch_id, ) .unwrap() .unwrap(), @@ -585,7 +591,8 @@ pub fn test_load_store_update_nakamoto_blocks() { NakamotoChainState::load_nakamoto_block( chainstate.db(), &epoch2_header_info.consensus_hash, - &epoch2_header.block_hash() + &epoch2_header.block_hash(), + epoch_id, ) .unwrap(), None @@ -802,14 +809,14 @@ pub fn test_load_store_update_nakamoto_blocks() { { let tx = chainstate.db_tx_begin().unwrap(); assert_eq!( - NakamotoChainState::next_ready_nakamoto_block(&tx).unwrap(), + NakamotoChainState::next_ready_nakamoto_block(&tx, StacksEpochId::latest()).unwrap(), None ); // set burn processed, but this isn't enough NakamotoChainState::set_burn_block_processed(&tx, &nakamoto_header.consensus_hash).unwrap(); assert_eq!( - NakamotoChainState::next_ready_nakamoto_block(&tx).unwrap(), + NakamotoChainState::next_ready_nakamoto_block(&tx, StacksEpochId::latest()).unwrap(), None ); @@ -819,7 +826,7 @@ pub fn test_load_store_update_nakamoto_blocks() { // this works now assert_eq!( - NakamotoChainState::next_ready_nakamoto_block(&tx) + NakamotoChainState::next_ready_nakamoto_block(&tx, StacksEpochId::latest()) .unwrap() .unwrap() .0, diff --git a/stackslib/src/chainstate/stacks/auth.rs b/stackslib/src/chainstate/stacks/auth.rs index d2981683c0..24caa33402 100644 --- a/stackslib/src/chainstate/stacks/auth.rs +++ b/stackslib/src/chainstate/stacks/auth.rs @@ -30,12 +30,13 @@ use stacks_common::util::secp256k1::{MessageSignature, MESSAGE_SIGNATURE_ENCODED use crate::burnchains::{PrivateKey, PublicKey, Txid}; use crate::chainstate::stacks::{ - Error, MultisigHashMode, MultisigSpendingCondition, SinglesigHashMode, - SinglesigSpendingCondition, StacksPrivateKey, StacksPublicKey, TransactionAuth, - TransactionAuthField, TransactionAuthFieldID, TransactionAuthFlags, - TransactionPublicKeyEncoding, TransactionSpendingCondition, - C32_ADDRESS_VERSION_MAINNET_MULTISIG, C32_ADDRESS_VERSION_MAINNET_SINGLESIG, - C32_ADDRESS_VERSION_TESTNET_MULTISIG, C32_ADDRESS_VERSION_TESTNET_SINGLESIG, + Error, MultisigHashMode, MultisigSpendingCondition, OrderIndependentMultisigHashMode, + OrderIndependentMultisigSpendingCondition, SinglesigHashMode, SinglesigSpendingCondition, + StacksPrivateKey, StacksPublicKey, TransactionAuth, TransactionAuthField, + TransactionAuthFieldID, TransactionAuthFlags, TransactionPublicKeyEncoding, + TransactionSpendingCondition, C32_ADDRESS_VERSION_MAINNET_MULTISIG, + C32_ADDRESS_VERSION_MAINNET_SINGLESIG, C32_ADDRESS_VERSION_TESTNET_MULTISIG, + C32_ADDRESS_VERSION_TESTNET_SINGLESIG, }; use crate::net::{Error as net_error, STACKS_PUBLIC_KEY_ENCODED_SIZE}; @@ -314,6 +315,210 @@ impl MultisigSpendingCondition { } } +impl StacksMessageCodec for OrderIndependentMultisigSpendingCondition { + fn consensus_serialize(&self, fd: &mut W) -> Result<(), codec_error> { + write_next(fd, &(self.hash_mode.clone() as u8))?; + write_next(fd, &self.signer)?; + write_next(fd, &self.nonce)?; + write_next(fd, &self.tx_fee)?; + write_next(fd, &self.fields)?; + write_next(fd, &self.signatures_required)?; + Ok(()) + } + + fn consensus_deserialize( + fd: &mut R, + ) -> Result { + let hash_mode_u8: u8 = read_next(fd)?; + let hash_mode = OrderIndependentMultisigHashMode::from_u8(hash_mode_u8).ok_or( + codec_error::DeserializeError(format!( + "Failed to parse multisig spending condition: unknown hash mode {}", + hash_mode_u8 + )), + )?; + + let signer: Hash160 = read_next(fd)?; + let nonce: u64 = read_next(fd)?; + let tx_fee: u64 = read_next(fd)?; + let fields: Vec = { + let mut bound_read = BoundReader::from_reader(fd, MAX_MESSAGE_LEN as u64); + read_next(&mut bound_read) + }?; + + let signatures_required: u16 = read_next(fd)?; + + // read and decode _exactly_ num_signatures signature buffers + let mut num_sigs_given: u16 = 0; + let mut have_uncompressed = false; + for f in fields.iter() { + match *f { + TransactionAuthField::Signature(ref key_encoding, _) => { + num_sigs_given = + num_sigs_given + .checked_add(1) + .ok_or(codec_error::DeserializeError( + "Failed to parse order independent multisig spending condition: too many signatures" + .to_string(), + ))?; + if *key_encoding == TransactionPublicKeyEncoding::Uncompressed { + have_uncompressed = true; + } + } + TransactionAuthField::PublicKey(ref pubk) => { + if !pubk.compressed() { + have_uncompressed = true; + } + } + }; + } + + // must be given the right number of signatures + if num_sigs_given != signatures_required { + test_debug!( + "Failed to deserialize order independent multisig spending condition: got {} sigs, expected {}", + num_sigs_given, + signatures_required + ); + return Err(codec_error::DeserializeError(format!( + "Failed to parse order independent multisig spending condition: got {} sigs, expected {}", + num_sigs_given, signatures_required + ))); + } + + // must all be compressed if we're using P2WSH + if have_uncompressed && hash_mode == OrderIndependentMultisigHashMode::P2WSH { + test_debug!( + "Failed to deserialize order independent multisig spending condition: expected compressed keys only" + ); + return Err(codec_error::DeserializeError( + "Failed to parse order independent multisig spending condition: expected compressed keys only" + .to_string(), + )); + } + + Ok(OrderIndependentMultisigSpendingCondition { + signer, + nonce, + tx_fee, + hash_mode, + fields, + signatures_required, + }) + } +} + +impl OrderIndependentMultisigSpendingCondition { + pub fn push_signature( + &mut self, + key_encoding: TransactionPublicKeyEncoding, + signature: MessageSignature, + ) -> () { + self.fields + .push(TransactionAuthField::Signature(key_encoding, signature)); + } + + pub fn push_public_key(&mut self, public_key: StacksPublicKey) -> () { + self.fields + .push(TransactionAuthField::PublicKey(public_key)); + } + + pub fn pop_auth_field(&mut self) -> Option { + self.fields.pop() + } + + pub fn address_mainnet(&self) -> StacksAddress { + StacksAddress { + version: C32_ADDRESS_VERSION_MAINNET_MULTISIG, + bytes: self.signer.clone(), + } + } + + pub fn address_testnet(&self) -> StacksAddress { + StacksAddress { + version: C32_ADDRESS_VERSION_TESTNET_MULTISIG, + bytes: self.signer.clone(), + } + } + + /// Authenticate a spending condition against an initial sighash. + /// In doing so, recover all public keys and verify that they hash to the signer + /// via the given hash mode. + pub fn verify( + &self, + initial_sighash: &Txid, + cond_code: &TransactionAuthFlags, + ) -> Result { + let mut pubkeys = vec![]; + let cur_sighash = initial_sighash.clone(); + let mut num_sigs: u16 = 0; + let mut have_uncompressed = false; + for field in self.fields.iter() { + let pubkey = match field { + TransactionAuthField::PublicKey(ref pubkey) => { + if !pubkey.compressed() { + have_uncompressed = true; + } + pubkey.clone() + } + TransactionAuthField::Signature(ref pubkey_encoding, ref sigbuf) => { + if *pubkey_encoding == TransactionPublicKeyEncoding::Uncompressed { + have_uncompressed = true; + } + + let (pubkey, _next_sighash) = TransactionSpendingCondition::next_verification( + &cur_sighash, + cond_code, + self.tx_fee, + self.nonce, + pubkey_encoding, + sigbuf, + )?; + num_sigs = num_sigs + .checked_add(1) + .ok_or(net_error::VerifyingError("Too many signatures".to_string()))?; + pubkey + } + }; + pubkeys.push(pubkey); + } + + if num_sigs != self.signatures_required { + return Err(net_error::VerifyingError( + "Incorrect number of signatures".to_string(), + )); + } + + if have_uncompressed && self.hash_mode == OrderIndependentMultisigHashMode::P2WSH { + return Err(net_error::VerifyingError( + "Uncompressed keys are not allowed in this hash mode".to_string(), + )); + } + + let addr_bytes = match StacksAddress::from_public_keys( + 0, + &self.hash_mode.to_address_hash_mode(), + self.signatures_required as usize, + &pubkeys, + ) { + Some(a) => a.bytes, + None => { + return Err(net_error::VerifyingError( + "Failed to generate address from public keys".to_string(), + )); + } + }; + + if addr_bytes != self.signer { + return Err(net_error::VerifyingError(format!( + "Signer hash does not equal hash of public key(s): {} != {}", + addr_bytes, self.signer + ))); + } + + Ok(cur_sighash) + } +} + impl StacksMessageCodec for SinglesigSpendingCondition { fn consensus_serialize(&self, fd: &mut W) -> Result<(), codec_error> { write_next(fd, &(self.hash_mode.clone() as u8))?; @@ -461,6 +666,9 @@ impl StacksMessageCodec for TransactionSpendingCondition { TransactionSpendingCondition::Multisig(ref data) => { data.consensus_serialize(fd)?; } + TransactionSpendingCondition::OrderIndependentMultisig(ref data) => { + data.consensus_serialize(fd)?; + } } Ok(()) } @@ -479,6 +687,10 @@ impl StacksMessageCodec for TransactionSpendingCondition { } else if MultisigHashMode::from_u8(hash_mode_u8).is_some() { let cond = MultisigSpendingCondition::consensus_deserialize(&mut rrd)?; TransactionSpendingCondition::Multisig(cond) + } else if OrderIndependentMultisigHashMode::from_u8(hash_mode_u8).is_some() { + let cond = + OrderIndependentMultisigSpendingCondition::consensus_deserialize(&mut rrd)?; + TransactionSpendingCondition::OrderIndependentMultisig(cond) } else { test_debug!("Invalid address hash mode {}", hash_mode_u8); return Err(codec_error::DeserializeError(format!( @@ -541,7 +753,7 @@ impl TransactionSpendingCondition { let signer_addr = StacksAddress::from_public_keys( 0, &AddressHashMode::SerializeP2SH, - num_sigs as usize, + usize::from(num_sigs), &pubkeys, )?; @@ -557,6 +769,52 @@ impl TransactionSpendingCondition { )) } + pub fn new_multisig_order_independent_p2sh( + num_sigs: u16, + pubkeys: Vec, + ) -> Option { + let signer_addr = StacksAddress::from_public_keys( + 0, + &AddressHashMode::SerializeP2SH, + usize::from(num_sigs), + &pubkeys, + )?; + + Some(TransactionSpendingCondition::OrderIndependentMultisig( + OrderIndependentMultisigSpendingCondition { + signer: signer_addr.bytes.clone(), + nonce: 0, + tx_fee: 0, + hash_mode: OrderIndependentMultisigHashMode::P2SH, + fields: vec![], + signatures_required: num_sigs, + }, + )) + } + + pub fn new_multisig_order_independent_p2wsh( + num_sigs: u16, + pubkeys: Vec, + ) -> Option { + let signer_addr = StacksAddress::from_public_keys( + 0, + &AddressHashMode::SerializeP2WSH, + usize::from(num_sigs), + &pubkeys, + )?; + + Some(TransactionSpendingCondition::OrderIndependentMultisig( + OrderIndependentMultisigSpendingCondition { + signer: signer_addr.bytes.clone(), + nonce: 0, + tx_fee: 0, + hash_mode: OrderIndependentMultisigHashMode::P2WSH, + fields: vec![], + signatures_required: num_sigs, + }, + )) + } + pub fn new_multisig_p2wsh( num_sigs: u16, pubkeys: Vec, @@ -564,7 +822,7 @@ impl TransactionSpendingCondition { let signer_addr = StacksAddress::from_public_keys( 0, &AddressHashMode::SerializeP2WSH, - num_sigs as usize, + usize::from(num_sigs), &pubkeys, )?; @@ -614,6 +872,17 @@ impl TransactionSpendingCondition { } num_sigs } + TransactionSpendingCondition::OrderIndependentMultisig(ref data) => { + let mut num_sigs: u16 = 0; + for field in data.fields.iter() { + if field.is_signature() { + num_sigs = num_sigs + .checked_add(1) + .expect("Unreasonable amount of signatures"); // something is seriously wrong if this fails + } + } + num_sigs + } } } @@ -623,6 +892,9 @@ impl TransactionSpendingCondition { TransactionSpendingCondition::Multisig(ref multisig_data) => { multisig_data.signatures_required } + TransactionSpendingCondition::OrderIndependentMultisig(ref multisig_data) => { + multisig_data.signatures_required + } } } @@ -630,6 +902,7 @@ impl TransactionSpendingCondition { match *self { TransactionSpendingCondition::Singlesig(ref data) => data.nonce, TransactionSpendingCondition::Multisig(ref data) => data.nonce, + TransactionSpendingCondition::OrderIndependentMultisig(ref data) => data.nonce, } } @@ -637,6 +910,7 @@ impl TransactionSpendingCondition { match *self { TransactionSpendingCondition::Singlesig(ref data) => data.tx_fee, TransactionSpendingCondition::Multisig(ref data) => data.tx_fee, + TransactionSpendingCondition::OrderIndependentMultisig(ref data) => data.tx_fee, } } @@ -648,6 +922,9 @@ impl TransactionSpendingCondition { TransactionSpendingCondition::Multisig(ref mut multisig_data) => { multisig_data.nonce = n; } + TransactionSpendingCondition::OrderIndependentMultisig(ref mut multisig_data) => { + multisig_data.nonce = n; + } } } @@ -659,6 +936,9 @@ impl TransactionSpendingCondition { TransactionSpendingCondition::Multisig(ref mut multisig_data) => { multisig_data.tx_fee = tx_fee; } + TransactionSpendingCondition::OrderIndependentMultisig(ref mut multisig_data) => { + multisig_data.tx_fee = tx_fee; + } } } @@ -666,6 +946,9 @@ impl TransactionSpendingCondition { match *self { TransactionSpendingCondition::Singlesig(ref singlesig_data) => singlesig_data.tx_fee, TransactionSpendingCondition::Multisig(ref multisig_data) => multisig_data.tx_fee, + TransactionSpendingCondition::OrderIndependentMultisig(ref multisig_data) => { + multisig_data.tx_fee + } } } @@ -674,6 +957,9 @@ impl TransactionSpendingCondition { match *self { TransactionSpendingCondition::Singlesig(ref data) => data.address_mainnet(), TransactionSpendingCondition::Multisig(ref data) => data.address_mainnet(), + TransactionSpendingCondition::OrderIndependentMultisig(ref data) => { + data.address_mainnet() + } } } @@ -682,6 +968,9 @@ impl TransactionSpendingCondition { match *self { TransactionSpendingCondition::Singlesig(ref data) => data.address_testnet(), TransactionSpendingCondition::Multisig(ref data) => data.address_testnet(), + TransactionSpendingCondition::OrderIndependentMultisig(ref data) => { + data.address_testnet() + } } } @@ -707,6 +996,11 @@ impl TransactionSpendingCondition { multisig_data.nonce = 0; multisig_data.fields.clear(); } + TransactionSpendingCondition::OrderIndependentMultisig(ref mut multisig_data) => { + multisig_data.tx_fee = 0; + multisig_data.nonce = 0; + multisig_data.fields.clear(); + } } } @@ -842,6 +1136,9 @@ impl TransactionSpendingCondition { TransactionSpendingCondition::Multisig(ref data) => { data.verify(initial_sighash, cond_code) } + TransactionSpendingCondition::OrderIndependentMultisig(ref data) => { + data.verify(initial_sighash, cond_code) + } } } } @@ -908,9 +1205,35 @@ impl TransactionAuth { } } - pub fn from_p2wpkh(privk: &StacksPrivateKey) -> Option { - match TransactionSpendingCondition::new_singlesig_p2wpkh(StacksPublicKey::from_private( - privk, + pub fn from_order_independent_p2sh( + privks: &[StacksPrivateKey], + num_sigs: u16, + ) -> Option { + let mut pubks = vec![]; + for privk in privks.iter() { + pubks.push(StacksPublicKey::from_private(privk)); + } + + TransactionSpendingCondition::new_multisig_order_independent_p2sh(num_sigs, pubks) + .map(|auth| TransactionAuth::Standard(auth)) + } + + pub fn from_order_independent_p2wsh( + privks: &[StacksPrivateKey], + num_sigs: u16, + ) -> Option { + let mut pubks = vec![]; + for privk in privks.iter() { + pubks.push(StacksPublicKey::from_private(privk)); + } + + TransactionSpendingCondition::new_multisig_order_independent_p2wsh(num_sigs, pubks) + .map(|auth| TransactionAuth::Standard(auth)) + } + + pub fn from_p2wpkh(privk: &StacksPrivateKey) -> Option { + match TransactionSpendingCondition::new_singlesig_p2wpkh(StacksPublicKey::from_private( + privk, )) { Some(auth) => Some(TransactionAuth::Standard(auth)), None => None, @@ -1078,6 +1401,7 @@ impl TransactionAuth { } } +#[rustfmt::skip] #[cfg(test)] mod test { use super::*; @@ -1102,112 +1426,15 @@ mod test { // hash mode SinglesigHashMode::P2PKH as u8, // signer - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, // nonce - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x7b, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7b, // fee rate - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x01, - 0xc8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc8, // key encoding, TransactionPublicKeyEncoding::Uncompressed as u8, // signature - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, ]; let spending_condition_p2pkh_compressed = SinglesigSpendingCondition { @@ -1223,112 +1450,15 @@ mod test { // hash mode SinglesigHashMode::P2PKH as u8, // signer - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, // nonce - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x01, - 0x59, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x59, // fee rate - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x01, - 0xc8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc8, // key encoding TransactionPublicKeyEncoding::Compressed as u8, // signature - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, + 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, ]; let spending_conditions = vec![ @@ -1368,224 +1498,27 @@ mod test { // hash mode MultisigHashMode::P2SH as u8, // signer - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, // nonce - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x7b, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7b, // fee rate - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x01, - 0xc8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc8, // fields length - 0x00, - 0x00, - 0x00, - 0x03, + 0x00, 0x00, 0x00, 0x03, // field #1: signature TransactionAuthFieldID::SignatureUncompressed as u8, // field #1: signature - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // field #2: signature TransactionAuthFieldID::SignatureUncompressed as u8, // filed #2: signature - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, + 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, // field #3: public key TransactionAuthFieldID::PublicKeyUncompressed as u8, // field #3: key (compressed) - 0x03, - 0xef, - 0x23, - 0x40, - 0x51, - 0x8b, - 0x58, - 0x67, - 0xb2, - 0x35, - 0x98, - 0xa9, - 0xcf, - 0x74, - 0x61, - 0x1f, - 0x8b, - 0x98, - 0x06, - 0x4f, - 0x7d, - 0x55, - 0xcd, - 0xb8, - 0xc1, - 0x07, - 0xc6, - 0x7b, - 0x5e, - 0xfc, - 0xbc, - 0x5c, - 0x77, + 0x03, 0xef, 0x23, 0x40, 0x51, 0x8b, 0x58, 0x67, 0xb2, 0x35, 0x98, 0xa9, 0xcf, 0x74, 0x61, 0x1f, 0x8b, 0x98, 0x06, 0x4f, 0x7d, 0x55, 0xcd, 0xb8, 0xc1, 0x07, 0xc6, 0x7b, 0x5e, 0xfc, 0xbc, 0x5c, 0x77, // number of signatures required - 0x00, - 0x02, + 0x00, 0x02, ]; let spending_condition_p2sh_compressed = MultisigSpendingCondition { @@ -1616,224 +1549,27 @@ mod test { // hash mode MultisigHashMode::P2SH as u8, // signer - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, // nonce - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x01, - 0xc8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc8, // fee rate - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x02, - 0x37, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x37, // fields length - 0x00, - 0x00, - 0x00, - 0x03, + 0x00, 0x00, 0x00, 0x03, // field #1: signature TransactionAuthFieldID::SignatureCompressed as u8, // field #1: signature - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // field #2: signature TransactionAuthFieldID::SignatureCompressed as u8, // filed #2: signature - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, + 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, // field #3: public key TransactionAuthFieldID::PublicKeyCompressed as u8, // field #3: key (compressed) - 0x03, - 0xef, - 0x23, - 0x40, - 0x51, - 0x8b, - 0x58, - 0x67, - 0xb2, - 0x35, - 0x98, - 0xa9, - 0xcf, - 0x74, - 0x61, - 0x1f, - 0x8b, - 0x98, - 0x06, - 0x4f, - 0x7d, - 0x55, - 0xcd, - 0xb8, - 0xc1, - 0x07, - 0xc6, - 0x7b, - 0x5e, - 0xfc, - 0xbc, - 0x5c, - 0x77, + 0x03, 0xef, 0x23, 0x40, 0x51, 0x8b, 0x58, 0x67, 0xb2, 0x35, 0x98, 0xa9, 0xcf, 0x74, 0x61, 0x1f, 0x8b, 0x98, 0x06, 0x4f, 0x7d, 0x55, 0xcd, 0xb8, 0xc1, 0x07, 0xc6, 0x7b, 0x5e, 0xfc, 0xbc, 0x5c, 0x77, // number of signatures - 0x00, - 0x02, + 0x00, 0x02, ]; let spending_conditions = vec![ @@ -1854,139 +1590,153 @@ mod test { } #[test] - fn tx_stacks_spending_condition_p2wpkh() { - let spending_condition_p2wpkh_compressed = SinglesigSpendingCondition { + fn tx_stacks_spending_condition_order_independent_p2sh() { + // order independent p2sh + let spending_condition_order_independent_p2sh_uncompressed = OrderIndependentMultisigSpendingCondition { signer: Hash160([0x11; 20]), - hash_mode: SinglesigHashMode::P2WPKH, - key_encoding: TransactionPublicKeyEncoding::Compressed, - nonce: 345, - tx_fee: 567, - signature: MessageSignature::from_raw(&vec![0xfe; 65]), + hash_mode: OrderIndependentMultisigHashMode::P2SH, + nonce: 123, + tx_fee: 456, + fields: vec![ + TransactionAuthField::Signature(TransactionPublicKeyEncoding::Uncompressed, MessageSignature::from_raw(&vec![0xff; 65])), + TransactionAuthField::Signature(TransactionPublicKeyEncoding::Uncompressed, MessageSignature::from_raw(&vec![0xfe; 65])), + TransactionAuthField::PublicKey(PubKey::from_hex("04ef2340518b5867b23598a9cf74611f8b98064f7d55cdb8c107c67b5efcbc5c771f112f919b00a6c6c5f51f7c63e1762fe9fac9b66ec75a053db7f51f4a52712b").unwrap()), + ], + signatures_required: 2 }; - let spending_condition_p2wpkh_compressed_bytes = vec![ + let spending_condition_order_independent_p2sh_uncompressed_bytes = vec![ // hash mode - SinglesigHashMode::P2WPKH as u8, + OrderIndependentMultisigHashMode::P2SH as u8, // signer - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, // nonce - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x01, - 0x59, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7b, // fee rate - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x02, - 0x37, - // key encoding - TransactionPublicKeyEncoding::Compressed as u8, - // signature - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc8, + // fields length + 0x00, 0x00, 0x00, 0x03, + // field #1: signature + TransactionAuthFieldID::SignatureUncompressed as u8, + // field #1: signature + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + // field #2: signature + TransactionAuthFieldID::SignatureUncompressed as u8, + // filed #2: signature + 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, + // field #3: public key + TransactionAuthFieldID::PublicKeyUncompressed as u8, + // field #3: key (compressed) + 0x03, 0xef, 0x23, 0x40, 0x51, 0x8b, 0x58, 0x67, 0xb2, 0x35, 0x98, 0xa9, 0xcf, 0x74, 0x61, 0x1f, 0x8b, 0x98, 0x06, 0x4f, 0x7d, 0x55, 0xcd, 0xb8, 0xc1, 0x07, 0xc6, 0x7b, 0x5e, 0xfc, 0xbc, 0x5c, 0x77, + // number of signatures required + 0x00, 0x02, ]; - let spending_conditions = vec![spending_condition_p2wpkh_compressed]; - let spending_conditions_bytes = vec![spending_condition_p2wpkh_compressed_bytes]; - - for i in 0..spending_conditions.len() { - check_codec_and_corruption::( - &spending_conditions[i], - &spending_conditions_bytes[i], - ); - } - } - + let spending_condition_order_independent_p2sh_compressed = OrderIndependentMultisigSpendingCondition { + signer: Hash160([0x11; 20]), + hash_mode: OrderIndependentMultisigHashMode::P2SH, + nonce: 456, + tx_fee: 567, + fields: vec![ + TransactionAuthField::Signature( + TransactionPublicKeyEncoding::Compressed, + MessageSignature::from_raw(&vec![0xff; 65]), + ), + TransactionAuthField::Signature( + TransactionPublicKeyEncoding::Compressed, + MessageSignature::from_raw(&vec![0xfe; 65]), + ), + TransactionAuthField::PublicKey( + PubKey::from_hex( + "03ef2340518b5867b23598a9cf74611f8b98064f7d55cdb8c107c67b5efcbc5c77", + ) + .unwrap(), + ), + ], + signatures_required: 2, + }; + + let spending_condition_order_independent_p2sh_compressed_bytes = vec![ + // hash mode + OrderIndependentMultisigHashMode::P2SH as u8, + // signer + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + // nonce + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc8, + // fee rate + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x37, + // fields length + 0x00, 0x00, 0x00, 0x03, + // field #1: signature + TransactionAuthFieldID::SignatureCompressed as u8, + // field #1: signature + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + // field #2: signature + TransactionAuthFieldID::SignatureCompressed as u8, + // filed #2: signature + 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, + // field #3: public key + TransactionAuthFieldID::PublicKeyCompressed as u8, + // field #3: key (compressed) + 0x03, 0xef, 0x23, 0x40, 0x51, 0x8b, 0x58, 0x67, 0xb2, 0x35, 0x98, 0xa9, 0xcf, 0x74, 0x61, 0x1f, 0x8b, 0x98, 0x06, 0x4f, 0x7d, 0x55, 0xcd, 0xb8, 0xc1, 0x07, 0xc6, 0x7b, 0x5e, 0xfc, 0xbc, 0x5c, 0x77, + // number of signatures + 0x00, 0x02, + ]; + + let spending_conditions = vec![ + spending_condition_order_independent_p2sh_compressed, + spending_condition_order_independent_p2sh_uncompressed, + ]; + let spending_conditions_bytes = vec![ + spending_condition_order_independent_p2sh_compressed_bytes, + spending_condition_order_independent_p2sh_uncompressed_bytes, + ]; + + for i in 0..spending_conditions.len() { + check_codec_and_corruption::( + &spending_conditions[i], + &spending_conditions_bytes[i], + ); + } + } + + #[test] + fn tx_stacks_spending_condition_p2wpkh() { + let spending_condition_p2wpkh_compressed = SinglesigSpendingCondition { + signer: Hash160([0x11; 20]), + hash_mode: SinglesigHashMode::P2WPKH, + key_encoding: TransactionPublicKeyEncoding::Compressed, + nonce: 345, + tx_fee: 567, + signature: MessageSignature::from_raw(&vec![0xfe; 65]), + }; + + let spending_condition_p2wpkh_compressed_bytes = vec![ + // hash mode + SinglesigHashMode::P2WPKH as u8, + // signer + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + // nonce + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x59, + // fee rate + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x37, + // key encoding + TransactionPublicKeyEncoding::Compressed as u8, + // signature + 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, + ]; + + let spending_conditions = vec![spending_condition_p2wpkh_compressed]; + let spending_conditions_bytes = vec![spending_condition_p2wpkh_compressed_bytes]; + + for i in 0..spending_conditions.len() { + check_codec_and_corruption::( + &spending_conditions[i], + &spending_conditions_bytes[i], + ); + } + } + #[test] fn tx_stacks_spending_condition_p2wsh() { let spending_condition_p2wsh = MultisigSpendingCondition { @@ -2017,224 +1767,27 @@ mod test { // hash mode MultisigHashMode::P2WSH as u8, // signer - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, // nonce - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x01, - 0xc8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc8, // fee rate - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x02, - 0x37, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x37, // fields length - 0x00, - 0x00, - 0x00, - 0x03, + 0x00, 0x00, 0x00, 0x03, // field #1: signature TransactionAuthFieldID::SignatureCompressed as u8, // field #1: signature - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // field #2: signature TransactionAuthFieldID::SignatureCompressed as u8, // filed #2: signature - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, + 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, // field #3: public key TransactionAuthFieldID::PublicKeyCompressed as u8, // field #3: key (compressed) - 0x03, - 0xef, - 0x23, - 0x40, - 0x51, - 0x8b, - 0x58, - 0x67, - 0xb2, - 0x35, - 0x98, - 0xa9, - 0xcf, - 0x74, - 0x61, - 0x1f, - 0x8b, - 0x98, - 0x06, - 0x4f, - 0x7d, - 0x55, - 0xcd, - 0xb8, - 0xc1, - 0x07, - 0xc6, - 0x7b, - 0x5e, - 0xfc, - 0xbc, - 0x5c, - 0x77, + 0x03, 0xef, 0x23, 0x40, 0x51, 0x8b, 0x58, 0x67, 0xb2, 0x35, 0x98, 0xa9, 0xcf, 0x74, 0x61, 0x1f, 0x8b, 0x98, 0x06, 0x4f, 0x7d, 0x55, 0xcd, 0xb8, 0xc1, 0x07, 0xc6, 0x7b, 0x5e, 0xfc, 0xbc, 0x5c, 0x77, // number of signatures - 0x00, - 0x02, + 0x00, 0x02, ]; let spending_conditions = vec![spending_condition_p2wsh]; @@ -2292,6 +1845,30 @@ mod test { ], signatures_required: 2 }), + TransactionSpendingCondition::OrderIndependentMultisig(OrderIndependentMultisigSpendingCondition { + signer: Hash160([0x11; 20]), + hash_mode: OrderIndependentMultisigHashMode::P2SH, + nonce: 123, + tx_fee: 567, + fields: vec![ + TransactionAuthField::Signature(TransactionPublicKeyEncoding::Uncompressed, MessageSignature::from_raw(&vec![0xff; 65])), + TransactionAuthField::Signature(TransactionPublicKeyEncoding::Uncompressed, MessageSignature::from_raw(&vec![0xfe; 65])), + TransactionAuthField::PublicKey(PubKey::from_hex("04ef2340518b5867b23598a9cf74611f8b98064f7d55cdb8c107c67b5efcbc5c771f112f919b00a6c6c5f51f7c63e1762fe9fac9b66ec75a053db7f51f4a52712b").unwrap()), + ], + signatures_required: 2 + }), + TransactionSpendingCondition::OrderIndependentMultisig(OrderIndependentMultisigSpendingCondition { + signer: Hash160([0x11; 20]), + hash_mode: OrderIndependentMultisigHashMode::P2SH, + nonce: 456, + tx_fee: 567, + fields: vec![ + TransactionAuthField::Signature(TransactionPublicKeyEncoding::Compressed, MessageSignature::from_raw(&vec![0xff; 65])), + TransactionAuthField::Signature(TransactionPublicKeyEncoding::Compressed, MessageSignature::from_raw(&vec![0xfe; 65])), + TransactionAuthField::PublicKey(PubKey::from_hex("03ef2340518b5867b23598a9cf74611f8b98064f7d55cdb8c107c67b5efcbc5c77").unwrap()) + ], + signatures_required: 2 + }), TransactionSpendingCondition::Singlesig(SinglesigSpendingCondition { signer: Hash160([0x11; 20]), hash_mode: SinglesigHashMode::P2WPKH, @@ -2311,6 +1888,18 @@ mod test { TransactionAuthField::PublicKey(PubKey::from_hex("03ef2340518b5867b23598a9cf74611f8b98064f7d55cdb8c107c67b5efcbc5c77").unwrap()) ], signatures_required: 2 + }), + TransactionSpendingCondition::OrderIndependentMultisig(OrderIndependentMultisigSpendingCondition { + signer: Hash160([0x11; 20]), + hash_mode: OrderIndependentMultisigHashMode::P2WSH, + nonce: 456, + tx_fee: 567, + fields: vec![ + TransactionAuthField::Signature(TransactionPublicKeyEncoding::Compressed, MessageSignature::from_raw(&vec![0xff; 65])), + TransactionAuthField::Signature(TransactionPublicKeyEncoding::Compressed, MessageSignature::from_raw(&vec![0xfe; 65])), + TransactionAuthField::PublicKey(PubKey::from_hex("03ef2340518b5867b23598a9cf74611f8b98064f7d55cdb8c107c67b5efcbc5c77").unwrap()) + ], + signatures_required: 2 }) ]; @@ -2349,226 +1938,45 @@ mod test { // hash mode 0xff, // signer - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, // nonce - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x01, - 0xc8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc8, // fee rate - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x02, - 0x37, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x37, // key encoding, TransactionPublicKeyEncoding::Compressed as u8, // signature - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, + 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, ]; let bad_hash_mode_multisig_bytes = vec![ // hash mode MultisigHashMode::P2SH as u8, // signer - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + // nonce + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc8, + // fee rate + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x37, + // key encoding, + TransactionPublicKeyEncoding::Compressed as u8, + // signature + 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, + ]; + + let bad_hash_mode_order_independent_multisig_bytes = vec![ + // hash mode + OrderIndependentMultisigHashMode::P2SH as u8, + // signer + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, // nonce - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x01, - 0xc8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc8, // fee rate - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x02, - 0x37, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x37, // key encoding, TransactionPublicKeyEncoding::Compressed as u8, // signature - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, - 0xfd, + 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, ]; // this will parse into a singlesig spending condition, but data will still remain. @@ -2578,120 +1986,19 @@ mod test { // hash mode SinglesigHashMode::P2PKH as u8, // signer - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, // nonce (embeds key encoding and part of the parsed nonce) - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x01, - 0xc8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc8, // fee rate - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x02, - 0x37, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x37, // number of fields (embed part of the signature) - 0x00, - 0x00, - 0x00, - 0x01, + 0x00, 0x00, 0x00, 0x01, // field #1: signature TransactionAuthFieldID::SignatureCompressed as u8, // field #1: signature - 0x01, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, + 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // number of signatures - 0x00, - 0x01, + 0x00, 0x01, ]; // wrong number of public keys (too many signatures) @@ -2699,224 +2006,55 @@ mod test { // hash mode MultisigHashMode::P2SH as u8, // signer - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, // nonce - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x01, - 0xc8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc8, // fee rate - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x02, - 0x37, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x37, // fields length - 0x00, - 0x00, - 0x00, - 0x03, + 0x00, 0x00, 0x00, 0x03, // field #1: signature TransactionAuthFieldID::SignatureCompressed as u8, // field #1: signature - 0x01, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, + 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // field #2: signature TransactionAuthFieldID::SignatureCompressed as u8, // filed #2: signature - 0x02, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, + 0x02, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, // field #3: public key TransactionAuthFieldID::PublicKeyCompressed as u8, // field #3: key (compressed) - 0x03, - 0xef, - 0x23, - 0x40, - 0x51, - 0x8b, - 0x58, - 0x67, - 0xb2, - 0x35, - 0x98, - 0xa9, - 0xcf, - 0x74, - 0x61, - 0x1f, - 0x8b, - 0x98, - 0x06, - 0x4f, - 0x7d, - 0x55, - 0xcd, - 0xb8, - 0xc1, - 0x07, - 0xc6, - 0x7b, - 0x5e, - 0xfc, - 0xbc, - 0x5c, - 0x77, + 0x03, 0xef, 0x23, 0x40, 0x51, 0x8b, 0x58, 0x67, 0xb2, 0x35, 0x98, 0xa9, 0xcf, 0x74, 0x61, 0x1f, 0x8b, 0x98, 0x06, 0x4f, 0x7d, 0x55, 0xcd, 0xb8, 0xc1, 0x07, 0xc6, 0x7b, 0x5e, 0xfc, 0xbc, 0x5c, 0x77, // number of signatures - 0x00, - 0x01, + 0x00, 0x01, + ]; + + // wrong number of public keys (too many signatures) + let bad_public_order_independent_key_count_bytes = vec![ + // hash mode + OrderIndependentMultisigHashMode::P2SH as u8, + // signer + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + // nonce + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc8, + // fee rate + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x37, + // fields length + 0x00, 0x00, 0x00, 0x03, + // field #1: signature + TransactionAuthFieldID::SignatureCompressed as u8, + // field #1: signature + 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + // field #2: signature + TransactionAuthFieldID::SignatureCompressed as u8, + // filed #2: signature + 0x02, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, + // field #3: public key + TransactionAuthFieldID::PublicKeyCompressed as u8, + // field #3: key (compressed) + 0x03, 0xef, 0x23, 0x40, 0x51, 0x8b, 0x58, 0x67, 0xb2, 0x35, 0x98, 0xa9, 0xcf, 0x74, 0x61, 0x1f, 0x8b, 0x98, 0x06, 0x4f, 0x7d, 0x55, 0xcd, 0xb8, 0xc1, 0x07, 0xc6, 0x7b, 0x5e, 0xfc, 0xbc, 0x5c, 0x77, + // number of signatures + 0x00, 0x01, ]; // wrong number of public keys (not enough signatures) @@ -2924,224 +2062,55 @@ mod test { // hash mode MultisigHashMode::P2SH as u8, // signer - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, // nonce - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x01, - 0xc8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc8, // fee rate - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x02, - 0x37, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x37, // fields length - 0x00, - 0x00, - 0x00, - 0x03, + 0x00, 0x00, 0x00, 0x03, // field #1: signature TransactionAuthFieldID::SignatureCompressed as u8, // field #1: signature - 0x01, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, + 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // field #2: signature TransactionAuthFieldID::SignatureCompressed as u8, // filed #2: signature - 0x02, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, + 0x02, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, // field #3: public key TransactionAuthFieldID::PublicKeyCompressed as u8, // field #3: key (compressed) - 0x03, - 0xef, - 0x23, - 0x40, - 0x51, - 0x8b, - 0x58, - 0x67, - 0xb2, - 0x35, - 0x98, - 0xa9, - 0xcf, - 0x74, - 0x61, - 0x1f, - 0x8b, - 0x98, - 0x06, - 0x4f, - 0x7d, - 0x55, - 0xcd, - 0xb8, - 0xc1, - 0x07, - 0xc6, - 0x7b, - 0x5e, - 0xfc, - 0xbc, - 0x5c, - 0x77, + 0x03, 0xef, 0x23, 0x40, 0x51, 0x8b, 0x58, 0x67, 0xb2, 0x35, 0x98, 0xa9, 0xcf, 0x74, 0x61, 0x1f, 0x8b, 0x98, 0x06, 0x4f, 0x7d, 0x55, 0xcd, 0xb8, 0xc1, 0x07, 0xc6, 0x7b, 0x5e, 0xfc, 0xbc, 0x5c, 0x77, // number of signatures - 0x00, - 0x03, + 0x00, 0x03, + ]; + + // wrong number of public keys (not enough signatures) + let bad_public_key_count_bytes_3 = vec![ + // hash mode + OrderIndependentMultisigHashMode::P2SH as u8, + // signer + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + // nonce + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc8, + // fee rate + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x37, + // fields length + 0x00, 0x00, 0x00, 0x03, + // field #1: signature + TransactionAuthFieldID::SignatureCompressed as u8, + // field #1: signature + 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + // field #2: signature + TransactionAuthFieldID::SignatureCompressed as u8, + // filed #2: signature + 0x02, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, + // field #3: public key + TransactionAuthFieldID::PublicKeyCompressed as u8, + // field #3: key (compressed) + 0x03, 0xef, 0x23, 0x40, 0x51, 0x8b, 0x58, 0x67, 0xb2, 0x35, 0x98, 0xa9, 0xcf, 0x74, 0x61, 0x1f, 0x8b, 0x98, 0x06, 0x4f, 0x7d, 0x55, 0xcd, 0xb8, 0xc1, 0x07, 0xc6, 0x7b, 0x5e, 0xfc, 0xbc, 0x5c, 0x77, + // number of signatures + 0x00, 0x03, ]; // hashing mode doesn't allow uncompressed keys @@ -3159,112 +2128,15 @@ mod test { // hash mode SinglesigHashMode::P2WPKH as u8, // signer - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, // nonce - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x7b, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7b, // fee rate - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x02, - 0x37, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x37, // public key uncompressed TransactionPublicKeyEncoding::Uncompressed as u8, // signature - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, ]; // hashing mode doesn't allow uncompressed keys @@ -3285,221 +2157,62 @@ mod test { // hash mode MultisigHashMode::P2WSH as u8, // signer - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, - 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, // nonce - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x01, - 0xc8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc8, // fee rate - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x02, - 0x37, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x37, // number of fields - 0x00, - 0x00, - 0x00, - 0x03, + 0x00, 0x00, 0x00, 0x03, // signature TransactionAuthFieldID::SignatureUncompressed as u8, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // signature TransactionAuthFieldID::SignatureUncompressed as u8, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, - 0xfe, + 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, // key TransactionAuthFieldID::PublicKeyUncompressed as u8, - 0x02, - 0xb7, - 0xe1, - 0x0d, - 0xd2, - 0xc0, - 0x2d, - 0xec, - 0x64, - 0x88, - 0x80, - 0xea, - 0x34, - 0x6e, - 0xce, - 0x86, - 0xa7, - 0x82, - 0x0c, - 0x4f, - 0xa5, - 0x11, - 0x4f, - 0xb5, - 0x00, - 0xb2, - 0x64, - 0x5f, - 0x6c, - 0x97, - 0x20, - 0x92, - 0xdb, + 0x02, 0xb7, 0xe1, 0x0d, 0xd2, 0xc0, 0x2d, 0xec, 0x64, 0x88, 0x80, 0xea, 0x34, 0x6e, 0xce, 0x86, 0xa7, 0x82, 0x0c, 0x4f, 0xa5, 0x11, 0x4f, 0xb5, 0x00, 0xb2, 0x64, 0x5f, 0x6c, 0x97, 0x20, 0x92, 0xdb, // signatures - 0x00, - 0x02, + 0x00, 0x02, + ]; + + // hashing mode doesn't allow uncompressed keys + let bad_order_independent_p2wsh_uncompressed = TransactionSpendingCondition::OrderIndependentMultisig(OrderIndependentMultisigSpendingCondition { + signer: Hash160([0x11; 20]), + hash_mode: OrderIndependentMultisigHashMode::P2WSH, + nonce: 456, + tx_fee: 567, + fields: vec![ + TransactionAuthField::Signature(TransactionPublicKeyEncoding::Uncompressed, MessageSignature::from_raw(&vec![0xff; 65])), + TransactionAuthField::Signature(TransactionPublicKeyEncoding::Uncompressed, MessageSignature::from_raw(&vec![0xfe; 65])), + TransactionAuthField::PublicKey(PubKey::from_hex("04b7e10dd2c02dec648880ea346ece86a7820c4fa5114fb500b2645f6c972092dbe2334a653db0ab8d8ccffa6c35d3919e4cf8da3aeedafc7b9eb8235d0f2e7fdc").unwrap()), + ], + signatures_required: 2 + }); + + let bad_order_independent_p2wsh_uncompressed_bytes = vec![ + // hash mode + OrderIndependentMultisigHashMode::P2WSH as u8, + // signer + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + // nonce + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc8, + // fee rate + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x37, + // number of fields + 0x00, 0x00, 0x00, 0x03, + // signature + TransactionAuthFieldID::SignatureUncompressed as u8, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + // signature + TransactionAuthFieldID::SignatureUncompressed as u8, + 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, + // key + TransactionAuthFieldID::PublicKeyUncompressed as u8, + 0x02, 0xb7, 0xe1, 0x0d, 0xd2, 0xc0, 0x2d, 0xec, 0x64, 0x88, 0x80, 0xea, 0x34, 0x6e, 0xce, 0x86, 0xa7, 0x82, 0x0c, 0x4f, 0xa5, 0x11, 0x4f, 0xb5, 0x00, 0xb2, 0x64, 0x5f, 0x6c, 0x97, 0x20, 0x92, 0xdb, + // signatures + 0x00, 0x02, ]; // we can serialize the invalid p2wpkh uncompressed condition, but we can't deserialize it @@ -3516,6 +2229,13 @@ mod test { .unwrap(); assert_eq!(actual_bytes, bad_p2wsh_uncompressed_bytes); + // we can serialize the invalid p2wsh uncompressed condition, but we can't deserialize it + let mut actual_bytes = vec![]; + bad_order_independent_p2wsh_uncompressed + .consensus_serialize(&mut actual_bytes) + .unwrap(); + assert_eq!(actual_bytes, bad_order_independent_p2wsh_uncompressed_bytes); + assert!(TransactionSpendingCondition::consensus_deserialize( &mut &bad_public_key_count_bytes[..] ) @@ -3524,6 +2244,10 @@ mod test { &mut &bad_public_key_count_bytes_2[..] ) .is_err()); + assert!(TransactionSpendingCondition::consensus_deserialize( + &mut &bad_public_key_count_bytes_3[..] + ) + .is_err()); assert!( TransactionSpendingCondition::consensus_deserialize(&mut &bad_hash_mode_bytes[..]) .is_err() @@ -3532,6 +2256,10 @@ mod test { &mut &bad_hash_mode_multisig_bytes[..] ) .is_err()); + assert!(TransactionSpendingCondition::consensus_deserialize( + &mut &bad_hash_mode_order_independent_multisig_bytes[..] + ) + .is_err()); assert!(TransactionSpendingCondition::consensus_deserialize( &mut &bad_p2wpkh_uncompressed_bytes[..] ) @@ -3540,6 +2268,10 @@ mod test { &mut &bad_p2wsh_uncompressed_bytes[..] ) .is_err()); + assert!(TransactionSpendingCondition::consensus_deserialize( + &mut &bad_order_independent_p2wsh_uncompressed_bytes[..] + ) + .is_err()); // corrupt but will parse with trailing bits assert!(TransactionSpendingCondition::consensus_deserialize( diff --git a/stackslib/src/chainstate/stacks/block.rs b/stackslib/src/chainstate/stacks/block.rs index 074223a59c..5871474732 100644 --- a/stackslib/src/chainstate/stacks/block.rs +++ b/stackslib/src/chainstate/stacks/block.rs @@ -21,7 +21,7 @@ use std::io::{Read, Write}; use sha2::{Digest, Sha512_256}; use stacks_common::codec::{ - read_next, write_next, Error as codec_error, StacksMessageCodec, MAX_MESSAGE_LEN, + read_next, write_next, read_next_at_most_with_epoch, Error as codec_error, StacksMessageCodec, MAX_MESSAGE_LEN, DeserializeWithEpoch }; use stacks_common::types::chainstate::{ BlockHeaderHash, BurnchainHeaderHash, StacksBlockId, StacksWorkScore, TrieHash, VRFSeed, @@ -302,13 +302,22 @@ impl StacksMessageCodec for StacksBlock { Ok(()) } - fn consensus_deserialize(fd: &mut R) -> Result { + fn consensus_deserialize(_fd: &mut R) -> Result { + panic!("StacksBlock should be deserialized with consensus_deserialize_with_epoch instead") + } +} + +impl DeserializeWithEpoch for StacksBlock { + fn consensus_deserialize_with_epoch( + fd: &mut R, + epoch_id: StacksEpochId, + ) -> Result { // NOTE: don't worry about size clamps here; do that when receiving the data from the peer // network. This code assumes that the block will be small enough. let header: StacksBlockHeader = read_next(fd)?; let txs: Vec = { let mut bound_read = BoundReader::from_reader(fd, MAX_MESSAGE_LEN as u64); - read_next(&mut bound_read) + read_next_at_most_with_epoch(&mut bound_read, u32::MAX, epoch_id) }?; // there must be at least one transaction (the coinbase) @@ -567,38 +576,86 @@ impl StacksBlock { pub fn validate_transactions_static_epoch( txs: &[StacksTransaction], epoch_id: StacksEpochId, + quiet: bool, ) -> bool { for tx in txs.iter() { if let TransactionPayload::Coinbase(_, ref recipient_opt, ref proof_opt) = &tx.payload { if proof_opt.is_some() && epoch_id < StacksEpochId::Epoch30 { // not supported - error!("Coinbase with VRF proof not supported before Stacks 3.0"; "txid" => %tx.txid()); + if !quiet { + error!("Coinbase with VRF proof not supported before Stacks 3.0"; "txid" => %tx.txid()); + } return false; } if proof_opt.is_none() && epoch_id >= StacksEpochId::Epoch30 { // not supported - error!("Coinbase with VRF proof is required in Stacks 3.0 and later"; "txid" => %tx.txid()); + if !quiet { + error!("Coinbase with VRF proof is required in Stacks 3.0 and later"; "txid" => %tx.txid()); + } return false; } if recipient_opt.is_some() && epoch_id < StacksEpochId::Epoch21 { // not supported - error!("Coinbase pay-to-alt-recipient not supported before Stacks 2.1"; "txid" => %tx.txid()); + if !quiet { + error!("Coinbase pay-to-alt-recipient not supported before Stacks 2.1"; "txid" => %tx.txid()); + } return false; } } if let TransactionPayload::SmartContract(_, ref version_opt) = &tx.payload { if version_opt.is_some() && epoch_id < StacksEpochId::Epoch21 { // not supported - error!("Versioned smart contracts not supported before Stacks 2.1"); + if !quiet { + error!("Versioned smart contracts not supported before Stacks 2.1"); + } return false; } } if let TransactionPayload::TenureChange(..) = &tx.payload { if epoch_id < StacksEpochId::Epoch30 { - error!("TenureChange transaction not supported before Stacks 3.0"; "txid" => %tx.txid()); + if !quiet { + error!("TenureChange transaction not supported before Stacks 3.0"; "txid" => %tx.txid()); + } return false; } } + match &tx.auth { + TransactionAuth::Sponsored(ref origin, ref sponsor) => { + match origin { + TransactionSpendingCondition::OrderIndependentMultisig(..) => { + if epoch_id < StacksEpochId::Epoch30 { + if !quiet { + error!("Order independent multisig transactions not supported before Stacks 3.0"); + } + return false; + } + } + _ => (), + } + match sponsor { + TransactionSpendingCondition::OrderIndependentMultisig(..) => { + if epoch_id < StacksEpochId::Epoch30 { + if !quiet { + error!("Order independent multisig transactions not supported before Stacks 3.0"); + } + return false; + } + } + _ => (), + } + } + TransactionAuth::Standard(ref origin) => match origin { + TransactionSpendingCondition::OrderIndependentMultisig(..) => { + if epoch_id < StacksEpochId::Epoch30 { + if !quiet { + error!("Order independent multisig transactions not supported before Stacks 3.0"); + } + return false; + } + } + _ => (), + }, + }; } return true; } @@ -625,7 +682,7 @@ impl StacksBlock { if !StacksBlock::validate_coinbase(&self.txs, true) { return false; } - if !StacksBlock::validate_transactions_static_epoch(&self.txs, epoch_id) { + if !StacksBlock::validate_transactions_static_epoch(&self.txs, epoch_id, false) { return false; } return true; @@ -804,7 +861,66 @@ impl StacksMessageCodec for StacksMicroblock { let header: StacksMicroblockHeader = read_next(fd)?; let txs: Vec = { let mut bound_read = BoundReader::from_reader(fd, MAX_MESSAGE_LEN as u64); - read_next(&mut bound_read) + // The latest epoch where StacksMicroblock exist is Epoch25 + read_next_at_most_with_epoch(&mut bound_read, u32::MAX, StacksEpochId::Epoch25) + }?; + + if txs.len() == 0 { + warn!("Invalid microblock: zero transactions"); + return Err(codec_error::DeserializeError( + "Invalid microblock: zero transactions".to_string(), + )); + } + + if !StacksBlock::validate_transactions_unique(&txs) { + warn!("Invalid microblock: duplicate transaction"); + return Err(codec_error::DeserializeError( + "Invalid microblock: duplicate transaction".to_string(), + )); + } + + if !StacksBlock::validate_anchor_mode(&txs, false) { + warn!("Invalid microblock: found on-chain-only transaction"); + return Err(codec_error::DeserializeError( + "Invalid microblock: found on-chain-only transaction".to_string(), + )); + } + + // header and transactions must be consistent + let txid_vecs = txs.iter().map(|tx| tx.txid().as_bytes().to_vec()).collect(); + + let merkle_tree = MerkleTree::::new(&txid_vecs); + let tx_merkle_root = merkle_tree.root(); + + if tx_merkle_root != header.tx_merkle_root { + return Err(codec_error::DeserializeError( + "Invalid microblock: tx Merkle root mismatch".to_string(), + )); + } + + if !StacksBlock::validate_coinbase(&txs, false) { + warn!("Invalid microblock: found coinbase transaction"); + return Err(codec_error::DeserializeError( + "Invalid microblock: found coinbase transaction".to_string(), + )); + } + + Ok(StacksMicroblock { header, txs }) + } +} + +// This implementation is used for testing purposes, StacksMicroblock won't be used in Epoch 3.0 +impl DeserializeWithEpoch for StacksMicroblock { + fn consensus_deserialize_with_epoch( + fd: &mut R, + epoch_id: StacksEpochId, + ) -> Result { + // NOTE: maximum size must be checked elsewhere! + let header: StacksMicroblockHeader = read_next(fd)?; + let txs: Vec = { + let mut bound_read = BoundReader::from_reader(fd, MAX_MESSAGE_LEN as u64); + // The latest epoch where StacksMicroblock exist is Epoch24 + read_next_at_most_with_epoch(&mut bound_read, u32::MAX, StacksEpochId::Epoch24) }?; if txs.len() == 0 { @@ -1061,7 +1177,7 @@ mod test { signature: MessageSignature([0x0cu8; 65]), }; - let mut block = make_codec_test_block(100000000); + let mut block = make_codec_test_block(100000000, StacksEpochId::latest()); block.header.version = 0x24; let ph = block.header.parent_block.as_bytes().to_vec(); @@ -1114,7 +1230,11 @@ mod test { block_bytes.len(), block.txs.len() ); - check_codec_and_corruption::(&block, &block_bytes); + check_codec_and_corruption_with_epoch::( + &block, + &block_bytes, + StacksEpochId::latest(), + ); } #[test] @@ -1196,7 +1316,11 @@ mod test { txs: txs, }; - check_codec_and_corruption::(&mblock, &block_bytes); + check_codec_and_corruption_with_epoch::( + &mblock, + &block_bytes, + StacksEpochId::latest(), + ); } } @@ -1540,11 +1664,14 @@ mod test { for (ref block, ref msg) in invalid_blocks.iter() { let mut bytes: Vec = vec![]; block.consensus_serialize(&mut bytes).unwrap(); - assert!(StacksBlock::consensus_deserialize(&mut &bytes[..]) - .unwrap_err() - .to_string() - .find(msg) - .is_some()); + assert!(StacksBlock::consensus_deserialize_with_epoch( + &mut &bytes[..], + StacksEpochId::Epoch25, + ) + .unwrap_err() + .to_string() + .find(msg) + .is_some()); } } @@ -1672,6 +1799,147 @@ mod test { } } + fn verify_block_epoch_validation( + txs: &[StacksTransaction], + tx_coinbase_old: StacksTransaction, + tx_coinbase_nakamoto: StacksTransaction, + activation_epoch_id: StacksEpochId, + header: StacksBlockHeader, + need_to_include_coinbase_old: bool, + need_to_include_coinbase_nakamoto: bool, + deactivation_epoch_id: Option, + ) { + let epoch_list = [ + StacksEpochId::Epoch10, + StacksEpochId::Epoch20, + StacksEpochId::Epoch2_05, + StacksEpochId::Epoch21, + StacksEpochId::Epoch22, + StacksEpochId::Epoch23, + StacksEpochId::Epoch24, + StacksEpochId::Epoch25, + StacksEpochId::Epoch30, + ]; + let get_tx_root = |txs: &Vec| { + let txid_vecs = txs.iter().map(|tx| tx.txid().as_bytes().to_vec()).collect(); + + let merkle_tree = MerkleTree::::new(&txid_vecs); + let tx_merkle_root = merkle_tree.root(); + tx_merkle_root + }; + let mut block_header_dup_tx = header.clone(); + block_header_dup_tx.tx_merkle_root = get_tx_root(&txs.to_vec()); + + let block = StacksBlock { + header: block_header_dup_tx.clone(), + txs: txs.to_vec(), + }; + + let mut txs_with_coinbase = txs.to_vec(); + txs_with_coinbase.insert(0, tx_coinbase_old); + + let mut block_header_dup_tx_with_coinbase = header.clone(); + block_header_dup_tx_with_coinbase.tx_merkle_root = get_tx_root(&txs_with_coinbase.to_vec()); + + let block_with_coinbase_tx = StacksBlock { + header: block_header_dup_tx_with_coinbase.clone(), + txs: txs_with_coinbase, + }; + + let mut txs_with_coinbase_nakamoto = txs.to_vec(); + txs_with_coinbase_nakamoto.insert(0, tx_coinbase_nakamoto); + + let mut block_header_dup_tx_with_coinbase_nakamoto = header.clone(); + block_header_dup_tx_with_coinbase_nakamoto.tx_merkle_root = + get_tx_root(&txs_with_coinbase_nakamoto.to_vec()); + + let block_with_coinbase_tx_nakamoto = StacksBlock { + header: block_header_dup_tx_with_coinbase_nakamoto.clone(), + txs: txs_with_coinbase_nakamoto, + }; + + for epoch_id in epoch_list.iter() { + let block_to_check = + if *epoch_id >= StacksEpochId::Epoch30 || need_to_include_coinbase_nakamoto { + block_with_coinbase_tx_nakamoto.clone() + } else if *epoch_id >= StacksEpochId::Epoch21 + && *epoch_id < StacksEpochId::Epoch30 + && need_to_include_coinbase_old + { + block_with_coinbase_tx.clone() + } else { + block.clone() + }; + + let mut bytes: Vec = vec![]; + block_to_check.consensus_serialize(&mut bytes).unwrap(); + + if *epoch_id < activation_epoch_id { + assert!(!StacksBlock::validate_transactions_static_epoch( + &txs, + epoch_id.clone(), + false, + )); + + for tx in txs.iter() { + let mut bytes: Vec = vec![]; + tx.consensus_serialize(&mut bytes).unwrap(); + + assert!(StacksTransaction::consensus_deserialize_with_epoch( + &mut &bytes[..], + *epoch_id + ) + .unwrap_err() + .to_string() + .find("target epoch is not activated") + .is_some()); + } + + assert!( + StacksBlock::consensus_deserialize_with_epoch(&mut &bytes[..], *epoch_id) + .unwrap_err() + .to_string() + .find("target epoch is not activated") + .is_some() + ); + } else if deactivation_epoch_id.is_none() || deactivation_epoch_id.unwrap() > *epoch_id { + assert!(StacksBlock::validate_transactions_static_epoch( + &txs, + *epoch_id, + false, + )); + + for tx in txs.iter() { + let mut bytes: Vec = vec![]; + tx.consensus_serialize(&mut bytes).unwrap(); + + StacksTransaction::consensus_deserialize_with_epoch(&mut &bytes[..], *epoch_id) + .unwrap(); + } + + StacksBlock::consensus_deserialize_with_epoch(&mut &bytes[..], *epoch_id).unwrap(); + } else { + for tx in txs.iter() { + let mut bytes: Vec = vec![]; + tx.consensus_serialize(&mut bytes).unwrap(); + + let _ = StacksTransaction::consensus_deserialize_with_epoch( + &mut &bytes[..], + *epoch_id + ).unwrap_err(); + } + + let _ = StacksBlock::consensus_deserialize_with_epoch(&mut &bytes[..], *epoch_id).unwrap_err(); + + assert!(!StacksBlock::validate_transactions_static_epoch( + &txs, + *epoch_id, + false, + )); + } + } + } + #[test] fn test_block_validate_transactions_static() { let header = StacksBlockHeader { @@ -1689,6 +1957,11 @@ mod test { microblock_pubkey_hash: Hash160([9u8; 20]), }; + let stx_address = StacksAddress { + version: 0, + bytes: Hash160([0u8; 20]), + }; + let privk = StacksPrivateKey::from_hex( "6d430bb91222408e7706c9001cfaeb91b08c2be6d5ac95779ab52c6b431950e001", ) @@ -1699,6 +1972,135 @@ mod test { )) .unwrap(), ); + + let privk_1 = StacksPrivateKey::from_hex( + "6d430bb91222408e7706c9001cfaeb91b08c2be6d5ac95779ab52c6b431950e001", + ) + .unwrap(); + let privk_2 = StacksPrivateKey::from_hex( + "2a584d899fed1d24e26b524f202763c8ab30260167429f157f1c119f550fa6af01", + ) + .unwrap(); + let privk_3 = StacksPrivateKey::from_hex( + "d5200dee706ee53ae98a03fba6cf4fdcc5084c30cfa9e1b3462dcdeaa3e0f1d201", + ) + .unwrap(); + + let pubk_1 = StacksPublicKey::from_private(&privk_1); + let pubk_2 = StacksPublicKey::from_private(&privk_2); + let pubk_3 = StacksPublicKey::from_private(&privk_3); + + let order_independent_multisig_condition_p2wsh = + TransactionSpendingCondition::new_multisig_order_independent_p2wsh( + 2, + vec![pubk_1.clone(), pubk_2.clone(), pubk_3.clone()], + ) + .unwrap(); + + let order_independent_multisig_condition_p2sh = + TransactionSpendingCondition::new_multisig_order_independent_p2sh( + 2, + vec![pubk_1.clone(), pubk_2.clone(), pubk_3.clone()], + ) + .unwrap(); + + let order_independent_sponsored_auth_p2sh = TransactionAuth::Sponsored( + TransactionSpendingCondition::new_singlesig_p2pkh(StacksPublicKey::from_private( + &privk, + )) + .unwrap(), + order_independent_multisig_condition_p2sh.clone(), + ); + + let order_independent_sponsored_auth_p2wsh = TransactionAuth::Sponsored( + TransactionSpendingCondition::new_singlesig_p2pkh(StacksPublicKey::from_private( + &privk, + )) + .unwrap(), + order_independent_multisig_condition_p2wsh.clone(), + ); + let order_independent_origin_auth_p2sh = + TransactionAuth::Standard(order_independent_multisig_condition_p2sh.clone()); + + let order_independent_origin_auth_p2wsh = + TransactionAuth::Standard(order_independent_multisig_condition_p2wsh.clone()); + + let order_independent_multisig_tx_transfer_mainnet_p2sh = StacksTransaction::new( + TransactionVersion::Mainnet, + order_independent_origin_auth_p2sh.clone(), + TransactionPayload::TokenTransfer( + stx_address.into(), + 123, + TokenTransferMemo([1u8; 34]), + ), + ); + + let order_independent_multisig_tx_transfer_mainnet_p2wsh = StacksTransaction::new( + TransactionVersion::Mainnet, + order_independent_origin_auth_p2wsh.clone(), + TransactionPayload::TokenTransfer( + stx_address.into(), + 123, + TokenTransferMemo([1u8; 34]), + ), + ); + + let order_independent_sponsored_multisig_tx_transfer_mainnet_p2sh = StacksTransaction::new( + TransactionVersion::Mainnet, + order_independent_sponsored_auth_p2sh.clone(), + TransactionPayload::TokenTransfer( + stx_address.into(), + 123, + TokenTransferMemo([1u8; 34]), + ), + ); + + let order_independent_sponsored_multisig_tx_transfer_mainnet_p2wsh = StacksTransaction::new( + TransactionVersion::Mainnet, + order_independent_sponsored_auth_p2wsh.clone(), + TransactionPayload::TokenTransfer( + stx_address.into(), + 123, + TokenTransferMemo([1u8; 34]), + ), + ); + + let mut tx_signer = + StacksTransactionSigner::new(&order_independent_multisig_tx_transfer_mainnet_p2sh); + tx_signer.sign_origin(&privk_1).unwrap(); + tx_signer.sign_origin(&privk_2).unwrap(); + tx_signer.append_origin(&pubk_3).unwrap(); + let order_independent_multisig_tx_transfer_mainnet_p2sh_signed = + tx_signer.get_tx().unwrap(); + + let mut tx_signer = + StacksTransactionSigner::new(&order_independent_multisig_tx_transfer_mainnet_p2wsh); + tx_signer.sign_origin(&privk_1).unwrap(); + tx_signer.sign_origin(&privk_2).unwrap(); + tx_signer.append_origin(&pubk_3).unwrap(); + let order_independent_multisig_tx_transfer_mainnet_p2wsh_signed = + tx_signer.get_tx().unwrap(); + + let mut tx_signer = StacksTransactionSigner::new( + &order_independent_sponsored_multisig_tx_transfer_mainnet_p2sh, + ); + tx_signer.sign_origin(&privk).unwrap(); + tx_signer.sign_sponsor(&privk_1).unwrap(); + tx_signer.sign_sponsor(&privk_2).unwrap(); + tx_signer.append_sponsor(&pubk_3).unwrap(); + let order_independent_sponsored_multisig_tx_transfer_mainnet_p2sh_signed = + tx_signer.get_tx().unwrap(); + + let mut tx_signer = StacksTransactionSigner::new( + &order_independent_sponsored_multisig_tx_transfer_mainnet_p2wsh, + ); + tx_signer.sign_origin(&privk).unwrap(); + tx_signer.sign_sponsor(&privk_1).unwrap(); + tx_signer.sign_sponsor(&privk_2).unwrap(); + tx_signer.append_sponsor(&pubk_3).unwrap(); + let order_independent_sponsored_multisig_tx_transfer_mainnet_p2wsh_signed = + tx_signer.get_tx().unwrap(); + let tx_coinbase = StacksTransaction::new( TransactionVersion::Testnet, origin_auth.clone(), @@ -1808,6 +2210,12 @@ mod test { let nakamoto_coinbase = vec![tx_coinbase_proof.clone()]; let tenure_change_tx = vec![tx_tenure_change.clone()]; let nakamoto_txs = vec![tx_coinbase_proof.clone(), tx_tenure_change.clone()]; + let order_independent_multisig_txs = vec![ + order_independent_multisig_tx_transfer_mainnet_p2sh_signed.clone(), + order_independent_sponsored_multisig_tx_transfer_mainnet_p2sh_signed.clone(), + order_independent_multisig_tx_transfer_mainnet_p2wsh_signed.clone(), + order_independent_sponsored_multisig_tx_transfer_mainnet_p2wsh_signed.clone(), + ]; assert!(!StacksBlock::validate_transactions_unique(&dup_txs)); assert!(!StacksBlock::validate_transactions_network( @@ -1820,47 +2228,67 @@ mod test { )); assert!(!StacksBlock::validate_anchor_mode(&offchain_txs, true)); assert!(!StacksBlock::validate_coinbase(&no_coinbase, true)); - assert!(!StacksBlock::validate_transactions_static_epoch( - &coinbase_contract, - StacksEpochId::Epoch2_05 - )); - assert!(StacksBlock::validate_transactions_static_epoch( - &coinbase_contract, - StacksEpochId::Epoch21 - )); - assert!(!StacksBlock::validate_transactions_static_epoch( - &versioned_contract, - StacksEpochId::Epoch2_05 - )); - assert!(StacksBlock::validate_transactions_static_epoch( + verify_block_epoch_validation( &versioned_contract, - StacksEpochId::Epoch21 - )); - assert!(!StacksBlock::validate_transactions_static_epoch( - &nakamoto_coinbase, - StacksEpochId::Epoch21 - )); - assert!(StacksBlock::validate_transactions_static_epoch( - &nakamoto_coinbase, - StacksEpochId::Epoch30 - )); - assert!(!StacksBlock::validate_transactions_static_epoch( + tx_coinbase.clone(), + tx_coinbase_proof.clone(), + StacksEpochId::Epoch21, + header.clone(), + true, + true, + None, + ); + verify_block_epoch_validation( &coinbase_contract, - StacksEpochId::Epoch30 - )); - assert!(!StacksBlock::validate_transactions_static_epoch( - &tenure_change_tx, - StacksEpochId::Epoch21 - )); - assert!(StacksBlock::validate_transactions_static_epoch( - &nakamoto_txs, - StacksEpochId::Epoch30 - )); - assert!(!StacksBlock::validate_transactions_static_epoch( + tx_coinbase.clone(), + tx_coinbase_proof.clone(), + StacksEpochId::Epoch21, + header.clone(), + false, + false, + Some(StacksEpochId::Epoch30), + ); + verify_block_epoch_validation( + &order_independent_multisig_txs, + tx_coinbase.clone(), + tx_coinbase_proof.clone(), + StacksEpochId::Epoch30, + header.clone(), + true, + true, + None, + ); + verify_block_epoch_validation( &nakamoto_txs, - StacksEpochId::Epoch21 - )); + tx_coinbase.clone(), + tx_coinbase_proof.clone(), + StacksEpochId::Epoch30, + header.clone(), + true, + false, + None, + ); + verify_block_epoch_validation( + &nakamoto_coinbase, + tx_coinbase.clone(), + tx_coinbase_proof.clone(), + StacksEpochId::Epoch30, + header.clone(), + true, + false, + None, + ); + verify_block_epoch_validation( + &tenure_change_tx, + tx_coinbase.clone(), + tx_coinbase_proof.clone(), + StacksEpochId::Epoch30, + header.clone(), + true, + true, + None, + ); } // TODO: diff --git a/stackslib/src/chainstate/stacks/db/blocks.rs b/stackslib/src/chainstate/stacks/db/blocks.rs index 4412c9d1ea..878bdf29ea 100644 --- a/stackslib/src/chainstate/stacks/db/blocks.rs +++ b/stackslib/src/chainstate/stacks/db/blocks.rs @@ -38,7 +38,7 @@ use rand::{thread_rng, Rng, RngCore}; use rusqlite::{Connection, DatabaseName, Error as sqlite_error, OptionalExtension}; use serde::Serialize; use serde_json::json; -use stacks_common::codec::{read_next, write_next, MAX_MESSAGE_LEN}; +use stacks_common::codec::{read_next, write_next, DeserializeWithEpoch, MAX_MESSAGE_LEN}; use stacks_common::types::chainstate::{ BurnchainHeaderHash, SortitionId, StacksAddress, StacksBlockId, }; @@ -575,6 +575,28 @@ impl StacksChainState { Ok(inst) } + pub fn consensus_load_with_epoch( + path: &str, + epoch_id: StacksEpochId, + ) -> Result { + let mut fd = fs::OpenOptions::new() + .read(true) + .write(false) + .open(path) + .map_err(|e| { + if e.kind() == io::ErrorKind::NotFound { + Error::DBError(db_error::NotFoundError) + } else { + Error::DBError(db_error::IOError(e)) + } + })?; + + let mut bound_reader = BoundReader::from_reader(&mut fd, MAX_MESSAGE_LEN as u64); + let inst = T::consensus_deserialize_with_epoch(&mut bound_reader, epoch_id) + .map_err(Error::CodecError)?; + Ok(inst) + } + /// Do we have a stored a block in the chunk store? /// Will be true even if it's invalid. pub fn has_block_indexed( @@ -847,7 +869,8 @@ impl StacksChainState { return Ok(None); } - let block: StacksBlock = StacksChainState::consensus_load(&block_path)?; + let block: StacksBlock = + StacksChainState::consensus_load_with_epoch(&block_path, StacksEpochId::latest())?; Ok(Some(block)) } @@ -1052,7 +1075,10 @@ impl StacksChainState { return Ok(None); } - match StacksBlock::consensus_deserialize(&mut &staging_block.block_data[..]) { + match StacksBlock::consensus_deserialize_with_epoch( + &mut &staging_block.block_data[..], + StacksEpochId::latest(), + ) { Ok(block) => Ok(Some(block)), Err(e) => Err(Error::CodecError(e)), } @@ -4493,10 +4519,10 @@ impl StacksChainState { // strictly speaking this check is defensive. It will never be the case // that a `miner_reward` has a `recipient_contract` that is `Some(..)` // unless the block was mined in Epoch 2.1. But you can't be too - // careful... + // careful... if evaluated_epoch >= StacksEpochId::Epoch21 { // in 2.1 or later, the coinbase may optionally specify a contract into - // which the tokens get sent. If this is not given, then they are sent + // which the tokens get sent. If this is not given, then they are sent // to the miner address. miner_reward.recipient.clone() } @@ -5735,10 +5761,16 @@ impl StacksChainState { } /// Extract and parse the block from a loaded staging block, and verify its integrity. - fn extract_stacks_block(next_staging_block: &StagingBlock) -> Result { + fn extract_stacks_block( + next_staging_block: &StagingBlock, + epoch_id: StacksEpochId, + ) -> Result { let block = { - StacksBlock::consensus_deserialize(&mut &next_staging_block.block_data[..]) - .map_err(Error::CodecError)? + StacksBlock::consensus_deserialize_with_epoch( + &mut &next_staging_block.block_data[..], + epoch_id, + ) + .map_err(Error::CodecError)? }; let block_hash = block.block_hash(); @@ -5869,7 +5901,13 @@ impl StacksChainState { None => return Ok((None, None)), }; - let block = StacksChainState::extract_stacks_block(&next_staging_block)?; + let epoch_id = SortitionDB::get_stacks_epoch(sort_tx, burn_header_height as u64)? + .expect(&format!( + "FATAL: no epoch defined at burn height {}", + burn_header_height + )) + .epoch_id; + let block = StacksChainState::extract_stacks_block(&next_staging_block, epoch_id)?; let block_size = u64::try_from(next_staging_block.block_data.len()) .expect("FATAL: more than 2^64 transactions"); @@ -6434,7 +6472,19 @@ impl StacksChainState { )); } - // 4: the account nonces must be correct + // 4: check if transaction is valid in the current epoch + let epoch = clarity_connection.get_epoch().clone(); + + let is_transaction_valid_in_epoch = + StacksBlock::validate_transactions_static_epoch(&vec![tx.clone()], epoch, true); + + if !is_transaction_valid_in_epoch { + return Err(MemPoolRejection::Other( + "Transaction is not supported in this epoch".to_string(), + )); + } + + // 5: the account nonces must be correct let (origin, payer) = match StacksChainState::check_transaction_nonces(clarity_connection, &tx, true) { Ok(x) => x, @@ -6493,7 +6543,7 @@ impl StacksChainState { ) }); - // 5: the paying account must have enough funds + // 6: the paying account must have enough funds if !payer.stx_balance.can_transfer_at_burn_block( u128::from(fee), block_height, @@ -6519,7 +6569,7 @@ impl StacksChainState { } } - // 6: payload-specific checks + // 7: payload-specific checks match &tx.payload { TransactionPayload::TokenTransfer(addr, amount, _memo) => { // version byte matches? @@ -6622,7 +6672,7 @@ impl StacksChainState { } if let Some(_version) = version_opt.as_ref() { - if clarity_connection.get_epoch() < StacksEpochId::Epoch21 { + if epoch < StacksEpochId::Epoch21 { return Err(MemPoolRejection::Other( "Versioned smart contract transactions are not supported in this epoch" .to_string(), diff --git a/stackslib/src/chainstate/stacks/db/transactions.rs b/stackslib/src/chainstate/stacks/db/transactions.rs index b6d30aac0a..8702d4dc4a 100644 --- a/stackslib/src/chainstate/stacks/db/transactions.rs +++ b/stackslib/src/chainstate/stacks/db/transactions.rs @@ -1403,6 +1403,17 @@ impl StacksChainState { return Err(Error::InvalidStacksTransaction(msg, false)); } } + let is_transaction_valid_in_epoch = + StacksBlock::validate_transactions_static_epoch(&vec![tx.clone()], epoch, quiet); + + if !is_transaction_valid_in_epoch { + let msg = format!( + "Invalid transaction {}: target epoch is not activated", + tx.txid() + ); + warn!("{}", &msg); + return Err(Error::InvalidStacksTransaction(msg, false)); + } let mut transaction = clarity_block.connection().start_transaction_processing(); @@ -8947,7 +8958,7 @@ pub mod test { (as-contract (stx-transfer? amount tx-sender recipient)) ) - + (stx-transfer? u500000000 tx-sender (as-contract tx-sender)) "#; @@ -9112,7 +9123,7 @@ pub mod test { (as-contract (stx-transfer? amount tx-sender recipient)) ) - + (stx-transfer? u500000000 tx-sender (as-contract tx-sender)) "#; @@ -9348,6 +9359,28 @@ pub mod test { let mut chainstate = instantiate_chainstate_with_balances(false, 0x80000000, function_name!(), balances); + let mut tx_runtime_checkerror_trait_no_version = StacksTransaction::new( + TransactionVersion::Testnet, + auth.clone(), + TransactionPayload::new_smart_contract( + &"foo".to_string(), + &runtime_checkerror_trait.to_string(), + None, + ) + .unwrap(), + ); + + tx_runtime_checkerror_trait_no_version.post_condition_mode = + TransactionPostConditionMode::Allow; + tx_runtime_checkerror_trait_no_version.chain_id = 0x80000000; + tx_runtime_checkerror_trait_no_version.set_tx_fee(1); + tx_runtime_checkerror_trait_no_version.set_origin_nonce(0); + + let mut signer = StacksTransactionSigner::new(&tx_runtime_checkerror_trait_no_version); + signer.sign_origin(&privk).unwrap(); + + let signed_runtime_checkerror_trait_tx_no_version = signer.get_tx().unwrap(); + let mut tx_runtime_checkerror_trait = StacksTransaction::new( TransactionVersion::Testnet, auth.clone(), @@ -9390,6 +9423,28 @@ pub mod test { let signed_runtime_checkerror_impl_tx = signer.get_tx().unwrap(); + let mut tx_runtime_checkerror_impl_no_version = StacksTransaction::new( + TransactionVersion::Testnet, + auth.clone(), + TransactionPayload::new_smart_contract( + &"foo-impl".to_string(), + &runtime_checkerror_impl.to_string(), + None, + ) + .unwrap(), + ); + + tx_runtime_checkerror_impl_no_version.post_condition_mode = + TransactionPostConditionMode::Allow; + tx_runtime_checkerror_impl_no_version.chain_id = 0x80000000; + tx_runtime_checkerror_impl_no_version.set_tx_fee(1); + tx_runtime_checkerror_impl_no_version.set_origin_nonce(1); + + let mut signer = StacksTransactionSigner::new(&tx_runtime_checkerror_impl_no_version); + signer.sign_origin(&privk).unwrap(); + + let signed_runtime_checkerror_impl_tx_no_version = signer.get_tx().unwrap(); + let mut tx_runtime_checkerror_clar1 = StacksTransaction::new( TransactionVersion::Testnet, auth.clone(), @@ -9411,6 +9466,28 @@ pub mod test { let signed_runtime_checkerror_tx_clar1 = signer.get_tx().unwrap(); + let mut tx_runtime_checkerror_clar1_no_version = StacksTransaction::new( + TransactionVersion::Testnet, + auth.clone(), + TransactionPayload::new_smart_contract( + &"trait-checkerror".to_string(), + &runtime_checkerror.to_string(), + None, + ) + .unwrap(), + ); + + tx_runtime_checkerror_clar1_no_version.post_condition_mode = + TransactionPostConditionMode::Allow; + tx_runtime_checkerror_clar1_no_version.chain_id = 0x80000000; + tx_runtime_checkerror_clar1_no_version.set_tx_fee(1); + tx_runtime_checkerror_clar1_no_version.set_origin_nonce(2); + + let mut signer = StacksTransactionSigner::new(&tx_runtime_checkerror_clar1_no_version); + signer.sign_origin(&privk).unwrap(); + + let signed_runtime_checkerror_tx_clar1_no_version = signer.get_tx().unwrap(); + let mut tx_runtime_checkerror_clar2 = StacksTransaction::new( TransactionVersion::Testnet, auth.clone(), @@ -9478,6 +9555,29 @@ pub mod test { let signed_runtime_checkerror_cc_contract_tx_clar1 = signer.get_tx().unwrap(); + let mut tx_runtime_checkerror_cc_contract_clar1_no_version = StacksTransaction::new( + TransactionVersion::Testnet, + auth.clone(), + TransactionPayload::new_smart_contract( + &"trait-checkerror-cc".to_string(), + &runtime_checkerror_contract.to_string(), + None, + ) + .unwrap(), + ); + + tx_runtime_checkerror_cc_contract_clar1_no_version.post_condition_mode = + TransactionPostConditionMode::Allow; + tx_runtime_checkerror_cc_contract_clar1_no_version.chain_id = 0x80000000; + tx_runtime_checkerror_cc_contract_clar1_no_version.set_tx_fee(1); + tx_runtime_checkerror_cc_contract_clar1_no_version.set_origin_nonce(3); + + let mut signer = + StacksTransactionSigner::new(&tx_runtime_checkerror_cc_contract_clar1_no_version); + signer.sign_origin(&privk).unwrap(); + + let signed_runtime_checkerror_cc_contract_tx_clar1_no_version = signer.get_tx().unwrap(); + let mut tx_runtime_checkerror_cc_contract_clar2 = StacksTransaction::new( TransactionVersion::Testnet, auth.clone(), @@ -9516,7 +9616,7 @@ pub mod test { let (fee, _) = StacksChainState::process_transaction( &mut conn, - &signed_runtime_checkerror_trait_tx, + &signed_runtime_checkerror_trait_tx_no_version, false, ASTRules::PrecheckSize, ) @@ -9525,7 +9625,7 @@ pub mod test { let (fee, _) = StacksChainState::process_transaction( &mut conn, - &signed_runtime_checkerror_impl_tx, + &signed_runtime_checkerror_impl_tx_no_version, false, ASTRules::PrecheckSize, ) @@ -9534,7 +9634,7 @@ pub mod test { let (fee, _) = StacksChainState::process_transaction( &mut conn, - &signed_runtime_checkerror_tx_clar1, + &signed_runtime_checkerror_tx_clar1_no_version, false, ASTRules::PrecheckSize, ) @@ -9555,12 +9655,52 @@ pub mod test { } else { panic!("Did not get unchecked interpreter error"); } + + let err = StacksChainState::process_transaction( + &mut conn, + &signed_runtime_checkerror_impl_tx, + false, + ASTRules::PrecheckSize, + ) + .unwrap_err(); + if let Error::InvalidStacksTransaction(msg, _ignored) = err { + assert!(msg.find("target epoch is not activated").is_some()); + } else { + panic!("Did not get epoch is not activated error"); + } + + let err = StacksChainState::process_transaction( + &mut conn, + &signed_runtime_checkerror_tx_clar1, + false, + ASTRules::PrecheckSize, + ) + .unwrap_err(); + if let Error::InvalidStacksTransaction(msg, _ignored) = err { + assert!(msg.find("target epoch is not activated").is_some()); + } else { + panic!("Did not get epoch is not activated error"); + } + + let err = StacksChainState::process_transaction( + &mut conn, + &signed_runtime_checkerror_trait_tx, + false, + ASTRules::PrecheckSize, + ) + .unwrap_err(); + if let Error::InvalidStacksTransaction(msg, _ignored) = err { + assert!(msg.find("target epoch is not activated").is_some()); + } else { + panic!("Did not get epoch is not activated error"); + } + let acct = StacksChainState::get_account(&mut conn, &addr.into()); assert_eq!(acct.nonce, 3); let err = StacksChainState::process_transaction( &mut conn, - &signed_runtime_checkerror_cc_contract_tx_clar1, + &signed_runtime_checkerror_cc_contract_tx_clar1_no_version, false, ASTRules::PrecheckSize, ) @@ -9579,7 +9719,7 @@ pub mod test { // in 2.05, this invalidates the block let mut conn = chainstate.block_begin( - &TestBurnStateDB_20, + &TestBurnStateDB_2_05, &FIRST_BURNCHAIN_CONSENSUS_HASH, &FIRST_STACKS_BLOCK_HASH, &ConsensusHash([2u8; 20]), @@ -9588,7 +9728,7 @@ pub mod test { let (fee, _) = StacksChainState::process_transaction( &mut conn, - &signed_runtime_checkerror_trait_tx, + &signed_runtime_checkerror_trait_tx_no_version, false, ASTRules::PrecheckSize, ) @@ -9597,7 +9737,7 @@ pub mod test { let (fee, _) = StacksChainState::process_transaction( &mut conn, - &signed_runtime_checkerror_impl_tx, + &signed_runtime_checkerror_impl_tx_no_version, false, ASTRules::PrecheckSize, ) @@ -9606,7 +9746,7 @@ pub mod test { let (fee, _) = StacksChainState::process_transaction( &mut conn, - &signed_runtime_checkerror_tx_clar1, + &signed_runtime_checkerror_tx_clar1_no_version, false, ASTRules::PrecheckSize, ) @@ -9627,12 +9767,51 @@ pub mod test { } else { panic!("Did not get unchecked interpreter error"); } + + let err = StacksChainState::process_transaction( + &mut conn, + &signed_runtime_checkerror_impl_tx, + false, + ASTRules::PrecheckSize, + ) + .unwrap_err(); + if let Error::InvalidStacksTransaction(msg, _ignored) = err { + assert!(msg.find("target epoch is not activated").is_some()); + } else { + panic!("Did not get epoch is not activated error"); + } + + let err = StacksChainState::process_transaction( + &mut conn, + &signed_runtime_checkerror_tx_clar1, + false, + ASTRules::PrecheckSize, + ) + .unwrap_err(); + if let Error::InvalidStacksTransaction(msg, _ignored) = err { + assert!(msg.find("target epoch is not activated").is_some()); + } else { + panic!("Did not get epoch is not activated error"); + } + + let err = StacksChainState::process_transaction( + &mut conn, + &signed_runtime_checkerror_trait_tx, + false, + ASTRules::PrecheckSize, + ) + .unwrap_err(); + if let Error::InvalidStacksTransaction(msg, _ignored) = err { + assert!(msg.find("target epoch is not activated").is_some()); + } else { + panic!("Did not get epoch is not activated error"); + } let acct = StacksChainState::get_account(&mut conn, &addr.into()); assert_eq!(acct.nonce, 3); let err = StacksChainState::process_transaction( &mut conn, - &signed_runtime_checkerror_cc_contract_tx_clar1, + &signed_runtime_checkerror_cc_contract_tx_clar1_no_version, false, ASTRules::PrecheckSize, ) @@ -9885,6 +10064,27 @@ pub mod test { let signed_foo_trait_tx = signer.get_tx().unwrap(); + let mut tx_foo_trait_no_version = StacksTransaction::new( + TransactionVersion::Testnet, + auth.clone(), + TransactionPayload::new_smart_contract( + &"foo".to_string(), + &foo_trait.to_string(), + None, + ) + .unwrap(), + ); + + tx_foo_trait_no_version.post_condition_mode = TransactionPostConditionMode::Allow; + tx_foo_trait_no_version.chain_id = 0x80000000; + tx_foo_trait_no_version.set_tx_fee(1); + tx_foo_trait_no_version.set_origin_nonce(0); + + let mut signer = StacksTransactionSigner::new(&tx_foo_trait_no_version); + signer.sign_origin(&privk).unwrap(); + + let signed_foo_trait_tx_no_version = signer.get_tx().unwrap(); + let mut tx_foo_impl = StacksTransaction::new( TransactionVersion::Testnet, auth.clone(), @@ -9906,6 +10106,27 @@ pub mod test { let signed_foo_impl_tx = signer.get_tx().unwrap(); + let mut tx_foo_impl_no_version = StacksTransaction::new( + TransactionVersion::Testnet, + auth.clone(), + TransactionPayload::new_smart_contract( + &"foo-impl".to_string(), + &foo_impl.to_string(), + None, + ) + .unwrap(), + ); + + tx_foo_impl_no_version.post_condition_mode = TransactionPostConditionMode::Allow; + tx_foo_impl_no_version.chain_id = 0x80000000; + tx_foo_impl_no_version.set_tx_fee(1); + tx_foo_impl_no_version.set_origin_nonce(1); + + let mut signer = StacksTransactionSigner::new(&tx_foo_impl_no_version); + signer.sign_origin(&privk).unwrap(); + + let signed_foo_impl_tx_no_version = signer.get_tx().unwrap(); + let mut tx_call_foo_clar1 = StacksTransaction::new( TransactionVersion::Testnet, auth.clone(), @@ -9927,6 +10148,27 @@ pub mod test { let signed_call_foo_tx_clar1 = signer.get_tx().unwrap(); + let mut tx_call_foo_clar1_no_version = StacksTransaction::new( + TransactionVersion::Testnet, + auth.clone(), + TransactionPayload::new_smart_contract( + &"call-foo".to_string(), + &call_foo.to_string(), + None, + ) + .unwrap(), + ); + + tx_call_foo_clar1_no_version.post_condition_mode = TransactionPostConditionMode::Allow; + tx_call_foo_clar1_no_version.chain_id = 0x80000000; + tx_call_foo_clar1_no_version.set_tx_fee(1); + tx_call_foo_clar1_no_version.set_origin_nonce(2); + + let mut signer = StacksTransactionSigner::new(&tx_call_foo_clar1_no_version); + signer.sign_origin(&privk).unwrap(); + + let signed_call_foo_tx_clar1_no_version = signer.get_tx().unwrap(); + let mut tx_call_foo_clar2 = StacksTransaction::new( TransactionVersion::Testnet, auth.clone(), @@ -9989,7 +10231,7 @@ pub mod test { let (fee, _) = StacksChainState::process_transaction( &mut conn, - &signed_foo_trait_tx, + &signed_foo_trait_tx_no_version, false, ASTRules::PrecheckSize, ) @@ -9998,7 +10240,7 @@ pub mod test { let (fee, _) = StacksChainState::process_transaction( &mut conn, - &signed_foo_impl_tx, + &signed_foo_impl_tx_no_version, false, ASTRules::PrecheckSize, ) @@ -10007,7 +10249,7 @@ pub mod test { let (fee, tx_receipt) = StacksChainState::process_transaction( &mut conn, - &signed_call_foo_tx_clar1, + &signed_call_foo_tx_clar1_no_version, false, ASTRules::PrecheckSize, ) @@ -10021,11 +10263,50 @@ pub mod test { _ => panic!("expected the contract publish to fail"), } + let err = StacksChainState::process_transaction( + &mut conn, + &signed_foo_trait_tx, + false, + ASTRules::PrecheckSize, + ) + .unwrap_err(); + if let Error::InvalidStacksTransaction(msg, _ignored) = err { + assert!(msg.find("target epoch is not activated").is_some()); + } else { + panic!("Did not get epoch is not activated error"); + } + + let err = StacksChainState::process_transaction( + &mut conn, + &signed_foo_impl_tx, + false, + ASTRules::PrecheckSize, + ) + .unwrap_err(); + if let Error::InvalidStacksTransaction(msg, _ignored) = err { + assert!(msg.find("target epoch is not activated").is_some()); + } else { + panic!("Did not get epoch is not activated error"); + } + + let err = StacksChainState::process_transaction( + &mut conn, + &signed_call_foo_tx_clar1, + false, + ASTRules::PrecheckSize, + ) + .unwrap_err(); + if let Error::InvalidStacksTransaction(msg, _ignored) = err { + assert!(msg.find("target epoch is not activated").is_some()); + } else { + panic!("Did not get epoch is not activated error"); + } + conn.commit_block(); // in 2.05: analysis error should cause contract publish to fail let mut conn = chainstate.block_begin( - &TestBurnStateDB_20, + &TestBurnStateDB_2_05, &FIRST_BURNCHAIN_CONSENSUS_HASH, &FIRST_STACKS_BLOCK_HASH, &ConsensusHash([2u8; 20]), @@ -10034,7 +10315,7 @@ pub mod test { let (fee, _) = StacksChainState::process_transaction( &mut conn, - &signed_foo_trait_tx, + &signed_foo_trait_tx_no_version, false, ASTRules::PrecheckSize, ) @@ -10043,7 +10324,7 @@ pub mod test { let (fee, _) = StacksChainState::process_transaction( &mut conn, - &signed_foo_impl_tx, + &signed_foo_impl_tx_no_version, false, ASTRules::PrecheckSize, ) @@ -10052,7 +10333,7 @@ pub mod test { let (fee, tx_receipt) = StacksChainState::process_transaction( &mut conn, - &signed_call_foo_tx_clar1, + &signed_call_foo_tx_clar1_no_version, false, ASTRules::PrecheckSize, ) @@ -10081,6 +10362,45 @@ pub mod test { panic!("Did not get unchecked interpreter error"); } + let err = StacksChainState::process_transaction( + &mut conn, + &signed_foo_trait_tx, + false, + ASTRules::PrecheckSize, + ) + .unwrap_err(); + if let Error::InvalidStacksTransaction(msg, _ignored) = err { + assert!(msg.find("target epoch is not activated").is_some()); + } else { + panic!("Did not get epoch is not activated error"); + } + + let err = StacksChainState::process_transaction( + &mut conn, + &signed_foo_impl_tx, + false, + ASTRules::PrecheckSize, + ) + .unwrap_err(); + if let Error::InvalidStacksTransaction(msg, _ignored) = err { + assert!(msg.find("target epoch is not activated").is_some()); + } else { + panic!("Did not get epoch is not activated error"); + } + + let err = StacksChainState::process_transaction( + &mut conn, + &signed_call_foo_tx_clar1, + false, + ASTRules::PrecheckSize, + ) + .unwrap_err(); + if let Error::InvalidStacksTransaction(msg, _ignored) = err { + assert!(msg.find("target epoch is not activated").is_some()); + } else { + panic!("Did not get epoch is not activated error"); + } + conn.commit_block(); // in 2.1, using clarity 1: analysis error should cause contract publish to fail @@ -10257,6 +10577,27 @@ pub mod test { let signed_foo_trait_tx = signer.get_tx().unwrap(); + let mut tx_foo_trait_no_version = StacksTransaction::new( + TransactionVersion::Testnet, + auth.clone(), + TransactionPayload::new_smart_contract( + &"foo".to_string(), + &foo_trait.to_string(), + None, + ) + .unwrap(), + ); + + tx_foo_trait_no_version.post_condition_mode = TransactionPostConditionMode::Allow; + tx_foo_trait_no_version.chain_id = 0x80000000; + tx_foo_trait_no_version.set_tx_fee(1); + tx_foo_trait_no_version.set_origin_nonce(0); + + let mut signer = StacksTransactionSigner::new(&tx_foo_trait_no_version); + signer.sign_origin(&privk).unwrap(); + + let signed_foo_trait_tx_no_version = signer.get_tx().unwrap(); + let mut tx_transitive_trait_clar1 = StacksTransaction::new( TransactionVersion::Testnet, auth.clone(), @@ -10278,6 +10619,28 @@ pub mod test { let signed_transitive_trait_clar1_tx = signer.get_tx().unwrap(); + let mut tx_transitive_trait_clar1_no_version = StacksTransaction::new( + TransactionVersion::Testnet, + auth.clone(), + TransactionPayload::new_smart_contract( + &"transitive".to_string(), + &transitive_trait.to_string(), + None, + ) + .unwrap(), + ); + + tx_transitive_trait_clar1_no_version.post_condition_mode = + TransactionPostConditionMode::Allow; + tx_transitive_trait_clar1_no_version.chain_id = 0x80000000; + tx_transitive_trait_clar1_no_version.set_tx_fee(1); + tx_transitive_trait_clar1_no_version.set_origin_nonce(1); + + let mut signer = StacksTransactionSigner::new(&tx_transitive_trait_clar1_no_version); + signer.sign_origin(&privk).unwrap(); + + let signed_transitive_trait_clar1_tx_no_version = signer.get_tx().unwrap(); + let mut tx_transitive_trait_clar2 = StacksTransaction::new( TransactionVersion::Testnet, auth.clone(), @@ -10320,6 +10683,27 @@ pub mod test { let signed_foo_impl_tx = signer.get_tx().unwrap(); + let mut tx_foo_impl_no_version = StacksTransaction::new( + TransactionVersion::Testnet, + auth.clone(), + TransactionPayload::new_smart_contract( + &"foo-impl".to_string(), + &foo_impl.to_string(), + None, + ) + .unwrap(), + ); + + tx_foo_impl_no_version.post_condition_mode = TransactionPostConditionMode::Allow; + tx_foo_impl_no_version.chain_id = 0x80000000; + tx_foo_impl_no_version.set_tx_fee(1); + tx_foo_impl_no_version.set_origin_nonce(2); + + let mut signer = StacksTransactionSigner::new(&tx_foo_impl_no_version); + signer.sign_origin(&privk).unwrap(); + + let signed_foo_impl_tx_no_version = signer.get_tx().unwrap(); + let mut tx_call_foo_clar1 = StacksTransaction::new( TransactionVersion::Testnet, auth.clone(), @@ -10341,6 +10725,27 @@ pub mod test { let signed_call_foo_tx_clar1 = signer.get_tx().unwrap(); + let mut tx_call_foo_clar1_no_version = StacksTransaction::new( + TransactionVersion::Testnet, + auth.clone(), + TransactionPayload::new_smart_contract( + &"call-foo".to_string(), + &call_foo.to_string(), + None, + ) + .unwrap(), + ); + + tx_call_foo_clar1_no_version.post_condition_mode = TransactionPostConditionMode::Allow; + tx_call_foo_clar1_no_version.chain_id = 0x80000000; + tx_call_foo_clar1_no_version.set_tx_fee(1); + tx_call_foo_clar1_no_version.set_origin_nonce(3); + + let mut signer = StacksTransactionSigner::new(&tx_call_foo_clar1_no_version); + signer.sign_origin(&privk).unwrap(); + + let signed_call_foo_tx_clar1_no_version = signer.get_tx().unwrap(); + let mut tx_call_foo_clar2 = StacksTransaction::new( TransactionVersion::Testnet, auth.clone(), @@ -10402,7 +10807,7 @@ pub mod test { let (fee, _) = StacksChainState::process_transaction( &mut conn, - &signed_foo_trait_tx, + &signed_foo_trait_tx_no_version, false, ASTRules::PrecheckSize, ) @@ -10411,7 +10816,7 @@ pub mod test { let (fee, _) = StacksChainState::process_transaction( &mut conn, - &signed_transitive_trait_clar1_tx, + &signed_transitive_trait_clar1_tx_no_version, false, ASTRules::PrecheckSize, ) @@ -10420,7 +10825,7 @@ pub mod test { let (fee, _) = StacksChainState::process_transaction( &mut conn, - &signed_foo_impl_tx, + &signed_foo_impl_tx_no_version, false, ASTRules::PrecheckSize, ) @@ -10429,7 +10834,7 @@ pub mod test { let (fee, tx_receipt) = StacksChainState::process_transaction( &mut conn, - &signed_call_foo_tx_clar1, + &signed_call_foo_tx_clar1_no_version, false, ASTRules::PrecheckSize, ) @@ -10452,11 +10857,63 @@ pub mod test { } assert_eq!(fee, 1); + let err = StacksChainState::process_transaction( + &mut conn, + &signed_foo_trait_tx, + false, + ASTRules::PrecheckSize, + ) + .unwrap_err(); + if let Error::InvalidStacksTransaction(msg, _ignored) = err { + assert!(msg.find("target epoch is not activated").is_some()); + } else { + panic!("Did not get epoch is not activated error"); + } + + let err = StacksChainState::process_transaction( + &mut conn, + &signed_transitive_trait_clar1_tx, + false, + ASTRules::PrecheckSize, + ) + .unwrap_err(); + if let Error::InvalidStacksTransaction(msg, _ignored) = err { + assert!(msg.find("target epoch is not activated").is_some()); + } else { + panic!("Did not get epoch is not activated error"); + } + + let err = StacksChainState::process_transaction( + &mut conn, + &signed_foo_impl_tx, + false, + ASTRules::PrecheckSize, + ) + .unwrap_err(); + if let Error::InvalidStacksTransaction(msg, _ignored) = err { + assert!(msg.find("target epoch is not activated").is_some()); + } else { + panic!("Did not get epoch is not activated error"); + } + + let err = StacksChainState::process_transaction( + &mut conn, + &signed_call_foo_tx_clar1, + false, + ASTRules::PrecheckSize, + ) + .unwrap_err(); + if let Error::InvalidStacksTransaction(msg, _ignored) = err { + assert!(msg.find("target epoch is not activated").is_some()); + } else { + panic!("Did not get epoch is not activated error"); + } + conn.commit_block(); // in 2.05: calling call-foo invalidates the block let mut conn = chainstate.block_begin( - &TestBurnStateDB_20, + &TestBurnStateDB_2_05, &FIRST_BURNCHAIN_CONSENSUS_HASH, &FIRST_STACKS_BLOCK_HASH, &ConsensusHash([2u8; 20]), @@ -10465,7 +10922,7 @@ pub mod test { let (fee, _) = StacksChainState::process_transaction( &mut conn, - &signed_foo_trait_tx, + &signed_foo_trait_tx_no_version, false, ASTRules::PrecheckSize, ) @@ -10474,7 +10931,7 @@ pub mod test { let (fee, _) = StacksChainState::process_transaction( &mut conn, - &signed_transitive_trait_clar1_tx, + &signed_transitive_trait_clar1_tx_no_version, false, ASTRules::PrecheckSize, ) @@ -10483,7 +10940,7 @@ pub mod test { let (fee, _) = StacksChainState::process_transaction( &mut conn, - &signed_foo_impl_tx, + &signed_foo_impl_tx_no_version, false, ASTRules::PrecheckSize, ) @@ -10492,7 +10949,7 @@ pub mod test { let (fee, tx_receipt) = StacksChainState::process_transaction( &mut conn, - &signed_call_foo_tx_clar1, + &signed_call_foo_tx_clar1_no_version, false, ASTRules::PrecheckSize, ) @@ -10514,6 +10971,58 @@ pub mod test { panic!("Did not get unchecked interpreter error"); } + let err = StacksChainState::process_transaction( + &mut conn, + &signed_foo_trait_tx, + false, + ASTRules::PrecheckSize, + ) + .unwrap_err(); + if let Error::InvalidStacksTransaction(msg, _ignored) = err { + assert!(msg.find("target epoch is not activated").is_some()); + } else { + panic!("Did not get epoch is not activated error"); + } + + let err = StacksChainState::process_transaction( + &mut conn, + &signed_transitive_trait_clar1_tx, + false, + ASTRules::PrecheckSize, + ) + .unwrap_err(); + if let Error::InvalidStacksTransaction(msg, _ignored) = err { + assert!(msg.find("target epoch is not activated").is_some()); + } else { + panic!("Did not get epoch is not activated error"); + } + + let err = StacksChainState::process_transaction( + &mut conn, + &signed_foo_impl_tx, + false, + ASTRules::PrecheckSize, + ) + .unwrap_err(); + if let Error::InvalidStacksTransaction(msg, _ignored) = err { + assert!(msg.find("target epoch is not activated").is_some()); + } else { + panic!("Did not get epoch is not activated error"); + } + + let err = StacksChainState::process_transaction( + &mut conn, + &signed_call_foo_tx_clar1, + false, + ASTRules::PrecheckSize, + ) + .unwrap_err(); + if let Error::InvalidStacksTransaction(msg, _ignored) = err { + assert!(msg.find("target epoch is not activated").is_some()); + } else { + panic!("Did not get epoch is not activated error"); + } + conn.commit_block(); // in 2.1, using clarity 1 for both `transitive` and `call-foo`: calling call-foo causes an analysis error diff --git a/stackslib/src/chainstate/stacks/mod.rs b/stackslib/src/chainstate/stacks/mod.rs index a822ea5403..fc6b107878 100644 --- a/stackslib/src/chainstate/stacks/mod.rs +++ b/stackslib/src/chainstate/stacks/mod.rs @@ -494,6 +494,13 @@ pub enum MultisigHashMode { P2WSH = 0x03, } +#[repr(u8)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum OrderIndependentMultisigHashMode { + P2SH = 0x05, + P2WSH = 0x07, +} + impl SinglesigHashMode { pub fn to_address_hash_mode(&self) -> AddressHashMode { match *self { @@ -544,6 +551,35 @@ impl MultisigHashMode { } } +impl OrderIndependentMultisigHashMode { + pub fn to_address_hash_mode(&self) -> AddressHashMode { + match *self { + OrderIndependentMultisigHashMode::P2SH => AddressHashMode::SerializeP2SH, + OrderIndependentMultisigHashMode::P2WSH => AddressHashMode::SerializeP2WSH, + } + } + + pub fn from_address_hash_mode(hm: AddressHashMode) -> Option { + match hm { + AddressHashMode::SerializeP2SH => Some(OrderIndependentMultisigHashMode::P2SH), + AddressHashMode::SerializeP2WSH => Some(OrderIndependentMultisigHashMode::P2WSH), + _ => None, + } + } + + pub fn from_u8(n: u8) -> Option { + match n { + x if x == OrderIndependentMultisigHashMode::P2SH as u8 => { + Some(OrderIndependentMultisigHashMode::P2SH) + } + x if x == OrderIndependentMultisigHashMode::P2WSH as u8 => { + Some(OrderIndependentMultisigHashMode::P2WSH) + } + _ => None, + } + } +} + /// A structure that encodes enough state to authenticate /// a transaction's execution against a Stacks address. /// public_keys + signatures_required determines the Principal. @@ -568,10 +604,21 @@ pub struct SinglesigSpendingCondition { pub signature: MessageSignature, } +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct OrderIndependentMultisigSpendingCondition { + pub hash_mode: OrderIndependentMultisigHashMode, + pub signer: Hash160, + pub nonce: u64, // nth authorization from this account + pub tx_fee: u64, // microSTX/compute rate offered by this account + pub fields: Vec, + pub signatures_required: u16, +} + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum TransactionSpendingCondition { Singlesig(SinglesigSpendingCondition), Multisig(MultisigSpendingCondition), + OrderIndependentMultisig(OrderIndependentMultisigSpendingCondition), } /// Types of transaction authorizations @@ -1069,6 +1116,30 @@ pub mod test { ], signatures_required: 2 }), + TransactionSpendingCondition::OrderIndependentMultisig(OrderIndependentMultisigSpendingCondition { + signer: Hash160([0x11; 20]), + hash_mode: OrderIndependentMultisigHashMode::P2SH, + nonce: 345, + tx_fee: 678, + fields: vec![ + TransactionAuthField::Signature(TransactionPublicKeyEncoding::Uncompressed, MessageSignature::from_raw(&vec![0xff; 65])), + TransactionAuthField::Signature(TransactionPublicKeyEncoding::Uncompressed, MessageSignature::from_raw(&vec![0xfe; 65])), + TransactionAuthField::PublicKey(PubKey::from_hex("04ef2340518b5867b23598a9cf74611f8b98064f7d55cdb8c107c67b5efcbc5c771f112f919b00a6c6c5f51f7c63e1762fe9fac9b66ec75a053db7f51f4a52712b").unwrap()), + ], + signatures_required: 2 + }), + TransactionSpendingCondition::OrderIndependentMultisig(OrderIndependentMultisigSpendingCondition { + signer: Hash160([0x11; 20]), + hash_mode: OrderIndependentMultisigHashMode::P2SH, + nonce: 456, + tx_fee: 789, + fields: vec![ + TransactionAuthField::Signature(TransactionPublicKeyEncoding::Compressed, MessageSignature::from_raw(&vec![0xff; 65])), + TransactionAuthField::Signature(TransactionPublicKeyEncoding::Compressed, MessageSignature::from_raw(&vec![0xfe; 65])), + TransactionAuthField::PublicKey(PubKey::from_hex("03ef2340518b5867b23598a9cf74611f8b98064f7d55cdb8c107c67b5efcbc5c77").unwrap()) + ], + signatures_required: 2 + }), TransactionSpendingCondition::Singlesig(SinglesigSpendingCondition { signer: Hash160([0x11; 20]), hash_mode: SinglesigHashMode::P2WPKH, @@ -1088,6 +1159,18 @@ pub mod test { TransactionAuthField::PublicKey(PubKey::from_hex("03ef2340518b5867b23598a9cf74611f8b98064f7d55cdb8c107c67b5efcbc5c77").unwrap()) ], signatures_required: 2 + }), + TransactionSpendingCondition::OrderIndependentMultisig(OrderIndependentMultisigSpendingCondition { + signer: Hash160([0x11; 20]), + hash_mode: OrderIndependentMultisigHashMode::P2WSH, + nonce: 678, + tx_fee: 901, + fields: vec![ + TransactionAuthField::Signature(TransactionPublicKeyEncoding::Compressed, MessageSignature::from_raw(&vec![0xff; 65])), + TransactionAuthField::Signature(TransactionPublicKeyEncoding::Compressed, MessageSignature::from_raw(&vec![0xfe; 65])), + TransactionAuthField::PublicKey(PubKey::from_hex("03ef2340518b5867b23598a9cf74611f8b98064f7d55cdb8c107c67b5efcbc5c77").unwrap()) + ], + signatures_required: 2 }) ]; @@ -1357,7 +1440,7 @@ pub mod test { all_txs } - pub fn make_codec_test_block(num_txs: usize) -> StacksBlock { + pub fn make_codec_test_block(num_txs: usize, epoch_id: StacksEpochId) -> StacksBlock { let proof_bytes = hex_bytes("9275df67a68c8745c0ff97b48201ee6db447f7c93b23ae24cdc2400f52fdb08a1a6ac7ec71bf9c9c76e96ee4675ebff60625af28718501047bfd87b810c2d2139b73c23bd69de66360953a642c2a330a").unwrap(); let proof = VRFProof::from_bytes(&proof_bytes[..].to_vec()).unwrap(); @@ -1376,6 +1459,11 @@ pub mod test { origin_auth.clone(), TransactionPayload::Coinbase(CoinbasePayload([0u8; 32]), None, None), ); + let mut tx_coinbase_proof = StacksTransaction::new( + TransactionVersion::Mainnet, + origin_auth.clone(), + TransactionPayload::Coinbase(CoinbasePayload([0u8; 32]), None, Some(proof.clone())), + ); tx_coinbase.anchor_mode = TransactionAnchorMode::OnChainOnly; @@ -1388,7 +1476,12 @@ pub mod test { // remove all coinbases, except for an initial coinbase let mut txs_anchored = vec![]; - txs_anchored.push(tx_coinbase); + + if epoch_id >= StacksEpochId::Epoch30 { + txs_anchored.push(tx_coinbase_proof); + } else { + txs_anchored.push(tx_coinbase); + } for tx in all_txs.drain(..) { match tx.payload { diff --git a/stackslib/src/chainstate/stacks/tests/mod.rs b/stackslib/src/chainstate/stacks/tests/mod.rs index 678ee17f28..20d8d50ab2 100644 --- a/stackslib/src/chainstate/stacks/tests/mod.rs +++ b/stackslib/src/chainstate/stacks/tests/mod.rs @@ -1210,6 +1210,108 @@ pub fn make_versioned_user_contract_publish( sign_standard_singlesig_tx(payload, sender, nonce, tx_fee) } +pub fn sign_tx_order_independent_p2sh( + payload: TransactionPayload, + privks: &[StacksPrivateKey], + num_sigs: usize, + sender_nonce: u64, + tx_fee: u64, +) -> StacksTransaction { + let mut pubks = vec![]; + for privk in privks.iter() { + pubks.push(StacksPublicKey::from_private(privk)); + } + let mut sender_spending_condition = + TransactionSpendingCondition::new_multisig_order_independent_p2sh( + num_sigs as u16, + pubks.clone(), + ) + .expect("Failed to create p2sh spending condition."); + sender_spending_condition.set_nonce(sender_nonce); + sender_spending_condition.set_tx_fee(tx_fee); + let auth = TransactionAuth::Standard(sender_spending_condition); + let mut unsigned_tx = StacksTransaction::new(TransactionVersion::Testnet, auth, payload); + unsigned_tx.anchor_mode = TransactionAnchorMode::OnChainOnly; + unsigned_tx.post_condition_mode = TransactionPostConditionMode::Allow; + unsigned_tx.chain_id = 0x80000000; + + let mut tx_signer = StacksTransactionSigner::new(&unsigned_tx); + + for signer in 0..num_sigs { + tx_signer.sign_origin(&privks[signer]).unwrap(); + } + + for signer in num_sigs..pubks.len() { + tx_signer.append_origin(&pubks[signer]).unwrap(); + } + + tx_signer.get_tx().unwrap() +} + +pub fn sign_tx_order_independent_p2wsh( + payload: TransactionPayload, + privks: &[StacksPrivateKey], + num_sigs: usize, + sender_nonce: u64, + tx_fee: u64, +) -> StacksTransaction { + let mut pubks = vec![]; + for privk in privks.iter() { + pubks.push(StacksPublicKey::from_private(privk)); + } + let mut sender_spending_condition = + TransactionSpendingCondition::new_multisig_order_independent_p2wsh( + num_sigs as u16, + pubks.clone(), + ) + .expect("Failed to create p2wsh spending condition."); + sender_spending_condition.set_nonce(sender_nonce); + sender_spending_condition.set_tx_fee(tx_fee); + let auth = TransactionAuth::Standard(sender_spending_condition); + let mut unsigned_tx = StacksTransaction::new(TransactionVersion::Testnet, auth, payload); + unsigned_tx.anchor_mode = TransactionAnchorMode::OnChainOnly; + unsigned_tx.post_condition_mode = TransactionPostConditionMode::Allow; + unsigned_tx.chain_id = 0x80000000; + + let mut tx_signer = StacksTransactionSigner::new(&unsigned_tx); + + for signer in 0..num_sigs { + tx_signer.sign_origin(&privks[signer]).unwrap(); + } + + for signer in num_sigs..pubks.len() { + tx_signer.append_origin(&pubks[signer]).unwrap(); + } + + tx_signer.get_tx().unwrap() +} + +pub fn make_stacks_transfer_order_independent_p2sh( + privks: &[StacksPrivateKey], + num_sigs: usize, + nonce: u64, + tx_fee: u64, + recipient: &PrincipalData, + amount: u64, +) -> StacksTransaction { + let payload = + TransactionPayload::TokenTransfer(recipient.clone(), amount, TokenTransferMemo([0; 34])); + sign_tx_order_independent_p2sh(payload, privks, num_sigs, nonce, tx_fee) +} + +pub fn make_stacks_transfer_order_independent_p2wsh( + privks: &[StacksPrivateKey], + num_sigs: usize, + nonce: u64, + tx_fee: u64, + recipient: &PrincipalData, + amount: u64, +) -> StacksTransaction { + let payload = + TransactionPayload::TokenTransfer(recipient.clone(), amount, TokenTransferMemo([0; 34])); + sign_tx_order_independent_p2wsh(payload, privks, num_sigs, nonce, tx_fee) +} + pub fn make_user_contract_call( sender: &StacksPrivateKey, nonce: u64, diff --git a/stackslib/src/chainstate/stacks/transaction.rs b/stackslib/src/chainstate/stacks/transaction.rs index 06a738730d..50bbce23d4 100644 --- a/stackslib/src/chainstate/stacks/transaction.rs +++ b/stackslib/src/chainstate/stacks/transaction.rs @@ -23,7 +23,9 @@ use clarity::vm::representations::{ClarityName, ContractName}; use clarity::vm::types::serialization::SerializationError as clarity_serialization_error; use clarity::vm::types::{QualifiedContractIdentifier, StandardPrincipalData}; use clarity::vm::{ClarityVersion, SymbolicExpression, SymbolicExpressionType, Value}; -use stacks_common::codec::{read_next, write_next, Error as codec_error, StacksMessageCodec}; +use stacks_common::codec::{ + read_next, write_next, DeserializeWithEpoch, Error as codec_error, StacksMessageCodec, +}; use stacks_common::types::chainstate::StacksAddress; use stacks_common::types::StacksPublicKeyBuffer; use stacks_common::util::hash::{to_hex, MerkleHashFunc, MerkleTree, Sha512Trunc256Sum}; @@ -632,6 +634,7 @@ impl StacksTransaction { pub fn consensus_deserialize_with_len( fd: &mut R, + epoch_id: StacksEpochId, ) -> Result<(StacksTransaction, u64), codec_error> { let mut bound_read = BoundReader::from_reader(fd, MAX_TRANSACTION_LEN.into()); let fd = &mut bound_read; @@ -707,19 +710,24 @@ impl StacksTransaction { ))); } }; + let tx = StacksTransaction { + version, + chain_id, + auth, + anchor_mode, + post_condition_mode, + post_conditions, + payload, + }; - Ok(( - StacksTransaction { - version, - chain_id, - auth, - anchor_mode, - post_condition_mode, - post_conditions, - payload, - }, - fd.num_read(), - )) + if !StacksBlock::validate_transactions_static_epoch(&vec![tx.clone()], epoch_id, false) { + warn!("Invalid tx: target epoch is not activated"); + return Err(codec_error::DeserializeError( + "Failed to parse transaction: target epoch is not activated".to_string(), + )); + } + + Ok((tx, fd.num_read())) } /// Try to convert to a coinbase payload @@ -747,8 +755,17 @@ impl StacksMessageCodec for StacksTransaction { Ok(()) } - fn consensus_deserialize(fd: &mut R) -> Result { - StacksTransaction::consensus_deserialize_with_len(fd).map(|(result, _)| result) + fn consensus_deserialize(_fd: &mut R) -> Result { + panic!("StacksTransaction should be deserialized with consensus_deserialize_with_epoch instead") + } +} + +impl DeserializeWithEpoch for StacksTransaction { + fn consensus_deserialize_with_epoch( + fd: &mut R, + epoch_id: StacksEpochId, + ) -> Result { + StacksTransaction::consensus_deserialize_with_len(fd, epoch_id).map(|(result, _)| result) } } @@ -886,6 +903,10 @@ impl StacksTransaction { privk, )?; match condition { + TransactionSpendingCondition::Singlesig(ref mut cond) => { + cond.set_signature(next_sig); + Ok(next_sighash) + } TransactionSpendingCondition::Multisig(ref mut cond) => { cond.push_signature( if privk.compress_public() { @@ -897,9 +918,16 @@ impl StacksTransaction { ); Ok(next_sighash) } - TransactionSpendingCondition::Singlesig(ref mut cond) => { - cond.set_signature(next_sig); - Ok(next_sighash) + TransactionSpendingCondition::OrderIndependentMultisig(ref mut cond) => { + cond.push_signature( + if privk.compress_public() { + TransactionPublicKeyEncoding::Compressed + } else { + TransactionPublicKeyEncoding::Uncompressed + }, + next_sig, + ); + Ok(*cur_sighash) } } } @@ -910,6 +938,9 @@ impl StacksTransaction { ) -> Option { match condition { TransactionSpendingCondition::Multisig(ref mut cond) => cond.pop_auth_field(), + TransactionSpendingCondition::OrderIndependentMultisig(ref mut cond) => { + cond.pop_auth_field() + } TransactionSpendingCondition::Singlesig(ref mut cond) => cond.pop_signature(), } } @@ -924,6 +955,10 @@ impl StacksTransaction { cond.push_public_key(pubkey.clone()); Ok(()) } + TransactionSpendingCondition::OrderIndependentMultisig(ref mut cond) => { + cond.push_public_key(pubkey.clone()); + Ok(()) + } _ => Err(net_error::SigningError( "Not a multisig condition".to_string(), )), @@ -951,6 +986,98 @@ impl StacksTransaction { Ok(next_sighash) } + pub fn sign_no_append_origin( + &self, + cur_sighash: &Txid, + privk: &StacksPrivateKey, + ) -> Result { + let next_sig = match self.auth { + TransactionAuth::Standard(ref origin_condition) + | TransactionAuth::Sponsored(ref origin_condition, _) => { + let (next_sig, _next_sighash) = TransactionSpendingCondition::next_signature( + cur_sighash, + &TransactionAuthFlags::AuthStandard, + origin_condition.tx_fee(), + origin_condition.nonce(), + privk, + )?; + next_sig + } + }; + Ok(next_sig) + } + + pub fn append_origin_signature( + &mut self, + signature: MessageSignature, + key_encoding: TransactionPublicKeyEncoding, + ) -> Result<(), net_error> { + match self.auth { + TransactionAuth::Standard(ref mut origin_condition) + | TransactionAuth::Sponsored(ref mut origin_condition, _) => match origin_condition { + TransactionSpendingCondition::Singlesig(ref mut cond) => { + cond.set_signature(signature); + } + TransactionSpendingCondition::Multisig(ref mut cond) => { + cond.push_signature(key_encoding, signature); + } + TransactionSpendingCondition::OrderIndependentMultisig(ref mut cond) => { + cond.push_signature(key_encoding, signature); + } + }, + }; + Ok(()) + } + + pub fn sign_no_append_sponsor( + &mut self, + cur_sighash: &Txid, + privk: &StacksPrivateKey, + ) -> Result { + let next_sig = match self.auth { + TransactionAuth::Standard(_) => { + return Err(net_error::SigningError( + "Cannot sign standard authorization with a sponsoring private key".to_string(), + )); + } + TransactionAuth::Sponsored(_, ref mut sponsor_condition) => { + let (next_sig, _next_sighash) = TransactionSpendingCondition::next_signature( + cur_sighash, + &TransactionAuthFlags::AuthSponsored, + sponsor_condition.tx_fee(), + sponsor_condition.nonce(), + privk, + )?; + next_sig + } + }; + Ok(next_sig) + } + + pub fn append_sponsor_signature( + &mut self, + signature: MessageSignature, + key_encoding: TransactionPublicKeyEncoding, + ) -> Result<(), net_error> { + match self.auth { + TransactionAuth::Standard(_) => Err(net_error::SigningError( + "Cannot appned a public key to the sponsor of a standard auth condition" + .to_string(), + )), + TransactionAuth::Sponsored(_, ref mut sponsor_condition) => match sponsor_condition { + TransactionSpendingCondition::Singlesig(ref mut cond) => { + Ok(cond.set_signature(signature)) + } + TransactionSpendingCondition::Multisig(ref mut cond) => { + Ok(cond.push_signature(key_encoding, signature)) + } + TransactionSpendingCondition::OrderIndependentMultisig(ref mut cond) => { + Ok(cond.push_signature(key_encoding, signature)) + } + }, + } + } + /// Append the next public key to the origin account authorization. pub fn append_next_origin(&mut self, pubk: &StacksPublicKey) -> Result<(), net_error> { match self.auth { @@ -1229,21 +1356,24 @@ impl StacksTransactionSigner { #[cfg(test)] mod test { + use super::*; use std::error::Error; + use crate::chainstate::stacks::test::codec_all_transactions; + use crate::chainstate::stacks::*; + use crate::net::codec::test::{ + check_codec_and_corruption, check_codec_and_corruption_with_epoch, + }; use clarity::vm::representations::{ClarityName, ContractName}; use clarity::vm::types::{PrincipalData, QualifiedContractIdentifier}; use stacks_common::util::hash::*; use stacks_common::util::log; use stacks_common::util::retry::{BoundReader, LogReader}; - use super::*; - use crate::chainstate::stacks::test::codec_all_transactions; use crate::chainstate::stacks::{ StacksPublicKey as PubKey, C32_ADDRESS_VERSION_MAINNET_MULTISIG, C32_ADDRESS_VERSION_MAINNET_SINGLESIG, *, }; - use crate::net::codec::test::check_codec_and_corruption; use crate::net::codec::*; use crate::net::*; @@ -1277,6 +1407,20 @@ mod test { }; data.fields[i] = corrupt_field } + TransactionSpendingCondition::OrderIndependentMultisig(ref mut data) => { + let corrupt_field = match data.fields[i] { + TransactionAuthField::PublicKey(ref pubkey) => { + TransactionAuthField::PublicKey(StacksPublicKey::from_hex("0270790e675116a63a75008832d82ad93e4332882ab0797b0f156de9d739160a0b").unwrap()) + } + TransactionAuthField::Signature(ref key_encoding, ref sig) => { + let mut sig_bytes = sig.as_bytes().to_vec(); + sig_bytes[1] ^= 1u8; // this breaks the `r` paramter + let corrupt_sig = MessageSignature::from_raw(&sig_bytes); + TransactionAuthField::Signature(*key_encoding, corrupt_sig) + } + }; + data.fields[i] = corrupt_field + } } } } @@ -1302,6 +1446,20 @@ mod test { }; data.fields[i] = corrupt_field } + TransactionSpendingCondition::OrderIndependentMultisig(ref mut data) => { + let corrupt_field = match data.fields[i] { + TransactionAuthField::PublicKey(_) => { + TransactionAuthField::PublicKey(StacksPublicKey::from_hex("0270790e675116a63a75008832d82ad93e4332882ab0797b0f156de9d739160a0b").unwrap()) + } + TransactionAuthField::Signature(ref key_encoding, ref sig) => { + let mut sig_bytes = sig.as_bytes().to_vec(); + sig_bytes[1] ^= 1u8; // this breaks the `r` paramter + let corrupt_sig = MessageSignature::from_raw(&sig_bytes); + TransactionAuthField::Signature(*key_encoding, corrupt_sig) + } + }; + data.fields[i] = corrupt_field + } } } if corrupt_sponsor { @@ -1325,6 +1483,20 @@ mod test { }; data.fields[i] = corrupt_field } + TransactionSpendingCondition::OrderIndependentMultisig(ref mut data) => { + let corrupt_field = match data.fields[i] { + TransactionAuthField::PublicKey(ref pubkey) => { + TransactionAuthField::PublicKey(StacksPublicKey::from_hex("0270790e675116a63a75008832d82ad93e4332882ab0797b0f156de9d739160a0b").unwrap()) + } + TransactionAuthField::Signature(ref key_encoding, ref sig) => { + let mut sig_bytes = sig.as_bytes().to_vec(); + sig_bytes[1] ^= 1u8; // this breaks the `r` paramter + let corrupt_sig = MessageSignature::from_raw(&sig_bytes); + TransactionAuthField::Signature(*key_encoding, corrupt_sig) + } + }; + data.fields[i] = corrupt_field + } } } } @@ -1350,6 +1522,21 @@ mod test { } j } + TransactionSpendingCondition::OrderIndependentMultisig(ref data) => { + let mut j = 0; + for f in 0..data.fields.len() { + match data.fields[f] { + TransactionAuthField::Signature(_, _) => { + j = f; + break; + } + _ => { + continue; + } + } + } + j + } } } @@ -1371,6 +1558,21 @@ mod test { } j } + TransactionSpendingCondition::OrderIndependentMultisig(ref data) => { + let mut j = 0; + for f in 0..data.fields.len() { + match data.fields[f] { + TransactionAuthField::PublicKey(_) => { + j = f; + break; + } + _ => { + continue; + } + } + } + j + } } } @@ -1459,6 +1661,14 @@ mod test { MultisigHashMode::P2SH }; } + TransactionSpendingCondition::OrderIndependentMultisig(ref mut data) => { + data.hash_mode = + if data.hash_mode == OrderIndependentMultisigHashMode::P2SH { + OrderIndependentMultisigHashMode::P2WSH + } else { + OrderIndependentMultisigHashMode::P2SH + }; + } } } } @@ -1479,6 +1689,14 @@ mod test { MultisigHashMode::P2SH }; } + TransactionSpendingCondition::OrderIndependentMultisig(ref mut data) => { + data.hash_mode = + if data.hash_mode == OrderIndependentMultisigHashMode::P2SH { + OrderIndependentMultisigHashMode::P2WSH + } else { + OrderIndependentMultisigHashMode::P2SH + }; + } } } if corrupt_sponsor { @@ -1497,6 +1715,14 @@ mod test { MultisigHashMode::P2SH }; } + TransactionSpendingCondition::OrderIndependentMultisig(ref mut data) => { + data.hash_mode = + if data.hash_mode == OrderIndependentMultisigHashMode::P2SH { + OrderIndependentMultisigHashMode::P2WSH + } else { + OrderIndependentMultisigHashMode::P2SH + }; + } } } } @@ -1517,6 +1743,9 @@ mod test { TransactionSpendingCondition::Multisig(ref mut data) => { data.nonce += 1; } + TransactionSpendingCondition::OrderIndependentMultisig(ref mut data) => { + data.nonce += 1; + } }; } } @@ -1529,6 +1758,9 @@ mod test { TransactionSpendingCondition::Multisig(ref mut data) => { data.nonce += 1; } + TransactionSpendingCondition::OrderIndependentMultisig(ref mut data) => { + data.nonce += 1; + } } } if corrupt_sponsor { @@ -1539,6 +1771,9 @@ mod test { TransactionSpendingCondition::Multisig(ref mut data) => { data.nonce += 1; } + TransactionSpendingCondition::OrderIndependentMultisig(ref mut data) => { + data.nonce += 1; + } } } } @@ -1579,6 +1814,10 @@ mod test { is_multisig_origin = true; data.signatures_required += 1; } + TransactionSpendingCondition::OrderIndependentMultisig(ref mut data) => { + is_multisig_origin = true; + data.signatures_required += 1; + } }; } } @@ -1590,6 +1829,10 @@ mod test { is_multisig_origin = true; data.signatures_required += 1; } + TransactionSpendingCondition::OrderIndependentMultisig(ref mut data) => { + is_multisig_origin = true; + data.signatures_required += 1; + } } } if corrupt_sponsor { @@ -1599,6 +1842,10 @@ mod test { is_multisig_sponsor = true; data.signatures_required += 1; } + TransactionSpendingCondition::OrderIndependentMultisig(ref mut data) => { + is_multisig_sponsor = true; + data.signatures_required += 1; + } } } } @@ -1758,7 +2005,10 @@ mod test { // test_debug!("mutate byte {}", &i); let mut cursor = io::Cursor::new(&tx_bytes); let mut reader = LogReader::from_reader(&mut cursor); - match StacksTransaction::consensus_deserialize(&mut reader) { + match StacksTransaction::consensus_deserialize_with_epoch( + &mut reader, + StacksEpochId::latest(), + ) { Ok(corrupt_tx) => { let mut corrupt_tx_bytes = vec![]; corrupt_tx @@ -3697,7 +3947,11 @@ mod test { test_debug!("---------"); test_debug!("text tx bytes:\n{}", &to_hex(&tx_bytes)); - check_codec_and_corruption::(&tx, &tx_bytes); + check_codec_and_corruption_with_epoch::( + &tx, + &tx_bytes, + StacksEpochId::latest(), + ); } } @@ -4084,7 +4338,7 @@ mod test { sponsor_address, StacksAddress { version: C32_ADDRESS_VERSION_MAINNET_SINGLESIG, - bytes: Hash160::from_hex("3597aaa4bde720be93e3829aae24e76e7fcdfd3e").unwrap() + bytes: Hash160::from_hex("3597aaa4bde720be93e3829aae24e76e7fcdfd3e").unwrap(), } ); @@ -4194,7 +4448,7 @@ mod test { origin_address, StacksAddress { version: C32_ADDRESS_VERSION_MAINNET_SINGLESIG, - bytes: Hash160::from_hex("693cd53eb47d4749762d7cfaf46902bda5be5f97").unwrap() + bytes: Hash160::from_hex("693cd53eb47d4749762d7cfaf46902bda5be5f97").unwrap(), } ); @@ -4279,14 +4533,14 @@ mod test { origin_address, StacksAddress { version: C32_ADDRESS_VERSION_MAINNET_SINGLESIG, - bytes: Hash160::from_hex("3597aaa4bde720be93e3829aae24e76e7fcdfd3e").unwrap() + bytes: Hash160::from_hex("3597aaa4bde720be93e3829aae24e76e7fcdfd3e").unwrap(), } ); assert_eq!( sponsor_address, StacksAddress { version: C32_ADDRESS_VERSION_MAINNET_SINGLESIG, - bytes: Hash160::from_hex("693cd53eb47d4749762d7cfaf46902bda5be5f97").unwrap() + bytes: Hash160::from_hex("693cd53eb47d4749762d7cfaf46902bda5be5f97").unwrap(), } ); @@ -4394,7 +4648,7 @@ mod test { origin_address, StacksAddress { version: C32_ADDRESS_VERSION_MAINNET_MULTISIG, - bytes: Hash160::from_hex("a23ea89d6529ac48ac766f720e480beec7f19273").unwrap() + bytes: Hash160::from_hex("a23ea89d6529ac48ac766f720e480beec7f19273").unwrap(), } ); @@ -4504,14 +4758,14 @@ mod test { origin_address, StacksAddress { version: C32_ADDRESS_VERSION_MAINNET_SINGLESIG, - bytes: Hash160::from_hex("3597aaa4bde720be93e3829aae24e76e7fcdfd3e").unwrap() + bytes: Hash160::from_hex("3597aaa4bde720be93e3829aae24e76e7fcdfd3e").unwrap(), } ); assert_eq!( sponsor_address, StacksAddress { version: C32_ADDRESS_VERSION_MAINNET_MULTISIG, - bytes: Hash160::from_hex("a23ea89d6529ac48ac766f720e480beec7f19273").unwrap() + bytes: Hash160::from_hex("a23ea89d6529ac48ac766f720e480beec7f19273").unwrap(), } ); @@ -4632,7 +4886,7 @@ mod test { origin_address, StacksAddress { version: C32_ADDRESS_VERSION_MAINNET_MULTISIG, - bytes: Hash160::from_hex("73a8b4a751a678fe83e9d35ce301371bb3d397f7").unwrap() + bytes: Hash160::from_hex("73a8b4a751a678fe83e9d35ce301371bb3d397f7").unwrap(), } ); @@ -4745,14 +4999,14 @@ mod test { origin_address, StacksAddress { version: C32_ADDRESS_VERSION_MAINNET_SINGLESIG, - bytes: Hash160::from_hex("3597aaa4bde720be93e3829aae24e76e7fcdfd3e").unwrap() + bytes: Hash160::from_hex("3597aaa4bde720be93e3829aae24e76e7fcdfd3e").unwrap(), } ); assert_eq!( sponsor_address, StacksAddress { version: C32_ADDRESS_VERSION_MAINNET_MULTISIG, - bytes: Hash160::from_hex("73a8b4a751a678fe83e9d35ce301371bb3d397f7").unwrap() + bytes: Hash160::from_hex("73a8b4a751a678fe83e9d35ce301371bb3d397f7").unwrap(), } ); @@ -4871,7 +5125,7 @@ mod test { origin_address, StacksAddress { version: C32_ADDRESS_VERSION_MAINNET_MULTISIG, - bytes: Hash160::from_hex("2136367c9c740e7dbed8795afdf8a6d273096718").unwrap() + bytes: Hash160::from_hex("2136367c9c740e7dbed8795afdf8a6d273096718").unwrap(), } ); @@ -4981,14 +5235,14 @@ mod test { origin_address, StacksAddress { version: C32_ADDRESS_VERSION_MAINNET_SINGLESIG, - bytes: Hash160::from_hex("3597aaa4bde720be93e3829aae24e76e7fcdfd3e").unwrap() + bytes: Hash160::from_hex("3597aaa4bde720be93e3829aae24e76e7fcdfd3e").unwrap(), } ); assert_eq!( sponsor_address, StacksAddress { version: C32_ADDRESS_VERSION_MAINNET_MULTISIG, - bytes: Hash160::from_hex("2136367c9c740e7dbed8795afdf8a6d273096718").unwrap() + bytes: Hash160::from_hex("2136367c9c740e7dbed8795afdf8a6d273096718").unwrap(), } ); @@ -5094,7 +5348,7 @@ mod test { origin_address, StacksAddress { version: C32_ADDRESS_VERSION_MAINNET_MULTISIG, - bytes: Hash160::from_hex("f15fa5c59d14ffcb615fa6153851cd802bb312d2").unwrap() + bytes: Hash160::from_hex("f15fa5c59d14ffcb615fa6153851cd802bb312d2").unwrap(), } ); @@ -5175,14 +5429,14 @@ mod test { origin_address, StacksAddress { version: C32_ADDRESS_VERSION_MAINNET_SINGLESIG, - bytes: Hash160::from_hex("3597aaa4bde720be93e3829aae24e76e7fcdfd3e").unwrap() + bytes: Hash160::from_hex("3597aaa4bde720be93e3829aae24e76e7fcdfd3e").unwrap(), } ); assert_eq!( sponsor_address, StacksAddress { version: C32_ADDRESS_VERSION_MAINNET_MULTISIG, - bytes: Hash160::from_hex("f15fa5c59d14ffcb615fa6153851cd802bb312d2").unwrap() + bytes: Hash160::from_hex("f15fa5c59d14ffcb615fa6153851cd802bb312d2").unwrap(), } ); @@ -5286,7 +5540,7 @@ mod test { origin_address, StacksAddress { version: C32_ADDRESS_VERSION_MAINNET_MULTISIG, - bytes: Hash160::from_hex("f5cfb61a07fb41a32197da01ce033888f0fe94a7").unwrap() + bytes: Hash160::from_hex("f5cfb61a07fb41a32197da01ce033888f0fe94a7").unwrap(), } ); @@ -5397,14 +5651,14 @@ mod test { origin_address, StacksAddress { version: C32_ADDRESS_VERSION_MAINNET_SINGLESIG, - bytes: Hash160::from_hex("3597aaa4bde720be93e3829aae24e76e7fcdfd3e").unwrap() + bytes: Hash160::from_hex("3597aaa4bde720be93e3829aae24e76e7fcdfd3e").unwrap(), } ); assert_eq!( sponsor_address, StacksAddress { version: C32_ADDRESS_VERSION_MAINNET_MULTISIG, - bytes: Hash160::from_hex("f5cfb61a07fb41a32197da01ce033888f0fe94a7").unwrap() + bytes: Hash160::from_hex("f5cfb61a07fb41a32197da01ce033888f0fe94a7").unwrap(), } ); @@ -5493,6 +5747,1625 @@ mod test { } } - // TODO(test): test with different tx versions - // TODO(test): test error values for signing and verifying + #[test] + fn tx_stacks_transaction_sign_verify_standard_order_independent_p2sh() { + let privk_1 = StacksPrivateKey::from_hex( + "6d430bb91222408e7706c9001cfaeb91b08c2be6d5ac95779ab52c6b431950e001", + ) + .unwrap(); + let privk_2 = StacksPrivateKey::from_hex( + "2a584d899fed1d24e26b524f202763c8ab30260167429f157f1c119f550fa6af01", + ) + .unwrap(); + let privk_3 = StacksPrivateKey::from_hex( + "d5200dee706ee53ae98a03fba6cf4fdcc5084c30cfa9e1b3462dcdeaa3e0f1d201", + ) + .unwrap(); + + let pubk_1 = StacksPublicKey::from_private(&privk_1); + let pubk_2 = StacksPublicKey::from_private(&privk_2); + let pubk_3 = StacksPublicKey::from_private(&privk_3); + + let origin_auth = TransactionAuth::Standard( + TransactionSpendingCondition::new_multisig_order_independent_p2sh( + 2, + vec![pubk_1.clone(), pubk_2.clone(), pubk_3.clone()], + ) + .unwrap(), + ); + + let origin_address = origin_auth.origin().address_mainnet(); + assert_eq!( + origin_address, + StacksAddress { + version: C32_ADDRESS_VERSION_MAINNET_MULTISIG, + bytes: Hash160::from_hex("a23ea89d6529ac48ac766f720e480beec7f19273").unwrap(), + } + ); + + let txs = tx_stacks_transaction_test_txs(&origin_auth); + + for mut tx in txs { + assert_eq!(tx.auth().origin().num_signatures(), 0); + + let tx_signer = StacksTransactionSigner::new(&tx); + + let initialSigHash = tx.sign_begin(); + let sig3 = tx.sign_no_append_origin(&initialSigHash, &privk_3).unwrap(); + let sig2 = tx.sign_no_append_origin(&initialSigHash, &privk_2).unwrap(); + + let _ = tx.append_next_origin(&pubk_1); + let _ = tx.append_origin_signature(sig2, TransactionPublicKeyEncoding::Compressed); + let _ = tx.append_origin_signature(sig3, TransactionPublicKeyEncoding::Compressed); + + check_oversign_origin_multisig(&mut tx); + check_sign_no_sponsor(&mut tx); + + assert_eq!(tx.auth().origin().num_signatures(), 2); + + // auth is standard and first two auth fields are signatures for compressed keys. + // third field is the third public key + match tx.auth { + TransactionAuth::Standard(ref origin) => match origin { + TransactionSpendingCondition::OrderIndependentMultisig(ref data) => { + assert_eq!(data.signer, origin_address.bytes); + assert_eq!(data.fields.len(), 3); + assert!(data.fields[0].is_public_key()); + assert!(data.fields[1].is_signature()); + assert!(data.fields[2].is_signature()); + + assert_eq!(data.fields[0].as_public_key().unwrap(), pubk_1); + assert_eq!( + data.fields[1].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + assert_eq!( + data.fields[2].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + } + _ => assert!(false), + }, + _ => assert!(false), + }; + + test_signature_and_corruption(&tx, true, false); + } + } + + #[test] + fn tx_stacks_transaction_sign_verify_sponsored_order_independent_p2sh() { + let origin_privk = StacksPrivateKey::from_hex( + "807bbe9e471ac976592cc35e3056592ecc0f778ee653fced3b491a122dd8d59701", + ) + .unwrap(); + + let privk_1 = StacksPrivateKey::from_hex( + "6d430bb91222408e7706c9001cfaeb91b08c2be6d5ac95779ab52c6b431950e001", + ) + .unwrap(); + let privk_2 = StacksPrivateKey::from_hex( + "2a584d899fed1d24e26b524f202763c8ab30260167429f157f1c119f550fa6af01", + ) + .unwrap(); + let privk_3 = StacksPrivateKey::from_hex( + "d5200dee706ee53ae98a03fba6cf4fdcc5084c30cfa9e1b3462dcdeaa3e0f1d201", + ) + .unwrap(); + + let pubk_1 = StacksPublicKey::from_private(&privk_1); + let pubk_2 = StacksPublicKey::from_private(&privk_2); + let pubk_3 = StacksPublicKey::from_private(&privk_3); + + let random_sponsor = StacksPrivateKey::new(); // what the origin sees + + let auth = TransactionAuth::Sponsored( + TransactionSpendingCondition::new_singlesig_p2pkh(StacksPublicKey::from_private( + &origin_privk, + )) + .unwrap(), + TransactionSpendingCondition::new_singlesig_p2pkh(StacksPublicKey::from_private( + &random_sponsor, + )) + .unwrap(), + ); + + let real_sponsor = TransactionSpendingCondition::new_multisig_order_independent_p2sh( + 2, + vec![pubk_1.clone(), pubk_2.clone(), pubk_3.clone()], + ) + .unwrap(); + + let origin_address = auth.origin().address_mainnet(); + let sponsor_address = real_sponsor.address_mainnet(); + + assert_eq!( + origin_address, + StacksAddress { + version: C32_ADDRESS_VERSION_MAINNET_SINGLESIG, + bytes: Hash160::from_hex("3597aaa4bde720be93e3829aae24e76e7fcdfd3e").unwrap(), + } + ); + assert_eq!( + sponsor_address, + StacksAddress { + version: C32_ADDRESS_VERSION_MAINNET_MULTISIG, + bytes: Hash160::from_hex("a23ea89d6529ac48ac766f720e480beec7f19273").unwrap(), + } + ); + + let txs = tx_stacks_transaction_test_txs(&auth); + + for mut tx in txs { + assert_eq!(tx.auth().origin().num_signatures(), 0); + assert_eq!(tx.auth().sponsor().unwrap().num_signatures(), 0); + + tx.set_tx_fee(123); + tx.set_sponsor_nonce(456).unwrap(); + let mut tx_signer = StacksTransactionSigner::new(&tx); + + tx_signer.sign_origin(&origin_privk).unwrap(); + + // sponsor sets and pays fee after origin signs + let mut origin_tx = tx_signer.get_tx_incomplete(); + origin_tx.auth.set_sponsor(real_sponsor.clone()).unwrap(); + origin_tx.set_tx_fee(456); + origin_tx.set_sponsor_nonce(789).unwrap(); + + let initialSigHash = tx_signer.sighash; + let sig1 = origin_tx + .sign_no_append_sponsor(&initialSigHash, &privk_1) + .unwrap(); + let sig2 = origin_tx + .sign_no_append_sponsor(&initialSigHash, &privk_2) + .unwrap(); + + let _ = + origin_tx.append_sponsor_signature(sig1, TransactionPublicKeyEncoding::Compressed); + let _ = + origin_tx.append_sponsor_signature(sig2, TransactionPublicKeyEncoding::Compressed); + let _ = origin_tx.append_next_sponsor(&pubk_3); + + tx.set_tx_fee(456); + tx.set_sponsor_nonce(789).unwrap(); + + check_oversign_origin_singlesig(&mut origin_tx); + check_oversign_sponsor_multisig(&mut origin_tx); + + assert_eq!(origin_tx.auth().origin().num_signatures(), 1); + assert_eq!(origin_tx.auth().sponsor().unwrap().num_signatures(), 2); + + // tx and origin_tx are otherwise equal + assert_eq!(tx.version, origin_tx.version); + assert_eq!(tx.chain_id, origin_tx.chain_id); + assert_eq!(tx.get_tx_fee(), origin_tx.get_tx_fee()); + assert_eq!(tx.get_origin_nonce(), origin_tx.get_origin_nonce()); + assert_eq!(tx.get_sponsor_nonce(), origin_tx.get_sponsor_nonce()); + assert_eq!(tx.anchor_mode, origin_tx.anchor_mode); + assert_eq!(tx.post_condition_mode, origin_tx.post_condition_mode); + assert_eq!(tx.post_conditions, origin_tx.post_conditions); + assert_eq!(tx.payload, origin_tx.payload); + + // auth is standard and first two auth fields are signatures for compressed keys. + // third field is the third public key + match origin_tx.auth { + TransactionAuth::Sponsored(ref origin, ref sponsor) => { + match origin { + TransactionSpendingCondition::Singlesig(ref data) => { + assert_eq!(data.key_encoding, TransactionPublicKeyEncoding::Compressed); + assert_eq!(data.signer, origin_address.bytes); + } + _ => assert!(false), + } + match sponsor { + TransactionSpendingCondition::OrderIndependentMultisig(ref data) => { + assert_eq!(data.signer, sponsor_address.bytes); + assert_eq!(data.fields.len(), 3); + assert!(data.fields[0].is_signature()); + assert!(data.fields[1].is_signature()); + assert!(data.fields[2].is_public_key()); + + assert_eq!( + data.fields[0].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + assert_eq!( + data.fields[1].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + assert_eq!(data.fields[2].as_public_key().unwrap(), pubk_3); + } + _ => assert!(false), + } + } + _ => assert!(false), + }; + + test_signature_and_corruption(&origin_tx, true, false); + test_signature_and_corruption(&origin_tx, false, true); + } + } + + #[test] + fn tx_stacks_transaction_sign_verify_standard_order_independent_p2sh_uncompressed() { + let privk_1 = StacksPrivateKey::from_hex( + "6d430bb91222408e7706c9001cfaeb91b08c2be6d5ac95779ab52c6b431950e0", + ) + .unwrap(); + let privk_2 = StacksPrivateKey::from_hex( + "2a584d899fed1d24e26b524f202763c8ab30260167429f157f1c119f550fa6af", + ) + .unwrap(); + let privk_3 = StacksPrivateKey::from_hex( + "d5200dee706ee53ae98a03fba6cf4fdcc5084c30cfa9e1b3462dcdeaa3e0f1d2", + ) + .unwrap(); + + let pubk_1 = StacksPublicKey::from_private(&privk_1); + let pubk_2 = StacksPublicKey::from_private(&privk_2); + let pubk_3 = StacksPublicKey::from_private(&privk_3); + + let origin_auth = TransactionAuth::Standard( + TransactionSpendingCondition::new_multisig_order_independent_p2sh( + 2, + vec![pubk_1.clone(), pubk_2.clone(), pubk_3.clone()], + ) + .unwrap(), + ); + + let origin_address = origin_auth.origin().address_mainnet(); + assert_eq!( + origin_address, + StacksAddress { + version: C32_ADDRESS_VERSION_MAINNET_MULTISIG, + bytes: Hash160::from_hex("73a8b4a751a678fe83e9d35ce301371bb3d397f7").unwrap(), + } + ); + + let txs = tx_stacks_transaction_test_txs(&origin_auth); + + for mut tx in txs { + assert_eq!(tx.auth().origin().num_signatures(), 0); + + let tx_signer = StacksTransactionSigner::new(&tx); + + let initialSigHash = tx.sign_begin(); + let sig3 = tx.sign_no_append_origin(&initialSigHash, &privk_3).unwrap(); + let sig2 = tx.sign_no_append_origin(&initialSigHash, &privk_2).unwrap(); + + let _ = tx.append_next_origin(&pubk_1); + let _ = tx.append_origin_signature(sig2, TransactionPublicKeyEncoding::Uncompressed); + let _ = tx.append_origin_signature(sig3, TransactionPublicKeyEncoding::Uncompressed); + + check_oversign_origin_multisig(&mut tx); + check_sign_no_sponsor(&mut tx); + + assert_eq!(tx.auth().origin().num_signatures(), 2); + + // auth is standard and first two auth fields are signatures for compressed keys. + // third field is the third public key + match tx.auth { + TransactionAuth::Standard(ref origin) => match origin { + TransactionSpendingCondition::OrderIndependentMultisig(ref data) => { + assert_eq!(data.signer, origin_address.bytes); + assert_eq!(data.fields.len(), 3); + assert!(data.fields[0].is_public_key()); + assert!(data.fields[1].is_signature()); + assert!(data.fields[2].is_signature()); + + assert_eq!(data.fields[0].as_public_key().unwrap(), pubk_1); + assert_eq!( + data.fields[1].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Uncompressed + ); + assert_eq!( + data.fields[2].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Uncompressed + ); + } + _ => assert!(false), + }, + _ => assert!(false), + }; + + test_signature_and_corruption(&tx, true, false); + } + } + + #[test] + fn tx_stacks_transaction_sign_verify_sponsored_order_independent_p2sh_uncompressed() { + let origin_privk = StacksPrivateKey::from_hex( + "807bbe9e471ac976592cc35e3056592ecc0f778ee653fced3b491a122dd8d59701", + ) + .unwrap(); + + let privk_1 = StacksPrivateKey::from_hex( + "6d430bb91222408e7706c9001cfaeb91b08c2be6d5ac95779ab52c6b431950e0", + ) + .unwrap(); + let privk_2 = StacksPrivateKey::from_hex( + "2a584d899fed1d24e26b524f202763c8ab30260167429f157f1c119f550fa6af", + ) + .unwrap(); + let privk_3 = StacksPrivateKey::from_hex( + "d5200dee706ee53ae98a03fba6cf4fdcc5084c30cfa9e1b3462dcdeaa3e0f1d2", + ) + .unwrap(); + + let pubk_1 = StacksPublicKey::from_private(&privk_1); + let pubk_2 = StacksPublicKey::from_private(&privk_2); + let pubk_3 = StacksPublicKey::from_private(&privk_3); + + let random_sponsor = StacksPrivateKey::new(); // what the origin sees + + let auth = TransactionAuth::Sponsored( + TransactionSpendingCondition::new_singlesig_p2pkh(StacksPublicKey::from_private( + &origin_privk, + )) + .unwrap(), + TransactionSpendingCondition::new_singlesig_p2pkh(StacksPublicKey::from_private( + &random_sponsor, + )) + .unwrap(), + ); + + let real_sponsor = TransactionSpendingCondition::new_multisig_order_independent_p2sh( + 2, + vec![pubk_1.clone(), pubk_2.clone(), pubk_3.clone()], + ) + .unwrap(); + + let origin_address = auth.origin().address_mainnet(); + let sponsor_address = real_sponsor.address_mainnet(); + + assert_eq!( + origin_address, + StacksAddress { + version: C32_ADDRESS_VERSION_MAINNET_SINGLESIG, + bytes: Hash160::from_hex("3597aaa4bde720be93e3829aae24e76e7fcdfd3e").unwrap(), + } + ); + assert_eq!( + sponsor_address, + StacksAddress { + version: C32_ADDRESS_VERSION_MAINNET_MULTISIG, + bytes: Hash160::from_hex("73a8b4a751a678fe83e9d35ce301371bb3d397f7").unwrap(), + } + ); + + let txs = tx_stacks_transaction_test_txs(&auth); + + for mut tx in txs { + assert_eq!(tx.auth().origin().num_signatures(), 0); + assert_eq!(tx.auth().sponsor().unwrap().num_signatures(), 0); + + tx.set_tx_fee(123); + tx.set_sponsor_nonce(456).unwrap(); + let mut tx_signer = StacksTransactionSigner::new(&tx); + + tx_signer.sign_origin(&origin_privk).unwrap(); + + // sponsor sets and pays fee after origin signs + let mut origin_tx = tx_signer.get_tx_incomplete(); + origin_tx.auth.set_sponsor(real_sponsor.clone()).unwrap(); + origin_tx.set_tx_fee(456); + origin_tx.set_sponsor_nonce(789).unwrap(); + + let initialSigHash = tx_signer.sighash; + let sig1 = origin_tx + .sign_no_append_sponsor(&initialSigHash, &privk_1) + .unwrap(); + let sig2 = origin_tx + .sign_no_append_sponsor(&initialSigHash, &privk_2) + .unwrap(); + + let _ = origin_tx + .append_sponsor_signature(sig1, TransactionPublicKeyEncoding::Uncompressed); + let _ = origin_tx + .append_sponsor_signature(sig2, TransactionPublicKeyEncoding::Uncompressed); + let _ = origin_tx.append_next_sponsor(&pubk_3); + + tx.set_tx_fee(456); + tx.set_sponsor_nonce(789).unwrap(); + + check_oversign_origin_singlesig(&mut origin_tx); + check_oversign_sponsor_multisig(&mut origin_tx); + + assert_eq!(origin_tx.auth().origin().num_signatures(), 1); + assert_eq!(origin_tx.auth().sponsor().unwrap().num_signatures(), 2); + + // tx and origin_tx are otherwise equal + assert_eq!(tx.version, origin_tx.version); + assert_eq!(tx.chain_id, origin_tx.chain_id); + assert_eq!(tx.get_tx_fee(), origin_tx.get_tx_fee()); + assert_eq!(tx.get_origin_nonce(), origin_tx.get_origin_nonce()); + assert_eq!(tx.get_sponsor_nonce(), origin_tx.get_sponsor_nonce()); + assert_eq!(tx.anchor_mode, origin_tx.anchor_mode); + assert_eq!(tx.post_condition_mode, origin_tx.post_condition_mode); + assert_eq!(tx.post_conditions, origin_tx.post_conditions); + assert_eq!(tx.payload, origin_tx.payload); + + // auth is standard and first two auth fields are signatures for compressed keys. + // third field is the third public key + match origin_tx.auth { + TransactionAuth::Sponsored(ref origin, ref sponsor) => { + match origin { + TransactionSpendingCondition::Singlesig(ref data) => { + assert_eq!(data.key_encoding, TransactionPublicKeyEncoding::Compressed); + assert_eq!(data.signer, origin_address.bytes); + } + _ => assert!(false), + } + match sponsor { + TransactionSpendingCondition::OrderIndependentMultisig(ref data) => { + assert_eq!(data.signer, sponsor_address.bytes); + assert_eq!(data.fields.len(), 3); + assert!(data.fields[0].is_signature()); + assert!(data.fields[1].is_signature()); + assert!(data.fields[2].is_public_key()); + + assert_eq!( + data.fields[0].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Uncompressed + ); + assert_eq!( + data.fields[1].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Uncompressed + ); + assert_eq!(data.fields[2].as_public_key().unwrap(), pubk_3); + } + _ => assert!(false), + } + } + _ => assert!(false), + }; + + test_signature_and_corruption(&origin_tx, true, false); + test_signature_and_corruption(&origin_tx, false, true); + } + } + + #[test] + fn tx_stacks_transaction_sign_verify_standard_order_independent_p2sh_mixed() { + let privk_1 = StacksPrivateKey::from_hex( + "6d430bb91222408e7706c9001cfaeb91b08c2be6d5ac95779ab52c6b431950e001", + ) + .unwrap(); + let privk_2 = StacksPrivateKey::from_hex( + "2a584d899fed1d24e26b524f202763c8ab30260167429f157f1c119f550fa6af01", + ) + .unwrap(); + let privk_3 = StacksPrivateKey::from_hex( + "d5200dee706ee53ae98a03fba6cf4fdcc5084c30cfa9e1b3462dcdeaa3e0f1d201", + ) + .unwrap(); + + let pubk_1 = StacksPublicKey::from_private(&privk_1); + let pubk_2 = StacksPublicKey::from_private(&privk_2); + let pubk_3 = StacksPublicKey::from_private(&privk_3); + + let origin_auth = TransactionAuth::Standard( + TransactionSpendingCondition::new_multisig_order_independent_p2sh( + 2, + vec![pubk_1.clone(), pubk_2.clone(), pubk_3.clone()], + ) + .unwrap(), + ); + + let origin_address = origin_auth.origin().address_mainnet(); + assert_eq!( + origin_address, + StacksAddress { + version: C32_ADDRESS_VERSION_MAINNET_MULTISIG, + bytes: Hash160::from_hex("a23ea89d6529ac48ac766f720e480beec7f19273").unwrap(), + } + ); + + let txs = tx_stacks_transaction_test_txs(&origin_auth); + + for mut tx in txs { + assert_eq!(tx.auth().origin().num_signatures(), 0); + + let tx_signer = StacksTransactionSigner::new(&tx); + + let initialSigHash = tx.sign_begin(); + let sig3 = tx.sign_no_append_origin(&initialSigHash, &privk_3).unwrap(); + let sig1 = tx.sign_no_append_origin(&initialSigHash, &privk_1).unwrap(); + + let _ = tx.append_origin_signature(sig1, TransactionPublicKeyEncoding::Compressed); + let _ = tx.append_next_origin(&pubk_2); + let _ = tx.append_origin_signature(sig3, TransactionPublicKeyEncoding::Compressed); + + check_oversign_origin_multisig(&mut tx); + check_sign_no_sponsor(&mut tx); + + assert_eq!(tx.auth().origin().num_signatures(), 2); + + // auth is standard and first two auth fields are signatures for compressed keys. + // third field is the third public key + match tx.auth { + TransactionAuth::Standard(ref origin) => match origin { + TransactionSpendingCondition::OrderIndependentMultisig(ref data) => { + assert_eq!(data.signer, origin_address.bytes); + assert_eq!(data.fields.len(), 3); + assert!(data.fields[0].is_signature()); + assert!(data.fields[1].is_public_key()); + assert!(data.fields[2].is_signature()); + + assert_eq!(data.fields[1].as_public_key().unwrap(), pubk_2); + assert_eq!( + data.fields[0].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + assert_eq!( + data.fields[2].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + } + _ => assert!(false), + }, + _ => assert!(false), + }; + + test_signature_and_corruption(&tx, true, false); + } + } + + #[test] + fn tx_stacks_transaction_sign_verify_standard_order_independent_p2sh_mixed_3_out_of_9() { + let privk_1 = StacksPrivateKey::from_hex( + "6d430bb91222408e7706c9001cfaeb91b08c2be6d5ac95779ab52c6b431950e001", + ) + .unwrap(); + let privk_2 = StacksPrivateKey::from_hex( + "2a584d899fed1d24e26b524f202763c8ab30260167429f157f1c119f550fa6af01", + ) + .unwrap(); + let privk_3 = StacksPrivateKey::from_hex( + "d5200dee706ee53ae98a03fba6cf4fdcc5084c30cfa9e1b3462dcdeaa3e0f1d201", + ) + .unwrap(); + let privk_4 = StacksPrivateKey::from_hex( + "3beb8916404874f5d5de162c95470951de5b4a7f6ec8d7a20511551821f16db501", + ) + .unwrap(); + let privk_5 = StacksPrivateKey::from_hex( + "601aa0939e98efec29a4dc645377c9d4acaa0b7318444ec8fd7d090d0b36d85b01", + ) + .unwrap(); + let privk_6 = StacksPrivateKey::from_hex( + "5a4ca3db5a3b36bc32d9f2f0894435cbc4b2b1207e95ee283616d9a0797210da01", + ) + .unwrap(); + let privk_7 = StacksPrivateKey::from_hex( + "068856c242bfebdc57700fa598fae4e8ebb6b5f6bf932177018071489737d3ff01", + ) + .unwrap(); + let privk_8 = StacksPrivateKey::from_hex( + "a07a397f6b31c803f5d7f0c4620576cb03c66c12cdbdb6cd91d001d6f0052de201", + ) + .unwrap(); + let privk_9 = StacksPrivateKey::from_hex( + "f395129abc42c57e394dcceebeca9f51f0cb0a3f1c3a899d62e40b9340c7cc1101", + ) + .unwrap(); + + let pubk_1 = StacksPublicKey::from_private(&privk_1); + let pubk_2 = StacksPublicKey::from_private(&privk_2); + let pubk_3 = StacksPublicKey::from_private(&privk_3); + let pubk_4 = StacksPublicKey::from_private(&privk_4); + let pubk_5 = StacksPublicKey::from_private(&privk_5); + let pubk_6 = StacksPublicKey::from_private(&privk_6); + let pubk_7 = StacksPublicKey::from_private(&privk_7); + let pubk_8 = StacksPublicKey::from_private(&privk_8); + let pubk_9 = StacksPublicKey::from_private(&privk_9); + + let origin_auth = TransactionAuth::Standard( + TransactionSpendingCondition::new_multisig_order_independent_p2sh( + 3, + vec![ + pubk_1.clone(), + pubk_2.clone(), + pubk_3.clone(), + pubk_4.clone(), + pubk_5.clone(), + pubk_6.clone(), + pubk_7.clone(), + pubk_8.clone(), + pubk_9.clone(), + ], + ) + .unwrap(), + ); + + let origin_address = origin_auth.origin().address_mainnet(); + assert_eq!( + origin_address, + StacksAddress { + version: C32_ADDRESS_VERSION_MAINNET_MULTISIG, + bytes: Hash160::from_hex("315d672961ef2583faf4107ab4ec5566014c867c").unwrap(), + } + ); + + let txs = tx_stacks_transaction_test_txs(&origin_auth); + + for mut tx in txs { + assert_eq!(tx.auth().origin().num_signatures(), 0); + + let tx_signer = StacksTransactionSigner::new(&tx); + + let initialSigHash = tx.sign_begin(); + let sig3 = tx.sign_no_append_origin(&initialSigHash, &privk_3).unwrap(); + let sig1 = tx.sign_no_append_origin(&initialSigHash, &privk_1).unwrap(); + let sig9 = tx.sign_no_append_origin(&initialSigHash, &privk_9).unwrap(); + + let _ = tx.append_origin_signature(sig1, TransactionPublicKeyEncoding::Compressed); + let _ = tx.append_next_origin(&pubk_2); + let _ = tx.append_origin_signature(sig3, TransactionPublicKeyEncoding::Compressed); + let _ = tx.append_next_origin(&pubk_4); + let _ = tx.append_next_origin(&pubk_5); + let _ = tx.append_next_origin(&pubk_6); + let _ = tx.append_next_origin(&pubk_7); + let _ = tx.append_next_origin(&pubk_8); + let _ = tx.append_origin_signature(sig9, TransactionPublicKeyEncoding::Compressed); + + check_oversign_origin_multisig(&mut tx); + check_sign_no_sponsor(&mut tx); + + assert_eq!(tx.auth().origin().num_signatures(), 3); + + // auth is standard and first two auth fields are signatures for compressed keys. + // third field is the third public key + match tx.auth { + TransactionAuth::Standard(ref origin) => match origin { + TransactionSpendingCondition::OrderIndependentMultisig(ref data) => { + assert_eq!(data.signer, origin_address.bytes); + assert_eq!(data.fields.len(), 9); + assert!(data.fields[0].is_signature()); + assert!(data.fields[1].is_public_key()); + assert!(data.fields[2].is_signature()); + assert!(data.fields[3].is_public_key()); + assert!(data.fields[4].is_public_key()); + assert!(data.fields[5].is_public_key()); + assert!(data.fields[6].is_public_key()); + assert!(data.fields[7].is_public_key()); + assert!(data.fields[8].is_signature()); + + assert_eq!(data.fields[1].as_public_key().unwrap(), pubk_2); + assert_eq!(data.fields[3].as_public_key().unwrap(), pubk_4); + assert_eq!(data.fields[4].as_public_key().unwrap(), pubk_5); + assert_eq!(data.fields[5].as_public_key().unwrap(), pubk_6); + assert_eq!(data.fields[6].as_public_key().unwrap(), pubk_7); + assert_eq!(data.fields[7].as_public_key().unwrap(), pubk_8); + assert_eq!( + data.fields[0].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + assert_eq!( + data.fields[2].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + assert_eq!( + data.fields[8].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + } + _ => assert!(false), + }, + _ => assert!(false), + }; + + test_signature_and_corruption(&tx, true, false); + } + } + + #[test] + fn tx_stacks_transaction_sign_verify_sponsored_order_independent_p2sh_mixed() { + let origin_privk = StacksPrivateKey::from_hex( + "807bbe9e471ac976592cc35e3056592ecc0f778ee653fced3b491a122dd8d59701", + ) + .unwrap(); + + let privk_1 = StacksPrivateKey::from_hex( + "6d430bb91222408e7706c9001cfaeb91b08c2be6d5ac95779ab52c6b431950e001", + ) + .unwrap(); + let privk_2 = StacksPrivateKey::from_hex( + "2a584d899fed1d24e26b524f202763c8ab30260167429f157f1c119f550fa6af01", + ) + .unwrap(); + let privk_3 = StacksPrivateKey::from_hex( + "d5200dee706ee53ae98a03fba6cf4fdcc5084c30cfa9e1b3462dcdeaa3e0f1d201", + ) + .unwrap(); + + let pubk_1 = StacksPublicKey::from_private(&privk_1); + let pubk_2 = StacksPublicKey::from_private(&privk_2); + let pubk_3 = StacksPublicKey::from_private(&privk_3); + + let random_sponsor = StacksPrivateKey::new(); // what the origin sees + + let auth = TransactionAuth::Sponsored( + TransactionSpendingCondition::new_singlesig_p2pkh(StacksPublicKey::from_private( + &origin_privk, + )) + .unwrap(), + TransactionSpendingCondition::new_singlesig_p2pkh(StacksPublicKey::from_private( + &random_sponsor, + )) + .unwrap(), + ); + + let real_sponsor = TransactionSpendingCondition::new_multisig_order_independent_p2sh( + 2, + vec![pubk_1.clone(), pubk_2.clone(), pubk_3.clone()], + ) + .unwrap(); + + let origin_address = auth.origin().address_mainnet(); + let sponsor_address = real_sponsor.address_mainnet(); + + assert_eq!( + origin_address, + StacksAddress { + version: C32_ADDRESS_VERSION_MAINNET_SINGLESIG, + bytes: Hash160::from_hex("3597aaa4bde720be93e3829aae24e76e7fcdfd3e").unwrap(), + } + ); + assert_eq!( + sponsor_address, + StacksAddress { + version: C32_ADDRESS_VERSION_MAINNET_MULTISIG, + bytes: Hash160::from_hex("a23ea89d6529ac48ac766f720e480beec7f19273").unwrap(), + } + ); + + let txs = tx_stacks_transaction_test_txs(&auth); + + for mut tx in txs { + assert_eq!(tx.auth().origin().num_signatures(), 0); + assert_eq!(tx.auth().sponsor().unwrap().num_signatures(), 0); + + tx.set_tx_fee(123); + tx.set_sponsor_nonce(456).unwrap(); + let mut tx_signer = StacksTransactionSigner::new(&tx); + + tx_signer.sign_origin(&origin_privk).unwrap(); + + // sponsor sets and pays fee after origin signs + let mut origin_tx = tx_signer.get_tx_incomplete(); + origin_tx.auth.set_sponsor(real_sponsor.clone()).unwrap(); + origin_tx.set_tx_fee(456); + origin_tx.set_sponsor_nonce(789).unwrap(); + + let initialSigHash = tx_signer.sighash; + let sig1 = origin_tx + .sign_no_append_sponsor(&initialSigHash, &privk_1) + .unwrap(); + let sig3 = origin_tx + .sign_no_append_sponsor(&initialSigHash, &privk_3) + .unwrap(); + + let _ = + origin_tx.append_sponsor_signature(sig1, TransactionPublicKeyEncoding::Compressed); + let _ = origin_tx.append_next_sponsor(&pubk_2); + let _ = + origin_tx.append_sponsor_signature(sig3, TransactionPublicKeyEncoding::Compressed); + + tx.set_tx_fee(456); + tx.set_sponsor_nonce(789).unwrap(); + + check_oversign_origin_singlesig(&mut origin_tx); + check_oversign_sponsor_multisig(&mut origin_tx); + + assert_eq!(origin_tx.auth().origin().num_signatures(), 1); + assert_eq!(origin_tx.auth().sponsor().unwrap().num_signatures(), 2); + + // tx and origin_tx are otherwise equal + assert_eq!(tx.version, origin_tx.version); + assert_eq!(tx.chain_id, origin_tx.chain_id); + assert_eq!(tx.get_tx_fee(), origin_tx.get_tx_fee()); + assert_eq!(tx.get_origin_nonce(), origin_tx.get_origin_nonce()); + assert_eq!(tx.get_sponsor_nonce(), origin_tx.get_sponsor_nonce()); + assert_eq!(tx.anchor_mode, origin_tx.anchor_mode); + assert_eq!(tx.post_condition_mode, origin_tx.post_condition_mode); + assert_eq!(tx.post_conditions, origin_tx.post_conditions); + assert_eq!(tx.payload, origin_tx.payload); + + // auth is standard and first two auth fields are signatures for compressed keys. + // third field is the third public key + match origin_tx.auth { + TransactionAuth::Sponsored(ref origin, ref sponsor) => { + match origin { + TransactionSpendingCondition::Singlesig(ref data) => { + assert_eq!(data.key_encoding, TransactionPublicKeyEncoding::Compressed); + assert_eq!(data.signer, origin_address.bytes); + } + _ => assert!(false), + } + match sponsor { + TransactionSpendingCondition::OrderIndependentMultisig(ref data) => { + assert_eq!(data.signer, sponsor_address.bytes); + assert_eq!(data.fields.len(), 3); + assert!(data.fields[0].is_signature()); + assert!(data.fields[1].is_public_key()); + assert!(data.fields[2].is_signature()); + + assert_eq!( + data.fields[0].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + assert_eq!( + data.fields[2].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + assert_eq!(data.fields[1].as_public_key().unwrap(), pubk_2); + } + _ => assert!(false), + } + } + _ => assert!(false), + }; + + test_signature_and_corruption(&origin_tx, true, false); + test_signature_and_corruption(&origin_tx, false, true); + } + } + + #[test] + fn tx_stacks_transaction_sign_verify_sponsored_order_independent_p2sh_mixed_5_out_of_5() { + let origin_privk = StacksPrivateKey::from_hex( + "807bbe9e471ac976592cc35e3056592ecc0f778ee653fced3b491a122dd8d59701", + ) + .unwrap(); + + let privk_1 = StacksPrivateKey::from_hex( + "6d430bb91222408e7706c9001cfaeb91b08c2be6d5ac95779ab52c6b431950e001", + ) + .unwrap(); + let privk_2 = StacksPrivateKey::from_hex( + "2a584d899fed1d24e26b524f202763c8ab30260167429f157f1c119f550fa6af01", + ) + .unwrap(); + let privk_3 = StacksPrivateKey::from_hex( + "d5200dee706ee53ae98a03fba6cf4fdcc5084c30cfa9e1b3462dcdeaa3e0f1d201", + ) + .unwrap(); + let privk_4 = StacksPrivateKey::from_hex( + "3beb8916404874f5d5de162c95470951de5b4a7f6ec8d7a20511551821f16db501", + ) + .unwrap(); + let privk_5 = StacksPrivateKey::from_hex( + "601aa0939e98efec29a4dc645377c9d4acaa0b7318444ec8fd7d090d0b36d85b01", + ) + .unwrap(); + + let pubk_1 = StacksPublicKey::from_private(&privk_1); + let pubk_2 = StacksPublicKey::from_private(&privk_2); + let pubk_3 = StacksPublicKey::from_private(&privk_3); + let pubk_4 = StacksPublicKey::from_private(&privk_4); + let pubk_5 = StacksPublicKey::from_private(&privk_5); + + let random_sponsor = StacksPrivateKey::new(); // what the origin sees + + let auth = TransactionAuth::Sponsored( + TransactionSpendingCondition::new_singlesig_p2pkh(StacksPublicKey::from_private( + &origin_privk, + )) + .unwrap(), + TransactionSpendingCondition::new_singlesig_p2pkh(StacksPublicKey::from_private( + &random_sponsor, + )) + .unwrap(), + ); + + let real_sponsor = TransactionSpendingCondition::new_multisig_order_independent_p2sh( + 5, + vec![ + pubk_1.clone(), + pubk_2.clone(), + pubk_3.clone(), + pubk_4.clone(), + pubk_5.clone(), + ], + ) + .unwrap(); + + let origin_address = auth.origin().address_mainnet(); + let sponsor_address = real_sponsor.address_mainnet(); + + assert_eq!( + origin_address, + StacksAddress { + version: C32_ADDRESS_VERSION_MAINNET_SINGLESIG, + bytes: Hash160::from_hex("3597aaa4bde720be93e3829aae24e76e7fcdfd3e").unwrap(), + } + ); + assert_eq!( + sponsor_address, + StacksAddress { + version: C32_ADDRESS_VERSION_MAINNET_MULTISIG, + bytes: Hash160::from_hex("fc29d14be615b0f72a66b920040c2b5b8124990b").unwrap(), + } + ); + + let txs = tx_stacks_transaction_test_txs(&auth); + + for mut tx in txs { + assert_eq!(tx.auth().origin().num_signatures(), 0); + assert_eq!(tx.auth().sponsor().unwrap().num_signatures(), 0); + + tx.set_tx_fee(123); + tx.set_sponsor_nonce(456).unwrap(); + let mut tx_signer = StacksTransactionSigner::new(&tx); + + tx_signer.sign_origin(&origin_privk).unwrap(); + + // sponsor sets and pays fee after origin signs + let mut origin_tx = tx_signer.get_tx_incomplete(); + origin_tx.auth.set_sponsor(real_sponsor.clone()).unwrap(); + origin_tx.set_tx_fee(456); + origin_tx.set_sponsor_nonce(789).unwrap(); + + let initialSigHash = tx_signer.sighash; + let sig1 = origin_tx + .sign_no_append_sponsor(&initialSigHash, &privk_1) + .unwrap(); + let sig3 = origin_tx + .sign_no_append_sponsor(&initialSigHash, &privk_3) + .unwrap(); + let sig2 = origin_tx + .sign_no_append_sponsor(&initialSigHash, &privk_2) + .unwrap(); + let sig4 = origin_tx + .sign_no_append_sponsor(&initialSigHash, &privk_4) + .unwrap(); + let sig5 = origin_tx + .sign_no_append_sponsor(&initialSigHash, &privk_5) + .unwrap(); + + let _ = + origin_tx.append_sponsor_signature(sig1, TransactionPublicKeyEncoding::Compressed); + let _ = + origin_tx.append_sponsor_signature(sig2, TransactionPublicKeyEncoding::Compressed); + let _ = + origin_tx.append_sponsor_signature(sig3, TransactionPublicKeyEncoding::Compressed); + let _ = + origin_tx.append_sponsor_signature(sig4, TransactionPublicKeyEncoding::Compressed); + let _ = + origin_tx.append_sponsor_signature(sig5, TransactionPublicKeyEncoding::Compressed); + + tx.set_tx_fee(456); + tx.set_sponsor_nonce(789).unwrap(); + + check_oversign_origin_singlesig(&mut origin_tx); + check_oversign_sponsor_multisig(&mut origin_tx); + + assert_eq!(origin_tx.auth().origin().num_signatures(), 1); + assert_eq!(origin_tx.auth().sponsor().unwrap().num_signatures(), 5); + + // tx and origin_tx are otherwise equal + assert_eq!(tx.version, origin_tx.version); + assert_eq!(tx.chain_id, origin_tx.chain_id); + assert_eq!(tx.get_tx_fee(), origin_tx.get_tx_fee()); + assert_eq!(tx.get_origin_nonce(), origin_tx.get_origin_nonce()); + assert_eq!(tx.get_sponsor_nonce(), origin_tx.get_sponsor_nonce()); + assert_eq!(tx.anchor_mode, origin_tx.anchor_mode); + assert_eq!(tx.post_condition_mode, origin_tx.post_condition_mode); + assert_eq!(tx.post_conditions, origin_tx.post_conditions); + assert_eq!(tx.payload, origin_tx.payload); + + // auth is standard and first two auth fields are signatures for compressed keys. + // third field is the third public key + match origin_tx.auth { + TransactionAuth::Sponsored(ref origin, ref sponsor) => { + match origin { + TransactionSpendingCondition::Singlesig(ref data) => { + assert_eq!(data.key_encoding, TransactionPublicKeyEncoding::Compressed); + assert_eq!(data.signer, origin_address.bytes); + } + _ => assert!(false), + } + match sponsor { + TransactionSpendingCondition::OrderIndependentMultisig(ref data) => { + assert_eq!(data.signer, sponsor_address.bytes); + assert_eq!(data.fields.len(), 5); + assert!(data.fields[0].is_signature()); + assert!(data.fields[1].is_signature()); + assert!(data.fields[2].is_signature()); + assert!(data.fields[3].is_signature()); + assert!(data.fields[4].is_signature()); + + assert_eq!( + data.fields[0].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + assert_eq!( + data.fields[1].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + assert_eq!( + data.fields[2].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + assert_eq!( + data.fields[3].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + assert_eq!( + data.fields[4].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + } + _ => assert!(false), + } + } + _ => assert!(false), + }; + + test_signature_and_corruption(&origin_tx, true, false); + test_signature_and_corruption(&origin_tx, false, true); + } + } + + #[test] + fn tx_stacks_transaction_sign_verify_standard_order_independent_p2wsh() { + let privk_1 = StacksPrivateKey::from_hex( + "6d430bb91222408e7706c9001cfaeb91b08c2be6d5ac95779ab52c6b431950e001", + ) + .unwrap(); + let privk_2 = StacksPrivateKey::from_hex( + "2a584d899fed1d24e26b524f202763c8ab30260167429f157f1c119f550fa6af01", + ) + .unwrap(); + let privk_3 = StacksPrivateKey::from_hex( + "d5200dee706ee53ae98a03fba6cf4fdcc5084c30cfa9e1b3462dcdeaa3e0f1d201", + ) + .unwrap(); + + let pubk_1 = StacksPublicKey::from_private(&privk_1); + let pubk_2 = StacksPublicKey::from_private(&privk_2); + let pubk_3 = StacksPublicKey::from_private(&privk_3); + + let origin_auth = TransactionAuth::Standard( + TransactionSpendingCondition::new_multisig_order_independent_p2wsh( + 2, + vec![pubk_1.clone(), pubk_2.clone(), pubk_3.clone()], + ) + .unwrap(), + ); + + let origin_address = origin_auth.origin().address_mainnet(); + assert_eq!( + origin_address, + StacksAddress { + version: C32_ADDRESS_VERSION_MAINNET_MULTISIG, + bytes: Hash160::from_hex("f5cfb61a07fb41a32197da01ce033888f0fe94a7").unwrap(), + } + ); + + let txs = tx_stacks_transaction_test_txs(&origin_auth); + + for mut tx in txs { + assert_eq!(tx.auth().origin().num_signatures(), 0); + + let tx_signer = StacksTransactionSigner::new(&tx); + + let initialSigHash = tx.sign_begin(); + let sig3 = tx.sign_no_append_origin(&initialSigHash, &privk_3).unwrap(); + let sig1 = tx.sign_no_append_origin(&initialSigHash, &privk_1).unwrap(); + + let _ = tx.append_origin_signature(sig1, TransactionPublicKeyEncoding::Compressed); + let _ = tx.append_next_origin(&pubk_2); + let _ = tx.append_origin_signature(sig3, TransactionPublicKeyEncoding::Compressed); + + check_oversign_origin_multisig(&mut tx); + check_oversign_origin_multisig_uncompressed(&mut tx); + check_sign_no_sponsor(&mut tx); + + assert_eq!(tx.auth().origin().num_signatures(), 2); + + // auth is standard and first two auth fields are signatures for compressed keys. + // third field is the third public key + match tx.auth { + TransactionAuth::Standard(ref origin) => match origin { + TransactionSpendingCondition::OrderIndependentMultisig(ref data) => { + assert_eq!(data.signer, origin_address.bytes); + assert_eq!(data.fields.len(), 3); + assert!(data.fields[0].is_signature()); + assert!(data.fields[1].is_public_key()); + assert!(data.fields[2].is_signature()); + + assert_eq!(data.fields[1].as_public_key().unwrap(), pubk_2); + assert_eq!( + data.fields[0].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + assert_eq!( + data.fields[2].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + } + _ => assert!(false), + }, + _ => assert!(false), + }; + + test_signature_and_corruption(&tx, true, false); + } + } + + #[test] + fn tx_stacks_transaction_sign_verify_standard_order_independent_p2wsh_4_out_of_6() { + let privk_1 = StacksPrivateKey::from_hex( + "6d430bb91222408e7706c9001cfaeb91b08c2be6d5ac95779ab52c6b431950e001", + ) + .unwrap(); + let privk_2 = StacksPrivateKey::from_hex( + "2a584d899fed1d24e26b524f202763c8ab30260167429f157f1c119f550fa6af01", + ) + .unwrap(); + let privk_3 = StacksPrivateKey::from_hex( + "d5200dee706ee53ae98a03fba6cf4fdcc5084c30cfa9e1b3462dcdeaa3e0f1d201", + ) + .unwrap(); + let privk_4 = StacksPrivateKey::from_hex( + "3beb8916404874f5d5de162c95470951de5b4a7f6ec8d7a20511551821f16db501", + ) + .unwrap(); + let privk_5 = StacksPrivateKey::from_hex( + "601aa0939e98efec29a4dc645377c9d4acaa0b7318444ec8fd7d090d0b36d85b01", + ) + .unwrap(); + let privk_6 = StacksPrivateKey::from_hex( + "5a4ca3db5a3b36bc32d9f2f0894435cbc4b2b1207e95ee283616d9a0797210da01", + ) + .unwrap(); + + let pubk_1 = StacksPublicKey::from_private(&privk_1); + let pubk_2 = StacksPublicKey::from_private(&privk_2); + let pubk_3 = StacksPublicKey::from_private(&privk_3); + let pubk_4 = StacksPublicKey::from_private(&privk_4); + let pubk_5 = StacksPublicKey::from_private(&privk_5); + let pubk_6 = StacksPublicKey::from_private(&privk_6); + + let origin_auth = TransactionAuth::Standard( + TransactionSpendingCondition::new_multisig_order_independent_p2wsh( + 4, + vec![ + pubk_1.clone(), + pubk_2.clone(), + pubk_3.clone(), + pubk_4.clone(), + pubk_5.clone(), + pubk_6.clone(), + ], + ) + .unwrap(), + ); + + let origin_address = origin_auth.origin().address_mainnet(); + assert_eq!( + origin_address, + StacksAddress { + version: C32_ADDRESS_VERSION_MAINNET_MULTISIG, + bytes: Hash160::from_hex("e2a4ae14ffb0a4a0982a06d07b97d57268d2bf94").unwrap(), + } + ); + + let txs = tx_stacks_transaction_test_txs(&origin_auth); + + for mut tx in txs { + assert_eq!(tx.auth().origin().num_signatures(), 0); + + let tx_signer = StacksTransactionSigner::new(&tx); + + let initialSigHash = tx.sign_begin(); + let sig3 = tx.sign_no_append_origin(&initialSigHash, &privk_3).unwrap(); + let sig1 = tx.sign_no_append_origin(&initialSigHash, &privk_1).unwrap(); + let sig6 = tx.sign_no_append_origin(&initialSigHash, &privk_6).unwrap(); + let sig5 = tx.sign_no_append_origin(&initialSigHash, &privk_5).unwrap(); + + let _ = tx.append_origin_signature(sig1, TransactionPublicKeyEncoding::Compressed); + let _ = tx.append_next_origin(&pubk_2); + let _ = tx.append_origin_signature(sig3, TransactionPublicKeyEncoding::Compressed); + let _ = tx.append_next_origin(&pubk_4); + let _ = tx.append_origin_signature(sig5, TransactionPublicKeyEncoding::Compressed); + let _ = tx.append_origin_signature(sig6, TransactionPublicKeyEncoding::Compressed); + + check_oversign_origin_multisig(&mut tx); + check_oversign_origin_multisig_uncompressed(&mut tx); + check_sign_no_sponsor(&mut tx); + + assert_eq!(tx.auth().origin().num_signatures(), 4); + + // auth is standard and first two auth fields are signatures for compressed keys. + // third field is the third public key + match tx.auth { + TransactionAuth::Standard(ref origin) => match origin { + TransactionSpendingCondition::OrderIndependentMultisig(ref data) => { + assert_eq!(data.signer, origin_address.bytes); + assert_eq!(data.fields.len(), 6); + assert!(data.fields[0].is_signature()); + assert!(data.fields[1].is_public_key()); + assert!(data.fields[2].is_signature()); + assert!(data.fields[3].is_public_key()); + assert!(data.fields[4].is_signature()); + assert!(data.fields[5].is_signature()); + + assert_eq!(data.fields[1].as_public_key().unwrap(), pubk_2); + assert_eq!(data.fields[3].as_public_key().unwrap(), pubk_4); + assert_eq!( + data.fields[0].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + assert_eq!( + data.fields[2].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + assert_eq!( + data.fields[4].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + assert_eq!( + data.fields[5].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + } + _ => assert!(false), + }, + _ => assert!(false), + }; + + test_signature_and_corruption(&tx, true, false); + } + } + + #[test] + fn tx_stacks_transaction_sign_verify_sponsored_order_independent_p2wsh() { + let origin_privk = StacksPrivateKey::from_hex( + "807bbe9e471ac976592cc35e3056592ecc0f778ee653fced3b491a122dd8d59701", + ) + .unwrap(); + + let privk_1 = StacksPrivateKey::from_hex( + "6d430bb91222408e7706c9001cfaeb91b08c2be6d5ac95779ab52c6b431950e001", + ) + .unwrap(); + let privk_2 = StacksPrivateKey::from_hex( + "2a584d899fed1d24e26b524f202763c8ab30260167429f157f1c119f550fa6af01", + ) + .unwrap(); + let privk_3 = StacksPrivateKey::from_hex( + "d5200dee706ee53ae98a03fba6cf4fdcc5084c30cfa9e1b3462dcdeaa3e0f1d201", + ) + .unwrap(); + + let pubk_1 = StacksPublicKey::from_private(&privk_1); + let pubk_2 = StacksPublicKey::from_private(&privk_2); + let pubk_3 = StacksPublicKey::from_private(&privk_3); + + let random_sponsor = StacksPrivateKey::new(); // what the origin sees + + let auth = TransactionAuth::Sponsored( + TransactionSpendingCondition::new_singlesig_p2pkh(StacksPublicKey::from_private( + &origin_privk, + )) + .unwrap(), + TransactionSpendingCondition::new_singlesig_p2pkh(StacksPublicKey::from_private( + &random_sponsor, + )) + .unwrap(), + ); + + let real_sponsor = TransactionSpendingCondition::new_multisig_order_independent_p2wsh( + 2, + vec![pubk_1.clone(), pubk_2.clone(), pubk_3.clone()], + ) + .unwrap(); + + let origin_address = auth.origin().address_mainnet(); + let sponsor_address = real_sponsor.address_mainnet(); + + assert_eq!( + origin_address, + StacksAddress { + version: C32_ADDRESS_VERSION_MAINNET_SINGLESIG, + bytes: Hash160::from_hex("3597aaa4bde720be93e3829aae24e76e7fcdfd3e").unwrap(), + } + ); + assert_eq!( + sponsor_address, + StacksAddress { + version: C32_ADDRESS_VERSION_MAINNET_MULTISIG, + bytes: Hash160::from_hex("f5cfb61a07fb41a32197da01ce033888f0fe94a7").unwrap(), + } + ); + + let txs = tx_stacks_transaction_test_txs(&auth); + + for mut tx in txs { + assert_eq!(tx.auth().origin().num_signatures(), 0); + assert_eq!(tx.auth().sponsor().unwrap().num_signatures(), 0); + + tx.set_tx_fee(123); + tx.set_sponsor_nonce(456).unwrap(); + let mut tx_signer = StacksTransactionSigner::new(&tx); + + tx_signer.sign_origin(&origin_privk).unwrap(); + + // sponsor sets and pays fee after origin signs + let mut origin_tx = tx_signer.get_tx_incomplete(); + origin_tx.auth.set_sponsor(real_sponsor.clone()).unwrap(); + origin_tx.set_tx_fee(456); + origin_tx.set_sponsor_nonce(789).unwrap(); + + let initialSigHash = tx_signer.sighash; + let sig1 = origin_tx + .sign_no_append_sponsor(&initialSigHash, &privk_1) + .unwrap(); + let sig3 = origin_tx + .sign_no_append_sponsor(&initialSigHash, &privk_3) + .unwrap(); + + let _ = + origin_tx.append_sponsor_signature(sig1, TransactionPublicKeyEncoding::Compressed); + let _ = origin_tx.append_next_sponsor(&pubk_2); + let _ = + origin_tx.append_sponsor_signature(sig3, TransactionPublicKeyEncoding::Compressed); + + tx.set_tx_fee(456); + tx.set_sponsor_nonce(789).unwrap(); + + check_oversign_origin_singlesig(&mut origin_tx); + check_oversign_sponsor_multisig(&mut origin_tx); + check_oversign_sponsor_multisig_uncompressed(&mut origin_tx); + + assert_eq!(origin_tx.auth().origin().num_signatures(), 1); + assert_eq!(origin_tx.auth().sponsor().unwrap().num_signatures(), 2); + + // tx and origin_tx are otherwise equal + assert_eq!(tx.version, origin_tx.version); + assert_eq!(tx.chain_id, origin_tx.chain_id); + assert_eq!(tx.get_tx_fee(), origin_tx.get_tx_fee()); + assert_eq!(tx.get_origin_nonce(), origin_tx.get_origin_nonce()); + assert_eq!(tx.get_sponsor_nonce(), origin_tx.get_sponsor_nonce()); + assert_eq!(tx.anchor_mode, origin_tx.anchor_mode); + assert_eq!(tx.post_condition_mode, origin_tx.post_condition_mode); + assert_eq!(tx.post_conditions, origin_tx.post_conditions); + assert_eq!(tx.payload, origin_tx.payload); + + // auth is standard and first two auth fields are signatures for compressed keys. + // third field is the third public key + match origin_tx.auth { + TransactionAuth::Sponsored(ref origin, ref sponsor) => { + match origin { + TransactionSpendingCondition::Singlesig(ref data) => { + assert_eq!(data.key_encoding, TransactionPublicKeyEncoding::Compressed); + assert_eq!(data.signer, origin_address.bytes); + } + _ => assert!(false), + } + match sponsor { + TransactionSpendingCondition::OrderIndependentMultisig(ref data) => { + assert_eq!(data.signer, sponsor_address.bytes); + assert_eq!(data.fields.len(), 3); + assert!(data.fields[0].is_signature()); + assert!(data.fields[1].is_public_key()); + assert!(data.fields[2].is_signature()); + + assert_eq!( + data.fields[0].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + assert_eq!( + data.fields[2].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + assert_eq!(data.fields[1].as_public_key().unwrap(), pubk_2); + } + _ => assert!(false), + } + } + _ => assert!(false), + }; + + test_signature_and_corruption(&origin_tx, true, false); + test_signature_and_corruption(&origin_tx, false, true); + } + } + + #[test] + fn tx_stacks_transaction_sign_verify_sponsored_order_independent_p2wsh_2_out_of_7() { + let origin_privk = StacksPrivateKey::from_hex( + "807bbe9e471ac976592cc35e3056592ecc0f778ee653fced3b491a122dd8d59701", + ) + .unwrap(); + + let privk_1 = StacksPrivateKey::from_hex( + "6d430bb91222408e7706c9001cfaeb91b08c2be6d5ac95779ab52c6b431950e001", + ) + .unwrap(); + let privk_2 = StacksPrivateKey::from_hex( + "2a584d899fed1d24e26b524f202763c8ab30260167429f157f1c119f550fa6af01", + ) + .unwrap(); + let privk_3 = StacksPrivateKey::from_hex( + "d5200dee706ee53ae98a03fba6cf4fdcc5084c30cfa9e1b3462dcdeaa3e0f1d201", + ) + .unwrap(); + let privk_4 = StacksPrivateKey::from_hex( + "3beb8916404874f5d5de162c95470951de5b4a7f6ec8d7a20511551821f16db501", + ) + .unwrap(); + let privk_5 = StacksPrivateKey::from_hex( + "601aa0939e98efec29a4dc645377c9d4acaa0b7318444ec8fd7d090d0b36d85b01", + ) + .unwrap(); + let privk_6 = StacksPrivateKey::from_hex( + "5a4ca3db5a3b36bc32d9f2f0894435cbc4b2b1207e95ee283616d9a0797210da01", + ) + .unwrap(); + let privk_7 = StacksPrivateKey::from_hex( + "068856c242bfebdc57700fa598fae4e8ebb6b5f6bf932177018071489737d3ff01", + ) + .unwrap(); + + let pubk_1 = StacksPublicKey::from_private(&privk_1); + let pubk_2 = StacksPublicKey::from_private(&privk_2); + let pubk_3 = StacksPublicKey::from_private(&privk_3); + let pubk_4 = StacksPublicKey::from_private(&privk_4); + let pubk_5 = StacksPublicKey::from_private(&privk_5); + let pubk_6 = StacksPublicKey::from_private(&privk_6); + let pubk_7 = StacksPublicKey::from_private(&privk_7); + + let random_sponsor = StacksPrivateKey::new(); // what the origin sees + + let auth = TransactionAuth::Sponsored( + TransactionSpendingCondition::new_singlesig_p2pkh(StacksPublicKey::from_private( + &origin_privk, + )) + .unwrap(), + TransactionSpendingCondition::new_singlesig_p2pkh(StacksPublicKey::from_private( + &random_sponsor, + )) + .unwrap(), + ); + + let real_sponsor = TransactionSpendingCondition::new_multisig_order_independent_p2wsh( + 2, + vec![ + pubk_1.clone(), + pubk_2.clone(), + pubk_3.clone(), + pubk_4.clone(), + pubk_5.clone(), + pubk_6.clone(), + pubk_7.clone(), + ], + ) + .unwrap(); + + let origin_address = auth.origin().address_mainnet(); + let sponsor_address = real_sponsor.address_mainnet(); + + assert_eq!( + origin_address, + StacksAddress { + version: C32_ADDRESS_VERSION_MAINNET_SINGLESIG, + bytes: Hash160::from_hex("3597aaa4bde720be93e3829aae24e76e7fcdfd3e").unwrap(), + } + ); + assert_eq!( + sponsor_address, + StacksAddress { + version: C32_ADDRESS_VERSION_MAINNET_MULTISIG, + bytes: Hash160::from_hex("e3001c2b12f24ba279116d7001e3bd82b2b5eab4").unwrap(), + } + ); + + let txs = tx_stacks_transaction_test_txs(&auth); + + for mut tx in txs { + assert_eq!(tx.auth().origin().num_signatures(), 0); + assert_eq!(tx.auth().sponsor().unwrap().num_signatures(), 0); + + tx.set_tx_fee(123); + tx.set_sponsor_nonce(456).unwrap(); + let mut tx_signer = StacksTransactionSigner::new(&tx); + + tx_signer.sign_origin(&origin_privk).unwrap(); + + // sponsor sets and pays fee after origin signs + let mut origin_tx = tx_signer.get_tx_incomplete(); + origin_tx.auth.set_sponsor(real_sponsor.clone()).unwrap(); + origin_tx.set_tx_fee(456); + origin_tx.set_sponsor_nonce(789).unwrap(); + + let initialSigHash = tx_signer.sighash; + let sig1 = origin_tx + .sign_no_append_sponsor(&initialSigHash, &privk_1) + .unwrap(); + let sig7 = origin_tx + .sign_no_append_sponsor(&initialSigHash, &privk_7) + .unwrap(); + + let _ = + origin_tx.append_sponsor_signature(sig1, TransactionPublicKeyEncoding::Compressed); + let _ = origin_tx.append_next_sponsor(&pubk_2); + let _ = origin_tx.append_next_sponsor(&pubk_3); + let _ = origin_tx.append_next_sponsor(&pubk_4); + let _ = origin_tx.append_next_sponsor(&pubk_5); + let _ = origin_tx.append_next_sponsor(&pubk_6); + let _ = + origin_tx.append_sponsor_signature(sig7, TransactionPublicKeyEncoding::Compressed); + + tx.set_tx_fee(456); + tx.set_sponsor_nonce(789).unwrap(); + + check_oversign_origin_singlesig(&mut origin_tx); + check_oversign_sponsor_multisig(&mut origin_tx); + check_oversign_sponsor_multisig_uncompressed(&mut origin_tx); + + assert_eq!(origin_tx.auth().origin().num_signatures(), 1); + assert_eq!(origin_tx.auth().sponsor().unwrap().num_signatures(), 2); + + // tx and origin_tx are otherwise equal + assert_eq!(tx.version, origin_tx.version); + assert_eq!(tx.chain_id, origin_tx.chain_id); + assert_eq!(tx.get_tx_fee(), origin_tx.get_tx_fee()); + assert_eq!(tx.get_origin_nonce(), origin_tx.get_origin_nonce()); + assert_eq!(tx.get_sponsor_nonce(), origin_tx.get_sponsor_nonce()); + assert_eq!(tx.anchor_mode, origin_tx.anchor_mode); + assert_eq!(tx.post_condition_mode, origin_tx.post_condition_mode); + assert_eq!(tx.post_conditions, origin_tx.post_conditions); + assert_eq!(tx.payload, origin_tx.payload); + + // auth is standard and first two auth fields are signatures for compressed keys. + // third field is the third public key + match origin_tx.auth { + TransactionAuth::Sponsored(ref origin, ref sponsor) => { + match origin { + TransactionSpendingCondition::Singlesig(ref data) => { + assert_eq!(data.key_encoding, TransactionPublicKeyEncoding::Compressed); + assert_eq!(data.signer, origin_address.bytes); + } + _ => assert!(false), + } + match sponsor { + TransactionSpendingCondition::OrderIndependentMultisig(ref data) => { + assert_eq!(data.signer, sponsor_address.bytes); + assert_eq!(data.fields.len(), 7); + assert!(data.fields[0].is_signature()); + assert!(data.fields[1].is_public_key()); + assert!(data.fields[2].is_public_key()); + assert!(data.fields[3].is_public_key()); + assert!(data.fields[4].is_public_key()); + assert!(data.fields[5].is_public_key()); + assert!(data.fields[6].is_signature()); + + assert_eq!( + data.fields[0].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + assert_eq!( + data.fields[6].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + assert_eq!(data.fields[1].as_public_key().unwrap(), pubk_2); + assert_eq!(data.fields[2].as_public_key().unwrap(), pubk_3); + assert_eq!(data.fields[3].as_public_key().unwrap(), pubk_4); + assert_eq!(data.fields[4].as_public_key().unwrap(), pubk_5); + assert_eq!(data.fields[5].as_public_key().unwrap(), pubk_6); + } + _ => assert!(false), + } + } + _ => assert!(false), + }; + + test_signature_and_corruption(&origin_tx, true, false); + test_signature_and_corruption(&origin_tx, false, true); + } + } } diff --git a/stackslib/src/core/mempool.rs b/stackslib/src/core/mempool.rs index 80ec178247..ccb500f17b 100644 --- a/stackslib/src/core/mempool.rs +++ b/stackslib/src/core/mempool.rs @@ -33,7 +33,8 @@ use rusqlite::{ }; use siphasher::sip::SipHasher; // this is SipHash-2-4 use stacks_common::codec::{ - read_next, write_next, Error as codec_error, StacksMessageCodec, MAX_MESSAGE_LEN, + read_next, write_next, DeserializeWithEpoch, Error as codec_error, StacksMessageCodec, + MAX_MESSAGE_LEN, }; use stacks_common::types::chainstate::{BlockHeaderHash, StacksAddress, StacksBlockId}; use stacks_common::util::hash::{to_hex, Sha512Trunc256Sum}; @@ -523,8 +524,11 @@ impl FromRow for MemPoolTxInfo { fn from_row<'a>(row: &'a Row) -> Result { let md = MemPoolTxMetadata::from_row(row)?; let tx_bytes: Vec = row.get_unwrap("tx"); - let tx = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]) - .map_err(|_e| db_error::ParseError)?; + let tx = StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_bytes[..], + StacksEpochId::latest(), + ) + .map_err(|_e| db_error::ParseError)?; if tx.txid() != md.txid { return Err(db_error::ParseError); @@ -1902,7 +1906,7 @@ impl MemPoolDB { nonce: u64, ) -> Result, db_error> { let sql = format!( - "SELECT + "SELECT txid, origin_address, origin_nonce, @@ -2331,8 +2335,11 @@ impl MemPoolDB { block_limit: &ExecutionCost, stacks_epoch_id: &StacksEpochId, ) -> Result<(), MemPoolRejection> { - let tx = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]) - .map_err(MemPoolRejection::DeserializationFailure)?; + let tx = StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_bytes[..], + StacksEpochId::latest(), + ) + .map_err(MemPoolRejection::DeserializationFailure)?; if self.is_tx_blacklisted(&tx.txid())? { // don't re-store this transaction @@ -2696,8 +2703,11 @@ impl MemPoolDB { } let tx_bytes: Vec = row.get_unwrap("tx"); - let tx = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]) - .map_err(|_e| db_error::ParseError)?; + let tx = StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_bytes[..], + StacksEpochId::latest(), + ) + .map_err(|_e| db_error::ParseError)?; test_debug!("Returning txid {}", &txid); ret.push(tx); diff --git a/stackslib/src/core/mod.rs b/stackslib/src/core/mod.rs index b03fe0c8e0..24dcc27688 100644 --- a/stackslib/src/core/mod.rs +++ b/stackslib/src/core/mod.rs @@ -1067,7 +1067,7 @@ impl StacksEpochExtension for StacksEpoch { StacksEpoch { epoch_id: StacksEpochId::Epoch24, start_height: first_burnchain_height + 20, - end_height: STACKS_EPOCH_MAX, + end_height: first_burnchain_height + 24, block_limit: ExecutionCost { write_length: 210210, write_count: 210210, @@ -1170,7 +1170,7 @@ impl StacksEpochExtension for StacksEpoch { StacksEpoch { epoch_id: StacksEpochId::Epoch24, start_height: first_burnchain_height + 20, - end_height: STACKS_EPOCH_MAX, + end_height: first_burnchain_height + 24, block_limit: ExecutionCost { write_length: 210210, write_count: 210210, diff --git a/stackslib/src/main.rs b/stackslib/src/main.rs index 69fb050fd3..9a0bee9e6b 100644 --- a/stackslib/src/main.rs +++ b/stackslib/src/main.rs @@ -69,7 +69,7 @@ use libstackerdb::StackerDBChunkData; use rusqlite::types::ToSql; use rusqlite::{Connection, OpenFlags}; use serde_json::{json, Value}; -use stacks_common::codec::StacksMessageCodec; +use stacks_common::codec::{DeserializeWithEpoch, StacksMessageCodec}; use stacks_common::types::chainstate::{ BlockHeaderHash, BurnchainHeaderHash, PoxId, StacksAddress, StacksBlockId, }; @@ -188,8 +188,9 @@ fn main() { let mut cursor = io::Cursor::new(&tx_bytes); let mut debug_cursor = LogReader::from_reader(&mut cursor); + let epoch_id = parse_input_epoch(3); - let tx = StacksTransaction::consensus_deserialize(&mut debug_cursor) + let tx = StacksTransaction::consensus_deserialize_with_epoch(&mut debug_cursor, epoch_id) .map_err(|e| { eprintln!("Failed to decode transaction: {:?}", &e); eprintln!("Bytes consumed:"); @@ -215,13 +216,17 @@ fn main() { let block_path = &argv[2]; let block_data = fs::read(block_path).expect(&format!("Failed to open {}", block_path)); + let epoch_id = parse_input_epoch(3); - let block = StacksBlock::consensus_deserialize(&mut io::Cursor::new(&block_data)) - .map_err(|_e| { - eprintln!("Failed to decode block"); - process::exit(1); - }) - .unwrap(); + let block = StacksBlock::consensus_deserialize_with_epoch( + &mut io::Cursor::new(&block_data), + epoch_id, + ) + .map_err(|_e| { + eprintln!("Failed to decode block"); + process::exit(1); + }) + .unwrap(); println!("{:#?}", &block); process::exit(0); @@ -256,13 +261,17 @@ fn main() { .unwrap() .expect("No such block"); - let block = - StacksBlock::consensus_deserialize(&mut io::Cursor::new(&block_info.block_data)) - .map_err(|_e| { - eprintln!("Failed to decode block"); - process::exit(1); - }) - .unwrap(); + let epoch_id = parse_input_epoch(4); + + let block = StacksBlock::consensus_deserialize_with_epoch( + &mut io::Cursor::new(&block_info.block_data), + epoch_id, + ) + .map_err(|_e| { + eprintln!("Failed to decode block"); + process::exit(1); + }) + .unwrap(); let microblocks = StacksChainState::find_parent_microblock_stream(chainstate.db(), &block_info) @@ -361,7 +370,7 @@ Given a , obtain a 2100 header hash block inventory (with an empty "Usage: {} can-download-microblock Given a , obtain a 2100 header hash inventory (with an empty header cache), and then -check if the associated microblocks can be downloaded +check if the associated microblocks can be downloaded ", argv[0] ); @@ -1246,6 +1255,7 @@ simulating a miner. let events_file = &argv[3]; let mine_tip_height: u64 = argv[4].parse().expect("Could not parse mine_tip_height"); let mine_max_txns: u64 = argv[5].parse().expect("Could not parse mine-num-txns"); + let epoch_id = parse_input_epoch(6); let sort_db = SortitionDB::open(&sort_db_path, false, PoxConstants::mainnet_default()) .expect(&format!("Failed to open {}", &sort_db_path)); @@ -1347,7 +1357,9 @@ simulating a miner. let raw_tx_hex = item.as_str().unwrap(); let raw_tx_bytes = hex_bytes(&raw_tx_hex[2..]).unwrap(); let mut cursor = io::Cursor::new(&raw_tx_bytes); - let raw_tx = StacksTransaction::consensus_deserialize(&mut cursor).unwrap(); + let raw_tx = + StacksTransaction::consensus_deserialize_with_epoch(&mut cursor, epoch_id) + .unwrap(); if found_block_height { if submit_tx_count >= mine_max_txns { info!("Reached mine_max_txns {}", submit_tx_count); @@ -1459,3 +1471,23 @@ simulating a miner. process::exit(0); } + +fn parse_input_epoch(epoch_arg_index: usize) -> StacksEpochId { + let argv: Vec = env::args().collect(); + + let mut epoch_id = StacksEpochId::latest(); + + if argv.len() > epoch_arg_index { + let epoch_id_u32 = argv[epoch_arg_index] + .parse::() + .expect("Failed to parse epoch id"); + epoch_id = StacksEpochId::try_from(epoch_id_u32) + .map_err(|_e| { + eprintln!("Failed to match epoch number"); + process::exit(1); + }) + .unwrap(); + } + + return epoch_id; +} diff --git a/stackslib/src/net/api/getblock.rs b/stackslib/src/net/api/getblock.rs index 924c165de7..24c04bb045 100644 --- a/stackslib/src/net/api/getblock.rs +++ b/stackslib/src/net/api/getblock.rs @@ -20,9 +20,10 @@ use std::{fs, io}; use regex::{Captures, Regex}; use serde::de::Error as de_Error; -use stacks_common::codec::{StacksMessageCodec, MAX_MESSAGE_LEN}; +use stacks_common::codec::{StacksMessageCodec, DeserializeWithEpoch, MAX_MESSAGE_LEN}; use stacks_common::types::chainstate::StacksBlockId; use stacks_common::types::net::PeerHost; +use stacks_common::types::StacksEpochId; use stacks_common::util::hash::to_hex; use {serde, serde_json}; @@ -301,7 +302,10 @@ impl StacksHttpResponse { // contents will be raw bytes let block_bytes: Vec = contents.try_into()?; - let block = StacksBlock::consensus_deserialize(&mut &block_bytes[..])?; + let block = StacksBlock::consensus_deserialize_with_epoch( + &mut &block_bytes[..], + StacksEpochId::latest(), + )?; Ok(block) } diff --git a/stackslib/src/net/api/postblock.rs b/stackslib/src/net/api/postblock.rs index 3380102101..0b9a3e19ad 100644 --- a/stackslib/src/net/api/postblock.rs +++ b/stackslib/src/net/api/postblock.rs @@ -18,11 +18,12 @@ use std::io::{Read, Write}; use clarity::vm::costs::ExecutionCost; use regex::{Captures, Regex}; -use stacks_common::codec::{Error as CodecError, StacksMessageCodec, MAX_PAYLOAD_LEN}; +use stacks_common::codec::{DeserializeWithEpoch, Error as CodecError, MAX_PAYLOAD_LEN}; use stacks_common::types::chainstate::{ BlockHeaderHash, ConsensusHash, StacksBlockId, StacksPublicKey, }; use stacks_common::types::net::PeerHost; +use stacks_common::types::StacksEpochId; use stacks_common::types::StacksPublicKeyBuffer; use stacks_common::util::hash::{hex_bytes, Hash160, Sha256Sum}; use stacks_common::util::retry::BoundReader; @@ -74,13 +75,18 @@ impl RPCPostBlockRequestHandler { /// Decode a bare block from the body fn parse_postblock_octets(mut body: &[u8]) -> Result { - let block = StacksBlock::consensus_deserialize(&mut body).map_err(|e| { - if let CodecError::DeserializeError(msg) = e { - Error::DecodeError(format!("Failed to deserialize posted transaction: {}", msg)) - } else { - e.into() - } - })?; + let block = + StacksBlock::consensus_deserialize_with_epoch(&mut body, StacksEpochId::latest()) + .map_err(|e| { + if let CodecError::DeserializeError(msg) = e { + Error::DecodeError(format!( + "Failed to deserialize posted transaction: {}", + msg + )) + } else { + e.into() + } + })?; Ok(block) } } diff --git a/stackslib/src/net/api/posttransaction.rs b/stackslib/src/net/api/posttransaction.rs index 3958550e16..ef78e771f2 100644 --- a/stackslib/src/net/api/posttransaction.rs +++ b/stackslib/src/net/api/posttransaction.rs @@ -18,11 +18,12 @@ use std::io::{Read, Write}; use clarity::vm::costs::ExecutionCost; use regex::{Captures, Regex}; -use stacks_common::codec::{Error as CodecError, StacksMessageCodec, MAX_PAYLOAD_LEN}; +use stacks_common::codec::{DeserializeWithEpoch, Error as CodecError, MAX_PAYLOAD_LEN, StacksMessageCodec}; use stacks_common::types::chainstate::{ BlockHeaderHash, ConsensusHash, StacksBlockId, StacksPublicKey, }; use stacks_common::types::net::PeerHost; +use stacks_common::types::StacksEpochId; use stacks_common::types::StacksPublicKeyBuffer; use stacks_common::util::hash::{hex_bytes, to_hex, Hash160, Sha256Sum}; use stacks_common::util::retry::BoundReader; @@ -68,13 +69,18 @@ impl RPCPostTransactionRequestHandler { /// Decode a bare transaction from the body fn parse_posttransaction_octets(mut body: &[u8]) -> Result { - let tx = StacksTransaction::consensus_deserialize(&mut body).map_err(|e| { - if let CodecError::DeserializeError(msg) = e { - Error::DecodeError(format!("Failed to deserialize posted transaction: {}", msg)) - } else { - e.into() - } - })?; + let tx = + StacksTransaction::consensus_deserialize_with_epoch(&mut body, StacksEpochId::latest()) + .map_err(|e| { + if let CodecError::DeserializeError(msg) = e { + Error::DecodeError(format!( + "Failed to deserialize posted transaction: {}", + msg + )) + } else { + e.into() + } + })?; Ok(tx) } @@ -88,7 +94,11 @@ impl RPCPostTransactionRequestHandler { let tx = { let tx_bytes = hex_bytes(&body.tx) .map_err(|_e| Error::DecodeError("Failed to parse tx".into()))?; - StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).map_err(|e| { + StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_bytes[..], + StacksEpochId::latest(), + ) + .map_err(|e| { if let CodecError::DeserializeError(msg) = e { Error::DecodeError(format!("Failed to deserialize posted transaction: {}", msg)) } else { diff --git a/stackslib/src/net/api/tests/getblock.rs b/stackslib/src/net/api/tests/getblock.rs index c873c52620..5e6024005b 100644 --- a/stackslib/src/net/api/tests/getblock.rs +++ b/stackslib/src/net/api/tests/getblock.rs @@ -18,12 +18,12 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use clarity::vm::types::{QualifiedContractIdentifier, StacksAddressExtensions}; use clarity::vm::{ClarityName, ContractName}; -use stacks_common::codec::StacksMessageCodec; +use stacks_common::codec::DeserializeWithEpoch; use stacks_common::types::chainstate::{ ConsensusHash, StacksAddress, StacksBlockId, StacksPrivateKey, }; use stacks_common::types::net::PeerHost; -use stacks_common::types::Address; +use stacks_common::types::{Address, StacksEpochId}; use super::TestRPC; use crate::chainstate::stacks::db::blocks::test::*; @@ -159,7 +159,11 @@ fn test_stream_blocks() { } // should decode back into the block - let staging_block = StacksBlock::consensus_deserialize(&mut &all_block_bytes[..]).unwrap(); + let staging_block = StacksBlock::consensus_deserialize_with_epoch( + &mut &all_block_bytes[..], + StacksEpochId::latest(), + ) + .unwrap(); assert_eq!(staging_block, block); // accept it @@ -184,6 +188,10 @@ fn test_stream_blocks() { } // should decode back into the block - let staging_block = StacksBlock::consensus_deserialize(&mut &all_block_bytes[..]).unwrap(); + let staging_block = StacksBlock::consensus_deserialize_with_epoch( + &mut &all_block_bytes[..], + StacksEpochId::latest(), + ) + .unwrap(); assert_eq!(staging_block, block); } diff --git a/stackslib/src/net/api/tests/getmicroblocks_confirmed.rs b/stackslib/src/net/api/tests/getmicroblocks_confirmed.rs index a4eb372abf..db609b68a0 100644 --- a/stackslib/src/net/api/tests/getmicroblocks_confirmed.rs +++ b/stackslib/src/net/api/tests/getmicroblocks_confirmed.rs @@ -24,6 +24,7 @@ use stacks_common::types::chainstate::{ }; use stacks_common::types::net::PeerHost; use stacks_common::types::Address; +use stacks_common::types::StacksEpochId; use super::TestRPC; use crate::chainstate::stacks::db::blocks::test::*; @@ -91,13 +92,13 @@ fn test_try_make_response() { ) .unwrap(); - let parent_block = make_codec_test_block(25); + let parent_block = make_codec_test_block(25, StacksEpochId::latest()); let parent_consensus_hash = ConsensusHash([0x02; 20]); let mut mblocks = make_sample_microblock_stream(&privk, &parent_block.block_hash()); mblocks.truncate(15); - let mut child_block = make_codec_test_block(25); + let mut child_block = make_codec_test_block(25, StacksEpochId::latest()); let child_consensus_hash = ConsensusHash([0x03; 20]); child_block.header.parent_block = parent_block.block_hash(); diff --git a/stackslib/src/net/api/tests/getmicroblocks_indexed.rs b/stackslib/src/net/api/tests/getmicroblocks_indexed.rs index 0676ecc497..dc249520f6 100644 --- a/stackslib/src/net/api/tests/getmicroblocks_indexed.rs +++ b/stackslib/src/net/api/tests/getmicroblocks_indexed.rs @@ -24,6 +24,7 @@ use stacks_common::types::chainstate::{ }; use stacks_common::types::net::PeerHost; use stacks_common::types::Address; +use stacks_common::types::StacksEpochId; use super::TestRPC; use crate::chainstate::stacks::db::blocks::test::*; @@ -89,7 +90,7 @@ fn test_try_make_response() { "eb05c83546fdd2c79f10f5ad5434a90dd28f7e3acb7c092157aa1bc3656b012c01", ) .unwrap(); - let parent_block = make_codec_test_block(25); + let parent_block = make_codec_test_block(25, StacksEpochId::latest()); let parent_consensus_hash = ConsensusHash([0x02; 20]); let parent_index_block_hash = StacksBlockHeader::make_index_block_hash( &parent_consensus_hash, @@ -99,7 +100,7 @@ fn test_try_make_response() { let mut mblocks = make_sample_microblock_stream(&privk, &parent_block.block_hash()); mblocks.truncate(15); - let mut child_block = make_codec_test_block(25); + let mut child_block = make_codec_test_block(25, StacksEpochId::latest()); let child_consensus_hash = ConsensusHash([0x03; 20]); child_block.header.parent_block = parent_block.block_hash(); diff --git a/stackslib/src/net/api/tests/postblock.rs b/stackslib/src/net/api/tests/postblock.rs index c3d1f29359..19e0edfda5 100644 --- a/stackslib/src/net/api/tests/postblock.rs +++ b/stackslib/src/net/api/tests/postblock.rs @@ -21,6 +21,7 @@ use clarity::vm::{ClarityName, ContractName, Value}; use stacks_common::types::chainstate::{ConsensusHash, StacksAddress}; use stacks_common::types::net::PeerHost; use stacks_common::types::Address; +use stacks_common::types::StacksEpochId; use super::TestRPC; use crate::chainstate::stacks::test::make_codec_test_block; @@ -38,7 +39,7 @@ fn test_try_parse_request() { let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 33333); let mut http = StacksHttp::new(addr.clone(), &ConnectionOptions::default()); - let block = make_codec_test_block(3); + let block = make_codec_test_block(3, StacksEpochId::latest()); let request = StacksHttpRequest::new_post_block(addr.into(), ConsensusHash([0x11; 20]), block.clone()); let bytes = request.try_serialize().unwrap(); diff --git a/stackslib/src/net/codec.rs b/stackslib/src/net/codec.rs index a9de074061..09e7a2a0d6 100644 --- a/stackslib/src/net/codec.rs +++ b/stackslib/src/net/codec.rs @@ -1494,7 +1494,8 @@ impl ProtocolFamily for StacksP2P { #[cfg(test)] pub mod test { - use stacks_common::codec::NEIGHBOR_ADDRESS_ENCODED_SIZE; + use stacks_common::codec::{DeserializeWithEpoch, NEIGHBOR_ADDRESS_ENCODED_SIZE}; + use stacks_common::types::StacksEpochId; use stacks_common::util::hash::hex_bytes; use stacks_common::util::secp256k1::*; @@ -1606,6 +1607,62 @@ pub mod test { } } + pub fn check_codec_and_corruption_with_epoch< + T: StacksMessageCodec + fmt::Debug + Clone + PartialEq + DeserializeWithEpoch, + >( + obj: &T, + bytes: &Vec, + epoch_id: StacksEpochId, + ) -> () { + // obj should serialize to bytes + let mut write_buf: Vec = Vec::with_capacity(bytes.len()); + obj.consensus_serialize(&mut write_buf).unwrap(); + assert_eq!(write_buf, *bytes); + + // bytes should deserialize to obj + let read_buf: Vec = write_buf.clone(); + let res = T::consensus_deserialize_with_epoch(&mut &read_buf[..], epoch_id); + match res { + Ok(out) => { + assert_eq!(out, *obj); + } + Err(e) => { + test_debug!("\nFailed to parse to {:?}: {:?}", obj, bytes); + test_debug!("error: {:?}", &e); + assert!(false); + } + } + + // short message shouldn't parse, but should EOF + if write_buf.len() > 0 { + let mut short_buf = write_buf.clone(); + let short_len = short_buf.len() - 1; + short_buf.truncate(short_len); + + let underflow_res = T::consensus_deserialize_with_epoch(&mut &short_buf[..], epoch_id); + match underflow_res { + Ok(oops) => { + test_debug!( + "\nMissing Underflow: Parsed {:?}\nFrom {:?}\n", + &oops, + &write_buf[0..short_len].to_vec() + ); + } + Err(codec_error::ReadError(io_error)) => match io_error.kind() { + io::ErrorKind::UnexpectedEof => {} + _ => { + test_debug!("Got unexpected I/O error: {:?}", &io_error); + assert!(false); + } + }, + Err(e) => { + test_debug!("Got unexpected Net error: {:?}", &e); + assert!(false); + } + }; + } + } + #[test] fn codec_primitive_types() { check_codec_and_corruption::(&0x01, &vec![0x01]); diff --git a/stackslib/src/net/relay.rs b/stackslib/src/net/relay.rs index 8e2cf2200d..39fe7537f3 100644 --- a/stackslib/src/net/relay.rs +++ b/stackslib/src/net/relay.rs @@ -25,6 +25,7 @@ use clarity::vm::types::{QualifiedContractIdentifier, StacksAddressExtensions}; use clarity::vm::ClarityVersion; use rand::prelude::*; use rand::{thread_rng, Rng}; +use stacks_common::address::public_keys_to_address_hash; use stacks_common::codec::MAX_PAYLOAD_LEN; use stacks_common::types::chainstate::{BurnchainHeaderHash, PoxId, SortitionId, StacksBlockId}; use stacks_common::types::StacksEpochId; @@ -1405,7 +1406,7 @@ impl Relayer { /// Verify that a relayed microblock is not problematic -- i.e. it doesn't contain any /// problematic transactions. This is a static check -- we only look at the microblock /// contents. - /// + /// /// Returns true if the check passed -- i.e. no problems. /// Returns false if not pub fn static_check_problematic_relayed_microblock( @@ -2574,6 +2575,7 @@ pub mod test { use crate::chainstate::stacks::test::codec_all_transactions; use crate::chainstate::stacks::tests::{ make_coinbase, make_coinbase_with_nonce, make_smart_contract_with_version, + make_stacks_transfer_order_independent_p2sh, make_stacks_transfer_order_independent_p2wsh, make_user_stacks_transfer, }; use crate::chainstate::stacks::{Error as ChainstateError, *}; @@ -5745,6 +5747,245 @@ pub mod test { let sortdb = peer.sortdb.take().unwrap(); let mut node = peer.stacks_node.take().unwrap(); + // transaction with versioned contract was filtered and no error in the block will appear + assert_eq!(stacks_block.txs.len(), 1); + peer.sortdb = Some(sortdb); + peer.stacks_node = Some(node); + } + + // *now* it should succeed, since tenure 28 was in epoch 2.1 + let (burn_ops, stacks_block, microblocks) = peer.make_tenure(&mut make_tenure); + + let (_, _, consensus_hash) = peer.next_burnchain_block(burn_ops.clone()); + + let sortdb = peer.sortdb.take().unwrap(); + let mut node = peer.stacks_node.take().unwrap(); + // no filtered transactions in epoch 2.1, all valid + assert_eq!(stacks_block.txs.len(), 2); + match Relayer::process_new_anchored_block( + &sortdb.index_conn(), + &mut node.chainstate, + &consensus_hash, + &stacks_block, + 123, + ) { + Ok(x) => { + assert!(x, "Failed to process valid pay-to-contract block"); + } + Err(e) => { + panic!("Got unexpected error {:?}", &e); + } + }; + peer.sortdb = Some(sortdb); + peer.stacks_node = Some(node); + } + + #[test] + fn test_block_order_independent_multisig_mempool_rejection_until_v210() { + let mut peer_config = TestPeerConfig::new(function_name!(), 4250, 4251); + + let privk_1 = StacksPrivateKey::from_hex( + "6d430bb91222408e7706c9001cfaeb91b08c2be6d5ac95779ab52c6b431950e001", + ) + .unwrap(); + let privk_2 = StacksPrivateKey::from_hex( + "2a584d899fed1d24e26b524f202763c8ab30260167429f157f1c119f550fa6af01", + ) + .unwrap(); + let privk_3 = StacksPrivateKey::from_hex( + "d5200dee706ee53ae98a03fba6cf4fdcc5084c30cfa9e1b3462dcdeaa3e0f1d201", + ) + .unwrap(); + let num_sigs = 2; + + let first_multisig = StacksAddress::from_public_keys( + C32_ADDRESS_VERSION_TESTNET_MULTISIG, + &AddressHashMode::SerializeP2SH, + num_sigs, + &vec![ + StacksPublicKey::from_private(&privk_1), + StacksPublicKey::from_private(&privk_2), + StacksPublicKey::from_private(&privk_3), + ], + ) + .unwrap() + .to_account_principal(); + let second_multisig = StacksAddress::from_public_keys( + C32_ADDRESS_VERSION_TESTNET_MULTISIG, + &AddressHashMode::SerializeP2WSH, + num_sigs, + &vec![ + StacksPublicKey::from_private(&privk_1), + StacksPublicKey::from_private(&privk_2), + StacksPublicKey::from_private(&privk_3), + ], + ) + .unwrap() + .to_account_principal(); + let initial_balances = vec![ + ( + PrincipalData::from(peer_config.spending_account.origin_address().unwrap()), + 1000000, + ), + (first_multisig.clone(), 1000000), + (second_multisig.clone(), 1000000), + ]; + + let epochs = vec![ + StacksEpoch { + epoch_id: StacksEpochId::Epoch10, + start_height: 0, + end_height: 0, + block_limit: ExecutionCost::max_value(), + network_epoch: PEER_VERSION_EPOCH_1_0, + }, + StacksEpoch { + epoch_id: StacksEpochId::Epoch20, + start_height: 0, + end_height: 0, + block_limit: ExecutionCost::max_value(), + network_epoch: PEER_VERSION_EPOCH_2_0, + }, + StacksEpoch { + epoch_id: StacksEpochId::Epoch2_05, + start_height: 0, + end_height: 28, // NOTE: the first 25 burnchain blocks have no sortition + block_limit: ExecutionCost::max_value(), + network_epoch: PEER_VERSION_EPOCH_2_05, + }, + StacksEpoch { + epoch_id: StacksEpochId::Epoch21, + start_height: 28, + end_height: 29, + block_limit: ExecutionCost::max_value(), + network_epoch: PEER_VERSION_EPOCH_2_1, + }, + StacksEpoch { + epoch_id: StacksEpochId::Epoch24, + start_height: 29, + end_height: 30, + block_limit: ExecutionCost::max_value(), + network_epoch: PEER_VERSION_EPOCH_2_4, + }, + StacksEpoch { + epoch_id: StacksEpochId::Epoch30, + start_height: 30, + end_height: STACKS_EPOCH_MAX, + block_limit: ExecutionCost::max_value(), + network_epoch: PEER_VERSION_EPOCH_3_0, + }, + ]; + + peer_config.epochs = Some(epochs); + peer_config.initial_balances = initial_balances; + + let mut peer = TestPeer::new(peer_config); + let order_independent_p2sh_opt: RefCell> = RefCell::new(None); + let order_independent_p2wsh_opt: RefCell> = RefCell::new(None); + let nonce: RefCell = RefCell::new(0); + + let mut make_tenure = + |miner: &mut TestMiner, + sortdb: &mut SortitionDB, + chainstate: &mut StacksChainState, + vrfproof: VRFProof, + parent_opt: Option<&StacksBlock>, + microblock_parent_opt: Option<&StacksMicroblockHeader>| { + let tip = SortitionDB::get_canonical_burn_chain_tip(sortdb.conn()).unwrap(); + + let stacks_tip_opt = + NakamotoChainState::get_canonical_block_header(chainstate.db(), sortdb) + .unwrap(); + let parent_tip = match stacks_tip_opt { + None => StacksChainState::get_genesis_header_info(chainstate.db()).unwrap(), + Some(header_tip) => { + let ic = sortdb.index_conn(); + let snapshot = SortitionDB::get_block_snapshot_for_winning_stacks_block( + &ic, + &tip.sortition_id, + &header_tip.anchored_header.block_hash(), + ) + .unwrap() + .unwrap(); // succeeds because we don't fork + StacksChainState::get_anchored_block_header_info( + chainstate.db(), + &snapshot.consensus_hash, + &snapshot.winning_stacks_block_hash, + ) + .unwrap() + .unwrap() + } + }; + + let parent_header_hash = parent_tip.anchored_header.block_hash(); + let parent_consensus_hash = parent_tip.consensus_hash.clone(); + let parent_index_hash = StacksBlockHeader::make_index_block_hash( + &parent_consensus_hash, + &parent_header_hash, + ); + + let next_nonce = *nonce.borrow(); + let coinbase_tx = make_coinbase_with_nonce( + miner, + parent_tip.stacks_block_height as usize, + next_nonce, + None, + ); + + let order_independent_p2sh = make_stacks_transfer_order_independent_p2sh( + &[privk_1, privk_2, privk_3], + num_sigs, + next_nonce, + 1000, + &second_multisig, + 100, + ); + + let order_independent_p2wsh = make_stacks_transfer_order_independent_p2wsh( + &[privk_1, privk_2, privk_3], + num_sigs, + next_nonce, + 1000, + &first_multisig, + 100, + ); + + *order_independent_p2sh_opt.borrow_mut() = Some(order_independent_p2sh); + *order_independent_p2wsh_opt.borrow_mut() = Some(order_independent_p2wsh); + *nonce.borrow_mut() = next_nonce + 1; + + let mut mblock_pubkey_hash_bytes = [0u8; 20]; + mblock_pubkey_hash_bytes.copy_from_slice(&coinbase_tx.txid()[0..20]); + + let builder = StacksBlockBuilder::make_block_builder( + chainstate.mainnet, + &parent_tip, + vrfproof, + tip.total_burn, + Hash160(mblock_pubkey_hash_bytes), + ) + .unwrap(); + + let anchored_block = StacksBlockBuilder::make_anchored_block_from_txs( + builder, + chainstate, + &sortdb.index_conn(), + vec![coinbase_tx], + ) + .unwrap(); + + eprintln!("{:?}", &anchored_block.0); + (anchored_block.0, vec![]) + }; + + for i in 0..4 { + let (burn_ops, stacks_block, microblocks) = peer.make_tenure(&mut make_tenure); + let (_, _, consensus_hash) = peer.next_burnchain_block(burn_ops.clone()); + + let sortdb = peer.sortdb.take().unwrap(); + let mut node = peer.stacks_node.take().unwrap(); + + // the empty block should be accepted match Relayer::process_new_anchored_block( &sortdb.index_conn(), &mut node.chainstate, @@ -5753,20 +5994,63 @@ pub mod test { 123, ) { Ok(x) => { - panic!("Stored pay-to-contract stacks block before epoch 2.1"); + assert!(x, "Did not accept valid block"); } - Err(chainstate_error::InvalidStacksBlock(_)) => {} Err(e) => { panic!("Got unexpected error {:?}", &e); } }; + + // process it + peer.coord.handle_new_stacks_block().unwrap(); + + // the mempool would reject both order independent multisig transactions, since we're not yet at tenure 30 + let order_independent_p2sh = (*order_independent_p2sh_opt.borrow()).clone().unwrap(); + let order_independent_p2sh_len = order_independent_p2sh.serialize_to_vec().len(); + match node.chainstate.will_admit_mempool_tx( + &sortdb.index_conn(), + &consensus_hash, + &stacks_block.block_hash(), + &order_independent_p2sh, + order_independent_p2sh_len as u64, + ) { + Err(MemPoolRejection::Other(msg)) => { + assert!(msg.find("not supported in this epoch").is_some()); + } + Err(e) => { + panic!("will_admit_mempool_tx {:?}", &e); + } + Ok(_) => { + panic!("will_admit_mempool_tx succeeded"); + } + }; + + let order_independent_p2wsh = (*order_independent_p2wsh_opt.borrow()).clone().unwrap(); + let order_independent_p2wsh_len = order_independent_p2wsh.serialize_to_vec().len(); + match node.chainstate.will_admit_mempool_tx( + &sortdb.index_conn(), + &consensus_hash, + &stacks_block.block_hash(), + &order_independent_p2wsh, + order_independent_p2wsh_len as u64, + ) { + Err(MemPoolRejection::Other(msg)) => { + assert!(msg.find("not supported in this epoch").is_some()); + } + Err(e) => { + panic!("will_admit_mempool_tx {:?}", &e); + } + Ok(_) => { + panic!("will_admit_mempool_tx succeeded"); + } + }; + peer.sortdb = Some(sortdb); peer.stacks_node = Some(node); } - // *now* it should succeed, since tenure 28 was in epoch 2.1 + // *now* it should succeed, since tenure 30 was in epoch 3.0 let (burn_ops, stacks_block, microblocks) = peer.make_tenure(&mut make_tenure); - let (_, _, consensus_hash) = peer.next_burnchain_block(burn_ops.clone()); let sortdb = peer.sortdb.take().unwrap(); @@ -5779,12 +6063,45 @@ pub mod test { 123, ) { Ok(x) => { - assert!(x, "Failed to process valid pay-to-contract block"); + assert!(x, "Failed to process valid versioned smart contract block"); } Err(e) => { panic!("Got unexpected error {:?}", &e); } }; + + peer.coord.handle_new_stacks_block().unwrap(); + + let order_independent_p2sh = (*order_independent_p2sh_opt.borrow()).clone().unwrap(); + let order_independent_p2sh_len = order_independent_p2sh.serialize_to_vec().len(); + match node.chainstate.will_admit_mempool_tx( + &sortdb.index_conn(), + &consensus_hash, + &stacks_block.block_hash(), + &order_independent_p2sh, + order_independent_p2sh_len as u64, + ) { + Err(e) => { + panic!("will_admit_mempool_tx {:?}", &e); + } + Ok(_) => {} + }; + + let order_independent_p2wsh = (*order_independent_p2wsh_opt.borrow()).clone().unwrap(); + let order_independent_p2wsh_len = order_independent_p2wsh.serialize_to_vec().len(); + match node.chainstate.will_admit_mempool_tx( + &sortdb.index_conn(), + &consensus_hash, + &stacks_block.block_hash(), + &order_independent_p2wsh, + order_independent_p2wsh_len as u64, + ) { + Err(e) => { + panic!("will_admit_mempool_tx {:?}", &e); + } + Ok(_) => {} + }; + peer.sortdb = Some(sortdb); peer.stacks_node = Some(node); } diff --git a/stackslib/src/net/server.rs b/stackslib/src/net/server.rs index e93819e34e..88ba77554e 100644 --- a/stackslib/src/net/server.rs +++ b/stackslib/src/net/server.rs @@ -21,6 +21,7 @@ use std::sync::mpsc::{sync_channel, Receiver, RecvError, SendError, SyncSender, use mio::net as mio_net; use stacks_common::types::net::{PeerAddress, PeerHost}; use stacks_common::util::get_epoch_time_secs; +use stacks_common::types::StacksEpochId; use crate::burnchains::{Burnchain, BurnchainView}; use crate::chainstate::burn::db::sortdb::SortitionDB; @@ -883,7 +884,7 @@ mod test { 1, 0, |client_id, ref mut chainstate| { - let peer_server_block = make_codec_test_block(25); + let peer_server_block = make_codec_test_block(25, StacksEpochId::latest()); let peer_server_consensus_hash = ConsensusHash([(client_id + 1) as u8; 20]); let index_block_hash = StacksBlockHeader::make_index_block_hash( &peer_server_consensus_hash, @@ -916,7 +917,7 @@ mod test { // should be a Block let http_response_bytes = http_response_bytes_res.unwrap(); - let peer_server_block = make_codec_test_block(25); + let peer_server_block = make_codec_test_block(25, StacksEpochId::latest()); let peer_server_consensus_hash = ConsensusHash([(client_id + 1) as u8; 20]); let index_block_hash = StacksBlockHeader::make_index_block_hash( &peer_server_consensus_hash, @@ -951,7 +952,7 @@ mod test { 10, 0, |client_id, ref mut chainstate| { - let peer_server_block = make_codec_test_block(25); + let peer_server_block = make_codec_test_block(25, StacksEpochId::latest()); let peer_server_consensus_hash = ConsensusHash([(client_id + 1) as u8; 20]); let index_block_hash = StacksBlockHeader::make_index_block_hash( &peer_server_consensus_hash, @@ -984,7 +985,7 @@ mod test { // should be a Block let http_response_bytes = http_response_bytes_res.unwrap(); - let peer_server_block = make_codec_test_block(25); + let peer_server_block = make_codec_test_block(25, StacksEpochId::latest()); let peer_server_consensus_hash = ConsensusHash([(client_id + 1) as u8; 20]); let index_block_hash = StacksBlockHeader::make_index_block_hash( &peer_server_consensus_hash, @@ -1300,7 +1301,7 @@ mod test { 1, 600, |client_id, ref mut chainstate| { - let peer_server_block = make_codec_test_block(25); + let peer_server_block = make_codec_test_block(25, StacksEpochId::latest()); let peer_server_consensus_hash = ConsensusHash([(client_id + 1) as u8; 20]); let index_block_hash = StacksBlockHeader::make_index_block_hash( &peer_server_consensus_hash, diff --git a/stackslib/src/net/tests/httpcore.rs b/stackslib/src/net/tests/httpcore.rs index 8cd42f45b7..f85dd52f92 100644 --- a/stackslib/src/net/tests/httpcore.rs +++ b/stackslib/src/net/tests/httpcore.rs @@ -25,6 +25,7 @@ use stacks_common::util::chunked_encoding::{ HttpChunkedTransferWriter, HttpChunkedTransferWriterState, }; use stacks_common::util::hash::{hex_bytes, to_hex, Hash160}; +use stacks_common::types::StacksEpochId; use crate::burnchains::Txid; use crate::chainstate::stacks::db::blocks::test::make_sample_microblock_stream; @@ -442,7 +443,7 @@ fn test_http_response_type_codec() { "6d430bb91222408e7706c9001cfaeb91b08c2be6d5ac95779ab52c6b431950e001", ) .unwrap(); - let test_block_info = make_codec_test_block(5); + let test_block_info = make_codec_test_block(5, StacksEpochId::latest()); let test_microblock_info = make_sample_microblock_stream(&privk, &test_block_info.block_hash()); let mut test_block_info_bytes = vec![]; diff --git a/testnet/stacks-node/src/config.rs b/testnet/stacks-node/src/config.rs index 193e50c863..187ed5f144 100644 --- a/testnet/stacks-node/src/config.rs +++ b/testnet/stacks-node/src/config.rs @@ -583,6 +583,10 @@ impl Config { Ok(StacksEpochId::Epoch23) } else if epoch_name == EPOCH_CONFIG_2_4_0 { Ok(StacksEpochId::Epoch24) + } else if epoch_name == EPOCH_CONFIG_2_5_0 { + Ok(StacksEpochId::Epoch25) + } else if epoch_name == EPOCH_CONFIG_3_0_0 { + Ok(StacksEpochId::Epoch30) } else { Err(format!("Unknown epoch name specified: {}", epoch_name)) }?; @@ -1518,6 +1522,8 @@ pub const EPOCH_CONFIG_2_1_0: &'static str = "2.1"; pub const EPOCH_CONFIG_2_2_0: &'static str = "2.2"; pub const EPOCH_CONFIG_2_3_0: &'static str = "2.3"; pub const EPOCH_CONFIG_2_4_0: &'static str = "2.4"; +pub const EPOCH_CONFIG_2_5_0: &'static str = "2.5"; +pub const EPOCH_CONFIG_3_0_0: &'static str = "3.0"; #[derive(Clone, Deserialize, Default, Debug)] pub struct BurnchainConfigFile { diff --git a/testnet/stacks-node/src/mockamoto.rs b/testnet/stacks-node/src/mockamoto.rs index 7168bd5630..d3e0ebe4bd 100644 --- a/testnet/stacks-node/src/mockamoto.rs +++ b/testnet/stacks-node/src/mockamoto.rs @@ -489,7 +489,7 @@ impl MockamotoNode { let (mut chainstate_tx, clarity_instance) = self.chainstate.chainstate_tx_begin()?; let pox_constants = self.sortdb.pox_constants.clone(); let mut sortdb_tx = self.sortdb.tx_begin_at_tip(); - let Some((next_block, _)) = NakamotoChainState::next_ready_nakamoto_block(&chainstate_tx)? + let Some((next_block, _)) = NakamotoChainState::next_ready_nakamoto_block(&chainstate_tx, StacksEpochId::latest())? else { return Ok(false); }; diff --git a/testnet/stacks-node/src/node.rs b/testnet/stacks-node/src/node.rs index 5b32de84e0..71cc577ed4 100644 --- a/testnet/stacks-node/src/node.rs +++ b/testnet/stacks-node/src/node.rs @@ -909,7 +909,7 @@ impl Node { &metadata.anchored_header.block_hash(), ) .unwrap(); - StacksChainState::consensus_load(&block_path).unwrap() + StacksChainState::consensus_load_with_epoch(&block_path, stacks_epoch.epoch_id).unwrap() }; let chain_tip = ChainTip { diff --git a/testnet/stacks-node/src/tests/epoch_205.rs b/testnet/stacks-node/src/tests/epoch_205.rs index 60577cb690..c1ce088f26 100644 --- a/testnet/stacks-node/src/tests/epoch_205.rs +++ b/testnet/stacks-node/src/tests/epoch_205.rs @@ -19,7 +19,7 @@ use stacks::core::{ StacksEpoch, StacksEpochId, PEER_VERSION_EPOCH_1_0, PEER_VERSION_EPOCH_2_0, PEER_VERSION_EPOCH_2_05, PEER_VERSION_EPOCH_2_1, }; -use stacks_common::codec::StacksMessageCodec; +use stacks_common::codec::DeserializeWithEpoch; use stacks_common::types::chainstate::{ BlockHeaderHash, BurnchainHeaderHash, StacksAddress, VRFSeed, }; @@ -232,7 +232,11 @@ fn test_exact_block_costs() { .filter_map(|tx| { let raw_tx = tx.get("raw_tx").unwrap().as_str().unwrap(); let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); + let parsed = StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_bytes[..], + StacksEpochId::Epoch2_05, + ) + .unwrap(); if let TransactionPayload::ContractCall(ref cc) = &parsed.payload { if cc.function_name.as_str() == "db-get2" { Some(parsed) @@ -422,7 +426,11 @@ fn test_dynamic_db_method_costs() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); + let parsed = StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_bytes[..], + StacksEpochId::Epoch2_05, + ) + .unwrap(); if let TransactionPayload::ContractCall(ref cc) = parsed.payload { assert_eq!( @@ -823,6 +831,7 @@ fn test_cost_limit_switch_version205() { } _ => false, }, + StacksEpochId::Epoch2_05, ); assert_eq!(increment_contract_defines.len(), 1); @@ -854,6 +863,7 @@ fn test_cost_limit_switch_version205() { } _ => false, }, + StacksEpochId::Epoch2_05, ); assert_eq!(increment_calls_alice.len(), 1); @@ -887,6 +897,7 @@ fn test_cost_limit_switch_version205() { } _ => false, }, + StacksEpochId::Epoch2_05, ); assert_eq!(increment_calls_bob.len(), 0); @@ -920,7 +931,7 @@ fn bigger_microblock_streams_in_2_05() { &format!("large-{}", ix), &format!(" ;; a single one of these transactions consumes over half the runtime budget - (define-constant BUFF_TO_BYTE (list + (define-constant BUFF_TO_BYTE (list 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f 0x10 0x11 0x12 0x13 0x14 0x15 0x16 0x17 0x18 0x19 0x1a 0x1b 0x1c 0x1d 0x1e 0x1f 0x20 0x21 0x22 0x23 0x24 0x25 0x26 0x27 0x28 0x29 0x2a 0x2b 0x2c 0x2d 0x2e 0x2f @@ -1163,7 +1174,11 @@ fn bigger_microblock_streams_in_2_05() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); + let parsed = StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_bytes[..], + StacksEpochId::Epoch2_05, + ) + .unwrap(); if let TransactionPayload::SmartContract(tsc, ..) = parsed.payload { if tsc.name.to_string().find("costs-2").is_some() { in_205 = true; diff --git a/testnet/stacks-node/src/tests/epoch_21.rs b/testnet/stacks-node/src/tests/epoch_21.rs index 7a109f89ee..034915f526 100644 --- a/testnet/stacks-node/src/tests/epoch_21.rs +++ b/testnet/stacks-node/src/tests/epoch_21.rs @@ -26,6 +26,7 @@ use stacks::clarity_cli::vm_execute as execute; use stacks::core; use stacks::core::BURNCHAIN_TX_SEARCH_WINDOW; use stacks::util_lib::boot::boot_code_id; +use stacks_common::codec::DeserializeWithEpoch; use stacks_common::types::chainstate::{ BlockHeaderHash, BurnchainHeaderHash, StacksAddress, StacksBlockId, VRFSeed, }; @@ -385,9 +386,10 @@ fn transition_adds_burn_block_height() { ); submit_tx(&http_origin, &tx); - let cc_txid = StacksTransaction::consensus_deserialize(&mut &tx[..]) - .unwrap() - .txid(); + let cc_txid = + StacksTransaction::consensus_deserialize_with_epoch(&mut &tx[..], StacksEpochId::Epoch21) + .unwrap() + .txid(); // mine it next_block_and_wait(&mut btc_regtest_controller, &blocks_processed); @@ -406,7 +408,11 @@ fn transition_adds_burn_block_height() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); + let parsed = StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_bytes[..], + StacksEpochId::Epoch21, + ) + .unwrap(); if parsed.txid() == cc_txid { // check events for this block for event in events.iter() { @@ -1222,9 +1228,12 @@ fn transition_adds_get_pox_addr_recipients() { "test-get-pox-addrs", &[Value::UInt((stack_sort_height).into())], ); - let cc_txid = StacksTransaction::consensus_deserialize(&mut &cc_tx[..]) - .unwrap() - .txid(); + let cc_txid = StacksTransaction::consensus_deserialize_with_epoch( + &mut &cc_tx[..], + StacksEpochId::Epoch21, + ) + .unwrap() + .txid(); submit_tx(&http_origin, &cc_tx); next_block_and_wait(&mut btc_regtest_controller, &blocks_processed); @@ -1243,7 +1252,11 @@ fn transition_adds_get_pox_addr_recipients() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); + let parsed = StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_bytes[..], + StacksEpochId::Epoch21, + ) + .unwrap(); if parsed.txid() == cc_txid { // check events for this block for (_i, event) in events.iter().enumerate() { @@ -1965,7 +1978,11 @@ fn transition_empty_blocks() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); + let parsed = StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_bytes[..], + StacksEpochId::Epoch21, + ) + .unwrap(); if let TransactionPayload::SmartContract(tsc, ..) = parsed.payload { if tsc.name == "pox-2".into() { have_pox2 = true; @@ -4893,7 +4910,11 @@ fn trait_invocation_cross_epoch() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); + let parsed = StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_bytes[..], + StacksEpochId::Epoch21, + ) + .unwrap(); if interesting_txids.contains(&parsed.txid().to_string()) { eprintln!( "{} => {}", diff --git a/testnet/stacks-node/src/tests/epoch_22.rs b/testnet/stacks-node/src/tests/epoch_22.rs index c65c51ad3a..fbfb522da1 100644 --- a/testnet/stacks-node/src/tests/epoch_22.rs +++ b/testnet/stacks-node/src/tests/epoch_22.rs @@ -11,6 +11,7 @@ use stacks::clarity_cli::vm_execute as execute; use stacks::core; use stacks::core::STACKS_EPOCH_MAX; use stacks::util_lib::boot::boot_code_id; +use stacks_common::codec::DeserializeWithEpoch; use stacks_common::types::chainstate::{StacksAddress, StacksBlockId}; use stacks_common::types::PrivateKey; use stacks_common::util::hash::Hash160; @@ -539,8 +540,11 @@ fn disable_pox() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); + let parsed = StacksTransaction::consensus_deserialize_with_epoch( + &mut tx_bytes.as_slice(), + StacksEpochId::Epoch22, + ) + .unwrap(); let tx_sender = PrincipalData::from(parsed.auth.origin().address_testnet()); if &tx_sender == &spender_addr && parsed.auth.get_origin_nonce() == aborted_increase_nonce @@ -1195,8 +1199,11 @@ fn pox_2_unlock_all() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); + let parsed = StacksTransaction::consensus_deserialize_with_epoch( + &mut tx_bytes.as_slice(), + StacksEpochId::Epoch22, + ) + .unwrap(); let tx_sender = PrincipalData::from(parsed.auth.origin().address_testnet()); if &tx_sender == &spender_addr && parsed.auth.get_origin_nonce() == nonce_of_2_2_unlock_ht_call diff --git a/testnet/stacks-node/src/tests/epoch_23.rs b/testnet/stacks-node/src/tests/epoch_23.rs index d3b8382c1a..49272a98b5 100644 --- a/testnet/stacks-node/src/tests/epoch_23.rs +++ b/testnet/stacks-node/src/tests/epoch_23.rs @@ -20,6 +20,7 @@ use clarity::vm::types::{PrincipalData, QualifiedContractIdentifier}; use stacks::burnchains::{Burnchain, PoxConstants}; use stacks::core; use stacks::core::STACKS_EPOCH_MAX; +use stacks_common::codec::DeserializeWithEpoch; use stacks_common::util::sleep_ms; use crate::config::{EventKeyType, EventObserverConfig, InitialBalance}; @@ -510,8 +511,11 @@ fn trait_invocation_behavior() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); + let parsed = StacksTransaction::consensus_deserialize_with_epoch( + &mut tx_bytes.as_slice(), + StacksEpochId::Epoch23, + ) + .unwrap(); let tx_sender = PrincipalData::from(parsed.auth.origin().address_testnet()); if &tx_sender == &spender_addr { let contract_call = match &parsed.payload { diff --git a/testnet/stacks-node/src/tests/epoch_24.rs b/testnet/stacks-node/src/tests/epoch_24.rs index 0a116353c2..c4fa97aaeb 100644 --- a/testnet/stacks-node/src/tests/epoch_24.rs +++ b/testnet/stacks-node/src/tests/epoch_24.rs @@ -28,13 +28,14 @@ use stacks::chainstate::stacks::{Error, StacksTransaction, TransactionPayload}; use stacks::clarity_cli::vm_execute as execute; use stacks::core; use stacks_common::address::{AddressHashMode, C32_ADDRESS_VERSION_TESTNET_SINGLESIG}; -use stacks_common::codec::StacksMessageCodec; +use stacks_common::codec::DeserializeWithEpoch; use stacks_common::consts::STACKS_EPOCH_MAX; use stacks_common::types::chainstate::{StacksAddress, StacksBlockId, StacksPrivateKey}; use stacks_common::types::Address; use stacks_common::util::hash::{bytes_to_hex, hex_bytes, Hash160}; use stacks_common::util::secp256k1::Secp256k1PublicKey; use stacks_common::util::sleep_ms; +use stacks_common::types::StacksEpochId; use crate::config::{EventKeyType, EventObserverConfig, InitialBalance}; use crate::tests::bitcoin_regtest::BitcoinCoreController; @@ -644,8 +645,11 @@ fn fix_to_pox_contract() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); + let parsed = StacksTransaction::consensus_deserialize_with_epoch( + &mut tx_bytes.as_slice(), + StacksEpochId::Epoch24, + ) + .unwrap(); let tx_sender = PrincipalData::from(parsed.auth.origin().address_testnet()); if &tx_sender == &spender_addr && (parsed.auth.get_origin_nonce() == aborted_increase_nonce_2_2 diff --git a/testnet/stacks-node/src/tests/integrations.rs b/testnet/stacks-node/src/tests/integrations.rs index 16e6897196..2e8e1b3294 100644 --- a/testnet/stacks-node/src/tests/integrations.rs +++ b/testnet/stacks-node/src/tests/integrations.rs @@ -22,6 +22,7 @@ use stacks::chainstate::stacks::{ TransactionContractCall, TransactionPayload, }; use stacks::clarity_vm::clarity::ClarityConnection; +use stacks::codec::{DeserializeWithEpoch, StacksMessageCodec}; use stacks::core::mempool::MAXIMUM_MEMPOOL_TX_CHAINING; use stacks::core::{ StacksEpoch, StacksEpochId, PEER_VERSION_EPOCH_2_0, PEER_VERSION_EPOCH_2_05, @@ -31,7 +32,6 @@ use stacks::net::api::callreadonly::CallReadOnlyRequestBody; use stacks::net::api::getaccount::AccountEntryResponse; use stacks::net::api::getcontractsrc::ContractSrcResponse; use stacks::net::api::getistraitimplemented::GetIsTraitImplementedResponse; -use stacks_common::codec::StacksMessageCodec; use stacks_common::types::chainstate::{StacksAddress, StacksBlockId, VRFSeed}; use stacks_common::util::hash::{hex_bytes, to_hex, Sha256Sum}; @@ -809,7 +809,7 @@ fn integration_test_get_info() { .json() .unwrap(); - assert_eq!(res, format!("{}", StacksTransaction::consensus_deserialize(&mut &tx_xfer[..]).unwrap().txid())); + assert_eq!(res, format!("{}", StacksTransaction::consensus_deserialize_with_epoch(&mut &tx_xfer[..], StacksEpochId::latest()).unwrap().txid())); // let's test a posttransaction call that fails to deserialize, let tx_hex = "80800000000400f942874ce525e87f21bbe8c121b12fac831d02f4000000000000000000000000000003e80001031734446f0870af42bb0cafad27f405e5d9eba441375eada8607a802b875fbb7ba7c4da3474f2bfd76851fb6314a48fe98b57440b8ccec6c9b8362c843a89f303020000000001047465737400000007282b2031203129"; @@ -833,7 +833,7 @@ fn integration_test_get_info() { let tx_xfer_invalid = make_stacks_transfer(&spender_sk, (round + 30).into(), 200, // bad nonce &StacksAddress::from_string(ADDR_4).unwrap().into(), 456); - let tx_xfer_invalid_tx = StacksTransaction::consensus_deserialize(&mut &tx_xfer_invalid[..]).unwrap(); + let tx_xfer_invalid_tx = StacksTransaction::consensus_deserialize_with_epoch(&mut &tx_xfer_invalid[..], StacksEpochId::latest()).unwrap(); let res = client.post(&path) .header("Content-Type", "application/octet-stream") @@ -1198,9 +1198,11 @@ fn contract_stx_transfer() { &contract_identifier.clone().into(), 1000, ); - let xfer_to_contract = - StacksTransaction::consensus_deserialize(&mut &xfer_to_contract[..]) - .unwrap(); + let xfer_to_contract = StacksTransaction::consensus_deserialize_with_epoch( + &mut &xfer_to_contract[..], + StacksEpochId::latest(), + ) + .unwrap(); tenure .mem_pool .submit( @@ -1218,8 +1220,11 @@ fn contract_stx_transfer() { // this one should fail because the nonce is already in the mempool let xfer_to_contract = make_stacks_transfer(&sk_3, 3, 190, &contract_identifier.clone().into(), 1000); - let xfer_to_contract = - StacksTransaction::consensus_deserialize(&mut &xfer_to_contract[..]).unwrap(); + let xfer_to_contract = StacksTransaction::consensus_deserialize_with_epoch( + &mut &xfer_to_contract[..], + StacksEpochId::latest(), + ) + .unwrap(); match tenure .mem_pool .submit( @@ -2167,8 +2172,11 @@ fn mempool_errors() { &send_to, 456, ); - let tx_xfer_invalid_tx = - StacksTransaction::consensus_deserialize(&mut &tx_xfer_invalid[..]).unwrap(); + let tx_xfer_invalid_tx = StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_xfer_invalid[..], + StacksEpochId::latest(), + ) + .unwrap(); let res = client .post(&path) @@ -2208,8 +2216,11 @@ fn mempool_errors() { &send_to, 456, ); - let tx_xfer_invalid_tx = - StacksTransaction::consensus_deserialize(&mut &tx_xfer_invalid[..]).unwrap(); + let tx_xfer_invalid_tx = StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_xfer_invalid[..], + StacksEpochId::latest(), + ) + .unwrap(); let res = client .post(&path) @@ -2241,8 +2252,11 @@ fn mempool_errors() { &send_to, 456, ); - let tx_xfer_invalid_tx = - StacksTransaction::consensus_deserialize(&mut &tx_xfer_invalid[..]).unwrap(); + let tx_xfer_invalid_tx = StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_xfer_invalid[..], + StacksEpochId::latest(), + ) + .unwrap(); let res = client .post(&path) @@ -2285,8 +2299,11 @@ fn mempool_errors() { &send_to, 1000, ); - let tx_xfer_invalid_tx = - StacksTransaction::consensus_deserialize(&mut &tx_xfer_invalid[..]).unwrap(); + let tx_xfer_invalid_tx = StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_xfer_invalid[..], + StacksEpochId::latest(), + ) + .unwrap(); let res = client .post(&path) diff --git a/testnet/stacks-node/src/tests/mempool.rs b/testnet/stacks-node/src/tests/mempool.rs index d6ed3af47e..f03b732383 100644 --- a/testnet/stacks-node/src/tests/mempool.rs +++ b/testnet/stacks-node/src/tests/mempool.rs @@ -14,13 +14,13 @@ use stacks::chainstate::stacks::{ TransactionAnchorMode, TransactionAuth, TransactionPayload, TransactionSpendingCondition, TransactionVersion, C32_ADDRESS_VERSION_MAINNET_SINGLESIG, }; +use stacks::codec::{DeserializeWithEpoch, StacksMessageCodec}; use stacks::core::mempool::MemPoolDB; use stacks::core::{StacksEpochId, CHAIN_ID_TESTNET}; use stacks::cost_estimates::metrics::UnitMetric; use stacks::cost_estimates::UnitEstimator; use stacks::net::Error as NetError; use stacks_common::address::AddressHashMode; -use stacks_common::codec::StacksMessageCodec; use stacks_common::types::chainstate::{BlockHeaderHash, StacksAddress}; use stacks_common::util::hash::*; use stacks_common::util::secp256k1::*; @@ -237,8 +237,11 @@ fn mempool_setup_chainstate() { // first a couple valid ones: let tx_bytes = make_contract_publish(&contract_sk, 5, 1000, "bar_contract", FOO_CONTRACT); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); + let tx = StacksTransaction::consensus_deserialize_with_epoch( + &mut tx_bytes.as_slice(), + StacksEpochId::Epoch25, + ) + .unwrap(); chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -258,8 +261,11 @@ fn mempool_setup_chainstate() { "bar", &[Value::UInt(1)], ); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); + let tx = StacksTransaction::consensus_deserialize_with_epoch( + &mut tx_bytes.as_slice(), + StacksEpochId::Epoch25, + ) + .unwrap(); chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -271,8 +277,11 @@ fn mempool_setup_chainstate() { .unwrap(); let tx_bytes = make_stacks_transfer(&contract_sk, 5, 200, &other_addr, 1000); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); + let tx = StacksTransaction::consensus_deserialize_with_epoch( + &mut tx_bytes.as_slice(), + StacksEpochId::Epoch25, + ) + .unwrap(); chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -285,8 +294,11 @@ fn mempool_setup_chainstate() { // bad signature let tx_bytes = make_bad_stacks_transfer(&contract_sk, 5, 200, &other_addr, 1000); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); + let tx = StacksTransaction::consensus_deserialize_with_epoch( + &mut tx_bytes.as_slice(), + StacksEpochId::Epoch25, + ) + .unwrap(); let e = chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -327,8 +339,11 @@ fn mempool_setup_chainstate() { "bar", &[Value::UInt(1), Value::Int(2)], ); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); + let tx = StacksTransaction::consensus_deserialize_with_epoch( + &mut tx_bytes.as_slice(), + StacksEpochId::Epoch25, + ) + .unwrap(); let e = chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -356,8 +371,11 @@ fn mempool_setup_chainstate() { .into(); let tx_bytes = make_stacks_transfer(&contract_sk, 5, 200, &bad_addr, 1000); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); + let tx = StacksTransaction::consensus_deserialize_with_epoch( + &mut tx_bytes.as_slice(), + StacksEpochId::Epoch25, + ) + .unwrap(); let e = chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -375,8 +393,11 @@ fn mempool_setup_chainstate() { // bad fees let tx_bytes = make_stacks_transfer(&contract_sk, 5, 0, &other_addr, 1000); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); + let tx = StacksTransaction::consensus_deserialize_with_epoch( + &mut tx_bytes.as_slice(), + StacksEpochId::Epoch25, + ) + .unwrap(); let e = chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -395,8 +416,11 @@ fn mempool_setup_chainstate() { // bad nonce let tx_bytes = make_stacks_transfer(&contract_sk, 0, 200, &other_addr, 1000); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); + let tx = StacksTransaction::consensus_deserialize_with_epoch( + &mut tx_bytes.as_slice(), + StacksEpochId::Epoch25, + ) + .unwrap(); let e = chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -415,8 +439,11 @@ fn mempool_setup_chainstate() { // not enough funds let tx_bytes = make_stacks_transfer(&contract_sk, 5, 110000, &other_addr, 1000); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); + let tx = StacksTransaction::consensus_deserialize_with_epoch( + &mut tx_bytes.as_slice(), + StacksEpochId::Epoch25, + ) + .unwrap(); let e = chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -436,8 +463,11 @@ fn mempool_setup_chainstate() { // sender == recipient let contract_princ = PrincipalData::from(contract_addr.clone()); let tx_bytes = make_stacks_transfer(&contract_sk, 5, 300, &contract_princ, 1000); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); + let tx = StacksTransaction::consensus_deserialize_with_epoch( + &mut tx_bytes.as_slice(), + StacksEpochId::Epoch25, + ) + .unwrap(); let e = chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -459,8 +489,11 @@ fn mempool_setup_chainstate() { mainnet_recipient.version = C32_ADDRESS_VERSION_MAINNET_SINGLESIG; let mainnet_princ = mainnet_recipient.into(); let tx_bytes = make_stacks_transfer(&contract_sk, 5, 300, &mainnet_princ, 1000); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); + let tx = StacksTransaction::consensus_deserialize_with_epoch( + &mut tx_bytes.as_slice(), + StacksEpochId::Epoch25, + ) + .unwrap(); let e = chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -492,8 +525,11 @@ fn mempool_setup_chainstate() { TransactionAnchorMode::OnChainOnly, TransactionVersion::Mainnet, ); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); + let tx = StacksTransaction::consensus_deserialize_with_epoch( + &mut tx_bytes.as_slice(), + StacksEpochId::Epoch25, + ) + .unwrap(); let e = chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -512,8 +548,11 @@ fn mempool_setup_chainstate() { // send amount must be positive let tx_bytes = make_stacks_transfer(&contract_sk, 5, 300, &other_addr, 0); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); + let tx = StacksTransaction::consensus_deserialize_with_epoch( + &mut tx_bytes.as_slice(), + StacksEpochId::Epoch25, + ) + .unwrap(); let e = chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -532,8 +571,11 @@ fn mempool_setup_chainstate() { // not enough funds let tx_bytes = make_stacks_transfer(&contract_sk, 5, 110000, &other_addr, 1000); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); + let tx = StacksTransaction::consensus_deserialize_with_epoch( + &mut tx_bytes.as_slice(), + StacksEpochId::Epoch25, + ) + .unwrap(); let e = chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -551,8 +593,11 @@ fn mempool_setup_chainstate() { }); let tx_bytes = make_stacks_transfer(&contract_sk, 5, 99700, &other_addr, 1000); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); + let tx = StacksTransaction::consensus_deserialize_with_epoch( + &mut tx_bytes.as_slice(), + StacksEpochId::Epoch25, + ) + .unwrap(); let e = chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -578,8 +623,11 @@ fn mempool_setup_chainstate() { "bar", &[Value::UInt(1)], ); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); + let tx = StacksTransaction::consensus_deserialize_with_epoch( + &mut tx_bytes.as_slice(), + StacksEpochId::Epoch25, + ) + .unwrap(); let e = chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -605,8 +653,11 @@ fn mempool_setup_chainstate() { "foobar", &[Value::UInt(1)], ); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); + let tx = StacksTransaction::consensus_deserialize_with_epoch( + &mut tx_bytes.as_slice(), + StacksEpochId::Epoch25, + ) + .unwrap(); let e = chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -632,8 +683,11 @@ fn mempool_setup_chainstate() { "bar", &[Value::UInt(1), Value::Int(2)], ); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); + let tx = StacksTransaction::consensus_deserialize_with_epoch( + &mut tx_bytes.as_slice(), + StacksEpochId::Epoch25, + ) + .unwrap(); let e = chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -652,8 +706,11 @@ fn mempool_setup_chainstate() { let tx_bytes = make_contract_publish(&contract_sk, 5, 1000, "foo_contract", FOO_CONTRACT); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); + let tx = StacksTransaction::consensus_deserialize_with_epoch( + &mut tx_bytes.as_slice(), + StacksEpochId::Epoch25, + ) + .unwrap(); let e = chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -687,8 +744,11 @@ fn mempool_setup_chainstate() { }; let tx_bytes = make_poison(&contract_sk, 5, 1000, microblock_1, microblock_2); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); + let tx = StacksTransaction::consensus_deserialize_with_epoch( + &mut tx_bytes.as_slice(), + StacksEpochId::Epoch25, + ) + .unwrap(); let e = chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -724,8 +784,11 @@ fn mempool_setup_chainstate() { }; let tx_bytes = make_poison(&contract_sk, 5, 1000, microblock_1, microblock_2); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); + let tx = StacksTransaction::consensus_deserialize_with_epoch( + &mut tx_bytes.as_slice(), + StacksEpochId::Epoch25, + ) + .unwrap(); let e = chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -762,8 +825,11 @@ fn mempool_setup_chainstate() { microblock_2.sign(&other_sk).unwrap(); let tx_bytes = make_poison(&contract_sk, 5, 1000, microblock_1, microblock_2); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); + let tx = StacksTransaction::consensus_deserialize_with_epoch( + &mut tx_bytes.as_slice(), + StacksEpochId::Epoch25, + ) + .unwrap(); let e = chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -783,8 +849,11 @@ fn mempool_setup_chainstate() { ); let tx_bytes = make_coinbase(&contract_sk, 5, 1000); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); + let tx = StacksTransaction::consensus_deserialize_with_epoch( + &mut tx_bytes.as_slice(), + StacksEpochId::Epoch25, + ) + .unwrap(); let e = chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -841,8 +910,11 @@ fn mempool_setup_chainstate() { microblock_2.sign(&secret_key).unwrap(); let tx_bytes = make_poison(&contract_sk, 5, 1000, microblock_1, microblock_2); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); + let tx = StacksTransaction::consensus_deserialize_with_epoch( + &mut tx_bytes.as_slice(), + StacksEpochId::Epoch25, + ) + .unwrap(); chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -868,8 +940,11 @@ fn mempool_setup_chainstate() { "baz", &[Value::Principal(contract_principal)], ); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); + let tx = StacksTransaction::consensus_deserialize_with_epoch( + &mut tx_bytes.as_slice(), + StacksEpochId::Epoch25, + ) + .unwrap(); chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -895,8 +970,11 @@ fn mempool_setup_chainstate() { "baz", &[Value::Principal(contract_principal)], ); - let tx = - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); + let tx = StacksTransaction::consensus_deserialize_with_epoch( + &mut tx_bytes.as_slice(), + StacksEpochId::Epoch25, + ) + .unwrap(); let e = chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, diff --git a/testnet/stacks-node/src/tests/mod.rs b/testnet/stacks-node/src/tests/mod.rs index faea7f99d9..0c85a1686f 100644 --- a/testnet/stacks-node/src/tests/mod.rs +++ b/testnet/stacks-node/src/tests/mod.rs @@ -20,10 +20,10 @@ use stacks::chainstate::stacks::{ TransactionPostConditionMode, TransactionSmartContract, TransactionSpendingCondition, TransactionVersion, C32_ADDRESS_VERSION_TESTNET_SINGLESIG, }; +use stacks::codec::{DeserializeWithEpoch, StacksMessageCodec}; use stacks::core::{StacksEpoch, StacksEpochExtension, StacksEpochId, CHAIN_ID_TESTNET}; use stacks::util_lib::strings::StacksString; use stacks_common::address::AddressHashMode; -use stacks_common::codec::StacksMessageCodec; use stacks_common::types::chainstate::StacksAddress; use stacks_common::util::get_epoch_time_secs; use stacks_common::util::hash::{hex_bytes, to_hex}; @@ -446,6 +446,7 @@ fn make_microblock( pub fn select_transactions_where( blocks: &Vec, test_fn: fn(&StacksTransaction) -> bool, + epoch_id: StacksEpochId, ) -> Vec { let mut result = vec![]; for block in blocks { @@ -453,7 +454,9 @@ pub fn select_transactions_where( for tx in transactions.iter() { let raw_tx = tx.get("raw_tx").unwrap().as_str().unwrap(); let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); + let parsed = + StacksTransaction::consensus_deserialize_with_epoch(&mut &tx_bytes[..], epoch_id) + .unwrap(); if test_fn(&parsed) { result.push(parsed); } @@ -551,7 +554,7 @@ fn should_succeed_mining_valid_txs() { }, 3 => { // On round 3, publish a "set:foo=bar" transaction - // ./blockstack-cli --testnet contract-call 043ff5004e3d695060fa48ac94c96049b8c14ef441c50a184a6a3875d2a000f3 10 2 STGT7GSMZG7EA0TS6MVSKT5JC1DCDFGZWJJZXN8A store set-value -e \"foo\" -e \"bar\" + // ./blockstack-cli --testnet contract-call 043ff5004e3d695060fa48ac94c96049b8c14ef441c50a184a6a3875d2a000f3 10 2 STGT7GSMZG7EA0TS6MVSKT5JC1DCDFGZWJJZXN8A store set-value -e \"foo\" -e \"bar\" let set_foo_bar = "8080000000040021a3c334fc0ee50359353799e8b2605ac6be1fe40000000000000002000000000000000a010142a01caf6a32b367664869182f0ebc174122a5a980937ba259d44cc3ebd280e769a53dd3913c8006ead680a6e1c98099fcd509ce94b0a4e90d9f4603b101922d030200000000021a21a3c334fc0ee50359353799e8b2605ac6be1fe40573746f7265097365742d76616c7565000000020d00000003666f6f0d00000003626172"; tenure.mem_pool.submit_raw(&mut chainstate_copy, &sortdb, &consensus_hash, &header_hash,hex_bytes(set_foo_bar).unwrap().to_vec(), &ExecutionCost::max_value(), diff --git a/testnet/stacks-node/src/tests/neon_integrations.rs b/testnet/stacks-node/src/tests/neon_integrations.rs index ad64ec4c0d..9f54613422 100644 --- a/testnet/stacks-node/src/tests/neon_integrations.rs +++ b/testnet/stacks-node/src/tests/neon_integrations.rs @@ -36,6 +36,7 @@ use stacks::chainstate::stacks::{ StacksPublicKey, StacksTransaction, TransactionContractCall, TransactionPayload, }; use stacks::clarity_cli::vm_execute as execute; +use stacks::codec::{DeserializeWithEpoch, StacksMessageCodec}; use stacks::core; use stacks::core::{ StacksEpoch, StacksEpochId, BLOCK_LIMIT_MAINNET_20, BLOCK_LIMIT_MAINNET_205, @@ -58,7 +59,6 @@ use stacks::net::BurnchainOps; use stacks::util_lib::boot::boot_code_id; use stacks::util_lib::db::{query_row_columns, query_rows, u64_to_sql}; use stacks_common::address::C32_ADDRESS_VERSION_TESTNET_SINGLESIG; -use stacks_common::codec::StacksMessageCodec; use stacks_common::types::chainstate::{ BlockHeaderHash, BurnchainHeaderHash, StacksAddress, StacksBlockId, }; @@ -641,10 +641,13 @@ pub fn submit_tx(http_origin: &str, tx: &Vec) -> String { let res: String = res.json().unwrap(); assert_eq!( res, - StacksTransaction::consensus_deserialize(&mut &tx[..]) - .unwrap() - .txid() - .to_string() + StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx[..], + StacksEpochId::latest() + ) + .unwrap() + .txid() + .to_string() ); return res; } else { @@ -686,9 +689,12 @@ pub fn submit_block( res.stacks_block_id, StacksBlockId::new( consensus_hash, - &StacksBlock::consensus_deserialize(&mut &block[..]) - .unwrap() - .block_hash() + &StacksBlock::consensus_deserialize_with_epoch( + &mut &block[..], + StacksEpochId::latest() + ) + .unwrap() + .block_hash() ) ); return res; @@ -731,7 +737,9 @@ pub fn get_block(http_origin: &str, block_id: &StacksBlockId) -> Option = res.bytes().unwrap().to_vec(); - let block = StacksBlock::consensus_deserialize(&mut &res[..]).unwrap(); + let block = + StacksBlock::consensus_deserialize_with_epoch(&mut &res[..], StacksEpochId::latest()) + .unwrap(); Some(block) } else { None @@ -785,7 +793,11 @@ fn get_tip_anchored_block(conf: &Config) -> (ConsensusHash, StacksBlock) { let client = reqwest::blocking::Client::new(); let path = format!("{}/v2/blocks/{}", &http_origin, &stacks_id_tip); let block_bytes = client.get(&path).send().unwrap().bytes().unwrap(); - let block = StacksBlock::consensus_deserialize(&mut block_bytes.as_ref()).unwrap(); + let block = StacksBlock::consensus_deserialize_with_epoch( + &mut block_bytes.as_ref(), + StacksEpochId::latest(), + ) + .unwrap(); (stacks_tip_consensus_hash, block) } @@ -1252,7 +1264,11 @@ fn deep_contract() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); + let parsed = StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_bytes[..], + StacksEpochId::latest(), + ) + .unwrap(); if let TransactionPayload::SmartContract(..) = parsed.payload { included_smart_contract = true; } @@ -1465,7 +1481,11 @@ fn liquid_ustx_integration() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); + let parsed = StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_bytes[..], + StacksEpochId::latest(), + ) + .unwrap(); if let TransactionPayload::ContractCall(contract_call) = parsed.payload { eprintln!("{}", contract_call.function_name.as_str()); if contract_call.function_name.as_str() == "execute" { @@ -2163,7 +2183,11 @@ fn bitcoind_resubmission_test() { ); let mut garbage_block = StacksMicroblock::first_unsigned( &chain_tip.1, - vec![StacksTransaction::consensus_deserialize(&mut garbage_tx.as_slice()).unwrap()], + vec![StacksTransaction::consensus_deserialize_with_epoch( + &mut garbage_tx.as_slice(), + StacksEpochId::latest(), + ) + .unwrap()], ); garbage_block.header.prev_block = BlockHeaderHash([3; 32]); garbage_block.header.sequence = 1; @@ -2508,12 +2532,18 @@ fn microblock_fork_poison_integration_test() { let recipient = StacksAddress::from_string(ADDR_4).unwrap(); let unconfirmed_tx_bytes = make_stacks_transfer_mblock_only(&spender_sk, 0, 1000, &recipient.into(), 1000); - let unconfirmed_tx = - StacksTransaction::consensus_deserialize(&mut &unconfirmed_tx_bytes[..]).unwrap(); + let unconfirmed_tx = StacksTransaction::consensus_deserialize_with_epoch( + &mut &unconfirmed_tx_bytes[..], + StacksEpochId::latest(), + ) + .unwrap(); let second_unconfirmed_tx_bytes = make_stacks_transfer_mblock_only(&second_spender_sk, 0, 1000, &recipient.into(), 1500); - let second_unconfirmed_tx = - StacksTransaction::consensus_deserialize(&mut &second_unconfirmed_tx_bytes[..]).unwrap(); + let second_unconfirmed_tx = StacksTransaction::consensus_deserialize_with_epoch( + &mut &second_unconfirmed_tx_bytes[..], + StacksEpochId::latest(), + ) + .unwrap(); // TODO (hack) instantiate the sortdb in the burnchain let _ = btc_regtest_controller.sortdb_mut(); @@ -2628,7 +2658,11 @@ fn microblock_fork_poison_integration_test() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); + let parsed = StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_bytes[..], + StacksEpochId::latest(), + ) + .unwrap(); if let TransactionPayload::PoisonMicroblock(..) = &parsed.payload { found = true; @@ -2766,12 +2800,18 @@ fn microblock_integration_test() { let recipient = StacksAddress::from_string(ADDR_4).unwrap(); let unconfirmed_tx_bytes = make_stacks_transfer_mblock_only(&spender_sk, 1, 1000, &recipient.into(), 1000); - let unconfirmed_tx = - StacksTransaction::consensus_deserialize(&mut &unconfirmed_tx_bytes[..]).unwrap(); + let unconfirmed_tx = StacksTransaction::consensus_deserialize_with_epoch( + &mut &unconfirmed_tx_bytes[..], + StacksEpochId::latest(), + ) + .unwrap(); let second_unconfirmed_tx_bytes = make_stacks_transfer_mblock_only(&second_spender_sk, 0, 1000, &recipient.into(), 1500); - let second_unconfirmed_tx = - StacksTransaction::consensus_deserialize(&mut &second_unconfirmed_tx_bytes[..]).unwrap(); + let second_unconfirmed_tx = StacksTransaction::consensus_deserialize_with_epoch( + &mut &second_unconfirmed_tx_bytes[..], + StacksEpochId::latest(), + ) + .unwrap(); // TODO (hack) instantiate the sortdb in the burnchain let _ = btc_regtest_controller.sortdb_mut(); @@ -3098,10 +3138,13 @@ fn microblock_integration_test() { let res: String = res.json().unwrap(); assert_eq!( res, - StacksTransaction::consensus_deserialize(&mut &unconfirmed_tx_bytes[..]) - .unwrap() - .txid() - .to_string() + StacksTransaction::consensus_deserialize_with_epoch( + &mut &unconfirmed_tx_bytes[..], + StacksEpochId::latest() + ) + .unwrap() + .txid() + .to_string() ); eprintln!("Sent {}", &res); } else { @@ -3460,7 +3503,11 @@ fn size_check_integration_test() { "large-0", &giant_contract, ); - let parsed_tx = StacksTransaction::consensus_deserialize(&mut &tx[..]).unwrap(); + let parsed_tx = StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx[..], + StacksEpochId::latest(), + ) + .unwrap(); debug!("Mine transaction {} in a microblock", &parsed_tx.txid()); tx } @@ -3764,7 +3811,11 @@ fn size_overflow_unconfirmed_microblocks_integration_test() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); + let parsed = StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_bytes[..], + StacksEpochId::latest(), + ) + .unwrap(); if let TransactionPayload::SmartContract(tsc, ..) = parsed.payload { if tsc.name.to_string().find("large-").is_some() { num_big_anchored_txs += 1; @@ -3967,7 +4018,11 @@ fn size_overflow_unconfirmed_stream_microblocks_integration_test() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); + let parsed = StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_bytes[..], + StacksEpochId::latest(), + ) + .unwrap(); if let TransactionPayload::SmartContract(tsc, ..) = parsed.payload { if tsc.name.to_string().find("small").is_some() { num_big_microblock_txs += 1; @@ -4143,7 +4198,11 @@ fn size_overflow_unconfirmed_invalid_stream_microblocks_integration_test() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); + let parsed = StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_bytes[..], + StacksEpochId::latest(), + ) + .unwrap(); if let TransactionPayload::SmartContract(tsc, ..) = parsed.payload { if tsc.name.to_string().find("small").is_some() { num_big_microblock_txs += 1; @@ -4196,7 +4255,7 @@ fn runtime_overflow_unconfirmed_microblocks_integration_test() { &format!("large-{}", ix), &format!(" ;; a single one of these transactions consumes over half the runtime budget - (define-constant BUFF_TO_BYTE (list + (define-constant BUFF_TO_BYTE (list 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f 0x10 0x11 0x12 0x13 0x14 0x15 0x16 0x17 0x18 0x19 0x1a 0x1b 0x1c 0x1d 0x1e 0x1f 0x20 0x21 0x22 0x23 0x24 0x25 0x26 0x27 0x28 0x29 0x2a 0x2b 0x2c 0x2d 0x2e 0x2f @@ -4250,7 +4309,7 @@ fn runtime_overflow_unconfirmed_microblocks_integration_test() { &format!("small-{}-{}", ix, i), &format!(" ;; a single one of these transactions consumes over half the runtime budget - (define-constant BUFF_TO_BYTE (list + (define-constant BUFF_TO_BYTE (list 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f 0x10 0x11 0x12 0x13 0x14 0x15 0x16 0x17 0x18 0x19 0x1a 0x1b 0x1c 0x1d 0x1e 0x1f 0x20 0x21 0x22 0x23 0x24 0x25 0x26 0x27 0x28 0x29 0x2a 0x2b 0x2c 0x2d 0x2e 0x2f @@ -4423,7 +4482,11 @@ fn runtime_overflow_unconfirmed_microblocks_integration_test() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); + let parsed = StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_bytes[..], + StacksEpochId::latest(), + ) + .unwrap(); eprintln!("tx: {:?}", &parsed); if let TransactionPayload::SmartContract(tsc, ..) = parsed.payload { if tsc.name.to_string().find("large-").is_some() { @@ -4744,7 +4807,11 @@ fn cost_voting_integration() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); + let parsed = StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_bytes[..], + StacksEpochId::latest(), + ) + .unwrap(); if let TransactionPayload::ContractCall(contract_call) = parsed.payload { eprintln!("{}", contract_call.function_name.as_str()); if contract_call.function_name.as_str() == "execute-2" { @@ -4793,7 +4860,11 @@ fn cost_voting_integration() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); + let parsed = StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_bytes[..], + StacksEpochId::latest(), + ) + .unwrap(); if let TransactionPayload::ContractCall(contract_call) = parsed.payload { eprintln!("{}", contract_call.function_name.as_str()); if contract_call.function_name.as_str() == "confirm-miners" { @@ -4842,7 +4913,11 @@ fn cost_voting_integration() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); + let parsed = StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_bytes[..], + StacksEpochId::latest(), + ) + .unwrap(); if let TransactionPayload::ContractCall(contract_call) = parsed.payload { eprintln!("{}", contract_call.function_name.as_str()); if contract_call.function_name.as_str() == "confirm-miners" { @@ -4887,7 +4962,11 @@ fn cost_voting_integration() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); + let parsed = StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_bytes[..], + StacksEpochId::latest(), + ) + .unwrap(); if let TransactionPayload::ContractCall(contract_call) = parsed.payload { eprintln!("{}", contract_call.function_name.as_str()); if contract_call.function_name.as_str() == "execute-2" { @@ -5110,7 +5189,7 @@ fn block_limit_hit_integration_test() { // 700 invocations let max_contract_src = format!( - "(define-private (work) (begin {} 1)) + "(define-private (work) (begin {} 1)) (define-private (times-100) (begin {} 1)) (define-private (times-200) (begin (times-100) (times-100) 1)) (define-private (times-500) (begin (times-200) (times-200) (times-100) 1)) @@ -5132,7 +5211,7 @@ fn block_limit_hit_integration_test() { // 2900 invocations let oversize_contract_src = format!( - "(define-private (work) (begin {} 1)) + "(define-private (work) (begin {} 1)) (define-private (times-100) (begin {} 1)) (define-private (times-200) (begin (times-100) (times-100) 1)) (define-private (times-500) (begin (times-200) (times-200) (times-100) 1)) @@ -5300,7 +5379,7 @@ fn microblock_limit_hit_integration_test() { } let max_contract_src = format!( - "(define-private (work) (begin {} 1)) + "(define-private (work) (begin {} 1)) (define-private (times-100) (begin {} 1)) (define-private (times-200) (begin (times-100) (times-100) 1)) (define-private (times-500) (begin (times-200) (times-200) (times-100) 1)) @@ -5321,7 +5400,7 @@ fn microblock_limit_hit_integration_test() { ); let oversize_contract_src = format!( - "(define-private (work) (begin {} 1)) + "(define-private (work) (begin {} 1)) (define-private (times-100) (begin {} 1)) (define-private (times-200) (begin (times-100) (times-100) 1)) (define-private (times-500) (begin (times-200) (times-200) (times-100) 1)) @@ -6099,7 +6178,11 @@ fn pox_integration_test() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); + let parsed = StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_bytes[..], + StacksEpochId::latest(), + ) + .unwrap(); if let TransactionPayload::ContractCall(contract_call) = parsed.payload { eprintln!("{}", contract_call.function_name.as_str()); if contract_call.function_name.as_str() == "stack-stx" { @@ -6513,10 +6596,13 @@ fn atlas_integration_test() { let res: String = res.json().unwrap(); assert_eq!( res, - StacksTransaction::consensus_deserialize(&mut &tx_1[..]) - .unwrap() - .txid() - .to_string() + StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_1[..], + StacksEpochId::latest() + ) + .unwrap() + .txid() + .to_string() ); } else { eprintln!("{}", res.text().unwrap()); @@ -6594,10 +6680,13 @@ fn atlas_integration_test() { let res: String = res.json().unwrap(); assert_eq!( res, - StacksTransaction::consensus_deserialize(&mut &tx_2[..]) - .unwrap() - .txid() - .to_string() + StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_2[..], + StacksEpochId::latest() + ) + .unwrap() + .txid() + .to_string() ); } else { eprintln!("{}", res.text().unwrap()); @@ -7296,10 +7385,13 @@ fn atlas_stress_integration_test() { let res: String = res.json().unwrap(); assert_eq!( res, - StacksTransaction::consensus_deserialize(&mut &tx_1[..]) - .unwrap() - .txid() - .to_string() + StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_1[..], + StacksEpochId::latest() + ) + .unwrap() + .txid() + .to_string() ); } else { eprintln!("{}", res.text().unwrap()); @@ -7377,10 +7469,13 @@ fn atlas_stress_integration_test() { let res: String = res.json().unwrap(); assert_eq!( res, - StacksTransaction::consensus_deserialize(&mut &tx_2[..]) - .unwrap() - .txid() - .to_string() + StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_2[..], + StacksEpochId::latest() + ) + .unwrap() + .txid() + .to_string() ); } else { eprintln!("{}", res.text().unwrap()); @@ -8213,8 +8308,16 @@ fn use_latest_tip_integration_test() { let publish_tx = make_contract_publish_microblock_only(&spender_sk, 1, 1000, "caller", caller_src); - let tx_1 = StacksTransaction::consensus_deserialize(&mut &transfer_tx[..]).unwrap(); - let tx_2 = StacksTransaction::consensus_deserialize(&mut &publish_tx[..]).unwrap(); + let tx_1 = StacksTransaction::consensus_deserialize_with_epoch( + &mut &transfer_tx[..], + StacksEpochId::latest(), + ) + .unwrap(); + let tx_2 = StacksTransaction::consensus_deserialize_with_epoch( + &mut &publish_tx[..], + StacksEpochId::latest(), + ) + .unwrap(); let vec_tx = vec![tx_1, tx_2]; let privk = find_microblock_privkey(&conf, &stacks_block.header.microblock_pubkey_hash, 1024).unwrap(); @@ -8561,9 +8664,12 @@ fn test_problematic_txs_are_not_stored() { "test-edge", &tx_edge_body, ); - let tx_edge_txid = StacksTransaction::consensus_deserialize(&mut &tx_edge[..]) - .unwrap() - .txid(); + let tx_edge_txid = StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_edge[..], + StacksEpochId::latest(), + ) + .unwrap() + .txid(); // something just over the limit of the expression depth let exceeds_repeat_factor = edge_repeat_factor + 1; @@ -8578,9 +8684,12 @@ fn test_problematic_txs_are_not_stored() { "test-exceeds", &tx_exceeds_body, ); - let tx_exceeds_txid = StacksTransaction::consensus_deserialize(&mut &tx_exceeds[..]) - .unwrap() - .txid(); + let tx_exceeds_txid = StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_exceeds[..], + StacksEpochId::latest(), + ) + .unwrap() + .txid(); // something stupidly high over the expression depth let high_repeat_factor = 128 * 1024; @@ -8595,9 +8704,12 @@ fn test_problematic_txs_are_not_stored() { "test-high", &tx_high_body, ); - let tx_high_txid = StacksTransaction::consensus_deserialize(&mut &tx_high[..]) - .unwrap() - .txid(); + let tx_high_txid = StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_high[..], + StacksEpochId::latest(), + ) + .unwrap() + .txid(); btc_regtest_controller.bootstrap_chain(201); @@ -8811,9 +8923,12 @@ fn test_problematic_blocks_are_not_mined() { "test-exceeds", &tx_exceeds_body, ); - let tx_exceeds_txid = StacksTransaction::consensus_deserialize(&mut &tx_exceeds[..]) - .unwrap() - .txid(); + let tx_exceeds_txid = StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_exceeds[..], + StacksEpochId::latest(), + ) + .unwrap() + .txid(); // something stupidly high over the expression depth let high_repeat_factor = 3200; @@ -8828,9 +8943,12 @@ fn test_problematic_blocks_are_not_mined() { "test-high", &tx_high_body, ); - let tx_high_txid = StacksTransaction::consensus_deserialize(&mut &tx_high[..]) - .unwrap() - .txid(); + let tx_high_txid = StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_high[..], + StacksEpochId::latest(), + ) + .unwrap() + .txid(); btc_regtest_controller.bootstrap_chain(201); @@ -8902,7 +9020,11 @@ fn test_problematic_blocks_are_not_mined() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); + let parsed = StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_bytes[..], + StacksEpochId::latest(), + ) + .unwrap(); if let TransactionPayload::SmartContract(..) = &parsed.payload { if parsed.txid() == tx_exceeds_txid { found = true; @@ -8999,7 +9121,11 @@ fn test_problematic_blocks_are_not_mined() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); + let parsed = StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_bytes[..], + StacksEpochId::latest(), + ) + .unwrap(); if let TransactionPayload::SmartContract(..) = &parsed.payload { assert!(parsed.txid() != tx_high_txid); } @@ -9169,9 +9295,12 @@ fn test_problematic_blocks_are_not_relayed_or_stored() { "test-exceeds", &tx_exceeds_body, ); - let tx_exceeds_txid = StacksTransaction::consensus_deserialize(&mut &tx_exceeds[..]) - .unwrap() - .txid(); + let tx_exceeds_txid = StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_exceeds[..], + StacksEpochId::latest(), + ) + .unwrap() + .txid(); let high_repeat_factor = 70; let tx_high_body_start = "{ a : ".repeat(high_repeat_factor as usize); @@ -9185,9 +9314,12 @@ fn test_problematic_blocks_are_not_relayed_or_stored() { "test-high", &tx_high_body, ); - let tx_high_txid = StacksTransaction::consensus_deserialize(&mut &tx_high[..]) - .unwrap() - .txid(); + let tx_high_txid = StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_high[..], + StacksEpochId::latest(), + ) + .unwrap() + .txid(); btc_regtest_controller.bootstrap_chain(201); @@ -9259,7 +9391,11 @@ fn test_problematic_blocks_are_not_relayed_or_stored() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); + let parsed = StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_bytes[..], + StacksEpochId::latest(), + ) + .unwrap(); if let TransactionPayload::SmartContract(..) = &parsed.payload { if parsed.txid() == tx_exceeds_txid { found = true; @@ -9380,7 +9516,11 @@ fn test_problematic_blocks_are_not_relayed_or_stored() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); + let parsed = StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_bytes[..], + StacksEpochId::latest(), + ) + .unwrap(); if let TransactionPayload::SmartContract(..) = &parsed.payload { if parsed.txid() == tx_high_txid { bad_block_height = Some(block.get("block_height").unwrap().as_u64().unwrap()); @@ -9563,9 +9703,12 @@ fn test_problematic_microblocks_are_not_mined() { "test-exceeds", &tx_exceeds_body, ); - let tx_exceeds_txid = StacksTransaction::consensus_deserialize(&mut &tx_exceeds[..]) - .unwrap() - .txid(); + let tx_exceeds_txid = StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_exceeds[..], + StacksEpochId::latest(), + ) + .unwrap() + .txid(); // something stupidly high over the expression depth let high_repeat_factor = @@ -9581,9 +9724,12 @@ fn test_problematic_microblocks_are_not_mined() { "test-high", &tx_high_body, ); - let tx_high_txid = StacksTransaction::consensus_deserialize(&mut &tx_high[..]) - .unwrap() - .txid(); + let tx_high_txid = StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_high[..], + StacksEpochId::latest(), + ) + .unwrap() + .txid(); btc_regtest_controller.bootstrap_chain(201); @@ -9662,7 +9808,11 @@ fn test_problematic_microblocks_are_not_mined() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); + let parsed = StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_bytes[..], + StacksEpochId::latest(), + ) + .unwrap(); if let TransactionPayload::SmartContract(..) = &parsed.payload { if parsed.txid() == tx_exceeds_txid { found = true; @@ -9771,7 +9921,11 @@ fn test_problematic_microblocks_are_not_mined() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); + let parsed = StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_bytes[..], + StacksEpochId::latest(), + ) + .unwrap(); if let TransactionPayload::SmartContract(..) = &parsed.payload { assert_ne!(parsed.txid(), tx_high_txid); } @@ -9949,9 +10103,12 @@ fn test_problematic_microblocks_are_not_relayed_or_stored() { "test-exceeds", &tx_exceeds_body, ); - let tx_exceeds_txid = StacksTransaction::consensus_deserialize(&mut &tx_exceeds[..]) - .unwrap() - .txid(); + let tx_exceeds_txid = StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_exceeds[..], + StacksEpochId::latest(), + ) + .unwrap() + .txid(); // greatly exceeds AST depth, but is still mineable without a stack overflow let high_repeat_factor = @@ -9967,9 +10124,12 @@ fn test_problematic_microblocks_are_not_relayed_or_stored() { "test-high", &tx_high_body, ); - let tx_high_txid = StacksTransaction::consensus_deserialize(&mut &tx_high[..]) - .unwrap() - .txid(); + let tx_high_txid = StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_high[..], + StacksEpochId::latest(), + ) + .unwrap() + .txid(); btc_regtest_controller.bootstrap_chain(201); @@ -10044,7 +10204,11 @@ fn test_problematic_microblocks_are_not_relayed_or_stored() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); + let parsed = StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_bytes[..], + StacksEpochId::latest(), + ) + .unwrap(); if let TransactionPayload::SmartContract(..) = &parsed.payload { if parsed.txid() == tx_exceeds_txid { found = true; @@ -10173,7 +10337,11 @@ fn test_problematic_microblocks_are_not_relayed_or_stored() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); + let parsed = StacksTransaction::consensus_deserialize_with_epoch( + &mut &tx_bytes[..], + StacksEpochId::latest(), + ) + .unwrap(); if let TransactionPayload::SmartContract(..) = &parsed.payload { if parsed.txid() == tx_high_txid { bad_block_id = { @@ -10395,7 +10563,7 @@ fn make_runtime_sized_contract(num_index_of: usize, nonce: u64, addr_prefix: &st let code = format!( " - (define-constant BUFF_TO_BYTE (list + (define-constant BUFF_TO_BYTE (list 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f 0x10 0x11 0x12 0x13 0x14 0x15 0x16 0x17 0x18 0x19 0x1a 0x1b 0x1c 0x1d 0x1e 0x1f 0x20 0x21 0x22 0x23 0x24 0x25 0x26 0x27 0x28 0x29 0x2a 0x2b 0x2c 0x2d 0x2e 0x2f From 2a001ae47b983209ba48681db57df5aafe080c73 Mon Sep 17 00:00:00 2001 From: Fess Date: Mon, 27 Nov 2023 20:30:26 +0400 Subject: [PATCH 02/41] fix: failing autotests fixes --- stackslib/src/chainstate/nakamoto/mod.rs | 15 +- stackslib/src/chainstate/stacks/block.rs | 27 +- stackslib/src/chainstate/stacks/db/blocks.rs | 58 ++- stackslib/src/chainstate/stacks/mod.rs | 164 ++++---- .../stacks/tests/block_construction.rs | 11 +- .../stacks/tests/chain_histories.rs | 23 +- .../src/chainstate/stacks/transaction.rs | 1 + stackslib/src/core/mempool.rs | 14 +- stackslib/src/core/tests/mod.rs | 9 + stackslib/src/net/api/getblock.rs | 16 +- stackslib/src/net/api/postblock.rs | 2 +- stackslib/src/net/api/posttransaction.rs | 4 +- stackslib/src/net/api/tests/getblock.rs | 8 +- stackslib/src/net/api/tests/postblock.rs | 2 +- .../src/net/api/tests/postmempoolquery.rs | 32 +- stackslib/src/net/relay.rs | 350 +----------------- stackslib/src/net/server.rs | 6 +- stackslib/src/net/tests/httpcore.rs | 2 +- testnet/stacks-node/src/tests/epoch_24.rs | 2 +- 19 files changed, 252 insertions(+), 494 deletions(-) diff --git a/stackslib/src/chainstate/nakamoto/mod.rs b/stackslib/src/chainstate/nakamoto/mod.rs index 5d2da1a9d3..82de8bf339 100644 --- a/stackslib/src/chainstate/nakamoto/mod.rs +++ b/stackslib/src/chainstate/nakamoto/mod.rs @@ -27,7 +27,8 @@ use rusqlite::types::{FromSql, FromSqlError}; use rusqlite::{params, Connection, OptionalExtension, ToSql, NO_PARAMS}; use sha2::{Digest as Sha2Digest, Sha512_256}; use stacks_common::codec::{ - read_next, write_next, Error as CodecError, StacksMessageCodec, MAX_MESSAGE_LEN, DeserializeWithEpoch, read_next_at_most_with_epoch + read_next, read_next_at_most_with_epoch, write_next, DeserializeWithEpoch, Error as CodecError, + StacksMessageCodec, MAX_MESSAGE_LEN, }; use stacks_common::consts::{ FIRST_BURNCHAIN_CONSENSUS_HASH, FIRST_STACKS_BLOCK_HASH, MINER_REWARD_MATURITY, @@ -938,7 +939,10 @@ impl NakamotoChainState { staging_db_conn .query_row_and_then(query, NO_PARAMS, |row| { let data: Vec = row.get("data")?; - let block = NakamotoBlock::consensus_deserialize_with_epoch(&mut data.as_slice(), epoch_id)?; + let block = NakamotoBlock::consensus_deserialize_with_epoch( + &mut data.as_slice(), + epoch_id, + )?; Ok(Some(( block, u64::try_from(data.len()).expect("FATAL: block is bigger than a u64"), @@ -970,8 +974,11 @@ impl NakamotoChainState { rusqlite::params![consensus_hash, block_hash], |row| { let data: Vec = row.get("data")?; - let block = NakamotoBlock::consensus_deserialize_with_epoch(&mut data.as_slice(), epoch_id) - .map_err(|_| DBError::ParseError)?; + let block = NakamotoBlock::consensus_deserialize_with_epoch( + &mut data.as_slice(), + epoch_id, + ) + .map_err(|_| DBError::ParseError)?; if &block.header.block_hash() != block_hash { error!( "Staging DB corruption: expected {}, got {}", diff --git a/stackslib/src/chainstate/stacks/block.rs b/stackslib/src/chainstate/stacks/block.rs index 5871474732..c6e01877be 100644 --- a/stackslib/src/chainstate/stacks/block.rs +++ b/stackslib/src/chainstate/stacks/block.rs @@ -21,7 +21,8 @@ use std::io::{Read, Write}; use sha2::{Digest, Sha512_256}; use stacks_common::codec::{ - read_next, write_next, read_next_at_most_with_epoch, Error as codec_error, StacksMessageCodec, MAX_MESSAGE_LEN, DeserializeWithEpoch + read_next, read_next_at_most_with_epoch, write_next, DeserializeWithEpoch, + Error as codec_error, StacksMessageCodec, MAX_MESSAGE_LEN, }; use stacks_common::types::chainstate::{ BlockHeaderHash, BurnchainHeaderHash, StacksBlockId, StacksWorkScore, TrieHash, VRFSeed, @@ -920,7 +921,7 @@ impl DeserializeWithEpoch for StacksMicroblock { let txs: Vec = { let mut bound_read = BoundReader::from_reader(fd, MAX_MESSAGE_LEN as u64); // The latest epoch where StacksMicroblock exist is Epoch24 - read_next_at_most_with_epoch(&mut bound_read, u32::MAX, StacksEpochId::Epoch24) + read_next_at_most_with_epoch(&mut bound_read, u32::MAX, epoch_id) }?; if txs.len() == 0 { @@ -1245,6 +1246,7 @@ mod test { 0x80000000, &TransactionAnchorMode::OffChainOnly, &TransactionPostConditionMode::Allow, + StacksEpochId::latest(), ); // remove all coinbases @@ -1860,7 +1862,7 @@ mod test { for epoch_id in epoch_list.iter() { let block_to_check = - if *epoch_id >= StacksEpochId::Epoch30 || need_to_include_coinbase_nakamoto { + if *epoch_id >= StacksEpochId::Epoch30 && need_to_include_coinbase_nakamoto { block_with_coinbase_tx_nakamoto.clone() } else if *epoch_id >= StacksEpochId::Epoch21 && *epoch_id < StacksEpochId::Epoch30 @@ -1902,11 +1904,10 @@ mod test { .find("target epoch is not activated") .is_some() ); - } else if deactivation_epoch_id.is_none() || deactivation_epoch_id.unwrap() > *epoch_id { + } else if deactivation_epoch_id.is_none() || deactivation_epoch_id.unwrap() > *epoch_id + { assert!(StacksBlock::validate_transactions_static_epoch( - &txs, - *epoch_id, - false, + &txs, *epoch_id, false, )); for tx in txs.iter() { @@ -1925,16 +1926,16 @@ mod test { let _ = StacksTransaction::consensus_deserialize_with_epoch( &mut &bytes[..], - *epoch_id - ).unwrap_err(); + *epoch_id, + ) + .unwrap_err(); } - let _ = StacksBlock::consensus_deserialize_with_epoch(&mut &bytes[..], *epoch_id).unwrap_err(); + let _ = StacksBlock::consensus_deserialize_with_epoch(&mut &bytes[..], *epoch_id) + .unwrap_err(); assert!(!StacksBlock::validate_transactions_static_epoch( - &txs, - *epoch_id, - false, + &txs, *epoch_id, false, )); } } diff --git a/stackslib/src/chainstate/stacks/db/blocks.rs b/stackslib/src/chainstate/stacks/db/blocks.rs index 878bdf29ea..ee7192bcc8 100644 --- a/stackslib/src/chainstate/stacks/db/blocks.rs +++ b/stackslib/src/chainstate/stacks/db/blocks.rs @@ -861,6 +861,20 @@ impl StacksChainState { blocks_dir: &str, consensus_hash: &ConsensusHash, block_hash: &BlockHeaderHash, + ) -> Result, Error> { + StacksChainState::load_block_with_epoch( + blocks_dir, + consensus_hash, + block_hash, + StacksEpochId::latest(), + ) + } + + pub fn load_block_with_epoch( + blocks_dir: &str, + consensus_hash: &ConsensusHash, + block_hash: &BlockHeaderHash, + epoch_id: StacksEpochId, ) -> Result, Error> { let block_path = StacksChainState::get_block_path(blocks_dir, consensus_hash, block_hash)?; let sz = StacksChainState::get_file_size(&block_path)?; @@ -870,7 +884,7 @@ impl StacksChainState { } let block: StacksBlock = - StacksChainState::consensus_load_with_epoch(&block_path, StacksEpochId::latest())?; + StacksChainState::consensus_load_with_epoch(&block_path, epoch_id)?; Ok(Some(block)) } @@ -1077,7 +1091,7 @@ impl StacksChainState { match StacksBlock::consensus_deserialize_with_epoch( &mut &staging_block.block_data[..], - StacksEpochId::latest(), + StacksEpochId::Epoch25, ) { Ok(block) => Ok(Some(block)), Err(e) => Err(Error::CodecError(e)), @@ -7088,10 +7102,11 @@ pub mod test { &block.block_hash() ) .unwrap()); - assert!(StacksChainState::load_block( + assert!(StacksChainState::load_block_with_epoch( &chainstate.blocks_path, consensus_hash, - &block.block_hash() + &block.block_hash(), + StacksEpochId::Epoch25, ) .unwrap() .is_none()); @@ -7149,18 +7164,20 @@ pub mod test { &block.block_hash() ) .unwrap()); - assert!(StacksChainState::load_block( + assert!(StacksChainState::load_block_with_epoch( &chainstate.blocks_path, consensus_hash, - &block.block_hash() + &block.block_hash(), + StacksEpochId::Epoch25, ) .unwrap() .is_some()); assert_eq!( - StacksChainState::load_block( + StacksChainState::load_block_with_epoch( &chainstate.blocks_path, consensus_hash, - &block.block_hash() + &block.block_hash(), + StacksEpochId::Epoch25, ) .unwrap() .unwrap(), @@ -7422,10 +7439,11 @@ pub mod test { &BlockHeaderHash([2u8; 32]) ) .unwrap()); - assert!(StacksChainState::load_block( + assert!(StacksChainState::load_block_with_epoch( &chainstate.blocks_path, &ConsensusHash([1u8; 20]), - &BlockHeaderHash([2u8; 32]) + &BlockHeaderHash([2u8; 32]), + StacksEpochId::Epoch25, ) .unwrap() .is_none()); @@ -7492,18 +7510,20 @@ pub mod test { ) .unwrap()); - assert!(StacksChainState::load_block( + assert!(StacksChainState::load_block_with_epoch( &chainstate.blocks_path, &ConsensusHash([1u8; 20]), - &block.block_hash() + &block.block_hash(), + StacksEpochId::Epoch25, ) .unwrap() .is_some()); assert_eq!( - StacksChainState::load_block( + StacksChainState::load_block_with_epoch( &chainstate.blocks_path, &ConsensusHash([1u8; 20]), - &block.block_hash() + &block.block_hash(), + StacksEpochId::Epoch25, ) .unwrap() .unwrap(), @@ -7534,10 +7554,11 @@ pub mod test { &block.block_hash() ) .unwrap()); - assert!(StacksChainState::load_block( + assert!(StacksChainState::load_block_with_epoch( &chainstate.blocks_path, &ConsensusHash([1u8; 20]), - &block.block_hash() + &block.block_hash(), + StacksEpochId::Epoch25, ) .unwrap() .is_none()); @@ -7576,10 +7597,11 @@ pub mod test { ) .unwrap()); - assert!(StacksChainState::load_block( + assert!(StacksChainState::load_block_with_epoch( &chainstate.blocks_path, &ConsensusHash([1u8; 20]), - &block.block_hash() + &block.block_hash(), + StacksEpochId::Epoch25, ) .unwrap() .is_none()); diff --git a/stackslib/src/chainstate/stacks/mod.rs b/stackslib/src/chainstate/stacks/mod.rs index fc6b107878..2d8ec0b780 100644 --- a/stackslib/src/chainstate/stacks/mod.rs +++ b/stackslib/src/chainstate/stacks/mod.rs @@ -1042,6 +1042,7 @@ pub mod test { chain_id: u32, anchor_mode: &TransactionAnchorMode, post_condition_mode: &TransactionPostConditionMode, + epoch_id: StacksEpochId, ) -> Vec { let addr = StacksAddress { version: 1, @@ -1075,7 +1076,7 @@ pub mod test { signature: MessageSignature([3u8; 65]), }; - let spending_conditions = vec![ + let mut spending_conditions = vec![ TransactionSpendingCondition::Singlesig(SinglesigSpendingCondition { signer: Hash160([0x11; 20]), hash_mode: SinglesigHashMode::P2PKH, @@ -1116,30 +1117,6 @@ pub mod test { ], signatures_required: 2 }), - TransactionSpendingCondition::OrderIndependentMultisig(OrderIndependentMultisigSpendingCondition { - signer: Hash160([0x11; 20]), - hash_mode: OrderIndependentMultisigHashMode::P2SH, - nonce: 345, - tx_fee: 678, - fields: vec![ - TransactionAuthField::Signature(TransactionPublicKeyEncoding::Uncompressed, MessageSignature::from_raw(&vec![0xff; 65])), - TransactionAuthField::Signature(TransactionPublicKeyEncoding::Uncompressed, MessageSignature::from_raw(&vec![0xfe; 65])), - TransactionAuthField::PublicKey(PubKey::from_hex("04ef2340518b5867b23598a9cf74611f8b98064f7d55cdb8c107c67b5efcbc5c771f112f919b00a6c6c5f51f7c63e1762fe9fac9b66ec75a053db7f51f4a52712b").unwrap()), - ], - signatures_required: 2 - }), - TransactionSpendingCondition::OrderIndependentMultisig(OrderIndependentMultisigSpendingCondition { - signer: Hash160([0x11; 20]), - hash_mode: OrderIndependentMultisigHashMode::P2SH, - nonce: 456, - tx_fee: 789, - fields: vec![ - TransactionAuthField::Signature(TransactionPublicKeyEncoding::Compressed, MessageSignature::from_raw(&vec![0xff; 65])), - TransactionAuthField::Signature(TransactionPublicKeyEncoding::Compressed, MessageSignature::from_raw(&vec![0xfe; 65])), - TransactionAuthField::PublicKey(PubKey::from_hex("03ef2340518b5867b23598a9cf74611f8b98064f7d55cdb8c107c67b5efcbc5c77").unwrap()) - ], - signatures_required: 2 - }), TransactionSpendingCondition::Singlesig(SinglesigSpendingCondition { signer: Hash160([0x11; 20]), hash_mode: SinglesigHashMode::P2WPKH, @@ -1160,20 +1137,49 @@ pub mod test { ], signatures_required: 2 }), - TransactionSpendingCondition::OrderIndependentMultisig(OrderIndependentMultisigSpendingCondition { - signer: Hash160([0x11; 20]), - hash_mode: OrderIndependentMultisigHashMode::P2WSH, - nonce: 678, - tx_fee: 901, - fields: vec![ - TransactionAuthField::Signature(TransactionPublicKeyEncoding::Compressed, MessageSignature::from_raw(&vec![0xff; 65])), - TransactionAuthField::Signature(TransactionPublicKeyEncoding::Compressed, MessageSignature::from_raw(&vec![0xfe; 65])), - TransactionAuthField::PublicKey(PubKey::from_hex("03ef2340518b5867b23598a9cf74611f8b98064f7d55cdb8c107c67b5efcbc5c77").unwrap()) - ], - signatures_required: 2 - }) ]; + if epoch_id >= StacksEpochId::Epoch30 { + spending_conditions.append(&mut vec![ + TransactionSpendingCondition::OrderIndependentMultisig(OrderIndependentMultisigSpendingCondition { + signer: Hash160([0x11; 20]), + hash_mode: OrderIndependentMultisigHashMode::P2WSH, + nonce: 678, + tx_fee: 901, + fields: vec![ + TransactionAuthField::Signature(TransactionPublicKeyEncoding::Compressed, MessageSignature::from_raw(&vec![0xff; 65])), + TransactionAuthField::Signature(TransactionPublicKeyEncoding::Compressed, MessageSignature::from_raw(&vec![0xfe; 65])), + TransactionAuthField::PublicKey(PubKey::from_hex("03ef2340518b5867b23598a9cf74611f8b98064f7d55cdb8c107c67b5efcbc5c77").unwrap()) + ], + signatures_required: 2 + }), + TransactionSpendingCondition::OrderIndependentMultisig(OrderIndependentMultisigSpendingCondition { + signer: Hash160([0x11; 20]), + hash_mode: OrderIndependentMultisigHashMode::P2SH, + nonce: 345, + tx_fee: 678, + fields: vec![ + TransactionAuthField::Signature(TransactionPublicKeyEncoding::Uncompressed, MessageSignature::from_raw(&vec![0xff; 65])), + TransactionAuthField::Signature(TransactionPublicKeyEncoding::Uncompressed, MessageSignature::from_raw(&vec![0xfe; 65])), + TransactionAuthField::PublicKey(PubKey::from_hex("04ef2340518b5867b23598a9cf74611f8b98064f7d55cdb8c107c67b5efcbc5c771f112f919b00a6c6c5f51f7c63e1762fe9fac9b66ec75a053db7f51f4a52712b").unwrap()), + ], + signatures_required: 2 + }), + TransactionSpendingCondition::OrderIndependentMultisig(OrderIndependentMultisigSpendingCondition { + signer: Hash160([0x11; 20]), + hash_mode: OrderIndependentMultisigHashMode::P2SH, + nonce: 456, + tx_fee: 789, + fields: vec![ + TransactionAuthField::Signature(TransactionPublicKeyEncoding::Compressed, MessageSignature::from_raw(&vec![0xff; 65])), + TransactionAuthField::Signature(TransactionPublicKeyEncoding::Compressed, MessageSignature::from_raw(&vec![0xfe; 65])), + TransactionAuthField::PublicKey(PubKey::from_hex("03ef2340518b5867b23598a9cf74611f8b98064f7d55cdb8c107c67b5efcbc5c77").unwrap()) + ], + signatures_required: 2 + }), + ]) + } + let mut tx_auths = vec![]; for i in 0..spending_conditions.len() { let spending_condition = &spending_conditions[i]; @@ -1321,7 +1327,7 @@ pub mod test { }; let proof_bytes = hex_bytes("9275df67a68c8745c0ff97b48201ee6db447f7c93b23ae24cdc2400f52fdb08a1a6ac7ec71bf9c9c76e96ee4675ebff60625af28718501047bfd87b810c2d2139b73c23bd69de66360953a642c2a330a").unwrap(); let proof = VRFProof::from_bytes(&proof_bytes[..].to_vec()).unwrap(); - let tx_payloads = vec![ + let mut tx_payloads = vec![ TransactionPayload::TokenTransfer( stx_address.into(), 123, @@ -1365,43 +1371,55 @@ pub mod test { }, Some(ClarityVersion::Clarity2), ), - TransactionPayload::Coinbase(CoinbasePayload([0x12; 32]), None, None), - TransactionPayload::Coinbase( - CoinbasePayload([0x12; 32]), - Some(PrincipalData::Contract( - QualifiedContractIdentifier::transient(), - )), - None, - ), - TransactionPayload::Coinbase( - CoinbasePayload([0x12; 32]), - Some(PrincipalData::Standard(StandardPrincipalData( - 0x01, [0x02; 20], - ))), - None, - ), - TransactionPayload::Coinbase(CoinbasePayload([0x12; 32]), None, Some(proof.clone())), - TransactionPayload::Coinbase( - CoinbasePayload([0x12; 32]), - Some(PrincipalData::Contract( - QualifiedContractIdentifier::transient(), - )), - Some(proof.clone()), - ), - TransactionPayload::Coinbase( - CoinbasePayload([0x12; 32]), - Some(PrincipalData::Standard(StandardPrincipalData( - 0x01, [0x02; 20], - ))), - Some(proof.clone()), - ), TransactionPayload::PoisonMicroblock(mblock_header_1, mblock_header_2), - TransactionPayload::TenureChange( - TenureChangePayload::mock(), - ThresholdSignature::mock(), - ), ]; + if epoch_id >= StacksEpochId::Epoch30 { + tx_payloads.append(&mut vec![ + TransactionPayload::TenureChange( + TenureChangePayload::mock(), + ThresholdSignature::mock(), + ), + TransactionPayload::Coinbase( + CoinbasePayload([0x12; 32]), + None, + Some(proof.clone()), + ), + TransactionPayload::Coinbase( + CoinbasePayload([0x12; 32]), + Some(PrincipalData::Contract( + QualifiedContractIdentifier::transient(), + )), + Some(proof.clone()), + ), + TransactionPayload::Coinbase( + CoinbasePayload([0x12; 32]), + Some(PrincipalData::Standard(StandardPrincipalData( + 0x01, [0x02; 20], + ))), + Some(proof.clone()), + ), + ]) + } else { + tx_payloads.append(&mut vec![ + TransactionPayload::Coinbase(CoinbasePayload([0x12; 32]), None, None), + TransactionPayload::Coinbase( + CoinbasePayload([0x12; 32]), + Some(PrincipalData::Contract( + QualifiedContractIdentifier::transient(), + )), + None, + ), + TransactionPayload::Coinbase( + CoinbasePayload([0x12; 32]), + Some(PrincipalData::Standard(StandardPrincipalData( + 0x01, [0x02; 20], + ))), + None, + ), + ]) + } + // create all kinds of transactions let mut all_txs = vec![]; for tx_auth in tx_auths.iter() { @@ -1472,6 +1490,7 @@ pub mod test { 0x80000000, &TransactionAnchorMode::OnChainOnly, &TransactionPostConditionMode::Allow, + epoch_id, ); // remove all coinbases, except for an initial coinbase @@ -1547,6 +1566,7 @@ pub mod test { 0x80000000, &TransactionAnchorMode::OffChainOnly, &TransactionPostConditionMode::Allow, + StacksEpochId::latest(), ); let txs_mblock: Vec<_> = all_txs.into_iter().take(num_txs).collect(); diff --git a/stackslib/src/chainstate/stacks/tests/block_construction.rs b/stackslib/src/chainstate/stacks/tests/block_construction.rs index c2b85c97e4..65384534bf 100644 --- a/stackslib/src/chainstate/stacks/tests/block_construction.rs +++ b/stackslib/src/chainstate/stacks/tests/block_construction.rs @@ -3399,7 +3399,7 @@ fn test_contract_call_across_clarity_versions() { let contract = format!(" (impl-trait .chain-id-trait-v1.trait-v1) (impl-trait .chain-id-trait-v2.trait-v2) - + (use-trait chain-info-v1 .chain-id-trait-v1.trait-v1) (use-trait chain-info-v2 .chain-id-trait-v2.trait-v2) @@ -3438,7 +3438,7 @@ fn test_contract_call_across_clarity_versions() { ) ) (define-read-only (test-at-block-recursive) - (at-block 0x{} + (at-block 0x{} (begin ;; this only works in clarity2 (print {{ tenure: u{}, version: u2, chain: chain-id, func: \"test-at-block-func-recursive-v2\" }}) @@ -3517,7 +3517,7 @@ fn test_contract_call_across_clarity_versions() { let contract = format!(" (impl-trait .chain-id-trait-v1.trait-v1) (impl-trait .chain-id-trait-v2.trait-v2) - + (use-trait chain-info-v1 .chain-id-trait-v1.trait-v1) (use-trait chain-info-v2 .chain-id-trait-v2.trait-v2) @@ -3553,14 +3553,14 @@ fn test_contract_call_across_clarity_versions() { ) ) (define-read-only (test-at-block-recursive) - (at-block 0x{} + (at-block 0x{} (begin (print {{ tenure: u{}, version: u1, func: \"test-at-block-func-recursive-v1\" }}) (contract-call? .test-{} test-at-block-recursive) ) ) ) - + (define-read-only (get-call-count) (var-get call-count) ) @@ -4706,6 +4706,7 @@ fn paramaterized_mempool_walk_test( 0x80000000, &TransactionAnchorMode::Any, &TransactionPostConditionMode::Allow, + StacksEpochId::latest(), ); let mut transaction_counter = 0; diff --git a/stackslib/src/chainstate/stacks/tests/chain_histories.rs b/stackslib/src/chainstate/stacks/tests/chain_histories.rs index 1d8704306b..c1a48718e3 100644 --- a/stackslib/src/chainstate/stacks/tests/chain_histories.rs +++ b/stackslib/src/chainstate/stacks/tests/chain_histories.rs @@ -33,6 +33,7 @@ use rand::seq::SliceRandom; use rand::{thread_rng, Rng}; use stacks_common::address::*; use stacks_common::types::chainstate::SortitionId; +use stacks_common::types::StacksEpochId; use stacks_common::util::hash::MerkleTree; use stacks_common::util::sleep_ms; use stacks_common::util::vrf::VRFProof; @@ -2484,12 +2485,20 @@ fn assert_chainstate_blocks_eq(test_name_1: &str, test_name_2: &str) { ) .unwrap(); - let chunk_1_opt = - StacksChainState::load_block(&ch1.blocks_path, &all_blocks_1[i].0, &all_blocks_1[i].1) - .unwrap(); - let chunk_2_opt = - StacksChainState::load_block(&ch2.blocks_path, &all_blocks_2[i].0, &all_blocks_2[i].1) - .unwrap(); + let chunk_1_opt = StacksChainState::load_block_with_epoch( + &ch1.blocks_path, + &all_blocks_1[i].0, + &all_blocks_1[i].1, + StacksEpochId::Epoch25, + ) + .unwrap(); + let chunk_2_opt = StacksChainState::load_block_with_epoch( + &ch2.blocks_path, + &all_blocks_2[i].0, + &all_blocks_2[i].1, + StacksEpochId::Epoch25, + ) + .unwrap(); match (staging_1_opt, staging_2_opt) { (Some(staging_1), Some(staging_2)) => { @@ -3117,7 +3126,7 @@ pub fn mine_smart_contract_block_contract_call_microblock_exception( microblocks.push(microblock); } - test_debug!("Produce anchored stacks block {} with smart contract and {} microblocks with contract call at burnchain height {} stacks height {}", + test_debug!("Produce anchored stacks block {} with smart contract and {} microblocks with contract call at burnchain height {} stacks height {}", stacks_block.block_hash(), microblocks.len(), burnchain_height, stacks_block.header.total_work.work); (stacks_block, microblocks) diff --git a/stackslib/src/chainstate/stacks/transaction.rs b/stackslib/src/chainstate/stacks/transaction.rs index 50bbce23d4..296bc469e2 100644 --- a/stackslib/src/chainstate/stacks/transaction.rs +++ b/stackslib/src/chainstate/stacks/transaction.rs @@ -3922,6 +3922,7 @@ mod test { 0, &TransactionAnchorMode::OnChainOnly, &TransactionPostConditionMode::Deny, + StacksEpochId::latest(), ); for tx in all_txs.iter() { let mut tx_bytes = vec![ diff --git a/stackslib/src/core/mempool.rs b/stackslib/src/core/mempool.rs index ccb500f17b..d7bf53d2c7 100644 --- a/stackslib/src/core/mempool.rs +++ b/stackslib/src/core/mempool.rs @@ -33,8 +33,8 @@ use rusqlite::{ }; use siphasher::sip::SipHasher; // this is SipHash-2-4 use stacks_common::codec::{ - read_next, write_next, DeserializeWithEpoch, Error as codec_error, StacksMessageCodec, - MAX_MESSAGE_LEN, + read_next, read_next_with_epoch, write_next, DeserializeWithEpoch, Error as codec_error, + StacksMessageCodec, MAX_MESSAGE_LEN, }; use stacks_common::types::chainstate::{BlockHeaderHash, StacksAddress, StacksBlockId}; use stacks_common::util::hash::{to_hex, Sha512Trunc256Sum}; @@ -216,6 +216,13 @@ fn parse_mempool_query_page_id( /// optional 32-byte page ID. Obtain both the transactions and page ID, if it exists. pub fn decode_tx_stream( fd: &mut R, +) -> Result<(Vec, Option), net_error> { + decode_tx_stream_with_epoch(fd, StacksEpochId::latest()) +} + +pub fn decode_tx_stream_with_epoch( + fd: &mut R, + epoch_id: StacksEpochId, ) -> Result<(Vec, Option), net_error> { // The wire format is `tx, tx, tx, tx, .., tx, txid`. // The last 32 bytes are the page ID for the next mempool query. @@ -228,7 +235,8 @@ pub fn decode_tx_stream( loop { let pos = retry_reader.position(); - let next_msg: Result = read_next(&mut retry_reader); + let next_msg: Result = + read_next_with_epoch(&mut retry_reader, epoch_id); match next_msg { Ok(tx) => { if expect_eof { diff --git a/stackslib/src/core/tests/mod.rs b/stackslib/src/core/tests/mod.rs index 83a26d27bc..5f9febe944 100644 --- a/stackslib/src/core/tests/mod.rs +++ b/stackslib/src/core/tests/mod.rs @@ -31,6 +31,7 @@ use stacks_common::types::chainstate::{ BlockHeaderHash, BurnchainHeaderHash, StacksAddress, StacksBlockId, StacksWorkScore, TrieHash, VRFSeed, }; +use stacks_common::types::StacksEpochId; use stacks_common::util::hash::{hex_bytes, to_hex, Hash160, *}; use stacks_common::util::secp256k1::{MessageSignature, *}; use stacks_common::util::vrf::VRFProof; @@ -192,6 +193,7 @@ fn mempool_walk_over_fork() { 0x80000000, &TransactionAnchorMode::Any, &TransactionPostConditionMode::Allow, + StacksEpochId::latest(), ); let blocks_to_broadcast_in = [&b_1, &b_2, &b_4]; @@ -603,6 +605,7 @@ fn test_iterate_candidates_consider_no_estimate_tx_prob() { 0x80000000, &TransactionAnchorMode::Any, &TransactionPostConditionMode::Allow, + StacksEpochId::latest(), ); // Load 24 transactions into the mempool, alternating whether or not they have a fee-rate. @@ -799,6 +802,7 @@ fn test_iterate_candidates_skipped_transaction() { 0x80000000, &TransactionAnchorMode::Any, &TransactionPostConditionMode::Allow, + StacksEpochId::latest(), ); // Load 3 transactions into the mempool @@ -912,6 +916,7 @@ fn test_iterate_candidates_processing_error_transaction() { 0x80000000, &TransactionAnchorMode::Any, &TransactionPostConditionMode::Allow, + StacksEpochId::latest(), ); // Load 3 transactions into the mempool @@ -1027,6 +1032,7 @@ fn test_iterate_candidates_problematic_transaction() { 0x80000000, &TransactionAnchorMode::Any, &TransactionPostConditionMode::Allow, + StacksEpochId::latest(), ); // Load 3 transactions into the mempool @@ -1142,6 +1148,7 @@ fn test_iterate_candidates_concurrent_write_lock() { 0x80000000, &TransactionAnchorMode::Any, &TransactionPostConditionMode::Allow, + StacksEpochId::latest(), ); let mut expected_addr_nonces = HashMap::new(); @@ -1300,6 +1307,7 @@ fn mempool_do_not_replace_tx() { 0x80000000, &TransactionAnchorMode::Any, &TransactionPostConditionMode::Allow, + StacksEpochId::latest(), ); let mut tx = txs.pop().unwrap(); @@ -1396,6 +1404,7 @@ fn mempool_db_load_store_replace_tx() { 0x80000000, &TransactionAnchorMode::Any, &TransactionPostConditionMode::Allow, + StacksEpochId::latest(), ); let num_txs = txs.len() as u64; diff --git a/stackslib/src/net/api/getblock.rs b/stackslib/src/net/api/getblock.rs index 24c04bb045..446bc41088 100644 --- a/stackslib/src/net/api/getblock.rs +++ b/stackslib/src/net/api/getblock.rs @@ -20,7 +20,7 @@ use std::{fs, io}; use regex::{Captures, Regex}; use serde::de::Error as de_Error; -use stacks_common::codec::{StacksMessageCodec, DeserializeWithEpoch, MAX_MESSAGE_LEN}; +use stacks_common::codec::{DeserializeWithEpoch, StacksMessageCodec, MAX_MESSAGE_LEN}; use stacks_common::types::chainstate::StacksBlockId; use stacks_common::types::net::PeerHost; use stacks_common::types::StacksEpochId; @@ -304,9 +304,21 @@ impl StacksHttpResponse { let block_bytes: Vec = contents.try_into()?; let block = StacksBlock::consensus_deserialize_with_epoch( &mut &block_bytes[..], - StacksEpochId::latest(), + StacksEpochId::Epoch25, )?; Ok(block) } + + /// Decode an HTTP response into a block. + /// If it fails, return Self::Error(..) + pub fn decode_block_with_epoch(self, epoch_id: StacksEpochId) -> Result { + let contents = self.get_http_payload_ok()?; + + // contents will be raw bytes + let block_bytes: Vec = contents.try_into()?; + let block = StacksBlock::consensus_deserialize_with_epoch(&mut &block_bytes[..], epoch_id)?; + + Ok(block) + } } diff --git a/stackslib/src/net/api/postblock.rs b/stackslib/src/net/api/postblock.rs index 0b9a3e19ad..d4a06d6872 100644 --- a/stackslib/src/net/api/postblock.rs +++ b/stackslib/src/net/api/postblock.rs @@ -76,7 +76,7 @@ impl RPCPostBlockRequestHandler { /// Decode a bare block from the body fn parse_postblock_octets(mut body: &[u8]) -> Result { let block = - StacksBlock::consensus_deserialize_with_epoch(&mut body, StacksEpochId::latest()) + StacksBlock::consensus_deserialize_with_epoch(&mut body, StacksEpochId::Epoch25) .map_err(|e| { if let CodecError::DeserializeError(msg) = e { Error::DecodeError(format!( diff --git a/stackslib/src/net/api/posttransaction.rs b/stackslib/src/net/api/posttransaction.rs index ef78e771f2..c4874c08fb 100644 --- a/stackslib/src/net/api/posttransaction.rs +++ b/stackslib/src/net/api/posttransaction.rs @@ -18,7 +18,9 @@ use std::io::{Read, Write}; use clarity::vm::costs::ExecutionCost; use regex::{Captures, Regex}; -use stacks_common::codec::{DeserializeWithEpoch, Error as CodecError, MAX_PAYLOAD_LEN, StacksMessageCodec}; +use stacks_common::codec::{ + DeserializeWithEpoch, Error as CodecError, StacksMessageCodec, MAX_PAYLOAD_LEN, +}; use stacks_common::types::chainstate::{ BlockHeaderHash, ConsensusHash, StacksBlockId, StacksPublicKey, }; diff --git a/stackslib/src/net/api/tests/getblock.rs b/stackslib/src/net/api/tests/getblock.rs index 5e6024005b..9dce17b252 100644 --- a/stackslib/src/net/api/tests/getblock.rs +++ b/stackslib/src/net/api/tests/getblock.rs @@ -98,7 +98,9 @@ fn test_try_make_response() { // got the block let response = responses.remove(0); - let resp = response.decode_block().unwrap(); + let resp = response + .decode_block_with_epoch(StacksEpochId::Epoch25) + .unwrap(); assert_eq!( StacksBlockHeader::make_index_block_hash(&consensus_hash, &resp.block_hash()), @@ -161,7 +163,7 @@ fn test_stream_blocks() { // should decode back into the block let staging_block = StacksBlock::consensus_deserialize_with_epoch( &mut &all_block_bytes[..], - StacksEpochId::latest(), + StacksEpochId::Epoch25, ) .unwrap(); assert_eq!(staging_block, block); @@ -190,7 +192,7 @@ fn test_stream_blocks() { // should decode back into the block let staging_block = StacksBlock::consensus_deserialize_with_epoch( &mut &all_block_bytes[..], - StacksEpochId::latest(), + StacksEpochId::Epoch25, ) .unwrap(); assert_eq!(staging_block, block); diff --git a/stackslib/src/net/api/tests/postblock.rs b/stackslib/src/net/api/tests/postblock.rs index 19e0edfda5..c3b546c4ac 100644 --- a/stackslib/src/net/api/tests/postblock.rs +++ b/stackslib/src/net/api/tests/postblock.rs @@ -39,7 +39,7 @@ fn test_try_parse_request() { let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 33333); let mut http = StacksHttp::new(addr.clone(), &ConnectionOptions::default()); - let block = make_codec_test_block(3, StacksEpochId::latest()); + let block = make_codec_test_block(3, StacksEpochId::Epoch25); let request = StacksHttpRequest::new_post_block(addr.into(), ConsensusHash([0x11; 20]), block.clone()); let bytes = request.try_serialize().unwrap(); diff --git a/stackslib/src/net/api/tests/postmempoolquery.rs b/stackslib/src/net/api/tests/postmempoolquery.rs index 1f528c57c5..e2b727c339 100644 --- a/stackslib/src/net/api/tests/postmempoolquery.rs +++ b/stackslib/src/net/api/tests/postmempoolquery.rs @@ -20,12 +20,13 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use clarity::vm::types::{PrincipalData, QualifiedContractIdentifier, StacksAddressExtensions}; use clarity::vm::{ClarityName, ContractName, Value}; -use stacks_common::codec::{read_next, Error as CodecError, StacksMessageCodec}; +use stacks_common::codec::{read_next_with_epoch, Error as CodecError, StacksMessageCodec}; use stacks_common::types::chainstate::{ BlockHeaderHash, ConsensusHash, StacksAddress, StacksPrivateKey, }; use stacks_common::types::net::PeerHost; use stacks_common::types::Address; +use stacks_common::types::StacksEpochId; use stacks_common::util::hash::{to_hex, Hash160}; use super::TestRPC; @@ -214,23 +215,24 @@ fn test_stream_mempool_txs() { let mut decoded_txs = vec![]; let mut ptr = &buf[..]; loop { - let tx: StacksTransaction = match read_next::(&mut ptr) { - Ok(tx) => tx, - Err(e) => match e { - CodecError::ReadError(ref ioe) => match ioe.kind() { - io::ErrorKind::UnexpectedEof => { - eprintln!("out of transactions"); - break; - } + let tx: StacksTransaction = + match read_next_with_epoch::(&mut ptr, StacksEpochId::latest()) { + Ok(tx) => tx, + Err(e) => match e { + CodecError::ReadError(ref ioe) => match ioe.kind() { + io::ErrorKind::UnexpectedEof => { + eprintln!("out of transactions"); + break; + } + _ => { + panic!("IO error: {:?}", &e); + } + }, _ => { - panic!("IO error: {:?}", &e); + panic!("other error: {:?}", &e); } }, - _ => { - panic!("other error: {:?}", &e); - } - }, - }; + }; decoded_txs.push(tx); } diff --git a/stackslib/src/net/relay.rs b/stackslib/src/net/relay.rs index 39fe7537f3..d796eeba94 100644 --- a/stackslib/src/net/relay.rs +++ b/stackslib/src/net/relay.rs @@ -2603,6 +2603,7 @@ pub mod test { 0x80000000, &TransactionAnchorMode::Any, &TransactionPostConditionMode::Allow, + StacksEpochId::latest(), ); assert!(all_transactions.len() > MAX_RECENT_MESSAGES); @@ -2754,6 +2755,7 @@ pub mod test { 0x80000000, &TransactionAnchorMode::Any, &TransactionPostConditionMode::Allow, + StacksEpochId::latest(), ); assert!(all_transactions.len() > MAX_RECENT_MESSAGES); @@ -5748,7 +5750,7 @@ pub mod test { let sortdb = peer.sortdb.take().unwrap(); let mut node = peer.stacks_node.take().unwrap(); // transaction with versioned contract was filtered and no error in the block will appear - assert_eq!(stacks_block.txs.len(), 1); + assert_eq!(stacks_block.txs.len(), 0); peer.sortdb = Some(sortdb); peer.stacks_node = Some(node); } @@ -5761,7 +5763,7 @@ pub mod test { let sortdb = peer.sortdb.take().unwrap(); let mut node = peer.stacks_node.take().unwrap(); // no filtered transactions in epoch 2.1, all valid - assert_eq!(stacks_block.txs.len(), 2); + assert_eq!(stacks_block.txs.len(), 1); match Relayer::process_new_anchored_block( &sortdb.index_conn(), &mut node.chainstate, @@ -5780,332 +5782,6 @@ pub mod test { peer.stacks_node = Some(node); } - #[test] - fn test_block_order_independent_multisig_mempool_rejection_until_v210() { - let mut peer_config = TestPeerConfig::new(function_name!(), 4250, 4251); - - let privk_1 = StacksPrivateKey::from_hex( - "6d430bb91222408e7706c9001cfaeb91b08c2be6d5ac95779ab52c6b431950e001", - ) - .unwrap(); - let privk_2 = StacksPrivateKey::from_hex( - "2a584d899fed1d24e26b524f202763c8ab30260167429f157f1c119f550fa6af01", - ) - .unwrap(); - let privk_3 = StacksPrivateKey::from_hex( - "d5200dee706ee53ae98a03fba6cf4fdcc5084c30cfa9e1b3462dcdeaa3e0f1d201", - ) - .unwrap(); - let num_sigs = 2; - - let first_multisig = StacksAddress::from_public_keys( - C32_ADDRESS_VERSION_TESTNET_MULTISIG, - &AddressHashMode::SerializeP2SH, - num_sigs, - &vec![ - StacksPublicKey::from_private(&privk_1), - StacksPublicKey::from_private(&privk_2), - StacksPublicKey::from_private(&privk_3), - ], - ) - .unwrap() - .to_account_principal(); - let second_multisig = StacksAddress::from_public_keys( - C32_ADDRESS_VERSION_TESTNET_MULTISIG, - &AddressHashMode::SerializeP2WSH, - num_sigs, - &vec![ - StacksPublicKey::from_private(&privk_1), - StacksPublicKey::from_private(&privk_2), - StacksPublicKey::from_private(&privk_3), - ], - ) - .unwrap() - .to_account_principal(); - let initial_balances = vec![ - ( - PrincipalData::from(peer_config.spending_account.origin_address().unwrap()), - 1000000, - ), - (first_multisig.clone(), 1000000), - (second_multisig.clone(), 1000000), - ]; - - let epochs = vec![ - StacksEpoch { - epoch_id: StacksEpochId::Epoch10, - start_height: 0, - end_height: 0, - block_limit: ExecutionCost::max_value(), - network_epoch: PEER_VERSION_EPOCH_1_0, - }, - StacksEpoch { - epoch_id: StacksEpochId::Epoch20, - start_height: 0, - end_height: 0, - block_limit: ExecutionCost::max_value(), - network_epoch: PEER_VERSION_EPOCH_2_0, - }, - StacksEpoch { - epoch_id: StacksEpochId::Epoch2_05, - start_height: 0, - end_height: 28, // NOTE: the first 25 burnchain blocks have no sortition - block_limit: ExecutionCost::max_value(), - network_epoch: PEER_VERSION_EPOCH_2_05, - }, - StacksEpoch { - epoch_id: StacksEpochId::Epoch21, - start_height: 28, - end_height: 29, - block_limit: ExecutionCost::max_value(), - network_epoch: PEER_VERSION_EPOCH_2_1, - }, - StacksEpoch { - epoch_id: StacksEpochId::Epoch24, - start_height: 29, - end_height: 30, - block_limit: ExecutionCost::max_value(), - network_epoch: PEER_VERSION_EPOCH_2_4, - }, - StacksEpoch { - epoch_id: StacksEpochId::Epoch30, - start_height: 30, - end_height: STACKS_EPOCH_MAX, - block_limit: ExecutionCost::max_value(), - network_epoch: PEER_VERSION_EPOCH_3_0, - }, - ]; - - peer_config.epochs = Some(epochs); - peer_config.initial_balances = initial_balances; - - let mut peer = TestPeer::new(peer_config); - let order_independent_p2sh_opt: RefCell> = RefCell::new(None); - let order_independent_p2wsh_opt: RefCell> = RefCell::new(None); - let nonce: RefCell = RefCell::new(0); - - let mut make_tenure = - |miner: &mut TestMiner, - sortdb: &mut SortitionDB, - chainstate: &mut StacksChainState, - vrfproof: VRFProof, - parent_opt: Option<&StacksBlock>, - microblock_parent_opt: Option<&StacksMicroblockHeader>| { - let tip = SortitionDB::get_canonical_burn_chain_tip(sortdb.conn()).unwrap(); - - let stacks_tip_opt = - NakamotoChainState::get_canonical_block_header(chainstate.db(), sortdb) - .unwrap(); - let parent_tip = match stacks_tip_opt { - None => StacksChainState::get_genesis_header_info(chainstate.db()).unwrap(), - Some(header_tip) => { - let ic = sortdb.index_conn(); - let snapshot = SortitionDB::get_block_snapshot_for_winning_stacks_block( - &ic, - &tip.sortition_id, - &header_tip.anchored_header.block_hash(), - ) - .unwrap() - .unwrap(); // succeeds because we don't fork - StacksChainState::get_anchored_block_header_info( - chainstate.db(), - &snapshot.consensus_hash, - &snapshot.winning_stacks_block_hash, - ) - .unwrap() - .unwrap() - } - }; - - let parent_header_hash = parent_tip.anchored_header.block_hash(); - let parent_consensus_hash = parent_tip.consensus_hash.clone(); - let parent_index_hash = StacksBlockHeader::make_index_block_hash( - &parent_consensus_hash, - &parent_header_hash, - ); - - let next_nonce = *nonce.borrow(); - let coinbase_tx = make_coinbase_with_nonce( - miner, - parent_tip.stacks_block_height as usize, - next_nonce, - None, - ); - - let order_independent_p2sh = make_stacks_transfer_order_independent_p2sh( - &[privk_1, privk_2, privk_3], - num_sigs, - next_nonce, - 1000, - &second_multisig, - 100, - ); - - let order_independent_p2wsh = make_stacks_transfer_order_independent_p2wsh( - &[privk_1, privk_2, privk_3], - num_sigs, - next_nonce, - 1000, - &first_multisig, - 100, - ); - - *order_independent_p2sh_opt.borrow_mut() = Some(order_independent_p2sh); - *order_independent_p2wsh_opt.borrow_mut() = Some(order_independent_p2wsh); - *nonce.borrow_mut() = next_nonce + 1; - - let mut mblock_pubkey_hash_bytes = [0u8; 20]; - mblock_pubkey_hash_bytes.copy_from_slice(&coinbase_tx.txid()[0..20]); - - let builder = StacksBlockBuilder::make_block_builder( - chainstate.mainnet, - &parent_tip, - vrfproof, - tip.total_burn, - Hash160(mblock_pubkey_hash_bytes), - ) - .unwrap(); - - let anchored_block = StacksBlockBuilder::make_anchored_block_from_txs( - builder, - chainstate, - &sortdb.index_conn(), - vec![coinbase_tx], - ) - .unwrap(); - - eprintln!("{:?}", &anchored_block.0); - (anchored_block.0, vec![]) - }; - - for i in 0..4 { - let (burn_ops, stacks_block, microblocks) = peer.make_tenure(&mut make_tenure); - let (_, _, consensus_hash) = peer.next_burnchain_block(burn_ops.clone()); - - let sortdb = peer.sortdb.take().unwrap(); - let mut node = peer.stacks_node.take().unwrap(); - - // the empty block should be accepted - match Relayer::process_new_anchored_block( - &sortdb.index_conn(), - &mut node.chainstate, - &consensus_hash, - &stacks_block, - 123, - ) { - Ok(x) => { - assert!(x, "Did not accept valid block"); - } - Err(e) => { - panic!("Got unexpected error {:?}", &e); - } - }; - - // process it - peer.coord.handle_new_stacks_block().unwrap(); - - // the mempool would reject both order independent multisig transactions, since we're not yet at tenure 30 - let order_independent_p2sh = (*order_independent_p2sh_opt.borrow()).clone().unwrap(); - let order_independent_p2sh_len = order_independent_p2sh.serialize_to_vec().len(); - match node.chainstate.will_admit_mempool_tx( - &sortdb.index_conn(), - &consensus_hash, - &stacks_block.block_hash(), - &order_independent_p2sh, - order_independent_p2sh_len as u64, - ) { - Err(MemPoolRejection::Other(msg)) => { - assert!(msg.find("not supported in this epoch").is_some()); - } - Err(e) => { - panic!("will_admit_mempool_tx {:?}", &e); - } - Ok(_) => { - panic!("will_admit_mempool_tx succeeded"); - } - }; - - let order_independent_p2wsh = (*order_independent_p2wsh_opt.borrow()).clone().unwrap(); - let order_independent_p2wsh_len = order_independent_p2wsh.serialize_to_vec().len(); - match node.chainstate.will_admit_mempool_tx( - &sortdb.index_conn(), - &consensus_hash, - &stacks_block.block_hash(), - &order_independent_p2wsh, - order_independent_p2wsh_len as u64, - ) { - Err(MemPoolRejection::Other(msg)) => { - assert!(msg.find("not supported in this epoch").is_some()); - } - Err(e) => { - panic!("will_admit_mempool_tx {:?}", &e); - } - Ok(_) => { - panic!("will_admit_mempool_tx succeeded"); - } - }; - - peer.sortdb = Some(sortdb); - peer.stacks_node = Some(node); - } - - // *now* it should succeed, since tenure 30 was in epoch 3.0 - let (burn_ops, stacks_block, microblocks) = peer.make_tenure(&mut make_tenure); - let (_, _, consensus_hash) = peer.next_burnchain_block(burn_ops.clone()); - - let sortdb = peer.sortdb.take().unwrap(); - let mut node = peer.stacks_node.take().unwrap(); - match Relayer::process_new_anchored_block( - &sortdb.index_conn(), - &mut node.chainstate, - &consensus_hash, - &stacks_block, - 123, - ) { - Ok(x) => { - assert!(x, "Failed to process valid versioned smart contract block"); - } - Err(e) => { - panic!("Got unexpected error {:?}", &e); - } - }; - - peer.coord.handle_new_stacks_block().unwrap(); - - let order_independent_p2sh = (*order_independent_p2sh_opt.borrow()).clone().unwrap(); - let order_independent_p2sh_len = order_independent_p2sh.serialize_to_vec().len(); - match node.chainstate.will_admit_mempool_tx( - &sortdb.index_conn(), - &consensus_hash, - &stacks_block.block_hash(), - &order_independent_p2sh, - order_independent_p2sh_len as u64, - ) { - Err(e) => { - panic!("will_admit_mempool_tx {:?}", &e); - } - Ok(_) => {} - }; - - let order_independent_p2wsh = (*order_independent_p2wsh_opt.borrow()).clone().unwrap(); - let order_independent_p2wsh_len = order_independent_p2wsh.serialize_to_vec().len(); - match node.chainstate.will_admit_mempool_tx( - &sortdb.index_conn(), - &consensus_hash, - &stacks_block.block_hash(), - &order_independent_p2wsh, - order_independent_p2wsh_len as u64, - ) { - Err(e) => { - panic!("will_admit_mempool_tx {:?}", &e); - } - Ok(_) => {} - }; - - peer.sortdb = Some(sortdb); - peer.stacks_node = Some(node); - } - #[test] fn test_block_versioned_smart_contract_gated_at_v210() { let mut peer_config = TestPeerConfig::new(function_name!(), 4248, 4249); @@ -6240,22 +5916,8 @@ pub mod test { let sortdb = peer.sortdb.take().unwrap(); let mut node = peer.stacks_node.take().unwrap(); - match Relayer::process_new_anchored_block( - &sortdb.index_conn(), - &mut node.chainstate, - &consensus_hash, - &stacks_block, - 123, - ) { - Ok(x) => { - eprintln!("{:?}", &stacks_block); - panic!("Stored pay-to-contract stacks block before epoch 2.1"); - } - Err(chainstate_error::InvalidStacksBlock(_)) => {} - Err(e) => { - panic!("Got unexpected error {:?}", &e); - } - }; + // incorrect transaction was filtered and no error in the block will appear + assert_eq!(stacks_block.txs.len(), 1); peer.sortdb = Some(sortdb); peer.stacks_node = Some(node); } diff --git a/stackslib/src/net/server.rs b/stackslib/src/net/server.rs index 88ba77554e..bd43d308ea 100644 --- a/stackslib/src/net/server.rs +++ b/stackslib/src/net/server.rs @@ -20,8 +20,8 @@ use std::sync::mpsc::{sync_channel, Receiver, RecvError, SendError, SyncSender, use mio::net as mio_net; use stacks_common::types::net::{PeerAddress, PeerHost}; -use stacks_common::util::get_epoch_time_secs; use stacks_common::types::StacksEpochId; +use stacks_common::util::get_epoch_time_secs; use crate::burnchains::{Burnchain, BurnchainView}; use crate::chainstate::burn::db::sortdb::SortitionDB; @@ -884,7 +884,7 @@ mod test { 1, 0, |client_id, ref mut chainstate| { - let peer_server_block = make_codec_test_block(25, StacksEpochId::latest()); + let peer_server_block = make_codec_test_block(25, StacksEpochId::Epoch25); let peer_server_consensus_hash = ConsensusHash([(client_id + 1) as u8; 20]); let index_block_hash = StacksBlockHeader::make_index_block_hash( &peer_server_consensus_hash, @@ -917,7 +917,7 @@ mod test { // should be a Block let http_response_bytes = http_response_bytes_res.unwrap(); - let peer_server_block = make_codec_test_block(25, StacksEpochId::latest()); + let peer_server_block = make_codec_test_block(25, StacksEpochId::Epoch25); let peer_server_consensus_hash = ConsensusHash([(client_id + 1) as u8; 20]); let index_block_hash = StacksBlockHeader::make_index_block_hash( &peer_server_consensus_hash, diff --git a/stackslib/src/net/tests/httpcore.rs b/stackslib/src/net/tests/httpcore.rs index f85dd52f92..826aeb422e 100644 --- a/stackslib/src/net/tests/httpcore.rs +++ b/stackslib/src/net/tests/httpcore.rs @@ -21,11 +21,11 @@ use std::str; use stacks_common::codec::StacksMessageCodec; use stacks_common::types::chainstate::{StacksAddress, StacksBlockId, StacksPrivateKey}; use stacks_common::types::net::{PeerAddress, PeerHost}; +use stacks_common::types::StacksEpochId; use stacks_common::util::chunked_encoding::{ HttpChunkedTransferWriter, HttpChunkedTransferWriterState, }; use stacks_common::util::hash::{hex_bytes, to_hex, Hash160}; -use stacks_common::types::StacksEpochId; use crate::burnchains::Txid; use crate::chainstate::stacks::db::blocks::test::make_sample_microblock_stream; diff --git a/testnet/stacks-node/src/tests/epoch_24.rs b/testnet/stacks-node/src/tests/epoch_24.rs index c4fa97aaeb..92906e62d2 100644 --- a/testnet/stacks-node/src/tests/epoch_24.rs +++ b/testnet/stacks-node/src/tests/epoch_24.rs @@ -32,10 +32,10 @@ use stacks_common::codec::DeserializeWithEpoch; use stacks_common::consts::STACKS_EPOCH_MAX; use stacks_common::types::chainstate::{StacksAddress, StacksBlockId, StacksPrivateKey}; use stacks_common::types::Address; +use stacks_common::types::StacksEpochId; use stacks_common::util::hash::{bytes_to_hex, hex_bytes, Hash160}; use stacks_common::util::secp256k1::Secp256k1PublicKey; use stacks_common::util::sleep_ms; -use stacks_common::types::StacksEpochId; use crate::config::{EventKeyType, EventObserverConfig, InitialBalance}; use crate::tests::bitcoin_regtest::BitcoinCoreController; From 92bbea927c8b482239f5a97d752c58ab14bb8f38 Mon Sep 17 00:00:00 2001 From: Fess Date: Mon, 27 Nov 2023 20:54:47 +0400 Subject: [PATCH 03/41] fix: nakamoto autotest fix --- stackslib/src/chainstate/nakamoto/tests/mod.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/stackslib/src/chainstate/nakamoto/tests/mod.rs b/stackslib/src/chainstate/nakamoto/tests/mod.rs index 09f9289408..70c479ff6a 100644 --- a/stackslib/src/chainstate/nakamoto/tests/mod.rs +++ b/stackslib/src/chainstate/nakamoto/tests/mod.rs @@ -570,18 +570,13 @@ pub fn test_load_store_update_nakamoto_blocks() { tx.commit().unwrap(); } - let epoch_id = SortitionDB::get_stacks_epoch(chainstate.db(), nakamoto_header.chain_length) - .unwrap() - .unwrap() - .epoch_id; - // can load Nakamoto block, but only the Nakamoto block assert_eq!( NakamotoChainState::load_nakamoto_block( chainstate.db(), &nakamoto_header.consensus_hash, &nakamoto_header.block_hash(), - epoch_id, + StacksEpochId::latest(), ) .unwrap() .unwrap(), @@ -592,7 +587,7 @@ pub fn test_load_store_update_nakamoto_blocks() { chainstate.db(), &epoch2_header_info.consensus_hash, &epoch2_header.block_hash(), - epoch_id, + StacksEpochId::latest(), ) .unwrap(), None From bd6df0393bc7747d606183d1dc2dac5dc2d529f9 Mon Sep 17 00:00:00 2001 From: Fess Date: Wed, 6 Dec 2023 19:10:52 +0200 Subject: [PATCH 04/41] fix: autotests added for one multisig address and both multisig transaction types --- .../src/chainstate/stacks/transaction.rs | 1341 ++++++++++++++++- 1 file changed, 1297 insertions(+), 44 deletions(-) diff --git a/stackslib/src/chainstate/stacks/transaction.rs b/stackslib/src/chainstate/stacks/transaction.rs index 296bc469e2..47a9e48408 100644 --- a/stackslib/src/chainstate/stacks/transaction.rs +++ b/stackslib/src/chainstate/stacks/transaction.rs @@ -5789,11 +5789,13 @@ mod test { for mut tx in txs { assert_eq!(tx.auth().origin().num_signatures(), 0); - let tx_signer = StacksTransactionSigner::new(&tx); - - let initialSigHash = tx.sign_begin(); - let sig3 = tx.sign_no_append_origin(&initialSigHash, &privk_3).unwrap(); - let sig2 = tx.sign_no_append_origin(&initialSigHash, &privk_2).unwrap(); + let initial_sig_hash = tx.sign_begin(); + let sig3 = tx + .sign_no_append_origin(&initial_sig_hash, &privk_3) + .unwrap(); + let sig2 = tx + .sign_no_append_origin(&initial_sig_hash, &privk_2) + .unwrap(); let _ = tx.append_next_origin(&pubk_1); let _ = tx.append_origin_signature(sig2, TransactionPublicKeyEncoding::Compressed); @@ -5913,12 +5915,12 @@ mod test { origin_tx.set_tx_fee(456); origin_tx.set_sponsor_nonce(789).unwrap(); - let initialSigHash = tx_signer.sighash; + let initial_sig_hash = tx_signer.sighash; let sig1 = origin_tx - .sign_no_append_sponsor(&initialSigHash, &privk_1) + .sign_no_append_sponsor(&initial_sig_hash, &privk_1) .unwrap(); let sig2 = origin_tx - .sign_no_append_sponsor(&initialSigHash, &privk_2) + .sign_no_append_sponsor(&initial_sig_hash, &privk_2) .unwrap(); let _ = @@ -6030,9 +6032,13 @@ mod test { let tx_signer = StacksTransactionSigner::new(&tx); - let initialSigHash = tx.sign_begin(); - let sig3 = tx.sign_no_append_origin(&initialSigHash, &privk_3).unwrap(); - let sig2 = tx.sign_no_append_origin(&initialSigHash, &privk_2).unwrap(); + let initial_sig_hash = tx.sign_begin(); + let sig3 = tx + .sign_no_append_origin(&initial_sig_hash, &privk_3) + .unwrap(); + let sig2 = tx + .sign_no_append_origin(&initial_sig_hash, &privk_2) + .unwrap(); let _ = tx.append_next_origin(&pubk_1); let _ = tx.append_origin_signature(sig2, TransactionPublicKeyEncoding::Uncompressed); @@ -6152,12 +6158,12 @@ mod test { origin_tx.set_tx_fee(456); origin_tx.set_sponsor_nonce(789).unwrap(); - let initialSigHash = tx_signer.sighash; + let initial_sig_hash = tx_signer.sighash; let sig1 = origin_tx - .sign_no_append_sponsor(&initialSigHash, &privk_1) + .sign_no_append_sponsor(&initial_sig_hash, &privk_1) .unwrap(); let sig2 = origin_tx - .sign_no_append_sponsor(&initialSigHash, &privk_2) + .sign_no_append_sponsor(&initial_sig_hash, &privk_2) .unwrap(); let _ = origin_tx @@ -6269,9 +6275,13 @@ mod test { let tx_signer = StacksTransactionSigner::new(&tx); - let initialSigHash = tx.sign_begin(); - let sig3 = tx.sign_no_append_origin(&initialSigHash, &privk_3).unwrap(); - let sig1 = tx.sign_no_append_origin(&initialSigHash, &privk_1).unwrap(); + let initial_sig_hash = tx.sign_begin(); + let sig3 = tx + .sign_no_append_origin(&initial_sig_hash, &privk_3) + .unwrap(); + let sig1 = tx + .sign_no_append_origin(&initial_sig_hash, &privk_1) + .unwrap(); let _ = tx.append_origin_signature(sig1, TransactionPublicKeyEncoding::Compressed); let _ = tx.append_next_origin(&pubk_2); @@ -6395,10 +6405,16 @@ mod test { let tx_signer = StacksTransactionSigner::new(&tx); - let initialSigHash = tx.sign_begin(); - let sig3 = tx.sign_no_append_origin(&initialSigHash, &privk_3).unwrap(); - let sig1 = tx.sign_no_append_origin(&initialSigHash, &privk_1).unwrap(); - let sig9 = tx.sign_no_append_origin(&initialSigHash, &privk_9).unwrap(); + let initial_sig_hash = tx.sign_begin(); + let sig3 = tx + .sign_no_append_origin(&initial_sig_hash, &privk_3) + .unwrap(); + let sig1 = tx + .sign_no_append_origin(&initial_sig_hash, &privk_1) + .unwrap(); + let sig9 = tx + .sign_no_append_origin(&initial_sig_hash, &privk_9) + .unwrap(); let _ = tx.append_origin_signature(sig1, TransactionPublicKeyEncoding::Compressed); let _ = tx.append_next_origin(&pubk_2); @@ -6539,12 +6555,12 @@ mod test { origin_tx.set_tx_fee(456); origin_tx.set_sponsor_nonce(789).unwrap(); - let initialSigHash = tx_signer.sighash; + let initial_sig_hash = tx_signer.sighash; let sig1 = origin_tx - .sign_no_append_sponsor(&initialSigHash, &privk_1) + .sign_no_append_sponsor(&initial_sig_hash, &privk_1) .unwrap(); let sig3 = origin_tx - .sign_no_append_sponsor(&initialSigHash, &privk_3) + .sign_no_append_sponsor(&initial_sig_hash, &privk_3) .unwrap(); let _ = @@ -6708,21 +6724,21 @@ mod test { origin_tx.set_tx_fee(456); origin_tx.set_sponsor_nonce(789).unwrap(); - let initialSigHash = tx_signer.sighash; + let initial_sig_hash = tx_signer.sighash; let sig1 = origin_tx - .sign_no_append_sponsor(&initialSigHash, &privk_1) + .sign_no_append_sponsor(&initial_sig_hash, &privk_1) .unwrap(); let sig3 = origin_tx - .sign_no_append_sponsor(&initialSigHash, &privk_3) + .sign_no_append_sponsor(&initial_sig_hash, &privk_3) .unwrap(); let sig2 = origin_tx - .sign_no_append_sponsor(&initialSigHash, &privk_2) + .sign_no_append_sponsor(&initial_sig_hash, &privk_2) .unwrap(); let sig4 = origin_tx - .sign_no_append_sponsor(&initialSigHash, &privk_4) + .sign_no_append_sponsor(&initial_sig_hash, &privk_4) .unwrap(); let sig5 = origin_tx - .sign_no_append_sponsor(&initialSigHash, &privk_5) + .sign_no_append_sponsor(&initial_sig_hash, &privk_5) .unwrap(); let _ = @@ -6852,9 +6868,13 @@ mod test { let tx_signer = StacksTransactionSigner::new(&tx); - let initialSigHash = tx.sign_begin(); - let sig3 = tx.sign_no_append_origin(&initialSigHash, &privk_3).unwrap(); - let sig1 = tx.sign_no_append_origin(&initialSigHash, &privk_1).unwrap(); + let initial_sig_hash = tx.sign_begin(); + let sig3 = tx + .sign_no_append_origin(&initial_sig_hash, &privk_3) + .unwrap(); + let sig1 = tx + .sign_no_append_origin(&initial_sig_hash, &privk_1) + .unwrap(); let _ = tx.append_origin_signature(sig1, TransactionPublicKeyEncoding::Compressed); let _ = tx.append_next_origin(&pubk_2); @@ -6961,11 +6981,19 @@ mod test { let tx_signer = StacksTransactionSigner::new(&tx); - let initialSigHash = tx.sign_begin(); - let sig3 = tx.sign_no_append_origin(&initialSigHash, &privk_3).unwrap(); - let sig1 = tx.sign_no_append_origin(&initialSigHash, &privk_1).unwrap(); - let sig6 = tx.sign_no_append_origin(&initialSigHash, &privk_6).unwrap(); - let sig5 = tx.sign_no_append_origin(&initialSigHash, &privk_5).unwrap(); + let initial_sig_hash = tx.sign_begin(); + let sig3 = tx + .sign_no_append_origin(&initial_sig_hash, &privk_3) + .unwrap(); + let sig1 = tx + .sign_no_append_origin(&initial_sig_hash, &privk_1) + .unwrap(); + let sig6 = tx + .sign_no_append_origin(&initial_sig_hash, &privk_6) + .unwrap(); + let sig5 = tx + .sign_no_append_origin(&initial_sig_hash, &privk_5) + .unwrap(); let _ = tx.append_origin_signature(sig1, TransactionPublicKeyEncoding::Compressed); let _ = tx.append_next_origin(&pubk_2); @@ -7101,12 +7129,12 @@ mod test { origin_tx.set_tx_fee(456); origin_tx.set_sponsor_nonce(789).unwrap(); - let initialSigHash = tx_signer.sighash; + let initial_sig_hash = tx_signer.sighash; let sig1 = origin_tx - .sign_no_append_sponsor(&initialSigHash, &privk_1) + .sign_no_append_sponsor(&initial_sig_hash, &privk_1) .unwrap(); let sig3 = origin_tx - .sign_no_append_sponsor(&initialSigHash, &privk_3) + .sign_no_append_sponsor(&initial_sig_hash, &privk_3) .unwrap(); let _ = @@ -7283,12 +7311,12 @@ mod test { origin_tx.set_tx_fee(456); origin_tx.set_sponsor_nonce(789).unwrap(); - let initialSigHash = tx_signer.sighash; + let initial_sig_hash = tx_signer.sighash; let sig1 = origin_tx - .sign_no_append_sponsor(&initialSigHash, &privk_1) + .sign_no_append_sponsor(&initial_sig_hash, &privk_1) .unwrap(); let sig7 = origin_tx - .sign_no_append_sponsor(&initialSigHash, &privk_7) + .sign_no_append_sponsor(&initial_sig_hash, &privk_7) .unwrap(); let _ = @@ -7369,4 +7397,1229 @@ mod test { test_signature_and_corruption(&origin_tx, false, true); } } + + #[test] + fn tx_stacks_transaction_sign_verify_standard_both_multisig_p2sh() { + let privk_1 = StacksPrivateKey::from_hex( + "6d430bb91222408e7706c9001cfaeb91b08c2be6d5ac95779ab52c6b431950e001", + ) + .unwrap(); + let privk_2 = StacksPrivateKey::from_hex( + "2a584d899fed1d24e26b524f202763c8ab30260167429f157f1c119f550fa6af01", + ) + .unwrap(); + let privk_3 = StacksPrivateKey::from_hex( + "d5200dee706ee53ae98a03fba6cf4fdcc5084c30cfa9e1b3462dcdeaa3e0f1d201", + ) + .unwrap(); + + let pubk_1 = StacksPublicKey::from_private(&privk_1); + let pubk_2 = StacksPublicKey::from_private(&privk_2); + let pubk_3 = StacksPublicKey::from_private(&privk_3); + + let origin_auth = TransactionAuth::Standard( + TransactionSpendingCondition::new_multisig_p2sh( + 2, + vec![pubk_1.clone(), pubk_2.clone(), pubk_3.clone()], + ) + .unwrap(), + ); + + let order_independent_origin_auth = TransactionAuth::Standard( + TransactionSpendingCondition::new_multisig_order_independent_p2sh( + 2, + vec![pubk_1.clone(), pubk_2.clone(), pubk_3.clone()], + ) + .unwrap(), + ); + + let origin_address = origin_auth.origin().address_mainnet(); + let order_independent_origin_address = + order_independent_origin_auth.origin().address_mainnet(); + + assert_eq!(origin_address, order_independent_origin_address); + assert_eq!( + origin_address, + StacksAddress { + version: C32_ADDRESS_VERSION_MAINNET_MULTISIG, + bytes: Hash160::from_hex("a23ea89d6529ac48ac766f720e480beec7f19273").unwrap(), + } + ); + + let txs = tx_stacks_transaction_test_txs(&origin_auth); + let order_independent_txs = tx_stacks_transaction_test_txs(&order_independent_origin_auth); + + assert_eq!(txs.len(), order_independent_txs.len()); + + for tx in txs { + assert_eq!(tx.auth().origin().num_signatures(), 0); + + let mut tx_signer = StacksTransactionSigner::new(&tx); + tx_signer.sign_origin(&privk_1).unwrap(); + tx_signer.sign_origin(&privk_2).unwrap(); + tx_signer.append_origin(&pubk_3).unwrap(); + let mut signed_tx = tx_signer.get_tx().unwrap(); + assert_eq!(signed_tx.auth().origin().num_signatures(), 2); + + check_oversign_origin_multisig(&mut signed_tx); + check_sign_no_sponsor(&mut signed_tx); + + // tx and signed_tx are otherwise equal + assert_eq!(tx.version, signed_tx.version); + assert_eq!(tx.get_tx_fee(), signed_tx.get_tx_fee()); + assert_eq!(tx.get_origin_nonce(), signed_tx.get_origin_nonce()); + assert_eq!(tx.get_sponsor_nonce(), signed_tx.get_sponsor_nonce()); + assert_eq!(tx.anchor_mode, signed_tx.anchor_mode); + assert_eq!(tx.post_condition_mode, signed_tx.post_condition_mode); + assert_eq!(tx.post_conditions, signed_tx.post_conditions); + assert_eq!(tx.payload, signed_tx.payload); + + match signed_tx.auth { + TransactionAuth::Standard(ref origin) => match origin { + TransactionSpendingCondition::Multisig(ref data) => { + assert_eq!(data.signer, origin_address.bytes); + assert_eq!(data.fields.len(), 3); + assert!(data.fields[0].is_signature()); + assert!(data.fields[1].is_signature()); + assert!(data.fields[2].is_public_key()); + + assert_eq!( + data.fields[0].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + assert_eq!( + data.fields[1].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + assert_eq!(data.fields[2].as_public_key().unwrap(), pubk_3); + } + _ => assert!(false), + }, + _ => assert!(false), + }; + + test_signature_and_corruption(&signed_tx, true, false); + } + + for mut order_independent_tx in order_independent_txs { + assert_eq!(order_independent_tx.auth().origin().num_signatures(), 0); + + let order_independent_initial_sig_hash = order_independent_tx.sign_begin(); + let sig3 = order_independent_tx + .sign_no_append_origin(&order_independent_initial_sig_hash, &privk_3) + .unwrap(); + let sig2 = order_independent_tx + .sign_no_append_origin(&order_independent_initial_sig_hash, &privk_2) + .unwrap(); + + let _ = order_independent_tx.append_next_origin(&pubk_1); + let _ = order_independent_tx + .append_origin_signature(sig2, TransactionPublicKeyEncoding::Compressed); + let _ = order_independent_tx + .append_origin_signature(sig3, TransactionPublicKeyEncoding::Compressed); + + check_oversign_origin_multisig(&mut order_independent_tx); + check_sign_no_sponsor(&mut order_independent_tx); + + assert_eq!(order_independent_tx.auth().origin().num_signatures(), 2); + + match order_independent_tx.auth { + TransactionAuth::Standard(ref origin) => match origin { + TransactionSpendingCondition::OrderIndependentMultisig(ref data) => { + assert_eq!(data.signer, origin_address.bytes); + assert_eq!(data.fields.len(), 3); + assert!(data.fields[0].is_public_key()); + assert!(data.fields[1].is_signature()); + assert!(data.fields[2].is_signature()); + + assert_eq!(data.fields[0].as_public_key().unwrap(), pubk_1); + assert_eq!( + data.fields[1].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + assert_eq!( + data.fields[2].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + } + _ => assert!(false), + }, + _ => assert!(false), + }; + + test_signature_and_corruption(&order_independent_tx, true, false); + } + } + + #[test] + fn tx_stacks_transaction_sign_verify_standard_both_multisig_p2sh_uncompressed() { + let privk_1 = StacksPrivateKey::from_hex( + "6d430bb91222408e7706c9001cfaeb91b08c2be6d5ac95779ab52c6b431950e0", + ) + .unwrap(); + let privk_2 = StacksPrivateKey::from_hex( + "2a584d899fed1d24e26b524f202763c8ab30260167429f157f1c119f550fa6af", + ) + .unwrap(); + let privk_3 = StacksPrivateKey::from_hex( + "d5200dee706ee53ae98a03fba6cf4fdcc5084c30cfa9e1b3462dcdeaa3e0f1d2", + ) + .unwrap(); + + let pubk_1 = StacksPublicKey::from_private(&privk_1); + let pubk_2 = StacksPublicKey::from_private(&privk_2); + let pubk_3 = StacksPublicKey::from_private(&privk_3); + + let origin_auth = TransactionAuth::Standard( + TransactionSpendingCondition::new_multisig_p2sh( + 2, + vec![pubk_1.clone(), pubk_2.clone(), pubk_3.clone()], + ) + .unwrap(), + ); + + let order_independent_origin_auth = TransactionAuth::Standard( + TransactionSpendingCondition::new_multisig_order_independent_p2sh( + 2, + vec![pubk_1.clone(), pubk_2.clone(), pubk_3.clone()], + ) + .unwrap(), + ); + + let origin_address = origin_auth.origin().address_mainnet(); + let order_independent_origin_address = + order_independent_origin_auth.origin().address_mainnet(); + assert_eq!(origin_address, order_independent_origin_address); + + assert_eq!( + origin_address, + StacksAddress { + version: C32_ADDRESS_VERSION_MAINNET_MULTISIG, + bytes: Hash160::from_hex("73a8b4a751a678fe83e9d35ce301371bb3d397f7").unwrap(), + } + ); + + let txs = tx_stacks_transaction_test_txs(&origin_auth); + let order_independent_txs = tx_stacks_transaction_test_txs(&order_independent_origin_auth); + + assert_eq!(txs.len(), order_independent_txs.len()); + + for tx in txs { + assert_eq!(tx.auth().origin().num_signatures(), 0); + + let mut tx_signer = StacksTransactionSigner::new(&tx); + + tx_signer.sign_origin(&privk_1).unwrap(); + tx_signer.sign_origin(&privk_2).unwrap(); + tx_signer.append_origin(&pubk_3).unwrap(); + + let mut signed_tx = tx_signer.get_tx().unwrap(); + + check_oversign_origin_multisig(&mut signed_tx); + check_sign_no_sponsor(&mut signed_tx); + + assert_eq!(signed_tx.auth().origin().num_signatures(), 2); + + // tx and signed_tx are otherwise equal + assert_eq!(tx.version, signed_tx.version); + assert_eq!(tx.chain_id, signed_tx.chain_id); + assert_eq!(tx.get_tx_fee(), signed_tx.get_tx_fee()); + assert_eq!(tx.get_origin_nonce(), signed_tx.get_origin_nonce()); + assert_eq!(tx.get_sponsor_nonce(), signed_tx.get_sponsor_nonce()); + assert_eq!(tx.anchor_mode, signed_tx.anchor_mode); + assert_eq!(tx.post_condition_mode, signed_tx.post_condition_mode); + assert_eq!(tx.post_conditions, signed_tx.post_conditions); + assert_eq!(tx.payload, signed_tx.payload); + + // auth is standard and first two auth fields are signatures for uncompressed keys. + // third field is the third public key + match signed_tx.auth { + TransactionAuth::Standard(ref origin) => match origin { + TransactionSpendingCondition::Multisig(ref data) => { + assert_eq!(data.signer, origin_address.bytes); + assert_eq!(data.fields.len(), 3); + assert!(data.fields[0].is_signature()); + assert!(data.fields[1].is_signature()); + assert!(data.fields[2].is_public_key()); + + assert_eq!( + data.fields[0].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Uncompressed + ); + assert_eq!( + data.fields[1].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Uncompressed + ); + assert_eq!(data.fields[2].as_public_key().unwrap(), pubk_3); + } + _ => assert!(false), + }, + _ => assert!(false), + }; + + test_signature_and_corruption(&signed_tx, true, false); + } + + for mut tx in order_independent_txs { + assert_eq!(tx.auth().origin().num_signatures(), 0); + + let tx_signer = StacksTransactionSigner::new(&tx); + + let initial_sig_hash = tx.sign_begin(); + let sig3 = tx + .sign_no_append_origin(&initial_sig_hash, &privk_3) + .unwrap(); + let sig2 = tx + .sign_no_append_origin(&initial_sig_hash, &privk_2) + .unwrap(); + + let _ = tx.append_next_origin(&pubk_1); + let _ = tx.append_origin_signature(sig2, TransactionPublicKeyEncoding::Uncompressed); + let _ = tx.append_origin_signature(sig3, TransactionPublicKeyEncoding::Uncompressed); + + check_oversign_origin_multisig(&mut tx); + check_sign_no_sponsor(&mut tx); + + assert_eq!(tx.auth().origin().num_signatures(), 2); + + // auth is standard and first two auth fields are signatures for compressed keys. + // third field is the third public key + match tx.auth { + TransactionAuth::Standard(ref origin) => match origin { + TransactionSpendingCondition::OrderIndependentMultisig(ref data) => { + assert_eq!(data.signer, origin_address.bytes); + assert_eq!(data.fields.len(), 3); + assert!(data.fields[0].is_public_key()); + assert!(data.fields[1].is_signature()); + assert!(data.fields[2].is_signature()); + + assert_eq!(data.fields[0].as_public_key().unwrap(), pubk_1); + assert_eq!( + data.fields[1].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Uncompressed + ); + assert_eq!( + data.fields[2].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Uncompressed + ); + } + _ => assert!(false), + }, + _ => assert!(false), + }; + + test_signature_and_corruption(&tx, true, false); + } + } + + #[test] + fn tx_stacks_transaction_sign_verify_standard_both_multisig_p2wsh() { + let privk_1 = StacksPrivateKey::from_hex( + "6d430bb91222408e7706c9001cfaeb91b08c2be6d5ac95779ab52c6b431950e001", + ) + .unwrap(); + let privk_2 = StacksPrivateKey::from_hex( + "2a584d899fed1d24e26b524f202763c8ab30260167429f157f1c119f550fa6af01", + ) + .unwrap(); + let privk_3 = StacksPrivateKey::from_hex( + "d5200dee706ee53ae98a03fba6cf4fdcc5084c30cfa9e1b3462dcdeaa3e0f1d201", + ) + .unwrap(); + + let pubk_1 = StacksPublicKey::from_private(&privk_1); + let pubk_2 = StacksPublicKey::from_private(&privk_2); + let pubk_3 = StacksPublicKey::from_private(&privk_3); + + let origin_auth = TransactionAuth::Standard( + TransactionSpendingCondition::new_multisig_p2wsh( + 2, + vec![pubk_1.clone(), pubk_2.clone(), pubk_3.clone()], + ) + .unwrap(), + ); + + let order_independent_origin_auth = TransactionAuth::Standard( + TransactionSpendingCondition::new_multisig_order_independent_p2wsh( + 2, + vec![pubk_1.clone(), pubk_2.clone(), pubk_3.clone()], + ) + .unwrap(), + ); + + let origin_address = origin_auth.origin().address_mainnet(); + let order_independent_origin_address = + order_independent_origin_auth.origin().address_mainnet(); + assert_eq!(origin_address, order_independent_origin_address); + + assert_eq!( + origin_address, + StacksAddress { + version: C32_ADDRESS_VERSION_MAINNET_MULTISIG, + bytes: Hash160::from_hex("f5cfb61a07fb41a32197da01ce033888f0fe94a7").unwrap(), + } + ); + + let txs = tx_stacks_transaction_test_txs(&origin_auth); + let order_independent_txs = tx_stacks_transaction_test_txs(&order_independent_origin_auth); + + for tx in txs { + assert_eq!(tx.auth().origin().num_signatures(), 0); + + let mut tx_signer = StacksTransactionSigner::new(&tx); + tx_signer.sign_origin(&privk_1).unwrap(); + tx_signer.sign_origin(&privk_2).unwrap(); + tx_signer.append_origin(&pubk_3).unwrap(); + let mut signed_tx = tx_signer.get_tx().unwrap(); + + check_oversign_origin_multisig(&mut signed_tx); + check_oversign_origin_multisig_uncompressed(&mut signed_tx); + check_sign_no_sponsor(&mut signed_tx); + + assert_eq!(signed_tx.auth().origin().num_signatures(), 2); + + // tx and signed_tx are otherwise equal + assert_eq!(tx.version, signed_tx.version); + assert_eq!(tx.get_tx_fee(), signed_tx.get_tx_fee()); + assert_eq!(tx.get_origin_nonce(), signed_tx.get_origin_nonce()); + assert_eq!(tx.get_sponsor_nonce(), signed_tx.get_sponsor_nonce()); + assert_eq!(tx.anchor_mode, signed_tx.anchor_mode); + assert_eq!(tx.post_condition_mode, signed_tx.post_condition_mode); + assert_eq!(tx.post_conditions, signed_tx.post_conditions); + assert_eq!(tx.payload, signed_tx.payload); + + // auth is standard and first two auth fields are signatures for compressed keys. + // third field is the third public key + match signed_tx.auth { + TransactionAuth::Standard(ref origin) => match origin { + TransactionSpendingCondition::Multisig(ref data) => { + assert_eq!(data.signer, origin_address.bytes); + assert_eq!(data.fields.len(), 3); + assert!(data.fields[0].is_signature()); + assert!(data.fields[1].is_signature()); + assert!(data.fields[2].is_public_key()); + + assert_eq!( + data.fields[0].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + assert_eq!( + data.fields[1].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + assert_eq!(data.fields[2].as_public_key().unwrap(), pubk_3); + } + _ => assert!(false), + }, + _ => assert!(false), + }; + + test_signature_and_corruption(&signed_tx, true, false); + } + + for mut tx in order_independent_txs { + assert_eq!(tx.auth().origin().num_signatures(), 0); + + let tx_signer = StacksTransactionSigner::new(&tx); + + let initial_sig_hash = tx.sign_begin(); + let sig3 = tx + .sign_no_append_origin(&initial_sig_hash, &privk_3) + .unwrap(); + let sig1 = tx + .sign_no_append_origin(&initial_sig_hash, &privk_1) + .unwrap(); + + let _ = tx.append_origin_signature(sig1, TransactionPublicKeyEncoding::Compressed); + let _ = tx.append_next_origin(&pubk_2); + let _ = tx.append_origin_signature(sig3, TransactionPublicKeyEncoding::Compressed); + + check_oversign_origin_multisig(&mut tx); + check_oversign_origin_multisig_uncompressed(&mut tx); + check_sign_no_sponsor(&mut tx); + + assert_eq!(tx.auth().origin().num_signatures(), 2); + + // auth is standard and first two auth fields are signatures for compressed keys. + // third field is the third public key + match tx.auth { + TransactionAuth::Standard(ref origin) => match origin { + TransactionSpendingCondition::OrderIndependentMultisig(ref data) => { + assert_eq!(data.signer, origin_address.bytes); + assert_eq!(data.fields.len(), 3); + assert!(data.fields[0].is_signature()); + assert!(data.fields[1].is_public_key()); + assert!(data.fields[2].is_signature()); + + assert_eq!(data.fields[1].as_public_key().unwrap(), pubk_2); + assert_eq!( + data.fields[0].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + assert_eq!( + data.fields[2].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + } + _ => assert!(false), + }, + _ => assert!(false), + }; + + test_signature_and_corruption(&tx, true, false); + } + } + + #[test] + fn tx_stacks_transaction_sign_verify_sponsored_both_multisig_p2sh() { + let origin_privk = StacksPrivateKey::from_hex( + "807bbe9e471ac976592cc35e3056592ecc0f778ee653fced3b491a122dd8d59701", + ) + .unwrap(); + + let privk_1 = StacksPrivateKey::from_hex( + "6d430bb91222408e7706c9001cfaeb91b08c2be6d5ac95779ab52c6b431950e001", + ) + .unwrap(); + let privk_2 = StacksPrivateKey::from_hex( + "2a584d899fed1d24e26b524f202763c8ab30260167429f157f1c119f550fa6af01", + ) + .unwrap(); + let privk_3 = StacksPrivateKey::from_hex( + "d5200dee706ee53ae98a03fba6cf4fdcc5084c30cfa9e1b3462dcdeaa3e0f1d201", + ) + .unwrap(); + + let pubk_1 = StacksPublicKey::from_private(&privk_1); + let pubk_2 = StacksPublicKey::from_private(&privk_2); + let pubk_3 = StacksPublicKey::from_private(&privk_3); + + let random_sponsor = StacksPrivateKey::new(); // what the origin sees + + let auth = TransactionAuth::Sponsored( + TransactionSpendingCondition::new_singlesig_p2pkh(StacksPublicKey::from_private( + &origin_privk, + )) + .unwrap(), + TransactionSpendingCondition::new_singlesig_p2pkh(StacksPublicKey::from_private( + &random_sponsor, + )) + .unwrap(), + ); + + let real_sponsor = TransactionSpendingCondition::new_multisig_p2sh( + 2, + vec![pubk_1.clone(), pubk_2.clone(), pubk_3.clone()], + ) + .unwrap(); + + let real_order_independent_sponsor = + TransactionSpendingCondition::new_multisig_order_independent_p2sh( + 2, + vec![pubk_1.clone(), pubk_2.clone(), pubk_3.clone()], + ) + .unwrap(); + + let origin_address = auth.origin().address_mainnet(); + let sponsor_address = real_sponsor.address_mainnet(); + let order_independent_sponsor_address = real_order_independent_sponsor.address_mainnet(); + + assert_eq!( + origin_address, + StacksAddress { + version: C32_ADDRESS_VERSION_MAINNET_SINGLESIG, + bytes: Hash160::from_hex("3597aaa4bde720be93e3829aae24e76e7fcdfd3e").unwrap(), + } + ); + assert_eq!(sponsor_address, order_independent_sponsor_address); + assert_eq!( + sponsor_address, + StacksAddress { + version: C32_ADDRESS_VERSION_MAINNET_MULTISIG, + bytes: Hash160::from_hex("a23ea89d6529ac48ac766f720e480beec7f19273").unwrap(), + } + ); + + let txs = tx_stacks_transaction_test_txs(&auth); + let order_independent_txs = tx_stacks_transaction_test_txs(&auth); // no difference + + for mut tx in txs { + assert_eq!(tx.auth().origin().num_signatures(), 0); + assert_eq!(tx.auth().sponsor().unwrap().num_signatures(), 0); + + tx.set_tx_fee(123); + tx.set_sponsor_nonce(456).unwrap(); + let mut tx_signer = StacksTransactionSigner::new(&tx); + + tx_signer.sign_origin(&origin_privk).unwrap(); + + // sponsor sets and pays fee after origin signs + let mut origin_tx = tx_signer.get_tx_incomplete(); + origin_tx.auth.set_sponsor(real_sponsor.clone()).unwrap(); + origin_tx.set_tx_fee(456); + origin_tx.set_sponsor_nonce(789).unwrap(); + tx_signer.resume(&origin_tx); + + tx_signer.sign_sponsor(&privk_1).unwrap(); + tx_signer.sign_sponsor(&privk_2).unwrap(); + tx_signer.append_sponsor(&pubk_3).unwrap(); + + tx.set_tx_fee(456); + tx.set_sponsor_nonce(789).unwrap(); + let mut signed_tx = tx_signer.get_tx().unwrap(); + + check_oversign_origin_singlesig(&mut signed_tx); + check_oversign_sponsor_multisig(&mut signed_tx); + + assert_eq!(signed_tx.auth().origin().num_signatures(), 1); + assert_eq!(signed_tx.auth().sponsor().unwrap().num_signatures(), 2); + + // tx and signed_tx are otherwise equal + assert_eq!(tx.version, signed_tx.version); + assert_eq!(tx.chain_id, signed_tx.chain_id); + assert_eq!(tx.get_tx_fee(), signed_tx.get_tx_fee()); + assert_eq!(tx.get_origin_nonce(), signed_tx.get_origin_nonce()); + assert_eq!(tx.get_sponsor_nonce(), signed_tx.get_sponsor_nonce()); + assert_eq!(tx.anchor_mode, signed_tx.anchor_mode); + assert_eq!(tx.post_condition_mode, signed_tx.post_condition_mode); + assert_eq!(tx.post_conditions, signed_tx.post_conditions); + assert_eq!(tx.payload, signed_tx.payload); + + // auth is standard and first two auth fields are signatures for compressed keys. + // third field is the third public key + match signed_tx.auth { + TransactionAuth::Sponsored(ref origin, ref sponsor) => { + match origin { + TransactionSpendingCondition::Singlesig(ref data) => { + assert_eq!(data.key_encoding, TransactionPublicKeyEncoding::Compressed); + assert_eq!(data.signer, origin_address.bytes); + } + _ => assert!(false), + } + match sponsor { + TransactionSpendingCondition::Multisig(ref data) => { + assert_eq!(data.signer, sponsor_address.bytes); + assert_eq!(data.fields.len(), 3); + assert!(data.fields[0].is_signature()); + assert!(data.fields[1].is_signature()); + assert!(data.fields[2].is_public_key()); + + assert_eq!( + data.fields[0].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + assert_eq!( + data.fields[1].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + assert_eq!(data.fields[2].as_public_key().unwrap(), pubk_3); + } + _ => assert!(false), + } + } + _ => assert!(false), + }; + + test_signature_and_corruption(&signed_tx, true, false); + test_signature_and_corruption(&signed_tx, false, true); + } + + for mut tx in order_independent_txs { + assert_eq!(tx.auth().origin().num_signatures(), 0); + assert_eq!(tx.auth().sponsor().unwrap().num_signatures(), 0); + + tx.set_tx_fee(123); + tx.set_sponsor_nonce(456).unwrap(); + let mut tx_signer = StacksTransactionSigner::new(&tx); + + tx_signer.sign_origin(&origin_privk).unwrap(); + + // sponsor sets and pays fee after origin signs + let mut origin_tx = tx_signer.get_tx_incomplete(); + origin_tx + .auth + .set_sponsor(real_order_independent_sponsor.clone()) + .unwrap(); + origin_tx.set_tx_fee(456); + origin_tx.set_sponsor_nonce(789).unwrap(); + + let initial_sig_hash = tx_signer.sighash; + let sig1 = origin_tx + .sign_no_append_sponsor(&initial_sig_hash, &privk_1) + .unwrap(); + let sig2 = origin_tx + .sign_no_append_sponsor(&initial_sig_hash, &privk_2) + .unwrap(); + + let _ = + origin_tx.append_sponsor_signature(sig1, TransactionPublicKeyEncoding::Compressed); + let _ = + origin_tx.append_sponsor_signature(sig2, TransactionPublicKeyEncoding::Compressed); + let _ = origin_tx.append_next_sponsor(&pubk_3); + + tx.set_tx_fee(456); + tx.set_sponsor_nonce(789).unwrap(); + + check_oversign_origin_singlesig(&mut origin_tx); + check_oversign_sponsor_multisig(&mut origin_tx); + + assert_eq!(origin_tx.auth().origin().num_signatures(), 1); + assert_eq!(origin_tx.auth().sponsor().unwrap().num_signatures(), 2); + + // tx and origin_tx are otherwise equal + assert_eq!(tx.version, origin_tx.version); + assert_eq!(tx.chain_id, origin_tx.chain_id); + assert_eq!(tx.get_tx_fee(), origin_tx.get_tx_fee()); + assert_eq!(tx.get_origin_nonce(), origin_tx.get_origin_nonce()); + assert_eq!(tx.get_sponsor_nonce(), origin_tx.get_sponsor_nonce()); + assert_eq!(tx.anchor_mode, origin_tx.anchor_mode); + assert_eq!(tx.post_condition_mode, origin_tx.post_condition_mode); + assert_eq!(tx.post_conditions, origin_tx.post_conditions); + assert_eq!(tx.payload, origin_tx.payload); + + // auth is standard and first two auth fields are signatures for compressed keys. + // third field is the third public key + match origin_tx.auth { + TransactionAuth::Sponsored(ref origin, ref sponsor) => { + match origin { + TransactionSpendingCondition::Singlesig(ref data) => { + assert_eq!(data.key_encoding, TransactionPublicKeyEncoding::Compressed); + assert_eq!(data.signer, origin_address.bytes); + } + _ => assert!(false), + } + match sponsor { + TransactionSpendingCondition::OrderIndependentMultisig(ref data) => { + assert_eq!(data.signer, sponsor_address.bytes); + assert_eq!(data.fields.len(), 3); + assert!(data.fields[0].is_signature()); + assert!(data.fields[1].is_signature()); + assert!(data.fields[2].is_public_key()); + + assert_eq!( + data.fields[0].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + assert_eq!( + data.fields[1].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + assert_eq!(data.fields[2].as_public_key().unwrap(), pubk_3); + } + _ => assert!(false), + } + } + _ => assert!(false), + }; + + test_signature_and_corruption(&origin_tx, true, false); + test_signature_and_corruption(&origin_tx, false, true); + } + } + + #[test] + fn tx_stacks_transaction_sign_verify_sponsored_both_multisig_p2sh_uncompressed() { + let origin_privk = StacksPrivateKey::from_hex( + "807bbe9e471ac976592cc35e3056592ecc0f778ee653fced3b491a122dd8d59701", + ) + .unwrap(); + + let privk_1 = StacksPrivateKey::from_hex( + "6d430bb91222408e7706c9001cfaeb91b08c2be6d5ac95779ab52c6b431950e0", + ) + .unwrap(); + let privk_2 = StacksPrivateKey::from_hex( + "2a584d899fed1d24e26b524f202763c8ab30260167429f157f1c119f550fa6af", + ) + .unwrap(); + let privk_3 = StacksPrivateKey::from_hex( + "d5200dee706ee53ae98a03fba6cf4fdcc5084c30cfa9e1b3462dcdeaa3e0f1d2", + ) + .unwrap(); + + let pubk_1 = StacksPublicKey::from_private(&privk_1); + let pubk_2 = StacksPublicKey::from_private(&privk_2); + let pubk_3 = StacksPublicKey::from_private(&privk_3); + + let random_sponsor = StacksPrivateKey::new(); // what the origin sees + + let auth = TransactionAuth::Sponsored( + TransactionSpendingCondition::new_singlesig_p2pkh(StacksPublicKey::from_private( + &origin_privk, + )) + .unwrap(), + TransactionSpendingCondition::new_singlesig_p2pkh(StacksPublicKey::from_private( + &random_sponsor, + )) + .unwrap(), + ); + + let real_sponsor = TransactionSpendingCondition::new_multisig_order_independent_p2sh( + 2, + vec![pubk_1.clone(), pubk_2.clone(), pubk_3.clone()], + ) + .unwrap(); + + let real_order_independent_sponsor = + TransactionSpendingCondition::new_multisig_order_independent_p2sh( + 2, + vec![pubk_1.clone(), pubk_2.clone(), pubk_3.clone()], + ) + .unwrap(); + + let origin_address = auth.origin().address_mainnet(); + let sponsor_address = real_sponsor.address_mainnet(); + let order_independent_sponsor_address = real_order_independent_sponsor.address_mainnet(); + + assert_eq!( + origin_address, + StacksAddress { + version: C32_ADDRESS_VERSION_MAINNET_SINGLESIG, + bytes: Hash160::from_hex("3597aaa4bde720be93e3829aae24e76e7fcdfd3e").unwrap(), + } + ); + assert_eq!(sponsor_address, order_independent_sponsor_address); + + assert_eq!( + sponsor_address, + StacksAddress { + version: C32_ADDRESS_VERSION_MAINNET_MULTISIG, + bytes: Hash160::from_hex("73a8b4a751a678fe83e9d35ce301371bb3d397f7").unwrap(), + } + ); + + let txs = tx_stacks_transaction_test_txs(&auth); + let order_independent_txs = tx_stacks_transaction_test_txs(&auth); // no difference + + for mut tx in txs { + assert_eq!(tx.auth().origin().num_signatures(), 0); + assert_eq!(tx.auth().sponsor().unwrap().num_signatures(), 0); + + tx.set_tx_fee(123); + tx.set_sponsor_nonce(456).unwrap(); + let mut tx_signer = StacksTransactionSigner::new(&tx); + + tx_signer.sign_origin(&origin_privk).unwrap(); + + // sponsor sets and pays fee after origin signs + let mut origin_tx = tx_signer.get_tx_incomplete(); + origin_tx.auth.set_sponsor(real_sponsor.clone()).unwrap(); + origin_tx.set_tx_fee(456); + origin_tx.set_sponsor_nonce(789).unwrap(); + + let initial_sig_hash = tx_signer.sighash; + let sig1 = origin_tx + .sign_no_append_sponsor(&initial_sig_hash, &privk_1) + .unwrap(); + let sig2 = origin_tx + .sign_no_append_sponsor(&initial_sig_hash, &privk_2) + .unwrap(); + + let _ = origin_tx + .append_sponsor_signature(sig1, TransactionPublicKeyEncoding::Uncompressed); + let _ = origin_tx + .append_sponsor_signature(sig2, TransactionPublicKeyEncoding::Uncompressed); + let _ = origin_tx.append_next_sponsor(&pubk_3); + + tx.set_tx_fee(456); + tx.set_sponsor_nonce(789).unwrap(); + + check_oversign_origin_singlesig(&mut origin_tx); + check_oversign_sponsor_multisig(&mut origin_tx); + + assert_eq!(origin_tx.auth().origin().num_signatures(), 1); + assert_eq!(origin_tx.auth().sponsor().unwrap().num_signatures(), 2); + + // tx and origin_tx are otherwise equal + assert_eq!(tx.version, origin_tx.version); + assert_eq!(tx.chain_id, origin_tx.chain_id); + assert_eq!(tx.get_tx_fee(), origin_tx.get_tx_fee()); + assert_eq!(tx.get_origin_nonce(), origin_tx.get_origin_nonce()); + assert_eq!(tx.get_sponsor_nonce(), origin_tx.get_sponsor_nonce()); + assert_eq!(tx.anchor_mode, origin_tx.anchor_mode); + assert_eq!(tx.post_condition_mode, origin_tx.post_condition_mode); + assert_eq!(tx.post_conditions, origin_tx.post_conditions); + assert_eq!(tx.payload, origin_tx.payload); + + // auth is standard and first two auth fields are signatures for compressed keys. + // third field is the third public key + match origin_tx.auth { + TransactionAuth::Sponsored(ref origin, ref sponsor) => { + match origin { + TransactionSpendingCondition::Singlesig(ref data) => { + assert_eq!(data.key_encoding, TransactionPublicKeyEncoding::Compressed); + assert_eq!(data.signer, origin_address.bytes); + } + _ => assert!(false), + } + match sponsor { + TransactionSpendingCondition::OrderIndependentMultisig(ref data) => { + assert_eq!(data.signer, sponsor_address.bytes); + assert_eq!(data.fields.len(), 3); + assert!(data.fields[0].is_signature()); + assert!(data.fields[1].is_signature()); + assert!(data.fields[2].is_public_key()); + + assert_eq!( + data.fields[0].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Uncompressed + ); + assert_eq!( + data.fields[1].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Uncompressed + ); + assert_eq!(data.fields[2].as_public_key().unwrap(), pubk_3); + } + _ => assert!(false), + } + } + _ => assert!(false), + }; + + test_signature_and_corruption(&origin_tx, true, false); + test_signature_and_corruption(&origin_tx, false, true); + } + + for mut tx in order_independent_txs { + assert_eq!(tx.auth().origin().num_signatures(), 0); + assert_eq!(tx.auth().sponsor().unwrap().num_signatures(), 0); + + tx.set_tx_fee(123); + tx.set_sponsor_nonce(456).unwrap(); + let mut tx_signer = StacksTransactionSigner::new(&tx); + + tx_signer.sign_origin(&origin_privk).unwrap(); + + // sponsor sets and pays fee after origin signs + let mut origin_tx = tx_signer.get_tx_incomplete(); + origin_tx + .auth + .set_sponsor(real_order_independent_sponsor.clone()) + .unwrap(); + origin_tx.set_tx_fee(456); + origin_tx.set_sponsor_nonce(789).unwrap(); + + let initial_sig_hash = tx_signer.sighash; + let sig1 = origin_tx + .sign_no_append_sponsor(&initial_sig_hash, &privk_1) + .unwrap(); + let sig2 = origin_tx + .sign_no_append_sponsor(&initial_sig_hash, &privk_2) + .unwrap(); + + let _ = origin_tx + .append_sponsor_signature(sig1, TransactionPublicKeyEncoding::Uncompressed); + let _ = origin_tx + .append_sponsor_signature(sig2, TransactionPublicKeyEncoding::Uncompressed); + let _ = origin_tx.append_next_sponsor(&pubk_3); + + tx.set_tx_fee(456); + tx.set_sponsor_nonce(789).unwrap(); + + check_oversign_origin_singlesig(&mut origin_tx); + check_oversign_sponsor_multisig(&mut origin_tx); + + assert_eq!(origin_tx.auth().origin().num_signatures(), 1); + assert_eq!(origin_tx.auth().sponsor().unwrap().num_signatures(), 2); + + // tx and origin_tx are otherwise equal + assert_eq!(tx.version, origin_tx.version); + assert_eq!(tx.chain_id, origin_tx.chain_id); + assert_eq!(tx.get_tx_fee(), origin_tx.get_tx_fee()); + assert_eq!(tx.get_origin_nonce(), origin_tx.get_origin_nonce()); + assert_eq!(tx.get_sponsor_nonce(), origin_tx.get_sponsor_nonce()); + assert_eq!(tx.anchor_mode, origin_tx.anchor_mode); + assert_eq!(tx.post_condition_mode, origin_tx.post_condition_mode); + assert_eq!(tx.post_conditions, origin_tx.post_conditions); + assert_eq!(tx.payload, origin_tx.payload); + + // auth is standard and first two auth fields are signatures for compressed keys. + // third field is the third public key + match origin_tx.auth { + TransactionAuth::Sponsored(ref origin, ref sponsor) => { + match origin { + TransactionSpendingCondition::Singlesig(ref data) => { + assert_eq!(data.key_encoding, TransactionPublicKeyEncoding::Compressed); + assert_eq!(data.signer, origin_address.bytes); + } + _ => assert!(false), + } + match sponsor { + TransactionSpendingCondition::OrderIndependentMultisig(ref data) => { + assert_eq!(data.signer, sponsor_address.bytes); + assert_eq!(data.fields.len(), 3); + assert!(data.fields[0].is_signature()); + assert!(data.fields[1].is_signature()); + assert!(data.fields[2].is_public_key()); + + assert_eq!( + data.fields[0].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Uncompressed + ); + assert_eq!( + data.fields[1].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Uncompressed + ); + assert_eq!(data.fields[2].as_public_key().unwrap(), pubk_3); + } + _ => assert!(false), + } + } + _ => assert!(false), + }; + + test_signature_and_corruption(&origin_tx, true, false); + test_signature_and_corruption(&origin_tx, false, true); + } + } + + #[test] + fn tx_stacks_transaction_sign_verify_sponsored_both_multisig_p2wsh() { + let origin_privk = StacksPrivateKey::from_hex( + "807bbe9e471ac976592cc35e3056592ecc0f778ee653fced3b491a122dd8d59701", + ) + .unwrap(); + + let privk_1 = StacksPrivateKey::from_hex( + "6d430bb91222408e7706c9001cfaeb91b08c2be6d5ac95779ab52c6b431950e001", + ) + .unwrap(); + let privk_2 = StacksPrivateKey::from_hex( + "2a584d899fed1d24e26b524f202763c8ab30260167429f157f1c119f550fa6af01", + ) + .unwrap(); + let privk_3 = StacksPrivateKey::from_hex( + "d5200dee706ee53ae98a03fba6cf4fdcc5084c30cfa9e1b3462dcdeaa3e0f1d201", + ) + .unwrap(); + + let pubk_1 = StacksPublicKey::from_private(&privk_1); + let pubk_2 = StacksPublicKey::from_private(&privk_2); + let pubk_3 = StacksPublicKey::from_private(&privk_3); + + let random_sponsor = StacksPrivateKey::new(); // what the origin sees + + let auth = TransactionAuth::Sponsored( + TransactionSpendingCondition::new_singlesig_p2pkh(StacksPublicKey::from_private( + &origin_privk, + )) + .unwrap(), + TransactionSpendingCondition::new_singlesig_p2pkh(StacksPublicKey::from_private( + &random_sponsor, + )) + .unwrap(), + ); + + let real_sponsor = TransactionSpendingCondition::new_multisig_p2wsh( + 2, + vec![pubk_1.clone(), pubk_2.clone(), pubk_3.clone()], + ) + .unwrap(); + + let real_order_independent_sponsor = + TransactionSpendingCondition::new_multisig_order_independent_p2wsh( + 2, + vec![pubk_1.clone(), pubk_2.clone(), pubk_3.clone()], + ) + .unwrap(); + + let origin_address = auth.origin().address_mainnet(); + let sponsor_address = real_sponsor.address_mainnet(); + let order_independent_sponsor_address = real_order_independent_sponsor.address_mainnet(); + + assert_eq!( + origin_address, + StacksAddress { + version: C32_ADDRESS_VERSION_MAINNET_SINGLESIG, + bytes: Hash160::from_hex("3597aaa4bde720be93e3829aae24e76e7fcdfd3e").unwrap(), + } + ); + assert_eq!(sponsor_address, order_independent_sponsor_address); + + assert_eq!( + sponsor_address, + StacksAddress { + version: C32_ADDRESS_VERSION_MAINNET_MULTISIG, + bytes: Hash160::from_hex("f5cfb61a07fb41a32197da01ce033888f0fe94a7").unwrap(), + } + ); + + let txs = tx_stacks_transaction_test_txs(&auth); + let order_independent_txs = tx_stacks_transaction_test_txs(&auth); // no difference + + for mut tx in txs { + assert_eq!(tx.auth().origin().num_signatures(), 0); + assert_eq!(tx.auth().sponsor().unwrap().num_signatures(), 0); + + tx.set_tx_fee(123); + tx.set_sponsor_nonce(456).unwrap(); + let mut tx_signer = StacksTransactionSigner::new(&tx); + + tx_signer.sign_origin(&origin_privk).unwrap(); + + // sponsor sets and pays fee after origin signs + let mut origin_tx = tx_signer.get_tx_incomplete(); + origin_tx.auth.set_sponsor(real_sponsor.clone()).unwrap(); + origin_tx.set_tx_fee(456); + origin_tx.set_sponsor_nonce(789).unwrap(); + tx_signer.resume(&origin_tx); + + tx_signer.sign_sponsor(&privk_1).unwrap(); + tx_signer.sign_sponsor(&privk_2).unwrap(); + tx_signer.append_sponsor(&pubk_3).unwrap(); + + tx.set_tx_fee(456); + tx.set_sponsor_nonce(789).unwrap(); + let mut signed_tx = tx_signer.get_tx().unwrap(); + + check_oversign_origin_singlesig(&mut signed_tx); + check_oversign_sponsor_multisig(&mut signed_tx); + check_oversign_sponsor_multisig_uncompressed(&mut signed_tx); + + assert_eq!(signed_tx.auth().origin().num_signatures(), 1); + assert_eq!(signed_tx.auth().sponsor().unwrap().num_signatures(), 2); + + // tx and signed_tx are otherwise equal + assert_eq!(tx.version, signed_tx.version); + assert_eq!(tx.chain_id, signed_tx.chain_id); + assert_eq!(tx.get_tx_fee(), signed_tx.get_tx_fee()); + assert_eq!(tx.get_origin_nonce(), signed_tx.get_origin_nonce()); + assert_eq!(tx.get_sponsor_nonce(), signed_tx.get_sponsor_nonce()); + assert_eq!(tx.anchor_mode, signed_tx.anchor_mode); + assert_eq!(tx.post_condition_mode, signed_tx.post_condition_mode); + assert_eq!(tx.post_conditions, signed_tx.post_conditions); + assert_eq!(tx.payload, signed_tx.payload); + + // auth is standard and first two auth fields are signatures for compressed keys. + // third field is the third public key + match signed_tx.auth { + TransactionAuth::Sponsored(ref origin, ref sponsor) => { + match origin { + TransactionSpendingCondition::Singlesig(ref data) => { + assert_eq!(data.key_encoding, TransactionPublicKeyEncoding::Compressed); + assert_eq!(data.signer, origin_address.bytes); + } + _ => assert!(false), + } + match sponsor { + TransactionSpendingCondition::Multisig(ref data) => { + assert_eq!(data.signer, sponsor_address.bytes); + assert_eq!(data.fields.len(), 3); + assert!(data.fields[0].is_signature()); + assert!(data.fields[1].is_signature()); + assert!(data.fields[2].is_public_key()); + + assert_eq!( + data.fields[0].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + assert_eq!( + data.fields[1].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + assert_eq!(data.fields[2].as_public_key().unwrap(), pubk_3); + } + _ => assert!(false), + } + } + _ => assert!(false), + }; + + test_signature_and_corruption(&signed_tx, true, false); + test_signature_and_corruption(&signed_tx, false, true); + } + + for mut tx in order_independent_txs { + assert_eq!(tx.auth().origin().num_signatures(), 0); + assert_eq!(tx.auth().sponsor().unwrap().num_signatures(), 0); + + tx.set_tx_fee(123); + tx.set_sponsor_nonce(456).unwrap(); + let mut tx_signer = StacksTransactionSigner::new(&tx); + + tx_signer.sign_origin(&origin_privk).unwrap(); + + // sponsor sets and pays fee after origin signs + let mut origin_tx = tx_signer.get_tx_incomplete(); + origin_tx + .auth + .set_sponsor(real_order_independent_sponsor.clone()) + .unwrap(); + origin_tx.set_tx_fee(456); + origin_tx.set_sponsor_nonce(789).unwrap(); + + let initial_sig_hash = tx_signer.sighash; + let sig1 = origin_tx + .sign_no_append_sponsor(&initial_sig_hash, &privk_1) + .unwrap(); + let sig3 = origin_tx + .sign_no_append_sponsor(&initial_sig_hash, &privk_3) + .unwrap(); + + let _ = + origin_tx.append_sponsor_signature(sig1, TransactionPublicKeyEncoding::Compressed); + let _ = origin_tx.append_next_sponsor(&pubk_2); + let _ = + origin_tx.append_sponsor_signature(sig3, TransactionPublicKeyEncoding::Compressed); + + tx.set_tx_fee(456); + tx.set_sponsor_nonce(789).unwrap(); + + check_oversign_origin_singlesig(&mut origin_tx); + check_oversign_sponsor_multisig(&mut origin_tx); + check_oversign_sponsor_multisig_uncompressed(&mut origin_tx); + + assert_eq!(origin_tx.auth().origin().num_signatures(), 1); + assert_eq!(origin_tx.auth().sponsor().unwrap().num_signatures(), 2); + + // tx and origin_tx are otherwise equal + assert_eq!(tx.version, origin_tx.version); + assert_eq!(tx.chain_id, origin_tx.chain_id); + assert_eq!(tx.get_tx_fee(), origin_tx.get_tx_fee()); + assert_eq!(tx.get_origin_nonce(), origin_tx.get_origin_nonce()); + assert_eq!(tx.get_sponsor_nonce(), origin_tx.get_sponsor_nonce()); + assert_eq!(tx.anchor_mode, origin_tx.anchor_mode); + assert_eq!(tx.post_condition_mode, origin_tx.post_condition_mode); + assert_eq!(tx.post_conditions, origin_tx.post_conditions); + assert_eq!(tx.payload, origin_tx.payload); + + // auth is standard and first two auth fields are signatures for compressed keys. + // third field is the third public key + match origin_tx.auth { + TransactionAuth::Sponsored(ref origin, ref sponsor) => { + match origin { + TransactionSpendingCondition::Singlesig(ref data) => { + assert_eq!(data.key_encoding, TransactionPublicKeyEncoding::Compressed); + assert_eq!(data.signer, origin_address.bytes); + } + _ => assert!(false), + } + match sponsor { + TransactionSpendingCondition::OrderIndependentMultisig(ref data) => { + assert_eq!(data.signer, sponsor_address.bytes); + assert_eq!(data.fields.len(), 3); + assert!(data.fields[0].is_signature()); + assert!(data.fields[1].is_public_key()); + assert!(data.fields[2].is_signature()); + + assert_eq!( + data.fields[0].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + assert_eq!( + data.fields[2].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + assert_eq!(data.fields[1].as_public_key().unwrap(), pubk_2); + } + _ => assert!(false), + } + } + _ => assert!(false), + }; + + test_signature_and_corruption(&origin_tx, true, false); + test_signature_and_corruption(&origin_tx, false, true); + } + } } From be879382c70fdf1996ed26a5bf718469d05c9da0 Mon Sep 17 00:00:00 2001 From: Jeff Bencin Date: Thu, 14 Mar 2024 16:10:26 -0400 Subject: [PATCH 05/41] fix: Unit/integration tests (except `test_epoch_switch_pox_3_contract_instantiation`) --- libsigner/src/messages.rs | 19 +++++++++++++++---- stackslib/src/main.rs | 2 +- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/libsigner/src/messages.rs b/libsigner/src/messages.rs index 477712b224..765827237d 100644 --- a/libsigner/src/messages.rs +++ b/libsigner/src/messages.rs @@ -33,10 +33,12 @@ use clarity::vm::types::QualifiedContractIdentifier; use hashbrown::HashMap; use serde::{Deserialize, Serialize}; use stacks_common::codec::{ - read_next, read_next_at_most, read_next_exact, write_next, Error as CodecError, - StacksMessageCodec, + read_next, read_next_at_most, read_next_at_most_with_epoch, read_next_exact, write_next, + Error as CodecError, StacksMessageCodec, MAX_MESSAGE_LEN, }; +use stacks_common::types::StacksEpochId; use stacks_common::util::hash::Sha512Trunc256Sum; +use stacks_common::util::retry::BoundReader; use tiny_http::{ Method as HttpMethod, Request as HttpRequest, Response as HttpResponse, Server as HttpServer, }; @@ -208,7 +210,11 @@ impl StacksMessageCodec for SignerMessage { SignerMessage::BlockResponse(block_response) } SignerMessageTypePrefix::Transactions => { - let transactions = read_next::, _>(fd)?; + // I don't think these messages are stored on the blockchain, so `StacksEpochId::latest()` should be fine + let transactions: Vec = { + let mut bound_read = BoundReader::from_reader(fd, MAX_MESSAGE_LEN as u64); + read_next_at_most_with_epoch(&mut bound_read, u32::MAX, StacksEpochId::latest()) + }?; SignerMessage::Transactions(transactions) } }; @@ -852,7 +858,12 @@ impl StacksMessageCodec for RejectCode { RejectCode::InsufficientSigners(read_next::, _>(fd)?) } RejectCodeTypePrefix::MissingTransactions => { - RejectCode::MissingTransactions(read_next::, _>(fd)?) + // I don't think these messages are stored on the blockchain, so `StacksEpochId::latest()` should be fine + let transactions: Vec = { + let mut bound_read = BoundReader::from_reader(fd, MAX_MESSAGE_LEN as u64); + read_next_at_most_with_epoch(&mut bound_read, u32::MAX, StacksEpochId::latest()) + }?; + RejectCode::MissingTransactions(transactions) } RejectCodeTypePrefix::ConnectivityIssues => RejectCode::ConnectivityIssues, }; diff --git a/stackslib/src/main.rs b/stackslib/src/main.rs index 9f682bbfd7..6af5a958d2 100644 --- a/stackslib/src/main.rs +++ b/stackslib/src/main.rs @@ -70,7 +70,7 @@ use libstackerdb::StackerDBChunkData; use rusqlite::types::ToSql; use rusqlite::{Connection, OpenFlags}; use serde_json::{json, Value}; -use stacks_common::codec::{DeserializeWithEpoch, read_next, StacksMessageCodec}; +use stacks_common::codec::{read_next, DeserializeWithEpoch, StacksMessageCodec}; use stacks_common::types::chainstate::{ BlockHeaderHash, BurnchainHeaderHash, PoxId, StacksAddress, StacksBlockId, }; From 23b44ebf7cfd83c2dfe5ecad30904e933a0dae98 Mon Sep 17 00:00:00 2001 From: Jeff Bencin Date: Fri, 15 Mar 2024 16:56:14 -0400 Subject: [PATCH 06/41] fix: Unit/integration tests (except `test_epoch_switch_pox_3_contract_instantiation`) --- stackslib/src/chainstate/nakamoto/staging_blocks.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/stackslib/src/chainstate/nakamoto/staging_blocks.rs b/stackslib/src/chainstate/nakamoto/staging_blocks.rs index c0d9177783..4673793a98 100644 --- a/stackslib/src/chainstate/nakamoto/staging_blocks.rs +++ b/stackslib/src/chainstate/nakamoto/staging_blocks.rs @@ -30,7 +30,8 @@ use crate::chainstate::nakamoto::{NakamotoBlock, NakamotoChainState}; use crate::chainstate::stacks::db::StacksChainState; use crate::chainstate::stacks::index::marf::MarfConnection; use crate::chainstate::stacks::{Error as ChainstateError, StacksBlock, StacksBlockHeader}; -use crate::stacks_common::codec::StacksMessageCodec; +use crate::stacks_common::codec::{DeserializeWithEpoch, StacksMessageCodec}; +use crate::stacks_common::types::StacksEpochId; use crate::util_lib::db::{ query_int, query_row, query_row_panic, query_rows, sqlite_open, tx_begin_immediate, u64_to_sql, DBConn, Error as DBError, FromRow, @@ -173,7 +174,10 @@ impl<'a> NakamotoStagingBlocksConnRef<'a> { let Some(block_bytes) = res else { return Ok(None); }; - let block = NakamotoBlock::consensus_deserialize(&mut block_bytes.as_slice())?; + let block = NakamotoBlock::consensus_deserialize_with_epoch( + &mut block_bytes.as_slice(), + StacksEpochId::latest(), + )?; if &block.header.block_id() != index_block_hash { error!( "Staging DB corruption: expected {}, got {}", @@ -208,7 +212,7 @@ impl<'a> NakamotoStagingBlocksConnRef<'a> { self .query_row_and_then(query, NO_PARAMS, |row| { let data: Vec = row.get("data")?; - let block = NakamotoBlock::consensus_deserialize(&mut data.as_slice())?; + let block = NakamotoBlock::consensus_deserialize_with_epoch(&mut data.as_slice(), StacksEpochId::latest())?; Ok(Some(( block, u64::try_from(data.len()).expect("FATAL: block is bigger than a u64"), From 8cdd2dd46fb08467c176b61af06338769b329b7b Mon Sep 17 00:00:00 2001 From: Jeff Bencin Date: Mon, 18 Mar 2024 15:30:06 -0400 Subject: [PATCH 07/41] fix: `stackslib/src/chainstate/coordinator/tests.rs` passes --- stackslib/src/chainstate/coordinator/tests.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/stackslib/src/chainstate/coordinator/tests.rs b/stackslib/src/chainstate/coordinator/tests.rs index bfd54ca4ca..0a7d0e50d9 100644 --- a/stackslib/src/chainstate/coordinator/tests.rs +++ b/stackslib/src/chainstate/coordinator/tests.rs @@ -702,7 +702,7 @@ fn make_genesis_block_with_recipients( ), key_block_ptr: 1, // all registers happen in block height 1 key_vtxindex: (1 + key_index) as u16, - memo: vec![STACKS_EPOCH_3_0_MARKER], + memo: vec![STACKS_EPOCH_2_4_MARKER], new_seed: VRFSeed::from_proof(&proof), commit_outs, @@ -973,7 +973,7 @@ fn make_stacks_block_with_input( ), key_block_ptr: 1, // all registers happen in block height 1 key_vtxindex: (1 + key_index) as u16, - memo: vec![STACKS_EPOCH_3_0_MARKER], + memo: vec![STACKS_EPOCH_2_4_MARKER], new_seed: VRFSeed::from_proof(&proof), commit_outs, @@ -4427,7 +4427,7 @@ fn test_epoch_switch_pox_3_contract_instantiation() { &committers, pox_consts.clone(), None, - StacksEpochId::Epoch30, + StacksEpochId::Epoch24, ); let mut coord = make_coordinator(path, Some(burnchain_conf)); @@ -4541,9 +4541,7 @@ fn test_epoch_switch_pox_3_contract_instantiation() { x if x >= 8 && x < 12 => StacksEpochId::Epoch21, x if x >= 12 && x < 16 => StacksEpochId::Epoch22, x if x >= 16 && x < 20 => StacksEpochId::Epoch23, - x if x >= 20 && x < 24 => StacksEpochId::Epoch24, - x if x >= 24 && x < 28 => StacksEpochId::Epoch25, - _ => StacksEpochId::Epoch30, + _ => StacksEpochId::Epoch24, }; assert_eq!( chainstate From 78b05a6adc222f86d5e56b5a95d32d2630d9990f Mon Sep 17 00:00:00 2001 From: Jeff Bencin Date: Tue, 26 Mar 2024 15:54:04 -0400 Subject: [PATCH 08/41] chore: Address PR comments and fix tests --- libsigner/src/messages.rs | 2 +- .../src/chainstate/nakamoto/staging_blocks.rs | 5 +- .../src/chainstate/nakamoto/tests/mod.rs | 7 ++- stackslib/src/chainstate/stacks/auth.rs | 19 ++---- stackslib/src/chainstate/stacks/block.rs | 44 ++++++------- stackslib/src/chainstate/stacks/db/blocks.rs | 2 +- .../src/chainstate/stacks/transaction.rs | 63 +++++++------------ stackslib/src/main.rs | 6 +- stackslib/src/net/api/getblock_v3.rs | 8 ++- stackslib/src/net/api/gettenure.rs | 6 +- stackslib/src/net/api/tests/getblock_v3.rs | 10 ++- stackslib/src/net/api/tests/gettenure.rs | 7 ++- 12 files changed, 86 insertions(+), 93 deletions(-) diff --git a/libsigner/src/messages.rs b/libsigner/src/messages.rs index 431267af6e..86752135e5 100644 --- a/libsigner/src/messages.rs +++ b/libsigner/src/messages.rs @@ -1315,7 +1315,7 @@ mod test { use blockstack_lib::util_lib::strings::StacksString; use rand::Rng; use rand_core::OsRng; - use stacks_common::consts::CHAIN_ID_TESTNET; + use stacks_common::consts::{CHAIN_ID_TESTNET, SIGNER_SLOTS_PER_USER}; use stacks_common::types::chainstate::StacksPrivateKey; use wsts::common::Signature; diff --git a/stackslib/src/chainstate/nakamoto/staging_blocks.rs b/stackslib/src/chainstate/nakamoto/staging_blocks.rs index a20f870063..6f11d73694 100644 --- a/stackslib/src/chainstate/nakamoto/staging_blocks.rs +++ b/stackslib/src/chainstate/nakamoto/staging_blocks.rs @@ -218,7 +218,10 @@ impl<'a> NakamotoStagingBlocksConnRef<'a> { let Some(block_bytes) = data else { return Ok(None); }; - let block = NakamotoBlock::consensus_deserialize(&mut block_bytes.as_slice())?; + let block = NakamotoBlock::consensus_deserialize_with_epoch( + &mut block_bytes.as_slice(), + StacksEpochId::latest(), + )?; if &block.header.consensus_hash != consensus_hash { error!( "Staging DB corruption: expected {}, got {}", diff --git a/stackslib/src/chainstate/nakamoto/tests/mod.rs b/stackslib/src/chainstate/nakamoto/tests/mod.rs index 9844fa7b74..acc8965c85 100644 --- a/stackslib/src/chainstate/nakamoto/tests/mod.rs +++ b/stackslib/src/chainstate/nakamoto/tests/mod.rs @@ -27,7 +27,7 @@ use rand::{thread_rng, RngCore}; use rusqlite::{Connection, ToSql}; use stacks_common::address::AddressHashMode; use stacks_common::bitvec::BitVec; -use stacks_common::codec::StacksMessageCodec; +use stacks_common::codec::{DeserializeWithEpoch, StacksMessageCodec}; use stacks_common::consts::{ CHAIN_ID_MAINNET, CHAIN_ID_TESTNET, FIRST_BURNCHAIN_CONSENSUS_HASH, FIRST_STACKS_BLOCK_HASH, }; @@ -101,7 +101,10 @@ impl<'a> NakamotoStagingBlocksConnRef<'a> { let block_data: Vec> = query_rows(self, qry, args)?; let mut blocks = Vec::with_capacity(block_data.len()); for data in block_data.into_iter() { - let block = NakamotoBlock::consensus_deserialize(&mut data.as_slice())?; + let block = NakamotoBlock::consensus_deserialize_with_epoch( + &mut data.as_slice(), + StacksEpochId::latest(), + )?; blocks.push(block); } Ok(blocks) diff --git a/stackslib/src/chainstate/stacks/auth.rs b/stackslib/src/chainstate/stacks/auth.rs index 24caa33402..c7d0ec72c6 100644 --- a/stackslib/src/chainstate/stacks/auth.rs +++ b/stackslib/src/chainstate/stacks/auth.rs @@ -449,7 +449,6 @@ impl OrderIndependentMultisigSpendingCondition { cond_code: &TransactionAuthFlags, ) -> Result { let mut pubkeys = vec![]; - let cur_sighash = initial_sighash.clone(); let mut num_sigs: u16 = 0; let mut have_uncompressed = false; for field in self.fields.iter() { @@ -466,7 +465,7 @@ impl OrderIndependentMultisigSpendingCondition { } let (pubkey, _next_sighash) = TransactionSpendingCondition::next_verification( - &cur_sighash, + &initial_sighash, cond_code, self.tx_fee, self.nonce, @@ -515,7 +514,7 @@ impl OrderIndependentMultisigSpendingCondition { ))); } - Ok(cur_sighash) + Ok(initial_sighash.clone()) } } @@ -1209,26 +1208,20 @@ impl TransactionAuth { privks: &[StacksPrivateKey], num_sigs: u16, ) -> Option { - let mut pubks = vec![]; - for privk in privks.iter() { - pubks.push(StacksPublicKey::from_private(privk)); - } + let pubks = privks.iter().map(StacksPublicKey::from_private).collect(); TransactionSpendingCondition::new_multisig_order_independent_p2sh(num_sigs, pubks) - .map(|auth| TransactionAuth::Standard(auth)) + .map(TransactionAuth::Standard) } pub fn from_order_independent_p2wsh( privks: &[StacksPrivateKey], num_sigs: u16, ) -> Option { - let mut pubks = vec![]; - for privk in privks.iter() { - pubks.push(StacksPublicKey::from_private(privk)); - } + let pubks = privks.iter().map(StacksPublicKey::from_private).collect(); TransactionSpendingCondition::new_multisig_order_independent_p2wsh(num_sigs, pubks) - .map(|auth| TransactionAuth::Standard(auth)) + .map(TransactionAuth::Standard) } pub fn from_p2wpkh(privk: &StacksPrivateKey) -> Option { diff --git a/stackslib/src/chainstate/stacks/block.rs b/stackslib/src/chainstate/stacks/block.rs index 8760460ce4..911ab07b6d 100644 --- a/stackslib/src/chainstate/stacks/block.rs +++ b/stackslib/src/chainstate/stacks/block.rs @@ -41,6 +41,15 @@ use crate::chainstate::stacks::{Error, StacksBlockHeader, StacksMicroblockHeader use crate::core::*; use crate::net::Error as net_error; +/// Quietable error +macro_rules! qerror { + ($cond: expr, $($arg:tt)*) => ({ + if ($cond) { + error!($($arg)*); + } + }); +} + impl StacksMessageCodec for StacksBlockHeader { fn consensus_serialize(&self, fd: &mut W) -> Result<(), codec_error> { write_next(fd, &self.version)?; @@ -583,40 +592,33 @@ impl StacksBlock { if let TransactionPayload::Coinbase(_, ref recipient_opt, ref proof_opt) = &tx.payload { if proof_opt.is_some() && epoch_id < StacksEpochId::Epoch30 { // not supported - if !quiet { - error!("Coinbase with VRF proof not supported before Stacks 3.0"; "txid" => %tx.txid()); - } + qerror!(quiet, "Coinbase with VRF proof not supported before Stacks 3.0"; "txid" => %tx.txid()); return false; } if proof_opt.is_none() && epoch_id >= StacksEpochId::Epoch30 { // not supported - if !quiet { - error!("Coinbase with VRF proof is required in Stacks 3.0 and later"; "txid" => %tx.txid()); - } + qerror!(quiet, "Coinbase with VRF proof is required in Stacks 3.0 and later"; "txid" => %tx.txid()); return false; } if recipient_opt.is_some() && epoch_id < StacksEpochId::Epoch21 { // not supported - if !quiet { - error!("Coinbase pay-to-alt-recipient not supported before Stacks 2.1"; "txid" => %tx.txid()); - } + qerror!(quiet, "Coinbase pay-to-alt-recipient not supported before Stacks 2.1"; "txid" => %tx.txid()); return false; } } if let TransactionPayload::SmartContract(_, ref version_opt) = &tx.payload { if version_opt.is_some() && epoch_id < StacksEpochId::Epoch21 { // not supported - if !quiet { - error!("Versioned smart contracts not supported before Stacks 2.1"); - } + qerror!( + quiet, + "Versioned smart contracts not supported before Stacks 2.1" + ); return false; } } if let TransactionPayload::TenureChange(..) = &tx.payload { if epoch_id < StacksEpochId::Epoch30 { - if !quiet { - error!("TenureChange transaction not supported before Stacks 3.0"; "txid" => %tx.txid()); - } + qerror!(quiet, "TenureChange transaction not supported before Stacks 3.0"; "txid" => %tx.txid()); return false; } } @@ -625,9 +627,7 @@ impl StacksBlock { match origin { TransactionSpendingCondition::OrderIndependentMultisig(..) => { if epoch_id < StacksEpochId::Epoch30 { - if !quiet { - error!("Order independent multisig transactions not supported before Stacks 3.0"); - } + qerror!(quiet, "Order independent multisig transactions not supported before Stacks 3.0"); return false; } } @@ -636,9 +636,7 @@ impl StacksBlock { match sponsor { TransactionSpendingCondition::OrderIndependentMultisig(..) => { if epoch_id < StacksEpochId::Epoch30 { - if !quiet { - error!("Order independent multisig transactions not supported before Stacks 3.0"); - } + qerror!(quiet, "Order independent multisig transactions not supported before Stacks 3.0"); return false; } } @@ -648,9 +646,7 @@ impl StacksBlock { TransactionAuth::Standard(ref origin) => match origin { TransactionSpendingCondition::OrderIndependentMultisig(..) => { if epoch_id < StacksEpochId::Epoch30 { - if !quiet { - error!("Order independent multisig transactions not supported before Stacks 3.0"); - } + qerror!(quiet, "Order independent multisig transactions not supported before Stacks 3.0"); return false; } } diff --git a/stackslib/src/chainstate/stacks/db/blocks.rs b/stackslib/src/chainstate/stacks/db/blocks.rs index bdd3be4f30..1f6ac10e69 100644 --- a/stackslib/src/chainstate/stacks/db/blocks.rs +++ b/stackslib/src/chainstate/stacks/db/blocks.rs @@ -6719,7 +6719,7 @@ impl StacksChainState { let epoch = clarity_connection.get_epoch().clone(); let is_transaction_valid_in_epoch = - StacksBlock::validate_transactions_static_epoch(&vec![tx.clone()], epoch, true); + StacksBlock::validate_transactions_static_epoch(&[tx.clone()], epoch, true); if !is_transaction_valid_in_epoch { return Err(MemPoolRejection::Other( diff --git a/stackslib/src/chainstate/stacks/transaction.rs b/stackslib/src/chainstate/stacks/transaction.rs index 47b06e32e4..9d94addf42 100644 --- a/stackslib/src/chainstate/stacks/transaction.rs +++ b/stackslib/src/chainstate/stacks/transaction.rs @@ -699,7 +699,7 @@ impl StacksTransaction { payload, }; - if !StacksBlock::validate_transactions_static_epoch(&vec![tx.clone()], epoch_id, false) { + if !StacksBlock::validate_transactions_static_epoch(&[tx.clone()], epoch_id, false) { warn!("Invalid tx: target epoch is not activated"); return Err(codec_error::DeserializeError( "Failed to parse transaction: target epoch is not activated".to_string(), @@ -973,7 +973,8 @@ impl StacksTransaction { Ok(next_sighash) } - pub fn sign_no_append_origin( + #[cfg(any(test, feature = "testing"))] + fn sign_no_append_origin( &self, cur_sighash: &Txid, privk: &StacksPrivateKey, @@ -994,7 +995,8 @@ impl StacksTransaction { Ok(next_sig) } - pub fn append_origin_signature( + #[cfg(any(test, feature = "testing"))] + fn append_origin_signature( &mut self, signature: MessageSignature, key_encoding: TransactionPublicKeyEncoding, @@ -1016,7 +1018,8 @@ impl StacksTransaction { Ok(()) } - pub fn sign_no_append_sponsor( + #[cfg(any(test, feature = "testing"))] + fn sign_no_append_sponsor( &mut self, cur_sighash: &Txid, privk: &StacksPrivateKey, @@ -1496,30 +1499,20 @@ mod test { TransactionSpendingCondition::Multisig(ref data) => { let mut j = 0; for f in 0..data.fields.len() { - match data.fields[f] { - TransactionAuthField::Signature(_, _) => { - j = f; - break; - } - _ => { - continue; - } - } + if matches!(data.fields[f], TransactionAuthField::Signature(..)) { + j = f; + break; + }; } j } TransactionSpendingCondition::OrderIndependentMultisig(ref data) => { let mut j = 0; for f in 0..data.fields.len() { - match data.fields[f] { - TransactionAuthField::Signature(_, _) => { - j = f; - break; - } - _ => { - continue; - } - } + if matches!(data.fields[f], TransactionAuthField::Signature(..)) { + j = f; + break; + }; } j } @@ -1532,30 +1525,20 @@ mod test { TransactionSpendingCondition::Multisig(ref data) => { let mut j = 0; for f in 0..data.fields.len() { - match data.fields[f] { - TransactionAuthField::PublicKey(_) => { - j = f; - break; - } - _ => { - continue; - } - } + if matches!(data.fields[f], TransactionAuthField::PublicKey(_)) { + j = f; + break; + }; } j } TransactionSpendingCondition::OrderIndependentMultisig(ref data) => { let mut j = 0; for f in 0..data.fields.len() { - match data.fields[f] { - TransactionAuthField::PublicKey(_) => { - j = f; - break; - } - _ => { - continue; - } - } + if matches!(data.fields[f], TransactionAuthField::PublicKey(_)) { + j = f; + break; + }; } j } diff --git a/stackslib/src/main.rs b/stackslib/src/main.rs index e7c4787cc1..7aebe72ea0 100644 --- a/stackslib/src/main.rs +++ b/stackslib/src/main.rs @@ -1354,7 +1354,6 @@ simulating a miner. let events_file = &argv[3]; let mine_tip_height: u64 = argv[4].parse().expect("Could not parse mine_tip_height"); let mine_max_txns: u64 = argv[5].parse().expect("Could not parse mine-num-txns"); - let epoch_id = parse_input_epoch(6); let sort_db = SortitionDB::open(&sort_db_path, false, PoxConstants::mainnet_default()) .unwrap_or_else(|_| panic!("Failed to open {sort_db_path}")); @@ -1368,6 +1367,11 @@ simulating a miner. let estimator = Box::new(UnitEstimator); let metric = Box::new(UnitMetric); + let epoch_id = SortitionDB::get_stacks_epoch(&sort_db.conn(), chain_tip.block_height) + .expect("Error getting Epoch") + .expect("No Epoch found") + .epoch_id; + let mut mempool_db = MemPoolDB::open(true, chain_id, &chain_state_path, estimator, metric) .expect("Failed to open mempool db"); diff --git a/stackslib/src/net/api/getblock_v3.rs b/stackslib/src/net/api/getblock_v3.rs index 090afec04c..ab359f8ecb 100644 --- a/stackslib/src/net/api/getblock_v3.rs +++ b/stackslib/src/net/api/getblock_v3.rs @@ -20,9 +20,10 @@ use std::{fs, io}; use regex::{Captures, Regex}; use rusqlite::Connection; use serde::de::Error as de_Error; -use stacks_common::codec::{StacksMessageCodec, MAX_MESSAGE_LEN}; +use stacks_common::codec::{DeserializeWithEpoch, StacksMessageCodec, MAX_MESSAGE_LEN}; use stacks_common::types::chainstate::{ConsensusHash, StacksBlockId}; use stacks_common::types::net::PeerHost; +use stacks_common::types::StacksEpochId; use stacks_common::util::hash::to_hex; use {serde, serde_json}; @@ -317,7 +318,10 @@ impl StacksHttpResponse { // contents will be raw bytes let block_bytes: Vec = contents.try_into()?; - let block = NakamotoBlock::consensus_deserialize(&mut &block_bytes[..])?; + let block = NakamotoBlock::consensus_deserialize_with_epoch( + &mut &block_bytes[..], + StacksEpochId::latest(), + )?; Ok(block) } diff --git a/stackslib/src/net/api/gettenure.rs b/stackslib/src/net/api/gettenure.rs index 58ff2f96f0..17bd3b8a73 100644 --- a/stackslib/src/net/api/gettenure.rs +++ b/stackslib/src/net/api/gettenure.rs @@ -19,9 +19,10 @@ use std::{fs, io}; use regex::{Captures, Regex}; use serde::de::Error as de_Error; -use stacks_common::codec::{StacksMessageCodec, MAX_MESSAGE_LEN}; +use stacks_common::codec::{DeserializeWithEpoch, StacksMessageCodec, MAX_MESSAGE_LEN}; use stacks_common::types::chainstate::{ConsensusHash, StacksBlockId}; use stacks_common::types::net::PeerHost; +use stacks_common::types::StacksEpochId; use stacks_common::util::hash::to_hex; use {serde, serde_json}; @@ -355,7 +356,8 @@ impl StacksHttpResponse { let mut blocks = vec![]; while ptr.len() > 0 { - let block = NakamotoBlock::consensus_deserialize(ptr)?; + let block = + NakamotoBlock::consensus_deserialize_with_epoch(ptr, StacksEpochId::latest())?; blocks.push(block); } diff --git a/stackslib/src/net/api/tests/getblock_v3.rs b/stackslib/src/net/api/tests/getblock_v3.rs index de1a76f748..b24a81f815 100644 --- a/stackslib/src/net/api/tests/getblock_v3.rs +++ b/stackslib/src/net/api/tests/getblock_v3.rs @@ -18,12 +18,12 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use clarity::vm::types::{QualifiedContractIdentifier, StacksAddressExtensions}; use clarity::vm::{ClarityName, ContractName}; -use stacks_common::codec::StacksMessageCodec; +use stacks_common::codec::{DeserializeWithEpoch, StacksMessageCodec}; use stacks_common::types::chainstate::{ ConsensusHash, StacksAddress, StacksBlockId, StacksPrivateKey, }; use stacks_common::types::net::PeerHost; -use stacks_common::types::Address; +use stacks_common::types::{Address, StacksEpochId}; use super::TestRPC; use crate::chainstate::burn::db::sortdb::{SortitionDB, SortitionHandle}; @@ -183,6 +183,10 @@ fn test_stream_nakamoto_blocks() { all_block_bytes.append(&mut next_bytes); } - let staging_block = NakamotoBlock::consensus_deserialize(&mut &all_block_bytes[..]).unwrap(); + let staging_block = NakamotoBlock::consensus_deserialize_with_epoch( + &mut &all_block_bytes[..], + StacksEpochId::latest(), + ) + .unwrap(); assert_eq!(staging_block.header.block_id(), nakamoto_tip_block_id); } diff --git a/stackslib/src/net/api/tests/gettenure.rs b/stackslib/src/net/api/tests/gettenure.rs index c4f179acc9..1b3a280510 100644 --- a/stackslib/src/net/api/tests/gettenure.rs +++ b/stackslib/src/net/api/tests/gettenure.rs @@ -18,12 +18,12 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use clarity::vm::types::{QualifiedContractIdentifier, StacksAddressExtensions}; use clarity::vm::{ClarityName, ContractName}; -use stacks_common::codec::StacksMessageCodec; +use stacks_common::codec::{DeserializeWithEpoch, StacksMessageCodec}; use stacks_common::types::chainstate::{ ConsensusHash, StacksAddress, StacksBlockId, StacksPrivateKey, }; use stacks_common::types::net::PeerHost; -use stacks_common::types::Address; +use stacks_common::types::{Address, StacksEpochId}; use super::TestRPC; use crate::chainstate::burn::db::sortdb::{SortitionDB, SortitionHandle}; @@ -192,7 +192,8 @@ fn test_stream_nakamoto_tenure() { let ptr = &mut all_block_bytes.as_slice(); let mut blocks = vec![]; while ptr.len() > 0 { - let block = NakamotoBlock::consensus_deserialize(ptr).unwrap(); + let block = + NakamotoBlock::consensus_deserialize_with_epoch(ptr, StacksEpochId::latest()).unwrap(); blocks.push(block); } From 7b4794db06525d254bf6cff1c1dbb8e4e46eb3c5 Mon Sep 17 00:00:00 2001 From: Jeff Bencin Date: Wed, 3 Apr 2024 23:35:48 -0400 Subject: [PATCH 09/41] chore: Remove `DeserializeWithEpoch` trait --- libsigner/src/messages.rs | 8 +- stacks-common/src/codec/mod.rs | 69 ----- stackslib/src/blockstack_cli.rs | 16 +- stackslib/src/chainstate/nakamoto/mod.rs | 15 +- .../src/chainstate/nakamoto/staging_blocks.rs | 14 +- .../src/chainstate/nakamoto/tests/mod.rs | 7 +- stackslib/src/chainstate/stacks/block.rs | 103 +------ stackslib/src/chainstate/stacks/db/blocks.rs | 39 +-- .../src/chainstate/stacks/transaction.rs | 40 +-- stackslib/src/core/mempool.rs | 27 +- stackslib/src/main.rs | 39 +-- stackslib/src/net/api/getblock.rs | 9 +- stackslib/src/net/api/getblock_v3.rs | 7 +- stackslib/src/net/api/gettenure.rs | 5 +- stackslib/src/net/api/postblock.rs | 21 +- stackslib/src/net/api/posttransaction.rs | 29 +- stackslib/src/net/api/tests/getblock.rs | 13 +- stackslib/src/net/api/tests/getblock_v3.rs | 8 +- stackslib/src/net/api/tests/gettenure.rs | 5 +- stackslib/src/net/codec.rs | 58 +--- testnet/stacks-node/src/node.rs | 2 +- testnet/stacks-node/src/tests/epoch_205.rs | 19 +- testnet/stacks-node/src/tests/epoch_21.rs | 41 +-- testnet/stacks-node/src/tests/epoch_22.rs | 15 +- testnet/stacks-node/src/tests/epoch_23.rs | 8 +- testnet/stacks-node/src/tests/epoch_24.rs | 8 +- testnet/stacks-node/src/tests/integrations.rs | 47 +-- testnet/stacks-node/src/tests/mempool.rs | 184 ++++-------- testnet/stacks-node/src/tests/mod.rs | 6 +- .../src/tests/neon_integrations.rs | 277 +++++------------- 30 files changed, 254 insertions(+), 885 deletions(-) diff --git a/libsigner/src/messages.rs b/libsigner/src/messages.rs index 86752135e5..6f644b9ef6 100644 --- a/libsigner/src/messages.rs +++ b/libsigner/src/messages.rs @@ -43,8 +43,8 @@ use clarity::vm::types::QualifiedContractIdentifier; use hashbrown::{HashMap, HashSet}; use serde::{Deserialize, Serialize}; use stacks_common::codec::{ - read_next, read_next_at_most, read_next_at_most_with_epoch, read_next_exact, write_next, - Error as CodecError, StacksMessageCodec, MAX_MESSAGE_LEN, + read_next, read_next_at_most, read_next_exact, write_next, Error as CodecError, + StacksMessageCodec, MAX_MESSAGE_LEN, }; use stacks_common::types::StacksEpochId; use stacks_common::util::hash::Sha512Trunc256Sum; @@ -366,7 +366,7 @@ impl StacksMessageCodec for SignerMessage { // I don't think these messages are stored on the blockchain, so `StacksEpochId::latest()` should be fine let transactions: Vec = { let mut bound_read = BoundReader::from_reader(fd, MAX_MESSAGE_LEN as u64); - read_next_at_most_with_epoch(&mut bound_read, u32::MAX, StacksEpochId::latest()) + read_next_at_most(&mut bound_read, u32::MAX) }?; SignerMessage::Transactions(transactions) } @@ -1224,7 +1224,7 @@ impl StacksMessageCodec for RejectCode { // I don't think these messages are stored on the blockchain, so `StacksEpochId::latest()` should be fine let transactions: Vec = { let mut bound_read = BoundReader::from_reader(fd, MAX_MESSAGE_LEN as u64); - read_next_at_most_with_epoch(&mut bound_read, u32::MAX, StacksEpochId::latest()) + read_next_at_most(&mut bound_read, u32::MAX) }?; RejectCode::MissingTransactions(transactions) } diff --git a/stacks-common/src/codec/mod.rs b/stacks-common/src/codec/mod.rs index 1cf30146f1..aa09348b0f 100644 --- a/stacks-common/src/codec/mod.rs +++ b/stacks-common/src/codec/mod.rs @@ -87,15 +87,6 @@ pub trait StacksMessageCodec { } } -pub trait DeserializeWithEpoch { - fn consensus_deserialize_with_epoch( - fd: &mut R, - epoch_id: StacksEpochId, - ) -> Result - where - Self: Sized; -} - // impl_byte_array_message_codec!(MARFValue, 40); impl_byte_array_message_codec!(SortitionId, 32); @@ -193,66 +184,6 @@ pub fn read_next_exact( read_next_vec::(fd, num_items, 0) } -pub fn read_next_with_epoch( - fd: &mut R, - epoch_id: StacksEpochId, -) -> Result { - let item: T = T::consensus_deserialize_with_epoch(fd, epoch_id)?; - Ok(item) -} - -fn read_next_vec_with_epoch( - fd: &mut R, - num_items: u32, - max_items: u32, - epoch_id: StacksEpochId, -) -> Result, Error> { - let len = u32::consensus_deserialize(fd)?; - - if max_items > 0 { - if len > max_items { - // too many items - return Err(Error::DeserializeError(format!( - "Array has too many items ({} > {}", - len, max_items - ))); - } - } else { - if len != num_items { - // inexact item count - return Err(Error::DeserializeError(format!( - "Array has incorrect number of items ({} != {})", - len, num_items - ))); - } - } - - if (mem::size_of::() as u128) * (len as u128) > MAX_MESSAGE_LEN as u128 { - return Err(Error::DeserializeError(format!( - "Message occupies too many bytes (tried to allocate {}*{}={})", - mem::size_of::() as u128, - len, - (mem::size_of::() as u128) * (len as u128) - ))); - } - - let mut ret = Vec::with_capacity(len as usize); - for _i in 0..len { - let next_item = T::consensus_deserialize_with_epoch(fd, epoch_id)?; - ret.push(next_item); - } - - Ok(ret) -} - -pub fn read_next_at_most_with_epoch( - fd: &mut R, - max_items: u32, - epoch_id: StacksEpochId, -) -> Result, Error> { - read_next_vec_with_epoch::(fd, 0, max_items, epoch_id) -} - impl StacksMessageCodec for Vec where T: StacksMessageCodec + Sized, diff --git a/stackslib/src/blockstack_cli.rs b/stackslib/src/blockstack_cli.rs index 2df300a90c..a4f679ac6a 100644 --- a/stackslib/src/blockstack_cli.rs +++ b/stackslib/src/blockstack_cli.rs @@ -46,7 +46,7 @@ use clarity::vm::errors::{Error as ClarityError, RuntimeErrorType}; use clarity::vm::types::PrincipalData; use clarity::vm::{ClarityName, ClarityVersion, ContractName, Value}; use stacks_common::address::{b58, AddressHashMode}; -use stacks_common::codec::{DeserializeWithEpoch, Error as CodecError, StacksMessageCodec}; +use stacks_common::codec::{Error as CodecError, StacksMessageCodec}; use stacks_common::types::chainstate::StacksAddress; use stacks_common::types::StacksEpochId; use stacks_common::util::hash::{hex_bytes, to_hex}; @@ -315,10 +315,8 @@ fn sign_transaction_single_sig_standard( transaction: &str, secret_key: &StacksPrivateKey, ) -> Result { - let transaction = StacksTransaction::consensus_deserialize_with_epoch( - &mut io::Cursor::new(&hex_bytes(transaction)?), - StacksEpochId::latest(), - )?; + let transaction = + StacksTransaction::consensus_deserialize(&mut io::Cursor::new(&hex_bytes(transaction)?))?; let mut tx_signer = StacksTransactionSigner::new(&transaction); tx_signer.sign_origin(secret_key)?; @@ -665,10 +663,7 @@ fn decode_transaction(args: &[String], _version: TransactionVersion) -> Result Ok(serde_json::to_string(&tx).expect("Failed to serialize transaction to JSON")), Err(e) => { let mut ret = String::new(); @@ -744,8 +739,7 @@ fn decode_block(args: &[String], _version: TransactionVersion) -> Result Ok(serde_json::to_string(&block).expect("Failed to serialize block to JSON")), Err(e) => { let mut ret = String::new(); diff --git a/stackslib/src/chainstate/nakamoto/mod.rs b/stackslib/src/chainstate/nakamoto/mod.rs index 82905d3265..5d5010ca1c 100644 --- a/stackslib/src/chainstate/nakamoto/mod.rs +++ b/stackslib/src/chainstate/nakamoto/mod.rs @@ -32,8 +32,8 @@ use rusqlite::{params, Connection, OpenFlags, OptionalExtension, ToSql, NO_PARAM use sha2::{Digest as Sha2Digest, Sha512_256}; use stacks_common::bitvec::BitVec; use stacks_common::codec::{ - read_next, read_next_at_most_with_epoch, write_next, DeserializeWithEpoch, Error as CodecError, - StacksMessageCodec, MAX_MESSAGE_LEN, MAX_PAYLOAD_LEN, + read_next, read_next_at_most, write_next, Error as CodecError, StacksMessageCodec, + MAX_MESSAGE_LEN, MAX_PAYLOAD_LEN, }; use stacks_common::consts::{ FIRST_BURNCHAIN_CONSENSUS_HASH, FIRST_STACKS_BLOCK_HASH, MINER_REWARD_MATURITY, @@ -3351,21 +3351,12 @@ impl StacksMessageCodec for NakamotoBlock { } fn consensus_deserialize(fd: &mut R) -> Result { - panic!("NakamotoBlock should be deserialized with consensus_deserialize_with_epoch instead") - } -} - -impl DeserializeWithEpoch for NakamotoBlock { - fn consensus_deserialize_with_epoch( - fd: &mut R, - epoch_id: StacksEpochId, - ) -> Result { let header: NakamotoBlockHeader = read_next(fd)?; let txs: Vec = { let mut bound_read = BoundReader::from_reader(fd, u64::from(MAX_MESSAGE_LEN)); // The latest epoch where StacksMicroblock exist is Epoch25 - read_next_at_most_with_epoch(&mut bound_read, u32::MAX, epoch_id) + read_next_at_most(&mut bound_read, u32::MAX) }?; // all transactions are unique diff --git a/stackslib/src/chainstate/nakamoto/staging_blocks.rs b/stackslib/src/chainstate/nakamoto/staging_blocks.rs index 6f11d73694..3e637af21f 100644 --- a/stackslib/src/chainstate/nakamoto/staging_blocks.rs +++ b/stackslib/src/chainstate/nakamoto/staging_blocks.rs @@ -31,7 +31,7 @@ use crate::chainstate::nakamoto::{NakamotoBlock, NakamotoChainState}; use crate::chainstate::stacks::db::StacksChainState; use crate::chainstate::stacks::index::marf::MarfConnection; use crate::chainstate::stacks::{Error as ChainstateError, StacksBlock, StacksBlockHeader}; -use crate::stacks_common::codec::{DeserializeWithEpoch, StacksMessageCodec}; +use crate::stacks_common::codec::StacksMessageCodec; use crate::stacks_common::types::StacksEpochId; use crate::util_lib::db::{ query_int, query_row, query_row_panic, query_rows, sqlite_open, tx_begin_immediate, u64_to_sql, @@ -218,10 +218,7 @@ impl<'a> NakamotoStagingBlocksConnRef<'a> { let Some(block_bytes) = data else { return Ok(None); }; - let block = NakamotoBlock::consensus_deserialize_with_epoch( - &mut block_bytes.as_slice(), - StacksEpochId::latest(), - )?; + let block = NakamotoBlock::consensus_deserialize(&mut block_bytes.as_slice())?; if &block.header.consensus_hash != consensus_hash { error!( "Staging DB corruption: expected {}, got {}", @@ -258,10 +255,7 @@ impl<'a> NakamotoStagingBlocksConnRef<'a> { let Some(block_bytes) = res else { return Ok(None); }; - let block = NakamotoBlock::consensus_deserialize_with_epoch( - &mut block_bytes.as_slice(), - StacksEpochId::latest(), - )?; + let block = NakamotoBlock::consensus_deserialize(&mut block_bytes.as_slice())?; if &block.header.block_id() != index_block_hash { error!( "Staging DB corruption: expected {}, got {}", @@ -311,7 +305,7 @@ impl<'a> NakamotoStagingBlocksConnRef<'a> { self .query_row_and_then(query, NO_PARAMS, |row| { let data: Vec = row.get("data")?; - let block = NakamotoBlock::consensus_deserialize_with_epoch(&mut data.as_slice(), StacksEpochId::latest())?; + let block = NakamotoBlock::consensus_deserialize(&mut data.as_slice())?; Ok(Some(( block, u64::try_from(data.len()).expect("FATAL: block is bigger than a u64"), diff --git a/stackslib/src/chainstate/nakamoto/tests/mod.rs b/stackslib/src/chainstate/nakamoto/tests/mod.rs index acc8965c85..9844fa7b74 100644 --- a/stackslib/src/chainstate/nakamoto/tests/mod.rs +++ b/stackslib/src/chainstate/nakamoto/tests/mod.rs @@ -27,7 +27,7 @@ use rand::{thread_rng, RngCore}; use rusqlite::{Connection, ToSql}; use stacks_common::address::AddressHashMode; use stacks_common::bitvec::BitVec; -use stacks_common::codec::{DeserializeWithEpoch, StacksMessageCodec}; +use stacks_common::codec::StacksMessageCodec; use stacks_common::consts::{ CHAIN_ID_MAINNET, CHAIN_ID_TESTNET, FIRST_BURNCHAIN_CONSENSUS_HASH, FIRST_STACKS_BLOCK_HASH, }; @@ -101,10 +101,7 @@ impl<'a> NakamotoStagingBlocksConnRef<'a> { let block_data: Vec> = query_rows(self, qry, args)?; let mut blocks = Vec::with_capacity(block_data.len()); for data in block_data.into_iter() { - let block = NakamotoBlock::consensus_deserialize_with_epoch( - &mut data.as_slice(), - StacksEpochId::latest(), - )?; + let block = NakamotoBlock::consensus_deserialize(&mut data.as_slice())?; blocks.push(block); } Ok(blocks) diff --git a/stackslib/src/chainstate/stacks/block.rs b/stackslib/src/chainstate/stacks/block.rs index 911ab07b6d..674dbc66d6 100644 --- a/stackslib/src/chainstate/stacks/block.rs +++ b/stackslib/src/chainstate/stacks/block.rs @@ -21,8 +21,8 @@ use std::io::{Read, Write}; use sha2::{Digest, Sha512_256}; use stacks_common::codec::{ - read_next, read_next_at_most_with_epoch, write_next, DeserializeWithEpoch, - Error as codec_error, StacksMessageCodec, MAX_MESSAGE_LEN, + read_next, read_next_at_most, write_next, Error as codec_error, StacksMessageCodec, + MAX_MESSAGE_LEN, }; use stacks_common::types::chainstate::{ BlockHeaderHash, BurnchainHeaderHash, StacksBlockId, StacksWorkScore, TrieHash, VRFSeed, @@ -312,22 +312,13 @@ impl StacksMessageCodec for StacksBlock { Ok(()) } - fn consensus_deserialize(_fd: &mut R) -> Result { - panic!("StacksBlock should be deserialized with consensus_deserialize_with_epoch instead") - } -} - -impl DeserializeWithEpoch for StacksBlock { - fn consensus_deserialize_with_epoch( - fd: &mut R, - epoch_id: StacksEpochId, - ) -> Result { + fn consensus_deserialize(fd: &mut R) -> Result { // NOTE: don't worry about size clamps here; do that when receiving the data from the peer // network. This code assumes that the block will be small enough. let header: StacksBlockHeader = read_next(fd)?; let txs: Vec = { let mut bound_read = BoundReader::from_reader(fd, MAX_MESSAGE_LEN as u64); - read_next_at_most_with_epoch(&mut bound_read, u32::MAX, epoch_id) + read_next_at_most(&mut bound_read, u32::MAX) }?; // there must be at least one transaction (the coinbase) @@ -859,65 +850,7 @@ impl StacksMessageCodec for StacksMicroblock { let txs: Vec = { let mut bound_read = BoundReader::from_reader(fd, MAX_MESSAGE_LEN as u64); // The latest epoch where StacksMicroblock exist is Epoch25 - read_next_at_most_with_epoch(&mut bound_read, u32::MAX, StacksEpochId::Epoch25) - }?; - - if txs.len() == 0 { - warn!("Invalid microblock: zero transactions"); - return Err(codec_error::DeserializeError( - "Invalid microblock: zero transactions".to_string(), - )); - } - - if !StacksBlock::validate_transactions_unique(&txs) { - warn!("Invalid microblock: duplicate transaction"); - return Err(codec_error::DeserializeError( - "Invalid microblock: duplicate transaction".to_string(), - )); - } - - if !StacksBlock::validate_anchor_mode(&txs, false) { - warn!("Invalid microblock: found on-chain-only transaction"); - return Err(codec_error::DeserializeError( - "Invalid microblock: found on-chain-only transaction".to_string(), - )); - } - - // header and transactions must be consistent - let txid_vecs = txs.iter().map(|tx| tx.txid().as_bytes().to_vec()).collect(); - - let merkle_tree = MerkleTree::::new(&txid_vecs); - let tx_merkle_root = merkle_tree.root(); - - if tx_merkle_root != header.tx_merkle_root { - return Err(codec_error::DeserializeError( - "Invalid microblock: tx Merkle root mismatch".to_string(), - )); - } - - if !StacksBlock::validate_coinbase(&txs, false) { - warn!("Invalid microblock: found coinbase transaction"); - return Err(codec_error::DeserializeError( - "Invalid microblock: found coinbase transaction".to_string(), - )); - } - - Ok(StacksMicroblock { header, txs }) - } -} - -// This implementation is used for testing purposes, StacksMicroblock won't be used in Epoch 3.0 -impl DeserializeWithEpoch for StacksMicroblock { - fn consensus_deserialize_with_epoch( - fd: &mut R, - epoch_id: StacksEpochId, - ) -> Result { - // NOTE: maximum size must be checked elsewhere! - let header: StacksMicroblockHeader = read_next(fd)?; - let txs: Vec = { - let mut bound_read = BoundReader::from_reader(fd, MAX_MESSAGE_LEN as u64); - // The latest epoch where StacksMicroblock exist is Epoch24 - read_next_at_most_with_epoch(&mut bound_read, u32::MAX, epoch_id) + read_next_at_most(&mut bound_read, u32::MAX) }?; if txs.len() == 0 { @@ -1227,11 +1160,7 @@ mod test { block_bytes.len(), block.txs.len() ); - check_codec_and_corruption_with_epoch::( - &block, - &block_bytes, - StacksEpochId::latest(), - ); + check_codec_and_corruption::(&block, &block_bytes); } #[test] @@ -1314,11 +1243,7 @@ mod test { txs: txs, }; - check_codec_and_corruption_with_epoch::( - &mblock, - &block_bytes, - StacksEpochId::latest(), - ); + check_codec_and_corruption::(&mblock, &block_bytes); } } @@ -1910,25 +1835,19 @@ mod test { let mut bytes: Vec = vec![]; tx.consensus_serialize(&mut bytes).unwrap(); - StacksTransaction::consensus_deserialize_with_epoch(&mut &bytes[..], *epoch_id) - .unwrap(); + StacksTransaction::consensus_deserialize(&mut &bytes[..]).unwrap(); } - StacksBlock::consensus_deserialize_with_epoch(&mut &bytes[..], *epoch_id).unwrap(); + StacksBlock::consensus_deserialize(&mut &bytes[..]).unwrap(); } else { for tx in txs.iter() { let mut bytes: Vec = vec![]; tx.consensus_serialize(&mut bytes).unwrap(); - let _ = StacksTransaction::consensus_deserialize_with_epoch( - &mut &bytes[..], - *epoch_id, - ) - .unwrap_err(); + let _ = StacksTransaction::consensus_deserialize(&mut &bytes[..]).unwrap_err(); } - let _ = StacksBlock::consensus_deserialize_with_epoch(&mut &bytes[..], *epoch_id) - .unwrap_err(); + let _ = StacksBlock::consensus_deserialize(&mut &bytes[..]).unwrap_err(); assert!(!StacksBlock::validate_transactions_static_epoch( &txs, *epoch_id, false, diff --git a/stackslib/src/chainstate/stacks/db/blocks.rs b/stackslib/src/chainstate/stacks/db/blocks.rs index 1f6ac10e69..31680023bd 100644 --- a/stackslib/src/chainstate/stacks/db/blocks.rs +++ b/stackslib/src/chainstate/stacks/db/blocks.rs @@ -38,7 +38,7 @@ use rusqlite::{Connection, DatabaseName, Error as sqlite_error, OptionalExtensio use serde::Serialize; use serde_json::json; use stacks_common::bitvec::BitVec; -use stacks_common::codec::{read_next, write_next, DeserializeWithEpoch, MAX_MESSAGE_LEN}; +use stacks_common::codec::{read_next, write_next, MAX_MESSAGE_LEN}; use stacks_common::types::chainstate::{ BurnchainHeaderHash, SortitionId, StacksAddress, StacksBlockId, }; @@ -560,28 +560,6 @@ impl StacksChainState { Ok(inst) } - pub fn consensus_load_with_epoch( - path: &str, - epoch_id: StacksEpochId, - ) -> Result { - let mut fd = fs::OpenOptions::new() - .read(true) - .write(false) - .open(path) - .map_err(|e| { - if e.kind() == io::ErrorKind::NotFound { - Error::DBError(db_error::NotFoundError) - } else { - Error::DBError(db_error::IOError(e)) - } - })?; - - let mut bound_reader = BoundReader::from_reader(&mut fd, MAX_MESSAGE_LEN as u64); - let inst = T::consensus_deserialize_with_epoch(&mut bound_reader, epoch_id) - .map_err(Error::CodecError)?; - Ok(inst) - } - /// Do we have a stored a block in the chunk store? /// Will be true even if it's invalid. pub fn has_block_indexed( @@ -871,8 +849,7 @@ impl StacksChainState { return Ok(None); } - let block: StacksBlock = - StacksChainState::consensus_load_with_epoch(&block_path, epoch_id)?; + let block: StacksBlock = StacksChainState::consensus_load(&block_path)?; Ok(Some(block)) } @@ -1077,10 +1054,7 @@ impl StacksChainState { return Ok(None); } - match StacksBlock::consensus_deserialize_with_epoch( - &mut &staging_block.block_data[..], - StacksEpochId::Epoch25, - ) { + match StacksBlock::consensus_deserialize(&mut &staging_block.block_data[..]) { Ok(block) => Ok(Some(block)), Err(e) => Err(Error::CodecError(e)), } @@ -6002,11 +5976,8 @@ impl StacksChainState { epoch_id: StacksEpochId, ) -> Result { let block = { - StacksBlock::consensus_deserialize_with_epoch( - &mut &next_staging_block.block_data[..], - epoch_id, - ) - .map_err(Error::CodecError)? + StacksBlock::consensus_deserialize(&mut &next_staging_block.block_data[..]) + .map_err(Error::CodecError)? }; let block_hash = block.block_hash(); diff --git a/stackslib/src/chainstate/stacks/transaction.rs b/stackslib/src/chainstate/stacks/transaction.rs index 9d94addf42..e904ded73a 100644 --- a/stackslib/src/chainstate/stacks/transaction.rs +++ b/stackslib/src/chainstate/stacks/transaction.rs @@ -22,9 +22,7 @@ use clarity::vm::representations::{ClarityName, ContractName}; use clarity::vm::types::serialization::SerializationError as clarity_serialization_error; use clarity::vm::types::{QualifiedContractIdentifier, StandardPrincipalData}; use clarity::vm::{ClarityVersion, SymbolicExpression, SymbolicExpressionType, Value}; -use stacks_common::codec::{ - read_next, write_next, DeserializeWithEpoch, Error as codec_error, StacksMessageCodec, -}; +use stacks_common::codec::{read_next, write_next, Error as codec_error, StacksMessageCodec}; use stacks_common::types::chainstate::StacksAddress; use stacks_common::types::StacksPublicKeyBuffer; use stacks_common::util::hash::{to_hex, MerkleHashFunc, MerkleTree, Sha512Trunc256Sum}; @@ -613,7 +611,6 @@ impl StacksTransaction { pub fn consensus_deserialize_with_len( fd: &mut R, - epoch_id: StacksEpochId, ) -> Result<(StacksTransaction, u64), codec_error> { let mut bound_read = BoundReader::from_reader(fd, MAX_TRANSACTION_LEN.into()); let fd = &mut bound_read; @@ -699,13 +696,6 @@ impl StacksTransaction { payload, }; - if !StacksBlock::validate_transactions_static_epoch(&[tx.clone()], epoch_id, false) { - warn!("Invalid tx: target epoch is not activated"); - return Err(codec_error::DeserializeError( - "Failed to parse transaction: target epoch is not activated".to_string(), - )); - } - Ok((tx, fd.num_read())) } @@ -742,17 +732,8 @@ impl StacksMessageCodec for StacksTransaction { Ok(()) } - fn consensus_deserialize(_fd: &mut R) -> Result { - panic!("StacksTransaction should be deserialized with consensus_deserialize_with_epoch instead") - } -} - -impl DeserializeWithEpoch for StacksTransaction { - fn consensus_deserialize_with_epoch( - fd: &mut R, - epoch_id: StacksEpochId, - ) -> Result { - StacksTransaction::consensus_deserialize_with_len(fd, epoch_id).map(|(result, _)| result) + fn consensus_deserialize(fd: &mut R) -> Result { + StacksTransaction::consensus_deserialize_with_len(fd).map(|(result, _)| result) } } @@ -1360,9 +1341,7 @@ mod test { StacksPublicKey as PubKey, C32_ADDRESS_VERSION_MAINNET_MULTISIG, C32_ADDRESS_VERSION_MAINNET_SINGLESIG, *, *, }; - use crate::net::codec::test::{ - check_codec_and_corruption, check_codec_and_corruption_with_epoch, - }; + use crate::net::codec::test::check_codec_and_corruption; use crate::net::codec::*; use crate::net::*; @@ -1974,10 +1953,7 @@ mod test { // test_debug!("mutate byte {}", &i); let mut cursor = io::Cursor::new(&tx_bytes); let mut reader = LogReader::from_reader(&mut cursor); - match StacksTransaction::consensus_deserialize_with_epoch( - &mut reader, - StacksEpochId::latest(), - ) { + match StacksTransaction::consensus_deserialize(&mut reader) { Ok(corrupt_tx) => { let mut corrupt_tx_bytes = vec![]; corrupt_tx @@ -3907,11 +3883,7 @@ mod test { test_debug!("---------"); test_debug!("text tx bytes:\n{}", &to_hex(&tx_bytes)); - check_codec_and_corruption_with_epoch::( - &tx, - &tx_bytes, - StacksEpochId::latest(), - ); + check_codec_and_corruption::(&tx, &tx_bytes); } } diff --git a/stackslib/src/core/mempool.rs b/stackslib/src/core/mempool.rs index 312623ac1c..4987786d85 100644 --- a/stackslib/src/core/mempool.rs +++ b/stackslib/src/core/mempool.rs @@ -34,8 +34,7 @@ use rusqlite::{ }; use siphasher::sip::SipHasher; // this is SipHash-2-4 use stacks_common::codec::{ - read_next, read_next_with_epoch, write_next, DeserializeWithEpoch, Error as codec_error, - StacksMessageCodec, MAX_MESSAGE_LEN, + read_next, write_next, Error as codec_error, StacksMessageCodec, MAX_MESSAGE_LEN, }; use stacks_common::types::chainstate::{BlockHeaderHash, StacksAddress, StacksBlockId}; use stacks_common::util::hash::{to_hex, Sha512Trunc256Sum}; @@ -237,8 +236,7 @@ pub fn decode_tx_stream_with_epoch( loop { let pos = retry_reader.position(); - let next_msg: Result = - read_next_with_epoch(&mut retry_reader, epoch_id); + let next_msg: Result = read_next(&mut retry_reader); match next_msg { Ok(tx) => { if expect_eof { @@ -598,11 +596,8 @@ impl FromRow for MemPoolTxInfo { fn from_row<'a>(row: &'a Row) -> Result { let md = MemPoolTxMetadata::from_row(row)?; let tx_bytes: Vec = row.get_unwrap("tx"); - let tx = StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_bytes[..], - StacksEpochId::latest(), - ) - .map_err(|_e| db_error::ParseError)?; + let tx = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]) + .map_err(|_e| db_error::ParseError)?; if tx.txid() != md.txid { return Err(db_error::ParseError); @@ -2461,11 +2456,8 @@ impl MemPoolDB { block_limit: &ExecutionCost, stacks_epoch_id: &StacksEpochId, ) -> Result<(), MemPoolRejection> { - let tx = StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_bytes[..], - StacksEpochId::latest(), - ) - .map_err(MemPoolRejection::DeserializationFailure)?; + let tx = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]) + .map_err(MemPoolRejection::DeserializationFailure)?; if self.is_tx_blacklisted(&tx.txid())? { // don't re-store this transaction @@ -2829,11 +2821,8 @@ impl MemPoolDB { } let tx_bytes: Vec = row.get_unwrap("tx"); - let tx = StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_bytes[..], - StacksEpochId::latest(), - ) - .map_err(|_e| db_error::ParseError)?; + let tx = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]) + .map_err(|_e| db_error::ParseError)?; test_debug!("Returning txid {}", &txid); ret.push(tx); diff --git a/stackslib/src/main.rs b/stackslib/src/main.rs index 7aebe72ea0..e7f9ee9d84 100644 --- a/stackslib/src/main.rs +++ b/stackslib/src/main.rs @@ -76,7 +76,7 @@ use libstackerdb::StackerDBChunkData; use rusqlite::types::ToSql; use rusqlite::{Connection, OpenFlags}; use serde_json::{json, Value}; -use stacks_common::codec::{read_next, DeserializeWithEpoch, StacksMessageCodec}; +use stacks_common::codec::{read_next, StacksMessageCodec}; use stacks_common::types::chainstate::{ BlockHeaderHash, BurnchainHeaderHash, PoxId, StacksAddress, StacksBlockId, }; @@ -197,7 +197,7 @@ fn main() { let mut debug_cursor = LogReader::from_reader(&mut cursor); let epoch_id = parse_input_epoch(3); - let tx = StacksTransaction::consensus_deserialize_with_epoch(&mut debug_cursor, epoch_id) + let tx = StacksTransaction::consensus_deserialize(&mut debug_cursor) .map_err(|e| { eprintln!("Failed to decode transaction: {:?}", &e); eprintln!("Bytes consumed:"); @@ -226,15 +226,12 @@ fn main() { fs::read(block_path).unwrap_or_else(|_| panic!("Failed to open {block_path}")); let epoch_id = parse_input_epoch(3); - let block = StacksBlock::consensus_deserialize_with_epoch( - &mut io::Cursor::new(&block_data), - epoch_id, - ) - .map_err(|_e| { - eprintln!("Failed to decode block"); - process::exit(1); - }) - .unwrap(); + let block = StacksBlock::consensus_deserialize(&mut io::Cursor::new(&block_data)) + .map_err(|_e| { + eprintln!("Failed to decode block"); + process::exit(1); + }) + .unwrap(); println!("{:#?}", &block); process::exit(0); @@ -300,15 +297,13 @@ fn main() { let epoch_id = parse_input_epoch(4); - let block = StacksBlock::consensus_deserialize_with_epoch( - &mut io::Cursor::new(&block_info.block_data), - epoch_id, - ) - .map_err(|_e| { - eprintln!("Failed to decode block"); - process::exit(1); - }) - .unwrap(); + let block = + StacksBlock::consensus_deserialize(&mut io::Cursor::new(&block_info.block_data)) + .map_err(|_e| { + eprintln!("Failed to decode block"); + process::exit(1); + }) + .unwrap(); let microblocks = StacksChainState::find_parent_microblock_stream(chainstate.db(), &block_info) @@ -1460,9 +1455,7 @@ simulating a miner. let raw_tx_hex = item.as_str().unwrap(); let raw_tx_bytes = hex_bytes(&raw_tx_hex[2..]).unwrap(); let mut cursor = io::Cursor::new(&raw_tx_bytes); - let raw_tx = - StacksTransaction::consensus_deserialize_with_epoch(&mut cursor, epoch_id) - .unwrap(); + let raw_tx = StacksTransaction::consensus_deserialize(&mut cursor).unwrap(); if found_block_height { if submit_tx_count >= mine_max_txns { info!("Reached mine_max_txns {}", submit_tx_count); diff --git a/stackslib/src/net/api/getblock.rs b/stackslib/src/net/api/getblock.rs index 287c0563bf..406cd67bc3 100644 --- a/stackslib/src/net/api/getblock.rs +++ b/stackslib/src/net/api/getblock.rs @@ -20,7 +20,7 @@ use std::{fs, io}; use regex::{Captures, Regex}; use serde::de::Error as de_Error; -use stacks_common::codec::{DeserializeWithEpoch, StacksMessageCodec, MAX_MESSAGE_LEN}; +use stacks_common::codec::{StacksMessageCodec, MAX_MESSAGE_LEN}; use stacks_common::types::chainstate::StacksBlockId; use stacks_common::types::net::PeerHost; use stacks_common::types::StacksEpochId; @@ -303,10 +303,7 @@ impl StacksHttpResponse { // contents will be raw bytes let block_bytes: Vec = contents.try_into()?; - let block = StacksBlock::consensus_deserialize_with_epoch( - &mut &block_bytes[..], - StacksEpochId::Epoch25, - )?; + let block = StacksBlock::consensus_deserialize(&mut &block_bytes[..])?; Ok(block) } @@ -318,7 +315,7 @@ impl StacksHttpResponse { // contents will be raw bytes let block_bytes: Vec = contents.try_into()?; - let block = StacksBlock::consensus_deserialize_with_epoch(&mut &block_bytes[..], epoch_id)?; + let block = StacksBlock::consensus_deserialize(&mut &block_bytes[..])?; Ok(block) } diff --git a/stackslib/src/net/api/getblock_v3.rs b/stackslib/src/net/api/getblock_v3.rs index ab359f8ecb..344aa6d746 100644 --- a/stackslib/src/net/api/getblock_v3.rs +++ b/stackslib/src/net/api/getblock_v3.rs @@ -20,7 +20,7 @@ use std::{fs, io}; use regex::{Captures, Regex}; use rusqlite::Connection; use serde::de::Error as de_Error; -use stacks_common::codec::{DeserializeWithEpoch, StacksMessageCodec, MAX_MESSAGE_LEN}; +use stacks_common::codec::{StacksMessageCodec, MAX_MESSAGE_LEN}; use stacks_common::types::chainstate::{ConsensusHash, StacksBlockId}; use stacks_common::types::net::PeerHost; use stacks_common::types::StacksEpochId; @@ -318,10 +318,7 @@ impl StacksHttpResponse { // contents will be raw bytes let block_bytes: Vec = contents.try_into()?; - let block = NakamotoBlock::consensus_deserialize_with_epoch( - &mut &block_bytes[..], - StacksEpochId::latest(), - )?; + let block = NakamotoBlock::consensus_deserialize(&mut &block_bytes[..])?; Ok(block) } diff --git a/stackslib/src/net/api/gettenure.rs b/stackslib/src/net/api/gettenure.rs index 17bd3b8a73..4bd1d362f5 100644 --- a/stackslib/src/net/api/gettenure.rs +++ b/stackslib/src/net/api/gettenure.rs @@ -19,7 +19,7 @@ use std::{fs, io}; use regex::{Captures, Regex}; use serde::de::Error as de_Error; -use stacks_common::codec::{DeserializeWithEpoch, StacksMessageCodec, MAX_MESSAGE_LEN}; +use stacks_common::codec::{StacksMessageCodec, MAX_MESSAGE_LEN}; use stacks_common::types::chainstate::{ConsensusHash, StacksBlockId}; use stacks_common::types::net::PeerHost; use stacks_common::types::StacksEpochId; @@ -356,8 +356,7 @@ impl StacksHttpResponse { let mut blocks = vec![]; while ptr.len() > 0 { - let block = - NakamotoBlock::consensus_deserialize_with_epoch(ptr, StacksEpochId::latest())?; + let block = NakamotoBlock::consensus_deserialize(ptr)?; blocks.push(block); } diff --git a/stackslib/src/net/api/postblock.rs b/stackslib/src/net/api/postblock.rs index 155c39ed4c..bf0b010879 100644 --- a/stackslib/src/net/api/postblock.rs +++ b/stackslib/src/net/api/postblock.rs @@ -18,7 +18,7 @@ use std::io::{Read, Write}; use clarity::vm::costs::ExecutionCost; use regex::{Captures, Regex}; -use stacks_common::codec::{DeserializeWithEpoch, Error as CodecError, MAX_PAYLOAD_LEN}; +use stacks_common::codec::{Error as CodecError, StacksMessageCodec, MAX_PAYLOAD_LEN}; use stacks_common::types::chainstate::{ BlockHeaderHash, ConsensusHash, StacksBlockId, StacksPublicKey, }; @@ -74,18 +74,13 @@ impl RPCPostBlockRequestHandler { /// Decode a bare block from the body fn parse_postblock_octets(mut body: &[u8]) -> Result { - let block = - StacksBlock::consensus_deserialize_with_epoch(&mut body, StacksEpochId::Epoch25) - .map_err(|e| { - if let CodecError::DeserializeError(msg) = e { - Error::DecodeError(format!( - "Failed to deserialize posted transaction: {}", - msg - )) - } else { - e.into() - } - })?; + let block = StacksBlock::consensus_deserialize(&mut body).map_err(|e| { + if let CodecError::DeserializeError(msg) = e { + Error::DecodeError(format!("Failed to deserialize posted transaction: {}", msg)) + } else { + e.into() + } + })?; Ok(block) } } diff --git a/stackslib/src/net/api/posttransaction.rs b/stackslib/src/net/api/posttransaction.rs index c68027b1ae..3e6cea3038 100644 --- a/stackslib/src/net/api/posttransaction.rs +++ b/stackslib/src/net/api/posttransaction.rs @@ -18,9 +18,7 @@ use std::io::{Read, Write}; use clarity::vm::costs::ExecutionCost; use regex::{Captures, Regex}; -use stacks_common::codec::{ - DeserializeWithEpoch, Error as CodecError, StacksMessageCodec, MAX_PAYLOAD_LEN, -}; +use stacks_common::codec::{Error as CodecError, StacksMessageCodec, MAX_PAYLOAD_LEN}; use stacks_common::types::chainstate::{ BlockHeaderHash, ConsensusHash, StacksBlockId, StacksPublicKey, }; @@ -70,18 +68,13 @@ impl RPCPostTransactionRequestHandler { /// Decode a bare transaction from the body fn parse_posttransaction_octets(mut body: &[u8]) -> Result { - let tx = - StacksTransaction::consensus_deserialize_with_epoch(&mut body, StacksEpochId::latest()) - .map_err(|e| { - if let CodecError::DeserializeError(msg) = e { - Error::DecodeError(format!( - "Failed to deserialize posted transaction: {}", - msg - )) - } else { - e.into() - } - })?; + let tx = StacksTransaction::consensus_deserialize(&mut body).map_err(|e| { + if let CodecError::DeserializeError(msg) = e { + Error::DecodeError(format!("Failed to deserialize posted transaction: {}", msg)) + } else { + e.into() + } + })?; Ok(tx) } @@ -95,11 +88,7 @@ impl RPCPostTransactionRequestHandler { let tx = { let tx_bytes = hex_bytes(&body.tx) .map_err(|_e| Error::DecodeError("Failed to parse tx".into()))?; - StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_bytes[..], - StacksEpochId::latest(), - ) - .map_err(|e| { + StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).map_err(|e| { if let CodecError::DeserializeError(msg) = e { Error::DecodeError(format!("Failed to deserialize posted transaction: {}", msg)) } else { diff --git a/stackslib/src/net/api/tests/getblock.rs b/stackslib/src/net/api/tests/getblock.rs index 9dce17b252..54eb79e7b5 100644 --- a/stackslib/src/net/api/tests/getblock.rs +++ b/stackslib/src/net/api/tests/getblock.rs @@ -18,7 +18,6 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use clarity::vm::types::{QualifiedContractIdentifier, StacksAddressExtensions}; use clarity::vm::{ClarityName, ContractName}; -use stacks_common::codec::DeserializeWithEpoch; use stacks_common::types::chainstate::{ ConsensusHash, StacksAddress, StacksBlockId, StacksPrivateKey, }; @@ -161,11 +160,7 @@ fn test_stream_blocks() { } // should decode back into the block - let staging_block = StacksBlock::consensus_deserialize_with_epoch( - &mut &all_block_bytes[..], - StacksEpochId::Epoch25, - ) - .unwrap(); + let staging_block = StacksBlock::consensus_deserialize(&mut &all_block_bytes[..]).unwrap(); assert_eq!(staging_block, block); // accept it @@ -190,10 +185,6 @@ fn test_stream_blocks() { } // should decode back into the block - let staging_block = StacksBlock::consensus_deserialize_with_epoch( - &mut &all_block_bytes[..], - StacksEpochId::Epoch25, - ) - .unwrap(); + let staging_block = StacksBlock::consensus_deserialize(&mut &all_block_bytes[..]).unwrap(); assert_eq!(staging_block, block); } diff --git a/stackslib/src/net/api/tests/getblock_v3.rs b/stackslib/src/net/api/tests/getblock_v3.rs index b24a81f815..4bd22f65f1 100644 --- a/stackslib/src/net/api/tests/getblock_v3.rs +++ b/stackslib/src/net/api/tests/getblock_v3.rs @@ -18,7 +18,7 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use clarity::vm::types::{QualifiedContractIdentifier, StacksAddressExtensions}; use clarity::vm::{ClarityName, ContractName}; -use stacks_common::codec::{DeserializeWithEpoch, StacksMessageCodec}; +use stacks_common::codec::StacksMessageCodec; use stacks_common::types::chainstate::{ ConsensusHash, StacksAddress, StacksBlockId, StacksPrivateKey, }; @@ -183,10 +183,6 @@ fn test_stream_nakamoto_blocks() { all_block_bytes.append(&mut next_bytes); } - let staging_block = NakamotoBlock::consensus_deserialize_with_epoch( - &mut &all_block_bytes[..], - StacksEpochId::latest(), - ) - .unwrap(); + let staging_block = NakamotoBlock::consensus_deserialize(&mut &all_block_bytes[..]).unwrap(); assert_eq!(staging_block.header.block_id(), nakamoto_tip_block_id); } diff --git a/stackslib/src/net/api/tests/gettenure.rs b/stackslib/src/net/api/tests/gettenure.rs index 1b3a280510..c7dcd9f3ea 100644 --- a/stackslib/src/net/api/tests/gettenure.rs +++ b/stackslib/src/net/api/tests/gettenure.rs @@ -18,7 +18,7 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use clarity::vm::types::{QualifiedContractIdentifier, StacksAddressExtensions}; use clarity::vm::{ClarityName, ContractName}; -use stacks_common::codec::{DeserializeWithEpoch, StacksMessageCodec}; +use stacks_common::codec::StacksMessageCodec; use stacks_common::types::chainstate::{ ConsensusHash, StacksAddress, StacksBlockId, StacksPrivateKey, }; @@ -192,8 +192,7 @@ fn test_stream_nakamoto_tenure() { let ptr = &mut all_block_bytes.as_slice(); let mut blocks = vec![]; while ptr.len() > 0 { - let block = - NakamotoBlock::consensus_deserialize_with_epoch(ptr, StacksEpochId::latest()).unwrap(); + let block = NakamotoBlock::consensus_deserialize(ptr).unwrap(); blocks.push(block); } diff --git a/stackslib/src/net/codec.rs b/stackslib/src/net/codec.rs index 802b355fb6..fa1c588695 100644 --- a/stackslib/src/net/codec.rs +++ b/stackslib/src/net/codec.rs @@ -1554,7 +1554,7 @@ impl ProtocolFamily for StacksP2P { #[cfg(test)] pub mod test { use stacks_common::bitvec::BitVec; - use stacks_common::codec::{DeserializeWithEpoch, NEIGHBOR_ADDRESS_ENCODED_SIZE}; + use stacks_common::codec::NEIGHBOR_ADDRESS_ENCODED_SIZE; use stacks_common::types::StacksEpochId; use stacks_common::util::hash::hex_bytes; use stacks_common::util::secp256k1::*; @@ -1668,62 +1668,6 @@ pub mod test { } } - pub fn check_codec_and_corruption_with_epoch< - T: StacksMessageCodec + fmt::Debug + Clone + PartialEq + DeserializeWithEpoch, - >( - obj: &T, - bytes: &Vec, - epoch_id: StacksEpochId, - ) -> () { - // obj should serialize to bytes - let mut write_buf: Vec = Vec::with_capacity(bytes.len()); - obj.consensus_serialize(&mut write_buf).unwrap(); - assert_eq!(write_buf, *bytes); - - // bytes should deserialize to obj - let read_buf: Vec = write_buf.clone(); - let res = T::consensus_deserialize_with_epoch(&mut &read_buf[..], epoch_id); - match res { - Ok(out) => { - assert_eq!(out, *obj); - } - Err(e) => { - test_debug!("\nFailed to parse to {:?}: {:?}", obj, bytes); - test_debug!("error: {:?}", &e); - assert!(false); - } - } - - // short message shouldn't parse, but should EOF - if write_buf.len() > 0 { - let mut short_buf = write_buf.clone(); - let short_len = short_buf.len() - 1; - short_buf.truncate(short_len); - - let underflow_res = T::consensus_deserialize_with_epoch(&mut &short_buf[..], epoch_id); - match underflow_res { - Ok(oops) => { - test_debug!( - "\nMissing Underflow: Parsed {:?}\nFrom {:?}\n", - &oops, - &write_buf[0..short_len].to_vec() - ); - } - Err(codec_error::ReadError(io_error)) => match io_error.kind() { - io::ErrorKind::UnexpectedEof => {} - _ => { - test_debug!("Got unexpected I/O error: {:?}", &io_error); - assert!(false); - } - }, - Err(e) => { - test_debug!("Got unexpected Net error: {:?}", &e); - assert!(false); - } - }; - } - } - #[test] fn codec_primitive_types() { check_codec_and_corruption::(&0x01, &vec![0x01]); diff --git a/testnet/stacks-node/src/node.rs b/testnet/stacks-node/src/node.rs index 118db772ec..77117a6822 100644 --- a/testnet/stacks-node/src/node.rs +++ b/testnet/stacks-node/src/node.rs @@ -921,7 +921,7 @@ impl Node { &metadata.anchored_header.block_hash(), ) .unwrap(); - StacksChainState::consensus_load_with_epoch(&block_path, stacks_epoch.epoch_id).unwrap() + StacksChainState::consensus_load(&block_path).unwrap() }; let chain_tip = ChainTip { diff --git a/testnet/stacks-node/src/tests/epoch_205.rs b/testnet/stacks-node/src/tests/epoch_205.rs index 252c2981f1..05bdcea419 100644 --- a/testnet/stacks-node/src/tests/epoch_205.rs +++ b/testnet/stacks-node/src/tests/epoch_205.rs @@ -18,7 +18,6 @@ use stacks::core::{ StacksEpoch, StacksEpochId, PEER_VERSION_EPOCH_1_0, PEER_VERSION_EPOCH_2_0, PEER_VERSION_EPOCH_2_05, PEER_VERSION_EPOCH_2_1, }; -use stacks_common::codec::DeserializeWithEpoch; use stacks_common::types::chainstate::{ BlockHeaderHash, BurnchainHeaderHash, StacksAddress, VRFSeed, }; @@ -231,11 +230,7 @@ fn test_exact_block_costs() { .filter_map(|tx| { let raw_tx = tx.get("raw_tx").unwrap().as_str().unwrap(); let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_bytes[..], - StacksEpochId::Epoch2_05, - ) - .unwrap(); + let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); if let TransactionPayload::ContractCall(ref cc) = &parsed.payload { if cc.function_name.as_str() == "db-get2" { Some(parsed) @@ -425,11 +420,7 @@ fn test_dynamic_db_method_costs() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_bytes[..], - StacksEpochId::Epoch2_05, - ) - .unwrap(); + let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); if let TransactionPayload::ContractCall(ref cc) = parsed.payload { assert_eq!( @@ -1172,11 +1163,7 @@ fn bigger_microblock_streams_in_2_05() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_bytes[..], - StacksEpochId::Epoch2_05, - ) - .unwrap(); + let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); if let TransactionPayload::SmartContract(tsc, ..) = parsed.payload { if tsc.name.to_string().find("costs-2").is_some() { in_205 = true; diff --git a/testnet/stacks-node/src/tests/epoch_21.rs b/testnet/stacks-node/src/tests/epoch_21.rs index f1cc653e0a..14db80f0b1 100644 --- a/testnet/stacks-node/src/tests/epoch_21.rs +++ b/testnet/stacks-node/src/tests/epoch_21.rs @@ -26,7 +26,6 @@ use stacks::clarity_cli::vm_execute as execute; use stacks::core; use stacks::core::BURNCHAIN_TX_SEARCH_WINDOW; use stacks::util_lib::boot::boot_code_id; -use stacks_common::codec::DeserializeWithEpoch; use stacks_common::types::chainstate::{ BlockHeaderHash, BurnchainHeaderHash, StacksAddress, StacksBlockId, VRFSeed, }; @@ -385,10 +384,9 @@ fn transition_adds_burn_block_height() { ); submit_tx(&http_origin, &tx); - let cc_txid = - StacksTransaction::consensus_deserialize_with_epoch(&mut &tx[..], StacksEpochId::Epoch21) - .unwrap() - .txid(); + let cc_txid = StacksTransaction::consensus_deserialize(&mut &tx[..]) + .unwrap() + .txid(); // mine it next_block_and_wait(&mut btc_regtest_controller, &blocks_processed); @@ -407,11 +405,7 @@ fn transition_adds_burn_block_height() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_bytes[..], - StacksEpochId::Epoch21, - ) - .unwrap(); + let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); if parsed.txid() == cc_txid { // check events for this block for event in events.iter() { @@ -1227,12 +1221,9 @@ fn transition_adds_get_pox_addr_recipients() { "test-get-pox-addrs", &[Value::UInt((stack_sort_height).into())], ); - let cc_txid = StacksTransaction::consensus_deserialize_with_epoch( - &mut &cc_tx[..], - StacksEpochId::Epoch21, - ) - .unwrap() - .txid(); + let cc_txid = StacksTransaction::consensus_deserialize(&mut &cc_tx[..]) + .unwrap() + .txid(); submit_tx(&http_origin, &cc_tx); next_block_and_wait(&mut btc_regtest_controller, &blocks_processed); @@ -1251,11 +1242,7 @@ fn transition_adds_get_pox_addr_recipients() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_bytes[..], - StacksEpochId::Epoch21, - ) - .unwrap(); + let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); if parsed.txid() == cc_txid { // check events for this block for (_i, event) in events.iter().enumerate() { @@ -1985,11 +1972,7 @@ fn transition_empty_blocks() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_bytes[..], - StacksEpochId::Epoch21, - ) - .unwrap(); + let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); if let TransactionPayload::SmartContract(tsc, ..) = parsed.payload { if tsc.name == "pox-2".into() { have_pox2 = true; @@ -4910,11 +4893,7 @@ fn trait_invocation_cross_epoch() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_bytes[..], - StacksEpochId::Epoch21, - ) - .unwrap(); + let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); if interesting_txids.contains(&parsed.txid().to_string()) { eprintln!( "{} => {}", diff --git a/testnet/stacks-node/src/tests/epoch_22.rs b/testnet/stacks-node/src/tests/epoch_22.rs index 1e52921848..4e387d6304 100644 --- a/testnet/stacks-node/src/tests/epoch_22.rs +++ b/testnet/stacks-node/src/tests/epoch_22.rs @@ -11,7 +11,6 @@ use stacks::clarity_cli::vm_execute as execute; use stacks::core; use stacks::core::STACKS_EPOCH_MAX; use stacks::util_lib::boot::boot_code_id; -use stacks_common::codec::DeserializeWithEpoch; use stacks_common::types::chainstate::{StacksAddress, StacksBlockId}; use stacks_common::types::PrivateKey; use stacks_common::util::hash::Hash160; @@ -541,11 +540,8 @@ fn disable_pox() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize_with_epoch( - &mut tx_bytes.as_slice(), - StacksEpochId::Epoch22, - ) - .unwrap(); + let parsed = + StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); let tx_sender = PrincipalData::from(parsed.auth.origin().address_testnet()); if &tx_sender == &spender_addr && parsed.auth.get_origin_nonce() == aborted_increase_nonce @@ -1201,11 +1197,8 @@ fn pox_2_unlock_all() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize_with_epoch( - &mut tx_bytes.as_slice(), - StacksEpochId::Epoch22, - ) - .unwrap(); + let parsed = + StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); let tx_sender = PrincipalData::from(parsed.auth.origin().address_testnet()); if &tx_sender == &spender_addr && parsed.auth.get_origin_nonce() == nonce_of_2_2_unlock_ht_call diff --git a/testnet/stacks-node/src/tests/epoch_23.rs b/testnet/stacks-node/src/tests/epoch_23.rs index 6f1ef70ba7..470eda9672 100644 --- a/testnet/stacks-node/src/tests/epoch_23.rs +++ b/testnet/stacks-node/src/tests/epoch_23.rs @@ -20,7 +20,6 @@ use clarity::vm::types::{PrincipalData, QualifiedContractIdentifier}; use stacks::burnchains::{Burnchain, PoxConstants}; use stacks::core; use stacks::core::STACKS_EPOCH_MAX; -use stacks_common::codec::DeserializeWithEpoch; use stacks_common::util::sleep_ms; use crate::config::{EventKeyType, EventObserverConfig, InitialBalance}; @@ -509,11 +508,8 @@ fn trait_invocation_behavior() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize_with_epoch( - &mut tx_bytes.as_slice(), - StacksEpochId::Epoch23, - ) - .unwrap(); + let parsed = + StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); let tx_sender = PrincipalData::from(parsed.auth.origin().address_testnet()); if &tx_sender == &spender_addr { let contract_call = match &parsed.payload { diff --git a/testnet/stacks-node/src/tests/epoch_24.rs b/testnet/stacks-node/src/tests/epoch_24.rs index b4e30da04c..d98178f946 100644 --- a/testnet/stacks-node/src/tests/epoch_24.rs +++ b/testnet/stacks-node/src/tests/epoch_24.rs @@ -28,7 +28,6 @@ use stacks::chainstate::stacks::{Error, StacksTransaction, TransactionPayload}; use stacks::clarity_cli::vm_execute as execute; use stacks::core; use stacks_common::address::{AddressHashMode, C32_ADDRESS_VERSION_TESTNET_SINGLESIG}; -use stacks_common::codec::DeserializeWithEpoch; use stacks_common::consts::STACKS_EPOCH_MAX; use stacks_common::types::chainstate::{StacksAddress, StacksBlockId, StacksPrivateKey}; use stacks_common::types::{Address, StacksEpochId}; @@ -645,11 +644,8 @@ fn fix_to_pox_contract() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize_with_epoch( - &mut tx_bytes.as_slice(), - StacksEpochId::Epoch24, - ) - .unwrap(); + let parsed = + StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); let tx_sender = PrincipalData::from(parsed.auth.origin().address_testnet()); if &tx_sender == &spender_addr && (parsed.auth.get_origin_nonce() == aborted_increase_nonce_2_2 diff --git a/testnet/stacks-node/src/tests/integrations.rs b/testnet/stacks-node/src/tests/integrations.rs index b13f6351b3..a0fa7408f4 100644 --- a/testnet/stacks-node/src/tests/integrations.rs +++ b/testnet/stacks-node/src/tests/integrations.rs @@ -22,7 +22,7 @@ use stacks::chainstate::stacks::{ TransactionContractCall, TransactionPayload, }; use stacks::clarity_vm::clarity::ClarityConnection; -use stacks::codec::{DeserializeWithEpoch, StacksMessageCodec}; +use stacks::codec::StacksMessageCodec; use stacks::core::mempool::MAXIMUM_MEMPOOL_TX_CHAINING; use stacks::core::{ StacksEpoch, StacksEpochId, PEER_VERSION_EPOCH_2_0, PEER_VERSION_EPOCH_2_05, @@ -832,7 +832,7 @@ fn integration_test_get_info() { let tx_xfer_invalid = make_stacks_transfer(&spender_sk, (round + 30).into(), 200, // bad nonce &StacksAddress::from_string(ADDR_4).unwrap().into(), 456); - let tx_xfer_invalid_tx = StacksTransaction::consensus_deserialize_with_epoch(&mut &tx_xfer_invalid[..], StacksEpochId::latest()).unwrap(); + let tx_xfer_invalid_tx = StacksTransaction::consensus_deserialize(&mut &tx_xfer_invalid[..]).unwrap(); let res = client.post(&path) .header("Content-Type", "application/octet-stream") @@ -1197,11 +1197,9 @@ fn contract_stx_transfer() { &contract_identifier.clone().into(), 1000, ); - let xfer_to_contract = StacksTransaction::consensus_deserialize_with_epoch( - &mut &xfer_to_contract[..], - StacksEpochId::latest(), - ) - .unwrap(); + let xfer_to_contract = + StacksTransaction::consensus_deserialize(&mut &xfer_to_contract[..]) + .unwrap(); tenure .mem_pool .submit( @@ -1219,11 +1217,8 @@ fn contract_stx_transfer() { // this one should fail because the nonce is already in the mempool let xfer_to_contract = make_stacks_transfer(&sk_3, 3, 190, &contract_identifier.clone().into(), 1000); - let xfer_to_contract = StacksTransaction::consensus_deserialize_with_epoch( - &mut &xfer_to_contract[..], - StacksEpochId::latest(), - ) - .unwrap(); + let xfer_to_contract = + StacksTransaction::consensus_deserialize(&mut &xfer_to_contract[..]).unwrap(); match tenure .mem_pool .submit( @@ -2184,11 +2179,8 @@ fn mempool_errors() { &send_to, 456, ); - let tx_xfer_invalid_tx = StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_xfer_invalid[..], - StacksEpochId::latest(), - ) - .unwrap(); + let tx_xfer_invalid_tx = + StacksTransaction::consensus_deserialize(&mut &tx_xfer_invalid[..]).unwrap(); let res = client .post(&path) @@ -2228,11 +2220,8 @@ fn mempool_errors() { &send_to, 456, ); - let tx_xfer_invalid_tx = StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_xfer_invalid[..], - StacksEpochId::latest(), - ) - .unwrap(); + let tx_xfer_invalid_tx = + StacksTransaction::consensus_deserialize(&mut &tx_xfer_invalid[..]).unwrap(); let res = client .post(&path) @@ -2264,11 +2253,8 @@ fn mempool_errors() { &send_to, 456, ); - let tx_xfer_invalid_tx = StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_xfer_invalid[..], - StacksEpochId::latest(), - ) - .unwrap(); + let tx_xfer_invalid_tx = + StacksTransaction::consensus_deserialize(&mut &tx_xfer_invalid[..]).unwrap(); let res = client .post(&path) @@ -2311,11 +2297,8 @@ fn mempool_errors() { &send_to, 1000, ); - let tx_xfer_invalid_tx = StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_xfer_invalid[..], - StacksEpochId::latest(), - ) - .unwrap(); + let tx_xfer_invalid_tx = + StacksTransaction::consensus_deserialize(&mut &tx_xfer_invalid[..]).unwrap(); let res = client .post(&path) diff --git a/testnet/stacks-node/src/tests/mempool.rs b/testnet/stacks-node/src/tests/mempool.rs index 5d6ab19601..6221c6cf11 100644 --- a/testnet/stacks-node/src/tests/mempool.rs +++ b/testnet/stacks-node/src/tests/mempool.rs @@ -13,7 +13,7 @@ use stacks::chainstate::stacks::{ TransactionAnchorMode, TransactionAuth, TransactionPayload, TransactionSpendingCondition, TransactionVersion, C32_ADDRESS_VERSION_MAINNET_SINGLESIG, }; -use stacks::codec::{DeserializeWithEpoch, StacksMessageCodec}; +use stacks::codec::StacksMessageCodec; use stacks::core::mempool::MemPoolDB; use stacks::core::{StacksEpochId, CHAIN_ID_TESTNET}; use stacks::cost_estimates::metrics::UnitMetric; @@ -236,11 +236,8 @@ fn mempool_setup_chainstate() { // first a couple valid ones: let tx_bytes = make_contract_publish(&contract_sk, 5, 1000, "bar_contract", FOO_CONTRACT); - let tx = StacksTransaction::consensus_deserialize_with_epoch( - &mut tx_bytes.as_slice(), - StacksEpochId::Epoch25, - ) - .unwrap(); + let tx = + StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -260,11 +257,8 @@ fn mempool_setup_chainstate() { "bar", &[Value::UInt(1)], ); - let tx = StacksTransaction::consensus_deserialize_with_epoch( - &mut tx_bytes.as_slice(), - StacksEpochId::Epoch25, - ) - .unwrap(); + let tx = + StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -276,11 +270,8 @@ fn mempool_setup_chainstate() { .unwrap(); let tx_bytes = make_stacks_transfer(&contract_sk, 5, 200, &other_addr, 1000); - let tx = StacksTransaction::consensus_deserialize_with_epoch( - &mut tx_bytes.as_slice(), - StacksEpochId::Epoch25, - ) - .unwrap(); + let tx = + StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -293,11 +284,8 @@ fn mempool_setup_chainstate() { // bad signature let tx_bytes = make_bad_stacks_transfer(&contract_sk, 5, 200, &other_addr, 1000); - let tx = StacksTransaction::consensus_deserialize_with_epoch( - &mut tx_bytes.as_slice(), - StacksEpochId::Epoch25, - ) - .unwrap(); + let tx = + StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); let e = chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -338,11 +326,8 @@ fn mempool_setup_chainstate() { "bar", &[Value::UInt(1), Value::Int(2)], ); - let tx = StacksTransaction::consensus_deserialize_with_epoch( - &mut tx_bytes.as_slice(), - StacksEpochId::Epoch25, - ) - .unwrap(); + let tx = + StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); let e = chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -370,11 +355,8 @@ fn mempool_setup_chainstate() { .into(); let tx_bytes = make_stacks_transfer(&contract_sk, 5, 200, &bad_addr, 1000); - let tx = StacksTransaction::consensus_deserialize_with_epoch( - &mut tx_bytes.as_slice(), - StacksEpochId::Epoch25, - ) - .unwrap(); + let tx = + StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); let e = chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -392,11 +374,8 @@ fn mempool_setup_chainstate() { // bad fees let tx_bytes = make_stacks_transfer(&contract_sk, 5, 0, &other_addr, 1000); - let tx = StacksTransaction::consensus_deserialize_with_epoch( - &mut tx_bytes.as_slice(), - StacksEpochId::Epoch25, - ) - .unwrap(); + let tx = + StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); let e = chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -415,11 +394,8 @@ fn mempool_setup_chainstate() { // bad nonce let tx_bytes = make_stacks_transfer(&contract_sk, 0, 200, &other_addr, 1000); - let tx = StacksTransaction::consensus_deserialize_with_epoch( - &mut tx_bytes.as_slice(), - StacksEpochId::Epoch25, - ) - .unwrap(); + let tx = + StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); let e = chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -438,11 +414,8 @@ fn mempool_setup_chainstate() { // not enough funds let tx_bytes = make_stacks_transfer(&contract_sk, 5, 110000, &other_addr, 1000); - let tx = StacksTransaction::consensus_deserialize_with_epoch( - &mut tx_bytes.as_slice(), - StacksEpochId::Epoch25, - ) - .unwrap(); + let tx = + StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); let e = chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -462,11 +435,8 @@ fn mempool_setup_chainstate() { // sender == recipient let contract_princ = PrincipalData::from(contract_addr.clone()); let tx_bytes = make_stacks_transfer(&contract_sk, 5, 300, &contract_princ, 1000); - let tx = StacksTransaction::consensus_deserialize_with_epoch( - &mut tx_bytes.as_slice(), - StacksEpochId::Epoch25, - ) - .unwrap(); + let tx = + StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); let e = chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -488,11 +458,8 @@ fn mempool_setup_chainstate() { mainnet_recipient.version = C32_ADDRESS_VERSION_MAINNET_SINGLESIG; let mainnet_princ = mainnet_recipient.into(); let tx_bytes = make_stacks_transfer(&contract_sk, 5, 300, &mainnet_princ, 1000); - let tx = StacksTransaction::consensus_deserialize_with_epoch( - &mut tx_bytes.as_slice(), - StacksEpochId::Epoch25, - ) - .unwrap(); + let tx = + StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); let e = chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -524,11 +491,8 @@ fn mempool_setup_chainstate() { TransactionAnchorMode::OnChainOnly, TransactionVersion::Mainnet, ); - let tx = StacksTransaction::consensus_deserialize_with_epoch( - &mut tx_bytes.as_slice(), - StacksEpochId::Epoch25, - ) - .unwrap(); + let tx = + StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); let e = chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -547,11 +511,8 @@ fn mempool_setup_chainstate() { // send amount must be positive let tx_bytes = make_stacks_transfer(&contract_sk, 5, 300, &other_addr, 0); - let tx = StacksTransaction::consensus_deserialize_with_epoch( - &mut tx_bytes.as_slice(), - StacksEpochId::Epoch25, - ) - .unwrap(); + let tx = + StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); let e = chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -570,11 +531,8 @@ fn mempool_setup_chainstate() { // not enough funds let tx_bytes = make_stacks_transfer(&contract_sk, 5, 110000, &other_addr, 1000); - let tx = StacksTransaction::consensus_deserialize_with_epoch( - &mut tx_bytes.as_slice(), - StacksEpochId::Epoch25, - ) - .unwrap(); + let tx = + StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); let e = chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -592,11 +550,8 @@ fn mempool_setup_chainstate() { }); let tx_bytes = make_stacks_transfer(&contract_sk, 5, 99700, &other_addr, 1000); - let tx = StacksTransaction::consensus_deserialize_with_epoch( - &mut tx_bytes.as_slice(), - StacksEpochId::Epoch25, - ) - .unwrap(); + let tx = + StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); let e = chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -622,11 +577,8 @@ fn mempool_setup_chainstate() { "bar", &[Value::UInt(1)], ); - let tx = StacksTransaction::consensus_deserialize_with_epoch( - &mut tx_bytes.as_slice(), - StacksEpochId::Epoch25, - ) - .unwrap(); + let tx = + StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); let e = chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -652,11 +604,8 @@ fn mempool_setup_chainstate() { "foobar", &[Value::UInt(1)], ); - let tx = StacksTransaction::consensus_deserialize_with_epoch( - &mut tx_bytes.as_slice(), - StacksEpochId::Epoch25, - ) - .unwrap(); + let tx = + StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); let e = chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -682,11 +631,8 @@ fn mempool_setup_chainstate() { "bar", &[Value::UInt(1), Value::Int(2)], ); - let tx = StacksTransaction::consensus_deserialize_with_epoch( - &mut tx_bytes.as_slice(), - StacksEpochId::Epoch25, - ) - .unwrap(); + let tx = + StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); let e = chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -705,11 +651,8 @@ fn mempool_setup_chainstate() { let tx_bytes = make_contract_publish(&contract_sk, 5, 1000, "foo_contract", FOO_CONTRACT); - let tx = StacksTransaction::consensus_deserialize_with_epoch( - &mut tx_bytes.as_slice(), - StacksEpochId::Epoch25, - ) - .unwrap(); + let tx = + StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); let e = chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -743,11 +686,8 @@ fn mempool_setup_chainstate() { }; let tx_bytes = make_poison(&contract_sk, 5, 1000, microblock_1, microblock_2); - let tx = StacksTransaction::consensus_deserialize_with_epoch( - &mut tx_bytes.as_slice(), - StacksEpochId::Epoch25, - ) - .unwrap(); + let tx = + StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); let e = chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -777,11 +717,8 @@ fn mempool_setup_chainstate() { }; let tx_bytes = make_poison(&contract_sk, 5, 1000, microblock_1, microblock_2); - let tx = StacksTransaction::consensus_deserialize_with_epoch( - &mut tx_bytes.as_slice(), - StacksEpochId::Epoch25, - ) - .unwrap(); + let tx = + StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); let e = chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -814,11 +751,8 @@ fn mempool_setup_chainstate() { microblock_2.sign(&other_sk).unwrap(); let tx_bytes = make_poison(&contract_sk, 5, 1000, microblock_1, microblock_2); - let tx = StacksTransaction::consensus_deserialize_with_epoch( - &mut tx_bytes.as_slice(), - StacksEpochId::Epoch25, - ) - .unwrap(); + let tx = + StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); let e = chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -832,11 +766,8 @@ fn mempool_setup_chainstate() { assert!(matches!(e, MemPoolRejection::Other(_))); let tx_bytes = make_coinbase(&contract_sk, 5, 1000); - let tx = StacksTransaction::consensus_deserialize_with_epoch( - &mut tx_bytes.as_slice(), - StacksEpochId::Epoch25, - ) - .unwrap(); + let tx = + StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); let e = chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -893,11 +824,8 @@ fn mempool_setup_chainstate() { microblock_2.sign(&secret_key).unwrap(); let tx_bytes = make_poison(&contract_sk, 5, 1000, microblock_1, microblock_2); - let tx = StacksTransaction::consensus_deserialize_with_epoch( - &mut tx_bytes.as_slice(), - StacksEpochId::Epoch25, - ) - .unwrap(); + let tx = + StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); let e = chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -925,11 +853,8 @@ fn mempool_setup_chainstate() { "baz", &[Value::Principal(contract_principal)], ); - let tx = StacksTransaction::consensus_deserialize_with_epoch( - &mut tx_bytes.as_slice(), - StacksEpochId::Epoch25, - ) - .unwrap(); + let tx = + StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, @@ -955,11 +880,8 @@ fn mempool_setup_chainstate() { "baz", &[Value::Principal(contract_principal)], ); - let tx = StacksTransaction::consensus_deserialize_with_epoch( - &mut tx_bytes.as_slice(), - StacksEpochId::Epoch25, - ) - .unwrap(); + let tx = + StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); let e = chain_state .will_admit_mempool_tx( &NULL_BURN_STATE_DB, diff --git a/testnet/stacks-node/src/tests/mod.rs b/testnet/stacks-node/src/tests/mod.rs index 182eee6ec2..ddcbaf70f7 100644 --- a/testnet/stacks-node/src/tests/mod.rs +++ b/testnet/stacks-node/src/tests/mod.rs @@ -35,7 +35,7 @@ use stacks::chainstate::stacks::{ TransactionPostConditionMode, TransactionSmartContract, TransactionSpendingCondition, TransactionVersion, C32_ADDRESS_VERSION_TESTNET_SINGLESIG, }; -use stacks::codec::{DeserializeWithEpoch, StacksMessageCodec}; +use stacks::codec::StacksMessageCodec; use stacks::core::{StacksEpoch, StacksEpochExtension, StacksEpochId, CHAIN_ID_TESTNET}; use stacks::util_lib::strings::StacksString; use stacks_common::address::AddressHashMode; @@ -472,9 +472,7 @@ pub fn select_transactions_where( for tx in transactions.iter() { let raw_tx = tx.get("raw_tx").unwrap().as_str().unwrap(); let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = - StacksTransaction::consensus_deserialize_with_epoch(&mut &tx_bytes[..], epoch_id) - .unwrap(); + let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); if test_fn(&parsed) { result.push(parsed); } diff --git a/testnet/stacks-node/src/tests/neon_integrations.rs b/testnet/stacks-node/src/tests/neon_integrations.rs index 1f2107fd01..b06f723dcf 100644 --- a/testnet/stacks-node/src/tests/neon_integrations.rs +++ b/testnet/stacks-node/src/tests/neon_integrations.rs @@ -36,7 +36,7 @@ use stacks::chainstate::stacks::{ StacksPublicKey, StacksTransaction, TransactionContractCall, TransactionPayload, }; use stacks::clarity_cli::vm_execute as execute; -use stacks::codec::{DeserializeWithEpoch, StacksMessageCodec}; +use stacks::codec::StacksMessageCodec; use stacks::core::mempool::MemPoolWalkTxTypes; use stacks::core::{ self, StacksEpoch, StacksEpochId, BLOCK_LIMIT_MAINNET_20, BLOCK_LIMIT_MAINNET_205, @@ -817,9 +817,7 @@ pub fn get_block(http_origin: &str, block_id: &StacksBlockId) -> Option = res.bytes().unwrap().to_vec(); - let block = - StacksBlock::consensus_deserialize_with_epoch(&mut &res[..], StacksEpochId::latest()) - .unwrap(); + let block = StacksBlock::consensus_deserialize(&mut &res[..]).unwrap(); Some(block) } else { None @@ -858,11 +856,7 @@ pub fn get_tip_anchored_block(conf: &Config) -> (ConsensusHash, StacksBlock) { let client = reqwest::blocking::Client::new(); let path = format!("{}/v2/blocks/{}", &http_origin, &stacks_id_tip); let block_bytes = client.get(&path).send().unwrap().bytes().unwrap(); - let block = StacksBlock::consensus_deserialize_with_epoch( - &mut block_bytes.as_ref(), - StacksEpochId::latest(), - ) - .unwrap(); + let block = StacksBlock::consensus_deserialize(&mut block_bytes.as_ref()).unwrap(); (stacks_tip_consensus_hash, block) } @@ -1419,11 +1413,7 @@ fn deep_contract() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_bytes[..], - StacksEpochId::latest(), - ) - .unwrap(); + let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); if let TransactionPayload::SmartContract(..) = parsed.payload { included_smart_contract = true; } @@ -1636,11 +1626,7 @@ fn liquid_ustx_integration() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_bytes[..], - StacksEpochId::latest(), - ) - .unwrap(); + let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); if let TransactionPayload::ContractCall(contract_call) = parsed.payload { eprintln!("{}", contract_call.function_name.as_str()); if contract_call.function_name.as_str() == "execute" { @@ -3411,18 +3397,12 @@ fn microblock_fork_poison_integration_test() { let recipient = StacksAddress::from_string(ADDR_4).unwrap(); let unconfirmed_tx_bytes = make_stacks_transfer_mblock_only(&spender_sk, 0, 1000, &recipient.into(), 1000); - let unconfirmed_tx = StacksTransaction::consensus_deserialize_with_epoch( - &mut &unconfirmed_tx_bytes[..], - StacksEpochId::latest(), - ) - .unwrap(); + let unconfirmed_tx = + StacksTransaction::consensus_deserialize(&mut &unconfirmed_tx_bytes[..]).unwrap(); let second_unconfirmed_tx_bytes = make_stacks_transfer_mblock_only(&second_spender_sk, 0, 1000, &recipient.into(), 1500); - let second_unconfirmed_tx = StacksTransaction::consensus_deserialize_with_epoch( - &mut &second_unconfirmed_tx_bytes[..], - StacksEpochId::latest(), - ) - .unwrap(); + let second_unconfirmed_tx = + StacksTransaction::consensus_deserialize(&mut &second_unconfirmed_tx_bytes[..]).unwrap(); // TODO (hack) instantiate the sortdb in the burnchain let _ = btc_regtest_controller.sortdb_mut(); @@ -3537,11 +3517,7 @@ fn microblock_fork_poison_integration_test() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_bytes[..], - StacksEpochId::latest(), - ) - .unwrap(); + let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); if let TransactionPayload::PoisonMicroblock(..) = &parsed.payload { found = true; @@ -3679,18 +3655,12 @@ fn microblock_integration_test() { let recipient = StacksAddress::from_string(ADDR_4).unwrap(); let unconfirmed_tx_bytes = make_stacks_transfer_mblock_only(&spender_sk, 1, 1000, &recipient.into(), 1000); - let unconfirmed_tx = StacksTransaction::consensus_deserialize_with_epoch( - &mut &unconfirmed_tx_bytes[..], - StacksEpochId::latest(), - ) - .unwrap(); + let unconfirmed_tx = + StacksTransaction::consensus_deserialize(&mut &unconfirmed_tx_bytes[..]).unwrap(); let second_unconfirmed_tx_bytes = make_stacks_transfer_mblock_only(&second_spender_sk, 0, 1000, &recipient.into(), 1500); - let second_unconfirmed_tx = StacksTransaction::consensus_deserialize_with_epoch( - &mut &second_unconfirmed_tx_bytes[..], - StacksEpochId::latest(), - ) - .unwrap(); + let second_unconfirmed_tx = + StacksTransaction::consensus_deserialize(&mut &second_unconfirmed_tx_bytes[..]).unwrap(); // TODO (hack) instantiate the sortdb in the burnchain let _ = btc_regtest_controller.sortdb_mut(); @@ -4374,11 +4344,7 @@ fn size_check_integration_test() { "large-0", &giant_contract, ); - let parsed_tx = StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx[..], - StacksEpochId::latest(), - ) - .unwrap(); + let parsed_tx = StacksTransaction::consensus_deserialize(&mut &tx[..]).unwrap(); debug!("Mine transaction {} in a microblock", &parsed_tx.txid()); tx } @@ -4680,11 +4646,7 @@ fn size_overflow_unconfirmed_microblocks_integration_test() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_bytes[..], - StacksEpochId::latest(), - ) - .unwrap(); + let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); if let TransactionPayload::SmartContract(tsc, ..) = parsed.payload { if tsc.name.to_string().find("large-").is_some() { num_big_anchored_txs += 1; @@ -4886,11 +4848,7 @@ fn size_overflow_unconfirmed_stream_microblocks_integration_test() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_bytes[..], - StacksEpochId::latest(), - ) - .unwrap(); + let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); if let TransactionPayload::SmartContract(tsc, ..) = parsed.payload { if tsc.name.to_string().find("small").is_some() { num_big_microblock_txs += 1; @@ -5065,11 +5023,7 @@ fn size_overflow_unconfirmed_invalid_stream_microblocks_integration_test() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_bytes[..], - StacksEpochId::latest(), - ) - .unwrap(); + let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); if let TransactionPayload::SmartContract(tsc, ..) = parsed.payload { if tsc.name.to_string().find("small").is_some() { num_big_microblock_txs += 1; @@ -5348,11 +5302,7 @@ fn runtime_overflow_unconfirmed_microblocks_integration_test() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_bytes[..], - StacksEpochId::latest(), - ) - .unwrap(); + let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); eprintln!("tx: {:?}", &parsed); if let TransactionPayload::SmartContract(tsc, ..) = parsed.payload { if tsc.name.to_string().find("large-").is_some() { @@ -5672,11 +5622,7 @@ fn cost_voting_integration() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_bytes[..], - StacksEpochId::latest(), - ) - .unwrap(); + let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); if let TransactionPayload::ContractCall(contract_call) = parsed.payload { eprintln!("{}", contract_call.function_name.as_str()); if contract_call.function_name.as_str() == "execute-2" { @@ -5725,11 +5671,7 @@ fn cost_voting_integration() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_bytes[..], - StacksEpochId::latest(), - ) - .unwrap(); + let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); if let TransactionPayload::ContractCall(contract_call) = parsed.payload { eprintln!("{}", contract_call.function_name.as_str()); if contract_call.function_name.as_str() == "confirm-miners" { @@ -5778,11 +5720,7 @@ fn cost_voting_integration() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_bytes[..], - StacksEpochId::latest(), - ) - .unwrap(); + let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); if let TransactionPayload::ContractCall(contract_call) = parsed.payload { eprintln!("{}", contract_call.function_name.as_str()); if contract_call.function_name.as_str() == "confirm-miners" { @@ -5827,11 +5765,7 @@ fn cost_voting_integration() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_bytes[..], - StacksEpochId::latest(), - ) - .unwrap(); + let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); if let TransactionPayload::ContractCall(contract_call) = parsed.payload { eprintln!("{}", contract_call.function_name.as_str()); if contract_call.function_name.as_str() == "execute-2" { @@ -7068,11 +7002,7 @@ fn pox_integration_test() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_bytes[..], - StacksEpochId::latest(), - ) - .unwrap(); + let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); if let TransactionPayload::ContractCall(contract_call) = parsed.payload { eprintln!("{}", contract_call.function_name.as_str()); if contract_call.function_name.as_str() == "stack-stx" { @@ -9198,16 +9128,8 @@ fn use_latest_tip_integration_test() { let publish_tx = make_contract_publish_microblock_only(&spender_sk, 1, 1000, "caller", caller_src); - let tx_1 = StacksTransaction::consensus_deserialize_with_epoch( - &mut &transfer_tx[..], - StacksEpochId::latest(), - ) - .unwrap(); - let tx_2 = StacksTransaction::consensus_deserialize_with_epoch( - &mut &publish_tx[..], - StacksEpochId::latest(), - ) - .unwrap(); + let tx_1 = StacksTransaction::consensus_deserialize(&mut &transfer_tx[..]).unwrap(); + let tx_2 = StacksTransaction::consensus_deserialize(&mut &publish_tx[..]).unwrap(); let vec_tx = vec![tx_1, tx_2]; let privk = find_microblock_privkey(&conf, &stacks_block.header.microblock_pubkey_hash, 1024).unwrap(); @@ -9554,12 +9476,9 @@ fn test_problematic_txs_are_not_stored() { "test-edge", &tx_edge_body, ); - let tx_edge_txid = StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_edge[..], - StacksEpochId::latest(), - ) - .unwrap() - .txid(); + let tx_edge_txid = StacksTransaction::consensus_deserialize(&mut &tx_edge[..]) + .unwrap() + .txid(); // something just over the limit of the expression depth let exceeds_repeat_factor = edge_repeat_factor + 1; @@ -9574,12 +9493,9 @@ fn test_problematic_txs_are_not_stored() { "test-exceeds", &tx_exceeds_body, ); - let tx_exceeds_txid = StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_exceeds[..], - StacksEpochId::latest(), - ) - .unwrap() - .txid(); + let tx_exceeds_txid = StacksTransaction::consensus_deserialize(&mut &tx_exceeds[..]) + .unwrap() + .txid(); // something stupidly high over the expression depth let high_repeat_factor = 128 * 1024; @@ -9594,12 +9510,9 @@ fn test_problematic_txs_are_not_stored() { "test-high", &tx_high_body, ); - let tx_high_txid = StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_high[..], - StacksEpochId::latest(), - ) - .unwrap() - .txid(); + let tx_high_txid = StacksTransaction::consensus_deserialize(&mut &tx_high[..]) + .unwrap() + .txid(); btc_regtest_controller.bootstrap_chain(201); @@ -9813,12 +9726,9 @@ fn test_problematic_blocks_are_not_mined() { "test-exceeds", &tx_exceeds_body, ); - let tx_exceeds_txid = StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_exceeds[..], - StacksEpochId::latest(), - ) - .unwrap() - .txid(); + let tx_exceeds_txid = StacksTransaction::consensus_deserialize(&mut &tx_exceeds[..]) + .unwrap() + .txid(); // something stupidly high over the expression depth let high_repeat_factor = 3200; @@ -9833,12 +9743,9 @@ fn test_problematic_blocks_are_not_mined() { "test-high", &tx_high_body, ); - let tx_high_txid = StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_high[..], - StacksEpochId::latest(), - ) - .unwrap() - .txid(); + let tx_high_txid = StacksTransaction::consensus_deserialize(&mut &tx_high[..]) + .unwrap() + .txid(); btc_regtest_controller.bootstrap_chain(201); @@ -9910,11 +9817,7 @@ fn test_problematic_blocks_are_not_mined() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_bytes[..], - StacksEpochId::latest(), - ) - .unwrap(); + let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); if let TransactionPayload::SmartContract(..) = &parsed.payload { if parsed.txid() == tx_exceeds_txid { found = true; @@ -10011,11 +9914,7 @@ fn test_problematic_blocks_are_not_mined() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_bytes[..], - StacksEpochId::latest(), - ) - .unwrap(); + let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); if let TransactionPayload::SmartContract(..) = &parsed.payload { assert!(parsed.txid() != tx_high_txid); } @@ -10185,12 +10084,9 @@ fn test_problematic_blocks_are_not_relayed_or_stored() { "test-exceeds", &tx_exceeds_body, ); - let tx_exceeds_txid = StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_exceeds[..], - StacksEpochId::latest(), - ) - .unwrap() - .txid(); + let tx_exceeds_txid = StacksTransaction::consensus_deserialize(&mut &tx_exceeds[..]) + .unwrap() + .txid(); let high_repeat_factor = 70; let tx_high_body_start = "{ a : ".repeat(high_repeat_factor as usize); @@ -10204,12 +10100,9 @@ fn test_problematic_blocks_are_not_relayed_or_stored() { "test-high", &tx_high_body, ); - let tx_high_txid = StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_high[..], - StacksEpochId::latest(), - ) - .unwrap() - .txid(); + let tx_high_txid = StacksTransaction::consensus_deserialize(&mut &tx_high[..]) + .unwrap() + .txid(); btc_regtest_controller.bootstrap_chain(201); @@ -10281,11 +10174,7 @@ fn test_problematic_blocks_are_not_relayed_or_stored() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_bytes[..], - StacksEpochId::latest(), - ) - .unwrap(); + let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); if let TransactionPayload::SmartContract(..) = &parsed.payload { if parsed.txid() == tx_exceeds_txid { found = true; @@ -10411,11 +10300,7 @@ fn test_problematic_blocks_are_not_relayed_or_stored() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_bytes[..], - StacksEpochId::latest(), - ) - .unwrap(); + let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); if let TransactionPayload::SmartContract(..) = &parsed.payload { if parsed.txid() == tx_high_txid { bad_block_height = Some(block.get("block_height").unwrap().as_u64().unwrap()); @@ -10598,12 +10483,9 @@ fn test_problematic_microblocks_are_not_mined() { "test-exceeds", &tx_exceeds_body, ); - let tx_exceeds_txid = StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_exceeds[..], - StacksEpochId::latest(), - ) - .unwrap() - .txid(); + let tx_exceeds_txid = StacksTransaction::consensus_deserialize(&mut &tx_exceeds[..]) + .unwrap() + .txid(); // something stupidly high over the expression depth let high_repeat_factor = @@ -10619,12 +10501,9 @@ fn test_problematic_microblocks_are_not_mined() { "test-high", &tx_high_body, ); - let tx_high_txid = StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_high[..], - StacksEpochId::latest(), - ) - .unwrap() - .txid(); + let tx_high_txid = StacksTransaction::consensus_deserialize(&mut &tx_high[..]) + .unwrap() + .txid(); btc_regtest_controller.bootstrap_chain(201); @@ -10703,11 +10582,7 @@ fn test_problematic_microblocks_are_not_mined() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_bytes[..], - StacksEpochId::latest(), - ) - .unwrap(); + let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); if let TransactionPayload::SmartContract(..) = &parsed.payload { if parsed.txid() == tx_exceeds_txid { found = true; @@ -10816,11 +10691,7 @@ fn test_problematic_microblocks_are_not_mined() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_bytes[..], - StacksEpochId::latest(), - ) - .unwrap(); + let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); if let TransactionPayload::SmartContract(..) = &parsed.payload { assert_ne!(parsed.txid(), tx_high_txid); } @@ -10998,12 +10869,9 @@ fn test_problematic_microblocks_are_not_relayed_or_stored() { "test-exceeds", &tx_exceeds_body, ); - let tx_exceeds_txid = StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_exceeds[..], - StacksEpochId::latest(), - ) - .unwrap() - .txid(); + let tx_exceeds_txid = StacksTransaction::consensus_deserialize(&mut &tx_exceeds[..]) + .unwrap() + .txid(); // greatly exceeds AST depth, but is still mineable without a stack overflow let high_repeat_factor = @@ -11019,12 +10887,9 @@ fn test_problematic_microblocks_are_not_relayed_or_stored() { "test-high", &tx_high_body, ); - let tx_high_txid = StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_high[..], - StacksEpochId::latest(), - ) - .unwrap() - .txid(); + let tx_high_txid = StacksTransaction::consensus_deserialize(&mut &tx_high[..]) + .unwrap() + .txid(); btc_regtest_controller.bootstrap_chain(201); @@ -11099,11 +10964,7 @@ fn test_problematic_microblocks_are_not_relayed_or_stored() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_bytes[..], - StacksEpochId::latest(), - ) - .unwrap(); + let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); if let TransactionPayload::SmartContract(..) = &parsed.payload { if parsed.txid() == tx_exceeds_txid { found = true; @@ -11232,11 +11093,7 @@ fn test_problematic_microblocks_are_not_relayed_or_stored() { continue; } let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); - let parsed = StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_bytes[..], - StacksEpochId::latest(), - ) - .unwrap(); + let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap(); if let TransactionPayload::SmartContract(..) = &parsed.payload { if parsed.txid() == tx_high_txid { bad_block_id = { From 758c58a44cbdb2297406abad6f03f067c66c901c Mon Sep 17 00:00:00 2001 From: Fess Date: Thu, 4 Apr 2024 19:39:39 +0400 Subject: [PATCH 10/41] fix: remove logic for epoch gating --- libsigner/src/messages.rs | 1 - stackslib/src/chainstate/stacks/block.rs | 97 ++++++------------- stackslib/src/chainstate/stacks/db/blocks.rs | 72 ++++---------- .../src/chainstate/stacks/db/transactions.rs | 4 +- stackslib/src/chainstate/stacks/mod.rs | 2 +- .../stacks/tests/chain_histories.rs | 21 ++-- .../src/chainstate/stacks/transaction.rs | 1 + stackslib/src/core/mempool.rs | 7 -- stackslib/src/main.rs | 37 +------ stackslib/src/net/api/getblock.rs | 12 --- stackslib/src/net/api/tests/getblock.rs | 4 +- .../src/net/api/tests/postmempoolquery.rs | 33 +++---- stackslib/src/net/relay.rs | 37 +++++-- testnet/stacks-node/src/tests/epoch_205.rs | 4 +- testnet/stacks-node/src/tests/epoch_24.rs | 3 +- testnet/stacks-node/src/tests/integrations.rs | 2 +- testnet/stacks-node/src/tests/mod.rs | 3 +- .../src/tests/neon_integrations.rs | 81 ++++++---------- 18 files changed, 140 insertions(+), 281 deletions(-) diff --git a/libsigner/src/messages.rs b/libsigner/src/messages.rs index 6f644b9ef6..86a1306ddc 100644 --- a/libsigner/src/messages.rs +++ b/libsigner/src/messages.rs @@ -363,7 +363,6 @@ impl StacksMessageCodec for SignerMessage { SignerMessage::BlockResponse(block_response) } SignerMessageTypePrefix::Transactions => { - // I don't think these messages are stored on the blockchain, so `StacksEpochId::latest()` should be fine let transactions: Vec = { let mut bound_read = BoundReader::from_reader(fd, MAX_MESSAGE_LEN as u64); read_next_at_most(&mut bound_read, u32::MAX) diff --git a/stackslib/src/chainstate/stacks/block.rs b/stackslib/src/chainstate/stacks/block.rs index 674dbc66d6..872e4fe9d2 100644 --- a/stackslib/src/chainstate/stacks/block.rs +++ b/stackslib/src/chainstate/stacks/block.rs @@ -41,15 +41,6 @@ use crate::chainstate::stacks::{Error, StacksBlockHeader, StacksMicroblockHeader use crate::core::*; use crate::net::Error as net_error; -/// Quietable error -macro_rules! qerror { - ($cond: expr, $($arg:tt)*) => ({ - if ($cond) { - error!($($arg)*); - } - }); -} - impl StacksMessageCodec for StacksBlockHeader { fn consensus_serialize(&self, fd: &mut W) -> Result<(), codec_error> { write_next(fd, &self.version)?; @@ -583,33 +574,40 @@ impl StacksBlock { if let TransactionPayload::Coinbase(_, ref recipient_opt, ref proof_opt) = &tx.payload { if proof_opt.is_some() && epoch_id < StacksEpochId::Epoch30 { // not supported - qerror!(quiet, "Coinbase with VRF proof not supported before Stacks 3.0"; "txid" => %tx.txid()); + if !quiet { + error!("Coinbase with VRF proof not supported before Stacks 3.0"; "txid" => %tx.txid()); + } return false; } if proof_opt.is_none() && epoch_id >= StacksEpochId::Epoch30 { // not supported - qerror!(quiet, "Coinbase with VRF proof is required in Stacks 3.0 and later"; "txid" => %tx.txid()); + if !quiet { + error!("Coinbase with VRF proof is required in Stacks 3.0 and later"; "txid" => %tx.txid()); + } return false; } if recipient_opt.is_some() && epoch_id < StacksEpochId::Epoch21 { // not supported - qerror!(quiet, "Coinbase pay-to-alt-recipient not supported before Stacks 2.1"; "txid" => %tx.txid()); + if !quiet { + error!("Coinbase pay-to-alt-recipient not supported before Stacks 2.1"; "txid" => %tx.txid()); + } return false; } } if let TransactionPayload::SmartContract(_, ref version_opt) = &tx.payload { if version_opt.is_some() && epoch_id < StacksEpochId::Epoch21 { // not supported - qerror!( - quiet, - "Versioned smart contracts not supported before Stacks 2.1" - ); + if !quiet { + error!("Versioned smart contracts not supported before Stacks 2.1"); + } return false; } } if let TransactionPayload::TenureChange(..) = &tx.payload { if epoch_id < StacksEpochId::Epoch30 { - qerror!(quiet, "TenureChange transaction not supported before Stacks 3.0"; "txid" => %tx.txid()); + if !quiet { + error!("TenureChange transaction not supported before Stacks 3.0"; "txid" => %tx.txid()); + } return false; } } @@ -618,7 +616,9 @@ impl StacksBlock { match origin { TransactionSpendingCondition::OrderIndependentMultisig(..) => { if epoch_id < StacksEpochId::Epoch30 { - qerror!(quiet, "Order independent multisig transactions not supported before Stacks 3.0"); + if !quiet { + error!("Order independent multisig transactions not supported before Stacks 3.0"); + } return false; } } @@ -627,7 +627,9 @@ impl StacksBlock { match sponsor { TransactionSpendingCondition::OrderIndependentMultisig(..) => { if epoch_id < StacksEpochId::Epoch30 { - qerror!(quiet, "Order independent multisig transactions not supported before Stacks 3.0"); + if !quiet { + error!("Order independent multisig transactions not supported before Stacks 3.0"); + } return false; } } @@ -637,7 +639,9 @@ impl StacksBlock { TransactionAuth::Standard(ref origin) => match origin { TransactionSpendingCondition::OrderIndependentMultisig(..) => { if epoch_id < StacksEpochId::Epoch30 { - qerror!(quiet, "Order independent multisig transactions not supported before Stacks 3.0"); + if !quiet { + error!("Order independent multisig transactions not supported before Stacks 3.0"); + } return false; } } @@ -1587,14 +1591,11 @@ mod test { for (ref block, ref msg) in invalid_blocks.iter() { let mut bytes: Vec = vec![]; block.consensus_serialize(&mut bytes).unwrap(); - assert!(StacksBlock::consensus_deserialize_with_epoch( - &mut &bytes[..], - StacksEpochId::Epoch25, - ) - .unwrap_err() - .to_string() - .find(msg) - .is_some()); + assert!(StacksBlock::consensus_deserialize(&mut &bytes[..]) + .unwrap_err() + .to_string() + .find(msg) + .is_some()); } } @@ -1803,52 +1804,12 @@ mod test { epoch_id.clone(), false, )); - - for tx in txs.iter() { - let mut bytes: Vec = vec![]; - tx.consensus_serialize(&mut bytes).unwrap(); - - assert!(StacksTransaction::consensus_deserialize_with_epoch( - &mut &bytes[..], - *epoch_id - ) - .unwrap_err() - .to_string() - .find("target epoch is not activated") - .is_some()); - } - - assert!( - StacksBlock::consensus_deserialize_with_epoch(&mut &bytes[..], *epoch_id) - .unwrap_err() - .to_string() - .find("target epoch is not activated") - .is_some() - ); } else if deactivation_epoch_id.is_none() || deactivation_epoch_id.unwrap() > *epoch_id { assert!(StacksBlock::validate_transactions_static_epoch( &txs, *epoch_id, false, )); - - for tx in txs.iter() { - let mut bytes: Vec = vec![]; - tx.consensus_serialize(&mut bytes).unwrap(); - - StacksTransaction::consensus_deserialize(&mut &bytes[..]).unwrap(); - } - - StacksBlock::consensus_deserialize(&mut &bytes[..]).unwrap(); } else { - for tx in txs.iter() { - let mut bytes: Vec = vec![]; - tx.consensus_serialize(&mut bytes).unwrap(); - - let _ = StacksTransaction::consensus_deserialize(&mut &bytes[..]).unwrap_err(); - } - - let _ = StacksBlock::consensus_deserialize(&mut &bytes[..]).unwrap_err(); - assert!(!StacksBlock::validate_transactions_static_epoch( &txs, *epoch_id, false, )); diff --git a/stackslib/src/chainstate/stacks/db/blocks.rs b/stackslib/src/chainstate/stacks/db/blocks.rs index 31680023bd..5b6b9d6e91 100644 --- a/stackslib/src/chainstate/stacks/db/blocks.rs +++ b/stackslib/src/chainstate/stacks/db/blocks.rs @@ -827,20 +827,6 @@ impl StacksChainState { blocks_dir: &str, consensus_hash: &ConsensusHash, block_hash: &BlockHeaderHash, - ) -> Result, Error> { - StacksChainState::load_block_with_epoch( - blocks_dir, - consensus_hash, - block_hash, - StacksEpochId::latest(), - ) - } - - pub fn load_block_with_epoch( - blocks_dir: &str, - consensus_hash: &ConsensusHash, - block_hash: &BlockHeaderHash, - epoch_id: StacksEpochId, ) -> Result, Error> { let block_path = StacksChainState::get_block_path(blocks_dir, consensus_hash, block_hash)?; let sz = StacksChainState::get_file_size(&block_path)?; @@ -5971,10 +5957,7 @@ impl StacksChainState { } /// Extract and parse the block from a loaded staging block, and verify its integrity. - pub fn extract_stacks_block( - next_staging_block: &StagingBlock, - epoch_id: StacksEpochId, - ) -> Result { + pub fn extract_stacks_block(next_staging_block: &StagingBlock) -> Result { let block = { StacksBlock::consensus_deserialize(&mut &next_staging_block.block_data[..]) .map_err(Error::CodecError)? @@ -6142,13 +6125,7 @@ impl StacksChainState { None => return Ok((None, None)), }; - let epoch_id = SortitionDB::get_stacks_epoch(sort_tx, burn_header_height as u64)? - .expect(&format!( - "FATAL: no epoch defined at burn height {}", - burn_header_height - )) - .epoch_id; - let block = StacksChainState::extract_stacks_block(&next_staging_block, epoch_id)?; + let block = StacksChainState::extract_stacks_block(&next_staging_block)?; let block_size = u64::try_from(next_staging_block.block_data.len()) .expect("FATAL: more than 2^64 transactions"); @@ -6689,10 +6666,7 @@ impl StacksChainState { // 4: check if transaction is valid in the current epoch let epoch = clarity_connection.get_epoch().clone(); - let is_transaction_valid_in_epoch = - StacksBlock::validate_transactions_static_epoch(&[tx.clone()], epoch, true); - - if !is_transaction_valid_in_epoch { + if !StacksBlock::validate_transactions_static_epoch(&[tx.clone()], epoch, true) { return Err(MemPoolRejection::Other( "Transaction is not supported in this epoch".to_string(), )); @@ -7305,11 +7279,10 @@ pub mod test { &block.block_hash() ) .unwrap()); - assert!(StacksChainState::load_block_with_epoch( + assert!(StacksChainState::load_block( &chainstate.blocks_path, consensus_hash, - &block.block_hash(), - StacksEpochId::Epoch25, + &block.block_hash() ) .unwrap() .is_none()); @@ -7367,20 +7340,18 @@ pub mod test { &block.block_hash() ) .unwrap()); - assert!(StacksChainState::load_block_with_epoch( + assert!(StacksChainState::load_block( &chainstate.blocks_path, consensus_hash, - &block.block_hash(), - StacksEpochId::Epoch25, + &block.block_hash() ) .unwrap() .is_some()); assert_eq!( - StacksChainState::load_block_with_epoch( + StacksChainState::load_block( &chainstate.blocks_path, consensus_hash, - &block.block_hash(), - StacksEpochId::Epoch25, + &block.block_hash() ) .unwrap() .unwrap(), @@ -7642,11 +7613,10 @@ pub mod test { &BlockHeaderHash([2u8; 32]) ) .unwrap()); - assert!(StacksChainState::load_block_with_epoch( + assert!(StacksChainState::load_block( &chainstate.blocks_path, &ConsensusHash([1u8; 20]), - &BlockHeaderHash([2u8; 32]), - StacksEpochId::Epoch25, + &BlockHeaderHash([2u8; 32]) ) .unwrap() .is_none()); @@ -7713,20 +7683,18 @@ pub mod test { ) .unwrap()); - assert!(StacksChainState::load_block_with_epoch( + assert!(StacksChainState::load_block( &chainstate.blocks_path, &ConsensusHash([1u8; 20]), - &block.block_hash(), - StacksEpochId::Epoch25, + &block.block_hash() ) .unwrap() .is_some()); assert_eq!( - StacksChainState::load_block_with_epoch( + StacksChainState::load_block( &chainstate.blocks_path, &ConsensusHash([1u8; 20]), - &block.block_hash(), - StacksEpochId::Epoch25, + &block.block_hash() ) .unwrap() .unwrap(), @@ -7757,11 +7725,10 @@ pub mod test { &block.block_hash() ) .unwrap()); - assert!(StacksChainState::load_block_with_epoch( + assert!(StacksChainState::load_block( &chainstate.blocks_path, &ConsensusHash([1u8; 20]), - &block.block_hash(), - StacksEpochId::Epoch25, + &block.block_hash() ) .unwrap() .is_none()); @@ -7800,11 +7767,10 @@ pub mod test { ) .unwrap()); - assert!(StacksChainState::load_block_with_epoch( + assert!(StacksChainState::load_block( &chainstate.blocks_path, &ConsensusHash([1u8; 20]), - &block.block_hash(), - StacksEpochId::Epoch25, + &block.block_hash() ) .unwrap() .is_none()); diff --git a/stackslib/src/chainstate/stacks/db/transactions.rs b/stackslib/src/chainstate/stacks/db/transactions.rs index f67468bc6f..cb2c569a2a 100644 --- a/stackslib/src/chainstate/stacks/db/transactions.rs +++ b/stackslib/src/chainstate/stacks/db/transactions.rs @@ -1479,10 +1479,8 @@ impl StacksChainState { return Err(Error::InvalidStacksTransaction(msg, false)); } } - let is_transaction_valid_in_epoch = - StacksBlock::validate_transactions_static_epoch(&vec![tx.clone()], epoch, quiet); - if !is_transaction_valid_in_epoch { + if !StacksBlock::validate_transactions_static_epoch(&vec![tx.clone()], epoch, quiet) { let msg = format!( "Invalid transaction {}: target epoch is not activated", tx.txid() diff --git a/stackslib/src/chainstate/stacks/mod.rs b/stackslib/src/chainstate/stacks/mod.rs index 396b2140b2..4cb958a248 100644 --- a/stackslib/src/chainstate/stacks/mod.rs +++ b/stackslib/src/chainstate/stacks/mod.rs @@ -1584,7 +1584,7 @@ pub mod test { origin_auth.clone(), TransactionPayload::Coinbase(CoinbasePayload([0u8; 32]), None, None), ); - let mut tx_coinbase_proof = StacksTransaction::new( + let tx_coinbase_proof = StacksTransaction::new( TransactionVersion::Mainnet, origin_auth.clone(), TransactionPayload::Coinbase(CoinbasePayload([0u8; 32]), None, Some(proof.clone())), diff --git a/stackslib/src/chainstate/stacks/tests/chain_histories.rs b/stackslib/src/chainstate/stacks/tests/chain_histories.rs index 830470b223..09b75047cc 100644 --- a/stackslib/src/chainstate/stacks/tests/chain_histories.rs +++ b/stackslib/src/chainstate/stacks/tests/chain_histories.rs @@ -33,7 +33,6 @@ use rand::seq::SliceRandom; use rand::{thread_rng, Rng}; use stacks_common::address::*; use stacks_common::types::chainstate::SortitionId; -use stacks_common::types::StacksEpochId; use stacks_common::util::hash::MerkleTree; use stacks_common::util::sleep_ms; use stacks_common::util::vrf::VRFProof; @@ -2485,20 +2484,12 @@ fn assert_chainstate_blocks_eq(test_name_1: &str, test_name_2: &str) { ) .unwrap(); - let chunk_1_opt = StacksChainState::load_block_with_epoch( - &ch1.blocks_path, - &all_blocks_1[i].0, - &all_blocks_1[i].1, - StacksEpochId::Epoch25, - ) - .unwrap(); - let chunk_2_opt = StacksChainState::load_block_with_epoch( - &ch2.blocks_path, - &all_blocks_2[i].0, - &all_blocks_2[i].1, - StacksEpochId::Epoch25, - ) - .unwrap(); + let chunk_1_opt = + StacksChainState::load_block(&ch1.blocks_path, &all_blocks_1[i].0, &all_blocks_1[i].1) + .unwrap(); + let chunk_2_opt = + StacksChainState::load_block(&ch2.blocks_path, &all_blocks_2[i].0, &all_blocks_2[i].1) + .unwrap(); match (staging_1_opt, staging_2_opt) { (Some(staging_1), Some(staging_2)) => { diff --git a/stackslib/src/chainstate/stacks/transaction.rs b/stackslib/src/chainstate/stacks/transaction.rs index e904ded73a..ffbf187728 100644 --- a/stackslib/src/chainstate/stacks/transaction.rs +++ b/stackslib/src/chainstate/stacks/transaction.rs @@ -1025,6 +1025,7 @@ impl StacksTransaction { Ok(next_sig) } + #[cfg(any(test, feature = "testing"))] pub fn append_sponsor_signature( &mut self, signature: MessageSignature, diff --git a/stackslib/src/core/mempool.rs b/stackslib/src/core/mempool.rs index 4987786d85..08fb9eeec9 100644 --- a/stackslib/src/core/mempool.rs +++ b/stackslib/src/core/mempool.rs @@ -217,13 +217,6 @@ fn parse_mempool_query_page_id( /// optional 32-byte page ID. Obtain both the transactions and page ID, if it exists. pub fn decode_tx_stream( fd: &mut R, -) -> Result<(Vec, Option), net_error> { - decode_tx_stream_with_epoch(fd, StacksEpochId::latest()) -} - -pub fn decode_tx_stream_with_epoch( - fd: &mut R, - epoch_id: StacksEpochId, ) -> Result<(Vec, Option), net_error> { // The wire format is `tx, tx, tx, tx, .., tx, txid`. // The last 32 bytes are the page ID for the next mempool query. diff --git a/stackslib/src/main.rs b/stackslib/src/main.rs index e7f9ee9d84..66641c1646 100644 --- a/stackslib/src/main.rs +++ b/stackslib/src/main.rs @@ -195,7 +195,6 @@ fn main() { let mut cursor = io::Cursor::new(&tx_bytes); let mut debug_cursor = LogReader::from_reader(&mut cursor); - let epoch_id = parse_input_epoch(3); let tx = StacksTransaction::consensus_deserialize(&mut debug_cursor) .map_err(|e| { @@ -224,7 +223,6 @@ fn main() { let block_path = &argv[2]; let block_data = fs::read(block_path).unwrap_or_else(|_| panic!("Failed to open {block_path}")); - let epoch_id = parse_input_epoch(3); let block = StacksBlock::consensus_deserialize(&mut io::Cursor::new(&block_data)) .map_err(|_e| { @@ -295,8 +293,6 @@ fn main() { .unwrap() .expect("No such block"); - let epoch_id = parse_input_epoch(4); - let block = StacksBlock::consensus_deserialize(&mut io::Cursor::new(&block_info.block_data)) .map_err(|_e| { @@ -1362,11 +1358,6 @@ simulating a miner. let estimator = Box::new(UnitEstimator); let metric = Box::new(UnitMetric); - let epoch_id = SortitionDB::get_stacks_epoch(&sort_db.conn(), chain_tip.block_height) - .expect("Error getting Epoch") - .expect("No Epoch found") - .epoch_id; - let mut mempool_db = MemPoolDB::open(true, chain_id, &chain_state_path, estimator, metric) .expect("Failed to open mempool db"); @@ -1569,26 +1560,6 @@ simulating a miner. process::exit(0); } -fn parse_input_epoch(epoch_arg_index: usize) -> StacksEpochId { - let argv: Vec = env::args().collect(); - - let mut epoch_id = StacksEpochId::latest(); - - if argv.len() > epoch_arg_index { - let epoch_id_u32 = argv[epoch_arg_index] - .parse::() - .expect("Failed to parse epoch id"); - epoch_id = StacksEpochId::try_from(epoch_id_u32) - .map_err(|_e| { - eprintln!("Failed to match epoch number"); - process::exit(1); - }) - .unwrap(); - } - - return epoch_id; -} - fn replay_block(stacks_path: &str, index_block_hash_hex: &str) { let index_block_hash = StacksBlockId::from_hex(index_block_hash_hex).unwrap(); let chain_state_path = format!("{stacks_path}/mainnet/chainstate/"); @@ -1674,12 +1645,8 @@ fn replay_block(stacks_path: &str, index_block_hash_hex: &str) { return; }; - let epoch_id = SortitionDB::get_stacks_epoch(&sort_tx, u64::from(burn_header_height)) - .expect("Error getting Epoch") - .expect("No Epoch found") - .epoch_id; - let block = StacksChainState::extract_stacks_block(&next_staging_block, epoch_id) - .expect("Failed to get block"); + let block = + StacksChainState::extract_stacks_block(&next_staging_block).expect("Failed to get block"); let block_size = next_staging_block.block_data.len() as u64; let parent_block_header = match &parent_header_info.anchored_header { diff --git a/stackslib/src/net/api/getblock.rs b/stackslib/src/net/api/getblock.rs index 406cd67bc3..bb75bb5168 100644 --- a/stackslib/src/net/api/getblock.rs +++ b/stackslib/src/net/api/getblock.rs @@ -307,16 +307,4 @@ impl StacksHttpResponse { Ok(block) } - - /// Decode an HTTP response into a block. - /// If it fails, return Self::Error(..) - pub fn decode_block_with_epoch(self, epoch_id: StacksEpochId) -> Result { - let contents = self.get_http_payload_ok()?; - - // contents will be raw bytes - let block_bytes: Vec = contents.try_into()?; - let block = StacksBlock::consensus_deserialize(&mut &block_bytes[..])?; - - Ok(block) - } } diff --git a/stackslib/src/net/api/tests/getblock.rs b/stackslib/src/net/api/tests/getblock.rs index 54eb79e7b5..b3208015ca 100644 --- a/stackslib/src/net/api/tests/getblock.rs +++ b/stackslib/src/net/api/tests/getblock.rs @@ -97,9 +97,7 @@ fn test_try_make_response() { // got the block let response = responses.remove(0); - let resp = response - .decode_block_with_epoch(StacksEpochId::Epoch25) - .unwrap(); + let resp = response.decode_block().unwrap(); assert_eq!( StacksBlockHeader::make_index_block_hash(&consensus_hash, &resp.block_hash()), diff --git a/stackslib/src/net/api/tests/postmempoolquery.rs b/stackslib/src/net/api/tests/postmempoolquery.rs index 620b9b20a1..b669beb2e4 100644 --- a/stackslib/src/net/api/tests/postmempoolquery.rs +++ b/stackslib/src/net/api/tests/postmempoolquery.rs @@ -20,12 +20,12 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use clarity::vm::types::{PrincipalData, QualifiedContractIdentifier, StacksAddressExtensions}; use clarity::vm::{ClarityName, ContractName, Value}; -use stacks_common::codec::{read_next_with_epoch, Error as CodecError, StacksMessageCodec}; +use stacks_common::codec::{Error as CodecError, StacksMessageCodec}; use stacks_common::types::chainstate::{ BlockHeaderHash, ConsensusHash, StacksAddress, StacksPrivateKey, }; use stacks_common::types::net::PeerHost; -use stacks_common::types::{Address, StacksEpochId}; +use stacks_common::types::Address; use stacks_common::util::hash::{to_hex, Hash160}; use super::TestRPC; @@ -214,24 +214,23 @@ fn test_stream_mempool_txs() { let mut decoded_txs = vec![]; let mut ptr = &buf[..]; loop { - let tx: StacksTransaction = - match read_next_with_epoch::(&mut ptr, StacksEpochId::latest()) { - Ok(tx) => tx, - Err(e) => match e { - CodecError::ReadError(ref ioe) => match ioe.kind() { - io::ErrorKind::UnexpectedEof => { - eprintln!("out of transactions"); - break; - } - _ => { - panic!("IO error: {:?}", &e); - } - }, + let tx: StacksTransaction = match read_next::(&mut ptr) { + Ok(tx) => tx, + Err(e) => match e { + CodecError::ReadError(ref ioe) => match ioe.kind() { + io::ErrorKind::UnexpectedEof => { + eprintln!("out of transactions"); + break; + } _ => { - panic!("other error: {:?}", &e); + panic!("IO error: {:?}", &e); } }, - }; + _ => { + panic!("other error: {:?}", &e); + } + }, + }; decoded_txs.push(tx); } diff --git a/stackslib/src/net/relay.rs b/stackslib/src/net/relay.rs index af950c8e8c..d4a6edfe3e 100644 --- a/stackslib/src/net/relay.rs +++ b/stackslib/src/net/relay.rs @@ -5827,8 +5827,21 @@ pub mod test { let sortdb = peer.sortdb.take().unwrap(); let mut node = peer.stacks_node.take().unwrap(); - // transaction with versioned contract was filtered and no error in the block will appear - assert_eq!(stacks_block.txs.len(), 0); + match Relayer::process_new_anchored_block( + &sortdb.index_conn(), + &mut node.chainstate, + &consensus_hash, + &stacks_block, + 123, + ) { + Ok(x) => { + panic!("Stored pay-to-contract stacks block before epoch 2.1"); + } + Err(chainstate_error::InvalidStacksBlock(_)) => {} + Err(e) => { + panic!("Got unexpected error {:?}", &e); + } + }; peer.sortdb = Some(sortdb); peer.stacks_node = Some(node); } @@ -5840,8 +5853,6 @@ pub mod test { let sortdb = peer.sortdb.take().unwrap(); let mut node = peer.stacks_node.take().unwrap(); - // no filtered transactions in epoch 2.1, all valid - assert_eq!(stacks_block.txs.len(), 1); match Relayer::process_new_anchored_block( &sortdb.index_conn(), &mut node.chainstate, @@ -5996,8 +6007,22 @@ pub mod test { let sortdb = peer.sortdb.take().unwrap(); let mut node = peer.stacks_node.take().unwrap(); - // incorrect transaction was filtered and no error in the block will appear - assert_eq!(stacks_block.txs.len(), 1); + match Relayer::process_new_anchored_block( + &sortdb.index_conn(), + &mut node.chainstate, + &consensus_hash, + &stacks_block, + 123, + ) { + Ok(x) => { + eprintln!("{:?}", &stacks_block); + panic!("Stored pay-to-contract stacks block before epoch 2.1"); + } + Err(chainstate_error::InvalidStacksBlock(_)) => {} + Err(e) => { + panic!("Got unexpected error {:?}", &e); + } + }; peer.sortdb = Some(sortdb); peer.stacks_node = Some(node); } diff --git a/testnet/stacks-node/src/tests/epoch_205.rs b/testnet/stacks-node/src/tests/epoch_205.rs index 05bdcea419..0b363081e0 100644 --- a/testnet/stacks-node/src/tests/epoch_205.rs +++ b/testnet/stacks-node/src/tests/epoch_205.rs @@ -18,6 +18,7 @@ use stacks::core::{ StacksEpoch, StacksEpochId, PEER_VERSION_EPOCH_1_0, PEER_VERSION_EPOCH_2_0, PEER_VERSION_EPOCH_2_05, PEER_VERSION_EPOCH_2_1, }; +use stacks_common::codec::StacksMessageCodec; use stacks_common::types::chainstate::{ BlockHeaderHash, BurnchainHeaderHash, StacksAddress, VRFSeed, }; @@ -821,7 +822,6 @@ fn test_cost_limit_switch_version205() { } _ => false, }, - StacksEpochId::Epoch2_05, ); assert_eq!(increment_contract_defines.len(), 1); @@ -853,7 +853,6 @@ fn test_cost_limit_switch_version205() { } _ => false, }, - StacksEpochId::Epoch2_05, ); assert_eq!(increment_calls_alice.len(), 1); @@ -887,7 +886,6 @@ fn test_cost_limit_switch_version205() { } _ => false, }, - StacksEpochId::Epoch2_05, ); assert_eq!(increment_calls_bob.len(), 0); diff --git a/testnet/stacks-node/src/tests/epoch_24.rs b/testnet/stacks-node/src/tests/epoch_24.rs index d98178f946..c5a30e350b 100644 --- a/testnet/stacks-node/src/tests/epoch_24.rs +++ b/testnet/stacks-node/src/tests/epoch_24.rs @@ -30,12 +30,13 @@ use stacks::core; use stacks_common::address::{AddressHashMode, C32_ADDRESS_VERSION_TESTNET_SINGLESIG}; use stacks_common::consts::STACKS_EPOCH_MAX; use stacks_common::types::chainstate::{StacksAddress, StacksBlockId, StacksPrivateKey}; -use stacks_common::types::{Address, StacksEpochId}; +use stacks_common::types::Address; use stacks_common::util::hash::{bytes_to_hex, hex_bytes, Hash160}; use stacks_common::util::secp256k1::Secp256k1PublicKey; use stacks_common::util::sleep_ms; use crate::config::{EventKeyType, EventObserverConfig, InitialBalance}; +use crate::stacks_common::codec::StacksMessageCodec; use crate::tests::bitcoin_regtest::BitcoinCoreController; use crate::tests::neon_integrations::{ get_account, get_chain_info, get_pox_info, neon_integration_test_conf, next_block_and_wait, diff --git a/testnet/stacks-node/src/tests/integrations.rs b/testnet/stacks-node/src/tests/integrations.rs index a0fa7408f4..694d27ca15 100644 --- a/testnet/stacks-node/src/tests/integrations.rs +++ b/testnet/stacks-node/src/tests/integrations.rs @@ -808,7 +808,7 @@ fn integration_test_get_info() { .json() .unwrap(); - assert_eq!(res, format!("{}", StacksTransaction::consensus_deserialize_with_epoch(&mut &tx_xfer[..], StacksEpochId::latest()).unwrap().txid())); + assert_eq!(res, format!("{}", StacksTransaction::consensus_deserialize(&mut &tx_xfer[..]).unwrap().txid())); // let's test a posttransaction call that fails to deserialize, let tx_hex = "80800000000400f942874ce525e87f21bbe8c121b12fac831d02f4000000000000000000000000000003e80001031734446f0870af42bb0cafad27f405e5d9eba441375eada8607a802b875fbb7ba7c4da3474f2bfd76851fb6314a48fe98b57440b8ccec6c9b8362c843a89f303020000000001047465737400000007282b2031203129"; diff --git a/testnet/stacks-node/src/tests/mod.rs b/testnet/stacks-node/src/tests/mod.rs index ddcbaf70f7..b82c3e7524 100644 --- a/testnet/stacks-node/src/tests/mod.rs +++ b/testnet/stacks-node/src/tests/mod.rs @@ -35,10 +35,10 @@ use stacks::chainstate::stacks::{ TransactionPostConditionMode, TransactionSmartContract, TransactionSpendingCondition, TransactionVersion, C32_ADDRESS_VERSION_TESTNET_SINGLESIG, }; -use stacks::codec::StacksMessageCodec; use stacks::core::{StacksEpoch, StacksEpochExtension, StacksEpochId, CHAIN_ID_TESTNET}; use stacks::util_lib::strings::StacksString; use stacks_common::address::AddressHashMode; +use stacks_common::codec::StacksMessageCodec; use stacks_common::types::chainstate::{BlockHeaderHash, StacksAddress}; use stacks_common::util::get_epoch_time_secs; use stacks_common::util::hash::{hex_bytes, to_hex}; @@ -464,7 +464,6 @@ fn make_microblock( pub fn select_transactions_where( blocks: &Vec, test_fn: fn(&StacksTransaction) -> bool, - epoch_id: StacksEpochId, ) -> Vec { let mut result = vec![]; for block in blocks { diff --git a/testnet/stacks-node/src/tests/neon_integrations.rs b/testnet/stacks-node/src/tests/neon_integrations.rs index b06f723dcf..5b192e8c1d 100644 --- a/testnet/stacks-node/src/tests/neon_integrations.rs +++ b/testnet/stacks-node/src/tests/neon_integrations.rs @@ -721,13 +721,10 @@ pub fn submit_tx(http_origin: &str, tx: &Vec) -> String { let res: String = res.json().unwrap(); assert_eq!( res, - StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx[..], - StacksEpochId::latest() - ) - .unwrap() - .txid() - .to_string() + StacksTransaction::consensus_deserialize(&mut &tx[..]) + .unwrap() + .txid() + .to_string() ); return res; } else { @@ -769,12 +766,9 @@ pub fn submit_block( res.stacks_block_id, StacksBlockId::new( consensus_hash, - &StacksBlock::consensus_deserialize_with_epoch( - &mut &block[..], - StacksEpochId::latest() - ) - .unwrap() - .block_hash() + &StacksBlock::consensus_deserialize(&mut &block[..]) + .unwrap() + .block_hash() ) ); return res; @@ -3049,11 +3043,7 @@ fn bitcoind_resubmission_test() { ); let mut garbage_block = StacksMicroblock::first_unsigned( &chain_tip.1, - vec![StacksTransaction::consensus_deserialize_with_epoch( - &mut garbage_tx.as_slice(), - StacksEpochId::latest(), - ) - .unwrap()], + vec![StacksTransaction::consensus_deserialize(&mut garbage_tx.as_slice()).unwrap()], ); garbage_block.header.prev_block = BlockHeaderHash([3; 32]); garbage_block.header.sequence = 1; @@ -3987,13 +3977,10 @@ fn microblock_integration_test() { let res: String = res.json().unwrap(); assert_eq!( res, - StacksTransaction::consensus_deserialize_with_epoch( - &mut &unconfirmed_tx_bytes[..], - StacksEpochId::latest() - ) - .unwrap() - .txid() - .to_string() + StacksTransaction::consensus_deserialize(&mut &unconfirmed_tx_bytes[..]) + .unwrap() + .txid() + .to_string() ); eprintln!("Sent {}", &res); } else { @@ -7416,13 +7403,10 @@ fn atlas_integration_test() { let res: String = res.json().unwrap(); assert_eq!( res, - StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_1[..], - StacksEpochId::latest() - ) - .unwrap() - .txid() - .to_string() + StacksTransaction::consensus_deserialize(&mut &tx_1[..]) + .unwrap() + .txid() + .to_string() ); } else { eprintln!("{}", res.text().unwrap()); @@ -7500,13 +7484,10 @@ fn atlas_integration_test() { let res: String = res.json().unwrap(); assert_eq!( res, - StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_2[..], - StacksEpochId::latest() - ) - .unwrap() - .txid() - .to_string() + StacksTransaction::consensus_deserialize(&mut &tx_2[..]) + .unwrap() + .txid() + .to_string() ); } else { eprintln!("{}", res.text().unwrap()); @@ -8205,13 +8186,10 @@ fn atlas_stress_integration_test() { let res: String = res.json().unwrap(); assert_eq!( res, - StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_1[..], - StacksEpochId::latest() - ) - .unwrap() - .txid() - .to_string() + StacksTransaction::consensus_deserialize(&mut &tx_1[..]) + .unwrap() + .txid() + .to_string() ); } else { eprintln!("{}", res.text().unwrap()); @@ -8289,13 +8267,10 @@ fn atlas_stress_integration_test() { let res: String = res.json().unwrap(); assert_eq!( res, - StacksTransaction::consensus_deserialize_with_epoch( - &mut &tx_2[..], - StacksEpochId::latest() - ) - .unwrap() - .txid() - .to_string() + StacksTransaction::consensus_deserialize(&mut &tx_2[..]) + .unwrap() + .txid() + .to_string() ); } else { eprintln!("{}", res.text().unwrap()); From bb66baee822107ba5d4857c0e546bbaf8eab785d Mon Sep 17 00:00:00 2001 From: Fess Date: Thu, 4 Apr 2024 22:00:57 +0400 Subject: [PATCH 11/41] fix: failing autotests fix and transaction.rs test code refactoring --- stackslib/src/chainstate/nakamoto/mod.rs | 2 +- stackslib/src/chainstate/stacks/auth.rs | 4 +- .../src/chainstate/stacks/transaction.rs | 190 +++++++++--------- stackslib/src/net/relay.rs | 31 +-- 4 files changed, 105 insertions(+), 122 deletions(-) diff --git a/stackslib/src/chainstate/nakamoto/mod.rs b/stackslib/src/chainstate/nakamoto/mod.rs index b55992074a..6786151796 100644 --- a/stackslib/src/chainstate/nakamoto/mod.rs +++ b/stackslib/src/chainstate/nakamoto/mod.rs @@ -1176,7 +1176,7 @@ impl NakamotoBlock { warn!("Not a well-formed tenure-extend block"); return false; } - if !StacksBlock::validate_transactions_static_epoch(&self.txs, epoch_id, true) { + if !StacksBlock::validate_transactions_static_epoch(&self.txs, epoch_id, false) { return false; } return true; diff --git a/stackslib/src/chainstate/stacks/auth.rs b/stackslib/src/chainstate/stacks/auth.rs index c7d0ec72c6..9cc1c1d30e 100644 --- a/stackslib/src/chainstate/stacks/auth.rs +++ b/stackslib/src/chainstate/stacks/auth.rs @@ -1508,7 +1508,7 @@ mod test { 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, // field #3: public key TransactionAuthFieldID::PublicKeyUncompressed as u8, - // field #3: key (compressed) + // field #3: key (uncompressed) 0x03, 0xef, 0x23, 0x40, 0x51, 0x8b, 0x58, 0x67, 0xb2, 0x35, 0x98, 0xa9, 0xcf, 0x74, 0x61, 0x1f, 0x8b, 0x98, 0x06, 0x4f, 0x7d, 0x55, 0xcd, 0xb8, 0xc1, 0x07, 0xc6, 0x7b, 0x5e, 0xfc, 0xbc, 0x5c, 0x77, // number of signatures required 0x00, 0x02, @@ -1619,7 +1619,7 @@ mod test { 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, // field #3: public key TransactionAuthFieldID::PublicKeyUncompressed as u8, - // field #3: key (compressed) + // field #3: key (uncompressed) 0x03, 0xef, 0x23, 0x40, 0x51, 0x8b, 0x58, 0x67, 0xb2, 0x35, 0x98, 0xa9, 0xcf, 0x74, 0x61, 0x1f, 0x8b, 0x98, 0x06, 0x4f, 0x7d, 0x55, 0xcd, 0xb8, 0xc1, 0x07, 0xc6, 0x7b, 0x5e, 0xfc, 0xbc, 0x5c, 0x77, // number of signatures required 0x00, 0x02, diff --git a/stackslib/src/chainstate/stacks/transaction.rs b/stackslib/src/chainstate/stacks/transaction.rs index ffbf187728..871a7feb0e 100644 --- a/stackslib/src/chainstate/stacks/transaction.rs +++ b/stackslib/src/chainstate/stacks/transaction.rs @@ -954,102 +954,6 @@ impl StacksTransaction { Ok(next_sighash) } - #[cfg(any(test, feature = "testing"))] - fn sign_no_append_origin( - &self, - cur_sighash: &Txid, - privk: &StacksPrivateKey, - ) -> Result { - let next_sig = match self.auth { - TransactionAuth::Standard(ref origin_condition) - | TransactionAuth::Sponsored(ref origin_condition, _) => { - let (next_sig, _next_sighash) = TransactionSpendingCondition::next_signature( - cur_sighash, - &TransactionAuthFlags::AuthStandard, - origin_condition.tx_fee(), - origin_condition.nonce(), - privk, - )?; - next_sig - } - }; - Ok(next_sig) - } - - #[cfg(any(test, feature = "testing"))] - fn append_origin_signature( - &mut self, - signature: MessageSignature, - key_encoding: TransactionPublicKeyEncoding, - ) -> Result<(), net_error> { - match self.auth { - TransactionAuth::Standard(ref mut origin_condition) - | TransactionAuth::Sponsored(ref mut origin_condition, _) => match origin_condition { - TransactionSpendingCondition::Singlesig(ref mut cond) => { - cond.set_signature(signature); - } - TransactionSpendingCondition::Multisig(ref mut cond) => { - cond.push_signature(key_encoding, signature); - } - TransactionSpendingCondition::OrderIndependentMultisig(ref mut cond) => { - cond.push_signature(key_encoding, signature); - } - }, - }; - Ok(()) - } - - #[cfg(any(test, feature = "testing"))] - fn sign_no_append_sponsor( - &mut self, - cur_sighash: &Txid, - privk: &StacksPrivateKey, - ) -> Result { - let next_sig = match self.auth { - TransactionAuth::Standard(_) => { - return Err(net_error::SigningError( - "Cannot sign standard authorization with a sponsoring private key".to_string(), - )); - } - TransactionAuth::Sponsored(_, ref mut sponsor_condition) => { - let (next_sig, _next_sighash) = TransactionSpendingCondition::next_signature( - cur_sighash, - &TransactionAuthFlags::AuthSponsored, - sponsor_condition.tx_fee(), - sponsor_condition.nonce(), - privk, - )?; - next_sig - } - }; - Ok(next_sig) - } - - #[cfg(any(test, feature = "testing"))] - pub fn append_sponsor_signature( - &mut self, - signature: MessageSignature, - key_encoding: TransactionPublicKeyEncoding, - ) -> Result<(), net_error> { - match self.auth { - TransactionAuth::Standard(_) => Err(net_error::SigningError( - "Cannot appned a public key to the sponsor of a standard auth condition" - .to_string(), - )), - TransactionAuth::Sponsored(_, ref mut sponsor_condition) => match sponsor_condition { - TransactionSpendingCondition::Singlesig(ref mut cond) => { - Ok(cond.set_signature(signature)) - } - TransactionSpendingCondition::Multisig(ref mut cond) => { - Ok(cond.push_signature(key_encoding, signature)) - } - TransactionSpendingCondition::OrderIndependentMultisig(ref mut cond) => { - Ok(cond.push_signature(key_encoding, signature)) - } - }, - } - } - /// Append the next public key to the origin account authorization. pub fn append_next_origin(&mut self, pubk: &StacksPublicKey) -> Result<(), net_error> { match self.auth { @@ -1346,6 +1250,100 @@ mod test { use crate::net::codec::*; use crate::net::*; + impl StacksTransaction { + fn sign_no_append_origin( + &self, + cur_sighash: &Txid, + privk: &StacksPrivateKey, + ) -> Result { + let next_sig = match self.auth { + TransactionAuth::Standard(ref origin_condition) + | TransactionAuth::Sponsored(ref origin_condition, _) => { + let (next_sig, _next_sighash) = TransactionSpendingCondition::next_signature( + cur_sighash, + &TransactionAuthFlags::AuthStandard, + origin_condition.tx_fee(), + origin_condition.nonce(), + privk, + )?; + next_sig + } + }; + Ok(next_sig) + } + + fn append_origin_signature( + &mut self, + signature: MessageSignature, + key_encoding: TransactionPublicKeyEncoding, + ) -> Result<(), net_error> { + match self.auth { + TransactionAuth::Standard(ref mut origin_condition) + | TransactionAuth::Sponsored(ref mut origin_condition, _) => match origin_condition { + TransactionSpendingCondition::Singlesig(ref mut cond) => { + cond.set_signature(signature); + } + TransactionSpendingCondition::Multisig(ref mut cond) => { + cond.push_signature(key_encoding, signature); + } + TransactionSpendingCondition::OrderIndependentMultisig(ref mut cond) => { + cond.push_signature(key_encoding, signature); + } + }, + }; + Ok(()) + } + + fn sign_no_append_sponsor( + &mut self, + cur_sighash: &Txid, + privk: &StacksPrivateKey, + ) -> Result { + let next_sig = match self.auth { + TransactionAuth::Standard(_) => { + return Err(net_error::SigningError( + "Cannot sign standard authorization with a sponsoring private key".to_string(), + )); + } + TransactionAuth::Sponsored(_, ref mut sponsor_condition) => { + let (next_sig, _next_sighash) = TransactionSpendingCondition::next_signature( + cur_sighash, + &TransactionAuthFlags::AuthSponsored, + sponsor_condition.tx_fee(), + sponsor_condition.nonce(), + privk, + )?; + next_sig + } + }; + Ok(next_sig) + } + + pub fn append_sponsor_signature( + &mut self, + signature: MessageSignature, + key_encoding: TransactionPublicKeyEncoding, + ) -> Result<(), net_error> { + match self.auth { + TransactionAuth::Standard(_) => Err(net_error::SigningError( + "Cannot appned a public key to the sponsor of a standard auth condition" + .to_string(), + )), + TransactionAuth::Sponsored(_, ref mut sponsor_condition) => match sponsor_condition { + TransactionSpendingCondition::Singlesig(ref mut cond) => { + Ok(cond.set_signature(signature)) + } + TransactionSpendingCondition::Multisig(ref mut cond) => { + Ok(cond.push_signature(key_encoding, signature)) + } + TransactionSpendingCondition::OrderIndependentMultisig(ref mut cond) => { + Ok(cond.push_signature(key_encoding, signature)) + } + }, + } + } + } + fn corrupt_auth_field( corrupt_auth_fields: &TransactionAuth, i: usize, diff --git a/stackslib/src/net/relay.rs b/stackslib/src/net/relay.rs index d4a6edfe3e..9d11b067d4 100644 --- a/stackslib/src/net/relay.rs +++ b/stackslib/src/net/relay.rs @@ -5870,7 +5870,6 @@ pub mod test { peer.sortdb = Some(sortdb); peer.stacks_node = Some(node); } - #[test] fn test_block_versioned_smart_contract_gated_at_v210() { let mut peer_config = TestPeerConfig::new(function_name!(), 4248, 4249); @@ -5938,15 +5937,15 @@ pub mod test { &tip.sortition_id, &header_tip.anchored_header.block_hash(), ) - .unwrap() - .unwrap(); // succeeds because we don't fork + .unwrap() + .unwrap(); // succeeds because we don't fork StacksChainState::get_anchored_block_header_info( chainstate.db(), &snapshot.consensus_hash, &snapshot.winning_stacks_block_hash, ) - .unwrap() - .unwrap() + .unwrap() + .unwrap() } }; @@ -5984,7 +5983,7 @@ pub mod test { tip.total_burn, Hash160(mblock_pubkey_hash_bytes), ) - .unwrap(); + .unwrap(); let anchored_block = StacksBlockBuilder::make_anchored_block_from_txs( builder, @@ -5992,7 +5991,7 @@ pub mod test { &sortdb.index_conn(), vec![coinbase_tx, versioned_contract], ) - .unwrap(); + .unwrap(); eprintln!("{:?}", &anchored_block.0); (anchored_block.0, vec![]) @@ -6007,22 +6006,8 @@ pub mod test { let sortdb = peer.sortdb.take().unwrap(); let mut node = peer.stacks_node.take().unwrap(); - match Relayer::process_new_anchored_block( - &sortdb.index_conn(), - &mut node.chainstate, - &consensus_hash, - &stacks_block, - 123, - ) { - Ok(x) => { - eprintln!("{:?}", &stacks_block); - panic!("Stored pay-to-contract stacks block before epoch 2.1"); - } - Err(chainstate_error::InvalidStacksBlock(_)) => {} - Err(e) => { - panic!("Got unexpected error {:?}", &e); - } - }; + // incorrect transaction was filtered and no error in the block will appear + assert_eq!(stacks_block.txs.len(), 1); peer.sortdb = Some(sortdb); peer.stacks_node = Some(node); } From aaa37f3692837a7d551f614d2de425f78b6c17b8 Mon Sep 17 00:00:00 2001 From: Fess Date: Thu, 4 Apr 2024 22:02:32 +0400 Subject: [PATCH 12/41] fix: format --- stackslib/src/chainstate/stacks/transaction.rs | 9 ++++++--- stackslib/src/net/relay.rs | 12 ++++++------ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/stackslib/src/chainstate/stacks/transaction.rs b/stackslib/src/chainstate/stacks/transaction.rs index 871a7feb0e..210ccb85c3 100644 --- a/stackslib/src/chainstate/stacks/transaction.rs +++ b/stackslib/src/chainstate/stacks/transaction.rs @@ -1279,7 +1279,8 @@ mod test { ) -> Result<(), net_error> { match self.auth { TransactionAuth::Standard(ref mut origin_condition) - | TransactionAuth::Sponsored(ref mut origin_condition, _) => match origin_condition { + | TransactionAuth::Sponsored(ref mut origin_condition, _) => match origin_condition + { TransactionSpendingCondition::Singlesig(ref mut cond) => { cond.set_signature(signature); } @@ -1302,7 +1303,8 @@ mod test { let next_sig = match self.auth { TransactionAuth::Standard(_) => { return Err(net_error::SigningError( - "Cannot sign standard authorization with a sponsoring private key".to_string(), + "Cannot sign standard authorization with a sponsoring private key" + .to_string(), )); } TransactionAuth::Sponsored(_, ref mut sponsor_condition) => { @@ -1329,7 +1331,8 @@ mod test { "Cannot appned a public key to the sponsor of a standard auth condition" .to_string(), )), - TransactionAuth::Sponsored(_, ref mut sponsor_condition) => match sponsor_condition { + TransactionAuth::Sponsored(_, ref mut sponsor_condition) => match sponsor_condition + { TransactionSpendingCondition::Singlesig(ref mut cond) => { Ok(cond.set_signature(signature)) } diff --git a/stackslib/src/net/relay.rs b/stackslib/src/net/relay.rs index 9d11b067d4..667a265b37 100644 --- a/stackslib/src/net/relay.rs +++ b/stackslib/src/net/relay.rs @@ -5937,15 +5937,15 @@ pub mod test { &tip.sortition_id, &header_tip.anchored_header.block_hash(), ) - .unwrap() - .unwrap(); // succeeds because we don't fork + .unwrap() + .unwrap(); // succeeds because we don't fork StacksChainState::get_anchored_block_header_info( chainstate.db(), &snapshot.consensus_hash, &snapshot.winning_stacks_block_hash, ) - .unwrap() - .unwrap() + .unwrap() + .unwrap() } }; @@ -5983,7 +5983,7 @@ pub mod test { tip.total_burn, Hash160(mblock_pubkey_hash_bytes), ) - .unwrap(); + .unwrap(); let anchored_block = StacksBlockBuilder::make_anchored_block_from_txs( builder, @@ -5991,7 +5991,7 @@ pub mod test { &sortdb.index_conn(), vec![coinbase_tx, versioned_contract], ) - .unwrap(); + .unwrap(); eprintln!("{:?}", &anchored_block.0); (anchored_block.0, vec![]) From a995f2190430c876e11e9e355ea304100d767c3c Mon Sep 17 00:00:00 2001 From: Fess Date: Fri, 5 Apr 2024 10:03:32 +0400 Subject: [PATCH 13/41] fix: warning --- stackslib/src/net/relay.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stackslib/src/net/relay.rs b/stackslib/src/net/relay.rs index 667a265b37..b7cf916ebf 100644 --- a/stackslib/src/net/relay.rs +++ b/stackslib/src/net/relay.rs @@ -6005,7 +6005,7 @@ pub mod test { let (_, _, consensus_hash) = peer.next_burnchain_block(burn_ops.clone()); let sortdb = peer.sortdb.take().unwrap(); - let mut node = peer.stacks_node.take().unwrap(); + let node = peer.stacks_node.take().unwrap(); // incorrect transaction was filtered and no error in the block will appear assert_eq!(stacks_block.txs.len(), 1); peer.sortdb = Some(sortdb); From 0fd03213da7efcaf4143afda15b7a1f5185943b5 Mon Sep 17 00:00:00 2001 From: Vlad Date: Sat, 6 Apr 2024 13:53:47 +0400 Subject: [PATCH 14/41] Update stackslib/src/net/api/tests/gettenure.rs Co-authored-by: Jeff Bencin --- stackslib/src/net/api/tests/gettenure.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stackslib/src/net/api/tests/gettenure.rs b/stackslib/src/net/api/tests/gettenure.rs index c7dcd9f3ea..c4f179acc9 100644 --- a/stackslib/src/net/api/tests/gettenure.rs +++ b/stackslib/src/net/api/tests/gettenure.rs @@ -23,7 +23,7 @@ use stacks_common::types::chainstate::{ ConsensusHash, StacksAddress, StacksBlockId, StacksPrivateKey, }; use stacks_common::types::net::PeerHost; -use stacks_common::types::{Address, StacksEpochId}; +use stacks_common::types::Address; use super::TestRPC; use crate::chainstate::burn::db::sortdb::{SortitionDB, SortitionHandle}; From 1cb0dcbe38ba552f12e232b2c4f812d16fb9b55e Mon Sep 17 00:00:00 2001 From: Vlad Date: Sat, 6 Apr 2024 13:53:58 +0400 Subject: [PATCH 15/41] Update stackslib/src/net/api/tests/getblock.rs Co-authored-by: Jeff Bencin --- stackslib/src/net/api/tests/getblock.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stackslib/src/net/api/tests/getblock.rs b/stackslib/src/net/api/tests/getblock.rs index b3208015ca..d670b55edc 100644 --- a/stackslib/src/net/api/tests/getblock.rs +++ b/stackslib/src/net/api/tests/getblock.rs @@ -22,7 +22,7 @@ use stacks_common::types::chainstate::{ ConsensusHash, StacksAddress, StacksBlockId, StacksPrivateKey, }; use stacks_common::types::net::PeerHost; -use stacks_common::types::{Address, StacksEpochId}; +use stacks_common::types::Address; use super::TestRPC; use crate::chainstate::stacks::db::blocks::test::*; From f7dbfed51598aad3229be2bc020b89acf4471053 Mon Sep 17 00:00:00 2001 From: Vlad Date: Sat, 6 Apr 2024 13:54:16 +0400 Subject: [PATCH 16/41] Update stackslib/src/net/api/tests/getblock_v3.rs Co-authored-by: Jeff Bencin --- stackslib/src/net/api/tests/getblock_v3.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stackslib/src/net/api/tests/getblock_v3.rs b/stackslib/src/net/api/tests/getblock_v3.rs index 4bd22f65f1..de1a76f748 100644 --- a/stackslib/src/net/api/tests/getblock_v3.rs +++ b/stackslib/src/net/api/tests/getblock_v3.rs @@ -23,7 +23,7 @@ use stacks_common::types::chainstate::{ ConsensusHash, StacksAddress, StacksBlockId, StacksPrivateKey, }; use stacks_common::types::net::PeerHost; -use stacks_common::types::{Address, StacksEpochId}; +use stacks_common::types::Address; use super::TestRPC; use crate::chainstate::burn::db::sortdb::{SortitionDB, SortitionHandle}; From 1549a9a95bbe875886871c9df9233ea388f6ec98 Mon Sep 17 00:00:00 2001 From: Vlad Date: Sat, 6 Apr 2024 13:54:27 +0400 Subject: [PATCH 17/41] Update stackslib/src/net/codec.rs Co-authored-by: Jeff Bencin --- stackslib/src/net/codec.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/stackslib/src/net/codec.rs b/stackslib/src/net/codec.rs index fa1c588695..c0496aa14c 100644 --- a/stackslib/src/net/codec.rs +++ b/stackslib/src/net/codec.rs @@ -1555,7 +1555,6 @@ impl ProtocolFamily for StacksP2P { pub mod test { use stacks_common::bitvec::BitVec; use stacks_common::codec::NEIGHBOR_ADDRESS_ENCODED_SIZE; - use stacks_common::types::StacksEpochId; use stacks_common::util::hash::hex_bytes; use stacks_common::util::secp256k1::*; From 6c4c50de3aafe2f0047c5b852cda3aaf32629f33 Mon Sep 17 00:00:00 2001 From: Vlad Date: Sat, 6 Apr 2024 13:54:38 +0400 Subject: [PATCH 18/41] Update stackslib/src/net/api/posttransaction.rs Co-authored-by: Jeff Bencin --- stackslib/src/net/api/posttransaction.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stackslib/src/net/api/posttransaction.rs b/stackslib/src/net/api/posttransaction.rs index 3e6cea3038..70682b0839 100644 --- a/stackslib/src/net/api/posttransaction.rs +++ b/stackslib/src/net/api/posttransaction.rs @@ -23,7 +23,7 @@ use stacks_common::types::chainstate::{ BlockHeaderHash, ConsensusHash, StacksBlockId, StacksPublicKey, }; use stacks_common::types::net::PeerHost; -use stacks_common::types::{StacksEpochId, StacksPublicKeyBuffer}; +use stacks_common::types::StacksPublicKeyBuffer; use stacks_common::util::hash::{hex_bytes, to_hex, Hash160, Sha256Sum}; use stacks_common::util::retry::BoundReader; From 551761c976697d8b200d089b24092ae5f84f8d99 Mon Sep 17 00:00:00 2001 From: Vlad Date: Sat, 6 Apr 2024 13:54:53 +0400 Subject: [PATCH 19/41] Update stackslib/src/net/api/postblock.rs Co-authored-by: Jeff Bencin --- stackslib/src/net/api/postblock.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stackslib/src/net/api/postblock.rs b/stackslib/src/net/api/postblock.rs index bf0b010879..3380102101 100644 --- a/stackslib/src/net/api/postblock.rs +++ b/stackslib/src/net/api/postblock.rs @@ -23,7 +23,7 @@ use stacks_common::types::chainstate::{ BlockHeaderHash, ConsensusHash, StacksBlockId, StacksPublicKey, }; use stacks_common::types::net::PeerHost; -use stacks_common::types::{StacksEpochId, StacksPublicKeyBuffer}; +use stacks_common::types::StacksPublicKeyBuffer; use stacks_common::util::hash::{hex_bytes, Hash160, Sha256Sum}; use stacks_common::util::retry::BoundReader; From f84cc685724afe5b6023ffcd3306a7ea05646129 Mon Sep 17 00:00:00 2001 From: Vlad Date: Sat, 6 Apr 2024 13:55:05 +0400 Subject: [PATCH 20/41] Update stackslib/src/net/api/getblock.rs Co-authored-by: Jeff Bencin --- stackslib/src/net/api/getblock.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/stackslib/src/net/api/getblock.rs b/stackslib/src/net/api/getblock.rs index bb75bb5168..9596fbe9f2 100644 --- a/stackslib/src/net/api/getblock.rs +++ b/stackslib/src/net/api/getblock.rs @@ -23,7 +23,6 @@ use serde::de::Error as de_Error; use stacks_common::codec::{StacksMessageCodec, MAX_MESSAGE_LEN}; use stacks_common::types::chainstate::StacksBlockId; use stacks_common::types::net::PeerHost; -use stacks_common::types::StacksEpochId; use stacks_common::util::hash::to_hex; use {serde, serde_json}; From a057bc2e300e1ab8b9cdd351619edd377d8288ae Mon Sep 17 00:00:00 2001 From: Vlad Date: Sat, 6 Apr 2024 13:55:16 +0400 Subject: [PATCH 21/41] Update stackslib/src/net/api/gettenure.rs Co-authored-by: Jeff Bencin --- stackslib/src/net/api/gettenure.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/stackslib/src/net/api/gettenure.rs b/stackslib/src/net/api/gettenure.rs index 4bd1d362f5..58ff2f96f0 100644 --- a/stackslib/src/net/api/gettenure.rs +++ b/stackslib/src/net/api/gettenure.rs @@ -22,7 +22,6 @@ use serde::de::Error as de_Error; use stacks_common::codec::{StacksMessageCodec, MAX_MESSAGE_LEN}; use stacks_common::types::chainstate::{ConsensusHash, StacksBlockId}; use stacks_common::types::net::PeerHost; -use stacks_common::types::StacksEpochId; use stacks_common::util::hash::to_hex; use {serde, serde_json}; From d644759fca2813727aba473242941b725b92d0b3 Mon Sep 17 00:00:00 2001 From: Vlad Date: Sat, 6 Apr 2024 13:55:35 +0400 Subject: [PATCH 22/41] Update stackslib/src/chainstate/nakamoto/staging_blocks.rs Co-authored-by: Jeff Bencin --- stackslib/src/chainstate/nakamoto/staging_blocks.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/stackslib/src/chainstate/nakamoto/staging_blocks.rs b/stackslib/src/chainstate/nakamoto/staging_blocks.rs index 3e637af21f..0702a89070 100644 --- a/stackslib/src/chainstate/nakamoto/staging_blocks.rs +++ b/stackslib/src/chainstate/nakamoto/staging_blocks.rs @@ -32,7 +32,6 @@ use crate::chainstate::stacks::db::StacksChainState; use crate::chainstate::stacks::index::marf::MarfConnection; use crate::chainstate::stacks::{Error as ChainstateError, StacksBlock, StacksBlockHeader}; use crate::stacks_common::codec::StacksMessageCodec; -use crate::stacks_common::types::StacksEpochId; use crate::util_lib::db::{ query_int, query_row, query_row_panic, query_rows, sqlite_open, tx_begin_immediate, u64_to_sql, DBConn, Error as DBError, FromRow, From 15cd52125471de1bc501966db9984729f347e7ce Mon Sep 17 00:00:00 2001 From: Vlad Date: Sat, 6 Apr 2024 13:55:55 +0400 Subject: [PATCH 23/41] Update stacks-common/src/codec/mod.rs Co-authored-by: Jeff Bencin --- stacks-common/src/codec/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/stacks-common/src/codec/mod.rs b/stacks-common/src/codec/mod.rs index aa09348b0f..c2d9fd9610 100644 --- a/stacks-common/src/codec/mod.rs +++ b/stacks-common/src/codec/mod.rs @@ -3,7 +3,6 @@ use std::{error, fmt, io, mem}; // use crate::types::chainstate::MARFValue; use crate::types::chainstate::SortitionId; -use crate::types::StacksEpochId; use crate::util::hash::HASH160_ENCODED_SIZE; use crate::util::secp256k1::MESSAGE_SIGNATURE_ENCODED_SIZE; From 341e17a714bb0dab6647d2c11e9f557757c66940 Mon Sep 17 00:00:00 2001 From: Vlad Date: Sat, 6 Apr 2024 13:56:07 +0400 Subject: [PATCH 24/41] Update stackslib/src/blockstack_cli.rs Co-authored-by: Jeff Bencin --- stackslib/src/blockstack_cli.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/stackslib/src/blockstack_cli.rs b/stackslib/src/blockstack_cli.rs index a4f679ac6a..6fb9f45ed6 100644 --- a/stackslib/src/blockstack_cli.rs +++ b/stackslib/src/blockstack_cli.rs @@ -48,7 +48,6 @@ use clarity::vm::{ClarityName, ClarityVersion, ContractName, Value}; use stacks_common::address::{b58, AddressHashMode}; use stacks_common::codec::{Error as CodecError, StacksMessageCodec}; use stacks_common::types::chainstate::StacksAddress; -use stacks_common::types::StacksEpochId; use stacks_common::util::hash::{hex_bytes, to_hex}; use stacks_common::util::retry::LogReader; From 2167f2f12d7dcee20fd48fd9091324b807159f5d Mon Sep 17 00:00:00 2001 From: Fess Date: Sat, 6 Apr 2024 14:03:25 +0400 Subject: [PATCH 25/41] fix: revert messages.rs file --- libsigner/src/messages.rs | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/libsigner/src/messages.rs b/libsigner/src/messages.rs index 86a1306ddc..76be0d3c89 100644 --- a/libsigner/src/messages.rs +++ b/libsigner/src/messages.rs @@ -44,9 +44,9 @@ use hashbrown::{HashMap, HashSet}; use serde::{Deserialize, Serialize}; use stacks_common::codec::{ read_next, read_next_at_most, read_next_exact, write_next, Error as CodecError, - StacksMessageCodec, MAX_MESSAGE_LEN, + StacksMessageCodec, }; -use stacks_common::types::StacksEpochId; +use stacks_common::consts::SIGNER_SLOTS_PER_USER; use stacks_common::util::hash::Sha512Trunc256Sum; use stacks_common::util::retry::BoundReader; use tiny_http::{ @@ -363,10 +363,7 @@ impl StacksMessageCodec for SignerMessage { SignerMessage::BlockResponse(block_response) } SignerMessageTypePrefix::Transactions => { - let transactions: Vec = { - let mut bound_read = BoundReader::from_reader(fd, MAX_MESSAGE_LEN as u64); - read_next_at_most(&mut bound_read, u32::MAX) - }?; + let transactions = read_next::, _>(fd)?; SignerMessage::Transactions(transactions) } SignerMessageTypePrefix::DkgResults => { @@ -1220,12 +1217,7 @@ impl StacksMessageCodec for RejectCode { RejectCode::InsufficientSigners(read_next::, _>(fd)?) } RejectCodeTypePrefix::MissingTransactions => { - // I don't think these messages are stored on the blockchain, so `StacksEpochId::latest()` should be fine - let transactions: Vec = { - let mut bound_read = BoundReader::from_reader(fd, MAX_MESSAGE_LEN as u64); - read_next_at_most(&mut bound_read, u32::MAX) - }?; - RejectCode::MissingTransactions(transactions) + RejectCode::MissingTransactions(read_next::, _>(fd)?) } RejectCodeTypePrefix::NonceTimeout => { RejectCode::NonceTimeout(read_next::, _>(fd)?) @@ -1314,7 +1306,7 @@ mod test { use blockstack_lib::util_lib::strings::StacksString; use rand::Rng; use rand_core::OsRng; - use stacks_common::consts::{CHAIN_ID_TESTNET, SIGNER_SLOTS_PER_USER}; + use stacks_common::consts::CHAIN_ID_TESTNET; use stacks_common::types::chainstate::StacksPrivateKey; use wsts::common::Signature; From ac057cadabd0830c15e3ddb54fea85acb7c8676f Mon Sep 17 00:00:00 2001 From: Fess Date: Sat, 6 Apr 2024 14:05:44 +0400 Subject: [PATCH 26/41] fix: redundant import removed --- libsigner/src/messages.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/libsigner/src/messages.rs b/libsigner/src/messages.rs index 76be0d3c89..1b6e7f179f 100644 --- a/libsigner/src/messages.rs +++ b/libsigner/src/messages.rs @@ -48,7 +48,6 @@ use stacks_common::codec::{ }; use stacks_common::consts::SIGNER_SLOTS_PER_USER; use stacks_common::util::hash::Sha512Trunc256Sum; -use stacks_common::util::retry::BoundReader; use tiny_http::{ Method as HttpMethod, Request as HttpRequest, Response as HttpResponse, Server as HttpServer, }; From c4b0952a62ce38fb22c201e9709521bcf07e790d Mon Sep 17 00:00:00 2001 From: Vlad Date: Sat, 6 Apr 2024 14:14:34 +0400 Subject: [PATCH 27/41] Update stackslib/src/net/api/getblock_v3.rs Co-authored-by: Jeff Bencin --- stackslib/src/net/api/getblock_v3.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/stackslib/src/net/api/getblock_v3.rs b/stackslib/src/net/api/getblock_v3.rs index 344aa6d746..090afec04c 100644 --- a/stackslib/src/net/api/getblock_v3.rs +++ b/stackslib/src/net/api/getblock_v3.rs @@ -23,7 +23,6 @@ use serde::de::Error as de_Error; use stacks_common::codec::{StacksMessageCodec, MAX_MESSAGE_LEN}; use stacks_common::types::chainstate::{ConsensusHash, StacksBlockId}; use stacks_common::types::net::PeerHost; -use stacks_common::types::StacksEpochId; use stacks_common::util::hash::to_hex; use {serde, serde_json}; From 13581510ccba22a67b817071f5316cf42b73ad6c Mon Sep 17 00:00:00 2001 From: Jeff Bencin Date: Tue, 9 Apr 2024 10:07:49 -0400 Subject: [PATCH 28/41] feat: Allow for more than min number of signers in `OrderIndependentMultisigSpendingCondition` --- stackslib/src/chainstate/stacks/auth.rs | 31 +++++++++++-------------- 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/stackslib/src/chainstate/stacks/auth.rs b/stackslib/src/chainstate/stacks/auth.rs index 9cc1c1d30e..5ba40fceea 100644 --- a/stackslib/src/chainstate/stacks/auth.rs +++ b/stackslib/src/chainstate/stacks/auth.rs @@ -373,27 +373,21 @@ impl StacksMessageCodec for OrderIndependentMultisigSpendingCondition { } // must be given the right number of signatures - if num_sigs_given != signatures_required { - test_debug!( - "Failed to deserialize order independent multisig spending condition: got {} sigs, expected {}", - num_sigs_given, - signatures_required + if num_sigs_given < signatures_required { + let msg = format!( + "Failed to deserialize order independent multisig spending condition: got {num_sigs_given} sigs, expected at least {signatures_required}" ); - return Err(codec_error::DeserializeError(format!( - "Failed to parse order independent multisig spending condition: got {} sigs, expected {}", - num_sigs_given, signatures_required - ))); + test_debug!("{msg}"); + return Err(codec_error::DeserializeError(msg)); } // must all be compressed if we're using P2WSH if have_uncompressed && hash_mode == OrderIndependentMultisigHashMode::P2WSH { - test_debug!( + let msg = format!( "Failed to deserialize order independent multisig spending condition: expected compressed keys only" ); - return Err(codec_error::DeserializeError( - "Failed to parse order independent multisig spending condition: expected compressed keys only" - .to_string(), - )); + test_debug!("{msg}"); + return Err(codec_error::DeserializeError(msg)); } Ok(OrderIndependentMultisigSpendingCondition { @@ -481,10 +475,11 @@ impl OrderIndependentMultisigSpendingCondition { pubkeys.push(pubkey); } - if num_sigs != self.signatures_required { - return Err(net_error::VerifyingError( - "Incorrect number of signatures".to_string(), - )); + if num_sigs < self.signatures_required { + return Err(net_error::VerifyingError(format!( + "Not enough signatures. Got {num_sigs}, expected at least {req}", + req = self.signatures_required + ))); } if have_uncompressed && self.hash_mode == OrderIndependentMultisigHashMode::P2WSH { From 72507cc90a0832d996a5b851cd9f82598c84449b Mon Sep 17 00:00:00 2001 From: Fess Date: Wed, 10 Apr 2024 13:36:36 +0400 Subject: [PATCH 29/41] fix: redundant code reverted, quiet parameter removed --- stackslib/src/chainstate/nakamoto/mod.rs | 17 ++++---- stackslib/src/chainstate/stacks/block.rs | 43 ++++++------------- stackslib/src/chainstate/stacks/db/blocks.rs | 2 +- .../src/chainstate/stacks/db/transactions.rs | 2 +- 4 files changed, 22 insertions(+), 42 deletions(-) diff --git a/stackslib/src/chainstate/nakamoto/mod.rs b/stackslib/src/chainstate/nakamoto/mod.rs index 6786151796..f0d2085cb4 100644 --- a/stackslib/src/chainstate/nakamoto/mod.rs +++ b/stackslib/src/chainstate/nakamoto/mod.rs @@ -32,8 +32,8 @@ use rusqlite::{params, Connection, OpenFlags, OptionalExtension, ToSql, NO_PARAM use sha2::{Digest as Sha2Digest, Sha512_256}; use stacks_common::bitvec::BitVec; use stacks_common::codec::{ - read_next, read_next_at_most, write_next, Error as CodecError, StacksMessageCodec, - MAX_MESSAGE_LEN, MAX_PAYLOAD_LEN, + read_next, write_next, Error as CodecError, StacksMessageCodec, MAX_MESSAGE_LEN, + MAX_PAYLOAD_LEN, }; use stacks_common::consts::{ FIRST_BURNCHAIN_CONSENSUS_HASH, FIRST_STACKS_BLOCK_HASH, MINER_REWARD_MATURITY, @@ -1176,7 +1176,7 @@ impl NakamotoBlock { warn!("Not a well-formed tenure-extend block"); return false; } - if !StacksBlock::validate_transactions_static_epoch(&self.txs, epoch_id, false) { + if !StacksBlock::validate_transactions_static_epoch(&self.txs, epoch_id) { return false; } return true; @@ -3357,13 +3357,12 @@ impl StacksMessageCodec for NakamotoBlock { } fn consensus_deserialize(fd: &mut R) -> Result { - let header: NakamotoBlockHeader = read_next(fd)?; - - let txs: Vec = { + let (header, txs) = { let mut bound_read = BoundReader::from_reader(fd, u64::from(MAX_MESSAGE_LEN)); - // The latest epoch where StacksMicroblock exist is Epoch25 - read_next_at_most(&mut bound_read, u32::MAX) - }?; + let header: NakamotoBlockHeader = read_next(&mut bound_read)?; + let txs: Vec<_> = read_next(&mut bound_read)?; + (header, txs) + }; // all transactions are unique if !StacksBlock::validate_transactions_unique(&txs) { diff --git a/stackslib/src/chainstate/stacks/block.rs b/stackslib/src/chainstate/stacks/block.rs index 872e4fe9d2..6126dfcfc4 100644 --- a/stackslib/src/chainstate/stacks/block.rs +++ b/stackslib/src/chainstate/stacks/block.rs @@ -568,46 +568,35 @@ impl StacksBlock { pub fn validate_transactions_static_epoch( txs: &[StacksTransaction], epoch_id: StacksEpochId, - quiet: bool, ) -> bool { for tx in txs.iter() { if let TransactionPayload::Coinbase(_, ref recipient_opt, ref proof_opt) = &tx.payload { if proof_opt.is_some() && epoch_id < StacksEpochId::Epoch30 { // not supported - if !quiet { - error!("Coinbase with VRF proof not supported before Stacks 3.0"; "txid" => %tx.txid()); - } + error!("Coinbase with VRF proof not supported before Stacks 3.0"; "txid" => %tx.txid()); return false; } if proof_opt.is_none() && epoch_id >= StacksEpochId::Epoch30 { // not supported - if !quiet { - error!("Coinbase with VRF proof is required in Stacks 3.0 and later"; "txid" => %tx.txid()); - } + error!("Coinbase with VRF proof is required in Stacks 3.0 and later"; "txid" => %tx.txid()); return false; } if recipient_opt.is_some() && epoch_id < StacksEpochId::Epoch21 { // not supported - if !quiet { - error!("Coinbase pay-to-alt-recipient not supported before Stacks 2.1"; "txid" => %tx.txid()); - } + error!("Coinbase pay-to-alt-recipient not supported before Stacks 2.1"; "txid" => %tx.txid()); return false; } } if let TransactionPayload::SmartContract(_, ref version_opt) = &tx.payload { if version_opt.is_some() && epoch_id < StacksEpochId::Epoch21 { // not supported - if !quiet { - error!("Versioned smart contracts not supported before Stacks 2.1"); - } + error!("Versioned smart contracts not supported before Stacks 2.1"); return false; } } if let TransactionPayload::TenureChange(..) = &tx.payload { if epoch_id < StacksEpochId::Epoch30 { - if !quiet { - error!("TenureChange transaction not supported before Stacks 3.0"; "txid" => %tx.txid()); - } + error!("TenureChange transaction not supported before Stacks 3.0"; "txid" => %tx.txid()); return false; } } @@ -616,9 +605,7 @@ impl StacksBlock { match origin { TransactionSpendingCondition::OrderIndependentMultisig(..) => { if epoch_id < StacksEpochId::Epoch30 { - if !quiet { - error!("Order independent multisig transactions not supported before Stacks 3.0"); - } + error!("Order independent multisig transactions not supported before Stacks 3.0"); return false; } } @@ -627,9 +614,7 @@ impl StacksBlock { match sponsor { TransactionSpendingCondition::OrderIndependentMultisig(..) => { if epoch_id < StacksEpochId::Epoch30 { - if !quiet { - error!("Order independent multisig transactions not supported before Stacks 3.0"); - } + error!("Order independent multisig transactions not supported before Stacks 3.0"); return false; } } @@ -639,9 +624,7 @@ impl StacksBlock { TransactionAuth::Standard(ref origin) => match origin { TransactionSpendingCondition::OrderIndependentMultisig(..) => { if epoch_id < StacksEpochId::Epoch30 { - if !quiet { - error!("Order independent multisig transactions not supported before Stacks 3.0"); - } + error!("Order independent multisig transactions not supported before Stacks 3.0"); return false; } } @@ -674,7 +657,7 @@ impl StacksBlock { if !StacksBlock::validate_coinbase(&self.txs, true) { return false; } - if !StacksBlock::validate_transactions_static_epoch(&self.txs, epoch_id, false) { + if !StacksBlock::validate_transactions_static_epoch(&self.txs, epoch_id) { return false; } return true; @@ -853,8 +836,7 @@ impl StacksMessageCodec for StacksMicroblock { let header: StacksMicroblockHeader = read_next(fd)?; let txs: Vec = { let mut bound_read = BoundReader::from_reader(fd, MAX_MESSAGE_LEN as u64); - // The latest epoch where StacksMicroblock exist is Epoch25 - read_next_at_most(&mut bound_read, u32::MAX) + read_next(&mut bound_read) }?; if txs.len() == 0 { @@ -1802,16 +1784,15 @@ mod test { assert!(!StacksBlock::validate_transactions_static_epoch( &txs, epoch_id.clone(), - false, )); } else if deactivation_epoch_id.is_none() || deactivation_epoch_id.unwrap() > *epoch_id { assert!(StacksBlock::validate_transactions_static_epoch( - &txs, *epoch_id, false, + &txs, *epoch_id, )); } else { assert!(!StacksBlock::validate_transactions_static_epoch( - &txs, *epoch_id, false, + &txs, *epoch_id, )); } } diff --git a/stackslib/src/chainstate/stacks/db/blocks.rs b/stackslib/src/chainstate/stacks/db/blocks.rs index 5b6b9d6e91..792ca8edc7 100644 --- a/stackslib/src/chainstate/stacks/db/blocks.rs +++ b/stackslib/src/chainstate/stacks/db/blocks.rs @@ -6666,7 +6666,7 @@ impl StacksChainState { // 4: check if transaction is valid in the current epoch let epoch = clarity_connection.get_epoch().clone(); - if !StacksBlock::validate_transactions_static_epoch(&[tx.clone()], epoch, true) { + if !StacksBlock::validate_transactions_static_epoch(&[tx.clone()], epoch) { return Err(MemPoolRejection::Other( "Transaction is not supported in this epoch".to_string(), )); diff --git a/stackslib/src/chainstate/stacks/db/transactions.rs b/stackslib/src/chainstate/stacks/db/transactions.rs index cb2c569a2a..e8412f5060 100644 --- a/stackslib/src/chainstate/stacks/db/transactions.rs +++ b/stackslib/src/chainstate/stacks/db/transactions.rs @@ -1480,7 +1480,7 @@ impl StacksChainState { } } - if !StacksBlock::validate_transactions_static_epoch(&vec![tx.clone()], epoch, quiet) { + if !StacksBlock::validate_transactions_static_epoch(&vec![tx.clone()], epoch) { let msg = format!( "Invalid transaction {}: target epoch is not activated", tx.txid() From 8df5b318a8c3014a48a94e7b42e42bfd68033674 Mon Sep 17 00:00:00 2001 From: Fess Date: Wed, 10 Apr 2024 14:06:58 +0400 Subject: [PATCH 30/41] fix: supported in epoch added --- stackslib/src/chainstate/stacks/auth.rs | 30 ++++++++++++++++++++ stackslib/src/chainstate/stacks/block.rs | 35 +++--------------------- 2 files changed, 34 insertions(+), 31 deletions(-) diff --git a/stackslib/src/chainstate/stacks/auth.rs b/stackslib/src/chainstate/stacks/auth.rs index 5ba40fceea..e2f2654efc 100644 --- a/stackslib/src/chainstate/stacks/auth.rs +++ b/stackslib/src/chainstate/stacks/auth.rs @@ -27,6 +27,7 @@ use stacks_common::types::StacksPublicKeyBuffer; use stacks_common::util::hash::{to_hex, Hash160, Sha512Trunc256Sum}; use stacks_common::util::retry::{BoundReader, RetryReader}; use stacks_common::util::secp256k1::{MessageSignature, MESSAGE_SIGNATURE_ENCODED_SIZE}; +use stacks_common::types::StacksEpochId; use crate::burnchains::{PrivateKey, PublicKey, Txid}; use crate::chainstate::stacks::{ @@ -1387,6 +1388,35 @@ impl TransactionAuth { } } } + + pub fn is_supported_in_epoch( + &self, + epoch_id: StacksEpochId, + ) -> bool { + match &self { + TransactionAuth::Sponsored(ref origin, ref sponsor) => { + let origin_supported = match origin { + TransactionSpendingCondition::OrderIndependentMultisig(..) => { + epoch_id >= StacksEpochId::Epoch30 + }, + _ => true, + }; + let sponsor_supported = match sponsor { + TransactionSpendingCondition::OrderIndependentMultisig(..) => { + epoch_id >= StacksEpochId::Epoch30 + }, + _ => true, + }; + origin_supported && sponsor_supported + }, + TransactionAuth::Standard(ref origin) => match origin { + TransactionSpendingCondition::OrderIndependentMultisig(..) => { + epoch_id >= StacksEpochId::Epoch30 + }, + _ => true, + }, + } + } } #[rustfmt::skip] diff --git a/stackslib/src/chainstate/stacks/block.rs b/stackslib/src/chainstate/stacks/block.rs index 6126dfcfc4..97c64f35f8 100644 --- a/stackslib/src/chainstate/stacks/block.rs +++ b/stackslib/src/chainstate/stacks/block.rs @@ -600,37 +600,10 @@ impl StacksBlock { return false; } } - match &tx.auth { - TransactionAuth::Sponsored(ref origin, ref sponsor) => { - match origin { - TransactionSpendingCondition::OrderIndependentMultisig(..) => { - if epoch_id < StacksEpochId::Epoch30 { - error!("Order independent multisig transactions not supported before Stacks 3.0"); - return false; - } - } - _ => (), - } - match sponsor { - TransactionSpendingCondition::OrderIndependentMultisig(..) => { - if epoch_id < StacksEpochId::Epoch30 { - error!("Order independent multisig transactions not supported before Stacks 3.0"); - return false; - } - } - _ => (), - } - } - TransactionAuth::Standard(ref origin) => match origin { - TransactionSpendingCondition::OrderIndependentMultisig(..) => { - if epoch_id < StacksEpochId::Epoch30 { - error!("Order independent multisig transactions not supported before Stacks 3.0"); - return false; - } - } - _ => (), - }, - }; + if !tx.auth.is_supported_in_epoch(epoch_id) { + error!("Order independent multisig transactions not supported before Stacks 3.0"); + return false; + } } return true; } From 9dfa3c422027ab97a7ed37c1629d577573b36244 Mon Sep 17 00:00:00 2001 From: Fess Date: Thu, 11 Apr 2024 11:33:39 +0400 Subject: [PATCH 31/41] fix: TransactionAuth is_supported_in_epoch docstring added, autotest added --- stackslib/src/chainstate/stacks/auth.rs | 104 ++++++++++++++++++++++-- 1 file changed, 95 insertions(+), 9 deletions(-) diff --git a/stackslib/src/chainstate/stacks/auth.rs b/stackslib/src/chainstate/stacks/auth.rs index e2f2654efc..33bf9e4bbb 100644 --- a/stackslib/src/chainstate/stacks/auth.rs +++ b/stackslib/src/chainstate/stacks/auth.rs @@ -23,11 +23,11 @@ use stacks_common::codec::{ read_next, write_next, Error as codec_error, StacksMessageCodec, MAX_MESSAGE_LEN, }; use stacks_common::types::chainstate::StacksAddress; +use stacks_common::types::StacksEpochId; use stacks_common::types::StacksPublicKeyBuffer; use stacks_common::util::hash::{to_hex, Hash160, Sha512Trunc256Sum}; use stacks_common::util::retry::{BoundReader, RetryReader}; use stacks_common::util::secp256k1::{MessageSignature, MESSAGE_SIGNATURE_ENCODED_SIZE}; -use stacks_common::types::StacksEpochId; use crate::burnchains::{PrivateKey, PublicKey, Txid}; use crate::chainstate::stacks::{ @@ -1389,30 +1389,29 @@ impl TransactionAuth { } } - pub fn is_supported_in_epoch( - &self, - epoch_id: StacksEpochId, - ) -> bool { + /// Checks if this TransactionAuth is supported in the passed epoch + /// OrderIndependent multisig is not supported before epoch 3.0 + pub fn is_supported_in_epoch(&self, epoch_id: StacksEpochId) -> bool { match &self { TransactionAuth::Sponsored(ref origin, ref sponsor) => { let origin_supported = match origin { TransactionSpendingCondition::OrderIndependentMultisig(..) => { epoch_id >= StacksEpochId::Epoch30 - }, + } _ => true, }; let sponsor_supported = match sponsor { TransactionSpendingCondition::OrderIndependentMultisig(..) => { epoch_id >= StacksEpochId::Epoch30 - }, + } _ => true, }; origin_supported && sponsor_supported - }, + } TransactionAuth::Standard(ref origin) => match origin { TransactionSpendingCondition::OrderIndependentMultisig(..) => { epoch_id >= StacksEpochId::Epoch30 - }, + } _ => true, }, } @@ -1422,6 +1421,7 @@ impl TransactionAuth { #[rustfmt::skip] #[cfg(test)] mod test { + use stacks_common::types::StacksEpochId::Epoch30; use super::*; use crate::chainstate::stacks::{StacksPublicKey as PubKey, *}; use crate::net::codec::test::check_codec_and_corruption; @@ -2383,4 +2383,90 @@ mod test { assert_eq!(next_pubkey, StacksPublicKey::from_private(&keys[i])); } } + + fn tx_auth_check_all_epochs( + auth: TransactionAuth, + activation_epoch_id: Option, + ) { + let epoch_list = [ + StacksEpochId::Epoch10, + StacksEpochId::Epoch20, + StacksEpochId::Epoch2_05, + StacksEpochId::Epoch21, + StacksEpochId::Epoch22, + StacksEpochId::Epoch23, + StacksEpochId::Epoch24, + StacksEpochId::Epoch25, + StacksEpochId::Epoch30, + ]; + + for epoch_id in epoch_list.iter() { + if activation_epoch_id.is_none() { + assert_eq!(auth.is_supported_in_epoch(*epoch_id), true); + } else if activation_epoch_id.unwrap() > *epoch_id { + assert_eq!(auth.is_supported_in_epoch(*epoch_id), false); + } else { + assert_eq!(auth.is_supported_in_epoch(*epoch_id), true); + } + } + } + + #[test] + fn tx_auth_is_supported_in_epoch() { + let privk_1 = StacksPrivateKey::from_hex( + "6d430bb91222408e7706c9001cfaeb91b08c2be6d5ac95779ab52c6b431950e001", + ).unwrap(); + + let privk_2 = StacksPrivateKey::from_hex( + "7e3af4db6af6b3c67e2c6c6d7d5983b519f4d9b3a6e00580ae96dcace3bde8bc01", + ).unwrap(); + + let auth_p2pkh = TransactionAuth::from_p2pkh(&privk_1).unwrap(); + let auth_sponsored_p2pkh = auth_p2pkh.clone().into_sponsored( + TransactionAuth::from_p2pkh(&privk_2).unwrap() + ).unwrap(); + + tx_auth_check_all_epochs(auth_p2pkh, None); + tx_auth_check_all_epochs(auth_sponsored_p2pkh, None); + + let auth_p2wpkh = TransactionAuth::from_p2wpkh(&privk_1).unwrap(); + let auth_sponsored_p2wpkh = auth_p2wpkh.clone().into_sponsored( + TransactionAuth::from_p2wpkh(&privk_2).unwrap() + ).unwrap(); + + tx_auth_check_all_epochs(auth_p2wpkh, None); + tx_auth_check_all_epochs(auth_sponsored_p2wpkh, None); + + let auth_p2sh = TransactionAuth::from_p2sh(&[privk_1, privk_2], 2).unwrap(); + let auth_sponsored_p2sh = auth_p2sh.clone().into_sponsored( + TransactionAuth::from_p2sh(&[privk_1, privk_2], 2).unwrap() + ).unwrap(); + + tx_auth_check_all_epochs(auth_p2sh, None); + tx_auth_check_all_epochs(auth_sponsored_p2sh, None); + + let auth_p2wsh = TransactionAuth::from_p2wsh(&[privk_1, privk_2], 2).unwrap(); + let auth_sponsored_p2wsh = auth_p2wsh.clone().into_sponsored( + TransactionAuth::from_p2wsh(&[privk_1, privk_2], 2).unwrap() + ).unwrap(); + + tx_auth_check_all_epochs(auth_p2wsh, None); + tx_auth_check_all_epochs(auth_sponsored_p2wsh, None); + + let auth_order_independent_p2sh = TransactionAuth::from_order_independent_p2sh(&[privk_1, privk_2], 2).unwrap(); + let auth_sponsored_order_independent_p2sh = auth_order_independent_p2sh.clone().into_sponsored( + TransactionAuth::from_order_independent_p2sh(&[privk_1, privk_2], 2).unwrap() + ).unwrap(); + + tx_auth_check_all_epochs(auth_order_independent_p2sh, Some(StacksEpochId::Epoch30)); + tx_auth_check_all_epochs(auth_sponsored_order_independent_p2sh, Some(StacksEpochId::Epoch30)); + + let auth_order_independent_p2wsh = TransactionAuth::from_order_independent_p2wsh(&[privk_1, privk_2], 2).unwrap(); + let auth_sponsored_order_independent_p2wsh = auth_order_independent_p2wsh.clone().into_sponsored( + TransactionAuth::from_order_independent_p2wsh(&[privk_1, privk_2], 2).unwrap() + ).unwrap(); + + tx_auth_check_all_epochs(auth_order_independent_p2wsh, Some(StacksEpochId::Epoch30)); + tx_auth_check_all_epochs(auth_sponsored_order_independent_p2wsh, Some(StacksEpochId::Epoch30)); + } } From 5d25b5c79c1e8c6f63cc17df1adc9a3fe4256796 Mon Sep 17 00:00:00 2001 From: Jeff Bencin Date: Wed, 10 Apr 2024 16:58:13 -0400 Subject: [PATCH 32/41] test: Add cases in `auth.rs` for num_sigs > required_sigs --- stackslib/src/chainstate/stacks/auth.rs | 64 ++++++++++++++----------- 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/stackslib/src/chainstate/stacks/auth.rs b/stackslib/src/chainstate/stacks/auth.rs index 33bf9e4bbb..93104e8046 100644 --- a/stackslib/src/chainstate/stacks/auth.rs +++ b/stackslib/src/chainstate/stacks/auth.rs @@ -1887,6 +1887,30 @@ mod test { ], signatures_required: 2 }), + TransactionSpendingCondition::OrderIndependentMultisig(OrderIndependentMultisigSpendingCondition { + signer: Hash160([0x11; 20]), + hash_mode: OrderIndependentMultisigHashMode::P2SH, + nonce: 123, + tx_fee: 567, + fields: vec![ + TransactionAuthField::Signature(TransactionPublicKeyEncoding::Uncompressed, MessageSignature::from_raw(&vec![0xff; 65])), + TransactionAuthField::Signature(TransactionPublicKeyEncoding::Uncompressed, MessageSignature::from_raw(&vec![0xfe; 65])), + TransactionAuthField::Signature(TransactionPublicKeyEncoding::Uncompressed, MessageSignature::from_raw(&vec![0xfd; 65])), + ], + signatures_required: 1 + }), + TransactionSpendingCondition::OrderIndependentMultisig(OrderIndependentMultisigSpendingCondition { + signer: Hash160([0x11; 20]), + hash_mode: OrderIndependentMultisigHashMode::P2SH, + nonce: 456, + tx_fee: 567, + fields: vec![ + TransactionAuthField::Signature(TransactionPublicKeyEncoding::Compressed, MessageSignature::from_raw(&vec![0xff; 65])), + TransactionAuthField::Signature(TransactionPublicKeyEncoding::Compressed, MessageSignature::from_raw(&vec![0xfe; 65])), + TransactionAuthField::Signature(TransactionPublicKeyEncoding::Compressed, MessageSignature::from_raw(&vec![0xfd; 65])), + ], + signatures_required: 1 + }), TransactionSpendingCondition::Singlesig(SinglesigSpendingCondition { signer: Hash160([0x11; 20]), hash_mode: SinglesigHashMode::P2WPKH, @@ -1918,6 +1942,18 @@ mod test { TransactionAuthField::PublicKey(PubKey::from_hex("03ef2340518b5867b23598a9cf74611f8b98064f7d55cdb8c107c67b5efcbc5c77").unwrap()) ], signatures_required: 2 + }), + TransactionSpendingCondition::OrderIndependentMultisig(OrderIndependentMultisigSpendingCondition { + signer: Hash160([0x11; 20]), + hash_mode: OrderIndependentMultisigHashMode::P2WSH, + nonce: 456, + tx_fee: 567, + fields: vec![ + TransactionAuthField::Signature(TransactionPublicKeyEncoding::Compressed, MessageSignature::from_raw(&vec![0xff; 65])), + TransactionAuthField::Signature(TransactionPublicKeyEncoding::Compressed, MessageSignature::from_raw(&vec![0xfe; 65])), + TransactionAuthField::Signature(TransactionPublicKeyEncoding::Compressed, MessageSignature::from_raw(&vec![0xfd; 65])), + ], + signatures_required: 1 }) ]; @@ -2047,34 +2083,6 @@ mod test { 0x00, 0x01, ]; - // wrong number of public keys (too many signatures) - let bad_public_order_independent_key_count_bytes = vec![ - // hash mode - OrderIndependentMultisigHashMode::P2SH as u8, - // signer - 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, - // nonce - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc8, - // fee rate - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x37, - // fields length - 0x00, 0x00, 0x00, 0x03, - // field #1: signature - TransactionAuthFieldID::SignatureCompressed as u8, - // field #1: signature - 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - // field #2: signature - TransactionAuthFieldID::SignatureCompressed as u8, - // filed #2: signature - 0x02, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, - // field #3: public key - TransactionAuthFieldID::PublicKeyCompressed as u8, - // field #3: key (compressed) - 0x03, 0xef, 0x23, 0x40, 0x51, 0x8b, 0x58, 0x67, 0xb2, 0x35, 0x98, 0xa9, 0xcf, 0x74, 0x61, 0x1f, 0x8b, 0x98, 0x06, 0x4f, 0x7d, 0x55, 0xcd, 0xb8, 0xc1, 0x07, 0xc6, 0x7b, 0x5e, 0xfc, 0xbc, 0x5c, 0x77, - // number of signatures - 0x00, 0x01, - ]; - // wrong number of public keys (not enough signatures) let bad_public_key_count_bytes_2 = vec![ // hash mode From d981cec0f61a05de05774a17f51f7d8c8fe261ae Mon Sep 17 00:00:00 2001 From: Jeff Bencin Date: Thu, 11 Apr 2024 13:54:43 -0400 Subject: [PATCH 33/41] fix: Tests broken by `13581510c` now work. Also, added test to confirm extra signers work --- stackslib/src/chainstate/stacks/auth.rs | 3 +- .../src/chainstate/stacks/transaction.rs | 123 +++++++++++++++++- 2 files changed, 122 insertions(+), 4 deletions(-) diff --git a/stackslib/src/chainstate/stacks/auth.rs b/stackslib/src/chainstate/stacks/auth.rs index 93104e8046..c83eedf0ff 100644 --- a/stackslib/src/chainstate/stacks/auth.rs +++ b/stackslib/src/chainstate/stacks/auth.rs @@ -23,8 +23,7 @@ use stacks_common::codec::{ read_next, write_next, Error as codec_error, StacksMessageCodec, MAX_MESSAGE_LEN, }; use stacks_common::types::chainstate::StacksAddress; -use stacks_common::types::StacksEpochId; -use stacks_common::types::StacksPublicKeyBuffer; +use stacks_common::types::{StacksEpochId, StacksPublicKeyBuffer}; use stacks_common::util::hash::{to_hex, Hash160, Sha512Trunc256Sum}; use stacks_common::util::retry::{BoundReader, RetryReader}; use stacks_common::util::secp256k1::{MessageSignature, MESSAGE_SIGNATURE_ENCODED_SIZE}; diff --git a/stackslib/src/chainstate/stacks/transaction.rs b/stackslib/src/chainstate/stacks/transaction.rs index 210ccb85c3..1095e8e534 100644 --- a/stackslib/src/chainstate/stacks/transaction.rs +++ b/stackslib/src/chainstate/stacks/transaction.rs @@ -4060,6 +4060,17 @@ mod test { assert_eq!(txid_before, signed_tx.txid()); } + fn is_order_independent_multisig(tx: &StacksTransaction) -> bool { + let spending_condition = match &tx.auth { + TransactionAuth::Standard(origin) => origin, + TransactionAuth::Sponsored(_, sponsor) => sponsor, + }; + match spending_condition { + TransactionSpendingCondition::OrderIndependentMultisig(..) => true, + _ => false, + } + } + fn check_oversign_origin_multisig(signed_tx: &StacksTransaction) -> () { let tx = signed_tx.clone(); let privk = StacksPrivateKey::from_hex( @@ -4076,7 +4087,14 @@ mod test { Ok(_) => assert!(false), Err(e) => match e { net_error::VerifyingError(msg) => { - assert_eq!(&msg, "Incorrect number of signatures") + if is_order_independent_multisig(&oversigned_tx) { + assert!( + msg.contains("Signer hash does not equal hash of public key(s)"), + "{msg}" + ) + } else { + assert_eq!(&msg, "Incorrect number of signatures") + } } _ => assert!(false), }, @@ -4133,7 +4151,14 @@ mod test { Ok(_) => assert!(false), Err(e) => match e { net_error::VerifyingError(msg) => { - assert_eq!(&msg, "Incorrect number of signatures") + if is_order_independent_multisig(&oversigned_tx) { + assert!( + msg.contains("Signer hash does not equal hash of public key(s)"), + "{msg}" + ) + } else { + assert_eq!(&msg, "Incorrect number of signatures") + } } _ => assert!(false), }, @@ -5774,6 +5799,100 @@ mod test { } } + #[test] + fn tx_stacks_transaction_sign_verify_standard_order_independent_p2sh_extra_signers() { + let privk_1 = StacksPrivateKey::from_hex( + "6d430bb91222408e7706c9001cfaeb91b08c2be6d5ac95779ab52c6b431950e001", + ) + .unwrap(); + let privk_2 = StacksPrivateKey::from_hex( + "2a584d899fed1d24e26b524f202763c8ab30260167429f157f1c119f550fa6af01", + ) + .unwrap(); + let privk_3 = StacksPrivateKey::from_hex( + "d5200dee706ee53ae98a03fba6cf4fdcc5084c30cfa9e1b3462dcdeaa3e0f1d201", + ) + .unwrap(); + + let pubk_1 = StacksPublicKey::from_private(&privk_1); + let pubk_2 = StacksPublicKey::from_private(&privk_2); + let pubk_3 = StacksPublicKey::from_private(&privk_3); + + let origin_auth = TransactionAuth::Standard( + TransactionSpendingCondition::new_multisig_order_independent_p2sh( + 2, + vec![pubk_1.clone(), pubk_2.clone(), pubk_3.clone()], + ) + .unwrap(), + ); + + let origin_address = origin_auth.origin().address_mainnet(); + assert_eq!( + origin_address, + StacksAddress { + version: C32_ADDRESS_VERSION_MAINNET_MULTISIG, + bytes: Hash160::from_hex("a23ea89d6529ac48ac766f720e480beec7f19273").unwrap(), + } + ); + + let txs = tx_stacks_transaction_test_txs(&origin_auth); + + for mut tx in txs { + assert_eq!(tx.auth().origin().num_signatures(), 0); + + let initial_sig_hash = tx.sign_begin(); + let sig3 = tx + .sign_no_append_origin(&initial_sig_hash, &privk_3) + .unwrap(); + let sig2 = tx + .sign_no_append_origin(&initial_sig_hash, &privk_2) + .unwrap(); + let sig1 = tx + .sign_no_append_origin(&initial_sig_hash, &privk_1) + .unwrap(); + + let _ = tx.append_origin_signature(sig1, TransactionPublicKeyEncoding::Compressed); + let _ = tx.append_origin_signature(sig2, TransactionPublicKeyEncoding::Compressed); + let _ = tx.append_origin_signature(sig3, TransactionPublicKeyEncoding::Compressed); + + //check_oversign_origin_multisig(&mut tx); + check_sign_no_sponsor(&mut tx); + + assert_eq!(tx.auth().origin().num_signatures(), 3); + + // auth is standard and first two auth fields are signatures for compressed keys. + // third field is the third public key + match tx.auth { + TransactionAuth::Standard(ref origin) => match origin { + TransactionSpendingCondition::OrderIndependentMultisig(ref data) => { + assert_eq!(data.signer, origin_address.bytes); + assert_eq!(data.fields.len(), 3); + assert!(data.fields[0].is_signature()); + assert!(data.fields[1].is_signature()); + assert!(data.fields[2].is_signature()); + + assert_eq!( + data.fields[0].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + assert_eq!( + data.fields[1].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + assert_eq!( + data.fields[2].as_signature().unwrap().0, + TransactionPublicKeyEncoding::Compressed + ); + } + _ => assert!(false), + }, + _ => assert!(false), + }; + + test_signature_and_corruption(&tx, true, false); + } + } + #[test] fn tx_stacks_transaction_sign_verify_sponsored_order_independent_p2sh() { let origin_privk = StacksPrivateKey::from_hex( From 6bd3a61e13d92d29b7c2da342461c29380fe06d2 Mon Sep 17 00:00:00 2001 From: Jeff Bencin Date: Mon, 29 Apr 2024 09:43:06 -0400 Subject: [PATCH 34/41] chore: Address PR comments from Jude --- stackslib/src/chainstate/stacks/auth.rs | 14 +++++++------- stackslib/src/chainstate/stacks/block.rs | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/stackslib/src/chainstate/stacks/auth.rs b/stackslib/src/chainstate/stacks/auth.rs index c83eedf0ff..06cf64d037 100644 --- a/stackslib/src/chainstate/stacks/auth.rs +++ b/stackslib/src/chainstate/stacks/auth.rs @@ -710,11 +710,11 @@ impl TransactionSpendingCondition { Some(TransactionSpendingCondition::Singlesig( SinglesigSpendingCondition { - signer: signer_addr.bytes.clone(), + signer: signer_addr.bytes, nonce: 0, tx_fee: 0, hash_mode: SinglesigHashMode::P2PKH, - key_encoding: key_encoding, + key_encoding, signature: MessageSignature::empty(), }, )) @@ -730,7 +730,7 @@ impl TransactionSpendingCondition { Some(TransactionSpendingCondition::Singlesig( SinglesigSpendingCondition { - signer: signer_addr.bytes.clone(), + signer: signer_addr.bytes, nonce: 0, tx_fee: 0, hash_mode: SinglesigHashMode::P2WPKH, @@ -753,7 +753,7 @@ impl TransactionSpendingCondition { Some(TransactionSpendingCondition::Multisig( MultisigSpendingCondition { - signer: signer_addr.bytes.clone(), + signer: signer_addr.bytes, nonce: 0, tx_fee: 0, hash_mode: MultisigHashMode::P2SH, @@ -776,7 +776,7 @@ impl TransactionSpendingCondition { Some(TransactionSpendingCondition::OrderIndependentMultisig( OrderIndependentMultisigSpendingCondition { - signer: signer_addr.bytes.clone(), + signer: signer_addr.bytes, nonce: 0, tx_fee: 0, hash_mode: OrderIndependentMultisigHashMode::P2SH, @@ -799,7 +799,7 @@ impl TransactionSpendingCondition { Some(TransactionSpendingCondition::OrderIndependentMultisig( OrderIndependentMultisigSpendingCondition { - signer: signer_addr.bytes.clone(), + signer: signer_addr.bytes, nonce: 0, tx_fee: 0, hash_mode: OrderIndependentMultisigHashMode::P2WSH, @@ -822,7 +822,7 @@ impl TransactionSpendingCondition { Some(TransactionSpendingCondition::Multisig( MultisigSpendingCondition { - signer: signer_addr.bytes.clone(), + signer: signer_addr.bytes, nonce: 0, tx_fee: 0, hash_mode: MultisigHashMode::P2WSH, diff --git a/stackslib/src/chainstate/stacks/block.rs b/stackslib/src/chainstate/stacks/block.rs index 97c64f35f8..1a155ad673 100644 --- a/stackslib/src/chainstate/stacks/block.rs +++ b/stackslib/src/chainstate/stacks/block.rs @@ -601,7 +601,7 @@ impl StacksBlock { } } if !tx.auth.is_supported_in_epoch(epoch_id) { - error!("Order independent multisig transactions not supported before Stacks 3.0"); + error!("Authentication mode not supported in Epoch {epoch_id}"); return false; } } From be71ee8ed9f9a331f6c707de48624b204a9dc28d Mon Sep 17 00:00:00 2001 From: Jeff Bencin Date: Mon, 29 Apr 2024 13:21:08 -0400 Subject: [PATCH 35/41] chore: Address PR comments from Jude --- stackslib/src/chainstate/stacks/db/blocks.rs | 6 +- .../src/chainstate/stacks/db/transactions.rs | 204 ++++++++++-------- 2 files changed, 116 insertions(+), 94 deletions(-) diff --git a/stackslib/src/chainstate/stacks/db/blocks.rs b/stackslib/src/chainstate/stacks/db/blocks.rs index 792ca8edc7..5d05ae0a4a 100644 --- a/stackslib/src/chainstate/stacks/db/blocks.rs +++ b/stackslib/src/chainstate/stacks/db/blocks.rs @@ -6650,7 +6650,9 @@ impl StacksChainState { // 1: must parse (done) // 2: it must be validly signed. - StacksChainState::process_transaction_precheck(&chainstate_config, &tx) + let epoch = clarity_connection.get_epoch().clone(); + + StacksChainState::process_transaction_precheck(&chainstate_config, &tx, epoch) .map_err(|e| MemPoolRejection::FailedToValidate(e))?; // 3: it must pay a tx fee @@ -6664,8 +6666,6 @@ impl StacksChainState { } // 4: check if transaction is valid in the current epoch - let epoch = clarity_connection.get_epoch().clone(); - if !StacksBlock::validate_transactions_static_epoch(&[tx.clone()], epoch) { return Err(MemPoolRejection::Other( "Transaction is not supported in this epoch".to_string(), diff --git a/stackslib/src/chainstate/stacks/db/transactions.rs b/stackslib/src/chainstate/stacks/db/transactions.rs index e8412f5060..ecf73e3655 100644 --- a/stackslib/src/chainstate/stacks/db/transactions.rs +++ b/stackslib/src/chainstate/stacks/db/transactions.rs @@ -521,8 +521,18 @@ impl StacksChainState { pub fn process_transaction_precheck( config: &DBConfig, tx: &StacksTransaction, + epoch_id: StacksEpochId, ) -> Result<(), Error> { // valid auth? + if !tx.auth.is_supported_in_epoch(epoch_id) { + let msg = format!( + "Invalid tx {}: authentication mode not supported in Epoch {epoch_id}", + tx.txid() + ); + warn!("{msg}"); + + return Err(Error::InvalidStacksTransaction(msg, false)); + } tx.verify().map_err(Error::NetError)?; // destined for us? @@ -1467,7 +1477,7 @@ impl StacksChainState { debug!("Process transaction {} ({})", tx.txid(), tx.payload.name()); let epoch = clarity_block.get_epoch(); - StacksChainState::process_transaction_precheck(&clarity_block.config, tx)?; + StacksChainState::process_transaction_precheck(&clarity_block.config, tx, epoch)?; // what version of Clarity did the transaction caller want? And, is it valid now? let clarity_version = StacksChainState::get_tx_clarity_version(clarity_block, tx)?; @@ -1480,15 +1490,6 @@ impl StacksChainState { } } - if !StacksBlock::validate_transactions_static_epoch(&vec![tx.clone()], epoch) { - let msg = format!( - "Invalid transaction {}: target epoch is not activated", - tx.txid() - ); - warn!("{}", &msg); - return Err(Error::InvalidStacksTransaction(msg, false)); - } - let mut transaction = clarity_block.connection().start_transaction_processing(); let fee = tx.get_tx_fee(); @@ -9381,6 +9382,27 @@ pub mod test { }; } + /// Call `process_transaction()` with prechecks + pub fn validate_transactions_static_epoch_and_process_transaction( + clarity_block: &mut ClarityTx, + tx: &StacksTransaction, + quiet: bool, + ast_rules: ASTRules, + ) -> Result<(u64, StacksTransactionReceipt), Error> { + let epoch = clarity_block.get_epoch(); + + if !StacksBlock::validate_transactions_static_epoch(&vec![tx.clone()], epoch) { + let msg = format!( + "Invalid transaction {}: target epoch is not activated", + tx.txid() + ); + warn!("{}", &msg); + return Err(Error::InvalidStacksTransaction(msg, false)); + } + + StacksChainState::process_transaction(clarity_block, tx, quiet, ast_rules) + } + #[test] fn test_checkerrors_at_runtime() { let privk = StacksPrivateKey::from_hex( @@ -9703,7 +9725,7 @@ pub mod test { &BlockHeaderHash([1u8; 32]), ); - let (fee, _) = StacksChainState::process_transaction( + let (fee, _) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_runtime_checkerror_trait_tx_no_version, false, @@ -9712,7 +9734,7 @@ pub mod test { .unwrap(); assert_eq!(fee, 1); - let (fee, _) = StacksChainState::process_transaction( + let (fee, _) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_runtime_checkerror_impl_tx_no_version, false, @@ -9721,7 +9743,7 @@ pub mod test { .unwrap(); assert_eq!(fee, 1); - let (fee, _) = StacksChainState::process_transaction( + let (fee, _) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_runtime_checkerror_tx_clar1_no_version, false, @@ -9730,7 +9752,7 @@ pub mod test { .unwrap(); assert_eq!(fee, 1); - let err = StacksChainState::process_transaction( + let err = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_test_trait_checkerror_tx, false, @@ -9745,7 +9767,7 @@ pub mod test { panic!("Did not get unchecked interpreter error"); } - let err = StacksChainState::process_transaction( + let err = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_runtime_checkerror_impl_tx, false, @@ -9758,7 +9780,7 @@ pub mod test { panic!("Did not get epoch is not activated error"); } - let err = StacksChainState::process_transaction( + let err = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_runtime_checkerror_tx_clar1, false, @@ -9771,7 +9793,7 @@ pub mod test { panic!("Did not get epoch is not activated error"); } - let err = StacksChainState::process_transaction( + let err = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_runtime_checkerror_trait_tx, false, @@ -9787,7 +9809,7 @@ pub mod test { let acct = StacksChainState::get_account(&mut conn, &addr.into()); assert_eq!(acct.nonce, 3); - let err = StacksChainState::process_transaction( + let err = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_runtime_checkerror_cc_contract_tx_clar1_no_version, false, @@ -9815,7 +9837,7 @@ pub mod test { &BlockHeaderHash([2u8; 32]), ); - let (fee, _) = StacksChainState::process_transaction( + let (fee, _) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_runtime_checkerror_trait_tx_no_version, false, @@ -9824,7 +9846,7 @@ pub mod test { .unwrap(); assert_eq!(fee, 1); - let (fee, _) = StacksChainState::process_transaction( + let (fee, _) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_runtime_checkerror_impl_tx_no_version, false, @@ -9833,7 +9855,7 @@ pub mod test { .unwrap(); assert_eq!(fee, 1); - let (fee, _) = StacksChainState::process_transaction( + let (fee, _) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_runtime_checkerror_tx_clar1_no_version, false, @@ -9842,7 +9864,7 @@ pub mod test { .unwrap(); assert_eq!(fee, 1); - let err = StacksChainState::process_transaction( + let err = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_test_trait_checkerror_tx, false, @@ -9857,7 +9879,7 @@ pub mod test { panic!("Did not get unchecked interpreter error"); } - let err = StacksChainState::process_transaction( + let err = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_runtime_checkerror_impl_tx, false, @@ -9870,7 +9892,7 @@ pub mod test { panic!("Did not get epoch is not activated error"); } - let err = StacksChainState::process_transaction( + let err = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_runtime_checkerror_tx_clar1, false, @@ -9883,7 +9905,7 @@ pub mod test { panic!("Did not get epoch is not activated error"); } - let err = StacksChainState::process_transaction( + let err = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_runtime_checkerror_trait_tx, false, @@ -9898,7 +9920,7 @@ pub mod test { let acct = StacksChainState::get_account(&mut conn, &addr.into()); assert_eq!(acct.nonce, 3); - let err = StacksChainState::process_transaction( + let err = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_runtime_checkerror_cc_contract_tx_clar1_no_version, false, @@ -9933,7 +9955,7 @@ pub mod test { let signed_runtime_checkerror_cc_contract_tx_clar1 = signer.get_tx().unwrap(); - let (fee, _) = StacksChainState::process_transaction( + let (fee, _) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_runtime_checkerror_trait_tx, false, @@ -9942,7 +9964,7 @@ pub mod test { .unwrap(); assert_eq!(fee, 1); - let (fee, _) = StacksChainState::process_transaction( + let (fee, _) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_runtime_checkerror_impl_tx, false, @@ -9960,7 +9982,7 @@ pub mod test { .unwrap(); assert_eq!(fee, 1); - let (fee, tx_receipt) = StacksChainState::process_transaction( + let (fee, tx_receipt) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_test_trait_checkerror_tx, false, @@ -9984,7 +10006,7 @@ pub mod test { .find("TypeValueError(OptionalType(CallableType(Trait(TraitIdentifier ") .is_some()); - let (fee, tx_receipt) = StacksChainState::process_transaction( + let (fee, tx_receipt) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_runtime_checkerror_cc_contract_tx_clar1, false, @@ -10019,7 +10041,7 @@ pub mod test { &BlockHeaderHash([4u8; 32]), ); - let (fee, _) = StacksChainState::process_transaction( + let (fee, _) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_runtime_checkerror_trait_tx, false, @@ -10028,7 +10050,7 @@ pub mod test { .unwrap(); assert_eq!(fee, 1); - let (fee, _) = StacksChainState::process_transaction( + let (fee, _) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_runtime_checkerror_impl_tx, false, @@ -10037,7 +10059,7 @@ pub mod test { .unwrap(); assert_eq!(fee, 1); - let (fee, _) = StacksChainState::process_transaction( + let (fee, _) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_runtime_checkerror_tx_clar2, false, @@ -10046,7 +10068,7 @@ pub mod test { .unwrap(); assert_eq!(fee, 1); - let (fee, tx_receipt) = StacksChainState::process_transaction( + let (fee, tx_receipt) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_test_trait_checkerror_tx, false, @@ -10066,7 +10088,7 @@ pub mod test { assert!(tx_receipt.vm_error.is_none()); - let (fee, tx_receipt) = StacksChainState::process_transaction( + let (fee, tx_receipt) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_runtime_checkerror_cc_contract_tx_clar2, false, @@ -10318,7 +10340,7 @@ pub mod test { &BlockHeaderHash([1u8; 32]), ); - let (fee, _) = StacksChainState::process_transaction( + let (fee, _) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_foo_trait_tx_no_version, false, @@ -10327,7 +10349,7 @@ pub mod test { .unwrap(); assert_eq!(fee, 1); - let (fee, _) = StacksChainState::process_transaction( + let (fee, _) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_foo_impl_tx_no_version, false, @@ -10336,7 +10358,7 @@ pub mod test { .unwrap(); assert_eq!(fee, 1); - let (fee, tx_receipt) = StacksChainState::process_transaction( + let (fee, tx_receipt) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_call_foo_tx_clar1_no_version, false, @@ -10352,7 +10374,7 @@ pub mod test { _ => panic!("expected the contract publish to fail"), } - let err = StacksChainState::process_transaction( + let err = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_foo_trait_tx, false, @@ -10365,7 +10387,7 @@ pub mod test { panic!("Did not get epoch is not activated error"); } - let err = StacksChainState::process_transaction( + let err = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_foo_impl_tx, false, @@ -10378,7 +10400,7 @@ pub mod test { panic!("Did not get epoch is not activated error"); } - let err = StacksChainState::process_transaction( + let err = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_call_foo_tx_clar1, false, @@ -10402,7 +10424,7 @@ pub mod test { &BlockHeaderHash([2u8; 32]), ); - let (fee, _) = StacksChainState::process_transaction( + let (fee, _) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_foo_trait_tx_no_version, false, @@ -10411,7 +10433,7 @@ pub mod test { .unwrap(); assert_eq!(fee, 1); - let (fee, _) = StacksChainState::process_transaction( + let (fee, _) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_foo_impl_tx_no_version, false, @@ -10420,7 +10442,7 @@ pub mod test { .unwrap(); assert_eq!(fee, 1); - let (fee, tx_receipt) = StacksChainState::process_transaction( + let (fee, tx_receipt) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_call_foo_tx_clar1_no_version, false, @@ -10436,7 +10458,7 @@ pub mod test { _ => panic!("expected the contract publish to fail"), } - let err = StacksChainState::process_transaction( + let err = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_test_call_foo_tx, false, @@ -10451,7 +10473,7 @@ pub mod test { panic!("Did not get unchecked interpreter error"); } - let err = StacksChainState::process_transaction( + let err = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_foo_trait_tx, false, @@ -10464,7 +10486,7 @@ pub mod test { panic!("Did not get epoch is not activated error"); } - let err = StacksChainState::process_transaction( + let err = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_foo_impl_tx, false, @@ -10477,7 +10499,7 @@ pub mod test { panic!("Did not get epoch is not activated error"); } - let err = StacksChainState::process_transaction( + let err = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_call_foo_tx_clar1, false, @@ -10501,7 +10523,7 @@ pub mod test { &BlockHeaderHash([3u8; 32]), ); - let (fee, _) = StacksChainState::process_transaction( + let (fee, _) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_foo_trait_tx, false, @@ -10510,7 +10532,7 @@ pub mod test { .unwrap(); assert_eq!(fee, 1); - let (fee, _) = StacksChainState::process_transaction( + let (fee, _) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_foo_impl_tx, false, @@ -10519,7 +10541,7 @@ pub mod test { .unwrap(); assert_eq!(fee, 1); - let (fee, tx_receipt) = StacksChainState::process_transaction( + let (fee, tx_receipt) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_call_foo_tx_clar1, false, @@ -10546,7 +10568,7 @@ pub mod test { &BlockHeaderHash([4u8; 32]), ); - let (fee, _) = StacksChainState::process_transaction( + let (fee, _) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_foo_trait_tx, false, @@ -10555,7 +10577,7 @@ pub mod test { .unwrap(); assert_eq!(fee, 1); - let (fee, _) = StacksChainState::process_transaction( + let (fee, _) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_foo_impl_tx, false, @@ -10564,7 +10586,7 @@ pub mod test { .unwrap(); assert_eq!(fee, 1); - let (fee, _) = StacksChainState::process_transaction( + let (fee, _) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_call_foo_tx_clar2, false, @@ -10573,7 +10595,7 @@ pub mod test { .unwrap(); assert_eq!(fee, 1); - let (fee, tx_receipt) = StacksChainState::process_transaction( + let (fee, tx_receipt) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_test_call_foo_tx, false, @@ -10894,7 +10916,7 @@ pub mod test { &BlockHeaderHash([1u8; 32]), ); - let (fee, _) = StacksChainState::process_transaction( + let (fee, _) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_foo_trait_tx_no_version, false, @@ -10903,7 +10925,7 @@ pub mod test { .unwrap(); assert_eq!(fee, 1); - let (fee, _) = StacksChainState::process_transaction( + let (fee, _) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_transitive_trait_clar1_tx_no_version, false, @@ -10912,7 +10934,7 @@ pub mod test { .unwrap(); assert_eq!(fee, 1); - let (fee, _) = StacksChainState::process_transaction( + let (fee, _) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_foo_impl_tx_no_version, false, @@ -10921,7 +10943,7 @@ pub mod test { .unwrap(); assert_eq!(fee, 1); - let (fee, tx_receipt) = StacksChainState::process_transaction( + let (fee, tx_receipt) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_call_foo_tx_clar1_no_version, false, @@ -10930,7 +10952,7 @@ pub mod test { .unwrap(); assert_eq!(fee, 1); - let err = StacksChainState::process_transaction( + let err = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_test_call_foo_tx, false, @@ -10946,7 +10968,7 @@ pub mod test { } assert_eq!(fee, 1); - let err = StacksChainState::process_transaction( + let err = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_foo_trait_tx, false, @@ -10959,7 +10981,7 @@ pub mod test { panic!("Did not get epoch is not activated error"); } - let err = StacksChainState::process_transaction( + let err = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_transitive_trait_clar1_tx, false, @@ -10972,7 +10994,7 @@ pub mod test { panic!("Did not get epoch is not activated error"); } - let err = StacksChainState::process_transaction( + let err = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_foo_impl_tx, false, @@ -10985,7 +11007,7 @@ pub mod test { panic!("Did not get epoch is not activated error"); } - let err = StacksChainState::process_transaction( + let err = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_call_foo_tx_clar1, false, @@ -11009,7 +11031,7 @@ pub mod test { &BlockHeaderHash([2u8; 32]), ); - let (fee, _) = StacksChainState::process_transaction( + let (fee, _) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_foo_trait_tx_no_version, false, @@ -11018,7 +11040,7 @@ pub mod test { .unwrap(); assert_eq!(fee, 1); - let (fee, _) = StacksChainState::process_transaction( + let (fee, _) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_transitive_trait_clar1_tx_no_version, false, @@ -11027,7 +11049,7 @@ pub mod test { .unwrap(); assert_eq!(fee, 1); - let (fee, _) = StacksChainState::process_transaction( + let (fee, _) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_foo_impl_tx_no_version, false, @@ -11036,7 +11058,7 @@ pub mod test { .unwrap(); assert_eq!(fee, 1); - let (fee, tx_receipt) = StacksChainState::process_transaction( + let (fee, tx_receipt) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_call_foo_tx_clar1_no_version, false, @@ -11045,7 +11067,7 @@ pub mod test { .unwrap(); assert_eq!(fee, 1); - let err = StacksChainState::process_transaction( + let err = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_test_call_foo_tx, false, @@ -11060,7 +11082,7 @@ pub mod test { panic!("Did not get unchecked interpreter error"); } - let err = StacksChainState::process_transaction( + let err = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_foo_trait_tx, false, @@ -11073,7 +11095,7 @@ pub mod test { panic!("Did not get epoch is not activated error"); } - let err = StacksChainState::process_transaction( + let err = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_transitive_trait_clar1_tx, false, @@ -11086,7 +11108,7 @@ pub mod test { panic!("Did not get epoch is not activated error"); } - let err = StacksChainState::process_transaction( + let err = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_foo_impl_tx, false, @@ -11099,7 +11121,7 @@ pub mod test { panic!("Did not get epoch is not activated error"); } - let err = StacksChainState::process_transaction( + let err = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_call_foo_tx_clar1, false, @@ -11123,7 +11145,7 @@ pub mod test { &BlockHeaderHash([3u8; 32]), ); - let (fee, _) = StacksChainState::process_transaction( + let (fee, _) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_foo_trait_tx, false, @@ -11132,7 +11154,7 @@ pub mod test { .unwrap(); assert_eq!(fee, 1); - let (fee, _) = StacksChainState::process_transaction( + let (fee, _) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_transitive_trait_clar1_tx, false, @@ -11141,7 +11163,7 @@ pub mod test { .unwrap(); assert_eq!(fee, 1); - let (fee, _) = StacksChainState::process_transaction( + let (fee, _) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_foo_impl_tx, false, @@ -11150,7 +11172,7 @@ pub mod test { .unwrap(); assert_eq!(fee, 1); - let (fee, tx_receipt) = StacksChainState::process_transaction( + let (fee, tx_receipt) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_call_foo_tx_clar1, false, @@ -11159,7 +11181,7 @@ pub mod test { .unwrap(); assert_eq!(fee, 1); - let (fee, tx_receipt) = StacksChainState::process_transaction( + let (fee, tx_receipt) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_test_call_foo_tx, false, @@ -11190,7 +11212,7 @@ pub mod test { &BlockHeaderHash([4u8; 32]), ); - let (fee, _) = StacksChainState::process_transaction( + let (fee, _) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_foo_trait_tx, false, @@ -11199,7 +11221,7 @@ pub mod test { .unwrap(); assert_eq!(fee, 1); - let (fee, _) = StacksChainState::process_transaction( + let (fee, _) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_transitive_trait_clar1_tx, false, @@ -11208,7 +11230,7 @@ pub mod test { .unwrap(); assert_eq!(fee, 1); - let (fee, _) = StacksChainState::process_transaction( + let (fee, _) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_foo_impl_tx, false, @@ -11217,7 +11239,7 @@ pub mod test { .unwrap(); assert_eq!(fee, 1); - let (fee, tx_receipt) = StacksChainState::process_transaction( + let (fee, tx_receipt) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_call_foo_tx_clar2, false, @@ -11226,7 +11248,7 @@ pub mod test { .unwrap(); assert_eq!(fee, 1); - let (fee, tx_receipt) = StacksChainState::process_transaction( + let (fee, tx_receipt) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_test_call_foo_tx, false, @@ -11257,7 +11279,7 @@ pub mod test { &BlockHeaderHash([5u8; 32]), ); - let (fee, _) = StacksChainState::process_transaction( + let (fee, _) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_foo_trait_tx, false, @@ -11266,7 +11288,7 @@ pub mod test { .unwrap(); assert_eq!(fee, 1); - let (fee, _) = StacksChainState::process_transaction( + let (fee, _) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_transitive_trait_clar2_tx, false, @@ -11275,7 +11297,7 @@ pub mod test { .unwrap(); assert_eq!(fee, 1); - let (fee, _) = StacksChainState::process_transaction( + let (fee, _) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_foo_impl_tx, false, @@ -11284,7 +11306,7 @@ pub mod test { .unwrap(); assert_eq!(fee, 1); - let (fee, tx_receipt) = StacksChainState::process_transaction( + let (fee, tx_receipt) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_call_foo_tx_clar2, false, @@ -11311,7 +11333,7 @@ pub mod test { &BlockHeaderHash([6u8; 32]), ); - let (fee, _) = StacksChainState::process_transaction( + let (fee, _) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_foo_trait_tx, false, @@ -11320,7 +11342,7 @@ pub mod test { .unwrap(); assert_eq!(fee, 1); - let (fee, _) = StacksChainState::process_transaction( + let (fee, _) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_transitive_trait_clar2_tx, false, @@ -11329,7 +11351,7 @@ pub mod test { .unwrap(); assert_eq!(fee, 1); - let (fee, _) = StacksChainState::process_transaction( + let (fee, _) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_foo_impl_tx, false, @@ -11338,7 +11360,7 @@ pub mod test { .unwrap(); assert_eq!(fee, 1); - let (fee, tx_receipt) = StacksChainState::process_transaction( + let (fee, tx_receipt) = validate_transactions_static_epoch_and_process_transaction( &mut conn, &signed_call_foo_tx_clar1, false, From f910e6d3821d4e30864aae5fb5b31621ecce7796 Mon Sep 17 00:00:00 2001 From: Jeff Bencin Date: Mon, 29 Apr 2024 13:21:08 -0400 Subject: [PATCH 36/41] fix: Unit tests pass --- stackslib/src/chainstate/stacks/miner.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/stackslib/src/chainstate/stacks/miner.rs b/stackslib/src/chainstate/stacks/miner.rs index ec8ac4a36c..64b131c17c 100644 --- a/stackslib/src/chainstate/stacks/miner.rs +++ b/stackslib/src/chainstate/stacks/miner.rs @@ -2580,9 +2580,11 @@ impl BlockBuilder for StacksBlockBuilder { } // preemptively skip problematic transactions + let epoch_id = clarity_tx.get_epoch(); + if let Err(e) = Relayer::static_check_problematic_relayed_tx( clarity_tx.config.mainnet, - clarity_tx.get_epoch(), + epoch_id, &tx, ast_rules, ) { @@ -2592,6 +2594,19 @@ impl BlockBuilder for StacksBlockBuilder { ); return TransactionResult::problematic(&tx, Error::NetError(e)); } + + if !StacksBlock::validate_transactions_static_epoch(&[tx.clone()], epoch_id) { + let msg = format!( + "Invalid transaction {}: target epoch is not activated", + tx.txid() + ); + warn!("{msg}"); + return TransactionResult::skipped_due_to_error( + tx, + Error::InvalidStacksTransaction(msg, false), + ); + } + let (fee, receipt) = match StacksChainState::process_transaction( clarity_tx, tx, quiet, ast_rules, ) { From b25eef8e22d0ff115bcfb74787b0c53b3a8a89ea Mon Sep 17 00:00:00 2001 From: Jeff Bencin Date: Tue, 30 Apr 2024 13:30:18 -0400 Subject: [PATCH 37/41] refactor: Pass `Option`s to `verify_block_epoch_validation()` --- stackslib/src/chainstate/stacks/block.rs | 104 +++++++++++------------ 1 file changed, 48 insertions(+), 56 deletions(-) diff --git a/stackslib/src/chainstate/stacks/block.rs b/stackslib/src/chainstate/stacks/block.rs index 1a155ad673..881b59be63 100644 --- a/stackslib/src/chainstate/stacks/block.rs +++ b/stackslib/src/chainstate/stacks/block.rs @@ -1680,12 +1680,10 @@ mod test { fn verify_block_epoch_validation( txs: &[StacksTransaction], - tx_coinbase_old: StacksTransaction, - tx_coinbase_nakamoto: StacksTransaction, + tx_coinbase_old: Option, + tx_coinbase_nakamoto: Option, activation_epoch_id: StacksEpochId, header: StacksBlockHeader, - need_to_include_coinbase_old: bool, - need_to_include_coinbase_nakamoto: bool, deactivation_epoch_id: Option, ) { let epoch_list = [ @@ -1714,41 +1712,47 @@ mod test { txs: txs.to_vec(), }; - let mut txs_with_coinbase = txs.to_vec(); - txs_with_coinbase.insert(0, tx_coinbase_old); + let block_with_coinbase_tx = tx_coinbase_old.map(|coinbase| { + let mut txs_with_coinbase = txs.to_vec(); + txs_with_coinbase.insert(0, coinbase); - let mut block_header_dup_tx_with_coinbase = header.clone(); - block_header_dup_tx_with_coinbase.tx_merkle_root = get_tx_root(&txs_with_coinbase.to_vec()); + let mut block_header_dup_tx_with_coinbase = header.clone(); + block_header_dup_tx_with_coinbase.tx_merkle_root = + get_tx_root(&txs_with_coinbase.to_vec()); - let block_with_coinbase_tx = StacksBlock { - header: block_header_dup_tx_with_coinbase.clone(), - txs: txs_with_coinbase, - }; + StacksBlock { + header: block_header_dup_tx_with_coinbase.clone(), + txs: txs_with_coinbase, + } + }); - let mut txs_with_coinbase_nakamoto = txs.to_vec(); - txs_with_coinbase_nakamoto.insert(0, tx_coinbase_nakamoto); + let block_with_coinbase_tx_nakamoto = tx_coinbase_nakamoto.map(|coinbase| { + let mut txs_with_coinbase_nakamoto = txs.to_vec(); + txs_with_coinbase_nakamoto.insert(0, coinbase); - let mut block_header_dup_tx_with_coinbase_nakamoto = header.clone(); - block_header_dup_tx_with_coinbase_nakamoto.tx_merkle_root = - get_tx_root(&txs_with_coinbase_nakamoto.to_vec()); + let mut block_header_dup_tx_with_coinbase_nakamoto = header.clone(); + block_header_dup_tx_with_coinbase_nakamoto.tx_merkle_root = + get_tx_root(&txs_with_coinbase_nakamoto.to_vec()); - let block_with_coinbase_tx_nakamoto = StacksBlock { - header: block_header_dup_tx_with_coinbase_nakamoto.clone(), - txs: txs_with_coinbase_nakamoto, - }; + StacksBlock { + header: block_header_dup_tx_with_coinbase_nakamoto.clone(), + txs: txs_with_coinbase_nakamoto, + } + }); for epoch_id in epoch_list.iter() { - let block_to_check = - if *epoch_id >= StacksEpochId::Epoch30 && need_to_include_coinbase_nakamoto { - block_with_coinbase_tx_nakamoto.clone() - } else if *epoch_id >= StacksEpochId::Epoch21 - && *epoch_id < StacksEpochId::Epoch30 - && need_to_include_coinbase_old - { - block_with_coinbase_tx.clone() - } else { - block.clone() - }; + let block_to_check = if *epoch_id >= StacksEpochId::Epoch30 + && block_with_coinbase_tx_nakamoto.is_some() + { + block_with_coinbase_tx_nakamoto.clone().unwrap() + } else if *epoch_id >= StacksEpochId::Epoch21 + && *epoch_id < StacksEpochId::Epoch30 + && block_with_coinbase_tx.is_some() + { + block_with_coinbase_tx.clone().unwrap() + } else { + block.clone() + }; let mut bytes: Vec = vec![]; block_to_check.consensus_serialize(&mut bytes).unwrap(); @@ -2064,62 +2068,50 @@ mod test { verify_block_epoch_validation( &versioned_contract, - tx_coinbase.clone(), - tx_coinbase_proof.clone(), + Some(tx_coinbase.clone()), + Some(tx_coinbase_proof.clone()), StacksEpochId::Epoch21, header.clone(), - true, - true, None, ); verify_block_epoch_validation( &coinbase_contract, - tx_coinbase.clone(), - tx_coinbase_proof.clone(), + None, + None, StacksEpochId::Epoch21, header.clone(), - false, - false, Some(StacksEpochId::Epoch30), ); verify_block_epoch_validation( &order_independent_multisig_txs, - tx_coinbase.clone(), - tx_coinbase_proof.clone(), + Some(tx_coinbase.clone()), + Some(tx_coinbase_proof.clone()), StacksEpochId::Epoch30, header.clone(), - true, - true, None, ); verify_block_epoch_validation( &nakamoto_txs, - tx_coinbase.clone(), - tx_coinbase_proof.clone(), + Some(tx_coinbase.clone()), + None, StacksEpochId::Epoch30, header.clone(), - true, - false, None, ); verify_block_epoch_validation( &nakamoto_coinbase, - tx_coinbase.clone(), - tx_coinbase_proof.clone(), + Some(tx_coinbase.clone()), + None, StacksEpochId::Epoch30, header.clone(), - true, - false, None, ); verify_block_epoch_validation( &tenure_change_tx, - tx_coinbase.clone(), - tx_coinbase_proof.clone(), + Some(tx_coinbase.clone()), + Some(tx_coinbase_proof.clone()), StacksEpochId::Epoch30, header.clone(), - true, - true, None, ); } From 7a49afae37505dc2c24bde4ba725f09ca1f4a9cb Mon Sep 17 00:00:00 2001 From: Fess Date: Thu, 2 May 2024 02:47:40 +0400 Subject: [PATCH 38/41] fix: rustdocs added for test functions --- stackslib/src/chainstate/stacks/transaction.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/stackslib/src/chainstate/stacks/transaction.rs b/stackslib/src/chainstate/stacks/transaction.rs index 1095e8e534..d31180ade0 100644 --- a/stackslib/src/chainstate/stacks/transaction.rs +++ b/stackslib/src/chainstate/stacks/transaction.rs @@ -1251,6 +1251,9 @@ mod test { use crate::net::*; impl StacksTransaction { + /// Sign a sighash without appending the signature and public key + /// to the given spending condition. + /// Returns the resulting signature fn sign_no_append_origin( &self, cur_sighash: &Txid, @@ -1272,6 +1275,7 @@ mod test { Ok(next_sig) } + /// Appends a signature and public key to the spending condition. fn append_origin_signature( &mut self, signature: MessageSignature, @@ -1295,6 +1299,9 @@ mod test { Ok(()) } + /// Sign a sighash as a sponsor without appending the signature and public key + /// to the given spending condition. + /// Returns the resulting signature fn sign_no_append_sponsor( &mut self, cur_sighash: &Txid, @@ -1321,6 +1328,7 @@ mod test { Ok(next_sig) } + /// Appends a sponsor signature and public key to the spending condition. pub fn append_sponsor_signature( &mut self, signature: MessageSignature, From 4a0a9666c639b21ed8f6a6fcf8c38111132b60b5 Mon Sep 17 00:00:00 2001 From: Fess Date: Mon, 6 May 2024 00:34:24 +0400 Subject: [PATCH 39/41] fix: autotest restored, epoch gating logic removed from `try_mine_tx_with_len` --- stackslib/src/chainstate/stacks/miner.rs | 21 +++------------------ stackslib/src/net/relay.rs | 20 +++++++++++++++++--- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/stackslib/src/chainstate/stacks/miner.rs b/stackslib/src/chainstate/stacks/miner.rs index dee570bc86..35cb97243f 100644 --- a/stackslib/src/chainstate/stacks/miner.rs +++ b/stackslib/src/chainstate/stacks/miner.rs @@ -1279,14 +1279,14 @@ impl<'a> StacksMicroblockBuilder<'a> { // Make the block from the transactions we did manage to get debug!("Block budget exceeded on tx {}", &mempool_tx.tx.txid()); if block_limit_hit == BlockLimitFunction::NO_LIMIT_HIT { - debug!("Block budget exceeded while mining microblock"; + debug!("Block budget exceeded while mining microblock"; "tx" => %mempool_tx.tx.txid(), "next_behavior" => "Switch to mining stx-transfers only"); block_limit_hit = BlockLimitFunction::CONTRACT_LIMIT_HIT; } else if block_limit_hit == BlockLimitFunction::CONTRACT_LIMIT_HIT { - debug!("Block budget exceeded while mining microblock"; + debug!("Block budget exceeded while mining microblock"; "tx" => %mempool_tx.tx.txid(), "next_behavior" => "Stop mining microblock"); block_limit_hit = BlockLimitFunction::LIMIT_REACHED; return Ok(None); @@ -2580,11 +2580,9 @@ impl BlockBuilder for StacksBlockBuilder { } // preemptively skip problematic transactions - let epoch_id = clarity_tx.get_epoch(); - if let Err(e) = Relayer::static_check_problematic_relayed_tx( clarity_tx.config.mainnet, - epoch_id, + clarity_tx.get_epoch(), &tx, ast_rules, ) { @@ -2594,19 +2592,6 @@ impl BlockBuilder for StacksBlockBuilder { ); return TransactionResult::problematic(&tx, Error::NetError(e)); } - - if !StacksBlock::validate_transactions_static_epoch(&[tx.clone()], epoch_id) { - let msg = format!( - "Invalid transaction {}: target epoch is not activated", - tx.txid() - ); - warn!("{msg}"); - return TransactionResult::skipped_due_to_error( - tx, - Error::InvalidStacksTransaction(msg, false), - ); - } - let (fee, receipt) = match StacksChainState::process_transaction( clarity_tx, tx, quiet, ast_rules, ) { diff --git a/stackslib/src/net/relay.rs b/stackslib/src/net/relay.rs index 72771facc0..5d7ef50099 100644 --- a/stackslib/src/net/relay.rs +++ b/stackslib/src/net/relay.rs @@ -6005,9 +6005,23 @@ pub mod test { let (_, _, consensus_hash) = peer.next_burnchain_block(burn_ops.clone()); let sortdb = peer.sortdb.take().unwrap(); - let node = peer.stacks_node.take().unwrap(); - // incorrect transaction was filtered and no error in the block will appear - assert_eq!(stacks_block.txs.len(), 1); + let mut node = peer.stacks_node.take().unwrap(); + match Relayer::process_new_anchored_block( + &sortdb.index_conn(), + &mut node.chainstate, + &consensus_hash, + &stacks_block, + 123, + ) { + Ok(x) => { + eprintln!("{:?}", &stacks_block); + panic!("Stored pay-to-contract stacks block before epoch 2.1"); + } + Err(chainstate_error::InvalidStacksBlock(_)) => {} + Err(e) => { + panic!("Got unexpected error {:?}", &e); + } + }; peer.sortdb = Some(sortdb); peer.stacks_node = Some(node); } From 2e67fa900b5a1ef9f7268529b9c2d1fdcc72c5d4 Mon Sep 17 00:00:00 2001 From: Vlad Date: Thu, 16 May 2024 20:47:10 +0400 Subject: [PATCH 40/41] Update stackslib/src/chainstate/stacks/transaction.rs Co-authored-by: Brice Dobry --- stackslib/src/chainstate/stacks/transaction.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stackslib/src/chainstate/stacks/transaction.rs b/stackslib/src/chainstate/stacks/transaction.rs index d31180ade0..8c691d505c 100644 --- a/stackslib/src/chainstate/stacks/transaction.rs +++ b/stackslib/src/chainstate/stacks/transaction.rs @@ -1244,7 +1244,7 @@ mod test { use crate::chainstate::stacks::test::codec_all_transactions; use crate::chainstate::stacks::{ StacksPublicKey as PubKey, C32_ADDRESS_VERSION_MAINNET_MULTISIG, - C32_ADDRESS_VERSION_MAINNET_SINGLESIG, *, *, + C32_ADDRESS_VERSION_MAINNET_SINGLESIG, *, }; use crate::net::codec::test::check_codec_and_corruption; use crate::net::codec::*; From 9addd7e69cd80b8b086c24233593b5f035cd7b4e Mon Sep 17 00:00:00 2001 From: Fess Date: Thu, 16 May 2024 21:36:57 +0400 Subject: [PATCH 41/41] fix: validate_transaction_static_epoch function added --- stackslib/src/chainstate/stacks/block.rs | 69 ++++++++++++-------- stackslib/src/chainstate/stacks/db/blocks.rs | 2 +- 2 files changed, 41 insertions(+), 30 deletions(-) diff --git a/stackslib/src/chainstate/stacks/block.rs b/stackslib/src/chainstate/stacks/block.rs index 881b59be63..9827d28e9c 100644 --- a/stackslib/src/chainstate/stacks/block.rs +++ b/stackslib/src/chainstate/stacks/block.rs @@ -570,41 +570,52 @@ impl StacksBlock { epoch_id: StacksEpochId, ) -> bool { for tx in txs.iter() { - if let TransactionPayload::Coinbase(_, ref recipient_opt, ref proof_opt) = &tx.payload { - if proof_opt.is_some() && epoch_id < StacksEpochId::Epoch30 { - // not supported - error!("Coinbase with VRF proof not supported before Stacks 3.0"; "txid" => %tx.txid()); - return false; - } - if proof_opt.is_none() && epoch_id >= StacksEpochId::Epoch30 { - // not supported - error!("Coinbase with VRF proof is required in Stacks 3.0 and later"; "txid" => %tx.txid()); - return false; - } - if recipient_opt.is_some() && epoch_id < StacksEpochId::Epoch21 { - // not supported - error!("Coinbase pay-to-alt-recipient not supported before Stacks 2.1"; "txid" => %tx.txid()); - return false; - } + if !StacksBlock::validate_transaction_static_epoch(tx, epoch_id) { + return false; } - if let TransactionPayload::SmartContract(_, ref version_opt) = &tx.payload { - if version_opt.is_some() && epoch_id < StacksEpochId::Epoch21 { - // not supported - error!("Versioned smart contracts not supported before Stacks 2.1"); - return false; - } + } + return true; + } + + /// Verify that one transaction is supported in the given epoch, as indicated by `epoch_id` + pub fn validate_transaction_static_epoch( + tx: &StacksTransaction, + epoch_id: StacksEpochId, + ) -> bool { + if let TransactionPayload::Coinbase(_, ref recipient_opt, ref proof_opt) = &tx.payload { + if proof_opt.is_some() && epoch_id < StacksEpochId::Epoch30 { + // not supported + error!("Coinbase with VRF proof not supported before Stacks 3.0"; "txid" => %tx.txid()); + return false; } - if let TransactionPayload::TenureChange(..) = &tx.payload { - if epoch_id < StacksEpochId::Epoch30 { - error!("TenureChange transaction not supported before Stacks 3.0"; "txid" => %tx.txid()); - return false; - } + if proof_opt.is_none() && epoch_id >= StacksEpochId::Epoch30 { + // not supported + error!("Coinbase with VRF proof is required in Stacks 3.0 and later"; "txid" => %tx.txid()); + return false; } - if !tx.auth.is_supported_in_epoch(epoch_id) { - error!("Authentication mode not supported in Epoch {epoch_id}"); + if recipient_opt.is_some() && epoch_id < StacksEpochId::Epoch21 { + // not supported + error!("Coinbase pay-to-alt-recipient not supported before Stacks 2.1"; "txid" => %tx.txid()); return false; } } + if let TransactionPayload::SmartContract(_, ref version_opt) = &tx.payload { + if version_opt.is_some() && epoch_id < StacksEpochId::Epoch21 { + // not supported + error!("Versioned smart contracts not supported before Stacks 2.1"); + return false; + } + } + if let TransactionPayload::TenureChange(..) = &tx.payload { + if epoch_id < StacksEpochId::Epoch30 { + error!("TenureChange transaction not supported before Stacks 3.0"; "txid" => %tx.txid()); + return false; + } + } + if !tx.auth.is_supported_in_epoch(epoch_id) { + error!("Authentication mode not supported in Epoch {epoch_id}"); + return false; + } return true; } diff --git a/stackslib/src/chainstate/stacks/db/blocks.rs b/stackslib/src/chainstate/stacks/db/blocks.rs index 5d05ae0a4a..0759ac0d01 100644 --- a/stackslib/src/chainstate/stacks/db/blocks.rs +++ b/stackslib/src/chainstate/stacks/db/blocks.rs @@ -6666,7 +6666,7 @@ impl StacksChainState { } // 4: check if transaction is valid in the current epoch - if !StacksBlock::validate_transactions_static_epoch(&[tx.clone()], epoch) { + if !StacksBlock::validate_transaction_static_epoch(tx, epoch) { return Err(MemPoolRejection::Other( "Transaction is not supported in this epoch".to_string(), ));