Skip to content

Commit

Permalink
feat(rpc): Implement payment_queryInfo RPC call (ChainSafe#1826)
Browse files Browse the repository at this point in the history
* chore: adding payment rpc module

* chore: adding unit test to runtime call

* chore: use defined extrinsic

* chore: add Payment Query Info

* chore: add unit test to runtime layer

* chore: add unit tests

* chore: resolve uint128

* chore: fix lint warns

* chore: put condition back

* chore: use pkg/scale uint128

* chore: fix unit tests

* chore: remove debug from config.toml

* fix: lint

* chore: remove tests from wasmtime and life

* chore: resolving conflicts

* chore: remove life impl form payment query info

* chore: simplify chain_test function

* chore: ignore unused meth receiver
  • Loading branch information
EclesioMeloJunior authored and timwu20 committed Dec 6, 2021
1 parent afe350c commit d6e3583
Show file tree
Hide file tree
Showing 25 changed files with 461 additions and 45 deletions.
2 changes: 1 addition & 1 deletion chain/dev/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,5 @@ enabled = true
ws = true
port = 8545
host = "localhost"
modules = ["system", "author", "chain", "state", "rpc", "grandpa", "offchain", "childstate"]
modules = ["system", "author", "chain", "state", "rpc", "grandpa", "offchain", "childstate", "payment"]
ws-port = 8546
2 changes: 1 addition & 1 deletion chain/dev/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ var (
// DefaultRPCHTTPPort rpc port
DefaultRPCHTTPPort = uint32(8545)
// DefaultRPCModules rpc modules
DefaultRPCModules = []string{"system", "author", "chain", "state", "rpc", "grandpa", "offchain", "childstate"}
DefaultRPCModules = []string{"system", "author", "chain", "state", "rpc", "grandpa", "offchain", "childstate", "payment"}
// DefaultRPCWSPort rpc websocket port
DefaultRPCWSPort = uint32(8546)
// DefaultRPCEnabled enables the RPC server
Expand Down
2 changes: 1 addition & 1 deletion chain/gssmr/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,5 @@ discovery-interval = 10
enabled = false
port = 8545
host = "localhost"
modules = ["system", "author", "chain", "state", "rpc", "grandpa", "offchain", "childstate"]
modules = ["system", "author", "chain", "state", "rpc", "grandpa", "offchain", "childstate", "payment"]
ws-port = 8546
2 changes: 1 addition & 1 deletion chain/gssmr/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ var (
// DefaultRPCHTTPPort rpc port
DefaultRPCHTTPPort = uint32(8545)
// DefaultRPCModules rpc modules
DefaultRPCModules = []string{"system", "author", "chain", "state", "rpc", "grandpa", "offchain", "childstate"}
DefaultRPCModules = []string{"system", "author", "chain", "state", "rpc", "grandpa", "offchain", "childstate", "payment"}
// DefaultRPCWSPort rpc websocket port
DefaultRPCWSPort = uint32(8546)
)
2 changes: 1 addition & 1 deletion chain/kusama/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ enabled = false
external = false
port = 8545
host = "localhost"
modules = ["system", "author", "chain", "state", "rpc", "grandpa", "offchain", "childstate"]
modules = ["system", "author", "chain", "state", "rpc", "grandpa", "offchain", "childstate", "payment"]
ws-port = 8546
ws = false
ws-external = false
2 changes: 1 addition & 1 deletion chain/kusama/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ var (
// DefaultRPCHTTPPort rpc port
DefaultRPCHTTPPort = uint32(8545)
// DefaultRPCModules rpc modules
DefaultRPCModules = []string{"system", "author", "chain", "state", "rpc", "grandpa", "offchain", "childstate"}
DefaultRPCModules = []string{"system", "author", "chain", "state", "rpc", "grandpa", "offchain", "childstate", "payment"}
// DefaultRPCWSPort rpc websocket port
DefaultRPCWSPort = uint32(8546)
)
2 changes: 1 addition & 1 deletion chain/polkadot/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,5 @@ nomdns = false
enabled = false
port = 8545
host = "localhost"
modules = ["system", "author", "chain", "state", "rpc", "grandpa", "offchain", "childstate"]
modules = ["system", "author", "chain", "state", "rpc", "grandpa", "offchain", "childstate", "payment"]
ws-port = 8546
2 changes: 1 addition & 1 deletion chain/polkadot/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ var (
// DefaultRPCHTTPPort rpc port
DefaultRPCHTTPPort = uint32(8545)
// DefaultRPCModules rpc modules
DefaultRPCModules = []string{"system", "author", "chain", "state", "rpc", "grandpa", "offchain", "childstate"}
DefaultRPCModules = []string{"system", "author", "chain", "state", "rpc", "grandpa", "offchain", "childstate", "payment"}
// DefaultRPCWSPort rpc websocket port
DefaultRPCWSPort = uint32(8546)
)
2 changes: 2 additions & 0 deletions dot/rpc/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ func (h *HTTPServer) RegisterModules(mods []string) {
srvc = modules.NewOffchainModule(h.serverConfig.NodeStorage)
case "childstate":
srvc = modules.NewChildStateModule(h.serverConfig.StorageAPI, h.serverConfig.BlockAPI)
case "payment":
srvc = modules.NewPaymentModule(h.serverConfig.BlockAPI)
default:
h.logger.Warn("Unrecognised module", "module", mod)
continue
Expand Down
1 change: 1 addition & 0 deletions dot/rpc/modules/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ type BlockAPI interface {
SubChain(start, end common.Hash) ([]common.Hash, error)
RegisterRuntimeUpdatedChannel(ch chan<- runtime.Version) (uint32, error)
UnregisterRuntimeUpdatedChannel(id uint32) bool
GetRuntime(hash *common.Hash) (runtime.Instance, error)
}

// NetworkAPI interface for network state methods
Expand Down
18 changes: 17 additions & 1 deletion dot/rpc/modules/chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,21 @@ package modules
import (
"io/ioutil"
"math/big"
"path/filepath"
"testing"

"github.com/ChainSafe/gossamer/dot/state"
"github.com/ChainSafe/gossamer/dot/types"
"github.com/ChainSafe/gossamer/lib/common"
"github.com/ChainSafe/gossamer/lib/genesis"
"github.com/ChainSafe/gossamer/lib/runtime"
"github.com/ChainSafe/gossamer/lib/runtime/wasmer"
"github.com/ChainSafe/gossamer/lib/trie"
"github.com/ChainSafe/gossamer/lib/utils"
"github.com/ChainSafe/gossamer/pkg/scale"

database "github.com/ChainSafe/chaindb"
rtstorage "github.com/ChainSafe/gossamer/lib/runtime/storage"
log "github.com/ChainSafe/log15"
"github.com/stretchr/testify/require"
)
Expand Down Expand Up @@ -350,7 +354,19 @@ func newTestStateService(t *testing.T) *state.Service {
err = stateSrvc.Start()
require.NoError(t, err)

rt, err := stateSrvc.CreateGenesisRuntime(genTrie, gen)
rtCfg := &wasmer.Config{}

rtCfg.Storage, err = rtstorage.NewTrieState(genTrie)
require.NoError(t, err)

if stateSrvc != nil {
rtCfg.NodeStorage.BaseDB = stateSrvc.Base
} else {
rtCfg.NodeStorage.BaseDB, err = utils.SetupDatabase(filepath.Join(testDatadirPath, "offline_storage"), false)
require.NoError(t, err)
}

rt, err := wasmer.NewRuntimeFromGenesis(gen, rtCfg)
require.NoError(t, err)

err = loadTestBlocks(t, genesisHeader.Hash(), stateSrvc.Block, rt)
Expand Down
25 changes: 24 additions & 1 deletion dot/rpc/modules/mocks/block_api.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

69 changes: 69 additions & 0 deletions dot/rpc/modules/payment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package modules

import (
"net/http"

"github.com/ChainSafe/gossamer/lib/common"
)

// PaymentQueryInfoRequest represents the request to get the fee of an extrinsic in a given block
type PaymentQueryInfoRequest struct {
// hex SCALE encoded extrinsic
Ext string
// hex optional block hash indicating the state
Hash *common.Hash
}

// PaymentQueryInfoResponse holds the response fields to the query info RPC method
type PaymentQueryInfoResponse struct {
Weight uint64 `json:"weight"`
Class int `json:"class"`
PartialFee string `json:"partialFee"`
}

// PaymentModule holds all the RPC implementation of polkadot payment rpc api
type PaymentModule struct {
blockAPI BlockAPI
}

// NewPaymentModule returns a pointer to PaymentModule
func NewPaymentModule(blockAPI BlockAPI) *PaymentModule {
return &PaymentModule{
blockAPI: blockAPI,
}
}

// QueryInfo query the known data about the fee of an extrinsic at the given block
func (p *PaymentModule) QueryInfo(_ *http.Request, req *PaymentQueryInfoRequest, res *PaymentQueryInfoResponse) error {
var hash common.Hash
if req.Hash == nil {
hash = p.blockAPI.BestBlockHash()
} else {
hash = *req.Hash
}

r, err := p.blockAPI.GetRuntime(&hash)
if err != nil {
return err
}

ext, err := common.HexToBytes(req.Ext)
if err != nil {
return err
}

encQueryInfo, err := r.PaymentQueryInfo(ext)
if err != nil {
return err
}

if encQueryInfo != nil {
*res = PaymentQueryInfoResponse{
Weight: encQueryInfo.Weight,
Class: encQueryInfo.Class,
PartialFee: encQueryInfo.PartialFee.String(),
}
}

return nil
}
142 changes: 142 additions & 0 deletions dot/rpc/modules/payment_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package modules

import (
"errors"
"testing"

"github.com/ChainSafe/gossamer/dot/rpc/modules/mocks"
"github.com/ChainSafe/gossamer/dot/types"
"github.com/ChainSafe/gossamer/pkg/scale"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"

"github.com/ChainSafe/gossamer/lib/common"
mocksruntime "github.com/ChainSafe/gossamer/lib/runtime/mocks"
)

func TestPaymentQueryInfo(t *testing.T) {
state := newTestStateService(t)
bestBlockHash := state.Block.BestBlockHash()

t.Run("When there is no errors", func(t *testing.T) {
mockedQueryInfo := &types.TransactionPaymentQueryInfo{
Weight: 0,
Class: 0,
PartialFee: scale.MaxUint128,
}

expected := PaymentQueryInfoResponse{
Weight: 0,
Class: 0,
PartialFee: scale.MaxUint128.String(),
}

runtimeMock := new(mocksruntime.MockInstance)
runtimeMock.On("PaymentQueryInfo", mock.AnythingOfType("[]uint8")).Return(mockedQueryInfo, nil)

blockAPIMock := new(mocks.MockBlockAPI)
blockAPIMock.On("BestBlockHash").Return(bestBlockHash)

blockAPIMock.On("GetRuntime", mock.AnythingOfType("*common.Hash")).Return(runtimeMock, nil)

mod := &PaymentModule{
blockAPI: blockAPIMock,
}

var req PaymentQueryInfoRequest
req.Ext = "0x0001"
req.Hash = nil

var res PaymentQueryInfoResponse
err := mod.QueryInfo(nil, &req, &res)

require.NoError(t, err)
require.Equal(t, expected, res)

// should be called because req.Hash is nil
blockAPIMock.AssertCalled(t, "BestBlockHash")
blockAPIMock.AssertCalled(t, "GetRuntime", mock.AnythingOfType("*common.Hash"))
runtimeMock.AssertCalled(t, "PaymentQueryInfo", mock.AnythingOfType("[]uint8"))
})

t.Run("When could not get runtime", func(t *testing.T) {
blockAPIMock := new(mocks.MockBlockAPI)
blockAPIMock.On("BestBlockHash").Return(bestBlockHash)

blockAPIMock.On("GetRuntime", mock.AnythingOfType("*common.Hash")).
Return(nil, errors.New("mocked problems"))

mod := &PaymentModule{
blockAPI: blockAPIMock,
}

var req PaymentQueryInfoRequest
req.Ext = "0x0011"
req.Hash = nil

var res PaymentQueryInfoResponse
err := mod.QueryInfo(nil, &req, &res)

require.Error(t, err)
require.Equal(t, res, PaymentQueryInfoResponse{})

blockAPIMock.AssertCalled(t, "BestBlockHash")
blockAPIMock.AssertCalled(t, "GetRuntime", mock.AnythingOfType("*common.Hash"))
})

t.Run("When PaymentQueryInfo returns error", func(t *testing.T) {
runtimeMock := new(mocksruntime.MockInstance)
runtimeMock.On("PaymentQueryInfo", mock.AnythingOfType("[]uint8")).Return(nil, errors.New("mocked error"))

blockAPIMock := new(mocks.MockBlockAPI)
blockAPIMock.On("GetRuntime", mock.AnythingOfType("*common.Hash")).Return(runtimeMock, nil)

mod := &PaymentModule{
blockAPI: blockAPIMock,
}

mockedHash := common.NewHash([]byte{0x01, 0x02})
var req PaymentQueryInfoRequest
req.Ext = "0x0000"
req.Hash = &mockedHash

var res PaymentQueryInfoResponse
err := mod.QueryInfo(nil, &req, &res)

require.Error(t, err)
require.Equal(t, res, PaymentQueryInfoResponse{})

// should be called because req.Hash is nil
blockAPIMock.AssertNotCalled(t, "BestBlockHash")
blockAPIMock.AssertCalled(t, "GetRuntime", mock.AnythingOfType("*common.Hash"))
runtimeMock.AssertCalled(t, "PaymentQueryInfo", mock.AnythingOfType("[]uint8"))
})

t.Run("When PaymentQueryInfo returns a nil info", func(t *testing.T) {
runtimeMock := new(mocksruntime.MockInstance)
runtimeMock.On("PaymentQueryInfo", mock.AnythingOfType("[]uint8")).Return(nil, nil)

blockAPIMock := new(mocks.MockBlockAPI)
blockAPIMock.On("GetRuntime", mock.AnythingOfType("*common.Hash")).Return(runtimeMock, nil)

mod := &PaymentModule{
blockAPI: blockAPIMock,
}

mockedHash := common.NewHash([]byte{0x01, 0x02})
var req PaymentQueryInfoRequest
req.Ext = "0x0020"
req.Hash = &mockedHash

var res PaymentQueryInfoResponse
err := mod.QueryInfo(nil, &req, &res)

require.NoError(t, err)
require.Equal(t, res, PaymentQueryInfoResponse{})

// should be called because req.Hash is nil
blockAPIMock.AssertNotCalled(t, "BestBlockHash")
blockAPIMock.AssertCalled(t, "GetRuntime", mock.AnythingOfType("*common.Hash"))
runtimeMock.AssertCalled(t, "PaymentQueryInfo", mock.AnythingOfType("[]uint8"))
})
}
2 changes: 2 additions & 0 deletions dot/rpc/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,13 @@ func (s *Service) BuildMethodNames(rcvr interface{}, name string) {
if mtype.NumIn() != 4 {
continue
}

// First argument must be a pointer and must be http.Request.
reqType := mtype.In(1)
if reqType.Kind() != reflect.Ptr || reqType.Elem() != typeOfRequest {
continue
}

// Second argument must be a pointer and must be exported.
args := mtype.In(2)
if args.Kind() != reflect.Ptr || !isExportedOrBuiltIn(args) {
Expand Down
Loading

0 comments on commit d6e3583

Please sign in to comment.