mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-01-08 22:28:12 -05:00
runtime: Cleanup and add payload support to init()
This commit is contained in:
@@ -1,15 +1,13 @@
|
||||
use darkfi_sdk::{
|
||||
crypto::Nullifier,
|
||||
db::{db_init, db_lookup, db_get, db_begin_tx, db_set, db_end_tx},
|
||||
db::{db_begin_tx, db_end_tx, db_get, db_init, db_lookup, db_set},
|
||||
entrypoint,
|
||||
error::{ContractError, ContractResult},
|
||||
error::ContractResult,
|
||||
initialize, msg,
|
||||
pasta::pallas,
|
||||
state::{nullifier_exists, set_update},
|
||||
state::set_update,
|
||||
tx::Transaction,
|
||||
update_state,
|
||||
};
|
||||
use darkfi_serial::{deserialize, serialize, SerialDecodable, SerialEncodable, ReadExt, Decodable};
|
||||
use darkfi_serial::{deserialize, serialize, SerialDecodable, SerialEncodable};
|
||||
|
||||
/// Available functions for this contract.
|
||||
/// We identify them with the first byte passed in through the payload.
|
||||
@@ -48,7 +46,7 @@ pub struct FooUpdate {
|
||||
}
|
||||
|
||||
initialize!(init_contract);
|
||||
fn init_contract() -> ContractResult {
|
||||
fn init_contract(_ix: &[u8]) -> ContractResult {
|
||||
msg!("wakeup wagies!");
|
||||
db_init("wagies")?;
|
||||
|
||||
@@ -73,7 +71,8 @@ fn process_instruction(ix: &[u8]) -> ContractResult {
|
||||
let tx_data = &ix[1..];
|
||||
// ...
|
||||
let (func_call_index, tx): (u32, Transaction) = deserialize(tx_data)?;
|
||||
let call_data: FooCallData = deserialize(&tx.func_calls[func_call_index as usize].call_data)?;
|
||||
let call_data: FooCallData =
|
||||
deserialize(&tx.func_calls[func_call_index as usize].call_data)?;
|
||||
msg!("call_data {{ a: {}, b: {} }}", call_data.a, call_data.b);
|
||||
// ...
|
||||
let update = FooUpdate { name: "john_doe".to_string(), age: 110 };
|
||||
|
||||
@@ -16,15 +16,14 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use darkfi::{
|
||||
crypto::contract_id::ContractId,
|
||||
runtime::{util::serialize_payload, vm_runtime::Runtime},
|
||||
Result,
|
||||
use darkfi::{crypto::contract_id::ContractId, runtime::vm_runtime::Runtime, Result};
|
||||
use darkfi_sdk::{
|
||||
pasta::pallas,
|
||||
tx::{FuncCall, Transaction},
|
||||
};
|
||||
use darkfi_sdk::{crypto::nullifier::Nullifier, pasta::pallas, tx::{Transaction, FuncCall}};
|
||||
use darkfi_serial::{serialize, Encodable, WriteExt};
|
||||
|
||||
use smart_contract::FooCallData;
|
||||
use smart_contract::{FooCallData, Function};
|
||||
|
||||
#[test]
|
||||
fn run_contract() -> Result<()> {
|
||||
@@ -52,43 +51,40 @@ fn run_contract() -> Result<()> {
|
||||
let contract_id = ContractId::new(pallas::Base::from(1));
|
||||
let mut runtime = Runtime::new(&wasm_bytes, contract_id)?;
|
||||
|
||||
runtime.deploy()?;
|
||||
// Deploy function to initialize the smart contract state.
|
||||
// Here we pass an empty payload, but it's possible to feed in arbitrary data.
|
||||
runtime.deploy(&[])?;
|
||||
|
||||
// =============================================
|
||||
// Build some kind of payload to show an example
|
||||
// =============================================
|
||||
let tx = Transaction {
|
||||
func_calls: vec![
|
||||
FuncCall {
|
||||
contract_id: pallas::Base::from(110),
|
||||
func_id: pallas::Base::from(4),
|
||||
call_data: serialize(&FooCallData { a: 777, b: 666 }),
|
||||
proofs: Vec::new()
|
||||
}
|
||||
],
|
||||
signatures: Vec::new()
|
||||
func_calls: vec![FuncCall {
|
||||
contract_id: pallas::Base::from(110),
|
||||
func_id: pallas::Base::from(4),
|
||||
call_data: serialize(&FooCallData { a: 777, b: 666 }),
|
||||
proofs: Vec::new(),
|
||||
}],
|
||||
signatures: Vec::new(),
|
||||
};
|
||||
let func_call_index: u32 = 0;
|
||||
|
||||
let mut payload = Vec::new();
|
||||
// Prepend the func id = 0x00
|
||||
// Selects which path executes in the contract.
|
||||
payload.write_u8(0x00);
|
||||
payload.write_u8(Function::Foo as u8)?;
|
||||
// Write the actual payload data
|
||||
payload.write_u32(func_call_index);
|
||||
payload.write_u32(func_call_index)?;
|
||||
tx.encode(&mut payload)?;
|
||||
|
||||
// ============================================================
|
||||
// Serialize the payload into the runtime format and execute it
|
||||
// ============================================================
|
||||
runtime.exec(&serialize_payload(&payload))?;
|
||||
runtime.exec(&payload)?;
|
||||
|
||||
// =====================================================
|
||||
// If exec was successful, try to apply the state change
|
||||
// =====================================================
|
||||
runtime.apply()?;
|
||||
//Ok(())
|
||||
|
||||
//runtime.exec(&serialize_payload(&payload))?;
|
||||
|
||||
//runtime.apply()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -16,17 +16,10 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use darkfi_sdk::crypto::{MerkleNode, Nullifier};
|
||||
use log::{debug, error};
|
||||
use wasmer::{AsStoreRef, FunctionEnvMut, WasmPtr};
|
||||
use log::debug;
|
||||
use wasmer::{FunctionEnvMut, WasmPtr};
|
||||
|
||||
use crate::{
|
||||
node::state::ProgramState,
|
||||
runtime::{
|
||||
memory::MemoryManipulation,
|
||||
vm_runtime::{ContractSection, Env},
|
||||
},
|
||||
};
|
||||
use crate::runtime::vm_runtime::{ContractSection, Env};
|
||||
|
||||
pub(crate) fn set_update(mut ctx: FunctionEnvMut<Env>, ptr: WasmPtr<u8>, len: u32) -> i32 {
|
||||
let env = ctx.data();
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
use darkfi_sdk::crypto::{MerkleNode, Nullifier};
|
||||
use log::{debug, error};
|
||||
use wasmer::{AsStoreRef, FunctionEnvMut, WasmPtr};
|
||||
use log::error;
|
||||
use wasmer::{FunctionEnvMut, WasmPtr};
|
||||
|
||||
use crate::{
|
||||
node::state::ProgramState,
|
||||
runtime::{
|
||||
memory::MemoryManipulation,
|
||||
vm_runtime::{ContractSection, Env},
|
||||
},
|
||||
};
|
||||
use crate::runtime::vm_runtime::{ContractSection, Env};
|
||||
|
||||
/// Only deploy() can call this. Creates a new database instance for this contract.
|
||||
///
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use log::{error, warn};
|
||||
use wasmer::{AsStoreRef, FunctionEnvMut, WasmPtr};
|
||||
use log::error;
|
||||
use wasmer::{FunctionEnvMut, WasmPtr};
|
||||
|
||||
use crate::runtime::{memory::MemoryManipulation, vm_runtime::Env};
|
||||
use crate::runtime::vm_runtime::Env;
|
||||
|
||||
/// Host function for logging strings.
|
||||
/// This is injected into the runtime with wasmer's `imports!` macro.
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
use wasmer::{MemoryView, WasmPtr};
|
||||
|
||||
use crate::{Error, Result};
|
||||
use crate::Result;
|
||||
|
||||
pub trait MemoryManipulation {
|
||||
fn write_slice(&self, value_slice: &[u8], mem_offset: u32) -> Result<()>;
|
||||
|
||||
@@ -22,8 +22,5 @@ pub mod vm_runtime;
|
||||
/// VM memory access (read/write)
|
||||
pub(crate) mod memory;
|
||||
|
||||
/// Utility functions
|
||||
pub mod util;
|
||||
|
||||
/// Imported host functions
|
||||
pub(crate) mod import;
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2022 Dyne.org foundation
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/// Serialize contract payload to format accepted by the runtime entrypoint.
|
||||
/// We keep the same payload as a slice of bytes, and prepend it with a
|
||||
/// little-endian u64 to tell the payload's length.
|
||||
pub fn serialize_payload(payload: &[u8]) -> Vec<u8> {
|
||||
let mut out = vec![];
|
||||
|
||||
let len = payload.len() as u64;
|
||||
out.extend_from_slice(&len.to_le_bytes());
|
||||
out.extend_from_slice(payload);
|
||||
|
||||
out
|
||||
}
|
||||
@@ -37,7 +37,6 @@ use super::{
|
||||
import,
|
||||
//chain_state::{is_valid_merkle, nullifier_exists, set_update},
|
||||
memory::MemoryManipulation,
|
||||
util::serialize_payload,
|
||||
};
|
||||
use crate::{crypto::contract_id::ContractId, Error, Result};
|
||||
|
||||
@@ -213,30 +212,46 @@ impl Runtime {
|
||||
Ok(Self { instance, store, ctx })
|
||||
}
|
||||
|
||||
pub fn deploy(&mut self) -> Result<()> {
|
||||
/// This function runs when a smart contract is initially deployed, or re-deployed.
|
||||
/// The runtime will look for an [`INITIALIZE`] symbol in the wasm code, and execute
|
||||
/// it if found. Optionally, it is possible to pass in a payload for any kind of special
|
||||
/// instructions the developer wants to manage in the initialize function.
|
||||
/// This process is supposed to set up the sled db trees for storing the smart contract
|
||||
/// state, and it can create, delete, modify, read, and write to databases it's allowed to.
|
||||
/// The permissions for this are handled by the `ContractId` in the sled db API so we
|
||||
/// assume that the contract is only able to do write operations on its own sled trees.
|
||||
pub fn deploy(&mut self, payload: &[u8]) -> Result<()> {
|
||||
let mut env_mut = self.ctx.as_mut(&mut self.store);
|
||||
env_mut.contract_section = ContractSection::Deploy;
|
||||
|
||||
debug!(target: "wasm_runtime::run", "Getting initialize function");
|
||||
// Serialize the payload for the format the wasm runtime is expecting.
|
||||
let payload = Self::serialize_payload(payload);
|
||||
|
||||
// Allocate enough memory for the payload and copy it into the memory.
|
||||
let pages_required = payload.len() / WASM_PAGE_SIZE + 1;
|
||||
self.set_memory_page_size(pages_required as u32)?;
|
||||
self.copy_to_memory(&payload)?;
|
||||
|
||||
debug!(target: "wasm_runtime::deploy", "Getting initialize function");
|
||||
let entrypoint = self.instance.exports.get_function(INITIALIZE)?;
|
||||
|
||||
debug!(target: "wasm_runtime::run", "Executing wasm");
|
||||
let ret = match entrypoint.call(&mut self.store, &[]) {
|
||||
debug!(target: "wasm_runtime::deploy", "Executing wasm");
|
||||
let ret = match entrypoint.call(&mut self.store, &[Value::I32(0 as i32)]) {
|
||||
Ok(retvals) => {
|
||||
self.print_logs();
|
||||
debug!(target: "wasm_runtime::run", "{}", self.gas_info());
|
||||
debug!(target: "wasm_runtime::deploy", "{}", self.gas_info());
|
||||
retvals
|
||||
}
|
||||
Err(e) => {
|
||||
self.print_logs();
|
||||
debug!(target: "wasm_runtime::run", "{}", self.gas_info());
|
||||
debug!(target: "wasm_runtime::deploy", "{}", self.gas_info());
|
||||
// WasmerRuntimeError panics are handled here. Return from run() immediately.
|
||||
return Err(e.into())
|
||||
}
|
||||
};
|
||||
|
||||
debug!(target: "wasm_runtime::run", "wasm executed successfully");
|
||||
debug!(target: "wasm_runtime::run", "Contract returned: {:?}", ret[0]);
|
||||
debug!(target: "wasm_runtime::deploy", "wasm executed successfully");
|
||||
debug!(target: "wasm_runtime::deploy", "Contract returned: {:?}", ret[0]);
|
||||
|
||||
let retval = match ret[0] {
|
||||
Value::I64(v) => v as u64,
|
||||
@@ -251,37 +266,43 @@ impl Runtime {
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the hardcoded `ENTRYPOINT` function with the given payload as input.
|
||||
/// This funcion runs when someone wants to execute a smart contract.
|
||||
/// The runtime will look for an [`ENTRYPOINT`] symbol in the wasm code, and
|
||||
/// execute it if found. A payload is also passed as an instruction that can
|
||||
/// be used inside the vm by the runtime.
|
||||
pub fn exec(&mut self, payload: &[u8]) -> Result<()> {
|
||||
let mut env_mut = self.ctx.as_mut(&mut self.store);
|
||||
env_mut.contract_section = ContractSection::Exec;
|
||||
|
||||
// Serialize the payload for the format the wasm runtime is expecting.
|
||||
let payload = Self::serialize_payload(payload);
|
||||
|
||||
// Allocate enough memory for the payload and copy it into the memory.
|
||||
let pages_required = payload.len() / WASM_PAGE_SIZE + 1;
|
||||
self.set_memory_page_size(pages_required as u32)?;
|
||||
self.copy_to_memory(&payload)?;
|
||||
|
||||
self.copy_to_memory(payload)?;
|
||||
|
||||
debug!(target: "wasm_runtime::run", "Getting entrypoint function");
|
||||
debug!(target: "wasm_runtime::exec", "Getting entrypoint function");
|
||||
let entrypoint = self.instance.exports.get_function(ENTRYPOINT)?;
|
||||
|
||||
debug!(target: "wasm_runtime::run", "Executing wasm");
|
||||
debug!(target: "wasm_runtime::exec", "Executing wasm");
|
||||
// We pass 0 to entrypoint() which is the location of the payload data in the memory
|
||||
let ret = match entrypoint.call(&mut self.store, &[Value::I32(0 as i32)]) {
|
||||
Ok(retvals) => {
|
||||
self.print_logs();
|
||||
debug!(target: "wasm_runtime::run", "{}", self.gas_info());
|
||||
debug!(target: "wasm_runtime::exec", "{}", self.gas_info());
|
||||
retvals
|
||||
}
|
||||
Err(e) => {
|
||||
self.print_logs();
|
||||
debug!(target: "wasm_runtime::run", "{}", self.gas_info());
|
||||
debug!(target: "wasm_runtime::exec", "{}", self.gas_info());
|
||||
// WasmerRuntimeError panics are handled here. Return from run() immediately.
|
||||
return Err(e.into())
|
||||
}
|
||||
};
|
||||
|
||||
debug!(target: "wasm_runtime::run", "wasm executed successfully");
|
||||
debug!(target: "wasm_runtime::run", "Contract returned: {:?}", ret[0]);
|
||||
debug!(target: "wasm_runtime::exec", "wasm executed successfully");
|
||||
debug!(target: "wasm_runtime::exec", "Contract returned: {:?}", ret[0]);
|
||||
|
||||
let retval = match ret[0] {
|
||||
Value::I64(v) => v as u64,
|
||||
@@ -294,43 +315,48 @@ impl Runtime {
|
||||
}
|
||||
}
|
||||
|
||||
/// This function runs after successful execution of [`exec`] and tries to
|
||||
/// apply the state change to the sled databases.
|
||||
/// The runtime will lok for an [`UPDATE`] symbol in the wasm code, and execute
|
||||
/// it if found. The function does not take an arbitrary payload, but just takes
|
||||
/// a state update from `env` and passes it into the wasm runtime.
|
||||
pub fn apply(&mut self) -> Result<()> {
|
||||
let mut env_mut = self.ctx.as_mut(&mut self.store);
|
||||
env_mut.contract_section = ContractSection::Update;
|
||||
|
||||
// Take the update data from env, and serialize it for the format the wasm
|
||||
// runtime is expecting.
|
||||
let update_data = env_mut.contract_update.take().unwrap();
|
||||
// FIXME: Less realloc
|
||||
let mut payload = vec![update_data.0];
|
||||
let mut payload = Vec::with_capacity(1 + update_data.1.len());
|
||||
payload.extend_from_slice(&[update_data.0]);
|
||||
payload.extend_from_slice(&update_data.1);
|
||||
let payload = serialize_payload(&payload);
|
||||
let payload = Self::serialize_payload(&payload);
|
||||
|
||||
// TODO: Test if this works when state update is larger than the initial payload
|
||||
// The question is if we need to allocate more memory or if it's ok to just
|
||||
// overwrite from zero (and even if overwrite - is there enough space?)
|
||||
// Allocate enough memory for the payload and copy it into the memory.
|
||||
let pages_required = payload.len() / WASM_PAGE_SIZE + 1;
|
||||
self.set_memory_page_size(pages_required as u32)?;
|
||||
self.copy_to_memory(&payload)?;
|
||||
|
||||
debug!(target: "wasm_runtime::run", "Getting initialize function");
|
||||
debug!(target: "wasm_runtime::apply", "Getting update function");
|
||||
let entrypoint = self.instance.exports.get_function(UPDATE)?;
|
||||
|
||||
debug!(target: "wasm_runtime::run", "Executing wasm");
|
||||
debug!(target: "wasm_runtime::apply", "Executing wasm");
|
||||
let ret = match entrypoint.call(&mut self.store, &[Value::I32(0 as i32)]) {
|
||||
Ok(retvals) => {
|
||||
self.print_logs();
|
||||
debug!(target: "wasm_runtime::run", "{}", self.gas_info());
|
||||
debug!(target: "wasm_runtime::apply", "{}", self.gas_info());
|
||||
retvals
|
||||
}
|
||||
Err(e) => {
|
||||
self.print_logs();
|
||||
debug!(target: "wasm_runtime::run", "{}", self.gas_info());
|
||||
debug!(target: "wasm_runtime::apply", "{}", self.gas_info());
|
||||
// WasmerRuntimeError panics are handled here. Return from run() immediately.
|
||||
return Err(e.into())
|
||||
}
|
||||
};
|
||||
|
||||
debug!(target: "wasm_runtime::run", "wasm executed successfully");
|
||||
debug!(target: "wasm_runtime::run", "Contract returned: {:?}", ret[0]);
|
||||
debug!(target: "wasm_runtime::apply", "wasm executed successfully");
|
||||
debug!(target: "wasm_runtime::apply", "Contract returned: {:?}", ret[0]);
|
||||
|
||||
let retval = match ret[0] {
|
||||
Value::I64(v) => v as u64,
|
||||
@@ -389,4 +415,15 @@ impl Runtime {
|
||||
let memory_view = env.memory_view(&self.store);
|
||||
memory_view.write_slice(payload, 0)
|
||||
}
|
||||
|
||||
/// Serialize contract payload to the format accepted by the runtime functions.
|
||||
/// We keep the same payload as a slice of bytes, and prepend it with a
|
||||
/// little-endian u64 to tell the payload's length.
|
||||
fn serialize_payload(payload: &[u8]) -> Vec<u8> {
|
||||
let payload_len = payload.len();
|
||||
let mut out = Vec::with_capacity(8 + payload_len);
|
||||
out.extend_from_slice(&(payload_len as u64).to_le_bytes());
|
||||
out.extend_from_slice(payload);
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
use super::{
|
||||
crypto::{MerkleNode, Nullifier},
|
||||
error::{ContractError, GenericResult},
|
||||
};
|
||||
use super::error::GenericResult;
|
||||
|
||||
type DbHandle = u32;
|
||||
type TxHandle = u32;
|
||||
|
||||
@@ -26,8 +26,10 @@ macro_rules! initialize {
|
||||
($process_init:ident) => {
|
||||
/// # Safety
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn __initialize() -> u64 {
|
||||
match $process_init() {
|
||||
pub unsafe extern "C" fn __initialize(input: *mut u8) -> u64 {
|
||||
let instruction_data = $crate::entrypoint::deserialize(input);
|
||||
|
||||
match $process_init(&instruction_data) {
|
||||
Ok(()) => $crate::entrypoint::SUCCESS,
|
||||
Err(e) => e.into(),
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
use super::{
|
||||
crypto::{MerkleNode, Nullifier},
|
||||
error::{ContractError, ContractResult},
|
||||
error::ContractError,
|
||||
};
|
||||
|
||||
pub fn set_update(update_data: &[u8]) -> Result<(), ContractError> {
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
use darkfi_serial::{SerialDecodable, SerialEncodable};
|
||||
use pasta_curves::{
|
||||
group::ff::{Field, PrimeField},
|
||||
pallas,
|
||||
};
|
||||
use pasta_curves::pallas;
|
||||
|
||||
type ContractId = pallas::Base;
|
||||
type FuncId = pallas::Base;
|
||||
|
||||
Reference in New Issue
Block a user