Skip to content

Commit

Permalink
Add MemoryClient (#36)
Browse files Browse the repository at this point in the history
This struct should emulate a functional client and take iterators of
`Transaction`s to mutate a storage.

This implementation should define the commit/revert/persist/rollback
strategy depending on the receipts returned by the VM.
  • Loading branch information
vlopes11 authored Oct 19, 2021
1 parent de04e6c commit 2397abc
Show file tree
Hide file tree
Showing 18 changed files with 180 additions and 72 deletions.
86 changes: 86 additions & 0 deletions src/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
use crate::error::InterpreterError;
use crate::interpreter::Interpreter;

use fuel_tx::{Receipt, Transaction};

mod storage;

pub use storage::MemoryStorage;

#[derive(Debug, Default, Clone)]
pub struct MemoryClient {
storage: MemoryStorage,
}

impl From<MemoryStorage> for MemoryClient {
fn from(s: MemoryStorage) -> Self {
Self::new(s)
}
}

impl AsRef<MemoryStorage> for MemoryClient {
fn as_ref(&self) -> &MemoryStorage {
&self.storage
}
}

impl AsMut<MemoryStorage> for MemoryClient {
fn as_mut(&mut self) -> &mut MemoryStorage {
&mut self.storage
}
}

impl MemoryClient {
pub const fn new(storage: MemoryStorage) -> Self {
Self { storage }
}

pub fn transition<I: IntoIterator<Item = Transaction>>(
&mut self,
txs: I,
) -> Result<Vec<Receipt>, InterpreterError> {
let mut interpreter = Interpreter::with_storage(&mut self.storage);

// A transaction that is considered valid will be reverted if the runtime
// returns an error.
//
// The exception is `InterpreterError::ValidationError` since this means the
// whole set of transactions must also fail.
//
// All the other variants of `InterpreterError` are supressed in this function
// and they will produce isolated `RVRT` operations.
let receipts = txs.into_iter().try_fold(vec![], |mut receipts, tx| {
match interpreter.transact(tx) {
Ok(state) => {
receipts.extend(state.receipts());

if !state.receipts().iter().any(|r| matches!(r, Receipt::Revert { .. })) {
interpreter.as_mut().commit();
} else {
interpreter.as_mut().revert();
}

Ok(receipts)
}

Err(InterpreterError::ValidationError(e)) => {
interpreter.as_mut().rollback();

Err(InterpreterError::ValidationError(e))
}

// TODO VM is to return a `RVRT` receipt on runtime error. This way, the return of
// `transact` should be `Err` only if `InterpreterError::ValidationError` happens
Err(_) => {
interpreter.as_mut().revert();

Ok(receipts)
}
}
})?;

self.storage.persist();

Ok(receipts)
}
}
78 changes: 52 additions & 26 deletions src/data/memory.rs → src/client/storage.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::InterpreterStorage;
use crate::contract::Contract;
use crate::crypto;
use crate::storage::InterpreterStorage;

use fuel_storage::{MerkleRoot, MerkleStorage, Storage};
use fuel_tx::crypto::Hasher;
Expand All @@ -11,35 +11,59 @@ use std::borrow::Cow;
use std::collections::HashMap;
use std::convert::Infallible;

#[derive(Debug, Clone)]
pub struct MemoryStorage {
block_height: u32,
coinbase: Address,
#[derive(Debug, Default, Clone, PartialEq, Eq)]
struct MemoryStorageInner {
contracts: HashMap<ContractId, Contract>,
balances: HashMap<(ContractId, Color), Word>,
contract_state: HashMap<(ContractId, Bytes32), Bytes32>,
contract_code_root: HashMap<ContractId, (Salt, Bytes32)>,
}

#[derive(Debug, Clone)]
pub struct MemoryStorage {
block_height: u32,
coinbase: Address,
memory: MemoryStorageInner,
transacted: MemoryStorageInner,
persisted: MemoryStorageInner,
}

impl MemoryStorage {
pub fn new(block_height: u32, coinbase: Address) -> Self {
Self {
block_height,
coinbase,
contracts: Default::default(),
balances: Default::default(),
contract_state: Default::default(),
contract_code_root: Default::default(),
memory: Default::default(),
transacted: Default::default(),
persisted: Default::default(),
}
}

pub fn contract_state(&self, contract: &ContractId, key: &Bytes32) -> Cow<'_, Bytes32> {
const DEFAULT_STATE: Bytes32 = Bytes32::zeroed();

<Self as MerkleStorage<ContractId, Bytes32, Bytes32>>::get(&self, contract, key)
<Self as MerkleStorage<ContractId, Bytes32, Bytes32>>::get(self, contract, key)
.expect("Infallible")
.unwrap_or(Cow::Borrowed(&DEFAULT_STATE))
}

pub fn commit(&mut self) {
self.transacted = self.memory.clone();
}

pub fn revert(&mut self) {
self.memory = self.transacted.clone();
}

pub fn rollback(&mut self) {
self.memory = self.persisted.clone();
self.transacted = self.persisted.clone();
}

pub fn persist(&mut self) {
self.memory = self.transacted.clone();
self.persisted = self.transacted.clone();
}
}

impl Default for MemoryStorage {
Expand All @@ -55,63 +79,64 @@ impl Storage<ContractId, Contract> for MemoryStorage {
type Error = Infallible;

fn insert(&mut self, key: &ContractId, value: &Contract) -> Result<Option<Contract>, Infallible> {
Ok(self.contracts.insert(*key, value.clone()))
Ok(self.memory.contracts.insert(*key, value.clone()))
}

fn remove(&mut self, key: &ContractId) -> Result<Option<Contract>, Infallible> {
Ok(self.contracts.remove(key))
Ok(self.memory.contracts.remove(key))
}

fn get(&self, key: &ContractId) -> Result<Option<Cow<'_, Contract>>, Infallible> {
Ok(self.contracts.get(key).map(Cow::Borrowed))
Ok(self.memory.contracts.get(key).map(Cow::Borrowed))
}

fn contains_key(&self, key: &ContractId) -> Result<bool, Infallible> {
Ok(self.contracts.contains_key(key))
Ok(self.memory.contracts.contains_key(key))
}
}

impl Storage<ContractId, (Salt, Bytes32)> for MemoryStorage {
type Error = Infallible;

fn insert(&mut self, key: &ContractId, value: &(Salt, Bytes32)) -> Result<Option<(Salt, Bytes32)>, Infallible> {
Ok(self.contract_code_root.insert(*key, *value))
Ok(self.memory.contract_code_root.insert(*key, *value))
}

fn remove(&mut self, key: &ContractId) -> Result<Option<(Salt, Bytes32)>, Infallible> {
Ok(self.contract_code_root.remove(key))
Ok(self.memory.contract_code_root.remove(key))
}

fn get(&self, key: &ContractId) -> Result<Option<Cow<'_, (Salt, Bytes32)>>, Infallible> {
Ok(self.contract_code_root.get(key).map(Cow::Borrowed))
Ok(self.memory.contract_code_root.get(key).map(Cow::Borrowed))
}

fn contains_key(&self, key: &ContractId) -> Result<bool, Infallible> {
Ok(self.contract_code_root.contains_key(key))
Ok(self.memory.contract_code_root.contains_key(key))
}
}

