Adjust `verify` for `Self-Spawn`
YaronWittenstein opened this issue · comments
Depends on #470 #467 (from the Simple Coin Iteration 3
)
In this SMIP, we want to extend the verify
of the Runtime
to work for Self-Spawn
transactions.
(and not only for Non-Self Spawn
ones).
Running verify
in Self-Spawn
mode is unique because the Principal
account is in a Pending
state (see #467).
It has no Template
it's associated with yet.
The way to make it work is to make the Runtime
execute the verify
functionally.
We know what code needs to run for verify
(since the Template
is specified at the Spawn
transaction).
Additionally, we know that we are only allowed reads from the Immutable Storage
of the-to-be-self-spawned Account.
Even though the Account
isn't active and has no Storage yet, we can make that verify
behave as if it had Storage.
We'll use the immutable_data
supplied in the Spawn Transaction,
add another layer of indirection to the Storage host functions, and it should work.
To add that layer of indirection, we'd bypass the AccountStorage
and leverage the FuncData struct
we've introduced in #470. Until now, the immutable_data
was being ignored - not anymore...
Implementation Proposal
In #470, we had this code:
...
...
...
#[derive(Debug, Clone, PartialEq)]
pub struct FuncData {
fn_kind: FuncKind,
tx_kind: TxKind,
immutable_data: Option<Vec<u8>>
}
...
...
...
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()
}
}
Now the read
should in high-level work such as this:
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() {
let immutable_data = env.func_data();
// We need to extract the `immutable layout` by taking the `Template Address`
// given in the `Spawn Transaction` and then do something such as:
//
// let gs = func_env.gs();
// immutable_section_idx = 0;
let immutable_layout = AccountStorge::template_layout(gs.clone(), &template_addr, immutable_section_idx)?;
// use the same code as in `AccountStorage::get_var`
// see issue #470: https://github.com/spacemeshos/svm/issues/471
}
else {
f()
}
}
We essentially reuse the same logic as in issue #471.
Given the immutable_data
buffer and the Immutable Storage Section Layout,
we should be able to extract the required variable.
We need to load that Layout
only once to optimize the code.
So the above line of code:
let immutable_layout = AccountStorge::template_layout(gs.clone(), &template_addr, immutable_section_idx)?;
The loading of the Immutable Storage Section Layout
should be done externally.
Probably it should become part of the FuncData
.
It is better to make it optional (of type Option<FixedLayout>
) since we want to have it only for Self-Spawn Verify
.
In any other case, we just hit the AccountStorage
directly.
Before the ctor
running
We've another leftover from issue #477 that we need to fill in.
Here is the code appearing under #477.
impl Runtime {
pub fn spawn(
&mut self,
envelope: &Envelope,
message: &[u8],
context: &Context,
) -> SpawnReceipt {
info!("Runtime `spawn`");
let gas_left = GasTank::new(envelope.gas_limit());
let spawn = SpawnAccount::decode_bytes(message).expect(ERR_VALIDATE_SPAWN);
// If the `Principal` is a `Pending Account` (a.k.a a `Stub Account`)
// then we say we're running a `Self-Spawn` transaction.
let self_spawn = ...
if self_spawn {
todo!("Simple Coin Iteration #4")
}
else {
// now the `self.create_account` expects an additional `immutable_data` param
self.create_account(
&target,
template_addr.clone(),
account.name().to_string(),
0,
0,
&spawn.immutable_data;
)
.unwrap();
self.call_ctor(&spawn, target, envelope, context, gas_left)
}
}
We need to do the following:
impl Runtime {
pub fn spawn(
&mut self,
envelope: &Envelope,
message: &[u8],
context: &Context,
) -> SpawnReceipt {
info!("Runtime `spawn`");
let gas_left = GasTank::new(envelope.gas_limit());
let spawn = SpawnAccount::decode_bytes(message).expect(ERR_VALIDATE_SPAWN);
// If the `Principal` is a `Pending Account` (a.k.a a `Stub Account`)
// then we say we're running a `Self-Spawn` transaction.
let self_spawn = ...
let mut spawned_account: Option<AccountStorage> = None;
if self_spawn {
// We don't need to create the `Account`
let mut account = AccountStorage::new(..);
account.set_immutable( &spawn.immutable_data);
}
else {
// now the `self.create_account` expects an additional `immutable_data` param
spawned_account = self.create_account(
&target,
template_addr.clone(),
account.name().to_string(),
0,
0,
&spawn.immutable_data;
)
.unwrap();
}
let receipt = self.call_ctor(&spawn, target, envelope, context, gas_left);
if receipt.success {
if self_spawn {
// Turn the `Account` into `Active` and set it's `Template Address`.
// We've set above its `Immutable Storage` so that the `ctor` would work.
AccountStorage::activate(&account_addr, &template_addr);
}
else {
spawned_account.activate(&account_addr);
}
}
else {
// The Transaction has failed
if self_spawn {
// We can do nothing, but it'd be nicer to remove the `immutable_storage`
// of the `Pending Principal`
let mut account = AccountStorage::new(..);
account.set_immutable(&[]);
}
else {
// The `Non-Self Spawn` has failed
// We need to rollback the account created before the `call_ctor`.
// Everything related to the `spawned_account` should be deleted.
//
// It was created as an implementation decision.
// But basically, we can't call it an `Account` only after the `ctor` has succeeded.
spawned_account.destroy();
}
}
receipt
}