Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[dot/rpc] implement RPC system_accountNextIndex #1376

Merged
merged 16 commits into from
Feb 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion dot/rpc/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ func (h *HTTPServer) RegisterModules(mods []string) {
var srvc interface{}
switch mod {
case "system":
srvc = modules.NewSystemModule(h.serverConfig.NetworkAPI, h.serverConfig.SystemAPI)
srvc = modules.NewSystemModule(h.serverConfig.NetworkAPI, h.serverConfig.SystemAPI,
h.serverConfig.CoreAPI, h.serverConfig.StorageAPI, h.serverConfig.TransactionQueueAPI)
case "author":
srvc = modules.NewAuthorModule(h.logger, h.serverConfig.CoreAPI, h.serverConfig.RuntimeAPI, h.serverConfig.TransactionQueueAPI)
case "chain":
Expand Down
95 changes: 94 additions & 1 deletion dot/rpc/modules/system.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,25 @@
package modules

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

// SystemModule is an RPC module providing access to core API points
type SystemModule struct {
networkAPI NetworkAPI
systemAPI SystemAPI
coreAPI CoreAPI
storageAPI StorageAPI
txStateAPI TransactionStateAPI
}

// EmptyRequest represents an RPC request with no fields
Expand All @@ -51,11 +61,23 @@ type SystemNetworkStateResponse struct {
// SystemPeersResponse struct to marshal json
type SystemPeersResponse []common.PeerInfo

// U64Response holds U64 response
type U64Response uint64

// StringRequest holds string request
type StringRequest struct {
String string
}

// NewSystemModule creates a new API instance
func NewSystemModule(net NetworkAPI, sys SystemAPI) *SystemModule {
func NewSystemModule(net NetworkAPI, sys SystemAPI, core CoreAPI,
storage StorageAPI, txAPI TransactionStateAPI) *SystemModule {
return &SystemModule{
networkAPI: net, // TODO: migrate to network state
systemAPI: sys,
coreAPI: core,
storageAPI: storage,
txStateAPI: txAPI,
}
}

Expand Down Expand Up @@ -135,3 +157,74 @@ func (sm *SystemModule) NodeRoles(r *http.Request, req *EmptyRequest, res *[]int
*res = resultArray
return nil
}

// AccountNextIndex Returns the next valid index (aka. nonce) for given account.
func (sm *SystemModule) AccountNextIndex(r *http.Request, req *StringRequest, res *U64Response) error {
if req == nil || len(req.String) == 0 {
return errors.New("Account address must be valid")
}
addressPubKey := crypto.PublicAddressToByteArray(common.Address(req.String))

// check pending transactions for extrinsics singed by addressPubKey
pending := sm.txStateAPI.Pending()
nonce := uint64(0)
found := false
for _, v := range pending {
var ext ctypes.Extrinsic
err := ctypes.DecodeFromBytes(v.Extrinsic[1:], &ext)
if err != nil {
return err
}
extSigner, err := common.HexToBytes(fmt.Sprintf("0x%x", ext.Signature.Signer.AsAccountID))
if err != nil {
return err
}
if bytes.Equal(extSigner, addressPubKey) {
found = true
sigNonce := big.Int(ext.Signature.Nonce)
if sigNonce.Uint64() > nonce {
nonce = sigNonce.Uint64()
Comment on lines +185 to +186
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wouldn't the nonce always be greater unless it's 0, in which case it's fine to directly set it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but there may be more than one tx signed by that user in the tx pool, so did this to make sure we have the maximum nonce.

}
}
}

if found {
*res = U64Response(nonce)
return nil
}

// no extrinsic signed by request found in pending transactions, so look in storage
// get metadata to build storage storageKey
rawMeta, err := sm.coreAPI.GetMetadata(nil)
if err != nil {
return err
}
sdMeta, err := scale.Decode(rawMeta, []byte{})
if err != nil {
return err
}
var metadata ctypes.Metadata
err = ctypes.DecodeFromBytes(sdMeta.([]byte), &metadata)
if err != nil {
return err
}

storageKey, err := ctypes.CreateStorageKey(&metadata, "System", "Account", addressPubKey, nil)
if err != nil {
return err
}

accountRaw, err := sm.storageAPI.GetStorage(nil, storageKey)
if err != nil {
return err
}

var accountInfo ctypes.AccountInfo
err = ctypes.DecodeFromBytes(accountRaw, &accountInfo)
if err != nil {
return err
}

*res = U64Response(accountInfo.Nonce)
return nil
}
119 changes: 110 additions & 9 deletions dot/rpc/modules/system_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,12 @@ import (
"testing"

"github.com/ChainSafe/gossamer/dot/network"
"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/scale"
"github.com/ChainSafe/gossamer/lib/transaction"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -97,7 +100,7 @@ func newNetworkService(t *testing.T) *network.Service {
// Test RPC's System.Health() response
func TestSystemModule_Health(t *testing.T) {
net := newNetworkService(t)
sys := NewSystemModule(net, nil)
sys := NewSystemModule(net, nil, nil, nil, nil)

res := &SystemHealthResponse{}
err := sys.Health(nil, nil, res)
Expand All @@ -111,7 +114,7 @@ func TestSystemModule_Health(t *testing.T) {
// Test RPC's System.NetworkState() response
func TestSystemModule_NetworkState(t *testing.T) {
net := newNetworkService(t)
sys := NewSystemModule(net, nil)
sys := NewSystemModule(net, nil, nil, nil, nil)

res := &SystemNetworkStateResponse{}
err := sys.NetworkState(nil, nil, res)
Expand All @@ -127,7 +130,7 @@ func TestSystemModule_NetworkState(t *testing.T) {
// Test RPC's System.Peers() response
func TestSystemModule_Peers(t *testing.T) {
net := newNetworkService(t)
sys := NewSystemModule(net, nil)
sys := NewSystemModule(net, nil, nil, nil, nil)

res := &SystemPeersResponse{}
err := sys.Peers(nil, nil, res)
Expand All @@ -140,7 +143,7 @@ func TestSystemModule_Peers(t *testing.T) {

func TestSystemModule_NodeRoles(t *testing.T) {
net := newNetworkService(t)
sys := NewSystemModule(net, nil)
sys := NewSystemModule(net, nil, nil, nil, nil)
expected := []interface{}{"Full"}

var res []interface{}
Expand Down Expand Up @@ -193,7 +196,7 @@ func (api *mockSystemAPI) ChainType() string {
}

func TestSystemModule_Chain(t *testing.T) {
sys := NewSystemModule(nil, newMockSystemAPI())
sys := NewSystemModule(nil, newMockSystemAPI(), nil, nil, nil)

res := new(string)
err := sys.Chain(nil, nil, res)
Expand All @@ -204,15 +207,15 @@ func TestSystemModule_Chain(t *testing.T) {
func TestSystemModule_ChainType(t *testing.T) {
api := newMockSystemAPI()

sys := NewSystemModule(nil, api)
sys := NewSystemModule(nil, api, nil, nil, nil)

res := new(string)
sys.ChainType(nil, nil, res)
require.Equal(t, api.genData.ChainType, *res)
}

func TestSystemModule_Name(t *testing.T) {
sys := NewSystemModule(nil, newMockSystemAPI())
sys := NewSystemModule(nil, newMockSystemAPI(), nil, nil, nil)

res := new(string)
err := sys.Name(nil, nil, res)
Expand All @@ -221,7 +224,7 @@ func TestSystemModule_Name(t *testing.T) {
}

func TestSystemModule_Version(t *testing.T) {
sys := NewSystemModule(nil, newMockSystemAPI())
sys := NewSystemModule(nil, newMockSystemAPI(), nil, nil, nil)

res := new(string)
err := sys.Version(nil, nil, res)
Expand All @@ -230,10 +233,108 @@ func TestSystemModule_Version(t *testing.T) {
}

func TestSystemModule_Properties(t *testing.T) {
sys := NewSystemModule(nil, newMockSystemAPI())
sys := NewSystemModule(nil, newMockSystemAPI(), nil, nil, nil)

expected := map[string]interface{}(nil)

res := new(interface{})
err := sys.Properties(nil, nil, res)
require.NoError(t, err)
require.Equal(t, expected, *res)
}

func TestSystemModule_AccountNextIndex_StoragePending(t *testing.T) {
sys := setupSystemModule(t)
expectedStored := U64Response(uint64(3))

res := new(U64Response)
req := StringRequest{
String: "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
}
err := sys.AccountNextIndex(nil, &req, res)
require.NoError(t, err)

require.Equal(t, expectedStored, *res)

// extrinsic for transfer signed by alice, nonce 4 (created with polkadot.js/api test_transaction)
signedExt := common.MustHexToBytes("0x022d0284ffd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d018c35943da8a04f06a36db9fadc7b2f02ccdef38dd89f88835c0af16b5fce816b117d8073aca078984d5b81bcf86e89cfa3195e5ec3c457d4282370b854f430850010000600ff90b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22e5c0")
vtx := &transaction.ValidTransaction{
Extrinsic: types.NewExtrinsic(signedExt),
Validity: new(transaction.Validity),
}
expectedPending := U64Response(uint64(4))
sys.txStateAPI.AddToPool(vtx)

err = sys.AccountNextIndex(nil, &req, res)
require.NoError(t, err)
require.Equal(t, expectedPending, *res)
}

func TestSystemModule_AccountNextIndex_Storage(t *testing.T) {
sys := setupSystemModule(t)
expectedStored := U64Response(uint64(3))

res := new(U64Response)
req := StringRequest{
String: "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
}
err := sys.AccountNextIndex(nil, &req, res)
require.NoError(t, err)

require.Equal(t, expectedStored, *res)
}

func TestSystemModule_AccountNextIndex_Pending(t *testing.T) {
sys := setupSystemModule(t)
res := new(U64Response)
req := StringRequest{
String: "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
}

// extrinsic for transfer signed by alice, nonce 4 (created with polkadot.js/api test_transaction)
signedExt := common.MustHexToBytes("0x022d0284ffd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d018c35943da8a04f06a36db9fadc7b2f02ccdef38dd89f88835c0af16b5fce816b117d8073aca078984d5b81bcf86e89cfa3195e5ec3c457d4282370b854f430850010000600ff90b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22e5c0")
vtx := &transaction.ValidTransaction{
Extrinsic: types.NewExtrinsic(signedExt),
Validity: new(transaction.Validity),
}
expectedPending := U64Response(uint64(4))
sys.txStateAPI.AddToPool(vtx)

err := sys.AccountNextIndex(nil, &req, res)
require.NoError(t, err)
require.Equal(t, expectedPending, *res)
}

func setupSystemModule(t *testing.T) *SystemModule {
// setup service
net := newNetworkService(t)
chain := newTestStateService(t)
// init storage with test data
ts, err := chain.Storage.TrieState(nil)
require.NoError(t, err)

aliceAcctStoKey, err := common.HexToBytes("0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9de1e86a9a8c739864cf3cc5ec2bea59fd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d")
require.NoError(t, err)
aliceAcctInfo := types.AccountInfo{
Nonce: 3,
RefCount: 0,
Data: struct {
Free common.Uint128
Reserved common.Uint128
MiscFrozen common.Uint128
FreeFrozen common.Uint128
}{},
}
aliceAcctEncoded, err := scale.Encode(aliceAcctInfo)
require.NoError(t, err)
err = ts.Set(aliceAcctStoKey, aliceAcctEncoded)
require.NoError(t, err)

err = chain.Storage.StoreTrie(ts)
require.NoError(t, err)

core := newCoreService(t, chain)
// TODO (ed) add transactions to txQueue and add test for those
txQueue := state.NewTransactionState()
return NewSystemModule(net, nil, core, chain.Storage, txQueue)
}
4 changes: 2 additions & 2 deletions dot/rpc/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,12 @@ func TestNewService(t *testing.T) {
}

func TestService_Methods(t *testing.T) {
qtySystemMethods := 9
qtySystemMethods := 10
qtyRPCMethods := 1
qtyAuthorMethods := 7

rpcService := NewService()
sysMod := modules.NewSystemModule(nil, nil)
sysMod := modules.NewSystemModule(nil, nil, nil, nil, nil)
rpcService.BuildMethodNames(sysMod, "system")
m := rpcService.Methods()
require.Equal(t, qtySystemMethods, len(m)) // check to confirm quantity for methods is correct
Expand Down
3 changes: 1 addition & 2 deletions tests/polkadotjs_test/test_transaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,14 @@ async function main() {
const api = await ApiPromise.create({ provider: wsProvider });

// Simple transaction
// TODO Issue: This currently fails with error: RPC-CORE: submitExtrinsic(extrinsic: Extrinsic): Hash:: -32000: validator: (nil *modules.Extrinsic): null
const keyring = new Keyring({type: 'sr25519' });
const aliceKey = keyring.addFromUri('//Alice', { name: 'Alice default' });
console.log(`${aliceKey.meta.name}: has address ${aliceKey.address} with publicKey [${aliceKey.publicKey}]`);

const ADDR_Bob = '0x90b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22';

const transfer = await api.tx.balances.transfer(ADDR_Bob, 12345)
.signAndSend(aliceKey, {era: 0, blockHash: '0x64597c55a052d484d9ff357266be326f62573bb4fbdbb3cd49f219396fcebf78', blockNumber:0, genesisHash: '0x64597c55a052d484d9ff357266be326f62573bb4fbdbb3cd49f219396fcebf78', nonce: 0, tip: 0, transactionVersion: 1});
.signAndSend(aliceKey, {era: 0, blockHash: '0x64597c55a052d484d9ff357266be326f62573bb4fbdbb3cd49f219396fcebf78', blockNumber:0, genesisHash: '0x64597c55a052d484d9ff357266be326f62573bb4fbdbb3cd49f219396fcebf78', nonce: 1, tip: 0, transactionVersion: 1});

console.log(`hxHash ${transfer}`);

Expand Down