spacemeshos / svm

SVM - Spacemesh Virtual Machine
https://spacemesh.io
MIT License
85 stars 14 forks source link

Extend the `FuncEnv` to support `Immutable Storage` for `Spawn` Transactions #470

Open YaronWittenstein opened 2 years ago

YaronWittenstein commented 2 years ago

Depends on: #469

When executing the verify method, we only allow access to the Immutable Storage of the Principal Account. (and of course, we're only talking about read-access since it's immutable data).

We'll implement the Self Spawn feature in the Simple Coin Integration #4. When we execute the verify within the Self-Spawn context, we won't know whether the Principal will become active. It'll depend on whether the verify returns true or not (and later whether the ctor will run to its completion without panicking).

Therefore we want to have these rules implemented:

Non-Self Spawn

That is the Spawn we've today. When running Non-Self Spawn, the Principal Account already exists as an active Account.

In that case, the immutable_data given in the Spawn Transaction (see #469) should not be used when running the verify method (since it runs on behalf of the Principal).

That immutable_data will be stored for the newly spawned account, right before executing its ctor (and of course, assuming that the verify passed).

Self Spawn

Again, we don't want to implement that feature fully. Instead, it's the topic of Simple Iteration #4. So right now, the code will consistently execute Non-Self Spawn, but it'll have some preparation work done for when we'll want to finish that feature (next iteration).

Implementation Proposal

To have more fine-grained control, I propose to add these Rust structs:

Important: We can discard the AccessMode struct in favor of the new FuncKind and TxKind.

#[derive(Debug, Copy, Clone, PartialEq, Hash)]
pub enum FuncKind {
  Verify,
  Ctor,
  Alloc,
  General,

  // Maybe in the future
  // Authorize
}

#[derive(Debug, Copy, Clone, PartialEq, Hash)]
pub enum TxKind {
  Deploy,
  Spawn,
  SelfSpawn,
  Call
}

#[derive(Debug, Clone, PartialEq)]
pub struct FuncData {
  fn_kind: FuncKind,
  tx_kind: TxKind,
  immutable_data: Option<Vec<u8>>
}

impl FuncData {
  fn within_alloc(&self) -> bool {
    matches!(self.tx_kind, TxKind::Alloc)
  }

  fn within_spawn(&self) -> bool {
    matches!(self.tx_kind, TxKind::Spawn)
  }

  fn within_self_spawn(&self) -> bool {
    matches!(self.tx_kind, TxKind::SelfSpawn)
  }

  fn within_call(&self) -> bool {
    matches!(self.tx_kind, TxKind::Call)
  }

  fn within_verify(&self) -> bool {
    matches!(self.fn_kind, FuncKind::Verify)
  }

  fn within_ctor(&self) -> bool {
    matches!(self.fn_kind, FuncKind::Ctor)
  }

  fn immutable_data(&self) -> Option<&[u8]> {
    self.immutable_data.as_ref()
  }
}

#[derive(wasmer::WasmerEnv, Clone)]
pub struct FuncEnv {
  pub fn new(
    storage: AccountStorage,
    envelope: &Envelope,
    context: &Context,
    template_addr: TemplateAddr,
    target_addr: Address,
    func_data: FuncData
  )
}

impl FuncEnv {
  fn within_alloc(&self) -> bool {
    self.func_data.within_alloc()
  }

  fn within_spawn(&self) -> bool {
    self.func_data.within_spawn()
  }

  fn within_self_spawn(&self) -> bool {
    self.func_data.within_self_spawn()
  }

  fn within_call(&self) -> bool {
    self.func_data.within_call()
  }

  fn within_self_spawn_verify(&self) -> bool {
    self.func_data.within_self_spawn() && self.within_verify()
  }

  fn within_verify(&self) -> bool {
    self.func_data.within_verify()
  }

  fn within_ctor(&self) -> bool {
    self.func_data.within_ctor()
  }

  fn assert_storage_allowed(&self) {
    if self.func_data.within_alloc() {
      panic("While allocating Memory can't ask for any Storage Access")
    }
  }

  fn read_allowed(&self, section_idx: i32) -> bool {
    // First, we need to confirm `Storage Access` isn't entirely disallowed. 
    self.assert_storage_allowed();

    // We disallow:
    // 1. Reads from `Non-Immutable Storage` within `verify`

    let disallowed = self.within_verify() && self.section_idx != 0;
    !(disallowed)
  }

  fn write_allowed(&self, section_idx: i32) -> bool {
    // First, we need to confirm `Storage Access` isn't entirely disallowed. 
    self.assert_storage_allowed();

    // We disallow:
    // 1. Writes within `verify`
    // 2. Writes to `Immutable Storage`

    let disallowed = self.within_verify() || self.section == 0;
    !(disallowed)
  }
}

After having the FuncEnv equipped with the new functionality, we need to update the Storage host functions: https://github.com/spacemeshos/svm/blob/master/crates/runtime/src/vmcalls/storage.rs

Here is a draft of how it should probably look like. The host functions dealing with Blob will probably have read_blob and write_blob helpers.

fn read<T, F: () -> T>(env: &FuncEnv, section_idx: u32, f: F) -> T {
  assert!(env.read_allowed(section_idx));

  if env.within_self_spawn_verify() {
    todo!("Simple Coin Iteration #4")
  }
  else {
    f()
  }
}

fn write<T, F: () -> ()>(env: &FuncEnv, section_idx: u32, f: F) {
  assert!(env.write_allowed(section_idx));
  f();
}

fn get32(env: &FuncEnv, var_id: u32, section_idx: u32) -> u32 {
  read(env, section_idx, || {
   let borrow = env.borrow();
   let storage = borrow.storage();
   storage.get_var_i64(var_id, section_idx).unwrap() as u32
  })
}

pub fn set32(env: &FuncEnv, var_id: u32, section_idx: u32, value: u32) {
  write(env, section_idx, || {
   let mut borrow = env.borrow_mut();
   let storage = borrow.storage_mut();
   storage.set_var_i32(var_id, section_idx, value as i32).unwrap();
  });
}