Browse Source

darkfid: Implement tx.transfer RPC method.

parazyd 4 years ago
parent
commit
d386ad607d
6 changed files with 150 additions and 18 deletions
  1. 1 0
      Cargo.lock
  2. 1 0
      bin/darkfid2/Cargo.toml
  3. 10 0
      bin/darkfid2/src/error.rs
  4. 2 0
      bin/darkfid2/src/main.rs
  5. 132 0
      bin/darkfid2/src/rpc_tx.rs
  6. 4 18
      src/node/client.rs

+ 1 - 0
Cargo.lock

@@ -1188,6 +1188,7 @@ dependencies = [
  "async-executor",
  "async-std",
  "async-trait",
+ "blake3",
  "chrono",
  "ctrlc-async",
  "darkfi",

+ 1 - 0
bin/darkfid2/Cargo.toml

@@ -13,6 +13,7 @@ async-channel = "1.6.1"
 async-executor = "1.4.1"
 async-std = "1.11.0"
 async-trait = "0.1.53"
+blake3 = "1.3.1"
 chrono = "0.4.19"
 ctrlc-async = {version = "3.2.2", default-features = false, features = ["async-std", "termination"]}
 darkfi = {path = "../../", features = ["blockchain", "wallet", "rpc", "net", "node"]}

+ 10 - 0
bin/darkfid2/src/error.rs

@@ -13,6 +13,11 @@ pub enum RpcError {
     KeypairNotFound = -32105,
     InvalidKeypair = -32106,
     UnknownSlot = -32107,
+    TxBuildFail = -32108,
+    NetworkNameError = -32109,
+    ParseError = -32110,
+    TxBroadcastFail = -32111,
+    NotYetSynced = -32112,
 }
 
 fn to_tuple(e: RpcError) -> (i64, String) {
@@ -24,6 +29,11 @@ fn to_tuple(e: RpcError) -> (i64, String) {
         RpcError::KeypairNotFound => "Keypair not found",
         RpcError::InvalidKeypair => "Invalid keypair",
         RpcError::UnknownSlot => "Did not find slot",
+        RpcError::TxBuildFail => "Failed building transaction",
+        RpcError::NetworkNameError => "Unknown network name",
+        RpcError::ParseError => "Parse error",
+        RpcError::TxBroadcastFail => "Failed broadcasting transaction",
+        RpcError::NotYetSynced => "Blockchain not yet synced",
     };
 
     (e as i64, msg.to_string())

+ 2 - 0
bin/darkfid2/src/main.rs

@@ -159,6 +159,7 @@ pub struct Darkfid {
 // JSON-RPC methods
 mod rpc_blockchain;
 mod rpc_misc;
+mod rpc_tx;
 mod rpc_wallet;
 
 #[async_trait]
@@ -173,6 +174,7 @@ impl RequestHandler for Darkfid {
         match req.method.as_str() {
             Some("ping") => return self.pong(req.id, params).await,
             Some("blockchain.get_slot") => return self.get_slot(req.id, params).await,
+            Some("tx.transfer") => return self.transfer(req.id, params).await,
             Some("wallet.keygen") => return self.keygen(req.id, params).await,
             Some("wallet.get_key") => return self.get_key(req.id, params).await,
             Some("wallet.export_keypair") => return self.export_keypair(req.id, params).await,

+ 132 - 0
bin/darkfid2/src/rpc_tx.rs

@@ -0,0 +1,132 @@
+use std::str::FromStr;
+
+use log::{error, warn};
+use serde_json::{json, Value};
+
+use darkfi::{
+    consensus::Tx,
+    crypto::{address::Address, keypair::PublicKey},
+    rpc::{
+        jsonrpc,
+        jsonrpc::{
+            ErrorCode::{
+                InternalError, InvalidAddressParam, InvalidAmountParam, InvalidParams,
+                InvalidTokenIdParam,
+            },
+            JsonResult,
+        },
+    },
+    util::{decode_base10, serial::serialize, NetworkName},
+};
+
+use super::Darkfid;
+use crate::{server_error, RpcError};
+
+impl Darkfid {
+    // RPCAPI:
+    // Transfer a given amount of some token to the given address.
+    // Returns a transaction ID upon success.
+    // --> {"jsonrpc": "2.0", "method": "tx.transfer", "params": ["darkfi" "gdrk", "1DarkFi...", 12.0], "id": 1}
+    // <-- {"jsonrpc": "2.0", "result": "txID...", "id": 1}
+    pub async fn transfer(&self, id: Value, params: &[Value]) -> JsonResult {
+        if params.len() != 4 ||
+            !params[0].is_string() ||
+            !params[1].is_string() ||
+            !params[2].is_string() ||
+            !params[3].is_f64()
+        {
+            return jsonrpc::error(InvalidParams, None, id).into()
+        }
+
+        let network = params[0].as_str().unwrap();
+        let token = params[1].as_str().unwrap();
+        let address = params[2].as_str().unwrap();
+        let amount = params[3].as_f64().unwrap();
+
+        if *self.synced.lock().await == false {
+            error!("transfer(): Blockchain is not yet synced");
+            return server_error(RpcError::NotYetSynced, id)
+        }
+
+        let address = match Address::from_str(address) {
+            Ok(v) => v,
+            Err(e) => {
+                error!("transfer(): Failed parsing address from string: {}", e);
+                return jsonrpc::error(InvalidAddressParam, None, id).into()
+            }
+        };
+
+        let pubkey = match PublicKey::try_from(address) {
+            Ok(v) => v,
+            Err(e) => {
+                error!("transfer(): Failed parsing PublicKey from Address: {}", e);
+                return server_error(RpcError::ParseError, id)
+            }
+        };
+
+        let amount = amount.to_string();
+        let amount = match decode_base10(&amount, 8, true) {
+            Ok(v) => v,
+            Err(e) => {
+                error!("transfer(): Failed parsing amount from string: {}", e);
+                return jsonrpc::error(InvalidAmountParam, None, id).into()
+            }
+        };
+        let amount: u64 = match amount.try_into() {
+            Ok(v) => v,
+            Err(e) => {
+                error!("transfer(): Failed converting biguint to u64: {}", e);
+                return jsonrpc::error(InternalError, None, id).into()
+            }
+        };
+
+        let network = match NetworkName::from_str(network) {
+            Ok(v) => v,
+            Err(e) => {
+                error!("transfer(): Failed parsing NetworkName: {}", e);
+                return server_error(RpcError::NetworkNameError, id)
+            }
+        };
+
+        let token_id = if let Some(token_id) =
+            self.drk_tokenlist.tokens[&network].get(&token.to_uppercase())
+        {
+            token_id
+        } else {
+            return jsonrpc::error(InvalidTokenIdParam, None, id).into()
+        };
+
+        let tx = match self
+            .client
+            .build_transaction(
+                pubkey,
+                amount,
+                *token_id,
+                false,
+                self.validator_state.read().await.state_machine.clone(),
+            )
+            .await
+        {
+            Ok(v) => v,
+            Err(e) => {
+                error!("transfer(): Failed building transaction: {}", e);
+                return server_error(RpcError::TxBuildFail, id)
+            }
+        };
+
+        if let Some(sync_p2p) = &self.sync_p2p {
+            match sync_p2p.broadcast(Tx(tx.clone())).await {
+                Ok(()) => {}
+                Err(e) => {
+                    error!("transfer(): Failed broadcasting transaction: {}", e);
+                    return server_error(RpcError::TxBroadcastFail, id)
+                }
+            }
+        } else {
+            warn!("No sync P2P network, not broadcasting transaction.");
+        }
+
+        let tx_hash = blake3::hash(&serialize(&tx)).to_hex().as_str().to_string();
+        jsonrpc::response(json!(tx_hash), id).into()
+    }
+}

+ 4 - 18
src/node/client.rs

@@ -165,6 +165,10 @@ impl Client {
             return Err(ClientFailed::InvalidAmount(0))
         }
 
+        if !self.wallet.token_id_exists(token_id).await? && !clear_input {
+            return Err(ClientFailed::NotEnoughValue(amount))
+        }
+
         let (tx, coins) =
             self.build_slab_from_tx(pubkey, amount, token_id, clear_input, state).await?;
         for coin in coins.iter() {
@@ -177,24 +181,6 @@ impl Client {
         Ok(tx)
     }
 
-    // TODO
-    // pub async fn transfer(
-    // &self,
-    // token_id: DrkTokenId,
-    // pubkey: PublicKey,
-    // amount: u64,
-    // state: Arc<Mutex<State>>,
-    // ) -> ClientResult<()> {
-    // debug!("transfer(): Start transfer {}", amount);
-    // if self.wallet.token_id_exists(token_id).await? {
-    // self.send(pubkey, amount, token_id, false, state).await?;
-    // debug!("transfer(): Finish transfer {}", amount);
-    // return Ok(())
-    // }
-    //
-    //      Err(ClientFailed::NotEnoughValue(amount))
-    //}
-
     pub async fn init_db(&self) -> Result<()> {
         self.wallet.init_db().await
     }