From 68ac803c7a456307de7af0a566493aee9d36095d Mon Sep 17 00:00:00 2001 From: Arijit Das Date: Mon, 22 Mar 2021 21:07:57 +0530 Subject: [PATCH] test: dot: test transfer extrinsic (#1448) --- dot/rpc/modules/author.go | 12 +-- dot/rpc/modules/author_test.go | 14 +-- lib/grandpa/grandpa.go | 1 - tests/rpc/rpc_02-author_test.go | 6 +- tests/stress/helpers.go | 97 +----------------- tests/stress/stress_test.go | 172 +++++++++++++++++++++++++++++++- tests/utils/common.go | 2 + tests/utils/gossamer_utils.go | 2 +- tests/utils/request_utils.go | 6 +- tests/utils/rpc_methods.go | 3 +- 10 files changed, 194 insertions(+), 121 deletions(-) diff --git a/dot/rpc/modules/author.go b/dot/rpc/modules/author.go index 9facc7186d..2066e4d40c 100644 --- a/dot/rpc/modules/author.go +++ b/dot/rpc/modules/author.go @@ -56,7 +56,7 @@ type ExtrinsicOrHashRequest []ExtrinsicOrHash type KeyInsertResponse []byte // PendingExtrinsicsResponse is a bi-dimensional array of bytes for allocating the pending extrisics -type PendingExtrinsicsResponse [][]byte +type PendingExtrinsicsResponse []string // RemoveExtrinsicsResponse is a array of hash used to Remove extrinsics type RemoveExtrinsicsResponse []common.Hash @@ -134,13 +134,9 @@ func (cm *AuthorModule) HasKey(r *http.Request, req *[]string, res *bool) error // PendingExtrinsics Returns all pending extrinsics func (cm *AuthorModule) PendingExtrinsics(r *http.Request, req *EmptyRequest, res *PendingExtrinsicsResponse) error { pending := cm.txStateAPI.Pending() - resp := [][]byte{} - for _, tx := range pending { - enc, err := tx.Encode() - if err != nil { - return err - } - resp = append(resp, enc) + resp := make([]string, len(pending)) + for idx, tx := range pending { + resp[idx] = common.BytesToHex(tx.Extrinsic) } *res = PendingExtrinsicsResponse(resp) diff --git a/dot/rpc/modules/author_test.go b/dot/rpc/modules/author_test.go index affb9b4968..ff3f48f785 100644 --- a/dot/rpc/modules/author_test.go +++ b/dot/rpc/modules/author_test.go @@ -51,8 +51,8 @@ func TestAuthorModule_Pending(t *testing.T) { t.Fatal(err) } - if !reflect.DeepEqual(*res, PendingExtrinsicsResponse([][]byte{})) { - t.Errorf("Fail: expected: %+v got: %+v\n", res, &[][]byte{}) + if !reflect.DeepEqual(*res, PendingExtrinsicsResponse([]string{})) { + t.Errorf("Fail: expected: %+v got: %+v\n", *res, PendingExtrinsicsResponse([]string{})) } vtx := &transaction.ValidTransaction{ @@ -68,13 +68,9 @@ func TestAuthorModule_Pending(t *testing.T) { t.Fatal(err) } - expected, err := vtx.Encode() - if err != nil { - t.Fatal(err) - } - - if !reflect.DeepEqual(*res, PendingExtrinsicsResponse([][]byte{expected})) { - t.Errorf("Fail: expected: %+v got: %+v\n", res, &[][]byte{expected}) + expected := common.BytesToHex(vtx.Extrinsic) + if !reflect.DeepEqual(*res, PendingExtrinsicsResponse([]string{expected})) { + t.Errorf("Fail: expected: %+v got: %+v\n", res, PendingExtrinsicsResponse([]string{expected})) } } diff --git a/lib/grandpa/grandpa.go b/lib/grandpa/grandpa.go index 07184abfb5..345b33ec74 100644 --- a/lib/grandpa/grandpa.go +++ b/lib/grandpa/grandpa.go @@ -515,7 +515,6 @@ func (s *Service) playGrandpaRound() error { // receive messages until current round is completable and previous round is finalizable // and the last finalized block is greater than the best final candidate from the previous round s.receiveMessages(func() bool { - //return false if s.paused.Load().(bool) { return true } diff --git a/tests/rpc/rpc_02-author_test.go b/tests/rpc/rpc_02-author_test.go index 82adcb9a23..72f86ba613 100644 --- a/tests/rpc/rpc_02-author_test.go +++ b/tests/rpc/rpc_02-author_test.go @@ -79,8 +79,8 @@ func TestAuthorSubmitExtrinsic(t *testing.T) { key, err := types.CreateStorageKey(meta, "System", "Account", signature.TestKeyringPairAlice.PublicKey, nil) require.NoError(t, err) - var nonce uint32 - ok, err := api.RPC.State.GetStorageLatest(key, &nonce) + var accInfo types.AccountInfo + ok, err := api.RPC.State.GetStorageLatest(key, &accInfo) require.NoError(t, err) require.True(t, ok) @@ -88,7 +88,7 @@ func TestAuthorSubmitExtrinsic(t *testing.T) { BlockHash: genesisHash, Era: types.ExtrinsicEra{IsImmortalEra: true}, GenesisHash: genesisHash, - Nonce: types.NewUCompactFromUInt(uint64(nonce)), + Nonce: types.NewUCompactFromUInt(uint64(accInfo.Nonce)), SpecVersion: rv.SpecVersion, Tip: types.NewUCompactFromUInt(0), TransactionVersion: rv.TransactionVersion, diff --git a/tests/stress/helpers.go b/tests/stress/helpers.go index e15d620a76..6751cb2223 100644 --- a/tests/stress/helpers.go +++ b/tests/stress/helpers.go @@ -17,19 +17,13 @@ package stress import ( - "bytes" - "encoding/hex" "fmt" - "math/rand" "sync" "testing" "time" "github.com/ChainSafe/gossamer/dot/rpc/modules" - "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/common" - "github.com/ChainSafe/gossamer/lib/runtime/extrinsic" - "github.com/ChainSafe/gossamer/lib/trie" "github.com/ChainSafe/gossamer/tests/utils" log "github.com/ChainSafe/log15" @@ -109,16 +103,7 @@ func compareBlocksByNumber(t *testing.T, nodes []*utils.Node, num string) (map[c }(node) } - done := make(chan struct{}) - go func() { - wg.Wait() - close(done) - }() - - select { - case <-time.After(time.Second * 30): - case <-done: - } + wg.Wait() var err error if len(errs) != 0 { @@ -240,8 +225,8 @@ func compareFinalizedHeadsWithRetry(t *testing.T, nodes []*utils.Node, round uin return common.Hash{}, nil } -func getPendingExtrinsics(t *testing.T, node *utils.Node) [][]byte { //nolint - respBody, err := utils.PostRPC(utils.AuthorSubmitExtrinsic, utils.NewEndpoint(node.RPCPort), "[]") +func getPendingExtrinsics(t *testing.T, node *utils.Node) []string { //nolint + respBody, err := utils.PostRPC(utils.AuthorPendingExtrinsics, utils.NewEndpoint(node.RPCPort), "[]") require.NoError(t, err) exts := new(modules.PendingExtrinsicsResponse) @@ -250,79 +235,3 @@ func getPendingExtrinsics(t *testing.T, node *utils.Node) [][]byte { //nolint return *exts } - -// submitExtrinsicAssertInclusion submits an extrinsic to a random node and asserts that the extrinsic was included in some block -// and that the nodes remain synced -func submitExtrinsicAssertInclusion(t *testing.T, nodes []*utils.Node, ext extrinsic.Extrinsic) { //nolint - tx, err := ext.Encode() - require.NoError(t, err) - - txStr := hex.EncodeToString(tx) - logger.Info("submitting transaction", "tx", txStr) - - // send extrinsic to random node - idx := rand.Intn(len(nodes)) //nolint - prevHeader := utils.GetChainHead(t, nodes[idx]) // get starting header so that we can lookup blocks by number later - respBody, err := utils.PostRPC(utils.AuthorSubmitExtrinsic, utils.NewEndpoint(nodes[idx].RPCPort), "\"0x"+txStr+"\"") - require.NoError(t, err) - - var hash modules.ExtrinsicHashResponse - err = utils.DecodeRPC(t, respBody, &hash) - require.Nil(t, err) - log.Info("submitted transaction", "hash", hash, "node", nodes[idx].Key) - t.Logf("submitted transaction to node %s", nodes[idx].Key) - - // wait for nodes to build block + sync, then get headers - time.Sleep(time.Second * 10) - - for i := 0; i < maxRetries; i++ { - exts := getPendingExtrinsics(t, nodes[idx]) - if len(exts) == 0 { - break - } - - time.Sleep(time.Second) - } - - header := utils.GetChainHead(t, nodes[idx]) - logger.Info("got header from node", "header", header, "hash", header.Hash(), "node", nodes[idx].Key) - - // search from child -> parent blocks for extrinsic - var resExts []types.Extrinsic - i := 0 - for header.ExtrinsicsRoot == trie.EmptyHash && i != maxRetries { - // check all nodes, since it might have been included on any of the block producers - var block *types.Block - - for j := 0; j < len(nodes); j++ { - block = utils.GetBlock(t, nodes[j], header.ParentHash) - if block == nil { - // couldn't get block, increment retry counter - i++ - continue - } - - header = block.Header - logger.Info("got block from node", "hash", header.Hash(), "node", nodes[j].Key) - logger.Debug("got block from node", "header", header, "body", block.Body, "hash", header.Hash(), "node", nodes[j].Key) - - if block.Body != nil && !bytes.Equal(*(block.Body), []byte{0}) { - resExts, err = block.Body.AsExtrinsics() - require.NoError(t, err, block.Body) - break - } - - if header.Hash() == prevHeader.Hash() && j == len(nodes)-1 { - t.Fatal("could not find extrinsic in any blocks") - } - } - - if block != nil && block.Body != nil && !bytes.Equal(*(block.Body), []byte{0}) { - break - } - } - - // assert that the extrinsic included is the one we submitted - require.Equal(t, 1, len(resExts), "did not find extrinsic in block on any node") - require.Equal(t, resExts[0], types.Extrinsic(tx)) -} diff --git a/tests/stress/stress_test.go b/tests/stress/stress_test.go index f8f7e26d39..23c7ee27a8 100644 --- a/tests/stress/stress_test.go +++ b/tests/stress/stress_test.go @@ -22,10 +22,17 @@ import ( "math/rand" "os" "strconv" + "strings" "testing" "time" + gosstypes "github.com/ChainSafe/gossamer/dot/types" + "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/lib/scale" "github.com/ChainSafe/gossamer/tests/utils" + gsrpc "github.com/centrifuge/go-substrate-rpc-client/v2" + "github.com/centrifuge/go-substrate-rpc-client/v2/signature" + "github.com/centrifuge/go-substrate-rpc-client/v2/types" log "github.com/ChainSafe/log15" "github.com/stretchr/testify/require" @@ -45,10 +52,18 @@ func TestMain(m *testing.M) { utils.CreateConfigBabeMaxThreshold() utils.CreateDefaultConfig() - // TODO: implement test log flag - utils.SetLogLevel(log.LvlInfo) + logLvl := log.LvlInfo + if utils.LOGLEVEL != "" { + var err error + logLvl, err = log.LvlFromString(utils.LOGLEVEL) + if err != nil { + panic(fmt.Sprint("Invalid log level: ", err)) + } + } + + utils.SetLogLevel(logLvl) h := log.StreamHandler(os.Stdout, log.TerminalFormat()) - logger.SetHandler(log.LvlFilterHandler(log.LvlInfo, h)) + logger.SetHandler(log.LvlFilterHandler(logLvl, h)) utils.GenerateGenesisThreeAuth() @@ -341,3 +356,154 @@ func TestSync_Restart(t *testing.T) { } close(done) } + +func TestPendingExtrinsic(t *testing.T) { + // TODO: Fix this test and enable it. Node syncing takes time. + t.Skip("skipping TestPendingExtrinsic") + + t.Log("starting gossamer...") + + utils.CreateConfigBabeMaxThreshold() + + numNodes := 3 + // index of node to submit tx to + idx := numNodes - 1 // TODO: randomize this + + // start block producing node first + node, err := utils.RunGossamer(t, numNodes-1, utils.TestDir(t, utils.KeyList[numNodes-1]), utils.GenesisDefault, utils.ConfigBABEMaxThreshold, false) + require.NoError(t, err) + + // send tx to non-authority node + api, err := gsrpc.NewSubstrateAPI(fmt.Sprintf("http://localhost:%s", node.RPCPort)) + require.NoError(t, err) + + meta, err := api.RPC.State.GetMetadataLatest() + require.NoError(t, err) + + // Create a call, transferring 12345 units to Bob + bob, err := types.NewAddressFromHexAccountID("0x90b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22") + require.NoError(t, err) + + c, err := types.NewCall(meta, "Balances.transfer", bob, types.NewUCompactFromUInt(12345)) + require.NoError(t, err) + + // Create the extrinsic + ext := types.NewExtrinsic(c) + + genesisHash, err := api.RPC.Chain.GetBlockHash(0) + require.NoError(t, err) + + rv, err := api.RPC.State.GetRuntimeVersionLatest() + require.NoError(t, err) + + key, err := types.CreateStorageKey(meta, "System", "Account", signature.TestKeyringPairAlice.PublicKey, nil) + require.NoError(t, err) + + var accInfo types.AccountInfo + ok, err := api.RPC.State.GetStorageLatest(key, &accInfo) + require.NoError(t, err) + require.True(t, ok) + + o := types.SignatureOptions{ + BlockHash: genesisHash, + Era: types.ExtrinsicEra{IsImmortalEra: true}, + GenesisHash: genesisHash, + Nonce: types.NewUCompactFromUInt(uint64(accInfo.Nonce)), + SpecVersion: rv.SpecVersion, + Tip: types.NewUCompactFromUInt(0), + TransactionVersion: rv.TransactionVersion, + } + + // Sign the transaction using Alice's default account + err = ext.Sign(signature.TestKeyringPairAlice, o) + require.NoError(t, err) + + extEnc, err := types.EncodeToHexString(ext) + require.NoError(t, err) + + prevHeader := utils.GetChainHead(t, node) // get starting header so that we can lookup blocks by number later + + // Send the extrinsic + hash, err := api.RPC.Author.SubmitExtrinsic(ext) + require.NoError(t, err) + require.NotEqual(t, hash, common.Hash{}) + + // wait and start rest of nodes + // TODO: it seems like the non-authority nodes don't sync properly if started before submitting the tx + time.Sleep(time.Second * 20) + nodes, err := utils.InitializeAndStartNodes(t, numNodes-1, utils.GenesisDefault, utils.ConfigNoBABE) + require.NoError(t, err) + nodes = append(nodes, node) + + defer func() { + t.Log("going to tear down gossamer...") + os.Remove(utils.ConfigBABEMaxThreshold) + errList := utils.StopNodes(t, nodes) + require.Len(t, errList, 0) + }() + + time.Sleep(time.Second * 10) + + // wait until there's no more pending extrinsics + for i := 0; i < maxRetries; i++ { + exts := getPendingExtrinsics(t, nodes[idx]) + if len(exts) == 0 { + break + } + + time.Sleep(time.Second) + } + + header := utils.GetChainHead(t, nodes[idx]) + + // search from child -> parent blocks for extrinsic + var ( + resExts []gosstypes.Extrinsic + extInBlock *big.Int + ) + + for i := 0; i < maxRetries; i++ { + block := utils.GetBlock(t, nodes[idx], header.ParentHash) + if block == nil { + // couldn't get block, increment retry counter + continue + } + + header = block.Header + logger.Debug("got block from node", "header", header, "body", block.Body, "node", nodes[idx].Key) + + if block.Body != nil { + resExts, err = block.Body.AsExtrinsics() + require.NoError(t, err, block.Body) + + logger.Debug("extrinsics", "exts", resExts) + if len(resExts) >= 2 { + extInBlock = block.Header.Number + break + } + } + + if header.Hash() == prevHeader.Hash() { + t.Fatal("could not find extrinsic in any blocks") + } + } + + var included bool + for _, ext := range resExts { + dec, err := scale.Decode(ext, []byte{}) //nolint + require.NoError(t, err) + decExt := dec.([]byte) + logger.Debug("comparing", "expected", extEnc, "in block", common.BytesToHex(decExt)) + if strings.Compare(extEnc, common.BytesToHex(decExt)) == 0 { + included = true + } + } + + require.True(t, included) + + // wait for nodes to sync + // TODO: seems like nodes don't sync properly :/ + time.Sleep(time.Second * 45) + hashes, err := compareBlocksByNumberWithRetry(t, nodes, extInBlock.String()) + require.NoError(t, err, hashes) +} diff --git a/tests/utils/common.go b/tests/utils/common.go index 87eb197e34..3410c0f46d 100644 --- a/tests/utils/common.go +++ b/tests/utils/common.go @@ -31,6 +31,8 @@ var ( HOSTNAME = os.Getenv("HOSTNAME") PORT = os.Getenv("PORT") + LOGLEVEL = os.Getenv("LOG") + NETWORK_SIZE = os.Getenv("NETWORK_SIZE") ContentTypeJSON = "application/json" diff --git a/tests/utils/gossamer_utils.go b/tests/utils/gossamer_utils.go index 2dc59706fd..c079ce4d21 100644 --- a/tests/utils/gossamer_utils.go +++ b/tests/utils/gossamer_utils.go @@ -42,7 +42,7 @@ var maxRetries = 24 // SetLogLevel sets the logging level for this package func SetLogLevel(lvl log.Lvl) { h := log.StreamHandler(os.Stdout, log.TerminalFormat()) - logger.SetHandler(log.LvlFilterHandler(log.LvlInfo, h)) + logger.SetHandler(log.LvlFilterHandler(lvl, h)) } var ( diff --git a/tests/utils/request_utils.go b/tests/utils/request_utils.go index 5ef15311a2..85136dbc9f 100644 --- a/tests/utils/request_utils.go +++ b/tests/utils/request_utils.go @@ -18,6 +18,7 @@ package utils import ( "bytes" + "context" "encoding/json" "errors" "fmt" @@ -45,10 +46,13 @@ func PostRPC(method, host, params string) ([]byte, error) { if err != nil { return nil, err } - r.Header.Set("Content-Type", ContentTypeJSON) r.Header.Set("Accept", ContentTypeJSON) + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + r = r.WithContext(ctx) + resp, err := httpClient.Do(r) if err != nil { return nil, err diff --git a/tests/utils/rpc_methods.go b/tests/utils/rpc_methods.go index 09d9eafe48..e20b255d4f 100644 --- a/tests/utils/rpc_methods.go +++ b/tests/utils/rpc_methods.go @@ -26,7 +26,8 @@ var ( ChainGetBlockHash = "chain_getBlockHash" // AUTHOR METHODS - AuthorSubmitExtrinsic = "author_submitExtrinsic" + AuthorSubmitExtrinsic = "author_submitExtrinsic" + AuthorPendingExtrinsics = "author_pendingExtrinsics" // STATE METHODS StateGetStorage = "state_getStorage"