Преглед изворни кода

drk: Working block subscription and wallet inserts.

parazyd пре 3 година
родитељ
комит
2bcbf955f1
5 измењених фајлова са 258 додато и 10 уклоњено
  1. 1 0
      Cargo.lock
  2. 2 1
      bin/drk/Cargo.toml
  3. 36 4
      bin/drk/src/main.rs
  4. 182 4
      bin/drk/src/rpc_blockchain.rs
  5. 37 1
      bin/drk/src/rpc_wallet.rs

+ 1 - 0
Cargo.lock

@@ -1749,6 +1749,7 @@ version = "0.3.0"
 dependencies = [
  "anyhow",
  "async-std",
+ "bs58",
  "clap 4.0.25",
  "darkfi",
  "darkfi-money-contract",

+ 2 - 1
bin/drk/Cargo.toml

@@ -11,8 +11,9 @@ edition = "2021"
 [dependencies]
 anyhow = "1.0.66"
 async-std = {version = "1.12.0", features = ["attributes"]}
+bs58 = "0.4.0"
 clap = {version = "4.0.25", features = ["derive"]}
-darkfi = {path = "../../", features = ["rpc", "util", "wallet"]}
+darkfi = {path = "../../", features = ["blockchain", "rpc", "util", "wallet"]}
 darkfi-sdk = {path = "../../src/sdk"}
 darkfi-serial = {path = "../../src/serial", features = ["derive", "crypto"]}
 darkfi-money-contract = {path = "../../src/contract/money", features = ["no-entrypoint", "client"]}

+ 36 - 4
bin/drk/src/main.rs

@@ -77,6 +77,14 @@ enum Subcmd {
         #[arg(long)]
         /// Get the default address in the wallet
         address: bool,
+
+        #[arg(long)]
+        /// Print all the secret keys from the wallet
+        secrets: bool,
+
+        #[arg(long)]
+        /// Print the Merkle tree in the wallet
+        tree: bool,
     },
 
     /// Airdrop some tokens
@@ -141,8 +149,8 @@ async fn main() -> Result<()> {
             Ok(())
         }
 
-        Subcmd::Wallet { initialize, keygen, balance, address } => {
-            if !initialize && !keygen && !balance && !address {
+        Subcmd::Wallet { initialize, keygen, balance, address, secrets, tree } => {
+            if !initialize && !keygen && !balance && !address && !secrets && !tree {
                 eprintln!("Error: You must use at least one flag for this subcommand");
                 eprintln!("Run with \"wallet -h\" to see the subcommand usage.");
                 exit(2);
@@ -180,6 +188,28 @@ async fn main() -> Result<()> {
                 return Ok(())
             }
 
+            if secrets {
+                let v =
+                    drk.wallet_secrets().await.with_context(|| "Failed to fetch wallet secrets")?;
+
+                drk.rpc_client.close().await?;
+
+                for i in v {
+                    println!("{}", i);
+                }
+
+                return Ok(())
+            }
+
+            if tree {
+                let v = drk.wallet_tree().await.with_context(|| "Failed to fetch Merkle tree")?;
+                drk.rpc_client.close().await?;
+
+                println!("{:#?}", v);
+
+                return Ok(())
+            }
+
             unreachable!()
         }
 
@@ -210,13 +240,15 @@ async fn main() -> Result<()> {
         }
 
         Subcmd::Subscribe => {
-            let rpc_client = RpcClient::new(args.endpoint)
+            let rpc_client = RpcClient::new(args.endpoint.clone())
                 .await
                 .with_context(|| "Could not connect to darkfid RPC endpoint")?;
 
             let drk = Drk { rpc_client };
 
-            drk.subscribe_blocks().await.with_context(|| "Block subscription failed")?;
+            drk.subscribe_blocks(args.endpoint)
+                .await
+                .with_context(|| "Block subscription failed")?;
 
             Ok(())
         }

+ 182 - 4
bin/drk/src/rpc_blockchain.rs

@@ -17,11 +17,35 @@
  */
 
 use anyhow::{anyhow, Result};
+use async_std::task;
 use darkfi::{
-    rpc::jsonrpc::{JsonRequest, JsonResult},
+    consensus::BlockInfo,
+    rpc::{
+        client::RpcClient,
+        jsonrpc::{JsonRequest, JsonResult},
+    },
     system::Subscriber,
+    wallet::walletdb::QueryType,
 };
+use darkfi_money_contract::{
+    client::{
+        Coin, EncryptedNote, OwnCoin, MONEY_COINS_COL_COIN, MONEY_COINS_COL_COIN_BLIND,
+        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_TREE_COL_TREE, MONEY_TREE_TABLE,
+    },
+    state::{MoneyTransferParams, Output},
+    MoneyFunction,
+};
+use darkfi_sdk::{
+    crypto::{poseidon_hash, ContractId, MerkleNode, Nullifier},
+    incrementalmerkletree::Tree,
+    pasta::pallas,
+};
+use darkfi_serial::{deserialize, serialize};
 use serde_json::json;
+use url::Url;
 
 use super::Drk;
 
@@ -31,18 +55,41 @@ impl Drk {
     /// scanned and we check if any of them call the money contract, and if
     /// the payments are intended for us. If so, we decrypt them and append
     /// the metadata to our wallet.
-    pub async fn subscribe_blocks(&self) -> Result<()> {
+    pub async fn subscribe_blocks(&self, endpoint: Url) -> Result<()> {
         eprintln!("Subscribing to receive notifications of incoming blocks");
         let subscriber = Subscriber::new();
         let subscription = subscriber.clone().subscribe().await;
 
+        let rpc_client = RpcClient::new(endpoint).await?;
+
         let req = JsonRequest::new("blockchain.subscribe_blocks", json!([]));
-        self.rpc_client.subscribe(req, subscriber).await?;
+        task::spawn(async move { rpc_client.subscribe(req, subscriber).await.unwrap() });
+        eprintln!("Detached subscription to background");
 
         let e = loop {
             match subscription.receive().await {
                 JsonResult::Notification(n) => {
-                    println!("Got Block notification: {:?}", n);
+                    eprintln!("Got Block notification from darkfid subscription");
+                    if n.method != "blockchain.subscribe_blocks" {
+                        break anyhow!("Got foreign notification from darkfid: {}", n.method)
+                    }
+
+                    let Some(params) = n.params.as_array() else {
+                        break anyhow!("Received notification params are not an array")
+                    };
+
+                    if params.len() != 1 {
+                        break anyhow!("Notification parameters are not len 1")
+                    }
+
+                    let params = n.params.as_array().unwrap()[0].as_str().unwrap();
+                    let bytes = bs58::decode(params).into_vec()?;
+
+                    let block_data: BlockInfo = deserialize(&bytes)?;
+
+                    // TODO: FIXME: Disallow this if last_scanned_slot is not this-1 or something
+                    eprintln!("Deserialized successfully. Scanning block...");
+                    self.scan_block(&block_data).await?;
                 }
 
                 JsonResult::Error(e) => {
@@ -59,4 +106,135 @@ impl Drk {
 
         Err(e)
     }
+
+    /// `scan_block` will go over transactions in a block and fetch the ones dealing
+    /// with the money contract. Then over all of them, try to see if any are related
+    /// to us. If any are found, the metadata is extracted and placed into the wallet
+    /// for future use.
+    async fn scan_block(&self, block: &BlockInfo) -> Result<()> {
+        eprintln!("Iterating over {} transactions", block.txs.len());
+
+        let mut outputs: Vec<Output> = vec![];
+
+        let mf = MoneyFunction::Transfer as u8;
+
+        // TODO: FIXME: This shouldn't be hardcoded here obviously.
+        let contract_id = ContractId::from(pallas::Base::from(u64::MAX - 420));
+
+        for (i, tx) in block.txs.iter().enumerate() {
+            for (j, call) in tx.calls.iter().enumerate() {
+                if call.contract_id == contract_id && call.data[0] == mf {
+                    eprintln!("Found money transfer in call {} in tx {}", j, i);
+                    let params: MoneyTransferParams = deserialize(&call.data[1..])?;
+                    for output in params.outputs {
+                        outputs.push(output);
+                    }
+                }
+            }
+        }
+
+        // Fetch our secret keys from the wallet
+        eprintln!("Fetching secret keys from wallet");
+        let secrets = self.wallet_secrets().await?;
+        if secrets.is_empty() {
+            eprintln!("Warning: No secrets found in wallet");
+        }
+
+        eprintln!("Fetching Merkle tree from wallet");
+        let mut tree = self.wallet_tree().await?;
+
+        let mut owncoins = vec![];
+
+        for output in outputs {
+            // Append the new coin to the Merkle tree. Every coin has to be added.
+            let coin = output.coin;
+            tree.append(&MerkleNode::from(coin));
+
+            // Attempt to decrypt the note
+            let enc_note =
+                EncryptedNote { ciphertext: output.ciphertext, ephem_public: output.ephem_public };
+
+            for secret in &secrets {
+                if let Ok(note) = enc_note.decrypt(secret) {
+                    eprintln!("Successfully decrypted a note");
+                    eprintln!("Witnessing coin in Merkle tree");
+                    let leaf_position = tree.witness().unwrap();
+
+                    let owncoin = OwnCoin {
+                        coin: Coin::from(coin),
+                        note: note.clone(),
+                        secret: *secret,
+                        nullifier: Nullifier::from(poseidon_hash([secret.inner(), note.serial])),
+                        leaf_position,
+                    };
+
+                    owncoins.push(owncoin);
+                }
+            }
+        }
+
+        eprintln!("Serializing the Merkle tree into the wallet");
+        let query =
+            format!("INSERT INTO {} ({}) VALUES (?1)", MONEY_TREE_TABLE, MONEY_TREE_COL_TREE);
+        let params = json!([query, QueryType::Blob as u8, serialize(&tree)]);
+        let req = JsonRequest::new("wallet.exec_sql", params);
+        self.rpc_client.request(req).await?;
+        eprintln!("Merkle tree written successfully");
+
+        // This is the SQL query we'll be executing to insert coins into the wallet
+        let query = format!(
+            "INSERT INTO {} ({}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12);",
+            MONEY_COINS_TABLE,
+            MONEY_COINS_COL_COIN,
+            MONEY_COINS_COL_IS_SPENT,
+            MONEY_COINS_COL_SERIAL,
+            MONEY_COINS_COL_VALUE,
+            MONEY_COINS_COL_TOKEN_ID,
+            MONEY_COINS_COL_COIN_BLIND,
+            MONEY_COINS_COL_VALUE_BLIND,
+            MONEY_COINS_COL_TOKEN_BLIND,
+            MONEY_COINS_COL_SECRET,
+            MONEY_COINS_COL_NULLIFIER,
+            MONEY_COINS_COL_LEAF_POSITION,
+            MONEY_COINS_COL_MEMO,
+        );
+
+        eprintln!("Found {} OwnCoin(s) in block", owncoins.len());
+        for owncoin in owncoins {
+            let params = json!([
+                query,
+                QueryType::Blob as u8,
+                serialize(&owncoin.coin),
+                QueryType::Integer as u8,
+                0, // <-- is_spent
+                QueryType::Blob as u8,
+                serialize(&owncoin.note.serial),
+                QueryType::Blob as u8,
+                serialize(&owncoin.note.value),
+                QueryType::Blob as u8,
+                serialize(&owncoin.note.token_id),
+                QueryType::Blob as u8,
+                serialize(&owncoin.note.coin_blind),
+                QueryType::Blob as u8,
+                serialize(&owncoin.note.value_blind),
+                QueryType::Blob as u8,
+                serialize(&owncoin.note.token_blind),
+                QueryType::Blob as u8,
+                serialize(&owncoin.secret),
+                QueryType::Blob as u8,
+                serialize(&owncoin.nullifier),
+                QueryType::Blob as u8,
+                serialize(&owncoin.leaf_position),
+                QueryType::Blob as u8,
+                serialize(&owncoin.note.memo),
+            ]);
+
+            eprintln!("Executing JSON-RPC request to add OwnCoin to wallet");
+            let req = JsonRequest::new("wallet.exec_sql", params);
+            self.rpc_client.request(req).await?;
+            eprintln!("Coin added successfully");
+        }
+
+        Ok(())
+    }
 }

+ 37 - 1
bin/drk/src/rpc_wallet.rs

@@ -26,7 +26,7 @@ use darkfi_money_contract::client::{
     MONEY_TREE_COL_TREE, MONEY_TREE_TABLE,
 };
 use darkfi_sdk::{
-    crypto::{constants::MERKLE_DEPTH, Keypair, MerkleNode, PublicKey, TokenId},
+    crypto::{constants::MERKLE_DEPTH, Keypair, MerkleNode, PublicKey, SecretKey, TokenId},
     incrementalmerkletree::bridgetree::BridgeTree,
 };
 use darkfi_serial::{deserialize, serialize};
@@ -224,4 +224,40 @@ impl Drk {
 
         Ok(public_key)
     }
+
+    /// Fetch secret keys from the wallet and return them if found.
+    pub async fn wallet_secrets(&self) -> Result<Vec<SecretKey>> {
+        let query = format!("SELECT {} FROM {};", MONEY_KEYS_COL_SECRET, MONEY_KEYS_TABLE);
+        let params = json!([query, QueryType::Blob as u8, MONEY_KEYS_COL_SECRET]);
+        let req = JsonRequest::new("wallet.query_row_multi", params);
+        let rep = self.rpc_client.request(req).await?;
+
+        // The returned thing should be an array of found rows.
+        let Some(rows) = rep.as_array() else {
+            return Err(anyhow!("Unexpected response from darkfid: {}", rep))
+        };
+
+        let mut secrets = vec![];
+
+        // Let's scan through the rows and see if we got anything.
+        for row in rows {
+            let secret_bytes: Vec<u8> = serde_json::from_value(row[0].clone())?;
+            let secret: SecretKey = deserialize(&secret_bytes)?;
+            secrets.push(secret);
+        }
+
+        Ok(secrets)
+    }
+
+    /// Get the Merkle tree from the wallet
+    pub async fn wallet_tree(&self) -> Result<BridgeTree<MerkleNode, MERKLE_DEPTH>> {
+        let query = format!("SELECT * FROM {}", MONEY_TREE_TABLE);
+        let params = json!([query, QueryType::Blob as u8, MONEY_TREE_COL_TREE]);
+        let req = JsonRequest::new("wallet.query_row_single", params);
+        let rep = self.rpc_client.request(req).await?;
+
+        let tree_bytes: Vec<u8> = serde_json::from_value(rep[0].clone())?;
+        let tree = deserialize(&tree_bytes)?;
+        Ok(tree)
+    }
 }