فهرست منبع

blockchain/tx_store: new tx location tree added

skoupidi 2 سال پیش
والد
کامیت
dad7577bed
4فایلهای تغییر یافته به همراه215 افزوده شده و 2 حذف شده
  1. 89 2
      src/blockchain/tx_store.rs
  2. 104 0
      src/runtime/import/util.rs
  3. 6 0
      src/runtime/vm_runtime.rs
  4. 16 0
      src/sdk/src/util.rs

+ 89 - 2
src/blockchain/tx_store.rs

@@ -25,6 +25,7 @@ use crate::{tx::Transaction, Error, Result};
 use super::{parse_record, parse_u64_key_record, SledDbOverlayPtr};
 
 const SLED_TX_TREE: &[u8] = b"_transactions";
+const SLED_TX_LOCATION_TREE: &[u8] = b"_transaction_location";
 const SLED_PENDING_TX_TREE: &[u8] = b"_pending_transactions";
 const SLED_PENDING_TX_ORDER_TREE: &[u8] = b"_pending_transactions_order";
 
@@ -36,6 +37,11 @@ pub struct TxStore {
     /// the key is the transaction hash, and the value is the serialized
     /// transaction.
     pub main: sled::Tree,
+    /// The `sled` tree storing the location of the blockchain's transactions
+    /// locations, where the key is the transaction hash, and the value is a
+    /// serialized tuple containing the height and the vector index of the
+    /// block the transaction is included.
+    pub location: sled::Tree,
     /// The `sled` tree storing all the node pending transactions, where
     /// the key is the transaction hash, and the value is the serialized
     /// transaction.
@@ -50,9 +56,10 @@ impl TxStore {
     /// Opens a new or existing `TxStore` on the given sled database.
     pub fn new(db: &sled::Db) -> Result<Self> {
         let main = db.open_tree(SLED_TX_TREE)?;
+        let location = db.open_tree(SLED_TX_LOCATION_TREE)?;
         let pending = db.open_tree(SLED_PENDING_TX_TREE)?;
         let pending_order = db.open_tree(SLED_PENDING_TX_ORDER_TREE)?;
-        Ok(Self { main, pending, pending_order })
+        Ok(Self { main, location, pending, pending_order })
     }
 
     /// Insert a slice of [`Transaction`] into the store's main tree.
@@ -62,6 +69,13 @@ impl TxStore {
         Ok(ret)
     }
 
+    /// Insert a slice of [`blake3::Hash`] into the store's location tree.
+    pub fn insert_location(&self, txs_hashes: &[blake3::Hash], block_height: u64) -> Result<()> {
+        let batch = self.insert_batch_location(txs_hashes, block_height)?;
+        self.location.apply_batch(batch)?;
+        Ok(())
+    }
+
     /// Insert a slice of [`Transaction`] into the store's pending txs tree.
     pub fn insert_pending(&self, transactions: &[Transaction]) -> Result<Vec<blake3::Hash>> {
         let (batch, ret) = self.insert_batch_pending(transactions)?;
@@ -70,7 +84,6 @@ impl TxStore {
     }
 
     /// Insert a slice of [`blake3::Hash`] into the store's pending txs order tree.
-    /// With sled, the operation is done as a batch.
     pub fn insert_pending_order(&self, txs_hashes: &[blake3::Hash]) -> Result<()> {
         let batch = self.insert_batch_pending_order(txs_hashes)?;
         self.pending_order.apply_batch(batch)?;
@@ -101,6 +114,25 @@ impl TxStore {
         Ok((batch, ret))
     }
 
+    /// Generate the sled batch corresponding to an insert to the location tree,
+    /// so caller can handle the write operation.
+    /// The tuple is built using the index of each location in the slice,
+    /// along with the provided block height
+    pub fn insert_batch_location(
+        &self,
+        txs_hashes: &[blake3::Hash],
+        block_height: u64,
+    ) -> Result<sled::Batch> {
+        let mut batch = sled::Batch::default();
+
+        for (index, tx_hash) in txs_hashes.iter().enumerate() {
+            let serialized = serialize(&(block_height, index as u64));
+            batch.insert(tx_hash.as_bytes(), serialized);
+        }
+
+        Ok(batch)
+    }
+
     /// Generate the sled batch corresponding to an insert to the pending txs tree,
     /// so caller can handle the write operation.
     /// The transactions are hashed with BLAKE3 and this hash is used as
@@ -185,6 +217,34 @@ impl TxStore {
         Ok(ret)
     }
 
+    /// Fetch given tx hashes locations from the store's location tree.
+    /// The resulting vector contains `Option`, which is `Some` if the tx
+    /// was found in the txstore, and otherwise it is `None`, if it has not.
+    /// The second parameter is a boolean which tells the function to fail in
+    /// case at least one tx was not found.
+    pub fn get_location(
+        &self,
+        tx_hashes: &[blake3::Hash],
+        strict: bool,
+    ) -> Result<Vec<Option<(u64, u64)>>> {
+        let mut ret = Vec::with_capacity(tx_hashes.len());
+
+        for tx_hash in tx_hashes {
+            if let Some(found) = self.location.get(tx_hash.as_bytes())? {
+                let location = deserialize(&found)?;
+                ret.push(Some(location));
+                continue
+            }
+            if strict {
+                let s = tx_hash.to_hex().as_str().to_string();
+                return Err(Error::TransactionNotFound(s))
+            }
+            ret.push(None);
+        }
+
+        Ok(ret)
+    }
+
     /// Fetch given tx hashes from the store's pending txs tree.
     /// The resulting vector contains `Option`, which is `Some` if the tx
     /// was found in the pending tx store, and otherwise it is `None`, if it has not.
@@ -226,6 +286,19 @@ impl TxStore {
         Ok(txs)
     }
 
+    /// Retrieve all transactions locations from the store's location tree in
+    /// the form of a tuple (`tx_hash`, (`block_height`, `index`)).
+    /// Be careful as this will try to load everything in memory.
+    pub fn get_all_location(&self) -> Result<Vec<(blake3::Hash, (u64, u64))>> {
+        let mut locations = vec![];
+
+        for location in self.location.iter() {
+            locations.push(parse_record(location.unwrap())?);
+        }
+
+        Ok(locations)
+    }
+
     /// Retrieve all transactions from the store's pending txs tree in the
     /// form of a HashMap with key the transaction hash and value the
     /// transaction itself.
@@ -309,6 +382,7 @@ pub struct TxStoreOverlay(SledDbOverlayPtr);
 impl TxStoreOverlay {
     pub fn new(overlay: &SledDbOverlayPtr) -> Result<Self> {
         overlay.lock().unwrap().open_tree(SLED_TX_TREE)?;
+        overlay.lock().unwrap().open_tree(SLED_TX_LOCATION_TREE)?;
         Ok(Self(overlay.clone()))
     }
 
@@ -371,4 +445,17 @@ impl TxStoreOverlay {
         }
         Ok(None)
     }
+
+    /// Fetch given tx hash location from the overlay's location tree.
+    /// This function uses raw bytes as input and doesn't deserialize the
+    /// retrieved value. The resulting vector contains `Option`, which is
+    /// `Some` if the location was found in the overlay, and otherwise it
+    /// is `None`, if it has not.
+    pub fn get_location_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_LOCATION_TREE, tx_hash)? {
+            return Ok(Some(found.to_vec()))
+        }
+        Ok(None)
+    }
 }

+ 104 - 0
src/runtime/import/util.rs

@@ -452,3 +452,107 @@ pub(crate) fn get_tx(mut ctx: FunctionEnvMut<Env>, ptr: WasmPtr<u8>) -> i64 {
     objects.push(return_data.to_vec());
     (objects.len() - 1) as i64
 }
+
+/// Reads a transaction location 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 location bytes vector in
+/// the environment. Otherwise, returns an error code.
+pub(crate) fn get_tx_location(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_location",
+            "[WASM] [{}] get_tx_location(): 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_location",
+            "[WASM] [{}] get_tx_location(): 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_location",
+            "[WASM] [{}] get_tx_location(): 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_location",
+                "[WASM] [{}] get_tx_location(): 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_location",
+            "[WASM] [{}] get_tx_location(): 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_location_raw(&hash) {
+        Ok(v) => v,
+        Err(e) => {
+            error!(
+                target: "runtime::util::get_tx_location",
+                "[WASM] [{}] get_tx_location(): 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_location",
+            "[WASM] [{}] get_tx_location(): 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

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

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

@@ -158,6 +158,21 @@ pub fn get_tx(hash: blake3::Hash) -> GenericResult<Option<Vec<u8>>> {
     parse_ret(ret)
 }
 
+/// Only metadata() and exec() can call this. Will return transaction
+/// location bytes by provided hash.
+///
+/// ```
+/// tx_location_bytes = get_tx_location(hash);
+/// (block_height, tx_index) = deserialize(&tx_location_bytes)?;
+/// ```
+pub fn get_tx_location(hash: blake3::Hash) -> GenericResult<Option<Vec<u8>>> {
+    let mut buf = vec![];
+    hash.encode(&mut buf)?;
+
+    let ret = unsafe { get_tx_location_(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;
@@ -170,4 +185,5 @@ extern "C" {
     fn get_blockchain_time_() -> i64;
     fn get_last_block_height_() -> i64;
     fn get_tx_(ptr: *const u8) -> i64;
+    fn get_tx_location_(ptr: *const u8) -> i64;
 }