forked from TrueCloudLab/neoneo-go
Merge pull request #646 from nspcc-dev/feature/gettxout
rpc: implement gettxout RPC Closes #345.
This commit is contained in:
commit
c681891017
6 changed files with 131 additions and 1 deletions
|
@ -51,7 +51,7 @@ which would yield the response:
|
||||||
| `getrawmempool` | No (#175) |
|
| `getrawmempool` | No (#175) |
|
||||||
| `getrawtransaction` | Yes |
|
| `getrawtransaction` | Yes |
|
||||||
| `getstorage` | No (#343) |
|
| `getstorage` | No (#343) |
|
||||||
| `gettxout` | No (#345) |
|
| `gettxout` | Yes |
|
||||||
| `getunspents` | Yes |
|
| `getunspents` | Yes |
|
||||||
| `getversion` | Yes |
|
| `getversion` | Yes |
|
||||||
| `invoke` | 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(
|
getrawtransactionCalled = prometheus.NewCounter(
|
||||||
prometheus.CounterOpts{
|
prometheus.CounterOpts{
|
||||||
Help: "Number of calls to getrawtransaction rpc endpoint",
|
Help: "Number of calls to getrawtransaction rpc endpoint",
|
||||||
|
@ -122,6 +130,7 @@ func init() {
|
||||||
getassetstateCalled,
|
getassetstateCalled,
|
||||||
getaccountstateCalled,
|
getaccountstateCalled,
|
||||||
getunspentsCalled,
|
getunspentsCalled,
|
||||||
|
gettxoutCalled,
|
||||||
getrawtransactionCalled,
|
getrawtransactionCalled,
|
||||||
sendrawtransactionCalled,
|
sendrawtransactionCalled,
|
||||||
)
|
)
|
||||||
|
|
|
@ -245,6 +245,10 @@ Methods:
|
||||||
getrawtransactionCalled.Inc()
|
getrawtransactionCalled.Inc()
|
||||||
results, resultsErr = s.getrawtransaction(reqParams)
|
results, resultsErr = s.getrawtransaction(reqParams)
|
||||||
|
|
||||||
|
case "gettxout":
|
||||||
|
gettxoutCalled.Inc()
|
||||||
|
results, resultsErr = s.getTxOut(reqParams)
|
||||||
|
|
||||||
case "getunspents":
|
case "getunspents":
|
||||||
getunspentsCalled.Inc()
|
getunspentsCalled.Inc()
|
||||||
results, resultsErr = s.getAccountState(reqParams, true)
|
results, resultsErr = s.getAccountState(reqParams, true)
|
||||||
|
@ -311,6 +315,40 @@ func (s *Server) getrawtransaction(reqParams Params) (interface{}, error) {
|
||||||
return results, resultsErr
|
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.
|
// 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 Params, unspents bool) (interface{}, error) {
|
||||||
var resultsErr error
|
var resultsErr error
|
||||||
|
|
|
@ -112,6 +112,38 @@ var rpcTestCases = map[string][]rpcTestCase{
|
||||||
fail: true,
|
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": {
|
"getblock": {
|
||||||
{
|
{
|
||||||
name: "positive",
|
name: "positive",
|
||||||
|
@ -489,6 +521,23 @@ func TestRPC(t *testing.T) {
|
||||||
require.NoErrorf(t, err, "could not parse response: %s", body)
|
require.NoErrorf(t, err, "could not parse response: %s", body)
|
||||||
assert.Equal(t, "400000455b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e882a1227d2c7b226c616e67223a22656e222c226e616d65223a22416e745368617265227d5d0000c16ff28623000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b00000000", res.Result)
|
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{}) {
|
func (tc rpcTestCase) getResultPair(e *executor) (expected interface{}, res interface{}) {
|
||||||
|
|
|
@ -96,6 +96,13 @@ type GetRawTxResponse struct {
|
||||||
Result *RawTxResponse `json:"result"`
|
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.
|
// RawTxResponse stores transaction with blockchain metadata to be sent as a response.
|
||||||
type RawTxResponse struct {
|
type RawTxResponse struct {
|
||||||
TxResponse
|
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