From 3d5adf577e6921ace4894b924e352433d55bf5f6 Mon Sep 17 00:00:00 2001 From: Hank Stoever Date: Tue, 13 Feb 2024 17:57:04 -0800 Subject: [PATCH] feat: burn op definition and storage for vote-for-agg-key --- stackslib/src/burnchains/burnchain.rs | 3 + .../src/chainstate/burn/db/processing.rs | 7 + stackslib/src/chainstate/burn/db/sortdb.rs | 116 +++++++++++- stackslib/src/chainstate/burn/mod.rs | 4 + .../src/chainstate/burn/operations/mod.rs | 31 ++++ .../burn/operations/vote_for_aggregate_key.rs | 173 ++++++++++++++++++ stackslib/src/chainstate/nakamoto/mod.rs | 3 +- stackslib/src/chainstate/stacks/db/blocks.rs | 64 ++++++- stackslib/src/net/mod.rs | 1 + .../burnchains/bitcoin_regtest_controller.rs | 3 + .../src/burnchains/mocknet_controller.rs | 9 +- 11 files changed, 401 insertions(+), 13 deletions(-) create mode 100644 stackslib/src/chainstate/burn/operations/vote_for_aggregate_key.rs diff --git a/stackslib/src/burnchains/burnchain.rs b/stackslib/src/burnchains/burnchain.rs index 5b5fd5a889b..b46b1dea874 100644 --- a/stackslib/src/burnchains/burnchain.rs +++ b/stackslib/src/burnchains/burnchain.rs @@ -141,6 +141,9 @@ impl BurnchainStateTransition { // the burn distribution, so just account for them for now. all_user_burns.insert(op.txid.clone(), op.clone()); } + BlockstackOperationType::VoteForAggregateKey(_) => { + accepted_ops.push(block_ops[i].clone()); + } }; } diff --git a/stackslib/src/chainstate/burn/db/processing.rs b/stackslib/src/chainstate/burn/db/processing.rs index bf1e83efd90..664e2907253 100644 --- a/stackslib/src/chainstate/burn/db/processing.rs +++ b/stackslib/src/chainstate/burn/db/processing.rs @@ -102,6 +102,13 @@ impl<'a> SortitionHandleTx<'a> { ); BurnchainError::OpError(e) }), + BlockstackOperationType::VoteForAggregateKey(ref op) => op.check().map_err(|e| { + warn!( + "REJECTED({}) vote for aggregate key op {} at {},{}: {:?}", + op.block_height, &op.txid, op.block_height, op.vtxindex, &e + ); + BurnchainError::OpError(e) + }), } } diff --git a/stackslib/src/chainstate/burn/db/sortdb.rs b/stackslib/src/chainstate/burn/db/sortdb.rs index 0be1c77487f..56820dfab35 100644 --- a/stackslib/src/chainstate/burn/db/sortdb.rs +++ b/stackslib/src/chainstate/burn/db/sortdb.rs @@ -39,6 +39,7 @@ use stacks_common::types::chainstate::{ BlockHeaderHash, BurnchainHeaderHash, PoxId, SortitionId, StacksAddress, StacksBlockId, TrieHash, VRFSeed, }; +use stacks_common::types::StacksPublicKeyBuffer; use stacks_common::util::hash::{hex_bytes, to_hex, Hash160, Sha512Trunc256Sum}; use stacks_common::util::secp256k1::{MessageSignature, Secp256k1PublicKey}; use stacks_common::util::vrf::*; @@ -59,7 +60,7 @@ use crate::chainstate::burn::operations::leader_block_commit::{ }; use crate::chainstate::burn::operations::{ BlockstackOperationType, DelegateStxOp, LeaderBlockCommitOp, LeaderKeyRegisterOp, PreStxOp, - StackStxOp, TransferStxOp, UserBurnSupportOp, + StackStxOp, TransferStxOp, UserBurnSupportOp, VoteForAggregateKeyOp, }; use crate::chainstate::burn::{ BlockSnapshot, ConsensusHash, ConsensusHashExtensions, Opcodes, OpsHash, SortitionHash, @@ -431,6 +432,39 @@ impl FromRow for TransferStxOp { } } +impl FromRow for VoteForAggregateKeyOp { + fn from_row<'a>(row: &'a Row) -> Result { + let txid = Txid::from_column(row, "txid")?; + let vtxindex: u32 = row.get_unwrap("vtxindex"); + let block_height = u64::from_column(row, "block_height")?; + let burn_header_hash = BurnchainHeaderHash::from_column(row, "burn_header_hash")?; + + let sender = StacksAddress::from_column(row, "sender_addr")?; + let aggregate_key_str: String = row.get_unwrap("aggregate_key"); + let aggregate_key: StacksPublicKeyBuffer = serde_json::from_str(&aggregate_key_str) + .expect("CORRUPTION: DB stored bad transition ops"); + let round: u32 = row.get_unwrap("round"); + let reward_cycle = u64::from_column(row, "reward_cycle")?; + let signer_index: u16 = row.get_unwrap("signer_index"); + let signer_key_str: String = row.get_unwrap("signer_key"); + let signer_key: StacksPublicKeyBuffer = serde_json::from_str(&signer_key_str) + .expect("CORRUPTION: DB stored bad transition ops"); + + Ok(VoteForAggregateKeyOp { + txid, + vtxindex, + block_height, + burn_header_hash, + sender, + aggregate_key, + round, + reward_cycle, + signer_index, + signer_key, + }) + } +} + impl FromColumn for ASTRules { fn from_column<'a>(row: &'a Row, column_name: &str) -> Result { let x: u8 = row.get_unwrap(column_name); @@ -497,7 +531,7 @@ impl FromRow for StacksEpoch { } } -pub const SORTITION_DB_VERSION: &'static str = "8"; +pub const SORTITION_DB_VERSION: &'static str = "9"; const SORTITION_DB_INITIAL_SCHEMA: &'static [&'static str] = &[ r#" @@ -729,6 +763,23 @@ const SORTITION_DB_SCHEMA_8: &'static [&'static str] = &[ );"#, ]; +const SORTITION_DB_SCHEMA_9: &'static [&'static str] = &[r#" + CREATE TABLE vote_for_aggregate_key ( + txid TEXT NOT NULL, + vtxindex INTEGER NOT NULL, + block_height INTEGER NOT NULL, + burn_header_hash TEXT NOT NULL, + + sender_addr TEXT NOT NULL, + aggregate_key TEXT NOT NULL, + round INTEGER NOT NULL, + reward_cycle INTEGER NOT NULL, + signer_index INTEGER NOT NULL, + signer_key TEXT NOT NULL, + + PRIMARY KEY(txid,burn_header_Hash) + );"#]; + const SORTITION_DB_INDEXES: &'static [&'static str] = &[ "CREATE INDEX IF NOT EXISTS snapshots_block_hashes ON snapshots(block_height,index_root,winning_stacks_block_hash);", "CREATE INDEX IF NOT EXISTS snapshots_block_stacks_hashes ON snapshots(num_sortitions,index_root,winning_stacks_block_hash);", @@ -2905,6 +2956,7 @@ impl SortitionDB { SortitionDB::apply_schema_6(&db_tx, epochs_ref)?; SortitionDB::apply_schema_7(&db_tx, epochs_ref)?; SortitionDB::apply_schema_8(&db_tx)?; + SortitionDB::apply_schema_9(&db_tx)?; db_tx.instantiate_index()?; @@ -3323,6 +3375,18 @@ impl SortitionDB { Ok(()) } + fn apply_schema_9(tx: &DBTx) -> Result<(), db_error> { + for sql_exec in SORTITION_DB_SCHEMA_9 { + tx.execute_batch(sql_exec)?; + } + + tx.execute( + "INSERT OR REPLACE INTO db_config (version) VALUES (?1)", + &["9"], + )?; + Ok(()) + } + fn check_schema_version_or_error(&mut self) -> Result<(), db_error> { match SortitionDB::get_schema_version(self.conn()) { Ok(Some(version)) => { @@ -3377,6 +3441,10 @@ impl SortitionDB { let tx = self.tx_begin()?; SortitionDB::apply_schema_8(&tx.deref())?; tx.commit()?; + } else if version == "8" { + let tx = self.tx_begin()?; + SortitionDB::apply_schema_9(&tx.deref())?; + tx.commit()?; } else if version == expected_version { return Ok(()); } else { @@ -4354,6 +4422,20 @@ impl SortitionDB { ) } + /// Get the list of `vote-for-aggregate-key` operations processed in a given burnchain block. + /// This will be the same list in each PoX fork; it's up to the Stacks block-processing logic + /// to reject them. + pub fn get_vote_for_aggregate_key_ops( + conn: &Connection, + burn_header_hash: &BurnchainHeaderHash, + ) -> Result, db_error> { + query_rows( + conn, + "SELECT * FROM vote_for_aggregate_key WHERE burn_header_hash = ? ORDER BY vtxindex", + &[burn_header_hash], + ) + } + /// Get the list of Transfer-STX operations processed in a given burnchain block. /// This will be the same list in each PoX fork; it's up to the Stacks block-processing logic /// to reject them. @@ -5321,6 +5403,13 @@ impl<'a> SortitionHandleTx<'a> { ); self.insert_delegate_stx(op) } + BlockstackOperationType::VoteForAggregateKey(ref op) => { + info!( + "ACCEPTED({}) vote for aggregate key {} at {},{}", + op.block_height, &op.txid, op.block_height, op.vtxindex + ); + self.insert_vote_for_aggregate_key(op) + } } } @@ -5388,6 +5477,29 @@ impl<'a> SortitionHandleTx<'a> { Ok(()) } + /// Insert a vote-for-aggregate-key op + fn insert_vote_for_aggregate_key( + &mut self, + op: &VoteForAggregateKeyOp, + ) -> Result<(), db_error> { + let args: &[&dyn ToSql] = &[ + &op.txid, + &op.vtxindex, + &u64_to_sql(op.block_height)?, + &op.burn_header_hash, + &op.sender.to_string(), + &serde_json::to_string(&op.aggregate_key).unwrap(), + &op.round, + &u64_to_sql(op.reward_cycle)?, + &op.signer_index, + &serde_json::to_string(&op.signer_key).unwrap(), + ]; + + self.execute("REPLACE INTO vote_for_aggregate_key (txid, vtxindex, block_height, burn_header_hash, sender_addr, aggregate_key, round, reward_cycle, signer_index, signer_key) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)", args)?; + + Ok(()) + } + /// Insert a transfer-stx op fn insert_transfer_stx(&mut self, op: &TransferStxOp) -> Result<(), db_error> { let args: &[&dyn ToSql] = &[ diff --git a/stackslib/src/chainstate/burn/mod.rs b/stackslib/src/chainstate/burn/mod.rs index 4fc937afeeb..c230aca1820 100644 --- a/stackslib/src/chainstate/burn/mod.rs +++ b/stackslib/src/chainstate/burn/mod.rs @@ -68,6 +68,7 @@ pub enum Opcodes { PreStx = 'p' as u8, TransferStx = '$' as u8, DelegateStx = '#' as u8, + VoteForAggregateKey = 'v' as u8, } // a burnchain block snapshot @@ -192,6 +193,7 @@ impl Opcodes { const HTTP_PEG_IN: &'static str = "peg_in"; const HTTP_PEG_OUT_REQUEST: &'static str = "peg_out_request"; const HTTP_PEG_OUT_FULFILL: &'static str = "peg_out_fulfill"; + const HTTP_VOTE_FOR_AGGREGATE_KEY: &'static str = "vote_for_aggregate_key"; pub fn to_http_str(&self) -> &'static str { match self { @@ -202,6 +204,7 @@ impl Opcodes { Opcodes::PreStx => Self::HTTP_PRE_STX, Opcodes::TransferStx => Self::HTTP_TRANSFER_STX, Opcodes::DelegateStx => Self::HTTP_DELEGATE_STX, + Opcodes::VoteForAggregateKey => Self::HTTP_VOTE_FOR_AGGREGATE_KEY, } } @@ -214,6 +217,7 @@ impl Opcodes { Self::HTTP_PRE_STX => Opcodes::PreStx, Self::HTTP_TRANSFER_STX => Opcodes::TransferStx, Self::HTTP_DELEGATE_STX => Opcodes::DelegateStx, + Self::HTTP_VOTE_FOR_AGGREGATE_KEY => Opcodes::VoteForAggregateKey, _ => return None, }; diff --git a/stackslib/src/chainstate/burn/operations/mod.rs b/stackslib/src/chainstate/burn/operations/mod.rs index 189acab16ce..35ae3ebbc09 100644 --- a/stackslib/src/chainstate/burn/operations/mod.rs +++ b/stackslib/src/chainstate/burn/operations/mod.rs @@ -23,6 +23,7 @@ use serde_json::json; use stacks_common::types::chainstate::{ BlockHeaderHash, BurnchainHeaderHash, StacksAddress, StacksBlockId, TrieHash, VRFSeed, }; +use stacks_common::types::StacksPublicKeyBuffer; use stacks_common::util::hash::{hex_bytes, to_hex, Hash160, Sha512Trunc256Sum}; use stacks_common::util::secp256k1::MessageSignature; use stacks_common::util::vrf::VRFPublicKey; @@ -46,6 +47,7 @@ pub mod leader_key_register; pub mod stack_stx; pub mod transfer_stx; pub mod user_burn_support; +pub mod vote_for_aggregate_key; #[cfg(test)] mod test; @@ -305,6 +307,22 @@ pub struct DelegateStxOp { pub burn_header_hash: BurnchainHeaderHash, // hash of the burn chain block header } +#[derive(Debug, PartialEq, Clone, Eq, Serialize, Deserialize)] +pub struct VoteForAggregateKeyOp { + pub sender: StacksAddress, + pub aggregate_key: StacksPublicKeyBuffer, + pub round: u32, + pub reward_cycle: u64, + pub signer_index: u16, + pub signer_key: StacksPublicKeyBuffer, + + // common to all transactions + pub txid: Txid, // transaction ID + pub vtxindex: u32, // index in the block where this tx occurs + pub block_height: u64, // block height at which this tx occurs + pub burn_header_hash: BurnchainHeaderHash, // hash of the burn chain block header +} + fn hex_ser_memo(bytes: &[u8], s: S) -> Result { let inst = to_hex(bytes); s.serialize_str(inst.as_str()) @@ -348,6 +366,7 @@ pub enum BlockstackOperationType { StackStx(StackStxOp), TransferStx(TransferStxOp), DelegateStx(DelegateStxOp), + VoteForAggregateKey(VoteForAggregateKeyOp), } // serialization helpers for blockstack_op_to_json function @@ -375,6 +394,7 @@ impl BlockstackOperationType { BlockstackOperationType::PreStx(_) => Opcodes::PreStx, BlockstackOperationType::TransferStx(_) => Opcodes::TransferStx, BlockstackOperationType::DelegateStx(_) => Opcodes::DelegateStx, + BlockstackOperationType::VoteForAggregateKey(_) => Opcodes::VoteForAggregateKey, } } @@ -391,6 +411,7 @@ impl BlockstackOperationType { BlockstackOperationType::PreStx(ref data) => &data.txid, BlockstackOperationType::TransferStx(ref data) => &data.txid, BlockstackOperationType::DelegateStx(ref data) => &data.txid, + BlockstackOperationType::VoteForAggregateKey(ref data) => &data.txid, } } @@ -403,6 +424,7 @@ impl BlockstackOperationType { BlockstackOperationType::PreStx(ref data) => data.vtxindex, BlockstackOperationType::TransferStx(ref data) => data.vtxindex, BlockstackOperationType::DelegateStx(ref data) => data.vtxindex, + BlockstackOperationType::VoteForAggregateKey(ref data) => data.vtxindex, } } @@ -415,6 +437,7 @@ impl BlockstackOperationType { BlockstackOperationType::PreStx(ref data) => data.block_height, BlockstackOperationType::TransferStx(ref data) => data.block_height, BlockstackOperationType::DelegateStx(ref data) => data.block_height, + BlockstackOperationType::VoteForAggregateKey(ref data) => data.block_height, } } @@ -427,6 +450,7 @@ impl BlockstackOperationType { BlockstackOperationType::PreStx(ref data) => data.burn_header_hash.clone(), BlockstackOperationType::TransferStx(ref data) => data.burn_header_hash.clone(), BlockstackOperationType::DelegateStx(ref data) => data.burn_header_hash.clone(), + BlockstackOperationType::VoteForAggregateKey(ref data) => data.burn_header_hash.clone(), } } @@ -442,6 +466,9 @@ impl BlockstackOperationType { BlockstackOperationType::PreStx(ref mut data) => data.block_height = height, BlockstackOperationType::TransferStx(ref mut data) => data.block_height = height, BlockstackOperationType::DelegateStx(ref mut data) => data.block_height = height, + BlockstackOperationType::VoteForAggregateKey(ref mut data) => { + data.block_height = height + } }; } @@ -459,6 +486,9 @@ impl BlockstackOperationType { BlockstackOperationType::PreStx(ref mut data) => data.burn_header_hash = hash, BlockstackOperationType::TransferStx(ref mut data) => data.burn_header_hash = hash, BlockstackOperationType::DelegateStx(ref mut data) => data.burn_header_hash = hash, + BlockstackOperationType::VoteForAggregateKey(ref mut data) => { + data.burn_header_hash = hash + } }; } @@ -550,6 +580,7 @@ impl fmt::Display for BlockstackOperationType { BlockstackOperationType::UserBurnSupport(ref op) => write!(f, "{:?}", op), BlockstackOperationType::TransferStx(ref op) => write!(f, "{:?}", op), BlockstackOperationType::DelegateStx(ref op) => write!(f, "{:?}", op), + BlockstackOperationType::VoteForAggregateKey(ref op) => write!(f, "{:?}", op), } } } diff --git a/stackslib/src/chainstate/burn/operations/vote_for_aggregate_key.rs b/stackslib/src/chainstate/burn/operations/vote_for_aggregate_key.rs new file mode 100644 index 00000000000..afc4108663a --- /dev/null +++ b/stackslib/src/chainstate/burn/operations/vote_for_aggregate_key.rs @@ -0,0 +1,173 @@ +// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation +// Copyright (C) 2020 Stacks Open Internet Foundation +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use std::io::{Read, Write}; + +use stacks_common::codec::{write_next, Error as codec_error, StacksMessageCodec}; +use stacks_common::types::chainstate::{BurnchainHeaderHash, StacksAddress}; +use stacks_common::types::StacksPublicKeyBuffer; +use stacks_common::util::secp256k1::Secp256k1PublicKey; + +use crate::burnchains::bitcoin::BitcoinTxInput; +use crate::burnchains::{BurnchainBlockHeader, BurnchainTransaction, Txid}; +use crate::chainstate::burn::operations::{ + parse_u128_from_be, parse_u16_from_be, parse_u32_from_be, parse_u64_from_be, + BlockstackOperationType, Error as op_error, PreStxOp, VoteForAggregateKeyOp, +}; +use crate::chainstate::burn::Opcodes; +use crate::chainstate::stacks::address::PoxAddress; + +struct ParsedData { + signer_index: u16, + aggregate_key: StacksPublicKeyBuffer, + round: u32, + reward_cycle: u64, +} + +impl VoteForAggregateKeyOp { + pub fn from_tx( + block_header: &BurnchainBlockHeader, + tx: &BurnchainTransaction, + sender: &StacksAddress, + ) -> Result { + VoteForAggregateKeyOp::parse_from_tx( + block_header.block_height, + &block_header.block_hash, + tx, + sender, + ) + } + + fn parse_data(data: &Vec) -> Option { + /* + Wire format: + + 0 2 3 5 38 42 50 + |-----|----|-----------|--------------|------|------------| + magic op signer_index aggregate_key round reward_cycle + + Note that `data` is missing the first 3 bytes -- the magic and op have been stripped + */ + + if data.len() != 47 { + warn!( + "Vote for aggregate key operation data has an invalid length ({} bytes)", + data.len() + ); + return None; + } + + let signer_index = parse_u16_from_be(&data[0..2]).unwrap(); + let aggregate_key = StacksPublicKeyBuffer::from(&data[2..35]); + + let round = parse_u32_from_be(&data[35..39]).unwrap(); + let reward_cycle = parse_u64_from_be(&data[39..47]).unwrap(); + + Some(ParsedData { + signer_index, + aggregate_key, + round, + reward_cycle, + }) + } + + pub fn get_sender_txid(tx: &BurnchainTransaction) -> Result<&Txid, op_error> { + match tx.get_input_tx_ref(0) { + Some((ref txid, vout)) => { + if *vout != 1 { + warn!("Invalid tx: DelegateStxOp must spend the second output of the PreStxOp"); + Err(op_error::InvalidInput) + } else { + Ok(txid) + } + } + None => { + warn!("Invalid tx: DelegateStxOp must have at least one input"); + Err(op_error::InvalidInput) + } + } + } + + pub fn get_sender_pubkey(tx: &BurnchainTransaction) -> Result<&Secp256k1PublicKey, op_error> { + match tx { + BurnchainTransaction::Bitcoin(ref btc) => match btc.inputs.get(0) { + Some(BitcoinTxInput::Raw(_)) => Err(op_error::InvalidInput), + Some(BitcoinTxInput::Structured(input)) => { + input.keys.get(0).ok_or(op_error::InvalidInput) + } + _ => Err(op_error::InvalidInput), + }, + } + } + + pub fn parse_from_tx( + block_height: u64, + block_hash: &BurnchainHeaderHash, + tx: &BurnchainTransaction, + sender: &StacksAddress, + ) -> Result { + let outputs = tx.get_recipients(); + + if tx.num_signers() == 0 { + warn!( + "Invalid tx: inputs: {}, outputs: {}", + tx.num_signers(), + outputs.len() + ); + return Err(op_error::InvalidInput); + } + + if outputs.len() == 0 { + warn!( + "Invalid tx: inputs: {}, outputs: {}", + tx.num_signers(), + outputs.len() + ); + return Err(op_error::InvalidInput); + } + + if tx.opcode() != Opcodes::VoteForAggregateKey as u8 { + warn!("Invalid tx: invalid opcode {}", tx.opcode()); + return Err(op_error::InvalidInput); + }; + + let data = VoteForAggregateKeyOp::parse_data(&tx.data()).ok_or_else(|| { + warn!("Invalid tx data"); + op_error::ParseError + })?; + + let signer_key = VoteForAggregateKeyOp::get_sender_pubkey(tx)?; + + Ok(VoteForAggregateKeyOp { + sender: sender.clone(), + signer_index: data.signer_index, + aggregate_key: data.aggregate_key, + round: data.round, + reward_cycle: data.reward_cycle, + signer_key: signer_key.to_bytes_compressed().as_slice().into(), + txid: tx.txid(), + vtxindex: tx.vtxindex(), + block_height, + burn_header_hash: block_hash.clone(), + }) + } + + pub fn check(&self) -> Result<(), op_error> { + // TODO + + Ok(()) + } +} diff --git a/stackslib/src/chainstate/nakamoto/mod.rs b/stackslib/src/chainstate/nakamoto/mod.rs index 806786cb24c..7c09a18518c 100644 --- a/stackslib/src/chainstate/nakamoto/mod.rs +++ b/stackslib/src/chainstate/nakamoto/mod.rs @@ -2488,7 +2488,7 @@ impl NakamotoChainState { }; // TODO: only need to do this if this is a tenure-start block - let (stacking_burn_ops, transfer_burn_ops, delegate_burn_ops) = + let (stacking_burn_ops, transfer_burn_ops, delegate_burn_ops, _vote_for_agg_key_ops) = StacksChainState::get_stacking_and_transfer_and_delegate_burn_ops( chainstate_tx, &parent_index_hash, @@ -2615,6 +2615,7 @@ impl NakamotoChainState { burn_header_height.into(), coinbase_height, )?; + // TODO: handle vote-for-aggregate-key ops } else { signer_set_calc = None; } diff --git a/stackslib/src/chainstate/stacks/db/blocks.rs b/stackslib/src/chainstate/stacks/db/blocks.rs index bbe4ef0be78..a62324d4c07 100644 --- a/stackslib/src/chainstate/stacks/db/blocks.rs +++ b/stackslib/src/chainstate/stacks/db/blocks.rs @@ -169,6 +169,7 @@ pub struct SetupBlockResult<'a, 'b> { pub burn_transfer_stx_ops: Vec, pub auto_unlock_events: Vec, pub burn_delegate_stx_ops: Vec, + pub burn_vote_for_aggregate_key_ops: Vec, /// Result of a signer set calculation if one occurred pub signer_set_calc: Option, } @@ -4680,7 +4681,15 @@ impl StacksChainState { burn_tip: &BurnchainHeaderHash, burn_tip_height: u64, epoch_start_height: u64, - ) -> Result<(Vec, Vec, Vec), Error> { + ) -> Result< + ( + Vec, + Vec, + Vec, + Vec, + ), + Error, + > { // only consider transactions in Stacks 2.1 let search_window: u8 = if epoch_start_height + u64::from(BURNCHAIN_TX_SEARCH_WINDOW) > burn_tip_height { @@ -4719,12 +4728,15 @@ impl StacksChainState { let mut all_stacking_burn_ops = vec![]; let mut all_transfer_burn_ops = vec![]; let mut all_delegate_burn_ops = vec![]; + let mut all_vote_for_aggregate_key_ops = vec![]; // go from oldest burn header hash to newest for ancestor_bhh in ancestor_burnchain_header_hashes.iter().rev() { let stacking_ops = SortitionDB::get_stack_stx_ops(sortdb_conn, ancestor_bhh)?; let transfer_ops = SortitionDB::get_transfer_stx_ops(sortdb_conn, ancestor_bhh)?; let delegate_ops = SortitionDB::get_delegate_stx_ops(sortdb_conn, ancestor_bhh)?; + let vote_for_aggregate_key_ops = + SortitionDB::get_vote_for_aggregate_key_ops(sortdb_conn, ancestor_bhh)?; for stacking_op in stacking_ops.into_iter() { if !processed_burnchain_txids.contains(&stacking_op.txid) { @@ -4743,11 +4755,18 @@ impl StacksChainState { all_delegate_burn_ops.push(delegate_op); } } + + for vote_op in vote_for_aggregate_key_ops.into_iter() { + if !processed_burnchain_txids.contains(&vote_op.txid) { + all_vote_for_aggregate_key_ops.push(vote_op); + } + } } Ok(( all_stacking_burn_ops, all_transfer_burn_ops, all_delegate_burn_ops, + all_vote_for_aggregate_key_ops, )) } @@ -4775,13 +4794,23 @@ impl StacksChainState { /// The change in Stacks 2.1+ makes it so that it's overwhelmingly likely to work /// the first time -- the choice of K is significantly bigger than the length of short-lived /// forks or periods of time with no sortition than have been observed in practice. + /// + /// In epoch 2.5+, the vote-for-aggregate-key op is included pub fn get_stacking_and_transfer_and_delegate_burn_ops( chainstate_tx: &mut ChainstateTx, parent_index_hash: &StacksBlockId, sortdb_conn: &Connection, burn_tip: &BurnchainHeaderHash, burn_tip_height: u64, - ) -> Result<(Vec, Vec, Vec), Error> { + ) -> Result< + ( + Vec, + Vec, + Vec, + Vec, + ), + Error, + > { let cur_epoch = SortitionDB::get_stacks_epoch(sortdb_conn, burn_tip_height)? .expect("FATAL: no epoch defined for current burnchain tip height"); @@ -4796,14 +4825,24 @@ impl StacksChainState { burn_tip, )?; // The DelegateStx bitcoin wire format does not exist before Epoch 2.1. - Ok((stack_ops, transfer_ops, vec![])) + Ok((stack_ops, transfer_ops, vec![], vec![])) } StacksEpochId::Epoch21 | StacksEpochId::Epoch22 | StacksEpochId::Epoch23 - | StacksEpochId::Epoch24 - | StacksEpochId::Epoch25 - | StacksEpochId::Epoch30 => { + | StacksEpochId::Epoch24 => { + let (stack_ops, transfer_ops, delegate_ops, _) = + StacksChainState::get_stacking_and_transfer_and_delegate_burn_ops_v210( + chainstate_tx, + parent_index_hash, + sortdb_conn, + burn_tip, + burn_tip_height, + cur_epoch.start_height, + )?; + Ok((stack_ops, transfer_ops, delegate_ops, vec![])) + } + StacksEpochId::Epoch25 | StacksEpochId::Epoch30 => { // TODO: sbtc ops in epoch 3.0 StacksChainState::get_stacking_and_transfer_and_delegate_burn_ops_v210( chainstate_tx, @@ -4962,7 +5001,7 @@ impl StacksChainState { (latest_miners, parent_miner) }; - let (stacking_burn_ops, transfer_burn_ops, delegate_burn_ops) = + let (stacking_burn_ops, transfer_burn_ops, delegate_burn_ops, vote_for_agg_key_burn_ops) = StacksChainState::get_stacking_and_transfer_and_delegate_burn_ops( chainstate_tx, &parent_index_hash, @@ -5167,6 +5206,10 @@ impl StacksChainState { &chain_tip.anchored_header.block_hash() ); } + // Vote for aggregate pubkey ops are allowed from epoch 2.4 onward + if evaluated_epoch >= StacksEpochId::Epoch25 { + // TODO: implement + } debug!( "Setup block: ready to go for {}/{}", @@ -5187,6 +5230,7 @@ impl StacksChainState { burn_transfer_stx_ops: transfer_burn_ops, auto_unlock_events, burn_delegate_stx_ops: delegate_burn_ops, + burn_vote_for_aggregate_key_ops: vote_for_agg_key_burn_ops, signer_set_calc, }) } @@ -5384,6 +5428,7 @@ impl StacksChainState { mut auto_unlock_events, burn_delegate_stx_ops, signer_set_calc, + burn_vote_for_aggregate_key_ops: _, } = StacksChainState::setup_block( chainstate_tx, clarity_instance, @@ -5677,6 +5722,7 @@ impl StacksChainState { burn_stack_stx_ops, burn_transfer_stx_ops, burn_delegate_stx_ops, + // TODO: vote for agg key ops affirmation_weight, ) .expect("FATAL: failed to advance chain tip"); @@ -10988,7 +11034,7 @@ pub mod test { let chainstate = peer.chainstate(); let (mut chainstate_tx, clarity_instance) = chainstate.chainstate_tx_begin().unwrap(); - let (stack_stx_ops, transfer_stx_ops, delegate_stx_ops) = + let (stack_stx_ops, transfer_stx_ops, delegate_stx_ops, vote_for_aggregate_key_ops) = StacksChainState::get_stacking_and_transfer_and_delegate_burn_ops_v210( &mut chainstate_tx, &last_block_id, @@ -11669,7 +11715,7 @@ pub mod test { let chainstate = peer.chainstate(); let (mut chainstate_tx, clarity_instance) = chainstate.chainstate_tx_begin().unwrap(); - let (stack_stx_ops, transfer_stx_ops, delegate_stx_ops) = + let (stack_stx_ops, transfer_stx_ops, delegate_stx_ops, _) = StacksChainState::get_stacking_and_transfer_and_delegate_burn_ops_v210( &mut chainstate_tx, &last_block_id, diff --git a/stackslib/src/net/mod.rs b/stackslib/src/net/mod.rs index 235259c0829..7c0fe4e7e3b 100644 --- a/stackslib/src/net/mod.rs +++ b/stackslib/src/net/mod.rs @@ -1674,6 +1674,7 @@ pub mod test { BlockstackOperationType::TransferStx(_) | BlockstackOperationType::DelegateStx(_) | BlockstackOperationType::PreStx(_) + | BlockstackOperationType::VoteForAggregateKey(_) | BlockstackOperationType::StackStx(_) => Ok(()), } } diff --git a/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs b/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs index 2c71b9a1f55..c092290fb89 100644 --- a/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs +++ b/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs @@ -901,6 +901,7 @@ impl BitcoinRegtestController { | BlockstackOperationType::LeaderKeyRegister(_) | BlockstackOperationType::StackStx(_) | BlockstackOperationType::DelegateStx(_) + | BlockstackOperationType::VoteForAggregateKey(_) | BlockstackOperationType::UserBurnSupport(_) => { unimplemented!(); } @@ -1843,6 +1844,8 @@ impl BitcoinRegtestController { BlockstackOperationType::DelegateStx(payload) => { self.build_delegate_stacks_tx(epoch_id, payload, op_signer, None) } + // TODO + BlockstackOperationType::VoteForAggregateKey(_payload) => unimplemented!(), }; transaction.map(|tx| SerializedTx::new(tx)) diff --git a/testnet/stacks-node/src/burnchains/mocknet_controller.rs b/testnet/stacks-node/src/burnchains/mocknet_controller.rs index 0c1ae9c84ef..a52c1ab0bb0 100644 --- a/testnet/stacks-node/src/burnchains/mocknet_controller.rs +++ b/testnet/stacks-node/src/burnchains/mocknet_controller.rs @@ -10,7 +10,7 @@ use stacks::chainstate::burn::db::sortdb::{SortitionDB, SortitionHandleTx}; use stacks::chainstate::burn::operations::leader_block_commit::BURN_BLOCK_MINED_AT_MODULUS; use stacks::chainstate::burn::operations::{ BlockstackOperationType, DelegateStxOp, LeaderBlockCommitOp, LeaderKeyRegisterOp, PreStxOp, - StackStxOp, TransferStxOp, UserBurnSupportOp, + StackStxOp, TransferStxOp, UserBurnSupportOp, VoteForAggregateKeyOp, }; use stacks::chainstate::burn::BlockSnapshot; use stacks::core::{ @@ -264,6 +264,13 @@ impl BurnchainController for MocknetController { ..payload }) } + BlockstackOperationType::VoteForAggregateKey(payload) => { + BlockstackOperationType::VoteForAggregateKey(VoteForAggregateKeyOp { + block_height: next_block_header.block_height, + burn_header_hash: next_block_header.block_hash, + ..payload + }) + } }; ops.push(op); }