Browse Source

darkfid: Add tx.broadcast RPC endpoint for simulating and broadcasting txs.

Luther Blissett 3 năm trước cách đây
mục cha
commit
e5983d0b6b
4 tập tin đã thay đổi với 108 bổ sung35 xóa
  1. 32 21
      bin/darkfid/src/error.rs
  2. 1 0
      bin/darkfid/src/main.rs
  3. 74 13
      bin/darkfid/src/rpc_tx.rs
  4. 1 1
      bin/darkfid/src/rpc_wallet.rs

+ 32 - 21
bin/darkfid/src/error.rs

@@ -2,41 +2,52 @@ use serde_json::Value;
 
 use darkfi::rpc::jsonrpc::{ErrorCode::ServerError, JsonError, JsonResult};
 
+/// Custom RPC errors available for darkfid.
+/// Please sort them sensefully.
 pub enum RpcError {
+    // Wallet/Key-related errors
     Keygen = -32101,
-    Nan = -32102,
-    LessThanNegOne = -32103,
-    KeypairFetch = -32104,
-    KeypairNotFound = -32105,
-    InvalidKeypair = -32106,
-    UnknownSlot = -32107,
-    TxBuildFail = -32108,
-    //    NetworkNameError = -32109,
-    ParseError = -32110,
+    KeypairFetch = -32102,
+    KeypairNotFound = -32103,
+    InvalidKeypair = -32104,
+    InvalidAddressParam = -32105,
+    DecryptionFailed = -32106,
+
+    // Transaction-related errors
+    TxBuildFail = -32110,
     TxBroadcastFail = -32111,
-    NotYetSynced = -32112,
-    InvalidAddressParam = -32113,
-    //    InvalidAmountParam = -32114,
-    DecryptionFailed = -32115,
+    TxSimulationFail = -32112,
+
+    // State-related errors,
+    NotYetSynced = -32120,
+    UnknownSlot = -32121,
+
+    // Parsing errors
+    ParseError = -32190,
+    NaN = -32191,
+    LessThanNegOne = -32192,
 }
 
 fn to_tuple(e: RpcError) -> (i64, String) {
     let msg = match e {
+        // Wallet/Key-related errors
         RpcError::Keygen => "Failed generating keypair",
-        RpcError::Nan => "Not a number",
-        RpcError::LessThanNegOne => "Number cannot be lower than -1",
         RpcError::KeypairFetch => "Failed fetching keypairs from wallet",
         RpcError::KeypairNotFound => "Keypair not found",
         RpcError::InvalidKeypair => "Invalid keypair",
-        RpcError::UnknownSlot => "Did not find slot",
+        RpcError::InvalidAddressParam => "Invalid address parameter",
+        RpcError::DecryptionFailed => "Decryption failed",
+        // Transaction-related errors
         RpcError::TxBuildFail => "Failed building transaction",
-        //        RpcError::NetworkNameError => "Unknown network name",
-        RpcError::ParseError => "Parse error",
         RpcError::TxBroadcastFail => "Failed broadcasting transaction",
+        RpcError::TxSimulationFail => "Failed simulating transaction state change",
+        // State-related errors
         RpcError::NotYetSynced => "Blockchain not yet synced",
-        RpcError::InvalidAddressParam => "Invalid address parameter",
-        //        RpcError::InvalidAmountParam => "invalid amount parameter",
-        RpcError::DecryptionFailed => "Decryption failed",
+        RpcError::UnknownSlot => "Did not find slot",
+        // Parsing errors
+        RpcError::ParseError => "Parse error",
+        RpcError::NaN => "Not a number",
+        RpcError::LessThanNegOne => "Number cannot be lower than -1",
     };
 
     (e as i64, msg.to_string())

+ 1 - 0
bin/darkfid/src/main.rs

@@ -187,6 +187,7 @@ impl RequestHandler for Darkfid {
             Some("blockchain.get_slot") => return self.get_slot(req.id, params).await,
             Some("blockchain.merkle_roots") => return self.merkle_roots(req.id, params).await,
             Some("tx.transfer") => return self.transfer(req.id, params).await,
+            Some("tx.broadcast") => return self.broadcast(req.id, params).await,
             Some("wallet.keygen") => return self.keygen(req.id, params).await,
             Some("wallet.get_addrs") => return self.get_addrs(req.id, params).await,
             Some("wallet.export_keypair") => return self.export_keypair(req.id, params).await,

+ 74 - 13
bin/darkfid/src/rpc_tx.rs

@@ -4,9 +4,12 @@ use log::{error, warn};
 use serde_json::{json, Value};
 
 use darkfi::{
+    consensus::ValidatorState,
     crypto::{address::Address, keypair::PublicKey, token_id},
+    node::MemoryState,
     rpc::jsonrpc::{ErrorCode::InvalidParams, JsonError, JsonResponse, JsonResult},
-    util::serial::serialize,
+    tx::Transaction,
+    util::serial::{deserialize, serialize},
 };
 
 use super::Darkfid;
@@ -33,7 +36,7 @@ impl Darkfid {
         }
 
         if !(*self.synced.lock().await) {
-            error!("transfer(): Blockchain is not yet synced");
+            error!("tx.transfer: Blockchain is not yet synced");
             return server_error(RpcError::NotYetSynced, id)
         }
 
@@ -44,7 +47,7 @@ impl Darkfid {
         let address = match Address::from_str(address) {
             Ok(v) => v,
             Err(e) => {
-                error!("transfer(): Failed parsing address from string: {}", e);
+                error!("tx.transfer: Failed parsing address from string: {}", e);
                 return server_error(RpcError::InvalidAddressParam, id)
             }
         };
@@ -52,7 +55,7 @@ impl Darkfid {
         let pubkey = match PublicKey::try_from(address) {
             Ok(v) => v,
             Err(e) => {
-                error!("transfer(): Failed parsing PublicKey from Address: {}", e);
+                error!("tx.transfer: Failed parsing PublicKey from Address: {}", e);
                 return server_error(RpcError::ParseError, id)
             }
         };
@@ -60,7 +63,7 @@ impl Darkfid {
         let token_id = match token_id::parse_b58(token) {
             Ok(v) => v,
             Err(e) => {
-                error!("transfer(): Failed parsing Token ID from string: {}", e);
+                error!("tx.transfer: Failed parsing Token ID from string: {}", e);
                 return server_error(RpcError::ParseError, id)
             }
         };
@@ -78,21 +81,79 @@ impl Darkfid {
         {
             Ok(v) => v,
             Err(e) => {
-                error!("transfer(): Failed building transaction: {}", e);
+                error!("tx.transfer: Failed building transaction: {}", e);
                 return server_error(RpcError::TxBuildFail, id)
             }
         };
 
         if let Some(sync_p2p) = &self.sync_p2p {
-            match sync_p2p.broadcast(tx.clone()).await {
-                Ok(()) => {}
-                Err(e) => {
-                    error!("transfer(): Failed broadcasting transaction: {}", e);
-                    return server_error(RpcError::TxBroadcastFail, id)
-                }
+            if let Err(e) = sync_p2p.broadcast(tx.clone()).await {
+                error!("tx.transfer: Failed broadcasting transaction: {}", e);
+                return server_error(RpcError::TxBroadcastFail, id)
             }
         } else {
-            warn!("No sync P2P network, not broadcasting transaction.");
+            warn!("tx.transfer: No sync P2P network, not broadcasting transaction.");
+            return server_error(RpcError::TxBroadcastFail, id)
+        }
+
+        let tx_hash = blake3::hash(&serialize(&tx)).to_hex().as_str().to_string();
+        JsonResponse::new(json!(tx_hash), id).into()
+    }
+
+    // RPCAPI:
+    // Broadcast a given transaction to the P2P network.
+    // The function will first simulate the state transition in order to see
+    // if the transaction is actually valid, and in turn it will return an
+    // error if this is the case. Otherwise, a transaction ID will be returned.
+    //
+    // --> {"jsonrpc": "2.0", "method": "tx.broadcast", "params": ["base58encodedTX"], "id": 1}
+    // <-- {"jsonrpc": "2.0", "result": "txID...", "id": 1}
+    pub async fn broadcast(&self, id: Value, params: &[Value]) -> JsonResult {
+        if params.len() != 1 || params[0].is_string() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+
+        if !(*self.synced.lock().await) {
+            error!("tx.transfer: Blockchain is not yet synced");
+            return server_error(RpcError::NotYetSynced, id)
+        }
+
+        // Try to deserialize the transaction
+        let tx_bytes = match bs58::decode(params[0].as_str().unwrap()).into_vec() {
+            Ok(v) => v,
+            Err(e) => {
+                error!("tx.broadcast: Failed decoding base58 transaction: {}", e);
+                return server_error(RpcError::ParseError, id)
+            }
+        };
+
+        let tx: Transaction = match deserialize(&tx_bytes) {
+            Ok(v) => v,
+            Err(e) => {
+                error!("tx.broadcast: Failed deserializing bytes into Transaction: {}", e);
+                return server_error(RpcError::ParseError, id)
+            }
+        };
+
+        // Grab the current state and apply a new MemoryState
+        let state_machine = self.validator_state.read().await.state_machine.lock().await.clone();
+        let mem_state = MemoryState::new(state_machine.clone());
+        drop(state_machine);
+
+        // Simulate state transition
+        if let Err(e) = ValidatorState::validate_state_transitions(mem_state, &[tx.clone()]) {
+            error!("tx.broadcast: Failed to validate state transition: {}", e);
+            return server_error(RpcError::TxSimulationFail, id)
+        }
+
+        if let Some(sync_p2p) = &self.sync_p2p {
+            if let Err(e) = sync_p2p.broadcast(tx.clone()).await {
+                error!("tx.broadcast: Failed broadcasting transaction: {}", e);
+                return server_error(RpcError::TxBroadcastFail, id)
+            }
+        } else {
+            warn!("tx.broadcast: No sync P2P network, not broadcasting transaction.");
+            return server_error(RpcError::TxBroadcastFail, id)
         }
 
         let tx_hash = blake3::hash(&serialize(&tx)).to_hex().as_str().to_string();

+ 1 - 1
bin/darkfid/src/rpc_wallet.rs

@@ -49,7 +49,7 @@ impl Darkfid {
         let mut fetch_all = false;
         for i in params {
             if !i.is_i64() {
-                return server_error(RpcError::Nan, id)
+                return server_error(RpcError::NaN, id)
             }
 
             if i.as_i64() == Some(-1) {