Skip to content

MarshalSSZTo and other incompatibilities with fastssz #170

Closed

Description

@jshufro

Fast ssz now expects HashTreeRootWith(hh ssz.HashWalker) (err error) in its HashTreeRoot interface. I believe the implementation is as simple as:

func (u *Uint256) HashTreeRootWith(hh ssz.HashWalker) (err error) {
        bytes := make([]byte, 32)
        bytes, err = u.MarshalSSZTo(bytes)
        if err != nil {
                return
        }

        hh.AppendBytes32(bytes)
        return
}

but I have been known to make mistakes.

Further, fastssz's MarshalSSZTo(buf []byte) ([]byte, error) interface function expects the implementation to append to the end of buf, not to overwrite it. Unfortunately, this library overwrites instead, and a wrapper is needed (one is needed anyway for sszgen to work):

type Uint256 struct {
        repr *uint256.Int `ssz:"-"`
}
[...]
func (u *Uint256) MarshalSSZTo(buf []byte) ([]byte, error) {
        bytes, err := u.repr.MarshalSSZ()
        if err != nil {
                return nil, err
        }
        return append(buf, bytes...), nil
}

Activity

  1. holiman commented on May 21, 2024

    @holiman
    Owner

    Thanks for the heads-up!

  2. holiman commented on May 21, 2024

    @holiman
    Owner

    Fast ssz now expects HashTreeRootWith(hh ssz.HashWalker) (err error) in its HashTreeRoot interface. I believe the implementation is as simple as:

    As for this, nothing we can do here. As the comment on HashTreeRoot says, https://github.com/holiman/uint256/blob/master/conversion.go#L585 ,

    // HashTreeRoot implements the fastssz.HashRoot interface's non-dependent part.
    

    That is, out of the methods in the HashRoot interface

    type HashRoot interface {
    	GetTree() (*Node, error)
    	HashTreeRoot() ([32]byte, error)
    	HashTreeRootWith(hh HashWalker) error
    }

    HashTreeRot() is the only one that can be implemented without introducing a dependency from uint256 to fastssz.

  3. jshufro commented on Jun 1, 2024

    @jshufro
    Author

    You know, taking a step back, the whole situation is disappointing, as the interface should expect a function that marshals ssz into a preallocated buffer. Maybe it does make([]byte, 0, N) to avoid reallocation, but that leaves you the unenviable implementation task of 1-copy serialization: first into a local buffer, then into the preallocated one.

    If fastssz wants to live up to its name, maybe we can change the upstream interface to require a new MarshalSSZInto in place of MarshalSSZTo.

    That way fastssz can preallocate and no extra copies need be made.

    At any rate, I'm on a quick two week vacation, so i can't tell you immediately why an alias or struct with anonymous field didn't work with sszgen, but i did attempt it previously. It was before I moved the wrapper into its own package though, so maybe it works that way (aside from this bug).

  4. holiman commented on Jun 12, 2024

    @holiman
    Owner

    , as the interface should expect a function that marshals ssz into a preallocated buffer.

    How so? I think it's fair to expect the marshalling to append to the buffer, that's how a lot of encoders work. With ssz, we also have the size, so the caller can easily ensure that there's no realloc by just providing a buffer with correct capacity.

    This is (mostly) fixed in #171 . At some point in the future, I'll add back the MarshalSSZTo method

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      ⌂ 换网址