impl MerkleStorage<ContractId, Color, Word> for MemoryStorage {
type Error = Infallible;

fn insert(&mut self, parent: &ContractId, key: &Color, value: &Word) -> Result<Option<Word>, Infallible> {
Ok(self.balances.insert((*parent, *key), *value))
Ok(self.memory.balances.insert((*parent, *key), *value))
}

fn get(&self, parent: &ContractId, key: &Color) -> Result<Option<Cow<'_, Word>>, Infallible> {
Ok(self.balances.get(&(*parent, *key)).copied().map(Cow::Owned))
Ok(self.memory.balances.get(&(*parent, *key)).copied().map(Cow::Owned))
}

fn remove(&mut self, parent: &ContractId, key: &Color) -> Result<Option<Word>, Infallible> {
Ok(self.balances.remove(&(*parent, *key)))
Ok(self.memory.balances.remove(&(*parent, *key)))
}

fn contains_key(&self, parent: &ContractId, key: &Color) -> Result<bool, Infallible> {
Ok(self.balances.contains_key(&(*parent, *key)))
Ok(self.memory.balances.contains_key(&(*parent, *key)))
}

fn root(&mut self, parent: &ContractId) -> Result<MerkleRoot, Infallible> {
let root = self
.memory
.balances
.iter()
.filter_map(|((contract, color), balance)| (contract == parent).then(|| (color, balance)))
Expand All @@ -127,23 +152,24 @@ impl MerkleStorage<ContractId, Bytes32, Bytes32> for MemoryStorage {
type Error = Infallible;

fn insert(&mut self, parent: &ContractId, key: &Bytes32, value: &Bytes32) -> Result<Option<Bytes32>, Infallible> {
Ok(self.contract_state.insert((*parent, *key), *value))
Ok(self.memory.contract_state.insert((*parent, *key), *value))
}

fn get(&self, parent: &ContractId, key: &Bytes32) -> Result<Option<Cow<'_, Bytes32>>, Infallible> {
Ok(self.contract_state.get(&(*parent, *key)).map(Cow::Borrowed))
Ok(self.memory.contract_state.get(&(*parent, *key)).map(Cow::Borrowed))
}

fn remove(&mut self, parent: &ContractId, key: &Bytes32) -> Result<Option<Bytes32>, Infallible> {
Ok(self.contract_state.remove(&(*parent, *key)))
Ok(self.memory.contract_state.remove(&(*parent, *key)))
}

fn contains_key(&self, parent: &ContractId, key: &Bytes32) -> Result<bool, Infallible> {
Ok(self.contract_state.contains_key(&(*parent, *key)))
Ok(self.memory.contract_state.contains_key(&(*parent, *key)))
}

fn root(&mut self, parent: &ContractId) -> Result<MerkleRoot, Infallible> {
let root = self
.memory
.contract_state
.iter()
.filter_map(|((contract, key), value)| (contract == parent).then(|| (key, value)))
Expand Down
2 changes: 1 addition & 1 deletion src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ impl TryFrom<&Transaction> for Contract {
} => witnesses
.get(*bytecode_witness_index as usize)
.map(|c| c.as_ref().into())
.ok_or(ValidationError::TransactionCreateBytecodeWitnessIndex.into()),
.ok_or_else(|| ValidationError::TransactionCreateBytecodeWitnessIndex.into()),

_ => Err(ValidationError::TransactionScriptOutputContractCreated { index: 0 }.into()),
}
Expand Down
4 changes: 2 additions & 2 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ impl From<io::Error> for InterpreterError {
}
}

