From 5e6a3e24b5a3d1244ed368c72fb9812dfa9bef6f Mon Sep 17 00:00:00 2001 From: Michael Tsitrin <114929630+mtsitrin@users.noreply.github.com> Date: Wed, 22 May 2024 13:20:00 +0300 Subject: [PATCH] refactor(settlement): settlement layer redundent abstraction layer (#873) --- block/manager.go | 6 +- block/retriever.go | 2 +- block/submit_test.go | 18 +- .../dymint/settlement/mock_HubClient.go | 410 ------------------ .../dymint/settlement/mock_LayerI.go | 336 ++++++++------ node/node.go | 2 +- node/node_test.go | 3 + p2p/validator.go | 4 +- settlement/base.go | 126 ------ settlement/dymension/dymension.go | 356 +++++---------- settlement/dymension/dymension_test.go | 14 +- settlement/dymension/options.go | 47 ++ settlement/dymension/utils.go | 82 ++++ settlement/grpc/grpc.go | 131 +++--- settlement/local/local.go | 152 +++---- settlement/local/local_test.go | 158 +++++++ settlement/registry/registry.go | 10 +- settlement/settlement.go | 32 +- settlement/settlement_test.go | 181 -------- store/badger.go | 5 + store/prefix.go | 5 + store/storeIface.go | 1 + testutil/block.go | 6 +- 23 files changed, 778 insertions(+), 1309 deletions(-) delete mode 100644 mocks/github.com/dymensionxyz/dymint/settlement/mock_HubClient.go delete mode 100644 settlement/base.go create mode 100644 settlement/dymension/options.go create mode 100644 settlement/dymension/utils.go create mode 100644 settlement/local/local_test.go delete mode 100644 settlement/settlement_test.go diff --git a/block/manager.go b/block/manager.go index 603182a26..81fb32efd 100644 --- a/block/manager.go +++ b/block/manager.go @@ -46,7 +46,7 @@ type Manager struct { Pubsub *pubsub.Server p2pClient *p2p.Client DAClient da.DataAvailabilityLayerClient - SLClient settlement.LayerI + SLClient settlement.ClientI /* Production @@ -86,7 +86,7 @@ func NewManager( mempool mempool.Mempool, proxyApp proxy.AppConns, dalc da.DataAvailabilityLayerClient, - settlementClient settlement.LayerI, + settlementClient settlement.ClientI, eventBus *tmtypes.EventBus, pubsub *pubsub.Server, p2pClient *p2p.Client, @@ -192,7 +192,7 @@ func (m *Manager) NextHeightToSubmit() uint64 { // syncBlockManager enforces the node to be synced on initial run. func (m *Manager) syncBlockManager() error { - res, err := m.SLClient.RetrieveBatch() + res, err := m.SLClient.GetLatestBatch() if errors.Is(err, gerr.ErrNotFound) { // The SL hasn't got any batches for this chain yet. m.logger.Info("No batches for chain found in SL.") diff --git a/block/retriever.go b/block/retriever.go index 9763224b4..1a84a6eb3 100644 --- a/block/retriever.go +++ b/block/retriever.go @@ -46,7 +46,7 @@ func (m *Manager) syncToTargetHeight(targetHeight uint64) error { return fmt.Errorf("query state index: %w", err) } - settlementBatch, err := m.SLClient.RetrieveBatch(stateIndex) + settlementBatch, err := m.SLClient.GetBatchAtIndex(stateIndex) if err != nil { return fmt.Errorf("retrieve batch: %w", err) } diff --git a/block/submit_test.go b/block/submit_test.go index 0e62cfcaf..7d7bebe26 100644 --- a/block/submit_test.go +++ b/block/submit_test.go @@ -18,7 +18,7 @@ import ( "github.com/libp2p/go-libp2p/core/crypto" "github.com/dymensionxyz/dymint/config" - mocks "github.com/dymensionxyz/dymint/mocks/github.com/dymensionxyz/dymint/settlement" + slmocks "github.com/dymensionxyz/dymint/mocks/github.com/dymensionxyz/dymint/settlement" "github.com/dymensionxyz/dymint/testutil" "github.com/dymensionxyz/dymint/types" ) @@ -74,13 +74,13 @@ func TestBatchSubmissionFailedSubmission(t *testing.T) { PublicKey: cosmosPrivKey.PubKey(), } - // Create a new mock LayerI - mockLayerI := &mocks.MockLayerI{} - mockLayerI.On("Init", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) - mockLayerI.On("Start").Return(nil) - mockLayerI.On("GetProposer").Return(proposer) + // Create a new mock ClientI + slmock := &slmocks.MockClientI{} + slmock.On("Init", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) + slmock.On("Start").Return(nil) + slmock.On("GetProposer").Return(proposer) - manager, err := testutil.GetManagerWithProposerKey(testutil.GetManagerConfig(), lib2pPrivKey, mockLayerI, nil, 1, 1, 0, proxyApp, nil) + manager, err := testutil.GetManagerWithProposerKey(testutil.GetManagerConfig(), lib2pPrivKey, slmock, nil, 1, 1, 0, proxyApp, nil) require.NoError(err) // Check initial assertions @@ -95,11 +95,11 @@ func TestBatchSubmissionFailedSubmission(t *testing.T) { assert.Zero(t, manager.LastSubmittedHeight.Load()) // try to submit, we expect failure - mockLayerI.On("SubmitBatch", mock.Anything, mock.Anything, mock.Anything).Return(fmt.Errorf("submit batch")).Once() + slmock.On("SubmitBatch", mock.Anything, mock.Anything, mock.Anything).Return(fmt.Errorf("submit batch")).Once() assert.Error(t, manager.HandleSubmissionTrigger()) // try to submit again, we expect success - mockLayerI.On("SubmitBatch", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() + slmock.On("SubmitBatch", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() manager.HandleSubmissionTrigger() assert.EqualValues(t, manager.State.Height(), manager.LastSubmittedHeight.Load()) } diff --git a/mocks/github.com/dymensionxyz/dymint/settlement/mock_HubClient.go b/mocks/github.com/dymensionxyz/dymint/settlement/mock_HubClient.go deleted file mode 100644 index 03ecfff21..000000000 --- a/mocks/github.com/dymensionxyz/dymint/settlement/mock_HubClient.go +++ /dev/null @@ -1,410 +0,0 @@ -// Code generated by mockery v2.43.0. DO NOT EDIT. - -package settlement - -import ( - da "github.com/dymensionxyz/dymint/da" - mock "github.com/stretchr/testify/mock" - - settlement "github.com/dymensionxyz/dymint/settlement" - - types "github.com/dymensionxyz/dymint/types" -) - -// MockHubClient is an autogenerated mock type for the HubClient type -type MockHubClient struct { - mock.Mock -} - -type MockHubClient_Expecter struct { - mock *mock.Mock -} - -func (_m *MockHubClient) EXPECT() *MockHubClient_Expecter { - return &MockHubClient_Expecter{mock: &_m.Mock} -} - -// GetBatchAtIndex provides a mock function with given fields: rollappID, index -func (_m *MockHubClient) GetBatchAtIndex(rollappID string, index uint64) (*settlement.ResultRetrieveBatch, error) { - ret := _m.Called(rollappID, index) - - if len(ret) == 0 { - panic("no return value specified for GetBatchAtIndex") - } - - var r0 *settlement.ResultRetrieveBatch - var r1 error - if rf, ok := ret.Get(0).(func(string, uint64) (*settlement.ResultRetrieveBatch, error)); ok { - return rf(rollappID, index) - } - if rf, ok := ret.Get(0).(func(string, uint64) *settlement.ResultRetrieveBatch); ok { - r0 = rf(rollappID, index) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*settlement.ResultRetrieveBatch) - } - } - - if rf, ok := ret.Get(1).(func(string, uint64) error); ok { - r1 = rf(rollappID, index) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// MockHubClient_GetBatchAtIndex_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetBatchAtIndex' -type MockHubClient_GetBatchAtIndex_Call struct { - *mock.Call -} - -// GetBatchAtIndex is a helper method to define mock.On call -// - rollappID string -// - index uint64 -func (_e *MockHubClient_Expecter) GetBatchAtIndex(rollappID interface{}, index interface{}) *MockHubClient_GetBatchAtIndex_Call { - return &MockHubClient_GetBatchAtIndex_Call{Call: _e.mock.On("GetBatchAtIndex", rollappID, index)} -} - -func (_c *MockHubClient_GetBatchAtIndex_Call) Run(run func(rollappID string, index uint64)) *MockHubClient_GetBatchAtIndex_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(uint64)) - }) - return _c -} - -func (_c *MockHubClient_GetBatchAtIndex_Call) Return(_a0 *settlement.ResultRetrieveBatch, _a1 error) *MockHubClient_GetBatchAtIndex_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *MockHubClient_GetBatchAtIndex_Call) RunAndReturn(run func(string, uint64) (*settlement.ResultRetrieveBatch, error)) *MockHubClient_GetBatchAtIndex_Call { - _c.Call.Return(run) - return _c -} - -// GetHeightState provides a mock function with given fields: index -func (_m *MockHubClient) GetHeightState(index uint64) (*settlement.ResultGetHeightState, error) { - ret := _m.Called(index) - - if len(ret) == 0 { - panic("no return value specified for GetHeightState") - } - - var r0 *settlement.ResultGetHeightState - var r1 error - if rf, ok := ret.Get(0).(func(uint64) (*settlement.ResultGetHeightState, error)); ok { - return rf(index) - } - if rf, ok := ret.Get(0).(func(uint64) *settlement.ResultGetHeightState); ok { - r0 = rf(index) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*settlement.ResultGetHeightState) - } - } - - if rf, ok := ret.Get(1).(func(uint64) error); ok { - r1 = rf(index) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// MockHubClient_GetHeightState_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetHeightState' -type MockHubClient_GetHeightState_Call struct { - *mock.Call -} - -// GetHeightState is a helper method to define mock.On call -// - index uint64 -func (_e *MockHubClient_Expecter) GetHeightState(index interface{}) *MockHubClient_GetHeightState_Call { - return &MockHubClient_GetHeightState_Call{Call: _e.mock.On("GetHeightState", index)} -} - -func (_c *MockHubClient_GetHeightState_Call) Run(run func(index uint64)) *MockHubClient_GetHeightState_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(uint64)) - }) - return _c -} - -func (_c *MockHubClient_GetHeightState_Call) Return(_a0 *settlement.ResultGetHeightState, _a1 error) *MockHubClient_GetHeightState_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *MockHubClient_GetHeightState_Call) RunAndReturn(run func(uint64) (*settlement.ResultGetHeightState, error)) *MockHubClient_GetHeightState_Call { - _c.Call.Return(run) - return _c -} - -// GetLatestBatch provides a mock function with given fields: rollappID -func (_m *MockHubClient) GetLatestBatch(rollappID string) (*settlement.ResultRetrieveBatch, error) { - ret := _m.Called(rollappID) - - if len(ret) == 0 { - panic("no return value specified for GetLatestBatch") - } - - var r0 *settlement.ResultRetrieveBatch - var r1 error - if rf, ok := ret.Get(0).(func(string) (*settlement.ResultRetrieveBatch, error)); ok { - return rf(rollappID) - } - if rf, ok := ret.Get(0).(func(string) *settlement.ResultRetrieveBatch); ok { - r0 = rf(rollappID) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*settlement.ResultRetrieveBatch) - } - } - - if rf, ok := ret.Get(1).(func(string) error); ok { - r1 = rf(rollappID) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// MockHubClient_GetLatestBatch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetLatestBatch' -type MockHubClient_GetLatestBatch_Call struct { - *mock.Call -} - -// GetLatestBatch is a helper method to define mock.On call -// - rollappID string -func (_e *MockHubClient_Expecter) GetLatestBatch(rollappID interface{}) *MockHubClient_GetLatestBatch_Call { - return &MockHubClient_GetLatestBatch_Call{Call: _e.mock.On("GetLatestBatch", rollappID)} -} - -func (_c *MockHubClient_GetLatestBatch_Call) Run(run func(rollappID string)) *MockHubClient_GetLatestBatch_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) - }) - return _c -} - -func (_c *MockHubClient_GetLatestBatch_Call) Return(_a0 *settlement.ResultRetrieveBatch, _a1 error) *MockHubClient_GetLatestBatch_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *MockHubClient_GetLatestBatch_Call) RunAndReturn(run func(string) (*settlement.ResultRetrieveBatch, error)) *MockHubClient_GetLatestBatch_Call { - _c.Call.Return(run) - return _c -} - -// GetSequencers provides a mock function with given fields: rollappID -func (_m *MockHubClient) GetSequencers(rollappID string) ([]*types.Sequencer, error) { - ret := _m.Called(rollappID) - - if len(ret) == 0 { - panic("no return value specified for GetSequencers") - } - - var r0 []*types.Sequencer - var r1 error - if rf, ok := ret.Get(0).(func(string) ([]*types.Sequencer, error)); ok { - return rf(rollappID) - } - if rf, ok := ret.Get(0).(func(string) []*types.Sequencer); ok { - r0 = rf(rollappID) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]*types.Sequencer) - } - } - - if rf, ok := ret.Get(1).(func(string) error); ok { - r1 = rf(rollappID) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// MockHubClient_GetSequencers_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetSequencers' -type MockHubClient_GetSequencers_Call struct { - *mock.Call -} - -// GetSequencers is a helper method to define mock.On call -// - rollappID string -func (_e *MockHubClient_Expecter) GetSequencers(rollappID interface{}) *MockHubClient_GetSequencers_Call { - return &MockHubClient_GetSequencers_Call{Call: _e.mock.On("GetSequencers", rollappID)} -} - -func (_c *MockHubClient_GetSequencers_Call) Run(run func(rollappID string)) *MockHubClient_GetSequencers_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) - }) - return _c -} - -func (_c *MockHubClient_GetSequencers_Call) Return(_a0 []*types.Sequencer, _a1 error) *MockHubClient_GetSequencers_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *MockHubClient_GetSequencers_Call) RunAndReturn(run func(string) ([]*types.Sequencer, error)) *MockHubClient_GetSequencers_Call { - _c.Call.Return(run) - return _c -} - -// PostBatch provides a mock function with given fields: batch, daClient, daResult -func (_m *MockHubClient) PostBatch(batch *types.Batch, daClient da.Client, daResult *da.ResultSubmitBatch) error { - ret := _m.Called(batch, daClient, daResult) - - if len(ret) == 0 { - panic("no return value specified for PostBatch") - } - - var r0 error - if rf, ok := ret.Get(0).(func(*types.Batch, da.Client, *da.ResultSubmitBatch) error); ok { - r0 = rf(batch, daClient, daResult) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockHubClient_PostBatch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PostBatch' -type MockHubClient_PostBatch_Call struct { - *mock.Call -} - -// PostBatch is a helper method to define mock.On call -// - batch *types.Batch -// - daClient da.Client -// - daResult *da.ResultSubmitBatch -func (_e *MockHubClient_Expecter) PostBatch(batch interface{}, daClient interface{}, daResult interface{}) *MockHubClient_PostBatch_Call { - return &MockHubClient_PostBatch_Call{Call: _e.mock.On("PostBatch", batch, daClient, daResult)} -} - -func (_c *MockHubClient_PostBatch_Call) Run(run func(batch *types.Batch, daClient da.Client, daResult *da.ResultSubmitBatch)) *MockHubClient_PostBatch_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(*types.Batch), args[1].(da.Client), args[2].(*da.ResultSubmitBatch)) - }) - return _c -} - -func (_c *MockHubClient_PostBatch_Call) Return(_a0 error) *MockHubClient_PostBatch_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockHubClient_PostBatch_Call) RunAndReturn(run func(*types.Batch, da.Client, *da.ResultSubmitBatch) error) *MockHubClient_PostBatch_Call { - _c.Call.Return(run) - return _c -} - -// Start provides a mock function with given fields: -func (_m *MockHubClient) Start() error { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for Start") - } - - var r0 error - if rf, ok := ret.Get(0).(func() error); ok { - r0 = rf() - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockHubClient_Start_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Start' -type MockHubClient_Start_Call struct { - *mock.Call -} - -// Start is a helper method to define mock.On call -func (_e *MockHubClient_Expecter) Start() *MockHubClient_Start_Call { - return &MockHubClient_Start_Call{Call: _e.mock.On("Start")} -} - -func (_c *MockHubClient_Start_Call) Run(run func()) *MockHubClient_Start_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *MockHubClient_Start_Call) Return(_a0 error) *MockHubClient_Start_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockHubClient_Start_Call) RunAndReturn(run func() error) *MockHubClient_Start_Call { - _c.Call.Return(run) - return _c -} - -// Stop provides a mock function with given fields: -func (_m *MockHubClient) Stop() error { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for Stop") - } - - var r0 error - if rf, ok := ret.Get(0).(func() error); ok { - r0 = rf() - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockHubClient_Stop_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Stop' -type MockHubClient_Stop_Call struct { - *mock.Call -} - -// Stop is a helper method to define mock.On call -func (_e *MockHubClient_Expecter) Stop() *MockHubClient_Stop_Call { - return &MockHubClient_Stop_Call{Call: _e.mock.On("Stop")} -} - -func (_c *MockHubClient_Stop_Call) Run(run func()) *MockHubClient_Stop_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *MockHubClient_Stop_Call) Return(_a0 error) *MockHubClient_Stop_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockHubClient_Stop_Call) RunAndReturn(run func() error) *MockHubClient_Stop_Call { - _c.Call.Return(run) - return _c -} - -// NewMockHubClient creates a new instance of MockHubClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewMockHubClient(t interface { - mock.TestingT - Cleanup(func()) -}) *MockHubClient { - mock := &MockHubClient{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/mocks/github.com/dymensionxyz/dymint/settlement/mock_LayerI.go b/mocks/github.com/dymensionxyz/dymint/settlement/mock_LayerI.go index 19bd117de..7c980a17e 100644 --- a/mocks/github.com/dymensionxyz/dymint/settlement/mock_LayerI.go +++ b/mocks/github.com/dymensionxyz/dymint/settlement/mock_LayerI.go @@ -13,21 +13,79 @@ import ( types "github.com/dymensionxyz/dymint/types" ) -// MockLayerI is an autogenerated mock type for the LayerI type -type MockLayerI struct { +// MockClientI is an autogenerated mock type for the ClientI type +type MockClientI struct { mock.Mock } -type MockLayerI_Expecter struct { +type MockClientI_Expecter struct { mock *mock.Mock } -func (_m *MockLayerI) EXPECT() *MockLayerI_Expecter { - return &MockLayerI_Expecter{mock: &_m.Mock} +func (_m *MockClientI) EXPECT() *MockClientI_Expecter { + return &MockClientI_Expecter{mock: &_m.Mock} +} + +// GetBatchAtIndex provides a mock function with given fields: index +func (_m *MockClientI) GetBatchAtIndex(index uint64) (*settlement.ResultRetrieveBatch, error) { + ret := _m.Called(index) + + if len(ret) == 0 { + panic("no return value specified for GetBatchAtIndex") + } + + var r0 *settlement.ResultRetrieveBatch + var r1 error + if rf, ok := ret.Get(0).(func(uint64) (*settlement.ResultRetrieveBatch, error)); ok { + return rf(index) + } + if rf, ok := ret.Get(0).(func(uint64) *settlement.ResultRetrieveBatch); ok { + r0 = rf(index) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*settlement.ResultRetrieveBatch) + } + } + + if rf, ok := ret.Get(1).(func(uint64) error); ok { + r1 = rf(index) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockClientI_GetBatchAtIndex_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetBatchAtIndex' +type MockClientI_GetBatchAtIndex_Call struct { + *mock.Call +} + +// GetBatchAtIndex is a helper method to define mock.On call +// - index uint64 +func (_e *MockClientI_Expecter) GetBatchAtIndex(index interface{}) *MockClientI_GetBatchAtIndex_Call { + return &MockClientI_GetBatchAtIndex_Call{Call: _e.mock.On("GetBatchAtIndex", index)} +} + +func (_c *MockClientI_GetBatchAtIndex_Call) Run(run func(index uint64)) *MockClientI_GetBatchAtIndex_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(uint64)) + }) + return _c +} + +func (_c *MockClientI_GetBatchAtIndex_Call) Return(_a0 *settlement.ResultRetrieveBatch, _a1 error) *MockClientI_GetBatchAtIndex_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockClientI_GetBatchAtIndex_Call) RunAndReturn(run func(uint64) (*settlement.ResultRetrieveBatch, error)) *MockClientI_GetBatchAtIndex_Call { + _c.Call.Return(run) + return _c } // GetHeightState provides a mock function with given fields: _a0 -func (_m *MockLayerI) GetHeightState(_a0 uint64) (*settlement.ResultGetHeightState, error) { +func (_m *MockClientI) GetHeightState(_a0 uint64) (*settlement.ResultGetHeightState, error) { ret := _m.Called(_a0) if len(ret) == 0 { @@ -56,36 +114,93 @@ func (_m *MockLayerI) GetHeightState(_a0 uint64) (*settlement.ResultGetHeightSta return r0, r1 } -// MockLayerI_GetHeightState_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetHeightState' -type MockLayerI_GetHeightState_Call struct { +// MockClientI_GetHeightState_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetHeightState' +type MockClientI_GetHeightState_Call struct { *mock.Call } // GetHeightState is a helper method to define mock.On call // - _a0 uint64 -func (_e *MockLayerI_Expecter) GetHeightState(_a0 interface{}) *MockLayerI_GetHeightState_Call { - return &MockLayerI_GetHeightState_Call{Call: _e.mock.On("GetHeightState", _a0)} +func (_e *MockClientI_Expecter) GetHeightState(_a0 interface{}) *MockClientI_GetHeightState_Call { + return &MockClientI_GetHeightState_Call{Call: _e.mock.On("GetHeightState", _a0)} } -func (_c *MockLayerI_GetHeightState_Call) Run(run func(_a0 uint64)) *MockLayerI_GetHeightState_Call { +func (_c *MockClientI_GetHeightState_Call) Run(run func(_a0 uint64)) *MockClientI_GetHeightState_Call { _c.Call.Run(func(args mock.Arguments) { run(args[0].(uint64)) }) return _c } -func (_c *MockLayerI_GetHeightState_Call) Return(_a0 *settlement.ResultGetHeightState, _a1 error) *MockLayerI_GetHeightState_Call { +func (_c *MockClientI_GetHeightState_Call) Return(_a0 *settlement.ResultGetHeightState, _a1 error) *MockClientI_GetHeightState_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockClientI_GetHeightState_Call) RunAndReturn(run func(uint64) (*settlement.ResultGetHeightState, error)) *MockClientI_GetHeightState_Call { + _c.Call.Return(run) + return _c +} + +// GetLatestBatch provides a mock function with given fields: +func (_m *MockClientI) GetLatestBatch() (*settlement.ResultRetrieveBatch, error) { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for GetLatestBatch") + } + + var r0 *settlement.ResultRetrieveBatch + var r1 error + if rf, ok := ret.Get(0).(func() (*settlement.ResultRetrieveBatch, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() *settlement.ResultRetrieveBatch); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*settlement.ResultRetrieveBatch) + } + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockClientI_GetLatestBatch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetLatestBatch' +type MockClientI_GetLatestBatch_Call struct { + *mock.Call +} + +// GetLatestBatch is a helper method to define mock.On call +func (_e *MockClientI_Expecter) GetLatestBatch() *MockClientI_GetLatestBatch_Call { + return &MockClientI_GetLatestBatch_Call{Call: _e.mock.On("GetLatestBatch")} +} + +func (_c *MockClientI_GetLatestBatch_Call) Run(run func()) *MockClientI_GetLatestBatch_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockClientI_GetLatestBatch_Call) Return(_a0 *settlement.ResultRetrieveBatch, _a1 error) *MockClientI_GetLatestBatch_Call { _c.Call.Return(_a0, _a1) return _c } -func (_c *MockLayerI_GetHeightState_Call) RunAndReturn(run func(uint64) (*settlement.ResultGetHeightState, error)) *MockLayerI_GetHeightState_Call { +func (_c *MockClientI_GetLatestBatch_Call) RunAndReturn(run func() (*settlement.ResultRetrieveBatch, error)) *MockClientI_GetLatestBatch_Call { _c.Call.Return(run) return _c } // GetProposer provides a mock function with given fields: -func (_m *MockLayerI) GetProposer() *types.Sequencer { +func (_m *MockClientI) GetProposer() *types.Sequencer { ret := _m.Called() if len(ret) == 0 { @@ -104,42 +219,46 @@ func (_m *MockLayerI) GetProposer() *types.Sequencer { return r0 } -// MockLayerI_GetProposer_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetProposer' -type MockLayerI_GetProposer_Call struct { +// MockClientI_GetProposer_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetProposer' +type MockClientI_GetProposer_Call struct { *mock.Call } // GetProposer is a helper method to define mock.On call -func (_e *MockLayerI_Expecter) GetProposer() *MockLayerI_GetProposer_Call { - return &MockLayerI_GetProposer_Call{Call: _e.mock.On("GetProposer")} +func (_e *MockClientI_Expecter) GetProposer() *MockClientI_GetProposer_Call { + return &MockClientI_GetProposer_Call{Call: _e.mock.On("GetProposer")} } -func (_c *MockLayerI_GetProposer_Call) Run(run func()) *MockLayerI_GetProposer_Call { +func (_c *MockClientI_GetProposer_Call) Run(run func()) *MockClientI_GetProposer_Call { _c.Call.Run(func(args mock.Arguments) { run() }) return _c } -func (_c *MockLayerI_GetProposer_Call) Return(_a0 *types.Sequencer) *MockLayerI_GetProposer_Call { +func (_c *MockClientI_GetProposer_Call) Return(_a0 *types.Sequencer) *MockClientI_GetProposer_Call { _c.Call.Return(_a0) return _c } -func (_c *MockLayerI_GetProposer_Call) RunAndReturn(run func() *types.Sequencer) *MockLayerI_GetProposer_Call { +func (_c *MockClientI_GetProposer_Call) RunAndReturn(run func() *types.Sequencer) *MockClientI_GetProposer_Call { _c.Call.Return(run) return _c } -// GetSequencersList provides a mock function with given fields: -func (_m *MockLayerI) GetSequencersList() []*types.Sequencer { +// GetSequencers provides a mock function with given fields: +func (_m *MockClientI) GetSequencers() ([]*types.Sequencer, error) { ret := _m.Called() if len(ret) == 0 { - panic("no return value specified for GetSequencersList") + panic("no return value specified for GetSequencers") } var r0 []*types.Sequencer + var r1 error + if rf, ok := ret.Get(0).(func() ([]*types.Sequencer, error)); ok { + return rf() + } if rf, ok := ret.Get(0).(func() []*types.Sequencer); ok { r0 = rf() } else { @@ -148,38 +267,44 @@ func (_m *MockLayerI) GetSequencersList() []*types.Sequencer { } } - return r0 + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 } -// MockLayerI_GetSequencersList_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetSequencersList' -type MockLayerI_GetSequencersList_Call struct { +// MockClientI_GetSequencers_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetSequencers' +type MockClientI_GetSequencers_Call struct { *mock.Call } -// GetSequencersList is a helper method to define mock.On call -func (_e *MockLayerI_Expecter) GetSequencersList() *MockLayerI_GetSequencersList_Call { - return &MockLayerI_GetSequencersList_Call{Call: _e.mock.On("GetSequencersList")} +// GetSequencers is a helper method to define mock.On call +func (_e *MockClientI_Expecter) GetSequencers() *MockClientI_GetSequencers_Call { + return &MockClientI_GetSequencers_Call{Call: _e.mock.On("GetSequencers")} } -func (_c *MockLayerI_GetSequencersList_Call) Run(run func()) *MockLayerI_GetSequencersList_Call { +func (_c *MockClientI_GetSequencers_Call) Run(run func()) *MockClientI_GetSequencers_Call { _c.Call.Run(func(args mock.Arguments) { run() }) return _c } -func (_c *MockLayerI_GetSequencersList_Call) Return(_a0 []*types.Sequencer) *MockLayerI_GetSequencersList_Call { - _c.Call.Return(_a0) +func (_c *MockClientI_GetSequencers_Call) Return(_a0 []*types.Sequencer, _a1 error) *MockClientI_GetSequencers_Call { + _c.Call.Return(_a0, _a1) return _c } -func (_c *MockLayerI_GetSequencersList_Call) RunAndReturn(run func() []*types.Sequencer) *MockLayerI_GetSequencersList_Call { +func (_c *MockClientI_GetSequencers_Call) RunAndReturn(run func() ([]*types.Sequencer, error)) *MockClientI_GetSequencers_Call { _c.Call.Return(run) return _c } // Init provides a mock function with given fields: config, _a1, logger, options -func (_m *MockLayerI) Init(config settlement.Config, _a1 *pubsub.Server, logger types.Logger, options ...settlement.Option) error { +func (_m *MockClientI) Init(config settlement.Config, _a1 *pubsub.Server, logger types.Logger, options ...settlement.Option) error { _va := make([]interface{}, len(options)) for _i := range options { _va[_i] = options[_i] @@ -203,8 +328,8 @@ func (_m *MockLayerI) Init(config settlement.Config, _a1 *pubsub.Server, logger return r0 } -// MockLayerI_Init_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Init' -type MockLayerI_Init_Call struct { +// MockClientI_Init_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Init' +type MockClientI_Init_Call struct { *mock.Call } @@ -213,12 +338,12 @@ type MockLayerI_Init_Call struct { // - _a1 *pubsub.Server // - logger types.Logger // - options ...settlement.Option -func (_e *MockLayerI_Expecter) Init(config interface{}, _a1 interface{}, logger interface{}, options ...interface{}) *MockLayerI_Init_Call { - return &MockLayerI_Init_Call{Call: _e.mock.On("Init", +func (_e *MockClientI_Expecter) Init(config interface{}, _a1 interface{}, logger interface{}, options ...interface{}) *MockClientI_Init_Call { + return &MockClientI_Init_Call{Call: _e.mock.On("Init", append([]interface{}{config, _a1, logger}, options...)...)} } -func (_c *MockLayerI_Init_Call) Run(run func(config settlement.Config, _a1 *pubsub.Server, logger types.Logger, options ...settlement.Option)) *MockLayerI_Init_Call { +func (_c *MockClientI_Init_Call) Run(run func(config settlement.Config, _a1 *pubsub.Server, logger types.Logger, options ...settlement.Option)) *MockClientI_Init_Call { _c.Call.Run(func(args mock.Arguments) { variadicArgs := make([]settlement.Option, len(args)-3) for i, a := range args[3:] { @@ -231,89 +356,18 @@ func (_c *MockLayerI_Init_Call) Run(run func(config settlement.Config, _a1 *pubs return _c } -func (_c *MockLayerI_Init_Call) Return(_a0 error) *MockLayerI_Init_Call { +func (_c *MockClientI_Init_Call) Return(_a0 error) *MockClientI_Init_Call { _c.Call.Return(_a0) return _c } -func (_c *MockLayerI_Init_Call) RunAndReturn(run func(settlement.Config, *pubsub.Server, types.Logger, ...settlement.Option) error) *MockLayerI_Init_Call { - _c.Call.Return(run) - return _c -} - -// RetrieveBatch provides a mock function with given fields: stateIndex -func (_m *MockLayerI) RetrieveBatch(stateIndex ...uint64) (*settlement.ResultRetrieveBatch, error) { - _va := make([]interface{}, len(stateIndex)) - for _i := range stateIndex { - _va[_i] = stateIndex[_i] - } - var _ca []interface{} - _ca = append(_ca, _va...) - ret := _m.Called(_ca...) - - if len(ret) == 0 { - panic("no return value specified for RetrieveBatch") - } - - var r0 *settlement.ResultRetrieveBatch - var r1 error - if rf, ok := ret.Get(0).(func(...uint64) (*settlement.ResultRetrieveBatch, error)); ok { - return rf(stateIndex...) - } - if rf, ok := ret.Get(0).(func(...uint64) *settlement.ResultRetrieveBatch); ok { - r0 = rf(stateIndex...) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*settlement.ResultRetrieveBatch) - } - } - - if rf, ok := ret.Get(1).(func(...uint64) error); ok { - r1 = rf(stateIndex...) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// MockLayerI_RetrieveBatch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RetrieveBatch' -type MockLayerI_RetrieveBatch_Call struct { - *mock.Call -} - -// RetrieveBatch is a helper method to define mock.On call -// - stateIndex ...uint64 -func (_e *MockLayerI_Expecter) RetrieveBatch(stateIndex ...interface{}) *MockLayerI_RetrieveBatch_Call { - return &MockLayerI_RetrieveBatch_Call{Call: _e.mock.On("RetrieveBatch", - append([]interface{}{}, stateIndex...)...)} -} - -func (_c *MockLayerI_RetrieveBatch_Call) Run(run func(stateIndex ...uint64)) *MockLayerI_RetrieveBatch_Call { - _c.Call.Run(func(args mock.Arguments) { - variadicArgs := make([]uint64, len(args)-0) - for i, a := range args[0:] { - if a != nil { - variadicArgs[i] = a.(uint64) - } - } - run(variadicArgs...) - }) - return _c -} - -func (_c *MockLayerI_RetrieveBatch_Call) Return(_a0 *settlement.ResultRetrieveBatch, _a1 error) *MockLayerI_RetrieveBatch_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *MockLayerI_RetrieveBatch_Call) RunAndReturn(run func(...uint64) (*settlement.ResultRetrieveBatch, error)) *MockLayerI_RetrieveBatch_Call { +func (_c *MockClientI_Init_Call) RunAndReturn(run func(settlement.Config, *pubsub.Server, types.Logger, ...settlement.Option) error) *MockClientI_Init_Call { _c.Call.Return(run) return _c } // Start provides a mock function with given fields: -func (_m *MockLayerI) Start() error { +func (_m *MockClientI) Start() error { ret := _m.Called() if len(ret) == 0 { @@ -330,35 +384,35 @@ func (_m *MockLayerI) Start() error { return r0 } -// MockLayerI_Start_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Start' -type MockLayerI_Start_Call struct { +// MockClientI_Start_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Start' +type MockClientI_Start_Call struct { *mock.Call } // Start is a helper method to define mock.On call -func (_e *MockLayerI_Expecter) Start() *MockLayerI_Start_Call { - return &MockLayerI_Start_Call{Call: _e.mock.On("Start")} +func (_e *MockClientI_Expecter) Start() *MockClientI_Start_Call { + return &MockClientI_Start_Call{Call: _e.mock.On("Start")} } -func (_c *MockLayerI_Start_Call) Run(run func()) *MockLayerI_Start_Call { +func (_c *MockClientI_Start_Call) Run(run func()) *MockClientI_Start_Call { _c.Call.Run(func(args mock.Arguments) { run() }) return _c } -func (_c *MockLayerI_Start_Call) Return(_a0 error) *MockLayerI_Start_Call { +func (_c *MockClientI_Start_Call) Return(_a0 error) *MockClientI_Start_Call { _c.Call.Return(_a0) return _c } -func (_c *MockLayerI_Start_Call) RunAndReturn(run func() error) *MockLayerI_Start_Call { +func (_c *MockClientI_Start_Call) RunAndReturn(run func() error) *MockClientI_Start_Call { _c.Call.Return(run) return _c } // Stop provides a mock function with given fields: -func (_m *MockLayerI) Stop() error { +func (_m *MockClientI) Stop() error { ret := _m.Called() if len(ret) == 0 { @@ -375,35 +429,35 @@ func (_m *MockLayerI) Stop() error { return r0 } -// MockLayerI_Stop_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Stop' -type MockLayerI_Stop_Call struct { +// MockClientI_Stop_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Stop' +type MockClientI_Stop_Call struct { *mock.Call } // Stop is a helper method to define mock.On call -func (_e *MockLayerI_Expecter) Stop() *MockLayerI_Stop_Call { - return &MockLayerI_Stop_Call{Call: _e.mock.On("Stop")} +func (_e *MockClientI_Expecter) Stop() *MockClientI_Stop_Call { + return &MockClientI_Stop_Call{Call: _e.mock.On("Stop")} } -func (_c *MockLayerI_Stop_Call) Run(run func()) *MockLayerI_Stop_Call { +func (_c *MockClientI_Stop_Call) Run(run func()) *MockClientI_Stop_Call { _c.Call.Run(func(args mock.Arguments) { run() }) return _c } -func (_c *MockLayerI_Stop_Call) Return(_a0 error) *MockLayerI_Stop_Call { +func (_c *MockClientI_Stop_Call) Return(_a0 error) *MockClientI_Stop_Call { _c.Call.Return(_a0) return _c } -func (_c *MockLayerI_Stop_Call) RunAndReturn(run func() error) *MockLayerI_Stop_Call { +func (_c *MockClientI_Stop_Call) RunAndReturn(run func() error) *MockClientI_Stop_Call { _c.Call.Return(run) return _c } // SubmitBatch provides a mock function with given fields: batch, daClient, daResult -func (_m *MockLayerI) SubmitBatch(batch *types.Batch, daClient da.Client, daResult *da.ResultSubmitBatch) error { +func (_m *MockClientI) SubmitBatch(batch *types.Batch, daClient da.Client, daResult *da.ResultSubmitBatch) error { ret := _m.Called(batch, daClient, daResult) if len(ret) == 0 { @@ -420,8 +474,8 @@ func (_m *MockLayerI) SubmitBatch(batch *types.Batch, daClient da.Client, daResu return r0 } -// MockLayerI_SubmitBatch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SubmitBatch' -type MockLayerI_SubmitBatch_Call struct { +// MockClientI_SubmitBatch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SubmitBatch' +type MockClientI_SubmitBatch_Call struct { *mock.Call } @@ -429,34 +483,34 @@ type MockLayerI_SubmitBatch_Call struct { // - batch *types.Batch // - daClient da.Client // - daResult *da.ResultSubmitBatch -func (_e *MockLayerI_Expecter) SubmitBatch(batch interface{}, daClient interface{}, daResult interface{}) *MockLayerI_SubmitBatch_Call { - return &MockLayerI_SubmitBatch_Call{Call: _e.mock.On("SubmitBatch", batch, daClient, daResult)} +func (_e *MockClientI_Expecter) SubmitBatch(batch interface{}, daClient interface{}, daResult interface{}) *MockClientI_SubmitBatch_Call { + return &MockClientI_SubmitBatch_Call{Call: _e.mock.On("SubmitBatch", batch, daClient, daResult)} } -func (_c *MockLayerI_SubmitBatch_Call) Run(run func(batch *types.Batch, daClient da.Client, daResult *da.ResultSubmitBatch)) *MockLayerI_SubmitBatch_Call { +func (_c *MockClientI_SubmitBatch_Call) Run(run func(batch *types.Batch, daClient da.Client, daResult *da.ResultSubmitBatch)) *MockClientI_SubmitBatch_Call { _c.Call.Run(func(args mock.Arguments) { run(args[0].(*types.Batch), args[1].(da.Client), args[2].(*da.ResultSubmitBatch)) }) return _c } -func (_c *MockLayerI_SubmitBatch_Call) Return(_a0 error) *MockLayerI_SubmitBatch_Call { +func (_c *MockClientI_SubmitBatch_Call) Return(_a0 error) *MockClientI_SubmitBatch_Call { _c.Call.Return(_a0) return _c } -func (_c *MockLayerI_SubmitBatch_Call) RunAndReturn(run func(*types.Batch, da.Client, *da.ResultSubmitBatch) error) *MockLayerI_SubmitBatch_Call { +func (_c *MockClientI_SubmitBatch_Call) RunAndReturn(run func(*types.Batch, da.Client, *da.ResultSubmitBatch) error) *MockClientI_SubmitBatch_Call { _c.Call.Return(run) return _c } -// NewMockLayerI creates a new instance of MockLayerI. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// NewMockClientI creates a new instance of MockClientI. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. -func NewMockLayerI(t interface { +func NewMockClientI(t interface { mock.TestingT Cleanup(func()) -}) *MockLayerI { - mock := &MockLayerI{} +}) *MockClientI { + mock := &MockClientI{} mock.Mock.Test(t) t.Cleanup(func() { mock.AssertExpectations(t) }) diff --git a/node/node.go b/node/node.go index 31e089a32..906214592 100644 --- a/node/node.go +++ b/node/node.go @@ -71,7 +71,7 @@ type Node struct { Store store.Store BlockManager *block.Manager dalc da.DataAvailabilityLayerClient - settlementlc settlement.LayerI + settlementlc settlement.ClientI TxIndexer txindex.TxIndexer BlockIndexer indexer.BlockIndexer diff --git a/node/node_test.go b/node/node_test.go index ddd743d4e..7c5b8c79e 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -124,4 +124,7 @@ func TestMempoolDirectly(t *testing.T) { time.Sleep(1 * time.Second) assert.Equal(int64(4*len("tx*")), node.Mempool.SizeBytes()) + + err = node.Stop() + require.NoError(err) } diff --git a/p2p/validator.go b/p2p/validator.go index 4d9ec849f..ee25939fd 100644 --- a/p2p/validator.go +++ b/p2p/validator.go @@ -24,13 +24,13 @@ type IValidator interface { // Validator is a validator for messages gossiped in the p2p network. type Validator struct { logger types.Logger - slClient settlement.LayerI + slClient settlement.ClientI } var _ IValidator = (*Validator)(nil) // NewValidator creates a new Validator. -func NewValidator(logger types.Logger, slClient settlement.LayerI) *Validator { +func NewValidator(logger types.Logger, slClient settlement.ClientI) *Validator { return &Validator{ logger: logger, slClient: slClient, diff --git a/settlement/base.go b/settlement/base.go deleted file mode 100644 index 562f4369c..000000000 --- a/settlement/base.go +++ /dev/null @@ -1,126 +0,0 @@ -package settlement - -import ( - "context" - "fmt" - - "github.com/dymensionxyz/dymint/gerr" - - "github.com/dymensionxyz/dymint/da" - "github.com/dymensionxyz/dymint/types" - "github.com/tendermint/tendermint/libs/pubsub" -) - -// BaseLayerClient is intended only for usage in tests. -type BaseLayerClient struct { - logger types.Logger - pubsub *pubsub.Server - sequencersList []*types.Sequencer - config Config - ctx context.Context - cancel context.CancelFunc - client HubClient -} - -var _ LayerI = &BaseLayerClient{} - -// WithHubClient is an option which sets the hub client. -func WithHubClient(hubClient HubClient) Option { - return func(settlementClient LayerI) { - settlementClient.(*BaseLayerClient).client = hubClient - } -} - -// Init is called once. it initializes the struct members. -func (b *BaseLayerClient) Init(config Config, pubsub *pubsub.Server, logger types.Logger, options ...Option) error { - var err error - b.config = config - b.pubsub = pubsub - b.logger = logger - b.ctx, b.cancel = context.WithCancel(context.Background()) - // Apply options - for _, apply := range options { - apply(b) - } - - // TODO(srene): For a correct validation, sequencer list would need to be updated after a sequencer list change on the Hub. - // e.g. after receiving an event from the Hub. Right now, node will need to be restarted after a sequencer change, since it is - // only getting the sequencers list during Init. - b.sequencersList, err = b.fetchSequencersList() - if err != nil { - return err - } - b.logger.Info("Updated sequencers list from settlement layer", "sequencersList", b.sequencersList) - - return nil -} - -// Start is called once, after init. It initializes the query client. -func (b *BaseLayerClient) Start() error { - b.logger.Debug("settlement Layer Client starting.") - - err := b.client.Start() - if err != nil { - return err - } - - return nil -} - -// Stop is called once, after Start. -func (b *BaseLayerClient) Stop() error { - b.logger.Info("Settlement Layer Client stopping") - err := b.client.Stop() - if err != nil { - return err - } - b.cancel() - return nil -} - -// SubmitBatch tries submitting the batch in an async broadcast mode to the settlement layer. Events are emitted on success or failure. -func (b *BaseLayerClient) SubmitBatch(batch *types.Batch, daClient da.Client, daResult *da.ResultSubmitBatch) error { - b.logger.Debug("Submitting batch to settlement layer", "start height", batch.StartHeight, "end height", batch.EndHeight) - return b.client.PostBatch(batch, daClient, daResult) -} - -// RetrieveBatch gets the batch at a particular index. If no index is given, it returns the latest batch. -func (b *BaseLayerClient) RetrieveBatch(stateIndex ...uint64) (*ResultRetrieveBatch, error) { - if len(stateIndex) == 0 { - b.logger.Debug("Getting latest batch from settlement layer") - return b.client.GetLatestBatch(b.config.RollappID) - } - if len(stateIndex) == 1 { - b.logger.Debug("Getting batch from settlement layer", "state index", stateIndex) - return b.client.GetBatchAtIndex(b.config.RollappID, stateIndex[0]) - } - return nil, fmt.Errorf("expected 0 or 1 index: got %d: %w", len(stateIndex), gerr.ErrInvalidArgument) -} - -// GetHeightState returns the state at the given height. -func (b *BaseLayerClient) GetHeightState(h uint64) (*ResultGetHeightState, error) { - return b.client.GetHeightState(h) -} - -// GetSequencersList returns the current list of sequencers from the settlement layer -func (b *BaseLayerClient) GetSequencersList() []*types.Sequencer { - return b.sequencersList -} - -// GetProposer returns the sequencer which is currently the proposer -func (b *BaseLayerClient) GetProposer() *types.Sequencer { - for _, sequencer := range b.sequencersList { - if sequencer.Status == types.Proposer { - return sequencer - } - } - return nil -} - -func (b *BaseLayerClient) fetchSequencersList() ([]*types.Sequencer, error) { - sequencers, err := b.client.GetSequencers(b.config.RollappID) - if err != nil { - return nil, err - } - return sequencers, nil -} diff --git a/settlement/dymension/dymension.go b/settlement/dymension/dymension.go index 4890c9774..cb559ce08 100644 --- a/settlement/dymension/dymension.go +++ b/settlement/dymension/dymension.go @@ -3,7 +3,6 @@ package dymension import ( "context" "fmt" - "strconv" "time" "github.com/dymensionxyz/dymint/gerr" @@ -28,7 +27,6 @@ import ( "github.com/dymensionxyz/dymint/da" "github.com/dymensionxyz/dymint/settlement" "github.com/dymensionxyz/dymint/types" - "github.com/hashicorp/go-multierror" "github.com/tendermint/tendermint/libs/pubsub" ctypes "github.com/tendermint/tendermint/rpc/core/types" ) @@ -48,50 +46,19 @@ const ( postBatchSubscriberPrefix = "postBatchSubscriber" ) -// LayerClient is intended only for usage in tests. -type LayerClient struct { - *settlement.BaseLayerClient -} - -var _ settlement.LayerI = &LayerClient{} - -// Init is called once. it initializes the struct members. -func (dlc *LayerClient) Init(config settlement.Config, pubsub *pubsub.Server, logger types.Logger, options ...settlement.Option) error { - DymensionCosmosClient, err := NewDymensionHubClient(config, pubsub, logger) - if err != nil { - return err - } - baseOptions := []settlement.Option{ - settlement.WithHubClient(DymensionCosmosClient), - } - if options == nil { - options = baseOptions - } else { - options = append(baseOptions, options...) - } - dlc.BaseLayerClient = &settlement.BaseLayerClient{} - err = dlc.BaseLayerClient.Init(config, pubsub, logger, options...) - if err != nil { - return err - } - return nil -} - -// HubClient is the client for the Dymension Hub. -type HubClient struct { - config *settlement.Config - logger types.Logger - pubsub *pubsub.Server - client CosmosClient - ctx context.Context - cancel context.CancelFunc - rollappQueryClient rollapptypes.QueryClient - sequencerQueryClient sequencertypes.QueryClient - protoCodec *codec.ProtoCodec - eventMap map[string]string - // channel for getting notified when a batch is accepted by the settlement layer. - // only one batch of a specific height can get accepted and we can are currently sending only one batch at a time. - // for that reason it's safe to assume that if a batch is accepted, it refers to the last batch we've sent. +// Client is the client for the Dymension Hub. +type Client struct { + config *settlement.Config + logger types.Logger + pubsub *pubsub.Server + cosmosClient CosmosClient + ctx context.Context + cancel context.CancelFunc + rollappQueryClient rollapptypes.QueryClient + sequencerQueryClient sequencertypes.QueryClient + protoCodec *codec.ProtoCodec + eventMap map[string]string + sequencerList []*types.Sequencer retryAttempts uint retryMinDelay time.Duration retryMaxDelay time.Duration @@ -99,47 +66,10 @@ type HubClient struct { batchAcceptanceAttempts uint } -var _ settlement.HubClient = &HubClient{} +var _ settlement.ClientI = &Client{} -// Option is a function that configures the HubClient. -type Option func(*HubClient) - -// WithCosmosClient is an option that sets the CosmosClient. -func WithCosmosClient(cosmosClient CosmosClient) Option { - return func(d *HubClient) { - d.client = cosmosClient - } -} - -// WithRetryAttempts is an option that sets the number of attempts to retry when interacting with the settlement layer. -func WithRetryAttempts(batchRetryAttempts uint) Option { - return func(d *HubClient) { - d.retryAttempts = batchRetryAttempts - } -} - -// WithBatchAcceptanceTimeout is an option that sets the timeout for waiting for a batch to be accepted by the settlement layer. -func WithBatchAcceptanceTimeout(batchAcceptanceTimeout time.Duration) Option { - return func(d *HubClient) { - d.batchAcceptanceTimeout = batchAcceptanceTimeout - } -} - -// WithRetryMinDelay is an option that sets the retry function mindelay between hub retry attempts. -func WithRetryMinDelay(retryMinDelay time.Duration) Option { - return func(d *HubClient) { - d.retryMinDelay = retryMinDelay - } -} - -// WithRetryMaxDelay is an option that sets the retry function max delay between hub retry attempts. -func WithRetryMaxDelay(retryMaxDelay time.Duration) Option { - return func(d *HubClient) { - d.retryMaxDelay = retryMaxDelay - } -} - -func NewDymensionHubClient(config settlement.Config, pubsub *pubsub.Server, logger types.Logger, options ...Option) (*HubClient, error) { +// Init is called once. it initializes the struct members. +func (c *Client) Init(config settlement.Config, pubsub *pubsub.Server, logger types.Logger, options ...settlement.Option) error { ctx, cancel := context.WithCancel(context.Background()) eventMap := map[string]string{ fmt.Sprintf(eventStateUpdate, config.RollappID): settlement.EventNewBatchAccepted, @@ -150,55 +80,54 @@ func NewDymensionHubClient(config settlement.Config, pubsub *pubsub.Server, logg cryptocodec.RegisterInterfaces(interfaceRegistry) protoCodec := codec.NewProtoCodec(interfaceRegistry) - dymensionHubClient := &HubClient{ - config: &config, - logger: logger, - pubsub: pubsub, - ctx: ctx, - cancel: cancel, - eventMap: eventMap, - protoCodec: protoCodec, - retryAttempts: config.RetryAttempts, - batchAcceptanceTimeout: config.BatchAcceptanceTimeout, - batchAcceptanceAttempts: config.BatchAcceptanceAttempts, - retryMinDelay: config.RetryMinDelay, - retryMaxDelay: config.RetryMaxDelay, - } - - for _, option := range options { - option(dymensionHubClient) - } - - if dymensionHubClient.client == nil { + c.config = &config + c.logger = logger + c.pubsub = pubsub + c.ctx = ctx + c.cancel = cancel + c.eventMap = eventMap + c.protoCodec = protoCodec + c.retryAttempts = config.RetryAttempts + c.batchAcceptanceTimeout = config.BatchAcceptanceTimeout + c.batchAcceptanceAttempts = config.BatchAcceptanceAttempts + c.retryMinDelay = config.RetryMinDelay + c.retryMaxDelay = config.RetryMaxDelay + + // Apply options + for _, apply := range options { + apply(c) + } + + if c.cosmosClient == nil { client, err := cosmosclient.New( ctx, getCosmosClientOptions(&config)..., ) if err != nil { - return nil, err + return err } - dymensionHubClient.client = NewCosmosClient(client) + c.cosmosClient = NewCosmosClient(client) } - dymensionHubClient.rollappQueryClient = dymensionHubClient.client.GetRollappClient() - dymensionHubClient.sequencerQueryClient = dymensionHubClient.client.GetSequencerClient() + c.rollappQueryClient = c.cosmosClient.GetRollappClient() + c.sequencerQueryClient = c.cosmosClient.GetSequencerClient() - return dymensionHubClient, nil + return nil } // Start starts the HubClient. -func (d *HubClient) Start() error { - err := d.client.StartEventListener() +func (c *Client) Start() error { + err := c.cosmosClient.StartEventListener() if err != nil { return err } - go d.eventHandler() + go c.eventHandler() return nil } // Stop stops the HubClient. -func (d *HubClient) Stop() error { - d.cancel() - err := d.client.StopEventListener() +func (c *Client) Stop() error { + c.cancel() + err := c.cosmosClient.StopEventListener() if err != nil { return err } @@ -206,32 +135,32 @@ func (d *HubClient) Stop() error { return nil } -// PostBatch posts a batch to the Dymension Hub. it tries to post the batch until it is accepted by the settlement layer. +// SubmitBatch posts a batch to the Dymension Hub. it tries to post the batch until it is accepted by the settlement layer. // it emits success and failure events to the event bus accordingly. -func (d *HubClient) PostBatch(batch *types.Batch, daClient da.Client, daResult *da.ResultSubmitBatch) error { - msgUpdateState, err := d.convertBatchToMsgUpdateState(batch, daResult) +func (c *Client) SubmitBatch(batch *types.Batch, daClient da.Client, daResult *da.ResultSubmitBatch) error { + msgUpdateState, err := c.convertBatchToMsgUpdateState(batch, daResult) if err != nil { return fmt.Errorf("convert batch to msg update state: %w", err) } // TODO: probably should be changed to be a channel, as the eventHandler is also in the HubClient in he produces the event postBatchSubscriberClient := fmt.Sprintf("%s-%d-%s", postBatchSubscriberPrefix, batch.StartHeight, uuid.New().String()) - subscription, err := d.pubsub.Subscribe(d.ctx, postBatchSubscriberClient, settlement.EventQueryNewSettlementBatchAccepted, 1000) + subscription, err := c.pubsub.Subscribe(c.ctx, postBatchSubscriberClient, settlement.EventQueryNewSettlementBatchAccepted, 1000) if err != nil { return fmt.Errorf("pub sub subscribe to settlement state updates: %w", err) } //nolint:errcheck - defer d.pubsub.Unsubscribe(d.ctx, postBatchSubscriberClient, settlement.EventQueryNewSettlementBatchAccepted) + defer c.pubsub.Unsubscribe(c.ctx, postBatchSubscriberClient, settlement.EventQueryNewSettlementBatchAccepted) // Try submitting the batch to the settlement layer: // 1. broadcast the transaction to the blockchain (with retries). // 2. wait for the batch to be accepted by the settlement layer. for { - err := d.RunWithRetryInfinitely(func() error { - err := d.broadcastBatch(msgUpdateState) + err := c.RunWithRetryInfinitely(func() error { + err := c.broadcastBatch(msgUpdateState) if err != nil { - d.logger.Error( + c.logger.Error( "Submit batch", "startHeight", batch.StartHeight, @@ -248,14 +177,14 @@ func (d *HubClient) PostBatch(batch *types.Batch, daClient da.Client, daResult * } // Batch was submitted successfully. Wait for it to be accepted by the settlement layer. - timer := time.NewTimer(d.batchAcceptanceTimeout) + timer := time.NewTimer(c.batchAcceptanceTimeout) defer timer.Stop() attempt := uint64(0) for { select { - case <-d.ctx.Done(): - return d.ctx.Err() + case <-c.ctx.Done(): + return c.ctx.Err() case <-subscription.Cancelled(): return fmt.Errorf("subscription cancelled") @@ -263,29 +192,29 @@ func (d *HubClient) PostBatch(batch *types.Batch, daClient da.Client, daResult * case event := <-subscription.Out(): eventData, _ := event.Data().(*settlement.EventDataNewBatchAccepted) if eventData.EndHeight != batch.EndHeight { - d.logger.Debug("Received event for a different batch, ignoring.", "event", eventData) + c.logger.Debug("Received event for a different batch, ignoring.", "event", eventData) continue } - d.logger.Info("Batch accepted.", "startHeight", batch.StartHeight, "endHeight", batch.EndHeight, "stateIndex", eventData.StateIndex) + c.logger.Info("Batch accepted.", "startHeight", batch.StartHeight, "endHeight", batch.EndHeight, "stateIndex", eventData.StateIndex) return nil case <-timer.C: // Check if the batch was accepted by the settlement layer, and we've just missed the event. attempt++ - includedBatch, err := d.pollForBatchInclusion(batch.EndHeight, attempt) + includedBatch, err := c.pollForBatchInclusion(batch.EndHeight, attempt) if err == nil && !includedBatch { // no error, but still not included - timer.Reset(d.batchAcceptanceTimeout) + timer.Reset(c.batchAcceptanceTimeout) continue } // all good if err == nil { - d.logger.Info("Batch accepted", "startHeight", batch.StartHeight, "endHeight", batch.EndHeight) + c.logger.Info("Batch accepted", "startHeight", batch.StartHeight, "endHeight", batch.EndHeight) return nil } } // If errored polling, start again the submission loop. - d.logger.Error( + c.logger.Error( "Wait for batch inclusion", "startHeight", batch.StartHeight, @@ -299,16 +228,16 @@ func (d *HubClient) PostBatch(batch *types.Batch, daClient da.Client, daResult * } } -func (d *HubClient) getStateInfo(index, height *uint64) (res *rollapptypes.QueryGetStateInfoResponse, err error) { - req := &rollapptypes.QueryGetStateInfoRequest{RollappId: d.config.RollappID} +func (c *Client) getStateInfo(index, height *uint64) (res *rollapptypes.QueryGetStateInfoResponse, err error) { + req := &rollapptypes.QueryGetStateInfoRequest{RollappId: c.config.RollappID} if index != nil { req.Index = *index } if height != nil { req.Height = *height } - err = d.RunWithRetry(func() error { - res, err = d.rollappQueryClient.StateInfo(d.ctx, req) + err = c.RunWithRetry(func() error { + res, err = c.rollappQueryClient.StateInfo(c.ctx, req) if status.Code(err) == codes.NotFound { return retry.Unrecoverable(gerr.ErrNotFound) @@ -325,25 +254,25 @@ func (d *HubClient) getStateInfo(index, height *uint64) (res *rollapptypes.Query } // GetLatestBatch returns the latest batch from the Dymension Hub. -func (d *HubClient) GetLatestBatch(rollappID string) (*settlement.ResultRetrieveBatch, error) { - res, err := d.getStateInfo(nil, nil) +func (c *Client) GetLatestBatch() (*settlement.ResultRetrieveBatch, error) { + res, err := c.getStateInfo(nil, nil) if err != nil { return nil, fmt.Errorf("get state info: %w", err) } - return d.convertStateInfoToResultRetrieveBatch(&res.StateInfo) + return convertStateInfoToResultRetrieveBatch(&res.StateInfo) } // GetBatchAtIndex returns the batch at the given index from the Dymension Hub. -func (d *HubClient) GetBatchAtIndex(rollappID string, index uint64) (*settlement.ResultRetrieveBatch, error) { - res, err := d.getStateInfo(&index, nil) +func (c *Client) GetBatchAtIndex(index uint64) (*settlement.ResultRetrieveBatch, error) { + res, err := c.getStateInfo(&index, nil) if err != nil { return nil, fmt.Errorf("get state info: %w", err) } - return d.convertStateInfoToResultRetrieveBatch(&res.StateInfo) + return convertStateInfoToResultRetrieveBatch(&res.StateInfo) } -func (d *HubClient) GetHeightState(h uint64) (*settlement.ResultGetHeightState, error) { - res, err := d.getStateInfo(nil, &h) +func (c *Client) GetHeightState(h uint64) (*settlement.ResultGetHeightState, error) { + res, err := c.getStateInfo(nil, &h) if err != nil { return nil, fmt.Errorf("get state info: %w", err) } @@ -355,16 +284,35 @@ func (d *HubClient) GetHeightState(h uint64) (*settlement.ResultGetHeightState, }, nil } +// GetProposer implements settlement.ClientI. +func (c *Client) GetProposer() *types.Sequencer { + seqs, err := c.GetSequencers() + if err != nil { + c.logger.Error("Get sequencers", "error", err) + return nil + } + for _, sequencer := range seqs { + if sequencer.Status == types.Proposer { + return sequencer + } + } + return nil +} + // GetSequencers returns the bonded sequencers of the given rollapp. -func (d *HubClient) GetSequencers(rollappID string) ([]*types.Sequencer, error) { +func (c *Client) GetSequencers() ([]*types.Sequencer, error) { + if c.sequencerList != nil { + return c.sequencerList, nil + } + var res *sequencertypes.QueryGetSequencersByRollappByStatusResponse req := &sequencertypes.QueryGetSequencersByRollappByStatusRequest{ - RollappId: d.config.RollappID, + RollappId: c.config.RollappID, Status: sequencertypes.Bonded, } - err := d.RunWithRetry(func() error { + err := c.RunWithRetry(func() error { var err error - res, err = d.sequencerQueryClient.SequencersByRollappByStatus(d.ctx, req) + res, err = c.sequencerQueryClient.SequencersByRollappByStatus(c.ctx, req) return err }) if err != nil { @@ -379,7 +327,7 @@ func (d *HubClient) GetSequencers(rollappID string) ([]*types.Sequencer, error) sequencersList := make([]*types.Sequencer, 0, len(res.Sequencers)) for _, sequencer := range res.Sequencers { var pubKey cryptotypes.PubKey - err := d.protoCodec.UnpackAny(sequencer.DymintPubKey, &pubKey) + err := c.protoCodec.UnpackAny(sequencer.DymintPubKey, &pubKey) if err != nil { return nil, err } @@ -394,21 +342,22 @@ func (d *HubClient) GetSequencers(rollappID string) ([]*types.Sequencer, error) Status: status, }) } + c.sequencerList = sequencersList return sequencersList, nil } -func (d *HubClient) broadcastBatch(msgUpdateState *rollapptypes.MsgUpdateState) error { - txResp, err := d.client.BroadcastTx(d.config.DymAccountName, msgUpdateState) +func (c *Client) broadcastBatch(msgUpdateState *rollapptypes.MsgUpdateState) error { + txResp, err := c.cosmosClient.BroadcastTx(c.config.DymAccountName, msgUpdateState) if err != nil || txResp.Code != 0 { return fmt.Errorf("broadcast tx: %w", err) } return nil } -func (d *HubClient) eventHandler() { +func (c *Client) eventHandler() { // TODO(omritoptix): eventsChannel should be a generic channel which is later filtered by the event type. subscriber := fmt.Sprintf("dymension-client-%s", uuid.New().String()) - eventsChannel, err := d.client.SubscribeToEvents(d.ctx, subscriber, fmt.Sprintf(eventStateUpdate, d.config.RollappID), 1000) + eventsChannel, err := c.cosmosClient.SubscribeToEvents(c.ctx, subscriber, fmt.Sprintf(eventStateUpdate, c.config.RollappID), 1000) if err != nil { panic("Error subscribing to events") } @@ -416,29 +365,29 @@ func (d *HubClient) eventHandler() { for { select { - case <-d.ctx.Done(): + case <-c.ctx.Done(): return - case <-d.client.EventListenerQuit(): + case <-c.cosmosClient.EventListenerQuit(): // TODO(omritoptix): Fallback to polling panic("Settlement WS disconnected") case event := <-eventsChannel: // Assert value is in map and publish it to the event bus - _, ok := d.eventMap[event.Query] + _, ok := c.eventMap[event.Query] if !ok { - d.logger.Debug("Ignoring event. Type not supported", "event", event) + c.logger.Debug("Ignoring event. Type not supported", "event", event) continue } - eventData, err := d.getEventData(d.eventMap[event.Query], event) + eventData, err := c.getEventData(c.eventMap[event.Query], event) if err != nil { panic(err) } - uevent.MustPublish(d.ctx, d.pubsub, eventData, map[string][]string{settlement.EventTypeKey: {d.eventMap[event.Query]}}) + uevent.MustPublish(c.ctx, c.pubsub, eventData, map[string][]string{settlement.EventTypeKey: {c.eventMap[event.Query]}}) } } } -func (d *HubClient) convertBatchToMsgUpdateState(batch *types.Batch, daResult *da.ResultSubmitBatch) (*rollapptypes.MsgUpdateState, error) { - account, err := d.client.GetAccount(d.config.DymAccountName) +func (c *Client) convertBatchToMsgUpdateState(batch *types.Batch, daResult *da.ResultSubmitBatch) (*rollapptypes.MsgUpdateState, error) { + account, err := c.cosmosClient.GetAccount(c.config.DymAccountName) if err != nil { return nil, fmt.Errorf("get account: %w", err) } @@ -459,7 +408,7 @@ func (d *HubClient) convertBatchToMsgUpdateState(batch *types.Batch, daResult *d settlementBatch := &rollapptypes.MsgUpdateState{ Creator: addr, - RollappId: d.config.RollappID, + RollappId: c.config.RollappID, StartHeight: batch.StartHeight, NumBlocks: batch.EndHeight - batch.StartHeight + 1, DAPath: daResult.SubmitMetaData.ToPath(), @@ -490,94 +439,25 @@ func getCosmosClientOptions(config *settlement.Config) []cosmosclient.Option { return options } -func (d *HubClient) getEventData(eventType string, rawEventData ctypes.ResultEvent) (interface{}, error) { +func (c *Client) getEventData(eventType string, rawEventData ctypes.ResultEvent) (interface{}, error) { switch eventType { case settlement.EventNewBatchAccepted: - return d.convertToNewBatchEvent(rawEventData) + return convertToNewBatchEvent(rawEventData) } return nil, fmt.Errorf("event type %s not recognized", eventType) } -func (d *HubClient) convertToNewBatchEvent(rawEventData ctypes.ResultEvent) (*settlement.EventDataNewBatchAccepted, error) { - // check all expected attributes exists - events := rawEventData.Events - if events["state_update.num_blocks"] == nil || events["state_update.start_height"] == nil || events["state_update.state_info_index"] == nil { - return nil, fmt.Errorf("missing expected attributes in event") - } - - var multiErr *multierror.Error - numBlocks, err := strconv.ParseInt(rawEventData.Events["state_update.num_blocks"][0], 10, 64) - multiErr = multierror.Append(multiErr, err) - startHeight, err := strconv.ParseInt(rawEventData.Events["state_update.start_height"][0], 10, 64) - multiErr = multierror.Append(multiErr, err) - stateIndex, err := strconv.ParseInt(rawEventData.Events["state_update.state_info_index"][0], 10, 64) - multiErr = multierror.Append(multiErr, err) - err = multiErr.ErrorOrNil() - if err != nil { - return nil, multiErr - } - endHeight := uint64(startHeight + numBlocks - 1) - NewBatchEvent := &settlement.EventDataNewBatchAccepted{ - EndHeight: endHeight, - StateIndex: uint64(stateIndex), - } - return NewBatchEvent, nil -} - -func (d *HubClient) convertStateInfoToResultRetrieveBatch(stateInfo *rollapptypes.StateInfo) (*settlement.ResultRetrieveBatch, error) { - daMetaData := &da.DASubmitMetaData{} - daMetaData, err := daMetaData.FromPath(stateInfo.DAPath) - if err != nil { - return nil, err - } - batchResult := &settlement.Batch{ - StartHeight: stateInfo.StartHeight, - EndHeight: stateInfo.StartHeight + stateInfo.NumBlocks - 1, - MetaData: &settlement.BatchMetaData{ - DA: daMetaData, - }, - } - return &settlement.ResultRetrieveBatch{ - ResultBase: settlement.ResultBase{Code: settlement.StatusSuccess, StateIndex: stateInfo.StateInfoIndex.Index}, - Batch: batchResult, - }, nil -} - // pollForBatchInclusion polls the hub for the inclusion of a batch with the given end height. -func (d *HubClient) pollForBatchInclusion(batchEndHeight, attempt uint64) (bool, error) { - latestBatch, err := d.GetLatestBatch(d.config.RollappID) +func (c *Client) pollForBatchInclusion(batchEndHeight, attempt uint64) (bool, error) { + latestBatch, err := c.GetLatestBatch() if err != nil { return false, fmt.Errorf("get latest batch: %w", err) } // no error, but still not included - if attempt >= uint64(d.batchAcceptanceAttempts) { + if attempt >= uint64(c.batchAcceptanceAttempts) { return false, fmt.Errorf("timed out waiting for batch inclusion on settlement layer") } return latestBatch.Batch.EndHeight == batchEndHeight, nil } - -// RunWithRetry runs the given operation with retry, doing a number of attempts, and taking the last -// error only. It uses the context of the HubClient. -func (d *HubClient) RunWithRetry(operation func() error) error { - return retry.Do(operation, - retry.Context(d.ctx), - retry.LastErrorOnly(true), - retry.Delay(d.retryMinDelay), - retry.Attempts(d.retryAttempts), - retry.MaxDelay(d.retryMaxDelay), - ) -} - -// RunWithRetryInfinitely runs the given operation with retry, doing a number of attempts, and taking the last -// error only. It uses the context of the HubClient. -func (d *HubClient) RunWithRetryInfinitely(operation func() error) error { - return retry.Do(operation, - retry.Context(d.ctx), - retry.LastErrorOnly(true), - retry.Delay(d.retryMinDelay), - retry.Attempts(0), - retry.MaxDelay(d.retryMaxDelay), - ) -} diff --git a/settlement/dymension/dymension_test.go b/settlement/dymension/dymension_test.go index fdaec3275..da0d5b9fa 100644 --- a/settlement/dymension/dymension_test.go +++ b/settlement/dymension/dymension_test.go @@ -51,7 +51,7 @@ func TestGetSequencers(t *testing.T) { cosmosClientMock.On("GetRollappClient").Return(rollapptypesmock.NewMockQueryClient(t)) cosmosClientMock.On("GetSequencerClient").Return(sequencerQueryClientMock) - options := []dymension.Option{ + options := []settlement.Option{ dymension.WithCosmosClient(cosmosClientMock), } @@ -59,10 +59,11 @@ func TestGetSequencers(t *testing.T) { err = pubsubServer.Start() require.NoError(err) - hubClient, err := dymension.NewDymensionHubClient(settlement.Config{}, pubsubServer, log.TestingLogger(), options...) + hubClient := dymension.Client{} + err = hubClient.Init(settlement.Config{}, pubsubServer, log.TestingLogger(), options...) require.NoError(err) - sequencers, err := hubClient.GetSequencers("mock-rollapp") + sequencers, err := hubClient.GetSequencers() require.NoError(err) require.Len(sequencers, count) } @@ -97,7 +98,7 @@ func TestPostBatch(t *testing.T) { require.NoError(err) cosmosClientMock.On("SubscribeToEvents", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return((<-chan coretypes.ResultEvent)(batchAcceptedCh), nil) - options := []dymension.Option{ + options := []settlement.Option{ dymension.WithCosmosClient(cosmosClientMock), dymension.WithBatchAcceptanceTimeout(time.Millisecond * 300), dymension.WithRetryAttempts(2), @@ -173,7 +174,8 @@ func TestPostBatch(t *testing.T) { rollappQueryClientMock.On("StateInfo", mock.Anything, mock.Anything).Return(nil, status.New(codes.NotFound, "not found").Err()) } } - hubClient, err := dymension.NewDymensionHubClient(settlement.Config{}, pubsubServer, log.TestingLogger(), options...) + hubClient := dymension.Client{} + err := hubClient.Init(settlement.Config{}, pubsubServer, log.TestingLogger(), options...) require.NoError(err) err = hubClient.Start() require.NoError(err) @@ -183,7 +185,7 @@ func TestPostBatch(t *testing.T) { errChan := make(chan error, 1) // Create a channel to receive an error from the goroutine // Post the batch in a goroutine and capture any error. go func() { - err := hubClient.PostBatch(batch, da.Mock, resultSubmitBatch) + err := hubClient.SubmitBatch(batch, da.Mock, resultSubmitBatch) errChan <- err // Send any error to the errChan }() diff --git a/settlement/dymension/options.go b/settlement/dymension/options.go new file mode 100644 index 000000000..b3d84e91e --- /dev/null +++ b/settlement/dymension/options.go @@ -0,0 +1,47 @@ +package dymension + +import ( + "time" + + "github.com/dymensionxyz/dymint/settlement" +) + +// WithCosmosClient is an option that sets the CosmosClient. +func WithCosmosClient(cosmosClient CosmosClient) settlement.Option { + return func(c settlement.ClientI) { + dlc, _ := c.(*Client) + dlc.cosmosClient = cosmosClient + } +} + +// WithRetryAttempts is an option that sets the number of attempts to retry when interacting with the settlement layer. +func WithRetryAttempts(batchRetryAttempts uint) settlement.Option { + return func(c settlement.ClientI) { + dlc, _ := c.(*Client) + dlc.retryAttempts = batchRetryAttempts + } +} + +// WithBatchAcceptanceTimeout is an option that sets the timeout for waiting for a batch to be accepted by the settlement layer. +func WithBatchAcceptanceTimeout(batchAcceptanceTimeout time.Duration) settlement.Option { + return func(c settlement.ClientI) { + dlc, _ := c.(*Client) + dlc.batchAcceptanceTimeout = batchAcceptanceTimeout + } +} + +// WithRetryMinDelay is an option that sets the retry function mindelay between hub retry attempts. +func WithRetryMinDelay(retryMinDelay time.Duration) settlement.Option { + return func(c settlement.ClientI) { + dlc, _ := c.(*Client) + dlc.retryMinDelay = retryMinDelay + } +} + +// WithRetryMaxDelay is an option that sets the retry function max delay between hub retry attempts. +func WithRetryMaxDelay(retryMaxDelay time.Duration) settlement.Option { + return func(c settlement.ClientI) { + dlc, _ := c.(*Client) + dlc.retryMaxDelay = retryMaxDelay + } +} diff --git a/settlement/dymension/utils.go b/settlement/dymension/utils.go new file mode 100644 index 000000000..950ee01b6 --- /dev/null +++ b/settlement/dymension/utils.go @@ -0,0 +1,82 @@ +package dymension + +import ( + "fmt" + "strconv" + + "github.com/avast/retry-go/v4" + rollapptypes "github.com/dymensionxyz/dymension/v3/x/rollapp/types" + "github.com/dymensionxyz/dymint/da" + "github.com/dymensionxyz/dymint/settlement" + "github.com/hashicorp/go-multierror" + ctypes "github.com/tendermint/tendermint/rpc/core/types" +) + +// RunWithRetry runs the given operation with retry, doing a number of attempts, and taking the last +// error only. It uses the context of the HubClient. +func (c *Client) RunWithRetry(operation func() error) error { + return retry.Do(operation, + retry.Context(c.ctx), + retry.LastErrorOnly(true), + retry.Delay(c.retryMinDelay), + retry.Attempts(c.retryAttempts), + retry.MaxDelay(c.retryMaxDelay), + ) +} + +// RunWithRetryInfinitely runs the given operation with retry, doing a number of attempts, and taking the last +// error only. It uses the context of the HubClient. +func (c *Client) RunWithRetryInfinitely(operation func() error) error { + return retry.Do(operation, + retry.Context(c.ctx), + retry.LastErrorOnly(true), + retry.Delay(c.retryMinDelay), + retry.Attempts(0), + retry.MaxDelay(c.retryMaxDelay), + ) +} + +func convertStateInfoToResultRetrieveBatch(stateInfo *rollapptypes.StateInfo) (*settlement.ResultRetrieveBatch, error) { + daMetaData := &da.DASubmitMetaData{} + daMetaData, err := daMetaData.FromPath(stateInfo.DAPath) + if err != nil { + return nil, err + } + batchResult := &settlement.Batch{ + StartHeight: stateInfo.StartHeight, + EndHeight: stateInfo.StartHeight + stateInfo.NumBlocks - 1, + MetaData: &settlement.BatchMetaData{ + DA: daMetaData, + }, + } + return &settlement.ResultRetrieveBatch{ + ResultBase: settlement.ResultBase{Code: settlement.StatusSuccess, StateIndex: stateInfo.StateInfoIndex.Index}, + Batch: batchResult, + }, nil +} + +func convertToNewBatchEvent(rawEventData ctypes.ResultEvent) (*settlement.EventDataNewBatchAccepted, error) { + // check all expected attributes exists + events := rawEventData.Events + if events["state_update.num_blocks"] == nil || events["state_update.start_height"] == nil || events["state_update.state_info_index"] == nil { + return nil, fmt.Errorf("missing expected attributes in event") + } + + var multiErr *multierror.Error + numBlocks, err := strconv.ParseInt(rawEventData.Events["state_update.num_blocks"][0], 10, 64) + multiErr = multierror.Append(multiErr, err) + startHeight, err := strconv.ParseInt(rawEventData.Events["state_update.start_height"][0], 10, 64) + multiErr = multierror.Append(multiErr, err) + stateIndex, err := strconv.ParseInt(rawEventData.Events["state_update.state_info_index"][0], 10, 64) + multiErr = multierror.Append(multiErr, err) + err = multiErr.ErrorOrNil() + if err != nil { + return nil, multiErr + } + endHeight := uint64(startHeight + numBlocks - 1) + NewBatchEvent := &settlement.EventDataNewBatchAccepted{ + EndHeight: endHeight, + StateIndex: uint64(stateIndex), + } + return NewBatchEvent, nil +} diff --git a/settlement/grpc/grpc.go b/settlement/grpc/grpc.go index dfd5960ea..3b593b59a 100644 --- a/settlement/grpc/grpc.go +++ b/settlement/grpc/grpc.go @@ -30,40 +30,11 @@ import ( slmock "github.com/dymensionxyz/dymint/settlement/grpc/mockserv/proto" ) -// LayerClient is an extension of the base settlement layer client +// Client is an extension of the base settlement layer client // for usage in tests and local development. -type LayerClient struct { - *settlement.BaseLayerClient -} - -var _ settlement.LayerI = (*LayerClient)(nil) - -// Init initializes the mock layer client. -func (m *LayerClient) Init(config settlement.Config, pubsub *pubsub.Server, logger types.Logger, options ...settlement.Option) error { - HubClientMock, err := newHubClient(config, pubsub, logger) - if err != nil { - return err - } - baseOptions := []settlement.Option{ - settlement.WithHubClient(HubClientMock), - } - if options == nil { - options = baseOptions - } else { - options = append(baseOptions, options...) - } - m.BaseLayerClient = &settlement.BaseLayerClient{} - err = m.BaseLayerClient.Init(config, pubsub, logger, options...) - if err != nil { - return err - } - return nil -} - -var _ settlement.HubClient = (*HubGrpcClient)(nil) - -type HubGrpcClient struct { +type Client struct { ctx context.Context + rollappID string ProposerPubKey string slStateIndex uint64 logger types.Logger @@ -75,14 +46,17 @@ type HubGrpcClient struct { refreshTime int } -func newHubClient(config settlement.Config, pubsub *pubsub.Server, logger types.Logger) (*HubGrpcClient, error) { +var _ settlement.ClientI = (*Client)(nil) + +// Init initializes the mock layer client. +func (c *Client) Init(config settlement.Config, pubsub *pubsub.Server, logger types.Logger, options ...settlement.Option) error { ctx := context.Background() latestHeight := uint64(0) slStateIndex := uint64(0) proposer, err := initConfig(config) if err != nil { - return nil, err + return err } var opts []grpc.DialOption opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials())) @@ -92,7 +66,7 @@ func newHubClient(config settlement.Config, pubsub *pubsub.Server, logger types. conn, err := grpc.Dial(config.SLGrpc.Host+":"+strconv.Itoa(config.SLGrpc.Port), opts...) if err != nil { logger.Error("grpc sl connecting") - return nil, err + return err } client := slmock.NewMockSLClient(conn) @@ -104,29 +78,29 @@ func newHubClient(config settlement.Config, pubsub *pubsub.Server, logger types. var settlementBatch rollapptypes.MsgUpdateState batchReply, err := client.GetBatch(ctx, &slmock.SLGetBatchRequest{Index: slStateIndex}) if err != nil { - return nil, err + return err } err = json.Unmarshal(batchReply.GetBatch(), &settlementBatch) if err != nil { - return nil, errors.New("error unmarshalling batch") + return errors.New("error unmarshalling batch") } latestHeight = settlementBatch.StartHeight + settlementBatch.NumBlocks - 1 } logger.Debug("Starting grpc SL ", "index", slStateIndex) - ret := &HubGrpcClient{ - ctx: ctx, - ProposerPubKey: proposer, - logger: logger, - pubsub: pubsub, - slStateIndex: slStateIndex, - conn: conn, - sl: client, - stopchan: stopchan, - refreshTime: config.SLGrpc.RefreshTime, - } - ret.latestHeight.Store(latestHeight) - return ret, nil + c.rollappID = config.RollappID + c.ProposerPubKey = proposer + c.logger = logger + c.ctx = ctx + c.pubsub = pubsub + c.slStateIndex = slStateIndex + c.conn = conn + c.sl = client + c.stopchan = stopchan + c.refreshTime = config.SLGrpc.RefreshTime + c.latestHeight.Store(latestHeight) + + return nil } func initConfig(conf settlement.Config) (proposer string, err error) { @@ -158,7 +132,7 @@ func initConfig(conf settlement.Config) (proposer string, err error) { } // Start starts the mock client -func (c *HubGrpcClient) Start() error { +func (c *Client) Start() error { c.logger.Info("Starting grpc mock settlement") go func() { @@ -194,19 +168,22 @@ func (c *HubGrpcClient) Start() error { } // Stop stops the mock client -func (c *HubGrpcClient) Stop() error { +func (c *Client) Stop() error { c.logger.Info("Stopping grpc mock settlement") close(c.stopchan) return nil } // PostBatch saves the batch to the kv store -func (c *HubGrpcClient) PostBatch(batch *types.Batch, daClient da.Client, daResult *da.ResultSubmitBatch) error { +func (c *Client) SubmitBatch(batch *types.Batch, daClient da.Client, daResult *da.ResultSubmitBatch) error { settlementBatch := c.convertBatchtoSettlementBatch(batch, daResult) - c.saveBatch(settlementBatch) + err := c.saveBatch(settlementBatch) + if err != nil { + return err + } time.Sleep(10 * time.Millisecond) // mimic a delay in batch acceptance - err := c.pubsub.PublishWithEvents(context.Background(), &settlement.EventDataNewBatchAccepted{EndHeight: settlementBatch.EndHeight}, settlement.EventNewBatchAcceptedList) + err = c.pubsub.PublishWithEvents(context.Background(), &settlement.EventDataNewBatchAccepted{EndHeight: settlementBatch.EndHeight}, settlement.EventNewBatchAcceptedList) if err != nil { return err } @@ -214,9 +191,9 @@ func (c *HubGrpcClient) PostBatch(batch *types.Batch, daClient da.Client, daResu } // GetLatestBatch returns the latest batch from the kv store -func (c *HubGrpcClient) GetLatestBatch(rollappID string) (*settlement.ResultRetrieveBatch, error) { +func (c *Client) GetLatestBatch() (*settlement.ResultRetrieveBatch, error) { c.logger.Info("GetLatestBatch grpc", "index", c.slStateIndex) - batchResult, err := c.GetBatchAtIndex(rollappID, atomic.LoadUint64(&c.slStateIndex)) + batchResult, err := c.GetBatchAtIndex(atomic.LoadUint64(&c.slStateIndex)) if err != nil { return nil, err } @@ -224,7 +201,7 @@ func (c *HubGrpcClient) GetLatestBatch(rollappID string) (*settlement.ResultRetr } // GetBatchAtIndex returns the batch at the given index -func (c *HubGrpcClient) GetBatchAtIndex(rollappID string, index uint64) (*settlement.ResultRetrieveBatch, error) { +func (c *Client) GetBatchAtIndex(index uint64) (*settlement.ResultRetrieveBatch, error) { batchResult, err := c.retrieveBatchAtStateIndex(index) if err != nil { return &settlement.ResultRetrieveBatch{ @@ -234,54 +211,58 @@ func (c *HubGrpcClient) GetBatchAtIndex(rollappID string, index uint64) (*settle return batchResult, nil } -func (c *HubGrpcClient) GetHeightState(index uint64) (*settlement.ResultGetHeightState, error) { +func (c *Client) GetHeightState(index uint64) (*settlement.ResultGetHeightState, error) { panic("hub grpc client get height state is not implemented: implement me") // TODO: impl } -// GetSequencers returns a list of sequencers. Currently only returns a single sequencer -func (c *HubGrpcClient) GetSequencers(rollappID string) ([]*types.Sequencer, error) { +// GetProposer implements settlement.ClientI. +func (c *Client) GetProposer() *types.Sequencer { pubKeyBytes, err := hex.DecodeString(c.ProposerPubKey) if err != nil { - return nil, err + return nil } var pubKey cryptotypes.PubKey = &ed25519.PubKey{Key: pubKeyBytes} - return []*types.Sequencer{ - { - PublicKey: pubKey, - Status: types.Proposer, - }, - }, nil + return &types.Sequencer{ + PublicKey: pubKey, + Status: types.Proposer, + } } -func (c *HubGrpcClient) saveBatch(batch *settlement.Batch) { +// GetSequencersList implements settlement.ClientI. +func (c *Client) GetSequencers() ([]*types.Sequencer, error) { + return []*types.Sequencer{c.GetProposer()}, nil +} + +func (c *Client) saveBatch(batch *settlement.Batch) error { c.logger.Debug("Saving batch to grpc settlement layer", "start height", batch.StartHeight, "end height", batch.EndHeight) b, err := json.Marshal(batch) if err != nil { - panic(err) + return err } // Save the batch to the next state index c.logger.Debug("Saving batch to grpc settlement layer", "index", c.slStateIndex+1) setBatchReply, err := c.sl.SetBatch(c.ctx, &slmock.SLSetBatchRequest{Index: c.slStateIndex + 1, Batch: b}) if err != nil { - panic(err) + return err } if setBatchReply.GetResult() != c.slStateIndex+1 { - panic(err) + return err } c.slStateIndex = setBatchReply.GetResult() setIndexReply, err := c.sl.SetIndex(c.ctx, &slmock.SLSetIndexRequest{Index: c.slStateIndex}) if err != nil || setIndexReply.GetIndex() != c.slStateIndex { - panic(err) + return err } c.logger.Debug("Setting grpc SL Index to ", "index", setIndexReply.GetIndex()) // Save latest height in memory and in store c.latestHeight.Store(batch.EndHeight) + return nil } -func (c *HubGrpcClient) convertBatchtoSettlementBatch(batch *types.Batch, daResult *da.ResultSubmitBatch) *settlement.Batch { +func (c *Client) convertBatchtoSettlementBatch(batch *types.Batch, daResult *da.ResultSubmitBatch) *settlement.Batch { settlementBatch := &settlement.Batch{ StartHeight: batch.StartHeight, EndHeight: batch.EndHeight, @@ -298,7 +279,7 @@ func (c *HubGrpcClient) convertBatchtoSettlementBatch(batch *types.Batch, daResu return settlementBatch } -func (c *HubGrpcClient) retrieveBatchAtStateIndex(slStateIndex uint64) (*settlement.ResultRetrieveBatch, error) { +func (c *Client) retrieveBatchAtStateIndex(slStateIndex uint64) (*settlement.ResultRetrieveBatch, error) { c.logger.Debug("Retrieving batch from grpc settlement layer", "SL state index", slStateIndex) getBatchReply, err := c.sl.GetBatch(c.ctx, &slmock.SLGetBatchRequest{Index: slStateIndex}) diff --git a/settlement/local/local.go b/settlement/local/local.go index 9cc3b4505..56c19640d 100644 --- a/settlement/local/local.go +++ b/settlement/local/local.go @@ -7,6 +7,7 @@ import ( "encoding/hex" "encoding/json" "errors" + "fmt" "path/filepath" "sync" "time" @@ -23,6 +24,7 @@ import ( "github.com/dymensionxyz/dymint/settlement" "github.com/dymensionxyz/dymint/store" "github.com/dymensionxyz/dymint/types" + uevent "github.com/dymensionxyz/dymint/utils/event" "github.com/tendermint/tendermint/libs/pubsub" ) @@ -34,38 +36,10 @@ var ( slStateIndexKey = []byte("slStateIndex") // used to recover after reboot ) -// LayerClient is an extension of the base settlement layer client +// Client is an extension of the base settlement layer client // for usage in tests and local development. -type LayerClient struct { - *settlement.BaseLayerClient -} - -var _ settlement.LayerI = (*LayerClient)(nil) - -// Init initializes the mock layer client. -func (m *LayerClient) Init(config settlement.Config, pubsub *pubsub.Server, logger types.Logger, options ...settlement.Option) error { - HubClientMock, err := newHubClient(config, pubsub, logger) - if err != nil { - return err - } - baseOptions := []settlement.Option{ - settlement.WithHubClient(HubClientMock), - } - if options == nil { - options = baseOptions - } else { - options = append(baseOptions, options...) - } - m.BaseLayerClient = &settlement.BaseLayerClient{} - err = m.BaseLayerClient.Init(config, pubsub, logger, options...) - if err != nil { - return err - } - return nil -} - -// HubClient implements The HubClient interface -type HubClient struct { +type Client struct { + rollappID string ProposerPubKey string logger types.Logger pubsub *pubsub.Server @@ -76,15 +50,17 @@ type HubClient struct { settlementKV store.KV } -var _ settlement.HubClient = &HubClient{} +var _ settlement.ClientI = (*Client)(nil) -func newHubClient(config settlement.Config, pubsub *pubsub.Server, logger types.Logger) (*HubClient, error) { - latestHeight := uint64(0) - slStateIndex := uint64(0) +// Init initializes the mock layer client. +func (c *Client) Init(config settlement.Config, pubsub *pubsub.Server, logger types.Logger, options ...settlement.Option) error { slstore, proposer, err := initConfig(config) if err != nil { - return nil, err + return err } + + latestHeight := uint64(0) + slStateIndex := uint64(0) settlementKV := store.NewPrefixKV(slstore, settlementKVPrefix) b, err := settlementKV.Get(slStateIndexKey) if err == nil { @@ -93,23 +69,22 @@ func newHubClient(config settlement.Config, pubsub *pubsub.Server, logger types. var settlementBatch rollapptypes.MsgUpdateState b, err := settlementKV.Get(keyFromIndex(slStateIndex)) if err != nil { - return nil, err + return err } err = json.Unmarshal(b, &settlementBatch) if err != nil { - return nil, errors.New("error unmarshalling batch") + return errors.New("error unmarshalling batch") } latestHeight = settlementBatch.StartHeight + settlementBatch.NumBlocks - 1 } - - return &HubClient{ - ProposerPubKey: proposer, - logger: logger, - pubsub: pubsub, - latestHeight: latestHeight, - slStateIndex: slStateIndex, - settlementKV: settlementKV, - }, nil + c.rollappID = config.RollappID + c.ProposerPubKey = proposer + c.logger = logger + c.pubsub = pubsub + c.latestHeight = latestHeight + c.slStateIndex = slStateIndex + c.settlementKV = settlementKV + return nil } func initConfig(conf settlement.Config) (slstore store.KV, proposer string, err error) { @@ -133,47 +108,52 @@ func initConfig(conf settlement.Config) (slstore store.KV, proposer string, err } } else { slstore = store.NewDefaultKVStore(conf.KeyringHomeDir, "data", kvStoreDBName) - proposerKeyPath := filepath.Join(conf.KeyringHomeDir, "config/priv_validator_key.json") - key, err := tmp2p.LoadOrGenNodeKey(proposerKeyPath) - if err != nil { - return nil, "", err + if conf.ProposerPubKey != "" { + proposer = conf.ProposerPubKey + } else { + proposerKeyPath := filepath.Join(conf.KeyringHomeDir, "config/priv_validator_key.json") + key, err := tmp2p.LoadOrGenNodeKey(proposerKeyPath) + if err != nil { + return nil, "", fmt.Errorf("loading sequencer pubkey: %w", err) + } + proposer = hex.EncodeToString(key.PubKey().Bytes()) } - proposer = hex.EncodeToString(key.PubKey().Bytes()) } return } // Start starts the mock client -func (c *HubClient) Start() error { +func (c *Client) Start() error { return nil } // Stop stops the mock client -func (c *HubClient) Stop() error { - return nil +func (c *Client) Stop() error { + return c.settlementKV.Close() } // PostBatch saves the batch to the kv store -func (c *HubClient) PostBatch(batch *types.Batch, daClient da.Client, daResult *da.ResultSubmitBatch) error { +func (c *Client) SubmitBatch(batch *types.Batch, daClient da.Client, daResult *da.ResultSubmitBatch) error { settlementBatch := convertBatchToSettlementBatch(batch, daResult) - c.saveBatch(settlementBatch) - go func() { - time.Sleep(10 * time.Millisecond) // mimic a delay in batch acceptance - err := c.pubsub.PublishWithEvents(context.Background(), &settlement.EventDataNewBatchAccepted{EndHeight: settlementBatch.EndHeight}, settlement.EventNewBatchAcceptedList) - if err != nil { - panic(err) - } - }() + err := c.saveBatch(settlementBatch) + if err != nil { + return err + } + + time.Sleep(100 * time.Millisecond) // mimic a delay in batch acceptance + ctx := context.Background() + uevent.MustPublish(ctx, c.pubsub, settlement.EventDataNewBatchAccepted{EndHeight: settlementBatch.EndHeight}, settlement.EventNewBatchAcceptedList) + return nil } // GetLatestBatch returns the latest batch from the kv store -func (c *HubClient) GetLatestBatch(rollappID string) (*settlement.ResultRetrieveBatch, error) { +func (c *Client) GetLatestBatch() (*settlement.ResultRetrieveBatch, error) { c.mu.Lock() ix := c.slStateIndex c.mu.Unlock() - batchResult, err := c.GetBatchAtIndex(rollappID, ix) + batchResult, err := c.GetBatchAtIndex(ix) if err != nil { return nil, err } @@ -181,7 +161,7 @@ func (c *HubClient) GetLatestBatch(rollappID string) (*settlement.ResultRetrieve } // GetBatchAtIndex returns the batch at the given index -func (c *HubClient) GetBatchAtIndex(rollappID string, index uint64) (*settlement.ResultRetrieveBatch, error) { +func (c *Client) GetBatchAtIndex(index uint64) (*settlement.ResultRetrieveBatch, error) { batchResult, err := c.retrieveBatchAtStateIndex(index) if err != nil { return &settlement.ResultRetrieveBatch{ @@ -191,14 +171,14 @@ func (c *HubClient) GetBatchAtIndex(rollappID string, index uint64) (*settlement return batchResult, nil } -func (c *HubClient) GetHeightState(h uint64) (*settlement.ResultGetHeightState, error) { +func (c *Client) GetHeightState(h uint64) (*settlement.ResultGetHeightState, error) { c.mu.Lock() defer c.mu.Unlock() // TODO: optimize (binary search, or just make another index) for i := c.slStateIndex; i > 0; i-- { - b, err := c.GetBatchAtIndex("", i) + b, err := c.GetBatchAtIndex(i) if err != nil { - panic(err) + return nil, err } if b.StartHeight <= h && b.EndHeight >= h { return &settlement.ResultGetHeightState{ @@ -212,28 +192,31 @@ func (c *HubClient) GetHeightState(h uint64) (*settlement.ResultGetHeightState, return nil, gerr.ErrNotFound // TODO: need to return a cosmos specific error? } -// GetSequencers returns a list of sequencers. Currently only returns a single sequencer -func (c *HubClient) GetSequencers(rollappID string) ([]*types.Sequencer, error) { +// GetProposer implements settlement.ClientI. +func (c *Client) GetProposer() *types.Sequencer { pubKeyBytes, err := hex.DecodeString(c.ProposerPubKey) if err != nil { - return nil, err + return nil } var pubKey cryptotypes.PubKey = &ed25519.PubKey{Key: pubKeyBytes} - return []*types.Sequencer{ - { - PublicKey: pubKey, - Status: types.Proposer, - }, - }, nil + return &types.Sequencer{ + PublicKey: pubKey, + Status: types.Proposer, + } } -func (c *HubClient) saveBatch(batch *settlement.Batch) { +// GetSequencersList implements settlement.ClientI. +func (c *Client) GetSequencers() ([]*types.Sequencer, error) { + return []*types.Sequencer{c.GetProposer()}, nil +} + +func (c *Client) saveBatch(batch *settlement.Batch) error { c.logger.Debug("Saving batch to settlement layer.", "start height", batch.StartHeight, "end height", batch.EndHeight) b, err := json.Marshal(batch) if err != nil { - panic(err) + return err } c.mu.Lock() @@ -242,18 +225,19 @@ func (c *HubClient) saveBatch(batch *settlement.Batch) { c.slStateIndex++ err = c.settlementKV.Set(keyFromIndex(c.slStateIndex), b) if err != nil { - panic(err) + return err } b = make([]byte, 8) binary.BigEndian.PutUint64(b, c.slStateIndex) err = c.settlementKV.Set(slStateIndexKey, b) if err != nil { - panic(err) + return err } c.latestHeight = batch.EndHeight + return nil } -func (c *HubClient) retrieveBatchAtStateIndex(slStateIndex uint64) (*settlement.ResultRetrieveBatch, error) { +func (c *Client) retrieveBatchAtStateIndex(slStateIndex uint64) (*settlement.ResultRetrieveBatch, error) { b, err := c.settlementKV.Get(keyFromIndex(slStateIndex)) c.logger.Debug("Retrieving batch from settlement layer.", "SL state index", slStateIndex) if err != nil { diff --git a/settlement/local/local_test.go b/settlement/local/local_test.go new file mode 100644 index 000000000..3268fa2c5 --- /dev/null +++ b/settlement/local/local_test.go @@ -0,0 +1,158 @@ +package local_test + +import ( + "encoding/hex" + "os" + "testing" + + "github.com/dymensionxyz/dymint/da" + "github.com/dymensionxyz/dymint/settlement" + "github.com/dymensionxyz/dymint/settlement/local" + "github.com/dymensionxyz/dymint/testutil" + "github.com/libp2p/go-libp2p/core/crypto" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/libs/log" + "github.com/tendermint/tendermint/libs/pubsub" +) + +func TestGetSequencers(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + + proposerKey, _, err := crypto.GenerateEd25519Key(nil) + require.NoError(err) + proposerPubKey := proposerKey.GetPublic() + pubKeybytes, err := proposerPubKey.Raw() + require.NoError(err) + + sllayer := local.Client{} + cfg := settlement.Config{ProposerPubKey: hex.EncodeToString(pubKeybytes)} + err = sllayer.Init(cfg, nil, log.TestingLogger()) + require.NoError(err) + + sequencers, err := sllayer.GetSequencers() + require.NoError(err) + assert.Equal(1, len(sequencers)) + assert.Equal(pubKeybytes, sequencers[0].PublicKey.Bytes()) + + proposer := sllayer.GetProposer() + require.NotNil(proposer) + assert.Equal(pubKeybytes, proposer.PublicKey.Bytes()) +} + +func TestSubmitBatch(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + + logger := log.TestingLogger() + pubsubServer := pubsub.NewServer() + err := pubsubServer.Start() + require.NoError(err) + + sllayer := local.Client{} + err = sllayer.Init(settlement.Config{}, pubsubServer, logger) + require.NoError(err) + _, err = sllayer.GetLatestBatch() + require.Error(err) // no batch should be present + + // Create a batches which will be submitted + propserKey, _, err := crypto.GenerateEd25519Key(nil) + require.NoError(err) + batch1, err := testutil.GenerateBatch(1, 1, propserKey) + require.NoError(err) + batch2, err := testutil.GenerateBatch(2, 2, propserKey) + require.NoError(err) + resultSubmitBatch := &da.ResultSubmitBatch{} + resultSubmitBatch.SubmitMetaData = &da.DASubmitMetaData{} + + // Submit the first batch and check if it was successful + err = sllayer.SubmitBatch(batch1, da.Mock, resultSubmitBatch) + assert.NoError(err) + assert.True(resultSubmitBatch.Code == 0) // success code + + // Check if the batch was submitted + queriedBatch, err := sllayer.GetLatestBatch() + require.NoError(err) + assert.Equal(batch1.EndHeight, queriedBatch.Batch.EndHeight) + + state, err := sllayer.GetHeightState(1) + require.NoError(err) + assert.Equal(queriedBatch.StateIndex, state.State.StateIndex) + + queriedBatch, err = sllayer.GetBatchAtIndex(state.State.StateIndex) + require.NoError(err) + assert.Equal(batch1.EndHeight, queriedBatch.Batch.EndHeight) + + // Submit the 2nd batch and check if it was successful + err = sllayer.SubmitBatch(batch2, da.Mock, resultSubmitBatch) + assert.NoError(err) + assert.True(resultSubmitBatch.Code == 0) // success code + + // Check if the batch was submitted + queriedBatch, err = sllayer.GetLatestBatch() + require.NoError(err) + assert.Equal(batch2.EndHeight, queriedBatch.Batch.EndHeight) + + state, err = sllayer.GetHeightState(2) + require.NoError(err) + assert.Equal(queriedBatch.StateIndex, state.State.StateIndex) + + queriedBatch, err = sllayer.GetBatchAtIndex(state.State.StateIndex) + require.NoError(err) + assert.Equal(batch2.EndHeight, queriedBatch.Batch.EndHeight) + + // TODO: test event emitted +} + +func TestPersistency(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + + logger := log.TestingLogger() + pubsubServer := pubsub.NewServer() + err := pubsubServer.Start() + require.NoError(err) + + proposerKey, _, err := crypto.GenerateEd25519Key(nil) + require.NoError(err) + proposerPubKey := proposerKey.GetPublic() + pubKeybytes, err := proposerPubKey.Raw() + require.NoError(err) + + sllayer := local.Client{} + tmpdir, err := os.MkdirTemp("/tmp", "") + defer os.RemoveAll(tmpdir) // Clean up after the test + require.NoError(err) + + cfg := settlement.Config{KeyringHomeDir: tmpdir, ProposerPubKey: hex.EncodeToString(pubKeybytes)} + err = sllayer.Init(cfg, pubsubServer, logger) + require.NoError(err) + + _, err = sllayer.GetLatestBatch() + assert.Error(err) // no batch should be present + + // Create a batches which will be submitted + batch1, err := testutil.GenerateBatch(1, 1, proposerKey) + require.NoError(err) + resultSubmitBatch := &da.ResultSubmitBatch{} + resultSubmitBatch.SubmitMetaData = &da.DASubmitMetaData{} + + // Submit the first batch and check if it was successful + err = sllayer.SubmitBatch(batch1, da.Mock, resultSubmitBatch) + assert.NoError(err) + assert.True(resultSubmitBatch.Code == 0) // success code + + queriedBatch, err := sllayer.GetLatestBatch() + require.NoError(err) + assert.Equal(batch1.EndHeight, queriedBatch.Batch.EndHeight) + + // Restart the layer and check if the batch is still present + err = sllayer.Stop() + require.NoError(err) + sllayer = local.Client{} + _ = sllayer.Init(cfg, pubsubServer, logger) + queriedBatch, err = sllayer.GetLatestBatch() + require.NoError(err) + assert.Equal(batch1.EndHeight, queriedBatch.Batch.EndHeight) +} diff --git a/settlement/registry/registry.go b/settlement/registry/registry.go index d375b7229..9649f5c5b 100644 --- a/settlement/registry/registry.go +++ b/settlement/registry/registry.go @@ -20,14 +20,14 @@ const ( ) // A central registry for all Settlement Layer Clients -var clients = map[Client]func() settlement.LayerI{ - Local: func() settlement.LayerI { return &local.LayerClient{} }, - Dymension: func() settlement.LayerI { return &dymension.LayerClient{} }, - Grpc: func() settlement.LayerI { return &grpc.LayerClient{} }, +var clients = map[Client]func() settlement.ClientI{ + Local: func() settlement.ClientI { return &local.Client{} }, + Dymension: func() settlement.ClientI { return &dymension.Client{} }, + Grpc: func() settlement.ClientI { return &grpc.Client{} }, } // GetClient returns client identified by name. -func GetClient(client Client) settlement.LayerI { +func GetClient(client Client) settlement.ClientI { f, ok := clients[client] if !ok { return nil diff --git a/settlement/settlement.go b/settlement/settlement.go index dc6c21791..8e6c603a5 100644 --- a/settlement/settlement.go +++ b/settlement/settlement.go @@ -54,44 +54,28 @@ type ResultGetHeightState struct { } // Option is a function that sets a parameter on the settlement layer. -type Option func(LayerI) +type Option func(ClientI) -// LayerI defines generic interface for Settlement layer interaction. -type LayerI interface { +// ClientI defines generic interface for Settlement layer interaction. +type ClientI interface { // Init is called once for the client initialization Init(config Config, pubsub *pubsub.Server, logger types.Logger, options ...Option) error - // Start is called once, after Init. It's implementation should start the client service. Start() error - // Stop is called once, after Start. It should stop the client service. Stop() error - // SubmitBatch tries submitting the batch in an async way to the settlement layer. This should create a transaction which (potentially) // triggers a state transition in the settlement layer. Events are emitted on success or failure. SubmitBatch(batch *types.Batch, daClient da.Client, daResult *da.ResultSubmitBatch) error - - // RetrieveBatch Gets the batch which contains the given height. Empty height returns the latest batch. - RetrieveBatch(stateIndex ...uint64) (*ResultRetrieveBatch, error) + // GetLatestBatch returns the latest batch from the settlement layer. + GetLatestBatch() (*ResultRetrieveBatch, error) + // GetBatchAtIndex returns the batch at the given index. + GetBatchAtIndex(index uint64) (*ResultRetrieveBatch, error) // GetSequencersList returns the list of the sequencers for this chain. - GetSequencersList() []*types.Sequencer - + GetSequencers() ([]*types.Sequencer, error) // GetProposer returns the current proposer for this chain. GetProposer() *types.Sequencer GetHeightState(uint64) (*ResultGetHeightState, error) } - -// HubClient is a helper interface for a more granular interaction with the hub. -// Implementing a new settlement layer client basically requires embedding the base client -// and implementing the helper interfaces. -type HubClient interface { - Start() error - Stop() error - PostBatch(batch *types.Batch, daClient da.Client, daResult *da.ResultSubmitBatch) error - GetLatestBatch(rollappID string) (*ResultRetrieveBatch, error) - GetBatchAtIndex(rollappID string, index uint64) (*ResultRetrieveBatch, error) - GetHeightState(index uint64) (*ResultGetHeightState, error) - GetSequencers(rollappID string) ([]*types.Sequencer, error) -} diff --git a/settlement/settlement_test.go b/settlement/settlement_test.go deleted file mode 100644 index 54108bf1d..000000000 --- a/settlement/settlement_test.go +++ /dev/null @@ -1,181 +0,0 @@ -package settlement_test - -import ( - "testing" - "time" - - "github.com/dymensionxyz/dymint/gerr" - - "github.com/tendermint/tendermint/libs/log" - - "github.com/libp2p/go-libp2p/core/crypto" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/libs/pubsub" - - "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" - "github.com/dymensionxyz/dymint/da" - mocks "github.com/dymensionxyz/dymint/mocks/github.com/dymensionxyz/dymint/settlement" - "github.com/dymensionxyz/dymint/settlement" - "github.com/dymensionxyz/dymint/settlement/registry" - "github.com/dymensionxyz/dymint/testutil" - "github.com/dymensionxyz/dymint/types" - tsmock "github.com/stretchr/testify/mock" -) - -const batchSize = 5 - -func TestLifecycle(t *testing.T) { - var err error - client := registry.GetClient(registry.Local) - require := require.New(t) - - pubsubServer := pubsub.NewServer() - err = pubsubServer.Start() - require.NoError(err) - err = client.Init(settlement.Config{}, pubsubServer, log.TestingLogger()) - require.NoError(err) - - err = client.Start() - require.NoError(err) - - err = client.Stop() - require.NoError(err) -} - -func TestSubmitAndRetrieve(t *testing.T) { - require := require.New(t) - assert := assert.New(t) - - settlementClient := registry.GetClient(registry.Local) - - initClient(t, settlementClient) - - // Get settlement lastest batch and check if there is an error as we haven't written anything yet. - _, err := settlementClient.RetrieveBatch() - assert.ErrorIs(err, gerr.ErrNotFound) - - // Get nonexisting stateIndex from the settlement layer - _, err = settlementClient.RetrieveBatch(uint64(100)) - assert.ErrorIs(err, gerr.ErrNotFound) - - // Create and submit multiple batches - numBatches := 4 - var batch *types.Batch - // iterate batches - for i := 0; i < numBatches; i++ { - startHeight := uint64(i)*batchSize + 1 - // Create the batch - propserKey, _, err := crypto.GenerateEd25519Key(nil) - require.NoError(err) - batch, err = testutil.GenerateBatch(startHeight, uint64(startHeight+batchSize-1), propserKey) - require.NoError(err) - // Submit the batch - daResult := &da.ResultSubmitBatch{ - BaseResult: da.BaseResult{}, - SubmitMetaData: &da.DASubmitMetaData{ - Height: batch.EndHeight, - }, - } - err = settlementClient.SubmitBatch(batch, da.Mock, daResult) - require.NoError(err) - // sleep for 500 ms to make sure batch got accepted by the settlement layer - time.Sleep(500 * time.Millisecond) - } - - // Retrieve the latest batch and make sure it matches latest batch submitted - lastestBatch, err := settlementClient.RetrieveBatch() - require.NoError(err) - assert.Equal(batch.EndHeight, lastestBatch.EndHeight) - - // Retrieve one batch before last - batchResult, err := settlementClient.RetrieveBatch(lastestBatch.StateIndex - 1) - require.NoError(err) - middleOfBatchHeight := uint64(numBatches-1)*(batchSize) - (batchSize / 2) - assert.LessOrEqual(batchResult.StartHeight, middleOfBatchHeight) - assert.GreaterOrEqual(batchResult.EndHeight, middleOfBatchHeight) -} - -func TestGetSequencersEmptyList(t *testing.T) { - var err error - settlementClient := registry.GetClient(registry.Local) - hubClientMock := mocks.NewMockHubClient(t) - hubClientMock.On("GetSequencers", tsmock.Anything, tsmock.Anything).Return(nil, gerr.ErrNotFound) - options := []settlement.Option{ - settlement.WithHubClient(hubClientMock), - } - - pubsubServer := pubsub.NewServer() - err = pubsubServer.Start() - require.NoError(t, err) - err = settlementClient.Init(settlement.Config{}, pubsubServer, log.TestingLogger(), options...) - assert.Error(t, err, "empty sequencer list should return an error") -} - -func TestGetSequencers(t *testing.T) { - hubClientMock := mocks.NewMockHubClient(t) - hubClientMock.On("Start", tsmock.Anything).Return(nil) - hubClientMock.On("Stop", tsmock.Anything).Return(nil) - // Mock a sequencer response by the sequencerByRollapp query - totalSequencers := 5 - sequencers, proposer := generateSequencers(totalSequencers) - hubClientMock.On("GetSequencers", tsmock.Anything, tsmock.Anything).Return(sequencers, nil) - options := []settlement.Option{ - settlement.WithHubClient(hubClientMock), - } - settlementClient := registry.GetClient(registry.Local) - initClient(t, settlementClient, options...) - - sequencersList := settlementClient.GetSequencersList() - - assert.Len(t, sequencersList, len(sequencers)) - assert.Equal(t, settlementClient.GetProposer().PublicKey, proposer.PublicKey) - - err := settlementClient.Stop() - // Wait until the settlement layer stops - <-time.After(1 * time.Second) - assert.NoError(t, err) - - // Validate the amount of inactive sequencers - var inactiveSequencerAmount int - for _, sequencer := range sequencersList { - if sequencer.Status == types.Inactive { - inactiveSequencerAmount += 1 - } - } - assert.Equal(t, inactiveSequencerAmount, totalSequencers-1) -} - -/* -------------------------------------------------------------------------- */ -/* Utils */ -/* -------------------------------------------------------------------------- */ - -func initClient(t *testing.T, settlementlc settlement.LayerI, options ...settlement.Option) { - require := require.New(t) - var err error - - pubsubServer := pubsub.NewServer() - err = pubsubServer.Start() - require.NoError(err) - err = settlementlc.Init(settlement.Config{}, pubsubServer, log.TestingLogger(), options...) - require.NoError(err) - - err = settlementlc.Start() - require.NoError(err) -} - -func generateSequencers(count int) ([]*types.Sequencer, *types.Sequencer) { - sequencers := make([]*types.Sequencer, count) - proposer := &types.Sequencer{ - PublicKey: ed25519.GenPrivKey().PubKey(), - Status: types.Proposer, - } - sequencers[0] = proposer - for i := 1; i < count; i++ { - sequencers[i] = &types.Sequencer{ - PublicKey: ed25519.GenPrivKey().PubKey(), - Status: types.Inactive, - } - } - return sequencers, proposer -} diff --git a/store/badger.go b/store/badger.go index 596b2e3c1..5430f545c 100644 --- a/store/badger.go +++ b/store/badger.go @@ -18,6 +18,11 @@ type BadgerKV struct { db *badger.DB } +// Close implements KVStore. +func (b *BadgerKV) Close() error { + return b.db.Close() +} + // Get returns value for given key, or error. func (b *BadgerKV) Get(key []byte) ([]byte, error) { txn := b.db.NewTransaction(false) diff --git a/store/prefix.go b/store/prefix.go index 585bfdc07..23842dff3 100644 --- a/store/prefix.go +++ b/store/prefix.go @@ -11,6 +11,11 @@ type PrefixKV struct { prefix []byte } +// Close implements KVStore. +func (p *PrefixKV) Close() error { + return p.kv.Close() +} + // NewPrefixKV creates new PrefixKV on top of other KVStore. func NewPrefixKV(kv KV, prefix []byte) *PrefixKV { return &PrefixKV{ diff --git a/store/storeIface.go b/store/storeIface.go index b89561f20..307ccde9c 100644 --- a/store/storeIface.go +++ b/store/storeIface.go @@ -15,6 +15,7 @@ type KV interface { Delete(key []byte) error // Delete deletes a key. NewBatch() KVBatch // NewBatch creates a new batch. PrefixIterator(prefix []byte) KVIterator // PrefixIterator creates iterator to traverse given prefix. + Close() error // Close closes the store. } // KVBatch enables batching of transactions. diff --git a/testutil/block.go b/testutil/block.go index 19abdef82..9f326dd7d 100644 --- a/testutil/block.go +++ b/testutil/block.go @@ -32,7 +32,7 @@ const ( /* -------------------------------------------------------------------------- */ /* utils */ /* -------------------------------------------------------------------------- */ -func GetManagerWithProposerKey(conf config.BlockManagerConfig, proposerKey crypto.PrivKey, settlementlc settlement.LayerI, dalc da.DataAvailabilityLayerClient, genesisHeight, storeInitialHeight, storeLastBlockHeight int64, proxyAppConns proxy.AppConns, mockStore store.Store) (*block.Manager, error) { +func GetManagerWithProposerKey(conf config.BlockManagerConfig, proposerKey crypto.PrivKey, settlementlc settlement.ClientI, dalc da.DataAvailabilityLayerClient, genesisHeight, storeInitialHeight, storeLastBlockHeight int64, proxyAppConns proxy.AppConns, mockStore store.Store) (*block.Manager, error) { genesis := GenerateGenesis(genesisHeight) // Change the LastBlockHeight to avoid calling InitChainSync within the manager // And updating the state according to the genesis. @@ -113,7 +113,7 @@ func GetManagerWithProposerKey(conf config.BlockManagerConfig, proposerKey crypt return manager, nil } -func GetManager(conf config.BlockManagerConfig, settlementlc settlement.LayerI, dalc da.DataAvailabilityLayerClient, genesisHeight, storeInitialHeight, storeLastBlockHeight int64, proxyAppConns proxy.AppConns, mockStore store.Store) (*block.Manager, error) { +func GetManager(conf config.BlockManagerConfig, settlementlc settlement.ClientI, dalc da.DataAvailabilityLayerClient, genesisHeight, storeInitialHeight, storeLastBlockHeight int64, proxyAppConns proxy.AppConns, mockStore store.Store) (*block.Manager, error) { proposerKey, _, err := crypto.GenerateEd25519Key(rand.Reader) if err != nil { return nil, err @@ -132,7 +132,7 @@ func initDALCMock(dalc da.DataAvailabilityLayerClient, pubsubServer *pubsub.Serv _ = dalc.Start() } -func initSettlementLayerMock(settlementlc settlement.LayerI, proposer string, pubsubServer *pubsub.Server, logger log.Logger) error { +func initSettlementLayerMock(settlementlc settlement.ClientI, proposer string, pubsubServer *pubsub.Server, logger log.Logger) error { err := settlementlc.Init(settlement.Config{ProposerPubKey: proposer}, pubsubServer, logger) if err != nil { return err