Browse Source

wasm: add example db usage to example/smart-contract/src/lib.rs

x 3 years ago
parent
commit
92b2cd1e02
5 changed files with 146 additions and 30 deletions
  1. 25 11
      example/smart-contract/src/lib.rs
  2. 37 8
      src/runtime/import/db.rs
  3. 8 0
      src/runtime/vm_runtime.rs
  4. 70 11
      src/sdk/src/db.rs
  5. 6 0
      src/sdk/src/error.rs

+ 25 - 11
example/smart-contract/src/lib.rs

@@ -1,5 +1,6 @@
 use darkfi_sdk::{
     crypto::Nullifier,
+    db::{db_init, db_lookup, db_get, db_begin_tx, db_set, db_end_tx},
     entrypoint,
     error::{ContractError, ContractResult},
     initialize, msg,
@@ -42,19 +43,22 @@ pub struct BarArgs {
 #[derive(SerialEncodable, SerialDecodable)]
 pub struct FooUpdate {
     pub name: String,
-    pub y: u32,
+    pub age: u32,
 }
 
 initialize!(init_contract);
 fn init_contract() -> ContractResult {
-    // db_exists(field_name) -> bool
-    // db_delete(field_name)
-    //     panics if NAME does not exist
-    //     abort deployment
-    // db_create(field_name)
-    //
-    // internal_db_name = blake3_hash(contract_id, field_name)
-    msg!("init!");
+    msg!("wakeup wagies!");
+    db_init("wagies")?;
+
+    // Lets write a value in there
+    let tx_handle = db_begin_tx()?;
+    db_set(tx_handle, "jason_gulag".as_bytes(), serialize(&110))?;
+    let db_handle = db_lookup("wagies")?;
+    db_end_tx(db_handle, tx_handle)?;
+
+    // Host will clear delete the batches array after calling this func.
+
     Ok(())
 }
 
@@ -69,12 +73,17 @@ fn process_instruction(ix: &[u8]) -> ContractResult {
             // ...
             let args: FooArgs = deserialize(tx_data)?;
             // ...
-            let update = FooUpdate { name: "john_doe".to_string(), y: 110 };
+            let update = FooUpdate { name: "john_doe".to_string(), age: 110 };
 
             let mut update_data = vec![Function::Foo as u8];
             update_data.extend_from_slice(&serialize(&update));
             set_update(&update_data)?;
             msg!("update is set!");
+
+            // Example: try to get a value from the db
+            let db_handle = db_lookup("wagies")?;
+            let age_data = db_get(db_handle, "jason_gulag".as_bytes())?;
+            msg!("wagie age data: {:?}", age_data);
         }
         Function::Bar => {
             let tx_data = &ix[1..];
@@ -119,7 +128,12 @@ fn process_update(update_data: &[u8]) -> ContractResult {
     match Function::from(update_data[0]) {
         Function::Foo => {
             let update: FooUpdate = deserialize(&update_data[1..])?;
-            // update.apply()
+
+            // Write the wagie to the db
+            let tx_handle = db_begin_tx()?;
+            db_set(tx_handle, update.name.as_bytes(), serialize(&update.age))?;
+            let db_handle = db_lookup("wagies")?;
+            db_end_tx(db_handle, tx_handle)?;
         }
         _ => unreachable!(),
     }

+ 37 - 8
src/runtime/import/db.rs

@@ -40,15 +40,44 @@ pub(crate) fn db_init(mut ctx: FunctionEnvMut<Env>, ptr: WasmPtr<u8>, len: u32)
     }
 }
 
+/// Everyone can call this. Lookups up a database handle from its name.
+///
+/// ```
+///     type DbHandle = u32;
+///     db_lookup(db_name) -> DbHandle
+/// ```
+pub(crate) fn db_lookup(mut ctx: FunctionEnvMut<Env>, ptr: WasmPtr<u8>, len: u32) -> i32 {
+    let env = ctx.data();
+    match env.contract_section {
+        ContractSection::Deploy | ContractSection::Exec | ContractSection::Update => {
+            let env = ctx.data();
+            let memory_view = env.memory_view(&ctx);
+
+            match ptr.read_utf8_string(&memory_view, len) {
+                Ok(db_name) => {
+                    // db_name = blake3_hash(contract_id, db_name)
+                    return 110;
+                }
+                Err(_) => {
+                    error!(target: "wasm_runtime::drk_log", "Failed to read UTF-8 string from VM memory");
+                    return -2;
+                }
+            }
+            0
+        }
+        _ => -1,
+    }
+}
+
 /// Everyone can call this. Will read a key from the key-value store.
 ///
 /// ```
 ///     value = db_get(db_handle, key);
 /// ```
-pub(crate) fn db_get(mut ctx: FunctionEnvMut<Env>, ptr: WasmPtr<u8>, len: u32) -> i32 {
+pub(crate) fn db_get(mut ctx: FunctionEnvMut<Env>) -> i32 {
     let env = ctx.data();
     match env.contract_section {
-        ContractSection::Update => 0,
+        ContractSection::Exec => 0,
         _ => -1,
     }
 }
@@ -58,10 +87,10 @@ pub(crate) fn db_get(mut ctx: FunctionEnvMut<Env>, ptr: WasmPtr<u8>, len: u32) -
 /// ```
 ///     tx_handle = db_begin_tx();
 /// ```
-pub(crate) fn db_begin_tx(mut ctx: FunctionEnvMut<Env>, ptr: WasmPtr<u8>, len: u32) -> i32 {
+pub(crate) fn db_begin_tx(mut ctx: FunctionEnvMut<Env>) -> i32 {
     let env = ctx.data();
     match env.contract_section {
-        ContractSection::Update => 0,
+        ContractSection::Deploy | ContractSection::Update => 0,
         _ => -1,
     }
 }
@@ -71,10 +100,10 @@ pub(crate) fn db_begin_tx(mut ctx: FunctionEnvMut<Env>, ptr: WasmPtr<u8>, len: u
 /// ```
 ///     db_set(tx_handle, key, value);
 /// ```
-pub(crate) fn db_set(mut ctx: FunctionEnvMut<Env>, ptr: WasmPtr<u8>, len: u32) -> i32 {
+pub(crate) fn db_set(mut ctx: FunctionEnvMut<Env>) -> i32 {
     let env = ctx.data();
     match env.contract_section {
-        ContractSection::Update => 0,
+        ContractSection::Deploy | ContractSection::Update => 0,
         _ => -1,
     }
 }
@@ -84,10 +113,10 @@ pub(crate) fn db_set(mut ctx: FunctionEnvMut<Env>, ptr: WasmPtr<u8>, len: u32) -
 /// ```
 ///     db_end_tx(db_handle, tx_handle);
 /// ```
-pub(crate) fn db_end_tx(mut ctx: FunctionEnvMut<Env>, ptr: WasmPtr<u8>, len: u32) -> i32 {
+pub(crate) fn db_end_tx(mut ctx: FunctionEnvMut<Env>) -> i32 {
     let env = ctx.data();
     match env.contract_section {
-        ContractSection::Update => 0,
+        ContractSection::Deploy | ContractSection::Update => 0,
         _ => -1,
     }
 }

+ 8 - 0
src/runtime/vm_runtime.rs

@@ -172,6 +172,12 @@ impl Runtime {
                         import::db::db_init,
                     ),
 
+                    "db_lookup_" => Function::new_typed_with_env(
+                        &mut store,
+                        &ctx,
+                        import::db::db_lookup,
+                    ),
+
                     "db_get_" => Function::new_typed_with_env(
                         &mut store,
                         &ctx,
@@ -239,6 +245,8 @@ impl Runtime {
 
         match retval {
             entrypoint::SUCCESS => Ok(()),
+            // FIXME: we should be able to see the error returned from the contract
+            // We can put sdk::Error inside of this.
             _ => Err(Error::ContractInitError(retval)),
         }
     }

+ 70 - 11
src/sdk/src/db.rs

@@ -12,15 +12,33 @@ type TxHandle = u32;
 ///     type DbHandle = u32;
 ///     db_init(db_name) -> DbHandle
 /// ```
-pub fn db_init(db_name: &str) -> GenericResult<DbHandle> {
-    // FIXME: how do I return the u32 db handle from db_init?
-    // I also want the status (whether an error occurred or success).
+pub fn db_init(db_name: &str) -> GenericResult<()> {
     #[cfg(target_arch = "wasm32")]
     unsafe {
-        return match db_init_(message.as_ptr(), message.len() as u32) {
-            0 => Ok(110),
+        return match db_init_(db_name.as_ptr(), db_name.len() as u32) {
+            0 => Ok(()),
             -1 => Err(ContractError::CallerAccessDenied),
-            -2 => Err(ContractError::DbInitFailed)
+            -2 => Err(ContractError::DbInitFailed),
+            _ => unreachable!(),
+        }
+    }
+
+    #[cfg(not(target_arch = "wasm32"))]
+    todo!("{}", db_name);
+}
+
+pub fn db_lookup(db_name: &str) -> GenericResult<DbHandle> {
+    #[cfg(target_arch = "wasm32")]
+    unsafe {
+        return match db_lookup_(db_name.as_ptr(), db_name.len() as u32) {
+            handle => {
+                if handle < 0 {
+                    unreachable!();
+                }
+                Ok(handle as u32)
+            },
+            -1 => Err(ContractError::CallerAccessDenied),
+            -2 => Err(ContractError::DbNotFound),
         }
     }
 
@@ -34,7 +52,17 @@ pub fn db_init(db_name: &str) -> GenericResult<DbHandle> {
 ///     value = db_get(db_handle, key);
 /// ```
 pub fn db_get(db_handle: DbHandle, key: &[u8]) -> GenericResult<Vec<u8>> {
-    Ok(Vec::new())
+    #[cfg(target_arch = "wasm32")]
+    unsafe {
+        return match db_get_() {
+            0 => Ok(Vec::new()),
+            -1 => Err(ContractError::CallerAccessDenied),
+            _ => unreachable!(),
+        }
+    }
+
+    #[cfg(not(target_arch = "wasm32"))]
+    todo!("db_get");
 }
 
 /// Only update() can call this. Starts an atomic transaction.
@@ -43,7 +71,17 @@ pub fn db_get(db_handle: DbHandle, key: &[u8]) -> GenericResult<Vec<u8>> {
 ///     tx_handle = db_begin_tx();
 /// ```
 pub fn db_begin_tx() -> GenericResult<TxHandle> {
-    Ok(4)
+    #[cfg(target_arch = "wasm32")]
+    unsafe {
+        return match db_begin_tx_() {
+            0 => Ok(4),
+            -1 => Err(ContractError::CallerAccessDenied),
+            _ => unreachable!(),
+        }
+    }
+
+    #[cfg(not(target_arch = "wasm32"))]
+    todo!("db_begin_tx");
 }
 
 /// Only update() can call this. Set a value within the transaction.
@@ -53,7 +91,17 @@ pub fn db_begin_tx() -> GenericResult<TxHandle> {
 /// ```
 pub fn db_set(tx_handle: TxHandle, key: &[u8], value: Vec<u8>) -> GenericResult<()> {
     // Check entry for tx_handle is not None
-    Ok(())
+    #[cfg(target_arch = "wasm32")]
+    unsafe {
+        return match db_set_() {
+            0 => Ok(()),
+            -1 => Err(ContractError::CallerAccessDenied),
+            _ => unreachable!(),
+        }
+    }
+
+    #[cfg(not(target_arch = "wasm32"))]
+    todo!("db_set");
 }
 
 /// Only update() can call this. This writes the atomic tx to the database.
@@ -63,7 +111,17 @@ pub fn db_set(tx_handle: TxHandle, key: &[u8], value: Vec<u8>) -> GenericResult<
 /// ```
 pub fn db_end_tx(db_handle: DbHandle, tx_handle: TxHandle) -> GenericResult<()> {
     // Don't forget to set the entry for the tx in the table to empty.
-    Ok(())
+    #[cfg(target_arch = "wasm32")]
+    unsafe {
+        return match db_end_tx_() {
+            0 => Ok(()),
+            -1 => Err(ContractError::CallerAccessDenied),
+            _ => unreachable!(),
+        }
+    }
+
+    #[cfg(not(target_arch = "wasm32"))]
+    todo!("db_end_tx");
 }
 
 #[cfg(target_arch = "wasm32")]
@@ -73,7 +131,8 @@ extern "C" {
     fn nullifier_exists_(ptr: *const u8, len: u32) -> i32;
     fn is_valid_merkle_(ptr: *const u8, len: u32) -> i32;
 
-    fn db_init_(ptr: *const u8, len: usize) -> i32;
+    fn db_init_(ptr: *const u8, len: u32) -> i32;
+    fn db_lookup_(ptr: *const u8, len: u32) -> i32;
     fn db_get_() -> i32;
     fn db_begin_tx_() -> i32;
     fn db_set_() -> i32;

+ 6 - 0
src/sdk/src/error.rs

@@ -53,6 +53,9 @@ pub enum ContractError {
 
     #[error("Caller access was denied")]
     CallerAccessDenied,
+
+    #[error("Db not found")]
+    DbNotFound,
 }
 
 /// Builtin return values occupy the upper 32 bits
@@ -72,6 +75,7 @@ pub const VALID_MERKLE_CHECK: u64 = to_builtin!(6);
 pub const UPDATE_ALREADY_SET: u64 = to_builtin!(7);
 pub const DB_INIT_FAILED: u64 = to_builtin!(8);
 pub const CALLER_ACCESS_DENIED: u64 = to_builtin!(9);
+pub const DB_NOT_FOUND: u64 = to_builtin!(10);
 
 impl From<ContractError> for u64 {
     fn from(err: ContractError) -> Self {
@@ -84,6 +88,7 @@ impl From<ContractError> for u64 {
             ContractError::UpdateAlreadySet => UPDATE_ALREADY_SET,
             ContractError::DbInitFailed => DB_INIT_FAILED,
             ContractError::CallerAccessDenied => CALLER_ACCESS_DENIED,
+            ContractError::DbNotFound => DB_NOT_FOUND,
             ContractError::Custom(error) => {
                 if error == 0 {
                     CUSTOM_ZERO
@@ -107,6 +112,7 @@ impl From<u64> for ContractError {
             UPDATE_ALREADY_SET => Self::UpdateAlreadySet,
             DB_INIT_FAILED => Self::DbInitFailed,
             CALLER_ACCESS_DENIED => Self::CallerAccessDenied,
+            DB_NOT_FOUND => Self::DbNotFound,
             _ => Self::Custom(error as u32),
         }
     }