Pārlūkot izejas kodu

validator|runtime: Add tx-local state

This adds a transaction-local, in-memory database in Runtime that
exists during single transaction execution.
x 5 mēneši atpakaļ
vecāks
revīzija
24e884106b

+ 1 - 0
Cargo.lock

@@ -1897,6 +1897,7 @@ dependencies = [
  "darkfi_deployooor_contract",
  "darkfi_money_contract",
  "num-bigint",
+ "parking_lot 0.12.5",
  "rand 0.8.5",
  "sled-overlay",
  "tracing",

+ 1 - 0
src/contract/test-harness/Cargo.toml

@@ -20,6 +20,7 @@ tracing = "0.1.44"
 tracing-subscriber = { version = "0.3.22", default-features = false, features = ["fmt"] }
 rand = "0.8.5"
 sled-overlay = "0.1.20"
+parking_lot = "0.12.5"
 
 [lints]
 workspace = true

+ 7 - 1
src/contract/test-harness/src/lib.rs

@@ -21,12 +21,13 @@ use std::{
     fs::OpenOptions,
     io::{Cursor, Write},
     slice,
+    sync::Arc,
     time::Instant,
 };
 
 use darkfi::{
     blockchain::{BlockInfo, Blockchain, BlockchainOverlay},
-    runtime::vm_runtime::Runtime,
+    runtime::vm_runtime::{Runtime, TxLocalState},
     tx::Transaction,
     util::{
         logger::{setup_test_logger, Level},
@@ -59,6 +60,7 @@ use darkfi_sdk::{
 };
 use darkfi_serial::{serialize, Encodable};
 use num_bigint::BigUint;
+use parking_lot::Mutex;
 use rand::rngs::OsRng;
 use sled_overlay::sled;
 use tracing::{debug, warn};
@@ -915,13 +917,17 @@ async fn benchmark_wasm_calls(
 ) -> Result<()> {
     let mut file = OpenOptions::new().create(true).append(true).open("bench.csv")?;
 
+    let tx_local_state = Arc::new(Mutex::new(TxLocalState::new()));
+
     let validator = validator.read().await;
     for (idx, call) in tx.calls.iter().enumerate() {
         let overlay = BlockchainOverlay::new(&validator.blockchain).expect("blockchain overlay");
         let wasm = overlay.lock().unwrap().contracts.get(call.data.contract_id)?;
+
         let mut runtime = Runtime::new(
             &wasm,
             overlay.clone(),
+            tx_local_state.clone(),
             call.data.contract_id,
             block_height,
             validator.consensus.module.target,

+ 21 - 0
src/runtime/vm_runtime.rs

@@ -18,6 +18,7 @@
 
 use std::{
     cell::{Cell, RefCell},
+    collections::BTreeMap,
     sync::Arc,
 };
 
@@ -29,6 +30,7 @@ use darkfi_sdk::{
     wasm, AsHex,
 };
 use darkfi_serial::serialize;
+use parking_lot::Mutex;
 use tracing::{debug, error, info};
 use wasmer::{
     imports, sys::CompilerConfig, wasmparser::Operator, AsStoreMut, AsStoreRef, Function,
@@ -78,12 +80,23 @@ impl ContractSection {
     }
 }
 
+/// Transaction-local state db.
+///
+/// This is an in-memory BTreeMap that works equivalently to the existing
+/// blockchain DB in contracts, except its lifetime is during a single
+/// transaction execution.
+pub type TxLocalState = BTreeMap<ContractId, BTreeMap<[u8; 32], BTreeMap<Vec<u8>, Vec<u8>>>>;
+
 /// The WASM VM runtime environment instantiated for every smart contract that runs.
 pub struct Env {
     /// Blockchain overlay access
     pub blockchain: BlockchainOverlayPtr,
     /// Overlay tree handles used with `db_*`
     pub db_handles: RefCell<Vec<DbHandle>>,
+    /// Transaction-local db handles used with `db_*_local`
+    pub local_db_handles: RefCell<Vec<DbHandle>>,
+    /// Transaction-local state
+    pub tx_local: Arc<Mutex<TxLocalState>>,
     /// The contract ID being executed
     pub contract_id: ContractId,
     /// The compiled wasm bincode being executed,
@@ -157,9 +170,11 @@ pub struct Runtime {
 
 impl Runtime {
     /// Create a new wasm runtime instance that contains the given wasm module.
+    #[allow(clippy::too_many_arguments)]
     pub fn new(
         wasm_bytes: &[u8],
         blockchain: BlockchainOverlayPtr,
+        tx_local: Arc<Mutex<TxLocalState>>,
         contract_id: ContractId,
         verifying_block_height: u32,
         block_target: u32,
@@ -197,8 +212,12 @@ impl Runtime {
 
         // Initialize data
         let db_handles = RefCell::new(vec![]);
+        let local_db_handles = RefCell::new(vec![]);
         let logs = RefCell::new(vec![]);
 
+        // Initialize a tx-local db for the calling contract
+        tx_local.lock().entry(contract_id).or_default();
+
         debug!(target: "runtime::vm_runtime", "Importing functions");
 
         let ctx = FunctionEnv::new(
@@ -206,6 +225,8 @@ impl Runtime {
             Env {
                 blockchain,
                 db_handles,
+                local_db_handles,
+                tx_local,
                 contract_id,
                 contract_bincode: wasm_bytes.to_vec(),
                 contract_section: ContractSection::Null,

+ 1 - 1
src/sdk/src/crypto/contract_id.rs

@@ -71,7 +71,7 @@ lazy_static! {
 }
 
 /// ContractId represents an on-chain identifier for a certain smart contract.
-#[derive(Copy, Clone, Debug, Eq, PartialEq, SerialEncodable, SerialDecodable)]
+#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, SerialEncodable, SerialDecodable)]
 pub struct ContractId(pallas::Base);
 
 impl ContractId {

+ 8 - 2
src/validator/utils.rs

@@ -16,18 +16,19 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use std::sync::LazyLock;
+use std::sync::{Arc, LazyLock};
 
 use darkfi_sdk::{
     crypto::{DAO_CONTRACT_ID, DEPLOYOOOR_CONTRACT_ID, MONEY_CONTRACT_ID},
     tx::TransactionHash,
 };
 use num_bigint::BigUint;
+use parking_lot::Mutex;
 use tracing::info;
 
 use crate::{
     blockchain::{BlockInfo, BlockchainOverlayPtr, Header},
-    runtime::vm_runtime::Runtime,
+    runtime::vm_runtime::{Runtime, TxLocalState},
     validator::{
         consensus::{Fork, Proposal},
         pow::PoWModule,
@@ -97,9 +98,14 @@ pub async fn deploy_native_contracts(
     for (call_idx, nc) in native_contracts.into_iter().enumerate() {
         info!(target: "validator::utils::deploy_native_contracts", "Deploying {} with ContractID {}", nc.0, nc.1);
 
+        // Create tx-local state. Here it remains unused since native
+        // contract deployments do not use tx-local state.
+        let tx_local_state = Arc::new(Mutex::new(TxLocalState::new()));
+
         let mut runtime = Runtime::new(
             &nc.2[..],
             overlay.clone(),
+            tx_local_state,
             nc.1,
             verifying_block_height,
             block_target,

+ 24 - 2
src/validator/verification.rs

@@ -16,7 +16,7 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use std::collections::HashMap;
+use std::{collections::HashMap, sync::Arc};
 
 use darkfi_sdk::{
     blockchain::{block_version, compute_fee},
@@ -30,6 +30,7 @@ use darkfi_sdk::{
 };
 use darkfi_serial::{deserialize_async, serialize_async, AsyncDecodable, AsyncEncodable};
 use num_bigint::BigUint;
+use parking_lot::Mutex;
 use sled_overlay::SledDbOverlayStateDiff;
 use smol::io::Cursor;
 use tracing::{debug, error, warn};
@@ -40,7 +41,7 @@ use crate::{
         Blockchain, BlockchainOverlayPtr, HeaderHash,
     },
     error::TxVerifyFailed,
-    runtime::vm_runtime::Runtime,
+    runtime::vm_runtime::{Runtime, TxLocalState},
     tx::{Transaction, MAX_TX_CALLS, MIN_TX_CALLS},
     validator::{
         consensus::{Consensus, Fork, Proposal, BLOCK_GAS_LIMIT},
@@ -447,9 +448,13 @@ pub async fn verify_producer_transaction(
     debug!(target: "validator::verification::verify_producer_transaction", "Instantiating WASM runtime");
     let wasm = overlay.lock().unwrap().contracts.get(call.data.contract_id)?;
 
+    // Create tx-local state. This lives through the entire tx.
+    let tx_local_state = Arc::new(Mutex::new(TxLocalState::new()));
+
     let mut runtime = Runtime::new(
         &wasm,
         overlay.clone(),
+        tx_local_state,
         call.data.contract_id,
         verifying_block_height,
         block_target,
@@ -574,9 +579,13 @@ pub async fn apply_producer_transaction(
     let call = &tx.calls[0];
     let wasm = overlay.lock().unwrap().contracts.get(call.data.contract_id)?;
 
+    // Create tx-local state. This lives through the entire tx.
+    let tx_local_state = Arc::new(Mutex::new(TxLocalState::new()));
+
     let mut runtime = Runtime::new(
         &wasm,
         overlay.clone(),
+        tx_local_state,
         call.data.contract_id,
         verifying_block_height,
         block_target,
@@ -705,6 +714,10 @@ pub async fn verify_transaction(
     // calculate their verification cost.
     let mut circuits_to_verify = vec![];
 
+    // Create the transaction-local state instance
+    // This state exists only during the single transaction verification.
+    let tx_local_state = Arc::new(Mutex::new(TxLocalState::new()));
+
     // Iterate over all calls to get the metadata
     for (idx, call) in tx.calls.iter().enumerate() {
         debug!(target: "validator::verification::verify_transaction", "Executing contract call {idx}");
@@ -726,9 +739,11 @@ pub async fn verify_transaction(
 
         debug!(target: "validator::verification::verify_transaction", "Instantiating WASM runtime");
         let wasm = overlay.lock().unwrap().contracts.get(call.data.contract_id)?;
+
         let mut runtime = Runtime::new(
             &wasm,
             overlay.clone(),
+            tx_local_state.clone(),
             call.data.contract_id,
             verifying_block_height,
             block_target,
@@ -813,6 +828,7 @@ pub async fn verify_transaction(
             let mut deploy_runtime = Runtime::new(
                 &deploy_params.wasm_bincode,
                 overlay.clone(),
+                tx_local_state.clone(),
                 deploy_cid,
                 verifying_block_height,
                 block_target,
@@ -943,15 +959,20 @@ pub async fn apply_transaction(
     let mut payload = vec![];
     tx.calls.encode_async(&mut payload).await?;
 
+    // Create tx-local state
+    let tx_local_state = Arc::new(Mutex::new(TxLocalState::new()));
+
     // Iterate over all calls to get the metadata
     for (idx, call) in tx.calls.iter().enumerate() {
         debug!(target: "validator::verification::apply_transaction", "Executing contract call {idx}");
 
         debug!(target: "validator::verification::apply_transaction", "Instantiating WASM runtime");
         let wasm = overlay.lock().unwrap().contracts.get(call.data.contract_id)?;
+
         let mut runtime = Runtime::new(
             &wasm,
             overlay.clone(),
+            tx_local_state.clone(),
             call.data.contract_id,
             verifying_block_height,
             block_target,
@@ -987,6 +1008,7 @@ pub async fn apply_transaction(
             let mut deploy_runtime = Runtime::new(
                 &deploy_params.wasm_bincode,
                 overlay.clone(),
+                tx_local_state.clone(),
                 deploy_cid,
                 verifying_block_height,
                 block_target,