Skip to content

Commit

Permalink
feat(ssz): consolidate lists into one type (#1666)
Browse files Browse the repository at this point in the history
  • Loading branch information
itsdevbear authored Jul 1, 2024
1 parent ed9bbea commit f2087fd
Show file tree
Hide file tree
Showing 8 changed files with 107 additions and 144 deletions.
2 changes: 1 addition & 1 deletion mod/cli/pkg/commands/genesis/payload.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ func executableDataToExecutionPayloadHeader(

g.Go(func() error {
var withdrawalsRootErr error
wds := ssz.ListCompositeFromElements(
wds := ssz.ListFromElements(
maxWithdrawalsPerPayload, withdrawals...,
)
withdrawalsRoot, withdrawalsRootErr = wds.HashTreeRoot()
Expand Down
2 changes: 1 addition & 1 deletion mod/consensus-types/pkg/genesis/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ func DefaultGenesisExecutionPayloadHeaderDeneb() (

g.Go(func() error {
var err error
wds := ssz.ListCompositeFromElements(
wds := ssz.ListFromElements(
spec.DevnetChainSpec().MaxWithdrawalsPerPayload(),
[]*engineprimitives.Withdrawal{}...,
)
Expand Down
2 changes: 1 addition & 1 deletion mod/consensus-types/pkg/types/payload.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func (e *ExecutionPayload) ToHeader(

g.Go(func() error {
var withdrawalsRootErr error
wds := ssz.ListCompositeFromElements(
wds := ssz.ListFromElements(
maxWithdrawalsPerPayload,
e.GetWithdrawals()...)
withdrawalsRoot, withdrawalsRootErr = wds.HashTreeRoot()
Expand Down
6 changes: 3 additions & 3 deletions mod/engine-primitives/pkg/engine-primitives/withdrawal.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func (w *Withdrawal) GetAmount() math.Gwei {
}

// NewFromSSZ returns a new Withdrawal from the provided SSZ bytes.
func (Withdrawal) NewFromSSZ(bytes []byte) (*Withdrawal, error) {
func (*Withdrawal) NewFromSSZ(bytes []byte) (*Withdrawal, error) {
w := new(Withdrawal)
if err := w.UnmarshalSSZ(bytes); err != nil {
return nil, err
Expand All @@ -80,11 +80,11 @@ func (Withdrawal) NewFromSSZ(bytes []byte) (*Withdrawal, error) {
}

// IsFixed returns true if the Withdrawal is fixed size.
func (Withdrawal) IsFixed() bool {
func (*Withdrawal) IsFixed() bool {
return true
}

// Type returns the type of the Withdrawal.
func (Withdrawal) Type() types.Type {
func (*Withdrawal) Type() types.Type {
return types.Composite
}
26 changes: 26 additions & 0 deletions mod/primitives/pkg/ssz/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: BUSL-1.1
//
// Copyright (C) 2024, Berachain Foundation. All rights reserved.
// Use of this software is governed by the Business Source License included
// in the LICENSE file of this repository and at www.mariadb.com/bsl11.
//
// ANY USE OF THE LICENSED WORK IN VIOLATION OF THIS LICENSE WILL AUTOMATICALLY
// TERMINATE YOUR RIGHTS UNDER THIS LICENSE FOR THE CURRENT AND ALL OTHER
// VERSIONS OF THE LICENSED WORK.
//
// THIS LICENSE DOES NOT GRANT YOU ANY RIGHT IN ANY TRADEMARK OR LOGO OF
// LICENSOR OR ITS AFFILIATES (PROVIDED THAT YOU MAY USE A TRADEMARK OR LOGO OF
// LICENSOR AS EXPRESSLY REQUIRED BY THIS LICENSE).
//
// TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
// AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
// EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
// TITLE.

package ssz

import "github.com/berachain/beacon-kit/mod/errors"

// ErrUnknownType is returned when an unknown type is encountered.
var ErrUnknownType = errors.New("unknown type")
200 changes: 67 additions & 133 deletions mod/primitives/pkg/ssz/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,199 +21,133 @@
package ssz

import (
"github.com/berachain/beacon-kit/mod/primitives/pkg/common"
"github.com/berachain/beacon-kit/mod/errors"
"github.com/berachain/beacon-kit/mod/primitives/pkg/ssz/constants"
"github.com/berachain/beacon-kit/mod/primitives/pkg/ssz/merkleizer"
"github.com/berachain/beacon-kit/mod/primitives/pkg/ssz/serializer"
"github.com/berachain/beacon-kit/mod/primitives/pkg/ssz/types"
)

/* -------------------------------------------------------------------------- */
/* Basic */
/* Type Definitions */
/* -------------------------------------------------------------------------- */

// ListBasic is a list of basic types.
type ListBasic[B Basic[B]] struct {
// Vector conforms to the SSZEenumerable interface.
var _ types.SSZEnumerable[U64] = (*List[U64])(nil)

// List is a list of basic types.
type List[B types.SSZType[B]] struct {
elements []B
limit uint64
}

// ListBasicFromElements creates a new ListComposite from elements.
// ListFromElements creates a new ListComposite from elements.
// TODO: Deprecate once off of Fastssz
func ListBasicFromElements[B Basic[B]](
func ListFromElements[B types.SSZType[B]](
limit uint64,
elements ...B,
) *ListBasic[B] {
return &ListBasic[B]{
) *List[B] {
return &List[B]{
elements: elements,
limit: limit,
}
}

// IsFixed returns true if the ListBasic is fixed size.
func (l ListBasic[B]) IsFixed() bool {
// We recursively define "variable-size" types to be lists, unions,
// Bitlists.
// Therefore all Lists are NOT fixed.
return false
}

// N returns the N value as defined in the SSZ specification.
func (l ListBasic[B]) N() uint64 {
// list: ordered variable-length homogeneous collection, limited to N values
// notation List[type, N], e.g. List[uint64, N]
return l.limit
}

// ChunkCount returns the number of chunks in the ListBasic.
func (l ListBasic[B]) ChunkCount() uint64 {
// List[B, N] and Vector[B, N], where B is a basic type:
// (N * size_of(B) + 31) // 32 (dividing by chunk size, rounding up)
var b B
//#nosec:G701 // its fine.
//nolint:mnd // 31 is okay.
return (l.N()*uint64(b.SizeSSZ()) + 31) / constants.BytesPerChunk
}

// SizeSSZ returns the size of the list in bytes.
func (l ListBasic[B]) SizeSSZ() int {
// The same for ListBasic as for Vector.
return Vector[B](l.elements).SizeSSZ()
}

// HashTreeRootWith returns the Merkle root of the ListBasic
// with a given merkleizer.
func (l ListBasic[B]) HashTreeRootWith(
merkleizer BasicMerkleizer[[32]byte, B],
) ([32]byte, error) {
return merkleizer.MerkleizeListBasic(l.elements, l.ChunkCount())
}

// HashTreeRoot returns the Merkle root of the ListBasic.
func (l ListBasic[B]) HashTreeRoot() ([32]byte, error) {
// Create a merkleizer
return l.HashTreeRootWith(merkleizer.New[[32]byte, B]())
}

// MarshalSSZTo marshals the ListBasic into SSZ format.
func (l ListBasic[B]) MarshalSSZTo(out []byte) ([]byte, error) {
return Vector[B](l.elements).MarshalSSZTo(out)
}

// MarshalSSZ marshals the ListBasic into SSZ format.
func (l ListBasic[B]) MarshalSSZ() ([]byte, error) {
// The same for ListBasic as for Vector.
return Vector[B](l.elements).MarshalSSZ()
}

// NewFromSSZ creates a new ListBasic from SSZ format.
func (l ListBasic[B]) NewFromSSZ(buf []byte) (*ListBasic[B], error) {
// The same for ListBasic as for Vector
var (
elements = make(Vector[B], 0)
err error
)

elements, err = elements.NewFromSSZ(buf)
return &ListBasic[B]{
elements: elements,
}, err
}

/* -------------------------------------------------------------------------- */
/* Composite */
/* BaseSSZType */
/* -------------------------------------------------------------------------- */

// ListComposite is a list of Composite types.
type ListComposite[C Composite[C]] struct {
elements []C
limit uint64
}

// ListCompositeFromElements creates a new ListComposite from elements.
// TODO: Deprecate once off of Fastssz
func ListCompositeFromElements[C Composite[C]](
limit uint64, elements ...C,
) *ListComposite[C] {
return &ListComposite[C]{
elements: elements,
limit: limit,
}
// SizeSSZ returns the size of the list in bytes.
func (l List[B]) SizeSSZ() int {
// The same for List as for Vector.
return Vector[B](l.elements).SizeSSZ()
}

// IsFixed returns true if the ListBasic is fixed size.
func (l ListComposite[C]) IsFixed() bool {
// IsFixed returns true if the List is fixed size.
func (l List[B]) IsFixed() bool {
// We recursively define "variable-size" types to be lists, unions,
// Bitlists.
// Therefore all Lists are NOT fixed.
return false
}

// N returns the N value as defined in the SSZ specification.
func (l ListComposite[C]) N() uint64 {
func (l List[B]) N() uint64 {
// list: ordered variable-length homogeneous collection, limited to N values
// notation List[type, N], e.g. List[uint64, N]
return l.limit
}

// ChunkCount returns the number of chunks in the VectorComposite.
func (l ListComposite[C]) ChunkCount() uint64 {
// List[C, N] and Vector[C, N], where C is a composite type: N
return l.N()
// ChunkCount returns the number of chunks in the List.
func (l List[B]) ChunkCount() uint64 {
var b B
switch b.Type() {
case types.Basic:
//#nosec:G701 // its fine.
//nolint:mnd // 31 is okay.
return (l.N()*uint64(b.SizeSSZ()) + 31) / constants.BytesPerChunk
default:
return l.N()
}
}

// SizeSSZ returns the size of the list in bytes.
func (l ListComposite[C]) SizeSSZ() int {
// The same for ListComposite as for VectorComposite.
return Vector[C](l.elements).SizeSSZ()
// Type returns the type of the List.
func (l List[B]) Type() types.Type {
return types.Composite
}

// Elements returns the elements of the List.
func (l List[B]) Elements() []B {
return l.elements
}

// HashTreeRootWith returns the Merkle root of the ListComposite
// HashTreeRootWith returns the Merkle root of the List
// with a given merkleizer.
func (l ListComposite[C]) HashTreeRootWith(
merkleizer CompositeMerkleizer[common.ChainSpec, [32]byte, C],
func (l List[B]) HashTreeRootWith(
merkleizer ListMerkleizer[[32]byte, B],
) ([32]byte, error) {
return merkleizer.MerkleizeListComposite(l.elements, l.ChunkCount())
var b B
switch b.Type() {
case types.Basic:
return merkleizer.MerkleizeListBasic(l.elements, l.ChunkCount())
case types.Composite:
return merkleizer.MerkleizeListComposite(l.elements, l.ChunkCount())
default:
return [32]byte{}, errors.Wrapf(ErrUnknownType, "%v", b.Type())
}
}

// HashTreeRoot returns the Merkle root of the ListComposite.
func (l ListComposite[C]) HashTreeRoot() ([32]byte, error) {
// HashTreeRoot returns the Merkle root of the List.
func (l List[B]) HashTreeRoot() ([32]byte, error) {
// Create a merkleizer
return l.HashTreeRootWith(merkleizer.New[[32]byte, C]())
return l.HashTreeRootWith(merkleizer.New[[32]byte, B]())
}

// MarshalSSZTo marshals the ListComposite into SSZ format.
func (l ListComposite[C]) MarshalSSZTo(out []byte) ([]byte, error) {
var c C
if !c.IsFixed() {
panic("not implemented yet")
}

// Safe to use Vector helper for a list here.
return serializer.MarshalVectorFixed(out, l.elements)
// MarshalSSZTo marshals the List into SSZ format.
func (l List[B]) MarshalSSZTo(out []byte) ([]byte, error) {
return Vector[B](l.elements).MarshalSSZTo(out)
}

// MarshalSSZ marshals the ListComposite into SSZ format.
func (l ListComposite[C]) MarshalSSZ() ([]byte, error) {
return l.MarshalSSZTo(make([]byte, 0, l.SizeSSZ()))
// MarshalSSZ marshals the List into SSZ format.
func (l List[B]) MarshalSSZ() ([]byte, error) {
// The same for List as for Vector.
return Vector[B](l.elements).MarshalSSZ()
}

// NewFromSSZ creates a new ListComposite from SSZ format.
func (ListComposite[C]) NewFromSSZ(
buf []byte,
limit uint64,
) (*ListComposite[C], error) {
var c C
if !c.IsFixed() {
// NewFromSSZ creates a new List from SSZ format.
func (l List[B]) NewFromSSZ(buf []byte, limit uint64) (*List[B], error) {
var b B
if !b.IsFixed() {
panic("not implemented yet")
}

// We can use Vector helper for a list here, it is safe.
elements, err := serializer.UnmarshalVectorFixed[C](buf)
elements, err := serializer.UnmarshalVectorFixed[B](buf)
if err != nil {
return nil, err
}

return &ListComposite[C]{
return &List[B]{
elements: elements,
limit: limit,
}, nil
Expand Down
3 changes: 1 addition & 2 deletions mod/primitives/pkg/ssz/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,9 @@ type SSZType[T any] interface {

// SSZEnumerable is the interface for all SSZ enumerable types must implement.
type SSZEnumerable[
SelfT BaseSSZType,
ElementT any,
] interface {
SSZType[SelfT]
BaseSSZType
// N returns the N value as defined in the SSZ specification.
N() uint64
// Elements returns the elements of the enumerable type.
Expand Down
10 changes: 7 additions & 3 deletions mod/primitives/pkg/ssz/vector.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import (
/* -------------------------------------------------------------------------- */

// Vector conforms to the SSZEenumerable interface.
var _ types.SSZEnumerable[Vector[U64], U64] = (Vector[U64])(nil)
var _ types.SSZEnumerable[U64] = (Vector[U64])(nil)

// Vector represents a vector of elements.
type Vector[B types.SSZType[B]] []B
Expand Down Expand Up @@ -102,10 +102,14 @@ func (v Vector[B]) HashTreeRootWith(
merkleizer VectorMerkleizer[[32]byte, B],
) ([32]byte, error) {
var b B
if b.Type() == types.Basic {
switch b.Type() {
case types.Basic:
return merkleizer.MerkleizeVectorBasic(v)
case types.Composite:
return merkleizer.MerkleizeVectorCompositeOrContainer(v)
default:
return [32]byte{}, errors.Wrapf(ErrUnknownType, "%v", b.Type())
}
return merkleizer.MerkleizeVectorCompositeOrContainer(v)
}

// HashTreeRoot returns the Merkle root of the VectorBasic.
Expand Down

0 comments on commit f2087fd

Please sign in to comment.