Skip to content

Commit

Permalink
roots for enabling direct ledger access
Browse files Browse the repository at this point in the history
  • Loading branch information
heytdep committed Mar 20, 2024
1 parent 2aac0a0 commit dfa0486
Show file tree
Hide file tree
Showing 7 changed files with 153 additions and 31 deletions.
4 changes: 2 additions & 2 deletions rs-zephyr-env/src/budget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use anyhow::Result;
use std::{cell::RefCell, rc::Rc};
use wasmi::{Store, errors::FuelError};

use crate::{db::database::ZephyrDatabase, host::Host, ZephyrStandard};
use crate::{db::{database::ZephyrDatabase, ledger::LedgerStateRead}, host::Host, ZephyrStandard};

const STANDARD_FUEL: u64 = 1_000_000_000;
const STANDARD_WRITE_MAX: usize = 64_000;
Expand Down Expand Up @@ -60,7 +60,7 @@ impl ZephyrStandard for Budget {

impl Budget {
/// Allocates the maximum fuel to the provided store object.
pub fn infer_fuel<DB: ZephyrDatabase>(&self, store: &mut Store<Host<DB>>) -> Result<(), FuelError> {
pub fn infer_fuel<DB: ZephyrDatabase, L: LedgerStateRead>(&self, store: &mut Store<Host<DB, L>>) -> Result<(), FuelError> {
store.add_fuel(self.0.borrow().limits.fuel)
}
}
82 changes: 82 additions & 0 deletions rs-zephyr-env/src/db/ledger.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
//! This module defines the interaction between the ZephyrVM
//! and Stellar's ledger state. This implementation is stricter
//! than the [`ZephyrDatabase`] implementation because it is not
//! implementation agnostic, as we're always talking about the Stellar
//! ledger.

use std::{cell::RefCell, rc::Rc};

use anyhow::Result;
use stellar_xdr::next::{ContractDataDurability, LedgerEntry, ScAddress, ScVal};

use crate::{ZephyrMock, ZephyrStandard};

#[derive(Debug)]
pub struct ContractDataEntry {
pub contract_id: ScAddress,
pub key: ScVal,
pub entry: LedgerEntry,
pub durability: ContractDataDurability,
pub last_modified: i32
}

/// Reads state from the Stellar Ledger.
pub trait LedgerStateRead {
/// Returns a vector of Contract Data Entries given a set of contract addresses.
fn read_contract_data_entries_by_contract_ids(&self, contracts: impl IntoIterator<Item = ScAddress>) -> Vec<ContractDataEntry>;

/// Returns a vector of contract instance entries given a set of contract addresses.
fn read_contract_instance_by_contract_ids(&self, contracts: impl IntoIterator<Item = ScAddress>) -> Vec<ContractDataEntry>;

/// Returns a contract instance entry given a contract address.
fn read_contract_instance_by_contract_id(&self, contract: ScAddress) -> Option<ContractDataEntry>;

/// Returns a contract data entry given a contract address and a ledger key.
fn read_contract_data_entry_by_contract_id_and_key(&self, contract: ScAddress, key: ScVal) -> Option<ContractDataEntry>;
}

#[derive(Clone)]
pub struct LedgerImpl<L: LedgerStateRead> {
/// Implementor's ledger.
pub ledger: Box<L>,
}

/// Wrapper of the database implementation.
#[derive(Clone)]
pub struct Ledger<L: LedgerStateRead>(pub(crate) LedgerImpl<L>);

impl<L: LedgerStateRead + ZephyrStandard> ZephyrStandard for LedgerImpl<L> {
fn zephyr_standard() -> Result<Self>
where
Self: Sized {
Ok(Self {
ledger: Box::new(L::zephyr_standard()?)
})
}
}

impl<L: LedgerStateRead + ZephyrStandard> ZephyrStandard for Ledger<L> {
fn zephyr_standard() -> Result<Self>
where
Self: Sized {
Ok(Self(LedgerImpl::zephyr_standard()?))
}
}

impl<L: LedgerStateRead + ZephyrMock> ZephyrMock for LedgerImpl<L> {
fn mocked() -> Result<Self>
where
Self: Sized {
Ok(Self {
ledger: Box::new(L::mocked()?)
})
}
}

impl<L: LedgerStateRead + ZephyrMock> ZephyrMock for Ledger<L> {
fn mocked() -> Result<Self>
where
Self: Sized {
Ok(Self(LedgerImpl::mocked()?))
}
}
2 changes: 1 addition & 1 deletion rs-zephyr-env/src/db/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@

pub mod database;
pub mod shield;

pub mod ledger;
41 changes: 30 additions & 11 deletions rs-zephyr-env/src/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

use anyhow::{Result, anyhow};
use rs_zephyr_common::{DatabaseError, ZephyrStatus};
use stellar_xdr::next::{Hash, ScAddress, ScVal};

//use sha2::{Digest, Sha256};
use std::{
Expand All @@ -15,8 +16,7 @@ use wasmi::{core::Pages, Caller, Func, Memory, Store, Value};
use crate::{
budget::Budget,
db::{
database::{Database, DatabasePermissions, WhereCond, ZephyrDatabase},
shield::ShieldedStore,
database::{Database, DatabasePermissions, WhereCond, ZephyrDatabase}, ledger::{Ledger, LedgerStateRead}, shield::ShieldedStore
},
error::HostError,
stack::Stack,
Expand Down Expand Up @@ -72,7 +72,7 @@ impl ZephyrStandard for EntryPointInfo {

/// Zephyr Host State Implementation.
#[derive(Clone)]
pub struct HostImpl<DB: ZephyrDatabase> {
pub struct HostImpl<DB: ZephyrDatabase, L: LedgerStateRead> {
/// Host id.
pub id: i64,

Expand All @@ -86,25 +86,28 @@ pub struct HostImpl<DB: ZephyrDatabase> {
/// Database implementation.
pub database: RefCell<Database<DB>>,

/// Ledger state.
pub ledger: Ledger<L>,

/// Budget implementation.
pub budget: RefCell<Budget>,

/// Entry point info.
pub entry_point_info: RefCell<EntryPointInfo>,

/// VM context.
pub context: RefCell<VmContext<DB>>,
pub context: RefCell<VmContext<DB, L>>,

/// Host pseudo stack implementation.
pub stack: RefCell<Stack>,
}

/// Zephyr Host State.
#[derive(Clone)]
pub struct Host<DB: ZephyrDatabase>(Rc<HostImpl<DB>>); // We wrap [`HostImpl`] here inside an rc pointer for multi ownership.
pub struct Host<DB: ZephyrDatabase, L: LedgerStateRead>(Rc<HostImpl<DB, L>>); // We wrap [`HostImpl`] here inside an rc pointer for multi ownership.

#[allow(dead_code)]
impl<DB: ZephyrDatabase + ZephyrStandard> Host<DB> {
impl<DB: ZephyrDatabase + ZephyrStandard, L: LedgerStateRead + ZephyrStandard> Host<DB, L> {
/// Creates a standard Host object starting from a given
/// host ID. The host ID is the only relation between the VM
/// and the entity it is bound to. For instance, in Mercury
Expand All @@ -116,6 +119,7 @@ impl<DB: ZephyrDatabase + ZephyrStandard> Host<DB> {
latest_close: RefCell::new(None),
shielded_store: RefCell::new(ShieldedStore::default()),
database: RefCell::new(Database::zephyr_standard()?),
ledger: Ledger::zephyr_standard()?,
budget: RefCell::new(Budget::zephyr_standard()?),
entry_point_info: RefCell::new(EntryPointInfo::zephyr_standard()?),
context: RefCell::new(VmContext::zephyr_standard()?),
Expand All @@ -124,7 +128,7 @@ impl<DB: ZephyrDatabase + ZephyrStandard> Host<DB> {
}
}

impl<DB: ZephyrDatabase + ZephyrMock> ZephyrMock for Host<DB> {
impl<DB: ZephyrDatabase + ZephyrMock, L: LedgerStateRead + ZephyrMock> ZephyrMock for Host<DB, L> {
/// Creates a Host object designed to be used in tests with potentially
/// mocked data such as host id, databases and context.
fn mocked() -> Result<Self> {
Expand All @@ -133,6 +137,7 @@ impl<DB: ZephyrDatabase + ZephyrMock> ZephyrMock for Host<DB> {
latest_close: RefCell::new(None),
shielded_store: RefCell::new(ShieldedStore::default()),
database: RefCell::new(Database::mocked()?),
ledger: Ledger::mocked()?,
budget: RefCell::new(Budget::zephyr_standard()?),
entry_point_info: RefCell::new(EntryPointInfo::zephyr_standard()?),
context: RefCell::new(VmContext::mocked()?),
Expand All @@ -157,7 +162,7 @@ pub struct FunctionInfo {
}

#[allow(dead_code)]
impl<DB: ZephyrDatabase + Clone> Host<DB> {
impl<DB: ZephyrDatabase + Clone, L: LedgerStateRead> Host<DB, L> {
/// Loads the ledger close meta bytes of the ledger the Zephyr VM will have
/// access to.
///
Expand Down Expand Up @@ -198,7 +203,7 @@ impl<DB: ZephyrDatabase + Clone> Host<DB> {
}

/// Loads VM context in the host if needed.
pub fn load_context(&self, vm: Weak<Vm<DB>>) -> Result<()> {
pub fn load_context(&self, vm: Weak<Vm<DB, L>>) -> Result<()> {
let mut vm_context = self.0.context.borrow_mut();

vm_context.load_vm(vm)
Expand Down Expand Up @@ -506,6 +511,20 @@ impl<DB: ZephyrDatabase + Clone> Host<DB> {
Self::write_to_memory(caller, read.as_slice())
}

pub fn read_contract_data_entry_by_contract_id_and_key(caller: Caller<Self>) -> Result<(i64, i64)> {
let host = caller.data();

let contract = ScAddress::Contract(Hash([0;32]));
let key = ScVal::LedgerKeyContractInstance;

let read = {
let ledger = &host.0.ledger.0.ledger;
ledger.read_contract_data_entry_by_contract_id_and_key(contract, key)
};

Self::write_to_memory(caller, &[])
}

/// Returns all the host functions that must be defined in the linker.
/// This should be the only public function related to foreign functions
/// provided by the VM, the specific host functions should remain private.
Expand All @@ -528,7 +547,7 @@ impl<DB: ZephyrDatabase + Clone> Host<DB> {
/// - Read ledger close meta: Reads the host's latest ledger meta (if present) and
/// writes it to the module's memory. Returns the offset and the size of the bytes
/// written in the binary's memory.
pub fn host_functions(&self, store: &mut Store<Host<DB>>) -> [FunctionInfo; 6] {
pub fn host_functions(&self, store: &mut Store<Host<DB, L>>) -> [FunctionInfo; 6] {
let mut store = store;

let db_write_fn = {
Expand Down Expand Up @@ -600,7 +619,7 @@ impl<DB: ZephyrDatabase + Clone> Host<DB> {

let stack_push_fn = {
let wrapped = Func::wrap(&mut store, |caller: Caller<_>, param: i64| {
let host: &Host<DB> = caller.data();
let host: &Host<DB, L> = caller.data();
host.as_stack_mut().0.push(param);
});

Expand Down
23 changes: 22 additions & 1 deletion rs-zephyr-env/src/testutils/database.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{
db::database::ZephyrDatabase,
db::{database::ZephyrDatabase, ledger::{ContractDataEntry, LedgerStateRead}},
ZephyrMock, ZephyrStandard,
};
use anyhow::Result;
Expand Down Expand Up @@ -54,6 +54,27 @@ impl ZephyrMock for MercuryDatabase {
}
}

#[derive(Clone)]
pub struct LedgerReader {}

impl LedgerStateRead for LedgerReader {
fn read_contract_data_entries_by_contract_ids(&self, contracts: impl IntoIterator<Item = stellar_xdr::next::ScAddress>) -> Vec<crate::db::ledger::ContractDataEntry> {
vec![]
}

fn read_contract_instance_by_contract_ids(&self, contracts: impl IntoIterator<Item = stellar_xdr::next::ScAddress>) -> Vec<ContractDataEntry> {
vec![]
}

fn read_contract_data_entry_by_contract_id_and_key(&self, contract: stellar_xdr::next::ScAddress, key: stellar_xdr::next::ScVal) -> Option<ContractDataEntry> {
None
}

fn read_contract_instance_by_contract_id(&self, contract: stellar_xdr::next::ScAddress) -> Option<ContractDataEntry> {
None
}
}

impl ZephyrDatabase for MercuryDatabase {
fn read_raw(
&self,
Expand Down
18 changes: 9 additions & 9 deletions rs-zephyr-env/src/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use anyhow::{Result, anyhow};
use std::{cell::RefCell, rc::Rc};
use wasmi::{Engine, Instance, Linker, Memory, Module, StackLimits, Store};

use crate::{db::database::ZephyrDatabase, error::HostError, host::Host};
use crate::{db::{database::ZephyrDatabase, ledger::LedgerStateRead}, error::HostError, host::Host};


const MIN_VALUE_STACK_HEIGHT: usize = 1024;
Expand Down Expand Up @@ -38,13 +38,13 @@ impl MemoryManager {
}

/// The Zephyr VM.
pub struct Vm<DB: ZephyrDatabase> {
pub struct Vm<DB: ZephyrDatabase, L: LedgerStateRead> {
/// Module object.
#[allow(dead_code)]
module: Module, // currently not used.

/// VM's store object. Provides bindings to the host.
pub store: RefCell<Store<Host<DB>>>,
pub store: RefCell<Store<Host<DB, L>>>,

/// Memory manager.
pub memory_manager: MemoryManager,
Expand All @@ -53,9 +53,9 @@ pub struct Vm<DB: ZephyrDatabase> {
}

#[allow(dead_code)]
impl<DB: ZephyrDatabase + Clone> Vm<DB> {
impl<DB: ZephyrDatabase + Clone, L: LedgerStateRead + Clone> Vm<DB, L> {
/// Creates and instantiates the VM.
pub fn new(host: &Host<DB>, wasm_module_code_bytes: &[u8]) -> Result<Rc<Self>> {
pub fn new(host: &Host<DB, L>, wasm_module_code_bytes: &[u8]) -> Result<Rc<Self>> {
let mut config = wasmi::Config::default();
let stack_limits = StackLimits::new(MIN_VALUE_STACK_HEIGHT, MAX_VALUE_STACK_HEIGHT, MAX_RECURSION_DEPTH).unwrap();

Expand All @@ -75,7 +75,7 @@ impl<DB: ZephyrDatabase + Clone> Vm<DB> {

// TODO: set Store::limiter() once host implements ResourceLimiter

let mut linker = <Linker<Host<DB>>>::new(&engine);
let mut linker = <Linker<Host<DB, L>>>::new(&engine);

for func_info in host.host_functions(&mut store) {
linker.define(
Expand Down Expand Up @@ -109,7 +109,7 @@ impl<DB: ZephyrDatabase + Clone> Vm<DB> {
/// By default, the called function is defined in the host as the EntryPointInfo.
/// The function itself won't return anything but will have access to the Database
/// implementation and the ledger metadata through Host bindings.
pub fn metered_call(self: &Rc<Self>, host: &Host<DB>) -> Result<()> {
pub fn metered_call(self: &Rc<Self>, host: &Host<DB, L>) -> Result<()> {
let store = &self.store;
let entry_point_info = host.get_entry_point_info();
let mut retrn = entry_point_info.retrn.clone();
Expand All @@ -136,7 +136,7 @@ impl<DB: ZephyrDatabase + Clone> Vm<DB> {
Ok(())
}
}

/*
#[cfg(test)]
mod tests {
use std::fs::{read, read_to_string};
Expand Down Expand Up @@ -173,4 +173,4 @@ mod tests {
println!("elapsed {:?}", start.elapsed());
}
}
}*/
14 changes: 7 additions & 7 deletions rs-zephyr-env/src/vm_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,18 @@
use anyhow::Result;
use std::rc::{Rc, Weak};

use crate::{db::database::ZephyrDatabase, error::HostError, vm::Vm, ZephyrMock, ZephyrStandard};
use crate::{db::{database::ZephyrDatabase, ledger::LedgerStateRead}, error::HostError, vm::Vm, ZephyrMock, ZephyrStandard};

/// VM Context.
/// The object is currently simply a wrapper for an
/// optional reference to the Virtual Machine.
#[derive(Clone)]
pub struct VmContext<DB: ZephyrDatabase> {
pub struct VmContext<DB: ZephyrDatabase, L: LedgerStateRead> {
/// Optional Zephyr Virtual Machine.
pub vm: Option<Weak<Vm<DB>>>,
pub vm: Option<Weak<Vm<DB, L>>>,
}

impl<DB: ZephyrDatabase + ZephyrStandard> ZephyrStandard for VmContext<DB> {
impl<DB: ZephyrDatabase + ZephyrStandard, L: LedgerStateRead + ZephyrStandard> ZephyrStandard for VmContext<DB, L> {
fn zephyr_standard() -> Result<Self>
where
Self: Sized,
Expand All @@ -25,7 +25,7 @@ impl<DB: ZephyrDatabase + ZephyrStandard> ZephyrStandard for VmContext<DB> {
}
}

impl<DB: ZephyrDatabase + ZephyrMock> ZephyrMock for VmContext<DB> {
impl<DB: ZephyrDatabase + ZephyrMock, L: LedgerStateRead + ZephyrMock> ZephyrMock for VmContext<DB, L> {
fn mocked() -> Result<Self>
where
Self: Sized,
Expand All @@ -34,10 +34,10 @@ impl<DB: ZephyrDatabase + ZephyrMock> ZephyrMock for VmContext<DB> {
}
}

impl<DB: ZephyrDatabase> VmContext<DB> {
impl<DB: ZephyrDatabase, L: LedgerStateRead> VmContext<DB, L> {
/// Writes the provided VM as the context's Virtual Machine.
/// Errors when a VM is already present in the context.
pub fn load_vm(&mut self, vm: Weak<Vm<DB>>) -> Result<()> {
pub fn load_vm(&mut self, vm: Weak<Vm<DB, L>>) -> Result<()> {
if self.vm.is_some() {
return Err(HostError::ContextAlreadyExists.into());
}
Expand Down

0 comments on commit dfa0486

Please sign in to comment.