Skip to content

Commit

Permalink
Merge pull request #646 from nspcc-dev/feature/gettxout
Browse files Browse the repository at this point in the history
rpc: implement gettxout RPC

Closes #345.
  • Loading branch information
roman-khimov committed Feb 17, 2020
2 parents dfb38e1 + 7ee10ec commit c681891
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 1 deletion.
2 changes: 1 addition & 1 deletion docs/rpc.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ which would yield the response:
| `getrawmempool` | No (#175) |
| `getrawtransaction` | Yes |
| `getstorage` | No (#343) |
| `gettxout` | No (#345) |
| `gettxout` | Yes |
| `getunspents` | Yes |
| `getversion` | Yes |
| `invoke` | Yes |
Expand Down
9 changes: 9 additions & 0 deletions pkg/rpc/prometheus.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,14 @@ var (
},
)

gettxoutCalled = prometheus.NewCounter(
prometheus.CounterOpts{
Help: "Number of calls to gettxout rpc endpoint",
Name: "gettxout_called",
Namespace: "neogo",
},
)

getrawtransactionCalled = prometheus.NewCounter(
prometheus.CounterOpts{
Help: "Number of calls to getrawtransaction rpc endpoint",
Expand Down Expand Up @@ -122,6 +130,7 @@ func init() {
getassetstateCalled,
getaccountstateCalled,
getunspentsCalled,
gettxoutCalled,
getrawtransactionCalled,
sendrawtransactionCalled,
)
Expand Down
38 changes: 38 additions & 0 deletions pkg/rpc/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,10 @@ Methods:
getrawtransactionCalled.Inc()
results, resultsErr = s.getrawtransaction(reqParams)

case "gettxout":
gettxoutCalled.Inc()
results, resultsErr = s.getTxOut(reqParams)

case "getunspents":
getunspentsCalled.Inc()
results, resultsErr = s.getAccountState(reqParams, true)
Expand Down Expand Up @@ -311,6 +315,40 @@ func (s *Server) getrawtransaction(reqParams Params) (interface{}, error) {
return results, resultsErr
}

func (s *Server) getTxOut(ps Params) (interface{}, error) {
p, ok := ps.Value(0)
if !ok {
return nil, errInvalidParams
}

h, err := p.GetUint256()
if err != nil {
return nil, errInvalidParams
}

p, ok = ps.ValueWithType(1, numberT)
if !ok {
return nil, errInvalidParams
}

num, err := p.GetInt()
if err != nil || num < 0 {
return nil, errInvalidParams
}

tx, _, err := s.chain.GetTransaction(h)
if err != nil {
return nil, NewInvalidParamsError(err.Error(), err)
}

if num >= len(tx.Outputs) {
return nil, NewInvalidParamsError("invalid index", errors.New("too big index"))
}

out := tx.Outputs[num]
return wrappers.NewTxOutput(&out), nil
}

// getAccountState returns account state either in short or full (unspents included) form.
func (s *Server) getAccountState(reqParams Params, unspents bool) (interface{}, error) {
var resultsErr error
Expand Down
49 changes: 49 additions & 0 deletions pkg/rpc/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,38 @@ var rpcTestCases = map[string][]rpcTestCase{
fail: true,
},
},
"gettxout": {
{
name: "no params",
params: `[]`,
fail: true,
},
{
name: "invalid hash",
params: `["notahex"]`,
fail: true,
},
{
name: "missing hash",
params: `["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 0]`,
fail: true,
},
{
name: "invalid index",
params: `["7aadf91ca8ac1e2c323c025a7e492bee2dd90c783b86ebfc3b18db66b530a76d", "string"]`,
fail: true,
},
{
name: "negative index",
params: `["7aadf91ca8ac1e2c323c025a7e492bee2dd90c783b86ebfc3b18db66b530a76d", -1]`,
fail: true,
},
{
name: "too big index",
params: `["7aadf91ca8ac1e2c323c025a7e492bee2dd90c783b86ebfc3b18db66b530a76d", 100]`,
fail: true,
},
},
"getblock": {
{
name: "positive",
Expand Down Expand Up @@ -489,6 +521,23 @@ func TestRPC(t *testing.T) {
require.NoErrorf(t, err, "could not parse response: %s", body)
assert.Equal(t, "400000455b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e882a1227d2c7b226c616e67223a22656e222c226e616d65223a22416e745368617265227d5d0000c16ff28623000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b00000000", res.Result)
})

t.Run("gettxout", func(t *testing.T) {
block, _ := chain.GetBlock(chain.GetHeaderHash(0))
tx := block.Transactions[3]
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)

var result GetTxOutResponse
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)
})
}

func (tc rpcTestCase) getResultPair(e *executor) (expected interface{}, res interface{}) {
Expand Down
7 changes: 7 additions & 0 deletions pkg/rpc/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,13 @@ type GetRawTxResponse struct {
Result *RawTxResponse `json:"result"`
}

// GetTxOutResponse represents result of `gettxout` RPC call.
type GetTxOutResponse struct {
responseHeader
Error *Error
Result *wrappers.TransactionOutput
}

// RawTxResponse stores transaction with blockchain metadata to be sent as a response.
type RawTxResponse struct {
TxResponse
Expand Down
27 changes: 27 additions & 0 deletions pkg/rpc/wrappers/tx_output.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package wrappers

import (
"github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/encoding/address"
"github.com/CityOfZion/neo-go/pkg/util"
)

// TransactionOutput is a wrapper to represent transaction's output.
type TransactionOutput struct {
N int `json:"n"`
Asset string `json:"asset"`
Value util.Fixed8 `json:"value"`
Address string `json:"address"`
}

// NewTxOutput converts out to a TransactionOutput.
func NewTxOutput(out *transaction.Output) *TransactionOutput {
addr := address.Uint160ToString(out.ScriptHash)

return &TransactionOutput{
N: out.Position,
Asset: "0x" + out.AssetID.String(),
Value: out.Amount,
Address: addr,
}
}

0 comments on commit c681891

Please sign in to comment.