Selaa lähdekoodia

drk: add block height references to coins

skoupidi 1 vuosi sitten
vanhempi
sitoutus
b6032c2adc
3 muutettua tiedostoa jossa 119 lisäystä ja 42 poistoa
  1. 3 1
      bin/drk/money.sql
  2. 12 3
      bin/drk/src/main.rs
  3. 104 38
      bin/drk/src/money.rs

+ 3 - 1
bin/drk/money.sql

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

+ 12 - 3
bin/drk/src/main.rs

@@ -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,
                     ]);
                 }
 

+ 104 - 38
bin/drk/src/money.rs

@@ -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,71 +454,95 @@ 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 {
-            return Err(Error::ParseFailed("[parse_coin_record] Is spent parsing failed"))
-        };
-        let Ok(is_spent) = u64::try_from(is_spent) else {
-            return Err(Error::ParseFailed("[parse_coin_record] Is spent parsing failed"))
-        };
-        let is_spent = is_spent > 0;
-
-        let Value::Blob(ref value_bytes) = row[2] 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[3] else {
+        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[4] else {
+        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[5] else {
+        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[6] else {
+        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[7] else {
+        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[8] else {
+        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[9] else {
+        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[10] else {
+        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[11] else {
+        let Value::Blob(ref memo) = row[10] else {
             return Err(Error::ParseFailed("[parse_coin_record] Memo parsing failed"))
         };
 
-        let Value::Text(ref spent_tx_hash) = row[12] else {
+        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 {
+            return Err(Error::ParseFailed("[parse_coin_record] Is spent parsing failed"))
+        };
+        let is_spent = is_spent > 0;
+
+        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[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:?}"
                 )))