diff --git a/nodebuilder/header/data_root_tuple_root.go b/nodebuilder/header/data_root_tuple_root.go index 99336e932d..69dbfd1c17 100644 --- a/nodebuilder/header/data_root_tuple_root.go +++ b/nodebuilder/header/data_root_tuple_root.go @@ -58,14 +58,6 @@ func to32PaddedHexBytes(number uint64) ([]byte, error) { return paddedBytes, nil } -// dataRootTuple contains the data that will be used to generate the Blobstream data root tuple -// roots. For more information: -// https://github.com/celestiaorg/blobstream-contracts/blob/master/src/DataRootTuple.sol -type dataRootTuple struct { - height uint64 - dataRoot [32]byte -} - // encodeDataRootTuple takes a height and a data root, and returns the equivalent of // `abi.encode(...)` in Ethereum. // The encoded type is a dataRootTuple, which has the following ABI: @@ -137,24 +129,13 @@ func (s *Service) validateDataRootTupleRootRange(ctx context.Context, start, end return nil } -// hashDataRootTuples hashes a list of blocks data root tuples, i.e., height, data root and square -// size, then returns their merkle root. -func hashDataRootTuples(tuples []dataRootTuple) ([]byte, error) { - if len(tuples) == 0 { - return nil, fmt.Errorf("cannot hash an empty list of data root tuples") - } - dataRootEncodedTuples := make([][]byte, 0, len(tuples)) - for _, tuple := range tuples { - encodedTuple, err := encodeDataRootTuple( - tuple.height, - tuple.dataRoot, - ) - if err != nil { - return nil, err - } - dataRootEncodedTuples = append(dataRootEncodedTuples, encodedTuple) +// hashDataRootTuples hashes a list of encoded blocks data root tuples, i.e., height, data root and +// square size, then returns their merkle root. +func hashDataRootTuples(encodedDataRootTuples [][]byte) ([]byte, error) { + if len(encodedDataRootTuples) == 0 { + return nil, fmt.Errorf("cannot hash an empty list of encoded data root tuples") } - root := merkle.HashFromByteSlices(dataRootEncodedTuples) + root := merkle.HashFromByteSlices(encodedDataRootTuples) return root, nil } @@ -180,33 +161,23 @@ func (s *Service) validateDataRootInclusionProofRequest( } // proveDataRootTuples returns the merkle inclusion proof for a height. -func proveDataRootTuples(tuples []dataRootTuple, height uint64) (*merkle.Proof, error) { - if len(tuples) == 0 { - return nil, fmt.Errorf("cannot prove an empty list of tuples") +// expects the list of encoded data root tuples to be ordered and the heights to be consecutive. +func proveDataRootTuples(encodedDataRootTuples [][]byte, rangeStartHeight, height uint64) (*merkle.Proof, error) { + if len(encodedDataRootTuples) == 0 { + return nil, fmt.Errorf("cannot prove an empty list of encoded data root tuples") } - if height == 0 { + if height == 0 || rangeStartHeight == 0 { return nil, ErrHeightZero } - dataRootEncodedTuples := make([][]byte, 0, len(tuples)) - for _, tuple := range tuples { - encodedTuple, err := encodeDataRootTuple( - tuple.height, - tuple.dataRoot, - ) - if err != nil { - return nil, err - } - dataRootEncodedTuples = append(dataRootEncodedTuples, encodedTuple) - } - _, proofs := merkle.ProofsFromByteSlices(dataRootEncodedTuples) - return proofs[height-tuples[0].height], nil + _, proofs := merkle.ProofsFromByteSlices(encodedDataRootTuples) + return proofs[height-rangeStartHeight], nil } -// fetchDataRootTuples takes an end exclusive range of heights and fetches its +// fetchEncodedDataRootTuples takes an end exclusive range of heights and fetches its // corresponding data root tuples. // end is not included in the range. -func (s *Service) fetchDataRootTuples(ctx context.Context, start, end uint64) ([]dataRootTuple, error) { - tuples := make([]dataRootTuple, 0, end-start) +func (s *Service) fetchEncodedDataRootTuples(ctx context.Context, start, end uint64) ([][]byte, error) { + encodedDataRootTuples := make([][]byte, 0, end-start) startHeader, err := s.GetByHeight(ctx, start) if err != nil { return nil, err @@ -216,10 +187,11 @@ func (s *Service) fetchDataRootTuples(ctx context.Context, start, end uint64) ([ return nil, err } for _, header := range headerRange { - tuples = append(tuples, dataRootTuple{ - height: header.Height(), - dataRoot: *(*[32]byte)(header.DataHash), - }) + encodedDataRootTuple, err := encodeDataRootTuple(header.Height(), *(*[32]byte)(header.DataHash)) + if err != nil { + return nil, err + } + encodedDataRootTuples = append(encodedDataRootTuples, encodedDataRootTuple) } - return tuples, nil + return encodedDataRootTuples, nil } diff --git a/nodebuilder/header/service.go b/nodebuilder/header/service.go index 455d0bffd1..1e40a917c8 100644 --- a/nodebuilder/header/service.go +++ b/nodebuilder/header/service.go @@ -157,12 +157,12 @@ func (s *Service) GetDataRootTupleRoot(ctx context.Context, start, end uint64) ( return nil, err } log.Debugw("fetching the data root tuples", "start", start, "end", end) - tuples, err := s.fetchDataRootTuples(ctx, start, end) + encodedDataRootTuples, err := s.fetchEncodedDataRootTuples(ctx, start, end) if err != nil { return nil, err } log.Debugw("hashing the data root tuples", "start", start, "end", end) - root, err := hashDataRootTuples(tuples) + root, err := hashDataRootTuples(encodedDataRootTuples) if err != nil { return nil, err } @@ -187,18 +187,18 @@ func (s *Service) GetDataRootTupleInclusionProof( "height", height, ) - err := s.validateDataRootInclusionProofRequest(ctx, uint64(height), start, end) + err := s.validateDataRootInclusionProofRequest(ctx, height, start, end) if err != nil { return nil, err } log.Debugw("fetching the data root tuples", "start", start, "end", end) - tuples, err := s.fetchDataRootTuples(ctx, start, end) + encodedDataRootTuples, err := s.fetchEncodedDataRootTuples(ctx, start, end) if err != nil { return nil, err } log.Debugw("proving the data root tuples", "start", start, "end", end) - proof, err := proveDataRootTuples(tuples, height) + proof, err := proveDataRootTuples(encodedDataRootTuples, start, height) if err != nil { return nil, err } diff --git a/nodebuilder/header/service_test.go b/nodebuilder/header/service_test.go index 3bde8a6400..789f97d51e 100644 --- a/nodebuilder/header/service_test.go +++ b/nodebuilder/header/service_test.go @@ -141,22 +141,17 @@ func TestEncodeDataRootTuple(t *testing.T) { func TestHashDataRootTuples(t *testing.T) { tests := map[string]struct { - tuples []dataRootTuple + tuples [][]byte expectedHash []byte expectErr bool }{ "empty tuples list": {tuples: nil, expectErr: true}, - "valid list of data root tuples": { - tuples: []dataRootTuple{ - { - height: 1, - dataRoot: [32]byte{0x1}, - }, - { - height: 2, - dataRoot: [32]byte{0x2}, - }, - }, + "valid list of encoded data root tuples": { + tuples: func() [][]byte { + tuple1, _ := encodeDataRootTuple(1, [32]byte{0x1}) + tuple2, _ := encodeDataRootTuple(2, [32]byte{0x2}) + return [][]byte{tuple1, tuple2} + }(), expectedHash: func() []byte { tuple1, _ := encodeDataRootTuple(1, [32]byte{0x1}) tuple2, _ := encodeDataRootTuple(2, [32]byte{0x2}) @@ -181,146 +176,25 @@ func TestHashDataRootTuples(t *testing.T) { func TestProveDataRootTuples(t *testing.T) { tests := map[string]struct { - tuples []dataRootTuple + tuples [][]byte height uint64 + rangeStart uint64 expectedProof merkle.Proof expectErr bool }{ - "empty tuples list": {tuples: nil, expectErr: true}, - "non consecutive list of tuples at the beginning": { - tuples: []dataRootTuple{ - { - height: 1, - dataRoot: [32]byte{0x1}, - }, - { - height: 3, - dataRoot: [32]byte{0x2}, - }, - { - height: 4, - dataRoot: [32]byte{0x4}, - }, - }, - expectErr: true, - }, - "non consecutive list of tuples in the middle": { - tuples: []dataRootTuple{ - { - height: 1, - dataRoot: [32]byte{0x1}, - }, - { - height: 2, - dataRoot: [32]byte{0x2}, - }, - { - height: 3, - dataRoot: [32]byte{0x2}, - }, - { - height: 5, - dataRoot: [32]byte{0x4}, - }, - { - height: 6, - dataRoot: [32]byte{0x5}, - }, - }, - expectErr: true, - }, - "non consecutive list of tuples at the end": { - tuples: []dataRootTuple{ - { - height: 1, - dataRoot: [32]byte{0x1}, - }, - { - height: 2, - dataRoot: [32]byte{0x2}, - }, - { - height: 4, - dataRoot: [32]byte{0x4}, - }, - }, - expectErr: true, - }, - "duplicate height at the beginning": { - tuples: []dataRootTuple{ - { - height: 1, - dataRoot: [32]byte{0x1}, - }, - { - height: 1, - dataRoot: [32]byte{0x1}, - }, - { - height: 4, - dataRoot: [32]byte{0x4}, - }, - }, - expectErr: true, - }, - "duplicate height in the middle": { - tuples: []dataRootTuple{ - { - height: 1, - dataRoot: [32]byte{0x1}, - }, - { - height: 2, - dataRoot: [32]byte{0x2}, - }, - { - height: 2, - dataRoot: [32]byte{0x2}, - }, - { - height: 3, - dataRoot: [32]byte{0x3}, - }, - }, - expectErr: true, - }, - "duplicate height at the end": { - tuples: []dataRootTuple{ - { - height: 1, - dataRoot: [32]byte{0x1}, - }, - { - height: 2, - dataRoot: [32]byte{0x2}, - }, - { - height: 2, - dataRoot: [32]byte{0x2}, - }, - }, - expectErr: true, - }, + "empty tuples list": {tuples: [][]byte{{0x1}}, expectErr: true}, + "start height == 0": {tuples: [][]byte{{0x1}}, expectErr: true}, + "range start height == 0": {tuples: [][]byte{{0x1}}, expectErr: true}, "valid proof": { - height: 3, - tuples: []dataRootTuple{ - { - height: 1, - dataRoot: [32]byte{0x1}, - }, - { - height: 2, - dataRoot: [32]byte{0x2}, - }, - { - height: 3, - dataRoot: [32]byte{0x3}, - }, - { - height: 4, - dataRoot: [32]byte{0x4}, - }, - }, + height: 3, + rangeStart: 1, + tuples: func() [][]byte { + encodedTuple1, _ := encodeDataRootTuple(1, [32]byte{0x1}) + encodedTuple2, _ := encodeDataRootTuple(2, [32]byte{0x2}) + encodedTuple3, _ := encodeDataRootTuple(3, [32]byte{0x3}) + encodedTuple4, _ := encodeDataRootTuple(4, [32]byte{0x4}) + return [][]byte{encodedTuple1, encodedTuple2, encodedTuple3, encodedTuple4} + }(), expectedProof: func() merkle.Proof { encodedTuple1, _ := encodeDataRootTuple(1, [32]byte{0x1}) encodedTuple2, _ := encodeDataRootTuple(2, [32]byte{0x2}) @@ -334,7 +208,7 @@ func TestProveDataRootTuples(t *testing.T) { for name, tc := range tests { t.Run(name, func(t *testing.T) { - result, err := proveDataRootTuples(tc.tuples, tc.height) + result, err := proveDataRootTuples(tc.tuples, tc.rangeStart, tc.height) if tc.expectErr { assert.Error(t, err) } else {