Эх сурвалжийг харах

sdk: fn get_tx(hash) added

skoupidi 2 жил өмнө
parent
commit
7d4151c230

+ 12 - 0
src/blockchain/tx_store.rs

@@ -184,6 +184,18 @@ impl TxStoreOverlay {
 
         Ok(ret)
     }
+
+    /// Fetch given tx hash from the overlay. This function uses
+    /// raw bytes as input and doesn't deserialize the retrieved value.
+    /// The resulting vector contains `Option`, which is `Some` if the tx
+    /// was found in the overlay, and otherwise it is `None`, if it has not.
+    pub fn get_raw(&self, tx_hash: &[u8; blake3::OUT_LEN]) -> Result<Option<Vec<u8>>> {
+        let lock = self.0.lock().unwrap();
+        if let Some(found) = lock.get(SLED_TX_TREE, tx_hash)? {
+            return Ok(Some(found.to_vec()))
+        }
+        Ok(None)
+    }
 }
 
 /// The `PendingTxStore` is a `sled` tree storing all the node pending

+ 108 - 1
src/runtime/import/util.rs

@@ -16,7 +16,10 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use log::error;
+use std::io::Cursor;
+
+use darkfi_serial::Decodable;
+use log::{debug, error};
 use wasmer::{FunctionEnvMut, WasmPtr};
 
 use super::acl::acl_allow;
@@ -320,3 +323,107 @@ pub(crate) fn get_last_block_height(mut ctx: FunctionEnvMut<Env>) -> i64 {
 
     (objects.len() - 1) as i64
 }
+
+/// Reads a transaction by hash from the transactions store.
+///
+/// This function can be called from the Exec or Metadata [`ContractSection`].
+///
+/// On success, returns the length of the transaction bytes vector in the environment.
+/// Otherwise, returns an error code.
+pub(crate) fn get_tx(mut ctx: FunctionEnvMut<Env>, ptr: WasmPtr<u8>) -> i64 {
+    let (env, mut store) = ctx.data_and_store_mut();
+    let cid = env.contract_id;
+
+    if let Err(e) = acl_allow(env, &[ContractSection::Exec, ContractSection::Metadata]) {
+        error!(
+            target: "runtime::util::get_tx",
+            "[WASM] [{}] get_tx(): Called in unauthorized section: {}", cid, e,
+        );
+        return darkfi_sdk::error::CALLER_ACCESS_DENIED
+    }
+
+    // Subtract used gas. Here we count the length of the looked-up hash.
+    env.subtract_gas(&mut store, blake3::OUT_LEN as u64);
+
+    // Ensure that it is possible to read memory
+    let memory_view = env.memory_view(&store);
+    let Ok(mem_slice) = ptr.slice(&memory_view, blake3::OUT_LEN as u32) else {
+        error!(
+            target: "runtime::util::get_tx",
+            "[WASM] [{}] get_tx(): Failed to make slice from ptr", cid,
+        );
+        return darkfi_sdk::error::DB_GET_FAILED
+    };
+
+    let mut buf = vec![0_u8; blake3::OUT_LEN];
+    if let Err(e) = mem_slice.read_slice(&mut buf) {
+        error!(
+            target: "runtime::util::get_tx",
+            "[WASM] [{}] get_tx(): Failed to read from memory slice: {}", cid, e,
+        );
+        return darkfi_sdk::error::DB_GET_FAILED
+    };
+
+    let mut buf_reader = Cursor::new(buf);
+
+    // Decode hash bytes for transaction that we wish to retrieve
+    let hash: [u8; blake3::OUT_LEN] = match Decodable::decode(&mut buf_reader) {
+        Ok(v) => v,
+        Err(e) => {
+            error!(
+                target: "runtime::util::get_tx",
+                "[WASM] [{}] get_tx(): Failed to decode hash from vec: {}", cid, e,
+            );
+            return darkfi_sdk::error::DB_GET_FAILED
+        }
+    };
+
+    // Make sure there are no trailing bytes in the buffer. This means we've used all data that was
+    // supplied.
+    if buf_reader.position() != blake3::OUT_LEN as u64 {
+        error!(
+            target: "runtime::util::get_tx",
+            "[WASM] [{}] get_tx(): Trailing bytes in argument stream", cid,
+        );
+        return darkfi_sdk::error::DB_GET_FAILED
+    }
+
+    // Retrieve transaction using the `hash`
+    let ret = match env.blockchain.lock().unwrap().transactions.get_raw(&hash) {
+        Ok(v) => v,
+        Err(e) => {
+            error!(
+                target: "runtime::util::get_tx",
+                "[WASM] [{}] get_tx(): Internal error getting from tree: {}", cid, e,
+            );
+            return darkfi_sdk::error::DB_GET_FAILED
+        }
+    };
+
+    // Return special error if the data is empty
+    let Some(return_data) = ret else {
+        debug!(
+            target: "runtime::util::get_tx",
+            "[WASM] [{}] get_tx(): Return data is empty", cid,
+        );
+        return darkfi_sdk::error::DB_GET_EMPTY
+    };
+
+    if return_data.len() > u32::MAX as usize {
+        return darkfi_sdk::error::DATA_TOO_LARGE
+    }
+
+    // Subtract used gas. Here we count the length of the data read from db.
+    env.subtract_gas(&mut store, return_data.len() as u64);
+
+    // Copy the data (Vec<u8>) to the VM by pushing it to the objects Vector.
+    let mut objects = env.objects.borrow_mut();
+    if objects.len() == u32::MAX as usize {
+        return darkfi_sdk::error::DATA_TOO_LARGE
+    }
+
+    // Return the length of the objects Vector.
+    // This is the location of the data that was retrieved and pushed
+    objects.push(return_data.to_vec());
+    (objects.len() - 1) as i64
+}

+ 6 - 0
src/runtime/vm_runtime.rs

@@ -310,6 +310,12 @@ impl Runtime {
                     &ctx,
                     import::util::get_last_block_height,
                 ),
+
+                "get_tx_" => Function::new_typed_with_env(
+                    &mut store,
+                    &ctx,
+                    import::util::get_tx,
+                ),
             }
         };
 

+ 18 - 0
src/sdk/src/util.rs

@@ -16,6 +16,8 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
+use darkfi_serial::Encodable;
+
 use super::error::{ContractError, GenericResult};
 
 /// Calls the `set_return_data` WASM function. Returns Ok(()) on success.
@@ -117,6 +119,21 @@ pub fn get_last_block_height() -> GenericResult<Option<Vec<u8>>> {
     parse_ret(ret)
 }
 
+/// Only metadata() and exec() can call this. Will return transaction
+/// bytes by provided hash.
+///
+/// ```
+/// tx_bytes = get_tx(hash);
+/// tx = deserialize(&tx_bytes)?;
+/// ```
+pub fn get_tx(hash: blake3::Hash) -> GenericResult<Option<Vec<u8>>> {
+    let mut buf = vec![];
+    hash.encode(&mut buf)?;
+
+    let ret = unsafe { get_tx_(buf.as_ptr()) };
+    parse_ret(ret)
+}
+
 extern "C" {
     fn set_return_data_(ptr: *const u8, len: u32) -> i64;
     fn put_object_bytes_(ptr: *const u8, len: u32) -> i64;
@@ -127,4 +144,5 @@ extern "C" {
     fn get_verifying_block_height_epoch_() -> u64;
     fn get_blockchain_time_() -> i64;
     fn get_last_block_height_() -> i64;
+    fn get_tx_(ptr: *const u8) -> i64;
 }