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);