Skip to content

feat(ffi): add proposal root retrieval #910

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

Open
wants to merge 14 commits into
base: main
Choose a base branch
from

Conversation

alarso16
Copy link
Contributor

This is needed to enable support of Trie wrapping in geth.

@alarso16 alarso16 marked this pull request as ready for review May 27, 2025 17:35
Copy link
Collaborator

@rkuris rkuris left a comment

Choose a reason for hiding this comment

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

I would have thought that you would just change the code so that propose always returns the hash along with the proposal ID, rather than having to look things up again and dealing with errors.

Copy link
Collaborator

@rkuris rkuris left a comment

Choose a reason for hiding this comment

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

I would have thought that you would just change the code so that propose always returns the hash along with the proposal ID, rather than having to look things up again and dealing with errors.

@alarso16 alarso16 marked this pull request as draft May 28, 2025 14:47
// Get the root hash of the new proposal.
let mut root_hash: Value = match new_proposal.root_hash_sync().map_err(|e| e.to_string())? {
Some(root) => Value::from(root.as_slice()),
None => String::new().into(),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@rkuris This shouldn't occur in the ethhash case, but it does. Honestly I'd prefer that db.root_hash_sync() also returns nil in the no hash, but definitely out of scope. However, this must return a root for no data (the failing ethhash test)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should be addressed in #918

@@ -204,13 +204,19 @@ func kvForTest(i int) KeyValue {
// 1. By calling [Database.Propose] and then [Proposal.Commit].
// 2. By calling [Database.Update] directly - no proposal storage is needed.
func TestInsert100(t *testing.T) {
type dbView interface {
Get(key []byte) ([]byte, error)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@aaronbuchwald I think this conflicts with something you're doing, so I can get rid of this or edit if you like

Copy link
Collaborator

Choose a reason for hiding this comment

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

No need to worry about a conflict here, will be easy to fix when we get to it

@alarso16 alarso16 marked this pull request as ready for review May 29, 2025 19:33
Copy link
Collaborator

@rkuris rkuris left a comment

Choose a reason for hiding this comment

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

Rust code is fine, recommended some rewording of comments.

Would be good to get Aaron to look at the go code better than me.

ffi/firewood.h Outdated
Comment on lines 293 to 294
* A `Value` containing {id, root} if creating the proposal succeeded.
* The root will always be 32 bytes, and the id will be non-zero.
Copy link
Collaborator

Choose a reason for hiding this comment

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

I had to read this a few times; the second sentence is only true if the proposal succeeds.

Suggested change
* A `Value` containing {id, root} if creating the proposal succeeded.
* The root will always be 32 bytes, and the id will be non-zero.
* On success, a `Value` containing {len=id, data=hash}. In this case, the
* hash will always be 32 bytes, and the id will be non-zero.

ffi/firewood.h Outdated
@@ -318,7 +319,8 @@ struct Value fwd_propose_on_db(const struct DatabaseHandle *db,
*
* # Returns
*
* A `Value` containing {id, nil} if creating the proposal succeeded.
* A `Value` containing {id, root} if creating the proposal succeeded.
Copy link
Collaborator

Choose a reason for hiding this comment

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

same as above

Comment on lines +45 to +48
// If the hash is empty, return the empty root hash.
if p.root == nil {
return make([]byte, RootLength), nil
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

What's the rationale to do this lazily rather than ensure it's populated with the empty root hash if needed?

This may save a memory allocation, but seems that it will likely be simpler if we just guarantee proposals are always fully populated.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There is an issue #918 for this in the ethhash front, but it might be better to enforce this on "normal" firewood as well.

ffi/memory.go Outdated
}

// Expected empty case for Rust's `()`
// Ignores the length.
if v.data == nil {
return nil
return nil, uint32(v.len), nil
Copy link
Collaborator

Choose a reason for hiding this comment

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

What is this case?

Copy link
Collaborator

Choose a reason for hiding this comment

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

ffi/memory.go Outdated
Comment on lines 30 to 35
// extractBytesAndErrorThenFree converts the cgo `Value` payload into either:
// 1. a 32 byte slice, an id and nil error
// 2. a nil byte slice, 0 id, and a non-nil error
// This should only be called when the `Value` is expected to only contain an error or
// an ID and a hash, otherwise the behavior is undefined.
func extractBytesAndErrorThenFree(v *C.struct_Value) ([]byte, uint32, error) {
Copy link
Collaborator

@aaronbuchwald aaronbuchwald May 29, 2025

Choose a reason for hiding this comment

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

This function seems to be a bit overloaded and the comment doesn't quite align with

/// - When returning an ID, the length is the ID and the data is null.

Why do we need to handle each of these cases this way? Is this intended to pass as little data as possible over the FFI boundary?

Comment on lines +446 to +450
// TODO: Check proposal root
// Requires #918
// hash, err := proposal.Root()
// require.NoError(t, err, "%T.Root() after commit", proposal)
// require.Equalf(t, expectedHash, hash, "%T.Root() of empty trie", db)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we need to include this TODO here? If this requires 918, would it make more sense to include the added check in 918?

Copy link
Contributor Author

@alarso16 alarso16 May 30, 2025

Choose a reason for hiding this comment

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

#918 is an issue, but the code is trivial so we could remove that and fill it in when addressing the issue.

@alarso16 alarso16 force-pushed the alarso16/ffi-proposal-root branch from 2a69cc4 to 531b32a Compare May 30, 2025 16:47
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.

3 participants