From ad9ede455e710d048381ea20c804ed579ac15710 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 24 Mar 2021 15:36:10 +0300 Subject: [PATCH] rpc: add NNS-connected transactions to the test chain It is needed for RPC client integration tests. --- pkg/core/helper_test.go | 16 ++++++ pkg/rpc/server/server_test.go | 66 +++++++++++++++++++------ pkg/rpc/server/testdata/testblocks.acc | Bin 11104 -> 14439 bytes 3 files changed, 68 insertions(+), 14 deletions(-) diff --git a/pkg/core/helper_test.go b/pkg/core/helper_test.go index 447975817..5d8d8c47d 100644 --- a/pkg/core/helper_test.go +++ b/pkg/core/helper_test.go @@ -23,6 +23,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/chaindump" "github.com/nspcc-dev/neo-go/pkg/core/fee" "github.com/nspcc-dev/neo-go/pkg/core/native" + "github.com/nspcc-dev/neo-go/pkg/core/native/nnsrecords" "github.com/nspcc-dev/neo-go/pkg/core/native/noderoles" "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/storage" @@ -484,6 +485,21 @@ func initBasicChain(t *testing.T, bc *Blockchain) { require.NoError(t, bc.AddBlock(b)) checkTxHalt(t, bc, txDeploy3.Hash()) + // register `neo.com` with A record type and priv0 owner via NNS + transferFundsToCommittee(t, bc) // block #11 + res, err := invokeContractMethodGeneric(bc, defaultNameServiceSysfee, + bc.contracts.NameService.Hash, "addRoot", true, "com") // block #12 + require.NoError(t, err) + checkResult(t, res, stackitem.Null{}) + res, err = invokeContractMethodGeneric(bc, native.DefaultDomainPrice+defaultNameServiceSysfee, + bc.contracts.NameService.Hash, "register", acc0, "neo.com", priv0ScriptHash) // block #13 + require.NoError(t, err) + checkResult(t, res, stackitem.NewBool(true)) + res, err = invokeContractMethodGeneric(bc, defaultNameServiceSysfee, bc.contracts.NameService.Hash, + "setRecord", acc0, "neo.com", int64(nnsrecords.A), "1.2.3.4") // block #14 + require.NoError(t, err) + checkResult(t, res, stackitem.Null{}) + // Compile contract to test `invokescript` RPC call _, _ = newDeployTx(t, bc, priv0ScriptHash, prefix+"invokescript_contract.go", "ContractForInvokescriptTest", nil) } diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index c29c4cdd3..3e3ecd01b 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -60,7 +60,7 @@ type rpcTestCase struct { } const testContractHash = "c6ca2347bb84b99807221365c900ec069a265e7c" -const deploymentTxHash = "fdd4f9252cde778010d14e9710efeeb80796fd38d778e9943c1d5bb2dc656c99" +const deploymentTxHash = "26692315f71f4790263160c0f570828be77c6927493ae6657a6e5a6a09229eb9" const genesisBlockHash = "5b60644c6c6f58faca72c70689d7ed1f40c2e795772bd0de5a88e983ad55080c" const verifyContractHash = "5bb4bac40e961e334ba7bd36d2496010f67e246e" @@ -654,7 +654,7 @@ var rpcTestCases = map[string][]rpcTestCase{ require.True(t, ok) expected := result.UnclaimedGas{ Address: testchain.MultisigScriptHash(), - Unclaimed: *big.NewInt(5000), + Unclaimed: *big.NewInt(7000), } assert.Equal(t, expected, *actual) }, @@ -1415,7 +1415,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] require.NoErrorf(t, err, "could not parse response: %s", txOut) assert.Equal(t, *block.Transactions[0], actual.Transaction) - assert.Equal(t, 11, actual.Confirmations) + assert.Equal(t, 15, actual.Confirmations) assert.Equal(t, TXHash, actual.Transaction.Hash()) }) @@ -1533,12 +1533,12 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] require.NoError(t, json.Unmarshal(res, actual)) checkNep17TransfersAux(t, e, actual, sent, rcvd) } - t.Run("time frame only", func(t *testing.T) { testNEP17T(t, 4, 5, 0, 0, []int{6, 7, 8, 9}, []int{1, 2}) }) + t.Run("time frame only", func(t *testing.T) { testNEP17T(t, 4, 5, 0, 0, []int{8, 9, 10, 11}, []int{2, 3}) }) t.Run("no res", func(t *testing.T) { testNEP17T(t, 100, 100, 0, 0, []int{}, []int{}) }) - t.Run("limit", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 0, []int{3, 4}, []int{0}) }) - t.Run("limit 2", func(t *testing.T) { testNEP17T(t, 4, 5, 2, 0, []int{6}, []int{1}) }) - t.Run("limit with page", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 1, []int{5, 6}, []int{1}) }) - t.Run("limit with page 2", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 2, []int{7, 8}, []int{2}) }) + t.Run("limit", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 0, []int{5, 6}, []int{1}) }) + t.Run("limit 2", func(t *testing.T) { testNEP17T(t, 4, 5, 2, 0, []int{8}, []int{2}) }) + t.Run("limit with page", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 1, []int{7, 8}, []int{2}) }) + t.Run("limit with page 2", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 2, []int{9, 10}, []int{3}) }) }) } @@ -1639,8 +1639,8 @@ func checkNep17Balances(t *testing.T, e *executor, acc interface{}) { }, { Asset: e.chain.UtilityTokenHash(), - Amount: "68992456820", - LastUpdated: 10, + Amount: "67960042780", + LastUpdated: 14, }}, Address: testchain.PrivateKeyByID(0).GetScriptHash().StringLE(), } @@ -1649,7 +1649,7 @@ func checkNep17Balances(t *testing.T, e *executor, acc interface{}) { } func checkNep17Transfers(t *testing.T, e *executor, acc interface{}) { - checkNep17TransfersAux(t, e, acc, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, []int{0, 1, 2, 3, 4, 5, 6}) + checkNep17TransfersAux(t, e, acc, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}, []int{0, 1, 2, 3, 4, 5, 6, 7}) } func checkNep17TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rcvd []int) { @@ -1658,6 +1658,19 @@ func checkNep17TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rc rublesHash, err := util.Uint160DecodeStringLE(testContractHash) require.NoError(t, err) + blockSetRecord, err := e.chain.GetBlock(e.chain.GetHeaderHash(14)) // add type A record to `neo.com` domain via NNS + require.NoError(t, err) + require.Equal(t, 1, len(blockSetRecord.Transactions)) + txSetRecord := blockSetRecord.Transactions[0] + + blockRegisterDomain, err := e.chain.GetBlock(e.chain.GetHeaderHash(13)) // register `neo.com` domain via NNS + require.NoError(t, err) + require.Equal(t, 1, len(blockRegisterDomain.Transactions)) + txRegisterDomain := blockRegisterDomain.Transactions[0] + + blockGASBounty2, err := e.chain.GetBlock(e.chain.GetHeaderHash(12)) // size of committee = 6 + require.NoError(t, err) + blockDeploy3, err := e.chain.GetBlock(e.chain.GetHeaderHash(10)) // deploy verification_with_args_contract.go require.NoError(t, err) require.Equal(t, 1, len(blockDeploy3.Transactions)) @@ -1677,7 +1690,7 @@ func checkNep17TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rc require.NoError(t, err) require.Equal(t, 1, len(blockSendRubles.Transactions)) txSendRubles := blockSendRubles.Transactions[0] - blockGASBounty := blockSendRubles // index 6 = size of committee + blockGASBounty1 := blockSendRubles // index 6 = size of committee blockReceiveRubles, err := e.chain.GetBlock(e.chain.GetHeaderHash(5)) require.NoError(t, err) @@ -1716,6 +1729,22 @@ func checkNep17TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rc // duplicate the Server method. expected := result.NEP17Transfers{ Sent: []result.NEP17Transfer{ + { + Timestamp: blockSetRecord.Timestamp, + Asset: e.chain.UtilityTokenHash(), + Address: "", // burn + Amount: big.NewInt(txSetRecord.SystemFee + txSetRecord.NetworkFee).String(), + Index: 14, + TxHash: blockSetRecord.Hash(), + }, + { + Timestamp: blockRegisterDomain.Timestamp, + Asset: e.chain.UtilityTokenHash(), + Address: "", // burn + Amount: big.NewInt(txRegisterDomain.SystemFee + txRegisterDomain.NetworkFee).String(), + Index: 13, + TxHash: blockRegisterDomain.Hash(), + }, { Timestamp: blockDeploy3.Timestamp, Asset: e.chain.UtilityTokenHash(), @@ -1818,13 +1847,22 @@ func checkNep17TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rc }, Received: []result.NEP17Transfer{ { - Timestamp: blockGASBounty.Timestamp, + Timestamp: blockGASBounty2.Timestamp, + Asset: e.chain.UtilityTokenHash(), + Address: "", + Amount: "50000000", + Index: 12, + NotifyIndex: 0, + TxHash: blockGASBounty2.Hash(), + }, + { + Timestamp: blockGASBounty1.Timestamp, Asset: e.chain.UtilityTokenHash(), Address: "", Amount: "50000000", Index: 6, NotifyIndex: 0, - TxHash: blockGASBounty.Hash(), + TxHash: blockGASBounty1.Hash(), }, { Timestamp: blockReceiveRubles.Timestamp, diff --git a/pkg/rpc/server/testdata/testblocks.acc b/pkg/rpc/server/testdata/testblocks.acc index fd8c3bc97bf90122dbf379112f7bfc257d0b9b19..a5e2a6d209f22720a248f60a56659540f5baf72a 100644 GIT binary patch delta 5356 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 delta 3250 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