Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: ensure minimum non-dust amount as change output on regtest #5008

43 changes: 29 additions & 14 deletions testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,11 @@ use stacks_common::util::sleep_ms;
use super::super::operations::BurnchainOpSigner;
use super::super::Config;
use super::{BurnchainController, BurnchainTip, Error as BurnchainControllerError};
use crate::config::BurnchainConfig;
use crate::config::{
BurnchainConfig, OP_TX_BLOCK_COMMIT_ESTIM_SIZE, OP_TX_DELEGATE_STACKS_ESTIM_SIZE,
OP_TX_PRE_STACKS_ESTIM_SIZE, OP_TX_STACK_STX_ESTIM_SIZE, OP_TX_TRANSFER_STACKS_ESTIM_SIZE,
OP_TX_VOTE_AGG_ESTIM_SIZE,
};

/// The number of bitcoin blocks that can have
/// passed since the UTXO cache was last refreshed before
Expand Down Expand Up @@ -950,7 +954,7 @@ impl BitcoinRegtestController {
utxo_to_use: Option<UTXO>,
) -> Option<Transaction> {
let public_key = signer.get_public_key();
let max_tx_size = 230;
let max_tx_size = OP_TX_TRANSFER_STACKS_ESTIM_SIZE;
let (mut tx, mut utxos) = if let Some(utxo) = utxo_to_use {
(
Transaction {
Expand Down Expand Up @@ -1032,7 +1036,7 @@ impl BitcoinRegtestController {
utxo_to_use: Option<UTXO>,
) -> Option<Transaction> {
let public_key = signer.get_public_key();
let max_tx_size = 230;
let max_tx_size = OP_TX_DELEGATE_STACKS_ESTIM_SIZE;

let (mut tx, mut utxos) = if let Some(utxo) = utxo_to_use {
(
Expand Down Expand Up @@ -1110,7 +1114,7 @@ impl BitcoinRegtestController {
utxo_to_use: Option<UTXO>,
) -> Option<Transaction> {
let public_key = signer.get_public_key();
let max_tx_size = 230;
let max_tx_size = OP_TX_VOTE_AGG_ESTIM_SIZE;

let (mut tx, mut utxos) = if let Some(utxo) = utxo_to_use {
(
Expand Down Expand Up @@ -1204,9 +1208,11 @@ impl BitcoinRegtestController {
signer: &mut BurnchainOpSigner,
) -> Option<Transaction> {
let public_key = signer.get_public_key();
let max_tx_size = 280;
let max_tx_size = OP_TX_PRE_STACKS_ESTIM_SIZE;

let max_tx_size_any_op = 380;
let output_amt = DUST_UTXO_LIMIT + max_tx_size_any_op * get_satoshis_per_byte(&self.config);

let output_amt = DUST_UTXO_LIMIT + max_tx_size * get_satoshis_per_byte(&self.config);
let (mut tx, mut utxos) =
self.prepare_tx(epoch_id, &public_key, output_amt, None, None, 0)?;

Expand Down Expand Up @@ -1271,7 +1277,7 @@ impl BitcoinRegtestController {
utxo_to_use: Option<UTXO>,
) -> Option<Transaction> {
let public_key = signer.get_public_key();
let max_tx_size = 250;
let max_tx_size = OP_TX_STACK_STX_ESTIM_SIZE;

let (mut tx, mut utxos) = if let Some(utxo) = utxo_to_use {
(
Expand Down Expand Up @@ -1715,6 +1721,7 @@ impl BitcoinRegtestController {
spent_in_outputs + min_tx_size * fee_rate + estimated_rbf,
&mut utxos_cloned,
signer,
true,
);
let serialized_tx = SerializedTx::new(tx_cloned);
cmp::max(min_tx_size, serialized_tx.bytes.len() as u64)
Expand All @@ -1731,6 +1738,7 @@ impl BitcoinRegtestController {
spent_in_outputs + tx_size * fee_rate + rbf_fee,
utxos_set,
signer,
true,
);
signer.dispose();
Some(())
Expand All @@ -1744,38 +1752,45 @@ impl BitcoinRegtestController {
&mut self,
epoch_id: StacksEpochId,
tx: &mut Transaction,
total_to_spend: u64,
tx_cost: u64,
utxos_set: &mut UTXOSet,
signer: &mut BurnchainOpSigner,
force_change_output: bool,
) -> bool {
let mut public_key = signer.get_public_key();
let mut total_consumed = 0;

let total_target = if force_change_output {
tx_cost + DUST_UTXO_LIMIT
} else {
tx_cost
};

// select UTXOs until we have enough to cover the cost
let mut total_consumed = 0;
let mut available_utxos = vec![];
available_utxos.append(&mut utxos_set.utxos);
for utxo in available_utxos.into_iter() {
total_consumed += utxo.amount;
utxos_set.utxos.push(utxo);

if total_consumed >= total_to_spend {
if total_consumed >= total_target {
break;
}
}

if total_consumed < total_to_spend {
if total_consumed < total_target {
warn!(
"Consumed total {} is less than intended spend: {}",
total_consumed, total_to_spend
total_consumed, total_target
);
return false;
}

// Append the change output
let value = total_consumed - total_to_spend;
let value = total_consumed - tx_cost;
debug!(
"Payments value: {:?}, total_consumed: {:?}, total_spent: {:?}",
value, total_consumed, total_to_spend
value, total_consumed, total_target
);
if value >= DUST_UTXO_LIMIT {
let change_output = if self.config.miner.segwit && epoch_id >= StacksEpochId::Epoch21 {
Expand Down
14 changes: 10 additions & 4 deletions testnet/stacks-node/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,16 @@ use stacks_common::util::secp256k1::{Secp256k1PrivateKey, Secp256k1PublicKey};
use crate::chain_data::MinerStats;

pub const DEFAULT_SATS_PER_VB: u64 = 50;
pub const OP_TX_LEADER_KEY_ESTIM_SIZE: u64 = 290;
pub const OP_TX_BLOCK_COMMIT_ESTIM_SIZE: u64 = 350;
pub const OP_TX_TRANSFER_STACKS_ESTIM_SIZE: u64 = 230;
pub const OP_TX_DELEGATE_STACKS_ESTIM_SIZE: u64 = 230;
pub const OP_TX_VOTE_AGG_ESTIM_SIZE: u64 = 230;
pub const OP_TX_PRE_STACKS_ESTIM_SIZE: u64 = 280;
pub const OP_TX_STACK_STX_ESTIM_SIZE: u64 = 250;

const DEFAULT_MAX_RBF_RATE: u64 = 150; // 1.5x
const DEFAULT_RBF_FEE_RATE_INCREMENT: u64 = 5;
const LEADER_KEY_TX_ESTIM_SIZE: u64 = 290;
const BLOCK_COMMIT_TX_ESTIM_SIZE: u64 = 350;
const INV_REWARD_CYCLES_TESTNET: u64 = 6;

#[derive(Clone, Deserialize, Default, Debug)]
Expand Down Expand Up @@ -1427,8 +1433,8 @@ impl BurnchainConfig {
poll_time_secs: 10, // TODO: this is a testnet specific value.
satoshis_per_byte: DEFAULT_SATS_PER_VB,
max_rbf: DEFAULT_MAX_RBF_RATE,
leader_key_tx_estimated_size: LEADER_KEY_TX_ESTIM_SIZE,
block_commit_tx_estimated_size: BLOCK_COMMIT_TX_ESTIM_SIZE,
leader_key_tx_estimated_size: OP_TX_LEADER_KEY_ESTIM_SIZE,
block_commit_tx_estimated_size: OP_TX_BLOCK_COMMIT_ESTIM_SIZE,
rbf_fee_increment: DEFAULT_RBF_FEE_RATE_INCREMENT,
first_burn_block_height: None,
first_burn_block_timestamp: None,
Expand Down