-
Notifications
You must be signed in to change notification settings - Fork 91
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
feat: Use Vec of Bytes for Contract State #671
Conversation
@xgreenx would this negate the performance benefit of being able to directly copy the contract bytecode into vm memory out of the db? I.e. seems like this would require copying the whole bytecode onto the heap first? |
@@ -69,7 +58,7 @@ impl Distribution<StorageSlot> for Standard { | |||
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> StorageSlot { |
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.
Note: now sample
doesn't generate from all possible values anymore, which might affect test coverage. Seems to be a non-issue for now.
@Voxelot The change affects the |
pub const SLOT_SIZE: usize = Bytes32::LEN + Bytes32::LEN; | ||
|
||
pub const fn new(key: Bytes32, value: Bytes32) -> Self { | ||
pub const fn new(key: Bytes32, value: StorageData) -> Self { |
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.
We don't add support for dynamic storage in the current PR. We only want to use Vec<u8>
in the bytes. This change affects the layout of the serialized transaction.
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.
This PR now includes manual implementations of serialization/deserialization to maintain a fixed size for storage slots. These implementations will be removed when fully supporting dynamic storage.
@@ -1295,7 +1298,7 @@ fn state_write_qword<'vm, S: InterpreterStorage>( | |||
|
|||
let values: Vec<_> = memory[input.source_address_memory_range.usizes()] | |||
.chunks_exact(Bytes32::LEN) | |||
.flat_map(|chunk| Some(Bytes32::from(<[u8; 32]>::try_from(chunk).ok()?))) | |||
.flat_map(|chunk| Some(chunk.to_vec())) |
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.
Instead, we can use StorageWrite::write
or StorageWrite::replace
. In this case, we don't need to spend 1 byte to store the size of the vector. As we did for ContractsRawCode
table.
Thanks to @Voxelot for pointing of this idea=)
fuel-vm/src/storage.rs
Outdated
type OwnedValue = Self::Value; | ||
/// The table value is hash of the value. | ||
type Value = Bytes32; | ||
type Value = StorageData; |
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.
I think we can use [u8]
here and for OwnedValue = StorageData
=)
Additional note from today's Sync: We will not make any breaking changes to transaction serialization as part of this task. We will do that when we work directly on dynamic storage. |
@@ -124,4 +116,42 @@ mod tests { | |||
serde_json::from_reader(storage_slots_file).expect("read file"); | |||
assert_eq!(storage_slots.len(), 1); | |||
} | |||
|
|||
#[test] | |||
fn test_storage_slot_canonical_serialization() { |
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.
Could you include given/when/then sections for these tests?
@@ -18,49 +18,38 @@ use rand::{ | |||
|
|||
use core::cmp::Ordering; | |||
|
|||
pub type StorageData = Vec<u8>; |
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.
I'm curious about the naming of this. Are we intending all storage to move to Vec
or just for contract state? Or is there no distinction between storage and contract storage?
Just trying to understand the scope of all this.
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.
This is just for the ContractsState
table storage. This can be named better - something like ContractsStateData
could be more congruent with the table, where the key is ContractsStateKey
. I will make that change, but let me know if you have a suggestion you prefer.
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.
I renamed it to ContractsStateData
to illustrate that this is part of the ContractsState
table alongside the ContractsStateKey
.
fuel-vm/src/storage/interpreter.rs
Outdated
// StorageWrite::<ContractsState>::write(self, &(contract, key).into(), | ||
// value.into()) |
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.
Maybe these comments should be removed?
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.
This part specifically is unclear in my mind. We want to use a higher level of abstraction for the ContractsState
table, on top of StorageMutate
and StorageInspect
by implementing StorageWrite
and StorageRead
. This should consolidate the read and write patterns for vector based storage. I'm not sure I understand how/why that's used in this specific context of Merkle storage.
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.
Summarizing from the call earlier today - it sounds like we need to implement StorageRead and StorageWrite for the contracts state table. This will also allow us to structure the table using Vec<u8>
or [u8]
under the hood and avoid the need for the StorageData newtype wrapper.
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.
We will still use the new wrapper type to provide some convenience functions for converting between types, as well as to control the canonical serialization implementation while we need a fixed size.
Edit: We will use it only in the table definition
} | ||
} | ||
|
||
// TODO: Remove manual serialization when implementing dynamic storage |
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.
We don't need to use this type in the transaction. It should be used only inside of the ConstractsState
table. In this case you don't need to have custom serialization and deserialziation
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.
Ok, I can revert the changes to StorageSlot
and keep the value type as Bytes32
.
|
||
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)] | ||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] | ||
#[cfg_attr(feature = "typescript", wasm_bindgen::prelude::wasm_bindgen)] | ||
#[derive(Deserialize, Serialize)] | ||
pub struct StorageSlot { | ||
key: Bytes32, | ||
value: Bytes32, | ||
value: ContractsStateData, |
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.
Let's not change the type for the Transaction
to avoid breaking changes and to not affect the serialziation
….com/FuelLabs/fuel-vm into bvrooman/feat/dynamic-contract-state
@@ -1306,7 +1306,7 @@ fn state_write_qword<'vm, S: InterpreterStorage>( | |||
|
|||
let values: Vec<_> = memory[input.source_address_memory_range.usizes()] | |||
.chunks_exact(Bytes32::LEN) | |||
.flat_map(|chunk| Some(Bytes32::from(<[u8; 32]>::try_from(chunk).ok()?))) | |||
.flat_map(Some) |
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.
.flat_map(Some) |
Related issues:
Vec<u8>
as a value for theContractsState
table #655