drk: add block height references to coins

This commit is contained in:
skoupidi
2025-06-07 14:19:53 +03:00
parent 34982f1555
commit b6032c2adc
3 changed files with 149 additions and 72 deletions

View File

@@ -12,7 +12,6 @@ CREATE TABLE IF NOT EXISTS BZHKGQ26bzmBithTQYTJtjo2QdCqpkR9tjSBopT4yf4o_money_ke
-- The coins we have the information to and can spend
CREATE TABLE IF NOT EXISTS BZHKGQ26bzmBithTQYTJtjo2QdCqpkR9tjSBopT4yf4o_money_coins (
coin BLOB PRIMARY KEY NOT NULL,
is_spent INTEGER NOT NULL,
value BLOB NOT NULL,
token_id BLOB NOT NULL,
spend_hook BLOB NOT NULL,
@@ -23,6 +22,9 @@ CREATE TABLE IF NOT EXISTS BZHKGQ26bzmBithTQYTJtjo2QdCqpkR9tjSBopT4yf4o_money_co
secret BLOB NOT NULL,
leaf_position BLOB NOT NULL,
memo BLOB,
creation_height INTEGER NOT NULL,
is_spent INTEGER NOT NULL,
spent_height INTEGER,
spent_tx_hash TEXT DEFAULT '-'
);

View File

@@ -876,12 +876,14 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
table.set_titles(row![
"Coin",
"Spent",
"Token ID",
"Aliases",
"Value",
"Spend Hook",
"User Data",
"Creation Height",
"Spent",
"Spent Height",
"Spent TX"
]);
for coin in coins {
@@ -904,11 +906,15 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
String::from("-")
};
let spent_height = match coin.3 {
Some(spent_height) => spent_height.to_string(),
None => String::from("-"),
};
table.add_row(row![
bs58::encode(&serialize_async(&coin.0.coin.inner()).await)
.into_string()
.to_string(),
coin.1,
coin.0.note.token_id,
aliases,
format!(
@@ -918,7 +924,10 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
),
spend_hook,
user_data,
coin.2
coin.1,
coin.2,
spent_height,
coin.4,
]);
}

View File

