Skip to content

Commit c5a132a

Browse files
committed
feat: hot-reload burn_fee_cap in nakamoto
1 parent 5655256 commit c5a132a

File tree

3 files changed

+194
-3
lines changed

3 files changed

+194
-3
lines changed

testnet/stacks-node/src/globals.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ pub struct Globals<T> {
6363
pub leader_key_registration_state: Arc<Mutex<LeaderKeyRegistrationState>>,
6464
/// Last miner config loaded
6565
last_miner_config: Arc<Mutex<Option<MinerConfig>>>,
66+
/// Last miner spend amount
67+
last_miner_spend_amount: Arc<Mutex<Option<u64>>>,
6668
/// burnchain height at which we start mining
6769
start_mining_height: Arc<Mutex<u64>>,
6870
/// estimated winning probability at given bitcoin block heights
@@ -91,6 +93,7 @@ impl<T> Clone for Globals<T> {
9193
should_keep_running: self.should_keep_running.clone(),
9294
leader_key_registration_state: self.leader_key_registration_state.clone(),
9395
last_miner_config: self.last_miner_config.clone(),
96+
last_miner_spend_amount: self.last_miner_spend_amount.clone(),
9497
start_mining_height: self.start_mining_height.clone(),
9598
estimated_winning_probs: self.estimated_winning_probs.clone(),
9699
previous_best_tips: self.previous_best_tips.clone(),
@@ -122,6 +125,7 @@ impl<T> Globals<T> {
122125
should_keep_running,
123126
leader_key_registration_state: Arc::new(Mutex::new(leader_key_registration_state)),
124127
last_miner_config: Arc::new(Mutex::new(None)),
128+
last_miner_spend_amount: Arc::new(Mutex::new(None)),
125129
start_mining_height: Arc::new(Mutex::new(start_mining_height)),
126130
estimated_winning_probs: Arc::new(Mutex::new(HashMap::new())),
127131
previous_best_tips: Arc::new(Mutex::new(BTreeMap::new())),
@@ -351,6 +355,28 @@ impl<T> Globals<T> {
351355
}
352356
}
353357

358+
/// Get the last miner spend amount
359+
pub fn get_last_miner_spend_amount(&self) -> Option<u64> {
360+
match self.last_miner_spend_amount.lock() {
361+
Ok(last_miner_spend_amount) => (*last_miner_spend_amount).clone(),
362+
Err(_e) => {
363+
error!("FATAL; failed to lock last miner spend amount");
364+
panic!();
365+
}
366+
}
367+
}
368+
369+
/// Set the last miner spend amount
370+
pub fn set_last_miner_spend_amount(&self, spend_amount: u64) {
371+
match self.last_miner_spend_amount.lock() {
372+
Ok(ref mut last_miner_spend_amount) => **last_miner_spend_amount = Some(spend_amount),
373+
Err(_e) => {
374+
error!("FATAL; failed to lock last miner spend amount");
375+
panic!();
376+
}
377+
}
378+
}
379+
354380
/// Get the height at which we should start mining
355381
pub fn get_start_mining_height(&self) -> u64 {
356382
match self.start_mining_height.lock() {

testnet/stacks-node/src/nakamoto_node/relayer.rs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ use stacks::chainstate::nakamoto::{NakamotoBlockHeader, NakamotoChainState};
4040
use stacks::chainstate::stacks::address::PoxAddress;
4141
use stacks::chainstate::stacks::db::StacksChainState;
4242
use stacks::chainstate::stacks::miner::{
43-
get_mining_spend_amount, signal_mining_blocked, signal_mining_ready,
43+
set_mining_spend_amount, signal_mining_blocked, signal_mining_ready,
4444
};
4545
use stacks::chainstate::stacks::Error as ChainstateError;
4646
use stacks::core::mempool::MemPoolDB;
@@ -1030,8 +1030,29 @@ impl RelayerThread {
10301030
return Err(NakamotoNodeError::SnapshotNotFoundForChainTip);
10311031
};
10321032

1033+
let burnchain_config = self.config.get_burnchain_config();
1034+
let last_miner_spend_opt = self.globals.get_last_miner_spend_amount();
1035+
let force_remine = if let Some(last_miner_spend_amount) = last_miner_spend_opt {
1036+
last_miner_spend_amount != burnchain_config.burn_fee_cap
1037+
} else {
1038+
false
1039+
};
1040+
if force_remine {
1041+
info!(
1042+
"Miner config changed; updating spend amount {}",
1043+
burnchain_config.burn_fee_cap
1044+
);
1045+
}
1046+
1047+
self.globals
1048+
.set_last_miner_spend_amount(burnchain_config.burn_fee_cap);
1049+
1050+
set_mining_spend_amount(
1051+
self.globals.get_miner_status(),
1052+
burnchain_config.burn_fee_cap,
1053+
);
10331054
// amount of burnchain tokens (e.g. sats) we'll spend across the PoX outputs
1034-
let burn_fee_cap = get_mining_spend_amount(self.globals.get_miner_status());
1055+
let burn_fee_cap = burnchain_config.burn_fee_cap;
10351056

10361057
// let's commit, but target the current burnchain tip with our modulus so the commit is
10371058
// only valid if it lands in the targeted burnchain block height

testnet/stacks-node/src/tests/nakamoto_integrations.rs

Lines changed: 145 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
// You should have received a copy of the GNU General Public License
1515
// along with this program. If not, see <http://www.gnu.org/licenses/>.
1616
use std::collections::{BTreeMap, HashMap, HashSet};
17+
use std::fs::File;
1718
use std::ops::RangeBounds;
1819
use std::sync::atomic::{AtomicU64, Ordering};
1920
use std::sync::mpsc::{channel, Receiver, Sender};
@@ -10764,7 +10765,7 @@ fn test_tenure_extend_from_flashblocks() {
1076410765
(define-data-var my-var uint u0)
1076510766
(define-data-var my-counter uint u0)
1076610767
10767-
(define-public (f)
10768+
(define-public (f)
1076810769
(begin
1076910770
(var-set my-var burn-block-height)
1077010771
(if (is-eq u0 (mod burn-block-height u2))
@@ -11165,3 +11166,146 @@ fn mine_invalid_principal_from_consensus_buff() {
1116511166

1116611167
run_loop_thread.join().unwrap();
1116711168
}
11169+
11170+
/// Test hot-reloading of miner config
11171+
#[test]
11172+
#[ignore]
11173+
fn reload_miner_config() {
11174+
if env::var("BITCOIND_TEST") != Ok("1".into()) {
11175+
return;
11176+
}
11177+
11178+
let (mut conf, _miner_account) = naka_neon_integration_conf(None);
11179+
let password = "12345".to_string();
11180+
let _http_origin = format!("http://{}", &conf.node.rpc_bind);
11181+
conf.connection_options.auth_token = Some(password.clone());
11182+
conf.miner.wait_on_interim_blocks = Duration::from_secs(1);
11183+
let stacker_sk = setup_stacker(&mut conf);
11184+
let signer_sk = Secp256k1PrivateKey::random();
11185+
let signer_addr = tests::to_addr(&signer_sk);
11186+
let sender_sk = Secp256k1PrivateKey::random();
11187+
// setup sender + recipient for some test stx transfers
11188+
// these are necessary for the interim blocks to get mined at all
11189+
let sender_addr = tests::to_addr(&sender_sk);
11190+
conf.add_initial_balance(PrincipalData::from(sender_addr).to_string(), 1000000);
11191+
conf.add_initial_balance(PrincipalData::from(signer_addr).to_string(), 100000);
11192+
11193+
test_observer::spawn();
11194+
test_observer::register(&mut conf, &[EventKeyType::AnyEvent]);
11195+
11196+
let mut btcd_controller = BitcoinCoreController::new(conf.clone());
11197+
btcd_controller
11198+
.start_bitcoind()
11199+
.expect("Failed starting bitcoind");
11200+
let mut btc_regtest_controller = BitcoinRegtestController::new(conf.clone(), None);
11201+
btc_regtest_controller.bootstrap_chain(201);
11202+
11203+
let conf_path =
11204+
std::env::temp_dir().join(format!("miner-config-test-{}.toml", rand::random::<u64>()));
11205+
conf.config_path = Some(conf_path.clone().to_str().unwrap().to_string());
11206+
11207+
// Make a minimum-viable config file
11208+
let update_config = |burn_fee_cap: u64, sats_vbyte: u64| {
11209+
use std::io::Write;
11210+
11211+
let new_config = format!(
11212+
r#"
11213+
[burnchain]
11214+
burn_fee_cap = {}
11215+
satoshis_per_byte = {}
11216+
"#,
11217+
burn_fee_cap, sats_vbyte,
11218+
);
11219+
// Write to a file
11220+
let mut file = File::create(&conf_path).unwrap();
11221+
file.write_all(new_config.as_bytes()).unwrap();
11222+
};
11223+
11224+
update_config(100000, 50);
11225+
11226+
let mut run_loop = boot_nakamoto::BootRunLoop::new(conf.clone()).unwrap();
11227+
let run_loop_stopper = run_loop.get_termination_switch();
11228+
let counters = run_loop.counters();
11229+
let Counters {
11230+
blocks_processed,
11231+
naka_submitted_commits: commits_submitted,
11232+
naka_proposed_blocks: proposals_submitted,
11233+
..
11234+
} = run_loop.counters();
11235+
11236+
let coord_channel = run_loop.coordinator_channels();
11237+
11238+
let run_loop_thread = thread::spawn(move || run_loop.start(None, 0));
11239+
let mut signers = TestSigners::new(vec![signer_sk]);
11240+
wait_for_runloop(&blocks_processed);
11241+
boot_to_epoch_3(
11242+
&conf,
11243+
&blocks_processed,
11244+
&[stacker_sk],
11245+
&[signer_sk],
11246+
&mut Some(&mut signers),
11247+
&mut btc_regtest_controller,
11248+
);
11249+
11250+
info!("------------------------- Reached Epoch 3.0 -------------------------");
11251+
11252+
blind_signer(&conf, &signers, proposals_submitted);
11253+
11254+
wait_for_first_naka_block_commit(60, &commits_submitted);
11255+
11256+
next_block_and_mine_commit(&mut btc_regtest_controller, 60, &conf, &counters).unwrap();
11257+
11258+
let burn_blocks = test_observer::get_burn_blocks();
11259+
let burn_block = burn_blocks.last().unwrap();
11260+
info!("Burn block: {:?}", &burn_block);
11261+
11262+
let reward_amount = burn_block
11263+
.get("reward_recipients")
11264+
.unwrap()
11265+
.as_array()
11266+
.unwrap()
11267+
.get(0)
11268+
.unwrap()
11269+
.get("amt")
11270+
.unwrap()
11271+
.as_u64()
11272+
.unwrap();
11273+
11274+
assert_eq!(reward_amount, 200000);
11275+
11276+
next_block_and_mine_commit(&mut btc_regtest_controller, 60, &conf, &counters).unwrap();
11277+
11278+
info!("---- Updating config ----");
11279+
update_config(100010, 55);
11280+
11281+
// Due to timing of commits, just mine two blocks
11282+
11283+
next_block_and_mine_commit(&mut btc_regtest_controller, 60, &conf, &counters).unwrap();
11284+
next_block_and_mine_commit(&mut btc_regtest_controller, 60, &conf, &counters).unwrap();
11285+
11286+
let burn_blocks = test_observer::get_burn_blocks();
11287+
let burn_block = burn_blocks.last().unwrap();
11288+
info!("Burn block: {:?}", &burn_block);
11289+
11290+
let reward_amount = burn_block
11291+
.get("reward_recipients")
11292+
.unwrap()
11293+
.as_array()
11294+
.unwrap()
11295+
.get(0)
11296+
.unwrap()
11297+
.get("amt")
11298+
.unwrap()
11299+
.as_u64()
11300+
.unwrap();
11301+
11302+
assert_eq!(reward_amount, 100010);
11303+
11304+
coord_channel
11305+
.lock()
11306+
.expect("Mutex poisoned")
11307+
.stop_chains_coordinator();
11308+
run_loop_stopper.store(false, Ordering::SeqCst);
11309+
11310+
run_loop_thread.join().unwrap();
11311+
}

0 commit comments

Comments
 (0)