package client import ( "context" "encoding/base64" "encoding/hex" "net/http" "net/http/httptest" "strings" "testing" "time" "github.com/gorilla/websocket" "github.com/nspcc-dev/neo-go/pkg/core/block" "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/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/internal/testserdes" "github.com/nspcc-dev/neo-go/pkg/rpc/request" "github.com/nspcc-dev/neo-go/pkg/rpc/response/result" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) type rpcClientTestCase struct { name string invoke func(c *Client) (interface{}, error) serverResponse string result func(c *Client) interface{} check func(t *testing.T, c *Client, result interface{}) } const hexB1 = "00000000e862e7907fc987cd58ddb3abb754aeb8812c9377c45e737a036fe88a622c3b8f301f2e84a86b207270830e7929530ccb841a3df7379fe6f0ac8865b33316839501cdd0847201000001000000abec5362f11e75b6e02e407bb98d63675d14384101fd08010c40ab634ce91590e77b246cee8b204e8a270268ee1ef32434cece73f425a7dbc90f1bed1dbe914edcaa2653167ad170ae10e16a9b2c6b7e0af1f711fb848fbb1b7f0c40232de6ad07ee3846bafa96302d37602349501a556df575e7df0743e45b076d6a0c6c6dd4cad3898f9e8848dd054abd303b229fd12984042f241f0e668f39a0fb0c408b4af43057df189a9d471010b5150bab442040403147c5e502bda38cde3ff8bce803f01245e07e2bfb95d57349c55dcc27e3710b82f2735d0f40eb4342908e330c40cda66f743d4ed8d856f5376953f9169581c668a9370245aef16202ebef9bb3f7f81234be62ec287d701ad7d8bf5042648019af9fe5baa0a8e05d279bfdb1d4c994130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb030057040000000000000002000000abec5362f11e75b6e02e407bb98d63675d14384100000000000000003e5f0d0000000000b00400000001abec5362f11e75b6e02e407bb98d63675d14384101590218ddf5050c14316e851039019d39dfc2c37d6c3fee19fd5809870c14abec5362f11e75b6e02e407bb98d63675d14384113c00c087472616e736665720c14897720d8cd76f4f00abfa37c0edd889c208fde9b41627d5b523801fd08010c402d96d8fde4ba266f89bc71ef117361967e0d11ed84cd60942a27bc99994dc58adf36a0e74ce976aac657a87a3c19c38e8ca450c67420046b81d98c60fd8feb040c40b3c15d5d23e0403a36cf559caee2979ca6ef00fe255df0e5c3daac4da051016b41eba42668934cd3308359451bafdd5419d059179fd40859684a3b91388bf9d80c407ac048cf8540b091955a374a0f36dae560c92c0134886507a589edf58b9dfbb4e3dbd5450be34e269d2e5454eb14eb7d6280d6101b4529410f829d37634849be0c403bba4113a687ff8507c1753f8519557531cf9df51ecc20deeb2c2b003ec5a1f7588cdd50b99e40b4f8039bb56c5df7ec9e7d6ea4b02fe23792510da21c7557f394130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb0003000000abec5362f11e75b6e02e407bb98d63675d1438410000000000000000de6e0d0000000000b00400000001abec5362f11e75b6e02e407bb98d63675d143841015d0300e87648170000000c14316e851039019d39dfc2c37d6c3fee19fd5809870c14abec5362f11e75b6e02e407bb98d63675d14384113c00c087472616e736665720c143b7d3711c6f0ccf9b1dca903d1bfa1d896f1238c41627d5b523801fd08010c4063fb12aa9f3fb83f6324ea9c7ec11fa9e995b51140f480409d68cf4d625e598b0632d6610602984bfa2f5e5ea9bcc62a0e6d818dd271b38530c0d1b8a71b4e0c0c4013e091eac6f304668d647c5c032fd1020597ea5204545e21c38655a6343d58492118f1231ede91af848af7e1d987d1a8816966f5fc1a7821c6c6f62734267bde0c40daadd04a7a4141d96c58de2d373e672ca071e2b82138ef52df016ac522710385db2ac73743d2fe73061fa5d6cb0ff73a7ec7f0667e4c8bff6aa0d5783128d36e0c40dab85cd87d3f92be9532292bdc6f420b0ecbf2f877c70c6a9921ee0fc900dfc53998cf020a51fa9af3d0608f6a2b9048cea3c0b586485802bbd278b261eee8a494130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb" const hexTxMoveNeo = "0002000000abec5362f11e75b6e02e407bb98d63675d14384100000000000000003e5f0d0000000000b00400000001abec5362f11e75b6e02e407bb98d63675d14384101590218ddf5050c14316e851039019d39dfc2c37d6c3fee19fd5809870c14abec5362f11e75b6e02e407bb98d63675d14384113c00c087472616e736665720c14897720d8cd76f4f00abfa37c0edd889c208fde9b41627d5b523801fd08010c402d96d8fde4ba266f89bc71ef117361967e0d11ed84cd60942a27bc99994dc58adf36a0e74ce976aac657a87a3c19c38e8ca450c67420046b81d98c60fd8feb040c40b3c15d5d23e0403a36cf559caee2979ca6ef00fe255df0e5c3daac4da051016b41eba42668934cd3308359451bafdd5419d059179fd40859684a3b91388bf9d80c407ac048cf8540b091955a374a0f36dae560c92c0134886507a589edf58b9dfbb4e3dbd5450be34e269d2e5454eb14eb7d6280d6101b4529410f829d37634849be0c403bba4113a687ff8507c1753f8519557531cf9df51ecc20deeb2c2b003ec5a1f7588cdd50b99e40b4f8039bb56c5df7ec9e7d6ea4b02fe23792510da21c7557f394130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb" const b1Verbose = `{"id":5,"jsonrpc":"2.0","result":{"size":1681,"nextblockhash":"0xf2afe371a27c9dbac4f4a8ad8eba750898b7c04aa298e64fe9e488e947976045","confirmations":6,"hash":"0xbd178d8d4a28ec082c034f817ce2423221281a31e7e00014dbf732c4053033d2","version":0,"previousblockhash":"0x8f3b2c628ae86f037a735ec477932c81b8ae54b7abb3dd58cd87c97f90e762e8","merkleroot":"0x95831633b36588acf0e69f37f73d1a84cb0c5329790e837072206ba8842e1f30","time":1591366176001,"index":1,"nextconsensus":"AXSvJVzydxXuL9da4GVwK25zdesCrVKkHL","witnesses":[{"invocation":"DECrY0zpFZDneyRs7osgToonAmjuHvMkNM7Oc/Qlp9vJDxvtHb6RTtyqJlMWetFwrhDhapssa34K8fcR+4SPuxt/DEAjLeatB+44Rrr6ljAtN2AjSVAaVW31deffB0PkWwdtagxsbdTK04mPnohI3QVKvTA7Ip/RKYQELyQfDmaPOaD7DECLSvQwV98Ymp1HEBC1FQurRCBAQDFHxeUCvaOM3j/4vOgD8BJF4H4r+5XVc0nFXcwn43ELgvJzXQ9A60NCkI4zDEDNpm90PU7Y2Fb1N2lT+RaVgcZoqTcCRa7xYgLr75uz9/gSNL5i7Ch9cBrX2L9QQmSAGa+f5bqgqOBdJ5v9sdTJ","verification":"EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFAtBMHOzuw=="}],"consensus_data":{"primary":0,"nonce":"0000000000000457"},"tx":[{"txid":"0x8af9ccb8e7e0f0a73e77b78dc52750e77c50f78b09ecc2f0669c0b459cc7dd89","size":575,"version":0,"nonce":2,"sender":"AXSvJVzydxXuL9da4GVwK25zdesCrVKkHL","sys_fee":"0","net_fee":"0.0087635","valid_until_block":1200,"attributes":[],"cosigners":[{"account":"0x4138145d67638db97b402ee0b6751ef16253ecab","scopes":1}],"script":"Ahjd9QUMFDFuhRA5AZ0538LDfWw/7hn9WAmHDBSr7FNi8R51tuAuQHu5jWNnXRQ4QRPADAh0cmFuc2ZlcgwUiXcg2M129PAKv6N8Dt2InCCP3ptBYn1bUjg=","scripts":[{"invocation":"DEAtltj95Lomb4m8ce8Rc2GWfg0R7YTNYJQqJ7yZmU3Fit82oOdM6XaqxleoejwZw46MpFDGdCAEa4HZjGD9j+sEDECzwV1dI+BAOjbPVZyu4pecpu8A/iVd8OXD2qxNoFEBa0HrpCZok0zTMINZRRuv3VQZ0FkXn9QIWWhKO5E4i/nYDEB6wEjPhUCwkZVaN0oPNtrlYMksATSIZQelie31i537tOPb1UUL404mnS5UVOsU631igNYQG0UpQQ+CnTdjSEm+DEA7ukETpof/hQfBdT+FGVV1Mc+d9R7MIN7rLCsAPsWh91iM3VC5nkC0+AObtWxd9+yefW6ksC/iN5JRDaIcdVfz","verification":"EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFAtBMHOzuw=="}]},{"txid":"0xe7cff9e4820e53232dae619a3e6f57a9430dc240b5ed7b5c0ea2cfee3e90c985","size":579,"version":0,"nonce":3,"sender":"AXSvJVzydxXuL9da4GVwK25zdesCrVKkHL","sys_fee":"0","net_fee":"0.0088035","valid_until_block":1200,"attributes":[],"cosigners":[{"account":"0x4138145d67638db97b402ee0b6751ef16253ecab","scopes":1}],"script":"AwDodkgXAAAADBQxboUQOQGdOd/Cw31sP+4Z/VgJhwwUq+xTYvEedbbgLkB7uY1jZ10UOEETwAwIdHJhbnNmZXIMFDt9NxHG8Mz5sdypA9G/odiW8SOMQWJ9W1I4","scripts":[{"invocation":"DEBj+xKqnz+4P2Mk6px+wR+p6ZW1EUD0gECdaM9NYl5ZiwYy1mEGAphL+i9eXqm8xioObYGN0nGzhTDA0binG04MDEAT4JHqxvMEZo1kfFwDL9ECBZfqUgRUXiHDhlWmND1YSSEY8SMe3pGvhIr34dmH0aiBaWb1/Bp4IcbG9ic0JnveDEDardBKekFB2WxY3i03PmcsoHHiuCE471LfAWrFInEDhdsqxzdD0v5zBh+l1ssP9zp+x/BmfkyL/2qg1XgxKNNuDEDauFzYfT+SvpUyKSvcb0ILDsvy+HfHDGqZIe4PyQDfxTmYzwIKUfqa89Bgj2orkEjOo8C1hkhYArvSeLJh7uik","verification":"EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFAtBMHOzuw=="}]}]}}` const hexHeader1 = "00000000e862e7907fc987cd58ddb3abb754aeb8812c9377c45e737a036fe88a622c3b8f301f2e84a86b207270830e7929530ccb841a3df7379fe6f0ac8865b33316839501cdd0847201000001000000abec5362f11e75b6e02e407bb98d63675d14384101fd08010c40ab634ce91590e77b246cee8b204e8a270268ee1ef32434cece73f425a7dbc90f1bed1dbe914edcaa2653167ad170ae10e16a9b2c6b7e0af1f711fb848fbb1b7f0c40232de6ad07ee3846bafa96302d37602349501a556df575e7df0743e45b076d6a0c6c6dd4cad3898f9e8848dd054abd303b229fd12984042f241f0e668f39a0fb0c408b4af43057df189a9d471010b5150bab442040403147c5e502bda38cde3ff8bce803f01245e07e2bfb95d57349c55dcc27e3710b82f2735d0f40eb4342908e330c40cda66f743d4ed8d856f5376953f9169581c668a9370245aef16202ebef9bb3f7f81234be62ec287d701ad7d8bf5042648019af9fe5baa0a8e05d279bfdb1d4c994130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb00" const header1Verbose = `{"id":5,"jsonrpc":"2.0","result":{"hash":"0xbd178d8d4a28ec082c034f817ce2423221281a31e7e00014dbf732c4053033d2","size":518,"version":0,"previousblockhash":"0x8f3b2c628ae86f037a735ec477932c81b8ae54b7abb3dd58cd87c97f90e762e8","merkleroot":"0x95831633b36588acf0e69f37f73d1a84cb0c5329790e837072206ba8842e1f30","time":1591366176001,"index":1,"nextconsensus":"AXSvJVzydxXuL9da4GVwK25zdesCrVKkHL","witnesses":[{"invocation":"DECrY0zpFZDneyRs7osgToonAmjuHvMkNM7Oc/Qlp9vJDxvtHb6RTtyqJlMWetFwrhDhapssa34K8fcR+4SPuxt/DEAjLeatB+44Rrr6ljAtN2AjSVAaVW31deffB0PkWwdtagxsbdTK04mPnohI3QVKvTA7Ip/RKYQELyQfDmaPOaD7DECLSvQwV98Ymp1HEBC1FQurRCBAQDFHxeUCvaOM3j/4vOgD8BJF4H4r+5XVc0nFXcwn43ELgvJzXQ9A60NCkI4zDEDNpm90PU7Y2Fb1N2lT+RaVgcZoqTcCRa7xYgLr75uz9/gSNL5i7Ch9cBrX2L9QQmSAGa+f5bqgqOBdJ5v9sdTJ","verification":"EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFAtBMHOzuw=="}],"confirmations":6,"nextblockhash":"0xf2afe371a27c9dbac4f4a8ad8eba750898b7c04aa298e64fe9e488e947976045"}}` const txMoveNeoVerbose = `{"id":5,"jsonrpc":"2.0","result":{"blockhash":"0xbd178d8d4a28ec082c034f817ce2423221281a31e7e00014dbf732c4053033d2","confirmations":6,"blocktime":1591366176001,"txid":"0x8af9ccb8e7e0f0a73e77b78dc52750e77c50f78b09ecc2f0669c0b459cc7dd89","size":575,"version":0,"nonce":2,"sender":"AXSvJVzydxXuL9da4GVwK25zdesCrVKkHL","sys_fee":"0","net_fee":"0.0087635","valid_until_block":1200,"attributes":[],"cosigners":[{"account":"0x4138145d67638db97b402ee0b6751ef16253ecab","scopes":1}],"script":"Ahjd9QUMFDFuhRA5AZ0538LDfWw/7hn9WAmHDBSr7FNi8R51tuAuQHu5jWNnXRQ4QRPADAh0cmFuc2ZlcgwUiXcg2M129PAKv6N8Dt2InCCP3ptBYn1bUjg=","scripts":[{"invocation":"DEAtltj95Lomb4m8ce8Rc2GWfg0R7YTNYJQqJ7yZmU3Fit82oOdM6XaqxleoejwZw46MpFDGdCAEa4HZjGD9j+sEDECzwV1dI+BAOjbPVZyu4pecpu8A/iVd8OXD2qxNoFEBa0HrpCZok0zTMINZRRuv3VQZ0FkXn9QIWWhKO5E4i/nYDEB6wEjPhUCwkZVaN0oPNtrlYMksATSIZQelie31i537tOPb1UUL404mnS5UVOsU631igNYQG0UpQQ+CnTdjSEm+DEA7ukETpof/hQfBdT+FGVV1Mc+d9R7MIN7rLCsAPsWh91iM3VC5nkC0+AObtWxd9+yefW6ksC/iN5JRDaIcdVfz","verification":"EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFAtBMHOzuw=="}]}}` // getResultBlock1 returns data for block number 1 which is used by several tests. func getResultBlock1() *result.Block { binB, err := hex.DecodeString(hexB1) if err != nil { panic(err) } b := new(block.Block) err = testserdes.DecodeBinary(binB, b) if err != nil { panic(err) } b2Hash, err := util.Uint256DecodeStringLE("f2afe371a27c9dbac4f4a8ad8eba750898b7c04aa298e64fe9e488e947976045") if err != nil { panic(err) } return &result.Block{ Block: b, BlockMetadata: result.BlockMetadata{ Size: 1681, NextBlockHash: &b2Hash, Confirmations: 6, }, } } func getTxMoveNeo() *result.TransactionOutputRaw { b1 := getResultBlock1() txBin, err := hex.DecodeString(hexTxMoveNeo) if err != nil { panic(err) } tx := new(transaction.Transaction) err = testserdes.DecodeBinary(txBin, tx) if err != nil { panic(err) } return &result.TransactionOutputRaw{ Transaction: tx, TransactionMetadata: result.TransactionMetadata{ Timestamp: b1.Timestamp, Blockhash: b1.Block.Hash(), Confirmations: int(b1.Confirmations), }, } } // rpcClientTestCases contains `serverResponse` json data fetched from examples // published in official C# JSON-RPC API v2.10.3 reference // (see https://docs.neo.org/docs/en-us/reference/rpc/latest-version/api.html) var rpcClientTestCases = map[string][]rpcClientTestCase{ "getapplicationlog": { { name: "positive", invoke: func(c *Client) (interface{}, error) { return c.GetApplicationLog(util.Uint256{}) }, serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"txid":"0x17145a039fca704fcdbeb46e6b210af98a1a9e5b9768e46ffc38f71c79ac2521","executions":[{"trigger":"Application","contract":"0xb9fa3b421eb749d5dd585fe1c1133b311a14bcb1","vmstate":"HALT","gas_consumed":"1","stack":[{"type":"Integer","value":1}],"notifications":[]}]}}`, result: func(c *Client) interface{} { txHash, err := util.Uint256DecodeStringLE("17145a039fca704fcdbeb46e6b210af98a1a9e5b9768e46ffc38f71c79ac2521") if err != nil { panic(err) } scriptHash, err := util.Uint160DecodeStringLE("b9fa3b421eb749d5dd585fe1c1133b311a14bcb1") if err != nil { panic(err) } return &result.ApplicationLog{ TxHash: txHash, Executions: []result.Execution{ { Trigger: "Application", ScriptHash: scriptHash, VMState: "HALT", GasConsumed: util.Fixed8FromInt64(1), Stack: []smartcontract.Parameter{{Type: smartcontract.IntegerType, Value: int64(1)}}, Events: []result.NotificationEvent{}, }, }, } }, }, }, "getbestblockhash": { { name: "positive", invoke: func(c *Client) (interface{}, error) { return c.GetBestBlockHash() }, serverResponse: `{"jsonrpc":"2.0","id":1,"result":"0x773dd2dae4a9c9275290f89b56e67d7363ea4826dfd4fc13cc01cf73a44b0d0e"}`, result: func(c *Client) interface{} { result, err := util.Uint256DecodeStringLE("773dd2dae4a9c9275290f89b56e67d7363ea4826dfd4fc13cc01cf73a44b0d0e") if err != nil { panic(err) } return result }, }, }, "getblock": { { name: "byIndex_positive", invoke: func(c *Client) (interface{}, error) { return c.GetBlockByIndex(1) }, serverResponse: `{"id":1,"jsonrpc":"2.0","result":"` + hexB1 + `"}`, result: func(c *Client) interface{} { b := getResultBlock1() return b.Block }, }, { name: "byIndex_verbose_positive", invoke: func(c *Client) (i interface{}, err error) { return c.GetBlockByIndexVerbose(1) }, serverResponse: b1Verbose, result: func(c *Client) interface{} { return getResultBlock1() }, }, { name: "byHash_positive", invoke: func(c *Client) (interface{}, error) { hash, err := util.Uint256DecodeStringLE("d151651e86680a7ecbc87babf3346a42e7bc9974414ce192c9c22ac4f2e9d043") if err != nil { panic(err) } return c.GetBlockByHash(hash) }, serverResponse: `{"id":1,"jsonrpc":"2.0","result":"` + hexB1 + `"}`, result: func(c *Client) interface{} { b := getResultBlock1() return b.Block }, }, { name: "byHash_verbose_positive", invoke: func(c *Client) (i interface{}, err error) { hash, err := util.Uint256DecodeStringLE("86fe1061140b2ea791b0739fb9732abc6e5e47de4927228a1ac41de3d93eb7cb") if err != nil { panic(err) } return c.GetBlockByHashVerbose(hash) }, serverResponse: b1Verbose, result: func(c *Client) interface{} { return getResultBlock1() }, }, }, "getblockcount": { { name: "positive", invoke: func(c *Client) (interface{}, error) { return c.GetBlockCount() }, serverResponse: `{"jsonrpc":"2.0","id":1,"result":991991}`, result: func(c *Client) interface{} { return uint32(991991) }, }, }, "getblockhash": { { name: "positive", invoke: func(c *Client) (interface{}, error) { return c.GetBlockHash(1) }, serverResponse: `{"jsonrpc":"2.0","id":1,"result":"0x4c1e879872344349067c3b1a30781eeb4f9040d3795db7922f513f6f9660b9b2"}`, result: func(c *Client) interface{} { hash, err := util.Uint256DecodeStringLE("4c1e879872344349067c3b1a30781eeb4f9040d3795db7922f513f6f9660b9b2") if err != nil { panic(err) } return hash }, }, }, "getblockheader": { { name: "positive", invoke: func(c *Client) (interface{}, error) { hash, err := util.Uint256DecodeStringLE("68e4bd688b852e807eef13a0ff7da7b02223e359a35153667e88f9cb4a3b0801") if err != nil { panic(err) } return c.GetBlockHeader(hash) }, serverResponse: `{"id":1,"jsonrpc":"2.0","result":"` + hexHeader1 + `"}`, result: func(c *Client) interface{} { b := getResultBlock1() return b.Header() }, }, { name: "verbose_positive", invoke: func(c *Client) (i interface{}, err error) { hash, err := util.Uint256DecodeStringLE("cbb73ed9e31dc41a8a222749de475e6ebc2a73b99f73b091a72e0b146110fe86") if err != nil { panic(err) } return c.GetBlockHeaderVerbose(hash) }, serverResponse: header1Verbose, result: func(c *Client) interface{} { b := getResultBlock1() return &result.Header{ Hash: b.Hash(), Size: 518, Version: b.Version, NextBlockHash: b.NextBlockHash, PrevBlockHash: b.PrevHash, MerkleRoot: b.MerkleRoot, Timestamp: b.Timestamp, Index: b.Index, NextConsensus: address.Uint160ToString(b.NextConsensus), Witnesses: []transaction.Witness{b.Script}, Confirmations: 6, } }, }, }, "getblocksysfee": { { name: "positive", invoke: func(c *Client) (interface{}, error) { return c.GetBlockSysFee(1) }, serverResponse: `{"jsonrpc":"2.0","id":1,"result":"195500"}`, result: func(c *Client) interface{} { return util.Fixed8FromInt64(195500) }, }, }, "getconnectioncount": { { name: "positive", invoke: func(c *Client) (interface{}, error) { return c.GetConnectionCount() }, serverResponse: `{"jsonrpc":"2.0","id":1,"result":10}`, result: func(c *Client) interface{} { return 10 }, }, }, "getcontractstate": { { name: "positive", invoke: func(c *Client) (interface{}, error) { hash, err := util.Uint160DecodeStringLE("1b4357bff5a01bdf2a6581247cf9ed1e24629176") if err != nil { panic(err) } return c.GetContractState(hash) }, serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"id":0,"script":"VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==","manifest":{"abi":{"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176","entryPoint":{"name":"Main","parameters":[{"name":"method","type":"String"},{"name":"params","type":"Array"}],"returnType":"Boolean"},"methods":[],"events":[]},"groups":[],"features":{"payable":false,"storage":true},"permissions":null,"trusts":[],"safeMethods":[],"extra":null},"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176"}}`, result: func(c *Client) interface{} { script, err := base64.StdEncoding.DecodeString("VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==") if err != nil { panic(err) } m := manifest.NewManifest(hash.Hash160(script)) m.ABI.EntryPoint.Name = "Main" m.ABI.EntryPoint.Parameters = []manifest.Parameter{ manifest.NewParameter("method", smartcontract.StringType), manifest.NewParameter("params", smartcontract.ArrayType), } m.ABI.EntryPoint.ReturnType = smartcontract.BoolType m.Features = smartcontract.HasStorage cs := &state.Contract{ ID: 0, Script: script, Manifest: *m, } _ = cs.ScriptHash() return cs }, }, }, "getnep5balances": { { name: "positive", invoke: func(c *Client) (interface{}, error) { hash, err := util.Uint160DecodeStringLE("1aada0032aba1ef6d1f07bbd8bec1d85f5380fb3") if err != nil { panic(err) } return c.GetNEP5Balances(hash) }, serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"balance":[{"asset_hash":"a48b6e1291ba24211ad11bb90ae2a10bf1fcd5a8","amount":"50000000000","last_updated_block":251604}],"address":"AY6eqWjsUFCzsVELG7yG72XDukKvC34p2w"}}`, result: func(c *Client) interface{} { hash, err := util.Uint160DecodeStringLE("a48b6e1291ba24211ad11bb90ae2a10bf1fcd5a8") if err != nil { panic(err) } return &result.NEP5Balances{ Balances: []result.NEP5Balance{{ Asset: hash, Amount: "50000000000", LastUpdated: 251604, }}, Address: "AY6eqWjsUFCzsVELG7yG72XDukKvC34p2w", } }, }, }, "getnep5transfers": { { name: "positive", invoke: func(c *Client) (interface{}, error) { return c.GetNEP5Transfers("AbHgdBaWEnHkCiLtDZXjhvhaAK2cwFh5pF") }, serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"sent":[],"received":[{"timestamp":1555651816,"asset_hash":"600c4f5200db36177e3e8a09e9f18e2fc7d12a0f","transfer_address":"AYwgBNMepiv5ocGcyNT4mA8zPLTQ8pDBis","amount":"1000000","block_index":436036,"transfer_notify_index":0,"tx_hash":"df7683ece554ecfb85cf41492c5f143215dd43ef9ec61181a28f922da06aba58"}],"address":"AbHgdBaWEnHkCiLtDZXjhvhaAK2cwFh5pF"}}`, result: func(c *Client) interface{} { assetHash, err := util.Uint160DecodeStringLE("600c4f5200db36177e3e8a09e9f18e2fc7d12a0f") if err != nil { panic(err) } txHash, err := util.Uint256DecodeStringLE("df7683ece554ecfb85cf41492c5f143215dd43ef9ec61181a28f922da06aba58") if err != nil { panic(err) } return &result.NEP5Transfers{ Sent: []result.NEP5Transfer{}, Received: []result.NEP5Transfer{ { Timestamp: 1555651816, Asset: assetHash, Address: "AYwgBNMepiv5ocGcyNT4mA8zPLTQ8pDBis", Amount: "1000000", Index: 436036, NotifyIndex: 0, TxHash: txHash, }, }, Address: "AbHgdBaWEnHkCiLtDZXjhvhaAK2cwFh5pF", } }, }, }, "getpeers": { { name: "positive", invoke: func(c *Client) (interface{}, error) { return c.GetPeers() }, serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"unconnected":[{"address":"172.200.0.1","port":"20333"}],"connected":[{"address":"127.0.0.1","port":"20335"}],"bad":[{"address":"172.200.0.254","port":"20332"}]}}`, result: func(c *Client) interface{} { return &result.GetPeers{ Unconnected: result.Peers{ { Address: "172.200.0.1", Port: "20333", }, }, Connected: result.Peers{ { Address: "127.0.0.1", Port: "20335", }, }, Bad: result.Peers{ { Address: "172.200.0.254", Port: "20332", }, }, } }, }, }, "getrawmempool": { { name: "positive", invoke: func(c *Client) (interface{}, error) { return c.GetRawMemPool() }, serverResponse: `{"jsonrpc":"2.0","id":1,"result":["0x9786cce0dddb524c40ddbdd5e31a41ed1f6b5c8a683c122f627ca4a007a7cf4e"]}`, result: func(c *Client) interface{} { hash, err := util.Uint256DecodeStringLE("9786cce0dddb524c40ddbdd5e31a41ed1f6b5c8a683c122f627ca4a007a7cf4e") if err != nil { panic(err) } return []util.Uint256{hash} }, }, }, "getrawtransaction": { { name: "positive", invoke: func(c *Client) (i interface{}, err error) { hash, err := util.Uint256DecodeStringLE("ca23bd5df3249836849309ca2afe972bfd288b0a7ae61302c8fd545daa8bffd6") if err != nil { panic(err) } return c.GetRawTransaction(hash) }, serverResponse: `{"id":1,"jsonrpc":"2.0","result":"` + hexTxMoveNeo + `"}`, result: func(c *Client) interface{} { tx := getTxMoveNeo() return tx.Transaction }, }, { name: "verbose_positive", invoke: func(c *Client) (interface{}, error) { hash, err := util.Uint256DecodeStringLE("8185b0db7ed77190b93ac8bd44896822cd8f3cfcf702b3f50131e0efd200ef96") if err != nil { panic(err) } return c.GetRawTransactionVerbose(hash) }, serverResponse: txMoveNeoVerbose, result: func(c *Client) interface{} { return getTxMoveNeo() }, }, }, "getstorage": { { name: "positive", invoke: func(c *Client) (interface{}, error) { hash, err := util.Uint160DecodeStringLE("03febccf81ac85e3d795bc5cbd4e84e907812aa3") if err != nil { panic(err) } key, err := hex.DecodeString("5065746572") if err != nil { panic(err) } return c.GetStorage(hash, key) }, serverResponse: `{"jsonrpc":"2.0","id":1,"result":"4c696e"}`, result: func(c *Client) interface{} { value, err := hex.DecodeString("4c696e") if err != nil { panic(err) } return value }, }, }, "gettransactionheight": { { name: "positive", invoke: func(c *Client) (interface{}, error) { hash, err := util.Uint256DecodeStringLE("cb6ddb5f99d6af4c94a6c396d5294472f2eebc91a2c933e0f527422296fa9fb2") if err != nil { panic(err) } return c.GetTransactionHeight(hash) }, serverResponse: `{"jsonrpc":"2.0","id":1,"result":1}`, result: func(c *Client) interface{} { return uint32(1) }, }, }, "getunclaimedgas": { { name: "positive", invoke: func(c *Client) (interface{}, error) { return c.GetUnclaimedGas("AGofsxAUDwt52KjaB664GYsqVAkULYvKNt") }, serverResponse: `{"jsonrpc":"2.0","id":1,"result":"897299680935"}`, result: func(c *Client) interface{} { return util.Fixed8(897299680935) }, }, }, "getvalidators": { { name: "positive", invoke: func(c *Client) (interface{}, error) { return c.GetValidators() }, serverResponse: `{"id":1,"jsonrpc":"2.0","result":[{"publickey":"02b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc2","votes":"0","active":true},{"publickey":"02103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e","votes":"0","active":true},{"publickey":"03d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699","votes":"0","active":true},{"publickey":"02a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd62","votes":"0","active":true}]}`, result: func(c *Client) interface{} { return []result.Validator{} }, check: func(t *testing.T, c *Client, uns interface{}) { res, ok := uns.([]result.Validator) require.True(t, ok) assert.Equal(t, 4, len(res)) }, }, }, "getversion": { { name: "positive", invoke: func(c *Client) (interface{}, error) { return c.GetVersion() }, serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"tcp_port":20332,"ws_port":20342,"nonce":2153672787,"user_agent":"/NEO-GO:0.73.1-pre-273-ge381358/"}}`, result: func(c *Client) interface{} { return &result.Version{ TCPPort: uint16(20332), WSPort: uint16(20342), Nonce: 2153672787, UserAgent: "/NEO-GO:0.73.1-pre-273-ge381358/", } }, }, }, "invokefunction": { { name: "positive", invoke: func(c *Client) (interface{}, error) { hash, err := util.Uint160DecodeStringLE("91b83e96f2a7c4fdf0c1688441ec61986c7cae26") if err != nil { panic(err) } return c.InvokeFunction("af7c7328eee5a275a3bcaee2bf0cf662b5e739be", "balanceOf", []smartcontract.Parameter{ { Type: smartcontract.Hash160Type, Value: hash, }, }) }, serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"script":"1426ae7c6c9861ec418468c1f0fdc4a7f2963eb89151c10962616c616e63654f6667be39e7b562f60cbfe2aebca375a2e5ee28737caf","state":"HALT","gas_consumed":"0.311","stack":[{"type":"ByteArray","value":"JivsCEQy"}],"tx":"d101361426ae7c6c9861ec418468c1f0fdc4a7f2963eb89151c10962616c616e63654f6667be39e7b562f60cbfe2aebca375a2e5ee28737caf000000000000000000000000"}}`, result: func(c *Client) interface{} { bytes, err := hex.DecodeString("262bec084432") if err != nil { panic(err) } return &result.Invoke{ State: "HALT", GasConsumed: "0.311", Script: "1426ae7c6c9861ec418468c1f0fdc4a7f2963eb89151c10962616c616e63654f6667be39e7b562f60cbfe2aebca375a2e5ee28737caf", Stack: []smartcontract.Parameter{ { Type: smartcontract.ByteArrayType, Value: bytes, }, }, } }, }, }, "invokescript": { { name: "positive", invoke: func(c *Client) (interface{}, error) { return c.InvokeScript("00046e616d656724058e5e1b6008847cd662728549088a9ee82191") }, serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"script":"00046e616d656724058e5e1b6008847cd662728549088a9ee82191","state":"HALT","gas_consumed":"0.161","stack":[{"type":"ByteArray","value":"TkVQNSBHQVM="}],"tx":"d1011b00046e616d656724058e5e1b6008847cd662728549088a9ee82191000000000000000000000000"}}`, result: func(c *Client) interface{} { bytes, err := hex.DecodeString("4e45503520474153") if err != nil { panic(err) } return &result.Invoke{ State: "HALT", GasConsumed: "0.161", Script: "00046e616d656724058e5e1b6008847cd662728549088a9ee82191", Stack: []smartcontract.Parameter{ { Type: smartcontract.ByteArrayType, Value: bytes, }, }, } }, }, }, "sendrawtransaction": { { name: "positive", invoke: func(c *Client) (interface{}, error) { return nil, c.SendRawTransaction(transaction.New([]byte{byte(opcode.PUSH1)}, 0)) }, serverResponse: `{"jsonrpc":"2.0","id":1,"result":true}`, result: func(c *Client) interface{} { // no error expected return nil }, }, }, "submitblock": { { name: "positive", invoke: func(c *Client) (interface{}, error) { return nil, c.SubmitBlock(block.Block{ Base: block.Base{}, Transactions: nil, Trimmed: false, }) }, serverResponse: `{"jsonrpc":"2.0","id":1,"result":true}`, result: func(c *Client) interface{} { // no error expected return nil }, }, }, "validateaddress": { { name: "positive", invoke: func(c *Client) (interface{}, error) { return nil, c.ValidateAddress("AQVh2pG732YvtNaxEGkQUei3YA4cvo7d2i") }, serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"address":"AQVh2pG732YvtNaxEGkQUei3YA4cvo7d2i","isvalid":true}}`, result: func(c *Client) interface{} { // no error expected return nil }, }, }, } type rpcClientErrorCase struct { name string invoke func(c *Client) (interface{}, error) } var rpcClientErrorCases = map[string][]rpcClientErrorCase{ `{"jsonrpc":"2.0","id":1,"result":"not-a-hex-string"}`: { { name: "getblock_not_a_hex_response", invoke: func(c *Client) (interface{}, error) { return c.GetBlockByIndex(1) }, }, { name: "getblockheader_not_a_hex_response", invoke: func(c *Client) (interface{}, error) { hash, err := util.Uint256DecodeStringLE("e93d17a52967f9e69314385482bf86f85260e811b46bf4d4b261a7f4135a623c") if err != nil { panic(err) } return c.GetBlockHeader(hash) }, }, { name: "getrawtransaction_not_a_hex_response", invoke: func(c *Client) (interface{}, error) { hash, err := util.Uint256DecodeStringLE("e93d17a52967f9e69314385482bf86f85260e811b46bf4d4b261a7f4135a623c") if err != nil { panic(err) } return c.GetRawTransaction(hash) }, }, { name: "getstorage_not_a_hex_response", invoke: func(c *Client) (interface{}, error) { hash, err := util.Uint160DecodeStringLE("03febccf81ac85e3d795bc5cbd4e84e907812aa3") if err != nil { panic(err) } key, err := hex.DecodeString("5065746572") if err != nil { panic(err) } return c.GetStorage(hash, key) }, }, }, `{"jsonrpc":"2.0","id":1,"result":"01"}`: { { name: "getblock_decodebin_error", invoke: func(c *Client) (interface{}, error) { return c.GetBlockByIndex(1) }, }, { name: "getheader_decodebin_err", invoke: func(c *Client) (interface{}, error) { hash, err := util.Uint256DecodeStringLE("e93d17a52967f9e69314385482bf86f85260e811b46bf4d4b261a7f4135a623c") if err != nil { panic(err) } return c.GetBlockHeader(hash) }, }, { name: "getrawtransaction_decodebin_err", invoke: func(c *Client) (interface{}, error) { hash, err := util.Uint256DecodeStringLE("e93d17a52967f9e69314385482bf86f85260e811b46bf4d4b261a7f4135a623c") if err != nil { panic(err) } return c.GetRawTransaction(hash) }, }, }, `{"jsonrpc":"2.0","id":1,"result":false}`: { { name: "sendrawtransaction_bad_server_answer", invoke: func(c *Client) (interface{}, error) { return nil, c.SendRawTransaction(transaction.New([]byte{byte(opcode.PUSH1)}, 0)) }, }, { name: "submitblock_bad_server_answer", invoke: func(c *Client) (interface{}, error) { return nil, c.SubmitBlock(block.Block{ Base: block.Base{}, Transactions: nil, Trimmed: false, }) }, }, { name: "validateaddress_bad_server_answer", invoke: func(c *Client) (interface{}, error) { return nil, c.ValidateAddress("AQVh2pG732YvtNaxEGkQUei3YA4cvo7d2i") }, }, }, `{"id":1,"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid Params"}}`: { { name: "getapplicationlog_invalid_params_error", invoke: func(c *Client) (interface{}, error) { return c.GetApplicationLog(util.Uint256{}) }, }, { name: "getbestblockhash_invalid_params_error", invoke: func(c *Client) (interface{}, error) { return c.GetBestBlockHash() }, }, { name: "getblock_byindex_invalid_params_error", invoke: func(c *Client) (interface{}, error) { return c.GetBlockByIndex(1) }, }, { name: "getblock_byindex_verbose_invalid_params_error", invoke: func(c *Client) (interface{}, error) { return c.GetBlockByIndexVerbose(1) }, }, { name: "getblock_byhash_invalid_params_error", invoke: func(c *Client) (interface{}, error) { return c.GetBlockByHash(util.Uint256{}) }, }, { name: "getblock_byhash_verbose_invalid_params_error", invoke: func(c *Client) (interface{}, error) { return c.GetBlockByHashVerbose(util.Uint256{}) }, }, { name: "getblockhash_invalid_params_error", invoke: func(c *Client) (interface{}, error) { return c.GetBlockHash(0) }, }, { name: "getblockheader_invalid_params_error", invoke: func(c *Client) (interface{}, error) { return c.GetBlockHeader(util.Uint256{}) }, }, { name: "getblockheader_verbose_invalid_params_error", invoke: func(c *Client) (interface{}, error) { return c.GetBlockHeaderVerbose(util.Uint256{}) }, }, { name: "getblocksysfee_invalid_params_error", invoke: func(c *Client) (interface{}, error) { return c.GetBlockSysFee(1) }, }, { name: "getconnectioncount_invalid_params_error", invoke: func(c *Client) (interface{}, error) { return c.GetConnectionCount() }, }, { name: "getcontractstate_invalid_params_error", invoke: func(c *Client) (interface{}, error) { return c.GetContractState(util.Uint160{}) }, }, { name: "getnep5balances_invalid_params_error", invoke: func(c *Client) (interface{}, error) { return c.GetNEP5Balances(util.Uint160{}) }, }, { name: "getnep5transfers_invalid_params_error", invoke: func(c *Client) (interface{}, error) { return c.GetNEP5Transfers("") }, }, { name: "getrawtransaction_invalid_params_error", invoke: func(c *Client) (interface{}, error) { return c.GetRawTransaction(util.Uint256{}) }, }, { name: "getrawtransaction_verbose_invalid_params_error", invoke: func(c *Client) (interface{}, error) { return c.GetRawTransactionVerbose(util.Uint256{}) }, }, { name: "getstorage_invalid_params_error", invoke: func(c *Client) (interface{}, error) { return c.GetStorage(util.Uint160{}, []byte{}) }, }, { name: "gettransactionheight_invalid_params_error", invoke: func(c *Client) (interface{}, error) { return c.GetTransactionHeight(util.Uint256{}) }, }, { name: "getunclaimedgas_invalid_params_error", invoke: func(c *Client) (interface{}, error) { return c.GetUnclaimedGas("") }, }, { name: "invokefunction_invalid_params_error", invoke: func(c *Client) (interface{}, error) { return c.InvokeFunction("", "", []smartcontract.Parameter{}) }, }, { name: "invokescript_invalid_params_error", invoke: func(c *Client) (interface{}, error) { return c.InvokeScript("") }, }, { name: "sendrawtransaction_invalid_params_error", invoke: func(c *Client) (interface{}, error) { return nil, c.SendRawTransaction(&transaction.Transaction{}) }, }, { name: "submitblock_invalid_params_error", invoke: func(c *Client) (interface{}, error) { return nil, c.SubmitBlock(block.Block{}) }, }, { name: "validateaddress_invalid_params_error", invoke: func(c *Client) (interface{}, error) { return nil, c.ValidateAddress("") }, }, }, `{}`: { { name: "getapplicationlog_unmarshalling_error", invoke: func(c *Client) (interface{}, error) { return c.GetApplicationLog(util.Uint256{}) }, }, { name: "getbestblockhash_unmarshalling_error", invoke: func(c *Client) (interface{}, error) { return c.GetBestBlockHash() }, }, { name: "getblock_byindex_unmarshalling_error", invoke: func(c *Client) (interface{}, error) { return c.GetBlockByIndex(1) }, }, { name: "getblock_byindex_verbose_unmarshalling_error", invoke: func(c *Client) (interface{}, error) { return c.GetBlockByIndexVerbose(1) }, }, { name: "getblock_byhash_unmarshalling_error", invoke: func(c *Client) (interface{}, error) { return c.GetBlockByHash(util.Uint256{}) }, }, { name: "getblock_byhash_verbose_unmarshalling_error", invoke: func(c *Client) (interface{}, error) { return c.GetBlockByHashVerbose(util.Uint256{}) }, }, { name: "getblockcount_unmarshalling_error", invoke: func(c *Client) (interface{}, error) { return c.GetBlockCount() }, }, { name: "getblockhash_unmarshalling_error", invoke: func(c *Client) (interface{}, error) { return c.GetBlockHash(1) }, }, { name: "getblockheader_unmarshalling_error", invoke: func(c *Client) (interface{}, error) { return c.GetBlockHeader(util.Uint256{}) }, }, { name: "getblockheader_verbose_unmarshalling_error", invoke: func(c *Client) (interface{}, error) { return c.GetBlockHeaderVerbose(util.Uint256{}) }, }, { name: "getblocksysfee_unmarshalling_error", invoke: func(c *Client) (interface{}, error) { return c.GetBlockSysFee(1) }, }, { name: "getconnectioncount_unmarshalling_error", invoke: func(c *Client) (interface{}, error) { return c.GetConnectionCount() }, }, { name: "getcontractstate_unmarshalling_error", invoke: func(c *Client) (interface{}, error) { return c.GetContractState(util.Uint160{}) }, }, { name: "getnep5balances_unmarshalling_error", invoke: func(c *Client) (interface{}, error) { return c.GetNEP5Balances(util.Uint160{}) }, }, { name: "getnep5transfers_unmarshalling_error", invoke: func(c *Client) (interface{}, error) { return c.GetNEP5Transfers("") }, }, { name: "getpeers_unmarshalling_error", invoke: func(c *Client) (interface{}, error) { return c.GetPeers() }, }, { name: "getrawmempool_unmarshalling_error", invoke: func(c *Client) (interface{}, error) { return c.GetRawMemPool() }, }, { name: "getrawtransaction_unmarshalling_error", invoke: func(c *Client) (interface{}, error) { return c.GetRawTransaction(util.Uint256{}) }, }, { name: "getrawtransaction_verbose_unmarshalling_error", invoke: func(c *Client) (interface{}, error) { return c.GetRawTransactionVerbose(util.Uint256{}) }, }, { name: "getstorage_unmarshalling_error", invoke: func(c *Client) (interface{}, error) { return c.GetStorage(util.Uint160{}, []byte{}) }, }, { name: "gettransactionheight_unmarshalling_error", invoke: func(c *Client) (interface{}, error) { return c.GetTransactionHeight(util.Uint256{}) }, }, { name: "getunclaimedgas_unmarshalling_error", invoke: func(c *Client) (interface{}, error) { return c.GetUnclaimedGas("") }, }, { name: "getvalidators_unmarshalling_error", invoke: func(c *Client) (interface{}, error) { return c.GetValidators() }, }, { name: "getversion_unmarshalling_error", invoke: func(c *Client) (interface{}, error) { return c.GetVersion() }, }, { name: "invokefunction_unmarshalling_error", invoke: func(c *Client) (interface{}, error) { return c.InvokeFunction("", "", []smartcontract.Parameter{}) }, }, { name: "invokescript_unmarshalling_error", invoke: func(c *Client) (interface{}, error) { return c.InvokeScript("") }, }, { name: "sendrawtransaction_unmarshalling_error", invoke: func(c *Client) (interface{}, error) { return nil, c.SendRawTransaction(transaction.New([]byte{byte(opcode.PUSH1)}, 0)) }, }, { name: "submitblock_unmarshalling_error", invoke: func(c *Client) (interface{}, error) { return nil, c.SubmitBlock(block.Block{ Base: block.Base{}, Transactions: nil, Trimmed: false, }) }, }, { name: "validateaddress_unmarshalling_error", invoke: func(c *Client) (interface{}, error) { return nil, c.ValidateAddress("") }, }, }, } func TestRPCClients(t *testing.T) { t.Run("Client", func(t *testing.T) { testRPCClient(t, func(ctx context.Context, endpoint string, opts Options) (*Client, error) { return New(ctx, endpoint, opts) }) }) t.Run("WSClient", func(t *testing.T) { testRPCClient(t, func(ctx context.Context, endpoint string, opts Options) (*Client, error) { wsc, err := NewWS(ctx, httpURLtoWS(endpoint), opts) require.NoError(t, err) return &wsc.Client, nil }) }) } func testRPCClient(t *testing.T, newClient func(context.Context, string, Options) (*Client, error)) { for method, testBatch := range rpcClientTestCases { t.Run(method, func(t *testing.T) { for _, testCase := range testBatch { t.Run(testCase.name, func(t *testing.T) { srv := initTestServer(t, testCase.serverResponse) defer srv.Close() endpoint := srv.URL opts := Options{} c, err := newClient(context.TODO(), endpoint, opts) if err != nil { t.Fatal(err) } actual, err := testCase.invoke(c) assert.NoError(t, err) expected := testCase.result(c) if testCase.check == nil { assert.Equal(t, expected, actual) } else { testCase.check(t, c, actual) } }) } }) } for serverResponse, testBatch := range rpcClientErrorCases { srv := initTestServer(t, serverResponse) defer srv.Close() endpoint := srv.URL opts := Options{} c, err := newClient(context.TODO(), endpoint, opts) if err != nil { t.Fatal(err) } for _, testCase := range testBatch { t.Run(testCase.name, func(t *testing.T) { _, err := testCase.invoke(c) assert.Error(t, err) }) } } } func httpURLtoWS(url string) string { return "ws" + strings.TrimPrefix(url, "http") + "/ws" } func initTestServer(t *testing.T, resp string) *httptest.Server { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { if req.URL.Path == "/ws" && req.Method == "GET" { var upgrader = websocket.Upgrader{} ws, err := upgrader.Upgrade(w, req, nil) require.NoError(t, err) for { ws.SetReadDeadline(time.Now().Add(2 * time.Second)) _, _, err = ws.ReadMessage() if err != nil { break } ws.SetWriteDeadline(time.Now().Add(2 * time.Second)) err = ws.WriteMessage(1, []byte(resp)) if err != nil { break } } ws.Close() return } requestHandler(t, w, resp) })) return srv } func requestHandler(t *testing.T, w http.ResponseWriter, resp string) { w.Header().Set("Content-Type", "application/json; charset=utf-8") _, err := w.Write([]byte(resp)) if err != nil { t.Fatalf("Error writing response: %s", err.Error()) } } func TestCalculateValidUntilBlock(t *testing.T) { var ( getBlockCountCalled int getValidatorsCalled int ) srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { r := request.NewIn() err := r.DecodeData(req.Body) if err != nil { t.Fatalf("Cannot decode request body: %s", req.Body) } var response string switch r.Method { case "getblockcount": getBlockCountCalled++ response = `{"jsonrpc":"2.0","id":1,"result":50}` case "getvalidators": getValidatorsCalled++ response = `{"id":1,"jsonrpc":"2.0","result":[{"publickey":"02b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc2","votes":"0","active":true},{"publickey":"02103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e","votes":"0","active":true},{"publickey":"03d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699","votes":"0","active":true},{"publickey":"02a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd62","votes":"0","active":true}]}` default: t.Fatalf("Bad request method: %s", r.Method) } requestHandler(t, w, response) })) defer srv.Close() endpoint := srv.URL opts := Options{} c, err := New(context.TODO(), endpoint, opts) if err != nil { t.Fatal(err) } validUntilBlock, err := c.CalculateValidUntilBlock() assert.NoError(t, err) assert.Equal(t, uint32(54), validUntilBlock) assert.Equal(t, 1, getBlockCountCalled) assert.Equal(t, 1, getValidatorsCalled) // check, whether caching is working validUntilBlock, err = c.CalculateValidUntilBlock() assert.NoError(t, err) assert.Equal(t, uint32(54), validUntilBlock) assert.Equal(t, 2, getBlockCountCalled) assert.Equal(t, 1, getValidatorsCalled) }