rpc: implement gettxout RPC
This commit is contained in:
parent
dfb38e1da1
commit
7ee10ecea5
6 changed files with 131 additions and 1 deletions
|
@ -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 |
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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{}) {
|
||||
|
|
|
@ -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
|
||||
|
|
27
pkg/rpc/wrappers/tx_output.go
Normal file
27
pkg/rpc/wrappers/tx_output.go
Normal 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,
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue