Browse Source

darkfid/rpc: expose endpoints to retrieve contract tree state records, as per task UHqZgC

skoupidi 1 năm trước cách đây
mục cha
commit
1ae4b7c858

+ 4 - 0
bin/darkfid/src/error.rs

@@ -34,6 +34,8 @@ pub enum RpcError {
 
     // Contract-related errors
     ContractZkasDbNotFound = -32200,
+    ContractStateNotFound = -32201,
+    ContractStateKeyNotFound = -32202,
 
     // Misc errors
     PingFailed = -32300,
@@ -51,6 +53,8 @@ fn to_tuple(e: RpcError) -> (i32, String) {
         RpcError::ParseError => "Parse error",
         // Contract-related errors
         RpcError::ContractZkasDbNotFound => "zkas database not found for given contract",
+        RpcError::ContractStateNotFound => "Records not found for given contract state",
+        RpcError::ContractStateKeyNotFound => "Value not found for given contract state key",
         // Misc errors
         RpcError::PingFailed => "Miner daemon ping error",
     };

+ 2 - 0
bin/darkfid/src/rpc.rs

@@ -93,6 +93,8 @@ impl RequestHandler<DefaultRpcHandler> for DarkfiNode {
             "blockchain.best_fork_next_block_height" => self.blockchain_best_fork_next_block_height(req.id, req.params).await,
             "blockchain.block_target" => self.blockchain_block_target(req.id, req.params).await,
             "blockchain.lookup_zkas" => self.blockchain_lookup_zkas(req.id, req.params).await,
+            "blockchain.get_contract_state" => self.blockchain_get_contract_state(req.id, req.params).await,
+            "blockchain.get_contract_state_key" => self.blockchain_get_contract_state_key(req.id, req.params).await,
             "blockchain.subscribe_blocks" => self.blockchain_subscribe_blocks(req.id, req.params).await,
             "blockchain.subscribe_txs" =>  self.blockchain_subscribe_txs(req.id, req.params).await,
             "blockchain.subscribe_proposals" => self.blockchain_subscribe_proposals(req.id, req.params).await,

+ 108 - 2
bin/darkfid/src/rpc_blockchain.rs

@@ -90,7 +90,7 @@ impl DarkfiNode {
     // <-- {"jsonrpc": "2.0", "result": "ABCD...", "id": 1}
     pub async fn blockchain_get_tx(&self, id: u16, params: JsonValue) -> JsonResult {
         let params = params.get::<Vec<JsonValue>>().unwrap();
-        if params.len() != 1 {
+        if params.len() != 1 || !params[0].is_string() {
             return JsonError::new(InvalidParams, None, id).into()
         }
 
@@ -257,7 +257,7 @@ impl DarkfiNode {
     //   [`ZkBinary`](https://darkrenaissance.github.io/darkfi/dev/darkfi/zkas/decoder/struct.ZkBinary.html)
     //   object
     //
-    // --> {"jsonrpc": "2.0", "method": "blockchain.lookup_zkas", "params": ["6Ef42L1KLZXBoxBuCDto7coi9DA2D2SRtegNqNU4sd74"], "id": 1}
+    // --> {"jsonrpc": "2.0", "method": "blockchain.lookup_zkas", "params": ["BZHKGQ26bzmBithTQYTJtjo2QdCqpkR9tjSBopT4yf4o"], "id": 1}
     // <-- {"jsonrpc": "2.0", "result": [["Foo", "ABCD..."], ["Bar", "EFGH..."]], "id": 1}
     pub async fn blockchain_lookup_zkas(&self, id: u16, params: JsonValue) -> JsonResult {
         let params = params.get::<Vec<JsonValue>>().unwrap();
@@ -313,4 +313,110 @@ impl DarkfiNode {
 
         JsonResponse::new(JsonValue::Array(ret), id).into()
     }
+
+    // RPCAPI:
+    // Queries the blockchain database for a given contract state records.
+    // Returns the records value raw bytes as a `BTreeMap`.
+    //
+    // **Params:**
+    // * `array[0]`: base58-encoded contract ID string
+    // * `array[1]`: Contract tree name string
+    //
+    // **Returns:**
+    // * Records serialized `BTreeMap` encoded with base64
+    //
+    // --> {"jsonrpc": "2.0", "method": "blockchain.get_contract_state", "params": ["BZHK...", "tree"], "id": 1}
+    // <-- {"jsonrpc": "2.0", "result": "ABCD...", "id": 1}
+    pub async fn blockchain_get_contract_state(&self, id: u16, params: JsonValue) -> JsonResult {
+        let params = params.get::<Vec<JsonValue>>().unwrap();
+        if params.len() != 2 || !params[0].is_string() || !params[1].is_string() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+
+        let contract_id = params[0].get::<String>().unwrap();
+        let contract_id = match ContractId::from_str(contract_id) {
+            Ok(v) => v,
+            Err(e) => {
+                error!(target: "darkfid::rpc::blockchain_get_contract_state", "Error decoding string to ContractId: {}", e);
+                return JsonError::new(InvalidParams, None, id).into()
+            }
+        };
+
+        let tree_name = params[1].get::<String>().unwrap();
+
+        match self.validator.blockchain.contracts.get_state_tree_records(
+            &self.validator.blockchain.sled_db,
+            &contract_id,
+            tree_name,
+        ) {
+            Ok(records) => JsonResponse::new(
+                JsonValue::String(base64::encode(&serialize_async(&records).await)),
+                id,
+            )
+            .into(),
+            Err(e) => {
+                error!(target: "darkfid::rpc::blockchain_get_contract_state", "Failed fetching contract state records: {}", e);
+                server_error(RpcError::ContractStateNotFound, id, None)
+            }
+        }
+    }
+
+    // RPCAPI:
+    // Queries the blockchain database for a given contract state key raw bytes.
+    // Returns the record value raw bytes.
+    //
+    // **Params:**
+    // * `array[0]`: base58-encoded contract ID string
+    // * `array[1]`: Contract tree name string
+    // * `array[2]`: Key raw bytes, encoded with base64
+    //
+    // **Returns:**
+    // * Record value raw bytes encoded with base64
+    //
+    // --> {"jsonrpc": "2.0", "method": "blockchain.get_contract_state_key", "params": ["BZHK...", "tree", "ABCD..."], "id": 1}
+    // <-- {"jsonrpc": "2.0", "result": "ABCD...", "id": 1}
+    pub async fn blockchain_get_contract_state_key(
+        &self,
+        id: u16,
+        params: JsonValue,
+    ) -> JsonResult {
+        let params = params.get::<Vec<JsonValue>>().unwrap();
+        if params.len() != 3 ||
+            !params[0].is_string() ||
+            !params[1].is_string() ||
+            !params[2].is_string()
+        {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+
+        let contract_id = params[0].get::<String>().unwrap();
+        let contract_id = match ContractId::from_str(contract_id) {
+            Ok(v) => v,
+            Err(e) => {
+                error!(target: "darkfid::rpc::blockchain_get_contract_state_key", "Error decoding string to ContractId: {}", e);
+                return JsonError::new(InvalidParams, None, id).into()
+            }
+        };
+
+        let tree_name = params[1].get::<String>().unwrap();
+
+        let key_enc = params[2].get::<String>().unwrap().trim();
+        let Some(key) = base64::decode(key_enc) else {
+            error!(target: "darkfid::rpc::blockchain_get_contract_state_key", "Failed decoding base64 key");
+            return server_error(RpcError::ParseError, id, None)
+        };
+
+        match self.validator.blockchain.contracts.get_state_tree_value(
+            &self.validator.blockchain.sled_db,
+            &contract_id,
+            tree_name,
+            &key,
+        ) {
+            Ok(value) => JsonResponse::new(JsonValue::String(base64::encode(&value)), id).into(),
+            Err(e) => {
+                error!(target: "darkfid::rpc::blockchain_get_contract_state_key", "Failed fetching contract state key value: {}", e);
+                server_error(RpcError::ContractStateKeyNotFound, id, None)
+            }
+        }
+    }
 }

+ 47 - 1
src/blockchain/contract_store.rs

@@ -16,7 +16,7 @@ r* This program is distributed in the hope that it will be useful,
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use std::io::Cursor;
+use std::{collections::BTreeMap, io::Cursor};
 
 use darkfi_sdk::crypto::ContractId;
 use darkfi_serial::{deserialize, serialize};
@@ -207,6 +207,52 @@ impl ContractStore {
 
         Ok(contracts)
     }
+
+    /// Retrieve provided key value bytes from a contract's zkas sled tree.
+    pub fn get_state_tree_value(
+        &self,
+        db: &sled::Db,
+        contract_id: &ContractId,
+        tree_name: &str,
+        key: &[u8],
+    ) -> Result<Vec<u8>> {
+        debug!(target: "blockchain::contractstore", "Looking up state tree value for {}:{}", contract_id, tree_name);
+
+        // Grab the state tree
+        let state_tree = self.lookup(db, contract_id, tree_name)?;
+
+        // Grab the key value
+        match state_tree.get(key)? {
+            Some(value) => Ok(value.to_vec()),
+            None => Err(Error::DatabaseError(format!(
+                "State tree {}:{} doesn't contain key: {:?}",
+                contract_id, tree_name, key
+            ))),
+        }
+    }
+
+    /// Retrieve all records from a contract's zkas sled tree, as a `BTreeMap`.
+    /// Be careful as this will try to load everything in memory.
+    pub fn get_state_tree_records(
+        &self,
+        db: &sled::Db,
+        contract_id: &ContractId,
+        tree_name: &str,
+    ) -> Result<BTreeMap<Vec<u8>, Vec<u8>>> {
+        debug!(target: "blockchain::contractstore", "Looking up state tree records for {}:{}", contract_id, tree_name);
+
+        // Grab the state tree
+        let state_tree = self.lookup(db, contract_id, tree_name)?;
+
+        // Retrieve its records
+        let mut ret = BTreeMap::new();
+        for record in state_tree.iter() {
+            let (key, value) = record.unwrap();
+            ret.insert(key.to_vec(), value.to_vec());
+        }
+
+        Ok(ret)
+    }
 }
 
 /// Overlay structure over a [`ContractStore`] instance.