Skip to content

Commit 1d25da2

Browse files
committed
generic ssz list encoding
1 parent 97f416b commit 1d25da2

File tree

7 files changed

+261
-137
lines changed

7 files changed

+261
-137
lines changed

beacon-chain/p2p/types/BUILD.bazel

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ go_library(
2525
"//consensus-types/primitives:go_default_library",
2626
"//consensus-types/wrapper:go_default_library",
2727
"//encoding/bytesutil:go_default_library",
28+
"//encoding/ssz:go_default_library",
2829
"//proto/engine/v1:go_default_library",
2930
"//proto/prysm/v1alpha1:go_default_library",
3031
"//proto/prysm/v1alpha1/metadata:go_default_library",
@@ -45,10 +46,10 @@ go_test(
4546
"//config/params:go_default_library",
4647
"//consensus-types/primitives:go_default_library",
4748
"//encoding/bytesutil:go_default_library",
49+
"//encoding/ssz:go_default_library",
4850
"//proto/prysm/v1alpha1:go_default_library",
4951
"//runtime/version:go_default_library",
5052
"//testing/assert:go_default_library",
5153
"//testing/require:go_default_library",
52-
"@com_github_prysmaticlabs_fastssz//:go_default_library",
5354
],
5455
)

beacon-chain/p2p/types/types.go

Lines changed: 23 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ package types
55

66
import (
77
"bytes"
8-
"encoding/binary"
98
"sort"
109

1110
fieldparams "github.com/OffchainLabs/prysm/v6/config/fieldparams"
1211
"github.com/OffchainLabs/prysm/v6/config/params"
12+
"github.com/OffchainLabs/prysm/v6/encoding/ssz"
1313
eth "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1"
1414
"github.com/pkg/errors"
15-
ssz "github.com/prysmaticlabs/fastssz"
15+
fastssz "github.com/prysmaticlabs/fastssz"
1616
)
1717

