Skip to content

Commit e2eacd2

Browse files
authored
Merge pull request #5857 from stacks-network/feat/hot-reload-miner-spend
feat: hot-reload burn_fee_cap in nakamoto
2 parents a5f2fc6 + d6c0d6c commit e2eacd2

File tree

4 files changed

+189
-3
lines changed

4 files changed

+189
-3
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ and this project adheres to the versioning scheme outlined in the [README.md](RE
2525

2626
- The `BlockProposal` StackerDB message serialization struct now includes a `server_version` string, which represents the version of the node that the miner is using. ([#5803](https://github.com/stacks-network/stacks-core/pull/5803))
2727
- Add `vrf_seed` to the `/v3/sortitions` rpc endpoint
28+
- Added hot-reloading of `burnchain.burn_fee_cap` from a miner's config file ([#5857](https://github.com/stacks-network/stacks-core/pull/5857))
2829

2930
### Changed
3031

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
@@ -39,7 +39,7 @@ use stacks::chainstate::nakamoto::{NakamotoBlockHeader, NakamotoChainState};
3939
use stacks::chainstate::stacks::address::PoxAddress;
4040
use stacks::chainstate::stacks::db::StacksChainState;
4141
use stacks::chainstate::stacks::miner::{
42-
get_mining_spend_amount, signal_mining_blocked, signal_mining_ready,
42+
set_mining_spend_amount, signal_mining_blocked, signal_mining_ready,
4343
};
4444
use stacks::chainstate::stacks::Error as ChainstateError;
4545
use stacks::core::mempool::MemPoolDB;
@@ -1101,8 +1101,29 @@ impl RelayerThread {
11011101
return Err(NakamotoNodeError::SnapshotNotFoundForChainTip);
11021102
};
11031103

1104+
let burnchain_config = self.config.get_burnchain_config();
1105+
let last_miner_spend_opt = self.globals.get_last_miner_spend_amount();
1106+
let force_remine = if let Some(last_miner_spend_amount) = last_miner_spend_opt {
1107+
last_miner_spend_amount != burnchain_config.burn_fee_cap
1108+
} else {
1109+
false
1110+
};
1111+
if force_remine {
1112+
info!(
1113+
"Miner config changed; updating spend amount {}",
1114+
burnchain_config.burn_fee_cap
1115+
);
1116+
}
1117+
1118+
self.globals
1119+
.set_last_miner_spend_amount(burnchain_config.burn_fee_cap);
1120+
1121+
set_mining_spend_amount(
1122+
self.globals.get_miner_status(),
1123+
burnchain_config.burn_fee_cap,
1124+
);
11041125
// amount of burnchain tokens (e.g. sats) we'll spend across the PoX outputs
1105-
let burn_fee_cap = get_mining_spend_amount(self.globals.get_miner_status());
1126+
let burn_fee_cap = burnchain_config.burn_fee_cap;
11061127

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

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

Lines changed: 139 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};
@@ -10688,7 +10689,7 @@ fn test_tenure_extend_from_flashblocks() {
1068810689
(define-data-var my-var uint u0)
1068910690
(define-data-var my-counter uint u0)
1069010691
10691-
(define-public (f)
10692+
(define-public (f)
1069210693
(begin
1069310694
(var-set my-var burn-block-height)
1069410695
(if (is-eq u0 (mod burn-block-height u2))
@@ -11089,3 +11090,140 @@ fn mine_invalid_principal_from_consensus_buff() {
1108911090

1109011091
run_loop_thread.join().unwrap();
1109111092
}
11093+
11094+
/// Test hot-reloading of miner config
11095+
#[test]
11096+
#[ignore]
11097+
fn reload_miner_config() {
11098+
if env::var("BITCOIND_TEST") != Ok("1".into()) {
11099+
return;
11100+
}
11101+
11102+
let (mut conf, _miner_account) = naka_neon_integration_conf(None);
11103+
let password = "12345".to_string();
11104+
let _http_origin = format!("http://{}", &conf.node.rpc_bind);
11105+
conf.connection_options.auth_token = Some(password.clone());
11106+
conf.miner.wait_on_interim_blocks = Duration::from_secs(1);
11107+
let stacker_sk = setup_stacker(&mut conf);
11108+
let signer_sk = Secp256k1PrivateKey::random();
11109+
let signer_addr = tests::to_addr(&signer_sk);
11110+
let sender_sk = Secp256k1PrivateKey::random();
11111+
// setup sender + recipient for some test stx transfers
11112+
// these are necessary for the interim blocks to get mined at all
11113+
let sender_addr = tests::to_addr(&sender_sk);
11114+
conf.add_initial_balance(PrincipalData::from(sender_addr).to_string(), 1000000);
11115+
conf.add_initial_balance(PrincipalData::from(signer_addr).to_string(), 100000);
11116+
11117+
test_observer::spawn();
11118+
test_observer::register(&mut conf, &[EventKeyType::AnyEvent]);
11119+
11120+
let mut btcd_controller = BitcoinCoreController::new(conf.clone());
11121+
btcd_controller
11122+
.start_bitcoind()
11123+
.expect("Failed starting bitcoind");
11124+
let mut btc_regtest_controller = BitcoinRegtestController::new(conf.clone(), None);
11125+
btc_regtest_controller.bootstrap_chain(201);
11126+
11127+
let conf_path =
11128+
std::env::temp_dir().join(format!("miner-config-test-{}.toml", rand::random::<u64>()));
11129+
conf.config_path = Some(conf_path.clone().to_str().unwrap().to_string());
11130+
11131+
// Make a minimum-viable config file
11132+
let update_config = |burn_fee_cap: u64, sats_vbyte: u64| {
11133+
use std::io::Write;
11134+
11135+
let new_config = format!(
11136+
r#"
11137+
[burnchain]
11138+
burn_fee_cap = {}
11139+
satoshis_per_byte = {}
11140+
"#,
11141+
burn_fee_cap, sats_vbyte,
11142+
);
11143+
// Write to a file
11144+
let mut file = File::create(&conf_path).unwrap();
11145+
file.write_all(new_config.as_bytes()).unwrap();
11146+
};
11147+
11148+
update_config(100000, 50);
11149+
11150+
let mut run_loop = boot_nakamoto::BootRunLoop::new(conf.clone()).unwrap();
11151+
let run_loop_stopper = run_loop.get_termination_switch();
11152+
let counters = run_loop.counters();
11153+
let Counters {
11154+
blocks_processed,
11155+
naka_submitted_commits: commits_submitted,
11156+
..
11157+
} = run_loop.counters();
11158+
11159+
let coord_channel = run_loop.coordinator_channels();
11160+
11161+
let run_loop_thread = thread::spawn(move || run_loop.start(None, 0));
11162+
let mut signers = TestSigners::new(vec![signer_sk]);
11163+
wait_for_runloop(&blocks_processed);
11164+
boot_to_epoch_3(
11165+
&conf,
11166+
&blocks_processed,
11167+
&[stacker_sk],
11168+
&[signer_sk],
11169+
&mut Some(&mut signers),
11170+
&mut btc_regtest_controller,
11171+
);
11172+
11173+
info!("------------------------- Reached Epoch 3.0 -------------------------");
11174+
11175+
blind_signer(&conf, &signers, &counters);
11176+
11177+
wait_for_first_naka_block_commit(60, &commits_submitted);
11178+
11179+
next_block_and_mine_commit(&mut btc_regtest_controller, 60, &conf, &counters).unwrap();
11180+
11181+
let burn_blocks = test_observer::get_burn_blocks();
11182+
let burn_block = burn_blocks.last().unwrap();
11183+
info!("Burn block: {:?}", &burn_block);
11184+
11185+
let reward_amount = burn_block
11186+
.get("reward_recipients")
11187+
.unwrap()
11188+
.as_array()
11189+
.unwrap()
11190+
.iter()
11191+
.map(|r| r.get("amt").unwrap().as_u64().unwrap())
11192+
.sum::<u64>();
11193+
11194+
assert_eq!(reward_amount, 200000);
11195+
11196+
next_block_and_mine_commit(&mut btc_regtest_controller, 60, &conf, &counters).unwrap();
11197+
11198+
info!("---- Updating config ----");
11199+
let new_amount = 150000;
11200+
update_config(new_amount, 55);
11201+
11202+
// Due to timing of commits, just mine two blocks
11203+
11204+
next_block_and_mine_commit(&mut btc_regtest_controller, 60, &conf, &counters).unwrap();
11205+
next_block_and_mine_commit(&mut btc_regtest_controller, 60, &conf, &counters).unwrap();
11206+
11207+
let burn_blocks = test_observer::get_burn_blocks();
11208+
let burn_block = burn_blocks.last().unwrap();
11209+
info!("Burn block: {:?}", &burn_block);
11210+
11211+
let reward_amount = burn_block
11212+
.get("reward_recipients")
11213+
.unwrap()
11214+
.as_array()
11215+
.unwrap()
11216+
.iter()
11217+
.map(|r| r.get("amt").unwrap().as_u64().unwrap())
11218+
.sum::<u64>();
11219+
11220+
assert_eq!(reward_amount, new_amount);
11221+
11222+
coord_channel
11223+
.lock()
11224+
.expect("Mutex poisoned")
11225+
.stop_chains_coordinator();
11226+
run_loop_stopper.store(false, Ordering::SeqCst);
11227+
11228+
run_loop_thread.join().unwrap();
11229+
}

0 commit comments

Comments
 (0)