package server import ( "bytes" "encoding/hex" "encoding/json" "fmt" "io/ioutil" "net/http" "net/http/httptest" "reflect" "strings" "testing" "time" "github.com/gorilla/websocket" "github.com/nspcc-dev/neo-go/pkg/core" "github.com/nspcc-dev/neo-go/pkg/core/block" "github.com/nspcc-dev/neo-go/pkg/core/blockchainer" "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/internal/testchain" "github.com/nspcc-dev/neo-go/pkg/internal/testserdes" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/rpc/response" "github.com/nspcc-dev/neo-go/pkg/rpc/response/result" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) type executor struct { chain *core.Blockchain httpSrv *httptest.Server } const ( defaultJSONRPC = "2.0" defaultID = 1 ) type rpcTestCase struct { name string params string fail bool result func(e *executor) interface{} check func(t *testing.T, e *executor, result interface{}) } const testContractHash = "e65ff7b3a02d207b584a5c27057d4e9862ef01da" const deploymentTxHash = "b0428600383ec7f7b06734978a24dbe81edc87b781f58c0614f122c735d8cf6a" var rpcTestCases = map[string][]rpcTestCase{ "getapplicationlog": { { name: "positive", params: `["` + deploymentTxHash + `"]`, result: func(e *executor) interface{} { return &result.ApplicationLog{} }, check: func(t *testing.T, e *executor, acc interface{}) { res, ok := acc.(*result.ApplicationLog) require.True(t, ok) expectedTxHash, err := util.Uint256DecodeStringLE(deploymentTxHash) require.NoError(t, err) assert.Equal(t, expectedTxHash, res.TxHash) assert.Equal(t, 1, len(res.Executions)) assert.Equal(t, "Application", res.Executions[0].Trigger) assert.Equal(t, "HALT", res.Executions[0].VMState) }, }, { name: "no params", params: `[]`, fail: true, }, { name: "invalid address", params: `["notahash"]`, fail: true, }, { name: "invalid tx hash", params: `["d24cc1d52b5c0216cbf3835bb5bac8ccf32639fa1ab6627ec4e2b9f33f7ec02f"]`, fail: true, }, }, "getcontractstate": { { name: "positive", params: fmt.Sprintf(`["%s"]`, testContractHash), result: func(e *executor) interface{} { return &state.Contract{} }, check: func(t *testing.T, e *executor, cs interface{}) { res, ok := cs.(*state.Contract) require.True(t, ok) assert.Equal(t, testContractHash, res.ScriptHash().StringLE()) }, }, { name: "negative", params: `["6d1eeca891ee93de2b7a77eb91c26f3b3c04d6c3"]`, fail: true, }, { name: "no params", params: `[]`, fail: true, }, { name: "invalid hash", params: `["notahex"]`, fail: true, }, }, "getnep5balances": { { name: "no params", params: `[]`, fail: true, }, { name: "invalid address", params: `["notahex"]`, fail: true, }, { name: "positive", params: `["` + testchain.PrivateKeyByID(0).GetScriptHash().StringLE() + `"]`, result: func(e *executor) interface{} { return &result.NEP5Balances{} }, check: func(t *testing.T, e *executor, acc interface{}) { res, ok := acc.(*result.NEP5Balances) require.True(t, ok) rubles, err := util.Uint160DecodeStringLE(testContractHash) require.NoError(t, err) expected := result.NEP5Balances{ Balances: []result.NEP5Balance{ { Asset: rubles, Amount: "8.77", LastUpdated: 6, }, { Asset: e.chain.GoverningTokenHash(), Amount: "99998000", LastUpdated: 4, }, { Asset: e.chain.UtilityTokenHash(), Amount: "923.96937740", LastUpdated: 6, }}, Address: testchain.PrivateKeyByID(0).GetScriptHash().StringLE(), } require.Equal(t, testchain.PrivateKeyByID(0).Address(), res.Address) require.ElementsMatch(t, expected.Balances, res.Balances) }, }, }, "getnep5transfers": { { name: "no params", params: `[]`, fail: true, }, { name: "invalid address", params: `["notahex"]`, fail: true, }, { name: "positive", params: `["` + testchain.PrivateKeyByID(0).Address() + `"]`, result: func(e *executor) interface{} { return &result.NEP5Transfers{} }, check: func(t *testing.T, e *executor, acc interface{}) { res, ok := acc.(*result.NEP5Transfers) require.True(t, ok) rublesHash, err := util.Uint160DecodeStringLE(testContractHash) require.NoError(t, err) blockSendRubles, err := e.chain.GetBlock(e.chain.GetHeaderHash(6)) require.NoError(t, err) require.Equal(t, 1, len(blockSendRubles.Transactions)) txSendRublesHash := blockSendRubles.Transactions[0].Hash() blockReceiveRubles, err := e.chain.GetBlock(e.chain.GetHeaderHash(5)) require.NoError(t, err) require.Equal(t, 2, len(blockReceiveRubles.Transactions)) txReceiveRublesHash := blockReceiveRubles.Transactions[1].Hash() blockReceiveGAS, err := e.chain.GetBlock(e.chain.GetHeaderHash(1)) require.NoError(t, err) require.Equal(t, 2, len(blockReceiveGAS.Transactions)) txReceiveNEOHash := blockReceiveGAS.Transactions[0].Hash() txReceiveGASHash := blockReceiveGAS.Transactions[1].Hash() blockSendNEO, err := e.chain.GetBlock(e.chain.GetHeaderHash(4)) require.NoError(t, err) require.Equal(t, 1, len(blockSendNEO.Transactions)) txSendNEOHash := blockSendNEO.Transactions[0].Hash() expected := result.NEP5Transfers{ Sent: []result.NEP5Transfer{ { Timestamp: blockSendRubles.Timestamp, Asset: rublesHash, Address: testchain.PrivateKeyByID(1).Address(), Amount: "1.23", Index: 6, NotifyIndex: 0, TxHash: txSendRublesHash, }, { Timestamp: blockSendNEO.Timestamp, Asset: e.chain.GoverningTokenHash(), Address: testchain.PrivateKeyByID(1).Address(), Amount: "1000", Index: 4, NotifyIndex: 0, TxHash: txSendNEOHash, }, }, Received: []result.NEP5Transfer{ { Timestamp: blockReceiveRubles.Timestamp, Asset: rublesHash, Address: address.Uint160ToString(rublesHash), Amount: "10", Index: 5, NotifyIndex: 0, TxHash: txReceiveRublesHash, }, { Timestamp: blockSendNEO.Timestamp, Asset: e.chain.UtilityTokenHash(), Address: "", // Minted GAS. Amount: "23.99976000", Index: 4, NotifyIndex: 0, TxHash: txSendNEOHash, }, { Timestamp: blockReceiveGAS.Timestamp, Asset: e.chain.UtilityTokenHash(), Address: testchain.MultisigAddress(), Amount: "1000", Index: 1, NotifyIndex: 0, TxHash: txReceiveGASHash, }, { Timestamp: blockReceiveGAS.Timestamp, Asset: e.chain.GoverningTokenHash(), Address: testchain.MultisigAddress(), Amount: "99999000", Index: 1, NotifyIndex: 0, TxHash: txReceiveNEOHash, }, }, Address: testchain.PrivateKeyByID(0).Address(), } // take burned gas into account u := testchain.PrivateKeyByID(0).GetScriptHash() for i := 0; i <= int(e.chain.BlockHeight()); i++ { h := e.chain.GetHeaderHash(i) b, err := e.chain.GetBlock(h) require.NoError(t, err) for j := range b.Transactions { if u.Equals(b.Transactions[j].Sender) { amount := b.Transactions[j].SystemFee + b.Transactions[j].NetworkFee expected.Sent = append(expected.Sent, result.NEP5Transfer{ Timestamp: b.Timestamp, Asset: e.chain.UtilityTokenHash(), Address: "", // burn has empty receiver Amount: amountToString(int64(amount), 8), Index: b.Index, TxHash: b.Hash(), }) } } } require.Equal(t, expected.Address, res.Address) require.ElementsMatch(t, expected.Sent, res.Sent) require.ElementsMatch(t, expected.Received, res.Received) }, }, }, "getstorage": { { name: "positive", params: fmt.Sprintf(`["%s", "746573746b6579"]`, testContractHash), result: func(e *executor) interface{} { v := hex.EncodeToString([]byte("testvalue")) return &v }, }, { name: "missing key", params: fmt.Sprintf(`["%s", "7465"]`, testContractHash), result: func(e *executor) interface{} { v := "" return &v }, }, { name: "no params", params: `[]`, fail: true, }, { name: "no second parameter", params: fmt.Sprintf(`["%s"]`, testContractHash), fail: true, }, { name: "invalid hash", params: `["notahex"]`, fail: true, }, { name: "invalid key", params: fmt.Sprintf(`["%s", "notahex"]`, testContractHash), fail: true, }, }, "getbestblockhash": { { params: "[]", result: func(e *executor) interface{} { v := "0x" + e.chain.CurrentBlockHash().StringLE() return &v }, }, { params: "1", fail: true, }, }, "getblock": { { name: "positive", params: "[3, 1]", result: func(_ *executor) interface{} { return &result.Block{} }, check: func(t *testing.T, e *executor, blockRes interface{}) { res, ok := blockRes.(*result.Block) require.True(t, ok) block, err := e.chain.GetBlock(e.chain.GetHeaderHash(3)) require.NoErrorf(t, err, "could not get block") assert.Equal(t, block.Hash(), res.Hash()) for i, tx := range res.Transactions { actualTx := block.Transactions[i] require.True(t, ok) require.Equal(t, actualTx.Nonce, tx.Nonce) require.Equal(t, block.Transactions[i].Hash(), tx.Hash()) } }, }, { name: "no params", params: `[]`, fail: true, }, { name: "bad params", params: `[[]]`, fail: true, }, { name: "invalid height", params: `[-1]`, fail: true, }, { name: "invalid hash", params: `["notahex"]`, fail: true, }, { name: "missing hash", params: `["` + util.Uint256{}.String() + `"]`, fail: true, }, }, "getblockcount": { { params: "[]", result: func(e *executor) interface{} { v := int(e.chain.BlockHeight() + 1) return &v }, }, }, "getblockhash": { { params: "[1]", result: func(e *executor) interface{} { // We don't have `t` here for proper handling, but // error here would lead to panic down below. block, _ := e.chain.GetBlock(e.chain.GetHeaderHash(1)) expectedHash := "0x" + block.Hash().StringLE() return &expectedHash }, }, { name: "string height", params: `["first"]`, fail: true, }, { name: "invalid number height", params: `[-2]`, fail: true, }, }, "getblockheader": { { name: "invalid verbose type", params: `["9673799c5b5a294427401cb07d6cc615ada3a0d5c5bf7ed6f0f54f24abb2e2ac", true]`, fail: true, }, { name: "invalid block hash", params: `["notahash"]`, fail: true, }, { name: "unknown block", params: `["a6e526375a780335112299f2262501e5e9574c3ba61b16bbc1e282b344f6c141"]`, fail: true, }, { name: "no params", params: `[]`, fail: true, }, }, "getblocksysfee": { { name: "positive", params: "[1]", result: func(e *executor) interface{} { block, _ := e.chain.GetBlock(e.chain.GetHeaderHash(1)) var expectedBlockSysFee util.Fixed8 for _, tx := range block.Transactions { expectedBlockSysFee += tx.SystemFee } return &expectedBlockSysFee }, }, { name: "no params", params: `[]`, fail: true, }, { name: "string height", params: `["first"]`, fail: true, }, { name: "invalid number height", params: `[-2]`, fail: true, }, }, "getconnectioncount": { { params: "[]", result: func(*executor) interface{} { v := 0 return &v }, }, }, "getpeers": { { params: "[]", result: func(*executor) interface{} { return &result.GetPeers{ Unconnected: []result.Peer{}, Connected: []result.Peer{}, Bad: []result.Peer{}, } }, }, }, "getrawtransaction": { { name: "no params", params: `[]`, fail: true, }, { name: "invalid hash", params: `["notahex"]`, fail: true, }, { name: "missing hash", params: `["` + util.Uint256{}.String() + `"]`, fail: true, }, }, "gettransactionheight": { { name: "positive", params: `["` + deploymentTxHash + `"]`, result: func(e *executor) interface{} { h := 0 return &h }, check: func(t *testing.T, e *executor, resp interface{}) { h, ok := resp.(*int) require.True(t, ok) assert.Equal(t, 2, *h) }, }, { name: "no params", params: `[]`, fail: true, }, { name: "invalid hash", params: `["notahex"]`, fail: true, }, { name: "missing hash", params: `["` + util.Uint256{}.String() + `"]`, fail: true, }, }, "getunclaimedgas": { { name: "no params", params: "[]", fail: true, }, { name: "invalid address", params: `["invalid"]`, fail: true, }, { name: "positive", params: `["` + testchain.MultisigAddress() + `"]`, result: func(*executor) interface{} { var s string return &s }, check: func(t *testing.T, e *executor, resp interface{}) { s, ok := resp.(*string) require.True(t, ok) // Incorrect, to be fixed later. assert.Equal(t, "48000", *s) }, }, }, "getvalidators": { { params: "[]", result: func(*executor) interface{} { return &[]result.Validator{} }, check: func(t *testing.T, e *executor, validators interface{}) { var expected []result.Validator sBValidators, err := e.chain.GetStandByValidators() require.NoError(t, err) for _, sbValidator := range sBValidators { expected = append(expected, result.Validator{ PublicKey: *sbValidator, Votes: 0, Active: true, }) } actual, ok := validators.(*[]result.Validator) require.True(t, ok) assert.ElementsMatch(t, expected, *actual) }, }, }, "getversion": { { params: "[]", result: func(*executor) interface{} { return &result.Version{} }, check: func(t *testing.T, e *executor, ver interface{}) { resp, ok := ver.(*result.Version) require.True(t, ok) require.Equal(t, "/NEO-GO:/", resp.UserAgent) }, }, }, "invokefunction": { { name: "positive", params: `["50befd26fdf6e4d957c11e078b24ebce6291456f", "test", []]`, result: func(e *executor) interface{} { return &result.Invoke{} }, check: func(t *testing.T, e *executor, inv interface{}) { res, ok := inv.(*result.Invoke) require.True(t, ok) assert.NotEqual(t, "", res.Script) assert.NotEqual(t, "", res.State) assert.NotEqual(t, 0, res.GasConsumed) }, }, { name: "no params", params: `[]`, fail: true, }, { name: "not a string", params: `[42, "test", []]`, fail: true, }, { name: "not a scripthash", params: `["qwerty", "test", []]`, fail: true, }, { name: "bad params", params: `["50befd26fdf6e4d957c11e078b24ebce6291456f", "test", [{"type": "Integer", "value": "qwerty"}]]`, fail: true, }, }, "invokescript": { { name: "positive", params: `["51c56b0d48656c6c6f2c20776f726c6421680f4e656f2e52756e74696d652e4c6f67616c7566"]`, result: func(e *executor) interface{} { return &result.Invoke{} }, check: func(t *testing.T, e *executor, inv interface{}) { res, ok := inv.(*result.Invoke) require.True(t, ok) assert.NotEqual(t, "", res.Script) assert.NotEqual(t, "", res.State) assert.NotEqual(t, 0, res.GasConsumed) }, }, { name: "positive, good witness", // script is hex-encoded `test_verify.avm` representation, hashes are hex-encoded LE bytes of hashes used in the contract with `0x` prefix params: `["5707000c14010c030e05060c0d020e0f0d030e070900000000db307068115541f827ec8c21aa270700000011400c140d0f03020900020103070304050201000e060c09db307169115541f827ec8c21aa270700000012401340",["0x0000000009070e030d0f0e020d0c06050e030c01","0x090c060e00010205040307030102000902030f0d"]]`, result: func(e *executor) interface{} { return &result.Invoke{} }, check: func(t *testing.T, e *executor, inv interface{}) { res, ok := inv.(*result.Invoke) require.True(t, ok) assert.Equal(t, "HALT", res.State) require.Equal(t, 1, len(res.Stack)) require.Equal(t, int64(3), res.Stack[0].Value) }, }, { name: "positive, bad witness of second hash", params: `["5707000c14010c030e05060c0d020e0f0d030e070900000000db307068115541f827ec8c21aa270700000011400c140d0f03020900020103070304050201000e060c09db307169115541f827ec8c21aa270700000012401340",["0x0000000009070e030d0f0e020d0c06050e030c01"]]`, result: func(e *executor) interface{} { return &result.Invoke{} }, check: func(t *testing.T, e *executor, inv interface{}) { res, ok := inv.(*result.Invoke) require.True(t, ok) assert.Equal(t, "HALT", res.State) require.Equal(t, 1, len(res.Stack)) require.Equal(t, int64(2), res.Stack[0].Value) }, }, { name: "positive, no good hashes", params: `["5707000c14010c030e05060c0d020e0f0d030e070900000000db307068115541f827ec8c21aa270700000011400c140d0f03020900020103070304050201000e060c09db307169115541f827ec8c21aa270700000012401340"]`, result: func(e *executor) interface{} { return &result.Invoke{} }, check: func(t *testing.T, e *executor, inv interface{}) { res, ok := inv.(*result.Invoke) require.True(t, ok) assert.Equal(t, "HALT", res.State) require.Equal(t, 1, len(res.Stack)) require.Equal(t, int64(1), res.Stack[0].Value) }, }, { name: "positive, bad hashes witness", params: `["5707000c14010c030e05060c0d020e0f0d030e070900000000db307068115541f827ec8c21aa270700000011400c140d0f03020900020103070304050201000e060c09db307169115541f827ec8c21aa270700000012401340",["0x0000000009070e030d0f0e020d0c06050e030c02"]]`, result: func(e *executor) interface{} { return &result.Invoke{} }, check: func(t *testing.T, e *executor, inv interface{}) { res, ok := inv.(*result.Invoke) require.True(t, ok) assert.Equal(t, "HALT", res.State) assert.Equal(t, 1, len(res.Stack)) assert.Equal(t, int64(1), res.Stack[0].Value) }, }, { name: "no params", params: `[]`, fail: true, }, { name: "not a string", params: `[42]`, fail: true, }, { name: "bas string", params: `["qwerty"]`, fail: true, }, }, "sendrawtransaction": { { name: "positive", params: `["000a000000316e851039019d39dfc2c37d6c3fee19fd5809870000000000000000f2ad050000000000b00400000001316e851039019d39dfc2c37d6c3fee19fd580987015d0300e87648170000000c1420728274afafc36f43a071d328cfa3e629d9cbb00c14316e851039019d39dfc2c37d6c3fee19fd58098713c00c087472616e736665720c14897720d8cd76f4f00abfa37c0edd889c208fde9b41627d5b523801420c409803db41e66a94e0bd6fdd3d7a7b1e106c1e2281c9782a231f32df036bb80c7f19155cdb9a52a45cf8d93ec9c1e8df69d6ee35625f352d1710c7cc6eee26003c290c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4"]`, result: func(e *executor) interface{} { v := true return &v }, }, { name: "negative", params: `["000a000000316e851039019d39dfc2c37d6c3fee19fd5809870000000000000000f2ad050000000000b00400000001316e851039019d39dfc2c37d6c3fee19fd580987015d0300e87648170000000c1420728274afafc36f43a071d328cfa3e629d9cbb00c14316e851039019d39dfc2c37d6c3fee19fd58098713c00c087472616e736665720c14897720d8cd76f4f00abfa37c0edd889c208fde9b41627d5b523801420c40df953141271169421cebab5d27a0163e294d7c7f2d0525b4498745344814fd3d6c5c591c9b1723d05d42856f409adb084cf67acc921cfafc629133a5eb5e7a7e290c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906aff"]`, fail: true, }, { name: "no params", params: `[]`, fail: true, }, { name: "invalid string", params: `["notahex"]`, fail: true, }, { name: "invalid tx", params: `["0274d792072617720636f6e747261637"]`, fail: true, }, }, "submitblock": { { name: "invalid hex", params: `["000000005gb86f62eafe8e9246bc0d1648e4e5c8389dee9fb7fe03fcc6772ec8c5e4ec2aedb908054ac1409be5f77d5369c6e03490b2f6676d68d0b3370f8159e0fdadf99bc05f5e030000005704000000000000be48d3a3f5d10013ab9ffee489706078714f1ea201fd0401406f299c82b513f59f5bd120317974852c9694c6e10db1ef2f1bb848b1a33e47a08f8dc03ee784166b2060a94cd4e7af88899b39787938f7f2763ea4d2182776ed40f3bafd85214fef38a4836ca97793001ea411f553c51e88781f7b916c59c145bff28314b6e7ea246789422a996fc4937e290a1b40f6b97c5222540f65b0d47aca40d2b3d19203d456428bfdb529e846285052105957385b65388b9a617f6e2d56a64ec41aa73439eafccb52987bb1975c9b67518b053d9e61b445e4a3377dbc206640bd688489bd62adf6bed9d61a73905b9591eb87053c6f0f4dd70f3bee7295541b490caef044b55b6f9f01dc4a05a756a3f2edd06f5adcbe4e984c1e552f9023f08b532102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd622102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc22103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee69954ae0100000000000000000000"]`, fail: true, }, { name: "invalid block bytes", params: `["0000000027"]`, fail: true, }, { name: "no params", params: `[]`, fail: true, }, }, "validateaddress": { { name: "positive", params: `["Nbb1qkwcwNSBs9pAnrVVrnFbWnbWBk91U2"]`, result: func(*executor) interface{} { return &result.ValidateAddress{} }, check: func(t *testing.T, e *executor, va interface{}) { res, ok := va.(*result.ValidateAddress) require.True(t, ok) assert.Equal(t, "Nbb1qkwcwNSBs9pAnrVVrnFbWnbWBk91U2", res.Address) assert.True(t, res.IsValid) }, }, { name: "negative", params: "[1]", result: func(*executor) interface{} { return &result.ValidateAddress{ Address: float64(1), IsValid: false, } }, }, }, } func TestRPC(t *testing.T) { t.Run("http", func(t *testing.T) { testRPCProtocol(t, doRPCCallOverHTTP) }) t.Run("websocket", func(t *testing.T) { testRPCProtocol(t, doRPCCallOverWS) }) } // testRPCProtocol runs a full set of tests using given callback to make actual // calls. Some tests change the chain state, thus we reinitialize the chain from // scratch here. func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []byte) { chain, rpcSrv, httpSrv := initServerWithInMemoryChain(t) defer chain.Close() defer rpcSrv.Shutdown() e := &executor{chain: chain, httpSrv: httpSrv} for method, cases := range rpcTestCases { t.Run(method, func(t *testing.T) { rpc := `{"jsonrpc": "2.0", "id": 1, "method": "%s", "params": %s}` for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { body := doRPCCall(fmt.Sprintf(rpc, method, tc.params), httpSrv.URL, t) result := checkErrGetResult(t, body, tc.fail) if tc.fail { return } expected, res := tc.getResultPair(e) err := json.Unmarshal(result, res) require.NoErrorf(t, err, "could not parse response: %s", result) if tc.check == nil { assert.Equal(t, expected, res) } else { tc.check(t, e, res) } }) } }) } t.Run("submit", func(t *testing.T) { rpc := `{"jsonrpc": "2.0", "id": 1, "method": "submitblock", "params": ["%s"]}` t.Run("invalid signature", func(t *testing.T) { s := newBlock(t, chain, 1, 0) s.Script.VerificationScript[8] ^= 0xff body := doRPCCall(fmt.Sprintf(rpc, encodeBlock(t, s)), httpSrv.URL, t) checkErrGetResult(t, body, true) }) priv0 := testchain.PrivateKeyByID(0) acc0, err := wallet.NewAccountFromWIF(priv0.WIF()) require.NoError(t, err) addNetworkFee := func(tx *transaction.Transaction) { size := io.GetVarSize(tx) netFee, sizeDelta := core.CalculateNetworkFee(acc0.Contract.Script) tx.NetworkFee += netFee size += sizeDelta tx.NetworkFee = tx.NetworkFee.Add(util.Fixed8(int64(size) * int64(chain.FeePerByte()))) } newTx := func() *transaction.Transaction { height := chain.BlockHeight() tx := transaction.New(testchain.Network(), []byte{byte(opcode.PUSH1)}, 0) tx.Nonce = height + 1 tx.ValidUntilBlock = height + 10 tx.Sender = acc0.PrivateKey().GetScriptHash() addNetworkFee(tx) require.NoError(t, acc0.SignTx(tx)) return tx } t.Run("invalid height", func(t *testing.T) { b := newBlock(t, chain, 2, 0, newTx()) body := doRPCCall(fmt.Sprintf(rpc, encodeBlock(t, b)), httpSrv.URL, t) checkErrGetResult(t, body, true) }) t.Run("positive", func(t *testing.T) { b := newBlock(t, chain, 1, 0, newTx()) body := doRPCCall(fmt.Sprintf(rpc, encodeBlock(t, b)), httpSrv.URL, t) data := checkErrGetResult(t, body, false) var res bool require.NoError(t, json.Unmarshal(data, &res)) require.True(t, res) }) }) t.Run("getrawtransaction", func(t *testing.T) { block, _ := chain.GetBlock(chain.GetHeaderHash(0)) tx := block.Transactions[0] rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "getrawtransaction", "params": ["%s"]}"`, tx.Hash().StringLE()) body := doRPCCall(rpc, httpSrv.URL, t) result := checkErrGetResult(t, body, false) var res string err := json.Unmarshal(result, &res) require.NoErrorf(t, err, "could not parse response: %s", result) txBin, err := testserdes.EncodeBinary(tx) require.NoError(t, err) expected := hex.EncodeToString(txBin) assert.Equal(t, expected, res) }) t.Run("getrawtransaction 2 arguments", func(t *testing.T) { block, _ := chain.GetBlock(chain.GetHeaderHash(0)) tx := block.Transactions[0] rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "getrawtransaction", "params": ["%s", 0]}"`, tx.Hash().StringLE()) body := doRPCCall(rpc, httpSrv.URL, t) result := checkErrGetResult(t, body, false) var res string err := json.Unmarshal(result, &res) require.NoErrorf(t, err, "could not parse response: %s", result) txBin, err := testserdes.EncodeBinary(tx) require.NoError(t, err) expected := hex.EncodeToString(txBin) assert.Equal(t, expected, res) }) t.Run("getrawtransaction 2 arguments, verbose", func(t *testing.T) { block, _ := chain.GetBlock(chain.GetHeaderHash(0)) TXHash := block.Transactions[0].Hash() rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "getrawtransaction", "params": ["%s", 1]}"`, TXHash.StringLE()) body := doRPCCall(rpc, httpSrv.URL, t) txOut := checkErrGetResult(t, body, false) actual := result.TransactionOutputRaw{Transaction: transaction.Transaction{Network: testchain.Network()}} err := json.Unmarshal(txOut, &actual) require.NoErrorf(t, err, "could not parse response: %s", txOut) assert.Equal(t, *block.Transactions[0], actual.Transaction) assert.Equal(t, 8, actual.Confirmations) assert.Equal(t, TXHash, actual.Transaction.Hash()) }) t.Run("getblockheader_positive", func(t *testing.T) { rpc := `{"jsonrpc": "2.0", "id": 1, "method": "getblockheader", "params": %s}` testHeaderHash := chain.GetHeaderHash(1).StringLE() hdr := e.getHeader(testHeaderHash) runCase := func(t *testing.T, rpc string, expected, actual interface{}) { body := doRPCCall(rpc, httpSrv.URL, t) data := checkErrGetResult(t, body, false) require.NoError(t, json.Unmarshal(data, actual)) require.Equal(t, expected, actual) } t.Run("no verbose", func(t *testing.T) { w := io.NewBufBinWriter() hdr.EncodeBinary(w.BinWriter) require.NoError(t, w.Err) encoded := hex.EncodeToString(w.Bytes()) t.Run("missing", func(t *testing.T) { runCase(t, fmt.Sprintf(rpc, `["`+testHeaderHash+`"]`), &encoded, new(string)) }) t.Run("verbose=0", func(t *testing.T) { runCase(t, fmt.Sprintf(rpc, `["`+testHeaderHash+`", 0]`), &encoded, new(string)) }) t.Run("by number", func(t *testing.T) { runCase(t, fmt.Sprintf(rpc, `[1]`), &encoded, new(string)) }) }) t.Run("verbose != 0", func(t *testing.T) { nextHash := chain.GetHeaderHash(int(hdr.Index) + 1) expected := &result.Header{ Hash: hdr.Hash(), Size: io.GetVarSize(hdr), Version: hdr.Version, PrevBlockHash: hdr.PrevHash, MerkleRoot: hdr.MerkleRoot, Timestamp: hdr.Timestamp, Index: hdr.Index, NextConsensus: address.Uint160ToString(hdr.NextConsensus), Witnesses: []transaction.Witness{hdr.Script}, Confirmations: e.chain.BlockHeight() - hdr.Index + 1, NextBlockHash: &nextHash, } rpc := fmt.Sprintf(rpc, `["`+testHeaderHash+`", 2]`) runCase(t, rpc, expected, new(result.Header)) }) }) t.Run("getrawmempool", func(t *testing.T) { mp := chain.GetMemPool() // `expected` stores hashes of previously added txs expected := make([]util.Uint256, 0) for _, tx := range mp.GetVerifiedTransactions() { expected = append(expected, tx.Hash()) } for i := 0; i < 5; i++ { tx := transaction.New(testchain.Network(), []byte{byte(opcode.PUSH1)}, 0) assert.NoError(t, mp.Add(tx, &FeerStub{})) expected = append(expected, tx.Hash()) } rpc := `{"jsonrpc": "2.0", "id": 1, "method": "getrawmempool", "params": []}` body := doRPCCall(rpc, httpSrv.URL, t) res := checkErrGetResult(t, body, false) var actual []util.Uint256 err := json.Unmarshal(res, &actual) require.NoErrorf(t, err, "could not parse response: %s", res) assert.ElementsMatch(t, expected, actual) }) } func (e *executor) getHeader(s string) *block.Header { hash, err := util.Uint256DecodeStringLE(s) if err != nil { panic("can not decode hash parameter") } block, err := e.chain.GetBlock(hash) if err != nil { panic("unknown block (update block hash)") } return block.Header() } func encodeBlock(t *testing.T, b *block.Block) string { w := io.NewBufBinWriter() b.EncodeBinary(w.BinWriter) require.NoError(t, w.Err) return hex.EncodeToString(w.Bytes()) } func newBlock(t *testing.T, bc blockchainer.Blockchainer, index uint32, primary uint32, txs ...*transaction.Transaction) *block.Block { witness := transaction.Witness{VerificationScript: testchain.MultisigVerificationScript()} height := bc.BlockHeight() h := bc.GetHeaderHash(int(height)) hdr, err := bc.GetHeader(h) require.NoError(t, err) b := &block.Block{ Base: block.Base{ PrevHash: hdr.Hash(), Timestamp: (uint64(time.Now().UTC().Unix()) + uint64(hdr.Index)) * 1000, Index: hdr.Index + index, NextConsensus: witness.ScriptHash(), Script: witness, Network: bc.GetConfig().Magic, }, ConsensusData: block.ConsensusData{ PrimaryIndex: primary, Nonce: 1111, }, Transactions: txs, } _ = b.RebuildMerkleRoot() b.Script.InvocationScript = testchain.Sign(b.GetSignedPart()) return b } func (tc rpcTestCase) getResultPair(e *executor) (expected interface{}, res interface{}) { expected = tc.result(e) resVal := reflect.New(reflect.TypeOf(expected).Elem()) res = resVal.Interface() switch r := res.(type) { case *result.Block: r.Network = testchain.Network() } return expected, res } func checkErrGetResult(t *testing.T, body []byte, expectingFail bool) json.RawMessage { var resp response.Raw err := json.Unmarshal(body, &resp) require.Nil(t, err) if expectingFail { require.NotNil(t, resp.Error) assert.NotEqual(t, 0, resp.Error.Code) assert.NotEqual(t, "", resp.Error.Message) } else { assert.Nil(t, resp.Error) } return resp.Result } func doRPCCallOverWS(rpcCall string, url string, t *testing.T) []byte { dialer := websocket.Dialer{HandshakeTimeout: time.Second} url = "ws" + strings.TrimPrefix(url, "http") c, _, err := dialer.Dial(url+"/ws", nil) require.NoError(t, err) c.SetWriteDeadline(time.Now().Add(time.Second)) require.NoError(t, c.WriteMessage(1, []byte(rpcCall))) c.SetReadDeadline(time.Now().Add(time.Second)) _, body, err := c.ReadMessage() require.NoError(t, err) return bytes.TrimSpace(body) } func doRPCCallOverHTTP(rpcCall string, url string, t *testing.T) []byte { cl := http.Client{Timeout: time.Second} resp, err := cl.Post(url, "application/json", strings.NewReader(rpcCall)) require.NoErrorf(t, err, "could not make a POST request") body, err := ioutil.ReadAll(resp.Body) assert.NoErrorf(t, err, "could not read response from the request: %s", rpcCall) return bytes.TrimSpace(body) }