From b81a70e048d3fa7456225fb27aa4bded9a5cef76 Mon Sep 17 00:00:00 2001 From: Jude Nelson Date: Thu, 8 Sep 2022 14:59:49 -0400 Subject: [PATCH] feat: implement epoch-gated mining using a segwit p2wpkh input and output. If a miner has both legacy and segwit UTXOs, and epoch 2.1 rolls around, start using the segwit UTXOs --- .../burnchains/bitcoin_regtest_controller.rs | 432 +++++++++++++----- 1 file changed, 323 insertions(+), 109 deletions(-) diff --git a/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs b/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs index 2e2fdbc20..eab20976d 100644 --- a/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs +++ b/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs @@ -31,7 +31,9 @@ use stacks::burnchains::Error as burnchain_error; use stacks::burnchains::PoxConstants; use stacks::burnchains::PublicKey; use stacks::burnchains::{ - bitcoin::address::{BitcoinAddress, BitcoinAddressType}, + bitcoin::address::{ + BitcoinAddress, LegacyBitcoinAddress, LegacyBitcoinAddressType, SegwitBitcoinAddress, + }, Txid, }; use stacks::burnchains::{Burnchain, BurnchainParameters}; @@ -43,7 +45,7 @@ use stacks::chainstate::burn::operations::{ use stacks::chainstate::coordinator::comm::CoordinatorChannels; use stacks::chainstate::stacks::address::PoxAddress; use stacks::codec::StacksMessageCodec; -use stacks::core::StacksEpoch; +use stacks::core::{StacksEpoch, StacksEpochId}; use stacks::util::hash::{hex_bytes, Hash160}; use stacks::util::secp256k1::Secp256k1PublicKey; use stacks::util::sleep_ms; @@ -53,7 +55,9 @@ use stacks_common::deps_common::bitcoin::blockdata::transaction::{ OutPoint, Transaction, TxIn, TxOut, }; use stacks_common::deps_common::bitcoin::network::encodable::ConsensusEncodable; -use stacks_common::deps_common::bitcoin::network::serialize::RawEncoder; +use stacks_common::deps_common::bitcoin::network::serialize::{ + deserialize as btc_deserialize, RawEncoder, +}; use stacks_common::deps_common::bitcoin::util::hash::Sha256dHash; use stacks::monitoring::{increment_btc_blocks_received_counter, increment_btc_ops_sent_counter}; @@ -105,6 +109,23 @@ struct LeaderBlockCommitFees { final_size: u64, } +#[cfg(test)] +pub fn addr2str(btc_addr: &BitcoinAddress) -> String { + if let BitcoinAddress::Segwit(segwit_addr) = btc_addr { + // regtest segwit addresses use a different hrp + let s = segwit_addr.to_bech32_hrp("bcrt"); + warn!("Re-encoding {} to {}", &segwit_addr, &s); + s + } else { + format!("{}", &btc_addr) + } +} + +#[cfg(not(test))] +pub fn addr2str(btc_addr: &BitcoinAddress) -> String { + format!("{}", &btc_addr) +} + impl LeaderBlockCommitFees { pub fn fees_from_previous_tx( &self, @@ -491,23 +512,32 @@ impl BitcoinRegtestController { #[cfg(test)] pub fn get_all_utxos(&self, public_key: &Secp256k1PublicKey) -> Vec { - // Configure UTXO filter - let pkh = Hash160::from_data(&public_key.to_bytes()) - .to_bytes() - .to_vec(); - let (_, network_id) = self.config.burnchain.get_bitcoin_network(); - let address = - BitcoinAddress::from_bytes(network_id, BitcoinAddressType::PublicKeyHash, &pkh) - .expect("Public key incorrect"); - let filter_addresses = vec![address.to_b58()]; - let _result = BitcoinRPCRequest::import_public_key(&self.config, &public_key); + // Configure UTXO filter, disregard what epoch we're in + let address = self.get_miner_address(StacksEpochId::Epoch21, public_key); + let filter_addresses = vec![addr2str(&address)]; + + let pubk = if self.config.miner.segwit { + let mut p = public_key.clone(); + p.set_compressed(true); + p + } else { + public_key.clone() + }; + + test_debug!("Import public key '{}'", &pubk.to_hex()); + let _result = BitcoinRPCRequest::import_public_key(&self.config, &pubk); sleep_ms(1000); - let min_conf = 0; - let max_conf = 9999999; + let min_conf = 0i64; + let max_conf = 9999999i64; let minimum_amount = ParsedUTXO::sat_to_serialized_btc(1); + test_debug!( + "List unspent for '{}' ('{}')", + &addr2str(&address), + pubk.to_hex() + ); let payload = BitcoinRPCRequest { method: "listunspent".to_string(), params: vec![ @@ -587,7 +617,9 @@ impl BitcoinRegtestController { pub fn get_utxos( &self, + epoch_id: StacksEpochId, public_key: &Secp256k1PublicKey, + address: BitcoinAddress, total_required: u64, utxos_to_exclude: Option, block_height: u64, @@ -597,15 +629,17 @@ impl BitcoinRegtestController { return None; } + let pubk = if self.config.miner.segwit && epoch_id >= StacksEpochId::Epoch21 { + let mut p = public_key.clone(); + p.set_compressed(true); + p + } else { + public_key.clone() + }; + // Configure UTXO filter - let pkh = Hash160::from_data(&public_key.to_bytes()) - .to_bytes() - .to_vec(); - let (_, network_id) = self.config.burnchain.get_bitcoin_network(); - let address = - BitcoinAddress::from_bytes(network_id, BitcoinAddressType::PublicKeyHash, &pkh) - .expect("Public key incorrect"); - let filter_addresses = vec![address.to_b58()]; + let address = self.get_miner_address(epoch_id, &pubk); + let filter_addresses = vec![addr2str(&address)]; let mut utxos = loop { let result = BitcoinRPCRequest::list_unspent( @@ -638,7 +672,7 @@ impl BitcoinRegtestController { // Assuming that miners are in charge of correctly operating their bitcoind nodes sounds // reasonable to me. // $ bitcoin-cli importaddress mxVFsFW5N4mu1HPkxPttorvocvzeZ7KZyk - let _result = BitcoinRPCRequest::import_public_key(&self.config, &public_key); + let _result = BitcoinRPCRequest::import_public_key(&self.config, &pubk); sleep_ms(1000); } @@ -660,6 +694,8 @@ impl BitcoinRegtestController { } }; + test_debug!("Unspent for {:?}: {:?}", &filter_addresses, &utxos); + if utxos.is_empty() { return None; } else { @@ -667,6 +703,11 @@ impl BitcoinRegtestController { } } } else { + debug!( + "Got {} UTXOs for {:?}", + utxos.utxos.len(), + &filter_addresses + ); utxos }; @@ -676,7 +717,7 @@ impl BitcoinRegtestController { "Total unspent {} < {} for {:?}", total_unspent, total_required, - &public_key.to_hex() + &pubk.to_hex() ); return None; } @@ -686,6 +727,7 @@ impl BitcoinRegtestController { fn build_leader_key_register_tx( &mut self, + epoch_id: StacksEpochId, payload: LeaderKeyRegisterOp, signer: &mut BurnchainOpSigner, _attempt: u64, @@ -697,7 +739,8 @@ impl BitcoinRegtestController { let budget_for_outputs = DUST_UTXO_LIMIT; let total_required = btc_miner_fee + budget_for_outputs; - let (mut tx, mut utxos) = self.prepare_tx(&public_key, total_required, None, None, 0)?; + let (mut tx, mut utxos) = + self.prepare_tx(epoch_id, &public_key, total_required, None, None, 0)?; // Serialize the payload let op_bytes = { @@ -720,14 +763,10 @@ impl BitcoinRegtestController { tx.output = vec![consensus_output]; - let address_hash = Hash160::from_data(&public_key.to_bytes()); - let identifier_output = BitcoinAddress::to_p2pkh_tx_out(&address_hash, DUST_UTXO_LIMIT); - - tx.output.push(identifier_output); - let fee_rate = self.config.burnchain.satoshis_per_byte; self.finalize_tx( + epoch_id, &mut tx, budget_for_outputs, 0, @@ -760,6 +799,7 @@ impl BitcoinRegtestController { #[cfg(test)] pub fn submit_manual( &mut self, + epoch_id: StacksEpochId, operation: BlockstackOperationType, op_signer: &mut BurnchainOpSigner, utxo: Option, @@ -772,16 +812,16 @@ impl BitcoinRegtestController { unimplemented!(); } BlockstackOperationType::PreStx(payload) => { - self.build_pre_stacks_tx(payload, op_signer) + self.build_pre_stacks_tx(epoch_id, payload, op_signer) } BlockstackOperationType::TransferStx(payload) => { - self.build_transfer_stacks_tx(payload, op_signer, utxo) + self.build_transfer_stacks_tx(epoch_id, payload, op_signer, utxo) } }?; let ser_transaction = SerializedTx::new(transaction.clone()); - if self.send_transaction(ser_transaction) { + if self.send_transaction(ser_transaction).is_some() { Some(transaction) } else { None @@ -797,6 +837,7 @@ impl BitcoinRegtestController { /// for a functionality that won't be implemented for production via this controller. fn build_transfer_stacks_tx( &mut self, + epoch_id: StacksEpochId, payload: TransferStxOp, signer: &mut BurnchainOpSigner, utxo_to_use: Option, @@ -819,6 +860,7 @@ impl BitcoinRegtestController { ) } else { self.prepare_tx( + epoch_id, &public_key, DUST_UTXO_LIMIT + max_tx_size * self.config.burnchain.satoshis_per_byte, None, @@ -849,6 +891,7 @@ impl BitcoinRegtestController { ); self.finalize_tx( + epoch_id, &mut tx, DUST_UTXO_LIMIT, 0, @@ -880,6 +923,7 @@ impl BitcoinRegtestController { #[cfg(test)] fn build_pre_stacks_tx( &mut self, + epoch_id: StacksEpochId, payload: PreStxOp, signer: &mut BurnchainOpSigner, ) -> Option { @@ -887,7 +931,8 @@ impl BitcoinRegtestController { let max_tx_size = 280; let output_amt = DUST_UTXO_LIMIT + max_tx_size * self.config.burnchain.satoshis_per_byte; - let (mut tx, mut utxos) = self.prepare_tx(&public_key, output_amt, None, None, 0)?; + let (mut tx, mut utxos) = + self.prepare_tx(epoch_id, &public_key, output_amt, None, None, 0)?; // Serialize the payload let op_bytes = { @@ -909,6 +954,7 @@ impl BitcoinRegtestController { .push(PoxAddress::Standard(payload.output.clone(), None).to_bitcoin_tx_out(output_amt)); self.finalize_tx( + epoch_id, &mut tx, output_amt, 0, @@ -930,6 +976,7 @@ impl BitcoinRegtestController { fn send_block_commit_operation( &mut self, + epoch_id: StacksEpochId, payload: LeaderBlockCommitOp, signer: &mut BurnchainOpSigner, utxos_to_include: Option, @@ -944,6 +991,7 @@ impl BitcoinRegtestController { let public_key = signer.get_public_key(); let (mut tx, mut utxos) = self.prepare_tx( + epoch_id, &public_key, estimated_fees.estimated_amount_required(), utxos_to_include, @@ -979,6 +1027,7 @@ impl BitcoinRegtestController { let fee_rate = estimated_fees.fee_rate; self.finalize_tx( + epoch_id, &mut tx, estimated_fees.total_spent_in_outputs(), estimated_fees.spent_in_attempts, @@ -1024,6 +1073,7 @@ impl BitcoinRegtestController { fn build_leader_block_commit_tx( &mut self, + epoch_id: StacksEpochId, payload: LeaderBlockCommitOp, signer: &mut BurnchainOpSigner, _attempt: u64, @@ -1031,7 +1081,15 @@ impl BitcoinRegtestController { // Are we currently tracking an operation? if self.ongoing_block_commit.is_none() || !self.allow_rbf { // Good to go, let's build the transaction and send it. - let res = self.send_block_commit_operation(payload, signer, None, None, None, &vec![]); + let res = self.send_block_commit_operation( + epoch_id, + payload, + signer, + None, + None, + None, + &vec![], + ); return res; } @@ -1045,8 +1103,15 @@ impl BitcoinRegtestController { if mined_op.is_some() { // Good to go, the transaction in progress was mined debug!("Was able to retrieve ongoing TXID - {}", txid); - let res = - self.send_block_commit_operation(payload, signer, None, None, None, &vec![]); + let res = self.send_block_commit_operation( + epoch_id, + payload, + signer, + None, + None, + None, + &vec![], + ); return res; } else { debug!("Was unable to retrieve ongoing TXID - {}", txid); @@ -1075,7 +1140,15 @@ impl BitcoinRegtestController { "Possible presence of fork or stale UTXO cache, invalidating cached set of UTXOs."; "cached_burn_block_hash" => %ongoing_op.utxos.bhh, ); - let res = self.send_block_commit_operation(payload, signer, None, None, None, &vec![]); + let res = self.send_block_commit_operation( + epoch_id, + payload, + signer, + None, + None, + None, + &vec![], + ); return res; } @@ -1111,6 +1184,7 @@ impl BitcoinRegtestController { // Try to build and submit op, excluding UTXOs currently used info!("Attempt to submit another leader_block_commit, despite an ongoing (outdated) commit"); self.send_block_commit_operation( + epoch_id, payload, signer, None, @@ -1122,6 +1196,7 @@ impl BitcoinRegtestController { // Case 2) ii): Attempt to RBF info!("Attempt to replace by fee an outdated leader block commit"); self.send_block_commit_operation( + epoch_id, payload, signer, Some(ongoing_op.utxos.clone()), @@ -1138,8 +1213,31 @@ impl BitcoinRegtestController { res } + fn get_miner_address( + &self, + epoch_id: StacksEpochId, + public_key: &Secp256k1PublicKey, + ) -> BitcoinAddress { + let (_, network_id) = self.config.burnchain.get_bitcoin_network(); + + if self.config.miner.segwit && epoch_id >= StacksEpochId::Epoch21 { + let hash160 = Hash160::from_data(&public_key.to_bytes_compressed()); + BitcoinAddress::from_bytes_segwit_p2wpkh(network_id, &hash160.0) + .expect("Public key incorrect") + } else { + let hash160 = Hash160::from_data(&public_key.to_bytes()); + BitcoinAddress::from_bytes_legacy( + network_id, + LegacyBitcoinAddressType::PublicKeyHash, + &hash160.0, + ) + .expect("Public key incorrect") + } + } + fn prepare_tx( &mut self, + epoch_id: StacksEpochId, public_key: &Secp256k1PublicKey, total_required: u64, utxos_to_include: Option, @@ -1151,14 +1249,26 @@ impl BitcoinRegtestController { utxos } else { // Fetch some UTXOs - let utxos = - match self.get_utxos(&public_key, total_required, utxos_to_exclude, block_height) { - Some(utxos) => utxos, - None => { - debug!("No UTXOs for {}", &public_key.to_hex()); - return None; - } - }; + let addr = self.get_miner_address(epoch_id, public_key); + let utxos = match self.get_utxos( + epoch_id, + &public_key, + addr.clone(), + total_required, + utxos_to_exclude, + block_height, + ) { + Some(utxos) => utxos, + None => { + debug!( + "No UTXOs for {} ({}) in epoch {}", + &public_key.to_hex(), + &addr2str(&addr), + epoch_id + ); + return None; + } + }; utxos }; @@ -1175,6 +1285,7 @@ impl BitcoinRegtestController { fn finalize_tx( &mut self, + epoch_id: StacksEpochId, tx: &mut Transaction, spent_in_outputs: u64, spent_in_rbf: u64, @@ -1207,6 +1318,7 @@ impl BitcoinRegtestController { let mut tx_cloned = tx.clone(); let mut utxos_cloned = utxos_set.clone(); self.serialize_tx( + epoch_id, &mut tx_cloned, spent_in_outputs + min_tx_size * fee_rate + estimated_rbf, &mut utxos_cloned, @@ -1222,6 +1334,7 @@ impl BitcoinRegtestController { spent_in_rbf + tx_size // we're spending 1 sat / byte in RBF }; self.serialize_tx( + epoch_id, tx, spent_in_outputs + tx_size * fee_rate + rbf_fee, utxos_set, @@ -1231,14 +1344,19 @@ impl BitcoinRegtestController { Some(()) } + /// Sign and serialize a tx, consuming the UTXOs in utxo_set and spending total_to_spend + /// satoshis. Uses the key in signer. + /// If self.config.miner.segwit is true, the transaction's change address will be a p2wpkh + /// output. Otherwise, it will be a p2pkh output. fn serialize_tx( &mut self, + epoch_id: StacksEpochId, tx: &mut Transaction, total_to_spend: u64, utxos_set: &mut UTXOSet, signer: &mut BurnchainOpSigner, ) -> bool { - let public_key = signer.get_public_key(); + let mut public_key = signer.get_public_key(); let mut total_consumed = 0; // select UTXOs until we have enough to cover the cost @@ -1262,14 +1380,22 @@ impl BitcoinRegtestController { } // Append the change output - let change_address_hash = Hash160::from_data(&public_key.to_bytes()); let value = total_consumed - total_to_spend; debug!( "Payments value: {:?}, total_consumed: {:?}, total_spent: {:?}", value, total_consumed, total_to_spend ); if value >= DUST_UTXO_LIMIT { - let change_output = BitcoinAddress::to_p2pkh_tx_out(&change_address_hash, value); + let change_output = if self.config.miner.segwit && epoch_id >= StacksEpochId::Epoch21 { + // p2wpkh + public_key.set_compressed(true); + let change_address_hash = Hash160::from_data(&public_key.to_bytes()); + SegwitBitcoinAddress::to_p2wpkh_tx_out(&change_address_hash.0, value) + } else { + // p2pkh + let change_address_hash = Hash160::from_data(&public_key.to_bytes()); + LegacyBitcoinAddress::to_p2pkh_tx_out(&change_address_hash, value) + }; tx.output.push(change_output); } else { // Instead of leaving that change to the BTC miner, we could / should bump the sortition fee @@ -1290,7 +1416,19 @@ impl BitcoinRegtestController { let script_pub_key = utxo.script_pub_key.clone(); let sig_hash_all = 0x01; - let sig_hash = tx.signature_hash(i, &script_pub_key, sig_hash_all); + + let (sig_hash, is_segwit) = if script_pub_key.as_bytes().len() == 22 + && script_pub_key.as_bytes()[0..2] == [0x00, 0x14] + { + // p2wpkh + ( + tx.segwit_signature_hash(i, &script_pub_key, utxo.amount, sig_hash_all), + true, + ) + } else { + // p2pkh + (tx.signature_hash(i, &script_pub_key, sig_hash_all), false) + }; let sig1_der = { let message = signer @@ -1303,16 +1441,29 @@ impl BitcoinRegtestController { .serialize_der() }; - tx.input[i].script_sig = Builder::new() - .push_slice(&[&*sig1_der, &[sig_hash_all as u8][..]].concat()) - .push_slice(&public_key.to_bytes()) - .into_script(); + if is_segwit { + // segwit + public_key.set_compressed(true); + tx.input[i].script_sig = Script::from(vec![]); + tx.input[i].witness = vec![ + [&*sig1_der, &[sig_hash_all as u8][..]].concat().to_vec(), + public_key.to_bytes(), + ]; + } else { + // legacy scriptSig + tx.input[i].script_sig = Builder::new() + .push_slice(&[&*sig1_der, &[sig_hash_all as u8][..]].concat()) + .push_slice(&public_key.to_bytes()) + .into_script(); + tx.input[i].witness.clear(); + } } true } fn build_user_burn_support_tx( &mut self, + _epoch_id: StacksEpochId, _payload: UserBurnSupportOp, _signer: &mut BurnchainOpSigner, _attempt: u64, @@ -1320,18 +1471,22 @@ impl BitcoinRegtestController { unimplemented!() } - /// Send a serialized tx to the Bitcoin node. Return true on successful send; false on + /// Send a serialized tx to the Bitcoin node. Return Some(txid) on successful send; None on /// failure. - pub fn send_transaction(&self, transaction: SerializedTx) -> bool { + pub fn send_transaction(&self, transaction: SerializedTx) -> Option { + test_debug!("Send raw transaction: {}", transaction.to_hex()); let result = BitcoinRPCRequest::send_raw_transaction(&self.config, transaction.to_hex()); match result { - Ok(_) => true, + Ok(_) => { + test_debug!("Sent transaction {}", &transaction.txid); + Some(transaction.txid()) + } Err(e) => { error!( "Bitcoin RPC failure: transaction submission failed - {:?}", e ); - false + None } } } @@ -1385,21 +1540,20 @@ impl BitcoinRegtestController { } } + /// Instruct a regtest Bitcoin node to build the next block. pub fn build_next_block(&self, num_blocks: u64) { debug!("Generate {} block(s)", num_blocks); - let public_key = match &self.config.burnchain.local_mining_public_key { + let public_key_bytes = match &self.config.burnchain.local_mining_public_key { Some(public_key) => hex_bytes(public_key).expect("Invalid byte sequence"), None => panic!("Unable to make new block, mining public key"), }; - let pkh = Hash160::from_data(&public_key).to_bytes().to_vec(); - let (_, network_id) = self.config.burnchain.get_bitcoin_network(); - let address = - BitcoinAddress::from_bytes(network_id, BitcoinAddressType::PublicKeyHash, &pkh) - .expect("Public key incorrect"); - + // NOTE: miner address is whatever the configured segwit setting is + let public_key = Secp256k1PublicKey::from_slice(&public_key_bytes) + .expect("FATAL: invalid public key bytes"); + let address = self.get_miner_address(StacksEpochId::Epoch21, &public_key); let result = - BitcoinRPCRequest::generate_to_address(&self.config, num_blocks, address.to_b58()); + BitcoinRPCRequest::generate_to_address(&self.config, num_blocks, addr2str(&address)); match result { Ok(_) => {} @@ -1456,6 +1610,11 @@ impl BitcoinRegtestController { old_key } + #[cfg(test)] + pub fn set_use_segwit(&mut self, segwit: bool) { + self.config.miner.segwit = segwit; + } + #[cfg(test)] pub fn set_allow_rbf(&mut self, val: bool) { self.allow_rbf = val; @@ -1466,31 +1625,39 @@ impl BitcoinRegtestController { pub fn make_operation_tx( &mut self, + epoch_id: StacksEpochId, operation: BlockstackOperationType, op_signer: &mut BurnchainOpSigner, attempt: u64, ) -> Option { let transaction = match operation { BlockstackOperationType::LeaderBlockCommit(payload) => { - self.build_leader_block_commit_tx(payload, op_signer, attempt) + self.build_leader_block_commit_tx(epoch_id, payload, op_signer, attempt) } BlockstackOperationType::LeaderKeyRegister(payload) => { - self.build_leader_key_register_tx(payload, op_signer, attempt) + self.build_leader_key_register_tx(epoch_id, payload, op_signer, attempt) } BlockstackOperationType::UserBurnSupport(payload) => { - self.build_user_burn_support_tx(payload, op_signer, attempt) + self.build_user_burn_support_tx(epoch_id, payload, op_signer, attempt) } BlockstackOperationType::PreStx(payload) => { - self.build_pre_stacks_tx(payload, op_signer) + self.build_pre_stacks_tx(epoch_id, payload, op_signer) } BlockstackOperationType::TransferStx(payload) => { - self.build_transfer_stacks_tx(payload, op_signer, None) + self.build_transfer_stacks_tx(epoch_id, payload, op_signer, None) } BlockstackOperationType::StackStx(_payload) => unimplemented!(), }; transaction.map(|tx| SerializedTx::new(tx)) } + + #[cfg(test)] + pub fn get_raw_transaction(&self, txid: &Txid) -> Transaction { + let txstr = BitcoinRPCRequest::get_raw_transaction(&self.config, txid).unwrap(); + let tx: Transaction = btc_deserialize(&hex_bytes(&txstr).unwrap()).unwrap(); + tx + } } impl BurnchainController for BitcoinRegtestController { @@ -1595,37 +1762,40 @@ impl BurnchainController for BitcoinRegtestController { // returns true if the operation was submitted successfully, false otherwise fn submit_operation( &mut self, + epoch_id: StacksEpochId, operation: BlockstackOperationType, op_signer: &mut BurnchainOpSigner, attempt: u64, - ) -> bool { - let transaction = match self.make_operation_tx(operation, op_signer, attempt) { - Some(tx) => tx, - None => { - return false; - } - }; - + ) -> Option { + let transaction = self.make_operation_tx(epoch_id, operation, op_signer, attempt)?; self.send_transaction(transaction) } #[cfg(test)] fn bootstrap_chain(&mut self, num_blocks: u64) { - if let Some(local_mining_pubkey) = &self.config.burnchain.local_mining_public_key { - let pk = hex_bytes(&local_mining_pubkey).expect("Invalid byte sequence"); - let pkh = Hash160::from_data(&pk).to_bytes().to_vec(); - let (_, network_id) = self.config.burnchain.get_bitcoin_network(); - let address = - BitcoinAddress::from_bytes(network_id, BitcoinAddressType::PublicKeyHash, &pkh) - .expect("Public key incorrect"); + if let Some(ref local_mining_pubkey) = &self.config.burnchain.local_mining_public_key { + // NOTE: miner address is whatever the miner's segwit setting says it is here + let mut local_mining_pubkey = + Secp256k1PublicKey::from_hex(local_mining_pubkey).unwrap(); + let address = self.get_miner_address(StacksEpochId::Epoch21, &local_mining_pubkey); - let _result = BitcoinRPCRequest::import_public_key( - &self.config, - &Secp256k1PublicKey::from_hex(local_mining_pubkey).unwrap(), + if self.config.miner.segwit { + local_mining_pubkey.set_compressed(true); + } + test_debug!("Import public key '{}'", &local_mining_pubkey.to_hex()); + + let _result = BitcoinRPCRequest::import_public_key(&self.config, &local_mining_pubkey); + + test_debug!( + "Generate to address '{}' for public key '{}'", + &addr2str(&address), + &local_mining_pubkey.to_hex() + ); + let result = BitcoinRPCRequest::generate_to_address( + &self.config, + num_blocks, + addr2str(&address), ); - - let result = - BitcoinRPCRequest::generate_to_address(&self.config, num_blocks, address.to_b58()); match result { Ok(_) => {} @@ -1662,15 +1832,22 @@ impl UTXOSet { #[derive(Debug, Clone)] pub struct SerializedTx { pub bytes: Vec, + pub txid: Txid, } impl SerializedTx { pub fn new(tx: Transaction) -> SerializedTx { + let txid = Txid::from_vec_be(&tx.txid().as_bytes().to_vec()).unwrap(); let mut encoder = RawEncoder::new(Cursor::new(vec![])); tx.consensus_encode(&mut encoder) .expect("BUG: failed to serialize to a vec"); let bytes: Vec = encoder.into_inner().into_inner(); - SerializedTx { bytes } + + SerializedTx { txid, bytes } + } + + pub fn txid(&self) -> Txid { + self.txid.clone() } fn to_hex(&self) -> String { @@ -1812,16 +1989,34 @@ impl BitcoinRPCRequest { req } + #[cfg(test)] + pub fn get_raw_transaction(config: &Config, txid: &Txid) -> RPCResult { + debug!("Get raw transaction {}", txid); + let payload = BitcoinRPCRequest { + method: "getrawtransaction".to_string(), + params: vec![format!("{}", txid).into()], + id: "stacks".to_string(), + jsonrpc: "2.0".to_string(), + }; + let res = BitcoinRPCRequest::send(&config, payload)?; + debug!("Got raw transaction {}: {:?}", txid, &res); + Ok(res.get("result").unwrap().as_str().unwrap().to_string()) + } + pub fn generate_to_address(config: &Config, num_blocks: u64, address: String) -> RPCResult<()> { - debug!("Generate {} blocks to {}", num_blocks, address); + debug!("Generate {} blocks to {}", num_blocks, &address); let payload = BitcoinRPCRequest { method: "generatetoaddress".to_string(), - params: vec![num_blocks.into(), address.into()], + params: vec![num_blocks.into(), address.clone().into()], id: "stacks".to_string(), jsonrpc: "2.0".to_string(), }; - BitcoinRPCRequest::send(&config, payload)?; + let res = BitcoinRPCRequest::send(&config, payload)?; + debug!( + "Generated {} blocks to {}: {:?}", + num_blocks, &address, &res + ); Ok(()) } @@ -1855,8 +2050,8 @@ impl BitcoinRPCRequest { _ => return Err(RPCError::Parsing("Failed to get UTXOs".to_string())), }?; - let min_conf = 0; - let max_conf = 9999999; + let min_conf = 0i64; + let max_conf = 9999999i64; let minimum_amount = ParsedUTXO::sat_to_serialized_btc(minimum_sum_amount); let payload = BitcoinRPCRequest { @@ -1970,18 +2165,37 @@ impl BitcoinRPCRequest { .to_bytes() .to_vec(); let (_, network_id) = config.burnchain.get_bitcoin_network(); - let address = - BitcoinAddress::from_bytes(network_id, BitcoinAddressType::PublicKeyHash, &pkh) - .expect("Public key incorrect"); - let payload = BitcoinRPCRequest { - method: "importaddress".to_string(), - params: vec![address.to_b58().into(), label.into(), rescan.into()], - id: "stacks".to_string(), - jsonrpc: "2.0".to_string(), - }; + // import both the legacy and segwit variants of this public key + let mut addresses = vec![BitcoinAddress::from_bytes_legacy( + network_id, + LegacyBitcoinAddressType::PublicKeyHash, + &pkh, + ) + .expect("Public key incorrect")]; - BitcoinRPCRequest::send(&config, payload)?; + if config.miner.segwit { + addresses.push( + BitcoinAddress::from_bytes_segwit_p2wpkh(network_id, &pkh) + .expect("Public key incorrect"), + ); + } + + for address in addresses.into_iter() { + debug!( + "Import address {} for public key {}", + addr2str(&address), + public_key.to_hex() + ); + let payload = BitcoinRPCRequest { + method: "importaddress".to_string(), + params: vec![addr2str(&address).into(), label.into(), rescan.into()], + id: "stacks".to_string(), + jsonrpc: "2.0".to_string(), + }; + + BitcoinRPCRequest::send(&config, payload)?; + } Ok(()) }