-
Notifications
You must be signed in to change notification settings - Fork 79
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP] Implementation of EIP-1153: Transient Storage using Disk Persistence and Lifecycle Management #1588
base: master
Are you sure you want to change the base?
[WIP] Implementation of EIP-1153: Transient Storage using Disk Persistence and Lifecycle Management #1588
Changes from all commits
801dc0c
ef679de
a98c058
9f3e90d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,7 +16,7 @@ use fvm_shared::error::{ErrorNumber, ExitCode}; | |
use fvm_shared::sys::SendFlags; | ||
use fvm_shared::{MethodNum, Response, IPLD_RAW, METHOD_SEND}; | ||
|
||
use crate::state::{State, Tombstone}; | ||
use crate::state::{State, Tombstone, TransientDataLifespan}; | ||
use crate::BytecodeHash; | ||
|
||
use cid::Cid; | ||
|
@@ -91,6 +91,11 @@ pub struct System<'r, RT: Runtime> { | |
bytecode: Option<EvmBytecode>, | ||
/// The contract's EVM storage slots. | ||
slots: StateKamt<RT::Blockstore>, | ||
|
||
/// The contract's EVM transient storage slots. | ||
transient_slots: StateKamt<RT::Blockstore>, | ||
transient_data_lifespan: Option<TransientDataLifespan>, | ||
|
||
/// The contracts "nonce" (incremented when creating new actors). | ||
pub(crate) nonce: u64, | ||
/// The last saved state root. None if the current state hasn't been saved yet. | ||
|
@@ -111,9 +116,12 @@ impl<'r, RT: Runtime> System<'r, RT> { | |
RT::Blockstore: Clone, | ||
{ | ||
let store = rt.store().clone(); | ||
let transient_store = rt.store().clone(); | ||
Self { | ||
rt, | ||
slots: StateKamt::new_with_config(store, KAMT_CONFIG.clone()), | ||
transient_slots: StateKamt::new_with_config(transient_store, KAMT_CONFIG.clone()), | ||
transient_data_lifespan: None, | ||
nonce: 1, | ||
saved_state_root: None, | ||
bytecode: None, | ||
|
@@ -164,6 +172,7 @@ impl<'r, RT: Runtime> System<'r, RT> { | |
RT::Blockstore: Clone, | ||
{ | ||
let store = rt.store().clone(); | ||
let transient_store = rt.store().clone(); | ||
let state_root = rt.get_state_root()?; | ||
let state: State = store | ||
.get_cbor(&state_root) | ||
|
@@ -182,6 +191,13 @@ impl<'r, RT: Runtime> System<'r, RT> { | |
rt, | ||
slots: StateKamt::load_with_config(&state.contract_state, store, KAMT_CONFIG.clone()) | ||
.context_code(ExitCode::USR_ILLEGAL_STATE, "state not in blockstore")?, | ||
transient_slots: StateKamt::load_with_config( | ||
&state.transient_state, | ||
transient_store, | ||
KAMT_CONFIG.clone(), | ||
) | ||
.context_code(ExitCode::USR_ILLEGAL_STATE, "transient_state not in blockstore")?, | ||
transient_data_lifespan: state.transient_data_lifespan, | ||
nonce: state.nonce, | ||
saved_state_root: Some(state_root), | ||
bytecode: Some(EvmBytecode::new(state.bytecode, state.bytecode_hash)), | ||
|
@@ -255,7 +271,7 @@ impl<'r, RT: Runtime> System<'r, RT> { | |
Ok(result.map_err(|e| e.0)) | ||
} | ||
|
||
/// Flush the actor state (bytecode, nonce, and slots). | ||
/// Flush the actor state (bytecode, nonce, transient data and slots). | ||
pub fn flush(&mut self) -> Result<(), ActorError> { | ||
if self.saved_state_root.is_some() { | ||
return Ok(()); | ||
|
@@ -281,6 +297,11 @@ impl<'r, RT: Runtime> System<'r, RT> { | |
ExitCode::USR_ILLEGAL_STATE, | ||
"failed to flush contract state", | ||
)?, | ||
transient_state: self.transient_slots.flush().context_code( | ||
ExitCode::USR_ILLEGAL_STATE, | ||
"failed to flush contract state", | ||
)?, | ||
transient_data_lifespan: self.transient_data_lifespan, | ||
nonce: self.nonce, | ||
tombstone: self.tombstone, | ||
}, | ||
|
@@ -314,6 +335,10 @@ impl<'r, RT: Runtime> System<'r, RT> { | |
self.slots | ||
.set_root(&state.contract_state) | ||
.context_code(ExitCode::USR_ILLEGAL_STATE, "state not in blockstore")?; | ||
self.transient_slots | ||
.set_root(&state.transient_state) | ||
.context_code(ExitCode::USR_ILLEGAL_STATE, "transient_state not in blockstore")?; | ||
self.transient_data_lifespan = state.transient_data_lifespan; | ||
self.nonce = state.nonce; | ||
self.saved_state_root = Some(root); | ||
self.bytecode = Some(EvmBytecode::new(state.bytecode, state.bytecode_hash)); | ||
|
@@ -385,6 +410,89 @@ impl<'r, RT: Runtime> System<'r, RT> { | |
Ok(()) | ||
} | ||
|
||
/// Returns the current transient data lifespan based on the execution environment. | ||
pub fn get_current_transient_data_lifespan(&self) -> Option<TransientDataLifespan> { | ||
match self.rt.message().origin().id() { | ||
Ok(origin_id) => Some(TransientDataLifespan { | ||
origin: origin_id, | ||
nonce: self.rt.message().nonce(), | ||
}), | ||
Err(_) => None, // Handle the error case by returning None | ||
} | ||
} | ||
|
||
/// Get value of a transient storage key. | ||
pub fn get_transient_storage(&mut self, key: U256) -> Result<U256, ActorError> { | ||
if self.transient_data_lifespan == self.get_current_transient_data_lifespan() { | ||
// Lifespan matches, retrieve value | ||
Ok(self | ||
.transient_slots | ||
.get(&key) | ||
.context_code(ExitCode::USR_ILLEGAL_STATE, "failed to access transient storage slot")? | ||
.cloned() | ||
.unwrap_or_default()) | ||
} else { | ||
// Lifespan mismatch, return default value | ||
Ok(U256::zero()) | ||
} | ||
} | ||
|
||
|
||
/// Set value of a transient storage key. | ||
pub fn set_transient_storage(&mut self, key: U256, value: U256) -> Result<(), ActorError> { | ||
let current_lifespan = self.get_current_transient_data_lifespan(); | ||
|
||
if self.transient_data_lifespan == current_lifespan { | ||
// Lifespan matches, proceed to set value | ||
let changed = if value.is_zero() { | ||
self.transient_slots | ||
.delete(&key) | ||
.map(|v| v.is_some()) | ||
.context_code(ExitCode::USR_ILLEGAL_STATE, "failed to clear transient storage slot")? | ||
} else { | ||
self.transient_slots | ||
.set(key, value) | ||
.map(|v| v != Some(value)) | ||
.context_code(ExitCode::USR_ILLEGAL_STATE, "failed to update transient storage slot")? | ||
}; | ||
|
||
if changed { | ||
self.saved_state_root = None; // Mark state as dirty | ||
} | ||
} else { | ||
// Lifespan mismatch | ||
if value.is_zero() { | ||
// If setting to default value, skip storage reset | ||
return Ok(()); | ||
} | ||
|
||
// Reset transient storage and update lifespan | ||
self.reset_transient_storage(current_lifespan)?; | ||
self.transient_slots | ||
.set(key, value) | ||
.context_code(ExitCode::USR_ILLEGAL_STATE, "failed to set transient storage after reset")?; | ||
|
||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
/// Reset transient storage and update lifespan. | ||
pub fn reset_transient_storage(&mut self, new_lifespan: Option<TransientDataLifespan>) -> Result<(), ActorError> { | ||
// Update lifespan | ||
self.transient_data_lifespan = new_lifespan; | ||
|
||
// Reinitialize the transient_slots with a fresh KAMT | ||
//let transient_store = self.rt.store().clone(); | ||
//self.transient_slots = StateKamt::new_with_config(transient_store, KAMT_CONFIG.clone()); | ||
// TODO XXX reinitialize does not currently work due to blockstore reference issues | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd be interested to know what these errors are and how you end up diagnosing the problem, for my own educational purposes because your commented code looks like it should work. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's because Note: the Options are:
|
||
|
||
// Mark state as dirty | ||
self.saved_state_root = None; | ||
|
||
Ok(()) | ||
} | ||
|
||
/// Resolve the address to the ethereum equivalent, if possible. | ||
/// | ||
/// - Eth f4 maps directly to an Eth address. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,6 +17,15 @@ pub struct Tombstone { | |
pub nonce: u64, | ||
} | ||
|
||
/// A structure representing the transient data lifespan. | ||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize_tuple, Deserialize_tuple)] | ||
pub struct TransientDataLifespan { | ||
/// The origin actor ID associated with the transient data. | ||
pub origin: ActorID, | ||
/// A unique nonce identifying the transaction. | ||
pub nonce: u64, | ||
} | ||
|
||
/// A Keccak256 digest of EVM bytecode. | ||
#[derive(Deserialize, Serialize, Clone, Copy, Eq, PartialEq)] | ||
#[serde(transparent)] | ||
|
@@ -106,6 +115,11 @@ pub struct State { | |
/// KAMT<U256, U256> | ||
pub contract_state: Cid, | ||
|
||
/// The EVM contract state diciontary that represents the transient storage | ||
pub transient_state: Cid, | ||
/// The nonce and actor id that represents the lifespan of the transient storage data | ||
pub transient_data_lifespan: Option<TransientDataLifespan>, | ||
Comment on lines
+119
to
+121
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we combine these? That'll let us store a single "null" if there is no transient data. |
||
|
||
/// The EVM nonce used to track how many times CREATE or CREATE2 have been called. | ||
pub nonce: u64, | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice if we could load this lazily on first-use, or even just if non-empty.