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

Add pallet-storage-roots #18

Merged
merged 11 commits into from
Jan 16, 2024

Conversation

tmpolaczyk
Copy link
Collaborator

This pallet stores the latest MaxStorageRoots relay storage roots, which can be used to verify state proofs against an old state of the relay chain.

/// List of all the keys in `RelayStorageRoot`.
/// Used to remove the oldest key without having to iterate over all of them.
#[pallet::storage]
pub type RelayStorageRootKeys<T: Config> =
Copy link
Collaborator

@librelois librelois Jan 9, 2024

Choose a reason for hiding this comment

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

Please remove this storage item, you don't need it.
To delete old relay roots, you can iterate the RelayStorageRoot map with a drain iterator: https://paritytech.github.io/polkadot-sdk/master/frame_support/storage/trait.IterableStorageMap.html#tymethod.drain

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I also attempted this, the problem is that the drain iterator is not sorted. So if I want to remove the oldest key, I need to iterate over all of them. Removing the first element of the iterator will remove the element whose hash is the lowest. Actually now I see the docs mention this:

Remove all elements from the map and iterate through them in lexicographical order of the encoded key.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Maybe it will work if we set the hash to identity. Do you think that's a good idea?

Copy link
Collaborator

@librelois librelois Jan 9, 2024

Choose a reason for hiding this comment

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

@tmpolaczyk as long as MaxStorageRoots is not too high (less than 1000) I think that the Identity hash is safe.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ok we expect this value to be around 10 so I implemented this change. I am not sure if I like it, so I did it in a separate commit that is easy to revert in case we change our minds.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't understand why you see the need for a linked list.

We just want to keep the last K relay hash, don't we?
So you just need to increment OldestRelayNumber to CurrentRelayNumber-K and delete the corrresponding key at each increment.

Copy link
Collaborator

@librelois librelois Jan 15, 2024

Choose a reason for hiding this comment

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

In any case, VecDeque is a bad approach because it add more complex storage for nothing. At worst, store a BTreeMap<u32, Hash> directly in one StorageValue and for K ~ 10 it will work.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Oh I see, one thing I forgot to mention is that the relay number is not necessarily consecutive. In fact currently it increments by 2 for every parachain block, but not always. With async backing it may sometimes increase by 1 and sometimes not. And in the case of parathreads there may be huge gaps. So we cannot use that fact to delete items more efficiently. We could try to iterate starting from OldestRelayNumber up to OldestRelayNumber+10 until we find something, but it may not always work, and it is going to be less efficient than the alternatives.

And the problem with using a single BTreeMap is that it adds proof size. A key is 4 bytes and a value is 32 bytes, so mutating a VecDeque that only has the keys is much more efficient.

Copy link
Collaborator

Choose a reason for hiding this comment

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

And in the case of parathreads there may be huge gaps.

Sorry, I didn't think about parathreads, it's true that for parathreads the gap can be huge and we can't just increment.

Thanks for the explanation, I understand better the relevance of your initial design now, but at first glance it's not obvious, can you please add a comment in the code that explains the rationale for this design choice?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Sure, I added some docs. Also changed the storage item to use a BoundedVec, which is converted into a VecDeque after reading it.

let mut keys: VecDeque<_> = <RelayStorageRootKeys<T>>::get().into_inner().into();
keys.push_back(relay_state.number);
// Delete the oldest stored root if the total number is greater than MaxStorageRoots
if u32::try_from(keys.len()).unwrap() > T::MaxStorageRoots::get() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please not use unwrap inside runtime production code, use an explicit saturation to u32::MAX instead

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Agree, thanks for spotting!

keys.push_back(relay_state.number);
// Delete the oldest stored root if the total number is greater than MaxStorageRoots
if u32::try_from(keys.len()).unwrap() > T::MaxStorageRoots::get() {
let first_key = keys.pop_front().unwrap();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please never use unwrap inside runtime production code, move the remove instruction inside an if let Some instead

Copy link
Collaborator

@librelois librelois left a comment

Choose a reason for hiding this comment

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

Very good job, thanks @tmpolaczyk for this awesome contribution!

@librelois
Copy link
Collaborator

@Agusrodri can you review this PR?

Copy link
Contributor

@Agusrodri Agusrodri left a comment

Choose a reason for hiding this comment

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

Looks good to me, great job @tmpolaczyk!

@girazoki
Copy link
Collaborator

Indeed amazing job @tmpolaczyk !

@tmpolaczyk tmpolaczyk merged commit 8406e80 into Moonsong-Labs:main Jan 16, 2024
8 of 10 checks passed
tmpolaczyk added a commit to moondance-labs/moonkit that referenced this pull request Jan 16, 2024
ahmadkaouk pushed a commit that referenced this pull request Feb 8, 2024
(cherry picked from commit 8406e80)
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.

4 participants