Skip to content

Commit 9a76d39

Browse files
fix(rpc/subscription): subscribe runtime version notify when version changes (#1686)
* add code for runtime version changed fix * fix return type * update mock in Makefile * fix core_api mock * implement notifications for runtime updates * lint * implement unregister runtime version listener * make mocks and lint * refactor runtime subscription name, change error handeling * remove unneeded select case * lint, refactor GetID to GetChannelID * lint * add sync.WaitGroup to notify Runtime Updated * add test for Register UnRegister Runtime Update Channel * add check for id * add register panic test * add test to produce panic * generate id as uuid, add locks, delete from map * update runtime updated channel ids to use uint32 * change logging to debug * remove comment * move uuid into check loop * update runtime test * add test for notify runtime updated * update runtime tests * move runtime notify from coreAPI to blockStateAPI * fix mocks for tests * refactor state_test * add tests * lint * remove commented code, fix var assignment * fix merge conflict * add check if channel is ok * update unit test * send notification as go routine Co-authored-by: Eclésio Júnior <[email protected]>
1 parent 0932ee8 commit 9a76d39

File tree

15 files changed

+513
-36
lines changed

15 files changed

+513
-36
lines changed

dot/core/service_test.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import (
2525
"testing"
2626
"time"
2727

28+
"github.com/ChainSafe/gossamer/dot/core/mocks"
29+
coremocks "github.com/ChainSafe/gossamer/dot/core/mocks"
2830
"github.com/ChainSafe/gossamer/dot/network"
2931
"github.com/ChainSafe/gossamer/dot/state"
3032
"github.com/ChainSafe/gossamer/dot/sync"
@@ -33,6 +35,7 @@ import (
3335
"github.com/ChainSafe/gossamer/lib/keystore"
3436
"github.com/ChainSafe/gossamer/lib/runtime"
3537
"github.com/ChainSafe/gossamer/lib/runtime/extrinsic"
38+
runtimemocks "github.com/ChainSafe/gossamer/lib/runtime/mocks"
3639
"github.com/ChainSafe/gossamer/lib/runtime/storage"
3740
"github.com/ChainSafe/gossamer/lib/runtime/wasmer"
3841
"github.com/ChainSafe/gossamer/lib/transaction"
@@ -41,10 +44,6 @@ import (
4144
log "github.com/ChainSafe/log15"
4245
"github.com/stretchr/testify/mock"
4346
"github.com/stretchr/testify/require"
44-
45-
"github.com/ChainSafe/gossamer/dot/core/mocks"
46-
coremocks "github.com/ChainSafe/gossamer/dot/core/mocks"
47-
runtimemocks "github.com/ChainSafe/gossamer/lib/runtime/mocks"
4847
)
4948

5049
func addTestBlocksToState(t *testing.T, depth int, blockState BlockState) {

dot/rpc/modules/api.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ type BlockAPI interface {
4040
RegisterFinalizedChannel(ch chan<- *types.FinalisationInfo) (byte, error)
4141
UnregisterFinalisedChannel(id byte)
4242
SubChain(start, end common.Hash) ([]common.Hash, error)
43+
RegisterRuntimeUpdatedChannel(ch chan<- runtime.Version) (uint32, error)
44+
UnregisterRuntimeUpdatedChannel(id uint32) bool
4345
}
4446

4547
// NetworkAPI interface for network state methods

dot/rpc/modules/api_mocks.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package modules
33
import (
44
modulesmocks "github.com/ChainSafe/gossamer/dot/rpc/modules/mocks"
55
"github.com/ChainSafe/gossamer/lib/common"
6+
runtimemocks "github.com/ChainSafe/gossamer/lib/runtime/mocks"
67
"github.com/stretchr/testify/mock"
78
)
89

@@ -35,6 +36,8 @@ func NewMockBlockAPI() *modulesmocks.BlockAPI {
3536
m.On("GetJustification", mock.AnythingOfType("common.Hash")).Return(make([]byte, 10), nil)
3637
m.On("HasJustification", mock.AnythingOfType("common.Hash")).Return(true, nil)
3738
m.On("SubChain", mock.AnythingOfType("common.Hash"), mock.AnythingOfType("common.Hash")).Return(make([]common.Hash, 0), nil)
39+
m.On("RegisterRuntimeUpdatedChannel", mock.AnythingOfType("chan<- runtime.Version")).Return(uint32(0), nil)
40+
3841
return m
3942
}
4043

@@ -43,9 +46,22 @@ func NewMockCoreAPI() *modulesmocks.MockCoreAPI {
4346
m := new(modulesmocks.MockCoreAPI)
4447
m.On("InsertKey", mock.AnythingOfType("crypto.Keypair"))
4548
m.On("HasKey", mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return(false, nil)
46-
m.On("GetRuntimeVersion", mock.AnythingOfType("*common.Hash")).Return(nil, nil)
49+
m.On("GetRuntimeVersion", mock.AnythingOfType("*common.Hash")).Return(NewMockVersion(), nil)
4750
m.On("IsBlockProducer").Return(false)
4851
m.On("HandleSubmittedExtrinsic", mock.AnythingOfType("types.Extrinsic")).Return(nil)
4952
m.On("GetMetadata", mock.AnythingOfType("*common.Hash")).Return(nil, nil)
5053
return m
5154
}
55+
56+
// NewMockVersion creates and returns an runtime Version interface mock
57+
func NewMockVersion() *runtimemocks.MockVersion {
58+
m := new(runtimemocks.MockVersion)
59+
m.On("SpecName").Return([]byte(`mock-spec`))
60+
m.On("ImplName").Return(nil)
61+
m.On("AuthoringVersion").Return(uint32(0))
62+
m.On("SpecVersion").Return(uint32(0))
63+
m.On("ImplVersion").Return(uint32(0))
64+
m.On("TransactionVersion").Return(uint32(0))
65+
m.On("APIItems").Return(nil)
66+
return m
67+
}

dot/rpc/modules/api_mocks_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright 2019 ChainSafe Systems (ON) Corp.
2+
// This file is part of gossamer.
3+
//
4+
// The gossamer library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The gossamer library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the gossamer library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package modules
18+
19+
import (
20+
"testing"
21+
22+
"github.com/stretchr/testify/require"
23+
)
24+
25+
func TestNewMockStorageAPI(t *testing.T) {
26+
m := NewMockStorageAPI()
27+
require.NotNil(t, m)
28+
}
29+
30+
func TestNewMockBlockAPI(t *testing.T) {
31+
m := NewMockBlockAPI()
32+
require.NotNil(t, m)
33+
}
34+
35+
func TestNewMockCoreAPI(t *testing.T) {
36+
m := NewMockCoreAPI()
37+
require.NotNil(t, m)
38+
}
39+
40+
func TestNewMockVersion(t *testing.T) {
41+
m := NewMockVersion()
42+
require.NotNil(t, m)
43+
}

dot/rpc/modules/mocks/BlockAPI.go

Lines changed: 37 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dot/rpc/subscription/listeners.go

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"github.com/ChainSafe/gossamer/dot/state"
2727
"github.com/ChainSafe/gossamer/dot/types"
2828
"github.com/ChainSafe/gossamer/lib/common"
29+
"github.com/ChainSafe/gossamer/lib/runtime"
2930
)
3031

3132
const (
@@ -282,20 +283,26 @@ func (l *ExtrinsicSubmitListener) Stop() error {
282283

283284
// RuntimeVersionListener to handle listening for Runtime Version
284285
type RuntimeVersionListener struct {
285-
wsconn *WSConn
286-
subID uint32
286+
wsconn WSConnAPI
287+
subID uint32
288+
runtimeUpdate chan runtime.Version
289+
channelID uint32
290+
coreAPI modules.CoreAPI
291+
}
292+
293+
// VersionListener interface defining methods that version listener must implement
294+
type VersionListener interface {
295+
GetChannelID() uint32
287296
}
288297

289298
// Listen implementation of Listen interface to listen for runtime version changes
290299
func (l *RuntimeVersionListener) Listen() {
291300
// This sends current runtime version once when subscription is created
292-
// TODO (ed) add logic to send updates when runtime version changes
293-
rtVersion, err := l.wsconn.CoreAPI.GetRuntimeVersion(nil)
301+
rtVersion, err := l.coreAPI.GetRuntimeVersion(nil)
294302
if err != nil {
295303
return
296304
}
297305
ver := modules.StateRuntimeVersionResponse{}
298-
299306
ver.SpecName = string(rtVersion.SpecName())
300307
ver.ImplName = string(rtVersion.ImplName())
301308
ver.AuthoringVersion = rtVersion.AuthoringVersion()
@@ -304,7 +311,34 @@ func (l *RuntimeVersionListener) Listen() {
304311
ver.TransactionVersion = rtVersion.TransactionVersion()
305312
ver.Apis = modules.ConvertAPIs(rtVersion.APIItems())
306313

307-
l.wsconn.safeSend(newSubscriptionResponse(stateRuntimeVersionMethod, l.subID, ver))
314+
go l.wsconn.safeSend(newSubscriptionResponse(stateRuntimeVersionMethod, l.subID, ver))
315+
316+
// listen for runtime updates
317+
go func() {
318+
for {
319+
info, ok := <-l.runtimeUpdate
320+
if !ok {
321+
return
322+
}
323+
324+
ver := modules.StateRuntimeVersionResponse{}
325+
326+
ver.SpecName = string(info.SpecName())
327+
ver.ImplName = string(info.ImplName())
328+
ver.AuthoringVersion = info.AuthoringVersion()
329+
ver.SpecVersion = info.SpecVersion()
330+
ver.ImplVersion = info.ImplVersion()
331+
ver.TransactionVersion = info.TransactionVersion()
332+
ver.Apis = modules.ConvertAPIs(info.APIItems())
333+
334+
l.wsconn.safeSend(newSubscriptionResponse(stateRuntimeVersionMethod, l.subID, ver))
335+
}
336+
}()
337+
}
338+
339+
// GetChannelID function that returns listener's channel ID
340+
func (l *RuntimeVersionListener) GetChannelID() uint32 {
341+
return l.channelID
308342
}
309343

310344
// Stop to runtimeVersionListener not implemented yet because the listener

dot/rpc/subscription/listeners_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@ package subscription
1919
import (
2020
"encoding/json"
2121
"fmt"
22+
"io/ioutil"
2223
"log"
2324
"math/big"
2425
"net/http"
2526
"net/http/httptest"
27+
"path/filepath"
2628
"strings"
2729
"testing"
2830
"time"
@@ -33,6 +35,8 @@ import (
3335
"github.com/ChainSafe/gossamer/dot/types"
3436
"github.com/ChainSafe/gossamer/lib/common"
3537
"github.com/ChainSafe/gossamer/lib/grandpa"
38+
"github.com/ChainSafe/gossamer/lib/runtime"
39+
"github.com/ChainSafe/gossamer/lib/runtime/wasmer"
3640
"github.com/gorilla/websocket"
3741
"github.com/stretchr/testify/mock"
3842
"github.com/stretchr/testify/require"
@@ -325,3 +329,58 @@ func setupWSConn(t *testing.T) (*WSConn, *websocket.Conn, func()) {
325329

326330
return wskt, ws, cancel
327331
}
332+
333+
func TestRuntimeChannelListener_Listen(t *testing.T) {
334+
notifyChan := make(chan runtime.Version)
335+
mockConnection := &MockWSConnAPI{}
336+
rvl := RuntimeVersionListener{
337+
wsconn: mockConnection,
338+
subID: 0,
339+
runtimeUpdate: notifyChan,
340+
coreAPI: modules.NewMockCoreAPI(),
341+
}
342+
343+
expectedInitialVersion := modules.StateRuntimeVersionResponse{
344+
SpecName: "mock-spec",
345+
Apis: modules.ConvertAPIs(nil),
346+
}
347+
348+
expectedInitialResponse := newSubcriptionBaseResponseJSON()
349+
expectedInitialResponse.Method = "state_runtimeVersion"
350+
expectedInitialResponse.Params.Result = expectedInitialVersion
351+
352+
instance := wasmer.NewTestInstance(t, runtime.NODE_RUNTIME)
353+
_, err := runtime.GetRuntimeBlob(runtime.POLKADOT_RUNTIME_FP, runtime.POLKADOT_RUNTIME_URL)
354+
require.NoError(t, err)
355+
fp, err := filepath.Abs(runtime.POLKADOT_RUNTIME_FP)
356+
require.NoError(t, err)
357+
code, err := ioutil.ReadFile(fp)
358+
require.NoError(t, err)
359+
version, err := instance.CheckRuntimeVersion(code)
360+
require.NoError(t, err)
361+
362+
expectedUpdatedVersion := modules.StateRuntimeVersionResponse{
363+
SpecName: "polkadot",
364+
ImplName: "parity-polkadot",
365+
AuthoringVersion: 0,
366+
SpecVersion: 25,
367+
ImplVersion: 0,
368+
TransactionVersion: 5,
369+
Apis: modules.ConvertAPIs(version.APIItems()),
370+
}
371+
372+
expectedUpdateResponse := newSubcriptionBaseResponseJSON()
373+
expectedUpdateResponse.Method = "state_runtimeVersion"
374+
expectedUpdateResponse.Params.Result = expectedUpdatedVersion
375+
376+
go rvl.Listen()
377+
378+
//check initial response
379+
time.Sleep(time.Millisecond * 10)
380+
require.Equal(t, expectedInitialResponse, mockConnection.lastMessage)
381+
382+
// check response after update
383+
notifyChan <- version
384+
time.Sleep(time.Millisecond * 10)
385+
require.Equal(t, expectedUpdateResponse, mockConnection.lastMessage)
386+
}

dot/rpc/subscription/subscription.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ func (c *WSConn) getUnsubListener(method string, params interface{}) (unsubListe
4747
switch method {
4848
case "state_unsubscribeStorage":
4949
unsub = c.unsubscribeStorageListener
50+
case "state_unsubscribeRuntimeVersion":
51+
unsub = c.unsubscribeRuntimeVersionListener
5052
case "grandpa_unsubscribeJustifications":
5153
unsub = c.unsubscribeGrandpaJustificationListener
5254
default:

0 commit comments

Comments
 (0)