Skip to content

Commit 5a3121a

Browse files
authored
Merge pull request #5017 from stacks-network/chore/clarity-wasm-merge-develop
Merge develop into clarity-wasm develop branch
2 parents abdd46e + c27d1ad commit 5a3121a

File tree

11 files changed

+450
-120
lines changed

11 files changed

+450
-120
lines changed

.github/workflows/bitcoin-tests.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ jobs:
8989
- tests::signer::v0::end_of_tenure
9090
- tests::signer::v0::forked_tenure_okay
9191
- tests::signer::v0::forked_tenure_invalid
92+
- tests::signer::v0::bitcoind_forking_test
9293
- tests::nakamoto_integrations::stack_stx_burn_op_integration_test
9394
- tests::nakamoto_integrations::check_block_heights
9495
- tests::nakamoto_integrations::clarity_burn_state

libsigner/src/events.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,6 @@ fn process_stackerdb_event<T: SignerEventTrait>(
393393
local_addr: Option<SocketAddr>,
394394
mut request: HttpRequest,
395395
) -> Result<SignerEvent<T>, EventError> {
396-
debug!("Got stackerdb_chunks event");
397396
let mut body = String::new();
398397
if let Err(e) = request.as_reader().read_to_string(&mut body) {
399398
error!("Failed to read body: {:?}", &e);
@@ -404,6 +403,7 @@ fn process_stackerdb_event<T: SignerEventTrait>(
404403
)));
405404
}
406405

406+
debug!("Got stackerdb_chunks event"; "chunks_event_body" => %body);
407407
let event: StackerDBChunksEvent = serde_json::from_slice(body.as_bytes())
408408
.map_err(|e| EventError::Deserialize(format!("Could not decode body to JSON: {:?}", &e)))?;
409409