@@ -90,7 +90,6 @@ pub const MONEY_KEYS_COL_SECRET: &str = "secret";
// MONEY_COINS_TABLE
pub const MONEY_COINS_COL_COIN: &str = "coin";
pub const MONEY_COINS_COL_IS_SPENT: &str = "is_spent";
pub const MONEY_COINS_COL_VALUE: &str = "value";
pub const MONEY_COINS_COL_TOKEN_ID: &str = "token_id";
pub const MONEY_COINS_COL_SPEND_HOOK: &str = "spend_hook";
@@ -101,6 +100,9 @@ pub const MONEY_COINS_COL_TOKEN_BLIND: &str = "token_blind";
pub const MONEY_COINS_COL_SECRET: &str = "secret";
pub const MONEY_COINS_COL_LEAF_POSITION: &str = "leaf_position";
pub const MONEY_COINS_COL_MEMO: &str = "memo";
pub const MONEY_COINS_COL_CREATION_HEIGHT: &str = "creation_height";
pub const MONEY_COINS_COL_IS_SPENT: &str = "is_spent";
pub const MONEY_COINS_COL_SPENT_HEIGHT: &str = "spent_height";
pub const MONEY_COINS_COL_SPENT_TX_HASH: &str = "spent_tx_hash";
// MONEY_TOKENS_TABLE
@@ -354,8 +356,12 @@ impl Drk {
/// Fetch all coins and their metadata related to the Money contract from the wallet.
/// Optionally also fetch spent ones.
/// The boolean in the returned tuple notes if the coin was marked as spent.
pub async fn get_coins(&self, fetch_spent: bool) -> Result<Vec<(OwnCoin, bool, String)>> {
/// The boolean in the returned tuple notes if the coin was marked
/// as spent, along with the height and tx it was spent in.
pub async fn get_coins(
&self,
fetch_spent: bool,
) -> Result<Vec<(OwnCoin, u32, bool, Option<u32>, String)>> {
let query = if fetch_spent {
self.wallet.query_multiple(&MONEY_COINS_TABLE, &[], &[])
} else {
@@ -388,7 +394,11 @@ impl Drk {
let query = self.wallet.query_multiple(
&MONEY_COINS_TABLE,
&[],
convert_named_params! {(MONEY_COINS_COL_IS_SPENT, false), (MONEY_COINS_COL_TOKEN_ID, serialize_async(token_id).await), (MONEY_COINS_COL_SPEND_HOOK, serialize_async(&FuncId::none()).await)},
convert_named_params! {
(MONEY_COINS_COL_TOKEN_ID, serialize_async(token_id).await),
(MONEY_COINS_COL_SPEND_HOOK, serialize_async(&FuncId::none()).await),
(MONEY_COINS_COL_IS_SPENT, false),
},
);
let rows = match query {
@@ -418,7 +428,12 @@ impl Drk {
let query = self.wallet.query_multiple(
&MONEY_COINS_TABLE,
&[],
convert_named_params! {(MONEY_COINS_COL_IS_SPENT, false), (MONEY_COINS_COL_TOKEN_ID, serialize_async(token_id).await), (MONEY_COINS_COL_SPEND_HOOK, serialize_async(spend_hook).await), (MONEY_COINS_COL_USER_DATA, serialize_async(user_data).await)},
convert_named_params! {
(MONEY_COINS_COL_TOKEN_ID, serialize_async(token_id).await),
(MONEY_COINS_COL_SPEND_HOOK, serialize_async(spend_hook).await),
(MONEY_COINS_COL_USER_DATA, serialize_async(user_data).await),
(MONEY_COINS_COL_IS_SPENT, false),
},
);
let rows = match query {
@@ -439,14 +454,74 @@ impl Drk {
}
/// Auxiliary function to parse a `MONEY_COINS_TABLE` record.
/// The boolean in the returned tuple notes if the coin was marked as spent.
async fn parse_coin_record(&self, row: &[Value]) -> Result<(OwnCoin, bool, String)> {
/// The boolean in the returned tuple notes if the coin was marked
/// as spent, along with the height and tx it was spent in.
async fn parse_coin_record(
&self,
row: &[Value],
) -> Result<(OwnCoin, u32, bool, Option<u32>, String)> {
let Value::Blob(ref coin_bytes) = row[0] else {
return Err(Error::ParseFailed("[parse_coin_record] Coin bytes parsing failed"))
};
let coin: Coin = deserialize_async(coin_bytes).await?;
let Value::Integer(is_spent) = row[1] else {
let Value::Blob(ref value_bytes) = row[1] else {
return Err(Error::ParseFailed("[parse_coin_record] Value bytes parsing failed"))
};
let value: u64 = deserialize_async(value_bytes).await?;
let Value::Blob(ref token_id_bytes) = row[2] else {
return Err(Error::ParseFailed("[parse_coin_record] Token ID bytes parsing failed"))
};
let token_id: TokenId = deserialize_async(token_id_bytes).await?;
let Value::Blob(ref spend_hook_bytes) = row[3] else {
return Err(Error::ParseFailed("[parse_coin_record] Spend hook bytes parsing failed"))
};
let spend_hook: pallas::Base = deserialize_async(spend_hook_bytes).await?;
let Value::Blob(ref user_data_bytes) = row[4] else {
return Err(Error::ParseFailed("[parse_coin_record] User data bytes parsing failed"))
};
let user_data: pallas::Base = deserialize_async(user_data_bytes).await?;
let Value::Blob(ref coin_blind_bytes) = row[5] else {
return Err(Error::ParseFailed("[parse_coin_record] Coin blind bytes parsing failed"))
};
let coin_blind: BaseBlind = deserialize_async(coin_blind_bytes).await?;
let Value::Blob(ref value_blind_bytes) = row[6] else {
return Err(Error::ParseFailed("[parse_coin_record] Value blind bytes parsing failed"))
};
let value_blind: ScalarBlind = deserialize_async(value_blind_bytes).await?;
let Value::Blob(ref token_blind_bytes) = row[7] else {
return Err(Error::ParseFailed("[parse_coin_record] Token blind bytes parsing failed"))
};
let token_blind: BaseBlind = deserialize_async(token_blind_bytes).await?;
let Value::Blob(ref secret_bytes) = row[8] else {
return Err(Error::ParseFailed("[parse_coin_record] Secret bytes parsing failed"))
};
let secret: SecretKey = deserialize_async(secret_bytes).await?;
let Value::Blob(ref leaf_position_bytes) = row[9] else {
return Err(Error::ParseFailed("[parse_coin_record] Leaf position bytes parsing failed"))
};
let leaf_position: bridgetree::Position = deserialize_async(leaf_position_bytes).await?;
let Value::Blob(ref memo) = row[10] else {
return Err(Error::ParseFailed("[parse_coin_record] Memo parsing failed"))
};
let Value::Integer(creation_height) = row[11] else {
return Err(Error::ParseFailed("[parse_coin_record] Creation height parsing failed"))
};
let Ok(creation_height) = u32::try_from(creation_height) else {
return Err(Error::ParseFailed("[parse_coin_record] Creation height parsing failed"))
};
let Value::Integer(is_spent) = row[12] else {
return Err(Error::ParseFailed("[parse_coin_record] Is spent parsing failed"))
};
let Ok(is_spent) = u64::try_from(is_spent) else {
@@ -454,56 +529,20 @@ impl Drk {
};
let is_spent = is_spent > 0;
let Value::Blob(ref value_bytes) = row[2] else {
return Err(Error::ParseFailed("[parse_coin_record] Value bytes parsing failed"))
};
let value: u64 = deserialize_async(value_bytes).await?;
let Value::Blob(ref token_id_bytes) = row[3] else {
return Err(Error::ParseFailed("[parse_coin_record] Token ID bytes parsing failed"))
};
let token_id: TokenId = deserialize_async(token_id_bytes).await?;
let Value::Blob(ref spend_hook_bytes) = row[4] else {
return Err(Error::ParseFailed("[parse_coin_record] Spend hook bytes parsing failed"))
};
let spend_hook: pallas::Base = deserialize_async(spend_hook_bytes).await?;
let Value::Blob(ref user_data_bytes) = row[5] else {
return Err(Error::ParseFailed("[parse_coin_record] User data bytes parsing failed"))
};
let user_data: pallas::Base = deserialize_async(user_data_bytes).await?;
let Value::Blob(ref coin_blind_bytes) = row[6] else {
return Err(Error::ParseFailed("[parse_coin_record] Coin blind bytes parsing failed"))
};
let coin_blind: BaseBlind = deserialize_async(coin_blind_bytes).await?;
let Value::Blob(ref value_blind_bytes) = row[7] else {
return Err(Error::ParseFailed("[parse_coin_record] Value blind bytes parsing failed"))
};
let value_blind: ScalarBlind = deserialize_async(value_blind_bytes).await?;
let Value::Blob(ref token_blind_bytes) = row[8] else {
return Err(Error::ParseFailed("[parse_coin_record] Token blind bytes parsing failed"))
};
let token_blind: BaseBlind = deserialize_async(token_blind_bytes).await?;
let Value::Blob(ref secret_bytes) = row[9] else {
return Err(Error::ParseFailed("[parse_coin_record] Secret bytes parsing failed"))
};
let secret: SecretKey = deserialize_async(secret_bytes).await?;
let Value::Blob(ref leaf_position_bytes) = row[10] else {
return Err(Error::ParseFailed("[parse_coin_record] Leaf position bytes parsing failed"))
};
let leaf_position: bridgetree::Position = deserialize_async(leaf_position_bytes).await?;
let Value::Blob(ref memo) = row[11] else {
return Err(Error::ParseFailed("[parse_coin_record] Memo parsing failed"))
let spent_height = match row[13] {
Value::Integer(spent_height) => {
let Ok(spent_height) = u32::try_from(spent_height) else {
return Err(Error::ParseFailed(
"[parse_coin_record] Spent height parsing failed",
))
};
Some(spent_height)
}
Value::Null => None,
_ => return Err(Error::ParseFailed("[parse_coin_record] Spent height parsing failed")),
};
let Value::Text(ref spent_tx_hash) = row[12] else {
let Value::Text(ref spent_tx_hash) = row[14] else {
return Err(Error::ParseFailed(
"[parse_coin_record] Spent transaction hash parsing failed",
))
@@ -520,7 +559,13 @@ impl Drk {
memo: memo.clone(),
};
Ok((OwnCoin { coin, note, secret, leaf_position }, is_spent, spent_tx_hash.clone()))
Ok((
OwnCoin { coin, note, secret, leaf_position },
creation_height,
is_spent,
spent_height,
spent_tx_hash.clone(),
))
}
/// Create an alias record for provided Token ID.
@@ -607,16 +652,18 @@ impl Drk {
/// Mark a given coin in the wallet as unspent.
pub async fn unspend_coin(&self, coin: &Coin) -> WalletDbResult<()> {
let is_spend = 0;
let spent_height: Option<u32> = None;
let query = format!(
"UPDATE {} SET {} = ?1, {} = ?2 WHERE {} = ?3;",
"UPDATE {} SET {} = ?1, {} = ?2, {} = ?3 WHERE {} = ?4;",
*MONEY_COINS_TABLE,
MONEY_COINS_COL_IS_SPENT,
MONEY_COINS_COL_SPENT_HEIGHT,
MONEY_COINS_COL_SPENT_TX_HASH,
MONEY_COINS_COL_COIN
);
self.wallet.exec_sql(
&query,
rusqlite::params![is_spend, "-", serialize_async(&coin.inner()).await],
rusqlite::params![is_spend, spent_height, "-", serialize_async(&coin.inner()).await],
)
}
@@ -758,6 +805,7 @@ impl Drk {
&self,
owncoins_nullifiers: &mut BTreeMap<[u8; 32], [u8; 32]>,
coins: &[OwnCoin],
creation_height: &u32,
) -> Result<()> {
println!("Found {} OwnCoin(s) in transaction", coins.len());
@@ -768,10 +816,9 @@ impl Drk {
// This is the SQL query we'll be executing to insert new coins into the wallet
let query = format!(
"INSERT INTO {} ({}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12);",
"INSERT INTO {} ({}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14);",
*MONEY_COINS_TABLE,
MONEY_COINS_COL_COIN,
MONEY_COINS_COL_IS_SPENT,
MONEY_COINS_COL_VALUE,
MONEY_COINS_COL_TOKEN_ID,
MONEY_COINS_COL_SPEND_HOOK,
@@ -782,9 +829,13 @@ impl Drk {
MONEY_COINS_COL_SECRET,
MONEY_COINS_COL_LEAF_POSITION,
MONEY_COINS_COL_MEMO,
MONEY_COINS_COL_CREATION_HEIGHT,
MONEY_COINS_COL_IS_SPENT,
MONEY_COINS_COL_SPENT_HEIGHT,
);
// Handle our own coins
let spent_height: Option<u32> = None;
for coin in coins {
println!("OwnCoin: {:?}", coin.coin);
// Grab coin record key
@@ -796,7 +847,6 @@ impl Drk {
// Execute the query
let params = rusqlite::params![
key,
0, // <-- is_spent
serialize_async(&coin.note.value).await,
serialize_async(&coin.note.token_id).await,
serialize_async(&coin.note.spend_hook).await,
@@ -807,6 +857,9 @@ impl Drk {
serialize_async(&coin.secret).await,
serialize_async(&coin.leaf_position).await,
serialize_async(&coin.note.memo).await,
creation_height,
0, // <-- is_spent
spent_height,
];
if let Err(e) = self.wallet.exec_sql(&query, params) {
@@ -899,11 +952,20 @@ impl Drk {
self.smt_insert(&mut scan_cache.money_smt, &nullifiers)?;
// Check if we have any spent coins
let wallet_spent_coins =
self.mark_spent_coins(&scan_cache.owncoins_nullifiers, &nullifiers, tx_hash)?;
let wallet_spent_coins = self.mark_spent_coins(
&scan_cache.owncoins_nullifiers,
&nullifiers,
&Some(*block_height),
tx_hash,
)?;
// Handle our own coins
self.handle_money_call_owncoins(&mut scan_cache.owncoins_nullifiers, &owncoins).await?;
self.handle_money_call_owncoins(
&mut scan_cache.owncoins_nullifiers,
&owncoins,
block_height,
)
.await?;
// Handle freezes
let wallet_freezes =
@@ -963,7 +1025,7 @@ impl Drk {
println!("[mark_tx_spend] Found Money contract in call {i}");
let nullifiers = self.money_call_nullifiers(call).await?;
self.mark_spent_coins(&owncoins_nullifiers, &nullifiers, &tx_hash)?;
self.mark_spent_coins(&owncoins_nullifiers, &nullifiers, &None, &tx_hash)?;
}
Ok(())
@@ -975,6 +1037,7 @@ impl Drk {
&self,
owncoins_nullifiers: &BTreeMap<[u8; 32], [u8; 32]>,
nullifiers: &[Nullifier],
spent_height: &Option<u32>,
spent_tx_hash: &String,
) -> Result<bool> {
if nullifiers.is_empty() {
@@ -994,9 +1057,10 @@ impl Drk {
// Create an SQL `UPDATE` query to mark rows as spent(1)
let query = format!(
"UPDATE {} SET {} = 1, {} = ?1 WHERE {} = ?2;",
"UPDATE {} SET {} = 1, {} = ?1, {} = ?2 WHERE {} = ?3;",
*MONEY_COINS_TABLE,
MONEY_COINS_COL_IS_SPENT,
MONEY_COINS_COL_SPENT_HEIGHT,
MONEY_COINS_COL_SPENT_TX_HASH,
MONEY_COINS_COL_COIN
);
@@ -1004,7 +1068,9 @@ impl Drk {
// Mark spent own coins
for ownoin in spent_owncoins {
// Execute the query
if let Err(e) = self.wallet.exec_sql(&query, rusqlite::params![spent_tx_hash, ownoin]) {
if let Err(e) =
self.wallet.exec_sql(&query, rusqlite::params![spent_height, spent_tx_hash, ownoin])
{
return Err(Error::DatabaseError(format!(
"[mark_spent_coins] Marking spent coin failed: {e:?}"
)))