Sfoglia il codice sorgente

faucetd: Build money transfer transaction.

parazyd 3 anni fa
parent
commit
4d3b8a10b8
5 ha cambiato i file con 155 aggiunte e 64 eliminazioni
  1. 1 0
      Cargo.lock
  2. 1 0
      bin/faucetd/Cargo.toml
  3. 5 3
      bin/faucetd/src/error.rs
  4. 144 58
      bin/faucetd/src/main.rs
  5. 4 3
      src/contract/money/src/client.rs

+ 1 - 0
Cargo.lock

@@ -1941,6 +1941,7 @@ dependencies = [
  "chrono",
  "ctrlc",
  "darkfi",
+ "darkfi-money-contract",
  "darkfi-sdk",
  "darkfi-serial",
  "easy-parallel",

+ 1 - 0
bin/faucetd/Cargo.toml

@@ -18,6 +18,7 @@ ctrlc = { version = "3.2.3", features = ["termination"] }
 darkfi = {path = "../../", features = ["blockchain", "wallet", "rpc", "net", "zkas", "crypto"]}
 darkfi-serial = {path = "../../src/serial"}
 darkfi-sdk = {path = "../../src/sdk"}
+darkfi-money-contract = {path = "../../src/contract/money", features = ["no-entrypoint", "client"]}
 easy-parallel = "3.2.0"
 hex = "0.4.3"
 lazy-init = "0.5.1"

+ 5 - 3
bin/faucetd/src/error.rs

@@ -24,13 +24,15 @@ pub enum RpcError {
     AmountExceedsLimit = -32107,
     TimeLimitReached = -32108,
     ParseError = -32109,
+    InternalError = -32110,
 }
 
 fn to_tuple(e: RpcError) -> (i64, String) {
     let msg = match e {
-        RpcError::AmountExceedsLimit => "Amount requested is higher than the faucet limit.",
-        RpcError::TimeLimitReached => "Timeout not expired. Try again later.",
-        RpcError::ParseError => "Parse error.",
+        RpcError::AmountExceedsLimit => "Amount requested is higher than the faucet limit",
+        RpcError::TimeLimitReached => "Timeout not expired, try again later",
+        RpcError::ParseError => "Parse error",
+        RpcError::InternalError => "Internal error",
     };
 
     (e as i64, msg.to_string())

+ 144 - 58
bin/faucetd/src/main.rs

@@ -22,17 +22,24 @@ use async_std::sync::{Arc, Mutex, RwLock};
 use async_trait::async_trait;
 use chrono::Utc;
 use darkfi::{
-    crypto::proof::{ProvingKey, VerifyingKey},
+    crypto::proof::ProvingKey,
+    tx::Transaction,
     zk::{vm::ZkCircuit, vm_stack::empty_witnesses},
     zkas::ZkBinary,
 };
+use darkfi_money_contract::client::build_transfer_tx;
 use darkfi_sdk::{
-    crypto::{constants::MERKLE_DEPTH, ContractId, MerkleNode, PublicKey, TokenId},
+    crypto::{
+        constants::MERKLE_DEPTH, schnorr::SchnorrSecret, ContractId, Keypair, MerkleNode,
+        PublicKey, TokenId,
+    },
     incrementalmerkletree::bridgetree::BridgeTree,
     pasta::{group::ff::PrimeField, pallas},
+    tx::ContractCall,
 };
-use darkfi_serial::{deserialize, serialize};
+use darkfi_serial::{deserialize, serialize, Encodable};
 use log::{debug, error, info};
+use rand::rngs::OsRng;
 use serde_json::{json, Value};
 use sqlx::Row;
 use structopt_toml::{serde::Deserialize, structopt::StructOpt, StructOptToml};
@@ -74,31 +81,31 @@ use error::{server_error, RpcError};
 // care of it. The wallet's SQL schema comes from the money contract
 // and here we just hardcode it. There should be a nice way to parse
 // the schema and fill some map.
-const MONEY_INFO_TABLE: &str = "money_info";
-const MONEY_INFO_COL_LAST_SCANNED_SLOT: &str = "last_scanned_slot";
+//const MONEY_INFO_TABLE: &str = "money_info";
+//const MONEY_INFO_COL_LAST_SCANNED_SLOT: &str = "last_scanned_slot";
 
 const MONEY_TREE_TABLE: &str = "money_tree";
 const MONEY_TREE_COL_TREE: &str = "tree";
 
 const MONEY_KEYS_TABLE: &str = "money_keys";
-const MONEY_KEYS_COL_KEY_ID: &str = "key_id";
+//const MONEY_KEYS_COL_KEY_ID: &str = "key_id";
 const MONEY_KEYS_COL_IS_DEFAULT: &str = "is_default";
 const MONEY_KEYS_COL_PUBLIC: &str = "public";
 const MONEY_KEYS_COL_SECRET: &str = "secret";
 
-const MONEY_COINS_TABLE: &str = "money_coins";
-const MONEY_COINS_COL_COIN: &str = "coin";
-const MONEY_COINS_COL_IS_SPENT: &str = "is_spent";
-const MONEY_COINS_COL_SERIAL: &str = "serial";
-const MONEY_COINS_COL_VALUE: &str = "value";
-const MONEY_COINS_COL_TOKEN_ID: &str = "token_id";
-const MONEY_COINS_COL_COIN_BLIND: &str = "coin_blind";
-const MONEY_COINS_COL_VALUE_BLIND: &str = "value_blind";
-const MONEY_COINS_COL_TOKEN_BLIND: &str = "token_blind";
-const MONEY_COINS_COL_SECRET: &str = "secret";
-const MONEY_COINS_COL_NULLIFIER: &str = "nullifier";
-const MONEY_COINS_COL_LEAF_POSITION: &str = "leaf_position";
-const MONEY_COINS_COL_MEMO: &str = "memo";
+//const MONEY_COINS_TABLE: &str = "money_coins";
+//const MONEY_COINS_COL_COIN: &str = "coin";
+//const MONEY_COINS_COL_IS_SPENT: &str = "is_spent";
+//const MONEY_COINS_COL_SERIAL: &str = "serial";
+//const MONEY_COINS_COL_VALUE: &str = "value";
+//const MONEY_COINS_COL_TOKEN_ID: &str = "token_id";
+//const MONEY_COINS_COL_COIN_BLIND: &str = "coin_blind";
+//const MONEY_COINS_COL_VALUE_BLIND: &str = "value_blind";
+//const MONEY_COINS_COL_TOKEN_BLIND: &str = "token_blind";
+//const MONEY_COINS_COL_SECRET: &str = "secret";
+//const MONEY_COINS_COL_NULLIFIER: &str = "nullifier";
+//const MONEY_COINS_COL_LEAF_POSITION: &str = "leaf_position";
+//const MONEY_COINS_COL_MEMO: &str = "memo";
 
 const CONFIG_FILE: &str = "faucetd_config.toml";
 const CONFIG_FILE_CONTENTS: &str = include_str!("../faucetd_config.toml");
@@ -188,13 +195,13 @@ pub struct Faucetd {
     synced: Mutex<bool>, // AtomicBool is weird in Arc
     sync_p2p: P2pPtr,
     validator_state: ValidatorStatePtr,
-    wallet: WalletPtr,
+    keypair: Keypair,
+    _wallet: WalletPtr,
     merkle_tree: BridgeTree<MerkleNode, MERKLE_DEPTH>,
     airdrop_timeout: i64,
     airdrop_limit: u64,
     airdrop_map: Arc<Mutex<HashMap<[u8; 32], i64>>>,
     proving_keys: Arc<RwLock<HashMap<[u8; 32], Vec<(String, ProvingKey)>>>>,
-    verifying_keys: Arc<RwLock<HashMap<[u8; 32], Vec<(String, VerifyingKey)>>>>,
 }
 
 #[async_trait]
@@ -224,11 +231,9 @@ impl Faucetd {
         // Here we initialize the wallet for the money contract.
         let merkle_tree = Self::initialize_wallet(wallet.clone()).await?;
 
-        // This is kinda bad, but whatever. The hashmaps hold proving and verifying
-        // keys for various zk circuits. They should be periodically pruned because
-        // of memory issues.
+        // This is kinda bad, but whatever. The hashmaps hold proving keys for
+        // the money contract
         let proving_keys = Arc::new(RwLock::new(HashMap::new()));
-        let verifying_keys = Arc::new(RwLock::new(HashMap::new()));
 
         // For now we'll create the keys for the money contract
         // FIXME: This hardcoded shit (see consensus/state.rs)
@@ -255,11 +260,9 @@ impl Faucetd {
         let mint_circuit = ZkCircuit::new(mint_witnesses, mint_zkbin);
         let burn_circuit = ZkCircuit::new(burn_witnesses, burn_zkbin);
 
-        info!("Creating mint circuit proving and verifying keys");
+        info!("Creating mint circuit proving key");
         let mint_provingkey = ProvingKey::build(k, &mint_circuit);
-        let mint_verifyingkey = VerifyingKey::build(k, &mint_circuit);
-        info!("Creating burn circuit proving and verifying keys");
-        let burn_verifyingkey = VerifyingKey::build(k, &burn_circuit);
+        info!("Creating burn circuit proving key");
         let burn_provingkey = ProvingKey::build(k, &burn_circuit);
 
         {
@@ -271,26 +274,21 @@ impl Faucetd {
             proving_keys_w.insert(cid.inner().to_repr(), provingkeys);
         }
 
-        {
-            let verifyingkeys = vec![
-                (zkas_mint_ns.clone(), mint_verifyingkey),
-                (zkas_burn_ns.clone(), burn_verifyingkey),
-            ];
-            let mut verifying_keys_w = verifying_keys.write().await;
-            verifying_keys_w.insert(cid.inner().to_repr(), verifyingkeys);
-        }
+        // Get or create an initial keypair for signing transactions
+        let keypair = Self::initialize_keypair(wallet.clone()).await?;
+        info!("Faucet pubkey: {}", keypair.public);
 
         let faucetd = Self {
             synced: Mutex::new(false),
             sync_p2p,
             validator_state,
-            wallet,
+            keypair,
+            _wallet: wallet,
             merkle_tree,
             airdrop_timeout: timeout,
             airdrop_limit: limit,
             airdrop_map: Arc::new(Mutex::new(HashMap::new())),
             proving_keys,
-            verifying_keys,
         };
 
         Ok(faucetd)
@@ -330,6 +328,46 @@ impl Faucetd {
         Ok(merkle_tree)
     }
 
+    async fn initialize_keypair(wallet: WalletPtr) -> Result<Keypair> {
+        let mut conn = wallet.conn.acquire().await?;
+
+        let query = format!(
+            "SELECT {}, {} FROM {};",
+            MONEY_KEYS_COL_PUBLIC, MONEY_KEYS_COL_SECRET, MONEY_KEYS_TABLE
+        );
+        let keypair = match sqlx::query(&query).fetch_one(&mut conn).await {
+            Ok(row) => {
+                let public = deserialize(row.get(MONEY_KEYS_COL_PUBLIC))?;
+                let secret = deserialize(row.get(MONEY_KEYS_COL_SECRET))?;
+                Keypair { public, secret }
+            }
+            Err(_) => {
+                let keypair = Keypair::random(&mut OsRng);
+                let is_default = 0;
+                let public_bytes = serialize(&keypair.public);
+                let secret_bytes = serialize(&keypair.secret);
+                let query = format!(
+                    "INSERT INTO {} ({}, {}, {}) VALUES (?1, ?2, ?3)",
+                    MONEY_KEYS_TABLE,
+                    MONEY_KEYS_COL_IS_DEFAULT,
+                    MONEY_KEYS_COL_PUBLIC,
+                    MONEY_KEYS_COL_SECRET
+                );
+                sqlx::query(&query)
+                    .bind(is_default)
+                    .bind(public_bytes)
+                    .bind(secret_bytes)
+                    .execute(&mut conn)
+                    .await?;
+
+                info!("Wrote keypair to wallet");
+                keypair
+            }
+        };
+
+        Ok(keypair)
+    }
+
     // RPCAPI:
     // Processes an airdrop request and airdrops requested token and amount to address.
     // Returns the transaction ID upon success.
@@ -391,27 +429,77 @@ impl Faucetd {
         };
         drop(map);
 
-        // Create airdrop transaction
-
-        /*
-        let tx = match self
-            .client
-            .build_transaction(
-                pubkey,
-                amount,
-                token_id,
-                true,
-                self.validator_state.read().await.state_machine.clone(),
-            )
-            .await
-        {
+        // Get zk stuff for transaction
+        let cid = ContractId::from(pallas::Base::from(u64::MAX - 420));
+        let (mint_zkbin, mint_pk, burn_zkbin, burn_pk) = {
+            // FIXME: This hardcoded shit (see consensus/state.rs)
+            // FIXME: Unwraps, should also be solved when above is solved.
+            let zkas_tree = String::from("zkas");
+            let zkas_mint_ns = String::from("Mint");
+            let zkas_burn_ns = String::from("Burn");
+
+            // Do a lookup for the money contract's zkas database and fetch the circuits.
+            let blockchain = { self.validator_state.read().await.blockchain.clone() };
+            let db_handle =
+                blockchain.contracts.lookup(&blockchain.sled_db, &cid, &zkas_tree).unwrap();
+
+            // TODO: Handle possible panic of these Option unwraps
+            let mint_zkbin = db_handle.get(&serialize(&zkas_mint_ns)).unwrap().unwrap();
+            let burn_zkbin = db_handle.get(&serialize(&zkas_burn_ns)).unwrap().unwrap();
+
+            let mint_zkbin = ZkBinary::decode(&mint_zkbin).unwrap();
+            let burn_zkbin = ZkBinary::decode(&burn_zkbin).unwrap();
+
+            let proving_keys_r = self.proving_keys.read().await;
+            let (mint_pk, burn_pk) = match proving_keys_r.get(&cid.inner().to_repr()) {
+                Some(arr) => {
+                    let mint_pk = arr.iter().find(|x| x.0 == zkas_mint_ns).unwrap();
+                    let burn_pk = arr.iter().find(|x| x.0 == zkas_burn_ns).unwrap();
+                    (mint_pk.1.clone(), burn_pk.1.clone())
+                }
+                None => {
+                    todo!("Create proving keys");
+                }
+            };
+
+            (mint_zkbin, mint_pk, burn_zkbin, burn_pk)
+        };
+
+        // Create money contract params and proofs
+        let (params, proofs, secret_keys) = match build_transfer_tx(
+            &self.keypair,
+            &pubkey,
+            amount,
+            token_id,
+            &[],
+            &self.merkle_tree,
+            &mint_zkbin,
+            &mint_pk,
+            &burn_zkbin,
+            &burn_pk,
+            true,
+        ) {
             Ok(v) => v,
             Err(e) => {
-                error!("airdrop(): Failed building transaction: {}", e);
-                return JsonError::new(InternalError, None, id).into()
+                error!("Failed to build transfer tx params: {}", e);
+                return server_error(RpcError::InternalError, id)
             }
         };
 
+        // Build transaction
+        let calls = vec![ContractCall { contract_id: cid, data: serialize(&params) }];
+        let proofs = vec![proofs];
+        let mut signatures = vec![];
+        let mut encoded_tx = vec![];
+        calls.encode(&mut encoded_tx).unwrap();
+        proofs.encode(&mut encoded_tx).unwrap();
+        for secret in secret_keys {
+            let signature = secret.sign(&mut OsRng, &encoded_tx);
+            signatures.push(signature);
+        }
+
+        let tx = Transaction { calls, proofs, signatures: vec![signatures] };
+
         // Broadcast transaction to the network.
         match self.sync_p2p.broadcast(tx.clone()).await {
             Ok(()) => {}
@@ -420,15 +508,13 @@ impl Faucetd {
                 return JsonError::new(InternalError, None, id).into()
             }
         }
-        */
 
         // Add/Update this airdrop into the hashmap
         let mut map = self.airdrop_map.lock().await;
         map.insert(pubkey.to_bytes(), now);
         drop(map);
 
-        //let tx_hash = blake3::hash(&serialize(&tx)).to_hex().as_str().to_string();
-        let tx_hash = "foo";
+        let tx_hash = blake3::hash(&serialize(&tx)).to_hex().as_str().to_string();
         JsonResponse::new(json!(tx_hash), id).into()
     }
 }

+ 4 - 3
src/contract/money/src/client.rs

@@ -449,8 +449,9 @@ pub fn build_transfer_tx(
     keypair: &Keypair,
     pubkey: &PublicKey,
     value: u64,
-    coins: Vec<OwnCoin>,
-    tree: BridgeTree<MerkleNode, MERKLE_DEPTH>,
+    token_id: TokenId,
+    coins: &[OwnCoin],
+    tree: &BridgeTree<MerkleNode, MERKLE_DEPTH>,
     mint_zkbin: &ZkBinary,
     mint_pk: &ProvingKey,
     burn_zkbin: &ZkBinary,
@@ -462,7 +463,6 @@ pub fn build_transfer_tx(
     assert!(!coins.is_empty());
     // Ensure the coins given to us are all of the same token_id.
     // The money contract base transfer doesn't allow conversions.
-    let token_id = coins[0].note.token_id;
     for coin in coins.iter() {
         assert_eq!(token_id, coin.note.token_id);
     }
@@ -652,6 +652,7 @@ pub fn build_transfer_tx(
 
     // Now we should have all the params, zk proofs, and signature secrets.
     // We return it all and let the caller deal with it.
+    // TODO: Return also spent coins
 
     Ok((params, zk_proofs, signature_secrets))
 }