From 86c740d9a0e1ee54ddbb9c1b4782d67c65579a83 Mon Sep 17 00:00:00 2001 From: skoupidi Date: Wed, 17 Dec 2025 19:29:38 +0200 Subject: [PATCH] drk: store each block signing key in scanned blocks and display it so miners can grab it if ever needed --- bin/drk/src/cache.rs | 22 +++++++++---- bin/drk/src/common.rs | 11 +++++++ bin/drk/src/interactive.rs | 13 +++----- bin/drk/src/main.rs | 13 +++----- bin/drk/src/money.rs | 60 +++++++++++++++++++++-------------- bin/drk/src/rpc.rs | 23 ++++++++------ bin/drk/src/scanned_blocks.rs | 18 +++++------ 7 files changed, 94 insertions(+), 66 deletions(-) diff --git a/bin/drk/src/cache.rs b/bin/drk/src/cache.rs index 92bafe92a..6e57e934d 100644 --- a/bin/drk/src/cache.rs +++ b/bin/drk/src/cache.rs @@ -23,7 +23,7 @@ use darkfi_sdk::{ crypto::{ pasta_prelude::PrimeField, smt::{PoseidonFp, SparseMerkleTree, StorageAdapter, SMT_FP_DEPTH}, - MerkleTree, + MerkleTree, SecretKey, }, error::{ContractError, ContractResult}, pasta::pallas, @@ -139,14 +139,24 @@ impl CacheOverlay { Ok(Self(overlay)) } - /// Insert a `u32` and a block hash into overlay's scanned blocks - /// tree. The block height is used as the key, and the serialized - /// blockhash string is used as value. - pub fn insert_scanned_block(&mut self, height: &u32, hash: &HeaderHash) -> Result<()> { + /// Insert a `u32`, a block hash and an optional signing key into + /// overlay's scanned blocks tree. The block height is used as the + /// key, while the serialized blockhash and key strings are used as + /// the value. + pub fn insert_scanned_block( + &mut self, + height: &u32, + hash: &HeaderHash, + signing_key: &Option, + ) -> Result<()> { + let block_signing_key = match signing_key { + Some(key) => key.to_string(), + None => String::from("-"), + }; self.0.insert( SLED_SCANNED_BLOCKS_TREE, &height.to_be_bytes(), - &serialize(&hash.to_string()), + &serialize(&(hash.to_string(), block_signing_key)), )?; Ok(()) } diff --git a/bin/drk/src/common.rs b/bin/drk/src/common.rs index c687f6f4e..b6ee8968c 100644 --- a/bin/drk/src/common.rs +++ b/bin/drk/src/common.rs @@ -207,3 +207,14 @@ pub fn prettytable_aliases(alimap: &HashMap) -> Table { table } + +pub fn prettytable_scanned_blocks(scanned_blocks: &[(u32, String, String)]) -> Table { + let mut table = Table::new(); + table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR); + table.set_titles(row!["Height", "Hash", "Signing Key"]); + for (height, hash, signing_key) in scanned_blocks { + table.add_row(row![height, hash, signing_key]); + } + + table +} diff --git a/bin/drk/src/interactive.rs b/bin/drk/src/interactive.rs index 0f371e5b7..39b76390d 100644 --- a/bin/drk/src/interactive.rs +++ b/bin/drk/src/interactive.rs @@ -2687,10 +2687,11 @@ async fn handle_explorer_scanned_blocks(drk: &DrkPtr, parts: &[&str], output: &m } }; - match lock.get_scanned_block_hash(&height) { - Ok(hash) => { + match lock.get_scanned_block(&height) { + Ok((hash, signing_key)) => { output.push(format!("Height: {height}")); output.push(format!("Hash: {hash}")); + output.push(format!("Signing key: {signing_key}")); } Err(e) => output.push(format!("Failed to retrieve scanned block record: {e}")), }; @@ -2705,13 +2706,7 @@ async fn handle_explorer_scanned_blocks(drk: &DrkPtr, parts: &[&str], output: &m } }; - // Create a prettytable with the new data: - let mut table = Table::new(); - table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR); - table.set_titles(row!["Height", "Hash"]); - for (height, hash) in map.iter() { - table.add_row(row![height, hash]); - } + let table = prettytable_scanned_blocks(&map); if table.is_empty() { output.push(String::from("No scanned blocks records found")); diff --git a/bin/drk/src/main.rs b/bin/drk/src/main.rs index 1b64f97f6..130fcf87c 100644 --- a/bin/drk/src/main.rs +++ b/bin/drk/src/main.rs @@ -2258,8 +2258,8 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> { .await; if let Some(height) = height { - let hash = match drk.get_scanned_block_hash(&height) { - Ok(h) => h, + let (hash, signing_key) = match drk.get_scanned_block(&height) { + Ok(p) => p, Err(e) => { eprintln!("Failed to retrieve scanned block record: {e}"); exit(2); @@ -2268,6 +2268,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> { println!("Height: {height}"); println!("Hash: {hash}"); + println!("Signing key: {signing_key}"); return Ok(()) } @@ -2280,13 +2281,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> { } }; - // Create a prettytable with the new data: - let mut table = Table::new(); - table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR); - table.set_titles(row!["Height", "Hash"]); - for (height, hash) in map.iter() { - table.add_row(row![height, hash]); - } + let table = prettytable_scanned_blocks(&map); if table.is_empty() { println!("No scanned blocks records found"); diff --git a/bin/drk/src/money.rs b/bin/drk/src/money.rs index ba8deb038..ca1d5796d 100644 --- a/bin/drk/src/money.rs +++ b/bin/drk/src/money.rs @@ -58,7 +58,7 @@ use darkfi_sdk::{ pasta::pallas, ContractCall, }; -use darkfi_serial::{deserialize_async, serialize, serialize_async, AsyncEncodable}; +use darkfi_serial::{deserialize, deserialize_async, serialize, serialize_async, AsyncEncodable}; use crate::{ cache::CacheSmt, @@ -742,15 +742,16 @@ impl Drk { } /// Auxiliary function to grab all the nullifiers, coins with their - /// notes and freezes from a transaction money call. + /// notes and a flag indicating if its a block reward, and freezes + /// from a transaction money call. async fn parse_money_call( &self, scan_cache: &mut ScanCache, call_idx: &usize, calls: &[DarkLeaf], - ) -> Result<(Vec, Vec<(Coin, AeadEncryptedNote)>, Vec)> { + ) -> Result<(Vec, Vec<(Coin, AeadEncryptedNote, bool)>, Vec)> { let mut nullifiers: Vec = vec![]; - let mut coins: Vec<(Coin, AeadEncryptedNote)> = vec![]; + let mut coins: Vec<(Coin, AeadEncryptedNote, bool)> = vec![]; let mut freezes: Vec = vec![]; let call = &calls[*call_idx]; @@ -760,19 +761,19 @@ impl Drk { scan_cache.log(String::from("[parse_money_call] Found Money::FeeV1 call")); let params: MoneyFeeParamsV1 = deserialize_async(&data[9..]).await?; nullifiers.push(params.input.nullifier); - coins.push((params.output.coin, params.output.note)); + coins.push((params.output.coin, params.output.note, false)); } MoneyFunction::GenesisMintV1 => { scan_cache.log(String::from("[parse_money_call] Found Money::GenesisMintV1 call")); let params: MoneyGenesisMintParamsV1 = deserialize_async(&data[1..]).await?; for output in params.outputs { - coins.push((output.coin, output.note)); + coins.push((output.coin, output.note, false)); } } MoneyFunction::PoWRewardV1 => { scan_cache.log(String::from("[parse_money_call] Found Money::PoWRewardV1 call")); let params: MoneyPoWRewardParamsV1 = deserialize_async(&data[1..]).await?; - coins.push((params.output.coin, params.output.note)); + coins.push((params.output.coin, params.output.note, true)); } MoneyFunction::TransferV1 => { scan_cache.log(String::from("[parse_money_call] Found Money::TransferV1 call")); @@ -783,7 +784,7 @@ impl Drk { } for output in params.outputs { - coins.push((output.coin, output.note)); + coins.push((output.coin, output.note, false)); } } MoneyFunction::OtcSwapV1 => { @@ -795,7 +796,7 @@ impl Drk { } for output in params.outputs { - coins.push((output.coin, output.note)); + coins.push((output.coin, output.note, false)); } } MoneyFunction::AuthTokenMintV1 => { @@ -817,33 +818,38 @@ impl Drk { let child_call = &calls[child_idx]; let child_params: MoneyAuthTokenMintParamsV1 = deserialize_async(&child_call.data.data[1..]).await?; - coins.push((params.coin, child_params.enc_note)); + coins.push((params.coin, child_params.enc_note, false)); } } Ok((nullifiers, coins, freezes)) } - /// Auxiliary function to handle coins with their notes from a - /// transaction money call. - /// Returns our found own coins. + /// Auxiliary function to handle coins with their notes and flag + /// indicating if its a block reward from a transaction money call. + /// Returns our found own coins along with the block signing key, + /// if found. fn handle_money_call_coins( &self, tree: &mut MerkleTree, secrets: &[SecretKey], messages_buffer: &mut Vec, - coins: &[(Coin, AeadEncryptedNote)], - ) -> Vec { + coins: &[(Coin, AeadEncryptedNote, bool)], + ) -> Result<(Vec, Option)> { // Keep track of our own coins found in the vec let mut owncoins = vec![]; // Check if provided coins vec is empty if coins.is_empty() { - return owncoins + return Ok((owncoins, None)) } - // Handle provided coins vector and grab our own - for (coin, note) in coins { + // Handle provided coins vector and grab our own, + // along with the block signing key if its a block + // reward coin. Only one reward call and coin exists + // in each block. + let mut block_signing_key = None; + for (coin, note, is_block_reward) in coins { // Append the new coin to the Merkle tree. // Every coin has to be added. tree.append(MerkleNode::from(coin.inner())); @@ -857,12 +863,18 @@ impl Drk { messages_buffer .push(String::from("[handle_money_call_coins] Witnessing coin in Merkle tree")); let leaf_position = tree.mark().unwrap(); + if *is_block_reward { + messages_buffer + .push(String::from("[handle_money_call_coins] Grabing block signing key")); + block_signing_key = Some(deserialize(¬e.memo)?); + } let owncoin = OwnCoin { coin: *coin, note, secret: *secret, leaf_position }; owncoins.push(owncoin); + break } } - owncoins + Ok((owncoins, block_signing_key)) } /// Auxiliary function to handle own coins from a transaction money @@ -997,7 +1009,7 @@ impl Drk { /// Append data related to Money contract transactions into the /// wallet database and update the provided scan cache. /// Returns a flag indicating if provided data refer to our own - /// wallet. + /// wallet along with the block signing key, if found. pub async fn apply_tx_money_data( &self, scan_cache: &mut ScanCache, @@ -1005,18 +1017,18 @@ impl Drk { calls: &[DarkLeaf], tx_hash: &String, block_height: &u32, - ) -> Result { + ) -> Result<(bool, Option)> { // Parse the call let (nullifiers, coins, freezes) = self.parse_money_call(scan_cache, call_idx, calls).await?; // Parse call coins and grab our own - let owncoins = self.handle_money_call_coins( + let (owncoins, block_signing_key) = self.handle_money_call_coins( &mut scan_cache.money_tree, &scan_cache.notes_secrets, &mut scan_cache.messages_buffer, &coins, - ); + )?; // Update nullifiers smt self.smt_insert(&mut scan_cache.money_smt, &nullifiers)?; @@ -1041,7 +1053,7 @@ impl Drk { kaching().await; } - Ok(wallet_spent_coins || !owncoins.is_empty() || wallet_freezes) + Ok((wallet_spent_coins || !owncoins.is_empty() || wallet_freezes, block_signing_key)) } /// Auxiliary function to grab all the nullifiers from a transaction money call. diff --git a/bin/drk/src/rpc.rs b/bin/drk/src/rpc.rs index 074c66114..e9142cef3 100644 --- a/bin/drk/src/rpc.rs +++ b/bin/drk/src/rpc.rs @@ -187,6 +187,7 @@ impl Drk { scan_cache.log(format!("{}", block.header)); scan_cache.log(String::from("=======================================")); scan_cache.log(format!("[scan_block] Iterating over {} transactions", block.txs.len())); + let mut block_signing_key = None; for tx in block.txs.iter() { let tx_hash = tx.hash(); let tx_hash_string = tx_hash.to_string(); @@ -195,7 +196,7 @@ impl Drk { for (i, call) in tx.calls.iter().enumerate() { if call.data.contract_id == *MONEY_CONTRACT_ID { scan_cache.log(format!("[scan_block] Found Money contract in call {i}")); - if self + let (is_wallet_tx, signing_key) = self .apply_tx_money_data( scan_cache, &i, @@ -203,9 +204,13 @@ impl Drk { &tx_hash_string, &block.header.height, ) - .await? - { + .await?; + if is_wallet_tx { wallet_tx = true; + // Only one block signing key exists per block + if signing_key.is_some() { + block_signing_key = signing_key; + } } continue } @@ -255,11 +260,11 @@ impl Drk { } // Insert the block record - scan_cache - .money_smt - .store - .overlay - .insert_scanned_block(&block.header.height, &block.header.hash())?; + scan_cache.money_smt.store.overlay.insert_scanned_block( + &block.header.height, + &block.header.hash(), + &block_signing_key, + )?; // Grab the overlay current diff let diff = scan_cache.money_smt.store.overlay.0.diff(&[])?; @@ -329,7 +334,7 @@ impl Drk { height = height.saturating_sub(1); while height != 0 { // Grab our scanned block hash for that height - let scanned_block_hash = self.get_scanned_block_hash(&height)?; + let (scanned_block_hash, _) = self.get_scanned_block(&height)?; // Grab the block from darkfid for that height let block = match self.get_block_by_height(height).await { diff --git a/bin/drk/src/scanned_blocks.rs b/bin/drk/src/scanned_blocks.rs index a1411f4c1..8635a5135 100644 --- a/bin/drk/src/scanned_blocks.rs +++ b/bin/drk/src/scanned_blocks.rs @@ -28,21 +28,21 @@ use crate::{ impl Drk { /// Get a scanned block information record. - pub fn get_scanned_block_hash(&self, height: &u32) -> WalletDbResult { + pub fn get_scanned_block(&self, height: &u32) -> WalletDbResult<(String, String)> { let Ok(query_result) = self.cache.scanned_blocks.get(height.to_be_bytes()) else { return Err(WalletDbError::QueryExecutionFailed); }; - let Some(hash_bytes) = query_result else { + let Some(value_bytes) = query_result else { return Err(WalletDbError::RowNotFound); }; - let Ok(hash) = deserialize(&hash_bytes) else { + let Ok((hash, signing_key)) = deserialize(&value_bytes) else { return Err(WalletDbError::ParseColumnValueError); }; - Ok(hash) + Ok((hash, signing_key)) } /// Fetch all scanned block information records. - pub fn get_scanned_block_records(&self) -> WalletDbResult> { + pub fn get_scanned_block_records(&self) -> WalletDbResult> { let mut scanned_blocks = vec![]; for record in self.cache.scanned_blocks.iter() { @@ -54,10 +54,10 @@ impl Drk { Err(_) => return Err(WalletDbError::ParseColumnValueError), }; let key = u32::from_be_bytes(key); - let Ok(value) = deserialize(&value) else { + let Ok((hash, signing_key)) = deserialize(&value) else { return Err(WalletDbError::ParseColumnValueError); }; - scanned_blocks.push((key, value)); + scanned_blocks.push((key, hash, signing_key)); } Ok(scanned_blocks) @@ -75,10 +75,10 @@ impl Drk { Err(_) => return Err(WalletDbError::ParseColumnValueError), }; let key = u32::from_be_bytes(key); - let Ok(value) = deserialize(&value) else { + let Ok((hash, _)) = deserialize::<(String, String)>(&value) else { return Err(WalletDbError::ParseColumnValueError); }; - Ok((key, value)) + Ok((key, hash)) } /// Reset the scanned blocks information records in the cache.