diff --git a/dot/rpc/http.go b/dot/rpc/http.go
index fd98877331..4934cafa1b 100644
--- a/dot/rpc/http.go
+++ b/dot/rpc/http.go
@@ -177,9 +177,8 @@ func (h *HTTPServer) Stop() error {
for _, conn := range h.wsConns {
for _, sub := range conn.Subscriptions {
switch v := sub.(type) {
- case *subscription.StorageChangeListener:
- h.serverConfig.StorageAPI.UnregisterStorageChangeChannel(v.ChanID)
- close(v.Channel)
+ case *subscription.StorageObserver:
+ h.serverConfig.StorageAPI.UnregisterStorageObserver(v)
case *subscription.BlockListener:
h.serverConfig.BlockAPI.UnregisterImportedChannel(v.ChanID)
close(v.Channel)
@@ -234,8 +233,8 @@ func (h *HTTPServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
func NewWSConn(conn *websocket.Conn, cfg *HTTPServerConfig) *subscription.WSConn {
c := &subscription.WSConn{
Wsconn: conn,
- Subscriptions: make(map[int]subscription.Listener),
- BlockSubChannels: make(map[int]byte),
+ Subscriptions: make(map[uint]subscription.Listener),
+ BlockSubChannels: make(map[uint]byte),
StorageSubChannels: make(map[int]byte),
StorageAPI: cfg.StorageAPI,
BlockAPI: cfg.BlockAPI,
diff --git a/dot/rpc/modules/api.go b/dot/rpc/modules/api.go
index 28eea721d5..4951294139 100644
--- a/dot/rpc/modules/api.go
+++ b/dot/rpc/modules/api.go
@@ -16,10 +16,10 @@ type StorageAPI interface {
GetStorage(root *common.Hash, key []byte) ([]byte, error)
GetStorageByBlockHash(bhash common.Hash, key []byte) ([]byte, error)
Entries(root *common.Hash) (map[string][]byte, error)
- RegisterStorageChangeChannel(sub state.StorageSubscription) (byte, error)
- UnregisterStorageChangeChannel(id byte)
GetStateRootFromBlock(bhash *common.Hash) (*common.Hash, error)
GetKeysWithPrefix(root *common.Hash, prefix []byte) ([][]byte, error)
+ RegisterStorageObserver(observer state.Observer)
+ UnregisterStorageObserver(observer state.Observer)
}
// BlockAPI is the interface for the block state
diff --git a/dot/rpc/subscription/listeners.go b/dot/rpc/subscription/listeners.go
index 27a7b89c28..46213e4e56 100644
--- a/dot/rpc/subscription/listeners.go
+++ b/dot/rpc/subscription/listeners.go
@@ -30,119 +30,67 @@ type Listener interface {
Listen()
}
-func (c *WSConn) startListener(lid int) {
- go c.Subscriptions[lid].Listen()
+// WSConnAPI interface defining methors a WSConn should have
+type WSConnAPI interface {
+ safeSend(interface{})
}
-func (c *WSConn) initStorageChangeListener(reqID float64, params interface{}) (int, error) {
- scl := &StorageChangeListener{
- Channel: make(chan *state.SubscriptionResult),
- wsconn: c,
- }
- sub := &state.StorageSubscription{
- Filter: make(map[string]bool),
- Listener: scl.Channel,
- }
+// StorageObserver struct to hold data for observer (Observer Design Pattern)
+type StorageObserver struct {
+ id uint
+ filter map[string][]byte
+ wsconn WSConnAPI
+}
- pA := params.([]interface{})
- for _, param := range pA {
- switch p := param.(type) {
- case []interface{}:
- for _, pp := range param.([]interface{}) {
- sub.Filter[pp.(string)] = true
- }
- case string:
- sub.Filter[p] = true
- default:
- return 0, fmt.Errorf("unknow parameter type")
- }
- }
+// Change type defining key value pair representing change
+type Change [2]string
- if c.StorageAPI == nil {
- c.safeSendError(reqID, nil, "error StorageAPI not set")
- return 0, fmt.Errorf("error StorageAPI not set")
- }
+// ChangeResult struct to hold change result data
+type ChangeResult struct {
+ Changes []Change `json:"changes"`
+ Block string `json:"block"`
+}
- chanID, err := c.StorageAPI.RegisterStorageChangeChannel(*sub)
- if err != nil {
- return 0, err
+// Update is called to notify observer of new value
+func (s *StorageObserver) Update(change *state.SubscriptionResult) {
+ if change == nil {
+ return
}
- scl.ChanID = chanID
-
- c.qtyListeners++
- scl.subID = c.qtyListeners
- c.Subscriptions[scl.subID] = scl
- c.StorageSubChannels[scl.subID] = chanID
- initRes := newSubscriptionResponseJSON(scl.subID, reqID)
- c.safeSend(initRes)
+ changeResult := ChangeResult{
+ Block: change.Hash.String(),
+ Changes: make([]Change, len(change.Changes)),
+ }
+ for i, v := range change.Changes {
+ changeResult.Changes[i] = Change{common.BytesToHex(v.Key), common.BytesToHex(v.Value)}
+ }
- return scl.subID, nil
+ res := newSubcriptionBaseResponseJSON()
+ res.Method = "state_storage"
+ res.Params.Result = changeResult
+ res.Params.SubscriptionID = s.GetID()
+ s.wsconn.safeSend(res)
}
-// StorageChangeListener for listening to state change channels
-type StorageChangeListener struct {
- Channel chan *state.SubscriptionResult
- wsconn *WSConn
- ChanID byte
- subID int
+// GetID the id for the Observer
+func (s *StorageObserver) GetID() uint {
+ return s.id
}
-// Listen implementation of Listen interface to listen for importedChan changes
-func (l *StorageChangeListener) Listen() {
- for change := range l.Channel {
- if change == nil {
- continue
- }
-
- result := make(map[string]interface{})
- result["block"] = change.Hash.String()
- changes := [][]string{}
- for _, v := range change.Changes {
- kv := []string{common.BytesToHex(v.Key), common.BytesToHex(v.Value)}
- changes = append(changes, kv)
- }
- result["changes"] = changes
-
- res := newSubcriptionBaseResponseJSON()
- res.Method = "state_storage"
- res.Params.Result = result
- res.Params.SubscriptionID = l.subID
- l.wsconn.safeSend(res)
- }
+// GetFilter returns the filter the Observer is using
+func (s *StorageObserver) GetFilter() map[string][]byte {
+ return s.filter
}
+// Listen to satisfy Listener interface (but is no longer used by StorageObserver)
+func (s *StorageObserver) Listen() {}
+
// BlockListener to handle listening for blocks importedChan
type BlockListener struct {
Channel chan *types.Block
- wsconn *WSConn
+ wsconn WSConnAPI
ChanID byte
- subID int
-}
-
-func (c *WSConn) initBlockListener(reqID float64) (int, error) {
- bl := &BlockListener{
- Channel: make(chan *types.Block),
- wsconn: c,
- }
-
- if c.BlockAPI == nil {
- c.safeSendError(reqID, nil, "error BlockAPI not set")
- return 0, fmt.Errorf("error BlockAPI not set")
- }
- chanID, err := c.BlockAPI.RegisterImportedChannel(bl.Channel)
- if err != nil {
- return 0, err
- }
- bl.ChanID = chanID
- c.qtyListeners++
- bl.subID = c.qtyListeners
- c.Subscriptions[bl.subID] = bl
- c.BlockSubChannels[bl.subID] = chanID
- initRes := newSubscriptionResponseJSON(bl.subID, reqID)
- c.safeSend(initRes)
-
- return bl.subID, nil
+ subID uint
}
// Listen implementation of Listen interface to listen for importedChan changes
@@ -167,34 +115,9 @@ func (l *BlockListener) Listen() {
// BlockFinalizedListener to handle listening for finalized blocks
type BlockFinalizedListener struct {
channel chan *types.Header
- wsconn *WSConn
+ wsconn WSConnAPI
chanID byte
- subID int
-}
-
-func (c *WSConn) initBlockFinalizedListener(reqID float64) (int, error) {
- bfl := &BlockFinalizedListener{
- channel: make(chan *types.Header),
- wsconn: c,
- }
-
- if c.BlockAPI == nil {
- c.safeSendError(reqID, nil, "error BlockAPI not set")
- return 0, fmt.Errorf("error BlockAPI not set")
- }
- chanID, err := c.BlockAPI.RegisterFinalizedChannel(bfl.channel)
- if err != nil {
- return 0, err
- }
- bfl.chanID = chanID
- c.qtyListeners++
- bfl.subID = c.qtyListeners
- c.Subscriptions[bfl.subID] = bfl
- c.BlockSubChannels[bfl.subID] = chanID
- initRes := newSubscriptionResponseJSON(bfl.subID, reqID)
- c.safeSend(initRes)
-
- return bfl.subID, nil
+ subID uint
}
// Listen implementation of Listen interface to listen for importedChan changes
@@ -217,8 +140,8 @@ func (l *BlockFinalizedListener) Listen() {
// ExtrinsicSubmitListener to handle listening for extrinsic events
type ExtrinsicSubmitListener struct {
- wsconn *WSConn
- subID int
+ wsconn WSConnAPI
+ subID uint
extrinsic types.Extrinsic
importedChan chan *types.Block
@@ -231,55 +154,6 @@ type ExtrinsicSubmitListener struct {
// AuthorExtrinsicUpdates method name
const AuthorExtrinsicUpdates = "author_extrinsicUpdate"
-func (c *WSConn) initExtrinsicWatch(reqID float64, params interface{}) (int, error) {
- pA := params.([]interface{})
- extBytes, err := common.HexToBytes(pA[0].(string))
- if err != nil {
- return 0, err
- }
-
- // listen for built blocks
- esl := &ExtrinsicSubmitListener{
- importedChan: make(chan *types.Block),
- wsconn: c,
- extrinsic: types.Extrinsic(extBytes),
- finalizedChan: make(chan *types.Header),
- }
-
- if c.BlockAPI == nil {
- return 0, fmt.Errorf("error BlockAPI not set")
- }
- esl.importedChanID, err = c.BlockAPI.RegisterImportedChannel(esl.importedChan)
- if err != nil {
- return 0, err
- }
-
- esl.finalizedChanID, err = c.BlockAPI.RegisterFinalizedChannel(esl.finalizedChan)
- if err != nil {
- return 0, err
- }
-
- c.qtyListeners++
- esl.subID = c.qtyListeners
- c.Subscriptions[esl.subID] = esl
- c.BlockSubChannels[esl.subID] = esl.importedChanID
-
- err = c.CoreAPI.HandleSubmittedExtrinsic(extBytes)
- if err != nil {
- return 0, err
- }
- c.safeSend(newSubscriptionResponseJSON(esl.subID, reqID))
-
- // TODO (ed) since HandleSubmittedExtrinsic has been called we assume the extrinsic is in the tx queue
- // should we add a channel to tx queue so we're notified when it's in the queue
- if c.CoreAPI.IsBlockProducer() {
- c.safeSend(newSubscriptionResponse(AuthorExtrinsicUpdates, esl.subID, "ready"))
- }
-
- // todo (ed) determine which peer extrinsic has been broadcast to, and set status
- return esl.subID, err
-}
-
// Listen implementation of Listen interface to listen for importedChan changes
func (l *ExtrinsicSubmitListener) Listen() {
// listen for imported blocks with extrinsic
@@ -319,28 +193,13 @@ func (l *ExtrinsicSubmitListener) Listen() {
// RuntimeVersionListener to handle listening for Runtime Version
type RuntimeVersionListener struct {
wsconn *WSConn
- subID int
-}
-
-func (c *WSConn) initRuntimeVersionListener(reqID float64) (int, error) {
- rvl := &RuntimeVersionListener{
- wsconn: c,
- }
- if c.CoreAPI == nil {
- c.safeSendError(reqID, nil, "error CoreAPI not set")
- return 0, fmt.Errorf("error CoreAPI not set")
- }
- c.qtyListeners++
- rvl.subID = c.qtyListeners
- c.Subscriptions[rvl.subID] = rvl
- initRes := newSubscriptionResponseJSON(rvl.subID, reqID)
- c.safeSend(initRes)
-
- return rvl.subID, nil
+ subID uint
}
// Listen implementation of Listen interface to listen for runtime version changes
func (l *RuntimeVersionListener) Listen() {
+ // This sends current runtime version once when subscription is created
+ // TODO (ed) add logic to send updates when runtime version changes
rtVersion, err := l.wsconn.CoreAPI.GetRuntimeVersion(nil)
if err != nil {
return
diff --git a/dot/rpc/subscription/listeners_test.go b/dot/rpc/subscription/listeners_test.go
new file mode 100644
index 0000000000..1ebf13c798
--- /dev/null
+++ b/dot/rpc/subscription/listeners_test.go
@@ -0,0 +1,157 @@
+// Copyright 2020 ChainSafe Systems (ON) Corp.
+// This file is part of gossamer.
+//
+// The gossamer library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The gossamer library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the gossamer library. If not, see .
+
+package subscription
+
+import (
+ "math/big"
+ "testing"
+ "time"
+
+ "github.com/ChainSafe/gossamer/dot/rpc/modules"
+ "github.com/ChainSafe/gossamer/dot/state"
+ "github.com/ChainSafe/gossamer/dot/types"
+ "github.com/ChainSafe/gossamer/lib/common"
+ "github.com/stretchr/testify/require"
+)
+
+type MockWSConnAPI struct {
+ lastMessage BaseResponseJSON
+}
+
+func (m *MockWSConnAPI) safeSend(msg interface{}) {
+ m.lastMessage = msg.(BaseResponseJSON)
+}
+
+func TestStorageObserver_Update(t *testing.T) {
+ mockConnection := &MockWSConnAPI{}
+ storageObserver := StorageObserver{
+ id: 0,
+ wsconn: mockConnection,
+ }
+
+ data := []state.KeyValue{{
+ Key: []byte("key"),
+ Value: []byte("value"),
+ }}
+ change := &state.SubscriptionResult{
+ Hash: common.Hash{},
+ Changes: data,
+ }
+
+ expected := ChangeResult{
+ Block: change.Hash.String(),
+ Changes: make([]Change, len(change.Changes)),
+ }
+ for i, v := range change.Changes {
+ expected.Changes[i] = Change{common.BytesToHex(v.Key), common.BytesToHex(v.Value)}
+ }
+
+ expectedRespones := newSubcriptionBaseResponseJSON()
+ expectedRespones.Method = "state_storage"
+ expectedRespones.Params.Result = expected
+
+ storageObserver.Update(change)
+ time.Sleep(time.Millisecond * 10)
+ require.Equal(t, expectedRespones, mockConnection.lastMessage)
+}
+
+func TestBlockListener_Listen(t *testing.T) {
+ notifyChan := make(chan *types.Block)
+ mockConnection := &MockWSConnAPI{}
+ bl := BlockListener{
+ Channel: notifyChan,
+ wsconn: mockConnection,
+ }
+
+ block := types.NewEmptyBlock()
+ block.Header.Number = big.NewInt(1)
+
+ head, err := modules.HeaderToJSON(*block.Header)
+ require.NoError(t, err)
+
+ expectedResposnse := newSubcriptionBaseResponseJSON()
+ expectedResposnse.Method = "chain_newHead"
+ expectedResposnse.Params.Result = head
+
+ go bl.Listen()
+
+ notifyChan <- block
+ time.Sleep(time.Millisecond * 10)
+ require.Equal(t, expectedResposnse, mockConnection.lastMessage)
+}
+
+func TestBlockFinalizedListener_Listen(t *testing.T) {
+ notifyChan := make(chan *types.Header)
+ mockConnection := &MockWSConnAPI{}
+ bfl := BlockFinalizedListener{
+ channel: notifyChan,
+ wsconn: mockConnection,
+ }
+
+ header := types.NewEmptyHeader()
+ head, err := modules.HeaderToJSON(*header)
+ if err != nil {
+ logger.Error("failed to convert header to JSON", "error", err)
+ }
+ expectedResponse := newSubcriptionBaseResponseJSON()
+ expectedResponse.Method = "chain_finalizedHead"
+ expectedResponse.Params.Result = head
+
+ go bfl.Listen()
+
+ notifyChan <- header
+ time.Sleep(time.Millisecond * 10)
+ require.Equal(t, expectedResponse, mockConnection.lastMessage)
+}
+
+func TestExtrinsicSubmitListener_Listen(t *testing.T) {
+ notifyImportedChan := make(chan *types.Block)
+ notifyFinalizedChan := make(chan *types.Header)
+
+ mockConnection := &MockWSConnAPI{}
+ esl := ExtrinsicSubmitListener{
+ importedChan: notifyImportedChan,
+ finalizedChan: notifyFinalizedChan,
+ wsconn: mockConnection,
+ extrinsic: types.Extrinsic{1, 2, 3},
+ }
+ header := types.NewEmptyHeader()
+ exts := []types.Extrinsic{{1, 2, 3}, {7, 8, 9, 0}, {0xa, 0xb}}
+
+ body, err := types.NewBodyFromExtrinsics(exts)
+ require.NoError(t, err)
+
+ block := &types.Block{
+ Header: header,
+ Body: body,
+ }
+
+ resImported := map[string]interface{}{"inBlock": block.Header.Hash().String()}
+ expectedImportedRespones := newSubscriptionResponse(AuthorExtrinsicUpdates, esl.subID, resImported)
+
+ go esl.Listen()
+
+ notifyImportedChan <- block
+ time.Sleep(time.Millisecond * 10)
+ require.Equal(t, expectedImportedRespones, mockConnection.lastMessage)
+
+ notifyFinalizedChan <- header
+ time.Sleep(time.Millisecond * 10)
+ resFinalized := map[string]interface{}{"finalized": block.Header.Hash().String()}
+ expectedFinalizedRespones := newSubscriptionResponse(AuthorExtrinsicUpdates, esl.subID, resFinalized)
+ require.Equal(t, expectedFinalizedRespones, mockConnection.lastMessage)
+}
diff --git a/dot/rpc/subscription/messages.go b/dot/rpc/subscription/messages.go
index 0722483689..d6df1258dd 100644
--- a/dot/rpc/subscription/messages.go
+++ b/dot/rpc/subscription/messages.go
@@ -15,10 +15,6 @@
// along with the gossamer library. If not, see .
package subscription
-import (
- "math/big"
-)
-
// BaseResponseJSON for base json response
type BaseResponseJSON struct {
Jsonrpc string `json:"jsonrpc"`
@@ -29,7 +25,7 @@ type BaseResponseJSON struct {
// Params for json param response
type Params struct {
Result interface{} `json:"result"`
- SubscriptionID int `json:"subscription"`
+ SubscriptionID uint `json:"subscription"`
}
func newSubcriptionBaseResponseJSON() BaseResponseJSON {
@@ -38,7 +34,7 @@ func newSubcriptionBaseResponseJSON() BaseResponseJSON {
}
}
-func newSubscriptionResponse(method string, subID int, result interface{}) BaseResponseJSON {
+func newSubscriptionResponse(method string, subID uint, result interface{}) BaseResponseJSON {
return BaseResponseJSON{
Jsonrpc: "2.0",
Method: method,
@@ -52,52 +48,14 @@ func newSubscriptionResponse(method string, subID int, result interface{}) BaseR
// ResponseJSON for json subscription responses
type ResponseJSON struct {
Jsonrpc string `json:"jsonrpc"`
- Result int `json:"result"`
+ Result uint `json:"result"`
ID float64 `json:"id"`
}
-func newSubscriptionResponseJSON(subID int, reqID float64) ResponseJSON {
+func newSubscriptionResponseJSON(subID uint, reqID float64) ResponseJSON {
return ResponseJSON{
Jsonrpc: "2.0",
Result: subID,
ID: reqID,
}
}
-
-// ErrorResponseJSON json for error responses
-type ErrorResponseJSON struct {
- Jsonrpc string `json:"jsonrpc"`
- Error *ErrorMessageJSON `json:"error"`
- ID float64 `json:"id"`
-}
-
-// ErrorMessageJSON json for error messages
-type ErrorMessageJSON struct {
- Code *big.Int `json:"code"`
- Message string `json:"message"`
-}
-
-func (c *WSConn) safeSend(msg interface{}) {
- c.mu.Lock()
- defer c.mu.Unlock()
- err := c.Wsconn.WriteJSON(msg)
- if err != nil {
- logger.Debug("error sending websocket message", "error", err)
- }
-}
-func (c *WSConn) safeSendError(reqID float64, errorCode *big.Int, message string) {
- res := &ErrorResponseJSON{
- Jsonrpc: "2.0",
- Error: &ErrorMessageJSON{
- Code: errorCode,
- Message: message,
- },
- ID: reqID,
- }
- c.mu.Lock()
- defer c.mu.Unlock()
- err := c.Wsconn.WriteJSON(res)
- if err != nil {
- logger.Debug("error sending websocket message", "error", err)
- }
-}
diff --git a/dot/rpc/subscription/websocket.go b/dot/rpc/subscription/websocket.go
index fa4a878638..9c03f7c77a 100644
--- a/dot/rpc/subscription/websocket.go
+++ b/dot/rpc/subscription/websocket.go
@@ -27,20 +27,22 @@ import (
"sync"
"github.com/ChainSafe/gossamer/dot/rpc/modules"
+ "github.com/ChainSafe/gossamer/dot/types"
+ "github.com/ChainSafe/gossamer/lib/common"
log "github.com/ChainSafe/log15"
"github.com/gorilla/websocket"
)
-var logger = log.New("pkg", "subscription")
+var logger = log.New("pkg", "rpc/subscription")
// WSConn struct to hold WebSocket Connection references
type WSConn struct {
Wsconn *websocket.Conn
mu sync.Mutex
- BlockSubChannels map[int]byte
+ BlockSubChannels map[uint]byte
StorageSubChannels map[int]byte
- qtyListeners int
- Subscriptions map[int]Listener
+ qtyListeners uint
+ Subscriptions map[uint]Listener
StorageAPI modules.StorageAPI
BlockAPI modules.BlockAPI
RuntimeAPI modules.RuntimeAPI
@@ -81,12 +83,12 @@ func (c *WSConn) HandleComm() {
}
c.startListener(bl)
case "state_subscribeStorage":
- scl, err2 := c.initStorageChangeListener(reqid, params)
+ _, err2 := c.initStorageChangeListener(reqid, params)
if err2 != nil {
logger.Warn("failed to create state change listener", "error", err2)
continue
}
- c.startListener(scl)
+
case "chain_subscribeFinalizedHeads":
bfl, err3 := c.initBlockFinalizedListener(reqid)
if err3 != nil {
@@ -161,3 +163,206 @@ func (c *WSConn) HandleComm() {
c.safeSend(wsSend)
}
}
+
+func (c *WSConn) initStorageChangeListener(reqID float64, params interface{}) (uint, error) {
+ if c.StorageAPI == nil {
+ c.safeSendError(reqID, nil, "error StorageAPI not set")
+ return 0, fmt.Errorf("error StorageAPI not set")
+ }
+
+ myObs := &StorageObserver{
+ filter: make(map[string][]byte),
+ wsconn: c,
+ }
+
+ pA, ok := params.([]interface{})
+ if !ok {
+ return 0, fmt.Errorf("unknown parameter type")
+ }
+ for _, param := range pA {
+ switch p := param.(type) {
+ case []interface{}:
+ for _, pp := range param.([]interface{}) {
+ data, ok := pp.(string)
+ if !ok {
+ return 0, fmt.Errorf("unknown parameter type")
+ }
+ myObs.filter[data] = []byte{}
+ }
+ case string:
+ myObs.filter[p] = []byte{}
+ default:
+ return 0, fmt.Errorf("unknown parameter type")
+ }
+ }
+
+ c.qtyListeners++
+ myObs.id = c.qtyListeners
+
+ c.StorageAPI.RegisterStorageObserver(myObs)
+
+ c.Subscriptions[myObs.id] = myObs
+
+ initRes := newSubscriptionResponseJSON(myObs.id, reqID)
+ c.safeSend(initRes)
+
+ return myObs.id, nil
+}
+
+func (c *WSConn) initBlockListener(reqID float64) (uint, error) {
+ bl := &BlockListener{
+ Channel: make(chan *types.Block),
+ wsconn: c,
+ }
+
+ if c.BlockAPI == nil {
+ c.safeSendError(reqID, nil, "error BlockAPI not set")
+ return 0, fmt.Errorf("error BlockAPI not set")
+ }
+ chanID, err := c.BlockAPI.RegisterImportedChannel(bl.Channel)
+ if err != nil {
+ return 0, err
+ }
+ bl.ChanID = chanID
+ c.qtyListeners++
+ bl.subID = c.qtyListeners
+ c.Subscriptions[bl.subID] = bl
+ c.BlockSubChannels[bl.subID] = chanID
+ initRes := newSubscriptionResponseJSON(bl.subID, reqID)
+ c.safeSend(initRes)
+
+ return bl.subID, nil
+}
+
+func (c *WSConn) initBlockFinalizedListener(reqID float64) (uint, error) {
+ bfl := &BlockFinalizedListener{
+ channel: make(chan *types.Header),
+ wsconn: c,
+ }
+
+ if c.BlockAPI == nil {
+ c.safeSendError(reqID, nil, "error BlockAPI not set")
+ return 0, fmt.Errorf("error BlockAPI not set")
+ }
+ chanID, err := c.BlockAPI.RegisterFinalizedChannel(bfl.channel)
+ if err != nil {
+ return 0, err
+ }
+ bfl.chanID = chanID
+ c.qtyListeners++
+ bfl.subID = c.qtyListeners
+ c.Subscriptions[bfl.subID] = bfl
+ c.BlockSubChannels[bfl.subID] = chanID
+ initRes := newSubscriptionResponseJSON(bfl.subID, reqID)
+ c.safeSend(initRes)
+
+ return bfl.subID, nil
+}
+
+func (c *WSConn) initExtrinsicWatch(reqID float64, params interface{}) (uint, error) {
+ pA := params.([]interface{})
+ extBytes, err := common.HexToBytes(pA[0].(string))
+ if err != nil {
+ return 0, err
+ }
+
+ // listen for built blocks
+ esl := &ExtrinsicSubmitListener{
+ importedChan: make(chan *types.Block),
+ wsconn: c,
+ extrinsic: types.Extrinsic(extBytes),
+ finalizedChan: make(chan *types.Header),
+ }
+
+ if c.BlockAPI == nil {
+ return 0, fmt.Errorf("error BlockAPI not set")
+ }
+ esl.importedChanID, err = c.BlockAPI.RegisterImportedChannel(esl.importedChan)
+ if err != nil {
+ return 0, err
+ }
+
+ esl.finalizedChanID, err = c.BlockAPI.RegisterFinalizedChannel(esl.finalizedChan)
+ if err != nil {
+ return 0, err
+ }
+
+ c.qtyListeners++
+ esl.subID = c.qtyListeners
+ c.Subscriptions[esl.subID] = esl
+ c.BlockSubChannels[esl.subID] = esl.importedChanID
+
+ err = c.CoreAPI.HandleSubmittedExtrinsic(extBytes)
+ if err != nil {
+ return 0, err
+ }
+ c.safeSend(newSubscriptionResponseJSON(esl.subID, reqID))
+
+ // TODO (ed) since HandleSubmittedExtrinsic has been called we assume the extrinsic is in the tx queue
+ // should we add a channel to tx queue so we're notified when it's in the queue (See issue #1535)
+ if c.CoreAPI.IsBlockProducer() {
+ c.safeSend(newSubscriptionResponse(AuthorExtrinsicUpdates, esl.subID, "ready"))
+ }
+
+ // todo (ed) determine which peer extrinsic has been broadcast to, and set status
+ return esl.subID, err
+}
+
+func (c *WSConn) initRuntimeVersionListener(reqID float64) (uint, error) {
+ rvl := &RuntimeVersionListener{
+ wsconn: c,
+ }
+ if c.CoreAPI == nil {
+ c.safeSendError(reqID, nil, "error CoreAPI not set")
+ return 0, fmt.Errorf("error CoreAPI not set")
+ }
+ c.qtyListeners++
+ rvl.subID = c.qtyListeners
+ c.Subscriptions[rvl.subID] = rvl
+ initRes := newSubscriptionResponseJSON(rvl.subID, reqID)
+ c.safeSend(initRes)
+
+ return rvl.subID, nil
+}
+
+func (c *WSConn) safeSend(msg interface{}) {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ err := c.Wsconn.WriteJSON(msg)
+ if err != nil {
+ logger.Debug("error sending websocket message", "error", err)
+ }
+}
+func (c *WSConn) safeSendError(reqID float64, errorCode *big.Int, message string) {
+ res := &ErrorResponseJSON{
+ Jsonrpc: "2.0",
+ Error: &ErrorMessageJSON{
+ Code: errorCode,
+ Message: message,
+ },
+ ID: reqID,
+ }
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ err := c.Wsconn.WriteJSON(res)
+ if err != nil {
+ logger.Debug("error sending websocket message", "error", err)
+ }
+}
+
+// ErrorResponseJSON json for error responses
+type ErrorResponseJSON struct {
+ Jsonrpc string `json:"jsonrpc"`
+ Error *ErrorMessageJSON `json:"error"`
+ ID float64 `json:"id"`
+}
+
+// ErrorMessageJSON json for error messages
+type ErrorMessageJSON struct {
+ Code *big.Int `json:"code"`
+ Message string `json:"message"`
+}
+
+func (c *WSConn) startListener(lid uint) {
+ go c.Subscriptions[lid].Listen()
+}
diff --git a/dot/rpc/subscription/websocket_test.go b/dot/rpc/subscription/websocket_test.go
new file mode 100644
index 0000000000..43e189c39c
--- /dev/null
+++ b/dot/rpc/subscription/websocket_test.go
@@ -0,0 +1,265 @@
+package subscription
+
+import (
+ "log"
+ "math/big"
+ "net/http"
+ "os"
+ "testing"
+ "time"
+
+ "github.com/ChainSafe/gossamer/dot/state"
+ "github.com/ChainSafe/gossamer/dot/types"
+ "github.com/ChainSafe/gossamer/lib/common"
+ "github.com/ChainSafe/gossamer/lib/crypto"
+ "github.com/ChainSafe/gossamer/lib/runtime"
+ "github.com/gorilla/websocket"
+ "github.com/stretchr/testify/require"
+)
+
+var upgrader = websocket.Upgrader{
+ CheckOrigin: func(r *http.Request) bool { return true },
+}
+var wsconn = &WSConn{
+ Subscriptions: make(map[uint]Listener),
+ BlockSubChannels: make(map[uint]byte),
+}
+
+func handler(w http.ResponseWriter, r *http.Request) {
+ c, err := upgrader.Upgrade(w, r, nil)
+ if err != nil {
+ log.Print("upgrade:", err)
+ return
+ }
+ defer c.Close()
+
+ wsconn.Wsconn = c
+ wsconn.HandleComm()
+}
+
+func TestMain(m *testing.M) {
+ http.HandleFunc("/", handler)
+
+ go func() {
+ err := http.ListenAndServe("localhost:8546", nil)
+ if err != nil {
+ log.Fatal("error", err)
+ }
+ }()
+ time.Sleep(time.Millisecond * 100)
+ // Start all tests
+ os.Exit(m.Run())
+}
+
+func TestWSConn_HandleComm(t *testing.T) {
+ c, _, err := websocket.DefaultDialer.Dial("ws://localhost:8546", nil) //nolint
+ if err != nil {
+ log.Fatal("dial:", err)
+ }
+ defer c.Close()
+
+ // test storageChangeListener
+ res, err := wsconn.initStorageChangeListener(1, nil)
+ require.EqualError(t, err, "error StorageAPI not set")
+ require.Equal(t, uint(0), res)
+ _, msg, err := c.ReadMessage()
+ require.NoError(t, err)
+ require.Equal(t, []byte(`{"jsonrpc":"2.0","error":{"code":null,"message":"error StorageAPI not set"},"id":1}`+"\n"), msg)
+
+ wsconn.StorageAPI = new(MockStorageAPI)
+
+ res, err = wsconn.initStorageChangeListener(1, nil)
+ require.EqualError(t, err, "unknown parameter type")
+ require.Equal(t, uint(0), res)
+
+ res, err = wsconn.initStorageChangeListener(2, []interface{}{})
+ require.NoError(t, err)
+ require.Equal(t, uint(1), res)
+ _, msg, err = c.ReadMessage()
+ require.NoError(t, err)
+ require.Equal(t, []byte(`{"jsonrpc":"2.0","result":1,"id":2}`+"\n"), msg)
+
+ res, err = wsconn.initStorageChangeListener(3, []interface{}{"0x26aa"})
+ require.NoError(t, err)
+ require.Equal(t, uint(2), res)
+ _, msg, err = c.ReadMessage()
+ require.NoError(t, err)
+ require.Equal(t, []byte(`{"jsonrpc":"2.0","result":2,"id":3}`+"\n"), msg)
+
+ var testFilters = []interface{}{}
+ var testFilter1 = []interface{}{"0x26aa", "0x26a1"}
+ res, err = wsconn.initStorageChangeListener(4, append(testFilters, testFilter1))
+ require.NoError(t, err)
+ require.Equal(t, uint(3), res)
+ _, msg, err = c.ReadMessage()
+ require.NoError(t, err)
+ require.Equal(t, []byte(`{"jsonrpc":"2.0","result":3,"id":4}`+"\n"), msg)
+
+ var testFilterWrongType = []interface{}{"0x26aa", 1}
+ res, err = wsconn.initStorageChangeListener(5, append(testFilters, testFilterWrongType))
+ require.EqualError(t, err, "unknown parameter type")
+ require.Equal(t, uint(0), res)
+
+ res, err = wsconn.initStorageChangeListener(6, []interface{}{1})
+ require.EqualError(t, err, "unknown parameter type")
+ require.Equal(t, uint(0), res)
+
+ c.WriteMessage(websocket.TextMessage, []byte(`{
+ "jsonrpc": "2.0",
+ "method": "state_subscribeStorage",
+ "params": ["0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9de1e86a9a8c739864cf3cc5ec2bea59fd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"],
+ "id": 7}`))
+ _, msg, err = c.ReadMessage()
+ require.NoError(t, err)
+ require.Equal(t, []byte(`{"jsonrpc":"2.0","result":4,"id":7}`+"\n"), msg)
+
+ // test initBlockListener
+ res, err = wsconn.initBlockListener(1)
+ require.EqualError(t, err, "error BlockAPI not set")
+ require.Equal(t, uint(0), res)
+ _, msg, err = c.ReadMessage()
+ require.NoError(t, err)
+ require.Equal(t, []byte(`{"jsonrpc":"2.0","error":{"code":null,"message":"error BlockAPI not set"},"id":1}`+"\n"), msg)
+
+ wsconn.BlockAPI = new(MockBlockAPI)
+
+ res, err = wsconn.initBlockListener(1)
+ require.NoError(t, err)
+ require.Equal(t, uint(5), res)
+ _, msg, err = c.ReadMessage()
+ require.NoError(t, err)
+ require.Equal(t, []byte(`{"jsonrpc":"2.0","result":5,"id":1}`+"\n"), msg)
+
+ c.WriteMessage(websocket.TextMessage, []byte(`{
+ "jsonrpc": "2.0",
+ "method": "chain_subscribeNewHeads",
+ "params": [],
+ "id": 8
+ }`))
+ _, msg, err = c.ReadMessage()
+ require.NoError(t, err)
+ require.Equal(t, []byte(`{"jsonrpc":"2.0","result":6,"id":8}`+"\n"), msg)
+
+ // test initBlockFinalizedListener
+ wsconn.BlockAPI = nil
+
+ res, err = wsconn.initBlockFinalizedListener(1)
+ require.EqualError(t, err, "error BlockAPI not set")
+ require.Equal(t, uint(0), res)
+ _, msg, err = c.ReadMessage()
+ require.NoError(t, err)
+ require.Equal(t, []byte(`{"jsonrpc":"2.0","error":{"code":null,"message":"error BlockAPI not set"},"id":1}`+"\n"), msg)
+
+ wsconn.BlockAPI = new(MockBlockAPI)
+
+ res, err = wsconn.initBlockFinalizedListener(1)
+ require.NoError(t, err)
+ require.Equal(t, uint(7), res)
+ _, msg, err = c.ReadMessage()
+ require.NoError(t, err)
+ require.Equal(t, []byte(`{"jsonrpc":"2.0","result":7,"id":1}`+"\n"), msg)
+
+ // test initExtrinsicWatch
+ wsconn.CoreAPI = new(MockCoreAPI)
+ wsconn.BlockAPI = nil
+ res, err = wsconn.initExtrinsicWatch(0, []interface{}{"NotHex"})
+ require.EqualError(t, err, "could not byteify non 0x prefixed string")
+ require.Equal(t, uint(0), res)
+
+ res, err = wsconn.initExtrinsicWatch(0, []interface{}{"0x26aa"})
+ require.EqualError(t, err, "error BlockAPI not set")
+ require.Equal(t, uint(0), res)
+
+ wsconn.BlockAPI = new(MockBlockAPI)
+ res, err = wsconn.initExtrinsicWatch(0, []interface{}{"0x26aa"})
+ require.NoError(t, err)
+ require.Equal(t, uint(8), res)
+
+}
+
+type MockStorageAPI struct{}
+
+func (m *MockStorageAPI) GetStorage(_ *common.Hash, key []byte) ([]byte, error) {
+ return nil, nil
+}
+func (m *MockStorageAPI) Entries(_ *common.Hash) (map[string][]byte, error) {
+ return nil, nil
+}
+func (m *MockStorageAPI) GetStorageByBlockHash(_ common.Hash, key []byte) ([]byte, error) {
+ return nil, nil
+}
+func (m *MockStorageAPI) RegisterStorageObserver(observer state.Observer) {
+}
+
+func (m *MockStorageAPI) UnregisterStorageObserver(observer state.Observer) {
+}
+func (m *MockStorageAPI) GetStateRootFromBlock(bhash *common.Hash) (*common.Hash, error) {
+ return nil, nil
+}
+func (m *MockStorageAPI) GetKeysWithPrefix(root *common.Hash, prefix []byte) ([][]byte, error) {
+ return nil, nil
+}
+
+type MockBlockAPI struct {
+}
+
+func (m *MockBlockAPI) GetHeader(hash common.Hash) (*types.Header, error) {
+ return nil, nil
+}
+func (m *MockBlockAPI) BestBlockHash() common.Hash {
+ return common.Hash{}
+}
+func (m *MockBlockAPI) GetBlockByHash(hash common.Hash) (*types.Block, error) {
+ return nil, nil
+}
+func (m *MockBlockAPI) GetBlockHash(blockNumber *big.Int) (*common.Hash, error) {
+ return nil, nil
+}
+func (m *MockBlockAPI) GetFinalizedHash(uint64, uint64) (common.Hash, error) {
+ return common.Hash{}, nil
+}
+func (m *MockBlockAPI) RegisterImportedChannel(ch chan<- *types.Block) (byte, error) {
+ return 0, nil
+}
+func (m *MockBlockAPI) UnregisterImportedChannel(id byte) {
+}
+func (m *MockBlockAPI) RegisterFinalizedChannel(ch chan<- *types.Header) (byte, error) {
+ return 0, nil
+}
+func (m *MockBlockAPI) UnregisterFinalizedChannel(id byte) {}
+
+func (m *MockBlockAPI) GetJustification(hash common.Hash) ([]byte, error) {
+ return make([]byte, 10), nil
+}
+
+func (m *MockBlockAPI) HasJustification(hash common.Hash) (bool, error) {
+ return true, nil
+}
+
+func (m *MockBlockAPI) SubChain(start, end common.Hash) ([]common.Hash, error) {
+ return make([]common.Hash, 0), nil
+}
+
+type MockCoreAPI struct{}
+
+func (m *MockCoreAPI) InsertKey(kp crypto.Keypair) {}
+
+func (m *MockCoreAPI) HasKey(pubKeyStr string, keyType string) (bool, error) {
+ return false, nil
+}
+
+func (m *MockCoreAPI) GetRuntimeVersion(bhash *common.Hash) (runtime.Version, error) {
+ return nil, nil
+}
+
+func (m *MockCoreAPI) IsBlockProducer() bool {
+ return false
+}
+
+func (m *MockCoreAPI) HandleSubmittedExtrinsic(types.Extrinsic) error {
+ return nil
+}
+
+func (m *MockCoreAPI) GetMetadata(bhash *common.Hash) ([]byte, error) {
+ return nil, nil
+}
diff --git a/dot/rpc/websocket_test.go b/dot/rpc/websocket_test.go
index 393c8c8348..4cdd9e271d 100644
--- a/dot/rpc/websocket_test.go
+++ b/dot/rpc/websocket_test.go
@@ -33,6 +33,7 @@ import (
)
var addr = flag.String("addr", "localhost:8546", "http service address")
+
var testCalls = []struct {
call []byte
expected []byte
@@ -43,6 +44,8 @@ var testCalls = []struct {
{[]byte(`{"jsonrpc":"2.0","method":"chain_subscribeNewHeads","params":[],"id":3}`), []byte(`{"jsonrpc":"2.0","result":1,"id":3}` + "\n")},
{[]byte(`{"jsonrpc":"2.0","method":"state_subscribeStorage","params":[],"id":4}`), []byte(`{"jsonrpc":"2.0","result":2,"id":4}` + "\n")},
{[]byte(`{"jsonrpc":"2.0","method":"chain_subscribeFinalizedHeads","params":[],"id":5}`), []byte(`{"jsonrpc":"2.0","result":3,"id":5}` + "\n")},
+ {[]byte(`{"jsonrpc":"2.0","method":"author_submitAndWatchExtrinsic","params":["0x010203"],"id":6}`), []byte("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":null,\"message\":\"Failed to call the `TaggedTransactionQueue_validate_transaction` exported function.\"},\"id\":6}\n")},
+ {[]byte(`{"jsonrpc":"2.0","method":"state_subscribeRuntimeVersion","params":[],"id":7}`), []byte("{\"jsonrpc\":\"2.0\",\"result\":5,\"id\":7}\n")},
}
func TestHTTPServer_ServeHTTP(t *testing.T) {
@@ -145,11 +148,10 @@ func (m *MockStorageAPI) Entries(_ *common.Hash) (map[string][]byte, error) {
func (m *MockStorageAPI) GetStorageByBlockHash(_ common.Hash, key []byte) ([]byte, error) {
return nil, nil
}
-func (m *MockStorageAPI) RegisterStorageChangeChannel(sub state.StorageSubscription) (byte, error) {
- return 0, nil
+func (m *MockStorageAPI) RegisterStorageObserver(observer state.Observer) {
}
-func (m *MockStorageAPI) UnregisterStorageChangeChannel(id byte) {
+func (m *MockStorageAPI) UnregisterStorageObserver(observer state.Observer) {
}
func (m *MockStorageAPI) GetStateRootFromBlock(bhash *common.Hash) (*common.Hash, error) {
return nil, nil
diff --git a/dot/state/storage.go b/dot/state/storage.go
index 73bea66666..e00a2468b0 100644
--- a/dot/state/storage.go
+++ b/dot/state/storage.go
@@ -50,8 +50,8 @@ type StorageState struct {
lock sync.RWMutex
// change notifiers
- changedLock sync.RWMutex
- subscriptions map[byte]*StorageSubscription
+ changedLock sync.RWMutex
+ observerList []Observer
syncing bool
}
@@ -70,11 +70,11 @@ func NewStorageState(db chaindb.Database, blockState *BlockState, t *trie.Trie)
tries[t.MustHash()] = t
return &StorageState{
- blockState: blockState,
- tries: tries,
- baseDB: db,
- db: chaindb.NewTable(db, storagePrefix),
- subscriptions: make(map[byte]*StorageSubscription),
+ blockState: blockState,
+ tries: tries,
+ baseDB: db,
+ db: chaindb.NewTable(db, storagePrefix),
+ observerList: []Observer{},
}, nil
}
@@ -116,11 +116,7 @@ func (s *StorageState) StoreTrie(ts *rtstorage.TrieState) error {
return err
}
- go func() {
- if err := s.notifyStorageSubscriptions(root); err != nil {
- logger.Warn("failed to notify storage subscriptions", "error", err)
- }
- }()
+ go s.notifyAll(root)
return nil
}
@@ -163,55 +159,6 @@ func (s *StorageState) TrieState(root *common.Hash) (*rtstorage.TrieState, error
return curr, nil
}
-// StoreInDB encodes the entire trie and writes it to the DB
-// The key to the DB entry is the root hash of the trie
-func (s *StorageState) notifyStorageSubscriptions(root common.Hash) error {
- s.lock.RLock()
- t := s.tries[root]
- s.lock.RUnlock()
-
- if t == nil {
- return errTrieDoesNotExist(root)
- }
-
- // notify subscribers of database changes
- s.changedLock.Lock()
- defer s.changedLock.Unlock()
-
- for _, sub := range s.subscriptions {
- subRes := &SubscriptionResult{
- Hash: root,
- }
- if len(sub.Filter) == 0 {
- // no filter, so send all changes
- ent := t.Entries()
- for k, v := range ent {
- if k != ":code" {
- // todo, currently we're ignoring :code since this is a lot of data
- kv := &KeyValue{
- Key: common.MustHexToBytes(fmt.Sprintf("0x%x", k)),
- Value: v,
- }
- subRes.Changes = append(subRes.Changes, *kv)
- }
- }
- } else {
- // filter result to include only interested keys
- for k := range sub.Filter {
- value := t.Get(common.MustHexToBytes(k))
- kv := &KeyValue{
- Key: common.MustHexToBytes(k),
- Value: value,
- }
- subRes.Changes = append(subRes.Changes, *kv)
- }
- }
- s.notifyChanged(subRes)
- }
-
- return nil
-}
-
// LoadFromDB loads an encoded trie from the DB where the key is `root`
func (s *StorageState) LoadFromDB(root common.Hash) (*trie.Trie, error) {
t := trie.NewEmptyTrie()
diff --git a/dot/state/storage_notify.go b/dot/state/storage_notify.go
index 6e9fbac8e7..0d632c09ec 100644
--- a/dot/state/storage_notify.go
+++ b/dot/state/storage_notify.go
@@ -16,7 +16,8 @@
package state
import (
- "errors"
+ "fmt"
+ "reflect"
"github.com/ChainSafe/gossamer/lib/common"
)
@@ -33,55 +34,107 @@ type SubscriptionResult struct {
Changes []KeyValue
}
-//StorageSubscription holds data for Subscription to Storage
-type StorageSubscription struct {
- Filter map[string]bool
- Listener chan<- *SubscriptionResult
+// Observer interface defines functions needed for observers, Observer Design Pattern
+type Observer interface {
+ Update(result *SubscriptionResult)
+ GetID() uint
+ GetFilter() map[string][]byte
}
-// RegisterStorageChangeChannel function to register storage change channels
-func (s *StorageState) RegisterStorageChangeChannel(sub StorageSubscription) (byte, error) {
- s.changedLock.RLock()
+// RegisterStorageObserver to add abserver to notification list
+func (s *StorageState) RegisterStorageObserver(o Observer) {
+ s.observerList = append(s.observerList, o)
- if len(s.subscriptions) == 256 {
- return 0, errors.New("storage subscriptions limit reached")
+ // notifyObserver here to send storage value of current state
+ sr, err := s.blockState.BestBlockStateRoot()
+ if err != nil {
+ logger.Debug("error registering storage change channel", "error", err)
+ return
}
-
- var id byte
- for {
- id = generateID()
- if s.subscriptions[id] == nil {
- break
+ go func() {
+ if err := s.notifyObserver(sr, o); err != nil {
+ logger.Warn("failed to notify storage subscriptions", "error", err)
}
- }
+ }()
- s.changedLock.RUnlock()
-
- s.changedLock.Lock()
- s.subscriptions[id] = &sub
- s.changedLock.Unlock()
- return id, nil
}
-// UnregisterStorageChangeChannel removes the storage change notification channel with the given ID.
-// A channel must be unregistered before closing it.
-func (s *StorageState) UnregisterStorageChangeChannel(id byte) {
- s.changedLock.Lock()
- defer s.changedLock.Unlock()
+// UnregisterStorageObserver removes observer from notification list
+func (s *StorageState) UnregisterStorageObserver(o Observer) {
+ s.observerList = s.removeFromSlice(s.observerList, o)
+}
- delete(s.subscriptions, id)
+func (s *StorageState) notifyAll(root common.Hash) {
+ s.changedLock.RLock()
+ defer s.changedLock.RUnlock()
+ for _, observer := range s.observerList {
+ err := s.notifyObserver(root, observer)
+ if err != nil {
+ logger.Warn("failed to notify storage subscriptions", "error", err)
+ }
+ }
}
-func (s *StorageState) notifyChanged(change *SubscriptionResult) {
- if len(s.subscriptions) == 0 {
- return
+func (s *StorageState) notifyObserver(root common.Hash, o Observer) error {
+ t, err := s.TrieState(&root)
+ if err != nil {
+ return err
}
- logger.Trace("notifying changed storage chans...", "chans", s.subscriptions)
+ if t == nil {
+ return errTrieDoesNotExist(root)
+ }
- for _, ch := range s.subscriptions {
- go func(ch chan<- *SubscriptionResult) {
- ch <- change
- }(ch.Listener)
+ subRes := &SubscriptionResult{
+ Hash: root,
+ }
+ if len(o.GetFilter()) == 0 {
+ // no filter, so send all changes
+ ent := t.TrieEntries()
+ for k, v := range ent {
+ if k != ":code" {
+ // todo, currently we're ignoring :code since this is a lot of data
+ kv := &KeyValue{
+ Key: common.MustHexToBytes(fmt.Sprintf("0x%x", k)),
+ Value: v,
+ }
+ subRes.Changes = append(subRes.Changes, *kv)
+ }
+ }
+ } else {
+ // filter result to include only interested keys
+ for k, cachedValue := range o.GetFilter() {
+ value := t.Get(common.MustHexToBytes(k))
+ if !reflect.DeepEqual(cachedValue, value) {
+ kv := &KeyValue{
+ Key: common.MustHexToBytes(k),
+ Value: value,
+ }
+ subRes.Changes = append(subRes.Changes, *kv)
+ o.GetFilter()[k] = value
+ }
+ }
+ }
+
+ if len(subRes.Changes) > 0 {
+ logger.Trace("update observer", "changes", subRes.Changes)
+ go func() {
+ o.Update(subRes)
+ }()
+ }
+
+ return nil
+}
+
+func (s *StorageState) removeFromSlice(observerList []Observer, observerToRemove Observer) []Observer {
+ s.changedLock.Lock()
+ defer s.changedLock.Unlock()
+ observerListLength := len(observerList)
+ for i, observer := range observerList {
+ if observerToRemove.GetID() == observer.GetID() {
+ observerList[i] = observerList[observerListLength-1]
+ return observerList[:observerListLength-1]
+ }
}
+ return observerList
}
diff --git a/dot/state/storage_notify_test.go b/dot/state/storage_notify_test.go
index 4ed90dc073..1a832d1e5b 100644
--- a/dot/state/storage_notify_test.go
+++ b/dot/state/storage_notify_test.go
@@ -30,51 +30,69 @@ import (
"github.com/stretchr/testify/require"
)
-func TestStorageState_RegisterStorageChangeChannel(t *testing.T) {
+type MockStorageObserver struct {
+ id uint
+ filter map[string][]byte
+ lastUpdate *SubscriptionResult
+ m sync.RWMutex
+}
+
+func (m *MockStorageObserver) Update(change *SubscriptionResult) {
+ m.m.Lock()
+ m.lastUpdate = change
+ m.m.Unlock()
+
+}
+func (m *MockStorageObserver) GetID() uint {
+ return m.id
+}
+func (m *MockStorageObserver) GetFilter() map[string][]byte {
+ return m.filter
+}
+
+func TestStorageState_RegisterStorageObserver(t *testing.T) {
ss := newTestStorageState(t)
ts, err := ss.TrieState(nil)
require.NoError(t, err)
- ch := make(chan *SubscriptionResult)
- sub := StorageSubscription{
- Filter: make(map[string]bool),
- Listener: ch,
- }
- id, err := ss.RegisterStorageChangeChannel(sub)
- require.NoError(t, err)
+ observer := &MockStorageObserver{}
+ ss.RegisterStorageObserver(observer)
- defer ss.UnregisterStorageChangeChannel(id)
+ defer ss.UnregisterStorageObserver(observer)
ts.Set([]byte("mackcom"), []byte("wuz here"))
err = ss.StoreTrie(ts)
require.NoError(t, err)
- for i := 0; i < 1; i++ {
- select {
- case <-ch:
- case <-time.After(testMessageTimeout):
- t.Fatal("did not receive storage change message")
- }
+ expectedResult := &SubscriptionResult{
+ Hash: ts.MustRoot(),
+ Changes: []KeyValue{{
+ Key: []byte("mackcom"),
+ Value: []byte("wuz here"),
+ }},
}
+ time.Sleep(time.Millisecond)
+ observer.m.RLock()
+ defer observer.m.RUnlock()
+ require.Equal(t, expectedResult, observer.lastUpdate)
}
-func TestStorageState_RegisterStorageChangeChannel_Multi(t *testing.T) {
- //t.Skip()
+func TestStorageState_RegisterStorageObserver_Multi(t *testing.T) {
ss := newTestStorageState(t)
ts, err := ss.TrieState(nil)
require.NoError(t, err)
num := 5
- chs := make([]chan *SubscriptionResult, num)
- ids := make([]byte, num)
+
+ var observers []*MockStorageObserver
for i := 0; i < num; i++ {
- chs[i] = make(chan *SubscriptionResult)
- sub := StorageSubscription{
- Listener: chs[i],
+ observer := &MockStorageObserver{
+ id: uint(i),
}
- ids[i], err = ss.RegisterStorageChangeChannel(sub)
+ observers = append(observers, observer)
+ ss.RegisterStorageObserver(observer)
require.NoError(t, err)
}
@@ -86,33 +104,22 @@ func TestStorageState_RegisterStorageChangeChannel_Multi(t *testing.T) {
err = ss.StoreTrie(ts)
require.NoError(t, err)
- var wg sync.WaitGroup
- wg.Add(num)
-
- for i, ch := range chs {
-
- go func(i int, ch chan *SubscriptionResult) {
- select {
- case c := <-ch:
- require.NotNil(t, c.Hash)
- require.Equal(t, key1, c.Changes[0].Key)
- require.Equal(t, value1, c.Changes[0].Value)
- case <-time.After(testMessageTimeout):
- t.Error("did not receive storage change: ch=", i)
- }
- wg.Done()
- }(i, ch)
+ time.Sleep(time.Millisecond * 10)
+ for _, observer := range observers {
+ observer.m.RLock()
+ require.NotNil(t, observer.lastUpdate.Hash)
+ require.Equal(t, key1, observer.lastUpdate.Changes[0].Key)
+ require.Equal(t, value1, observer.lastUpdate.Changes[0].Value)
+ observer.m.RUnlock()
}
- wg.Wait()
-
- for _, id := range ids {
- ss.UnregisterStorageChangeChannel(id)
+ for _, observer := range observers {
+ ss.UnregisterStorageObserver(observer)
}
}
-func TestStorageState_RegisterStorageChangeChannel_Multi_Filter(t *testing.T) {
+func TestStorageState_RegisterStorageObserver_Multi_Filter(t *testing.T) {
ss := newTestStorageState(t)
ts, err := ss.TrieState(nil)
require.NoError(t, err)
@@ -121,51 +128,35 @@ func TestStorageState_RegisterStorageChangeChannel_Multi_Filter(t *testing.T) {
value1 := []byte("value1")
num := 5
- chs := make([]chan *SubscriptionResult, num)
- ids := make([]byte, num)
- subFilter := make(map[string]bool)
- subFilter[common.BytesToHex(key1)] = true
+ var observers []*MockStorageObserver
for i := 0; i < num; i++ {
- chs[i] = make(chan *SubscriptionResult)
- sub := StorageSubscription{
- Filter: subFilter,
- Listener: chs[i],
+ observer := &MockStorageObserver{
+ id: uint(i),
+ filter: map[string][]byte{
+ common.BytesToHex(key1): {},
+ },
}
- ids[i], err = ss.RegisterStorageChangeChannel(sub)
- require.NoError(t, err)
+ observers = append(observers, observer)
+ ss.RegisterStorageObserver(observer)
}
ts.Set(key1, value1)
-
err = ss.StoreTrie(ts)
require.NoError(t, err)
- time.Sleep(time.Millisecond * 500)
-
- var wg sync.WaitGroup
- wg.Add(num)
-
- for i, ch := range chs {
-
- go func(i int, ch chan *SubscriptionResult) {
- select {
- case c := <-ch:
- require.NotNil(t, c.Hash)
- require.Equal(t, key1, c.Changes[0].Key)
- require.Equal(t, value1, c.Changes[0].Value)
- wg.Done()
- case <-time.After(testMessageTimeout):
- t.Error("did not receive storage change: ch=", i)
- }
- }(i, ch)
+ time.Sleep(time.Millisecond * 10)
+ for _, observer := range observers {
+ observer.m.RLock()
+ require.NotNil(t, observer.lastUpdate.Hash)
+ require.Equal(t, key1, observer.lastUpdate.Changes[0].Key)
+ require.Equal(t, value1, observer.lastUpdate.Changes[0].Value)
+ observer.m.RUnlock()
}
- wg.Wait()
-
- for _, id := range ids {
- ss.UnregisterStorageChangeChannel(id)
+ for _, observer := range observers {
+ ss.UnregisterStorageObserver(observer)
}
}
diff --git a/go.mod b/go.mod
index e16612d2e1..70bb9c6b12 100644
--- a/go.mod
+++ b/go.mod
@@ -58,7 +58,7 @@ require (
golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 // indirect
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect
- golang.org/x/tools v0.0.0-20200221224223-e1da425f72fd // indirect
+ golang.org/x/tools v0.0.0-20200221224223-e1da425f72fd
google.golang.org/appengine v1.6.5 // indirect
google.golang.org/protobuf v1.25.0
)
diff --git a/tests/polkadotjs_test/package-lock.json b/tests/polkadotjs_test/package-lock.json
index 5c28d80371..dd4cb1c748 100644
--- a/tests/polkadotjs_test/package-lock.json
+++ b/tests/polkadotjs_test/package-lock.json
@@ -5,164 +5,164 @@
"requires": true,
"dependencies": {
"@babel/runtime": {
- "version": "7.13.8",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.8.tgz",
- "integrity": "sha512-CwQljpw6qSayc0fRG1soxHAKs1CnQMOChm4mlQP6My0kf9upVGizj/KhlTTgyUnETmHpcUXjaluNAkteRFuafg==",
+ "version": "7.13.10",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz",
+ "integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==",
"requires": {
"regenerator-runtime": "^0.13.4"
}
},
"@polkadot/api": {
- "version": "2.8.1",
- "resolved": "https://registry.npmjs.org/@polkadot/api/-/api-2.8.1.tgz",
- "integrity": "sha512-IvR8aTUzd3759tJVkHEsnpXqdvv72mTkST3poO2/v30GusqTH6KQDWhQy7MhgYjElk9hLIPZRsmA62WVOlSG2Q==",
- "requires": {
- "@babel/runtime": "^7.12.5",
- "@polkadot/api-derive": "2.8.1",
- "@polkadot/keyring": "^4.2.1",
- "@polkadot/metadata": "2.8.1",
- "@polkadot/rpc-core": "2.8.1",
- "@polkadot/rpc-provider": "2.8.1",
- "@polkadot/types": "2.8.1",
- "@polkadot/types-known": "2.8.1",
- "@polkadot/util": "^4.2.1",
- "@polkadot/util-crypto": "^4.2.1",
+ "version": "3.11.1",
+ "resolved": "https://registry.npmjs.org/@polkadot/api/-/api-3.11.1.tgz",
+ "integrity": "sha512-VqEh2n13ESLxnTUKujUfZ3Spct+lTycNgrX+IWD7/f05GsMwhCZLYtt708K8nqGFH2OKDl8xzwuGCvRN/05U1Q==",
+ "requires": {
+ "@babel/runtime": "^7.13.8",
+ "@polkadot/api-derive": "3.11.1",
+ "@polkadot/keyring": "^5.9.2",
+ "@polkadot/metadata": "3.11.1",
+ "@polkadot/rpc-core": "3.11.1",
+ "@polkadot/rpc-provider": "3.11.1",
+ "@polkadot/types": "3.11.1",
+ "@polkadot/types-known": "3.11.1",
+ "@polkadot/util": "^5.9.2",
+ "@polkadot/util-crypto": "^5.9.2",
+ "@polkadot/x-rxjs": "^5.9.2",
"bn.js": "^4.11.9",
- "eventemitter3": "^4.0.7",
- "rxjs": "^6.6.3"
+ "eventemitter3": "^4.0.7"
}
},
"@polkadot/api-derive": {
- "version": "2.8.1",
- "resolved": "https://registry.npmjs.org/@polkadot/api-derive/-/api-derive-2.8.1.tgz",
- "integrity": "sha512-5oJ7V7yRHHSSnWQ/l3MQQ8+ki/g+v4NbqgI/FTOIUQl7Ja1lPwjKYpqXgP7EGob+pcdFj6VRqywzAOkVA730tw==",
- "requires": {
- "@babel/runtime": "^7.12.5",
- "@polkadot/api": "2.8.1",
- "@polkadot/rpc-core": "2.8.1",
- "@polkadot/types": "2.8.1",
- "@polkadot/util": "^4.2.1",
- "@polkadot/util-crypto": "^4.2.1",
- "bn.js": "^4.11.9",
- "memoizee": "^0.4.14",
- "rxjs": "^6.6.3"
+ "version": "3.11.1",
+ "resolved": "https://registry.npmjs.org/@polkadot/api-derive/-/api-derive-3.11.1.tgz",
+ "integrity": "sha512-/v/fNSivgucQrDJvwLU17u8iZ0oQipQzgpofCJGQhRv8OaSv/E9g5EXcHJ1ri/Ozevgu5cPmGs96lLkQaPieAw==",
+ "requires": {
+ "@babel/runtime": "^7.13.8",
+ "@polkadot/api": "3.11.1",
+ "@polkadot/rpc-core": "3.11.1",
+ "@polkadot/types": "3.11.1",
+ "@polkadot/util": "^5.9.2",
+ "@polkadot/util-crypto": "^5.9.2",
+ "@polkadot/x-rxjs": "^5.9.2",
+ "bn.js": "^4.11.9"
}
},
"@polkadot/keyring": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/@polkadot/keyring/-/keyring-4.2.1.tgz",
- "integrity": "sha512-8kH8jXSIA3I2Gn96o7KjGoLBa7fmc2iB/VKOmEEcMCgJR32HyE8YbeXwc/85OQCheQjG4rJA3RxPQ4CsTsjO7w==",
+ "version": "5.9.2",
+ "resolved": "https://registry.npmjs.org/@polkadot/keyring/-/keyring-5.9.2.tgz",
+ "integrity": "sha512-h9AhrzyUmludbmo0ixRFLEyRJvUc7GTl5koSBrG0uv+9Yn0I/7YRgAKn3zKcUVZyvgoLvzZnBFwekGbdFcl9Yg==",
"requires": {
- "@babel/runtime": "^7.12.5",
- "@polkadot/util": "4.2.1",
- "@polkadot/util-crypto": "4.2.1"
+ "@babel/runtime": "^7.13.8",
+ "@polkadot/util": "5.9.2",
+ "@polkadot/util-crypto": "5.9.2"
}
},
"@polkadot/metadata": {
- "version": "2.8.1",
- "resolved": "https://registry.npmjs.org/@polkadot/metadata/-/metadata-2.8.1.tgz",
- "integrity": "sha512-tJ+hTXsvve1f2pziPGp/nELK+W/xvMsc2xGgoVwccxv1mPFNSny8RPDl7Wgmli0PPztXG6eBnLvWt4FXYnp7vA==",
- "requires": {
- "@babel/runtime": "^7.12.5",
- "@polkadot/types": "2.8.1",
- "@polkadot/types-known": "2.8.1",
- "@polkadot/util": "^4.2.1",
- "@polkadot/util-crypto": "^4.2.1",
+ "version": "3.11.1",
+ "resolved": "https://registry.npmjs.org/@polkadot/metadata/-/metadata-3.11.1.tgz",
+ "integrity": "sha512-Z3KtOTX2kU+vvbRDiGY+qyPpF/4xTpnUipoNGijIGQ/EWWcgrm8sSgPzZQhHCfgIqM+jq3g9GvPMYeQp2Yy3ng==",
+ "requires": {
+ "@babel/runtime": "^7.13.8",
+ "@polkadot/types": "3.11.1",
+ "@polkadot/types-known": "3.11.1",
+ "@polkadot/util": "^5.9.2",
+ "@polkadot/util-crypto": "^5.9.2",
"bn.js": "^4.11.9"
}
},
"@polkadot/networks": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/@polkadot/networks/-/networks-4.2.1.tgz",
- "integrity": "sha512-T1tg0V0uG09Vdce2O4KfEcWO3/fZh4VYt0bmJ6iPwC+x6yv939X2BKvuFTDDVNT3fqBpGzWQlwiTXYQ15o9bGA==",
+ "version": "5.9.2",
+ "resolved": "https://registry.npmjs.org/@polkadot/networks/-/networks-5.9.2.tgz",
+ "integrity": "sha512-JQyXJDJTZKQtn8y3HBHWDhiBfijhpiXjVEhY+fKvFcQ82TaKmzhnipYX0EdBoopZbuxpn/BJy6Y1Y/3y85EC+g==",
"requires": {
- "@babel/runtime": "^7.12.5"
+ "@babel/runtime": "^7.13.8"
}
},
"@polkadot/rpc-core": {
- "version": "2.8.1",
- "resolved": "https://registry.npmjs.org/@polkadot/rpc-core/-/rpc-core-2.8.1.tgz",
- "integrity": "sha512-tMSH2D5wu28UMhLIjWxZ7br0HRC0T7crYu/BSBE8m3GzLJU4mwsygn2VLDVxQOz4DvHvWh+xQzd2QFc/z02SQw==",
+ "version": "3.11.1",
+ "resolved": "https://registry.npmjs.org/@polkadot/rpc-core/-/rpc-core-3.11.1.tgz",
+ "integrity": "sha512-8KTEZ/c2/TrsTOrrqxxNbyjO5P/033R/yTDgwqL0gwmF+ApnH3vB65YfKqaxn+rBWOMQS0jQhF6KZdtXvRcuYg==",
"requires": {
- "@babel/runtime": "^7.12.5",
- "@polkadot/metadata": "2.8.1",
- "@polkadot/rpc-provider": "2.8.1",
- "@polkadot/types": "2.8.1",
- "@polkadot/util": "^4.2.1",
- "memoizee": "^0.4.14",
- "rxjs": "^6.6.3"
+ "@babel/runtime": "^7.13.8",
+ "@polkadot/metadata": "3.11.1",
+ "@polkadot/rpc-provider": "3.11.1",
+ "@polkadot/types": "3.11.1",
+ "@polkadot/util": "^5.9.2",
+ "@polkadot/x-rxjs": "^5.9.2"
}
},
"@polkadot/rpc-provider": {
- "version": "2.8.1",
- "resolved": "https://registry.npmjs.org/@polkadot/rpc-provider/-/rpc-provider-2.8.1.tgz",
- "integrity": "sha512-PtLZcbNMx6+sN04f4T+j3fqJPYG3qsPX+k1DU5FFDUZ3GVRphfyXmswjbwmH9nkCyr04eBGLb1M1EipsqiP8Ig==",
- "requires": {
- "@babel/runtime": "^7.12.5",
- "@polkadot/types": "2.8.1",
- "@polkadot/util": "^4.2.1",
- "@polkadot/util-crypto": "^4.2.1",
- "@polkadot/x-fetch": "^4.2.1",
- "@polkadot/x-ws": "^4.2.1",
+ "version": "3.11.1",
+ "resolved": "https://registry.npmjs.org/@polkadot/rpc-provider/-/rpc-provider-3.11.1.tgz",
+ "integrity": "sha512-5OKh3rAg8l10M+tGLCoxhEoH9uEtK0ehJfOHUmdtwmwIk5aBFZ/ZTeiDkPM+/l84PCzYmp2uzO+YNsyMWUoVLw==",
+ "requires": {
+ "@babel/runtime": "^7.13.8",
+ "@polkadot/types": "3.11.1",
+ "@polkadot/util": "^5.9.2",
+ "@polkadot/util-crypto": "^5.9.2",
+ "@polkadot/x-fetch": "^5.9.2",
+ "@polkadot/x-global": "^5.9.2",
+ "@polkadot/x-ws": "^5.9.2",
"bn.js": "^4.11.9",
"eventemitter3": "^4.0.7"
}
},
"@polkadot/types": {
- "version": "2.8.1",
- "resolved": "https://registry.npmjs.org/@polkadot/types/-/types-2.8.1.tgz",
- "integrity": "sha512-D7K2wG7xytkMJ0s6W/JwzU4LPiQdFThqmRY+kXdbXrYF1UdiUkiS5MMjUUG9CseRITYUigtF6D6B/PiOv9zupQ==",
- "requires": {
- "@babel/runtime": "^7.12.5",
- "@polkadot/metadata": "2.8.1",
- "@polkadot/util": "^4.2.1",
- "@polkadot/util-crypto": "^4.2.1",
+ "version": "3.11.1",
+ "resolved": "https://registry.npmjs.org/@polkadot/types/-/types-3.11.1.tgz",
+ "integrity": "sha512-+BWsmveYVkLFx/csvPmU+NhNFhf+0srAt2d0f+7y663nitc/sng1AcEDPbrbXHSQVyPdvI20Mh4Escl4aR+TLw==",
+ "requires": {
+ "@babel/runtime": "^7.13.8",
+ "@polkadot/metadata": "3.11.1",
+ "@polkadot/util": "^5.9.2",
+ "@polkadot/util-crypto": "^5.9.2",
+ "@polkadot/x-rxjs": "^5.9.2",
"@types/bn.js": "^4.11.6",
- "bn.js": "^4.11.9",
- "memoizee": "^0.4.14",
- "rxjs": "^6.6.3"
+ "bn.js": "^4.11.9"
}
},
"@polkadot/types-known": {
- "version": "2.8.1",
- "resolved": "https://registry.npmjs.org/@polkadot/types-known/-/types-known-2.8.1.tgz",
- "integrity": "sha512-aTriYfu5l8Fz73Ti8rT0q2DfwMIk4eLTqb3VBDR21XcAbjVxZHc24jdhnnnbc6RxvGOg2ertrN9fTz3xhvtPyg==",
- "requires": {
- "@babel/runtime": "^7.12.5",
- "@polkadot/types": "2.8.1",
- "@polkadot/util": "^4.2.1",
+ "version": "3.11.1",
+ "resolved": "https://registry.npmjs.org/@polkadot/types-known/-/types-known-3.11.1.tgz",
+ "integrity": "sha512-ImAxyCdqblmlXaMlgvuXZ6wzZgOYgE40FgWaYRJpFXRGJLDwtcJcpVI+7m/ns5dJ3WujboEMOHVR1HPpquw8Jw==",
+ "requires": {
+ "@babel/runtime": "^7.13.8",
+ "@polkadot/networks": "^5.9.2",
+ "@polkadot/types": "3.11.1",
+ "@polkadot/util": "^5.9.2",
"bn.js": "^4.11.9"
}
},
"@polkadot/util": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/@polkadot/util/-/util-4.2.1.tgz",
- "integrity": "sha512-eO/IFbSDjqVPPWPnARDFydy2Kt992Th+8ByleTkCRqWk0aNYaseO1pGKNdwrYbLfUR3JlyWqvJ60lITeS+qAfQ==",
+ "version": "5.9.2",
+ "resolved": "https://registry.npmjs.org/@polkadot/util/-/util-5.9.2.tgz",
+ "integrity": "sha512-p225NJusnXeu7i2iAb8HAGWiMOUAnRaIyblIjJ4F89ZFZZ4amyliGxe5gKcyjRgxAJ44WdKyBLl/8L3rNv8hmQ==",
"requires": {
- "@babel/runtime": "^7.12.5",
- "@polkadot/x-textdecoder": "4.2.1",
- "@polkadot/x-textencoder": "4.2.1",
+ "@babel/runtime": "^7.13.8",
+ "@polkadot/x-textdecoder": "5.9.2",
+ "@polkadot/x-textencoder": "5.9.2",
"@types/bn.js": "^4.11.6",
"bn.js": "^4.11.9",
"camelcase": "^5.3.1",
- "ip-regex": "^4.2.0"
+ "ip-regex": "^4.3.0"
}
},
"@polkadot/util-crypto": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/@polkadot/util-crypto/-/util-crypto-4.2.1.tgz",
- "integrity": "sha512-U1rCdzBQxVTA854HRpt2d4InDnPCfHD15JiWAwIzjBvq7i59EcTbVSqV02fcwet/KpmT3XYa25xoiff+alzCBA==",
- "requires": {
- "@babel/runtime": "^7.12.5",
- "@polkadot/networks": "4.2.1",
- "@polkadot/util": "4.2.1",
- "@polkadot/wasm-crypto": "^2.0.1",
- "@polkadot/x-randomvalues": "4.2.1",
+ "version": "5.9.2",
+ "resolved": "https://registry.npmjs.org/@polkadot/util-crypto/-/util-crypto-5.9.2.tgz",
+ "integrity": "sha512-d8CW2grI3gWi6d/brmcZQWaMPHqQq5z7VcM74/v8D2KZ+hPYL3B0Jn8zGL1vtgMz2qdpWrZdAe89LBC8BvM9bw==",
+ "requires": {
+ "@babel/runtime": "^7.13.8",
+ "@polkadot/networks": "5.9.2",
+ "@polkadot/util": "5.9.2",
+ "@polkadot/wasm-crypto": "^3.2.4",
+ "@polkadot/x-randomvalues": "5.9.2",
"base-x": "^3.0.8",
+ "base64-js": "^1.5.1",
"blakejs": "^1.1.0",
"bn.js": "^4.11.9",
"create-hash": "^1.2.0",
- "elliptic": "^6.5.3",
+ "elliptic": "^6.5.4",
"hash.js": "^1.1.7",
"js-sha3": "^0.8.0",
"scryptsy": "^2.1.0",
@@ -171,52 +171,97 @@
}
},
"@polkadot/wasm-crypto": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto/-/wasm-crypto-2.0.1.tgz",
- "integrity": "sha512-Vb0q4NToCRHXYJwhLWc4NTy77+n1dtJmkiE1tt8j1pmY4IJ4UL25yBxaS8NCS1LGqofdUYK1wwgrHiq5A78PFA=="
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto/-/wasm-crypto-3.2.4.tgz",
+ "integrity": "sha512-poeRU91zzZza0ZectT63vBiAqh6DsHCyd3Ogx1U6jsYiRa0yuECMWJx1onvnseDW4tIqsC8vZ/9xHXWwhjTAVg==",
+ "requires": {
+ "@babel/runtime": "^7.13.7",
+ "@polkadot/wasm-crypto-asmjs": "^3.2.4",
+ "@polkadot/wasm-crypto-wasm": "^3.2.4"
+ }
+ },
+ "@polkadot/wasm-crypto-asmjs": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto-asmjs/-/wasm-crypto-asmjs-3.2.4.tgz",
+ "integrity": "sha512-fgN26iL+Pbb35OYsDIRHC74Xnwde+A5u3OjEcQ9zJhM391eOTuKsQ2gyC9TLNAKqeYH8pxsa27yjRO71We7FUA==",
+ "requires": {
+ "@babel/runtime": "^7.13.7"
+ }
+ },
+ "@polkadot/wasm-crypto-wasm": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto-wasm/-/wasm-crypto-wasm-3.2.4.tgz",
+ "integrity": "sha512-Q/3IEpoo7vkTzg40GxehRK000A9oBgjbh/uWCNQ8cMqWLYYCfzZy4NIzw8szpxNiSiGfGL0iZlP4ZSx2ZqEe2g==",
+ "requires": {
+ "@babel/runtime": "^7.13.7"
+ }
},
"@polkadot/x-fetch": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/@polkadot/x-fetch/-/x-fetch-4.2.1.tgz",
- "integrity": "sha512-dfVYvCQQXo2AgoWPi4jQp47eIMjAi6glQQ8Y1OsK4sCqmX7BSkNl9ONUKQuH27oi0BkJ/BL7fwDg55JeB5QrKg==",
+ "version": "5.9.2",
+ "resolved": "https://registry.npmjs.org/@polkadot/x-fetch/-/x-fetch-5.9.2.tgz",
+ "integrity": "sha512-Nx7GfyOmMdqn5EX+wf6PnIwleQX+aGqzdbYhozNLF54IoNFLHLOs6hCYnBlKbmM1WyukMZMjg2YxyZRQWcHKPQ==",
"requires": {
- "@babel/runtime": "^7.12.5",
- "@types/node-fetch": "^2.5.7",
+ "@babel/runtime": "^7.13.8",
+ "@polkadot/x-global": "5.9.2",
+ "@types/node-fetch": "^2.5.8",
+ "node-fetch": "^2.6.1"
+ }
+ },
+ "@polkadot/x-global": {
+ "version": "5.9.2",
+ "resolved": "https://registry.npmjs.org/@polkadot/x-global/-/x-global-5.9.2.tgz",
+ "integrity": "sha512-wpY6IAOZMGiJQa8YMm7NeTLi9bwnqqVauR+v7HwyrssnGPuYX8heb6BQLOnnnPh/EK0+M8zNtwRBU48ez0/HOg==",
+ "requires": {
+ "@babel/runtime": "^7.13.8",
+ "@types/node-fetch": "^2.5.8",
"node-fetch": "^2.6.1"
}
},
"@polkadot/x-randomvalues": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/@polkadot/x-randomvalues/-/x-randomvalues-4.2.1.tgz",
- "integrity": "sha512-eOfz/KnHYFVl9l0zlhlwomKMzFASgolaQV6uXSN38np+99/+F38wlbOSXFbfZ5H3vmMCt4y/UUTLtoGV/44yLg==",
+ "version": "5.9.2",
+ "resolved": "https://registry.npmjs.org/@polkadot/x-randomvalues/-/x-randomvalues-5.9.2.tgz",
+ "integrity": "sha512-Zv+eXSP3oBImMnB82y05Doo0A96WUFsQDbnLHI3jFHioIg848cL0nndB9TgBwPaFkZ2oiwoHEC8yxqNI6/jkzQ==",
"requires": {
- "@babel/runtime": "^7.12.5"
+ "@babel/runtime": "^7.13.8",
+ "@polkadot/x-global": "5.9.2"
+ }
+ },
+ "@polkadot/x-rxjs": {
+ "version": "5.9.2",
+ "resolved": "https://registry.npmjs.org/@polkadot/x-rxjs/-/x-rxjs-5.9.2.tgz",
+ "integrity": "sha512-cuF4schclspOfAqEPvbcA3aQ9d3TBy2ORZ8YehxD0ZSHWJNhefHDIUDgS5T3NtPhSKgcEmSlI5TfVfgGFxgVMg==",
+ "requires": {
+ "@babel/runtime": "^7.13.8",
+ "rxjs": "^6.6.6"
}
},
"@polkadot/x-textdecoder": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/@polkadot/x-textdecoder/-/x-textdecoder-4.2.1.tgz",
- "integrity": "sha512-B5t20PryMKr7kdd7q+kmzJPU01l28ZDD06cQ/ZFkybI7avI6PIz/U33ctXxiHOatbBRO6Ez8uzrWd3JmaQ2bGQ==",
+ "version": "5.9.2",
+ "resolved": "https://registry.npmjs.org/@polkadot/x-textdecoder/-/x-textdecoder-5.9.2.tgz",
+ "integrity": "sha512-MCkgITwGY3tG0UleDkBJEoiKGk/YWYwMM5OR6fNo07RymHRtJ8OLJC+Sej9QD05yz6TIhFaaRRYzmtungIcwTw==",
"requires": {
- "@babel/runtime": "^7.12.5"
+ "@babel/runtime": "^7.13.8",
+ "@polkadot/x-global": "5.9.2"
}
},
"@polkadot/x-textencoder": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/@polkadot/x-textencoder/-/x-textencoder-4.2.1.tgz",
- "integrity": "sha512-EHc6RS9kjdP28q6EYlSgHF2MrJCdOTc5EVlqHL7V1UKLh3vD6QaWGYBwbzXNFPXO3RYPO/DKYCu4RxAVSM1OOg==",
+ "version": "5.9.2",
+ "resolved": "https://registry.npmjs.org/@polkadot/x-textencoder/-/x-textencoder-5.9.2.tgz",
+ "integrity": "sha512-IjdLY3xy0nUfps1Bdi0tRxAX7X081YyoiSWExwqUkChdcYGMqMe3T2wqrrt9qBr2IkW8O/tlfYBiZXdII0YCcw==",
"requires": {
- "@babel/runtime": "^7.12.5"
+ "@babel/runtime": "^7.13.8",
+ "@polkadot/x-global": "5.9.2"
}
},
"@polkadot/x-ws": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/@polkadot/x-ws/-/x-ws-4.2.1.tgz",
- "integrity": "sha512-7L1ve2rshBFI/00/0zkX1k0OP/rSD6Tp0Mj/GSg2UvnsmUb2Bb3OpwUJ4aTDr1En6OVGWj9c0fNO0tZR7rtoYA==",
+ "version": "5.9.2",
+ "resolved": "https://registry.npmjs.org/@polkadot/x-ws/-/x-ws-5.9.2.tgz",
+ "integrity": "sha512-6A/cteC0B3hm64/xG6DNG8qGsHAXJgAy9wjcB38qnoJGYl12hysIFjPeHD+V0W/LOl9payW6kpZzhisLlVOZpQ==",
"requires": {
- "@babel/runtime": "^7.12.5",
+ "@babel/runtime": "^7.13.8",
+ "@polkadot/x-global": "5.9.2",
"@types/websocket": "^1.0.1",
- "websocket": "^1.0.32"
+ "websocket": "^1.0.33"
}
},
"@types/bn.js": {
@@ -228,23 +273,23 @@
}
},
"@types/node": {
- "version": "14.14.31",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.31.tgz",
- "integrity": "sha512-vFHy/ezP5qI0rFgJ7aQnjDXwAMrG0KqqIH7tQG5PPv3BWBayOPIQNBjVc/P6hhdZfMx51REc6tfDNXHUio893g=="
+ "version": "14.14.41",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.41.tgz",
+ "integrity": "sha512-dueRKfaJL4RTtSa7bWeTK1M+VH+Gns73oCgzvYfHZywRCoPSd8EkXBL0mZ9unPTveBn+D9phZBaxuzpwjWkW0g=="
},
"@types/node-fetch": {
- "version": "2.5.8",
- "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.8.tgz",
- "integrity": "sha512-fbjI6ja0N5ZA8TV53RUqzsKNkl9fv8Oj3T7zxW7FGv1GSH7gwJaNF8dzCjrqKaxKeUpTz4yT1DaJFq/omNpGfw==",
+ "version": "2.5.10",
+ "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.10.tgz",
+ "integrity": "sha512-IpkX0AasN44hgEad0gEF/V6EgR5n69VEqPEgnmoM8GsIGro3PowbWs4tR6IhxUTyPLpOn+fiGG6nrQhcmoCuIQ==",
"requires": {
"@types/node": "*",
"form-data": "^3.0.0"
}
},
"@types/websocket": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@types/websocket/-/websocket-1.0.1.tgz",
- "integrity": "sha512-f5WLMpezwVxCLm1xQe/kdPpQIOmL0TXYx2O15VYfYzc7hTIdxiOoOvez+McSIw3b7z/1zGovew9YSL7+h4h7/Q==",
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@types/websocket/-/websocket-1.0.2.tgz",
+ "integrity": "sha512-B5m9aq7cbbD/5/jThEr33nUY8WEfVi6A2YKCTOvw5Ldy7mtsOkqRvGjnzy6g7iMMDsgu7xREuCzqATLDLQVKcQ==",
"requires": {
"@types/node": "*"
}
@@ -317,6 +362,11 @@
"safe-buffer": "^5.0.1"
}
},
+ "base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
+ },
"binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
@@ -604,13 +654,6 @@
"es6-iterator": "~2.0.3",
"es6-symbol": "~3.1.3",
"next-tick": "~1.0.0"
- },
- "dependencies": {
- "next-tick": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
- "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
- }
}
},
"es6-iterator": {
@@ -632,17 +675,6 @@
"ext": "^1.1.2"
}
},
- "es6-weak-map": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz",
- "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==",
- "requires": {
- "d": "1",
- "es5-ext": "^0.10.46",
- "es6-iterator": "^2.0.3",
- "es6-symbol": "^3.1.1"
- }
- },
"escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@@ -655,15 +687,6 @@
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"dev": true
},
- "event-emitter": {
- "version": "0.3.5",
- "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz",
- "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=",
- "requires": {
- "d": "1",
- "es5-ext": "~0.10.14"
- }
- },
"eventemitter3": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
@@ -678,9 +701,9 @@
},
"dependencies": {
"type": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/type/-/type-2.3.0.tgz",
- "integrity": "sha512-rgPIqOdfK/4J9FhiVrZ3cveAjRRo5rsQBAIhnylX874y1DX/kEKSVdLsnuHB6l1KTjHyU01VjiMBHgU2adejyg=="
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/type/-/type-2.5.0.tgz",
+ "integrity": "sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw=="
}
}
},
@@ -876,11 +899,6 @@
"integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
"dev": true
},
- "is-promise": {
- "version": "2.2.2",
- "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz",
- "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ=="
- },
"is-typedarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
@@ -924,14 +942,6 @@
"chalk": "^4.0.0"
}
},
- "lru-queue": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz",
- "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=",
- "requires": {
- "es5-ext": "~0.10.2"
- }
- },
"md5.js": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
@@ -942,32 +952,17 @@
"safe-buffer": "^5.1.2"
}
},
- "memoizee": {
- "version": "0.4.15",
- "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.15.tgz",
- "integrity": "sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ==",
- "requires": {
- "d": "^1.0.1",
- "es5-ext": "^0.10.53",
- "es6-weak-map": "^2.0.3",
- "event-emitter": "^0.3.5",
- "is-promise": "^2.2.2",
- "lru-queue": "^0.1.0",
- "next-tick": "^1.1.0",
- "timers-ext": "^0.1.7"
- }
- },
"mime-db": {
- "version": "1.46.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.46.0.tgz",
- "integrity": "sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ=="
+ "version": "1.47.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.47.0.tgz",
+ "integrity": "sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw=="
},
"mime-types": {
- "version": "2.1.29",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.29.tgz",
- "integrity": "sha512-Y/jMt/S5sR9OaqteJtslsFZKWOIIqMACsJSiHghlCAyhf7jfVYjKBmLiX8OgpWeW+fjJ2b+Az69aPFPkUOY6xQ==",
+ "version": "2.1.30",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.30.tgz",
+ "integrity": "sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==",
"requires": {
- "mime-db": "1.46.0"
+ "mime-db": "1.47.0"
}
},
"minimalistic-assert": {
@@ -1059,9 +1054,9 @@
"dev": true
},
"next-tick": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz",
- "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
+ "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
},
"node-fetch": {
"version": "2.6.1",
@@ -1179,9 +1174,9 @@
}
},
"rxjs": {
- "version": "6.6.6",
- "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.6.tgz",
- "integrity": "sha512-/oTwee4N4iWzAMAL9xdGKjkEHmIwupR3oXbQjCKywF1BeFohswF3vZdogbmEF6pZkOsXTzWkrZszrWpQTByYVg==",
+ "version": "6.6.7",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
+ "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
"requires": {
"tslib": "^1.9.0"
}
@@ -1256,15 +1251,6 @@
"has-flag": "^4.0.0"
}
},
- "timers-ext": {
- "version": "0.1.7",
- "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz",
- "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==",
- "requires": {
- "es5-ext": "~0.10.46",
- "next-tick": "1"
- }
- },
"to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -1317,9 +1303,9 @@
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
},
"websocket": {
- "version": "1.0.33",
- "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.33.tgz",
- "integrity": "sha512-XwNqM2rN5eh3G2CUQE3OHZj+0xfdH42+OFK6LdC2yqiC0YU8e5UK0nYre220T0IyyN031V/XOvtHvXozvJYFWA==",
+ "version": "1.0.34",
+ "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz",
+ "integrity": "sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==",
"requires": {
"bufferutil": "^4.0.1",
"debug": "^2.2.0",
diff --git a/tests/polkadotjs_test/test_transaction.js b/tests/polkadotjs_test/test_transaction.js
index 5e9373e036..ef7e448cd5 100644
--- a/tests/polkadotjs_test/test_transaction.js
+++ b/tests/polkadotjs_test/test_transaction.js
@@ -20,10 +20,8 @@ async function main() {
// bob 5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty
const transfer = await api.tx.balances.transfer(bobKey.address, 12345)
- .signAndSend(aliceKey, {era: 0, blockHash: '0x64597c55a052d484d9ff357266be326f62573bb4fbdbb3cd49f219396fcebf78', blockNumber:0, genesisHash: '0x64597c55a052d484d9ff357266be326f62573bb4fbdbb3cd49f219396fcebf78', nonce: 1, tip: 0, transactionVersion: 1});
-
+ .signAndSend(aliceKey);
console.log(`hxHash ${transfer}`);
-
}
main().catch(console.error);