testnet/stacks-node/src/event_dispatcher.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,9 @@ impl RewardSetEventPayload {
296296

297297
impl EventObserver {
298298
pub fn send_payload(&self, payload: &serde_json::Value, path: &str) {
299+
debug!(
300+
"Event dispatcher: Sending payload"; "url" => %path, "payload" => ?payload
301+
);
299302
let body = match serde_json::to_vec(&payload) {
300303
Ok(body) => body,
301304
Err(err) => {

testnet/stacks-node/src/nakamoto_node.rs

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@
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::HashSet;
17+
use std::io::Write;
1718
use std::sync::mpsc::Receiver;
18-
use std::thread;
1919
use std::thread::JoinHandle;
20+
use std::{fs, thread};
2021

2122
use stacks::burnchains::{BurnchainSigner, Txid};
2223
use stacks::chainstate::burn::db::sortdb::SortitionDB;
@@ -277,6 +278,7 @@ impl StacksNode {
277278
/// Called from the main thread.
278279
pub fn process_burnchain_state(
279280
&mut self,
281+
config: &Config,
280282
sortdb: &SortitionDB,
281283
sort_id: &SortitionId,
282284
ibd: bool,
@@ -316,9 +318,18 @@ impl StacksNode {
316318

317319
let num_key_registers = key_registers.len();
318320

319-
self.globals
321+
let activated_key_opt = self
322+
.globals
320323
.try_activate_leader_key_registration(block_height, key_registers);
321324

325+
// save the registered VRF key
326+
if let (Some(activated_key), Some(path)) = (
327+
activated_key_opt,
328+
config.miner.activated_vrf_key_path.as_ref(),
329+
) {
330+
save_activated_vrf_key(path, &activated_key);
331+
}
332+
322333
debug!(
323334
"Processed burnchain state";
324335
"burn_height" => block_height,
@@ -339,3 +350,27 @@ impl StacksNode {
339350
self.p2p_thread_handle.join().unwrap();
340351
}
341352
}
353+
354+
pub(crate) fn save_activated_vrf_key(path: &str, activated_key: &RegisteredKey) {
355+
info!("Activated VRF key; saving to {}", path);
356+
357+
let Ok(key_json) = serde_json::to_string(&activated_key) else {
358+
warn!("Failed to serialize VRF key");
359+
return;
360+
};
361+
362+
let mut f = match fs::File::create(&path) {
363+
Ok(f) => f,
364+
Err(e) => {
365+
warn!("Failed to create {}: {:?}", &path, &e);
366+
return;
367+
}
368+
};
369+
370+
if let Err(e) = f.write_all(key_json.as_str().as_bytes()) {
371+
warn!("Failed to write activated VRF key to {}: {:?}", &path, &e);
372+
return;
373+
}
374+
375+
info!("Saved activated VRF key to {}", &path);
376+
}

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

Lines changed: 183 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
// along with this program. If not, see <http://www.gnu.org/licenses/>.
1616
use core::fmt;
1717
use std::collections::HashSet;
18+
use std::fs;
19+
use std::io::Read;
1820
use std::sync::mpsc::{Receiver, RecvTimeoutError};
1921
use std::thread::JoinHandle;
2022
use std::time::{Duration, Instant};
@@ -110,6 +112,7 @@ pub struct LastCommit {
110112
/// the tenure consensus hash for the tip's tenure
111113
tenure_consensus_hash: ConsensusHash,
112114
/// the start-block hash of the tip's tenure
115+
#[allow(dead_code)]
113116
start_block_hash: BlockHeaderHash,
114117
/// What is the epoch in which this was sent?
115118
epoch_id: StacksEpochId,
@@ -836,14 +839,20 @@ impl RelayerThread {
836839
})?
837840
};
838841

839-
if last_winner_snapshot.miner_pk_hash != Some(mining_pkh) {
840-
debug!("Relayer: the miner did not win the last sortition. No tenure to continue.";
841-
"current_mining_pkh" => %mining_pkh,
842-
"last_winner_snapshot.miner_pk_hash" => ?last_winner_snapshot.miner_pk_hash,
843-
);
842+
let won_last_sortition = last_winner_snapshot.miner_pk_hash == Some(mining_pkh);
843+
debug!(
844+
"Relayer: Current burn block had no sortition. Checking for tenure continuation.";
845+
"won_last_sortition" => won_last_sortition,
846+
"current_mining_pkh" => %mining_pkh,
847+
"last_winner_snapshot.miner_pk_hash" => ?last_winner_snapshot.miner_pk_hash,
848+
"canonical_stacks_tip_id" => %canonical_stacks_tip,
849+
"canonical_stacks_tip_ch" => %canonical_stacks_tip_ch,
850+
"block_election_ch" => %block_election_snapshot.consensus_hash,
851+
"burn_view_ch" => %new_burn_view,
852+
);
853+
854+
if !won_last_sortition {
844855
return Ok(());
845-
} else {
846-
debug!("Relayer: the miner won the last sortition. Continuing tenure.");
847856
}
848857

849858
match self.start_new_tenure(
@@ -1095,6 +1104,43 @@ impl RelayerThread {
10951104
debug!("Relayer exit!");
10961105
}
10971106

1107+
/// Try loading up a saved VRF key
1108+
pub(crate) fn load_saved_vrf_key(path: &str, pubkey_hash: &Hash160) -> Option<RegisteredKey> {
1109+
let mut f = match fs::File::open(path) {
1110+
Ok(f) => f,
1111+
Err(e) => {
1112+
warn!("Could not open {}: {:?}", &path, &e);
1113+
return None;
1114+
}
1115+
};
1116+
let mut registered_key_bytes = vec![];
1117+
if let Err(e) = f.read_to_end(&mut registered_key_bytes) {
1118+
warn!(
1119+
"Failed to read registered key bytes from {}: {:?}",
1120+
path, &e
1121+
);
1122+
return None;
1123+
}
1124+
1125+
let Ok(registered_key) = serde_json::from_slice::<RegisteredKey>(&registered_key_bytes)
1126+
else {
1127+
warn!(
1128+
"Did not load registered key from {}: could not decode JSON",
1129+
&path
1130+
);
1131+
return None;
1132+
};
1133+
1134+
// Check that the loaded key's memo matches the current miner's key
1135+
if registered_key.memo != pubkey_hash.as_ref() {
1136+
warn!("Loaded VRF key does not match mining key");
1137+
return None;
1138+
}
1139+
1140+
info!("Loaded registered key from {}", &path);
1141+
Some(registered_key)
1142+
}
1143+
10981144
/// Top-level dispatcher
10991145
pub fn handle_directive(&mut self, directive: RelayerDirective) -> bool {
11001146
debug!("Relayer: handling directive"; "directive" => %directive);
@@ -1113,7 +1159,18 @@ impl RelayerThread {
11131159
info!("In initial block download, will not submit VRF registration");
11141160
return true;
11151161
}
1116-
self.rotate_vrf_and_register(&last_burn_block);
1162+
let mut saved_key_opt = None;
1163+
if let Some(path) = self.config.miner.activated_vrf_key_path.as_ref() {
1164+
saved_key_opt =
1165+
Self::load_saved_vrf_key(&path, &self.keychain.get_nakamoto_pkh());
1166+
}
1167+
if let Some(saved_key) = saved_key_opt {
1168+
debug!("Relayer: resuming VRF key");
1169+
self.globals.resume_leader_key(saved_key);
1170+
} else {
1171+
self.rotate_vrf_and_register(&last_burn_block);
1172+
debug!("Relayer: directive Registered VRF key");
1173+
}
11171174
self.globals.counters.bump_blocks_processed();
11181175
true
11191176
}
@@ -1154,3 +1211,121 @@ impl RelayerThread {
11541211
continue_running
11551212
}
11561213
}
1214+
1215+
#[cfg(test)]
1216+
pub mod test {
1217+
use std::fs::File;
1218+
use std::io::Write;
1219+
use std::path::Path;
1220+
1221+
use stacks::util::hash::Hash160;
1222+
use stacks::util::secp256k1::Secp256k1PublicKey;
1223+
use stacks::util::vrf::VRFPublicKey;
1224+
1225+
use super::RelayerThread;
1226+
use crate::nakamoto_node::save_activated_vrf_key;
1227+
use crate::run_loop::RegisteredKey;
1228+
use crate::Keychain;
1229+
1230+
#[test]
1231+
fn load_nonexistent_vrf_key() {
1232+
let keychain = Keychain::default(vec![0u8; 32]);
1233+
let pk = Secp256k1PublicKey::from_private(keychain.get_nakamoto_sk());
1234+
let pubkey_hash = Hash160::from_node_public_key(&pk);
1235+
1236+
let path = "/tmp/does_not_exist.json";
1237+
_ = std::fs::remove_file(&path);
1238+
1239+
let res = RelayerThread::load_saved_vrf_key(&path, &pubkey_hash);
1240+
assert!(res.is_none());
1241+
}
1242+
1243+
#[test]
1244+
fn load_empty_vrf_key() {
1245+
let keychain = Keychain::default(vec![0u8; 32]);
1246+
let pk = Secp256k1PublicKey::from_private(keychain.get_nakamoto_sk());
1247+
let pubkey_hash = Hash160::from_node_public_key(&pk);
1248+
1249+
let path = "/tmp/empty.json";
1250+
File::create(&path).expect("Failed to create test file");
1251+
assert!(Path::new(&path).exists());
1252+
1253+
let res = RelayerThread::load_saved_vrf_key(&path, &pubkey_hash);
1254+
assert!(res.is_none());
1255+
1256+
std::fs::remove_file(&path).expect("Failed to delete test file");
1257+
}
1258+
1259+
#[test]
1260+
fn load_bad_vrf_key() {
1261+
let keychain = Keychain::default(vec![0u8; 32]);
1262+
let pk = Secp256k1PublicKey::from_private(keychain.get_nakamoto_sk());
1263+
let pubkey_hash = Hash160::from_node_public_key(&pk);
1264+
1265+
let path = "/tmp/invalid_saved_key.json";
1266+
let json_content = r#"{ "hello": "world" }"#;
1267+
1268+
// Write the JSON content to the file
1269+
let mut file = File::create(&path).expect("Failed to create test file");
1270+
file.write_all(json_content.as_bytes())
1271+
.expect("Failed to write to test file");
1272+
assert!(Path::new(&path).exists());
1273+
1274+
let res = RelayerThread::load_saved_vrf_key(&path, &pubkey_hash);
1275+
assert!(res.is_none());
1276+
1277+
std::fs::remove_file(&path).expect("Failed to delete test file");
1278+
}
1279+
1280+
#[test]
1281+
fn save_load_vrf_key() {
1282+
let keychain = Keychain::default(vec![0u8; 32]);
1283+
let pk = Secp256k1PublicKey::from_private(keychain.get_nakamoto_sk());
1284+
let pubkey_hash = Hash160::from_node_public_key(&pk);
1285+
let key = RegisteredKey {
1286+
target_block_height: 101,
1287+
block_height: 102,
1288+
op_vtxindex: 1,
1289+
vrf_public_key: VRFPublicKey::from_hex(
1290+
"1da75863a7e1ef86f0f550d92b1f77dc60af23694b884b2816b703137ff94e71",
1291+
)
1292+
.unwrap(),
1293+
memo: pubkey_hash.as_ref().to_vec(),
1294+
};
1295+
let path = "/tmp/vrf_key.json";
1296+
save_activated_vrf_key(path, &key);
1297+
1298+
let res = RelayerThread::load_saved_vrf_key(&path, &pubkey_hash);
1299+
assert!(res.is_some());
1300+
1301+
std::fs::remove_file(&path).expect("Failed to delete test file");
1302+
}
1303+
1304+
#[test]
1305+
fn invalid_saved_memo() {
1306+
let keychain = Keychain::default(vec![0u8; 32]);
1307+
let pk = Secp256k1PublicKey::from_private(keychain.get_nakamoto_sk());
1308+
let pubkey_hash = Hash160::from_node_public_key(&pk);
1309+
let key = RegisteredKey {
1310+
target_block_height: 101,
1311+
block_height: 102,
1312+
op_vtxindex: 1,
1313+
vrf_public_key: VRFPublicKey::from_hex(
1314+
"1da75863a7e1ef86f0f550d92b1f77dc60af23694b884b2816b703137ff94e71",
1315+
)
1316+
.unwrap(),
1317+
memo: pubkey_hash.as_ref().to_vec(),
1318+
};
1319+
let path = "/tmp/vrf_key.json";
1320+
save_activated_vrf_key(path, &key);
1321+
1322+
let keychain = Keychain::default(vec![1u8; 32]);
1323+
let pk = Secp256k1PublicKey::from_private(keychain.get_nakamoto_sk());
1324+
let pubkey_hash = Hash160::from_node_public_key(&pk);
1325+
1326+
let res = RelayerThread::load_saved_vrf_key(&path, &pubkey_hash);
1327+
assert!(res.is_none());
1328+
1329+
std::fs::remove_file(&path).expect("Failed to delete test file");
1330+
}
1331+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -653,7 +653,7 @@ impl SignCoordinator {
653653

654654
let block_proposal_message = SignerMessageV0::BlockProposal(block_proposal);
655655
debug!("Sending block proposal message to signers";
656-
"signer_signature_hash" => ?&block.header.signer_signature_hash().0,
656+
"signer_signature_hash" => %block.header.signer_signature_hash(),
657657
);
658658
Self::send_miners_message_scalar::<SignerMessageV0>(
659659
&self.message_key,

testnet/stacks-node/src/neon_node.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3971,7 +3971,6 @@ impl RelayerThread {
39713971
if let Some(saved_key) = saved_key_opt {
39723972
self.globals.resume_leader_key(saved_key);
39733973
} else {
3974-
debug!("Relayer: directive Register VRF key");
39753974
self.rotate_vrf_and_register(&last_burn_block);
39763975
debug!("Relayer: directive Registered VRF key");
39773976
}

testnet/stacks-node/src/run_loop/nakamoto.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -635,9 +635,12 @@ impl RunLoop {
635635
let sortition_id = &block.sortition_id;
636636

637637
// Have the node process the new block, that can include, or not, a sortition.
638-
if let Err(e) =
639-
node.process_burnchain_state(burnchain.sortdb_mut(), sortition_id, ibd)
640-
{
638+
if let Err(e) = node.process_burnchain_state(
639+
self.config(),
640+
burnchain.sortdb_mut(),
641+
sortition_id,
642+
ibd,
643+
) {
641644
// relayer errored, exit.
642645
error!("Runloop: Block relayer and miner errored, exiting."; "err" => ?e);
643646
return;

0 commit comments

Comments
 (0)