diff --git a/dot/network/service.go b/dot/network/service.go index 28e05a6c3e..96d7bc8c32 100644 --- a/dot/network/service.go +++ b/dot/network/service.go @@ -691,3 +691,13 @@ func (s *Service) Peers() []common.PeerInfo { func (s *Service) NodeRoles() byte { return s.cfg.Roles } + +// HighestBlock returns the highest known block number +func (s *Service) HighestBlock() int64 { + return s.syncQueue.goal +} + +// StartingBlock return the starting block number that's currently being synced +func (s *Service) StartingBlock() int64 { + return s.syncQueue.currStart +} diff --git a/dot/rpc/http.go b/dot/rpc/http.go index 3cac6e803d..3a337f39fb 100644 --- a/dot/rpc/http.go +++ b/dot/rpc/http.go @@ -95,7 +95,7 @@ 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.CoreAPI, h.serverConfig.StorageAPI, h.serverConfig.TransactionQueueAPI, h.serverConfig.BlockAPI) case "author": srvc = modules.NewAuthorModule(h.logger, h.serverConfig.CoreAPI, h.serverConfig.RuntimeAPI, h.serverConfig.TransactionQueueAPI) case "chain": diff --git a/dot/rpc/modules/api.go b/dot/rpc/modules/api.go index 6a95bb22e4..636707eca1 100644 --- a/dot/rpc/modules/api.go +++ b/dot/rpc/modules/api.go @@ -49,6 +49,8 @@ type NetworkAPI interface { Stop() error Start() error IsStopped() bool + HighestBlock() int64 + StartingBlock() int64 } // BlockProducerAPI is the interface for BlockProducer methods diff --git a/dot/rpc/modules/mocks/network_api.go b/dot/rpc/modules/mocks/network_api.go index dd9fa4fa40..7802cdf6dd 100644 --- a/dot/rpc/modules/mocks/network_api.go +++ b/dot/rpc/modules/mocks/network_api.go @@ -26,6 +26,20 @@ func (_m *MockNetworkAPI) Health() common.Health { return r0 } +// HighestBlock provides a mock function with given fields: +func (_m *MockNetworkAPI) 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 *MockNetworkAPI) IsStopped() bool { ret := _m.Called() @@ -98,6 +112,20 @@ func (_m *MockNetworkAPI) Start() error { return r0 } +// StartingBlock provides a mock function with given fields: +func (_m *MockNetworkAPI) StartingBlock() 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 +} + // Stop provides a mock function with given fields: func (_m *MockNetworkAPI) Stop() error { ret := _m.Called() diff --git a/dot/rpc/modules/system.go b/dot/rpc/modules/system.go index b7a46225c2..80a6a46006 100644 --- a/dot/rpc/modules/system.go +++ b/dot/rpc/modules/system.go @@ -36,6 +36,7 @@ type SystemModule struct { coreAPI CoreAPI storageAPI StorageAPI txStateAPI TransactionStateAPI + blockAPI BlockAPI } // EmptyRequest represents an RPC request with no fields @@ -69,15 +70,23 @@ type StringRequest struct { String string } +// SyncStateResponse is the struct to return on the system_syncState rpc call +type SyncStateResponse struct { + CurrentBlock uint32 `json:"currentBlock"` + HighestBlock uint32 `json:"highestBlock"` + StartingBlock uint32 `json:"startingBlock"` +} + // NewSystemModule creates a new API instance func NewSystemModule(net NetworkAPI, sys SystemAPI, core CoreAPI, - storage StorageAPI, txAPI TransactionStateAPI) *SystemModule { + storage StorageAPI, txAPI TransactionStateAPI, blockAPI BlockAPI) *SystemModule { return &SystemModule{ networkAPI: net, // TODO: migrate to network state systemAPI: sys, coreAPI: core, storageAPI: storage, txStateAPI: txAPI, + blockAPI: blockAPI, } } @@ -228,6 +237,21 @@ func (sm *SystemModule) AccountNextIndex(r *http.Request, req *StringRequest, re return nil } +// SyncState Returns the state of the syncing of the node. +func (sm *SystemModule) SyncState(r *http.Request, req *EmptyRequest, res *SyncStateResponse) error { + h, err := sm.blockAPI.GetHeader(sm.blockAPI.BestBlockHash()) + if err != nil { + return err + } + + *res = SyncStateResponse{ + CurrentBlock: uint32(h.Number.Int64()), + HighestBlock: uint32(sm.networkAPI.HighestBlock()), + StartingBlock: uint32(sm.networkAPI.StartingBlock()), + } + return nil +} + // LocalListenAddresses Returns the libp2p multiaddresses that the local node is listening on func (sm *SystemModule) LocalListenAddresses(r *http.Request, req *EmptyRequest, res *[]string) error { netstate := sm.networkAPI.NetworkState() diff --git a/dot/rpc/modules/system_test.go b/dot/rpc/modules/system_test.go index 9410fb7010..d7819b0261 100644 --- a/dot/rpc/modules/system_test.go +++ b/dot/rpc/modules/system_test.go @@ -17,6 +17,7 @@ package modules import ( + "errors" "fmt" "math/big" "os" @@ -89,7 +90,7 @@ func newNetworkService(t *testing.T) *network.Service { func TestSystemModule_Health(t *testing.T) { net := newNetworkService(t) net.Stop() - sys := NewSystemModule(net, nil, nil, nil, nil) + sys := NewSystemModule(net, nil, nil, nil, nil, nil) res := &SystemHealthResponse{} err := sys.Health(nil, nil, res) @@ -103,7 +104,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) + sys := NewSystemModule(net, nil, nil, nil, nil, nil) res := &SystemNetworkStateResponse{} err := sys.NetworkState(nil, nil, res) @@ -120,7 +121,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) + sys := NewSystemModule(net, nil, nil, nil, nil, nil) res := &SystemPeersResponse{} err := sys.Peers(nil, nil, res) @@ -133,7 +134,7 @@ func TestSystemModule_Peers(t *testing.T) { func TestSystemModule_NodeRoles(t *testing.T) { net := newNetworkService(t) - sys := NewSystemModule(net, nil, nil, nil, nil) + sys := NewSystemModule(net, nil, nil, nil, nil, nil) expected := []interface{}{"Full"} var res []interface{} @@ -165,7 +166,7 @@ func newMockSystemAPI() *mocks.MockSystemAPI { } func TestSystemModule_Chain(t *testing.T) { - sys := NewSystemModule(nil, newMockSystemAPI(), nil, nil, nil) + sys := NewSystemModule(nil, newMockSystemAPI(), nil, nil, nil, nil) res := new(string) err := sys.Chain(nil, nil, res) @@ -176,14 +177,14 @@ func TestSystemModule_Chain(t *testing.T) { func TestSystemModule_ChainType(t *testing.T) { api := newMockSystemAPI() - sys := NewSystemModule(nil, api, nil, nil, nil) + sys := NewSystemModule(nil, api, 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) + sys := NewSystemModule(nil, newMockSystemAPI(), nil, nil, nil, nil) res := new(string) err := sys.Name(nil, nil, res) @@ -192,7 +193,7 @@ func TestSystemModule_Name(t *testing.T) { } func TestSystemModule_Version(t *testing.T) { - sys := NewSystemModule(nil, newMockSystemAPI(), nil, nil, nil) + sys := NewSystemModule(nil, newMockSystemAPI(), nil, nil, nil, nil) res := new(string) err := sys.Version(nil, nil, res) @@ -201,7 +202,7 @@ func TestSystemModule_Version(t *testing.T) { } func TestSystemModule_Properties(t *testing.T) { - sys := NewSystemModule(nil, newMockSystemAPI(), nil, nil, nil) + sys := NewSystemModule(nil, newMockSystemAPI(), nil, nil, nil, nil) expected := map[string]interface{}(nil) @@ -318,7 +319,7 @@ func setupSystemModule(t *testing.T) *SystemModule { core := newCoreService(t, chain) // TODO (ed) add transactions to txQueue and add test for those txQueue := state.NewTransactionState() - return NewSystemModule(net, nil, core, chain.Storage, txQueue) + return NewSystemModule(net, nil, core, chain.Storage, txQueue, nil) } func newCoreService(t *testing.T, srvc *state.Service) *core.Service { @@ -356,6 +357,41 @@ func newCoreService(t *testing.T, srvc *state.Service) *core.Service { return core.NewTestService(t, cfg) } +func TestSyncState(t *testing.T) { + fakeCommonHash := common.NewHash([]byte("fake")) + fakeHeader := &types.Header{ + Number: big.NewInt(int64(49)), + } + + blockapiMock := new(mocks.MockBlockAPI) + blockapiMock.On("BestBlockHash").Return(fakeCommonHash) + blockapiMock.On("GetHeader", fakeCommonHash).Return(fakeHeader, nil).Once() + + netapiMock := new(mocks.MockNetworkAPI) + netapiMock.On("HighestBlock").Return(int64(90)) + netapiMock.On("StartingBlock").Return(int64(10)) + + sysmodule := new(SystemModule) + sysmodule.blockAPI = blockapiMock + sysmodule.networkAPI = netapiMock + + var res SyncStateResponse + err := sysmodule.SyncState(nil, nil, &res) + require.NoError(t, err) + + expectedSyncState := SyncStateResponse{ + CurrentBlock: uint32(49), + HighestBlock: uint32(90), + StartingBlock: uint32(10), + } + + require.Equal(t, expectedSyncState, res) + + blockapiMock.On("GetHeader", fakeCommonHash).Return(nil, errors.New("Problems while getting header")).Once() + err = sysmodule.SyncState(nil, nil, nil) + require.Error(t, err) +} + func TestLocalListenAddresses(t *testing.T) { ma, err := multiaddr.NewMultiaddr("/ip4/127.0.0.1/tcp/7001/p2p/12D3KooWCYyh5xoAc5oRyiGU4d9ktcqFQ23JjitNFR6bEcbw7YdN") require.NoError(t, err) diff --git a/dot/rpc/service_test.go b/dot/rpc/service_test.go index c72a6a607c..0f8fc047c0 100644 --- a/dot/rpc/service_test.go +++ b/dot/rpc/service_test.go @@ -33,12 +33,12 @@ func TestNewService(t *testing.T) { } func TestService_Methods(t *testing.T) { - qtySystemMethods := 12 + qtySystemMethods := 13 qtyRPCMethods := 1 qtyAuthorMethods := 7 rpcService := NewService() - sysMod := modules.NewSystemModule(nil, nil, nil, nil, nil) + sysMod := modules.NewSystemModule(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