drk: store each block signing key in scanned blocks and display it so miners can grab it if ever needed

This commit is contained in:
skoupidi
2025-12-17 19:29:38 +02:00
parent 596a498e8b
commit 86c740d9a0
7 changed files with 94 additions and 66 deletions

View File

@@ -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<SecretKey>,
) -> 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(())
}

View File

@@ -207,3 +207,14 @@ pub fn prettytable_aliases(alimap: &HashMap<String, TokenId>) -> 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
}

View File

@@ -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"));

View File

@@ -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");

View File

@@ -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<ContractCall>],
) -> Result<(Vec<Nullifier>, Vec<(Coin, AeadEncryptedNote)>, Vec<TokenId>)> {
) -> Result<(Vec<Nullifier>, Vec<(Coin, AeadEncryptedNote, bool)>, Vec<TokenId>)> {
let mut nullifiers: Vec<Nullifier> = vec![];
let mut coins: Vec<(Coin, AeadEncryptedNote)> = vec![];
let mut coins: Vec<(Coin, AeadEncryptedNote, bool)> = vec![];
let mut freezes: Vec<TokenId> = 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<String>,
coins: &[(Coin, AeadEncryptedNote)],
) -> Vec<OwnCoin> {
coins: &[(Coin, AeadEncryptedNote, bool)],
) -> Result<(Vec<OwnCoin>, Option<SecretKey>)> {
// 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(&note.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<ContractCall>],
tx_hash: &String,
block_height: &u32,
) -> Result<bool> {
) -> Result<(bool, Option<SecretKey>)> {
// 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.

View File

@@ -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 {

View File

@@ -28,21 +28,21 @@ use crate::{
impl Drk {
/// Get a scanned block information record.
pub fn get_scanned_block_hash(&self, height: &u32) -> WalletDbResult<String> {
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<Vec<(u32, String)>> {
pub fn get_scanned_block_records(&self) -> WalletDbResult<Vec<(u32, String, String)>> {
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.