From af287cb43a770becf2bb6a98969675ec659c12bf Mon Sep 17 00:00:00 2001 From: Edward Mack Date: Fri, 12 Mar 2021 16:18:52 -0500 Subject: [PATCH 01/30] move websocket messages and listeners into own files --- dot/rpc/websocket.go | 420 ---------------------------------- dot/rpc/websocketListeners.go | 359 +++++++++++++++++++++++++++++ dot/rpc/websocketMessages.go | 101 ++++++++ 3 files changed, 460 insertions(+), 420 deletions(-) create mode 100644 dot/rpc/websocketListeners.go create mode 100644 dot/rpc/websocketMessages.go diff --git a/dot/rpc/websocket.go b/dot/rpc/websocket.go index ec53b1078c..20000ecee5 100644 --- a/dot/rpc/websocket.go +++ b/dot/rpc/websocket.go @@ -24,74 +24,11 @@ import ( "math/big" "net" "net/http" - "reflect" "strings" - "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/gorilla/websocket" ) -// SubscriptionBaseResponseJSON for base json response -type SubscriptionBaseResponseJSON struct { - Jsonrpc string `json:"jsonrpc"` - Method string `json:"method"` - Params SubscriptionParams `json:"params"` -} - -// SubscriptionParams for json param response -type SubscriptionParams struct { - Result interface{} `json:"result"` - SubscriptionID int `json:"subscription"` -} - -func newSubcriptionBaseResponseJSON() SubscriptionBaseResponseJSON { - return SubscriptionBaseResponseJSON{ - Jsonrpc: "2.0", - } -} - -func newSubscriptionResponse(method string, subID int, result interface{}) SubscriptionBaseResponseJSON { - return SubscriptionBaseResponseJSON{ - Jsonrpc: "2.0", - Method: method, - Params: SubscriptionParams{ - Result: result, - SubscriptionID: subID, - }, - } -} - -// SubscriptionResponseJSON for json subscription responses -type SubscriptionResponseJSON struct { - Jsonrpc string `json:"jsonrpc"` - Result int `json:"result"` - ID float64 `json:"id"` -} - -func newSubscriptionResponseJSON(subID int, reqID float64) SubscriptionResponseJSON { - return SubscriptionResponseJSON{ - 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"` -} - var rpcHost string // ServeHTTP implemented to handle WebSocket connections @@ -146,31 +83,6 @@ func NewWSConn(conn *websocket.Conn, cfg *HTTPServerConfig) *WSConn { return c } -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) - } -} - func (c *WSConn) handleComm() { for { _, mbytes, err := c.wsconn.ReadMessage() @@ -282,335 +194,3 @@ func (c *WSConn) handleComm() { c.safeSend(wsSend) } } -func (c *WSConn) startListener(lid int) { - go c.subscriptions[lid].Listen() -} - -// Listener interface for functions that define Listener related functions -type Listener interface { - Listen() -} - -// StorageChangeListener for listening to state change channels -type StorageChangeListener struct { - channel chan *state.SubscriptionResult - wsconn *WSConn - chanID byte - subID int -} - -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, - } - - 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") - } - } - - if c.storageAPI == nil { - c.safeSendError(reqID, nil, "error StorageAPI not set") - return 0, fmt.Errorf("error StorageAPI not set") - } - - chanID, err := c.storageAPI.RegisterStorageChangeChannel(*sub) - if err != nil { - return 0, err - } - 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) - - return scl.subID, nil -} - -// 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) - } -} - -// BlockListener to handle listening for blocks importedChan -type BlockListener struct { - channel chan *types.Block - wsconn *WSConn - 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 -} - -// Listen implementation of Listen interface to listen for importedChan changes -func (l *BlockListener) Listen() { - for block := range l.channel { - if block == nil { - continue - } - head, err := modules.HeaderToJSON(*block.Header) - if err != nil { - logger.Error("failed to convert header to JSON", "error", err) - } - - res := newSubcriptionBaseResponseJSON() - res.Method = "chain_newHead" - res.Params.Result = head - res.Params.SubscriptionID = l.subID - l.wsconn.safeSend(res) - } -} - -// BlockFinalizedListener to handle listening for finalized blocks -type BlockFinalizedListener struct { - channel chan *types.Header - wsconn *WSConn - 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 -} - -// Listen implementation of Listen interface to listen for importedChan changes -func (l *BlockFinalizedListener) Listen() { - for header := range l.channel { - if header == nil { - continue - } - head, err := modules.HeaderToJSON(*header) - if err != nil { - logger.Error("failed to convert header to JSON", "error", err) - } - res := newSubcriptionBaseResponseJSON() - res.Method = "chain_finalizedHead" - res.Params.Result = head - res.Params.SubscriptionID = l.subID - l.wsconn.safeSend(res) - } -} - -// ExtrinsicSubmitListener to handle listening for extrinsic events -type ExtrinsicSubmitListener struct { - wsconn *WSConn - subID int - extrinsic types.Extrinsic - - importedChan chan *types.Block - importedChanID byte - importedHash common.Hash - finalizedChan chan *types.Header - finalizedChanID byte -} - -// 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 - go func() { - for block := range l.importedChan { - if block == nil { - continue - } - exts, err := block.Body.AsExtrinsics() - if err != nil { - fmt.Printf("error %v\n", err) - } - for _, v := range exts { - if reflect.DeepEqual(v, l.extrinsic) { - resM := make(map[string]interface{}) - resM["inBlock"] = block.Header.Hash().String() - - l.importedHash = block.Header.Hash() - l.wsconn.safeSend(newSubscriptionResponse(AuthorExtrinsicUpdates, l.subID, resM)) - } - } - } - }() - - // listen for finalized headers - go func() { - for header := range l.finalizedChan { - if reflect.DeepEqual(l.importedHash, header.Hash()) { - resM := make(map[string]interface{}) - resM["finalized"] = header.Hash().String() - l.wsconn.safeSend(newSubscriptionResponse(AuthorExtrinsicUpdates, l.subID, resM)) - } - } - }() -} - -// 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 -} - -// Listen implementation of Listen interface to listen for runtime version changes -func (l *RuntimeVersionListener) Listen() { - rtVersion, err := l.wsconn.coreAPI.GetRuntimeVersion(nil) - if err != nil { - return - } - ver := modules.StateRuntimeVersionResponse{} - - ver.SpecName = string(rtVersion.SpecName()) - ver.ImplName = string(rtVersion.ImplName()) - ver.AuthoringVersion = rtVersion.AuthoringVersion() - ver.SpecVersion = rtVersion.SpecVersion() - ver.ImplVersion = rtVersion.ImplVersion() - ver.TransactionVersion = rtVersion.TransactionVersion() - ver.Apis = modules.ConvertAPIs(rtVersion.APIItems()) - - l.wsconn.safeSend(newSubscriptionResponse("state_runtimeVersion", l.subID, ver)) -} diff --git a/dot/rpc/websocketListeners.go b/dot/rpc/websocketListeners.go new file mode 100644 index 0000000000..31f3a3d11d --- /dev/null +++ b/dot/rpc/websocketListeners.go @@ -0,0 +1,359 @@ +// 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 rpc + +import ( + "fmt" + "reflect" + + "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" +) + +// Listener interface for functions that define Listener related functions +type Listener interface { + Listen() +} + +func (c *WSConn) startListener(lid int) { + go c.subscriptions[lid].Listen() +} + +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, + } + + 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") + } + } + + if c.storageAPI == nil { + c.safeSendError(reqID, nil, "error StorageAPI not set") + return 0, fmt.Errorf("error StorageAPI not set") + } + + chanID, err := c.storageAPI.RegisterStorageChangeChannel(*sub) + if err != nil { + return 0, err + } + 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) + + return scl.subID, nil +} + +// StorageChangeListener for listening to state change channels +type StorageChangeListener struct { + channel chan *state.SubscriptionResult + wsconn *WSConn + chanID byte + subID int +} + +// 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) + } +} + +// BlockListener to handle listening for blocks importedChan +type BlockListener struct { + channel chan *types.Block + wsconn *WSConn + 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 +} + +// Listen implementation of Listen interface to listen for importedChan changes +func (l *BlockListener) Listen() { + for block := range l.channel { + if block == nil { + continue + } + head, err := modules.HeaderToJSON(*block.Header) + if err != nil { + logger.Error("failed to convert header to JSON", "error", err) + } + + res := newSubcriptionBaseResponseJSON() + res.Method = "chain_newHead" + res.Params.Result = head + res.Params.SubscriptionID = l.subID + l.wsconn.safeSend(res) + } +} + +// BlockFinalizedListener to handle listening for finalized blocks +type BlockFinalizedListener struct { + channel chan *types.Header + wsconn *WSConn + 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 +} + +// Listen implementation of Listen interface to listen for importedChan changes +func (l *BlockFinalizedListener) Listen() { + for header := range l.channel { + if header == nil { + continue + } + head, err := modules.HeaderToJSON(*header) + if err != nil { + logger.Error("failed to convert header to JSON", "error", err) + } + res := newSubcriptionBaseResponseJSON() + res.Method = "chain_finalizedHead" + res.Params.Result = head + res.Params.SubscriptionID = l.subID + l.wsconn.safeSend(res) + } +} + +// ExtrinsicSubmitListener to handle listening for extrinsic events +type ExtrinsicSubmitListener struct { + wsconn *WSConn + subID int + extrinsic types.Extrinsic + + importedChan chan *types.Block + importedChanID byte + importedHash common.Hash + finalizedChan chan *types.Header + finalizedChanID byte +} + +// 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 + go func() { + for block := range l.importedChan { + if block == nil { + continue + } + exts, err := block.Body.AsExtrinsics() + if err != nil { + fmt.Printf("error %v\n", err) + } + for _, v := range exts { + if reflect.DeepEqual(v, l.extrinsic) { + resM := make(map[string]interface{}) + resM["inBlock"] = block.Header.Hash().String() + + l.importedHash = block.Header.Hash() + l.wsconn.safeSend(newSubscriptionResponse(AuthorExtrinsicUpdates, l.subID, resM)) + } + } + } + }() + + // listen for finalized headers + go func() { + for header := range l.finalizedChan { + if reflect.DeepEqual(l.importedHash, header.Hash()) { + resM := make(map[string]interface{}) + resM["finalized"] = header.Hash().String() + l.wsconn.safeSend(newSubscriptionResponse(AuthorExtrinsicUpdates, l.subID, resM)) + } + } + }() +} + +// 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 +} + +// Listen implementation of Listen interface to listen for runtime version changes +func (l *RuntimeVersionListener) Listen() { + rtVersion, err := l.wsconn.coreAPI.GetRuntimeVersion(nil) + if err != nil { + return + } + ver := modules.StateRuntimeVersionResponse{} + + ver.SpecName = string(rtVersion.SpecName()) + ver.ImplName = string(rtVersion.ImplName()) + ver.AuthoringVersion = rtVersion.AuthoringVersion() + ver.SpecVersion = rtVersion.SpecVersion() + ver.ImplVersion = rtVersion.ImplVersion() + ver.TransactionVersion = rtVersion.TransactionVersion() + ver.Apis = modules.ConvertAPIs(rtVersion.APIItems()) + + l.wsconn.safeSend(newSubscriptionResponse("state_runtimeVersion", l.subID, ver)) +} diff --git a/dot/rpc/websocketMessages.go b/dot/rpc/websocketMessages.go new file mode 100644 index 0000000000..c8958e3820 --- /dev/null +++ b/dot/rpc/websocketMessages.go @@ -0,0 +1,101 @@ +// 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 rpc + +import "math/big" + +// SubscriptionBaseResponseJSON for base json response +type SubscriptionBaseResponseJSON struct { + Jsonrpc string `json:"jsonrpc"` + Method string `json:"method"` + Params SubscriptionParams `json:"params"` +} + +// SubscriptionParams for json param response +type SubscriptionParams struct { + Result interface{} `json:"result"` + SubscriptionID int `json:"subscription"` +} + +func newSubcriptionBaseResponseJSON() SubscriptionBaseResponseJSON { + return SubscriptionBaseResponseJSON{ + Jsonrpc: "2.0", + } +} + +func newSubscriptionResponse(method string, subID int, result interface{}) SubscriptionBaseResponseJSON { + return SubscriptionBaseResponseJSON{ + Jsonrpc: "2.0", + Method: method, + Params: SubscriptionParams{ + Result: result, + SubscriptionID: subID, + }, + } +} + +// SubscriptionResponseJSON for json subscription responses +type SubscriptionResponseJSON struct { + Jsonrpc string `json:"jsonrpc"` + Result int `json:"result"` + ID float64 `json:"id"` +} + +func newSubscriptionResponseJSON(subID int, reqID float64) SubscriptionResponseJSON { + return SubscriptionResponseJSON{ + 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) + } +} From 9d0003a928f5cf765c843adc942f0cb9bed79cf5 Mon Sep 17 00:00:00 2001 From: Edward Mack Date: Fri, 12 Mar 2021 18:44:39 -0500 Subject: [PATCH 02/30] fix notifyStorageSubscriptions to only notify for changes --- dot/rpc/websocketListeners.go | 28 ++++++++++++++++++++-------- dot/state/storage.go | 23 +++++++++++++++-------- dot/state/storage_notify.go | 26 +++++++++++--------------- dot/state/storage_notify_test.go | 19 ++++++++----------- 4 files changed, 54 insertions(+), 42 deletions(-) diff --git a/dot/rpc/websocketListeners.go b/dot/rpc/websocketListeners.go index 31f3a3d11d..58986880e9 100644 --- a/dot/rpc/websocketListeners.go +++ b/dot/rpc/websocketListeners.go @@ -35,12 +35,17 @@ func (c *WSConn) startListener(lid int) { } func (c *WSConn) initStorageChangeListener(reqID float64, params interface{}) (int, error) { + if c.storageAPI == nil { + c.safeSendError(reqID, nil, "error StorageAPI not set") + return 0, fmt.Errorf("error StorageAPI not set") + } + scl := &StorageChangeListener{ channel: make(chan *state.SubscriptionResult), wsconn: c, } sub := &state.StorageSubscription{ - Filter: make(map[string]bool), + Filter: make(map[string][]byte), Listener: scl.channel, } @@ -49,20 +54,15 @@ func (c *WSConn) initStorageChangeListener(reqID float64, params interface{}) (i switch p := param.(type) { case []interface{}: for _, pp := range param.([]interface{}) { - sub.Filter[pp.(string)] = true + sub.Filter[pp.(string)] = []byte{} } case string: - sub.Filter[p] = true + sub.Filter[p] = []byte{} default: return 0, fmt.Errorf("unknow parameter type") } } - if c.storageAPI == nil { - c.safeSendError(reqID, nil, "error StorageAPI not set") - return 0, fmt.Errorf("error StorageAPI not set") - } - chanID, err := c.storageAPI.RegisterStorageChangeChannel(*sub) if err != nil { return 0, err @@ -77,6 +77,18 @@ func (c *WSConn) initStorageChangeListener(reqID float64, params interface{}) (i initRes := newSubscriptionResponseJSON(scl.subID, reqID) c.safeSend(initRes) + //for k := range sub.Filter { + // fmt.Printf("Lookup Key %v\n", k) + // + // res, err := c.storageAPI.GetStorage(nil, common.MustHexToBytes(k)) + // if err != nil { + // logger.Debug("error retrieving from storage", "error", err) + // } + // sub.Filter[k] = res + //} + // respond with current value + //res, err := c.storageAPI.GetStorage(nil, sub.) + return scl.subID, nil } diff --git a/dot/state/storage.go b/dot/state/storage.go index 0074f120d5..19fecb0f35 100644 --- a/dot/state/storage.go +++ b/dot/state/storage.go @@ -20,6 +20,7 @@ import ( "encoding/binary" "errors" "fmt" + "reflect" "sync" "github.com/ChainSafe/chaindb" @@ -153,7 +154,7 @@ func (s *StorageState) notifyStorageSubscriptions(root common.Hash) error { s.changedLock.Lock() defer s.changedLock.Unlock() - for _, sub := range s.subscriptions { + for subKey, sub := range s.subscriptions { subRes := &SubscriptionResult{ Hash: root, } @@ -172,18 +173,24 @@ func (s *StorageState) notifyStorageSubscriptions(root common.Hash) error { } } else { // filter result to include only interested keys - for k := range sub.Filter { + for k, currentValue := range sub.Filter { value := t.Get(common.MustHexToBytes(k)) - kv := &KeyValue{ - Key: common.MustHexToBytes(k), - Value: value, + if !reflect.DeepEqual(currentValue, value) { + kv := &KeyValue{ + Key: common.MustHexToBytes(k), + Value: value, + } + subRes.Changes = append(subRes.Changes, *kv) + s.subscriptions[subKey].Filter[k] = value } - subRes.Changes = append(subRes.Changes, *kv) } } - s.notifyChanged(subRes) + if len(subRes.Changes) > 0 { + go func(ch chan<- *SubscriptionResult) { + ch <- subRes + }(sub.Listener) + } } - return nil } diff --git a/dot/state/storage_notify.go b/dot/state/storage_notify.go index 6e9fbac8e7..dda1f197bd 100644 --- a/dot/state/storage_notify.go +++ b/dot/state/storage_notify.go @@ -35,7 +35,7 @@ type SubscriptionResult struct { //StorageSubscription holds data for Subscription to Storage type StorageSubscription struct { - Filter map[string]bool + Filter map[string][]byte Listener chan<- *SubscriptionResult } @@ -60,6 +60,16 @@ func (s *StorageState) RegisterStorageChangeChannel(sub StorageSubscription) (by s.changedLock.Lock() s.subscriptions[id] = &sub s.changedLock.Unlock() + // notifyStorageSubscriptions 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) + } + go func() { + if err := s.notifyStorageSubscriptions(sr); err != nil { + logger.Warn("failed to notify storage subscriptions", "error", err) + } + }() return id, nil } @@ -71,17 +81,3 @@ func (s *StorageState) UnregisterStorageChangeChannel(id byte) { delete(s.subscriptions, id) } - -func (s *StorageState) notifyChanged(change *SubscriptionResult) { - if len(s.subscriptions) == 0 { - return - } - - logger.Trace("notifying changed storage chans...", "chans", s.subscriptions) - - for _, ch := range s.subscriptions { - go func(ch chan<- *SubscriptionResult) { - ch <- change - }(ch.Listener) - } -} diff --git a/dot/state/storage_notify_test.go b/dot/state/storage_notify_test.go index 4ed90dc073..0efc30b5f4 100644 --- a/dot/state/storage_notify_test.go +++ b/dot/state/storage_notify_test.go @@ -38,7 +38,7 @@ func TestStorageState_RegisterStorageChangeChannel(t *testing.T) { ch := make(chan *SubscriptionResult) sub := StorageSubscription{ - Filter: make(map[string]bool), + Filter: make(map[string][]byte), Listener: ch, } id, err := ss.RegisterStorageChangeChannel(sub) @@ -60,7 +60,6 @@ func TestStorageState_RegisterStorageChangeChannel(t *testing.T) { } func TestStorageState_RegisterStorageChangeChannel_Multi(t *testing.T) { - //t.Skip() ss := newTestStorageState(t) ts, err := ss.TrieState(nil) require.NoError(t, err) @@ -120,14 +119,18 @@ func TestStorageState_RegisterStorageChangeChannel_Multi_Filter(t *testing.T) { key1 := []byte("key1") value1 := []byte("value1") + ts.Set(key1, value1) + err = ss.StoreTrie(ts) + require.NoError(t, err) + num := 5 chs := make([]chan *SubscriptionResult, num) ids := make([]byte, num) - subFilter := make(map[string]bool) - subFilter[common.BytesToHex(key1)] = true for i := 0; i < num; i++ { chs[i] = make(chan *SubscriptionResult) + subFilter := make(map[string][]byte) + subFilter[common.BytesToHex(key1)] = []byte{} sub := StorageSubscription{ Filter: subFilter, Listener: chs[i], @@ -136,13 +139,6 @@ func TestStorageState_RegisterStorageChangeChannel_Multi_Filter(t *testing.T) { require.NoError(t, err) } - ts.Set(key1, value1) - - err = ss.StoreTrie(ts) - require.NoError(t, err) - - time.Sleep(time.Millisecond * 500) - var wg sync.WaitGroup wg.Add(num) @@ -157,6 +153,7 @@ func TestStorageState_RegisterStorageChangeChannel_Multi_Filter(t *testing.T) { wg.Done() case <-time.After(testMessageTimeout): t.Error("did not receive storage change: ch=", i) + wg.Done() } }(i, ch) From 090028f90ca3fc6e6e840cacb0c742d1e19804f7 Mon Sep 17 00:00:00 2001 From: Edward Mack Date: Wed, 17 Mar 2021 15:12:48 -0400 Subject: [PATCH 03/30] address PR comments --- dot/rpc/websocketListeners.go | 27 ++++++++++++--------------- dot/state/storage.go | 4 ++-- dot/state/storage_notify_test.go | 3 +-- 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/dot/rpc/websocketListeners.go b/dot/rpc/websocketListeners.go index 58986880e9..3f6cff0b1f 100644 --- a/dot/rpc/websocketListeners.go +++ b/dot/rpc/websocketListeners.go @@ -49,12 +49,19 @@ func (c *WSConn) initStorageChangeListener(reqID float64, params interface{}) (i Listener: scl.channel, } - pA := params.([]interface{}) + pA, ok := params.([]interface{}) + if !ok { + return 0, fmt.Errorf("unknow parameter type") + } for _, param := range pA { switch p := param.(type) { case []interface{}: for _, pp := range param.([]interface{}) { - sub.Filter[pp.(string)] = []byte{} + data, ok := pp.(string) + if !ok { + return 0, fmt.Errorf("unknow parameter type") + } + sub.Filter[data] = []byte{} } case string: sub.Filter[p] = []byte{} @@ -77,18 +84,6 @@ func (c *WSConn) initStorageChangeListener(reqID float64, params interface{}) (i initRes := newSubscriptionResponseJSON(scl.subID, reqID) c.safeSend(initRes) - //for k := range sub.Filter { - // fmt.Printf("Lookup Key %v\n", k) - // - // res, err := c.storageAPI.GetStorage(nil, common.MustHexToBytes(k)) - // if err != nil { - // logger.Debug("error retrieving from storage", "error", err) - // } - // sub.Filter[k] = res - //} - // respond with current value - //res, err := c.storageAPI.GetStorage(nil, sub.) - return scl.subID, nil } @@ -109,7 +104,7 @@ func (l *StorageChangeListener) Listen() { result := make(map[string]interface{}) result["block"] = change.Hash.String() - changes := [][]string{} + changes := make([][]string, len(change.Changes)) for _, v := range change.Changes { kv := []string{common.BytesToHex(v.Key), common.BytesToHex(v.Value)} changes = append(changes, kv) @@ -353,6 +348,8 @@ func (c *WSConn) initRuntimeVersionListener(reqID float64) (int, error) { // 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/state/storage.go b/dot/state/storage.go index 3ff6f4b99d..50e65df90a 100644 --- a/dot/state/storage.go +++ b/dot/state/storage.go @@ -173,9 +173,9 @@ func (s *StorageState) notifyStorageSubscriptions(root common.Hash) error { } } else { // filter result to include only interested keys - for k, currentValue := range sub.Filter { + for k, cachedValue := range sub.Filter { value := t.Get(common.MustHexToBytes(k)) - if !reflect.DeepEqual(currentValue, value) { + if !reflect.DeepEqual(cachedValue, value) { kv := &KeyValue{ Key: common.MustHexToBytes(k), Value: value, diff --git a/dot/state/storage_notify_test.go b/dot/state/storage_notify_test.go index 0efc30b5f4..f415d96802 100644 --- a/dot/state/storage_notify_test.go +++ b/dot/state/storage_notify_test.go @@ -145,15 +145,14 @@ func TestStorageState_RegisterStorageChangeChannel_Multi_Filter(t *testing.T) { for i, ch := range chs { go func(i int, ch chan *SubscriptionResult) { + defer wg.Done() 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) - wg.Done() } }(i, ch) From a9279a7e49c2d345a7b9e64009e45cfa4c6b790f Mon Sep 17 00:00:00 2001 From: Edward Mack Date: Thu, 18 Mar 2021 16:54:58 -0400 Subject: [PATCH 04/30] add to websocket tests --- dot/rpc/websocket_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dot/rpc/websocket_test.go b/dot/rpc/websocket_test.go index e2f6f8da83..26e16bc6d6 100644 --- a/dot/rpc/websocket_test.go +++ b/dot/rpc/websocket_test.go @@ -17,6 +17,7 @@ package rpc import ( "flag" + "fmt" "log" "math/big" "net/url" @@ -33,6 +34,7 @@ import ( ) var addr = flag.String("addr", "localhost:8546", "http service address") + var testCalls = []struct { call []byte expected []byte @@ -43,6 +45,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) { @@ -88,6 +92,7 @@ func TestHTTPServer_ServeHTTP(t *testing.T) { _, message, err := c.ReadMessage() require.Nil(t, err) + fmt.Printf("ACT MSG %s\n", message) require.Equal(t, item.expected, message) } } From 0595ca87e73461e6e9af1702f06139a99dea1803 Mon Sep 17 00:00:00 2001 From: Edward Mack Date: Thu, 18 Mar 2021 17:12:49 -0400 Subject: [PATCH 05/30] repair append, cleanup filter declareation --- dot/rpc/websocketListeners.go | 2 +- dot/state/storage_notify_test.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dot/rpc/websocketListeners.go b/dot/rpc/websocketListeners.go index 3f6cff0b1f..98e75a9eda 100644 --- a/dot/rpc/websocketListeners.go +++ b/dot/rpc/websocketListeners.go @@ -104,7 +104,7 @@ func (l *StorageChangeListener) Listen() { result := make(map[string]interface{}) result["block"] = change.Hash.String() - changes := make([][]string, len(change.Changes)) + changes := make([][]string, 0, len(change.Changes)) for _, v := range change.Changes { kv := []string{common.BytesToHex(v.Key), common.BytesToHex(v.Value)} changes = append(changes, kv) diff --git a/dot/state/storage_notify_test.go b/dot/state/storage_notify_test.go index f415d96802..a1c5081a5d 100644 --- a/dot/state/storage_notify_test.go +++ b/dot/state/storage_notify_test.go @@ -129,10 +129,10 @@ func TestStorageState_RegisterStorageChangeChannel_Multi_Filter(t *testing.T) { for i := 0; i < num; i++ { chs[i] = make(chan *SubscriptionResult) - subFilter := make(map[string][]byte) - subFilter[common.BytesToHex(key1)] = []byte{} sub := StorageSubscription{ - Filter: subFilter, + Filter: map[string][]byte{ + common.BytesToHex(key1): {}, + }, Listener: chs[i], } ids[i], err = ss.RegisterStorageChangeChannel(sub) From a479786f7826de7ff23e04603d953b8336a956a9 Mon Sep 17 00:00:00 2001 From: Edward Mack Date: Thu, 18 Mar 2021 17:37:11 -0400 Subject: [PATCH 06/30] fix anti-pattern in log message --- dot/rpc/websocket.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dot/rpc/websocket.go b/dot/rpc/websocket.go index 20000ecee5..af826094f2 100644 --- a/dot/rpc/websocket.go +++ b/dot/rpc/websocket.go @@ -90,7 +90,7 @@ func (c *WSConn) handleComm() { logger.Warn("websocket failed to read message", "error", err) return } - logger.Debug("websocket received", "message", fmt.Sprintf("%s", mbytes)) + logger.Debug("websocket received", "message", mbytes) // determine if request is for subscribe method type var msg map[string]interface{} From a9ecb4a09c19b40813bae24332366febdda04525 Mon Sep 17 00:00:00 2001 From: Edward Mack Date: Thu, 18 Mar 2021 18:07:42 -0400 Subject: [PATCH 07/30] create notifyStorageSubscription for individual sub notify --- dot/state/storage.go | 79 +++++++++++++++++++++---------------- dot/state/storage_notify.go | 4 +- 2 files changed, 48 insertions(+), 35 deletions(-) diff --git a/dot/state/storage.go b/dot/state/storage.go index 50e65df90a..cda817a26b 100644 --- a/dot/state/storage.go +++ b/dot/state/storage.go @@ -139,9 +139,22 @@ func (s *StorageState) TrieState(root *common.Hash) (*rtstorage.TrieState, error return rtstorage.NewTrieState(tr) } -// StoreInDB encodes the entire trie and writes it to the DB -// The key to the DB entry is the root hash of the trie +// notifyStorageSubscriptions interates StorageState subscrirptions and notifies listener +// if the value filter value has changed func (s *StorageState) notifyStorageSubscriptions(root common.Hash) error { + + for subKey := range s.subscriptions { + err := s.notifyStorageSubscription(root, subKey) + if err != nil { + return err + } + } + return nil +} + +// notifyStorageSubscriptions interates StorageState subscrirptions and notifies listener +// if the value filter value has changed +func (s *StorageState) notifyStorageSubscription(root common.Hash, subID byte) error { s.lock.RLock() t := s.tries[root] s.lock.RUnlock() @@ -154,43 +167,43 @@ func (s *StorageState) notifyStorageSubscriptions(root common.Hash) error { s.changedLock.Lock() defer s.changedLock.Unlock() - for subKey, 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) + sub := s.subscriptions[subID] + 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, cachedValue := range sub.Filter { - 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) - s.subscriptions[subKey].Filter[k] = value + } + } else { + // filter result to include only interested keys + for k, cachedValue := range sub.Filter { + 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) + s.subscriptions[subID].Filter[k] = value } } - if len(subRes.Changes) > 0 { - go func(ch chan<- *SubscriptionResult) { - ch <- subRes - }(sub.Listener) - } } + if len(subRes.Changes) > 0 { + go func(ch chan<- *SubscriptionResult) { + ch <- subRes + }(sub.Listener) + } + return nil } diff --git a/dot/state/storage_notify.go b/dot/state/storage_notify.go index dda1f197bd..295e61cc8b 100644 --- a/dot/state/storage_notify.go +++ b/dot/state/storage_notify.go @@ -60,13 +60,13 @@ func (s *StorageState) RegisterStorageChangeChannel(sub StorageSubscription) (by s.changedLock.Lock() s.subscriptions[id] = &sub s.changedLock.Unlock() - // notifyStorageSubscriptions here to send storage value of current state + // notifyStorageSubscription 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) } go func() { - if err := s.notifyStorageSubscriptions(sr); err != nil { + if err := s.notifyStorageSubscription(sr, id); err != nil { logger.Warn("failed to notify storage subscriptions", "error", err) } }() From 299448bffe4a0e96b6772fabdad9a4f30498288d Mon Sep 17 00:00:00 2001 From: Edward Mack Date: Fri, 19 Mar 2021 14:53:07 -0400 Subject: [PATCH 08/30] add websocket listeners unit tests --- dot/rpc/http.go | 5 + dot/rpc/websocket.go | 4 + dot/rpc/websocketListeners.go | 8 +- dot/rpc/websocketListeners_test.go | 160 +++++++++++++++++++++++++++++ dot/rpc/websocketMessages.go | 8 ++ 5 files changed, 181 insertions(+), 4 deletions(-) create mode 100644 dot/rpc/websocketListeners_test.go diff --git a/dot/rpc/http.go b/dot/rpc/http.go index 5f47dfdb0a..dd041f0abd 100644 --- a/dot/rpc/http.go +++ b/dot/rpc/http.go @@ -75,6 +75,11 @@ type WSConn struct { txStateAPI modules.TransactionStateAPI } +// WSConnAPI interface defining methors a WSConn should have +type WSConnAPI interface { + safeSend(interface{}) +} + var logger log.Logger // NewHTTPServer creates a new http server and registers an associated rpc server diff --git a/dot/rpc/websocket.go b/dot/rpc/websocket.go index af826094f2..2cdc1d5b25 100644 --- a/dot/rpc/websocket.go +++ b/dot/rpc/websocket.go @@ -26,6 +26,7 @@ import ( "net/http" "strings" + log "github.com/ChainSafe/log15" "github.com/gorilla/websocket" ) @@ -68,6 +69,9 @@ func (h *HTTPServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { // NewWSConn to create new WebSocket Connection struct func NewWSConn(conn *websocket.Conn, cfg *HTTPServerConfig) *WSConn { + if logger == nil { + logger = log.New("pkg", "rpc") + } rpcHost = fmt.Sprintf("http://%s:%d/", cfg.Host, cfg.RPCPort) c := &WSConn{ wsconn: conn, diff --git a/dot/rpc/websocketListeners.go b/dot/rpc/websocketListeners.go index 98e75a9eda..56d4c5749f 100644 --- a/dot/rpc/websocketListeners.go +++ b/dot/rpc/websocketListeners.go @@ -90,7 +90,7 @@ func (c *WSConn) initStorageChangeListener(reqID float64, params interface{}) (i // StorageChangeListener for listening to state change channels type StorageChangeListener struct { channel chan *state.SubscriptionResult - wsconn *WSConn + wsconn WSConnAPI chanID byte subID int } @@ -122,7 +122,7 @@ func (l *StorageChangeListener) Listen() { // BlockListener to handle listening for blocks importedChan type BlockListener struct { channel chan *types.Block - wsconn *WSConn + wsconn WSConnAPI chanID byte subID int } @@ -174,7 +174,7 @@ 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 } @@ -224,7 +224,7 @@ func (l *BlockFinalizedListener) Listen() { // ExtrinsicSubmitListener to handle listening for extrinsic events type ExtrinsicSubmitListener struct { - wsconn *WSConn + wsconn WSConnAPI subID int extrinsic types.Extrinsic diff --git a/dot/rpc/websocketListeners_test.go b/dot/rpc/websocketListeners_test.go new file mode 100644 index 0000000000..1551078bb4 --- /dev/null +++ b/dot/rpc/websocketListeners_test.go @@ -0,0 +1,160 @@ +// 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 rpc + +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 SubscriptionBaseResponseJSON +} + +func (m *MockWSConnAPI) safeSend(msg interface{}) { + m.lastMessage = msg.(SubscriptionBaseResponseJSON) +} + +func TestStorageChangeListener_Listen(t *testing.T) { + notifyChan := make(chan *state.SubscriptionResult) + mockConnection := &MockWSConnAPI{} + scl := StorageChangeListener{ + channel: notifyChan, + wsconn: mockConnection, + } + + go scl.Listen() + + data := []state.KeyValue{{ + Key: []byte("key"), + Value: []byte("value"), + }} + change := &state.SubscriptionResult{ + Hash: common.Hash{}, + Changes: data, + } + + expected := make(map[string]interface{}) + expected["block"] = change.Hash.String() + changes := make([][]string, 0, len(change.Changes)) + for _, v := range change.Changes { + kv := []string{common.BytesToHex(v.Key), common.BytesToHex(v.Value)} + changes = append(changes, kv) + } + expected["changes"] = changes + + expectedRespones := newSubcriptionBaseResponseJSON() + expectedRespones.Method = "state_storage" + expectedRespones.Params.Result = expected + + notifyChan <- change + + 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 + + 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 + + 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) + require.Equal(t, expectedImportedRespones, mockConnection.lastMessage) + + notifyFinalizedChan <- header + time.Sleep(time.Millisecond) + 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/websocketMessages.go b/dot/rpc/websocketMessages.go index c8958e3820..5d072a19c1 100644 --- a/dot/rpc/websocketMessages.go +++ b/dot/rpc/websocketMessages.go @@ -76,6 +76,10 @@ type ErrorMessageJSON struct { } func (c *WSConn) safeSend(msg interface{}) { + if c.wsconn == nil { + logger.Debug("error trying to send message to nil websocket connection") + return + } c.mu.Lock() defer c.mu.Unlock() err := c.wsconn.WriteJSON(msg) @@ -84,6 +88,10 @@ func (c *WSConn) safeSend(msg interface{}) { } } func (c *WSConn) safeSendError(reqID float64, errorCode *big.Int, message string) { + if c.wsconn == nil { + logger.Debug("error trying to send message to nil websocket connection") + return + } res := &ErrorResponseJSON{ Jsonrpc: "2.0", Error: &ErrorMessageJSON{ From ca61c54eb98811a731419293f0c1d3157a589e71 Mon Sep 17 00:00:00 2001 From: Edward Mack Date: Fri, 19 Mar 2021 15:58:24 -0400 Subject: [PATCH 09/30] cleanup merge conflicts --- dot/rpc/http.go | 20 - dot/rpc/subscription/listeners.go | 45 ++- .../listeners_test.go} | 6 +- dot/rpc/subscription/messages.go | 36 +- dot/rpc/websocketListeners.go | 368 ------------------ dot/rpc/websocketMessages.go | 109 ------ 6 files changed, 48 insertions(+), 536 deletions(-) rename dot/rpc/{websocketListeners_test.go => subscription/listeners_test.go} (98%) delete mode 100644 dot/rpc/websocketListeners.go delete mode 100644 dot/rpc/websocketMessages.go diff --git a/dot/rpc/http.go b/dot/rpc/http.go index 50c3ef0fd8..fd98877331 100644 --- a/dot/rpc/http.go +++ b/dot/rpc/http.go @@ -61,26 +61,6 @@ type HTTPServerConfig struct { Modules []string } -// WSConn struct to hold WebSocket Connection references -type WSConn struct { - wsconn *websocket.Conn - mu sync.Mutex - blockSubChannels map[int]byte - storageSubChannels map[int]byte - qtyListeners int - subscriptions map[int]Listener - storageAPI modules.StorageAPI - blockAPI modules.BlockAPI - runtimeAPI modules.RuntimeAPI - coreAPI modules.CoreAPI - txStateAPI modules.TransactionStateAPI -} - -// WSConnAPI interface defining methors a WSConn should have -type WSConnAPI interface { - safeSend(interface{}) -} - var logger log.Logger // NewHTTPServer creates a new http server and registers an associated rpc server diff --git a/dot/rpc/subscription/listeners.go b/dot/rpc/subscription/listeners.go index 3ec46b3dad..75ad803f6e 100644 --- a/dot/rpc/subscription/listeners.go +++ b/dot/rpc/subscription/listeners.go @@ -17,8 +17,6 @@ package subscription import ( "fmt" - - "reflect" "github.com/ChainSafe/gossamer/dot/rpc/modules" @@ -32,39 +30,51 @@ type Listener interface { Listen() } +// WSConnAPI interface defining methors a WSConn should have +type WSConnAPI interface { + safeSend(interface{}) +} + func (c *WSConn) startListener(lid int) { go c.Subscriptions[lid].Listen() } func (c *WSConn) initStorageChangeListener(reqID float64, params interface{}) (int, error) { + if c.StorageAPI == nil { + c.safeSendError(reqID, nil, "error StorageAPI not set") + return 0, fmt.Errorf("error StorageAPI not set") + } + scl := &StorageChangeListener{ Channel: make(chan *state.SubscriptionResult), wsconn: c, } sub := &state.StorageSubscription{ - Filter: make(map[string]bool), + Filter: make(map[string][]byte), Listener: scl.Channel, } - pA := params.([]interface{}) + pA, ok := params.([]interface{}) + if !ok { + return 0, fmt.Errorf("unknow parameter type") + } for _, param := range pA { switch p := param.(type) { case []interface{}: for _, pp := range param.([]interface{}) { - sub.Filter[pp.(string)] = true + data, ok := pp.(string) + if !ok { + return 0, fmt.Errorf("unknow parameter type") + } + sub.Filter[data] = []byte{} } case string: - sub.Filter[p] = true + sub.Filter[p] = []byte{} default: return 0, fmt.Errorf("unknow parameter type") } } - if c.StorageAPI == nil { - c.safeSendError(reqID, nil, "error StorageAPI not set") - return 0, fmt.Errorf("error StorageAPI not set") - } - chanID, err := c.StorageAPI.RegisterStorageChangeChannel(*sub) if err != nil { return 0, err @@ -85,7 +95,7 @@ func (c *WSConn) initStorageChangeListener(reqID float64, params interface{}) (i // StorageChangeListener for listening to state change channels type StorageChangeListener struct { Channel chan *state.SubscriptionResult - wsconn *WSConn + wsconn WSConnAPI ChanID byte subID int } @@ -99,7 +109,7 @@ func (l *StorageChangeListener) Listen() { result := make(map[string]interface{}) result["block"] = change.Hash.String() - changes := [][]string{} + changes := make([][]string, 0, len(change.Changes)) for _, v := range change.Changes { kv := []string{common.BytesToHex(v.Key), common.BytesToHex(v.Value)} changes = append(changes, kv) @@ -117,7 +127,7 @@ func (l *StorageChangeListener) Listen() { // BlockListener to handle listening for blocks importedChan type BlockListener struct { Channel chan *types.Block - wsconn *WSConn + wsconn WSConnAPI ChanID byte subID int } @@ -169,7 +179,7 @@ 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 } @@ -219,7 +229,7 @@ func (l *BlockFinalizedListener) Listen() { // ExtrinsicSubmitListener to handle listening for extrinsic events type ExtrinsicSubmitListener struct { - wsconn *WSConn + wsconn WSConnAPI subID int extrinsic types.Extrinsic @@ -343,6 +353,8 @@ func (c *WSConn) initRuntimeVersionListener(reqID float64) (int, error) { // 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 @@ -358,5 +370,4 @@ func (l *RuntimeVersionListener) Listen() { ver.Apis = modules.ConvertAPIs(rtVersion.APIItems()) l.wsconn.safeSend(newSubscriptionResponse("state_runtimeVersion", l.subID, ver)) - } diff --git a/dot/rpc/websocketListeners_test.go b/dot/rpc/subscription/listeners_test.go similarity index 98% rename from dot/rpc/websocketListeners_test.go rename to dot/rpc/subscription/listeners_test.go index 1551078bb4..eb466241da 100644 --- a/dot/rpc/websocketListeners_test.go +++ b/dot/rpc/subscription/listeners_test.go @@ -13,7 +13,7 @@ // // You should have received a copy of the GNU Lesser General Public License // along with the gossamer library. If not, see . -package rpc +package subscription import ( "math/big" @@ -39,7 +39,7 @@ func TestStorageChangeListener_Listen(t *testing.T) { notifyChan := make(chan *state.SubscriptionResult) mockConnection := &MockWSConnAPI{} scl := StorageChangeListener{ - channel: notifyChan, + Channel: notifyChan, wsconn: mockConnection, } @@ -76,7 +76,7 @@ func TestBlockListener_Listen(t *testing.T) { notifyChan := make(chan *types.Block) mockConnection := &MockWSConnAPI{} bl := BlockListener{ - channel: notifyChan, + Channel: notifyChan, wsconn: mockConnection, } diff --git a/dot/rpc/subscription/messages.go b/dot/rpc/subscription/messages.go index 0722483689..14aafcf23f 100644 --- a/dot/rpc/subscription/messages.go +++ b/dot/rpc/subscription/messages.go @@ -15,49 +15,47 @@ // along with the gossamer library. If not, see . package subscription -import ( - "math/big" -) +import "math/big" -// BaseResponseJSON for base json response -type BaseResponseJSON struct { - Jsonrpc string `json:"jsonrpc"` - Method string `json:"method"` - Params Params `json:"params"` +// SubscriptionBaseResponseJSON for base json response +type SubscriptionBaseResponseJSON struct { + Jsonrpc string `json:"jsonrpc"` + Method string `json:"method"` + Params SubscriptionParams `json:"params"` } -// Params for json param response -type Params struct { +// SubscriptionParams for json param response +type SubscriptionParams struct { Result interface{} `json:"result"` SubscriptionID int `json:"subscription"` } -func newSubcriptionBaseResponseJSON() BaseResponseJSON { - return BaseResponseJSON{ +func newSubcriptionBaseResponseJSON() SubscriptionBaseResponseJSON { + return SubscriptionBaseResponseJSON{ Jsonrpc: "2.0", } } -func newSubscriptionResponse(method string, subID int, result interface{}) BaseResponseJSON { - return BaseResponseJSON{ +func newSubscriptionResponse(method string, subID int, result interface{}) SubscriptionBaseResponseJSON { + return SubscriptionBaseResponseJSON{ Jsonrpc: "2.0", Method: method, - Params: Params{ + Params: SubscriptionParams{ Result: result, SubscriptionID: subID, }, } } -// ResponseJSON for json subscription responses -type ResponseJSON struct { +// SubscriptionResponseJSON for json subscription responses +type SubscriptionResponseJSON struct { Jsonrpc string `json:"jsonrpc"` Result int `json:"result"` ID float64 `json:"id"` } -func newSubscriptionResponseJSON(subID int, reqID float64) ResponseJSON { - return ResponseJSON{ +func newSubscriptionResponseJSON(subID int, reqID float64) SubscriptionResponseJSON { + return SubscriptionResponseJSON{ Jsonrpc: "2.0", Result: subID, ID: reqID, diff --git a/dot/rpc/websocketListeners.go b/dot/rpc/websocketListeners.go deleted file mode 100644 index 56d4c5749f..0000000000 --- a/dot/rpc/websocketListeners.go +++ /dev/null @@ -1,368 +0,0 @@ -// 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 rpc - -import ( - "fmt" - "reflect" - - "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" -) - -// Listener interface for functions that define Listener related functions -type Listener interface { - Listen() -} - -func (c *WSConn) startListener(lid int) { - go c.subscriptions[lid].Listen() -} - -func (c *WSConn) initStorageChangeListener(reqID float64, params interface{}) (int, error) { - if c.storageAPI == nil { - c.safeSendError(reqID, nil, "error StorageAPI not set") - return 0, fmt.Errorf("error StorageAPI not set") - } - - scl := &StorageChangeListener{ - channel: make(chan *state.SubscriptionResult), - wsconn: c, - } - sub := &state.StorageSubscription{ - Filter: make(map[string][]byte), - Listener: scl.channel, - } - - pA, ok := params.([]interface{}) - if !ok { - return 0, fmt.Errorf("unknow 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("unknow parameter type") - } - sub.Filter[data] = []byte{} - } - case string: - sub.Filter[p] = []byte{} - default: - return 0, fmt.Errorf("unknow parameter type") - } - } - - chanID, err := c.storageAPI.RegisterStorageChangeChannel(*sub) - if err != nil { - return 0, err - } - 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) - - return scl.subID, nil -} - -// StorageChangeListener for listening to state change channels -type StorageChangeListener struct { - channel chan *state.SubscriptionResult - wsconn WSConnAPI - chanID byte - subID int -} - -// 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 := make([][]string, 0, len(change.Changes)) - 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) - } -} - -// BlockListener to handle listening for blocks importedChan -type BlockListener struct { - channel chan *types.Block - 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 -} - -// Listen implementation of Listen interface to listen for importedChan changes -func (l *BlockListener) Listen() { - for block := range l.channel { - if block == nil { - continue - } - head, err := modules.HeaderToJSON(*block.Header) - if err != nil { - logger.Error("failed to convert header to JSON", "error", err) - } - - res := newSubcriptionBaseResponseJSON() - res.Method = "chain_newHead" - res.Params.Result = head - res.Params.SubscriptionID = l.subID - l.wsconn.safeSend(res) - } -} - -// BlockFinalizedListener to handle listening for finalized blocks -type BlockFinalizedListener struct { - channel chan *types.Header - 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 -} - -// Listen implementation of Listen interface to listen for importedChan changes -func (l *BlockFinalizedListener) Listen() { - for header := range l.channel { - if header == nil { - continue - } - head, err := modules.HeaderToJSON(*header) - if err != nil { - logger.Error("failed to convert header to JSON", "error", err) - } - res := newSubcriptionBaseResponseJSON() - res.Method = "chain_finalizedHead" - res.Params.Result = head - res.Params.SubscriptionID = l.subID - l.wsconn.safeSend(res) - } -} - -// ExtrinsicSubmitListener to handle listening for extrinsic events -type ExtrinsicSubmitListener struct { - wsconn WSConnAPI - subID int - extrinsic types.Extrinsic - - importedChan chan *types.Block - importedChanID byte - importedHash common.Hash - finalizedChan chan *types.Header - finalizedChanID byte -} - -// 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 - go func() { - for block := range l.importedChan { - if block == nil { - continue - } - exts, err := block.Body.AsExtrinsics() - if err != nil { - fmt.Printf("error %v\n", err) - } - for _, v := range exts { - if reflect.DeepEqual(v, l.extrinsic) { - resM := make(map[string]interface{}) - resM["inBlock"] = block.Header.Hash().String() - - l.importedHash = block.Header.Hash() - l.wsconn.safeSend(newSubscriptionResponse(AuthorExtrinsicUpdates, l.subID, resM)) - } - } - } - }() - - // listen for finalized headers - go func() { - for header := range l.finalizedChan { - if reflect.DeepEqual(l.importedHash, header.Hash()) { - resM := make(map[string]interface{}) - resM["finalized"] = header.Hash().String() - l.wsconn.safeSend(newSubscriptionResponse(AuthorExtrinsicUpdates, l.subID, resM)) - } - } - }() -} - -// 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 -} - -// 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 - } - ver := modules.StateRuntimeVersionResponse{} - - ver.SpecName = string(rtVersion.SpecName()) - ver.ImplName = string(rtVersion.ImplName()) - ver.AuthoringVersion = rtVersion.AuthoringVersion() - ver.SpecVersion = rtVersion.SpecVersion() - ver.ImplVersion = rtVersion.ImplVersion() - ver.TransactionVersion = rtVersion.TransactionVersion() - ver.Apis = modules.ConvertAPIs(rtVersion.APIItems()) - - l.wsconn.safeSend(newSubscriptionResponse("state_runtimeVersion", l.subID, ver)) -} diff --git a/dot/rpc/websocketMessages.go b/dot/rpc/websocketMessages.go deleted file mode 100644 index 5d072a19c1..0000000000 --- a/dot/rpc/websocketMessages.go +++ /dev/null @@ -1,109 +0,0 @@ -// 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 rpc - -import "math/big" - -// SubscriptionBaseResponseJSON for base json response -type SubscriptionBaseResponseJSON struct { - Jsonrpc string `json:"jsonrpc"` - Method string `json:"method"` - Params SubscriptionParams `json:"params"` -} - -// SubscriptionParams for json param response -type SubscriptionParams struct { - Result interface{} `json:"result"` - SubscriptionID int `json:"subscription"` -} - -func newSubcriptionBaseResponseJSON() SubscriptionBaseResponseJSON { - return SubscriptionBaseResponseJSON{ - Jsonrpc: "2.0", - } -} - -func newSubscriptionResponse(method string, subID int, result interface{}) SubscriptionBaseResponseJSON { - return SubscriptionBaseResponseJSON{ - Jsonrpc: "2.0", - Method: method, - Params: SubscriptionParams{ - Result: result, - SubscriptionID: subID, - }, - } -} - -// SubscriptionResponseJSON for json subscription responses -type SubscriptionResponseJSON struct { - Jsonrpc string `json:"jsonrpc"` - Result int `json:"result"` - ID float64 `json:"id"` -} - -func newSubscriptionResponseJSON(subID int, reqID float64) SubscriptionResponseJSON { - return SubscriptionResponseJSON{ - 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{}) { - if c.wsconn == nil { - logger.Debug("error trying to send message to nil websocket connection") - return - } - 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) { - if c.wsconn == nil { - logger.Debug("error trying to send message to nil websocket connection") - return - } - 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) - } -} From 3230d709810715b8115734ac77cc9192d1ed66c9 Mon Sep 17 00:00:00 2001 From: Edward Mack Date: Fri, 19 Mar 2021 16:06:07 -0400 Subject: [PATCH 10/30] lint --- dot/rpc/subscription/listeners_test.go | 4 ++-- dot/rpc/subscription/messages.go | 32 +++++++++++++------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/dot/rpc/subscription/listeners_test.go b/dot/rpc/subscription/listeners_test.go index eb466241da..716962fbdd 100644 --- a/dot/rpc/subscription/listeners_test.go +++ b/dot/rpc/subscription/listeners_test.go @@ -28,11 +28,11 @@ import ( ) type MockWSConnAPI struct { - lastMessage SubscriptionBaseResponseJSON + lastMessage BaseResponseJSON } func (m *MockWSConnAPI) safeSend(msg interface{}) { - m.lastMessage = msg.(SubscriptionBaseResponseJSON) + m.lastMessage = msg.(BaseResponseJSON) } func TestStorageChangeListener_Listen(t *testing.T) { diff --git a/dot/rpc/subscription/messages.go b/dot/rpc/subscription/messages.go index 14aafcf23f..24f4fd7df4 100644 --- a/dot/rpc/subscription/messages.go +++ b/dot/rpc/subscription/messages.go @@ -17,45 +17,45 @@ package subscription import "math/big" -// SubscriptionBaseResponseJSON for base json response -type SubscriptionBaseResponseJSON struct { - Jsonrpc string `json:"jsonrpc"` - Method string `json:"method"` - Params SubscriptionParams `json:"params"` +// BaseResponseJSON for base json response +type BaseResponseJSON struct { + Jsonrpc string `json:"jsonrpc"` + Method string `json:"method"` + Params Params `json:"params"` } -// SubscriptionParams for json param response -type SubscriptionParams struct { +// Params for json param response +type Params struct { Result interface{} `json:"result"` SubscriptionID int `json:"subscription"` } -func newSubcriptionBaseResponseJSON() SubscriptionBaseResponseJSON { - return SubscriptionBaseResponseJSON{ +func newSubcriptionBaseResponseJSON() BaseResponseJSON { + return BaseResponseJSON{ Jsonrpc: "2.0", } } -func newSubscriptionResponse(method string, subID int, result interface{}) SubscriptionBaseResponseJSON { - return SubscriptionBaseResponseJSON{ +func newSubscriptionResponse(method string, subID int, result interface{}) BaseResponseJSON { + return BaseResponseJSON{ Jsonrpc: "2.0", Method: method, - Params: SubscriptionParams{ + Params: Params{ Result: result, SubscriptionID: subID, }, } } -// SubscriptionResponseJSON for json subscription responses -type SubscriptionResponseJSON struct { +// ResponseJSON for json subscription responses +type ResponseJSON struct { Jsonrpc string `json:"jsonrpc"` Result int `json:"result"` ID float64 `json:"id"` } -func newSubscriptionResponseJSON(subID int, reqID float64) SubscriptionResponseJSON { - return SubscriptionResponseJSON{ +func newSubscriptionResponseJSON(subID int, reqID float64) ResponseJSON { + return ResponseJSON{ Jsonrpc: "2.0", Result: subID, ID: reqID, From d076caff0e1c4ba7e5ba5101e77f46d32e3fdba2 Mon Sep 17 00:00:00 2001 From: Edward Mack Date: Fri, 19 Mar 2021 16:35:58 -0400 Subject: [PATCH 11/30] add sleep timer --- dot/rpc/subscription/listeners_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dot/rpc/subscription/listeners_test.go b/dot/rpc/subscription/listeners_test.go index 716962fbdd..f6b2a5539b 100644 --- a/dot/rpc/subscription/listeners_test.go +++ b/dot/rpc/subscription/listeners_test.go @@ -68,7 +68,7 @@ func TestStorageChangeListener_Listen(t *testing.T) { expectedRespones.Params.Result = expected notifyChan <- change - + time.Sleep(time.Millisecond * 10) require.Equal(t, expectedRespones, mockConnection.lastMessage) } @@ -93,7 +93,7 @@ func TestBlockListener_Listen(t *testing.T) { go bl.Listen() notifyChan <- block - + time.Sleep(time.Millisecond * 10) require.Equal(t, expectedResposnse, mockConnection.lastMessage) } @@ -117,7 +117,7 @@ func TestBlockFinalizedListener_Listen(t *testing.T) { go bfl.Listen() notifyChan <- header - + time.Sleep(time.Millisecond * 10) require.Equal(t, expectedResponse, mockConnection.lastMessage) } @@ -149,11 +149,11 @@ func TestExtrinsicSubmitListener_Listen(t *testing.T) { go esl.Listen() notifyImportedChan <- block - time.Sleep(time.Millisecond) + time.Sleep(time.Millisecond * 10) require.Equal(t, expectedImportedRespones, mockConnection.lastMessage) notifyFinalizedChan <- header - time.Sleep(time.Millisecond) + 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) From ea3b80aaff3a49a7e3b78c47c16ed23bec4cd518 Mon Sep 17 00:00:00 2001 From: Edward Mack Date: Mon, 22 Mar 2021 15:28:30 -0400 Subject: [PATCH 12/30] refactor websocket files --- dot/rpc/subscription/listeners.go | 171 ----------------------- dot/rpc/subscription/messages.go | 37 ----- dot/rpc/subscription/websocket.go | 219 +++++++++++++++++++++++++++++- 3 files changed, 216 insertions(+), 211 deletions(-) diff --git a/dot/rpc/subscription/listeners.go b/dot/rpc/subscription/listeners.go index 75ad803f6e..99ad1c925c 100644 --- a/dot/rpc/subscription/listeners.go +++ b/dot/rpc/subscription/listeners.go @@ -35,62 +35,7 @@ type WSConnAPI interface { safeSend(interface{}) } -func (c *WSConn) startListener(lid int) { - go c.Subscriptions[lid].Listen() -} - -func (c *WSConn) initStorageChangeListener(reqID float64, params interface{}) (int, error) { - if c.StorageAPI == nil { - c.safeSendError(reqID, nil, "error StorageAPI not set") - return 0, fmt.Errorf("error StorageAPI not set") - } - - scl := &StorageChangeListener{ - Channel: make(chan *state.SubscriptionResult), - wsconn: c, - } - sub := &state.StorageSubscription{ - Filter: make(map[string][]byte), - Listener: scl.Channel, - } - - pA, ok := params.([]interface{}) - if !ok { - return 0, fmt.Errorf("unknow 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("unknow parameter type") - } - sub.Filter[data] = []byte{} - } - case string: - sub.Filter[p] = []byte{} - default: - return 0, fmt.Errorf("unknow parameter type") - } - } - - chanID, err := c.StorageAPI.RegisterStorageChangeChannel(*sub) - if err != nil { - return 0, err - } - 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) - - return scl.subID, nil -} // StorageChangeListener for listening to state change channels type StorageChangeListener struct { @@ -132,31 +77,6 @@ type BlockListener struct { 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 -} - // Listen implementation of Listen interface to listen for importedChan changes func (l *BlockListener) Listen() { for block := range l.Channel { @@ -184,31 +104,6 @@ type BlockFinalizedListener struct { 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 -} - // Listen implementation of Listen interface to listen for importedChan changes func (l *BlockFinalizedListener) Listen() { for header := range l.channel { @@ -243,55 +138,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 @@ -334,23 +180,6 @@ type RuntimeVersionListener struct { 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 -} - // 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 diff --git a/dot/rpc/subscription/messages.go b/dot/rpc/subscription/messages.go index 24f4fd7df4..b9fdf8600b 100644 --- a/dot/rpc/subscription/messages.go +++ b/dot/rpc/subscription/messages.go @@ -15,8 +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"` @@ -62,40 +60,5 @@ func newSubscriptionResponseJSON(subID int, reqID float64) ResponseJSON { } } -// 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 4df42907ae..53dfb6c2f5 100644 --- a/dot/rpc/subscription/websocket.go +++ b/dot/rpc/subscription/websocket.go @@ -20,18 +20,20 @@ import ( "bytes" "encoding/json" "fmt" + "github.com/ChainSafe/gossamer/dot/state" + "github.com/ChainSafe/gossamer/dot/types" + "github.com/ChainSafe/gossamer/lib/common" "io/ioutil" "math/big" "net/http" "strings" "sync" - - "github.com/ChainSafe/gossamer/dot/rpc/modules" log "github.com/ChainSafe/log15" + "github.com/ChainSafe/gossamer/dot/rpc/modules" "github.com/gorilla/websocket" ) -var logger = log.New("pkg", "subscription") +var logger = log.New("pkg", "rpc") // WSConn struct to hold WebSocket Connection references type WSConn struct { @@ -161,3 +163,214 @@ func (c *WSConn) HandleComm() { c.safeSend(wsSend) } } + +func (c *WSConn) initStorageChangeListener(reqID float64, params interface{}) (int, error) { + if c.StorageAPI == nil { + c.safeSendError(reqID, nil, "error StorageAPI not set") + return 0, fmt.Errorf("error StorageAPI not set") + } + + scl := &StorageChangeListener{ + Channel: make(chan *state.SubscriptionResult), + wsconn: c, + } + sub := &state.StorageSubscription{ + Filter: make(map[string][]byte), + Listener: scl.Channel, + } + + pA, ok := params.([]interface{}) + if !ok { + return 0, fmt.Errorf("unknow 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("unknow parameter type") + } + sub.Filter[data] = []byte{} + } + case string: + sub.Filter[p] = []byte{} + default: + return 0, fmt.Errorf("unknow parameter type") + } + } + + chanID, err := c.StorageAPI.RegisterStorageChangeChannel(*sub) + if err != nil { + return 0, err + } + 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) + + return scl.subID, nil +} + +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 +} + +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 +} + +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 +} + +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 +} + +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 int) { + go c.Subscriptions[lid].Listen() +} From 90706bef20e59d3121dd8cf601c343676cb8f466 Mon Sep 17 00:00:00 2001 From: Edward Mack Date: Mon, 22 Mar 2021 15:30:34 -0400 Subject: [PATCH 13/30] lint --- dot/rpc/subscription/listeners.go | 2 -- dot/rpc/subscription/messages.go | 3 --- dot/rpc/subscription/websocket.go | 9 +++++---- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/dot/rpc/subscription/listeners.go b/dot/rpc/subscription/listeners.go index 99ad1c925c..5c83100c7f 100644 --- a/dot/rpc/subscription/listeners.go +++ b/dot/rpc/subscription/listeners.go @@ -35,8 +35,6 @@ type WSConnAPI interface { safeSend(interface{}) } - - // StorageChangeListener for listening to state change channels type StorageChangeListener struct { Channel chan *state.SubscriptionResult diff --git a/dot/rpc/subscription/messages.go b/dot/rpc/subscription/messages.go index b9fdf8600b..038f279561 100644 --- a/dot/rpc/subscription/messages.go +++ b/dot/rpc/subscription/messages.go @@ -59,6 +59,3 @@ func newSubscriptionResponseJSON(subID int, reqID float64) ResponseJSON { ID: reqID, } } - - - diff --git a/dot/rpc/subscription/websocket.go b/dot/rpc/subscription/websocket.go index 53dfb6c2f5..54b81e24d0 100644 --- a/dot/rpc/subscription/websocket.go +++ b/dot/rpc/subscription/websocket.go @@ -20,16 +20,17 @@ import ( "bytes" "encoding/json" "fmt" - "github.com/ChainSafe/gossamer/dot/state" - "github.com/ChainSafe/gossamer/dot/types" - "github.com/ChainSafe/gossamer/lib/common" "io/ioutil" "math/big" "net/http" "strings" "sync" - log "github.com/ChainSafe/log15" + "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" + log "github.com/ChainSafe/log15" "github.com/gorilla/websocket" ) From c76452b617788eed5ae2c294a89bb07710e6528a Mon Sep 17 00:00:00 2001 From: Edward Mack Date: Tue, 23 Mar 2021 11:46:05 -0400 Subject: [PATCH 14/30] a locks to fix data race --- dot/state/storage.go | 5 +++-- lib/trie/trie.go | 7 +++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/dot/state/storage.go b/dot/state/storage.go index cda817a26b..bc2bd7e4df 100644 --- a/dot/state/storage.go +++ b/dot/state/storage.go @@ -142,7 +142,8 @@ func (s *StorageState) TrieState(root *common.Hash) (*rtstorage.TrieState, error // notifyStorageSubscriptions interates StorageState subscrirptions and notifies listener // if the value filter value has changed func (s *StorageState) notifyStorageSubscriptions(root common.Hash) error { - + s.changedLock.Lock() + defer s.changedLock.Unlock() for subKey := range s.subscriptions { err := s.notifyStorageSubscription(root, subKey) if err != nil { @@ -157,7 +158,7 @@ func (s *StorageState) notifyStorageSubscriptions(root common.Hash) error { func (s *StorageState) notifyStorageSubscription(root common.Hash, subID byte) error { s.lock.RLock() t := s.tries[root] - s.lock.RUnlock() + defer s.lock.RUnlock() if t == nil { return errTrieDoesNotExist(root) diff --git a/lib/trie/trie.go b/lib/trie/trie.go index 48dd9e5a09..32589ef450 100644 --- a/lib/trie/trie.go +++ b/lib/trie/trie.go @@ -18,6 +18,7 @@ package trie import ( "bytes" + "sync" "github.com/ChainSafe/gossamer/lib/common" ) @@ -32,6 +33,7 @@ type Trie struct { generation uint64 root node children map[common.Hash]*Trie // Used to store the child tries. + mutex sync.Mutex } // NewEmptyTrie creates a trie with a nil root @@ -128,6 +130,8 @@ func (t *Trie) Hash() (common.Hash, error) { // Entries returns all the key-value pairs in the trie as a map of keys to values func (t *Trie) Entries() map[string][]byte { + t.mutex.Lock() + defer t.mutex.Unlock() return t.entries(t.root, nil, make(map[string][]byte)) } @@ -239,9 +243,12 @@ func (t *Trie) Put(key, value []byte) { } func (t *Trie) tryPut(key, value []byte) { + t.mutex.Lock() + defer t.mutex.Unlock() k := keyToNibbles(key) t.root = t.insert(t.root, k, &leaf{key: nil, value: value, dirty: true, generation: t.generation}) + } // TryPut attempts to insert a key with value into the trie From dcf5d2acb1c90cfd42435325773707bf368d50ae Mon Sep 17 00:00:00 2001 From: Edward Mack Date: Mon, 12 Apr 2021 20:42:08 -0400 Subject: [PATCH 15/30] implement observer design pattern --- dot/rpc/http.go | 5 +- dot/rpc/modules/api.go | 4 +- dot/rpc/subscription/listeners.go | 62 +++++++----- dot/rpc/subscription/listeners_test.go | 13 +-- dot/rpc/subscription/websocket.go | 37 +++---- dot/rpc/websocket_test.go | 5 +- dot/state/storage.go | 88 ++-------------- dot/state/storage_notify.go | 117 +++++++++++++++------- dot/state/storage_notify_test.go | 133 +++++++++++-------------- 9 files changed, 210 insertions(+), 254 deletions(-) diff --git a/dot/rpc/http.go b/dot/rpc/http.go index fd98877331..eb7665cba9 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) 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 5c83100c7f..46a9109c6d 100644 --- a/dot/rpc/subscription/listeners.go +++ b/dot/rpc/subscription/listeners.go @@ -35,38 +35,48 @@ type WSConnAPI interface { safeSend(interface{}) } -// StorageChangeListener for listening to state change channels -type StorageChangeListener struct { - Channel chan *state.SubscriptionResult - wsconn WSConnAPI - ChanID byte - subID int +// StorageObserver struct to hold data for observer (Observer Design Pattern) +type StorageObserver struct { + id int + filter map[string][]byte + wsconn WSConnAPI } -// 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 := make([][]string, 0, len(change.Changes)) - for _, v := range change.Changes { - kv := []string{common.BytesToHex(v.Key), common.BytesToHex(v.Value)} - changes = append(changes, kv) - } - result["changes"] = changes +// Update is called to notify observer of new value +func (s *StorageObserver) Update(change *state.SubscriptionResult) { + if change == nil { + return + } - res := newSubcriptionBaseResponseJSON() - res.Method = "state_storage" - res.Params.Result = result - res.Params.SubscriptionID = l.subID - l.wsconn.safeSend(res) + result := make(map[string]interface{}) + result["block"] = change.Hash.String() + changes := make([][]string, 0, len(change.Changes)) + 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 = s.GetID() + s.wsconn.safeSend(res) } +// GetID the id for the Observer +func (s *StorageObserver) GetID() int { + return s.id +} + +// 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 diff --git a/dot/rpc/subscription/listeners_test.go b/dot/rpc/subscription/listeners_test.go index f6b2a5539b..bc8c0ffb43 100644 --- a/dot/rpc/subscription/listeners_test.go +++ b/dot/rpc/subscription/listeners_test.go @@ -35,16 +35,13 @@ func (m *MockWSConnAPI) safeSend(msg interface{}) { m.lastMessage = msg.(BaseResponseJSON) } -func TestStorageChangeListener_Listen(t *testing.T) { - notifyChan := make(chan *state.SubscriptionResult) +func TestStorageObserver_Update(t *testing.T) { mockConnection := &MockWSConnAPI{} - scl := StorageChangeListener{ - Channel: notifyChan, - wsconn: mockConnection, + storageObserver := StorageObserver{ + id: 0, + wsconn: mockConnection, } - go scl.Listen() - data := []state.KeyValue{{ Key: []byte("key"), Value: []byte("value"), @@ -67,7 +64,7 @@ func TestStorageChangeListener_Listen(t *testing.T) { expectedRespones.Method = "state_storage" expectedRespones.Params.Result = expected - notifyChan <- change + storageObserver.Update(change) time.Sleep(time.Millisecond * 10) require.Equal(t, expectedRespones, mockConnection.lastMessage) } diff --git a/dot/rpc/subscription/websocket.go b/dot/rpc/subscription/websocket.go index 54b81e24d0..d013f5b00a 100644 --- a/dot/rpc/subscription/websocket.go +++ b/dot/rpc/subscription/websocket.go @@ -27,7 +27,6 @@ import ( "sync" "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" log "github.com/ChainSafe/log15" @@ -84,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 { @@ -171,13 +170,9 @@ func (c *WSConn) initStorageChangeListener(reqID float64, params interface{}) (i return 0, fmt.Errorf("error StorageAPI not set") } - scl := &StorageChangeListener{ - Channel: make(chan *state.SubscriptionResult), - wsconn: c, - } - sub := &state.StorageSubscription{ - Filter: make(map[string][]byte), - Listener: scl.Channel, + myObs := &StorageObserver{ + filter: make(map[string][]byte), + wsconn: c, } pA, ok := params.([]interface{}) @@ -192,30 +187,26 @@ func (c *WSConn) initStorageChangeListener(reqID float64, params interface{}) (i if !ok { return 0, fmt.Errorf("unknow parameter type") } - sub.Filter[data] = []byte{} + myObs.filter[data] = []byte{} } case string: - sub.Filter[p] = []byte{} + myObs.filter[p] = []byte{} default: return 0, fmt.Errorf("unknow parameter type") } } - chanID, err := c.StorageAPI.RegisterStorageChangeChannel(*sub) - if err != nil { - return 0, err - } - scl.ChanID = chanID - c.qtyListeners++ - scl.subID = c.qtyListeners - c.Subscriptions[scl.subID] = scl - c.StorageSubChannels[scl.subID] = chanID + myObs.id = c.qtyListeners + + c.StorageAPI.RegisterStorageObserver(myObs) + + c.Subscriptions[myObs.id] = myObs - initRes := newSubscriptionResponseJSON(scl.subID, reqID) + initRes := newSubscriptionResponseJSON(myObs.id, reqID) c.safeSend(initRes) - return scl.subID, nil + return myObs.id, nil } func (c *WSConn) initBlockListener(reqID float64) (int, error) { diff --git a/dot/rpc/websocket_test.go b/dot/rpc/websocket_test.go index 8d977f425a..8f84d88fbf 100644 --- a/dot/rpc/websocket_test.go +++ b/dot/rpc/websocket_test.go @@ -150,11 +150,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 3f4aaf1d6b..5533cb0419 100644 --- a/dot/state/storage.go +++ b/dot/state/storage.go @@ -20,7 +20,6 @@ import ( "encoding/binary" "errors" "fmt" - "reflect" "sync" "github.com/ChainSafe/chaindb" @@ -51,8 +50,7 @@ type StorageState struct { lock sync.RWMutex // change notifiers - changedLock sync.RWMutex - subscriptions map[byte]*StorageSubscription + observerList []Observer syncing bool } @@ -71,11 +69,10 @@ 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), }, nil } @@ -117,11 +114,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 } @@ -164,75 +157,6 @@ func (s *StorageState) TrieState(root *common.Hash) (*rtstorage.TrieState, error return curr, nil } -// notifyStorageSubscriptions interates StorageState subscrirptions and notifies listener -// if the value filter value has changed -func (s *StorageState) notifyStorageSubscriptions(root common.Hash) error { - s.changedLock.Lock() - defer s.changedLock.Unlock() - for subKey := range s.subscriptions { - err := s.notifyStorageSubscription(root, subKey) - if err != nil { - return err - } - } - return nil -} - -// notifyStorageSubscriptions interates StorageState subscrirptions and notifies listener -// if the value filter value has changed -func (s *StorageState) notifyStorageSubscription(root common.Hash, subID byte) error { - s.lock.RLock() - t := s.tries[root] - defer s.lock.RUnlock() - - if t == nil { - return errTrieDoesNotExist(root) - } - - // notify subscribers of database changes - s.changedLock.Lock() - defer s.changedLock.Unlock() - - sub := s.subscriptions[subID] - 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, cachedValue := range sub.Filter { - 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) - s.subscriptions[subID].Filter[k] = value - } - } - } - if len(subRes.Changes) > 0 { - go func(ch chan<- *SubscriptionResult) { - ch <- subRes - }(sub.Listener) - } - - 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 295e61cc8b..7bca0ca2f5 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,51 +34,99 @@ type SubscriptionResult struct { Changes []KeyValue } -//StorageSubscription holds data for Subscription to Storage -type StorageSubscription struct { - Filter map[string][]byte - Listener chan<- *SubscriptionResult +// Observer interface defines functions needed for observers, Observer Design Pattern +type Observer interface { + Update(result *SubscriptionResult) + GetID() int + 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") - } - - var id byte - for { - id = generateID() - if s.subscriptions[id] == nil { - break - } - } - - s.changedLock.RUnlock() - - s.changedLock.Lock() - s.subscriptions[id] = &sub - s.changedLock.Unlock() - // notifyStorageSubscription here to send storage value of current state + // 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) } go func() { - if err := s.notifyStorageSubscription(sr, id); err != nil { + if err := s.notifyObserver(sr, o); err != nil { logger.Warn("failed to notify storage subscriptions", "error", err) } }() - return id, nil + +} + +// UnregisterStorageObserver removes observer from notification list +func (s *StorageState) UnregisterStorageObserver(o Observer) { + s.observerList = removeFromSlice(s.observerList, o) +} + +func (s *StorageState) notifyAll(root common.Hash) { + for _, observer := range s.observerList { + err := s.notifyObserver(root, observer) + if err != nil { + logger.Warn("failed to notify storage subscriptions", "error", err) + } + } } -// 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() +func (s *StorageState) notifyObserver(root common.Hash, o Observer) error { + s.lock.RLock() + t := s.tries[root] + defer s.lock.RUnlock() + + if t == nil { + return errTrieDoesNotExist(root) + } - delete(s.subscriptions, id) + subRes := &SubscriptionResult{ + Hash: root, + } + if len(o.GetFilter()) == 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, 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 { + go func() { + o.Update(subRes) + }() + } + + return nil +} + +func removeFromSlice(observerList []Observer, observerToRemove Observer) []Observer { + observerListLength := len(observerList) + for i, observer := range observerList { + if observerToRemove.GetID() == observer.GetID() { + observerList[observerListLength-1], observerList[i] = 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 a1c5081a5d..e49f9534d0 100644 --- a/dot/state/storage_notify_test.go +++ b/dot/state/storage_notify_test.go @@ -30,33 +30,46 @@ import ( "github.com/stretchr/testify/require" ) -func TestStorageState_RegisterStorageChangeChannel(t *testing.T) { +type MockStorageObserver struct { + id int + filter map[string][]byte + lastUpdate *SubscriptionResult +} + +func (m *MockStorageObserver) Update(change *SubscriptionResult) { + m.lastUpdate = change +} +func (m *MockStorageObserver) GetID() int { + 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][]byte), - 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) + require.Equal(t, expectedResult, observer.lastUpdate) } func TestStorageState_RegisterStorageChangeChannel_Multi(t *testing.T) { @@ -65,15 +78,15 @@ func TestStorageState_RegisterStorageChangeChannel_Multi(t *testing.T) { 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: i, } - ids[i], err = ss.RegisterStorageChangeChannel(sub) + observers = append(observers, observer) + ss.RegisterStorageObserver(observer) require.NoError(t, err) } @@ -85,29 +98,16 @@ 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) + for _, observer := range observers { + require.NotNil(t, observer.lastUpdate.Hash) + require.Equal(t, key1, observer.lastUpdate.Changes[0].Key) + require.Equal(t, value1, observer.lastUpdate.Changes[0].Value) } - wg.Wait() - - for _, id := range ids { - ss.UnregisterStorageChangeChannel(id) + for _, observer := range observers { + ss.UnregisterStorageObserver(observer) } } @@ -119,49 +119,36 @@ func TestStorageState_RegisterStorageChangeChannel_Multi_Filter(t *testing.T) { key1 := []byte("key1") value1 := []byte("value1") - ts.Set(key1, value1) - err = ss.StoreTrie(ts) - 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{ - Filter: map[string][]byte{ + observer := &MockStorageObserver{ + id: i, + filter: map[string][]byte{ common.BytesToHex(key1): {}, }, - Listener: chs[i], } - ids[i], err = ss.RegisterStorageChangeChannel(sub) - require.NoError(t, err) + observers = append(observers, observer) + ss.RegisterStorageObserver(observer) } - var wg sync.WaitGroup - wg.Add(num) - - for i, ch := range chs { - - go func(i int, ch chan *SubscriptionResult) { - defer wg.Done() - 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) - } - }(i, ch) + ts.Set(key1, value1) + err = ss.StoreTrie(ts) + require.NoError(t, err) - } + time.Sleep(time.Millisecond) - wg.Wait() + for _, observer := range observers { + + require.NotNil(t, observer.lastUpdate.Hash) + require.Equal(t, key1, observer.lastUpdate.Changes[0].Key) + require.Equal(t, value1, observer.lastUpdate.Changes[0].Value) + + } - for _, id := range ids { - ss.UnregisterStorageChangeChannel(id) + for _, observer := range observers { + ss.UnregisterStorageObserver(observer) } } From a44e40fb92bb513c25b8cf284748513782b169a1 Mon Sep 17 00:00:00 2001 From: Edward Mack Date: Tue, 13 Apr 2021 17:34:22 -0400 Subject: [PATCH 16/30] fix race conditions --- dot/state/storage.go | 1 + dot/state/storage_notify.go | 10 ++++++++-- dot/state/storage_notify_test.go | 20 ++++++++++++++------ 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/dot/state/storage.go b/dot/state/storage.go index 5533cb0419..cad387a11d 100644 --- a/dot/state/storage.go +++ b/dot/state/storage.go @@ -50,6 +50,7 @@ type StorageState struct { lock sync.RWMutex // change notifiers + changedLock sync.RWMutex observerList []Observer syncing bool diff --git a/dot/state/storage_notify.go b/dot/state/storage_notify.go index 7bca0ca2f5..f15f50cc78 100644 --- a/dot/state/storage_notify.go +++ b/dot/state/storage_notify.go @@ -60,10 +60,12 @@ func (s *StorageState) RegisterStorageObserver(o Observer) { // UnregisterStorageObserver removes observer from notification list func (s *StorageState) UnregisterStorageObserver(o Observer) { - s.observerList = removeFromSlice(s.observerList, o) + s.observerList = s.removeFromSlice(s.observerList, o) } 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 { @@ -111,7 +113,9 @@ func (s *StorageState) notifyObserver(root common.Hash, o Observer) error { } } } + if len(subRes.Changes) > 0 { + logger.Trace("update observer", "changes", subRes.Changes) go func() { o.Update(subRes) }() @@ -120,7 +124,9 @@ func (s *StorageState) notifyObserver(root common.Hash, o Observer) error { return nil } -func removeFromSlice(observerList []Observer, observerToRemove Observer) []Observer { +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() { diff --git a/dot/state/storage_notify_test.go b/dot/state/storage_notify_test.go index e49f9534d0..64736ec61a 100644 --- a/dot/state/storage_notify_test.go +++ b/dot/state/storage_notify_test.go @@ -34,10 +34,14 @@ type MockStorageObserver struct { id int 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() int { return m.id @@ -69,10 +73,12 @@ func TestStorageState_RegisterStorageObserver(t *testing.T) { }}, } time.Sleep(time.Millisecond) + observer.m.RLock() + defer observer.m.RUnlock() require.Equal(t, expectedResult, observer.lastUpdate) } -func TestStorageState_RegisterStorageChangeChannel_Multi(t *testing.T) { +func TestStorageState_RegisterStorageObserver_Multi(t *testing.T) { ss := newTestStorageState(t) ts, err := ss.TrieState(nil) require.NoError(t, err) @@ -98,12 +104,14 @@ func TestStorageState_RegisterStorageChangeChannel_Multi(t *testing.T) { err = ss.StoreTrie(ts) require.NoError(t, err) - time.Sleep(time.Millisecond) + 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() } for _, observer := range observers { @@ -111,7 +119,7 @@ func TestStorageState_RegisterStorageChangeChannel_Multi(t *testing.T) { } } -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) @@ -137,14 +145,14 @@ func TestStorageState_RegisterStorageChangeChannel_Multi_Filter(t *testing.T) { err = ss.StoreTrie(ts) require.NoError(t, err) - time.Sleep(time.Millisecond) + 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() } for _, observer := range observers { From b344e5a9fbea8ed1acec9be65cde46a04991d224 Mon Sep 17 00:00:00 2001 From: Edward Mack Date: Tue, 13 Apr 2021 20:21:28 -0400 Subject: [PATCH 17/30] add tests --- dot/rpc/subscription/websocket_test.go | 93 ++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 dot/rpc/subscription/websocket_test.go diff --git a/dot/rpc/subscription/websocket_test.go b/dot/rpc/subscription/websocket_test.go new file mode 100644 index 0000000000..e45fbcc24f --- /dev/null +++ b/dot/rpc/subscription/websocket_test.go @@ -0,0 +1,93 @@ +package subscription + +import ( + "log" + "net/http" + "os" + "testing" + "time" + + "github.com/ChainSafe/gossamer/dot/state" + "github.com/ChainSafe/gossamer/lib/common" + "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[int]Listener), +} + +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, 0, res) + + wsconn.StorageAPI = new(MockStorageAPI) + + res, err = wsconn.initStorageChangeListener(1, nil) + require.EqualError(t, err, "unknow parameter type") + require.Equal(t, 0, res) + + res, err = wsconn.initStorageChangeListener(1, []interface{}{}) + require.NoError(t, err) + require.Equal(t, 1, 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 +} From 1d247f2427acfa8988707ed8bccaa2cf55ef4399 Mon Sep 17 00:00:00 2001 From: Edward Mack Date: Wed, 14 Apr 2021 12:30:23 -0400 Subject: [PATCH 18/30] add tests --- dot/rpc/subscription/websocket.go | 6 +++--- dot/rpc/subscription/websocket_test.go | 29 +++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/dot/rpc/subscription/websocket.go b/dot/rpc/subscription/websocket.go index d9732835c2..e260632e13 100644 --- a/dot/rpc/subscription/websocket.go +++ b/dot/rpc/subscription/websocket.go @@ -177,7 +177,7 @@ func (c *WSConn) initStorageChangeListener(reqID float64, params interface{}) (i pA, ok := params.([]interface{}) if !ok { - return 0, fmt.Errorf("unknow parameter type") + return 0, fmt.Errorf("unknown parameter type") } for _, param := range pA { switch p := param.(type) { @@ -185,14 +185,14 @@ func (c *WSConn) initStorageChangeListener(reqID float64, params interface{}) (i for _, pp := range param.([]interface{}) { data, ok := pp.(string) if !ok { - return 0, fmt.Errorf("unknow parameter type") + return 0, fmt.Errorf("unknown parameter type") } myObs.filter[data] = []byte{} } case string: myObs.filter[p] = []byte{} default: - return 0, fmt.Errorf("unknow parameter type") + return 0, fmt.Errorf("unknown parameter type") } } diff --git a/dot/rpc/subscription/websocket_test.go b/dot/rpc/subscription/websocket_test.go index e45fbcc24f..d446e5f069 100644 --- a/dot/rpc/subscription/websocket_test.go +++ b/dot/rpc/subscription/websocket_test.go @@ -57,16 +57,43 @@ func TestWSConn_HandleComm(t *testing.T) { res, err := wsconn.initStorageChangeListener(1, nil) require.EqualError(t, err, "error StorageAPI not set") require.Equal(t, 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, "unknow parameter type") + require.EqualError(t, err, "unknown parameter type") require.Equal(t, 0, res) res, err = wsconn.initStorageChangeListener(1, []interface{}{}) require.NoError(t, err) require.Equal(t, 1, res) + _, msg, err = c.ReadMessage() + require.NoError(t, err) + require.Equal(t, []byte(`{"jsonrpc":"2.0","result":1,"id":1}`+"\n"), msg) + + res, err = wsconn.initStorageChangeListener(1, []interface{}{"0x26aa"}) + require.NoError(t, err) + require.Equal(t, 2, res) + _, msg, err = c.ReadMessage() + require.NoError(t, err) + require.Equal(t, []byte(`{"jsonrpc":"2.0","result":2,"id":1}`+"\n"), msg) + + res, err = wsconn.initStorageChangeListener(1, []interface{}{1}) + require.EqualError(t, err, "unknown parameter type") + require.Equal(t, 0, res) + + c.WriteMessage(websocket.TextMessage, []byte(`{ + "jsonrpc": "2.0", + "method": "state_subscribeStorage", + "params": ["0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9de1e86a9a8c739864cf3cc5ec2bea59fd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"], + "id": 1 +}`)) + _, msg, err = c.ReadMessage() + require.NoError(t, err) + require.Equal(t, []byte(`{"jsonrpc":"2.0","result":3,"id":1}`+"\n"), msg) } type MockStorageAPI struct{} From e57ccb56e99d679803997e69195d0f8690dd68e6 Mon Sep 17 00:00:00 2001 From: Edward Mack Date: Wed, 14 Apr 2021 14:26:30 -0400 Subject: [PATCH 19/30] add tests --- dot/rpc/subscription/websocket_test.go | 28 +++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/dot/rpc/subscription/websocket_test.go b/dot/rpc/subscription/websocket_test.go index d446e5f069..02d561ba3e 100644 --- a/dot/rpc/subscription/websocket_test.go +++ b/dot/rpc/subscription/websocket_test.go @@ -67,21 +67,35 @@ func TestWSConn_HandleComm(t *testing.T) { require.EqualError(t, err, "unknown parameter type") require.Equal(t, 0, res) - res, err = wsconn.initStorageChangeListener(1, []interface{}{}) + res, err = wsconn.initStorageChangeListener(2, []interface{}{}) require.NoError(t, err) require.Equal(t, 1, res) _, msg, err = c.ReadMessage() require.NoError(t, err) - require.Equal(t, []byte(`{"jsonrpc":"2.0","result":1,"id":1}`+"\n"), msg) + require.Equal(t, []byte(`{"jsonrpc":"2.0","result":1,"id":2}`+"\n"), msg) - res, err = wsconn.initStorageChangeListener(1, []interface{}{"0x26aa"}) + res, err = wsconn.initStorageChangeListener(3, []interface{}{"0x26aa"}) require.NoError(t, err) require.Equal(t, 2, res) _, msg, err = c.ReadMessage() require.NoError(t, err) - require.Equal(t, []byte(`{"jsonrpc":"2.0","result":2,"id":1}`+"\n"), msg) + require.Equal(t, []byte(`{"jsonrpc":"2.0","result":2,"id":3}`+"\n"), msg) - res, err = wsconn.initStorageChangeListener(1, []interface{}{1}) + var testFilters = []interface{}{} + var testFilter1 = []interface{}{"0x26aa", "0x26a1"} + res, err = wsconn.initStorageChangeListener(4, append(testFilters, testFilter1)) + require.NoError(t, err) + require.Equal(t, 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, 0, res) + + res, err = wsconn.initStorageChangeListener(6, []interface{}{1}) require.EqualError(t, err, "unknown parameter type") require.Equal(t, 0, res) @@ -89,11 +103,11 @@ func TestWSConn_HandleComm(t *testing.T) { "jsonrpc": "2.0", "method": "state_subscribeStorage", "params": ["0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9de1e86a9a8c739864cf3cc5ec2bea59fd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"], - "id": 1 + "id": 7 }`)) _, msg, err = c.ReadMessage() require.NoError(t, err) - require.Equal(t, []byte(`{"jsonrpc":"2.0","result":3,"id":1}`+"\n"), msg) + require.Equal(t, []byte(`{"jsonrpc":"2.0","result":4,"id":7}`+"\n"), msg) } type MockStorageAPI struct{} From f013cf5f6c4bad0cc6f34170e951249744a0b090 Mon Sep 17 00:00:00 2001 From: Edward Mack Date: Wed, 14 Apr 2021 15:34:21 -0400 Subject: [PATCH 20/30] add tests --- dot/rpc/subscription/websocket_test.go | 95 +++++++++++++++++++++++++- 1 file changed, 92 insertions(+), 3 deletions(-) diff --git a/dot/rpc/subscription/websocket_test.go b/dot/rpc/subscription/websocket_test.go index 02d561ba3e..5c77bb3412 100644 --- a/dot/rpc/subscription/websocket_test.go +++ b/dot/rpc/subscription/websocket_test.go @@ -2,12 +2,14 @@ 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/gorilla/websocket" "github.com/stretchr/testify/require" @@ -17,7 +19,8 @@ var upgrader = websocket.Upgrader{ CheckOrigin: func(r *http.Request) bool { return true }, } var wsconn = &WSConn{ - Subscriptions: make(map[int]Listener), + Subscriptions: make(map[int]Listener), + BlockSubChannels: make(map[int]byte), } func handler(w http.ResponseWriter, r *http.Request) { @@ -103,11 +106,57 @@ func TestWSConn_HandleComm(t *testing.T) { "jsonrpc": "2.0", "method": "state_subscribeStorage", "params": ["0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9de1e86a9a8c739864cf3cc5ec2bea59fd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"], - "id": 7 -}`)) + "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, 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, 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, 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, 7, res) + _, msg, err = c.ReadMessage() + require.NoError(t, err) + require.Equal(t, []byte(`{"jsonrpc":"2.0","result":7,"id":1}`+"\n"), msg) + } type MockStorageAPI struct{} @@ -132,3 +181,43 @@ func (m *MockStorageAPI) GetStateRootFromBlock(bhash *common.Hash) (*common.Hash 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 +} From bfcd6a8293721c87bdf892be74e2462e109357ee Mon Sep 17 00:00:00 2001 From: Edward Mack Date: Thu, 15 Apr 2021 10:03:12 -0400 Subject: [PATCH 21/30] add tests --- dot/rpc/subscription/websocket_test.go | 32 ++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/dot/rpc/subscription/websocket_test.go b/dot/rpc/subscription/websocket_test.go index 5c77bb3412..4016d8b16a 100644 --- a/dot/rpc/subscription/websocket_test.go +++ b/dot/rpc/subscription/websocket_test.go @@ -11,6 +11,8 @@ import ( "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" ) @@ -157,6 +159,12 @@ func TestWSConn_HandleComm(t *testing.T) { require.NoError(t, err) require.Equal(t, []byte(`{"jsonrpc":"2.0","result":7,"id":1}`+"\n"), msg) + // test initExtrinsicWatch + wsconn.CoreAPI = new(MockCoreAPI) + res, err = wsconn.initExtrinsicWatch(0, []interface{}{"0x26aa"}) + require.NoError(t, err) + require.Equal(t, 8, res) + } type MockStorageAPI struct{} @@ -221,3 +229,27 @@ func (m *MockBlockAPI) HasJustification(hash common.Hash) (bool, error) { 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 +} From dcf1493ed500169bacf03da11bde2c0f0b3d374f Mon Sep 17 00:00:00 2001 From: Edward Mack Date: Thu, 15 Apr 2021 10:35:09 -0400 Subject: [PATCH 22/30] add tests --- dot/rpc/subscription/websocket_test.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/dot/rpc/subscription/websocket_test.go b/dot/rpc/subscription/websocket_test.go index 4016d8b16a..c83b0e41b5 100644 --- a/dot/rpc/subscription/websocket_test.go +++ b/dot/rpc/subscription/websocket_test.go @@ -161,6 +161,16 @@ func TestWSConn_HandleComm(t *testing.T) { // 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, 0, res) + + res, err = wsconn.initExtrinsicWatch(0, []interface{}{"0x26aa"}) + require.EqualError(t, err, "error BlockAPI not set") + require.Equal(t, 0, res) + + wsconn.BlockAPI = new(MockBlockAPI) res, err = wsconn.initExtrinsicWatch(0, []interface{}{"0x26aa"}) require.NoError(t, err) require.Equal(t, 8, res) From c30f34e1943f1efefca400d50c906ba831b05263 Mon Sep 17 00:00:00 2001 From: Edward Mack Date: Tue, 20 Apr 2021 10:37:01 -0400 Subject: [PATCH 23/30] add troubleshooting stuff for testing transactions --- dot/core/service.go | 2 + dot/rpc/modules/system.go | 12 +- dot/rpc/subscription/websocket.go | 2 +- go.mod | 2 +- lib/runtime/wasmer/exports.go | 2 +- lib/runtime/wasmer/exports_test.go | 23 +- tests/polkadotjs_test/package-lock.json | 432 +++++++++++----------- tests/polkadotjs_test/test_transaction.js | 28 +- tests/rpc/rpc_02-author_test.go | 22 +- 9 files changed, 281 insertions(+), 244 deletions(-) diff --git a/dot/core/service.go b/dot/core/service.go index 93120e87c1..a3a03d2fe2 100644 --- a/dot/core/service.go +++ b/dot/core/service.go @@ -460,6 +460,8 @@ func (s *Service) HandleSubmittedExtrinsic(ext types.Extrinsic) error { // the transaction source is External // validate the transaction txv, err := s.rt.ValidateTransaction(append([]byte{byte(types.TxnExternal)}, ext...)) + // todo ed testing validate + //txv, err := s.rt.ValidateTransaction(ext) if err != nil { return err } diff --git a/dot/rpc/modules/system.go b/dot/rpc/modules/system.go index e7b1175eb3..4c540ea9b8 100644 --- a/dot/rpc/modules/system.go +++ b/dot/rpc/modules/system.go @@ -17,16 +17,11 @@ package modules import ( - "bytes" - "errors" - "fmt" - "math/big" + "github.com/ChainSafe/gossamer/lib/crypto" + ctypes "github.com/centrifuge/go-substrate-rpc-client/v2/types" "net/http" "github.com/ChainSafe/gossamer/lib/common" - "github.com/ChainSafe/gossamer/lib/crypto" - "github.com/ChainSafe/gossamer/lib/scale" - ctypes "github.com/centrifuge/go-substrate-rpc-client/v2/types" ) // SystemModule is an RPC module providing access to core API points @@ -171,7 +166,8 @@ func (sm *SystemModule) AccountNextIndex(r *http.Request, req *StringRequest, re found := false for _, v := range pending { var ext ctypes.Extrinsic - err := ctypes.DecodeFromBytes(v.Extrinsic[1:], &ext) + //err := ctypes.DecodeFromBytes(v.Extrinsic[1:], &ext) + err := ctypes.DecodeFromBytes(v.Extrinsic[:], &ext) if err != nil { return err } diff --git a/dot/rpc/subscription/websocket.go b/dot/rpc/subscription/websocket.go index e260632e13..0c0ba06d5a 100644 --- a/dot/rpc/subscription/websocket.go +++ b/dot/rpc/subscription/websocket.go @@ -60,7 +60,7 @@ func (c *WSConn) HandleComm() { return } logger.Debug("websocket received", "message", mbytes) - +fmt.Printf("Method %s\n", mbytes) // determine if request is for subscribe method type var msg map[string]interface{} err = json.Unmarshal(mbytes, &msg) 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/lib/runtime/wasmer/exports.go b/lib/runtime/wasmer/exports.go index fa5234f2ff..6d3bc9b274 100644 --- a/lib/runtime/wasmer/exports.go +++ b/lib/runtime/wasmer/exports.go @@ -33,7 +33,7 @@ func (in *Instance) ValidateTransaction(e types.Extrinsic) (*transaction.Validit if err != nil { return nil, err } - +fmt.Printf("TRAN VALID %v\n", ret) if ret[0] != 0 { return nil, runtime.NewValidateTransactionError(ret) } diff --git a/lib/runtime/wasmer/exports_test.go b/lib/runtime/wasmer/exports_test.go index 3d6a993084..047fe6fa64 100644 --- a/lib/runtime/wasmer/exports_test.go +++ b/lib/runtime/wasmer/exports_test.go @@ -3,6 +3,7 @@ package wasmer import ( "bytes" "encoding/json" + "fmt" "io/ioutil" "math/big" "testing" @@ -147,7 +148,7 @@ func balanceKey(t *testing.T, pub []byte) []byte { //nolint } func TestNodeRuntime_ValidateTransaction(t *testing.T) { - t.Skip("fixing next_key breaks this... :(") + //t.Skip("fixing next_key breaks this... :(") alicePub := common.MustHexToBytes("0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d") aliceBalanceKey := balanceKey(t, alicePub) @@ -174,14 +175,30 @@ func TestNodeRuntime_ValidateTransaction(t *testing.T) { rt.ctx.Storage.Set(aliceBalanceKey, encBal) - extBytes, err := common.HexToBytes("0x2d0284ffd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01ccacd0447dd220241dfb510e6e0554dff73899e79a068c58c7a149f568c71e046893a7e4726b5532af338b7780d0e9a83e9acc00e1610b02468405b2394769840000000600ff90b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22e5c0") + //extBytes, err := common.HexToBytes("0x2d0284ffd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01ccacd0447dd220241dfb510e6e0554dff73899e79a068c58c7a149f568c71e046893a7e4726b5532af338b7780d0e9a83e9acc00e1610b02468405b2394769840000000600ff90b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22e5c0") + + // test script forever era + //extBytes, err := common.HexToBytes("0x2d0284ffd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01300bffca729d3cea8272ea91ec6b0ab996d2bc9552ecbe8d058b164a68fcdb4c1517f3fd75b75e0647ff6329cc5d225fe8ea6c3b7fa773900d064e1c8b6b468b0000000600ff8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48e5c0") + + // test script, with era + //extBytes, err := common.HexToBytes("0x310284ffd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01baa421a005084e18fae90fca2913b86279b1e6ebe767a1494f2095bd5c01c041700926854de12e86139f00b51ea0fae4e7cf1e98e647a2a54ae904a1dd78bf81b60004000600ff8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48e5c0") + + // test from palkadot js (add ff) + extBytes, err := common.HexToBytes("0x410284ffd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01d42e5b210638ff8823810492dbe4ac6ec356b7cc51275ceeb3a3f80676dba44aa9c26836870d7cf5b5bedb12b977928fe90e4c07ee362c1e57617abaa6828e8b8502000006008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a480f0090c04bb6db2b") + + // test script, no options + //extBytes, err := common.HexToBytes("0x310284ffd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d012ef8c324562f94231efe061b6ab2d945c849782d6d0ae7c798d3b19e850c7f39431c97f3642054398c51035670456ab0c93dd6eac7909403fea750201c64168a260100000600ff8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48e5c0") + + // test from rpc test + //extBytes, err := common.HexToBytes("0x2d0284ffd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01f01a562624929a271de6fdd63b8aefcc0213069a2ea6ead7d5d2575858a30d7656ff8b86150dbe8f1a633226b54d5552e21f9f4e81a7aa68d9095d6f708268800000000600ff90b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22e5c0") require.NoError(t, err) _ = buildBlock(t, rt) ext := types.Extrinsic(append([]byte{byte(types.TxnExternal)}, extBytes...)) - _, err = rt.ValidateTransaction(ext) + res, err := rt.ValidateTransaction(ext) + fmt.Printf("ValTrans res %v\n", res) require.NoError(t, err) } 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..72331248ed 100644 --- a/tests/polkadotjs_test/test_transaction.js +++ b/tests/polkadotjs_test/test_transaction.js @@ -19,10 +19,32 @@ async function main() { const ADDR_Bob = '0x90b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22'; // 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}); + // 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}); - console.log(`hxHash ${transfer}`); + // const transfer = await api.tx.balances.transfer(bobKey.address, 12345) + // .signAndSend(aliceKey, {nonce: 1}); + + // console.log(`hxHash ${transfer}`); + + // Make a transfer from Alice to BOB, waiting for inclusion + // .signAndSend(aliceKey, {era: 0, blockHash: '0x64597c55a052d484d9ff357266be326f62573bb4fbdbb3cd49f219396fcebf78', blockNumber:0, genesisHash: '0x64597c55a052d484d9ff357266be326f62573bb4fbdbb3cd49f219396fcebf78', nonce: 1, tip: 0, transactionVersion: 1}, ({ events = [], status }) => { + const unsub = await api.tx.balances + .transfer(bobKey.address, 12345) + .signAndSend(aliceKey, ({ events = [], status }) => { + console.log(`Current status is ${status.type}`); + + if (status.isFinalized) { + console.log(`Transaction included at blockHash ${status.asFinalized}`); + + // Loop through Vec to display all events + events.forEach(({ phase, event: { data, method, section } }) => { + console.log(`\t' ${phase}: ${section}.${method}:: ${data}`); + }); + + unsub(); + } + }); } diff --git a/tests/rpc/rpc_02-author_test.go b/tests/rpc/rpc_02-author_test.go index 72f86ba613..c704713d0f 100644 --- a/tests/rpc/rpc_02-author_test.go +++ b/tests/rpc/rpc_02-author_test.go @@ -86,9 +86,16 @@ func TestAuthorSubmitExtrinsic(t *testing.T) { o := types.SignatureOptions{ BlockHash: genesisHash, - Era: types.ExtrinsicEra{IsImmortalEra: true}, + //Era: types.ExtrinsicEra{IsImmortalEra: true}, + Era: types.ExtrinsicEra{ + IsMortalEra: true, + AsMortalEra: types.MortalEra{ + First: 132, + Second: 1, + }, + }, GenesisHash: genesisHash, - Nonce: types.NewUCompactFromUInt(uint64(accInfo.Nonce)), + //Nonce: types.NewUCompactFromUInt(uint64(accInfo.Nonce)), SpecVersion: rv.SpecVersion, Tip: types.NewUCompactFromUInt(0), TransactionVersion: rv.TransactionVersion, @@ -101,7 +108,7 @@ func TestAuthorSubmitExtrinsic(t *testing.T) { buffer := bytes.Buffer{} encoder := scale.NewEncoder(&buffer) ext.Encode(*encoder) - +fmt.Printf("SUBMIT %x\n", buffer.Bytes()) // Send the extrinsic hash, err := api.RPC.Author.SubmitExtrinsic(ext) require.NoError(t, err) @@ -113,7 +120,14 @@ func TestAuthorSubmitExtrinsic(t *testing.T) { func TestDecodeExt(t *testing.T) { buffer := bytes.Buffer{} decoder := scale.NewDecoder(&buffer) - buffer.Write(common.MustHexToBytes("0x410284ffd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01f8efbe48487e57a22abf7e3acd491b7f3528a33a111b1298601554863d27eb129eaa4e718e1365414ff3d028b62bebc651194c6b5001e5c2839b982757e08a8c0000000600ff8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a480b00c465f14670")) + //buffer.Write(common.MustHexToBytes("0x410284ffd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01f8efbe48487e57a22abf7e3acd491b7f3528a33a111b1298601554863d27eb129eaa4e718e1365414ff3d028b62bebc651194c6b5001e5c2839b982757e08a8c0000000600ff8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a480b00c465f14670")) + // todo failing from polkadot.js apps + //buffer.Write(common.MustHexToBytes("0x410284ffd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01d42e5b210638ff8823810492dbe4ac6ec356b7cc51275ceeb3a3f80676dba44aa9c26836870d7cf5b5bedb12b977928fe90e4c07ee362c1e57617abaa6828e8b8502000006008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a480f0090c04bb6db2b")) + // todo passing from test_transaction js + //buffer.Write(common.MustHexToBytes("0x410284ffd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d015cbead600584b7701b8ab8384fd6550cda9de51525d861c2c7543f5ab323cf631ed69fceb4a93bccc576894f9b0d7ac8ae7b57c75e074b9c01238a72bd08f4830004000600ff8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48e5c0")) + + // todo from test_transaction (no options) fails + buffer.Write(common.MustHexToBytes("0x410284d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01b2d848cc5ce98c49afa180f7224ab38291e758e40a1d5bd1af9bc0d325c22c2f2d15a59b83791733355d4d4c5161e717334b2793ee6e0d739c175d241c70b98e8602040006038eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a480f0090c04bb6db2b")) ext := types.Extrinsic{} err := decoder.Decode(&ext) require.NoError(t, err) From ab9fd78a322241c23fc885b4c74e67f9d84338d4 Mon Sep 17 00:00:00 2001 From: Edward Mack Date: Tue, 20 Apr 2021 13:44:02 -0400 Subject: [PATCH 24/30] save commit --- dot/rpc/modules/system.go | 8 +++-- tests/polkadotjs_test/test_transaction.js | 36 +++++++++++------------ tests/rpc/rpc_02-author_test.go | 4 +-- 3 files changed, 26 insertions(+), 22 deletions(-) diff --git a/dot/rpc/modules/system.go b/dot/rpc/modules/system.go index 4c540ea9b8..4d0113d961 100644 --- a/dot/rpc/modules/system.go +++ b/dot/rpc/modules/system.go @@ -17,11 +17,15 @@ package modules import ( + "bytes" + "errors" + "fmt" + "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/crypto" + "github.com/ChainSafe/gossamer/lib/scale" ctypes "github.com/centrifuge/go-substrate-rpc-client/v2/types" + "math/big" "net/http" - - "github.com/ChainSafe/gossamer/lib/common" ) // SystemModule is an RPC module providing access to core API points diff --git a/tests/polkadotjs_test/test_transaction.js b/tests/polkadotjs_test/test_transaction.js index 72331248ed..cf8ba0530a 100644 --- a/tests/polkadotjs_test/test_transaction.js +++ b/tests/polkadotjs_test/test_transaction.js @@ -22,29 +22,29 @@ async function main() { // 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}); - // const transfer = await api.tx.balances.transfer(bobKey.address, 12345) - // .signAndSend(aliceKey, {nonce: 1}); + const transfer = await api.tx.balances.transfer(bobKey.address, 12345) + .signAndSend(aliceKey); // console.log(`hxHash ${transfer}`); // Make a transfer from Alice to BOB, waiting for inclusion // .signAndSend(aliceKey, {era: 0, blockHash: '0x64597c55a052d484d9ff357266be326f62573bb4fbdbb3cd49f219396fcebf78', blockNumber:0, genesisHash: '0x64597c55a052d484d9ff357266be326f62573bb4fbdbb3cd49f219396fcebf78', nonce: 1, tip: 0, transactionVersion: 1}, ({ events = [], status }) => { - const unsub = await api.tx.balances - .transfer(bobKey.address, 12345) - .signAndSend(aliceKey, ({ events = [], status }) => { - console.log(`Current status is ${status.type}`); - - if (status.isFinalized) { - console.log(`Transaction included at blockHash ${status.asFinalized}`); - - // Loop through Vec to display all events - events.forEach(({ phase, event: { data, method, section } }) => { - console.log(`\t' ${phase}: ${section}.${method}:: ${data}`); - }); - - unsub(); - } - }); + // const unsub = await api.tx.balances + // .transfer(bobKey.address, 12345) + // .signAndSend(aliceKey, ({ events = [], status }) => { + // console.log(`Current status is ${status.type}`); + // + // if (status.isFinalized) { + // console.log(`Transaction included at blockHash ${status.asFinalized}`); + // + // // Loop through Vec to display all events + // events.forEach(({ phase, event: { data, method, section } }) => { + // console.log(`\t' ${phase}: ${section}.${method}:: ${data}`); + // }); + // + // unsub(); + // } + // }); } diff --git a/tests/rpc/rpc_02-author_test.go b/tests/rpc/rpc_02-author_test.go index c704713d0f..9fe880a534 100644 --- a/tests/rpc/rpc_02-author_test.go +++ b/tests/rpc/rpc_02-author_test.go @@ -122,12 +122,12 @@ func TestDecodeExt(t *testing.T) { decoder := scale.NewDecoder(&buffer) //buffer.Write(common.MustHexToBytes("0x410284ffd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01f8efbe48487e57a22abf7e3acd491b7f3528a33a111b1298601554863d27eb129eaa4e718e1365414ff3d028b62bebc651194c6b5001e5c2839b982757e08a8c0000000600ff8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a480b00c465f14670")) // todo failing from polkadot.js apps - //buffer.Write(common.MustHexToBytes("0x410284ffd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01d42e5b210638ff8823810492dbe4ac6ec356b7cc51275ceeb3a3f80676dba44aa9c26836870d7cf5b5bedb12b977928fe90e4c07ee362c1e57617abaa6828e8b8502000006008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a480f0090c04bb6db2b")) + buffer.Write(common.MustHexToBytes("0x450284d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01380903d5f692eff4030ba9af1d8321e72cf828af54f64565766552b467c81914ecc82a10a00dbb70168c571b78fd61fd97f253f421c4091db4e7308c0cf1738e9604000006038eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48130010d2bd9f35b601")) // todo passing from test_transaction js //buffer.Write(common.MustHexToBytes("0x410284ffd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d015cbead600584b7701b8ab8384fd6550cda9de51525d861c2c7543f5ab323cf631ed69fceb4a93bccc576894f9b0d7ac8ae7b57c75e074b9c01238a72bd08f4830004000600ff8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48e5c0")) // todo from test_transaction (no options) fails - buffer.Write(common.MustHexToBytes("0x410284d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01b2d848cc5ce98c49afa180f7224ab38291e758e40a1d5bd1af9bc0d325c22c2f2d15a59b83791733355d4d4c5161e717334b2793ee6e0d739c175d241c70b98e8602040006038eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a480f0090c04bb6db2b")) + //buffer.Write(common.MustHexToBytes("0x410284d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01b2d848cc5ce98c49afa180f7224ab38291e758e40a1d5bd1af9bc0d325c22c2f2d15a59b83791733355d4d4c5161e717334b2793ee6e0d739c175d241c70b98e8602040006038eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a480f0090c04bb6db2b")) ext := types.Extrinsic{} err := decoder.Decode(&ext) require.NoError(t, err) From d9e228e20151b310523e2784b27d9a2eaaae086d Mon Sep 17 00:00:00 2001 From: Edward Mack Date: Tue, 20 Apr 2021 16:06:31 -0400 Subject: [PATCH 25/30] address PR comments --- dot/rpc/subscription/listeners.go | 33 +++++++++++++++----------- dot/rpc/subscription/listeners_test.go | 3 +++ dot/rpc/subscription/messages.go | 8 +++---- dot/rpc/subscription/websocket.go | 22 ++++++++--------- dot/rpc/websocket_test.go | 2 -- dot/state/storage.go | 1 + dot/state/storage_notify.go | 13 +++++----- dot/state/storage_notify_test.go | 8 +++---- lib/trie/trie.go | 1 - 9 files changed, 49 insertions(+), 42 deletions(-) diff --git a/dot/rpc/subscription/listeners.go b/dot/rpc/subscription/listeners.go index 46a9109c6d..d3cd004946 100644 --- a/dot/rpc/subscription/listeners.go +++ b/dot/rpc/subscription/listeners.go @@ -37,35 +37,40 @@ type WSConnAPI interface { // StorageObserver struct to hold data for observer (Observer Design Pattern) type StorageObserver struct { - id int + id uint filter map[string][]byte wsconn WSConnAPI } +type Change [2]string +type ChangeResult struct { + Changes []Change `json:"changes"` + Block string `json:"block"` +} + // Update is called to notify observer of new value func (s *StorageObserver) Update(change *state.SubscriptionResult) { if change == nil { return } - result := make(map[string]interface{}) - result["block"] = change.Hash.String() - changes := make([][]string, 0, len(change.Changes)) - for _, v := range change.Changes { - kv := []string{common.BytesToHex(v.Key), common.BytesToHex(v.Value)} - changes = append(changes, kv) + 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)} } - result["changes"] = changes res := newSubcriptionBaseResponseJSON() res.Method = "state_storage" - res.Params.Result = result + res.Params.Result = changeResult res.Params.SubscriptionID = s.GetID() s.wsconn.safeSend(res) } // GetID the id for the Observer -func (s *StorageObserver) GetID() int { +func (s *StorageObserver) GetID() uint { return s.id } @@ -82,7 +87,7 @@ type BlockListener struct { Channel chan *types.Block wsconn WSConnAPI ChanID byte - subID int + subID uint } // Listen implementation of Listen interface to listen for importedChan changes @@ -109,7 +114,7 @@ type BlockFinalizedListener struct { channel chan *types.Header wsconn WSConnAPI chanID byte - subID int + subID uint } // Listen implementation of Listen interface to listen for importedChan changes @@ -133,7 +138,7 @@ func (l *BlockFinalizedListener) Listen() { // ExtrinsicSubmitListener to handle listening for extrinsic events type ExtrinsicSubmitListener struct { wsconn WSConnAPI - subID int + subID uint extrinsic types.Extrinsic importedChan chan *types.Block @@ -185,7 +190,7 @@ func (l *ExtrinsicSubmitListener) Listen() { // RuntimeVersionListener to handle listening for Runtime Version type RuntimeVersionListener struct { wsconn *WSConn - subID int + subID uint } // Listen implementation of Listen interface to listen for runtime version changes diff --git a/dot/rpc/subscription/listeners_test.go b/dot/rpc/subscription/listeners_test.go index bc8c0ffb43..dc7857420a 100644 --- a/dot/rpc/subscription/listeners_test.go +++ b/dot/rpc/subscription/listeners_test.go @@ -13,9 +13,11 @@ // // You should have received a copy of the GNU Lesser General Public License // along with the gossamer library. If not, see . + package subscription import ( + "fmt" "math/big" "testing" "time" @@ -66,6 +68,7 @@ func TestStorageObserver_Update(t *testing.T) { storageObserver.Update(change) time.Sleep(time.Millisecond * 10) + fmt.Printf("RES %v\n", mockConnection.lastMessage) require.Equal(t, expectedRespones, mockConnection.lastMessage) } diff --git a/dot/rpc/subscription/messages.go b/dot/rpc/subscription/messages.go index 038f279561..34af8c3486 100644 --- a/dot/rpc/subscription/messages.go +++ b/dot/rpc/subscription/messages.go @@ -25,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 { @@ -34,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, @@ -48,11 +48,11 @@ 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, diff --git a/dot/rpc/subscription/websocket.go b/dot/rpc/subscription/websocket.go index 0c0ba06d5a..bbe4a42f23 100644 --- a/dot/rpc/subscription/websocket.go +++ b/dot/rpc/subscription/websocket.go @@ -33,16 +33,16 @@ import ( "github.com/gorilla/websocket" ) -var logger = log.New("pkg", "rpc") +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 @@ -164,7 +164,7 @@ fmt.Printf("Method %s\n", mbytes) } } -func (c *WSConn) initStorageChangeListener(reqID float64, params interface{}) (int, error) { +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") @@ -209,7 +209,7 @@ func (c *WSConn) initStorageChangeListener(reqID float64, params interface{}) (i return myObs.id, nil } -func (c *WSConn) initBlockListener(reqID float64) (int, error) { +func (c *WSConn) initBlockListener(reqID float64) (uint, error) { bl := &BlockListener{ Channel: make(chan *types.Block), wsconn: c, @@ -234,7 +234,7 @@ func (c *WSConn) initBlockListener(reqID float64) (int, error) { return bl.subID, nil } -func (c *WSConn) initBlockFinalizedListener(reqID float64) (int, error) { +func (c *WSConn) initBlockFinalizedListener(reqID float64) (uint, error) { bfl := &BlockFinalizedListener{ channel: make(chan *types.Header), wsconn: c, @@ -259,7 +259,7 @@ func (c *WSConn) initBlockFinalizedListener(reqID float64) (int, error) { return bfl.subID, nil } -func (c *WSConn) initExtrinsicWatch(reqID float64, params interface{}) (int, error) { +func (c *WSConn) initExtrinsicWatch(reqID float64, params interface{}) (uint, error) { pA := params.([]interface{}) extBytes, err := common.HexToBytes(pA[0].(string)) if err != nil { @@ -299,7 +299,7 @@ func (c *WSConn) initExtrinsicWatch(reqID float64, params interface{}) (int, 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 + // 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")) } @@ -308,7 +308,7 @@ func (c *WSConn) initExtrinsicWatch(reqID float64, params interface{}) (int, err return esl.subID, err } -func (c *WSConn) initRuntimeVersionListener(reqID float64) (int, error) { +func (c *WSConn) initRuntimeVersionListener(reqID float64) (uint, error) { rvl := &RuntimeVersionListener{ wsconn: c, } @@ -363,6 +363,6 @@ type ErrorMessageJSON struct { Message string `json:"message"` } -func (c *WSConn) startListener(lid int) { +func (c *WSConn) startListener(lid uint) { go c.Subscriptions[lid].Listen() } diff --git a/dot/rpc/websocket_test.go b/dot/rpc/websocket_test.go index 8f84d88fbf..4cdd9e271d 100644 --- a/dot/rpc/websocket_test.go +++ b/dot/rpc/websocket_test.go @@ -17,7 +17,6 @@ package rpc import ( "flag" - "fmt" "log" "math/big" "net/url" @@ -94,7 +93,6 @@ func TestHTTPServer_ServeHTTP(t *testing.T) { _, message, err := c.ReadMessage() require.Nil(t, err) - fmt.Printf("ACT MSG %s\n", message) require.Equal(t, item.expected, message) } } diff --git a/dot/state/storage.go b/dot/state/storage.go index cad387a11d..d80ee9dda1 100644 --- a/dot/state/storage.go +++ b/dot/state/storage.go @@ -74,6 +74,7 @@ func NewStorageState(db chaindb.Database, blockState *BlockState, t *trie.Trie) tries: tries, baseDB: db, db: chaindb.NewTable(db, storagePrefix), + observerList: []Observer{}, }, nil } diff --git a/dot/state/storage_notify.go b/dot/state/storage_notify.go index f15f50cc78..6e7a15df90 100644 --- a/dot/state/storage_notify.go +++ b/dot/state/storage_notify.go @@ -37,7 +37,7 @@ type SubscriptionResult struct { // Observer interface defines functions needed for observers, Observer Design Pattern type Observer interface { Update(result *SubscriptionResult) - GetID() int + GetID() uint GetFilter() map[string][]byte } @@ -75,9 +75,10 @@ func (s *StorageState) notifyAll(root common.Hash) { } func (s *StorageState) notifyObserver(root common.Hash, o Observer) error { - s.lock.RLock() - t := s.tries[root] - defer s.lock.RUnlock() + t, err := s.TrieState(&root) + if err != nil { + return err + } if t == nil { return errTrieDoesNotExist(root) @@ -88,7 +89,7 @@ func (s *StorageState) notifyObserver(root common.Hash, o Observer) error { } if len(o.GetFilter()) == 0 { // no filter, so send all changes - ent := t.Entries() + ent := t.TrieEntries() for k, v := range ent { if k != ":code" { // todo, currently we're ignoring :code since this is a lot of data @@ -130,7 +131,7 @@ func (s *StorageState) removeFromSlice(observerList []Observer, observerToRemove observerListLength := len(observerList) for i, observer := range observerList { if observerToRemove.GetID() == observer.GetID() { - observerList[observerListLength-1], observerList[i] = observerList[i], observerList[observerListLength-1] + observerList[i] = observerList[observerListLength-1] return observerList[:observerListLength-1] } } diff --git a/dot/state/storage_notify_test.go b/dot/state/storage_notify_test.go index 64736ec61a..1a832d1e5b 100644 --- a/dot/state/storage_notify_test.go +++ b/dot/state/storage_notify_test.go @@ -31,7 +31,7 @@ import ( ) type MockStorageObserver struct { - id int + id uint filter map[string][]byte lastUpdate *SubscriptionResult m sync.RWMutex @@ -43,7 +43,7 @@ func (m *MockStorageObserver) Update(change *SubscriptionResult) { m.m.Unlock() } -func (m *MockStorageObserver) GetID() int { +func (m *MockStorageObserver) GetID() uint { return m.id } func (m *MockStorageObserver) GetFilter() map[string][]byte { @@ -89,7 +89,7 @@ func TestStorageState_RegisterStorageObserver_Multi(t *testing.T) { for i := 0; i < num; i++ { observer := &MockStorageObserver{ - id: i, + id: uint(i), } observers = append(observers, observer) ss.RegisterStorageObserver(observer) @@ -132,7 +132,7 @@ func TestStorageState_RegisterStorageObserver_Multi_Filter(t *testing.T) { for i := 0; i < num; i++ { observer := &MockStorageObserver{ - id: i, + id: uint(i), filter: map[string][]byte{ common.BytesToHex(key1): {}, }, diff --git a/lib/trie/trie.go b/lib/trie/trie.go index 7f1c48718c..d1db892612 100644 --- a/lib/trie/trie.go +++ b/lib/trie/trie.go @@ -242,7 +242,6 @@ func (t *Trie) tryPut(key, value []byte) { k := keyToNibbles(key) t.root = t.insert(t.root, k, &leaf{key: nil, value: value, dirty: true, generation: t.generation}) - } // TryPut attempts to insert a key with value into the trie From 390fadf6cfbcdf608ddbefa70376293e11abef07 Mon Sep 17 00:00:00 2001 From: Edward Mack Date: Tue, 20 Apr 2021 16:16:13 -0400 Subject: [PATCH 26/30] lint --- dot/core/service.go | 2 -- dot/rpc/http.go | 4 ++-- dot/rpc/modules/system.go | 5 +++-- dot/rpc/subscription/listeners.go | 5 ++++- dot/rpc/subscription/messages.go | 4 ++-- dot/rpc/subscription/websocket.go | 2 +- dot/rpc/subscription/websocket_test.go | 4 ++-- dot/state/storage.go | 8 ++++---- lib/runtime/wasmer/exports.go | 2 +- lib/runtime/wasmer/exports_test.go | 19 ++----------------- tests/rpc/rpc_02-author_test.go | 13 +++---------- 11 files changed, 24 insertions(+), 44 deletions(-) diff --git a/dot/core/service.go b/dot/core/service.go index a3a03d2fe2..93120e87c1 100644 --- a/dot/core/service.go +++ b/dot/core/service.go @@ -460,8 +460,6 @@ func (s *Service) HandleSubmittedExtrinsic(ext types.Extrinsic) error { // the transaction source is External // validate the transaction txv, err := s.rt.ValidateTransaction(append([]byte{byte(types.TxnExternal)}, ext...)) - // todo ed testing validate - //txv, err := s.rt.ValidateTransaction(ext) if err != nil { return err } diff --git a/dot/rpc/http.go b/dot/rpc/http.go index eb7665cba9..4934cafa1b 100644 --- a/dot/rpc/http.go +++ b/dot/rpc/http.go @@ -233,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/system.go b/dot/rpc/modules/system.go index 4d0113d961..938f849715 100644 --- a/dot/rpc/modules/system.go +++ b/dot/rpc/modules/system.go @@ -20,12 +20,13 @@ import ( "bytes" "errors" "fmt" + "math/big" + "net/http" + "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/crypto" "github.com/ChainSafe/gossamer/lib/scale" ctypes "github.com/centrifuge/go-substrate-rpc-client/v2/types" - "math/big" - "net/http" ) // SystemModule is an RPC module providing access to core API points diff --git a/dot/rpc/subscription/listeners.go b/dot/rpc/subscription/listeners.go index d3cd004946..46213e4e56 100644 --- a/dot/rpc/subscription/listeners.go +++ b/dot/rpc/subscription/listeners.go @@ -42,10 +42,13 @@ type StorageObserver struct { wsconn WSConnAPI } +// Change type defining key value pair representing change type Change [2]string + +// ChangeResult struct to hold change result data type ChangeResult struct { Changes []Change `json:"changes"` - Block string `json:"block"` + Block string `json:"block"` } // Update is called to notify observer of new value diff --git a/dot/rpc/subscription/messages.go b/dot/rpc/subscription/messages.go index 34af8c3486..d6df1258dd 100644 --- a/dot/rpc/subscription/messages.go +++ b/dot/rpc/subscription/messages.go @@ -25,7 +25,7 @@ type BaseResponseJSON struct { // Params for json param response type Params struct { Result interface{} `json:"result"` - SubscriptionID uint `json:"subscription"` + SubscriptionID uint `json:"subscription"` } func newSubcriptionBaseResponseJSON() BaseResponseJSON { @@ -48,7 +48,7 @@ func newSubscriptionResponse(method string, subID uint, result interface{}) Base // ResponseJSON for json subscription responses type ResponseJSON struct { Jsonrpc string `json:"jsonrpc"` - Result uint `json:"result"` + Result uint `json:"result"` ID float64 `json:"id"` } diff --git a/dot/rpc/subscription/websocket.go b/dot/rpc/subscription/websocket.go index bbe4a42f23..9c03f7c77a 100644 --- a/dot/rpc/subscription/websocket.go +++ b/dot/rpc/subscription/websocket.go @@ -60,7 +60,7 @@ func (c *WSConn) HandleComm() { return } logger.Debug("websocket received", "message", mbytes) -fmt.Printf("Method %s\n", mbytes) + // determine if request is for subscribe method type var msg map[string]interface{} err = json.Unmarshal(mbytes, &msg) diff --git a/dot/rpc/subscription/websocket_test.go b/dot/rpc/subscription/websocket_test.go index c83b0e41b5..e4c9964d25 100644 --- a/dot/rpc/subscription/websocket_test.go +++ b/dot/rpc/subscription/websocket_test.go @@ -21,8 +21,8 @@ var upgrader = websocket.Upgrader{ CheckOrigin: func(r *http.Request) bool { return true }, } var wsconn = &WSConn{ - Subscriptions: make(map[int]Listener), - BlockSubChannels: make(map[int]byte), + Subscriptions: make(map[uint]Listener), + BlockSubChannels: make(map[uint]byte), } func handler(w http.ResponseWriter, r *http.Request) { diff --git a/dot/state/storage.go b/dot/state/storage.go index d80ee9dda1..e00a2468b0 100644 --- a/dot/state/storage.go +++ b/dot/state/storage.go @@ -70,10 +70,10 @@ 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), + blockState: blockState, + tries: tries, + baseDB: db, + db: chaindb.NewTable(db, storagePrefix), observerList: []Observer{}, }, nil } diff --git a/lib/runtime/wasmer/exports.go b/lib/runtime/wasmer/exports.go index 6d3bc9b274..fa5234f2ff 100644 --- a/lib/runtime/wasmer/exports.go +++ b/lib/runtime/wasmer/exports.go @@ -33,7 +33,7 @@ func (in *Instance) ValidateTransaction(e types.Extrinsic) (*transaction.Validit if err != nil { return nil, err } -fmt.Printf("TRAN VALID %v\n", ret) + if ret[0] != 0 { return nil, runtime.NewValidateTransactionError(ret) } diff --git a/lib/runtime/wasmer/exports_test.go b/lib/runtime/wasmer/exports_test.go index 047fe6fa64..e798fcc0c3 100644 --- a/lib/runtime/wasmer/exports_test.go +++ b/lib/runtime/wasmer/exports_test.go @@ -148,7 +148,7 @@ func balanceKey(t *testing.T, pub []byte) []byte { //nolint } func TestNodeRuntime_ValidateTransaction(t *testing.T) { - //t.Skip("fixing next_key breaks this... :(") + t.Skip("fixing next_key breaks this... :(") alicePub := common.MustHexToBytes("0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d") aliceBalanceKey := balanceKey(t, alicePub) @@ -175,22 +175,7 @@ func TestNodeRuntime_ValidateTransaction(t *testing.T) { rt.ctx.Storage.Set(aliceBalanceKey, encBal) - //extBytes, err := common.HexToBytes("0x2d0284ffd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01ccacd0447dd220241dfb510e6e0554dff73899e79a068c58c7a149f568c71e046893a7e4726b5532af338b7780d0e9a83e9acc00e1610b02468405b2394769840000000600ff90b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22e5c0") - - // test script forever era - //extBytes, err := common.HexToBytes("0x2d0284ffd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01300bffca729d3cea8272ea91ec6b0ab996d2bc9552ecbe8d058b164a68fcdb4c1517f3fd75b75e0647ff6329cc5d225fe8ea6c3b7fa773900d064e1c8b6b468b0000000600ff8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48e5c0") - - // test script, with era - //extBytes, err := common.HexToBytes("0x310284ffd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01baa421a005084e18fae90fca2913b86279b1e6ebe767a1494f2095bd5c01c041700926854de12e86139f00b51ea0fae4e7cf1e98e647a2a54ae904a1dd78bf81b60004000600ff8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48e5c0") - - // test from palkadot js (add ff) - extBytes, err := common.HexToBytes("0x410284ffd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01d42e5b210638ff8823810492dbe4ac6ec356b7cc51275ceeb3a3f80676dba44aa9c26836870d7cf5b5bedb12b977928fe90e4c07ee362c1e57617abaa6828e8b8502000006008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a480f0090c04bb6db2b") - - // test script, no options - //extBytes, err := common.HexToBytes("0x310284ffd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d012ef8c324562f94231efe061b6ab2d945c849782d6d0ae7c798d3b19e850c7f39431c97f3642054398c51035670456ab0c93dd6eac7909403fea750201c64168a260100000600ff8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48e5c0") - - // test from rpc test - //extBytes, err := common.HexToBytes("0x2d0284ffd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01f01a562624929a271de6fdd63b8aefcc0213069a2ea6ead7d5d2575858a30d7656ff8b86150dbe8f1a633226b54d5552e21f9f4e81a7aa68d9095d6f708268800000000600ff90b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22e5c0") + extBytes, err := common.HexToBytes("0x2d0284ffd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01ccacd0447dd220241dfb510e6e0554dff73899e79a068c58c7a149f568c71e046893a7e4726b5532af338b7780d0e9a83e9acc00e1610b02468405b2394769840000000600ff90b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22e5c0") require.NoError(t, err) _ = buildBlock(t, rt) diff --git a/tests/rpc/rpc_02-author_test.go b/tests/rpc/rpc_02-author_test.go index 9fe880a534..6bed1b5749 100644 --- a/tests/rpc/rpc_02-author_test.go +++ b/tests/rpc/rpc_02-author_test.go @@ -86,16 +86,9 @@ func TestAuthorSubmitExtrinsic(t *testing.T) { o := types.SignatureOptions{ BlockHash: genesisHash, - //Era: types.ExtrinsicEra{IsImmortalEra: true}, - Era: types.ExtrinsicEra{ - IsMortalEra: true, - AsMortalEra: types.MortalEra{ - First: 132, - Second: 1, - }, - }, + Era: types.ExtrinsicEra{IsImmortalEra: true}, GenesisHash: genesisHash, - //Nonce: types.NewUCompactFromUInt(uint64(accInfo.Nonce)), + Nonce: types.NewUCompactFromUInt(uint64(accInfo.Nonce)), SpecVersion: rv.SpecVersion, Tip: types.NewUCompactFromUInt(0), TransactionVersion: rv.TransactionVersion, @@ -108,7 +101,7 @@ func TestAuthorSubmitExtrinsic(t *testing.T) { buffer := bytes.Buffer{} encoder := scale.NewEncoder(&buffer) ext.Encode(*encoder) -fmt.Printf("SUBMIT %x\n", buffer.Bytes()) + // Send the extrinsic hash, err := api.RPC.Author.SubmitExtrinsic(ext) require.NoError(t, err) From 2ab0f550148b375eaf2443c7eb39545153c927cc Mon Sep 17 00:00:00 2001 From: Edward Mack Date: Tue, 20 Apr 2021 16:24:35 -0400 Subject: [PATCH 27/30] remove unused printf and comments --- dot/rpc/modules/system.go | 3 +-- lib/runtime/wasmer/exports_test.go | 4 +--- tests/polkadotjs_test/test_transaction.js | 26 +---------------------- tests/rpc/rpc_02-author_test.go | 9 +------- 4 files changed, 4 insertions(+), 38 deletions(-) diff --git a/dot/rpc/modules/system.go b/dot/rpc/modules/system.go index 938f849715..e7b1175eb3 100644 --- a/dot/rpc/modules/system.go +++ b/dot/rpc/modules/system.go @@ -171,8 +171,7 @@ func (sm *SystemModule) AccountNextIndex(r *http.Request, req *StringRequest, re found := false for _, v := range pending { var ext ctypes.Extrinsic - //err := ctypes.DecodeFromBytes(v.Extrinsic[1:], &ext) - err := ctypes.DecodeFromBytes(v.Extrinsic[:], &ext) + err := ctypes.DecodeFromBytes(v.Extrinsic[1:], &ext) if err != nil { return err } diff --git a/lib/runtime/wasmer/exports_test.go b/lib/runtime/wasmer/exports_test.go index e798fcc0c3..3d6a993084 100644 --- a/lib/runtime/wasmer/exports_test.go +++ b/lib/runtime/wasmer/exports_test.go @@ -3,7 +3,6 @@ package wasmer import ( "bytes" "encoding/json" - "fmt" "io/ioutil" "math/big" "testing" @@ -182,8 +181,7 @@ func TestNodeRuntime_ValidateTransaction(t *testing.T) { ext := types.Extrinsic(append([]byte{byte(types.TxnExternal)}, extBytes...)) - res, err := rt.ValidateTransaction(ext) - fmt.Printf("ValTrans res %v\n", res) + _, err = rt.ValidateTransaction(ext) require.NoError(t, err) } diff --git a/tests/polkadotjs_test/test_transaction.js b/tests/polkadotjs_test/test_transaction.js index cf8ba0530a..ef7e448cd5 100644 --- a/tests/polkadotjs_test/test_transaction.js +++ b/tests/polkadotjs_test/test_transaction.js @@ -19,33 +19,9 @@ async function main() { const ADDR_Bob = '0x90b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22'; // 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}); - const transfer = await api.tx.balances.transfer(bobKey.address, 12345) .signAndSend(aliceKey); - - // console.log(`hxHash ${transfer}`); - - // Make a transfer from Alice to BOB, waiting for inclusion - // .signAndSend(aliceKey, {era: 0, blockHash: '0x64597c55a052d484d9ff357266be326f62573bb4fbdbb3cd49f219396fcebf78', blockNumber:0, genesisHash: '0x64597c55a052d484d9ff357266be326f62573bb4fbdbb3cd49f219396fcebf78', nonce: 1, tip: 0, transactionVersion: 1}, ({ events = [], status }) => { - // const unsub = await api.tx.balances - // .transfer(bobKey.address, 12345) - // .signAndSend(aliceKey, ({ events = [], status }) => { - // console.log(`Current status is ${status.type}`); - // - // if (status.isFinalized) { - // console.log(`Transaction included at blockHash ${status.asFinalized}`); - // - // // Loop through Vec to display all events - // events.forEach(({ phase, event: { data, method, section } }) => { - // console.log(`\t' ${phase}: ${section}.${method}:: ${data}`); - // }); - // - // unsub(); - // } - // }); - + console.log(`hxHash ${transfer}`); } main().catch(console.error); diff --git a/tests/rpc/rpc_02-author_test.go b/tests/rpc/rpc_02-author_test.go index 6bed1b5749..72f86ba613 100644 --- a/tests/rpc/rpc_02-author_test.go +++ b/tests/rpc/rpc_02-author_test.go @@ -113,14 +113,7 @@ func TestAuthorSubmitExtrinsic(t *testing.T) { func TestDecodeExt(t *testing.T) { buffer := bytes.Buffer{} decoder := scale.NewDecoder(&buffer) - //buffer.Write(common.MustHexToBytes("0x410284ffd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01f8efbe48487e57a22abf7e3acd491b7f3528a33a111b1298601554863d27eb129eaa4e718e1365414ff3d028b62bebc651194c6b5001e5c2839b982757e08a8c0000000600ff8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a480b00c465f14670")) - // todo failing from polkadot.js apps - buffer.Write(common.MustHexToBytes("0x450284d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01380903d5f692eff4030ba9af1d8321e72cf828af54f64565766552b467c81914ecc82a10a00dbb70168c571b78fd61fd97f253f421c4091db4e7308c0cf1738e9604000006038eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48130010d2bd9f35b601")) - // todo passing from test_transaction js - //buffer.Write(common.MustHexToBytes("0x410284ffd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d015cbead600584b7701b8ab8384fd6550cda9de51525d861c2c7543f5ab323cf631ed69fceb4a93bccc576894f9b0d7ac8ae7b57c75e074b9c01238a72bd08f4830004000600ff8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48e5c0")) - - // todo from test_transaction (no options) fails - //buffer.Write(common.MustHexToBytes("0x410284d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01b2d848cc5ce98c49afa180f7224ab38291e758e40a1d5bd1af9bc0d325c22c2f2d15a59b83791733355d4d4c5161e717334b2793ee6e0d739c175d241c70b98e8602040006038eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a480f0090c04bb6db2b")) + buffer.Write(common.MustHexToBytes("0x410284ffd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01f8efbe48487e57a22abf7e3acd491b7f3528a33a111b1298601554863d27eb129eaa4e718e1365414ff3d028b62bebc651194c6b5001e5c2839b982757e08a8c0000000600ff8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a480b00c465f14670")) ext := types.Extrinsic{} err := decoder.Decode(&ext) require.NoError(t, err) From 39d874892e04c76a6e14b079cc94dad6cacdaa12 Mon Sep 17 00:00:00 2001 From: Edward Mack Date: Tue, 20 Apr 2021 17:19:13 -0400 Subject: [PATCH 28/30] fix test --- dot/rpc/subscription/listeners_test.go | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/dot/rpc/subscription/listeners_test.go b/dot/rpc/subscription/listeners_test.go index dc7857420a..1ebf13c798 100644 --- a/dot/rpc/subscription/listeners_test.go +++ b/dot/rpc/subscription/listeners_test.go @@ -17,7 +17,6 @@ package subscription import ( - "fmt" "math/big" "testing" "time" @@ -53,14 +52,13 @@ func TestStorageObserver_Update(t *testing.T) { Changes: data, } - expected := make(map[string]interface{}) - expected["block"] = change.Hash.String() - changes := make([][]string, 0, len(change.Changes)) - for _, v := range change.Changes { - kv := []string{common.BytesToHex(v.Key), common.BytesToHex(v.Value)} - changes = append(changes, kv) + 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)} } - expected["changes"] = changes expectedRespones := newSubcriptionBaseResponseJSON() expectedRespones.Method = "state_storage" @@ -68,7 +66,6 @@ func TestStorageObserver_Update(t *testing.T) { storageObserver.Update(change) time.Sleep(time.Millisecond * 10) - fmt.Printf("RES %v\n", mockConnection.lastMessage) require.Equal(t, expectedRespones, mockConnection.lastMessage) } From 4de9a74cea6768421afec1afe92de70b1afd8f14 Mon Sep 17 00:00:00 2001 From: Edward Mack Date: Tue, 20 Apr 2021 18:06:19 -0400 Subject: [PATCH 29/30] update tests --- dot/rpc/subscription/websocket_test.go | 28 +++++++++++++------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/dot/rpc/subscription/websocket_test.go b/dot/rpc/subscription/websocket_test.go index e4c9964d25..43e189c39c 100644 --- a/dot/rpc/subscription/websocket_test.go +++ b/dot/rpc/subscription/websocket_test.go @@ -61,7 +61,7 @@ func TestWSConn_HandleComm(t *testing.T) { // test storageChangeListener res, err := wsconn.initStorageChangeListener(1, nil) require.EqualError(t, err, "error StorageAPI not set") - require.Equal(t, 0, res) + 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) @@ -70,18 +70,18 @@ func TestWSConn_HandleComm(t *testing.T) { res, err = wsconn.initStorageChangeListener(1, nil) require.EqualError(t, err, "unknown parameter type") - require.Equal(t, 0, res) + require.Equal(t, uint(0), res) res, err = wsconn.initStorageChangeListener(2, []interface{}{}) require.NoError(t, err) - require.Equal(t, 1, res) + 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, 2, res) + 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) @@ -90,7 +90,7 @@ func TestWSConn_HandleComm(t *testing.T) { var testFilter1 = []interface{}{"0x26aa", "0x26a1"} res, err = wsconn.initStorageChangeListener(4, append(testFilters, testFilter1)) require.NoError(t, err) - require.Equal(t, 3, res) + 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) @@ -98,11 +98,11 @@ func TestWSConn_HandleComm(t *testing.T) { var testFilterWrongType = []interface{}{"0x26aa", 1} res, err = wsconn.initStorageChangeListener(5, append(testFilters, testFilterWrongType)) require.EqualError(t, err, "unknown parameter type") - require.Equal(t, 0, res) + require.Equal(t, uint(0), res) res, err = wsconn.initStorageChangeListener(6, []interface{}{1}) require.EqualError(t, err, "unknown parameter type") - require.Equal(t, 0, res) + require.Equal(t, uint(0), res) c.WriteMessage(websocket.TextMessage, []byte(`{ "jsonrpc": "2.0", @@ -116,7 +116,7 @@ func TestWSConn_HandleComm(t *testing.T) { // test initBlockListener res, err = wsconn.initBlockListener(1) require.EqualError(t, err, "error BlockAPI not set") - require.Equal(t, 0, res) + 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) @@ -125,7 +125,7 @@ func TestWSConn_HandleComm(t *testing.T) { res, err = wsconn.initBlockListener(1) require.NoError(t, err) - require.Equal(t, 5, res) + 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) @@ -145,7 +145,7 @@ func TestWSConn_HandleComm(t *testing.T) { res, err = wsconn.initBlockFinalizedListener(1) require.EqualError(t, err, "error BlockAPI not set") - require.Equal(t, 0, res) + 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) @@ -154,7 +154,7 @@ func TestWSConn_HandleComm(t *testing.T) { res, err = wsconn.initBlockFinalizedListener(1) require.NoError(t, err) - require.Equal(t, 7, res) + 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) @@ -164,16 +164,16 @@ func TestWSConn_HandleComm(t *testing.T) { wsconn.BlockAPI = nil res, err = wsconn.initExtrinsicWatch(0, []interface{}{"NotHex"}) require.EqualError(t, err, "could not byteify non 0x prefixed string") - require.Equal(t, 0, res) + require.Equal(t, uint(0), res) res, err = wsconn.initExtrinsicWatch(0, []interface{}{"0x26aa"}) require.EqualError(t, err, "error BlockAPI not set") - require.Equal(t, 0, res) + require.Equal(t, uint(0), res) wsconn.BlockAPI = new(MockBlockAPI) res, err = wsconn.initExtrinsicWatch(0, []interface{}{"0x26aa"}) require.NoError(t, err) - require.Equal(t, 8, res) + require.Equal(t, uint(8), res) } From a2068a3e80960698f058c960c1553512d6fc8141 Mon Sep 17 00:00:00 2001 From: Edward Mack Date: Wed, 21 Apr 2021 10:06:48 -0400 Subject: [PATCH 30/30] add return from error --- dot/state/storage_notify.go | 1 + 1 file changed, 1 insertion(+) diff --git a/dot/state/storage_notify.go b/dot/state/storage_notify.go index 6e7a15df90..0d632c09ec 100644 --- a/dot/state/storage_notify.go +++ b/dot/state/storage_notify.go @@ -49,6 +49,7 @@ func (s *StorageState) RegisterStorageObserver(o Observer) { sr, err := s.blockState.BestBlockStateRoot() if err != nil { logger.Debug("error registering storage change channel", "error", err) + return } go func() { if err := s.notifyObserver(sr, o); err != nil {