From f8d86579f3613443dc4d3748e3bae74fc90a92a5 Mon Sep 17 00:00:00 2001 From: edualb <39157101+edualb@users.noreply.github.com> Date: Fri, 28 Jan 2022 15:58:47 +0000 Subject: [PATCH] fix(dot/sync, dot/rpc): implement HighestBlock (#2195) --- dot/node.go | 13 +- dot/rpc/http.go | 4 +- dot/rpc/modules/api.go | 8 +- dot/rpc/modules/mock_sync_api_test.go | 48 ++++++++ dot/rpc/modules/mocks/network_api.go | 14 --- dot/rpc/modules/system.go | 7 +- dot/rpc/modules/system_integration_test.go | 26 ++-- dot/rpc/modules/system_test.go | 66 +++++----- dot/rpc/service_test.go | 2 +- dot/services.go | 74 ++++++----- dot/services_test.go | 20 ++- dot/sync/chain_sync.go | 29 +++++ dot/sync/chain_sync_test.go | 137 +++++++++++++++++++++ dot/sync/mock_chain_sync_test.go | 119 ++++++++++++++++++ dot/sync/syncer.go | 10 ++ dot/sync/syncer_test.go | 62 ++++++++++ 16 files changed, 546 insertions(+), 93 deletions(-) create mode 100644 dot/rpc/modules/mock_sync_api_test.go create mode 100644 dot/sync/mock_chain_sync_test.go diff --git a/dot/node.go b/dot/node.go index cecf0c7719..f80d009d46 100644 --- a/dot/node.go +++ b/dot/node.go @@ -317,7 +317,18 @@ func NewNode(cfg *Config, ks *keystore.GlobalKeystore) (*Node, error) { // check if rpc service is enabled if enabled := cfg.RPC.isRPCEnabled() || cfg.RPC.isWSEnabled(); enabled { var rpcSrvc *rpc.HTTPServer - rpcSrvc, err = createRPCService(cfg, ns, stateSrvc, coreSrvc, networkSrvc, bp, sysSrvc, fg) + cRPCParams := rpcServiceSettings{ + config: cfg, + nodeStorage: ns, + state: stateSrvc, + core: coreSrvc, + network: networkSrvc, + blockProducer: bp, + system: sysSrvc, + blockFinality: fg, + syncer: syncer, + } + rpcSrvc, err = createRPCService(cRPCParams) if err != nil { return nil, fmt.Errorf("failed to create rpc service: %s", err) } diff --git a/dot/rpc/http.go b/dot/rpc/http.go index 0729cd02d0..fb50e79503 100644 --- a/dot/rpc/http.go +++ b/dot/rpc/http.go @@ -41,6 +41,7 @@ type HTTPServerConfig struct { RPCAPI modules.RPCAPI SystemAPI modules.SystemAPI SyncStateAPI modules.SyncStateAPI + SyncAPI modules.SyncAPI NodeStorage *runtime.NodeStorage RPC bool RPCExternal bool @@ -97,7 +98,8 @@ func (h *HTTPServer) RegisterModules(mods []string) { switch mod { case "system": srvc = modules.NewSystemModule(h.serverConfig.NetworkAPI, h.serverConfig.SystemAPI, - h.serverConfig.CoreAPI, h.serverConfig.StorageAPI, h.serverConfig.TransactionQueueAPI, h.serverConfig.BlockAPI) + h.serverConfig.CoreAPI, h.serverConfig.StorageAPI, h.serverConfig.TransactionQueueAPI, + h.serverConfig.BlockAPI, h.serverConfig.SyncAPI) case "author": srvc = modules.NewAuthorModule(h.logger, h.serverConfig.CoreAPI, h.serverConfig.TransactionQueueAPI) case "chain": diff --git a/dot/rpc/modules/api.go b/dot/rpc/modules/api.go index 4d1940bda4..b0f2e4488b 100644 --- a/dot/rpc/modules/api.go +++ b/dot/rpc/modules/api.go @@ -67,7 +67,6 @@ type NetworkAPI interface { Stop() error Start() error IsStopped() bool - HighestBlock() int64 StartingBlock() int64 AddReservedPeers(addrs ...string) error RemoveReservedPeers(addrs ...string) error @@ -153,3 +152,10 @@ type RuntimeStorageAPI interface { type SyncStateAPI interface { GenSyncSpec(raw bool) (*genesis.Genesis, error) } + +//go:generate mockgen -destination=mock_sync_api_test.go -package $GOPACKAGE . SyncAPI + +// SyncAPI is the interface to interact with the sync service +type SyncAPI interface { + HighestBlock() int64 +} diff --git a/dot/rpc/modules/mock_sync_api_test.go b/dot/rpc/modules/mock_sync_api_test.go new file mode 100644 index 0000000000..2442bcfd1b --- /dev/null +++ b/dot/rpc/modules/mock_sync_api_test.go @@ -0,0 +1,48 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/ChainSafe/gossamer/dot/rpc/modules (interfaces: SyncAPI) + +// Package modules is a generated GoMock package. +package modules + +import ( + reflect "reflect" + + gomock "github.com/golang/mock/gomock" +) + +// MockSyncAPI is a mock of SyncAPI interface. +type MockSyncAPI struct { + ctrl *gomock.Controller + recorder *MockSyncAPIMockRecorder +} + +// MockSyncAPIMockRecorder is the mock recorder for MockSyncAPI. +type MockSyncAPIMockRecorder struct { + mock *MockSyncAPI +} + +// NewMockSyncAPI creates a new mock instance. +func NewMockSyncAPI(ctrl *gomock.Controller) *MockSyncAPI { + mock := &MockSyncAPI{ctrl: ctrl} + mock.recorder = &MockSyncAPIMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockSyncAPI) EXPECT() *MockSyncAPIMockRecorder { + return m.recorder +} + +// HighestBlock mocks base method. +func (m *MockSyncAPI) HighestBlock() int64 { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HighestBlock") + ret0, _ := ret[0].(int64) + return ret0 +} + +// HighestBlock indicates an expected call of HighestBlock. +func (mr *MockSyncAPIMockRecorder) HighestBlock() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HighestBlock", reflect.TypeOf((*MockSyncAPI)(nil).HighestBlock)) +} diff --git a/dot/rpc/modules/mocks/network_api.go b/dot/rpc/modules/mocks/network_api.go index d9dd66275a..67962141e3 100644 --- a/dot/rpc/modules/mocks/network_api.go +++ b/dot/rpc/modules/mocks/network_api.go @@ -46,20 +46,6 @@ func (_m *NetworkAPI) Health() common.Health { return r0 } -// HighestBlock provides a mock function with given fields: -func (_m *NetworkAPI) HighestBlock() int64 { - ret := _m.Called() - - var r0 int64 - if rf, ok := ret.Get(0).(func() int64); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(int64) - } - - return r0 -} - // IsStopped provides a mock function with given fields: func (_m *NetworkAPI) IsStopped() bool { ret := _m.Called() diff --git a/dot/rpc/modules/system.go b/dot/rpc/modules/system.go index 23a99f27be..6cb7add7fb 100644 --- a/dot/rpc/modules/system.go +++ b/dot/rpc/modules/system.go @@ -25,6 +25,7 @@ type SystemModule struct { storageAPI StorageAPI txStateAPI TransactionStateAPI blockAPI BlockAPI + syncAPI SyncAPI } // EmptyRequest represents an RPC request with no fields @@ -67,7 +68,8 @@ type SyncStateResponse struct { // NewSystemModule creates a new API instance func NewSystemModule(net NetworkAPI, sys SystemAPI, core CoreAPI, - storage StorageAPI, txAPI TransactionStateAPI, blockAPI BlockAPI) *SystemModule { + storage StorageAPI, txAPI TransactionStateAPI, blockAPI BlockAPI, + syncAPI SyncAPI) *SystemModule { return &SystemModule{ networkAPI: net, systemAPI: sys, @@ -75,6 +77,7 @@ func NewSystemModule(net NetworkAPI, sys SystemAPI, core CoreAPI, storageAPI: storage, txStateAPI: txAPI, blockAPI: blockAPI, + syncAPI: syncAPI, } } @@ -233,7 +236,7 @@ func (sm *SystemModule) SyncState(r *http.Request, req *EmptyRequest, res *SyncS *res = SyncStateResponse{ CurrentBlock: uint32(h.Number.Int64()), - HighestBlock: uint32(sm.networkAPI.HighestBlock()), + HighestBlock: uint32(sm.syncAPI.HighestBlock()), StartingBlock: uint32(sm.networkAPI.StartingBlock()), } return nil diff --git a/dot/rpc/modules/system_integration_test.go b/dot/rpc/modules/system_integration_test.go index daf7ffc90d..f9da374a5c 100644 --- a/dot/rpc/modules/system_integration_test.go +++ b/dot/rpc/modules/system_integration_test.go @@ -101,7 +101,7 @@ func TestSystemModule_Health(t *testing.T) { networkMock := new(mocks.NetworkAPI) networkMock.On("Health").Return(testHealth) - sys := NewSystemModule(networkMock, nil, nil, nil, nil, nil) + sys := NewSystemModule(networkMock, nil, nil, nil, nil, nil, nil) res := &SystemHealthResponse{} err := sys.Health(nil, nil, res) @@ -112,7 +112,7 @@ func TestSystemModule_Health(t *testing.T) { // Test RPC's System.NetworkState() response func TestSystemModule_NetworkState(t *testing.T) { net := newNetworkService(t) - sys := NewSystemModule(net, nil, nil, nil, nil, nil) + sys := NewSystemModule(net, nil, nil, nil, nil, nil, nil) res := &SystemNetworkStateResponse{} err := sys.NetworkState(nil, nil, res) @@ -129,7 +129,7 @@ func TestSystemModule_NetworkState(t *testing.T) { func TestSystemModule_Peers(t *testing.T) { net := newNetworkService(t) net.Stop() - sys := NewSystemModule(net, nil, nil, nil, nil, nil) + sys := NewSystemModule(net, nil, nil, nil, nil, nil, nil) res := &SystemPeersResponse{} err := sys.Peers(nil, nil, res) @@ -142,7 +142,7 @@ func TestSystemModule_Peers(t *testing.T) { func TestSystemModule_NodeRoles(t *testing.T) { net := newNetworkService(t) - sys := NewSystemModule(net, nil, nil, nil, nil, nil) + sys := NewSystemModule(net, nil, nil, nil, nil, nil, nil) expected := []interface{}{"Full"} var res []interface{} @@ -174,7 +174,7 @@ func newMockSystemAPI() *mocks.SystemAPI { } func TestSystemModule_Chain(t *testing.T) { - sys := NewSystemModule(nil, newMockSystemAPI(), nil, nil, nil, nil) + sys := NewSystemModule(nil, newMockSystemAPI(), nil, nil, nil, nil, nil) res := new(string) err := sys.Chain(nil, nil, res) @@ -185,14 +185,14 @@ func TestSystemModule_Chain(t *testing.T) { func TestSystemModule_ChainType(t *testing.T) { api := newMockSystemAPI() - sys := NewSystemModule(nil, api, nil, nil, nil, nil) + sys := NewSystemModule(nil, api, nil, nil, nil, nil, nil) res := new(string) sys.ChainType(nil, nil, res) require.Equal(t, testGenesisData.ChainType, *res) } func TestSystemModule_Name(t *testing.T) { - sys := NewSystemModule(nil, newMockSystemAPI(), nil, nil, nil, nil) + sys := NewSystemModule(nil, newMockSystemAPI(), nil, nil, nil, nil, nil) res := new(string) err := sys.Name(nil, nil, res) @@ -201,7 +201,7 @@ func TestSystemModule_Name(t *testing.T) { } func TestSystemModule_Version(t *testing.T) { - sys := NewSystemModule(nil, newMockSystemAPI(), nil, nil, nil, nil) + sys := NewSystemModule(nil, newMockSystemAPI(), nil, nil, nil, nil, nil) res := new(string) err := sys.Version(nil, nil, res) @@ -210,7 +210,7 @@ func TestSystemModule_Version(t *testing.T) { } func TestSystemModule_Properties(t *testing.T) { - sys := NewSystemModule(nil, newMockSystemAPI(), nil, nil, nil, nil) + sys := NewSystemModule(nil, newMockSystemAPI(), nil, nil, nil, nil, nil) expected := map[string]interface{}(nil) @@ -340,7 +340,7 @@ func setupSystemModule(t *testing.T) *SystemModule { AnyTimes() txQueue := state.NewTransactionState(telemetryMock) - return NewSystemModule(net, nil, core, chain.Storage, txQueue, nil) + return NewSystemModule(net, nil, core, chain.Storage, txQueue, nil, nil) } func newCoreService(t *testing.T, srvc *state.Service) *core.Service { @@ -399,12 +399,16 @@ func TestSyncState(t *testing.T) { blockapiMock.On("GetHeader", fakeCommonHash).Return(fakeHeader, nil).Once() netapiMock := new(mocks.NetworkAPI) - netapiMock.On("HighestBlock").Return(int64(90)) netapiMock.On("StartingBlock").Return(int64(10)) + syncapiCtrl := gomock.NewController(t) + syncapiMock := NewMockSyncAPI(syncapiCtrl) + syncapiMock.EXPECT().HighestBlock().Return(int64(90)) + sysmodule := new(SystemModule) sysmodule.blockAPI = blockapiMock sysmodule.networkAPI = netapiMock + sysmodule.syncAPI = syncapiMock var res SyncStateResponse err := sysmodule.SyncState(nil, nil, &res) diff --git a/dot/rpc/modules/system_test.go b/dot/rpc/modules/system_test.go index 6adfde9706..67a470df32 100644 --- a/dot/rpc/modules/system_test.go +++ b/dot/rpc/modules/system_test.go @@ -13,6 +13,7 @@ import ( "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/transaction" + "github.com/golang/mock/gomock" "github.com/multiformats/go-multiaddr" "github.com/stretchr/testify/assert" @@ -23,7 +24,7 @@ func TestSystemModule_ChainTest(t *testing.T) { mockSystemAPI := new(mocks.SystemAPI) mockSystemAPI.On("ChainName").Return("polkadot", nil) sm := NewSystemModule(new(mocks.NetworkAPI), mockSystemAPI, new(mocks.CoreAPI), - new(mocks.StorageAPI), new(mocks.TransactionStateAPI), new(mocks.BlockAPI)) + new(mocks.StorageAPI), new(mocks.TransactionStateAPI), new(mocks.BlockAPI), nil) req := &EmptyRequest{} var res string @@ -37,7 +38,7 @@ func TestSystemModule_NameTest(t *testing.T) { mockSystemAPI := new(mocks.SystemAPI) mockSystemAPI.On("SystemName").Return("kusama", nil) sm := NewSystemModule(new(mocks.NetworkAPI), mockSystemAPI, new(mocks.CoreAPI), - new(mocks.StorageAPI), new(mocks.TransactionStateAPI), new(mocks.BlockAPI)) + new(mocks.StorageAPI), new(mocks.TransactionStateAPI), new(mocks.BlockAPI), nil) req := &EmptyRequest{} var res string @@ -51,7 +52,7 @@ func TestSystemModule_ChainTypeTest(t *testing.T) { mockSystemAPI := new(mocks.SystemAPI) mockSystemAPI.On("ChainType").Return("testChainType", nil) sm := NewSystemModule(new(mocks.NetworkAPI), mockSystemAPI, new(mocks.CoreAPI), - new(mocks.StorageAPI), new(mocks.TransactionStateAPI), new(mocks.BlockAPI)) + new(mocks.StorageAPI), new(mocks.TransactionStateAPI), new(mocks.BlockAPI), nil) req := &EmptyRequest{} var res string @@ -66,7 +67,7 @@ func TestSystemModule_PropertiesTest(t *testing.T) { mockSystemAPI := new(mocks.SystemAPI) mockSystemAPI.On("Properties").Return(emptyMap) sm := NewSystemModule(new(mocks.NetworkAPI), mockSystemAPI, new(mocks.CoreAPI), - new(mocks.StorageAPI), new(mocks.TransactionStateAPI), new(mocks.BlockAPI)) + new(mocks.StorageAPI), new(mocks.TransactionStateAPI), new(mocks.BlockAPI), nil) req := &EmptyRequest{} var resMap interface{} @@ -79,7 +80,7 @@ func TestSystemModule_SystemVersionTest(t *testing.T) { mockSystemAPI := new(mocks.SystemAPI) mockSystemAPI.On("SystemVersion").Return("1.2.1", nil) sm := NewSystemModule(new(mocks.NetworkAPI), mockSystemAPI, new(mocks.CoreAPI), - new(mocks.StorageAPI), new(mocks.TransactionStateAPI), new(mocks.BlockAPI)) + new(mocks.StorageAPI), new(mocks.TransactionStateAPI), new(mocks.BlockAPI), nil) req := &EmptyRequest{} var res string @@ -93,7 +94,7 @@ func TestSystemModule_HealthTest(t *testing.T) { mockNetworkAPI := new(mocks.NetworkAPI) mockNetworkAPI.On("Health").Return(common.Health{}, nil) sm := NewSystemModule(mockNetworkAPI, new(mocks.SystemAPI), new(mocks.CoreAPI), - new(mocks.StorageAPI), new(mocks.TransactionStateAPI), new(mocks.BlockAPI)) + new(mocks.StorageAPI), new(mocks.TransactionStateAPI), new(mocks.BlockAPI), nil) req := &EmptyRequest{} var sysHealthRes SystemHealthResponse @@ -106,7 +107,7 @@ func TestSystemModule_NetworkStateTest(t *testing.T) { mockNetworkAPI := new(mocks.NetworkAPI) mockNetworkAPI.On("NetworkState").Return(common.NetworkState{}, nil) sm := NewSystemModule(mockNetworkAPI, new(mocks.SystemAPI), new(mocks.CoreAPI), - new(mocks.StorageAPI), new(mocks.TransactionStateAPI), new(mocks.BlockAPI)) + new(mocks.StorageAPI), new(mocks.TransactionStateAPI), new(mocks.BlockAPI), nil) req := &EmptyRequest{} var networkStateRes SystemNetworkStateResponse @@ -119,7 +120,7 @@ func TestSystemModule_PeersTest(t *testing.T) { mockNetworkAPI := new(mocks.NetworkAPI) mockNetworkAPI.On("Peers").Return([]common.PeerInfo{}, nil) sm := NewSystemModule(mockNetworkAPI, new(mocks.SystemAPI), new(mocks.CoreAPI), - new(mocks.StorageAPI), new(mocks.TransactionStateAPI), new(mocks.BlockAPI)) + new(mocks.StorageAPI), new(mocks.TransactionStateAPI), new(mocks.BlockAPI), nil) req := &EmptyRequest{} var sysPeerRes SystemPeersResponse @@ -154,7 +155,7 @@ func TestSystemModule_NodeRolesTest(t *testing.T) { }{ { name: "Full", - sysModule: NewSystemModule(mockNetworkAPI1, nil, nil, nil, nil, nil), + sysModule: NewSystemModule(mockNetworkAPI1, nil, nil, nil, nil, nil, nil), args: args{ req: &EmptyRequest{}, }, @@ -162,7 +163,7 @@ func TestSystemModule_NodeRolesTest(t *testing.T) { }, { name: "LightClient", - sysModule: NewSystemModule(mockNetworkAPI2, nil, nil, nil, nil, nil), + sysModule: NewSystemModule(mockNetworkAPI2, nil, nil, nil, nil, nil, nil), args: args{ req: &EmptyRequest{}, }, @@ -170,7 +171,7 @@ func TestSystemModule_NodeRolesTest(t *testing.T) { }, { name: "Authority", - sysModule: NewSystemModule(mockNetworkAPI3, nil, nil, nil, nil, nil), + sysModule: NewSystemModule(mockNetworkAPI3, nil, nil, nil, nil, nil, nil), args: args{ req: &EmptyRequest{}, }, @@ -178,7 +179,7 @@ func TestSystemModule_NodeRolesTest(t *testing.T) { }, { name: "UnknownRole", - sysModule: NewSystemModule(mockNetworkAPI4, nil, nil, nil, nil, nil), + sysModule: NewSystemModule(mockNetworkAPI4, nil, nil, nil, nil, nil, nil), args: args{ req: &EmptyRequest{}, }, @@ -246,13 +247,13 @@ func TestSystemModule_AccountNextIndex(t *testing.T) { }{ { name: "Nil Request", - sysModule: NewSystemModule(nil, nil, mockCoreAPI, mockStorageAPI, mockTxStateAPI, nil), + sysModule: NewSystemModule(nil, nil, mockCoreAPI, mockStorageAPI, mockTxStateAPI, nil, nil), args: args{}, expErr: errors.New("account address must be valid"), }, { name: "Found", - sysModule: NewSystemModule(nil, nil, mockCoreAPI, mockStorageAPI, mockTxStateAPI, nil), + sysModule: NewSystemModule(nil, nil, mockCoreAPI, mockStorageAPI, mockTxStateAPI, nil, nil), args: args{ req: &StringRequest{String: "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"}, }, @@ -260,7 +261,7 @@ func TestSystemModule_AccountNextIndex(t *testing.T) { }, { name: "Not found", - sysModule: NewSystemModule(nil, nil, mockCoreAPI, mockStorageAPI, mockTxStateAPI, nil), + sysModule: NewSystemModule(nil, nil, mockCoreAPI, mockStorageAPI, mockTxStateAPI, nil, nil), args: args{ req: &StringRequest{String: "5FrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"}, }, @@ -268,7 +269,7 @@ func TestSystemModule_AccountNextIndex(t *testing.T) { }, { name: "GetMetadata Err", - sysModule: NewSystemModule(nil, nil, mockCoreAPIErr, mockStorageAPI, mockTxStateAPI, nil), + sysModule: NewSystemModule(nil, nil, mockCoreAPIErr, mockStorageAPI, mockTxStateAPI, nil, nil), args: args{ req: &StringRequest{String: "5FrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"}, }, @@ -276,7 +277,7 @@ func TestSystemModule_AccountNextIndex(t *testing.T) { }, { name: "Magic Number Mismatch", - sysModule: NewSystemModule(nil, nil, mockCoreAPIMagicNumMismatch, mockStorageAPI, mockTxStateAPI, nil), + sysModule: NewSystemModule(nil, nil, mockCoreAPIMagicNumMismatch, mockStorageAPI, mockTxStateAPI, nil, nil), args: args{ req: &StringRequest{String: "5FrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"}, }, @@ -284,7 +285,7 @@ func TestSystemModule_AccountNextIndex(t *testing.T) { }, { name: "GetStorage Err", - sysModule: NewSystemModule(nil, nil, mockCoreAPI, mockStorageAPIErr, mockTxStateAPI, nil), + sysModule: NewSystemModule(nil, nil, mockCoreAPI, mockStorageAPIErr, mockTxStateAPI, nil, nil), args: args{ req: &StringRequest{String: "5FrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"}, }, @@ -317,9 +318,12 @@ func TestSystemModule_SyncState(t *testing.T) { mockBlockAPIErr.On("GetHeader", hash).Return(nil, errors.New("GetHeader Err")) mockNetworkAPI := new(mocks.NetworkAPI) - mockNetworkAPI.On("HighestBlock").Return(int64(21)) mockNetworkAPI.On("StartingBlock").Return(int64(23)) + ctrlSyncAPI := gomock.NewController(t) + mockSyncAPI := NewMockSyncAPI(ctrlSyncAPI) + mockSyncAPI.EXPECT().HighestBlock().Return(int64(21)) + type args struct { r *http.Request req *EmptyRequest @@ -333,7 +337,7 @@ func TestSystemModule_SyncState(t *testing.T) { }{ { name: "OK", - sysModule: NewSystemModule(mockNetworkAPI, nil, nil, nil, nil, mockBlockAPI), + sysModule: NewSystemModule(mockNetworkAPI, nil, nil, nil, nil, mockBlockAPI, mockSyncAPI), args: args{ req: &EmptyRequest{}, }, @@ -345,7 +349,7 @@ func TestSystemModule_SyncState(t *testing.T) { }, { name: "Err", - sysModule: NewSystemModule(mockNetworkAPI, nil, nil, nil, nil, mockBlockAPIErr), + sysModule: NewSystemModule(mockNetworkAPI, nil, nil, nil, nil, mockBlockAPIErr, nil), args: args{ req: &EmptyRequest{}, }, @@ -396,7 +400,7 @@ func TestSystemModule_LocalListenAddresses(t *testing.T) { }{ { name: "OK", - sysModule: NewSystemModule(mockNetworkAPI, nil, nil, nil, nil, nil), + sysModule: NewSystemModule(mockNetworkAPI, nil, nil, nil, nil, nil, nil), args: args{ req: &EmptyRequest{}, }, @@ -404,7 +408,7 @@ func TestSystemModule_LocalListenAddresses(t *testing.T) { }, { name: "Empty multiaddress list", - sysModule: NewSystemModule(mockNetworkAPIEmpty, nil, nil, nil, nil, nil), + sysModule: NewSystemModule(mockNetworkAPIEmpty, nil, nil, nil, nil, nil, nil), args: args{ req: &EmptyRequest{}, }, @@ -456,7 +460,7 @@ func TestSystemModule_LocalPeerId(t *testing.T) { }{ { name: "OK", - sysModule: NewSystemModule(mockNetworkAPI, nil, nil, nil, nil, nil), + sysModule: NewSystemModule(mockNetworkAPI, nil, nil, nil, nil, nil, nil), args: args{ req: &EmptyRequest{}, }, @@ -464,7 +468,7 @@ func TestSystemModule_LocalPeerId(t *testing.T) { }, { name: "Empty peerId", - sysModule: NewSystemModule(mockNetworkAPIEmpty, nil, nil, nil, nil, nil), + sysModule: NewSystemModule(mockNetworkAPIEmpty, nil, nil, nil, nil, nil, nil), args: args{ req: &EmptyRequest{}, }, @@ -506,7 +510,7 @@ func TestSystemModule_AddReservedPeer(t *testing.T) { }{ { name: "OK", - sysModule: NewSystemModule(mockNetworkAPI, nil, nil, nil, nil, nil), + sysModule: NewSystemModule(mockNetworkAPI, nil, nil, nil, nil, nil, nil), args: args{ req: &StringRequest{"jimbo"}, }, @@ -514,7 +518,7 @@ func TestSystemModule_AddReservedPeer(t *testing.T) { }, { name: "AddReservedPeer Error", - sysModule: NewSystemModule(mockNetworkAPIErr, nil, nil, nil, nil, nil), + sysModule: NewSystemModule(mockNetworkAPIErr, nil, nil, nil, nil, nil, nil), args: args{ req: &StringRequest{"jimbo"}, }, @@ -522,7 +526,7 @@ func TestSystemModule_AddReservedPeer(t *testing.T) { }, { name: "Empty StringRequest Error", - sysModule: NewSystemModule(mockNetworkAPI, nil, nil, nil, nil, nil), + sysModule: NewSystemModule(mockNetworkAPI, nil, nil, nil, nil, nil, nil), args: args{ req: &StringRequest{""}, }, @@ -564,7 +568,7 @@ func TestSystemModule_RemoveReservedPeer(t *testing.T) { }{ { name: "OK", - sysModule: NewSystemModule(mockNetworkAPI, nil, nil, nil, nil, nil), + sysModule: NewSystemModule(mockNetworkAPI, nil, nil, nil, nil, nil, nil), args: args{ req: &StringRequest{"jimbo"}, }, @@ -572,7 +576,7 @@ func TestSystemModule_RemoveReservedPeer(t *testing.T) { }, { name: "RemoveReservedPeer Error", - sysModule: NewSystemModule(mockNetworkAPIErr, nil, nil, nil, nil, nil), + sysModule: NewSystemModule(mockNetworkAPIErr, nil, nil, nil, nil, nil, nil), args: args{ req: &StringRequest{"jimbo"}, }, @@ -580,7 +584,7 @@ func TestSystemModule_RemoveReservedPeer(t *testing.T) { }, { name: "Empty StringRequest Error", - sysModule: NewSystemModule(mockNetworkAPI, nil, nil, nil, nil, nil), + sysModule: NewSystemModule(mockNetworkAPI, nil, nil, nil, nil, nil, nil), args: args{ req: &StringRequest{""}, }, diff --git a/dot/rpc/service_test.go b/dot/rpc/service_test.go index be27181b20..cf4a5bee90 100644 --- a/dot/rpc/service_test.go +++ b/dot/rpc/service_test.go @@ -28,7 +28,7 @@ func TestService_Methods(t *testing.T) { qtyAuthorMethods := 8 rpcService := NewService() - sysMod := modules.NewSystemModule(nil, nil, nil, nil, nil, nil) + sysMod := modules.NewSystemModule(nil, nil, nil, nil, nil, nil, nil) rpcService.BuildMethodNames(sysMod, "system") m := rpcService.Methods() require.Equal(t, qtySystemMethods, len(m)) // check to confirm quantity for methods is correct diff --git a/dot/services.go b/dot/services.go index 1afd937f76..9001c659a3 100644 --- a/dot/services.go +++ b/dot/services.go @@ -36,6 +36,18 @@ import ( "github.com/ChainSafe/gossamer/lib/utils" ) +type rpcServiceSettings struct { + config *Config + nodeStorage *runtime.NodeStorage + state *state.Service + core *core.Service + network *network.Service + blockProducer modules.BlockProducerAPI + system *system.Service + blockFinality *grandpa.Service + syncer *sync.Service +} + func newInMemoryDB(path string) (chaindb.Database, error) { return utils.SetupDatabase(filepath.Join(path, "local_storage"), true) } @@ -304,51 +316,55 @@ func createNetworkService(cfg *Config, stateSrvc *state.Service, // RPC Service // createRPCService creates the RPC service from the provided core configuration -func createRPCService(cfg *Config, ns *runtime.NodeStorage, stateSrvc *state.Service, - coreSrvc *core.Service, networkSrvc *network.Service, bp modules.BlockProducerAPI, - sysSrvc *system.Service, finSrvc *grandpa.Service) (*rpc.HTTPServer, error) { +func createRPCService(params rpcServiceSettings) (*rpc.HTTPServer, error) { logger.Infof( "creating rpc service with host %s, external=%t, port %d, modules %s, ws=%t, ws port %d and ws external=%t", - cfg.RPC.Host, cfg.RPC.External, cfg.RPC.Port, strings.Join(cfg.RPC.Modules, ","), cfg.RPC.WS, - cfg.RPC.WSPort, cfg.RPC.WSExternal, + params.config.RPC.Host, + params.config.RPC.External, + params.config.RPC.Port, + strings.Join(params.config.RPC.Modules, ","), + params.config.RPC.WS, + params.config.RPC.WSPort, + params.config.RPC.WSExternal, ) rpcService := rpc.NewService() - genesisData, err := stateSrvc.Base.LoadGenesisData() + genesisData, err := params.state.Base.LoadGenesisData() if err != nil { return nil, fmt.Errorf("failed to load genesis data: %s", err) } - syncStateSrvc, err := modules.NewStateSync(genesisData, stateSrvc.Storage) + syncStateSrvc, err := modules.NewStateSync(genesisData, params.state.Storage) if err != nil { return nil, fmt.Errorf("failed to create sync state service: %s", err) } rpcConfig := &rpc.HTTPServerConfig{ - LogLvl: cfg.Log.RPCLvl, - BlockAPI: stateSrvc.Block, - StorageAPI: stateSrvc.Storage, - NetworkAPI: networkSrvc, - CoreAPI: coreSrvc, - NodeStorage: ns, - BlockProducerAPI: bp, - BlockFinalityAPI: finSrvc, - TransactionQueueAPI: stateSrvc.Transaction, + LogLvl: params.config.Log.RPCLvl, + BlockAPI: params.state.Block, + StorageAPI: params.state.Storage, + NetworkAPI: params.network, + CoreAPI: params.core, + NodeStorage: params.nodeStorage, + BlockProducerAPI: params.blockProducer, + BlockFinalityAPI: params.blockFinality, + TransactionQueueAPI: params.state.Transaction, RPCAPI: rpcService, SyncStateAPI: syncStateSrvc, - SystemAPI: sysSrvc, - RPC: cfg.RPC.Enabled, - RPCExternal: cfg.RPC.External, - RPCUnsafe: cfg.RPC.Unsafe, - RPCUnsafeExternal: cfg.RPC.UnsafeExternal, - Host: cfg.RPC.Host, - RPCPort: cfg.RPC.Port, - WS: cfg.RPC.WS, - WSExternal: cfg.RPC.WSExternal, - WSUnsafe: cfg.RPC.WSUnsafe, - WSUnsafeExternal: cfg.RPC.WSUnsafeExternal, - WSPort: cfg.RPC.WSPort, - Modules: cfg.RPC.Modules, + SyncAPI: params.syncer, + SystemAPI: params.system, + RPC: params.config.RPC.Enabled, + RPCExternal: params.config.RPC.External, + RPCUnsafe: params.config.RPC.Unsafe, + RPCUnsafeExternal: params.config.RPC.UnsafeExternal, + Host: params.config.RPC.Host, + RPCPort: params.config.RPC.Port, + WS: params.config.RPC.WS, + WSExternal: params.config.RPC.WSExternal, + WSUnsafe: params.config.RPC.WSUnsafe, + WSUnsafeExternal: params.config.RPC.WSUnsafeExternal, + WSPort: params.config.RPC.WSPort, + Modules: params.config.RPC.Modules, } return rpc.NewHTTPServer(rpcConfig), nil diff --git a/dot/services_test.go b/dot/services_test.go index 2e844eefbe..c5f0d220d8 100644 --- a/dot/services_test.go +++ b/dot/services_test.go @@ -202,7 +202,15 @@ func TestCreateRPCService(t *testing.T) { sysSrvc, err := createSystemService(&cfg.System, stateSrvc) require.NoError(t, err) - rpcSrvc, err := createRPCService(cfg, ns, stateSrvc, coreSrvc, networkSrvc, nil, sysSrvc, nil) + paramsRPC := rpcServiceSettings{ + config: cfg, + nodeStorage: ns, + state: stateSrvc, + core: coreSrvc, + network: networkSrvc, + system: sysSrvc, + } + rpcSrvc, err := createRPCService(paramsRPC) require.NoError(t, err) require.NotNil(t, rpcSrvc) } @@ -357,7 +365,15 @@ func TestNewWebSocketServer(t *testing.T) { sysSrvc, err := createSystemService(&cfg.System, stateSrvc) require.NoError(t, err) - rpcSrvc, err := createRPCService(cfg, ns, stateSrvc, coreSrvc, networkSrvc, nil, sysSrvc, nil) + paramsRPC := rpcServiceSettings{ + config: cfg, + nodeStorage: ns, + state: stateSrvc, + core: coreSrvc, + network: networkSrvc, + system: sysSrvc, + } + rpcSrvc, err := createRPCService(paramsRPC) require.NoError(t, err) err = rpcSrvc.Start() require.Nil(t, err) diff --git a/dot/sync/chain_sync.go b/dot/sync/chain_sync.go index 6ee86adc8c..ba311caa2e 100644 --- a/dot/sync/chain_sync.go +++ b/dot/sync/chain_sync.go @@ -79,6 +79,8 @@ type workHandler interface { handleTick() ([]*worker, error) } +//go:generate mockgen -destination=mock_chain_sync_test.go -package $GOPACKAGE . ChainSync + // ChainSync contains the methods used by the high-level service into the `chainSync` module type ChainSync interface { start() @@ -92,6 +94,9 @@ type ChainSync interface { // syncState returns the current syncing state syncState() chainSyncState + + // getHighestBlock returns the highest block or an error + getHighestBlock() (int64, error) } type chainSync struct { @@ -933,6 +938,30 @@ func (cs *chainSync) validateJustification(bd *types.BlockData) error { return nil } +func (cs *chainSync) getHighestBlock() (int64, error) { + cs.RLock() + defer cs.RUnlock() + + if len(cs.peerState) == 0 { + return 0, errNoPeers + } + + highestBlock := big.NewInt(-1) + + for _, ps := range cs.peerState { + if ps.number == nil || ps.number.Cmp(highestBlock) < 0 { + continue + } + highestBlock = ps.number + } + + if highestBlock.Cmp(big.NewInt(-1)) == 0 { + return 0, errNilBlockData + } + + return highestBlock.Int64(), nil +} + func workerToRequests(w *worker) ([]*network.BlockRequestMessage, error) { // worker must specify a start number // empty start hash is ok (eg. in the case of bootstrap, start hash is unknown) diff --git a/dot/sync/chain_sync_test.go b/dot/sync/chain_sync_test.go index 8b78ddf352..e0a26935ca 100644 --- a/dot/sync/chain_sync_test.go +++ b/dot/sync/chain_sync_test.go @@ -827,3 +827,140 @@ func TestChainSync_determineSyncPeers(t *testing.T) { require.Equal(t, 1, len(peers)) require.Equal(t, []peer.ID{testPeerB}, peers) } + +func TestChainSync_highestBlock(t *testing.T) { + type input struct { + peerState map[peer.ID]*peerState + } + type output struct { + highestBlock int64 + err error + } + type test struct { + name string + in input + out output + } + tests := []test{ + { + name: "when has an empty map should return 0, errNoPeers", + in: input{ + peerState: map[peer.ID]*peerState{}, + }, + out: output{ + highestBlock: 0, + err: errNoPeers, + }, + }, + { + name: "when has a nil map should return 0, errNoPeers", + in: input{ + peerState: nil, + }, + out: output{ + highestBlock: 0, + err: errNoPeers, + }, + }, + { + name: "when has only one peer with number 90 should return 90, nil", + in: input{ + peerState: map[peer.ID]*peerState{ + "idtest": {number: big.NewInt(90)}, + }, + }, + out: output{ + highestBlock: 90, + err: nil, + }, + }, + { + name: "when has only one peer with number nil should return 0, errNilBlockData", + in: input{ + peerState: map[peer.ID]*peerState{ + "idtest": {number: nil}, + }, + }, + out: output{ + highestBlock: 0, + err: errNilBlockData, + }, + }, + { + name: "when has two peers (p1, p2) with p1.number 90 and p2.number 190 should return 190, nil", + in: input{ + peerState: map[peer.ID]*peerState{ + "idtest#1": {number: big.NewInt(90)}, + "idtest#2": {number: big.NewInt(190)}, + }, + }, + out: output{ + highestBlock: 190, + err: nil, + }, + }, + { + name: "when has two peers (p1, p2) with p1.number 190 and p2.number 90 should return 190, nil", + in: input{ + peerState: map[peer.ID]*peerState{ + "idtest#1": {number: big.NewInt(190)}, + "idtest#2": {number: big.NewInt(90)}, + }, + }, + out: output{ + highestBlock: 190, + err: nil, + }, + }, + { + name: "when has two peers (p1, p2) with p1.number nil and p2.number 90 should return 90, nil", + in: input{ + peerState: map[peer.ID]*peerState{ + "idtest#1": {number: nil}, + "idtest#2": {number: big.NewInt(90)}, + }, + }, + out: output{ + highestBlock: 90, + err: nil, + }, + }, + { + name: "when has two peers (p1, p2) with p1.number 90 and p2.number nil should return 90, nil", + in: input{ + peerState: map[peer.ID]*peerState{ + "idtest#1": {number: big.NewInt(90)}, + "idtest#2": {number: nil}, + }, + }, + out: output{ + highestBlock: 90, + err: nil, + }, + }, + { + name: "when has two peers (p1, p2) with p1.number nil and p2.number nil should return 0, errNilBlockData", + in: input{ + peerState: map[peer.ID]*peerState{ + "idtest#1": {number: nil}, + "idtest#2": {number: nil}, + }, + }, + out: output{ + highestBlock: 0, + err: errNilBlockData, + }, + }, + } + + for _, ts := range tests { + t.Run(ts.name, func(t *testing.T) { + cs, _ := newTestChainSync(t) + cs.peerState = ts.in.peerState + + highestBlock, err := cs.getHighestBlock() + require.ErrorIs(t, err, ts.out.err) + require.Equal(t, highestBlock, ts.out.highestBlock) + }) + } +} diff --git a/dot/sync/mock_chain_sync_test.go b/dot/sync/mock_chain_sync_test.go new file mode 100644 index 0000000000..2673bafa89 --- /dev/null +++ b/dot/sync/mock_chain_sync_test.go @@ -0,0 +1,119 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/ChainSafe/gossamer/dot/sync (interfaces: ChainSync) + +// Package sync is a generated GoMock package. +package sync + +import ( + big "math/big" + reflect "reflect" + + types "github.com/ChainSafe/gossamer/dot/types" + common "github.com/ChainSafe/gossamer/lib/common" + gomock "github.com/golang/mock/gomock" + peer "github.com/libp2p/go-libp2p-core/peer" +) + +// MockChainSync is a mock of ChainSync interface. +type MockChainSync struct { + ctrl *gomock.Controller + recorder *MockChainSyncMockRecorder +} + +// MockChainSyncMockRecorder is the mock recorder for MockChainSync. +type MockChainSyncMockRecorder struct { + mock *MockChainSync +} + +// NewMockChainSync creates a new mock instance. +func NewMockChainSync(ctrl *gomock.Controller) *MockChainSync { + mock := &MockChainSync{ctrl: ctrl} + mock.recorder = &MockChainSyncMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockChainSync) EXPECT() *MockChainSyncMockRecorder { + return m.recorder +} + +// getHighestBlock mocks base method. +func (m *MockChainSync) getHighestBlock() (int64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "getHighestBlock") + ret0, _ := ret[0].(int64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// getHighestBlock indicates an expected call of getHighestBlock. +func (mr *MockChainSyncMockRecorder) getHighestBlock() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "getHighestBlock", reflect.TypeOf((*MockChainSync)(nil).getHighestBlock)) +} + +// setBlockAnnounce mocks base method. +func (m *MockChainSync) setBlockAnnounce(arg0 peer.ID, arg1 *types.Header) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "setBlockAnnounce", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// setBlockAnnounce indicates an expected call of setBlockAnnounce. +func (mr *MockChainSyncMockRecorder) setBlockAnnounce(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "setBlockAnnounce", reflect.TypeOf((*MockChainSync)(nil).setBlockAnnounce), arg0, arg1) +} + +// setPeerHead mocks base method. +func (m *MockChainSync) setPeerHead(arg0 peer.ID, arg1 common.Hash, arg2 *big.Int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "setPeerHead", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// setPeerHead indicates an expected call of setPeerHead. +func (mr *MockChainSyncMockRecorder) setPeerHead(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "setPeerHead", reflect.TypeOf((*MockChainSync)(nil).setPeerHead), arg0, arg1, arg2) +} + +// start mocks base method. +func (m *MockChainSync) start() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "start") +} + +// start indicates an expected call of start. +func (mr *MockChainSyncMockRecorder) start() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "start", reflect.TypeOf((*MockChainSync)(nil).start)) +} + +// stop mocks base method. +func (m *MockChainSync) stop() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "stop") +} + +// stop indicates an expected call of stop. +func (mr *MockChainSyncMockRecorder) stop() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "stop", reflect.TypeOf((*MockChainSync)(nil).stop)) +} + +// syncState mocks base method. +func (m *MockChainSync) syncState() chainSyncState { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "syncState") + ret0, _ := ret[0].(chainSyncState) + return ret0 +} + +// syncState indicates an expected call of syncState. +func (mr *MockChainSyncMockRecorder) syncState() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "syncState", reflect.TypeOf((*MockChainSync)(nil).syncState)) +} diff --git a/dot/sync/syncer.go b/dot/sync/syncer.go index fbff7a498a..781af6b1bc 100644 --- a/dot/sync/syncer.go +++ b/dot/sync/syncer.go @@ -136,6 +136,16 @@ func (s *Service) IsSynced() bool { return s.chainSync.syncState() == tip } +// HighestBlock gets the highest known block number +func (s *Service) HighestBlock() int64 { + highestBlock, err := s.chainSync.getHighestBlock() + if err != nil { + logger.Warnf("failed to get the highest block: %s", err) + return 0 + } + return highestBlock +} + func reverseBlockData(data []*types.BlockData) { for i, j := 0, len(data)-1; i < j; i, j = i+1, j-1 { data[i], data[j] = data[j], data[i] diff --git a/dot/sync/syncer_test.go b/dot/sync/syncer_test.go index 44d64d481d..b29e35387e 100644 --- a/dot/sync/syncer_test.go +++ b/dot/sync/syncer_test.go @@ -4,6 +4,7 @@ package sync import ( + "errors" "math/big" "os" "path/filepath" @@ -167,3 +168,64 @@ func newTestGenesisWithTrieAndHeader(t *testing.T) (*genesis.Genesis, *trie.Trie require.NoError(t, err) return gen, genTrie, genesisHeader } + +func TestHighestBlock(t *testing.T) { + type input struct { + highestBlock int64 + err error + } + type output struct { + highestBlock int64 + } + type test struct { + name string + in input + out output + } + tests := []test{ + { + name: "when *chainSync.getHighestBlock() returns 0, error should return 0", + in: input{ + highestBlock: 0, + err: errors.New("fake error"), + }, + out: output{ + highestBlock: 0, + }, + }, + { + name: "when *chainSync.getHighestBlock() returns 0, nil should return 0", + in: input{ + highestBlock: 0, + err: nil, + }, + out: output{ + highestBlock: 0, + }, + }, + { + name: "when *chainSync.getHighestBlock() returns 50, nil should return 50", + in: input{ + highestBlock: 50, + err: nil, + }, + out: output{ + highestBlock: 50, + }, + }, + } + for _, ts := range tests { + t.Run(ts.name, func(t *testing.T) { + s := newTestSyncer(t) + + ctrl := gomock.NewController(t) + chainSync := NewMockChainSync(ctrl) + chainSync.EXPECT().getHighestBlock().Return(ts.in.highestBlock, ts.in.err) + + s.chainSync = chainSync + + result := s.HighestBlock() + require.Equal(t, result, ts.out.highestBlock) + }) + } +}