diff --git a/nodebuilder/blobstream/blobstream.go b/nodebuilder/blobstream/blobstream.go new file mode 100644 index 0000000000..57a8ab26cf --- /dev/null +++ b/nodebuilder/blobstream/blobstream.go @@ -0,0 +1,48 @@ +package blobstream + +import ( + "context" +) + +var _ Module = (*API)(nil) + +// Module defines the API related to interacting with the data root tuples proofs +// +//go:generate mockgen -destination=mocks/api.go -package=mocks . Module +type Module interface { + // GetDataRootTupleRoot collects the data roots over a provided ordered range of blocks, + // and then creates a new Merkle root of those data roots. The range is end exclusive. + // It's in the header module because it only needs access to the headers to generate the proof. + GetDataRootTupleRoot(ctx context.Context, start, end uint64) (*DataRootTupleRoot, error) + + // GetDataRootTupleInclusionProof creates an inclusion proof, for the data root tuple of block + // height `height`, in the set of blocks defined by `start` and `end`. The range + // is end exclusive. + // It's in the header module because it only needs access to the headers to generate the proof. + GetDataRootTupleInclusionProof( + ctx context.Context, + height, start, end uint64, + ) (*DataRootTupleInclusionProof, error) +} + +// API is a wrapper around the Module for RPC. +type API struct { + Internal struct { + GetDataRootTupleRoot func(ctx context.Context, start, end uint64) (*DataRootTupleRoot, error) `perm:"read"` + GetDataRootTupleInclusionProof func( + ctx context.Context, + height, start, end uint64, + ) (*DataRootTupleInclusionProof, error) `perm:"read"` + } +} + +func (api *API) GetDataRootTupleRoot(ctx context.Context, start, end uint64) (*DataRootTupleRoot, error) { + return api.Internal.GetDataRootTupleRoot(ctx, start, end) +} + +func (api *API) GetDataRootTupleInclusionProof( + ctx context.Context, + height, start, end uint64, +) (*DataRootTupleInclusionProof, error) { + return api.Internal.GetDataRootTupleInclusionProof(ctx, height, start, end) +} diff --git a/nodebuilder/header/data_root_tuple_root.go b/nodebuilder/blobstream/data_root_tuple_root.go similarity index 94% rename from nodebuilder/header/data_root_tuple_root.go rename to nodebuilder/blobstream/data_root_tuple_root.go index 69dbfd1c17..ae314d6d2d 100644 --- a/nodebuilder/header/data_root_tuple_root.go +++ b/nodebuilder/blobstream/data_root_tuple_root.go @@ -1,4 +1,4 @@ -package header +package blobstream import ( "context" @@ -8,6 +8,8 @@ import ( "github.com/tendermint/tendermint/crypto/merkle" "github.com/tendermint/tendermint/libs/bytes" + + "github.com/celestiaorg/celestia-node/nodebuilder/header" ) // DataRootTupleRoot is the root of the merkle tree created @@ -102,7 +104,7 @@ const dataRootTupleRootBlocksLimit = 10_000 // ~33 hours of blocks assuming 12-s // the defined set of heights by ensuring the range exists in the chain. func (s *Service) validateDataRootTupleRootRange(ctx context.Context, start, end uint64) error { if start == 0 { - return ErrHeightZero + return header.ErrHeightZero } if start >= end { return fmt.Errorf("end block is smaller or equal to the start block") @@ -113,7 +115,7 @@ func (s *Service) validateDataRootTupleRootRange(ctx context.Context, start, end return fmt.Errorf("the query exceeds the limit of allowed blocks %d", dataRootTupleRootBlocksLimit) } - currentLocalHeader, err := s.LocalHead(ctx) + currentLocalHeader, err := s.headerServ.LocalHead(ctx) if err != nil { return fmt.Errorf("couldn't get the local head to validate the data root tuple root range%w", err) } @@ -167,7 +169,7 @@ func proveDataRootTuples(encodedDataRootTuples [][]byte, rangeStartHeight, heigh return nil, fmt.Errorf("cannot prove an empty list of encoded data root tuples") } if height == 0 || rangeStartHeight == 0 { - return nil, ErrHeightZero + return nil, header.ErrHeightZero } _, proofs := merkle.ProofsFromByteSlices(encodedDataRootTuples) return proofs[height-rangeStartHeight], nil @@ -178,11 +180,11 @@ func proveDataRootTuples(encodedDataRootTuples [][]byte, rangeStartHeight, heigh // end is not included in the range. func (s *Service) fetchEncodedDataRootTuples(ctx context.Context, start, end uint64) ([][]byte, error) { encodedDataRootTuples := make([][]byte, 0, end-start) - startHeader, err := s.GetByHeight(ctx, start) + startHeader, err := s.headerServ.GetByHeight(ctx, start) if err != nil { return nil, err } - headerRange, err := s.GetRangeByHeight(ctx, startHeader, end) + headerRange, err := s.headerServ.GetRangeByHeight(ctx, startHeader, end) if err != nil { return nil, err } diff --git a/nodebuilder/blobstream/mocks/api.go b/nodebuilder/blobstream/mocks/api.go new file mode 100644 index 0000000000..e4dff86a78 --- /dev/null +++ b/nodebuilder/blobstream/mocks/api.go @@ -0,0 +1,66 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/celestiaorg/celestia-node/nodebuilder/blobstream (interfaces: Module) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + blobstream "github.com/celestiaorg/celestia-node/nodebuilder/blobstream" + gomock "github.com/golang/mock/gomock" +) + +// MockModule is a mock of Module interface. +type MockModule struct { + ctrl *gomock.Controller + recorder *MockModuleMockRecorder +} + +// MockModuleMockRecorder is the mock recorder for MockModule. +type MockModuleMockRecorder struct { + mock *MockModule +} + +// NewMockModule creates a new mock instance. +func NewMockModule(ctrl *gomock.Controller) *MockModule { + mock := &MockModule{ctrl: ctrl} + mock.recorder = &MockModuleMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockModule) EXPECT() *MockModuleMockRecorder { + return m.recorder +} + +// GetDataRootTupleInclusionProof mocks base method. +func (m *MockModule) GetDataRootTupleInclusionProof(arg0 context.Context, arg1, arg2, arg3 uint64) (*blobstream.DataRootTupleInclusionProof, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetDataRootTupleInclusionProof", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*blobstream.DataRootTupleInclusionProof) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetDataRootTupleInclusionProof indicates an expected call of GetDataRootTupleInclusionProof. +func (mr *MockModuleMockRecorder) GetDataRootTupleInclusionProof(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDataRootTupleInclusionProof", reflect.TypeOf((*MockModule)(nil).GetDataRootTupleInclusionProof), arg0, arg1, arg2, arg3) +} + +// GetDataRootTupleRoot mocks base method. +func (m *MockModule) GetDataRootTupleRoot(arg0 context.Context, arg1, arg2 uint64) (*blobstream.DataRootTupleRoot, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetDataRootTupleRoot", arg0, arg1, arg2) + ret0, _ := ret[0].(*blobstream.DataRootTupleRoot) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetDataRootTupleRoot indicates an expected call of GetDataRootTupleRoot. +func (mr *MockModuleMockRecorder) GetDataRootTupleRoot(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDataRootTupleRoot", reflect.TypeOf((*MockModule)(nil).GetDataRootTupleRoot), arg0, arg1, arg2) +} diff --git a/nodebuilder/blobstream/module.go b/nodebuilder/blobstream/module.go new file mode 100644 index 0000000000..c8deb1db10 --- /dev/null +++ b/nodebuilder/blobstream/module.go @@ -0,0 +1,12 @@ +package blobstream + +import "go.uber.org/fx" + +func ConstructModule() fx.Option { + return fx.Module("blobstream", + fx.Provide(NewService), + fx.Provide(func(serv *Service) Module { + return serv + }), + ) +} diff --git a/nodebuilder/blobstream/service.go b/nodebuilder/blobstream/service.go new file mode 100644 index 0000000000..5803e19012 --- /dev/null +++ b/nodebuilder/blobstream/service.go @@ -0,0 +1,81 @@ +package blobstream + +import ( + "context" + + logging "github.com/ipfs/go-log/v2" + + headerServ "github.com/celestiaorg/celestia-node/nodebuilder/header" +) + +var _ Module = (*Service)(nil) + +var log = logging.Logger("go-blobstream") + +type Service struct { + headerServ headerServ.Module +} + +func NewService(headerMod headerServ.Module) *Service { + return &Service{ + headerServ: headerMod, + } +} + +// GetDataRootTupleRoot collects the data roots over a provided ordered range of blocks, +// and then creates a new Merkle root of those data roots. The range is end exclusive. +func (s *Service) GetDataRootTupleRoot(ctx context.Context, start, end uint64) (*DataRootTupleRoot, error) { + log.Debugw("validating the data commitment range", "start", start, "end", end) + err := s.validateDataRootTupleRootRange(ctx, start, end) + if err != nil { + return nil, err + } + log.Debugw("fetching the data root tuples", "start", start, "end", 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(encodedDataRootTuples) + if err != nil { + return nil, err + } + // Create data commitment + dataRootTupleRoot := DataRootTupleRoot(root) + return &dataRootTupleRoot, nil +} + +// GetDataRootTupleInclusionProof creates an inclusion proof for the data root of block +// height `height` in the set of blocks defined by `start` and `end`. The range +// is end exclusive. +func (s *Service) GetDataRootTupleInclusionProof( + ctx context.Context, + height, start, end uint64, +) (*DataRootTupleInclusionProof, error) { + log.Debugw( + "validating the data root inclusion proof request", + "start", + start, + "end", + end, + "height", + height, + ) + err := s.validateDataRootInclusionProofRequest(ctx, height, start, end) + if err != nil { + return nil, err + } + log.Debugw("fetching the data root tuples", "start", start, "end", 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(encodedDataRootTuples, start, height) + if err != nil { + return nil, err + } + dataRootTupleInclusionProof := DataRootTupleInclusionProof(proof) + return &dataRootTupleInclusionProof, nil +} diff --git a/nodebuilder/blobstream/service_test.go b/nodebuilder/blobstream/service_test.go new file mode 100644 index 0000000000..77ee47efff --- /dev/null +++ b/nodebuilder/blobstream/service_test.go @@ -0,0 +1,187 @@ +package blobstream + +import ( + "encoding/hex" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/crypto/merkle" +) + +func TestPadBytes(t *testing.T) { + tests := []struct { + input []byte + length int + expected []byte + expectErr bool + }{ + {input: []byte{1, 2, 3}, length: 5, expected: []byte{0, 0, 1, 2, 3}}, + {input: []byte{1, 2, 3}, length: 3, expected: []byte{1, 2, 3}}, + {input: []byte{1, 2, 3}, length: 2, expected: nil, expectErr: true}, + {input: []byte{}, length: 3, expected: []byte{0, 0, 0}}, + } + + for _, test := range tests { + result, err := padBytes(test.input, test.length) + if test.expectErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.Equal(t, test.expected, result) + } + } +} + +func TestTo32PaddedHexBytes(t *testing.T) { + tests := []struct { + number uint64 + expected []byte + expectError bool + }{ + { + number: 10, + expected: func() []byte { + res, _ := hex.DecodeString("000000000000000000000000000000000000000000000000000000000000000a") + return res + }(), + }, + { + number: 255, + expected: func() []byte { + res, _ := hex.DecodeString("00000000000000000000000000000000000000000000000000000000000000ff") + return res + }(), + }, + { + number: 255, + expected: func() []byte { + res, _ := hex.DecodeString("00000000000000000000000000000000000000000000000000000000000000ff") + return res + }(), + }, + { + number: 4294967295, + expected: func() []byte { + res, _ := hex.DecodeString("00000000000000000000000000000000000000000000000000000000ffffffff") + return res + }(), + }, + } + + for _, test := range tests { + t.Run(fmt.Sprintf("number: %d", test.number), func(t *testing.T) { + result, err := to32PaddedHexBytes(test.number) + if test.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.Equal(t, test.expected, result) + } + }) + } +} + +func TestEncodeDataRootTuple(t *testing.T) { + height := uint64(2) + dataRoot, err := hex.DecodeString("82dc1607d84557d3579ce602a45f5872e821c36dbda7ec926dfa17ebc8d5c013") + require.NoError(t, err) + + expectedEncoding, err := hex.DecodeString( + // hex representation of height padded to 32 bytes + "0000000000000000000000000000000000000000000000000000000000000002" + + // data root + "82dc1607d84557d3579ce602a45f5872e821c36dbda7ec926dfa17ebc8d5c013", + ) + require.NoError(t, err) + require.NotNil(t, expectedEncoding) + + actualEncoding, err := encodeDataRootTuple(height, *(*[32]byte)(dataRoot)) + require.NoError(t, err) + require.NotNil(t, actualEncoding) + + // Check that the length of packed data is correct + assert.Equal(t, len(actualEncoding), 64) + assert.Equal(t, expectedEncoding, actualEncoding) +} + +func TestHashDataRootTuples(t *testing.T) { + tests := map[string]struct { + tuples [][]byte + expectedHash []byte + expectErr bool + }{ + "empty tuples list": {tuples: nil, expectErr: true}, + "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}) + + return merkle.HashFromByteSlices([][]byte{tuple1, tuple2}) + }(), + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + result, err := hashDataRootTuples(tc.tuples) + if tc.expectErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.Equal(t, tc.expectedHash, result) + } + }) + } +} + +func TestProveDataRootTuples(t *testing.T) { + tests := map[string]struct { + tuples [][]byte + height uint64 + rangeStart uint64 + expectedProof merkle.Proof + expectErr bool + }{ + "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, + 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}) + encodedTuple3, _ := encodeDataRootTuple(3, [32]byte{0x3}) + encodedTuple4, _ := encodeDataRootTuple(4, [32]byte{0x4}) + _, proofs := merkle.ProofsFromByteSlices([][]byte{encodedTuple1, encodedTuple2, encodedTuple3, encodedTuple4}) + return *proofs[2] + }(), + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + result, err := proveDataRootTuples(tc.tuples, tc.rangeStart, tc.height) + if tc.expectErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.Equal(t, tc.expectedProof, *result) + } + }) + } +} diff --git a/nodebuilder/header/header.go b/nodebuilder/header/header.go index 1892afeb94..f807796eb6 100644 --- a/nodebuilder/header/header.go +++ b/nodebuilder/header/header.go @@ -43,20 +43,6 @@ type Module interface { // Subscribe to recent ExtendedHeaders from the network. Subscribe(ctx context.Context) (<-chan *header.ExtendedHeader, error) - - // GetDataRootTupleRoot collects the data roots over a provided ordered range of blocks, - // and then creates a new Merkle root of those data roots. The range is end exclusive. - // It's in the header module because it only needs access to the headers to generate the proof. - GetDataRootTupleRoot(ctx context.Context, start, end uint64) (*DataRootTupleRoot, error) - - // GetDataRootTupleInclusionProof creates an inclusion proof, for the data root tuple of block - // height `height`, in the set of blocks defined by `start` and `end`. The range - // is end exclusive. - // It's in the header module because it only needs access to the headers to generate the proof. - GetDataRootTupleInclusionProof( - ctx context.Context, - height, start, end uint64, - ) (*DataRootTupleInclusionProof, error) } // API is a wrapper around Module for the RPC. @@ -73,17 +59,12 @@ type API struct { *header.ExtendedHeader, uint64, ) ([]*header.ExtendedHeader, error) `perm:"read"` - GetByHeight func(context.Context, uint64) (*header.ExtendedHeader, error) `perm:"read"` - WaitForHeight func(context.Context, uint64) (*header.ExtendedHeader, error) `perm:"read"` - SyncState func(ctx context.Context) (sync.State, error) `perm:"read"` - SyncWait func(ctx context.Context) error `perm:"read"` - NetworkHead func(ctx context.Context) (*header.ExtendedHeader, error) `perm:"read"` - Subscribe func(ctx context.Context) (<-chan *header.ExtendedHeader, error) `perm:"read"` - GetDataRootTupleRoot func(ctx context.Context, start, end uint64) (*DataRootTupleRoot, error) `perm:"read"` - GetDataRootTupleInclusionProof func( - ctx context.Context, - height, start, end uint64, - ) (*DataRootTupleInclusionProof, error) `perm:"read"` + GetByHeight func(context.Context, uint64) (*header.ExtendedHeader, error) `perm:"read"` + WaitForHeight func(context.Context, uint64) (*header.ExtendedHeader, error) `perm:"read"` + SyncState func(ctx context.Context) (sync.State, error) `perm:"read"` + SyncWait func(ctx context.Context) error `perm:"read"` + NetworkHead func(ctx context.Context) (*header.ExtendedHeader, error) `perm:"read"` + Subscribe func(ctx context.Context) (<-chan *header.ExtendedHeader, error) `perm:"read"` } } @@ -126,14 +107,3 @@ func (api *API) NetworkHead(ctx context.Context) (*header.ExtendedHeader, error) func (api *API) Subscribe(ctx context.Context) (<-chan *header.ExtendedHeader, error) { return api.Internal.Subscribe(ctx) } - -func (api *API) GetDataRootTupleRoot(ctx context.Context, start, end uint64) (*DataRootTupleRoot, error) { - return api.Internal.GetDataRootTupleRoot(ctx, start, end) -} - -func (api *API) GetDataRootTupleInclusionProof( - ctx context.Context, - height, start, end uint64, -) (*DataRootTupleInclusionProof, error) { - return api.Internal.GetDataRootTupleInclusionProof(ctx, height, start, end) -} diff --git a/nodebuilder/header/mocks/api.go b/nodebuilder/header/mocks/api.go index eaf7f49cc0..b0d2b961d9 100644 --- a/nodebuilder/header/mocks/api.go +++ b/nodebuilder/header/mocks/api.go @@ -9,8 +9,7 @@ import ( reflect "reflect" header "github.com/celestiaorg/celestia-node/header" - header0 "github.com/celestiaorg/celestia-node/nodebuilder/header" - header1 "github.com/celestiaorg/go-header" + header0 "github.com/celestiaorg/go-header" sync "github.com/celestiaorg/go-header/sync" gomock "github.com/golang/mock/gomock" ) @@ -39,7 +38,7 @@ func (m *MockModule) EXPECT() *MockModuleMockRecorder { } // GetByHash mocks base method. -func (m *MockModule) GetByHash(arg0 context.Context, arg1 header1.Hash) (*header.ExtendedHeader, error) { +func (m *MockModule) GetByHash(arg0 context.Context, arg1 header0.Hash) (*header.ExtendedHeader, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetByHash", arg0, arg1) ret0, _ := ret[0].(*header.ExtendedHeader) @@ -68,36 +67,6 @@ func (mr *MockModuleMockRecorder) GetByHeight(arg0, arg1 interface{}) *gomock.Ca return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetByHeight", reflect.TypeOf((*MockModule)(nil).GetByHeight), arg0, arg1) } -// GetDataRootTupleInclusionProof mocks base method. -func (m *MockModule) GetDataRootTupleInclusionProof(arg0 context.Context, arg1, arg2, arg3 uint64) (*header0.DataRootTupleInclusionProof, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetDataRootTupleInclusionProof", arg0, arg1, arg2, arg3) - ret0, _ := ret[0].(*header0.DataRootTupleInclusionProof) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetDataRootTupleInclusionProof indicates an expected call of GetDataRootTupleInclusionProof. -func (mr *MockModuleMockRecorder) GetDataRootTupleInclusionProof(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDataRootTupleInclusionProof", reflect.TypeOf((*MockModule)(nil).GetDataRootTupleInclusionProof), arg0, arg1, arg2, arg3) -} - -// GetDataRootTupleRoot mocks base method. -func (m *MockModule) GetDataRootTupleRoot(arg0 context.Context, arg1, arg2 uint64) (*header0.DataRootTupleRoot, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetDataRootTupleRoot", arg0, arg1, arg2) - ret0, _ := ret[0].(*header0.DataRootTupleRoot) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetDataRootTupleRoot indicates an expected call of GetDataRootTupleRoot. -func (mr *MockModuleMockRecorder) GetDataRootTupleRoot(arg0, arg1, arg2 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDataRootTupleRoot", reflect.TypeOf((*MockModule)(nil).GetDataRootTupleRoot), arg0, arg1, arg2) -} - // GetRangeByHeight mocks base method. func (m *MockModule) GetRangeByHeight(arg0 context.Context, arg1 *header.ExtendedHeader, arg2 uint64) ([]*header.ExtendedHeader, error) { m.ctrl.T.Helper() diff --git a/nodebuilder/header/service.go b/nodebuilder/header/service.go index 1e40a917c8..944562ee61 100644 --- a/nodebuilder/header/service.go +++ b/nodebuilder/header/service.go @@ -147,61 +147,3 @@ func (s *Service) Subscribe(ctx context.Context) (<-chan *header.ExtendedHeader, }() return headerCh, nil } - -// GetDataRootTupleRoot collects the data roots over a provided ordered range of blocks, -// and then creates a new Merkle root of those data roots. The range is end exclusive. -func (s *Service) GetDataRootTupleRoot(ctx context.Context, start, end uint64) (*DataRootTupleRoot, error) { - log.Debugw("validating the data commitment range", "start", start, "end", end) - err := s.validateDataRootTupleRootRange(ctx, start, end) - if err != nil { - return nil, err - } - log.Debugw("fetching the data root tuples", "start", start, "end", 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(encodedDataRootTuples) - if err != nil { - return nil, err - } - // Create data commitment - dataRootTupleRoot := DataRootTupleRoot(root) - return &dataRootTupleRoot, nil -} - -// GetDataRootTupleInclusionProof creates an inclusion proof for the data root of block -// height `height` in the set of blocks defined by `start` and `end`. The range -// is end exclusive. -func (s *Service) GetDataRootTupleInclusionProof( - ctx context.Context, - height, start, end uint64, -) (*DataRootTupleInclusionProof, error) { - log.Debugw( - "validating the data root inclusion proof request", - "start", - start, - "end", - end, - "height", - height, - ) - err := s.validateDataRootInclusionProofRequest(ctx, height, start, end) - if err != nil { - return nil, err - } - log.Debugw("fetching the data root tuples", "start", start, "end", 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(encodedDataRootTuples, start, height) - if err != nil { - return nil, err - } - dataRootTupleInclusionProof := DataRootTupleInclusionProof(proof) - return &dataRootTupleInclusionProof, nil -} diff --git a/nodebuilder/header/service_test.go b/nodebuilder/header/service_test.go index 789f97d51e..14d5ada87d 100644 --- a/nodebuilder/header/service_test.go +++ b/nodebuilder/header/service_test.go @@ -2,13 +2,10 @@ package header import ( "context" - "encoding/hex" "fmt" "testing" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/crypto/merkle" libhead "github.com/celestiaorg/go-header" "github.com/celestiaorg/go-header/sync" @@ -42,179 +39,3 @@ func (d *errorSyncer[H]) State() sync.State { func (d *errorSyncer[H]) SyncWait(context.Context) error { return fmt.Errorf("dummy error") } - -func TestPadBytes(t *testing.T) { - tests := []struct { - input []byte - length int - expected []byte - expectErr bool - }{ - {input: []byte{1, 2, 3}, length: 5, expected: []byte{0, 0, 1, 2, 3}}, - {input: []byte{1, 2, 3}, length: 3, expected: []byte{1, 2, 3}}, - {input: []byte{1, 2, 3}, length: 2, expected: nil, expectErr: true}, - {input: []byte{}, length: 3, expected: []byte{0, 0, 0}}, - } - - for _, test := range tests { - result, err := padBytes(test.input, test.length) - if test.expectErr { - assert.Error(t, err) - } else { - assert.NoError(t, err) - assert.Equal(t, test.expected, result) - } - } -} - -func TestTo32PaddedHexBytes(t *testing.T) { - tests := []struct { - number uint64 - expected []byte - expectError bool - }{ - { - number: 10, - expected: func() []byte { - res, _ := hex.DecodeString("000000000000000000000000000000000000000000000000000000000000000a") - return res - }(), - }, - { - number: 255, - expected: func() []byte { - res, _ := hex.DecodeString("00000000000000000000000000000000000000000000000000000000000000ff") - return res - }(), - }, - { - number: 255, - expected: func() []byte { - res, _ := hex.DecodeString("00000000000000000000000000000000000000000000000000000000000000ff") - return res - }(), - }, - { - number: 4294967295, - expected: func() []byte { - res, _ := hex.DecodeString("00000000000000000000000000000000000000000000000000000000ffffffff") - return res - }(), - }, - } - - for _, test := range tests { - t.Run(fmt.Sprintf("number: %d", test.number), func(t *testing.T) { - result, err := to32PaddedHexBytes(test.number) - if test.expectError { - assert.Error(t, err) - } else { - assert.NoError(t, err) - assert.Equal(t, test.expected, result) - } - }) - } -} - -func TestEncodeDataRootTuple(t *testing.T) { - height := uint64(2) - dataRoot, err := hex.DecodeString("82dc1607d84557d3579ce602a45f5872e821c36dbda7ec926dfa17ebc8d5c013") - require.NoError(t, err) - - expectedEncoding, err := hex.DecodeString( - // hex representation of height padded to 32 bytes - "0000000000000000000000000000000000000000000000000000000000000002" + - // data root - "82dc1607d84557d3579ce602a45f5872e821c36dbda7ec926dfa17ebc8d5c013", - ) - require.NoError(t, err) - require.NotNil(t, expectedEncoding) - - actualEncoding, err := encodeDataRootTuple(height, *(*[32]byte)(dataRoot)) - require.NoError(t, err) - require.NotNil(t, actualEncoding) - - // Check that the length of packed data is correct - assert.Equal(t, len(actualEncoding), 64) - assert.Equal(t, expectedEncoding, actualEncoding) -} - -func TestHashDataRootTuples(t *testing.T) { - tests := map[string]struct { - tuples [][]byte - expectedHash []byte - expectErr bool - }{ - "empty tuples list": {tuples: nil, expectErr: true}, - "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}) - - return merkle.HashFromByteSlices([][]byte{tuple1, tuple2}) - }(), - }, - } - - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - result, err := hashDataRootTuples(tc.tuples) - if tc.expectErr { - assert.Error(t, err) - } else { - assert.NoError(t, err) - assert.Equal(t, tc.expectedHash, result) - } - }) - } -} - -func TestProveDataRootTuples(t *testing.T) { - tests := map[string]struct { - tuples [][]byte - height uint64 - rangeStart uint64 - expectedProof merkle.Proof - expectErr bool - }{ - "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, - 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}) - encodedTuple3, _ := encodeDataRootTuple(3, [32]byte{0x3}) - encodedTuple4, _ := encodeDataRootTuple(4, [32]byte{0x4}) - _, proofs := merkle.ProofsFromByteSlices([][]byte{encodedTuple1, encodedTuple2, encodedTuple3, encodedTuple4}) - return *proofs[2] - }(), - }, - } - - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - result, err := proveDataRootTuples(tc.tuples, tc.rangeStart, tc.height) - if tc.expectErr { - assert.Error(t, err) - } else { - assert.NoError(t, err) - assert.Equal(t, tc.expectedProof, *result) - } - }) - } -} diff --git a/nodebuilder/module.go b/nodebuilder/module.go index 8f196f3b1d..5a774b8b9b 100644 --- a/nodebuilder/module.go +++ b/nodebuilder/module.go @@ -8,6 +8,7 @@ import ( "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/libs/fxutil" "github.com/celestiaorg/celestia-node/nodebuilder/blob" + "github.com/celestiaorg/celestia-node/nodebuilder/blobstream" "github.com/celestiaorg/celestia-node/nodebuilder/core" "github.com/celestiaorg/celestia-node/nodebuilder/da" "github.com/celestiaorg/celestia-node/nodebuilder/das" @@ -56,6 +57,7 @@ func ConstructModule(tp node.Type, network p2p.Network, cfg *Config, store Store node.ConstructModule(tp), pruner.ConstructModule(tp, &cfg.Pruner), rpc.ConstructModule(tp, &cfg.RPC), + blobstream.ConstructModule(), ) return fx.Module( diff --git a/nodebuilder/node.go b/nodebuilder/node.go index e17c9d3922..c0ba8f78e8 100644 --- a/nodebuilder/node.go +++ b/nodebuilder/node.go @@ -22,6 +22,7 @@ import ( "github.com/celestiaorg/celestia-node/api/gateway" "github.com/celestiaorg/celestia-node/api/rpc" "github.com/celestiaorg/celestia-node/nodebuilder/blob" + "github.com/celestiaorg/celestia-node/nodebuilder/blobstream" "github.com/celestiaorg/celestia-node/nodebuilder/da" "github.com/celestiaorg/celestia-node/nodebuilder/das" "github.com/celestiaorg/celestia-node/nodebuilder/fraud" @@ -69,14 +70,15 @@ type Node struct { // p2p protocols PubSub *pubsub.PubSub // services - ShareServ share.Module // not optional - HeaderServ header.Module // not optional - StateServ state.Module // not optional - FraudServ fraud.Module // not optional - BlobServ blob.Module // not optional - DASer das.Module // not optional - AdminServ node.Module // not optional - DAMod da.Module // not optional + ShareServ share.Module // not optional + HeaderServ header.Module // not optional + StateServ state.Module // not optional + FraudServ fraud.Module // not optional + BlobServ blob.Module // not optional + DASer das.Module // not optional + AdminServ node.Module // not optional + DAMod da.Module // not optional + BlobstreamMod blobstream.Module // start and stop control ref internal fx.App lifecycle funcs to be called from Start and Stop start, stop lifecycleFunc