From c50f3db6ad75763aed3f5fb4658fdb6defc4abfd Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 14 Oct 2020 16:46:06 +0300 Subject: [PATCH 1/3] rpc, cli: encode script in base64 for Invoke* --- cli/smartcontract/smart_contract.go | 12 ++-------- pkg/rpc/client/rpc.go | 2 +- pkg/rpc/client/rpc_test.go | 34 +++++++++++++++++++---------- pkg/rpc/response/result/invoke.go | 4 ++-- pkg/rpc/server/server.go | 4 ++-- pkg/rpc/server/server_test.go | 15 +++++++------ 6 files changed, 38 insertions(+), 33 deletions(-) diff --git a/cli/smartcontract/smart_contract.go b/cli/smartcontract/smart_contract.go index 231b7bc59..fbd6098be 100644 --- a/cli/smartcontract/smart_contract.go +++ b/cli/smartcontract/smart_contract.go @@ -500,11 +500,7 @@ func invokeInternal(ctx *cli.Context, signAndPush bool) error { fmt.Fprintln(ctx.App.Writer, errText+". Sending transaction...") } if out := ctx.String("out"); out != "" { - script, err := hex.DecodeString(resp.Script) - if err != nil { - return cli.NewExitError(fmt.Errorf("bad script returned from the RPC node: %w", err), 1) - } - tx, err := c.CreateTxFromScript(script, acc, resp.GasConsumed, int64(gas), cosigners...) + tx, err := c.CreateTxFromScript(resp.Script, acc, resp.GasConsumed, int64(gas), cosigners...) if err != nil { return cli.NewExitError(fmt.Errorf("failed to create tx: %w", err), 1) } @@ -518,11 +514,7 @@ func invokeInternal(ctx *cli.Context, signAndPush bool) error { if len(resp.Script) == 0 { return cli.NewExitError(errors.New("no script returned from the RPC node"), 1) } - script, err := hex.DecodeString(resp.Script) - if err != nil { - return cli.NewExitError(fmt.Errorf("bad script returned from the RPC node: %w", err), 1) - } - txHash, err := c.SignAndPushInvocationTx(script, acc, resp.GasConsumed, gas, cosigners) + txHash, err := c.SignAndPushInvocationTx(resp.Script, acc, resp.GasConsumed, gas, cosigners) if err != nil { return cli.NewExitError(fmt.Errorf("failed to push invocation tx: %w", err), 1) } diff --git a/pkg/rpc/client/rpc.go b/pkg/rpc/client/rpc.go index 8331a4f9b..e9122fcde 100644 --- a/pkg/rpc/client/rpc.go +++ b/pkg/rpc/client/rpc.go @@ -381,7 +381,7 @@ func (c *Client) GetVersion() (*result.Version, error) { // InvokeScript returns the result of the given script after running it true the VM. // NOTE: This is a test invoke and will not affect the blockchain. func (c *Client) InvokeScript(script []byte, signers []transaction.Signer) (*result.Invoke, error) { - var p = request.NewRawParams(hex.EncodeToString(script)) + var p = request.NewRawParams(script) return c.invokeSomething("invokescript", p, signers) } diff --git a/pkg/rpc/client/rpc_test.go b/pkg/rpc/client/rpc_test.go index 1d5669ecd..6c58e6075 100644 --- a/pkg/rpc/client/rpc_test.go +++ b/pkg/rpc/client/rpc_test.go @@ -347,7 +347,7 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ invoke: func(c *Client) (interface{}, error) { return c.GetFeePerByte() }, - serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"state":"HALT","gasconsumed":"2007390","script":"10c00c0d676574466565506572427974650c149a61a46eec97b89306d7ce81f15b462091d0093241627d5b52","stack":[{"type":"Integer","value":"1000"}],"tx":null}}`, + serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"state":"HALT","gasconsumed":"2007390","script":"EMAMDWdldEZlZVBlckJ5dGUMFJphpG7sl7iTBtfOgfFbRiCR0AkyQWJ9W1I=","stack":[{"type":"Integer","value":"1000"}],"tx":null}}`, result: func(c *Client) interface{} { return int64(1000) }, @@ -359,7 +359,7 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ invoke: func(c *Client) (interface{}, error) { return c.GetMaxTransactionsPerBlock() }, - serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"state":"HALT","gasconsumed":"2007390","script":"10c00c1a6765744d61785472616e73616374696f6e73506572426c6f636b0c149a61a46eec97b89306d7ce81f15b462091d0093241627d5b52","stack":[{"type":"Integer","value":"512"}],"tx":null}}`, + serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"state":"HALT","gasconsumed":"2007390","script":"EMAMGmdldE1heFRyYW5zYWN0aW9uc1BlckJsb2NrDBSaYaRu7Je4kwbXzoHxW0YgkdAJMkFifVtS","stack":[{"type":"Integer","value":"512"}],"tx":null}}`, result: func(c *Client) interface{} { return int64(512) }, @@ -371,7 +371,7 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ invoke: func(c *Client) (interface{}, error) { return c.GetMaxBlockSize() }, - serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"state":"HALT","gasconsumed":"2007390","script":"10c00c0f6765744d6178426c6f636b53697a650c149a61a46eec97b89306d7ce81f15b462091d0093241627d5b52","stack":[{"type":"Integer","value":"262144"}],"tx":null}}`, + serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"state":"HALT","gasconsumed":"2007390","script":"EMAMD2dldE1heEJsb2NrU2l6ZQwUmmGkbuyXuJMG186B8VtGIJHQCTJBYn1bUg==","stack":[{"type":"Integer","value":"262144"}],"tx":null}}`, result: func(c *Client) interface{} { return int64(262144) }, @@ -383,7 +383,7 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ invoke: func(c *Client) (interface{}, error) { return c.GetBlockedAccounts() }, - serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"state":"HALT","gasconsumed":"2007390","script":"10c00c12676574426c6f636b65644163636f756e74730c149a61a46eec97b89306d7ce81f15b462091d0093241627d5b52","stack":[{"type":"Array","value":[]}],"tx":null}}`, + serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"state":"HALT","gasconsumed":"2007390","script":"EMAMEmdldEJsb2NrZWRBY2NvdW50cwwUmmGkbuyXuJMG186B8VtGIJHQCTJBYn1bUg==","stack":[{"type":"Array","value":[]}],"tx":null}}`, result: func(c *Client) interface{} { return native.BlockedAccounts{} }, @@ -663,7 +663,7 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ Account: util.Uint160{1, 2, 3}, }}) }, - serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"script":"01e8030c14aa8acf859d4fe402b34e673f2156821796a488eb0c14e79eb66d3c134a4a776ee807d2e5b846dda4fdb013c00c087472616e736665720c14e79eb66d3c134a4a776ee807d2e5b846dda4fdb041627d5b5238","state":"HALT","gasconsumed":"31100000","stack":[{"type":"ByteString","value":"JivsCEQy"}],"tx":"000800000080969800000000000204130000000000b004000001aa8acf859d4fe402b34e673f2156821796a488eb01005701e8030c14aa8acf859d4fe402b34e673f2156821796a488eb0c14e79eb66d3c134a4a776ee807d2e5b846dda4fdb013c00c087472616e736665720c14e79eb66d3c134a4a776ee807d2e5b846dda4fdb041627d5b523801420c40d83408774d4bd8b19ae870561d06f2744eb7678f58d1b90cf2f50e98ae83f60b0824e2feeadef6de6418a4cfc43bbc1f916c33ec594cbe662a9d924786e17a14290c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b4195440d78"}}`, + serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"script":"FCaufGyYYexBhGjB8P3Ep/KWPriRUcEJYmFsYW5jZU9mZ74557Vi9gy/4q68o3Wi5e4oc3yv","state":"HALT","gasconsumed":"31100000","stack":[{"type":"ByteString","value":"JivsCEQy"}],"tx":"000800000080969800000000000204130000000000b004000001aa8acf859d4fe402b34e673f2156821796a488eb01005701e8030c14aa8acf859d4fe402b34e673f2156821796a488eb0c14e79eb66d3c134a4a776ee807d2e5b846dda4fdb013c00c087472616e736665720c14e79eb66d3c134a4a776ee807d2e5b846dda4fdb041627d5b523801420c40d83408774d4bd8b19ae870561d06f2744eb7678f58d1b90cf2f50e98ae83f60b0824e2feeadef6de6418a4cfc43bbc1f916c33ec594cbe662a9d924786e17a14290c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b4195440d78"}}`, result: func(c *Client) interface{} { return &result.Invoke{} }, @@ -674,9 +674,13 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ if err != nil { panic(err) } + script, err := base64.StdEncoding.DecodeString("FCaufGyYYexBhGjB8P3Ep/KWPriRUcEJYmFsYW5jZU9mZ74557Vi9gy/4q68o3Wi5e4oc3yv") + if err != nil { + panic(err) + } assert.Equal(t, "HALT", res.State) assert.Equal(t, int64(31100000), res.GasConsumed) - assert.Equal(t, "01e8030c14aa8acf859d4fe402b34e673f2156821796a488eb0c14e79eb66d3c134a4a776ee807d2e5b846dda4fdb013c00c087472616e736665720c14e79eb66d3c134a4a776ee807d2e5b846dda4fdb041627d5b5238", res.Script) + assert.Equal(t, script, res.Script) assert.Equal(t, []stackitem.Item{stackitem.NewByteArray(bytes)}, res.Stack) assert.NotNil(t, res.Transaction) }, @@ -701,7 +705,7 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ Account: util.Uint160{1, 2, 3}, }}) }, - serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"script":"1426ae7c6c9861ec418468c1f0fdc4a7f2963eb89151c10962616c616e63654f6667be39e7b562f60cbfe2aebca375a2e5ee28737caf","state":"FAULT","gasconsumed":"31100000","stack":[{"type":"ByteString","value":"JivsCEQy"}],"tx":"000800000080969800000000000204130000000000b004000001aa8acf859d4fe402b34e673f2156821796a488eb01005701e8030c14aa8acf859d4fe402b34e673f2156821796a488eb0c14e79eb66d3c134a4a776ee807d2e5b846dda4fdb013c00c087472616e736665720c14e79eb66d3c134a4a776ee807d2e5b846dda4fdb041627d5b523801420c40d83408774d4bd8b19ae870561d06f2744eb7678f58d1b90cf2f50e98ae83f60b0824e2feeadef6de6418a4cfc43bbc1f916c33ec594cbe662a9d924786e17a14290c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b4195440d78","exception":"gas limit exceeded"}}`, + serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"script":"FCaufGyYYexBhGjB8P3Ep/KWPriRUcEJYmFsYW5jZU9mZ74557Vi9gy/4q68o3Wi5e4oc3yv","state":"FAULT","gasconsumed":"31100000","stack":[{"type":"ByteString","value":"JivsCEQy"}],"tx":"000800000080969800000000000204130000000000b004000001aa8acf859d4fe402b34e673f2156821796a488eb01005701e8030c14aa8acf859d4fe402b34e673f2156821796a488eb0c14e79eb66d3c134a4a776ee807d2e5b846dda4fdb013c00c087472616e736665720c14e79eb66d3c134a4a776ee807d2e5b846dda4fdb041627d5b523801420c40d83408774d4bd8b19ae870561d06f2744eb7678f58d1b90cf2f50e98ae83f60b0824e2feeadef6de6418a4cfc43bbc1f916c33ec594cbe662a9d924786e17a14290c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b4195440d78","exception":"gas limit exceeded"}}`, result: func(c *Client) interface{} { return &result.Invoke{} }, @@ -712,9 +716,13 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ if err != nil { panic(err) } + script, err := base64.StdEncoding.DecodeString("FCaufGyYYexBhGjB8P3Ep/KWPriRUcEJYmFsYW5jZU9mZ74557Vi9gy/4q68o3Wi5e4oc3yv") + if err != nil { + panic(err) + } assert.Equal(t, "FAULT", res.State) assert.Equal(t, int64(31100000), res.GasConsumed) - assert.Equal(t, "1426ae7c6c9861ec418468c1f0fdc4a7f2963eb89151c10962616c616e63654f6667be39e7b562f60cbfe2aebca375a2e5ee28737caf", res.Script) + assert.Equal(t, script, res.Script) assert.Equal(t, []stackitem.Item{stackitem.NewByteArray(bytes)}, res.Stack) assert.NotNil(t, res.Transaction) }, @@ -724,7 +732,7 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ { name: "positive", invoke: func(c *Client) (interface{}, error) { - script, err := hex.DecodeString("00046e616d656724058e5e1b6008847cd662728549088a9ee82191") + script, err := base64.StdEncoding.DecodeString("AARuYW1lZyQFjl4bYAiEfNZicoVJCIqe6CGR") if err != nil { panic(err) } @@ -732,16 +740,20 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ Account: util.Uint160{1, 2, 3}, }}) }, - serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"script":"00046e616d656724058e5e1b6008847cd662728549088a9ee82191","state":"HALT","gasconsumed":"16100000","stack":[{"type":"ByteString","value":"TkVQNSBHQVM="}],"tx":null}}`, + serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"script":"AARuYW1lZyQFjl4bYAiEfNZicoVJCIqe6CGR","state":"HALT","gasconsumed":"16100000","stack":[{"type":"ByteString","value":"TkVQNSBHQVM="}],"tx":null}}`, result: func(c *Client) interface{} { bytes, err := hex.DecodeString("4e45503520474153") if err != nil { panic(err) } + script, err := base64.StdEncoding.DecodeString("AARuYW1lZyQFjl4bYAiEfNZicoVJCIqe6CGR") + if err != nil { + panic(err) + } return &result.Invoke{ State: "HALT", GasConsumed: 16100000, - Script: "00046e616d656724058e5e1b6008847cd662728549088a9ee82191", + Script: script, Stack: []stackitem.Item{stackitem.NewByteArray(bytes)}, } }, diff --git a/pkg/rpc/response/result/invoke.go b/pkg/rpc/response/result/invoke.go index 52c9d937d..3816ca971 100644 --- a/pkg/rpc/response/result/invoke.go +++ b/pkg/rpc/response/result/invoke.go @@ -18,7 +18,7 @@ import ( type Invoke struct { State string GasConsumed int64 - Script string + Script []byte Stack []stackitem.Item FaultException string // Transaction represents transaction bytes. Use GetTransaction method to decode it. @@ -28,7 +28,7 @@ type Invoke struct { type invokeAux struct { State string `json:"state"` GasConsumed int64 `json:"gasconsumed,string"` - Script string `json:"script"` + Script []byte `json:"script"` Stack json.RawMessage `json:"stack"` FaultException string `json:"exception,omitempty"` Transaction string `json:"tx,omitempty"` diff --git a/pkg/rpc/server/server.go b/pkg/rpc/server/server.go index 5d9f18bac..85630ca8a 100644 --- a/pkg/rpc/server/server.go +++ b/pkg/rpc/server/server.go @@ -977,7 +977,7 @@ func (s *Server) invokescript(reqParams request.Params) (interface{}, *response. return nil, response.ErrInvalidParams } - script, err := reqParams[0].GetBytesHex() + script, err := reqParams[0].GetBytesBase64() if err != nil { return nil, response.ErrInvalidParams } @@ -1011,7 +1011,7 @@ func (s *Server) runScriptInVM(script []byte, tx *transaction.Transaction) *resu result := &result.Invoke{ State: vm.State().String(), GasConsumed: vm.GasConsumed(), - Script: hex.EncodeToString(script), + Script: script, Stack: vm.Estack().ToArray(), FaultException: faultException, } diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index e28863d85..ee294da32 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -60,6 +60,7 @@ const deploymentTxHash = "59f7b22b90e26f883a56b916c1580e3ee4f13caded686353cd7757 const verifyContractHash = "c1213693b22cb0454a436d6e0bd76b8c0a3bfdf7" const verifyContractAVM = "570300412d51083021700c14aa8acf859d4fe402b34e673f2156821796a488ebdb30716813cedb2869db289740" +const testVerifyContractAVM = "VwcADBQBDAMOBQYMDQIODw0DDgcJAAAAANswcGgRVUH4J+yMIaonBwAAABFADBQNDwMCCQACAQMHAwQFAgEADgYMCdswcWkRVUH4J+yMIaonBwAAABJAE0A=" var rpcTestCases = map[string][]rpcTestCase{ "getapplicationlog": { @@ -537,7 +538,7 @@ var rpcTestCases = map[string][]rpcTestCase{ check: func(t *testing.T, e *executor, inv interface{}) { res, ok := inv.(*result.Invoke) require.True(t, ok) - assert.NotEqual(t, "", res.Script) + assert.NotNil(t, res.Script) assert.NotEqual(t, "", res.State) assert.NotEqual(t, 0, res.GasConsumed) }, @@ -566,7 +567,7 @@ var rpcTestCases = map[string][]rpcTestCase{ "invokescript": { { name: "positive", - params: `["51c56b0d48656c6c6f2c20776f726c6421680f4e656f2e52756e74696d652e4c6f67616c7566"]`, + params: `["UcVrDUhlbGxvLCB3b3JsZCFoD05lby5SdW50aW1lLkxvZ2FsdWY="]`, result: func(e *executor) interface{} { return &result.Invoke{} }, check: func(t *testing.T, e *executor, inv interface{}) { res, ok := inv.(*result.Invoke) @@ -578,8 +579,8 @@ var rpcTestCases = map[string][]rpcTestCase{ }, { 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"]]`, + // script is base64-encoded `test_verify.avm` representation, hashes are hex-encoded LE bytes of hashes used in the contract with `0x` prefix + params: fmt.Sprintf(`["%s",["0x0000000009070e030d0f0e020d0c06050e030c01","0x090c060e00010205040307030102000902030f0d"]]`, testVerifyContractAVM), result: func(e *executor) interface{} { return &result.Invoke{} }, check: func(t *testing.T, e *executor, inv interface{}) { res, ok := inv.(*result.Invoke) @@ -591,7 +592,7 @@ var rpcTestCases = map[string][]rpcTestCase{ }, { name: "positive, bad witness of second hash", - params: `["5707000c14010c030e05060c0d020e0f0d030e070900000000db307068115541f827ec8c21aa270700000011400c140d0f03020900020103070304050201000e060c09db307169115541f827ec8c21aa270700000012401340",["0x0000000009070e030d0f0e020d0c06050e030c01"]]`, + params: fmt.Sprintf(`["%s",["0x0000000009070e030d0f0e020d0c06050e030c01"]]`, testVerifyContractAVM), result: func(e *executor) interface{} { return &result.Invoke{} }, check: func(t *testing.T, e *executor, inv interface{}) { res, ok := inv.(*result.Invoke) @@ -603,7 +604,7 @@ var rpcTestCases = map[string][]rpcTestCase{ }, { name: "positive, no good hashes", - params: `["5707000c14010c030e05060c0d020e0f0d030e070900000000db307068115541f827ec8c21aa270700000011400c140d0f03020900020103070304050201000e060c09db307169115541f827ec8c21aa270700000012401340"]`, + params: fmt.Sprintf(`["%s"]`, testVerifyContractAVM), result: func(e *executor) interface{} { return &result.Invoke{} }, check: func(t *testing.T, e *executor, inv interface{}) { res, ok := inv.(*result.Invoke) @@ -615,7 +616,7 @@ var rpcTestCases = map[string][]rpcTestCase{ }, { name: "positive, bad hashes witness", - params: `["5707000c14010c030e05060c0d020e0f0d030e070900000000db307068115541f827ec8c21aa270700000011400c140d0f03020900020103070304050201000e060c09db307169115541f827ec8c21aa270700000012401340",["0x0000000009070e030d0f0e020d0c06050e030c02"]]`, + params: fmt.Sprintf(`["%s",["0x0000000009070e030d0f0e020d0c06050e030c02"]]`, testVerifyContractAVM), result: func(e *executor) interface{} { return &result.Invoke{} }, check: func(t *testing.T, e *executor, inv interface{}) { res, ok := inv.(*result.Invoke) From 474d2dfb658b1e3707ef6a12ab6c9cd3ebe44a04 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 14 Oct 2020 16:52:49 +0300 Subject: [PATCH 2/3] rpc: add Magic to Version --- pkg/rpc/client/rpc_test.go | 3 ++- pkg/rpc/response/result/version.go | 11 +++++++---- pkg/rpc/server/server.go | 1 + 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/pkg/rpc/client/rpc_test.go b/pkg/rpc/client/rpc_test.go index 6c58e6075..bc1a733d0 100644 --- a/pkg/rpc/client/rpc_test.go +++ b/pkg/rpc/client/rpc_test.go @@ -631,9 +631,10 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ invoke: func(c *Client) (interface{}, error) { return c.GetVersion() }, - serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"tcpport":20332,"wsport":20342,"nonce":2153672787,"useragent":"/NEO-GO:0.73.1-pre-273-ge381358/"}}`, + serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"magic":42,"tcpport":20332,"wsport":20342,"nonce":2153672787,"useragent":"/NEO-GO:0.73.1-pre-273-ge381358/"}}`, result: func(c *Client) interface{} { return &result.Version{ + Magic: netmode.UnitTestNet, TCPPort: uint16(20332), WSPort: uint16(20342), Nonce: 2153672787, diff --git a/pkg/rpc/response/result/version.go b/pkg/rpc/response/result/version.go index 3572d6685..4d0fd76d0 100644 --- a/pkg/rpc/response/result/version.go +++ b/pkg/rpc/response/result/version.go @@ -1,12 +1,15 @@ package result +import "github.com/nspcc-dev/neo-go/pkg/config/netmode" + type ( // Version model used for reporting server version // info. Version struct { - TCPPort uint16 `json:"tcpport"` - WSPort uint16 `json:"wsport,omitempty"` - Nonce uint32 `json:"nonce"` - UserAgent string `json:"useragent"` + Magic netmode.Magic `json:"magic"` + TCPPort uint16 `json:"tcpport"` + WSPort uint16 `json:"wsport,omitempty"` + Nonce uint32 `json:"nonce"` + UserAgent string `json:"useragent"` } ) diff --git a/pkg/rpc/server/server.go b/pkg/rpc/server/server.go index 85630ca8a..ed5a4e851 100644 --- a/pkg/rpc/server/server.go +++ b/pkg/rpc/server/server.go @@ -466,6 +466,7 @@ func (s *Server) getVersion(_ request.Params) (interface{}, *response.Error) { return nil, response.NewInternalServerError("Cannot fetch tcp port", err) } return result.Version{ + Magic: s.network, TCPPort: port, Nonce: s.coreServer.ID(), UserAgent: s.coreServer.UserAgent, From 590be7a58da7a2c0c4c8cc54ea030ea6a0e0897d Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 14 Oct 2020 18:13:20 +0300 Subject: [PATCH 3/3] rpc, cli: remove Network from RPC client and cli --- cli/candidate_test.go | 8 +-- cli/contract_test.go | 8 +-- cli/multisig_test.go | 10 ++-- cli/nep5_test.go | 3 +- cli/options/options.go | 10 ++-- cli/options/options_test.go | 16 +++++- cli/smartcontract/smart_contract.go | 3 + cli/wallet_test.go | 10 ++-- pkg/rpc/client/client.go | 19 ++++++- pkg/rpc/client/doc_test.go | 3 +- pkg/rpc/client/nep5.go | 8 ++- pkg/rpc/client/rpc.go | 52 +++++++++++++---- pkg/rpc/client/rpc_test.go | 86 +++++++++++++++++++++++------ pkg/rpc/client/wsclient.go | 6 +- pkg/rpc/client/wsclient_test.go | 25 ++++++--- pkg/rpc/server/client_test.go | 19 ++++--- 16 files changed, 206 insertions(+), 80 deletions(-) diff --git a/cli/candidate_test.go b/cli/candidate_test.go index 4600e2644..4358b992b 100644 --- a/cli/candidate_test.go +++ b/cli/candidate_test.go @@ -17,7 +17,7 @@ func TestRegisterCandidate(t *testing.T) { e.In.WriteString("one\r") e.Run(t, "neo-go", "wallet", "nep5", "multitransfer", - "--unittest", "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addr, "--wallet", validatorWallet, "--from", validatorAddr, "neo:"+validatorPriv.Address()+":10", @@ -26,7 +26,7 @@ func TestRegisterCandidate(t *testing.T) { e.In.WriteString("one\r") e.Run(t, "neo-go", "wallet", "candidate", "register", - "--unittest", "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addr, "--wallet", validatorWallet, "--address", validatorPriv.Address()) e.checkTxPersisted(t) @@ -40,7 +40,7 @@ func TestRegisterCandidate(t *testing.T) { t.Run("Vote", func(t *testing.T) { e.In.WriteString("one\r") e.Run(t, "neo-go", "wallet", "candidate", "vote", - "--unittest", "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addr, "--wallet", validatorWallet, "--address", validatorPriv.Address(), "--candidate", hex.EncodeToString(validatorPriv.PublicKey().Bytes())) @@ -55,7 +55,7 @@ func TestRegisterCandidate(t *testing.T) { e.In.WriteString("one\r") e.Run(t, "neo-go", "wallet", "candidate", "unregister", - "--unittest", "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addr, "--wallet", validatorWallet, "--address", validatorPriv.Address()) e.checkTxPersisted(t) diff --git a/cli/contract_test.go b/cli/contract_test.go index 99c93ea4b..002274a9f 100644 --- a/cli/contract_test.go +++ b/cli/contract_test.go @@ -40,7 +40,7 @@ func TestComlileAndInvokeFunction(t *testing.T) { e.In.WriteString("one\r") e.Run(t, "neo-go", "contract", "deploy", - "--unittest", "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addr, "--wallet", validatorWallet, "--address", validatorAddr, "--in", nefName, "--manifest", manifestName) @@ -53,7 +53,7 @@ func TestComlileAndInvokeFunction(t *testing.T) { e.In.WriteString("one\r") e.Run(t, "neo-go", "contract", "testinvokefunction", - "--unittest", "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addr, h.StringLE(), "getValue") res := new(result.Invoke) @@ -84,7 +84,7 @@ func TestComlileAndInvokeFunction(t *testing.T) { e.In.WriteString("one\r") e.Run(t, "neo-go", "contract", "invokefunction", - "--unittest", "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addr, "--wallet", validatorWallet, "--address", validatorAddr, h.StringLE(), "update", "bytes:"+hex.EncodeToString(realNef.Script), @@ -94,7 +94,7 @@ func TestComlileAndInvokeFunction(t *testing.T) { e.In.WriteString("one\r") e.Run(t, "neo-go", "contract", "testinvokefunction", - "--unittest", "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addr, hash.Hash160(realNef.Script).StringLE(), "getValue") res := new(result.Invoke) diff --git a/cli/multisig_test.go b/cli/multisig_test.go index 1e3e87093..66d093067 100644 --- a/cli/multisig_test.go +++ b/cli/multisig_test.go @@ -53,7 +53,7 @@ func TestSignMultisigTx(t *testing.T) { // Transfer funds to the multisig. e.In.WriteString("one\r") e.Run(t, "neo-go", "wallet", "nep5", "multitransfer", - "--unittest", "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addr, "--wallet", validatorWallet, "--from", validatorAddr, "neo:"+multisigAddr+":4", @@ -68,14 +68,14 @@ func TestSignMultisigTx(t *testing.T) { defer os.Remove(txPath) e.In.WriteString("pass\r") e.Run(t, "neo-go", "wallet", "nep5", "transfer", - "--unittest", "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addr, "--wallet", wallet1Path, "--from", multisigAddr, "--to", priv.Address(), "--token", "neo", "--amount", "1", "--out", txPath) e.In.WriteString("pass\r") e.Run(t, "neo-go", "wallet", "multisig", "sign", - "--unittest", "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addr, "--wallet", wallet2Path, "--address", multisigAddr, "--in", txPath, "--out", txPath) e.checkTxPersisted(t) @@ -88,7 +88,7 @@ func TestSignMultisigTx(t *testing.T) { t.Run("via invokefunction", func(t *testing.T) { e.In.WriteString("pass\r") e.Run(t, "neo-go", "contract", "invokefunction", - "--unittest", "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addr, "--wallet", wallet1Path, "--address", multisigAddr, "--out", txPath, client.NeoContractHash.StringLE(), "transfer", @@ -99,7 +99,7 @@ func TestSignMultisigTx(t *testing.T) { e.In.WriteString("pass\r") e.Run(t, "neo-go", "wallet", "multisig", "sign", - "--unittest", "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addr, "--wallet", wallet2Path, "--address", multisigAddr, "--in", txPath, "--out", txPath) e.checkTxPersisted(t) diff --git a/cli/nep5_test.go b/cli/nep5_test.go index 3c07ffceb..cdd7e8b42 100644 --- a/cli/nep5_test.go +++ b/cli/nep5_test.go @@ -109,7 +109,6 @@ func TestNEP5Transfer(t *testing.T) { defer e.Close(t) args := []string{ "neo-go", "wallet", "nep5", "transfer", - "--unittest", "--rpc-endpoint", "http://" + e.RPC.Addr, "--wallet", validatorWallet, "--from", validatorAddr, @@ -141,7 +140,7 @@ func TestNEP5MultiTransfer(t *testing.T) { defer e.Close(t) args := []string{ "neo-go", "wallet", "nep5", "multitransfer", - "--unittest", "--rpc-endpoint", "http://" + e.RPC.Addr, + "--rpc-endpoint", "http://" + e.RPC.Addr, "--wallet", validatorWallet, "--from", validatorAddr, "neo:" + privs[0].Address() + ":42", diff --git a/cli/options/options.go b/cli/options/options.go index 19f4c7e4e..56a65db50 100644 --- a/cli/options/options.go +++ b/cli/options/options.go @@ -34,10 +34,6 @@ var RPC = []cli.Flag{ Name: "timeout, s", Usage: "Timeout for the operation (10 seconds by default)", }, - cli.BoolFlag{Name: "privnet, p"}, - cli.BoolFlag{Name: "mainnet, m"}, - cli.BoolFlag{Name: "testnet, t"}, - cli.BoolFlag{Name: "unittest", Hidden: true}, } var errNoEndpoint = errors.New("no RPC endpoint specified, use option '--" + RPCEndpointFlag + "' or '-r'") @@ -73,7 +69,11 @@ func GetRPCClient(gctx context.Context, ctx *cli.Context) (*client.Client, cli.E if len(endpoint) == 0 { return nil, cli.NewExitError(errNoEndpoint, 1) } - c, err := client.New(gctx, endpoint, client.Options{Network: GetNetwork(ctx)}) + c, err := client.New(gctx, endpoint, client.Options{}) + if err != nil { + return nil, cli.NewExitError(err, 1) + } + err = c.Init() if err != nil { return nil, cli.NewExitError(err, 1) } diff --git a/cli/options/options_test.go b/cli/options/options_test.go index bd92c6416..ba0c48ecf 100644 --- a/cli/options/options_test.go +++ b/cli/options/options_test.go @@ -2,10 +2,13 @@ package options import ( "flag" + "net/http" + "net/http/httptest" "testing" "time" "github.com/nspcc-dev/neo-go/pkg/config/netmode" + "github.com/stretchr/testify/require" "github.com/urfave/cli" ) @@ -56,6 +59,17 @@ func TestGetTimeoutContext(t *testing.T) { } func TestGetRPCClient(t *testing.T) { + // need test server for proper client.Init() handling + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + response := `{"id":1,"jsonrpc":"2.0","result":{"magic":42,"tcpport":20332,"wsport":20342,"nonce":2153672787,"useragent":"/NEO-GO:0.73.1-pre-273-ge381358/"}}` + + _, err := w.Write([]byte(response)) + if err != nil { + t.Fatalf("Error writing response: %s", err.Error()) + } + })) + defer srv.Close() t.Run("no endpoint", func(t *testing.T) { set := flag.NewFlagSet("flagSet", flag.ExitOnError) ctx := cli.NewContext(cli.NewApp(), set, nil) @@ -66,7 +80,7 @@ func TestGetRPCClient(t *testing.T) { t.Run("success", func(t *testing.T) { set := flag.NewFlagSet("flagSet", flag.ExitOnError) - set.String(RPCEndpointFlag, "http://localhost:50333", "") + set.String(RPCEndpointFlag, srv.URL, "") ctx := cli.NewContext(cli.NewApp(), set, nil) gctx, _ := GetTimeoutContext(ctx) _, ec := GetRPCClient(gctx, ctx) diff --git a/cli/smartcontract/smart_contract.go b/cli/smartcontract/smart_contract.go index fbd6098be..bb4054401 100644 --- a/cli/smartcontract/smart_contract.go +++ b/cli/smartcontract/smart_contract.go @@ -487,6 +487,9 @@ func invokeInternal(ctx *cli.Context, signAndPush bool) error { if err != nil { return err } + if err = c.Init(); err != nil { + return err + } resp, err = c.InvokeFunction(script, operation, params, cosigners) if err != nil { diff --git a/cli/wallet_test.go b/cli/wallet_test.go index b2707f257..bae2449d6 100644 --- a/cli/wallet_test.go +++ b/cli/wallet_test.go @@ -176,7 +176,7 @@ func TestClaimGas(t *testing.T) { balanceBefore := e.Chain.GetUtilityTokenBalance(validatorHash) e.In.WriteString("one\r") e.Run(t, "neo-go", "wallet", "claim", - "--unittest", "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addr, "--wallet", validatorWallet, "--address", validatorAddr) tx, end := e.checkTxPersisted(t) @@ -195,7 +195,7 @@ func TestImportDeployed(t *testing.T) { e.In.WriteString("one\r") e.Run(t, "neo-go", "contract", "deploy", - "--unittest", "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addr, "--wallet", validatorWallet, "--address", validatorAddr, "--in", "testdata/verify.nef", "--manifest", "testdata/verify.manifest.json") @@ -217,7 +217,7 @@ func TestImportDeployed(t *testing.T) { e.In.WriteString("acc\rpass\rpass\r") e.Run(t, "neo-go", "wallet", "import-deployed", - "--unittest", "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addr, "--wallet", walletPath, "--wif", priv.WIF(), "--contract", h.StringLE()) @@ -232,7 +232,7 @@ func TestImportDeployed(t *testing.T) { t.Run("Sign", func(t *testing.T) { e.In.WriteString("one\r") e.Run(t, "neo-go", "wallet", "nep5", "multitransfer", - "--unittest", "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addr, "--wallet", validatorWallet, "--from", validatorAddr, "neo:"+contractAddr+":10", "gas:"+contractAddr+":10") @@ -243,7 +243,7 @@ func TestImportDeployed(t *testing.T) { e.In.WriteString("pass\r") e.Run(t, "neo-go", "wallet", "nep5", "transfer", - "--unittest", "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addr, "--wallet", walletPath, "--from", contractAddr, "--to", privTo.Address(), "--token", "neo", "--amount", "1") e.checkTxPersisted(t) diff --git a/pkg/rpc/client/client.go b/pkg/rpc/client/client.go index 1eae743fa..44fe25f29 100644 --- a/pkg/rpc/client/client.go +++ b/pkg/rpc/client/client.go @@ -29,6 +29,8 @@ const ( type Client struct { cli *http.Client endpoint *url.URL + network netmode.Magic + initDone bool ctx context.Context opts Options requestF func(*request.Raw) (*response.Raw, error) @@ -46,7 +48,6 @@ type Options struct { CACert string DialTimeout time.Duration RequestTimeout time.Duration - Network netmode.Magic } // cache stores cache values for the RPC client methods @@ -61,7 +62,8 @@ type calculateValidUntilBlockCache struct { expiresAt uint32 } -// New returns a new Client ready to use. +// New returns a new Client ready to use. You should call Init method to +// initialize network magic the client is operating on. func New(ctx context.Context, endpoint string, opts Options) (*Client, error) { url, err := url.Parse(endpoint) if err != nil { @@ -99,6 +101,19 @@ func New(ctx context.Context, endpoint string, opts Options) (*Client, error) { return cl, nil } +// Init sets magic of the network client connected to. This method should be called +// before any transaction-, header- or block-related requests in order to deserialize +// responses properly. +func (c *Client) Init() error { + version, err := c.GetVersion() + if err != nil { + return fmt.Errorf("failed to get network magic: %w", err) + } + c.network = version.Magic + c.initDone = true + return nil +} + func (c *Client) performRequest(method string, p request.RawParams, v interface{}) error { var r = request.Raw{ JSONRPC: request.JSONRPCVersion, diff --git a/pkg/rpc/client/doc_test.go b/pkg/rpc/client/doc_test.go index e3db66617..496ca8d4d 100644 --- a/pkg/rpc/client/doc_test.go +++ b/pkg/rpc/client/doc_test.go @@ -5,14 +5,13 @@ import ( "fmt" "os" - "github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/rpc/client" ) func Example() { endpoint := "http://seed5.bridgeprotocol.io:10332" - opts := client.Options{Network: netmode.MainNet} + opts := client.Options{} c, err := client.New(context.TODO(), endpoint, opts) if err != nil { diff --git a/pkg/rpc/client/nep5.go b/pkg/rpc/client/nep5.go index e92ee09ca..3de9fbae7 100644 --- a/pkg/rpc/client/nep5.go +++ b/pkg/rpc/client/nep5.go @@ -152,7 +152,8 @@ func (c *Client) CreateNEP5MultiTransferTx(acc *wallet.Account, gas int64, recip } // CreateTxFromScript creates transaction and properly sets cosigners and NetworkFee. -// If sysFee <= 0, it is determined via result of `invokescript` RPC. +// If sysFee <= 0, it is determined via result of `invokescript` RPC. You should +// initialize network magic with Init before calling CreateTxFromScript. func (c *Client) CreateTxFromScript(script []byte, acc *wallet.Account, sysFee, netFee int64, cosigners ...transaction.Signer) (*transaction.Transaction, error) { from, err := address.StringToUint160(acc.Address) @@ -172,7 +173,10 @@ func (c *Client) CreateTxFromScript(script []byte, acc *wallet.Account, sysFee, sysFee = result.GasConsumed } - tx := transaction.New(c.opts.Network, script, sysFee) + if !c.initDone { + return nil, errNetworkNotInitialized + } + tx := transaction.New(c.GetNetwork(), script, sysFee) tx.Signers = signers tx.ValidUntilBlock, err = c.CalculateValidUntilBlock() diff --git a/pkg/rpc/client/rpc.go b/pkg/rpc/client/rpc.go index e9122fcde..33a596c01 100644 --- a/pkg/rpc/client/rpc.go +++ b/pkg/rpc/client/rpc.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" + "github.com/nspcc-dev/neo-go/pkg/config/netmode" "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/fee" @@ -20,6 +21,8 @@ import ( "github.com/nspcc-dev/neo-go/pkg/wallet" ) +var errNetworkNotInitialized = errors.New("RPC client network is not initialized") + // GetApplicationLog returns the contract log based on the specified txid. func (c *Client) GetApplicationLog(hash util.Uint256) (*state.AppExecResult, error) { var ( @@ -50,12 +53,14 @@ func (c *Client) GetBlockCount() (uint32, error) { return resp, nil } -// GetBlockByIndex returns a block by its height. +// GetBlockByIndex returns a block by its height. You should initialize network magic +// with Init before calling GetBlockByIndex. func (c *Client) GetBlockByIndex(index uint32) (*block.Block, error) { return c.getBlock(request.NewRawParams(index)) } -// GetBlockByHash returns a block by its hash. +// GetBlockByHash returns a block by its hash. You should initialize network magic +// with Init before calling GetBlockByHash. func (c *Client) GetBlockByHash(hash util.Uint256) (*block.Block, error) { return c.getBlock(request.NewRawParams(hash.StringLE())) } @@ -66,6 +71,9 @@ func (c *Client) getBlock(params request.RawParams) (*block.Block, error) { err error b *block.Block ) + if !c.initDone { + return nil, errNetworkNotInitialized + } if err = c.performRequest("getblock", params, &resp); err != nil { return nil, err } @@ -74,7 +82,7 @@ func (c *Client) getBlock(params request.RawParams) (*block.Block, error) { return nil, err } r := io.NewBinReaderFromBuf(blockBytes) - b = block.New(c.opts.Network) + b = block.New(c.GetNetwork()) b.DecodeBinary(r) if r.Err != nil { return nil, r.Err @@ -83,14 +91,14 @@ func (c *Client) getBlock(params request.RawParams) (*block.Block, error) { } // GetBlockByIndexVerbose returns a block wrapper with additional metadata by -// its height. +// its height. You should initialize network magic with Init before calling GetBlockByIndexVerbose. // NOTE: to get transaction.ID and transaction.Size, use t.Hash() and io.GetVarSize(t) respectively. func (c *Client) GetBlockByIndexVerbose(index uint32) (*result.Block, error) { return c.getBlockVerbose(request.NewRawParams(index, 1)) } // GetBlockByHashVerbose returns a block wrapper with additional metadata by -// its hash. +// its hash. You should initialize network magic with Init before calling GetBlockByHashVerbose. func (c *Client) GetBlockByHashVerbose(hash util.Uint256) (*result.Block, error) { return c.getBlockVerbose(request.NewRawParams(hash.StringLE(), 1)) } @@ -100,7 +108,10 @@ func (c *Client) getBlockVerbose(params request.RawParams) (*result.Block, error resp = &result.Block{} err error ) - resp.Network = c.opts.Network + if !c.initDone { + return nil, errNetworkNotInitialized + } + resp.Network = c.GetNetwork() if err = c.performRequest("getblock", params, resp); err != nil { return nil, err } @@ -120,13 +131,17 @@ func (c *Client) GetBlockHash(index uint32) (util.Uint256, error) { } // GetBlockHeader returns the corresponding block header information from serialized hex string -// according to the specified script hash. +// according to the specified script hash. You should initialize network magic +// // with Init before calling GetBlockHeader. func (c *Client) GetBlockHeader(hash util.Uint256) (*block.Header, error) { var ( params = request.NewRawParams(hash.StringLE()) resp string h *block.Header ) + if !c.initDone { + return nil, errNetworkNotInitialized + } if err := c.performRequest("getblockheader", params, &resp); err != nil { return nil, err } @@ -136,7 +151,7 @@ func (c *Client) GetBlockHeader(hash util.Uint256) (*block.Header, error) { } r := io.NewBinReaderFromBuf(headerBytes) h = new(block.Header) - h.Network = c.opts.Network + h.Network = c.GetNetwork() h.DecodeBinary(r) if r.Err != nil { return nil, r.Err @@ -271,13 +286,17 @@ func (c *Client) GetRawMemPool() ([]util.Uint256, error) { return *resp, nil } -// GetRawTransaction returns a transaction by hash. +// GetRawTransaction returns a transaction by hash. You should initialize network magic +// with Init before calling GetRawTransaction. func (c *Client) GetRawTransaction(hash util.Uint256) (*transaction.Transaction, error) { var ( params = request.NewRawParams(hash.StringLE()) resp string err error ) + if !c.initDone { + return nil, errNetworkNotInitialized + } if err = c.performRequest("getrawtransaction", params, &resp); err != nil { return nil, err } @@ -285,7 +304,7 @@ func (c *Client) GetRawTransaction(hash util.Uint256) (*transaction.Transaction, if err != nil { return nil, err } - tx, err := transaction.NewTransactionFromBytes(c.opts.Network, txBytes) + tx, err := transaction.NewTransactionFromBytes(c.GetNetwork(), txBytes) if err != nil { return nil, err } @@ -293,7 +312,8 @@ func (c *Client) GetRawTransaction(hash util.Uint256) (*transaction.Transaction, } // GetRawTransactionVerbose returns a transaction wrapper with additional -// metadata by transaction's hash. +// metadata by transaction's hash. You should initialize network magic +// with Init before calling GetRawTransactionVerbose. // NOTE: to get transaction.ID and transaction.Size, use t.Hash() and io.GetVarSize(t) respectively. func (c *Client) GetRawTransactionVerbose(hash util.Uint256) (*result.TransactionOutputRaw, error) { var ( @@ -301,7 +321,10 @@ func (c *Client) GetRawTransactionVerbose(hash util.Uint256) (*result.Transactio resp = &result.TransactionOutputRaw{} err error ) - resp.Network = c.opts.Network + if !c.initDone { + return nil, errNetworkNotInitialized + } + resp.Network = c.GetNetwork() if err = c.performRequest("getrawtransaction", params, resp); err != nil { return nil, err } @@ -569,3 +592,8 @@ func (c *Client) AddNetworkFee(tx *transaction.Transaction, extraFee int64, accs tx.NetworkFee += int64(size)*fee + extraFee return nil } + +// GetNetwork returns the network magic of the RPC node client connected to. +func (c *Client) GetNetwork() netmode.Magic { + return c.network +} diff --git a/pkg/rpc/client/rpc_test.go b/pkg/rpc/client/rpc_test.go index bc1a733d0..1be95bba9 100644 --- a/pkg/rpc/client/rpc_test.go +++ b/pkg/rpc/client/rpc_test.go @@ -4,6 +4,7 @@ import ( "context" "encoding/base64" "encoding/hex" + "encoding/json" "fmt" "math/big" "net/http" @@ -1247,12 +1248,6 @@ var rpcClientErrorCases = map[string][]rpcClientErrorCase{ return c.GetNextBlockValidators() }, }, - { - name: "getversion_unmarshalling_error", - invoke: func(c *Client) (interface{}, error) { - return c.GetVersion() - }, - }, { name: "invokefunction_unmarshalling_error", invoke: func(c *Client) (interface{}, error) { @@ -1293,13 +1288,17 @@ var rpcClientErrorCases = map[string][]rpcClientErrorCase{ 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) + c, err := New(ctx, endpoint, opts) + require.NoError(t, err) + require.NoError(t, c.Init()) + return c, nil }) }) 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) + require.NoError(t, wsc.Init()) return &wsc.Client, nil }) }) @@ -1314,7 +1313,7 @@ func testRPCClient(t *testing.T, newClient func(context.Context, string, Options defer srv.Close() endpoint := srv.URL - opts := Options{Network: netmode.UnitTestNet} + opts := Options{} c, err := newClient(context.TODO(), endpoint, opts) if err != nil { t.Fatal(err) @@ -1338,7 +1337,7 @@ func testRPCClient(t *testing.T, newClient func(context.Context, string, Options defer srv.Close() endpoint := srv.URL - opts := Options{Network: netmode.UnitTestNet} + opts := Options{} c, err := newClient(context.TODO(), endpoint, opts) if err != nil { t.Fatal(err) @@ -1365,12 +1364,24 @@ func initTestServer(t *testing.T, resp string) *httptest.Server { require.NoError(t, err) for { ws.SetReadDeadline(time.Now().Add(2 * time.Second)) - _, _, err = ws.ReadMessage() + _, p, err := ws.ReadMessage() if err != nil { break } + r := request.NewIn() + err = json.Unmarshal(p, r) + if err != nil { + t.Fatalf("Cannot decode request body: %s", req.Body) + } + var response string + switch r.Method { + case "getversion": + response = `{"id":1,"jsonrpc":"2.0","result":{"magic":42,"tcpport":20332,"wsport":20342,"nonce":2153672787,"useragent":"/NEO-GO:0.73.1-pre-273-ge381358/"}}` + default: + response = resp + } ws.SetWriteDeadline(time.Now().Add(2 * time.Second)) - err = ws.WriteMessage(1, []byte(resp)) + err = ws.WriteMessage(1, []byte(response)) if err != nil { break } @@ -1378,16 +1389,27 @@ func initTestServer(t *testing.T, resp string) *httptest.Server { ws.Close() return } - requestHandler(t, w, resp) + r := request.NewIn() + err := r.DecodeData(req.Body) + if err != nil { + t.Fatalf("Cannot decode request body: %s", req.Body) + } + requestHandler(t, r.Method, w, resp) })) return srv } -func requestHandler(t *testing.T, w http.ResponseWriter, resp string) { +func requestHandler(t *testing.T, method string, w http.ResponseWriter, resp string) { w.Header().Set("Content-Type", "application/json; charset=utf-8") - _, err := w.Write([]byte(resp)) - + var response string + switch method { + case "getversion": + response = `{"id":1,"jsonrpc":"2.0","result":{"magic":42,"tcpport":20332,"wsport":20342,"nonce":2153672787,"useragent":"/NEO-GO:0.73.1-pre-273-ge381358/"}}` + default: + response = resp + } + _, err := w.Write([]byte(response)) if err != nil { t.Fatalf("Error writing response: %s", err.Error()) } @@ -1412,10 +1434,8 @@ func TestCalculateValidUntilBlock(t *testing.T) { case "getnextblockvalidators": 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) + requestHandler(t, r.Method, w, response) })) defer srv.Close() @@ -1425,6 +1445,7 @@ func TestCalculateValidUntilBlock(t *testing.T) { if err != nil { t.Fatal(err) } + require.NoError(t, c.Init()) validUntilBlock, err := c.CalculateValidUntilBlock() assert.NoError(t, err) @@ -1439,3 +1460,32 @@ func TestCalculateValidUntilBlock(t *testing.T) { assert.Equal(t, 2, getBlockCountCalled) assert.Equal(t, 1, getValidatorsCalled) } + +func TestGetNetwork(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + // request handler already have `getversion` response wrapper + requestHandler(t, "getversion", w, "") + })) + defer srv.Close() + endpoint := srv.URL + opts := Options{} + + t.Run("bad", func(t *testing.T) { + c, err := New(context.TODO(), endpoint, opts) + if err != nil { + t.Fatal(err) + } + // network was not initialised + require.Equal(t, netmode.Magic(0), c.GetNetwork()) + require.Equal(t, false, c.initDone) + }) + + t.Run("good", func(t *testing.T) { + c, err := New(context.TODO(), endpoint, opts) + if err != nil { + t.Fatal(err) + } + require.NoError(t, c.Init()) + require.Equal(t, netmode.UnitTestNet, c.GetNetwork()) + }) +} diff --git a/pkg/rpc/client/wsclient.go b/pkg/rpc/client/wsclient.go index 9b7a7eb73..266257ef1 100644 --- a/pkg/rpc/client/wsclient.go +++ b/pkg/rpc/client/wsclient.go @@ -69,6 +69,8 @@ const ( // NewWS returns a new WSClient ready to use (with established websocket // connection). You need to use websocket URL for it like `ws://1.2.3.4/ws`. +// You should call Init method to initialize network magic the client is +// operating on. func NewWS(ctx context.Context, endpoint string, opts Options) (*WSClient, error) { cl, err := New(ctx, endpoint, opts) if err != nil { @@ -137,9 +139,9 @@ readloop: var val interface{} switch event { case response.BlockEventID: - val = block.New(c.opts.Network) + val = block.New(c.GetNetwork()) case response.TransactionEventID: - val = &transaction.Transaction{Network: c.opts.Network} + val = &transaction.Transaction{Network: c.GetNetwork()} case response.NotificationEventID: val = new(state.NotificationEvent) case response.ExecutionEventID: diff --git a/pkg/rpc/client/wsclient_test.go b/pkg/rpc/client/wsclient_test.go index 7af7b8ca5..d9fdd30ee 100644 --- a/pkg/rpc/client/wsclient_test.go +++ b/pkg/rpc/client/wsclient_test.go @@ -18,7 +18,7 @@ import ( func TestWSClientClose(t *testing.T) { srv := initTestServer(t, "") defer srv.Close() - wsc, err := NewWS(context.TODO(), httpURLtoWS(srv.URL), Options{Network: netmode.UnitTestNet}) + wsc, err := NewWS(context.TODO(), httpURLtoWS(srv.URL), Options{}) require.NoError(t, err) wsc.Close() } @@ -43,8 +43,9 @@ func TestWSClientSubscription(t *testing.T) { t.Run(name, func(t *testing.T) { srv := initTestServer(t, `{"jsonrpc": "2.0", "id": 1, "result": "55aaff00"}`) defer srv.Close() - wsc, err := NewWS(context.TODO(), httpURLtoWS(srv.URL), Options{Network: netmode.UnitTestNet}) + wsc, err := NewWS(context.TODO(), httpURLtoWS(srv.URL), Options{}) require.NoError(t, err) + require.NoError(t, wsc.Init()) id, err := f(wsc) require.NoError(t, err) require.Equal(t, "55aaff00", id) @@ -56,8 +57,9 @@ func TestWSClientSubscription(t *testing.T) { t.Run(name, func(t *testing.T) { srv := initTestServer(t, `{"jsonrpc": "2.0", "id": 1, "error":{"code":-32602,"message":"Invalid Params"}}`) defer srv.Close() - wsc, err := NewWS(context.TODO(), httpURLtoWS(srv.URL), Options{Network: netmode.UnitTestNet}) + wsc, err := NewWS(context.TODO(), httpURLtoWS(srv.URL), Options{}) require.NoError(t, err) + require.NoError(t, wsc.Init()) _, err = f(wsc) require.Error(t, err) }) @@ -105,8 +107,9 @@ func TestWSClientUnsubscription(t *testing.T) { t.Run(name, func(t *testing.T) { srv := initTestServer(t, rc.response) defer srv.Close() - wsc, err := NewWS(context.TODO(), httpURLtoWS(srv.URL), Options{Network: netmode.UnitTestNet}) + wsc, err := NewWS(context.TODO(), httpURLtoWS(srv.URL), Options{}) require.NoError(t, err) + require.NoError(t, wsc.Init()) rc.code(t, wsc) }) } @@ -139,8 +142,9 @@ func TestWSClientEvents(t *testing.T) { } })) - wsc, err := NewWS(context.TODO(), httpURLtoWS(srv.URL), Options{Network: netmode.UnitTestNet}) + wsc, err := NewWS(context.TODO(), httpURLtoWS(srv.URL), Options{}) require.NoError(t, err) + wsc.network = netmode.UnitTestNet for range events { select { case _, ok = <-wsc.Notifications: @@ -162,8 +166,9 @@ func TestWSExecutionVMStateCheck(t *testing.T) { // Will answer successfully if request slips through. srv := initTestServer(t, `{"jsonrpc": "2.0", "id": 1, "result": "55aaff00"}`) defer srv.Close() - wsc, err := NewWS(context.TODO(), httpURLtoWS(srv.URL), Options{Network: netmode.UnitTestNet}) + wsc, err := NewWS(context.TODO(), httpURLtoWS(srv.URL), Options{}) require.NoError(t, err) + require.NoError(t, wsc.Init()) filter := "NONE" _, err = wsc.SubscribeForTransactionExecutions(&filter) require.Error(t, err) @@ -326,8 +331,9 @@ func TestWSFilteredSubscriptions(t *testing.T) { } })) defer srv.Close() - wsc, err := NewWS(context.TODO(), httpURLtoWS(srv.URL), Options{Network: netmode.UnitTestNet}) + wsc, err := NewWS(context.TODO(), httpURLtoWS(srv.URL), Options{}) require.NoError(t, err) + wsc.network = netmode.UnitTestNet c.clientCode(t, wsc) wsc.Close() }) @@ -339,11 +345,12 @@ func TestNewWS(t *testing.T) { defer srv.Close() t.Run("good", func(t *testing.T) { - _, err := NewWS(context.TODO(), httpURLtoWS(srv.URL), Options{Network: netmode.UnitTestNet}) + c, err := NewWS(context.TODO(), httpURLtoWS(srv.URL), Options{}) require.NoError(t, err) + require.NoError(t, c.Init()) }) t.Run("bad URL", func(t *testing.T) { - _, err := NewWS(context.TODO(), strings.Trim(srv.URL, "http://"), Options{Network: netmode.UnitTestNet}) + _, err := NewWS(context.TODO(), strings.Trim(srv.URL, "http://"), Options{}) require.Error(t, err) }) } diff --git a/pkg/rpc/server/client_test.go b/pkg/rpc/server/client_test.go index 11f845ead..f5e8adc96 100644 --- a/pkg/rpc/server/client_test.go +++ b/pkg/rpc/server/client_test.go @@ -5,7 +5,6 @@ import ( "encoding/hex" "testing" - "github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/core/fee" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/crypto/hash" @@ -25,8 +24,9 @@ func TestClient_NEP5(t *testing.T) { defer chain.Close() defer rpcSrv.Shutdown() - c, err := client.New(context.Background(), httpSrv.URL, client.Options{Network: netmode.UnitTestNet}) + c, err := client.New(context.Background(), httpSrv.URL, client.Options{}) require.NoError(t, err) + require.NoError(t, c.Init()) h, err := util.Uint160DecodeStringLE(testContractHash) require.NoError(t, err) @@ -72,8 +72,9 @@ func TestAddNetworkFee(t *testing.T) { defer chain.Close() defer rpcSrv.Shutdown() - c, err := client.New(context.Background(), httpSrv.URL, client.Options{Network: testchain.Network()}) + c, err := client.New(context.Background(), httpSrv.URL, client.Options{}) require.NoError(t, err) + require.NoError(t, c.Init()) getAccounts := func(t *testing.T, n int) []*wallet.Account { accs := make([]*wallet.Account, n) @@ -199,8 +200,9 @@ func TestSignAndPushInvocationTx(t *testing.T) { defer chain.Close() defer rpcSrv.Shutdown() - c, err := client.New(context.Background(), httpSrv.URL, client.Options{Network: testchain.Network()}) + c, err := client.New(context.Background(), httpSrv.URL, client.Options{}) require.NoError(t, err) + require.NoError(t, c.Init()) priv := testchain.PrivateKey(0) acc, err := wallet.NewAccountFromWIF(priv.WIF()) @@ -222,8 +224,9 @@ func TestPing(t *testing.T) { chain, rpcSrv, httpSrv := initServerWithInMemoryChain(t) defer chain.Close() - c, err := client.New(context.Background(), httpSrv.URL, client.Options{Network: testchain.Network()}) + c, err := client.New(context.Background(), httpSrv.URL, client.Options{}) require.NoError(t, err) + require.NoError(t, c.Init()) require.NoError(t, c.Ping()) require.NoError(t, rpcSrv.Shutdown()) @@ -236,8 +239,9 @@ func TestCreateTxFromScript(t *testing.T) { defer chain.Close() defer rpcSrv.Shutdown() - c, err := client.New(context.Background(), httpSrv.URL, client.Options{Network: testchain.Network()}) + c, err := client.New(context.Background(), httpSrv.URL, client.Options{}) require.NoError(t, err) + require.NoError(t, c.Init()) priv := testchain.PrivateKey(0) acc, err := wallet.NewAccountFromWIF(priv.WIF()) @@ -265,8 +269,9 @@ func TestCreateNEP5TransferTx(t *testing.T) { defer chain.Close() defer rpcSrv.Shutdown() - c, err := client.New(context.Background(), httpSrv.URL, client.Options{Network: testchain.Network()}) + c, err := client.New(context.Background(), httpSrv.URL, client.Options{}) require.NoError(t, err) + require.NoError(t, c.Init()) priv := testchain.PrivateKeyByID(0) acc, err := wallet.NewAccountFromWIF(priv.WIF())