Skip to content

Commit 5acf6c5

Browse files
authored
Merge pull request #4996 from stacks-network/feat/reuse-key
feat: support resuming from a saved VRF key
2 parents 914de0b + 09585f4 commit 5acf6c5

File tree

5 files changed

+213
-8
lines changed

5 files changed

+213
-8
lines changed

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: 169 additions & 1 deletion
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};
@@ -1095,6 +1097,43 @@ impl RelayerThread {
10951097
debug!("Relayer exit!");
10961098
}
10971099

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

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)