Просмотр исходного кода

Faucet airdrop implementations and interaction with drk.

Transaction verification from state.rs still has some issue on wasm execution.
A proper verification API has to be written next to simplify things, avoid
convoluted code repetition, and have less places where things can go wrong.
parazyd 3 лет назад
Родитель
Сommit
e8ebb9d240

+ 57 - 5
bin/drk/src/main.rs

@@ -16,10 +16,11 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use std::{process::exit, time::Instant};
+use std::{process::exit, str::FromStr, time::Instant};
 
 use anyhow::{Context, Result};
 use clap::{Parser, Subcommand};
+use darkfi_sdk::crypto::{PublicKey, TokenId};
 use serde_json::json;
 use simplelog::{ColorChoice, TermLogger, TerminalMode};
 use url::Url;
@@ -30,6 +31,9 @@ use darkfi::{
     util::cli::{get_log_config, get_log_level},
 };
 
+/// Airdrop methods
+mod rpc_airdrop;
+
 /// Wallet operation methods for darkfid's JSON-RPC
 mod rpc_wallet;
 
@@ -71,6 +75,22 @@ enum Subcmd {
         /// Get the default address in the wallet
         address: bool,
     },
+
+    /// Airdrop some tokens
+    Airdrop {
+        /// Faucet JSON-RPC endpoint
+        #[arg(short, long, default_value = "tcp://127.0.0.1:8340")]
+        faucet_endpoint: Url,
+
+        /// Amount to request from the faucet
+        amount: String,
+
+        /// Token ID to request from the faucet
+        token: String,
+
+        /// Optional address to send tokens to (defaults to main address in wallet)
+        address: Option<String>,
+    },
 }
 
 pub struct Drk {
@@ -103,10 +123,10 @@ async fn main() -> Result<()> {
         Subcmd::Ping => {
             let rpc_client = RpcClient::new(args.endpoint)
                 .await
-                .with_context(|| "Could not connect to RPC endpoint")?;
+                .with_context(|| "Could not connect to darkfid RPC endpoint")?;
 
             let drk = Drk { rpc_client };
-            drk.ping().await.with_context(|| "Failed to ping RPC endpoint")?;
+            drk.ping().await.with_context(|| "Failed to ping darkfid RPC endpoint")?;
             Ok(())
         }
 
@@ -119,7 +139,7 @@ async fn main() -> Result<()> {
 
             let rpc_client = RpcClient::new(args.endpoint)
                 .await
-                .with_context(|| "Could not connect to RPC endpoint")?;
+                .with_context(|| "Could not connect to darkfid RPC endpoint")?;
 
             let drk = Drk { rpc_client };
 
@@ -139,11 +159,43 @@ async fn main() -> Result<()> {
             }
 
             if address {
-                drk.wallet_address(0).await.with_context(|| "Failed to fetch default address")?;
+                let address = drk
+                    .wallet_address(0)
+                    .await
+                    .with_context(|| "Failed to fetch default address")?;
+
+                println!("{}", address);
+
                 return Ok(())
             }
 
             unreachable!()
         }
+
+        Subcmd::Airdrop { faucet_endpoint, amount, token, address } => {
+            let amount = f64::from_str(&amount).with_context(|| "Invalid amount")?;
+            let token_id = TokenId::try_from(token.as_str()).with_context(|| "Invalid Token ID")?;
+
+            let rpc_client = RpcClient::new(args.endpoint)
+                .await
+                .with_context(|| "Could not connect to darkfid RPC endpoint")?;
+
+            let drk = Drk { rpc_client };
+
+            let address = match address {
+                Some(v) => PublicKey::from_str(v.as_str()).with_context(|| "Invalid address")?,
+                None => drk.wallet_address(0).await.with_context(|| {
+                    "Failed to fetch default address, perhaps the wallet was not initialized?"
+                })?,
+            };
+
+            let txid = drk
+                .request_airdrop(faucet_endpoint, amount, token_id, address)
+                .await
+                .with_context(|| "Failed to request airdrop")?;
+
+            println!("Transaction ID: {}", txid);
+            Ok(())
+        }
     }
 }

+ 45 - 0
bin/drk/src/rpc_airdrop.rs