impl Into<InterpreterError> for Infallible {
fn into(self) -> InterpreterError {
impl From<Infallible> for InterpreterError {
fn from(_i: Infallible) -> InterpreterError {
unreachable!()
}
}
8 changes: 7 additions & 1 deletion src/interpreter.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::call::CallFrame;
use crate::client::MemoryStorage;
use crate::consts::*;
use crate::context::Context;
use crate::data::MemoryStorage;
use crate::state::Debugger;

use fuel_tx::{Receipt, Transaction};
Expand Down Expand Up @@ -100,3 +100,9 @@ impl<S> From<Interpreter<S>> for Transaction {
vm.tx
}
}

impl<S> AsMut<S> for Interpreter<S> {
fn as_mut(&mut self) -> &mut S {
&mut self.storage
}
}
6 changes: 3 additions & 3 deletions src/interpreter/blockchain.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use super::Interpreter;
use crate::consts::*;
use crate::data::InterpreterStorage;
use crate::error::InterpreterError;
use crate::storage::InterpreterStorage;

use fuel_tx::Input;
use fuel_types::{Address, Bytes32, Bytes8, Color, ContractId, RegisterId, Word};
Expand Down Expand Up @@ -153,7 +153,7 @@ where

self.registers[ra] = self
.storage
.merkle_contract_state(&contract, key)?
.merkle_contract_state(contract, key)?
.map(|state| unsafe { Bytes8::from_slice_unchecked(state.as_ref().as_ref()).into() })
.map(Word::from_be_bytes)
.unwrap_or(0);
Expand All @@ -175,7 +175,7 @@ where

let state = self
.storage
.merkle_contract_state(&contract, key)?
.merkle_contract_state(contract, key)?
.map(|s| s.into_owned())
.unwrap_or_default();

Expand Down
2 changes: 1 addition & 1 deletion src/interpreter/contract.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use super::Interpreter;
use crate::contract::Contract;
use crate::data::InterpreterStorage;
use crate::error::InterpreterError;
use crate::storage::InterpreterStorage;

use fuel_types::{Color, ContractId, Word};

Expand Down
2 changes: 1 addition & 1 deletion src/interpreter/executors/debug.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::data::InterpreterStorage;
use crate::error::InterpreterError;
use crate::interpreter::Interpreter;
use crate::state::ProgramState;
use crate::storage::InterpreterStorage;

impl<S> Interpreter<S>
where
Expand Down
2 changes: 1 addition & 1 deletion src/interpreter/executors/instruction.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::data::InterpreterStorage;
use crate::error::InterpreterError;
use crate::interpreter::Interpreter;
use crate::state::ExecuteState;
use crate::storage::InterpreterStorage;

use fuel_asm::Opcode;
use fuel_types::Word;
Expand Down
2 changes: 1 addition & 1 deletion src/interpreter/executors/main.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use crate::consts::*;
use crate::contract::Contract;
use crate::crypto;
use crate::data::InterpreterStorage;
use crate::error::InterpreterError;
use crate::interpreter::{Interpreter, MemoryRange};
use crate::state::{ExecuteState, ProgramState, StateTransition, StateTransitionRef};
use crate::storage::InterpreterStorage;

use fuel_asm::Opcode;
use fuel_tx::{Input, Output, Receipt, Transaction};
Expand Down
2 changes: 1 addition & 1 deletion src/interpreter/executors/predicate.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::consts::*;
use crate::data::InterpreterStorage;
use crate::error::InterpreterError;
use crate::interpreter::{Interpreter, MemoryRange};
use crate::state::{ExecuteState, ProgramState};
use crate::storage::InterpreterStorage;

use fuel_asm::Opcode;

Expand Down
2 changes: 1 addition & 1 deletion src/interpreter/flow.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use super::Interpreter;
use crate::call::{Call, CallFrame};
use crate::consts::*;
use crate::data::InterpreterStorage;
use crate::error::InterpreterError;
use crate::state::ProgramState;
use crate::storage::InterpreterStorage;

use fuel_tx::crypto::Hasher;
use fuel_tx::{Input, Receipt};
Expand Down
Loading

0 comments on commit 2397abc

Please sign in to comment.