Skip to content
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

Merged
merged 35 commits into from
Feb 27, 2024

Conversation

bvrooman
Copy link
Contributor

@bvrooman bvrooman commented Feb 6, 2024

@bvrooman bvrooman changed the title feat: Dynamic Contract State feat: Use Vec of Bytes for Contract State Feb 7, 2024
@bvrooman bvrooman self-assigned this Feb 7, 2024
@Voxelot
Copy link
Member

Voxelot commented Feb 8, 2024

@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?

@bvrooman bvrooman marked this pull request as ready for review February 9, 2024 00:47
@bvrooman bvrooman requested a review from a team February 9, 2024 00:47
@@ -69,7 +58,7 @@ impl Distribution<StorageSlot> for Standard {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> StorageSlot {
Copy link
Member

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.

@xgreenx
Copy link
Collaborator

xgreenx commented Feb 11, 2024

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?

@Voxelot The change affects the ContractsState table, not ContractsRawCode table=) So everything works as before

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 {
Copy link
Collaborator

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.

Copy link
Contributor Author

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()))
Copy link
Collaborator

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=)

Comment on lines 71 to 73
type OwnedValue = Self::Value;
/// The table value is hash of the value.
type Value = Bytes32;
type Value = StorageData;
Copy link
Collaborator

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=)

@bvrooman
Copy link
Contributor Author

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() {
Copy link
Member

@MitchTurner MitchTurner Feb 14, 2024

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>;
Copy link
Member

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.

Copy link
Contributor Author

@bvrooman bvrooman Feb 19, 2024

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.

Copy link
Contributor Author

@bvrooman bvrooman Feb 26, 2024

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.

@bvrooman bvrooman marked this pull request as draft February 16, 2024 19:36
@xgreenx xgreenx self-requested a review February 19, 2024 18:05
Comment on lines 151 to 152
// StorageWrite::<ContractsState>::write(self, &(contract, key).into(),
// value.into())
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

@Voxelot Voxelot Feb 21, 2024

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.

Copy link
Contributor Author

@bvrooman bvrooman Feb 26, 2024

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
Copy link
Collaborator

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

Copy link
Contributor Author

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,
Copy link
Collaborator

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

@@ -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)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.flat_map(Some)

@xgreenx xgreenx added this pull request to the merge queue Feb 27, 2024
Merged via the queue into master with commit ecf5b24 Feb 27, 2024
37 checks passed
@xgreenx xgreenx deleted the bvrooman/feat/dynamic-contract-state branch February 27, 2024 14:37
@xgreenx xgreenx mentioned this pull request Feb 29, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants