drk: Block scanning

This commit is contained in:
parazyd
2022-11-29 19:32:55 +01:00
parent b0bdc6b633
commit 90eb93a9e5
8 changed files with 160 additions and 12 deletions

8
Cargo.lock generated
View File

@@ -1681,7 +1681,10 @@ dependencies = [
"prettytable-rs",
"rand",
"serde_json",
"signal-hook",
"signal-hook-async-std",
"simplelog",
"smol",
"sqlx",
"url",
]
@@ -4039,9 +4042,9 @@ checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
[[package]]
name = "smol"
version = "1.2.5"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85cf3b5351f3e783c1d79ab5fc604eeed8b8ae9abd36b166e8b87a089efd85e4"
checksum = "13f2b548cd8447f8de0fdf1c592929f70f4fc7039a05e47404b0d096ec6987a1"
dependencies = [
"async-channel",
"async-executor",
@@ -4052,7 +4055,6 @@ dependencies = [
"async-process",
"blocking",
"futures-lite",
"once_cell",
]
[[package]]

View File

@@ -37,6 +37,7 @@ zkas: $(BINDEPS)
contracts: zkas
$(MAKE) -C src/contract/money
$(MAKE) -C src/contract/dao
$(PROOFS_BIN): $(PROOFS)
./zkas $(basename $@) -o $@

View File

@@ -208,6 +208,9 @@ impl RequestHandler for Darkfid {
// Blockchain methods
// ==================
Some("blockchain.get_slot") => return self.blockchain_get_slot(req.id, params).await,
Some("blockchain.last_known_slot") => {
return self.blockchain_last_known_slot(req.id, params).await
}
Some("blockchain.merkle_roots") => {
return self.blockchain_merkle_roots(req.id, params).await
}

View File

@@ -20,7 +20,7 @@ use darkfi_sdk::{
crypto::{ContractId, MerkleNode},
db::ZKAS_DB_NAME,
};
use darkfi_serial::deserialize;
use darkfi_serial::{deserialize, serialize};
use log::{debug, error};
use serde_json::{json, Value};
@@ -62,9 +62,25 @@ impl Darkfid {
return server_error(RpcError::UnknownSlot, id, None)
}
// TODO: Return block as JSON
debug!("{:#?}", blocks[0]);
JsonResponse::new(json!(true), id).into()
JsonResponse::new(json!(serialize(&blocks[0])), id).into()
}
// RPCAPI:
// Queries the blockchain database to find the last known slot
//
// --> {"jsonrpc": "2.0", "method": "blockchain.last_known_slot", "params": [], "id": 1}
// <-- {"jsonrpc": "2.0", "result": 1234, "id": 1}
pub async fn blockchain_last_known_slot(&self, id: Value, params: &[Value]) -> JsonResult {
if !params.is_empty() {
return JsonError::new(InvalidParams, None, id).into()
}
let blockchain = { self.validator_state.read().await.blockchain.clone() };
let Ok(last_slot) = blockchain.last() else {
return JsonError::new(InternalError, None, id).into()
};
JsonResponse::new(json!(last_slot.0), id).into()
}
// RPCAPI:

View File

@@ -20,6 +20,9 @@ darkfi-money-contract = {path = "../../src/contract/money", features = ["no-entr
prettytable-rs = "0.9.0"
rand = "0.8.5"
serde_json = "1.0.89"
smol = "1.3.0"
simplelog = "0.12.0"
signal-hook-async-std = "0.2.2"
signal-hook = "0.3.14"
sqlx = {version = "0.6.2", features = ["runtime-async-std-native-tls", "sqlite"]}
url = "2.3.1"

View File

@@ -152,6 +152,12 @@ enum Subcmd {
/// With `drk` we look at transactions calling the money contract so we can
/// find coins sent to us and fill our wallet with the necessary metadata.
Subscribe,
/// Scan the blockchain and parse relevant transactions
Scan {
/// Slot number to start scanning from (optional)
slot: Option<u64>,
},
}
pub struct Drk {
@@ -389,5 +395,18 @@ async fn main() -> Result<()> {
Ok(())
}
Subcmd::Scan { slot } => {
let rpc_client = RpcClient::new(args.endpoint)
.await
.with_context(|| "Could not connect to darkfid RPC endpoint")?;
let drk = Drk { rpc_client };
drk.scan_blocks(slot).await.with_context(|| "Failed during scanning")?;
eprintln!("Finished scanning blockchain");
Ok(())
}
}
}

View File

@@ -17,7 +17,7 @@
*/
use anyhow::{anyhow, Result};
use async_std::task;
use async_std::{stream::StreamExt, task};
use darkfi::{
consensus::BlockInfo,
rpc::{
@@ -34,7 +34,8 @@ use darkfi_money_contract::{
MONEY_COINS_COL_IS_SPENT, MONEY_COINS_COL_LEAF_POSITION, MONEY_COINS_COL_MEMO,
MONEY_COINS_COL_NULLIFIER, MONEY_COINS_COL_SECRET, MONEY_COINS_COL_SERIAL,
MONEY_COINS_COL_TOKEN_BLIND, MONEY_COINS_COL_TOKEN_ID, MONEY_COINS_COL_VALUE,
MONEY_COINS_COL_VALUE_BLIND, MONEY_COINS_TABLE,
MONEY_COINS_COL_VALUE_BLIND, MONEY_COINS_TABLE, MONEY_INFO_COL_LAST_SCANNED_SLOT,
MONEY_INFO_TABLE,
},
state::{MoneyTransferParams, Output},
MoneyFunction,
@@ -46,6 +47,8 @@ use darkfi_sdk::{
};
use darkfi_serial::{deserialize, serialize};
use serde_json::json;
use signal_hook::consts::{SIGINT, SIGQUIT, SIGTERM};
use signal_hook_async_std::Signals;
use url::Url;
use super::Drk;
@@ -263,4 +266,83 @@ impl Drk {
let txid = serde_json::from_value(rep)?;
Ok(txid)
}
/// Queries darkfid for a block with given slot
async fn get_block_by_slot(&self, slot: u64) -> Result<Option<BlockInfo>> {
let req = JsonRequest::new("blockchain.get_slot", json!([slot]));
// This API is weird, we need some way of telling it's an empty slot and
// not an error
match self.rpc_client.request(req).await {
Ok(v) => {
let block_bytes: Vec<u8> = serde_json::from_value(v)?;
let block = deserialize(&block_bytes)?;
Ok(Some(block))
}
Err(_) => Ok(None),
}
}
/// Scans the blockchain optionally starting from the given slot for relevant
/// money transfer transactions. Alternatively it looks for a checkpoint in the
/// wallet to start scanning from.
pub async fn scan_blocks(&self, slot: Option<u64>) -> Result<()> {
let mut sl = if let Some(sl) = slot { sl } else { self.wallet_last_scanned_slot().await? };
let req = JsonRequest::new("blockchain.last_known_slot", json!([]));
let rep = self.rpc_client.request(req).await?;
let last: u64 = serde_json::from_value(rep)?;
eprintln!("Requested to scan from slot number: {}", sl);
eprintln!("Last known slot number reported by darkfid: {}", last);
// We set this up to handle an interrupt
let mut signals = Signals::new(&[SIGTERM, SIGINT, SIGQUIT])?;
let handle = signals.handle();
let (term_tx, _term_rx) = smol::channel::bounded::<()>(1);
let term_tx_ = term_tx.clone();
let signals_task = task::spawn(async move {
while let Some(signal) = signals.next().await {
match signal {
SIGTERM | SIGINT | SIGQUIT => term_tx_.close(),
_ => unreachable!(),
};
}
});
while !term_tx.is_closed() {
if sl == last {
term_tx.close();
break
}
sl += 1;
eprint!("Requesting slot {}... ", sl);
if let Some(block) = self.get_block_by_slot(sl).await? {
eprintln!("Found");
self.scan_block(&block).await?;
} else {
eprintln!("Not found");
}
// Write down the slot number into back to the wallet
// TODO: Why doesn't it work?
let query = format!(
"INSERT INTO {} ({}) VALUES (?1);",
MONEY_INFO_TABLE, MONEY_INFO_COL_LAST_SCANNED_SLOT
);
let params = json!([query, QueryType::Integer as u8, sl]);
let req = JsonRequest::new("wallet.exec_sql", params);
let _ = self.rpc_client.request(req).await?;
}
handle.close();
signals_task.await;
Ok(())
}
}

View File

@@ -25,9 +25,9 @@ use darkfi_money_contract::client::{
MONEY_COINS_COL_IS_SPENT, MONEY_COINS_COL_LEAF_POSITION, MONEY_COINS_COL_MEMO,
MONEY_COINS_COL_NULLIFIER, MONEY_COINS_COL_SECRET, MONEY_COINS_COL_SERIAL,
MONEY_COINS_COL_TOKEN_BLIND, MONEY_COINS_COL_TOKEN_ID, MONEY_COINS_COL_VALUE,
MONEY_COINS_COL_VALUE_BLIND, MONEY_COINS_TABLE, MONEY_KEYS_COL_IS_DEFAULT,
MONEY_KEYS_COL_PUBLIC, MONEY_KEYS_COL_SECRET, MONEY_KEYS_TABLE, MONEY_TREE_COL_TREE,
MONEY_TREE_TABLE,
MONEY_COINS_COL_VALUE_BLIND, MONEY_COINS_TABLE, MONEY_INFO_COL_LAST_SCANNED_SLOT,
MONEY_INFO_TABLE, MONEY_KEYS_COL_IS_DEFAULT, MONEY_KEYS_COL_PUBLIC, MONEY_KEYS_COL_SECRET,
MONEY_KEYS_TABLE, MONEY_TREE_COL_TREE, MONEY_TREE_TABLE,
};
use darkfi_sdk::{
crypto::{
@@ -84,6 +84,16 @@ impl Drk {
println!("Successfully initialized Merkle tree");
}
if let Err(_) = self.wallet_last_scanned_slot().await {
let query = format!(
"INSERT INTO {} ({}) VALUES (?1);",
MONEY_INFO_TABLE, MONEY_INFO_COL_LAST_SCANNED_SLOT
);
let params = json!([query, QueryType::Integer as u8, 0]);
let req = JsonRequest::new("wallet.exec_sql", params);
let _ = self.rpc_client.request(req).await?;
}
Ok(())
}
@@ -362,6 +372,18 @@ impl Drk {
Ok(tree)
}
/// Get the last scanned slot from the wallet
pub async fn wallet_last_scanned_slot(&self) -> Result<u64> {
let query =
format!("SELECT {} FROM {};", MONEY_INFO_COL_LAST_SCANNED_SLOT, MONEY_INFO_TABLE);
let params = json!([query, QueryType::Integer as u8, MONEY_INFO_COL_LAST_SCANNED_SLOT]);
let req = JsonRequest::new("wallet.query_row_single", params);
let rep = self.rpc_client.request(req).await?;
Ok(serde_json::from_value(rep[0].clone())?)
}
/// Mark a coin in the wallet as spent
pub async fn mark_spent_coin(&self, coin: &Coin) -> Result<()> {
let query = format!(