From 336a94456ff73fecebb2c029bf158beb1224cf0d Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 9 Jan 2020 20:44:27 +0300 Subject: [PATCH 01/22] rpc: make blockHeightFromParam a pointer method Makes no sense copying Server here. --- pkg/rpc/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/rpc/server.go b/pkg/rpc/server.go index 0ed89cb6e..183019e67 100644 --- a/pkg/rpc/server.go +++ b/pkg/rpc/server.go @@ -549,7 +549,7 @@ func (s *Server) sendrawtransaction(reqParams Params) (interface{}, error) { return results, resultsErr } -func (s Server) blockHeightFromParam(param *Param) (int, error) { +func (s *Server) blockHeightFromParam(param *Param) (int, error) { num, err := param.GetInt() if err != nil { return 0, nil From d92e193e63929717c1c90c868821b88b59e202b2 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 10 Jan 2020 15:13:29 +0300 Subject: [PATCH 02/22] rpc/network: refactor getpeers logic Deduplicate and simplify code. --- pkg/network/server.go | 13 +++++++++ pkg/rpc/result/peers.go | 64 +++++++++++++++++++++-------------------- pkg/rpc/server.go | 17 +++-------- 3 files changed, 50 insertions(+), 44 deletions(-) diff --git a/pkg/network/server.go b/pkg/network/server.go index dacac6461..3772a6018 100644 --- a/pkg/network/server.go +++ b/pkg/network/server.go @@ -198,6 +198,19 @@ func (s *Server) BadPeers() []string { return []string{} } +// ConnectedPeers returns a list of currently connected peers. +func (s *Server) ConnectedPeers() []string { + s.lock.RLock() + defer s.lock.RUnlock() + + peers := make([]string, 0, len(s.peers)) + for k := range s.peers { + peers = append(peers, k.PeerAddr().String()) + } + + return peers +} + // run is a goroutine that starts another goroutine to manage protocol specifics // while itself dealing with peers management (handling connects/disconnects). func (s *Server) run() { diff --git a/pkg/rpc/result/peers.go b/pkg/rpc/result/peers.go index 98442acaa..b18acc0e8 100644 --- a/pkg/rpc/result/peers.go +++ b/pkg/rpc/result/peers.go @@ -5,13 +5,16 @@ import ( ) type ( - // Peers payload for outputting peers in `getpeers` RPC call. - Peers struct { - Unconnected []Peer `json:"unconnected"` - Connected []Peer `json:"connected"` - Bad []Peer `json:"bad"` + // GetPeers payload for outputting peers in `getpeers` RPC call. + GetPeers struct { + Unconnected Peers `json:"unconnected"` + Connected Peers `json:"connected"` + Bad Peers `json:"bad"` } + // Peers represent a slice of peers. + Peers []Peer + // Peer represents the peer. Peer struct { Address string `json:"address"` @@ -19,40 +22,39 @@ type ( } ) -// NewPeers creates a new Peers struct. -func NewPeers() Peers { - return Peers{ +// NewGetPeers creates a new GetPeers structure. +func NewGetPeers() GetPeers { + return GetPeers{ Unconnected: []Peer{}, Connected: []Peer{}, Bad: []Peer{}, } } -// AddPeer adds a peer to the given peer type slice. -func (p *Peers) AddPeer(peerType string, addr string) { - addressParts := strings.Split(addr, ":") - peer := Peer{ - Address: addressParts[0], - Port: addressParts[1], - } +// AddUnconnected adds a set of peers to the unconnected peers slice. +func (g *GetPeers) AddUnconnected(addrs []string) { + g.Unconnected.addPeers(addrs) +} - switch peerType { - case "unconnected": - p.Unconnected = append( - p.Unconnected, - peer, - ) +// AddConnected adds a set of peers to the connected peers slice. +func (g *GetPeers) AddConnected(addrs []string) { + g.Connected.addPeers(addrs) +} - case "connected": - p.Connected = append( - p.Connected, - peer, - ) +// AddBad adds a set of peers to the bad peers slice. +func (g *GetPeers) AddBad(addrs []string) { + g.Bad.addPeers(addrs) +} - case "bad": - p.Bad = append( - p.Bad, - peer, - ) +// addPeers adds a set of peers to the given peer slice. +func (p *Peers) addPeers(addrs []string) { + for i := range addrs { + addressParts := strings.Split(addrs[i], ":") + peer := Peer{ + Address: addressParts[0], + Port: addressParts[1], + } + + *p = append(*p, peer) } } diff --git a/pkg/rpc/server.go b/pkg/rpc/server.go index 183019e67..1ebb345b6 100644 --- a/pkg/rpc/server.go +++ b/pkg/rpc/server.go @@ -192,19 +192,10 @@ Methods: case "getpeers": getpeersCalled.Inc() - peers := result.NewPeers() - for _, addr := range s.coreServer.UnconnectedPeers() { - peers.AddPeer("unconnected", addr) - } - - for _, addr := range s.coreServer.BadPeers() { - peers.AddPeer("bad", addr) - } - - for addr := range s.coreServer.Peers() { - peers.AddPeer("connected", addr.PeerAddr().String()) - } - + peers := result.NewGetPeers() + peers.AddUnconnected(s.coreServer.UnconnectedPeers()) + peers.AddConnected(s.coreServer.ConnectedPeers()) + peers.AddBad(s.coreServer.BadPeers()) results = peers case "getstorage": From 723b33e1087486c1cd830d202a1554b2a4448463 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 10 Jan 2020 15:16:14 +0300 Subject: [PATCH 03/22] network: implement unconnected/bad peers getters Which allows node to respond to `getpeers` RPC request correctly. --- pkg/network/server.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/network/server.go b/pkg/network/server.go index 3772a6018..cfe241d9f 100644 --- a/pkg/network/server.go +++ b/pkg/network/server.go @@ -190,12 +190,12 @@ func (s *Server) Shutdown() { // UnconnectedPeers returns a list of peers that are in the discovery peer list // but are not connected to the server. func (s *Server) UnconnectedPeers() []string { - return []string{} + return s.discovery.UnconnectedPeers() } // BadPeers returns a list of peers the are flagged as "bad" peers. func (s *Server) BadPeers() []string { - return []string{} + return s.discovery.BadPeers() } // ConnectedPeers returns a list of currently connected peers. From 72a62f1292037f0f82382a94d14d40ff62539a1f Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 10 Jan 2020 15:24:32 +0300 Subject: [PATCH 04/22] result: add trivial test for peers management functions --- pkg/rpc/result/peers_test.go | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 pkg/rpc/result/peers_test.go diff --git a/pkg/rpc/result/peers_test.go b/pkg/rpc/result/peers_test.go new file mode 100644 index 000000000..6e5a9339e --- /dev/null +++ b/pkg/rpc/result/peers_test.go @@ -0,0 +1,26 @@ +package result + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGetPeers(t *testing.T) { + gp := NewGetPeers() + require.Equal(t, 0, len(gp.Unconnected)) + require.Equal(t, 0, len(gp.Connected)) + require.Equal(t, 0, len(gp.Bad)) + + gp.AddUnconnected([]string{"1.1.1.1:53", "8.8.8.8:53", "9.9.9.9:53"}) + gp.AddConnected([]string{"192.168.0.1:10333"}) + gp.AddBad([]string{"127.0.0.1:20333"}) + + require.Equal(t, 3, len(gp.Unconnected)) + require.Equal(t, 1, len(gp.Connected)) + require.Equal(t, 1, len(gp.Bad)) + require.Equal(t, "192.168.0.1", gp.Connected[0].Address) + require.Equal(t, "10333", gp.Connected[0].Port) + require.Equal(t, "127.0.0.1", gp.Bad[0].Address) + require.Equal(t, "20333", gp.Bad[0].Port) +} From bfa2d54e16a17613944a4f2d15d4c1836c487a30 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 13 Jan 2020 11:27:22 +0300 Subject: [PATCH 05/22] rpc: move validateAddress() function from wrappers into server It's a server implementation detail, it has nothing to do with the data format itself. It also makes no sense exporing it. --- pkg/rpc/server.go | 14 +++++++++++++- pkg/rpc/wrappers/validate_address.go | 19 +++---------------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/pkg/rpc/server.go b/pkg/rpc/server.go index 1ebb345b6..880d3959b 100644 --- a/pkg/rpc/server.go +++ b/pkg/rpc/server.go @@ -11,6 +11,7 @@ import ( "github.com/CityOfZion/neo-go/pkg/core" "github.com/CityOfZion/neo-go/pkg/core/state" "github.com/CityOfZion/neo-go/pkg/core/transaction" + "github.com/CityOfZion/neo-go/pkg/encoding/address" "github.com/CityOfZion/neo-go/pkg/io" "github.com/CityOfZion/neo-go/pkg/network" "github.com/CityOfZion/neo-go/pkg/rpc/result" @@ -209,7 +210,7 @@ Methods: resultsErr = errInvalidParams break Methods } - results = wrappers.ValidateAddress(param.Value) + results = validateAddress(param.Value) case "getassetstate": getassetstateCalled.Inc() @@ -551,3 +552,14 @@ func (s *Server) blockHeightFromParam(param *Param) (int, error) { } return num, nil } + +// validateAddress verifies that the address is a correct NEO address +// see https://docs.neo.org/en-us/node/cli/2.9.4/api/validateaddress.html +func validateAddress(addr interface{}) wrappers.ValidateAddressResponse { + resp := wrappers.ValidateAddressResponse{Address: addr} + if addr, ok := addr.(string); ok { + _, err := address.StringToUint160(addr) + resp.IsValid = (err == nil) + } + return resp +} diff --git a/pkg/rpc/wrappers/validate_address.go b/pkg/rpc/wrappers/validate_address.go index dfde7b833..3af2c0d23 100644 --- a/pkg/rpc/wrappers/validate_address.go +++ b/pkg/rpc/wrappers/validate_address.go @@ -1,22 +1,9 @@ package wrappers -import ( - "github.com/CityOfZion/neo-go/pkg/encoding/address" -) - -// ValidateAddressResponse represents response to validate address call. +// ValidateAddressResponse represents response to validate address call. Notice +// Address is an interface{} here because server echoes back whatever address +// value user has sent to it, even if it's not a string. type ValidateAddressResponse struct { Address interface{} `json:"address"` IsValid bool `json:"isvalid"` } - -// ValidateAddress verifies that the address is a correct NEO address -// see https://docs.neo.org/en-us/node/cli/2.9.4/api/validateaddress.html -func ValidateAddress(addr interface{}) ValidateAddressResponse { - resp := ValidateAddressResponse{Address: addr} - if addr, ok := addr.(string); ok { - _, err := address.StringToUint160(addr) - resp.IsValid = err == nil - } - return resp -} From c189062f405f84238f4bffbf84800b4dc2a096ac Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 13 Jan 2020 11:33:04 +0300 Subject: [PATCH 06/22] rpc: move ValidateAddress to the result package Because it is a result of the RPC call. --- pkg/rpc/{wrappers => result}/validate_address.go | 6 +++--- pkg/rpc/server.go | 4 ++-- pkg/rpc/server_helper_test.go | 6 +++--- pkg/rpc/server_test.go | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) rename pkg/rpc/{wrappers => result}/validate_address.go (61%) diff --git a/pkg/rpc/wrappers/validate_address.go b/pkg/rpc/result/validate_address.go similarity index 61% rename from pkg/rpc/wrappers/validate_address.go rename to pkg/rpc/result/validate_address.go index 3af2c0d23..bb4963893 100644 --- a/pkg/rpc/wrappers/validate_address.go +++ b/pkg/rpc/result/validate_address.go @@ -1,9 +1,9 @@ -package wrappers +package result -// ValidateAddressResponse represents response to validate address call. Notice +// ValidateAddress represents result of the `validateaddress` call. Notice that // Address is an interface{} here because server echoes back whatever address // value user has sent to it, even if it's not a string. -type ValidateAddressResponse struct { +type ValidateAddress struct { Address interface{} `json:"address"` IsValid bool `json:"isvalid"` } diff --git a/pkg/rpc/server.go b/pkg/rpc/server.go index 880d3959b..47f186c9d 100644 --- a/pkg/rpc/server.go +++ b/pkg/rpc/server.go @@ -555,8 +555,8 @@ func (s *Server) blockHeightFromParam(param *Param) (int, error) { // validateAddress verifies that the address is a correct NEO address // see https://docs.neo.org/en-us/node/cli/2.9.4/api/validateaddress.html -func validateAddress(addr interface{}) wrappers.ValidateAddressResponse { - resp := wrappers.ValidateAddressResponse{Address: addr} +func validateAddress(addr interface{}) result.ValidateAddress { + resp := result.ValidateAddress{Address: addr} if addr, ok := addr.(string); ok { _, err := address.StringToUint160(addr) resp.IsValid = (err == nil) diff --git a/pkg/rpc/server_helper_test.go b/pkg/rpc/server_helper_test.go index 5451d4e1b..b3350c82f 100644 --- a/pkg/rpc/server_helper_test.go +++ b/pkg/rpc/server_helper_test.go @@ -50,9 +50,9 @@ type InvokeFunctionResponse struct { // ValidateAddrResponse struct for testing. type ValidateAddrResponse struct { - Jsonrpc string `json:"jsonrpc"` - Result wrappers.ValidateAddressResponse `json:"result"` - ID int `json:"id"` + Jsonrpc string `json:"jsonrpc"` + Result result.ValidateAddress `json:"result"` + ID int `json:"id"` } // GetPeersResponse struct for testing. diff --git a/pkg/rpc/server_test.go b/pkg/rpc/server_test.go index 71826a559..5720299ea 100644 --- a/pkg/rpc/server_test.go +++ b/pkg/rpc/server_test.go @@ -14,7 +14,7 @@ import ( "github.com/CityOfZion/neo-go/pkg/core" "github.com/CityOfZion/neo-go/pkg/core/transaction" - "github.com/CityOfZion/neo-go/pkg/rpc/wrappers" + "github.com/CityOfZion/neo-go/pkg/rpc/result" "github.com/CityOfZion/neo-go/pkg/util" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -527,7 +527,7 @@ var rpcTestCases = map[string][]rpcTestCase{ result: func(*executor) interface{} { return &ValidateAddrResponse{ Jsonrpc: defaultJSONRPC, - Result: wrappers.ValidateAddressResponse{ + Result: result.ValidateAddress{ Address: float64(1), IsValid: false, }, From b8f7ab8e6a6fce13d59ffc6a5c36c4a4e763fe12 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 13 Jan 2020 12:27:34 +0300 Subject: [PATCH 07/22] rpc: move Invoke to result package It's just a data. --- pkg/rpc/{wrappers/invoke_result.go => result/invoke.go} | 7 ++++--- pkg/rpc/server.go | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) rename pkg/rpc/{wrappers/invoke_result.go => result/invoke.go} (52%) diff --git a/pkg/rpc/wrappers/invoke_result.go b/pkg/rpc/result/invoke.go similarity index 52% rename from pkg/rpc/wrappers/invoke_result.go rename to pkg/rpc/result/invoke.go index 4cc790bf5..f79d4d365 100644 --- a/pkg/rpc/wrappers/invoke_result.go +++ b/pkg/rpc/result/invoke.go @@ -1,11 +1,12 @@ -package wrappers +package result import ( "github.com/CityOfZion/neo-go/pkg/vm" ) -// InvokeResult is used as a wrapper to represent an invokation result. -type InvokeResult struct { +// Invoke represents code invocation result and is used by several RPC calls +// that invoke functions, scripts and generic bytecode. +type Invoke struct { State string `json:"state"` GasConsumed string `json:"gas_consumed"` Script string `json:"script"` diff --git a/pkg/rpc/server.go b/pkg/rpc/server.go index 47f186c9d..ae8b87a00 100644 --- a/pkg/rpc/server.go +++ b/pkg/rpc/server.go @@ -486,12 +486,12 @@ func (s *Server) invokescript(reqParams Params) (interface{}, error) { // runScriptInVM runs given script in a new test VM and returns the invocation // result. -func (s *Server) runScriptInVM(script []byte) *wrappers.InvokeResult { +func (s *Server) runScriptInVM(script []byte) *result.Invoke { vm, _ := s.chain.GetTestVM() vm.SetGasLimit(s.config.MaxGasInvoke) vm.LoadScript(script) _ = vm.Run() - result := &wrappers.InvokeResult{ + result := &result.Invoke{ State: vm.State(), GasConsumed: vm.GasConsumed().String(), Script: hex.EncodeToString(script), From 1801e545a0af44c62b02da84a2e5029550f4da58 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 13 Jan 2020 13:21:44 +0300 Subject: [PATCH 08/22] rpc: remove wrappers package, move things to result These are all RPC call results, `wrappers` package doesn't make much sense to me. --- pkg/rpc/neoScanBalanceGetter.go | 4 ++-- pkg/rpc/{wrappers => result}/account_state.go | 2 +- pkg/rpc/{wrappers => result}/asset_state.go | 2 +- pkg/rpc/{wrappers => result}/block.go | 2 +- pkg/rpc/{wrappers => result}/contract_state.go | 2 +- pkg/rpc/{wrappers => result}/tx_output.go | 2 +- pkg/rpc/{wrappers => result}/tx_raw_output.go | 2 +- pkg/rpc/{wrappers => result}/unspents.go | 2 +- pkg/rpc/server.go | 17 ++++++++--------- pkg/rpc/server_helper_test.go | 7 +++---- pkg/rpc/types.go | 8 ++++---- 11 files changed, 24 insertions(+), 26 deletions(-) rename pkg/rpc/{wrappers => result}/account_state.go (98%) rename pkg/rpc/{wrappers => result}/asset_state.go (98%) rename pkg/rpc/{wrappers => result}/block.go (99%) rename pkg/rpc/{wrappers => result}/contract_state.go (99%) rename pkg/rpc/{wrappers => result}/tx_output.go (97%) rename pkg/rpc/{wrappers => result}/tx_raw_output.go (98%) rename pkg/rpc/{wrappers => result}/unspents.go (98%) diff --git a/pkg/rpc/neoScanBalanceGetter.go b/pkg/rpc/neoScanBalanceGetter.go index c0e95c595..432a24c0e 100644 --- a/pkg/rpc/neoScanBalanceGetter.go +++ b/pkg/rpc/neoScanBalanceGetter.go @@ -8,7 +8,7 @@ import ( "github.com/CityOfZion/neo-go/pkg/core/state" "github.com/CityOfZion/neo-go/pkg/core/transaction" - "github.com/CityOfZion/neo-go/pkg/rpc/wrappers" + "github.com/CityOfZion/neo-go/pkg/rpc/result" "github.com/CityOfZion/neo-go/pkg/util" errs "github.com/pkg/errors" ) @@ -57,7 +57,7 @@ func (s NeoScanServer) CalculateInputs(address string, assetIDUint util.Uint256, err error us []*Unspent assetUnspent Unspent - assetID = wrappers.GlobalAssets[assetIDUint.StringLE()] + assetID = result.GlobalAssets[assetIDUint.StringLE()] ) if us, err = s.GetBalance(address); err != nil { return nil, util.Fixed8(0), errs.Wrapf(err, "Cannot get balance for address %v", address) diff --git a/pkg/rpc/wrappers/account_state.go b/pkg/rpc/result/account_state.go similarity index 98% rename from pkg/rpc/wrappers/account_state.go rename to pkg/rpc/result/account_state.go index b56981e38..df1dad2b9 100644 --- a/pkg/rpc/wrappers/account_state.go +++ b/pkg/rpc/result/account_state.go @@ -1,4 +1,4 @@ -package wrappers +package result import ( "bytes" diff --git a/pkg/rpc/wrappers/asset_state.go b/pkg/rpc/result/asset_state.go similarity index 98% rename from pkg/rpc/wrappers/asset_state.go rename to pkg/rpc/result/asset_state.go index 5ee365a97..ffedbb6bb 100644 --- a/pkg/rpc/wrappers/asset_state.go +++ b/pkg/rpc/result/asset_state.go @@ -1,4 +1,4 @@ -package wrappers +package result import ( "github.com/CityOfZion/neo-go/pkg/core/state" diff --git a/pkg/rpc/wrappers/block.go b/pkg/rpc/result/block.go similarity index 99% rename from pkg/rpc/wrappers/block.go rename to pkg/rpc/result/block.go index a203437ff..aa8a20377 100644 --- a/pkg/rpc/wrappers/block.go +++ b/pkg/rpc/result/block.go @@ -1,4 +1,4 @@ -package wrappers +package result import ( "strconv" diff --git a/pkg/rpc/wrappers/contract_state.go b/pkg/rpc/result/contract_state.go similarity index 99% rename from pkg/rpc/wrappers/contract_state.go rename to pkg/rpc/result/contract_state.go index ca1b11716..08e8a277a 100644 --- a/pkg/rpc/wrappers/contract_state.go +++ b/pkg/rpc/result/contract_state.go @@ -1,4 +1,4 @@ -package wrappers +package result import ( "github.com/CityOfZion/neo-go/pkg/core/state" diff --git a/pkg/rpc/wrappers/tx_output.go b/pkg/rpc/result/tx_output.go similarity index 97% rename from pkg/rpc/wrappers/tx_output.go rename to pkg/rpc/result/tx_output.go index e624dff8e..19a6fc66b 100644 --- a/pkg/rpc/wrappers/tx_output.go +++ b/pkg/rpc/result/tx_output.go @@ -1,4 +1,4 @@ -package wrappers +package result import ( "github.com/CityOfZion/neo-go/pkg/core/transaction" diff --git a/pkg/rpc/wrappers/tx_raw_output.go b/pkg/rpc/result/tx_raw_output.go similarity index 98% rename from pkg/rpc/wrappers/tx_raw_output.go rename to pkg/rpc/result/tx_raw_output.go index cdc2723cb..f36b7ab97 100644 --- a/pkg/rpc/wrappers/tx_raw_output.go +++ b/pkg/rpc/result/tx_raw_output.go @@ -1,4 +1,4 @@ -package wrappers +package result import ( "github.com/CityOfZion/neo-go/pkg/core" diff --git a/pkg/rpc/wrappers/unspents.go b/pkg/rpc/result/unspents.go similarity index 98% rename from pkg/rpc/wrappers/unspents.go rename to pkg/rpc/result/unspents.go index 38c14b901..d903f8a30 100644 --- a/pkg/rpc/wrappers/unspents.go +++ b/pkg/rpc/result/unspents.go @@ -1,4 +1,4 @@ -package wrappers +package result import ( "github.com/CityOfZion/neo-go/pkg/core" diff --git a/pkg/rpc/server.go b/pkg/rpc/server.go index ae8b87a00..4a098a535 100644 --- a/pkg/rpc/server.go +++ b/pkg/rpc/server.go @@ -15,7 +15,6 @@ import ( "github.com/CityOfZion/neo-go/pkg/io" "github.com/CityOfZion/neo-go/pkg/network" "github.com/CityOfZion/neo-go/pkg/rpc/result" - "github.com/CityOfZion/neo-go/pkg/rpc/wrappers" "github.com/CityOfZion/neo-go/pkg/util" "github.com/pkg/errors" "go.uber.org/zap" @@ -153,7 +152,7 @@ Methods: } if len(reqParams) == 2 && reqParams[1].Value == 1 { - results = wrappers.NewBlock(block, s.chain) + results = result.NewBlock(block, s.chain) } else { writer := io.NewBufBinWriter() block.EncodeBinary(writer.BinWriter) @@ -228,7 +227,7 @@ Methods: as := s.chain.GetAssetState(paramAssetID) if as != nil { - results = wrappers.NewAssetState(as) + results = result.NewAssetState(as) } else { resultsErr = NewRPCError("Unknown asset", "", nil) } @@ -334,10 +333,10 @@ func (s *Server) getrawtransaction(reqParams Params) (interface{}, error) { if v == 0 || v == "0" || v == 0.0 || v == false || v == "false" { results = hex.EncodeToString(tx.Bytes()) } else { - results = wrappers.NewTransactionOutputRaw(tx, header, s.chain) + results = result.NewTransactionOutputRaw(tx, header, s.chain) } default: - results = wrappers.NewTransactionOutputRaw(tx, header, s.chain) + results = result.NewTransactionOutputRaw(tx, header, s.chain) } } else { results = hex.EncodeToString(tx.Bytes()) @@ -377,7 +376,7 @@ func (s *Server) getTxOut(ps Params) (interface{}, error) { } out := tx.Outputs[num] - return wrappers.NewTxOutput(&out), nil + return result.NewTxOutput(&out), nil } // getContractState returns contract state (contract information, according to the contract script hash). @@ -392,7 +391,7 @@ func (s *Server) getContractState(reqParams Params) (interface{}, error) { } else { cs := s.chain.GetContractState(scriptHash) if cs != nil { - results = wrappers.NewContractState(cs) + results = result.NewContractState(cs) } else { return nil, NewRPCError("Unknown contract", "", nil) } @@ -420,9 +419,9 @@ func (s *Server) getAccountState(reqParams Params, unspents bool) (interface{}, if err != nil { return nil, errInvalidParams } - results = wrappers.NewUnspents(as, s.chain, str) + results = result.NewUnspents(as, s.chain, str) } else { - results = wrappers.NewAccountState(as) + results = result.NewAccountState(as) } } return results, resultsErr diff --git a/pkg/rpc/server_helper_test.go b/pkg/rpc/server_helper_test.go index b3350c82f..ef4035efd 100644 --- a/pkg/rpc/server_helper_test.go +++ b/pkg/rpc/server_helper_test.go @@ -12,7 +12,6 @@ import ( "github.com/CityOfZion/neo-go/pkg/io" "github.com/CityOfZion/neo-go/pkg/network" "github.com/CityOfZion/neo-go/pkg/rpc/result" - "github.com/CityOfZion/neo-go/pkg/rpc/wrappers" "github.com/CityOfZion/neo-go/pkg/util" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" @@ -89,9 +88,9 @@ type StringResultResponse struct { // GetBlockResponse struct for testing. type GetBlockResponse struct { - Jsonrpc string `json:"jsonrpc"` - Result wrappers.Block `json:"result"` - ID int `json:"id"` + Jsonrpc string `json:"jsonrpc"` + Result result.Block `json:"result"` + ID int `json:"id"` } // GetAssetResponse struct for testing. diff --git a/pkg/rpc/types.go b/pkg/rpc/types.go index 50beee7d5..3284bf873 100644 --- a/pkg/rpc/types.go +++ b/pkg/rpc/types.go @@ -2,7 +2,7 @@ package rpc import ( "github.com/CityOfZion/neo-go/pkg/core/transaction" - "github.com/CityOfZion/neo-go/pkg/rpc/wrappers" + "github.com/CityOfZion/neo-go/pkg/rpc/result" "github.com/CityOfZion/neo-go/pkg/vm" ) @@ -31,8 +31,8 @@ type AccountStateResponse struct { // UnspentResponse represents server response to the `getunspents` command. type UnspentResponse struct { responseHeader - Error *Error `json:"error,omitempty"` - Result *wrappers.Unspents `json:"result,omitempty"` + Error *Error `json:"error,omitempty"` + Result *result.Unspents `json:"result,omitempty"` } // Account represents details about a NEO account. @@ -100,7 +100,7 @@ type GetRawTxResponse struct { type GetTxOutResponse struct { responseHeader Error *Error - Result *wrappers.TransactionOutput + Result *result.TransactionOutput } // RawTxResponse stores transaction with blockchain metadata to be sent as a response. From 69e1ad512f2c010696a6d369c2062690e738dc6f Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 13 Jan 2020 18:20:30 +0300 Subject: [PATCH 09/22] rpc: move result package into response `response` is to be populated soon. --- pkg/rpc/neoScanBalanceGetter.go | 2 +- pkg/rpc/{ => response}/result/account_state.go | 0 pkg/rpc/{ => response}/result/asset_state.go | 0 pkg/rpc/{ => response}/result/block.go | 0 pkg/rpc/{ => response}/result/contract_state.go | 0 pkg/rpc/{ => response}/result/invoke.go | 0 pkg/rpc/{ => response}/result/peers.go | 0 pkg/rpc/{ => response}/result/peers_test.go | 0 pkg/rpc/{ => response}/result/tx_output.go | 0 pkg/rpc/{ => response}/result/tx_raw_output.go | 0 pkg/rpc/{ => response}/result/unspents.go | 0 pkg/rpc/{ => response}/result/validate_address.go | 0 pkg/rpc/{ => response}/result/version.go | 0 pkg/rpc/server.go | 2 +- pkg/rpc/server_helper_test.go | 2 +- pkg/rpc/server_test.go | 2 +- pkg/rpc/types.go | 2 +- 17 files changed, 5 insertions(+), 5 deletions(-) rename pkg/rpc/{ => response}/result/account_state.go (100%) rename pkg/rpc/{ => response}/result/asset_state.go (100%) rename pkg/rpc/{ => response}/result/block.go (100%) rename pkg/rpc/{ => response}/result/contract_state.go (100%) rename pkg/rpc/{ => response}/result/invoke.go (100%) rename pkg/rpc/{ => response}/result/peers.go (100%) rename pkg/rpc/{ => response}/result/peers_test.go (100%) rename pkg/rpc/{ => response}/result/tx_output.go (100%) rename pkg/rpc/{ => response}/result/tx_raw_output.go (100%) rename pkg/rpc/{ => response}/result/unspents.go (100%) rename pkg/rpc/{ => response}/result/validate_address.go (100%) rename pkg/rpc/{ => response}/result/version.go (100%) diff --git a/pkg/rpc/neoScanBalanceGetter.go b/pkg/rpc/neoScanBalanceGetter.go index 432a24c0e..8d9ef5181 100644 --- a/pkg/rpc/neoScanBalanceGetter.go +++ b/pkg/rpc/neoScanBalanceGetter.go @@ -8,7 +8,7 @@ import ( "github.com/CityOfZion/neo-go/pkg/core/state" "github.com/CityOfZion/neo-go/pkg/core/transaction" - "github.com/CityOfZion/neo-go/pkg/rpc/result" + "github.com/CityOfZion/neo-go/pkg/rpc/response/result" "github.com/CityOfZion/neo-go/pkg/util" errs "github.com/pkg/errors" ) diff --git a/pkg/rpc/result/account_state.go b/pkg/rpc/response/result/account_state.go similarity index 100% rename from pkg/rpc/result/account_state.go rename to pkg/rpc/response/result/account_state.go diff --git a/pkg/rpc/result/asset_state.go b/pkg/rpc/response/result/asset_state.go similarity index 100% rename from pkg/rpc/result/asset_state.go rename to pkg/rpc/response/result/asset_state.go diff --git a/pkg/rpc/result/block.go b/pkg/rpc/response/result/block.go similarity index 100% rename from pkg/rpc/result/block.go rename to pkg/rpc/response/result/block.go diff --git a/pkg/rpc/result/contract_state.go b/pkg/rpc/response/result/contract_state.go similarity index 100% rename from pkg/rpc/result/contract_state.go rename to pkg/rpc/response/result/contract_state.go diff --git a/pkg/rpc/result/invoke.go b/pkg/rpc/response/result/invoke.go similarity index 100% rename from pkg/rpc/result/invoke.go rename to pkg/rpc/response/result/invoke.go diff --git a/pkg/rpc/result/peers.go b/pkg/rpc/response/result/peers.go similarity index 100% rename from pkg/rpc/result/peers.go rename to pkg/rpc/response/result/peers.go diff --git a/pkg/rpc/result/peers_test.go b/pkg/rpc/response/result/peers_test.go similarity index 100% rename from pkg/rpc/result/peers_test.go rename to pkg/rpc/response/result/peers_test.go diff --git a/pkg/rpc/result/tx_output.go b/pkg/rpc/response/result/tx_output.go similarity index 100% rename from pkg/rpc/result/tx_output.go rename to pkg/rpc/response/result/tx_output.go diff --git a/pkg/rpc/result/tx_raw_output.go b/pkg/rpc/response/result/tx_raw_output.go similarity index 100% rename from pkg/rpc/result/tx_raw_output.go rename to pkg/rpc/response/result/tx_raw_output.go diff --git a/pkg/rpc/result/unspents.go b/pkg/rpc/response/result/unspents.go similarity index 100% rename from pkg/rpc/result/unspents.go rename to pkg/rpc/response/result/unspents.go diff --git a/pkg/rpc/result/validate_address.go b/pkg/rpc/response/result/validate_address.go similarity index 100% rename from pkg/rpc/result/validate_address.go rename to pkg/rpc/response/result/validate_address.go diff --git a/pkg/rpc/result/version.go b/pkg/rpc/response/result/version.go similarity index 100% rename from pkg/rpc/result/version.go rename to pkg/rpc/response/result/version.go diff --git a/pkg/rpc/server.go b/pkg/rpc/server.go index 4a098a535..f0080fa27 100644 --- a/pkg/rpc/server.go +++ b/pkg/rpc/server.go @@ -14,7 +14,7 @@ import ( "github.com/CityOfZion/neo-go/pkg/encoding/address" "github.com/CityOfZion/neo-go/pkg/io" "github.com/CityOfZion/neo-go/pkg/network" - "github.com/CityOfZion/neo-go/pkg/rpc/result" + "github.com/CityOfZion/neo-go/pkg/rpc/response/result" "github.com/CityOfZion/neo-go/pkg/util" "github.com/pkg/errors" "go.uber.org/zap" diff --git a/pkg/rpc/server_helper_test.go b/pkg/rpc/server_helper_test.go index ef4035efd..1e1ae64f6 100644 --- a/pkg/rpc/server_helper_test.go +++ b/pkg/rpc/server_helper_test.go @@ -11,7 +11,7 @@ import ( "github.com/CityOfZion/neo-go/pkg/core/storage" "github.com/CityOfZion/neo-go/pkg/io" "github.com/CityOfZion/neo-go/pkg/network" - "github.com/CityOfZion/neo-go/pkg/rpc/result" + "github.com/CityOfZion/neo-go/pkg/rpc/response/result" "github.com/CityOfZion/neo-go/pkg/util" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" diff --git a/pkg/rpc/server_test.go b/pkg/rpc/server_test.go index 5720299ea..6d47f08dd 100644 --- a/pkg/rpc/server_test.go +++ b/pkg/rpc/server_test.go @@ -14,7 +14,7 @@ import ( "github.com/CityOfZion/neo-go/pkg/core" "github.com/CityOfZion/neo-go/pkg/core/transaction" - "github.com/CityOfZion/neo-go/pkg/rpc/result" + "github.com/CityOfZion/neo-go/pkg/rpc/response/result" "github.com/CityOfZion/neo-go/pkg/util" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/pkg/rpc/types.go b/pkg/rpc/types.go index 3284bf873..3c02b2018 100644 --- a/pkg/rpc/types.go +++ b/pkg/rpc/types.go @@ -2,7 +2,7 @@ package rpc import ( "github.com/CityOfZion/neo-go/pkg/core/transaction" - "github.com/CityOfZion/neo-go/pkg/rpc/result" + "github.com/CityOfZion/neo-go/pkg/rpc/response/result" "github.com/CityOfZion/neo-go/pkg/vm" ) From f330f2f40b4074d7a1cd053ab1640f8d70913ce9 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Tue, 14 Jan 2020 15:02:38 +0300 Subject: [PATCH 10/22] rpc: separate out request and response structures Mostly as is, no real effort done yet to optimize them, so there are still a lot of duplicates there, but at least we sort them out into different smaller packages. --- cli/smartcontract/smart_contract.go | 12 +- pkg/rpc/client.go | 13 +- pkg/rpc/request.go | 128 -------------- pkg/rpc/{ => request}/param.go | 19 ++- pkg/rpc/{ => request}/param_test.go | 62 +++---- pkg/rpc/{ => request}/params.go | 2 +- pkg/rpc/{ => request}/stack_param.go | 2 +- pkg/rpc/{ => request}/stack_param_test.go | 2 +- pkg/rpc/request/types.go | 84 +++++++++ pkg/rpc/{ => response}/errors.go | 23 +-- pkg/rpc/{ => response}/types.go | 105 +++++------- pkg/rpc/rpc.go | 62 +++---- pkg/rpc/scdetails.go | 6 +- pkg/rpc/server.go | 198 +++++++++++++++------- pkg/rpc/server_helper_test.go | 11 +- pkg/rpc/server_test.go | 3 +- pkg/rpc/txBuilder.go | 27 +-- pkg/rpc/tx_builder_test.go | 59 +++---- 18 files changed, 422 insertions(+), 396 deletions(-) delete mode 100644 pkg/rpc/request.go rename pkg/rpc/{ => request}/param.go (94%) rename pkg/rpc/{ => request}/param_test.go (76%) rename pkg/rpc/{ => request}/params.go (97%) rename pkg/rpc/{ => request}/stack_param.go (99%) rename pkg/rpc/{ => request}/stack_param_test.go (99%) create mode 100644 pkg/rpc/request/types.go rename pkg/rpc/{ => response}/errors.go (67%) rename pkg/rpc/{ => response}/types.go (52%) diff --git a/cli/smartcontract/smart_contract.go b/cli/smartcontract/smart_contract.go index 38e1d0901..b3e31a028 100644 --- a/cli/smartcontract/smart_contract.go +++ b/cli/smartcontract/smart_contract.go @@ -15,6 +15,8 @@ import ( "github.com/CityOfZion/neo-go/pkg/crypto/hash" "github.com/CityOfZion/neo-go/pkg/crypto/keys" "github.com/CityOfZion/neo-go/pkg/rpc" + "github.com/CityOfZion/neo-go/pkg/rpc/request" + "github.com/CityOfZion/neo-go/pkg/rpc/response" "github.com/CityOfZion/neo-go/pkg/smartcontract" "github.com/CityOfZion/neo-go/pkg/util" "github.com/CityOfZion/neo-go/pkg/vm" @@ -294,10 +296,10 @@ func initSmartContract(ctx *cli.Context) error { // TODO: Fix the missing neo-go.yml file with the `init` command when the package manager is in place. if !ctx.Bool("skip-details") { details := parseContractDetails() - details.ReturnType = rpc.ByteArray - details.Parameters = make([]rpc.StackParamType, 2) - details.Parameters[0] = rpc.String - details.Parameters[1] = rpc.Array + details.ReturnType = request.ByteArray + details.Parameters = make([]request.StackParamType, 2) + details.Parameters[0] = request.String + details.Parameters[1] = request.Array project := &ProjectConfig{Contract: details} b, err := yaml.Marshal(project) @@ -362,7 +364,7 @@ func invokeInternal(ctx *cli.Context, withMethod bool, signAndPush bool) error { operation string params = make([]smartcontract.Parameter, 0) paramsStart = 1 - resp *rpc.InvokeScriptResponse + resp *response.InvokeScript wif *keys.WIF ) diff --git a/pkg/rpc/client.go b/pkg/rpc/client.go index e8a75bc81..167c7c8bf 100644 --- a/pkg/rpc/client.go +++ b/pkg/rpc/client.go @@ -14,6 +14,7 @@ import ( "github.com/CityOfZion/neo-go/pkg/core/state" "github.com/CityOfZion/neo-go/pkg/core/transaction" "github.com/CityOfZion/neo-go/pkg/crypto/keys" + "github.com/CityOfZion/neo-go/pkg/rpc/request" "github.com/CityOfZion/neo-go/pkg/util" "github.com/pkg/errors" ) @@ -177,13 +178,13 @@ func (c *Client) CalculateInputs(address string, asset util.Uint256, cost util.F } -func (c *Client) performRequest(method string, p params, v interface{}) error { +func (c *Client) performRequest(method string, p request.RawParams, v interface{}) error { var ( - r = request{ - JSONRPC: c.version, - Method: method, - Params: p.values, - ID: 1, + r = request.Raw{ + JSONRPC: c.version, + Method: method, + RawParams: p.Values, + ID: 1, } buf = new(bytes.Buffer) ) diff --git a/pkg/rpc/request.go b/pkg/rpc/request.go deleted file mode 100644 index 5a3c21b9a..000000000 --- a/pkg/rpc/request.go +++ /dev/null @@ -1,128 +0,0 @@ -package rpc - -import ( - "encoding/json" - "io" - "net/http" - - "github.com/pkg/errors" - "go.uber.org/zap" -) - -const ( - jsonRPCVersion = "2.0" -) - -type ( - // Request represents a standard JSON-RPC 2.0 - // request: http://www.jsonrpc.org/specification#request_object. - Request struct { - JSONRPC string `json:"jsonrpc"` - Method string `json:"method"` - RawParams json.RawMessage `json:"params,omitempty"` - RawID json.RawMessage `json:"id,omitempty"` - } - - // Response represents a standard JSON-RPC 2.0 - // response: http://www.jsonrpc.org/specification#response_object. - Response struct { - JSONRPC string `json:"jsonrpc"` - Result interface{} `json:"result,omitempty"` - Error *Error `json:"error,omitempty"` - ID json.RawMessage `json:"id,omitempty"` - } -) - -// NewRequest creates a new Request struct. -func NewRequest() *Request { - return &Request{ - JSONRPC: jsonRPCVersion, - } -} - -// DecodeData decodes the given reader into the the request -// struct. -func (r *Request) DecodeData(data io.ReadCloser) error { - defer data.Close() - - err := json.NewDecoder(data).Decode(r) - if err != nil { - return errors.Errorf("error parsing JSON payload: %s", err) - } - - if r.JSONRPC != jsonRPCVersion { - return errors.Errorf("invalid version, expected 2.0 got: '%s'", r.JSONRPC) - } - - return nil -} - -// Params takes a slice of any type and attempts to bind -// the params to it. -func (r *Request) Params() (*Params, error) { - params := Params{} - - err := json.Unmarshal(r.RawParams, ¶ms) - if err != nil { - return nil, errors.Errorf("error parsing params field in payload: %s", err) - } - - return ¶ms, nil -} - -// WriteErrorResponse writes an error response to the ResponseWriter. -func (s *Server) WriteErrorResponse(r *Request, w http.ResponseWriter, err error) { - jsonErr, ok := err.(*Error) - if !ok { - jsonErr = NewInternalServerError("Internal server error", err) - } - - response := Response{ - JSONRPC: r.JSONRPC, - Error: jsonErr, - ID: r.RawID, - } - - logFields := []zap.Field{ - zap.Error(jsonErr.Cause), - zap.String("method", r.Method), - } - - params, err := r.Params() - if err == nil { - logFields = append(logFields, zap.Any("params", params)) - } - - s.log.Error("Error encountered with rpc request", logFields...) - - w.WriteHeader(jsonErr.HTTPCode) - s.writeServerResponse(r, w, response) -} - -// WriteResponse encodes the response and writes it to the ResponseWriter. -func (s *Server) WriteResponse(r *Request, w http.ResponseWriter, result interface{}) { - response := Response{ - JSONRPC: r.JSONRPC, - Result: result, - ID: r.RawID, - } - - s.writeServerResponse(r, w, response) -} - -func (s *Server) writeServerResponse(r *Request, w http.ResponseWriter, response Response) { - w.Header().Set("Content-Type", "application/json; charset=utf-8") - if s.config.EnableCORSWorkaround { - w.Header().Set("Access-Control-Allow-Origin", "*") - w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With") - } - - encoder := json.NewEncoder(w) - err := encoder.Encode(response) - - if err != nil { - s.log.Error("Error encountered while encoding response", - zap.String("err", err.Error()), - zap.String("method", r.Method)) - } -} diff --git a/pkg/rpc/param.go b/pkg/rpc/request/param.go similarity index 94% rename from pkg/rpc/param.go rename to pkg/rpc/request/param.go index 1dc57eb07..e9f453175 100644 --- a/pkg/rpc/param.go +++ b/pkg/rpc/request/param.go @@ -1,4 +1,4 @@ -package rpc +package request import ( "bytes" @@ -29,12 +29,13 @@ type ( } ) +// These are parameter types accepted by RPC server. const ( defaultT paramType = iota - stringT - numberT - arrayT - funcParamT + StringT + NumberT + ArrayT + FuncParamT ) func (p Param) String() string { @@ -123,7 +124,7 @@ func (p Param) GetBytesHex() ([]byte, error) { func (p *Param) UnmarshalJSON(data []byte) error { var s string if err := json.Unmarshal(data, &s); err == nil { - p.Type = stringT + p.Type = StringT p.Value = s return nil @@ -131,7 +132,7 @@ func (p *Param) UnmarshalJSON(data []byte) error { var num float64 if err := json.Unmarshal(data, &num); err == nil { - p.Type = numberT + p.Type = NumberT p.Value = int(num) return nil @@ -142,7 +143,7 @@ func (p *Param) UnmarshalJSON(data []byte) error { jd.DisallowUnknownFields() var fp FuncParam if err := jd.Decode(&fp); err == nil { - p.Type = funcParamT + p.Type = FuncParamT p.Value = fp return nil @@ -150,7 +151,7 @@ func (p *Param) UnmarshalJSON(data []byte) error { var ps []Param if err := json.Unmarshal(data, &ps); err == nil { - p.Type = arrayT + p.Type = ArrayT p.Value = ps return nil diff --git a/pkg/rpc/param_test.go b/pkg/rpc/request/param_test.go similarity index 76% rename from pkg/rpc/param_test.go rename to pkg/rpc/request/param_test.go index 58cc9b879..127fab9ad 100644 --- a/pkg/rpc/param_test.go +++ b/pkg/rpc/request/param_test.go @@ -1,4 +1,4 @@ -package rpc +package request import ( "encoding/hex" @@ -15,35 +15,35 @@ func TestParam_UnmarshalJSON(t *testing.T) { msg := `["str1", 123, ["str2", 3], [{"type": "String", "value": "jajaja"}]]` expected := Params{ { - Type: stringT, + Type: StringT, Value: "str1", }, { - Type: numberT, + Type: NumberT, Value: 123, }, { - Type: arrayT, + Type: ArrayT, Value: []Param{ { - Type: stringT, + Type: StringT, Value: "str2", }, { - Type: numberT, + Type: NumberT, Value: 3, }, }, }, { - Type: arrayT, + Type: ArrayT, Value: []Param{ { - Type: funcParamT, + Type: FuncParamT, Value: FuncParam{ Type: String, Value: Param{ - Type: stringT, + Type: StringT, Value: "jajaja", }, }, @@ -61,34 +61,34 @@ func TestParam_UnmarshalJSON(t *testing.T) { } func TestParamGetString(t *testing.T) { - p := Param{stringT, "jajaja"} + p := Param{StringT, "jajaja"} str, err := p.GetString() assert.Equal(t, "jajaja", str) require.Nil(t, err) - p = Param{stringT, int(100500)} + p = Param{StringT, int(100500)} _, err = p.GetString() require.NotNil(t, err) } func TestParamGetInt(t *testing.T) { - p := Param{numberT, int(100500)} + p := Param{NumberT, int(100500)} i, err := p.GetInt() assert.Equal(t, 100500, i) require.Nil(t, err) - p = Param{numberT, "jajaja"} + p = Param{NumberT, "jajaja"} _, err = p.GetInt() require.NotNil(t, err) } func TestParamGetArray(t *testing.T) { - p := Param{arrayT, []Param{{numberT, 42}}} + p := Param{ArrayT, []Param{{NumberT, 42}}} a, err := p.GetArray() - assert.Equal(t, []Param{{numberT, 42}}, a) + assert.Equal(t, []Param{{NumberT, 42}}, a) require.Nil(t, err) - p = Param{arrayT, 42} + p = Param{ArrayT, 42} _, err = p.GetArray() require.NotNil(t, err) } @@ -96,16 +96,16 @@ func TestParamGetArray(t *testing.T) { func TestParamGetUint256(t *testing.T) { gas := "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7" u256, _ := util.Uint256DecodeStringLE(gas) - p := Param{stringT, gas} + p := Param{StringT, gas} u, err := p.GetUint256() assert.Equal(t, u256, u) require.Nil(t, err) - p = Param{stringT, 42} + p = Param{StringT, 42} _, err = p.GetUint256() require.NotNil(t, err) - p = Param{stringT, "qq2c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"} + p = Param{StringT, "qq2c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"} _, err = p.GetUint256() require.NotNil(t, err) } @@ -113,16 +113,16 @@ func TestParamGetUint256(t *testing.T) { func TestParamGetUint160FromHex(t *testing.T) { in := "50befd26fdf6e4d957c11e078b24ebce6291456f" u160, _ := util.Uint160DecodeStringLE(in) - p := Param{stringT, in} + p := Param{StringT, in} u, err := p.GetUint160FromHex() assert.Equal(t, u160, u) require.Nil(t, err) - p = Param{stringT, 42} + p = Param{StringT, 42} _, err = p.GetUint160FromHex() require.NotNil(t, err) - p = Param{stringT, "wwbefd26fdf6e4d957c11e078b24ebce6291456f"} + p = Param{StringT, "wwbefd26fdf6e4d957c11e078b24ebce6291456f"} _, err = p.GetUint160FromHex() require.NotNil(t, err) } @@ -130,16 +130,16 @@ func TestParamGetUint160FromHex(t *testing.T) { func TestParamGetUint160FromAddress(t *testing.T) { in := "AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y" u160, _ := address.StringToUint160(in) - p := Param{stringT, in} + p := Param{StringT, in} u, err := p.GetUint160FromAddress() assert.Equal(t, u160, u) require.Nil(t, err) - p = Param{stringT, 42} + p = Param{StringT, 42} _, err = p.GetUint160FromAddress() require.NotNil(t, err) - p = Param{stringT, "QK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y"} + p = Param{StringT, "QK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y"} _, err = p.GetUint160FromAddress() require.NotNil(t, err) } @@ -148,19 +148,19 @@ func TestParamGetFuncParam(t *testing.T) { fp := FuncParam{ Type: String, Value: Param{ - Type: stringT, + Type: StringT, Value: "jajaja", }, } p := Param{ - Type: funcParamT, + Type: FuncParamT, Value: fp, } newfp, err := p.GetFuncParam() assert.Equal(t, fp, newfp) require.Nil(t, err) - p = Param{funcParamT, 42} + p = Param{FuncParamT, 42} _, err = p.GetFuncParam() require.NotNil(t, err) } @@ -168,16 +168,16 @@ func TestParamGetFuncParam(t *testing.T) { func TestParamGetBytesHex(t *testing.T) { in := "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7" inb, _ := hex.DecodeString(in) - p := Param{stringT, in} + p := Param{StringT, in} bh, err := p.GetBytesHex() assert.Equal(t, inb, bh) require.Nil(t, err) - p = Param{stringT, 42} + p = Param{StringT, 42} _, err = p.GetBytesHex() require.NotNil(t, err) - p = Param{stringT, "qq2c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"} + p = Param{StringT, "qq2c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"} _, err = p.GetBytesHex() require.NotNil(t, err) } diff --git a/pkg/rpc/params.go b/pkg/rpc/request/params.go similarity index 97% rename from pkg/rpc/params.go rename to pkg/rpc/request/params.go index 1affcf267..8b1945cb1 100644 --- a/pkg/rpc/params.go +++ b/pkg/rpc/request/params.go @@ -1,4 +1,4 @@ -package rpc +package request type ( // Params represents the JSON-RPC params. diff --git a/pkg/rpc/stack_param.go b/pkg/rpc/request/stack_param.go similarity index 99% rename from pkg/rpc/stack_param.go rename to pkg/rpc/request/stack_param.go index 6c9a7b5e2..192a2f0ef 100644 --- a/pkg/rpc/stack_param.go +++ b/pkg/rpc/request/stack_param.go @@ -1,4 +1,4 @@ -package rpc +package request import ( "encoding/binary" diff --git a/pkg/rpc/stack_param_test.go b/pkg/rpc/request/stack_param_test.go similarity index 99% rename from pkg/rpc/stack_param_test.go rename to pkg/rpc/request/stack_param_test.go index dfd289e19..2d2029cff 100644 --- a/pkg/rpc/stack_param_test.go +++ b/pkg/rpc/request/stack_param_test.go @@ -1,4 +1,4 @@ -package rpc +package request import ( "encoding/json" diff --git a/pkg/rpc/request/types.go b/pkg/rpc/request/types.go new file mode 100644 index 000000000..9020d508e --- /dev/null +++ b/pkg/rpc/request/types.go @@ -0,0 +1,84 @@ +package request + +import ( + "encoding/json" + "io" + + "github.com/pkg/errors" +) + +const ( + // JSONRPCVersion is the only JSON-RPC protocol version supported. + JSONRPCVersion = "2.0" +) + +// RawParams is just a slice of abstract values, used to represent parameters +// passed from client to server. +type RawParams struct { + Values []interface{} +} + +// NewRawParams creates RawParams from its parameters. +func NewRawParams(vals ...interface{}) RawParams { + p := RawParams{} + p.Values = make([]interface{}, len(vals)) + for i := 0; i < len(p.Values); i++ { + p.Values[i] = vals[i] + } + return p +} + +// Raw represents JSON-RPC request. +type Raw struct { + JSONRPC string `json:"jsonrpc"` + Method string `json:"method"` + RawParams []interface{} `json:"params"` + ID int `json:"id"` +} + +// In represents a standard JSON-RPC 2.0 +// request: http://www.jsonrpc.org/specification#request_object. It's used in +// server to represent incoming queries. +type In struct { + JSONRPC string `json:"jsonrpc"` + Method string `json:"method"` + RawParams json.RawMessage `json:"params,omitempty"` + RawID json.RawMessage `json:"id,omitempty"` +} + +// NewIn creates a new Request struct. +func NewIn() *In { + return &In{ + JSONRPC: JSONRPCVersion, + } +} + +// DecodeData decodes the given reader into the the request +// struct. +func (r *In) DecodeData(data io.ReadCloser) error { + defer data.Close() + + err := json.NewDecoder(data).Decode(r) + if err != nil { + return errors.Errorf("error parsing JSON payload: %s", err) + } + + if r.JSONRPC != JSONRPCVersion { + return errors.Errorf("invalid version, expected 2.0 got: '%s'", r.JSONRPC) + } + + return nil +} + +// Params takes a slice of any type and attempts to bind +// the params to it. +func (r *In) Params() (*Params, error) { + params := Params{} + + err := json.Unmarshal(r.RawParams, ¶ms) + if err != nil { + return nil, errors.Errorf("error parsing params field in payload: %s", err) + } + + return ¶ms, nil +} diff --git a/pkg/rpc/errors.go b/pkg/rpc/response/errors.go similarity index 67% rename from pkg/rpc/errors.go rename to pkg/rpc/response/errors.go index 966f5f678..a0d468136 100644 --- a/pkg/rpc/errors.go +++ b/pkg/rpc/response/errors.go @@ -1,4 +1,4 @@ -package rpc +package response import ( "fmt" @@ -18,10 +18,13 @@ type ( ) var ( - errInvalidParams = NewInvalidParamsError("", nil) + // ErrInvalidParams represents a generic 'invalid parameters' error. + ErrInvalidParams = NewInvalidParamsError("", nil) ) -func newError(code int64, httpCode int, message string, data string, cause error) *Error { +// NewError is an Error constructor that takes Error contents from its +// parameters. +func NewError(code int64, httpCode int, message string, data string, cause error) *Error { return &Error{ Code: code, HTTPCode: httpCode, @@ -35,40 +38,40 @@ func newError(code int64, httpCode int, message string, data string, cause error // NewParseError creates a new error with code // -32700.:%s func NewParseError(data string, cause error) *Error { - return newError(-32700, http.StatusBadRequest, "Parse Error", data, cause) + return NewError(-32700, http.StatusBadRequest, "Parse Error", data, cause) } // NewInvalidRequestError creates a new error with // code -32600. func NewInvalidRequestError(data string, cause error) *Error { - return newError(-32600, http.StatusUnprocessableEntity, "Invalid Request", data, cause) + return NewError(-32600, http.StatusUnprocessableEntity, "Invalid Request", data, cause) } // NewMethodNotFoundError creates a new error with // code -32601. func NewMethodNotFoundError(data string, cause error) *Error { - return newError(-32601, http.StatusMethodNotAllowed, "Method not found", data, cause) + return NewError(-32601, http.StatusMethodNotAllowed, "Method not found", data, cause) } // NewInvalidParamsError creates a new error with // code -32602. func NewInvalidParamsError(data string, cause error) *Error { - return newError(-32602, http.StatusUnprocessableEntity, "Invalid Params", data, cause) + return NewError(-32602, http.StatusUnprocessableEntity, "Invalid Params", data, cause) } // NewInternalServerError creates a new error with // code -32603. func NewInternalServerError(data string, cause error) *Error { - return newError(-32603, http.StatusInternalServerError, "Internal error", data, cause) + return NewError(-32603, http.StatusInternalServerError, "Internal error", data, cause) } // NewRPCError creates a new error with // code -100 func NewRPCError(message string, data string, cause error) *Error { - return newError(-100, http.StatusUnprocessableEntity, message, data, cause) + return NewError(-100, http.StatusUnprocessableEntity, message, data, cause) } // Error implements the error interface. -func (e Error) Error() string { +func (e *Error) Error() string { return fmt.Sprintf("%s (%d) - %s - %s", e.Message, e.Code, e.Data, e.Cause) } diff --git a/pkg/rpc/types.go b/pkg/rpc/response/types.go similarity index 52% rename from pkg/rpc/types.go rename to pkg/rpc/response/types.go index 3c02b2018..cd0985b54 100644 --- a/pkg/rpc/types.go +++ b/pkg/rpc/response/types.go @@ -1,15 +1,17 @@ -package rpc +package response import ( + "encoding/json" + "github.com/CityOfZion/neo-go/pkg/core/transaction" + "github.com/CityOfZion/neo-go/pkg/rpc/request" "github.com/CityOfZion/neo-go/pkg/rpc/response/result" "github.com/CityOfZion/neo-go/pkg/vm" ) -// InvokeScriptResponse stores response for the invoke script call. -type InvokeScriptResponse struct { - responseHeader - Error *Error `json:"error,omitempty"` +// InvokeScript stores response for the invoke script call. +type InvokeScript struct { + HeaderAndError Result *InvokeResult `json:"result,omitempty"` } @@ -19,19 +21,18 @@ type InvokeResult struct { State vm.State `json:"state"` GasConsumed string `json:"gas_consumed"` Script string `json:"script"` - Stack []StackParam + Stack []request.StackParam } -// AccountStateResponse holds the getaccountstate response. -type AccountStateResponse struct { - responseHeader +// AccountState holds the getaccountstate response. +type AccountState struct { + Header Result *Account `json:"result"` } -// UnspentResponse represents server response to the `getunspents` command. -type UnspentResponse struct { - responseHeader - Error *Error `json:"error,omitempty"` +// Unspent represents server response to the `getunspents` command. +type Unspent struct { + HeaderAndError Result *result.Unspents `json:"result,omitempty"` } @@ -51,68 +52,54 @@ type Balance struct { Value string `json:"value"` } -type params struct { - values []interface{} +// Header is a generic JSON-RPC 2.0 response header (ID and JSON-RPC version). +type Header struct { + ID json.RawMessage `json:"id"` + JSONRPC string `json:"jsonrpc"` } -func newParams(vals ...interface{}) params { - p := params{} - p.values = make([]interface{}, len(vals)) - for i := 0; i < len(p.values); i++ { - p.values[i] = vals[i] - } - return p +// HeaderAndError adds an Error (that can be empty) to the Header, it's used +// to construct type-specific responses. +type HeaderAndError struct { + Header + Error *Error `json:"error,omitempty"` } -type request struct { - JSONRPC string `json:"jsonrpc"` - Method string `json:"method"` - Params []interface{} `json:"params"` - ID int `json:"id"` +// Raw represents a standard raw JSON-RPC 2.0 +// response: http://www.jsonrpc.org/specification#response_object. +type Raw struct { + HeaderAndError + Result interface{} `json:"result,omitempty"` } -type responseHeader struct { - ID int `json:"id"` - JSONRPC string `json:"jsonrpc"` +// SendToAddress stores response for the sendtoaddress call. +type SendToAddress struct { + HeaderAndError + Result *Tx } -type response struct { - responseHeader - Error *Error `json:"error"` - Result interface{} `json:"result"` -} - -// SendToAddressResponse stores response for the sendtoaddress call. -type SendToAddressResponse struct { - responseHeader - Error *Error `json:"error"` - Result *TxResponse -} - -// GetRawTxResponse represents verbose output of `getrawtransaction` RPC call. -type GetRawTxResponse struct { - responseHeader - Error *Error `json:"error"` - Result *RawTxResponse `json:"result"` -} - -// GetTxOutResponse represents result of `gettxout` RPC call. -type GetTxOutResponse struct { - responseHeader - Error *Error +// GetTxOut represents result of `gettxout` RPC call. +type GetTxOut struct { + HeaderAndError Result *result.TransactionOutput } -// RawTxResponse stores transaction with blockchain metadata to be sent as a response. -type RawTxResponse struct { - TxResponse +// GetRawTx represents verbose output of `getrawtransaction` RPC call. +type GetRawTx struct { + HeaderAndError + Result *RawTx `json:"result"` +} + +// RawTx stores transaction with blockchain metadata to be sent as a response. +type RawTx struct { + Tx BlockHash string `json:"blockhash"` Confirmations uint `json:"confirmations"` BlockTime uint `json:"blocktime"` } -// TxResponse stores transaction to be sent as a response. -type TxResponse struct { +// Tx stores transaction to be sent as a response. +type Tx struct { TxID string `json:"txid"` Size int `json:"size"` Type string `json:"type"` // todo: convert to TransactionType diff --git a/pkg/rpc/rpc.go b/pkg/rpc/rpc.go index 8ba1af6c5..29a9f1a15 100644 --- a/pkg/rpc/rpc.go +++ b/pkg/rpc/rpc.go @@ -7,6 +7,8 @@ import ( "github.com/CityOfZion/neo-go/pkg/core" "github.com/CityOfZion/neo-go/pkg/core/transaction" "github.com/CityOfZion/neo-go/pkg/crypto/keys" + "github.com/CityOfZion/neo-go/pkg/rpc/request" + "github.com/CityOfZion/neo-go/pkg/rpc/response" "github.com/CityOfZion/neo-go/pkg/smartcontract" "github.com/CityOfZion/neo-go/pkg/util" "github.com/pkg/errors" @@ -17,11 +19,11 @@ import ( // missing output wrapper at the moment, thus commented out // func (c *Client) getBlock(indexOrHash interface{}, verbose bool) (*response, error) { // var ( -// params = newParams(indexOrHash) +// params = request.NewRawParams(indexOrHash) // resp = &response{} // ) // if verbose { -// params = newParams(indexOrHash, 1) +// params = request.NewRawParams(indexOrHash, 1) // } // if err := c.performRequest("getblock", params, resp); err != nil { // return nil, err @@ -30,10 +32,10 @@ import ( // } // GetAccountState returns detailed information about a NEO account. -func (c *Client) GetAccountState(address string) (*AccountStateResponse, error) { +func (c *Client) GetAccountState(address string) (*response.AccountState, error) { var ( - params = newParams(address) - resp = &AccountStateResponse{} + params = request.NewRawParams(address) + resp = &response.AccountState{} ) if err := c.performRequest("getaccountstate", params, resp); err != nil { return nil, err @@ -42,10 +44,10 @@ func (c *Client) GetAccountState(address string) (*AccountStateResponse, error) } // GetUnspents returns UTXOs for the given NEO account. -func (c *Client) GetUnspents(address string) (*UnspentResponse, error) { +func (c *Client) GetUnspents(address string) (*response.Unspent, error) { var ( - params = newParams(address) - resp = &UnspentResponse{} + params = request.NewRawParams(address) + resp = &response.Unspent{} ) if err := c.performRequest("getunspents", params, resp); err != nil { return nil, err @@ -55,10 +57,10 @@ func (c *Client) GetUnspents(address string) (*UnspentResponse, 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 string) (*InvokeScriptResponse, error) { +func (c *Client) InvokeScript(script string) (*response.InvokeScript, error) { var ( - params = newParams(script) - resp = &InvokeScriptResponse{} + params = request.NewRawParams(script) + resp = &response.InvokeScript{} ) if err := c.performRequest("invokescript", params, resp); err != nil { return nil, err @@ -69,10 +71,10 @@ func (c *Client) InvokeScript(script string) (*InvokeScriptResponse, error) { // InvokeFunction returns the results after calling the smart contract scripthash // with the given operation and parameters. // NOTE: this is test invoke and will not affect the blockchain. -func (c *Client) InvokeFunction(script, operation string, params []smartcontract.Parameter) (*InvokeScriptResponse, error) { +func (c *Client) InvokeFunction(script, operation string, params []smartcontract.Parameter) (*response.InvokeScript, error) { var ( - p = newParams(script, operation, params) - resp = &InvokeScriptResponse{} + p = request.NewRawParams(script, operation, params) + resp = &response.InvokeScript{} ) if err := c.performRequest("invokefunction", p, resp); err != nil { return nil, err @@ -82,10 +84,10 @@ func (c *Client) InvokeFunction(script, operation string, params []smartcontract // Invoke returns the results after calling the smart contract scripthash // with the given parameters. -func (c *Client) Invoke(script string, params []smartcontract.Parameter) (*InvokeScriptResponse, error) { +func (c *Client) Invoke(script string, params []smartcontract.Parameter) (*response.InvokeScript, error) { var ( - p = newParams(script, params) - resp = &InvokeScriptResponse{} + p = request.NewRawParams(script, params) + resp = &response.InvokeScript{} ) if err := c.performRequest("invoke", p, resp); err != nil { return nil, err @@ -97,7 +99,7 @@ func (c *Client) Invoke(script string, params []smartcontract.Parameter) (*Invok // missing output wrapper at the moment, thus commented out // func (c *Client) getRawTransaction(hash string, verbose bool) (*response, error) { // var ( -// params = newParams(hash, verbose) +// params = request.NewRawParams(hash, verbose) // resp = &response{} // ) // if err := c.performRequest("getrawtransaction", params, resp); err != nil { @@ -110,10 +112,10 @@ func (c *Client) Invoke(script string, params []smartcontract.Parameter) (*Invok // The given hex string needs to be signed with a keypair. // When the result of the response object is true, the TX has successfully // been broadcasted to the network. -func (c *Client) sendRawTransaction(rawTX *transaction.Transaction) (*response, error) { +func (c *Client) sendRawTransaction(rawTX *transaction.Transaction) (*response.Raw, error) { var ( - params = newParams(hex.EncodeToString(rawTX.Bytes())) - resp = &response{} + params = request.NewRawParams(hex.EncodeToString(rawTX.Bytes())) + resp = &response.Raw{} ) if err := c.performRequest("sendrawtransaction", params, resp); err != nil { return nil, err @@ -124,7 +126,7 @@ func (c *Client) sendRawTransaction(rawTX *transaction.Transaction) (*response, // SendToAddress sends an amount of specific asset to a given address. // This call requires open wallet. (`wif` key in client struct.) // If response.Result is `true` then transaction was formed correctly and was written in blockchain. -func (c *Client) SendToAddress(asset util.Uint256, address string, amount util.Fixed8) (*SendToAddressResponse, error) { +func (c *Client) SendToAddress(asset util.Uint256, address string, amount util.Fixed8) (*response.SendToAddress, error) { var ( err error rawTx *transaction.Transaction @@ -135,23 +137,23 @@ func (c *Client) SendToAddress(asset util.Uint256, address string, amount util.F wif: c.WIF(), balancer: c.Balancer(), } - resp *response - response = &SendToAddressResponse{} + respRaw *response.Raw + resp = &response.SendToAddress{} ) if rawTx, err = CreateRawContractTransaction(txParams); err != nil { return nil, errors.Wrap(err, "failed to create raw transaction for `sendtoaddress`") } - if resp, err = c.sendRawTransaction(rawTx); err != nil { + if respRaw, err = c.sendRawTransaction(rawTx); err != nil { return nil, errors.Wrap(err, "failed to send raw transaction") } - response.Error = resp.Error - response.ID = resp.ID - response.JSONRPC = resp.JSONRPC - response.Result = &TxResponse{ + resp.Error = respRaw.Error + resp.ID = respRaw.ID + resp.JSONRPC = respRaw.JSONRPC + resp.Result = &response.Tx{ TxID: rawTx.Hash().StringLE(), } - return response, nil + return resp, nil } // SignAndPushInvocationTx signs and pushes given script as an invocation diff --git a/pkg/rpc/scdetails.go b/pkg/rpc/scdetails.go index 5988e3436..f5cd242c8 100644 --- a/pkg/rpc/scdetails.go +++ b/pkg/rpc/scdetails.go @@ -1,5 +1,7 @@ package rpc +import "github.com/CityOfZion/neo-go/pkg/rpc/request" + // ContractDetails contains contract metadata. type ContractDetails struct { Author string @@ -10,6 +12,6 @@ type ContractDetails struct { HasStorage bool HasDynamicInvocation bool IsPayable bool - ReturnType StackParamType - Parameters []StackParamType + ReturnType request.StackParamType + Parameters []request.StackParamType } diff --git a/pkg/rpc/server.go b/pkg/rpc/server.go index f0080fa27..8b3553366 100644 --- a/pkg/rpc/server.go +++ b/pkg/rpc/server.go @@ -3,6 +3,7 @@ package rpc import ( "context" "encoding/hex" + "encoding/json" "fmt" "net/http" "strconv" @@ -14,6 +15,8 @@ import ( "github.com/CityOfZion/neo-go/pkg/encoding/address" "github.com/CityOfZion/neo-go/pkg/io" "github.com/CityOfZion/neo-go/pkg/network" + "github.com/CityOfZion/neo-go/pkg/rpc/request" + "github.com/CityOfZion/neo-go/pkg/rpc/response" "github.com/CityOfZion/neo-go/pkg/rpc/response/result" "github.com/CityOfZion/neo-go/pkg/util" "github.com/pkg/errors" @@ -71,13 +74,13 @@ func (s *Server) Shutdown() error { } func (s *Server) requestHandler(w http.ResponseWriter, httpRequest *http.Request) { - req := NewRequest() + req := request.NewIn() if httpRequest.Method != "POST" { s.WriteErrorResponse( req, w, - NewInvalidParamsError( + response.NewInvalidParamsError( fmt.Sprintf("Invalid method '%s', please retry with 'POST'", httpRequest.Method), nil, ), ) @@ -86,20 +89,20 @@ func (s *Server) requestHandler(w http.ResponseWriter, httpRequest *http.Request err := req.DecodeData(httpRequest.Body) if err != nil { - s.WriteErrorResponse(req, w, NewParseError("Problem parsing JSON-RPC request body", err)) + s.WriteErrorResponse(req, w, response.NewParseError("Problem parsing JSON-RPC request body", err)) return } reqParams, err := req.Params() if err != nil { - s.WriteErrorResponse(req, w, NewInvalidParamsError("Problem parsing request parameters", err)) + s.WriteErrorResponse(req, w, response.NewInvalidParamsError("Problem parsing request parameters", err)) return } s.methodHandler(w, req, *reqParams) } -func (s *Server) methodHandler(w http.ResponseWriter, req *Request, reqParams Params) { +func (s *Server) methodHandler(w http.ResponseWriter, req *request.In, reqParams request.Params) { s.log.Debug("processing rpc request", zap.String("method", req.Method), zap.String("params", fmt.Sprintf("%v", reqParams))) @@ -121,33 +124,33 @@ Methods: param, ok := reqParams.Value(0) if !ok { - resultsErr = errInvalidParams + resultsErr = response.ErrInvalidParams break Methods } switch param.Type { - case stringT: + case request.StringT: var err error hash, err = param.GetUint256() if err != nil { - resultsErr = errInvalidParams + resultsErr = response.ErrInvalidParams break Methods } - case numberT: + case request.NumberT: num, err := s.blockHeightFromParam(param) if err != nil { - resultsErr = errInvalidParams + resultsErr = response.ErrInvalidParams break Methods } hash = s.chain.GetHeaderHash(num) default: - resultsErr = errInvalidParams + resultsErr = response.ErrInvalidParams break Methods } block, err := s.chain.GetBlock(hash) if err != nil { - resultsErr = NewInternalServerError(fmt.Sprintf("Problem locating block with hash: %s", hash), err) + resultsErr = response.NewInternalServerError(fmt.Sprintf("Problem locating block with hash: %s", hash), err) break } @@ -165,14 +168,14 @@ Methods: case "getblockhash": getblockHashCalled.Inc() - param, ok := reqParams.ValueWithType(0, numberT) + param, ok := reqParams.ValueWithType(0, request.NumberT) if !ok { - resultsErr = errInvalidParams + resultsErr = response.ErrInvalidParams break Methods } num, err := s.blockHeightFromParam(param) if err != nil { - resultsErr = errInvalidParams + resultsErr = response.ErrInvalidParams break Methods } @@ -206,22 +209,22 @@ Methods: validateaddressCalled.Inc() param, ok := reqParams.Value(0) if !ok { - resultsErr = errInvalidParams + resultsErr = response.ErrInvalidParams break Methods } results = validateAddress(param.Value) case "getassetstate": getassetstateCalled.Inc() - param, ok := reqParams.ValueWithType(0, stringT) + param, ok := reqParams.ValueWithType(0, request.StringT) if !ok { - resultsErr = errInvalidParams + resultsErr = response.ErrInvalidParams break Methods } paramAssetID, err := param.GetUint256() if err != nil { - resultsErr = errInvalidParams + resultsErr = response.ErrInvalidParams break } @@ -229,7 +232,7 @@ Methods: if as != nil { results = result.NewAssetState(as) } else { - resultsErr = NewRPCError("Unknown asset", "", nil) + resultsErr = response.NewRPCError("Unknown asset", "", nil) } case "getaccountstate": @@ -266,7 +269,7 @@ Methods: results, resultsErr = s.sendrawtransaction(reqParams) default: - resultsErr = NewMethodNotFoundError(fmt.Sprintf("Method '%s' not supported", req.Method), nil) + resultsErr = response.NewMethodNotFoundError(fmt.Sprintf("Method '%s' not supported", req.Method), nil) } if resultsErr != nil { @@ -277,27 +280,27 @@ Methods: s.WriteResponse(req, w, results) } -func (s *Server) getStorage(ps Params) (interface{}, error) { +func (s *Server) getStorage(ps request.Params) (interface{}, error) { param, ok := ps.Value(0) if !ok { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } scriptHash, err := param.GetUint160FromHex() if err != nil { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } scriptHash = scriptHash.Reverse() param, ok = ps.Value(1) if !ok { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } key, err := param.GetBytesHex() if err != nil { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } item := s.chain.GetStorageItem(scriptHash.Reverse(), key) @@ -308,22 +311,22 @@ func (s *Server) getStorage(ps Params) (interface{}, error) { return hex.EncodeToString(item.Value), nil } -func (s *Server) getrawtransaction(reqParams Params) (interface{}, error) { +func (s *Server) getrawtransaction(reqParams request.Params) (interface{}, error) { var resultsErr error var results interface{} if param0, ok := reqParams.Value(0); !ok { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } else if txHash, err := param0.GetUint256(); err != nil { - resultsErr = errInvalidParams + resultsErr = response.ErrInvalidParams } else if tx, height, err := s.chain.GetTransaction(txHash); err != nil { err = errors.Wrapf(err, "Invalid transaction hash: %s", txHash) - return nil, NewRPCError("Unknown transaction", err.Error(), err) + return nil, response.NewRPCError("Unknown transaction", err.Error(), err) } else if len(reqParams) >= 2 { _header := s.chain.GetHeaderHash(int(height)) header, err := s.chain.GetHeader(_header) if err != nil { - resultsErr = NewInvalidParamsError(err.Error(), err) + resultsErr = response.NewInvalidParamsError(err.Error(), err) } param1, _ := reqParams.Value(1) @@ -345,34 +348,34 @@ func (s *Server) getrawtransaction(reqParams Params) (interface{}, error) { return results, resultsErr } -func (s *Server) getTxOut(ps Params) (interface{}, error) { +func (s *Server) getTxOut(ps request.Params) (interface{}, error) { p, ok := ps.Value(0) if !ok { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } h, err := p.GetUint256() if err != nil { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } - p, ok = ps.ValueWithType(1, numberT) + p, ok = ps.ValueWithType(1, request.NumberT) if !ok { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } num, err := p.GetInt() if err != nil || num < 0 { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } tx, _, err := s.chain.GetTransaction(h) if err != nil { - return nil, NewInvalidParamsError(err.Error(), err) + return nil, response.NewInvalidParamsError(err.Error(), err) } if num >= len(tx.Outputs) { - return nil, NewInvalidParamsError("invalid index", errors.New("too big index")) + return nil, response.NewInvalidParamsError("invalid index", errors.New("too big index")) } out := tx.Outputs[num] @@ -380,35 +383,35 @@ func (s *Server) getTxOut(ps Params) (interface{}, error) { } // getContractState returns contract state (contract information, according to the contract script hash). -func (s *Server) getContractState(reqParams Params) (interface{}, error) { +func (s *Server) getContractState(reqParams request.Params) (interface{}, error) { var results interface{} - param, ok := reqParams.ValueWithType(0, stringT) + param, ok := reqParams.ValueWithType(0, request.StringT) if !ok { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } else if scriptHash, err := param.GetUint160FromHex(); err != nil { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } else { cs := s.chain.GetContractState(scriptHash) if cs != nil { results = result.NewContractState(cs) } else { - return nil, NewRPCError("Unknown contract", "", nil) + return nil, response.NewRPCError("Unknown contract", "", nil) } } return results, nil } // getAccountState returns account state either in short or full (unspents included) form. -func (s *Server) getAccountState(reqParams Params, unspents bool) (interface{}, error) { +func (s *Server) getAccountState(reqParams request.Params, unspents bool) (interface{}, error) { var resultsErr error var results interface{} - param, ok := reqParams.ValueWithType(0, stringT) + param, ok := reqParams.ValueWithType(0, request.StringT) if !ok { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } else if scriptHash, err := param.GetUint160FromAddress(); err != nil { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } else { as := s.chain.GetAccountState(scriptHash) if as == nil { @@ -417,7 +420,7 @@ func (s *Server) getAccountState(reqParams Params, unspents bool) (interface{}, if unspents { str, err := param.GetString() if err != nil { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } results = result.NewUnspents(as, s.chain, str) } else { @@ -428,18 +431,18 @@ func (s *Server) getAccountState(reqParams Params, unspents bool) (interface{}, } // invoke implements the `invoke` RPC call. -func (s *Server) invoke(reqParams Params) (interface{}, error) { - scriptHashHex, ok := reqParams.ValueWithType(0, stringT) +func (s *Server) invoke(reqParams request.Params) (interface{}, error) { + scriptHashHex, ok := reqParams.ValueWithType(0, request.StringT) if !ok { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } scriptHash, err := scriptHashHex.GetUint160FromHex() if err != nil { return nil, err } - sliceP, ok := reqParams.ValueWithType(1, arrayT) + sliceP, ok := reqParams.ValueWithType(1, request.ArrayT) if !ok { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } slice, err := sliceP.GetArray() if err != nil { @@ -453,10 +456,10 @@ func (s *Server) invoke(reqParams Params) (interface{}, error) { } // invokescript implements the `invokescript` RPC call. -func (s *Server) invokeFunction(reqParams Params) (interface{}, error) { - scriptHashHex, ok := reqParams.ValueWithType(0, stringT) +func (s *Server) invokeFunction(reqParams request.Params) (interface{}, error) { + scriptHashHex, ok := reqParams.ValueWithType(0, request.StringT) if !ok { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } scriptHash, err := scriptHashHex.GetUint160FromHex() if err != nil { @@ -470,14 +473,14 @@ func (s *Server) invokeFunction(reqParams Params) (interface{}, error) { } // invokescript implements the `invokescript` RPC call. -func (s *Server) invokescript(reqParams Params) (interface{}, error) { +func (s *Server) invokescript(reqParams request.Params) (interface{}, error) { if len(reqParams) < 1 { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } script, err := reqParams[0].GetBytesHex() if err != nil { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } return s.runScriptInVM(script), nil @@ -499,14 +502,14 @@ func (s *Server) runScriptInVM(script []byte) *result.Invoke { return result } -func (s *Server) sendrawtransaction(reqParams Params) (interface{}, error) { +func (s *Server) sendrawtransaction(reqParams request.Params) (interface{}, error) { var resultsErr error var results interface{} if len(reqParams) < 1 { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } else if byteTx, err := reqParams[0].GetBytesHex(); err != nil { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } else { r := io.NewBinReaderFromBuf(byteTx) tx := &transaction.Transaction{} @@ -533,14 +536,14 @@ func (s *Server) sendrawtransaction(reqParams Params) (interface{}, error) { } } if err != nil { - resultsErr = NewInternalServerError(err.Error(), err) + resultsErr = response.NewInternalServerError(err.Error(), err) } } return results, resultsErr } -func (s *Server) blockHeightFromParam(param *Param) (int, error) { +func (s *Server) blockHeightFromParam(param *request.Param) (int, error) { num, err := param.GetInt() if err != nil { return 0, nil @@ -552,6 +555,71 @@ func (s *Server) blockHeightFromParam(param *Param) (int, error) { return num, nil } +// WriteErrorResponse writes an error response to the ResponseWriter. +func (s *Server) WriteErrorResponse(r *request.In, w http.ResponseWriter, err error) { + jsonErr, ok := err.(*response.Error) + if !ok { + jsonErr = response.NewInternalServerError("Internal server error", err) + } + + resp := response.Raw{ + HeaderAndError: response.HeaderAndError{ + Header: response.Header{ + JSONRPC: r.JSONRPC, + ID: r.RawID, + }, + Error: jsonErr, + }, + } + + logFields := []zap.Field{ + zap.Error(jsonErr.Cause), + zap.String("method", r.Method), + } + + params, err := r.Params() + if err == nil { + logFields = append(logFields, zap.Any("params", params)) + } + + s.log.Error("Error encountered with rpc request", logFields...) + + w.WriteHeader(jsonErr.HTTPCode) + s.writeServerResponse(r, w, resp) +} + +// WriteResponse encodes the response and writes it to the ResponseWriter. +func (s *Server) WriteResponse(r *request.In, w http.ResponseWriter, result interface{}) { + resp := response.Raw{ + HeaderAndError: response.HeaderAndError{ + Header: response.Header{ + JSONRPC: r.JSONRPC, + ID: r.RawID, + }, + }, + Result: result, + } + + s.writeServerResponse(r, w, resp) +} + +func (s *Server) writeServerResponse(r *request.In, w http.ResponseWriter, resp response.Raw) { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + if s.config.EnableCORSWorkaround { + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With") + } + + encoder := json.NewEncoder(w) + err := encoder.Encode(resp) + + if err != nil { + s.log.Error("Error encountered while encoding response", + zap.String("err", err.Error()), + zap.String("method", r.Method)) + } +} + // validateAddress verifies that the address is a correct NEO address // see https://docs.neo.org/en-us/node/cli/2.9.4/api/validateaddress.html func validateAddress(addr interface{}) result.ValidateAddress { diff --git a/pkg/rpc/server_helper_test.go b/pkg/rpc/server_helper_test.go index 1e1ae64f6..8b00d684e 100644 --- a/pkg/rpc/server_helper_test.go +++ b/pkg/rpc/server_helper_test.go @@ -11,6 +11,7 @@ import ( "github.com/CityOfZion/neo-go/pkg/core/storage" "github.com/CityOfZion/neo-go/pkg/io" "github.com/CityOfZion/neo-go/pkg/network" + "github.com/CityOfZion/neo-go/pkg/rpc/request" "github.com/CityOfZion/neo-go/pkg/rpc/response/result" "github.com/CityOfZion/neo-go/pkg/util" "github.com/stretchr/testify/require" @@ -38,11 +39,11 @@ type SendTXResponse struct { type InvokeFunctionResponse struct { Jsonrpc string `json:"jsonrpc"` Result struct { - Script string `json:"script"` - State string `json:"state"` - GasConsumed string `json:"gas_consumed"` - Stack []FuncParam `json:"stack"` - TX string `json:"tx,omitempty"` + Script string `json:"script"` + State string `json:"state"` + GasConsumed string `json:"gas_consumed"` + Stack []request.FuncParam `json:"stack"` + TX string `json:"tx,omitempty"` } `json:"result"` ID int `json:"id"` } diff --git a/pkg/rpc/server_test.go b/pkg/rpc/server_test.go index 6d47f08dd..cad05c0fb 100644 --- a/pkg/rpc/server_test.go +++ b/pkg/rpc/server_test.go @@ -14,6 +14,7 @@ import ( "github.com/CityOfZion/neo-go/pkg/core" "github.com/CityOfZion/neo-go/pkg/core/transaction" + "github.com/CityOfZion/neo-go/pkg/rpc/response" "github.com/CityOfZion/neo-go/pkg/rpc/response/result" "github.com/CityOfZion/neo-go/pkg/util" "github.com/stretchr/testify/assert" @@ -602,7 +603,7 @@ func TestRPC(t *testing.T) { body := doRPCCall(rpc, handler, t) checkErrResponse(t, body, false) - var result GetTxOutResponse + var result response.GetTxOut err := json.Unmarshal(body, &result) require.NoErrorf(t, err, "could not parse response: %s", body) assert.Equal(t, 0, result.Result.N) diff --git a/pkg/rpc/txBuilder.go b/pkg/rpc/txBuilder.go index 349343bc6..a641592a3 100644 --- a/pkg/rpc/txBuilder.go +++ b/pkg/rpc/txBuilder.go @@ -9,6 +9,7 @@ import ( "github.com/CityOfZion/neo-go/pkg/crypto/keys" "github.com/CityOfZion/neo-go/pkg/encoding/address" "github.com/CityOfZion/neo-go/pkg/io" + "github.com/CityOfZion/neo-go/pkg/rpc/request" "github.com/CityOfZion/neo-go/pkg/smartcontract" "github.com/CityOfZion/neo-go/pkg/util" "github.com/CityOfZion/neo-go/pkg/vm/emit" @@ -141,38 +142,38 @@ func CreateDeploymentScript(avm []byte, contract *ContractDetails) ([]byte, erro // expandArrayIntoScript pushes all FuncParam parameters from the given array // into the given buffer in reverse order. -func expandArrayIntoScript(script *io.BinWriter, slice []Param) error { +func expandArrayIntoScript(script *io.BinWriter, slice []request.Param) error { for j := len(slice) - 1; j >= 0; j-- { fp, err := slice[j].GetFuncParam() if err != nil { return err } switch fp.Type { - case ByteArray, Signature: + case request.ByteArray, request.Signature: str, err := fp.Value.GetBytesHex() if err != nil { return err } emit.Bytes(script, str) - case String: + case request.String: str, err := fp.Value.GetString() if err != nil { return err } emit.String(script, str) - case Hash160: + case request.Hash160: hash, err := fp.Value.GetUint160FromHex() if err != nil { return err } emit.Bytes(script, hash.BytesBE()) - case Hash256: + case request.Hash256: hash, err := fp.Value.GetUint256() if err != nil { return err } emit.Bytes(script, hash.BytesBE()) - case PublicKey: + case request.PublicKey: str, err := fp.Value.GetString() if err != nil { return err @@ -182,13 +183,13 @@ func expandArrayIntoScript(script *io.BinWriter, slice []Param) error { return err } emit.Bytes(script, key.Bytes()) - case Integer: + case request.Integer: val, err := fp.Value.GetInt() if err != nil { return err } emit.Int(script, int64(val)) - case Boolean: + case request.Boolean: str, err := fp.Value.GetString() if err != nil { return err @@ -210,19 +211,19 @@ func expandArrayIntoScript(script *io.BinWriter, slice []Param) error { // CreateFunctionInvocationScript creates a script to invoke given contract with // given parameters. -func CreateFunctionInvocationScript(contract util.Uint160, params Params) ([]byte, error) { +func CreateFunctionInvocationScript(contract util.Uint160, params request.Params) ([]byte, error) { script := io.NewBufBinWriter() for i := len(params) - 1; i >= 0; i-- { switch params[i].Type { - case stringT: + case request.StringT: emit.String(script.BinWriter, params[i].String()) - case numberT: + case request.NumberT: num, err := params[i].GetInt() if err != nil { return nil, err } emit.String(script.BinWriter, strconv.Itoa(num)) - case arrayT: + case request.ArrayT: slice, err := params[i].GetArray() if err != nil { return nil, err @@ -244,7 +245,7 @@ func CreateFunctionInvocationScript(contract util.Uint160, params Params) ([]byt // given parameters. It differs from CreateFunctionInvocationScript in that it // expects one array of FuncParams and expands it onto the stack as independent // elements. -func CreateInvocationScript(contract util.Uint160, funcParams []Param) ([]byte, error) { +func CreateInvocationScript(contract util.Uint160, funcParams []request.Param) ([]byte, error) { script := io.NewBufBinWriter() err := expandArrayIntoScript(script.BinWriter, funcParams) if err != nil { diff --git a/pkg/rpc/tx_builder_test.go b/pkg/rpc/tx_builder_test.go index 2a705ae8e..453a503b3 100644 --- a/pkg/rpc/tx_builder_test.go +++ b/pkg/rpc/tx_builder_test.go @@ -4,56 +4,57 @@ import ( "encoding/hex" "testing" + "github.com/CityOfZion/neo-go/pkg/rpc/request" "github.com/CityOfZion/neo-go/pkg/util" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestInvocationScriptCreationGood(t *testing.T) { - p := Param{stringT, "50befd26fdf6e4d957c11e078b24ebce6291456f"} + p := request.Param{Type: request.StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"} contract, err := p.GetUint160FromHex() require.Nil(t, err) var paramScripts = []struct { - ps Params + ps request.Params script string }{{ script: "676f459162ceeb248b071ec157d9e4f6fd26fdbe50", }, { - ps: Params{{stringT, "transfer"}}, + ps: request.Params{{Type: request.StringT, Value: "transfer"}}, script: "087472616e73666572676f459162ceeb248b071ec157d9e4f6fd26fdbe50", }, { - ps: Params{{numberT, 42}}, + ps: request.Params{{Type: request.NumberT, Value: 42}}, script: "023432676f459162ceeb248b071ec157d9e4f6fd26fdbe50", }, { - ps: Params{{stringT, "a"}, {arrayT, []Param{}}}, + ps: request.Params{{Type: request.StringT, Value: "a"}, {Type: request.ArrayT, Value: []request.Param{}}}, script: "00c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", }, { - ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{ByteArray, Param{stringT, "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}}, + ps: request.Params{{Type: request.StringT, Value: "a"}, {Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.ByteArray, Value: request.Param{Type: request.StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}}, script: "1450befd26fdf6e4d957c11e078b24ebce6291456f51c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", }, { - ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{Signature, Param{stringT, "4edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f"}}}}}}, + ps: request.Params{{Type: request.StringT, Value: "a"}, {Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.Signature, Value: request.Param{Type: request.StringT, Value: "4edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f"}}}}}}, script: "404edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f51c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", }, { - ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{String, Param{stringT, "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}}, + ps: request.Params{{Type: request.StringT, Value: "a"}, {Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.String, Value: request.Param{Type: request.StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}}, script: "283530626566643236666466366534643935376331316530373862323465626365363239313435366651c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", }, { - ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{Hash160, Param{stringT, "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}}, + ps: request.Params{{Type: request.StringT, Value: "a"}, {Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.Hash160, Value: request.Param{Type: request.StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}}, script: "146f459162ceeb248b071ec157d9e4f6fd26fdbe5051c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", }, { - ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{Hash256, Param{stringT, "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"}}}}}}, + ps: request.Params{{Type: request.StringT, Value: "a"}, {Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.Hash256, Value: request.Param{Type: request.StringT, Value: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"}}}}}}, script: "20e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c6051c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", }, { - ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{PublicKey, Param{stringT, "03c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c1"}}}}}}, + ps: request.Params{{Type: request.StringT, Value: "a"}, {Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.PublicKey, Value: request.Param{Type: request.StringT, Value: "03c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c1"}}}}}}, script: "2103c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c151c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", }, { - ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{Integer, Param{numberT, 42}}}}}}, + ps: request.Params{{Type: request.StringT, Value: "a"}, {Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.Integer, Value: request.Param{Type: request.NumberT, Value: 42}}}}}}, script: "012a51c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", }, { - ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{Boolean, Param{stringT, "true"}}}}}}, + ps: request.Params{{Type: request.StringT, Value: "a"}, {Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.Boolean, Value: request.Param{Type: request.StringT, Value: "true"}}}}}}, script: "5151c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", }, { - ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{Boolean, Param{stringT, "false"}}}}}}, + ps: request.Params{{Type: request.StringT, Value: "a"}, {Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.Boolean, Value: request.Param{Type: request.StringT, Value: "false"}}}}}}, script: "0051c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", }} for _, ps := range paramScripts { @@ -66,21 +67,21 @@ func TestInvocationScriptCreationGood(t *testing.T) { func TestInvocationScriptCreationBad(t *testing.T) { contract := util.Uint160{} - var testParams = []Params{ - {{numberT, "qwerty"}}, - {{arrayT, 42}}, - {{arrayT, []Param{{numberT, 42}}}}, - {{arrayT, []Param{{funcParamT, FuncParam{ByteArray, Param{stringT, "qwerty"}}}}}}, - {{arrayT, []Param{{funcParamT, FuncParam{Signature, Param{stringT, "qwerty"}}}}}}, - {{arrayT, []Param{{funcParamT, FuncParam{String, Param{numberT, 42}}}}}}, - {{arrayT, []Param{{funcParamT, FuncParam{Hash160, Param{stringT, "qwerty"}}}}}}, - {{arrayT, []Param{{funcParamT, FuncParam{Hash256, Param{stringT, "qwerty"}}}}}}, - {{arrayT, []Param{{funcParamT, FuncParam{PublicKey, Param{numberT, 42}}}}}}, - {{arrayT, []Param{{funcParamT, FuncParam{PublicKey, Param{stringT, "qwerty"}}}}}}, - {{arrayT, []Param{{funcParamT, FuncParam{Integer, Param{stringT, "qwerty"}}}}}}, - {{arrayT, []Param{{funcParamT, FuncParam{Boolean, Param{numberT, 42}}}}}}, - {{arrayT, []Param{{funcParamT, FuncParam{Boolean, Param{stringT, "qwerty"}}}}}}, - {{arrayT, []Param{{funcParamT, FuncParam{Unknown, Param{}}}}}}, + var testParams = []request.Params{ + {{Type: request.NumberT, Value: "qwerty"}}, + {{Type: request.ArrayT, Value: 42}}, + {{Type: request.ArrayT, Value: []request.Param{{Type: request.NumberT, Value: 42}}}}, + {{Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.ByteArray, Value: request.Param{Type: request.StringT, Value: "qwerty"}}}}}}, + {{Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.Signature, Value: request.Param{Type: request.StringT, Value: "qwerty"}}}}}}, + {{Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.String, Value: request.Param{Type: request.NumberT, Value: 42}}}}}}, + {{Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.Hash160, Value: request.Param{Type: request.StringT, Value: "qwerty"}}}}}}, + {{Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.Hash256, Value: request.Param{Type: request.StringT, Value: "qwerty"}}}}}}, + {{Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.PublicKey, Value: request.Param{Type: request.NumberT, Value: 42}}}}}}, + {{Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.PublicKey, Value: request.Param{Type: request.StringT, Value: "qwerty"}}}}}}, + {{Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.Integer, Value: request.Param{Type: request.StringT, Value: "qwerty"}}}}}}, + {{Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.Boolean, Value: request.Param{Type: request.NumberT, Value: 42}}}}}}, + {{Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.Boolean, Value: request.Param{Type: request.StringT, Value: "qwerty"}}}}}}, + {{Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.Unknown, Value: request.Param{}}}}}}, } for _, ps := range testParams { _, err := CreateFunctionInvocationScript(contract, ps) From 63751a6e6bf037912946d716b970a667ce5bf93d Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Mon, 17 Feb 2020 14:40:40 +0300 Subject: [PATCH 11/22] rpc: merge neoscan-related definitions into one file It will make refactoring easier. --- pkg/rpc/neoScanTypes.go | 33 ------------------- .../{neoScanBalanceGetter.go => neoscan.go} | 27 +++++++++++++++ 2 files changed, 27 insertions(+), 33 deletions(-) delete mode 100644 pkg/rpc/neoScanTypes.go rename pkg/rpc/{neoScanBalanceGetter.go => neoscan.go} (81%) diff --git a/pkg/rpc/neoScanTypes.go b/pkg/rpc/neoScanTypes.go deleted file mode 100644 index beb1f8fe1..000000000 --- a/pkg/rpc/neoScanTypes.go +++ /dev/null @@ -1,33 +0,0 @@ -package rpc - -import ( - "github.com/CityOfZion/neo-go/pkg/core/state" - "github.com/CityOfZion/neo-go/pkg/util" -) - -/* - Definition of types, helper functions and variables - required for calculation of transaction inputs using - NeoScan API. -*/ - -type ( - // NeoScanServer stores NEOSCAN URL and API path. - NeoScanServer struct { - URL string // "protocol://host:port/" - Path string // path to API endpoint without wallet address - } - - // Unspent stores Unspents per asset - Unspent struct { - Unspent state.UnspentBalances - Asset string // "NEO" / "GAS" - Amount util.Fixed8 // total unspent of this asset - } - - // NeoScanBalance is a struct of NeoScan response to 'get_balance' request - NeoScanBalance struct { - Balance []*Unspent - Address string - } -) diff --git a/pkg/rpc/neoScanBalanceGetter.go b/pkg/rpc/neoscan.go similarity index 81% rename from pkg/rpc/neoScanBalanceGetter.go rename to pkg/rpc/neoscan.go index 8d9ef5181..6fdda4442 100644 --- a/pkg/rpc/neoScanBalanceGetter.go +++ b/pkg/rpc/neoscan.go @@ -13,6 +13,33 @@ import ( errs "github.com/pkg/errors" ) +/* + Definition of types, helper functions and variables + required for calculation of transaction inputs using + NeoScan API. +*/ + +type ( + // NeoScanServer stores NEOSCAN URL and API path. + NeoScanServer struct { + URL string // "protocol://host:port/" + Path string // path to API endpoint without wallet address + } + + // Unspent stores Unspents per asset + Unspent struct { + Unspent state.UnspentBalances + Asset string // "NEO" / "GAS" + Amount util.Fixed8 // total unspent of this asset + } + + // NeoScanBalance is a struct of NeoScan response to 'get_balance' request + NeoScanBalance struct { + Balance []*Unspent + Address string + } +) + // GetBalance performs a request to get balance for the address specified. func (s NeoScanServer) GetBalance(address string) ([]*Unspent, error) { var ( From 98a1e2170a0f09183b3c30c1beee18773d2a3b56 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Mon, 17 Feb 2020 14:42:57 +0300 Subject: [PATCH 12/22] rpc: move ContractDetails to request/ package --- cli/smartcontract/smart_contract.go | 6 +++--- pkg/rpc/{ => request}/scdetails.go | 8 +++----- pkg/rpc/txBuilder.go | 2 +- 3 files changed, 7 insertions(+), 9 deletions(-) rename pkg/rpc/{ => request}/scdetails.go (66%) diff --git a/cli/smartcontract/smart_contract.go b/cli/smartcontract/smart_contract.go index b3e31a028..d91abc333 100644 --- a/cli/smartcontract/smart_contract.go +++ b/cli/smartcontract/smart_contract.go @@ -477,11 +477,11 @@ func testInvokeScript(ctx *cli.Context) error { // ProjectConfig contains project metadata. type ProjectConfig struct { Version uint - Contract rpc.ContractDetails `yaml:"project"` + Contract request.ContractDetails `yaml:"project"` } -func parseContractDetails() rpc.ContractDetails { - details := rpc.ContractDetails{} +func parseContractDetails() request.ContractDetails { + details := request.ContractDetails{} reader := bufio.NewReader(os.Stdin) fmt.Print("Author: ") diff --git a/pkg/rpc/scdetails.go b/pkg/rpc/request/scdetails.go similarity index 66% rename from pkg/rpc/scdetails.go rename to pkg/rpc/request/scdetails.go index f5cd242c8..f6f910aff 100644 --- a/pkg/rpc/scdetails.go +++ b/pkg/rpc/request/scdetails.go @@ -1,6 +1,4 @@ -package rpc - -import "github.com/CityOfZion/neo-go/pkg/rpc/request" +package request // ContractDetails contains contract metadata. type ContractDetails struct { @@ -12,6 +10,6 @@ type ContractDetails struct { HasStorage bool HasDynamicInvocation bool IsPayable bool - ReturnType request.StackParamType - Parameters []request.StackParamType + ReturnType StackParamType + Parameters []StackParamType } diff --git a/pkg/rpc/txBuilder.go b/pkg/rpc/txBuilder.go index a641592a3..f93e7a89a 100644 --- a/pkg/rpc/txBuilder.go +++ b/pkg/rpc/txBuilder.go @@ -110,7 +110,7 @@ func GetInvocationScript(tx *transaction.Transaction, wif *keys.WIF) ([]byte, er // CreateDeploymentScript returns a script that deploys given smart contract // with its metadata. -func CreateDeploymentScript(avm []byte, contract *ContractDetails) ([]byte, error) { +func CreateDeploymentScript(avm []byte, contract *request.ContractDetails) ([]byte, error) { var props smartcontract.PropertyState script := io.NewBufBinWriter() From b6bc4e580a7de1af92716094936aff822f0fcf36 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Mon, 17 Feb 2020 14:54:53 +0300 Subject: [PATCH 13/22] rpc: move functions for Tx building in request package/ --- cli/smartcontract/smart_contract.go | 2 +- integration/performance_test.go | 4 +- pkg/rpc/client.go | 6 +- pkg/rpc/{ => request}/txBuilder.go | 33 +++++------ pkg/rpc/{ => request}/txTypes.go | 12 ++-- pkg/rpc/request/tx_builder_test.go | 89 ++++++++++++++++++++++++++++ pkg/rpc/rpc.go | 18 +++--- pkg/rpc/server.go | 4 +- pkg/rpc/tx_builder_test.go | 90 ----------------------------- 9 files changed, 128 insertions(+), 130 deletions(-) rename pkg/rpc/{ => request}/txBuilder.go (91%) rename pkg/rpc/{ => request}/txTypes.go (88%) create mode 100644 pkg/rpc/request/tx_builder_test.go delete mode 100644 pkg/rpc/tx_builder_test.go diff --git a/cli/smartcontract/smart_contract.go b/cli/smartcontract/smart_contract.go index d91abc333..5c9011770 100644 --- a/cli/smartcontract/smart_contract.go +++ b/cli/smartcontract/smart_contract.go @@ -578,7 +578,7 @@ func contractDeploy(ctx *cli.Context) error { return cli.NewExitError(err, 1) } - txScript, err := rpc.CreateDeploymentScript(avm, &conf.Contract) + txScript, err := request.CreateDeploymentScript(avm, &conf.Contract) if err != nil { return cli.NewExitError(fmt.Errorf("failed to create deployment script: %v", err), 1) } diff --git a/integration/performance_test.go b/integration/performance_test.go index 42c5e2011..f3b63be2f 100644 --- a/integration/performance_test.go +++ b/integration/performance_test.go @@ -11,7 +11,7 @@ import ( "github.com/CityOfZion/neo-go/pkg/crypto/keys" "github.com/CityOfZion/neo-go/pkg/encoding/address" "github.com/CityOfZion/neo-go/pkg/network" - "github.com/CityOfZion/neo-go/pkg/rpc" + "github.com/CityOfZion/neo-go/pkg/rpc/request" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" ) @@ -55,7 +55,7 @@ func prepareData(t *testing.B) []*transaction.Transaction { for n := 0; n < t.N; n++ { tx := getTX(t, wif) - require.NoError(t, rpc.SignTx(tx, wif)) + require.NoError(t, request.SignTx(tx, wif)) data = append(data, tx) } return data diff --git a/pkg/rpc/client.go b/pkg/rpc/client.go index 167c7c8bf..715c81411 100644 --- a/pkg/rpc/client.go +++ b/pkg/rpc/client.go @@ -38,7 +38,7 @@ type Client struct { wifMu *sync.Mutex wif *keys.WIF balancerMu *sync.Mutex - balancer BalanceGetter + balancer request.BalanceGetter } // ClientOptions defines options for the RPC client. @@ -122,14 +122,14 @@ func (c *Client) SetWIF(wif string) error { } // Balancer is a getter for balance field. -func (c *Client) Balancer() BalanceGetter { +func (c *Client) Balancer() request.BalanceGetter { c.balancerMu.Lock() defer c.balancerMu.Unlock() return c.balancer } // SetBalancer is a setter for balance field. -func (c *Client) SetBalancer(b BalanceGetter) { +func (c *Client) SetBalancer(b request.BalanceGetter) { c.balancerMu.Lock() defer c.balancerMu.Unlock() diff --git a/pkg/rpc/txBuilder.go b/pkg/rpc/request/txBuilder.go similarity index 91% rename from pkg/rpc/txBuilder.go rename to pkg/rpc/request/txBuilder.go index f93e7a89a..df14e84c2 100644 --- a/pkg/rpc/txBuilder.go +++ b/pkg/rpc/request/txBuilder.go @@ -1,4 +1,4 @@ -package rpc +package request import ( "errors" @@ -9,7 +9,6 @@ import ( "github.com/CityOfZion/neo-go/pkg/crypto/keys" "github.com/CityOfZion/neo-go/pkg/encoding/address" "github.com/CityOfZion/neo-go/pkg/io" - "github.com/CityOfZion/neo-go/pkg/rpc/request" "github.com/CityOfZion/neo-go/pkg/smartcontract" "github.com/CityOfZion/neo-go/pkg/util" "github.com/CityOfZion/neo-go/pkg/vm/emit" @@ -26,7 +25,7 @@ func CreateRawContractTransaction(params ContractTxParams) (*transaction.Transac fromAddress string receiverOutput *transaction.Output - wif, assetID, toAddress, amount, balancer = params.wif, params.assetID, params.address, params.value, params.balancer + wif, assetID, toAddress, amount, balancer = params.WIF, params.AssetID, params.Address, params.Value, params.Balancer ) fromAddress = wif.PrivateKey.Address() @@ -110,7 +109,7 @@ func GetInvocationScript(tx *transaction.Transaction, wif *keys.WIF) ([]byte, er // CreateDeploymentScript returns a script that deploys given smart contract // with its metadata. -func CreateDeploymentScript(avm []byte, contract *request.ContractDetails) ([]byte, error) { +func CreateDeploymentScript(avm []byte, contract *ContractDetails) ([]byte, error) { var props smartcontract.PropertyState script := io.NewBufBinWriter() @@ -142,38 +141,38 @@ func CreateDeploymentScript(avm []byte, contract *request.ContractDetails) ([]by // expandArrayIntoScript pushes all FuncParam parameters from the given array // into the given buffer in reverse order. -func expandArrayIntoScript(script *io.BinWriter, slice []request.Param) error { +func expandArrayIntoScript(script *io.BinWriter, slice []Param) error { for j := len(slice) - 1; j >= 0; j-- { fp, err := slice[j].GetFuncParam() if err != nil { return err } switch fp.Type { - case request.ByteArray, request.Signature: + case ByteArray, Signature: str, err := fp.Value.GetBytesHex() if err != nil { return err } emit.Bytes(script, str) - case request.String: + case String: str, err := fp.Value.GetString() if err != nil { return err } emit.String(script, str) - case request.Hash160: + case Hash160: hash, err := fp.Value.GetUint160FromHex() if err != nil { return err } emit.Bytes(script, hash.BytesBE()) - case request.Hash256: + case Hash256: hash, err := fp.Value.GetUint256() if err != nil { return err } emit.Bytes(script, hash.BytesBE()) - case request.PublicKey: + case PublicKey: str, err := fp.Value.GetString() if err != nil { return err @@ -183,13 +182,13 @@ func expandArrayIntoScript(script *io.BinWriter, slice []request.Param) error { return err } emit.Bytes(script, key.Bytes()) - case request.Integer: + case Integer: val, err := fp.Value.GetInt() if err != nil { return err } emit.Int(script, int64(val)) - case request.Boolean: + case Boolean: str, err := fp.Value.GetString() if err != nil { return err @@ -211,19 +210,19 @@ func expandArrayIntoScript(script *io.BinWriter, slice []request.Param) error { // CreateFunctionInvocationScript creates a script to invoke given contract with // given parameters. -func CreateFunctionInvocationScript(contract util.Uint160, params request.Params) ([]byte, error) { +func CreateFunctionInvocationScript(contract util.Uint160, params Params) ([]byte, error) { script := io.NewBufBinWriter() for i := len(params) - 1; i >= 0; i-- { switch params[i].Type { - case request.StringT: + case StringT: emit.String(script.BinWriter, params[i].String()) - case request.NumberT: + case NumberT: num, err := params[i].GetInt() if err != nil { return nil, err } emit.String(script.BinWriter, strconv.Itoa(num)) - case request.ArrayT: + case ArrayT: slice, err := params[i].GetArray() if err != nil { return nil, err @@ -245,7 +244,7 @@ func CreateFunctionInvocationScript(contract util.Uint160, params request.Params // given parameters. It differs from CreateFunctionInvocationScript in that it // expects one array of FuncParams and expands it onto the stack as independent // elements. -func CreateInvocationScript(contract util.Uint160, funcParams []request.Param) ([]byte, error) { +func CreateInvocationScript(contract util.Uint160, funcParams []Param) ([]byte, error) { script := io.NewBufBinWriter() err := expandArrayIntoScript(script.BinWriter, funcParams) if err != nil { diff --git a/pkg/rpc/txTypes.go b/pkg/rpc/request/txTypes.go similarity index 88% rename from pkg/rpc/txTypes.go rename to pkg/rpc/request/txTypes.go index 29065d41a..b97809bf0 100644 --- a/pkg/rpc/txTypes.go +++ b/pkg/rpc/request/txTypes.go @@ -1,4 +1,4 @@ -package rpc +package request /* Definition of types, interfaces and variables @@ -16,14 +16,14 @@ type ( // includes parameters duplication `sendtoaddress` RPC call params // and also some utility data; ContractTxParams struct { - assetID util.Uint256 - address string - value util.Fixed8 - wif keys.WIF // a WIF to send the transaction + AssetID util.Uint256 + Address string + Value util.Fixed8 + WIF keys.WIF // a WIF to send the transaction // since there are many ways to provide unspents, // transaction composer stays agnostic to that how // unspents was got; - balancer BalanceGetter + Balancer BalanceGetter } // BalanceGetter is an interface supporting CalculateInputs() method. diff --git a/pkg/rpc/request/tx_builder_test.go b/pkg/rpc/request/tx_builder_test.go new file mode 100644 index 000000000..a4e29edd5 --- /dev/null +++ b/pkg/rpc/request/tx_builder_test.go @@ -0,0 +1,89 @@ +package request + +import ( + "encoding/hex" + "testing" + + "github.com/CityOfZion/neo-go/pkg/util" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestInvocationScriptCreationGood(t *testing.T) { + p := Param{Type: StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"} + contract, err := p.GetUint160FromHex() + require.Nil(t, err) + + var paramScripts = []struct { + ps Params + script string + }{{ + script: "676f459162ceeb248b071ec157d9e4f6fd26fdbe50", + }, { + ps: Params{{Type: StringT, Value: "transfer"}}, + script: "087472616e73666572676f459162ceeb248b071ec157d9e4f6fd26fdbe50", + }, { + ps: Params{{Type: NumberT, Value: 42}}, + script: "023432676f459162ceeb248b071ec157d9e4f6fd26fdbe50", + }, { + ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{}}}, + script: "00c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", + }, { + ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: ByteArray, Value: Param{Type: StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}}, + script: "1450befd26fdf6e4d957c11e078b24ebce6291456f51c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", + }, { + ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Signature, Value: Param{Type: StringT, Value: "4edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f"}}}}}}, + script: "404edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f51c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", + }, { + ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: String, Value: Param{Type: StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}}, + script: "283530626566643236666466366534643935376331316530373862323465626365363239313435366651c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", + }, { + ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Hash160, Value: Param{Type: StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}}, + script: "146f459162ceeb248b071ec157d9e4f6fd26fdbe5051c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", + }, { + ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Hash256, Value: Param{Type: StringT, Value: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"}}}}}}, + script: "20e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c6051c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", + }, { + ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: PublicKey, Value: Param{Type: StringT, Value: "03c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c1"}}}}}}, + script: "2103c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c151c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", + }, { + ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Integer, Value: Param{Type: NumberT, Value: 42}}}}}}, + script: "012a51c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", + }, { + ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Boolean, Value: Param{Type: StringT, Value: "true"}}}}}}, + script: "5151c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", + }, { + ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Boolean, Value: Param{Type: StringT, Value: "false"}}}}}}, + script: "0051c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", + }} + for _, ps := range paramScripts { + script, err := CreateFunctionInvocationScript(contract, ps.ps) + assert.Nil(t, err) + assert.Equal(t, ps.script, hex.EncodeToString(script)) + } +} + +func TestInvocationScriptCreationBad(t *testing.T) { + contract := util.Uint160{} + + var testParams = []Params{ + {{Type: NumberT, Value: "qwerty"}}, + {{Type: ArrayT, Value: 42}}, + {{Type: ArrayT, Value: []Param{{Type: NumberT, Value: 42}}}}, + {{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: ByteArray, Value: Param{Type: StringT, Value: "qwerty"}}}}}}, + {{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Signature, Value: Param{Type: StringT, Value: "qwerty"}}}}}}, + {{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: String, Value: Param{Type: NumberT, Value: 42}}}}}}, + {{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Hash160, Value: Param{Type: StringT, Value: "qwerty"}}}}}}, + {{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Hash256, Value: Param{Type: StringT, Value: "qwerty"}}}}}}, + {{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: PublicKey, Value: Param{Type: NumberT, Value: 42}}}}}}, + {{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: PublicKey, Value: Param{Type: StringT, Value: "qwerty"}}}}}}, + {{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Integer, Value: Param{Type: StringT, Value: "qwerty"}}}}}}, + {{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Boolean, Value: Param{Type: NumberT, Value: 42}}}}}}, + {{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Boolean, Value: Param{Type: StringT, Value: "qwerty"}}}}}}, + {{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Unknown, Value: Param{}}}}}}, + } + for _, ps := range testParams { + _, err := CreateFunctionInvocationScript(contract, ps) + assert.NotNil(t, err) + } +} diff --git a/pkg/rpc/rpc.go b/pkg/rpc/rpc.go index 29a9f1a15..d778d1a1b 100644 --- a/pkg/rpc/rpc.go +++ b/pkg/rpc/rpc.go @@ -130,18 +130,18 @@ func (c *Client) SendToAddress(asset util.Uint256, address string, amount util.F var ( err error rawTx *transaction.Transaction - txParams = ContractTxParams{ - assetID: asset, - address: address, - value: amount, - wif: c.WIF(), - balancer: c.Balancer(), + txParams = request.ContractTxParams{ + AssetID: asset, + Address: address, + Value: amount, + WIF: c.WIF(), + Balancer: c.Balancer(), } respRaw *response.Raw resp = &response.SendToAddress{} ) - if rawTx, err = CreateRawContractTransaction(txParams); err != nil { + if rawTx, err = request.CreateRawContractTransaction(txParams); err != nil { return nil, errors.Wrap(err, "failed to create raw transaction for `sendtoaddress`") } if respRaw, err = c.sendRawTransaction(rawTx); err != nil { @@ -168,12 +168,12 @@ func (c *Client) SignAndPushInvocationTx(script []byte, wif *keys.WIF, gas util. fromAddress := wif.PrivateKey.Address() if gas > 0 { - if err = AddInputsAndUnspentsToTx(tx, fromAddress, core.UtilityTokenID(), gas, c); err != nil { + if err = request.AddInputsAndUnspentsToTx(tx, fromAddress, core.UtilityTokenID(), gas, c); err != nil { return txHash, errors.Wrap(err, "failed to add inputs and unspents to transaction") } } - if err = SignTx(tx, wif); err != nil { + if err = request.SignTx(tx, wif); err != nil { return txHash, errors.Wrap(err, "failed to sign tx") } txHash = tx.Hash() diff --git a/pkg/rpc/server.go b/pkg/rpc/server.go index 8b3553366..e795b1ee5 100644 --- a/pkg/rpc/server.go +++ b/pkg/rpc/server.go @@ -448,7 +448,7 @@ func (s *Server) invoke(reqParams request.Params) (interface{}, error) { if err != nil { return nil, err } - script, err := CreateInvocationScript(scriptHash, slice) + script, err := request.CreateInvocationScript(scriptHash, slice) if err != nil { return nil, err } @@ -465,7 +465,7 @@ func (s *Server) invokeFunction(reqParams request.Params) (interface{}, error) { if err != nil { return nil, err } - script, err := CreateFunctionInvocationScript(scriptHash, reqParams[1:]) + script, err := request.CreateFunctionInvocationScript(scriptHash, reqParams[1:]) if err != nil { return nil, err } diff --git a/pkg/rpc/tx_builder_test.go b/pkg/rpc/tx_builder_test.go deleted file mode 100644 index 453a503b3..000000000 --- a/pkg/rpc/tx_builder_test.go +++ /dev/null @@ -1,90 +0,0 @@ -package rpc - -import ( - "encoding/hex" - "testing" - - "github.com/CityOfZion/neo-go/pkg/rpc/request" - "github.com/CityOfZion/neo-go/pkg/util" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestInvocationScriptCreationGood(t *testing.T) { - p := request.Param{Type: request.StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"} - contract, err := p.GetUint160FromHex() - require.Nil(t, err) - - var paramScripts = []struct { - ps request.Params - script string - }{{ - script: "676f459162ceeb248b071ec157d9e4f6fd26fdbe50", - }, { - ps: request.Params{{Type: request.StringT, Value: "transfer"}}, - script: "087472616e73666572676f459162ceeb248b071ec157d9e4f6fd26fdbe50", - }, { - ps: request.Params{{Type: request.NumberT, Value: 42}}, - script: "023432676f459162ceeb248b071ec157d9e4f6fd26fdbe50", - }, { - ps: request.Params{{Type: request.StringT, Value: "a"}, {Type: request.ArrayT, Value: []request.Param{}}}, - script: "00c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", - }, { - ps: request.Params{{Type: request.StringT, Value: "a"}, {Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.ByteArray, Value: request.Param{Type: request.StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}}, - script: "1450befd26fdf6e4d957c11e078b24ebce6291456f51c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", - }, { - ps: request.Params{{Type: request.StringT, Value: "a"}, {Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.Signature, Value: request.Param{Type: request.StringT, Value: "4edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f"}}}}}}, - script: "404edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f51c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", - }, { - ps: request.Params{{Type: request.StringT, Value: "a"}, {Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.String, Value: request.Param{Type: request.StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}}, - script: "283530626566643236666466366534643935376331316530373862323465626365363239313435366651c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", - }, { - ps: request.Params{{Type: request.StringT, Value: "a"}, {Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.Hash160, Value: request.Param{Type: request.StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}}, - script: "146f459162ceeb248b071ec157d9e4f6fd26fdbe5051c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", - }, { - ps: request.Params{{Type: request.StringT, Value: "a"}, {Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.Hash256, Value: request.Param{Type: request.StringT, Value: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"}}}}}}, - script: "20e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c6051c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", - }, { - ps: request.Params{{Type: request.StringT, Value: "a"}, {Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.PublicKey, Value: request.Param{Type: request.StringT, Value: "03c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c1"}}}}}}, - script: "2103c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c151c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", - }, { - ps: request.Params{{Type: request.StringT, Value: "a"}, {Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.Integer, Value: request.Param{Type: request.NumberT, Value: 42}}}}}}, - script: "012a51c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", - }, { - ps: request.Params{{Type: request.StringT, Value: "a"}, {Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.Boolean, Value: request.Param{Type: request.StringT, Value: "true"}}}}}}, - script: "5151c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", - }, { - ps: request.Params{{Type: request.StringT, Value: "a"}, {Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.Boolean, Value: request.Param{Type: request.StringT, Value: "false"}}}}}}, - script: "0051c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", - }} - for _, ps := range paramScripts { - script, err := CreateFunctionInvocationScript(contract, ps.ps) - assert.Nil(t, err) - assert.Equal(t, ps.script, hex.EncodeToString(script)) - } -} - -func TestInvocationScriptCreationBad(t *testing.T) { - contract := util.Uint160{} - - var testParams = []request.Params{ - {{Type: request.NumberT, Value: "qwerty"}}, - {{Type: request.ArrayT, Value: 42}}, - {{Type: request.ArrayT, Value: []request.Param{{Type: request.NumberT, Value: 42}}}}, - {{Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.ByteArray, Value: request.Param{Type: request.StringT, Value: "qwerty"}}}}}}, - {{Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.Signature, Value: request.Param{Type: request.StringT, Value: "qwerty"}}}}}}, - {{Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.String, Value: request.Param{Type: request.NumberT, Value: 42}}}}}}, - {{Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.Hash160, Value: request.Param{Type: request.StringT, Value: "qwerty"}}}}}}, - {{Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.Hash256, Value: request.Param{Type: request.StringT, Value: "qwerty"}}}}}}, - {{Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.PublicKey, Value: request.Param{Type: request.NumberT, Value: 42}}}}}}, - {{Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.PublicKey, Value: request.Param{Type: request.StringT, Value: "qwerty"}}}}}}, - {{Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.Integer, Value: request.Param{Type: request.StringT, Value: "qwerty"}}}}}}, - {{Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.Boolean, Value: request.Param{Type: request.NumberT, Value: 42}}}}}}, - {{Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.Boolean, Value: request.Param{Type: request.StringT, Value: "qwerty"}}}}}}, - {{Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.Unknown, Value: request.Param{}}}}}}, - } - for _, ps := range testParams { - _, err := CreateFunctionInvocationScript(contract, ps) - assert.NotNil(t, err) - } -} From d24c6d1d9e8ff32a024fa2a1efef53b3c1a95204 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Mon, 17 Feb 2020 15:01:57 +0300 Subject: [PATCH 14/22] rpc: move client-related code to a separate package This includes Client struct with RPC methods and BalanceGetter implementation with NeoSCAN. --- cli/smartcontract/smart_contract.go | 18 ++++---- pkg/rpc/{ => client}/client.go | 10 ++--- pkg/rpc/client/doc.go | 69 +++++++++++++++++++++++++++++ pkg/rpc/{ => client}/neoscan.go | 2 +- pkg/rpc/{ => client}/rpc.go | 2 +- pkg/rpc/doc.go | 65 +-------------------------- 6 files changed, 86 insertions(+), 80 deletions(-) rename pkg/rpc/{ => client}/client.go (95%) create mode 100644 pkg/rpc/client/doc.go rename pkg/rpc/{ => client}/neoscan.go (99%) rename pkg/rpc/{ => client}/rpc.go (99%) diff --git a/cli/smartcontract/smart_contract.go b/cli/smartcontract/smart_contract.go index 5c9011770..fcb2f2c2b 100644 --- a/cli/smartcontract/smart_contract.go +++ b/cli/smartcontract/smart_contract.go @@ -14,7 +14,7 @@ import ( "github.com/CityOfZion/neo-go/pkg/compiler" "github.com/CityOfZion/neo-go/pkg/crypto/hash" "github.com/CityOfZion/neo-go/pkg/crypto/keys" - "github.com/CityOfZion/neo-go/pkg/rpc" + "github.com/CityOfZion/neo-go/pkg/rpc/client" "github.com/CityOfZion/neo-go/pkg/rpc/request" "github.com/CityOfZion/neo-go/pkg/rpc/response" "github.com/CityOfZion/neo-go/pkg/smartcontract" @@ -400,15 +400,15 @@ func invokeInternal(ctx *cli.Context, withMethod bool, signAndPush bool) error { return err } } - client, err := rpc.NewClient(context.TODO(), endpoint, rpc.ClientOptions{}) + c, err := client.New(context.TODO(), endpoint, client.Options{}) if err != nil { return cli.NewExitError(err, 1) } if withMethod { - resp, err = client.InvokeFunction(script, operation, params) + resp, err = c.InvokeFunction(script, operation, params) } else { - resp, err = client.Invoke(script, params) + resp, err = c.Invoke(script, params) } if err != nil { return cli.NewExitError(err, 1) @@ -421,7 +421,7 @@ func invokeInternal(ctx *cli.Context, withMethod bool, signAndPush bool) error { if err != nil { return cli.NewExitError(fmt.Errorf("bad script returned from the RPC node: %v", err), 1) } - txHash, err := client.SignAndPushInvocationTx(script, wif, gas) + txHash, err := c.SignAndPushInvocationTx(script, wif, gas) if err != nil { return cli.NewExitError(fmt.Errorf("failed to push invocation tx: %v", err), 1) } @@ -453,13 +453,13 @@ func testInvokeScript(ctx *cli.Context) error { return cli.NewExitError(err, 1) } - client, err := rpc.NewClient(context.TODO(), endpoint, rpc.ClientOptions{}) + c, err := client.New(context.TODO(), endpoint, client.Options{}) if err != nil { return cli.NewExitError(err, 1) } scriptHex := hex.EncodeToString(b) - resp, err := client.InvokeScript(scriptHex) + resp, err := c.InvokeScript(scriptHex) if err != nil { return cli.NewExitError(err, 1) } @@ -573,7 +573,7 @@ func contractDeploy(ctx *cli.Context) error { return cli.NewExitError(fmt.Errorf("bad config: %v", err), 1) } - client, err := rpc.NewClient(context.TODO(), endpoint, rpc.ClientOptions{}) + c, err := client.New(context.TODO(), endpoint, client.Options{}) if err != nil { return cli.NewExitError(err, 1) } @@ -583,7 +583,7 @@ func contractDeploy(ctx *cli.Context) error { return cli.NewExitError(fmt.Errorf("failed to create deployment script: %v", err), 1) } - txHash, err := client.SignAndPushInvocationTx(txScript, wif, gas) + txHash, err := c.SignAndPushInvocationTx(txScript, wif, gas) if err != nil { return cli.NewExitError(fmt.Errorf("failed to push invocation tx: %v", err), 1) } diff --git a/pkg/rpc/client.go b/pkg/rpc/client/client.go similarity index 95% rename from pkg/rpc/client.go rename to pkg/rpc/client/client.go index 715c81411..d41c6679a 100644 --- a/pkg/rpc/client.go +++ b/pkg/rpc/client/client.go @@ -1,4 +1,4 @@ -package rpc +package client import ( "bytes" @@ -41,10 +41,10 @@ type Client struct { balancer request.BalanceGetter } -// ClientOptions defines options for the RPC client. +// Options defines options for the RPC client. // All Values are optional. If any duration is not specified // a default of 3 seconds will be used. -type ClientOptions struct { +type Options struct { Cert string Key string CACert string @@ -56,8 +56,8 @@ type ClientOptions struct { Version string } -// NewClient returns a new Client ready to use. -func NewClient(ctx context.Context, endpoint string, opts ClientOptions) (*Client, error) { +// New returns a new Client ready to use. +func New(ctx context.Context, endpoint string, opts Options) (*Client, error) { url, err := url.Parse(endpoint) if err != nil { return nil, err diff --git a/pkg/rpc/client/doc.go b/pkg/rpc/client/doc.go new file mode 100644 index 000000000..8c82d20c9 --- /dev/null +++ b/pkg/rpc/client/doc.go @@ -0,0 +1,69 @@ +/* +Package client implements NEO-specific JSON-RPC 2.0 client. +This package is currently in alpha and is subject to change. + +Client + +After creating a client instance with or without a ClientConfig +you can interact with the NEO blockchain by its exposed methods. + +Some of the methods also allow to pass a verbose bool. This will +return a more pretty printed response from the server instead of +a raw hex string. + +An example: + endpoint := "http://seed5.bridgeprotocol.io:10332" + opts := client.Options{} + + c, err := client.New(context.TODO(), endpoint, opts) + if err != nil { + log.Fatal(err) + } + + if err := c.Ping(); err != nil { + log.Fatal(err) + } + + resp, err := c.GetAccountState("ATySFJAbLW7QHsZGHScLhxq6EyNBxx3eFP") + if err != nil { + log.Fatal(err) + } + log.Println(resp.Result.ScriptHash) + log.Println(resp.Result.Balances) + +TODO: + Merge structs so can be used by both server and client. + Add missing methods to client. + Allow client to connect using client cert. + More in-depth examples. + +Supported methods + + getblock + getaccountstate + getunspents + invokescript + invokefunction + sendrawtransaction + invoke + getrawtransaction + +Unsupported methods + + validateaddress + getblocksysfee + getcontractstate + getrawmempool + getstorage + submitblock + gettxout + getassetstate + getpeers + getversion + getconnectioncount + getblockhash + getblockcount + getbestblockhash + +*/ +package client diff --git a/pkg/rpc/neoscan.go b/pkg/rpc/client/neoscan.go similarity index 99% rename from pkg/rpc/neoscan.go rename to pkg/rpc/client/neoscan.go index 6fdda4442..76d65bcfc 100644 --- a/pkg/rpc/neoscan.go +++ b/pkg/rpc/client/neoscan.go @@ -1,4 +1,4 @@ -package rpc +package client import ( "encoding/json" diff --git a/pkg/rpc/rpc.go b/pkg/rpc/client/rpc.go similarity index 99% rename from pkg/rpc/rpc.go rename to pkg/rpc/client/rpc.go index d778d1a1b..e7c135c69 100644 --- a/pkg/rpc/rpc.go +++ b/pkg/rpc/client/rpc.go @@ -1,4 +1,4 @@ -package rpc +package client import ( "encoding/hex" diff --git a/pkg/rpc/doc.go b/pkg/rpc/doc.go index 5ce0884ef..fb44d1b1d 100644 --- a/pkg/rpc/doc.go +++ b/pkg/rpc/doc.go @@ -1,70 +1,7 @@ /* -Package rpc implements NEO-specific JSON-RPC 2.0 client and server. +Package rpc implements NEO-specific JSON-RPC 2.0 server. This package is currently in alpha and is subject to change. -Client - -After creating a client instance with or without a ClientConfig -you can interact with the NEO blockchain by its exposed methods. - -Some of the methods also allow to pass a verbose bool. This will -return a more pretty printed response from the server instead of -a raw hex string. - -An example: - endpoint := "http://seed5.bridgeprotocol.io:10332" - opts := rpc.ClientOptions{} - - client, err := rpc.NewClient(context.TODO(), endpoint, opts) - if err != nil { - log.Fatal(err) - } - - if err := client.Ping(); err != nil { - log.Fatal(err) - } - - resp, err := client.GetAccountState("ATySFJAbLW7QHsZGHScLhxq6EyNBxx3eFP") - if err != nil { - log.Fatal(err) - } - log.Println(resp.Result.ScriptHash) - log.Println(resp.Result.Balances) - -TODO: - Merge structs so can be used by both server and client. - Add missing methods to client. - Allow client to connect using client cert. - More in-depth examples. - -Supported methods - - getblock - getaccountstate - getunspents - invokescript - invokefunction - sendrawtransaction - invoke - getrawtransaction - -Unsupported methods - - validateaddress - getblocksysfee - getcontractstate - getrawmempool - getstorage - submitblock - gettxout - getassetstate - getpeers - getversion - getconnectioncount - getblockhash - getblockcount - getbestblockhash - Server The server is written to support as much of the JSON-RPC 2.0 Spec as possible. From b50704fd3bfce709975ced5e83ad9923be020388 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Mon, 17 Feb 2020 15:17:02 +0300 Subject: [PATCH 15/22] rpc: move server-related code to a separate package --- cli/server/server.go | 12 ++++++------ pkg/rpc/{ => server}/doc.go | 4 ++-- pkg/rpc/{ => server}/prometheus.go | 2 +- pkg/rpc/{ => server}/server.go | 6 +++--- pkg/rpc/{ => server}/server_helper_test.go | 6 +++--- pkg/rpc/{ => server}/server_test.go | 2 +- pkg/rpc/{ => server}/testdata/test_contract.avm | Bin pkg/rpc/{ => server}/testdata/testblocks.acc | Bin 8 files changed, 16 insertions(+), 16 deletions(-) rename pkg/rpc/{ => server}/doc.go (94%) rename pkg/rpc/{ => server}/prometheus.go (99%) rename pkg/rpc/{ => server}/server.go (98%) rename pkg/rpc/{ => server}/server_helper_test.go (98%) rename pkg/rpc/{ => server}/server_test.go (99%) rename pkg/rpc/{ => server}/testdata/test_contract.avm (100%) rename pkg/rpc/{ => server}/testdata/testblocks.acc (100%) diff --git a/cli/server/server.go b/cli/server/server.go index a8af9571a..f2bc41edd 100644 --- a/cli/server/server.go +++ b/cli/server/server.go @@ -14,7 +14,7 @@ import ( "github.com/CityOfZion/neo-go/pkg/io" "github.com/CityOfZion/neo-go/pkg/network" "github.com/CityOfZion/neo-go/pkg/network/metrics" - "github.com/CityOfZion/neo-go/pkg/rpc" + "github.com/CityOfZion/neo-go/pkg/rpc/server" "github.com/pkg/errors" "github.com/urfave/cli" "go.uber.org/zap" @@ -341,18 +341,18 @@ func startServer(ctx *cli.Context) error { return err } - server, err := network.NewServer(serverConfig, chain, log) + serv, err := network.NewServer(serverConfig, chain, log) if err != nil { return cli.NewExitError(fmt.Errorf("failed to create network server: %v", err), 1) } - rpcServer := rpc.NewServer(chain, cfg.ApplicationConfiguration.RPC, server, log) + rpcServer := server.New(chain, cfg.ApplicationConfiguration.RPC, serv, log) errChan := make(chan error) - go server.Start(errChan) + go serv.Start(errChan) go rpcServer.Start(errChan) fmt.Println(logo()) - fmt.Println(server.UserAgent) + fmt.Println(serv.UserAgent) fmt.Println() var shutdownErr error @@ -364,7 +364,7 @@ Main: cancel() case <-grace.Done(): - server.Shutdown() + serv.Shutdown() if serverErr := rpcServer.Shutdown(); serverErr != nil { shutdownErr = errors.Wrap(serverErr, "Error encountered whilst shutting down server") } diff --git a/pkg/rpc/doc.go b/pkg/rpc/server/doc.go similarity index 94% rename from pkg/rpc/doc.go rename to pkg/rpc/server/doc.go index fb44d1b1d..dbf7aae9a 100644 --- a/pkg/rpc/doc.go +++ b/pkg/rpc/server/doc.go @@ -1,5 +1,5 @@ /* -Package rpc implements NEO-specific JSON-RPC 2.0 server. +Package server implements NEO-specific JSON-RPC 2.0 server. This package is currently in alpha and is subject to change. Server @@ -45,4 +45,4 @@ Unsupported methods submitblock (needs to be implemented in pkg/core/blockchain.go) */ -package rpc +package server diff --git a/pkg/rpc/prometheus.go b/pkg/rpc/server/prometheus.go similarity index 99% rename from pkg/rpc/prometheus.go rename to pkg/rpc/server/prometheus.go index 2e5d5b4db..63312433d 100644 --- a/pkg/rpc/prometheus.go +++ b/pkg/rpc/server/prometheus.go @@ -1,4 +1,4 @@ -package rpc +package server import "github.com/prometheus/client_golang/prometheus" diff --git a/pkg/rpc/server.go b/pkg/rpc/server/server.go similarity index 98% rename from pkg/rpc/server.go rename to pkg/rpc/server/server.go index e795b1ee5..d4ecb4518 100644 --- a/pkg/rpc/server.go +++ b/pkg/rpc/server/server.go @@ -1,4 +1,4 @@ -package rpc +package server import ( "context" @@ -38,8 +38,8 @@ var invalidBlockHeightError = func(index int, height int) error { return errors.Errorf("Param at index %d should be greater than or equal to 0 and less then or equal to current block height, got: %d", index, height) } -// NewServer creates a new Server struct. -func NewServer(chain core.Blockchainer, conf config.RPCConfig, coreServer *network.Server, log *zap.Logger) Server { +// New creates a new Server struct. +func New(chain core.Blockchainer, conf config.RPCConfig, coreServer *network.Server, log *zap.Logger) Server { httpServer := &http.Server{ Addr: conf.Address + ":" + strconv.FormatUint(uint64(conf.Port), 10), } diff --git a/pkg/rpc/server_helper_test.go b/pkg/rpc/server/server_helper_test.go similarity index 98% rename from pkg/rpc/server_helper_test.go rename to pkg/rpc/server/server_helper_test.go index 8b00d684e..496d2f7bb 100644 --- a/pkg/rpc/server_helper_test.go +++ b/pkg/rpc/server/server_helper_test.go @@ -1,4 +1,4 @@ -package rpc +package server import ( "net/http" @@ -178,7 +178,7 @@ func initServerWithInMemoryChain(t *testing.T) (*core.Blockchain, http.HandlerFu var nBlocks uint32 net := config.ModeUnitTestNet - configPath := "../../config" + configPath := "../../../config" cfg, err := config.Load(configPath, net) require.NoError(t, err, "could not load config") @@ -210,7 +210,7 @@ func initServerWithInMemoryChain(t *testing.T) (*core.Blockchain, http.HandlerFu serverConfig := network.NewServerConfig(cfg) server, err := network.NewServer(serverConfig, chain, logger) require.NoError(t, err) - rpcServer := NewServer(chain, cfg.ApplicationConfiguration.RPC, server, logger) + rpcServer := New(chain, cfg.ApplicationConfiguration.RPC, server, logger) handler := http.HandlerFunc(rpcServer.requestHandler) return chain, handler diff --git a/pkg/rpc/server_test.go b/pkg/rpc/server/server_test.go similarity index 99% rename from pkg/rpc/server_test.go rename to pkg/rpc/server/server_test.go index cad05c0fb..3ba9096da 100644 --- a/pkg/rpc/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -1,4 +1,4 @@ -package rpc +package server import ( "bytes" diff --git a/pkg/rpc/testdata/test_contract.avm b/pkg/rpc/server/testdata/test_contract.avm similarity index 100% rename from pkg/rpc/testdata/test_contract.avm rename to pkg/rpc/server/testdata/test_contract.avm diff --git a/pkg/rpc/testdata/testblocks.acc b/pkg/rpc/server/testdata/testblocks.acc similarity index 100% rename from pkg/rpc/testdata/testblocks.acc rename to pkg/rpc/server/testdata/testblocks.acc From 465476201c7a4c1d4304cd51556ae84ba2d7c01a Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 20 Feb 2020 12:00:20 +0300 Subject: [PATCH 16/22] rpc/response: drop duplicating AccountState structure --- pkg/rpc/response/types.go | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/pkg/rpc/response/types.go b/pkg/rpc/response/types.go index cd0985b54..5b1e3ad79 100644 --- a/pkg/rpc/response/types.go +++ b/pkg/rpc/response/types.go @@ -27,7 +27,7 @@ type InvokeResult struct { // AccountState holds the getaccountstate response. type AccountState struct { Header - Result *Account `json:"result"` + Result *result.AccountState `json:"result"` } // Unspent represents server response to the `getunspents` command. @@ -36,22 +36,6 @@ type Unspent struct { Result *result.Unspents `json:"result,omitempty"` } -// Account represents details about a NEO account. -type Account struct { - Version int `json:"version"` - ScriptHash string `json:"script_hash"` - Frozen bool - // TODO: need to check this field out. - Votes []interface{} - Balances []*Balance -} - -// Balance represents details about a NEO account balance. -type Balance struct { - Asset string `json:"asset"` - Value string `json:"value"` -} - // Header is a generic JSON-RPC 2.0 response header (ID and JSON-RPC version). type Header struct { ID json.RawMessage `json:"id"` From bba8ac15ff3598203e006540f11e67d089c67403 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 20 Feb 2020 19:29:21 +0300 Subject: [PATCH 17/22] rpc: introduce proper type for sendrawtransaction, drop useless types sendrawtransaction just returns a bool, sendtoaddress returns a proper transaction and that should be the same as the one we have in TransactionOutputRaw. --- pkg/rpc/client/rpc.go | 24 ++++++++++------------ pkg/rpc/response/types.go | 43 ++++++--------------------------------- 2 files changed, 17 insertions(+), 50 deletions(-) diff --git a/pkg/rpc/client/rpc.go b/pkg/rpc/client/rpc.go index e7c135c69..d4e2c1e0b 100644 --- a/pkg/rpc/client/rpc.go +++ b/pkg/rpc/client/rpc.go @@ -112,10 +112,10 @@ func (c *Client) Invoke(script string, params []smartcontract.Parameter) (*respo // The given hex string needs to be signed with a keypair. // When the result of the response object is true, the TX has successfully // been broadcasted to the network. -func (c *Client) sendRawTransaction(rawTX *transaction.Transaction) (*response.Raw, error) { +func (c *Client) sendRawTransaction(rawTX *transaction.Transaction) (*response.SendRawTx, error) { var ( params = request.NewRawParams(hex.EncodeToString(rawTX.Bytes())) - resp = &response.Raw{} + resp = &response.SendRawTx{} ) if err := c.performRequest("sendrawtransaction", params, resp); err != nil { return nil, err @@ -126,7 +126,7 @@ func (c *Client) sendRawTransaction(rawTX *transaction.Transaction) (*response.R // SendToAddress sends an amount of specific asset to a given address. // This call requires open wallet. (`wif` key in client struct.) // If response.Result is `true` then transaction was formed correctly and was written in blockchain. -func (c *Client) SendToAddress(asset util.Uint256, address string, amount util.Fixed8) (*response.SendToAddress, error) { +func (c *Client) SendToAddress(asset util.Uint256, address string, amount util.Fixed8) (util.Uint256, error) { var ( err error rawTx *transaction.Transaction @@ -137,23 +137,21 @@ func (c *Client) SendToAddress(asset util.Uint256, address string, amount util.F WIF: c.WIF(), Balancer: c.Balancer(), } - respRaw *response.Raw - resp = &response.SendToAddress{} + respRaw *response.SendRawTx + resp = util.Uint256{} ) if rawTx, err = request.CreateRawContractTransaction(txParams); err != nil { - return nil, errors.Wrap(err, "failed to create raw transaction for `sendtoaddress`") + return resp, errors.Wrap(err, "failed to create raw transaction for `sendtoaddress`") } if respRaw, err = c.sendRawTransaction(rawTx); err != nil { - return nil, errors.Wrap(err, "failed to send raw transaction") + return resp, errors.Wrap(err, "failed to send raw transaction") } - resp.Error = respRaw.Error - resp.ID = respRaw.ID - resp.JSONRPC = respRaw.JSONRPC - resp.Result = &response.Tx{ - TxID: rawTx.Hash().StringLE(), + if respRaw.Result { + return rawTx.Hash(), nil + } else { + return resp, errors.New("failed to send raw transaction") } - return resp, nil } // SignAndPushInvocationTx signs and pushes given script as an invocation diff --git a/pkg/rpc/response/types.go b/pkg/rpc/response/types.go index 5b1e3ad79..917bed066 100644 --- a/pkg/rpc/response/types.go +++ b/pkg/rpc/response/types.go @@ -3,7 +3,6 @@ package response import ( "encoding/json" - "github.com/CityOfZion/neo-go/pkg/core/transaction" "github.com/CityOfZion/neo-go/pkg/rpc/request" "github.com/CityOfZion/neo-go/pkg/rpc/response/result" "github.com/CityOfZion/neo-go/pkg/vm" @@ -59,7 +58,7 @@ type Raw struct { // SendToAddress stores response for the sendtoaddress call. type SendToAddress struct { HeaderAndError - Result *Tx + Result *result.TransactionOutputRaw } // GetTxOut represents result of `gettxout` RPC call. @@ -71,41 +70,11 @@ type GetTxOut struct { // GetRawTx represents verbose output of `getrawtransaction` RPC call. type GetRawTx struct { HeaderAndError - Result *RawTx `json:"result"` + Result *result.TransactionOutputRaw `json:"result"` } -// RawTx stores transaction with blockchain metadata to be sent as a response. -type RawTx struct { - Tx - BlockHash string `json:"blockhash"` - Confirmations uint `json:"confirmations"` - BlockTime uint `json:"blocktime"` -} - -// Tx stores transaction to be sent as a response. -type Tx struct { - TxID string `json:"txid"` - Size int `json:"size"` - Type string `json:"type"` // todo: convert to TransactionType - Version int `json:"version"` - Attributes []transaction.Attribute `json:"attributes"` - Vins []Vin `json:"vin"` - Vouts []Vout `json:"vout"` - SysFee int `json:"sys_fee"` - NetFee int `json:"net_fee"` - Scripts []transaction.Witness `json:"scripts"` -} - -// Vin represents JSON-serializable tx input. -type Vin struct { - TxID string `json:"txid"` - Vout int `json:"vout"` -} - -// Vout represents JSON-serializable tx output. -type Vout struct { - N int `json:"n"` - Asset string `json:"asset"` - Value int `json:"value"` - Address string `json:"address"` +// SendRawTx represents a `sendrawtransaction` RPC call response. +type SendRawTx struct { + HeaderAndError + Result bool `json:"result"` } From 877b987ecfb1ff547efec5e785ffb632fb25bf5c Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 20 Feb 2020 19:31:16 +0300 Subject: [PATCH 18/22] rpc: omitempty some fields in TransactionOutputRaw These might be undefined for mempool transactions, thus they should be defined as omitempty. --- pkg/rpc/response/result/tx_raw_output.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/rpc/response/result/tx_raw_output.go b/pkg/rpc/response/result/tx_raw_output.go index f36b7ab97..512514471 100644 --- a/pkg/rpc/response/result/tx_raw_output.go +++ b/pkg/rpc/response/result/tx_raw_output.go @@ -16,9 +16,9 @@ type TransactionOutputRaw struct { Size int `json:"size"` SysFee util.Fixed8 `json:"sys_fee"` NetFee util.Fixed8 `json:"net_fee"` - Blockhash util.Uint256 `json:"blockhash"` - Confirmations int `json:"confirmations"` - Timestamp uint32 `json:"blocktime"` + Blockhash util.Uint256 `json:"blockhash,omitempty"` + Confirmations int `json:"confirmations,omitempty"` + Timestamp uint32 `json:"blocktime,omitempty"` } // NewTransactionOutputRaw returns a new ransactionOutputRaw object. From 28a26d2caeb4b0f1fe7c4b426cd6d6a82f32cad5 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 20 Feb 2020 20:24:31 +0300 Subject: [PATCH 19/22] rpc/client: look into data first, then HTTP error code In case of error our own server responds with an HTTP error and proper JSON-RPC error in the body, so look there first as it has more specific data. --- pkg/rpc/client/client.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pkg/rpc/client/client.go b/pkg/rpc/client/client.go index d41c6679a..58e1e95ad 100644 --- a/pkg/rpc/client/client.go +++ b/pkg/rpc/client/client.go @@ -203,11 +203,14 @@ func (c *Client) performRequest(method string, p request.RawParams, v interface{ } defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("remote responded with a non 200 response: %d", resp.StatusCode) + // The node might send us proper JSON anyway, so look there first and if + // it parses, then it has more relevant data than HTTP error code. + err = json.NewDecoder(resp.Body).Decode(v) + if resp.StatusCode != http.StatusOK && err != nil { + err = fmt.Errorf("HTTP %d/%s", resp.StatusCode, http.StatusText(resp.StatusCode)) } - return json.NewDecoder(resp.Body).Decode(v) + return err } // Ping attempts to create a connection to the endpoint. From 3fa9de764baeb9bf4d861d9db103b45fd64755a4 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 20 Feb 2020 21:08:22 +0300 Subject: [PATCH 20/22] rpc/client: only return the Result from calls, handle Error internally Adjust structures accordingly and throw away most of them, they're useless. --- cli/smartcontract/smart_contract.go | 10 +++--- pkg/rpc/client/client.go | 21 ++++++++---- pkg/rpc/client/rpc.go | 51 +++++++++++++---------------- pkg/rpc/response/types.go | 32 +----------------- pkg/rpc/server/server.go | 10 +++++- 5 files changed, 52 insertions(+), 72 deletions(-) diff --git a/cli/smartcontract/smart_contract.go b/cli/smartcontract/smart_contract.go index fcb2f2c2b..31e8c09cf 100644 --- a/cli/smartcontract/smart_contract.go +++ b/cli/smartcontract/smart_contract.go @@ -364,7 +364,7 @@ func invokeInternal(ctx *cli.Context, withMethod bool, signAndPush bool) error { operation string params = make([]smartcontract.Parameter, 0) paramsStart = 1 - resp *response.InvokeScript + resp *response.InvokeResult wif *keys.WIF ) @@ -414,10 +414,10 @@ func invokeInternal(ctx *cli.Context, withMethod bool, signAndPush bool) error { return cli.NewExitError(err, 1) } if signAndPush { - if len(resp.Result.Script) == 0 { + if len(resp.Script) == 0 { return cli.NewExitError(errors.New("no script returned from the RPC node"), 1) } - script, err := hex.DecodeString(resp.Result.Script) + script, err := hex.DecodeString(resp.Script) if err != nil { return cli.NewExitError(fmt.Errorf("bad script returned from the RPC node: %v", err), 1) } @@ -427,7 +427,7 @@ func invokeInternal(ctx *cli.Context, withMethod bool, signAndPush bool) error { } fmt.Printf("Sent invocation transaction %s\n", txHash.StringLE()) } else { - b, err := json.MarshalIndent(resp.Result, "", " ") + b, err := json.MarshalIndent(resp, "", " ") if err != nil { return cli.NewExitError(err, 1) } @@ -464,7 +464,7 @@ func testInvokeScript(ctx *cli.Context) error { return cli.NewExitError(err, 1) } - b, err = json.MarshalIndent(resp.Result, "", " ") + b, err = json.MarshalIndent(resp, "", " ") if err != nil { return cli.NewExitError(err, 1) } diff --git a/pkg/rpc/client/client.go b/pkg/rpc/client/client.go index 58e1e95ad..8f9298e70 100644 --- a/pkg/rpc/client/client.go +++ b/pkg/rpc/client/client.go @@ -15,6 +15,7 @@ import ( "github.com/CityOfZion/neo-go/pkg/core/transaction" "github.com/CityOfZion/neo-go/pkg/crypto/keys" "github.com/CityOfZion/neo-go/pkg/rpc/request" + "github.com/CityOfZion/neo-go/pkg/rpc/response" "github.com/CityOfZion/neo-go/pkg/util" "github.com/pkg/errors" ) @@ -162,13 +163,10 @@ func (c *Client) CalculateInputs(address string, asset util.Uint256, cost util.F var utxos state.UnspentBalances resp, err := c.GetUnspents(address) - if err != nil || resp.Error != nil { - if err == nil { - err = fmt.Errorf("remote returned %d: %s", resp.Error.Code, resp.Error.Message) - } + if err != nil { return nil, util.Fixed8(0), errors.Wrapf(err, "cannot get balance for address %v", address) } - for _, ubi := range resp.Result.Balance { + for _, ubi := range resp.Balance { if asset.Equals(ubi.AssetHash) { utxos = ubi.Unspents break @@ -187,6 +185,7 @@ func (c *Client) performRequest(method string, p request.RawParams, v interface{ ID: 1, } buf = new(bytes.Buffer) + raw = &response.Raw{} ) if err := json.NewEncoder(buf).Encode(r); err != nil { @@ -205,9 +204,17 @@ func (c *Client) performRequest(method string, p request.RawParams, v interface{ // The node might send us proper JSON anyway, so look there first and if // it parses, then it has more relevant data than HTTP error code. - err = json.NewDecoder(resp.Body).Decode(v) - if resp.StatusCode != http.StatusOK && err != nil { + err = json.NewDecoder(resp.Body).Decode(raw) + if err == nil { + if raw.Error != nil { + err = raw.Error + } else { + err = json.Unmarshal(raw.Result, v) + } + } else if resp.StatusCode != http.StatusOK { err = fmt.Errorf("HTTP %d/%s", resp.StatusCode, http.StatusText(resp.StatusCode)) + } else { + err = errors.Wrap(err, "JSON decoding") } return err diff --git a/pkg/rpc/client/rpc.go b/pkg/rpc/client/rpc.go index d4e2c1e0b..44f69e419 100644 --- a/pkg/rpc/client/rpc.go +++ b/pkg/rpc/client/rpc.go @@ -2,13 +2,13 @@ package client import ( "encoding/hex" - "fmt" "github.com/CityOfZion/neo-go/pkg/core" "github.com/CityOfZion/neo-go/pkg/core/transaction" "github.com/CityOfZion/neo-go/pkg/crypto/keys" "github.com/CityOfZion/neo-go/pkg/rpc/request" "github.com/CityOfZion/neo-go/pkg/rpc/response" + "github.com/CityOfZion/neo-go/pkg/rpc/response/result" "github.com/CityOfZion/neo-go/pkg/smartcontract" "github.com/CityOfZion/neo-go/pkg/util" "github.com/pkg/errors" @@ -32,10 +32,10 @@ import ( // } // GetAccountState returns detailed information about a NEO account. -func (c *Client) GetAccountState(address string) (*response.AccountState, error) { +func (c *Client) GetAccountState(address string) (*result.AccountState, error) { var ( params = request.NewRawParams(address) - resp = &response.AccountState{} + resp = &result.AccountState{} ) if err := c.performRequest("getaccountstate", params, resp); err != nil { return nil, err @@ -44,10 +44,10 @@ func (c *Client) GetAccountState(address string) (*response.AccountState, error) } // GetUnspents returns UTXOs for the given NEO account. -func (c *Client) GetUnspents(address string) (*response.Unspent, error) { +func (c *Client) GetUnspents(address string) (*result.Unspents, error) { var ( params = request.NewRawParams(address) - resp = &response.Unspent{} + resp = &result.Unspents{} ) if err := c.performRequest("getunspents", params, resp); err != nil { return nil, err @@ -57,10 +57,10 @@ func (c *Client) GetUnspents(address string) (*response.Unspent, 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 string) (*response.InvokeScript, error) { +func (c *Client) InvokeScript(script string) (*response.InvokeResult, error) { var ( params = request.NewRawParams(script) - resp = &response.InvokeScript{} + resp = &response.InvokeResult{} ) if err := c.performRequest("invokescript", params, resp); err != nil { return nil, err @@ -71,10 +71,10 @@ func (c *Client) InvokeScript(script string) (*response.InvokeScript, error) { // InvokeFunction returns the results after calling the smart contract scripthash // with the given operation and parameters. // NOTE: this is test invoke and will not affect the blockchain. -func (c *Client) InvokeFunction(script, operation string, params []smartcontract.Parameter) (*response.InvokeScript, error) { +func (c *Client) InvokeFunction(script, operation string, params []smartcontract.Parameter) (*response.InvokeResult, error) { var ( p = request.NewRawParams(script, operation, params) - resp = &response.InvokeScript{} + resp = &response.InvokeResult{} ) if err := c.performRequest("invokefunction", p, resp); err != nil { return nil, err @@ -84,10 +84,10 @@ func (c *Client) InvokeFunction(script, operation string, params []smartcontract // Invoke returns the results after calling the smart contract scripthash // with the given parameters. -func (c *Client) Invoke(script string, params []smartcontract.Parameter) (*response.InvokeScript, error) { +func (c *Client) Invoke(script string, params []smartcontract.Parameter) (*response.InvokeResult, error) { var ( p = request.NewRawParams(script, params) - resp = &response.InvokeScript{} + resp = &response.InvokeResult{} ) if err := c.performRequest("invoke", p, resp); err != nil { return nil, err @@ -112,15 +112,18 @@ func (c *Client) Invoke(script string, params []smartcontract.Parameter) (*respo // The given hex string needs to be signed with a keypair. // When the result of the response object is true, the TX has successfully // been broadcasted to the network. -func (c *Client) sendRawTransaction(rawTX *transaction.Transaction) (*response.SendRawTx, error) { +func (c *Client) sendRawTransaction(rawTX *transaction.Transaction) error { var ( params = request.NewRawParams(hex.EncodeToString(rawTX.Bytes())) - resp = &response.SendRawTx{} + resp bool ) - if err := c.performRequest("sendrawtransaction", params, resp); err != nil { - return nil, err + if err := c.performRequest("sendrawtransaction", params, &resp); err != nil { + return err } - return resp, nil + if !resp { + return errors.New("sendrawtransaction returned false") + } + return nil } // SendToAddress sends an amount of specific asset to a given address. @@ -137,21 +140,16 @@ func (c *Client) SendToAddress(asset util.Uint256, address string, amount util.F WIF: c.WIF(), Balancer: c.Balancer(), } - respRaw *response.SendRawTx - resp = util.Uint256{} + resp util.Uint256 ) if rawTx, err = request.CreateRawContractTransaction(txParams); err != nil { return resp, errors.Wrap(err, "failed to create raw transaction for `sendtoaddress`") } - if respRaw, err = c.sendRawTransaction(rawTx); err != nil { + if err = c.sendRawTransaction(rawTx); err != nil { return resp, errors.Wrap(err, "failed to send raw transaction") } - if respRaw.Result { - return rawTx.Hash(), nil - } else { - return resp, errors.New("failed to send raw transaction") - } + return rawTx.Hash(), nil } // SignAndPushInvocationTx signs and pushes given script as an invocation @@ -175,13 +173,10 @@ func (c *Client) SignAndPushInvocationTx(script []byte, wif *keys.WIF, gas util. return txHash, errors.Wrap(err, "failed to sign tx") } txHash = tx.Hash() - resp, err := c.sendRawTransaction(tx) + err = c.sendRawTransaction(tx) if err != nil { return txHash, errors.Wrap(err, "failed sendning tx") } - if resp.Error != nil { - return txHash, fmt.Errorf("remote returned %d: %s", resp.Error.Code, resp.Error.Message) - } return txHash, nil } diff --git a/pkg/rpc/response/types.go b/pkg/rpc/response/types.go index 917bed066..6b13cb018 100644 --- a/pkg/rpc/response/types.go +++ b/pkg/rpc/response/types.go @@ -8,12 +8,6 @@ import ( "github.com/CityOfZion/neo-go/pkg/vm" ) -// InvokeScript stores response for the invoke script call. -type InvokeScript struct { - HeaderAndError - Result *InvokeResult `json:"result,omitempty"` -} - // InvokeResult represents the outcome of a script that is // executed by the NEO VM. type InvokeResult struct { @@ -23,18 +17,6 @@ type InvokeResult struct { Stack []request.StackParam } -// AccountState holds the getaccountstate response. -type AccountState struct { - Header - Result *result.AccountState `json:"result"` -} - -// Unspent represents server response to the `getunspents` command. -type Unspent struct { - HeaderAndError - Result *result.Unspents `json:"result,omitempty"` -} - // Header is a generic JSON-RPC 2.0 response header (ID and JSON-RPC version). type Header struct { ID json.RawMessage `json:"id"` @@ -52,13 +34,7 @@ type HeaderAndError struct { // response: http://www.jsonrpc.org/specification#response_object. type Raw struct { HeaderAndError - Result interface{} `json:"result,omitempty"` -} - -// SendToAddress stores response for the sendtoaddress call. -type SendToAddress struct { - HeaderAndError - Result *result.TransactionOutputRaw + Result json.RawMessage `json:"result,omitempty"` } // GetTxOut represents result of `gettxout` RPC call. @@ -72,9 +48,3 @@ type GetRawTx struct { HeaderAndError Result *result.TransactionOutputRaw `json:"result"` } - -// SendRawTx represents a `sendrawtransaction` RPC call response. -type SendRawTx struct { - HeaderAndError - Result bool `json:"result"` -} diff --git a/pkg/rpc/server/server.go b/pkg/rpc/server/server.go index d4ecb4518..805c41168 100644 --- a/pkg/rpc/server/server.go +++ b/pkg/rpc/server/server.go @@ -590,6 +590,14 @@ func (s *Server) WriteErrorResponse(r *request.In, w http.ResponseWriter, err er // WriteResponse encodes the response and writes it to the ResponseWriter. func (s *Server) WriteResponse(r *request.In, w http.ResponseWriter, result interface{}) { + resJSON, err := json.Marshal(result) + if err != nil { + s.log.Error("Error encountered while encoding response", + zap.String("err", err.Error()), + zap.String("method", r.Method)) + return + } + resp := response.Raw{ HeaderAndError: response.HeaderAndError{ Header: response.Header{ @@ -597,7 +605,7 @@ func (s *Server) WriteResponse(r *request.In, w http.ResponseWriter, result inte ID: r.RawID, }, }, - Result: result, + Result: resJSON, } s.writeServerResponse(r, w, resp) From 6a3be6081c655bcf5e859e34bb9ef99d18cde75d Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 21 Feb 2020 15:16:30 +0300 Subject: [PATCH 21/22] smartcontract: support "ByteArray" string for ByteArrayType We actually do emit it ourselves in String() --- pkg/smartcontract/param_context.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/smartcontract/param_context.go b/pkg/smartcontract/param_context.go index 5e2f6f8ff..a60d13c1e 100644 --- a/pkg/smartcontract/param_context.go +++ b/pkg/smartcontract/param_context.go @@ -137,7 +137,7 @@ func parseParamType(typ string) (ParamType, error) { return Hash160Type, nil case "hash256": return Hash256Type, nil - case "bytes": + case "bytes", "bytearray": return ByteArrayType, nil case "key": return PublicKeyType, nil From 8da20055d6b592d66297d746225f8ae44efa5431 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 21 Feb 2020 15:10:59 +0300 Subject: [PATCH 22/22] rpc: drop duplicating structures used in tests We have proper results now, so use those. The only left is Invoke, but that depends on another issue at the moment. --- pkg/rpc/server/server_helper_test.go | 163 +--------------- pkg/rpc/server/server_test.go | 282 ++++++++++++--------------- 2 files changed, 131 insertions(+), 314 deletions(-) diff --git a/pkg/rpc/server/server_helper_test.go b/pkg/rpc/server/server_helper_test.go index 496d2f7bb..1c24132da 100644 --- a/pkg/rpc/server/server_helper_test.go +++ b/pkg/rpc/server/server_helper_test.go @@ -12,166 +12,17 @@ import ( "github.com/CityOfZion/neo-go/pkg/io" "github.com/CityOfZion/neo-go/pkg/network" "github.com/CityOfZion/neo-go/pkg/rpc/request" - "github.com/CityOfZion/neo-go/pkg/rpc/response/result" - "github.com/CityOfZion/neo-go/pkg/util" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" ) -// ErrorResponse struct represents JSON-RPC error. -type ErrorResponse struct { - Jsonrpc string `json:"jsonrpc"` - Error struct { - Code int `json:"code"` - Message string `json:"message"` - } `json:"error"` - ID int `json:"id"` -} - -// SendTXResponse struct for testing. -type SendTXResponse struct { - Jsonrpc string `json:"jsonrpc"` - Result bool `json:"result"` - ID int `json:"id"` -} - -// InvokeFunctionResponse struct for testing. -type InvokeFunctionResponse struct { - Jsonrpc string `json:"jsonrpc"` - Result struct { - Script string `json:"script"` - State string `json:"state"` - GasConsumed string `json:"gas_consumed"` - Stack []request.FuncParam `json:"stack"` - TX string `json:"tx,omitempty"` - } `json:"result"` - ID int `json:"id"` -} - -// ValidateAddrResponse struct for testing. -type ValidateAddrResponse struct { - Jsonrpc string `json:"jsonrpc"` - Result result.ValidateAddress `json:"result"` - ID int `json:"id"` -} - -// GetPeersResponse struct for testing. -type GetPeersResponse struct { - Jsonrpc string `json:"jsonrpc"` - Result struct { - Unconnected []int `json:"unconnected"` - Connected []int `json:"connected"` - Bad []int `json:"bad"` - } `json:"result"` - ID int `json:"id"` -} - -// GetVersionResponse struct for testing. -type GetVersionResponse struct { - Jsonrpc string `json:"jsonrpc"` - Result result.Version `json:"result"` - ID int `json:"id"` -} - -// IntResultResponse struct for testing. -type IntResultResponse struct { - Jsonrpc string `json:"jsonrpc"` - Result int `json:"result"` - ID int `json:"id"` -} - -// StringResultResponse struct for testing. -type StringResultResponse struct { - Jsonrpc string `json:"jsonrpc"` - Result string `json:"result"` - ID int `json:"id"` -} - -// GetBlockResponse struct for testing. -type GetBlockResponse struct { - Jsonrpc string `json:"jsonrpc"` - Result result.Block `json:"result"` - ID int `json:"id"` -} - -// GetAssetResponse struct for testing. -type GetAssetResponse struct { - Jsonrpc string `json:"jsonrpc"` - Result struct { - AssetID string `json:"assetID"` - AssetType int `json:"assetType"` - Name string `json:"name"` - Amount string `json:"amount"` - Available string `json:"available"` - Precision int `json:"precision"` - Fee int `json:"fee"` - Address string `json:"address"` - Owner string `json:"owner"` - Admin string `json:"admin"` - Issuer string `json:"issuer"` - Expiration int `json:"expiration"` - IsFrozen bool `json:"is_frozen"` - } `json:"result"` - ID int `json:"id"` -} - -// GetAccountStateResponse struct for testing. -type GetAccountStateResponse struct { - Jsonrpc string `json:"jsonrpc"` - Result struct { - Version int `json:"version"` - ScriptHash string `json:"script_hash"` - Frozen bool `json:"frozen"` - Votes []interface{} `json:"votes"` - Balances []struct { - Asset string `json:"asset"` - Value string `json:"value"` - } `json:"balances"` - } `json:"result"` - ID int `json:"id"` -} - -// GetUnspents struct for testing. -type GetUnspents struct { - Jsonrpc string `json:"jsonrpc"` - Result struct { - Balance []struct { - Unspents []struct { - TxID string `json:"txid"` - Index int `json:"n"` - Value string `json:"value"` - } `json:"unspent"` - AssetHash string `json:"asset_hash"` - Asset string `json:"asset"` - AssetSymbol string `json:"asset_symbol"` - Amount string `json:"amount"` - } `json:"balance"` - Address string `json:"address"` - } `json:"result"` - ID int `json:"id"` -} - -// GetContractStateResponse struct for testing. -type GetContractStateResponce struct { - Jsonrpc string `json:"jsonrpc"` - Result struct { - Version byte `json:"version"` - ScriptHash util.Uint160 `json:"hash"` - Script []byte `json:"script"` - ParamList interface{} `json:"parameters"` - ReturnType interface{} `json:"returntype"` - Name string `json:"name"` - CodeVersion string `json:"code_version"` - Author string `json:"author"` - Email string `json:"email"` - Description string `json:"description"` - Properties struct { - HasStorage bool `json:"storage"` - HasDynamicInvoke bool `json:"dynamic_invoke"` - IsPayable bool `json:"is_payable"` - } `json:"properties"` - } `json:"result"` - ID int `json:"id"` +// InvokeFunctionResult struct for testing. +type InvokeFunctionResult struct { + Script string `json:"script"` + State string `json:"state"` + GasConsumed string `json:"gas_consumed"` + Stack []request.FuncParam `json:"stack"` + TX string `json:"tx,omitempty"` } func initServerWithInMemoryChain(t *testing.T) (*core.Blockchain, http.HandlerFunc) { diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index 3ba9096da..cccddc12e 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -44,23 +44,23 @@ var rpcTestCases = map[string][]rpcTestCase{ { name: "positive", params: `["AZ81H31DMWzbSnFDLFkzh9vHwaDLayV7fU"]`, - result: func(e *executor) interface{} { return &GetAccountStateResponse{} }, - check: func(t *testing.T, e *executor, result interface{}) { - res, ok := result.(*GetAccountStateResponse) + result: func(e *executor) interface{} { return &result.AccountState{} }, + check: func(t *testing.T, e *executor, acc interface{}) { + res, ok := acc.(*result.AccountState) require.True(t, ok) - assert.Equal(t, 1, len(res.Result.Balances)) - assert.Equal(t, false, res.Result.Frozen) + assert.Equal(t, 1, len(res.Balances)) + assert.Equal(t, false, res.IsFrozen) }, }, { name: "positive null", params: `["AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y"]`, - result: func(e *executor) interface{} { return &GetAccountStateResponse{} }, - check: func(t *testing.T, e *executor, result interface{}) { - res, ok := result.(*GetAccountStateResponse) + result: func(e *executor) interface{} { return &result.AccountState{} }, + check: func(t *testing.T, e *executor, acc interface{}) { + res, ok := acc.(*result.AccountState) require.True(t, ok) - assert.Equal(t, 0, len(res.Result.Balances)) - assert.Equal(t, false, res.Result.Frozen) + assert.Equal(t, 0, len(res.Balances)) + assert.Equal(t, false, res.IsFrozen) }, }, { @@ -78,13 +78,13 @@ var rpcTestCases = map[string][]rpcTestCase{ { name: "positive", params: `["1a696b32e239dd5eace3f025cac0a193a5746a27"]`, - result: func(e *executor) interface{} { return &GetContractStateResponce{} }, - check: func(t *testing.T, e *executor, result interface{}) { - res, ok := result.(*GetContractStateResponce) + result: func(e *executor) interface{} { return &result.ContractState{} }, + check: func(t *testing.T, e *executor, cs interface{}) { + res, ok := cs.(*result.ContractState) require.True(t, ok) - assert.Equal(t, byte(0), res.Result.Version) - assert.Equal(t, util.Uint160{0x1a, 0x69, 0x6b, 0x32, 0xe2, 0x39, 0xdd, 0x5e, 0xac, 0xe3, 0xf0, 0x25, 0xca, 0xc0, 0xa1, 0x93, 0xa5, 0x74, 0x6a, 0x27}, res.Result.ScriptHash) - assert.Equal(t, "0.99", res.Result.CodeVersion) + assert.Equal(t, byte(0), res.Version) + assert.Equal(t, util.Uint160{0x1a, 0x69, 0x6b, 0x32, 0xe2, 0x39, 0xdd, 0x5e, 0xac, 0xe3, 0xf0, 0x25, 0xca, 0xc0, 0xa1, 0x93, 0xa5, 0x74, 0x6a, 0x27}, res.ScriptHash) + assert.Equal(t, "0.99", res.CodeVersion) }, }, { @@ -107,21 +107,17 @@ var rpcTestCases = map[string][]rpcTestCase{ { name: "positive", params: `["1a696b32e239dd5eace3f025cac0a193a5746a27", "746573746b6579"]`, - result: func(e *executor) interface{} { return &StringResultResponse{} }, - check: func(t *testing.T, e *executor, result interface{}) { - res, ok := result.(*StringResultResponse) - require.True(t, ok) - assert.Equal(t, hex.EncodeToString([]byte("testvalue")), res.Result) + result: func(e *executor) interface{} { + v := hex.EncodeToString([]byte("testvalue")) + return &v }, }, { name: "missing key", params: `["1a696b32e239dd5eace3f025cac0a193a5746a27", "7465"]`, - result: func(e *executor) interface{} { return &StringResultResponse{} }, - check: func(t *testing.T, e *executor, result interface{}) { - res, ok := result.(*StringResultResponse) - require.True(t, ok) - assert.Equal(t, "", res.Result) + result: func(e *executor) interface{} { + v := "" + return &v }, }, { @@ -149,12 +145,12 @@ var rpcTestCases = map[string][]rpcTestCase{ { name: "positive", params: `["602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"]`, - result: func(e *executor) interface{} { return &GetAssetResponse{} }, - check: func(t *testing.T, e *executor, result interface{}) { - res, ok := result.(*GetAssetResponse) + result: func(e *executor) interface{} { return &result.AssetState{} }, + check: func(t *testing.T, e *executor, as interface{}) { + res, ok := as.(*result.AssetState) require.True(t, ok) - assert.Equal(t, "00", res.Result.Owner) - assert.Equal(t, "AWKECj9RD8rS8RPcpCgYVjk1DeYyHwxZm3", res.Result.Admin) + assert.Equal(t, "00", res.Owner) + assert.Equal(t, "AWKECj9RD8rS8RPcpCgYVjk1DeYyHwxZm3", res.Admin) }, }, { @@ -177,7 +173,8 @@ var rpcTestCases = map[string][]rpcTestCase{ { params: "[]", result: func(e *executor) interface{} { - return "0x" + e.chain.CurrentBlockHash().StringLE() + v := "0x" + e.chain.CurrentBlockHash().StringLE() + return &v }, }, { @@ -221,17 +218,17 @@ var rpcTestCases = map[string][]rpcTestCase{ { name: "positive", params: "[1, 1]", - result: func(e *executor) interface{} { return &GetBlockResponse{} }, - check: func(t *testing.T, e *executor, result interface{}) { - res, ok := result.(*GetBlockResponse) + result: func(e *executor) interface{} { return &result.Block{} }, + check: func(t *testing.T, e *executor, blockRes interface{}) { + res, ok := blockRes.(*result.Block) require.True(t, ok) block, err := e.chain.GetBlock(e.chain.GetHeaderHash(1)) require.NoErrorf(t, err, "could not get block") - assert.Equal(t, block.Hash(), res.Result.Hash) - for i := range res.Result.Tx { - tx := res.Result.Tx[i] + assert.Equal(t, block.Hash(), res.Hash) + for i := range res.Tx { + tx := res.Tx[i] require.Equal(t, transaction.MinerType, tx.Type) miner, ok := block.Transactions[i].Data.(*transaction.MinerTX) @@ -270,22 +267,21 @@ var rpcTestCases = map[string][]rpcTestCase{ "getblockcount": { { params: "[]", - result: func(e *executor) interface{} { return int(e.chain.BlockHeight() + 1) }, + result: func(e *executor) interface{} { + v := int(e.chain.BlockHeight() + 1) + return &v + }, }, }, "getblockhash": { { params: "[1]", - result: func(e *executor) interface{} { return "" }, - check: func(t *testing.T, e *executor, result interface{}) { - res, ok := result.(*StringResultResponse) - require.True(t, ok) - - block, err := e.chain.GetBlock(e.chain.GetHeaderHash(1)) - require.NoErrorf(t, err, "could not get block") - + result: func(e *executor) interface{} { + // We don't have `t` here for proper handling, but + // error here would lead to panic down below. + block, _ := e.chain.GetBlock(e.chain.GetHeaderHash(1)) expectedHash := "0x" + block.Hash().StringLE() - assert.Equal(t, expectedHash, res.Result) + return &expectedHash }, }, { @@ -302,25 +298,20 @@ var rpcTestCases = map[string][]rpcTestCase{ "getconnectioncount": { { params: "[]", - result: func(*executor) interface{} { return 0 }, + result: func(*executor) interface{} { + v := 0 + return &v + }, }, }, "getpeers": { { params: "[]", result: func(*executor) interface{} { - return &GetPeersResponse{ - Jsonrpc: defaultJSONRPC, - Result: struct { - Unconnected []int `json:"unconnected"` - Connected []int `json:"connected"` - Bad []int `json:"bad"` - }{ - Unconnected: []int{}, - Connected: []int{}, - Bad: []int{}, - }, - ID: defaultID, + return &result.GetPeers{ + Unconnected: []result.Peer{}, + Connected: []result.Peer{}, + Bad: []result.Peer{}, } }, }, @@ -346,33 +337,33 @@ var rpcTestCases = map[string][]rpcTestCase{ { name: "positive", params: `["AZ81H31DMWzbSnFDLFkzh9vHwaDLayV7fU"]`, - result: func(e *executor) interface{} { return &GetUnspents{} }, - check: func(t *testing.T, e *executor, result interface{}) { - res, ok := result.(*GetUnspents) + result: func(e *executor) interface{} { return &result.Unspents{} }, + check: func(t *testing.T, e *executor, unsp interface{}) { + res, ok := unsp.(*result.Unspents) require.True(t, ok) - require.Equal(t, 1, len(res.Result.Balance)) - assert.Equal(t, 1, len(res.Result.Balance[0].Unspents)) + require.Equal(t, 1, len(res.Balance)) + assert.Equal(t, 1, len(res.Balance[0].Unspents)) }, }, { name: "positive null", params: `["AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y"]`, - result: func(e *executor) interface{} { return &GetUnspents{} }, - check: func(t *testing.T, e *executor, result interface{}) { - res, ok := result.(*GetUnspents) + result: func(e *executor) interface{} { return &result.Unspents{} }, + check: func(t *testing.T, e *executor, unsp interface{}) { + res, ok := unsp.(*result.Unspents) require.True(t, ok) - require.Equal(t, 0, len(res.Result.Balance)) + require.Equal(t, 0, len(res.Balance)) }, }, }, "getversion": { { params: "[]", - result: func(*executor) interface{} { return &GetVersionResponse{} }, - check: func(t *testing.T, e *executor, result interface{}) { - resp, ok := result.(*GetVersionResponse) + result: func(*executor) interface{} { return &result.Version{} }, + check: func(t *testing.T, e *executor, ver interface{}) { + resp, ok := ver.(*result.Version) require.True(t, ok) - require.Equal(t, "/NEO-GO:/", resp.Result.UserAgent) + require.Equal(t, "/NEO-GO:/", resp.UserAgent) }, }, }, @@ -380,13 +371,13 @@ var rpcTestCases = map[string][]rpcTestCase{ { name: "positive", params: `["50befd26fdf6e4d957c11e078b24ebce6291456f", [{"type": "String", "value": "qwerty"}]]`, - result: func(e *executor) interface{} { return &InvokeFunctionResponse{} }, - check: func(t *testing.T, e *executor, result interface{}) { - res, ok := result.(*InvokeFunctionResponse) + result: func(e *executor) interface{} { return &InvokeFunctionResult{} }, + check: func(t *testing.T, e *executor, inv interface{}) { + res, ok := inv.(*InvokeFunctionResult) require.True(t, ok) - assert.Equal(t, "06717765727479676f459162ceeb248b071ec157d9e4f6fd26fdbe50", res.Result.Script) - assert.NotEqual(t, "", res.Result.State) - assert.NotEqual(t, 0, res.Result.GasConsumed) + assert.Equal(t, "06717765727479676f459162ceeb248b071ec157d9e4f6fd26fdbe50", res.Script) + assert.NotEqual(t, "", res.State) + assert.NotEqual(t, 0, res.GasConsumed) }, }, { @@ -419,13 +410,13 @@ var rpcTestCases = map[string][]rpcTestCase{ { name: "positive", params: `["50befd26fdf6e4d957c11e078b24ebce6291456f", "test", []]`, - result: func(e *executor) interface{} { return &InvokeFunctionResponse{} }, - check: func(t *testing.T, e *executor, result interface{}) { - res, ok := result.(*InvokeFunctionResponse) + result: func(e *executor) interface{} { return &InvokeFunctionResult{} }, + check: func(t *testing.T, e *executor, inv interface{}) { + res, ok := inv.(*InvokeFunctionResult) require.True(t, ok) - assert.NotEqual(t, "", res.Result.Script) - assert.NotEqual(t, "", res.Result.State) - assert.NotEqual(t, 0, res.Result.GasConsumed) + assert.NotEqual(t, "", res.Script) + assert.NotEqual(t, "", res.State) + assert.NotEqual(t, 0, res.GasConsumed) }, }, { @@ -453,13 +444,13 @@ var rpcTestCases = map[string][]rpcTestCase{ { name: "positive", params: `["51c56b0d48656c6c6f2c20776f726c6421680f4e656f2e52756e74696d652e4c6f67616c7566"]`, - result: func(e *executor) interface{} { return &InvokeFunctionResponse{} }, - check: func(t *testing.T, e *executor, result interface{}) { - res, ok := result.(*InvokeFunctionResponse) + result: func(e *executor) interface{} { return &InvokeFunctionResult{} }, + check: func(t *testing.T, e *executor, inv interface{}) { + res, ok := inv.(*InvokeFunctionResult) require.True(t, ok) - assert.NotEqual(t, "", res.Result.Script) - assert.NotEqual(t, "", res.Result.State) - assert.NotEqual(t, 0, res.Result.GasConsumed) + assert.NotEqual(t, "", res.Script) + assert.NotEqual(t, "", res.State) + assert.NotEqual(t, 0, res.GasConsumed) }, }, { @@ -482,11 +473,9 @@ var rpcTestCases = map[string][]rpcTestCase{ { name: "positive", params: `["d1001b00046e616d6567d3d8602814a429a91afdbaa3914884a1c90c733101201cc9c05cefffe6cdd7b182816a9152ec218d2ec000000141403387ef7940a5764259621e655b3c621a6aafd869a611ad64adcc364d8dd1edf84e00a7f8b11b630a377eaef02791d1c289d711c08b7ad04ff0d6c9caca22cfe6232103cbb45da6072c14761c9da545749d9cfd863f860c351066d16df480602a2024c6ac"]`, - result: func(e *executor) interface{} { return &SendTXResponse{} }, - check: func(t *testing.T, e *executor, result interface{}) { - res, ok := result.(*SendTXResponse) - require.True(t, ok) - assert.True(t, res.Result) + result: func(e *executor) interface{} { + v := true + return &v }, }, { @@ -514,25 +503,21 @@ var rpcTestCases = map[string][]rpcTestCase{ { name: "positive", params: `["AQVh2pG732YvtNaxEGkQUei3YA4cvo7d2i"]`, - result: func(*executor) interface{} { return &ValidateAddrResponse{} }, - check: func(t *testing.T, e *executor, result interface{}) { - res, ok := result.(*ValidateAddrResponse) + result: func(*executor) interface{} { return &result.ValidateAddress{} }, + check: func(t *testing.T, e *executor, va interface{}) { + res, ok := va.(*result.ValidateAddress) require.True(t, ok) - assert.Equal(t, "AQVh2pG732YvtNaxEGkQUei3YA4cvo7d2i", res.Result.Address) - assert.True(t, res.Result.IsValid) + assert.Equal(t, "AQVh2pG732YvtNaxEGkQUei3YA4cvo7d2i", res.Address) + assert.True(t, res.IsValid) }, }, { name: "negative", params: "[1]", result: func(*executor) interface{} { - return &ValidateAddrResponse{ - Jsonrpc: defaultJSONRPC, - Result: result.ValidateAddress{ - Address: float64(1), - IsValid: false, - }, - ID: defaultID, + return &result.ValidateAddress{ + Address: float64(1), + IsValid: false, } }, }, @@ -552,14 +537,14 @@ func TestRPC(t *testing.T) { for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { body := doRPCCall(fmt.Sprintf(rpc, method, tc.params), handler, t) - checkErrResponse(t, body, tc.fail) + result := checkErrGetResult(t, body, tc.fail) if tc.fail { return } expected, res := tc.getResultPair(e) - err := json.Unmarshal(body, res) - require.NoErrorf(t, err, "could not parse response: %s", body) + err := json.Unmarshal(result, res) + require.NoErrorf(t, err, "could not parse response: %s", result) if tc.check == nil { assert.Equal(t, expected, res) @@ -576,11 +561,11 @@ func TestRPC(t *testing.T) { TXHash := block.Transactions[1].Hash() rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "getrawtransaction", "params": ["%s"]}"`, TXHash.StringLE()) body := doRPCCall(rpc, handler, t) - checkErrResponse(t, body, false) - var res StringResultResponse - err := json.Unmarshal(body, &res) - require.NoErrorf(t, err, "could not parse response: %s", body) - assert.Equal(t, "400000455b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e882a1227d2c7b226c616e67223a22656e222c226e616d65223a22416e745368617265227d5d0000c16ff28623000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b00000000", res.Result) + result := checkErrGetResult(t, body, false) + var res string + err := json.Unmarshal(result, &res) + require.NoErrorf(t, err, "could not parse response: %s", result) + assert.Equal(t, "400000455b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e882a1227d2c7b226c616e67223a22656e222c226e616d65223a22416e745368617265227d5d0000c16ff28623000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b00000000", res) }) t.Run("getrawtransaction 2 arguments", func(t *testing.T) { @@ -588,11 +573,11 @@ func TestRPC(t *testing.T) { TXHash := block.Transactions[1].Hash() rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "getrawtransaction", "params": ["%s", 0]}"`, TXHash.StringLE()) body := doRPCCall(rpc, handler, t) - checkErrResponse(t, body, false) - var res StringResultResponse - err := json.Unmarshal(body, &res) - require.NoErrorf(t, err, "could not parse response: %s", body) - assert.Equal(t, "400000455b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e882a1227d2c7b226c616e67223a22656e222c226e616d65223a22416e745368617265227d5d0000c16ff28623000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b00000000", res.Result) + result := checkErrGetResult(t, body, false) + var res string + err := json.Unmarshal(result, &res) + require.NoErrorf(t, err, "could not parse response: %s", result) + assert.Equal(t, "400000455b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e882a1227d2c7b226c616e67223a22656e222c226e616d65223a22416e745368617265227d5d0000c16ff28623000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b00000000", res) }) t.Run("gettxout", func(t *testing.T) { @@ -601,54 +586,35 @@ func TestRPC(t *testing.T) { rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "gettxout", "params": [%s, %d]}"`, `"`+tx.Hash().StringLE()+`"`, 0) body := doRPCCall(rpc, handler, t) - checkErrResponse(t, body, false) + res := checkErrGetResult(t, body, false) - var result response.GetTxOut - err := json.Unmarshal(body, &result) - require.NoErrorf(t, err, "could not parse response: %s", body) - assert.Equal(t, 0, result.Result.N) - assert.Equal(t, "0x9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc5", result.Result.Asset) - assert.Equal(t, util.Fixed8FromInt64(100000000), result.Result.Value) - assert.Equal(t, "AZ81H31DMWzbSnFDLFkzh9vHwaDLayV7fU", result.Result.Address) + var txOut result.TransactionOutput + err := json.Unmarshal(res, &txOut) + require.NoErrorf(t, err, "could not parse response: %s", res) + assert.Equal(t, 0, txOut.N) + assert.Equal(t, "0x9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc5", txOut.Asset) + assert.Equal(t, util.Fixed8FromInt64(100000000), txOut.Value) + assert.Equal(t, "AZ81H31DMWzbSnFDLFkzh9vHwaDLayV7fU", txOut.Address) }) } func (tc rpcTestCase) getResultPair(e *executor) (expected interface{}, res interface{}) { expected = tc.result(e) - switch exp := expected.(type) { - case string: - res = new(StringResultResponse) - expected = &StringResultResponse{ - Jsonrpc: defaultJSONRPC, - Result: exp, - ID: defaultID, - } - case int: - res = new(IntResultResponse) - expected = &IntResultResponse{ - Jsonrpc: defaultJSONRPC, - Result: exp, - ID: defaultID, - } - default: - resVal := reflect.New(reflect.TypeOf(expected).Elem()) - res = resVal.Interface() - } - - return + resVal := reflect.New(reflect.TypeOf(expected).Elem()) + return expected, resVal.Interface() } -func checkErrResponse(t *testing.T, body []byte, expectingFail bool) { - var errresp ErrorResponse - err := json.Unmarshal(body, &errresp) +func checkErrGetResult(t *testing.T, body []byte, expectingFail bool) json.RawMessage { + var resp response.Raw + err := json.Unmarshal(body, &resp) require.Nil(t, err) if expectingFail { - assert.NotEqual(t, 0, errresp.Error.Code) - assert.NotEqual(t, "", errresp.Error.Message) + assert.NotEqual(t, 0, resp.Error.Code) + assert.NotEqual(t, "", resp.Error.Message) } else { - assert.Equal(t, 0, errresp.Error.Code) - assert.Equal(t, "", errresp.Error.Message) + assert.Nil(t, resp.Error) } + return resp.Result } func doRPCCall(rpcCall string, handler http.HandlerFunc, t *testing.T) []byte {