Merge pull request #646 from nspcc-dev/feature/gettxout

rpc: implement gettxout RPC

Closes #345.
This commit is contained in:
Roman Khimov 2020-02-17 12:47:41 +03:00 committed by GitHub
commit c681891017
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 131 additions and 1 deletions

View file

@ -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 |

View file

@ -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",
@ -122,6 +130,7 @@ func init() {
getassetstateCalled,
getaccountstateCalled,
getunspentsCalled,
gettxoutCalled,
getrawtransactionCalled,
sendrawtransactionCalled,
)

View file

@ -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)
@ -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

View file

@ -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",
@ -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{}) {

View file

@ -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

View file

@ -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,
}
}