@@ -0,0 +1,45 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2022 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use anyhow::Result;
+use darkfi::rpc::{client::RpcClient, jsonrpc::JsonRequest};
+use darkfi_sdk::crypto::{PublicKey, TokenId};
+use serde_json::json;
+use url::Url;
+
+use super::Drk;
+
+impl Drk {
+    /// Request an airdrop of `amount` `token_id` tokens from a faucet.
+    /// Returns a transaction ID on success.
+    pub async fn request_airdrop(
+        &self,
+        faucet_endpoint: Url,
+        amount: f64,
+        token_id: TokenId,
+        address: PublicKey,
+    ) -> Result<String> {
+        let rpc_client = RpcClient::new(faucet_endpoint).await?;
+        let params = json!([format!("{}", address), amount, format!("{}", token_id),]);
+        let req = JsonRequest::new("airdrop", params);
+        let rep = rpc_client.oneshot_request(req).await?;
+
+        println!("{:#?}", rep);
+        Ok(format!("ack"))
+    }
+}

+ 14 - 16
bin/drk/src/rpc_wallet.rs

@@ -39,31 +39,31 @@ use super::Drk;
 // 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_COIN: &str = "coin";
 const MONEY_COINS_COL_IS_SPENT: &str = "is_spent";
-const MONEY_COINS_COL_SERIAL: &str = "serial";
+//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_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";
 
 impl Drk {
     /// Initialize wallet with tables for the Money contract.
@@ -234,7 +234,7 @@ impl Drk {
     }
 
     /// Fetch pubkeys from the wallet and print the requested index.
-    pub async fn wallet_address(&self, idx: u64) -> Result<()> {
+    pub async fn wallet_address(&self, idx: u64) -> Result<PublicKey> {
         let query = format!("SELECT {} FROM {};", MONEY_KEYS_COL_PUBLIC, MONEY_KEYS_TABLE);
         let params = json!([query, QueryType::Blob as u8, MONEY_KEYS_COL_PUBLIC]);
         let req = JsonRequest::new("wallet.query_row_single", params);
@@ -251,8 +251,6 @@ impl Drk {
         let key_bytes: Vec<u8> = serde_json::from_value(arr[0].clone())?;
         let public_key: PublicKey = deserialize(&key_bytes)?;
 
-        println!("{}", public_key);
-
-        Ok(())
+        Ok(public_key)
     }
 }

+ 4 - 0
bin/faucetd/src/main.rs

@@ -371,6 +371,10 @@ impl Faucetd {
     // RPCAPI:
     // Processes an airdrop request and airdrops requested token and amount to address.
     // Returns the transaction ID upon success.
+    // Params:
+    // 0: base58 encoded address of the recipient
+    // 1: Amount to airdrop in form of f64
+    // 2: base58 encoded token ID to airdrop
     //
     // --> {"jsonrpc": "2.0", "method": "airdrop", "params": ["1DarkFi...", 1.42, "1F00b4r..."], "id": 1}
     // <-- {"jsonrpc": "2.0", "result": "txID", "id": 1}

+ 58 - 51
src/consensus/state.rs

@@ -60,7 +60,7 @@ use crate::{
     Error, Result,
 };
 
-const PI_NULLIFIER_INDEX: usize  = 7;
+const PI_NULLIFIER_INDEX: usize = 7;
 const PI_COMMITMENT_X_INDEX: usize = 1;
 const PI_COMMITMENT_Y_INDEX: usize = 2;
 /// This struct represents the information required by the consensus algorithm
@@ -144,7 +144,7 @@ pub struct ValidatorState {
     /// lead coins
     pub lead: Vec<(pallas::Base, pallas::Base)>,
     /// f history
-    pub f_history : Vec<Float10>,
+    pub f_history: Vec<Float10>,
     /// Kp
     pub Kp: Float10,
     /// Ti
@@ -216,7 +216,7 @@ impl ValidatorState {
         let one = Float10::from_str_native("1").unwrap().with_precision(RADIX_BITS).value();
         let ten = Float10::from_str_native("10").unwrap().with_precision(RADIX_BITS).value();
         let three = Float10::from_str_native("3").unwrap().with_precision(RADIX_BITS).value();
-        let nine  = Float10::from_str_native("9").unwrap().with_precision(RADIX_BITS).value();
+        let nine = Float10::from_str_native("9").unwrap().with_precision(RADIX_BITS).value();
         let state = Arc::new(RwLock::new(ValidatorState {
             lead_proving_key,
             lead_verifying_key,
@@ -229,9 +229,9 @@ impl ValidatorState {
             spent: vec![],
             lead: vec![],
             f_history: vec![zero],
-            Kp: three/nine,
-            Ti: one.clone()/ten.clone(),
-            Td: one/ten,
+            Kp: three / nine,
+            Ti: one.clone() / ten.clone(),
+            Td: one / ten,
         }));
 
         Ok(state)
@@ -255,7 +255,7 @@ impl ValidatorState {
         }
 
         debug!("append_tx(): Starting state transition validation");
-        if let Err(e) = self.verify_transactions(&[tx.clone()]) {
+        if let Err(e) = self.verify_transactions(&[tx.clone()], false) {
             error!("append_tx(): Failed to verify transaction: {}", e);
             return false
         };
@@ -399,10 +399,7 @@ impl ValidatorState {
 
     /// Generate coins for provided sigmas.
     /// NOTE: The strategy here is having a single competing coin per slot.
-    async fn create_coins(
-        &self,
-        eta: pallas::Base,
-    ) -> Result<Vec<Vec<LeadCoin>>> {
+    async fn create_coins(&self, eta: pallas::Base) -> Result<Vec<Vec<LeadCoin>>> {
         let mut rng = thread_rng();
 
         let mut seeds: Vec<u64> = Vec::with_capacity(EPOCH_LENGTH);
@@ -464,7 +461,7 @@ impl ValidatorState {
     /// and only read from last block header.
     fn leads_per_block(&mut self) -> Float10 {
         //TODO: complete this
-        let fi64 : i64 = 1;
+        let fi64: i64 = 1;
         self.f_history.push(Float10::try_from(fi64).unwrap().with_precision(RADIX_BITS).value());
         Float10::try_from(fi64).unwrap().with_precision(RADIX_BITS).value()
     }
@@ -476,13 +473,13 @@ impl ValidatorState {
 
     fn f_der(&self) -> Float10 {
         let len = self.f_history.len();
-        (self.f_history[len-1].clone() - self.f_history[len-2].clone())/self.Td.clone()
+        (self.f_history[len - 1].clone() - self.f_history[len - 2].clone()) / self.Td.clone()
     }
 
     fn f_int(&self) -> Float10 {
-        let mut sum  = Float10::from_str_native("0").unwrap().with_precision(RADIX_BITS).value();;
+        let mut sum = Float10::from_str_native("0").unwrap().with_precision(RADIX_BITS).value();
         for f in &self.f_history {
-            sum += f.clone()*self.Td.clone();
+            sum += f.clone() * self.Td.clone();
         }
         sum
     }
@@ -493,16 +490,21 @@ impl ValidatorState {
         let one = Float10::from_str_native("1").unwrap().with_precision(RADIX_BITS).value();
         let zero = Float10::from_str_native("0").unwrap().with_precision(RADIX_BITS).value();
         let mut f = zero.clone();
-        let step = Float10::from_str_native("0.1").unwrap().with_precision(RADIX_BITS).value();;
+        let step = Float10::from_str_native("0.1").unwrap().with_precision(RADIX_BITS).value();
         let p = self.f_dif();
         let i = self.f_int();
         let d = self.f_der();
-        while f<=Float10::from_str_native("0").unwrap().with_precision(RADIX_BITS).value() && f>=Float10::from_str_native("1").unwrap().with_precision(RADIX_BITS).value() {
-            f = self.Kp.clone()*(p.clone() + one.clone()/self.Ti.clone() * i.clone() + self.Td.clone() * d.clone());
-            if f>= one {
-                self.Kp-=step.clone();
-            } else if f<=zero {
-                self.Kp+=step.clone();
+        while f <= Float10::from_str_native("0").unwrap().with_precision(RADIX_BITS).value() &&
+            f >= Float10::from_str_native("1").unwrap().with_precision(RADIX_BITS).value()
+        {
+            f = self.Kp.clone() *
+                (p.clone() +
+                    one.clone() / self.Ti.clone() * i.clone() +
+                    self.Td.clone() * d.clone());
+            if f >= one {
+                self.Kp -= step.clone();
+            } else if f <= zero {
+                self.Kp += step.clone();
             }
         }
         Float10::try_from(f).unwrap().with_precision(RADIX_BITS).value()
@@ -660,16 +662,16 @@ impl ValidatorState {
         //TODO: validate sn/cm
         let prop_sn = proposal.block.metadata.public_inputs[PI_NULLIFIER_INDEX];
         for sn in &self.nullifiers {
-            if *sn==prop_sn {
-                return Err(Error::ProposalIsSpent);
+            if *sn == prop_sn {
+                return Err(Error::ProposalIsSpent)
             }
         }
-        let prop_cm_x : pallas::Base = proposal.block.metadata.public_inputs[PI_COMMITMENT_X_INDEX];
-        let prop_cm_y : pallas::Base = proposal.block.metadata.public_inputs[PI_COMMITMENT_Y_INDEX];
+        let prop_cm_x: pallas::Base = proposal.block.metadata.public_inputs[PI_COMMITMENT_X_INDEX];
+        let prop_cm_y: pallas::Base = proposal.block.metadata.public_inputs[PI_COMMITMENT_Y_INDEX];
 
         for cm in &self.lead {
-            if *cm==(prop_cm_x, prop_cm_y) {
-                return Err(Error::ProposalIsSpent);
+            if *cm == (prop_cm_x, prop_cm_y) {
+                return Err(Error::ProposalIsSpent)
             }
         }
         let current = self.current_slot();
@@ -731,7 +733,7 @@ impl ValidatorState {
         // Validate state transition against canonical state
         // TODO: This should be validated against fork state
         debug!("receive_proposal(): Starting state transition validation");
-        if let Err(e) = self.verify_transactions(&proposal.block.txs) {
+        if let Err(e) = self.verify_transactions(&proposal.block.txs, false) {
             error!("receive_proposal(): Transaction verifications failed: {}", e);
             return Err(e.into())
         };
@@ -899,7 +901,7 @@ impl ValidatorState {
             // TODO: FIXME: The state transitions have already been written, they have to be in memory
             //              until this point.
             debug!(target: "consensus", "Applying state transition for finalized block");
-            if let Err(e) = self.verify_transactions(&proposal.txs) {
+            if let Err(e) = self.verify_transactions(&proposal.txs, true) {
                 error!(target: "consensus", "Finalized block transaction verifications failed: {}", e);
                 return Err(e)
             }
@@ -940,7 +942,7 @@ impl ValidatorState {
         // Verify state transitions for all blocks and their respective transactions.
         debug!("receive_blocks(): Starting state transition validations");
         for block in blocks {
-            if let Err(e) = self.verify_transactions(&block.txs) {
+            if let Err(e) = self.verify_transactions(&block.txs, false) {
                 error!("receive_blocks(): Transaction verifications failed: {}", e);
                 return Err(e)
             }
@@ -1013,11 +1015,11 @@ impl ValidatorState {
     /// If all of those succeed, try to execute a state update for the contract calls.
     /// Currently the verifications are sequential, and the function will fail if any
     /// of the verifications fail.
-    /// TODO: FIXME: TESTNET: The state changes should be in memory until a block with
-    ///                       it is finalized. Another option is to not apply and just
-    ///                       run this again when we see a finalized block (and apply
-    ///                       the update at that point). #finalization
-    pub fn verify_transactions(&self, txs: &[Transaction]) -> Result<()> {
+    /// The function takes a boolean called `write` which tells it to actually write
+    /// the state transitions to the database.
+    // TODO: This should be paralellized as if even one tx in the batch fails to verify,
+    //       we can drop everything.
+    pub fn verify_transactions(&self, txs: &[Transaction], write: bool) -> Result<()> {
         debug!("Verifying {} transaction(s)", txs.len());
         for tx in txs {
             // Table of public inputs used for ZK proof verification
@@ -1026,10 +1028,12 @@ impl ValidatorState {
             let mut sig_table = vec![];
             // State updates produced by contract execution
             let mut updates = vec![];
+            // ZK circuit verifying keys (FIXME: These should be in a more global scope)
+            let mut verifying_keys = vec![];
 
             // Iterate over all calls to get the metadata
             for (idx, call) in tx.calls.iter().enumerate() {
-                debug!("Working on call {}", idx);
+                debug!("Verifying contract call {}", idx);
                 // Check if the called contract exist as bincode.
                 let bincode = self.blockchain.wasm_bincode.get(call.contract_id)?;
                 debug!("Found wasm bincode for {}", call.contract_id);
@@ -1070,8 +1074,7 @@ impl ValidatorState {
 
             // Finally, verify the ZK proofs
             debug!("Verifying transaction ZK proofs");
-            // FIXME XXX:
-            tx.verify_zkps(&[], zkp_table)?;
+            tx.verify_zkps(&verifying_keys, zkp_table)?;
             debug!("Transaction ZK proofs verified successfully!");
 
             // When the verification stage has passed, just apply all the changes.
@@ -1080,18 +1083,22 @@ impl ValidatorState {
             //              for additional notes).
             // TODO: We instantiate new runtimes here, so pick up the gas fees from
             //       the previous runs and sum them all together.
-            debug!("Performing state updates");
-            assert!(tx.calls.len() == updates.len());
-            for (call, update) in tx.calls.iter().zip(updates.iter()) {
-                // Do the bincode lookups again
-                let bincode = self.blockchain.wasm_bincode.get(call.contract_id)?;
-                debug!("Found wasm bincode for {}", call.contract_id);
-
-                let mut runtime =
-                    Runtime::new(&bincode, self.blockchain.clone(), call.contract_id)?;
-
-                debug!("Executing \"apply\" call");
-                runtime.apply(&update)?;
+            if write {
+                debug!("Performing state updates");
+                assert!(tx.calls.len() == updates.len());
+                for (call, update) in tx.calls.iter().zip(updates.iter()) {
+                    // Do the bincode lookups again
+                    let bincode = self.blockchain.wasm_bincode.get(call.contract_id)?;
+                    debug!("Found wasm bincode for {}", call.contract_id);
+
+                    let mut runtime =
+                        Runtime::new(&bincode, self.blockchain.clone(), call.contract_id)?;
+
+                    debug!("Executing \"apply\" call");
+                    runtime.apply(&update)?;
+                }
+            } else {
+                debug!("Skipping state updates because write=false");
             }
         }
 

+ 4 - 1
src/contract/money/tests/contract_exec.rs

@@ -46,7 +46,7 @@ use darkfi_sdk::{
     tx::ContractCall,
 };
 use darkfi_serial::{deserialize, serialize, Decodable, Encodable, WriteExt};
-use log::info;
+use log::{debug, info};
 use rand::rngs::OsRng;
 
 use darkfi_money_contract::{
@@ -157,6 +157,9 @@ async fn money_contract_execution() -> Result<()> {
         true,
     )?;
 
+    debug!("PARAMS: {:#?}", params);
+    debug!("PROOFS: {:?}", proofs);
+
     // Build transaction
     let mut data = vec![MoneyFunction::Transfer as u8];
     params.encode(&mut data)?;

+ 3 - 2
src/runtime/vm_runtime.rs

@@ -23,7 +23,7 @@ use std::{
 
 use darkfi_sdk::{crypto::ContractId, entrypoint};
 use darkfi_serial::serialize;
-use log::{debug, info};
+use log::{debug, error, info};
 use wasmer::{
     imports, wasmparser::Operator, AsStoreRef, CompilerConfig, Function, FunctionEnv, Instance,
     Memory, MemoryView, Module, Pages, Store, Value, WASM_PAGE_SIZE,
@@ -281,6 +281,7 @@ impl Runtime {
                 self.print_logs();
                 debug!(target: "runtime", "{}", self.gas_info());
                 // WasmerRuntimeError panics are handled here. Return from run() immediately.
+                error!("Wasmer Runtime Error: {:#?}", e);
                 return Err(e.into())
             }
         };
@@ -297,7 +298,7 @@ impl Runtime {
 
         let retval = match ret[0] {
             Value::I64(v) => v,
-            _ => unreachable!(),
+            _ => unreachable!("Got unexpected result from ret: {:?}", ret),
         };
 
         match retval {