1818
const (
@@ -25,11 +25,11 @@ type SSZBytes []byte
2525

2626
// HashTreeRoot hashes the uint64 object following the SSZ standard.
2727
func (b *SSZBytes) HashTreeRoot() ([32]byte, error) {
28-
return ssz.HashWithDefaultHasher(b)
28+
return fastssz.HashWithDefaultHasher(b)
2929
}
3030

3131
// HashTreeRootWith hashes the uint64 object with the given hasher.
32-
func (b *SSZBytes) HashTreeRootWith(hh *ssz.Hasher) error {
32+
func (b *SSZBytes) HashTreeRootWith(hh *fastssz.Hasher) error {
3333
indx := hh.Index()
3434
hh.PutBytes(*b)
3535
hh.Merkleize(indx)
@@ -74,7 +74,7 @@ func (r *BeaconBlockByRootsReq) UnmarshalSSZ(buf []byte) error {
7474
return errors.Errorf("expected buffer with length of up to %d but received length %d", maxLength, bufLen)
7575
}
7676
if bufLen%fieldparams.RootLength != 0 {
77-
return ssz.ErrIncorrectByteSize
77+
return fastssz.ErrIncorrectByteSize
7878
}
7979
numOfRoots := bufLen / fieldparams.RootLength
8080
roots := make([][fieldparams.RootLength]byte, 0, numOfRoots)
@@ -131,14 +131,6 @@ func (m *ErrorMessage) UnmarshalSSZ(buf []byte) error {
131131
// BlobSidecarsByRootReq is used to specify a list of blob targets (root+index) in a BlobSidecarsByRoot RPC request.
132132
type BlobSidecarsByRootReq []*eth.BlobIdentifier
133133

134-
// BlobIdentifier is a fixed size value, so we can compute its fixed size at start time (see init below)
135-
var blobIdSize int
136-
137-
// SizeSSZ returns the size of the serialized representation.
138-
func (b *BlobSidecarsByRootReq) SizeSSZ() int {
139-
return len(*b) * blobIdSize
140-
}
141-
142134
// MarshalSSZTo appends the serialized BlobSidecarsByRootReq value to the provided byte slice.
143135
func (b *BlobSidecarsByRootReq) MarshalSSZTo(dst []byte) ([]byte, error) {
144136
// A List without an enclosing container is marshaled exactly like a vector, no length offset required.
@@ -151,38 +143,22 @@ func (b *BlobSidecarsByRootReq) MarshalSSZTo(dst []byte) ([]byte, error) {
151143

152144
// MarshalSSZ serializes the BlobSidecarsByRootReq value to a byte slice.
153145
func (b *BlobSidecarsByRootReq) MarshalSSZ() ([]byte, error) {
154-
buf := make([]byte, len(*b)*blobIdSize)
155-
for i, id := range *b {
156-
by, err := id.MarshalSSZ()
157-
if err != nil {
158-
return nil, err
159-
}
160-
copy(buf[i*blobIdSize:(i+1)*blobIdSize], by)
161-
}
162-
return buf, nil
146+
return ssz.MarshalListFixedElement[*eth.BlobIdentifier](*b)
163147
}
164148

149+
func newBSBR() *eth.BlobIdentifier { return &eth.BlobIdentifier{} }
150+
165151
// UnmarshalSSZ unmarshals the provided bytes buffer into the
166152
// BlobSidecarsByRootReq value.
167153
func (b *BlobSidecarsByRootReq) UnmarshalSSZ(buf []byte) error {
168-
bufLen := len(buf)
169-
maxLength := int(params.BeaconConfig().MaxRequestBlobSidecarsElectra) * blobIdSize
170-
if bufLen > maxLength {
171-
return errors.Wrapf(ssz.ErrIncorrectListSize, "expected buffer with length of up to %d but received length %d", maxLength, bufLen)
172-
}
173-
if bufLen%blobIdSize != 0 {
174-
return errors.Wrapf(ssz.ErrIncorrectByteSize, "size=%d", bufLen)
154+
v, err := ssz.UnmarshalListFixedElement[*eth.BlobIdentifier](buf, newBSBR)
155+
if err != nil {
156+
return errors.Wrapf(err, "failed to unmarshal BlobSidecarsByRootReq")
175157
}
176-
count := bufLen / blobIdSize
177-
*b = make([]*eth.BlobIdentifier, count)
178-
for i := 0; i < count; i++ {
179-
id := &eth.BlobIdentifier{}
180-
err := id.UnmarshalSSZ(buf[i*blobIdSize : (i+1)*blobIdSize])
181-
if err != nil {
182-
return err
183-
}
184-
(*b)[i] = id
158+
if len(v) > int(params.BeaconConfig().MaxRequestBlobSidecarsElectra) {
159+
return ErrMaxBlobReqExceeded
185160
}
161+
*b = v
186162
return nil
187163
}
188164

@@ -213,102 +189,25 @@ func (s BlobSidecarsByRootReq) Len() int {
213189
// ====================================
214190
// DataColumnsByRootIdentifiers section
215191
// ====================================
216-
var _ ssz.Marshaler = DataColumnsByRootIdentifiers{}
217-
var _ ssz.Unmarshaler = (*DataColumnsByRootIdentifiers)(nil)
192+
var _ fastssz.Marshaler = &DataColumnsByRootIdentifiers{}
193+
var _ fastssz.Unmarshaler = &DataColumnsByRootIdentifiers{}
218194

219195
// DataColumnsByRootIdentifiers is used to specify a list of data column targets (root+index) in a DataColumnSidecarsByRoot RPC request.
220196
type DataColumnsByRootIdentifiers []*eth.DataColumnsByRootIdentifier
221197

222-
// DataColumnIdentifier is a fixed size value, so we can compute its fixed size at start time (see init below)
223-
var dataColumnIdSize int
198+
func newDCRI() *eth.DataColumnsByRootIdentifier { return &eth.DataColumnsByRootIdentifier{} }
224199

225-
// UnmarshalSSZ implements ssz.Unmarshaler. It unmarshals the provided bytes buffer into the DataColumnSidecarsByRootReq value.
226200
func (d *DataColumnsByRootIdentifiers) UnmarshalSSZ(buf []byte) error {
227-
// Exit early if the buffer is too small.
228-
if len(buf) < bytesPerLengthOffset {
229-
return nil
230-
}
231-
232-
// Get the size of the offsets.
233-
offsetEnd := binary.LittleEndian.Uint32(buf[:bytesPerLengthOffset])
234-
if offsetEnd%bytesPerLengthOffset != 0 {
235-
return errors.Errorf("expected offsets size to be a multiple of %d but got %d", bytesPerLengthOffset, offsetEnd)
236-
}
237-
238-
count := offsetEnd / bytesPerLengthOffset
239-
if count < 1 {
240-
return nil
241-
}
242-
243-
maxSize := params.BeaconConfig().MaxRequestBlocksDeneb
244-
if uint64(count) > maxSize {
245-
return errors.Errorf("data column identifiers list exceeds max size: %d > %d", count, maxSize)
246-
}
247-
248-
if offsetEnd > uint32(len(buf)) {
249-
return errors.Errorf("offsets value %d larger than buffer %d", offsetEnd, len(buf))
250-
}
251-
valueStart := offsetEnd
252-
253-
// Decode the identifers.
254-
*d = make([]*eth.DataColumnsByRootIdentifier, count)
255-
var start uint32
256-
end := uint32(len(buf))
257-
for i := count; i > 0; i-- {
258-
offsetEnd -= bytesPerLengthOffset
259-
start = binary.LittleEndian.Uint32(buf[offsetEnd : offsetEnd+bytesPerLengthOffset])
260-
if start > end {
261-
return errors.Errorf("expected offset[%d] %d to be less than %d", i-1, start, end)
262-
}
263-
if start < valueStart {
264-
return errors.Errorf("offset[%d] %d indexes before value section %d", i-1, start, valueStart)
265-
}
266-
// Decode the identifier.
267-
ident := &eth.DataColumnsByRootIdentifier{}
268-
if err := ident.UnmarshalSSZ(buf[start:end]); err != nil {
269-
return err
270-
}
271-
(*d)[i-1] = ident
272-
end = start
201+
v, err := ssz.UnmarshalListVariableElement[*eth.DataColumnsByRootIdentifier](buf, newDCRI)
202+
if err != nil {
203+
return errors.Wrapf(err, "failed to unmarshal DataColumnsByRootIdentifiers")
273204
}
274-
205+
*d = v
275206
return nil
276207
}
277208

278-
func (d DataColumnsByRootIdentifiers) MarshalSSZ() ([]byte, error) {
279-
var err error
280-
count := len(d)
281-
maxSize := params.BeaconConfig().MaxRequestBlocksDeneb
282-
if uint64(count) > maxSize {
283-
return nil, errors.Errorf("data column identifiers list exceeds max size: %d > %d", count, maxSize)
284-
}
285-
286-
if len(d) == 0 {
287-
return []byte{}, nil
288-
}
289-
sizes := make([]uint32, count)
290-
valTotal := uint32(0)
291-
for i, elem := range d {
292-
if elem == nil {
293-
return nil, errors.New("nil item in DataColumnsByRootIdentifiers list")
294-
}
295-
sizes[i] = uint32(elem.SizeSSZ())
296-
valTotal += sizes[i]
297-
}
298-
offSize := uint32(4 * len(d))
299-
out := make([]byte, offSize, offSize+valTotal)
300-
for i := range sizes {
301-
binary.LittleEndian.PutUint32(out[i*4:i*4+4], offSize)
302-
offSize += sizes[i]
303-
}
304-
for _, elem := range d {
305-
out, err = elem.MarshalSSZTo(out)
306-
if err != nil {
307-
return nil, err
308-
}
309-
}
310-
311-
return out, nil
209+
func (d *DataColumnsByRootIdentifiers) MarshalSSZ() ([]byte, error) {
210+
return ssz.MarshalListVariableElement[*eth.DataColumnsByRootIdentifier](*d)
312211
}
313212

314213
// MarshalSSZTo implements ssz.Marshaler. It appends the serialized DataColumnSidecarsByRootReq value to the provided byte slice.
@@ -329,11 +228,3 @@ func (d DataColumnsByRootIdentifiers) SizeSSZ() int {
329228
}
330229
return size
331230
}
332-
333-
func init() {
334-
blobSizer := &eth.BlobIdentifier{}
335-
blobIdSize = blobSizer.SizeSSZ()
336-
337-
dataColumnSizer := &eth.DataColumnSidecarsByRangeRequest{}
338-
dataColumnIdSize = dataColumnSizer.SizeSSZ()
339-
}

beacon-chain/p2p/types/types_test.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ import (
88
"github.com/OffchainLabs/prysm/v6/config/params"
99
"github.com/OffchainLabs/prysm/v6/consensus-types/primitives"
1010
"github.com/OffchainLabs/prysm/v6/encoding/bytesutil"
11+
"github.com/OffchainLabs/prysm/v6/encoding/ssz"
1112
eth "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1"
1213
"github.com/OffchainLabs/prysm/v6/testing/assert"
1314
"github.com/OffchainLabs/prysm/v6/testing/require"
14-
ssz "github.com/prysmaticlabs/fastssz"
1515
)
1616

1717
func generateBlobIdentifiers(n int) []*eth.BlobIdentifier {
@@ -51,7 +51,7 @@ func TestBlobSidecarsByRootReq_MarshalSSZ(t *testing.T) {
5151
{
5252
name: "beyond max list",
5353
ids: generateBlobIdentifiers(int(params.BeaconConfig().MaxRequestBlobSidecarsElectra) + 1),
54-
unmarshalErr: ssz.ErrIncorrectListSize,
54+
unmarshalErr: ErrMaxBlobReqExceeded,
5555
},
5656
{
5757
name: "wonky unmarshal size",
@@ -60,7 +60,7 @@ func TestBlobSidecarsByRootReq_MarshalSSZ(t *testing.T) {
6060
in = append(in, byte(0))
6161
return in
6262
},
63-
unmarshalErr: ssz.ErrIncorrectByteSize,
63+
unmarshalErr: ssz.ErrInvalidFixedEncodingLen,
6464
},
6565
}
6666

@@ -305,7 +305,8 @@ func TestDataColumnSidecarsByRootReq_MarshalUnmarshal(t *testing.T) {
305305
name: "size too big",
306306
ids: generateDataColumnIdentifiers(1),
307307
unmarshalMod: func(in []byte) []byte {
308-
maxLen := params.BeaconConfig().MaxRequestDataColumnSidecars * uint64(dataColumnIdSize)
308+
sizer := &eth.DataColumnSidecarsByRangeRequest{}
309+
maxLen := params.BeaconConfig().MaxRequestDataColumnSidecars * uint64(sizer.SizeSSZ())
309310
add := make([]byte, maxLen)
310311
in = append(in, add...)
311312
return in
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
## Added
2+
- Methods to generically encode/decode independent lists of ssz values.

encoding/ssz/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ load("@prysm//tools/go:def.bzl", "go_library", "go_test")
33
go_library(
44
name = "go_default_library",
55
srcs = [
6+
"errors.go",
67
"hashers.go",
78
"helpers.go",
89
"htrutils.go",
10+
"list.go",
911
"merkleize.go",
1012
"slice_root.go",
1113
],

encoding/ssz/errors.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package ssz
2+
3+
import "github.com/pkg/errors"
4+
5+
var (
6+
ErrInvalidEncodingLength = errors.New("invalid encoded length")
7+
ErrInvalidFixedEncodingLen = errors.Wrap(ErrInvalidEncodingLength, "not multiple of fixed size")
8+
ErrEncodingSmallerThanOffset = errors.Wrap(ErrInvalidEncodingLength, "smaller than a single offset")
9+
ErrInvalidOffset = errors.New("invalid offset")
10+
ErrOffsetIntoFixed = errors.Wrap(ErrInvalidOffset, "does not point past fixed section of encoding")
11+
ErrOffsetExceedsBuffer = errors.Wrap(ErrInvalidOffset, "exceeds buffer length")
12+
ErrNegativeRelativeOffset = errors.Wrap(ErrInvalidOffset, "less than previous offset")
13+
ErrOffsetInsufficient = errors.Wrap(ErrInvalidOffset, "insufficient difference relative to previous")
14+
ErrOffsetSectionMisaligned = errors.Wrap(ErrInvalidOffset, "offset bytes are not a multiple of offset size")
15+
16+
ErrOffsetDecodedMismatch = errors.New("unmarshaled size does not relative offsets")
17+
)

0 commit comments

Comments
 (0)