Merge pull request #1516 from nspcc-dev/getblocktransfertx
Getblocktransfertx RPC API for 2.x
This commit is contained in:
commit
559024671a
7 changed files with 414 additions and 123 deletions
77
docs/rpc.md
77
docs/rpc.md
|
@ -260,6 +260,83 @@ Reply:
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### getblocktransfertx call
|
||||||
|
|
||||||
|
`getblocktransfertx` provides a list of transactions that did some asset
|
||||||
|
transfers in a block (either UTXO or NEP5). It gets a block number or hash as
|
||||||
|
a single parameter and its output format is similar to `getalltransfertx`
|
||||||
|
except for `events` where it doesn't use `address` and `type` fields, but
|
||||||
|
rather provides `from` and `to` (meaning that the asset was moved from `from`
|
||||||
|
to `to` address).
|
||||||
|
|
||||||
|
Example request:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "jsonrpc": "2.0", "id": 5, "method": "getblocktransfertx", "params": [6000003]}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Reply:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id" : 5,
|
||||||
|
"result" : [
|
||||||
|
{
|
||||||
|
"txid" : "0xaec0994211e5d7fd459a4445b113db0102ac79cb90a08b3211b9a9190a6feaa3",
|
||||||
|
"elements" : [
|
||||||
|
{
|
||||||
|
"asset" : "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7",
|
||||||
|
"type" : "output",
|
||||||
|
"value" : "0.19479178",
|
||||||
|
"address" : "AHwyehUHV8ujVJBN6Tz3jBDuPAHQ1wKU5R"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"block_index" : 6000003,
|
||||||
|
"timestamp" : 1597295221,
|
||||||
|
"sys_fee" : "0",
|
||||||
|
"net_fee" : "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sys_fee" : "0",
|
||||||
|
"net_fee" : "0",
|
||||||
|
"elements" : [
|
||||||
|
{
|
||||||
|
"value" : "971",
|
||||||
|
"address" : "AHFvPbmMbxnD6EQQWcope8VWKEMDtG1qTQ",
|
||||||
|
"asset" : "c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b",
|
||||||
|
"type" : "input"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address" : "AP18zgg58bK6vZ7MX51XfD63eEEuqKCgJt",
|
||||||
|
"value" : "971",
|
||||||
|
"asset" : "c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b",
|
||||||
|
"type" : "output"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"block_index" : 6000003,
|
||||||
|
"txid" : "0x6b0888b10b1150d301f749d56b7365b307d814cfd843bd064e68313bb30c9351",
|
||||||
|
"timestamp" : 1597295221
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sys_fee" : "0",
|
||||||
|
"net_fee" : "0",
|
||||||
|
"block_index" : 6000003,
|
||||||
|
"txid" : "0x6b2220834059710aecfe4b2cbdb56311bbb27ac5d94795c041b5a2e6fb76f96e",
|
||||||
|
"timestamp" : 1597295221,
|
||||||
|
"events" : [
|
||||||
|
{
|
||||||
|
"from" : "AeNAPrVp7ZWtYLaAWvZ3gkKQsJBZUJJz3r",
|
||||||
|
"asset" : "b951ecbbc5fe37a9c280a76cb0ce0014827294cf",
|
||||||
|
"to" : "AVkhaHaxLaboUVFD1Rke5abTJuKAqziCkY",
|
||||||
|
"value" : "69061428"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"jsonrpc" : "2.0"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
#### Websocket server
|
#### Websocket server
|
||||||
|
|
||||||
This server accepts websocket connections on `ws://$BASE_URL/ws` address. You
|
This server accepts websocket connections on `ws://$BASE_URL/ws` address. You
|
||||||
|
|
|
@ -3,7 +3,6 @@ package core
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
@ -801,31 +800,11 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
|
||||||
}
|
}
|
||||||
var index uint32
|
var index uint32
|
||||||
for _, note := range systemInterop.notifications {
|
for _, note := range systemInterop.notifications {
|
||||||
arr, ok := note.Item.Value().([]vm.StackItem)
|
transfer, err := state.NEP5TransferFromNotification(note, tx.Hash(), block.Index, block.Timestamp, index)
|
||||||
if !ok || len(arr) != 4 {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
op, ok := arr[0].Value().([]byte)
|
bc.processNEP5Transfer(cache, transfer)
|
||||||
if !ok || string(op) != "transfer" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
from, ok := arr[1].Value().([]byte)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
to, ok := arr[2].Value().([]byte)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
amount, ok := arr[3].Value().(*big.Int)
|
|
||||||
if !ok {
|
|
||||||
bs, ok := arr[3].Value().([]byte)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
amount = emit.BytesToInt(bs)
|
|
||||||
}
|
|
||||||
bc.processNEP5Transfer(cache, tx, block, note.ScriptHash, from, to, amount.Int64(), index)
|
|
||||||
index++
|
index++
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -957,66 +936,48 @@ func processTransfer(cache *dao.Cached, tx *transaction.Transaction, b *block.Bl
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseUint160(addr []byte) util.Uint160 {
|
func (bc *Blockchain) processNEP5Transfer(cache *dao.Cached, transfer *state.NEP5Transfer) {
|
||||||
if u, err := util.Uint160DecodeBytesBE(addr); err == nil {
|
if !transfer.From.Equals(util.Uint160{}) {
|
||||||
return u
|
balances, err := cache.GetNEP5Balances(transfer.From)
|
||||||
}
|
|
||||||
return util.Uint160{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bc *Blockchain) processNEP5Transfer(cache *dao.Cached, tx *transaction.Transaction, b *block.Block, sc util.Uint160, from, to []byte, amount int64, index uint32) {
|
|
||||||
toAddr := parseUint160(to)
|
|
||||||
fromAddr := parseUint160(from)
|
|
||||||
transfer := &state.NEP5Transfer{
|
|
||||||
Asset: sc,
|
|
||||||
From: fromAddr,
|
|
||||||
To: toAddr,
|
|
||||||
Block: b.Index,
|
|
||||||
Timestamp: b.Timestamp,
|
|
||||||
Tx: tx.Hash(),
|
|
||||||
Index: index,
|
|
||||||
}
|
|
||||||
if !fromAddr.Equals(util.Uint160{}) {
|
|
||||||
balances, err := cache.GetNEP5Balances(fromAddr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
bs := balances.Trackers[sc]
|
bs := balances.Trackers[transfer.Asset]
|
||||||
bs.Balance -= amount
|
bs.Balance -= transfer.Amount
|
||||||
bs.LastUpdatedBlock = b.Index
|
bs.LastUpdatedBlock = transfer.Block
|
||||||
balances.Trackers[sc] = bs
|
balances.Trackers[transfer.Asset] = bs
|
||||||
|
|
||||||
transfer.Amount = -amount
|
transfer.Amount = -transfer.Amount
|
||||||
isBig, err := cache.AppendNEP5Transfer(fromAddr, balances.NextTransferBatch, transfer)
|
isBig, err := cache.AppendNEP5Transfer(transfer.From, balances.NextTransferBatch, transfer)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
transfer.Amount = -transfer.Amount
|
||||||
|
if isBig {
|
||||||
|
balances.NextTransferBatch++
|
||||||
|
}
|
||||||
|
if err := cache.PutNEP5Balances(transfer.From, balances); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !transfer.To.Equals(util.Uint160{}) {
|
||||||
|
balances, err := cache.GetNEP5Balances(transfer.To)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bs := balances.Trackers[transfer.Asset]
|
||||||
|
bs.Balance += transfer.Amount
|
||||||
|
bs.LastUpdatedBlock = transfer.Block
|
||||||
|
balances.Trackers[transfer.Asset] = bs
|
||||||
|
|
||||||
|
isBig, err := cache.AppendNEP5Transfer(transfer.To, balances.NextTransferBatch, transfer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if isBig {
|
if isBig {
|
||||||
balances.NextTransferBatch++
|
balances.NextTransferBatch++
|
||||||
}
|
}
|
||||||
if err := cache.PutNEP5Balances(fromAddr, balances); err != nil {
|
if err := cache.PutNEP5Balances(transfer.To, balances); err != nil {
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !toAddr.Equals(util.Uint160{}) {
|
|
||||||
balances, err := cache.GetNEP5Balances(toAddr)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
bs := balances.Trackers[sc]
|
|
||||||
bs.Balance += amount
|
|
||||||
bs.LastUpdatedBlock = b.Index
|
|
||||||
balances.Trackers[sc] = bs
|
|
||||||
|
|
||||||
transfer.Amount = amount
|
|
||||||
isBig, err := cache.AppendNEP5Transfer(toAddr, balances.NextTransferBatch, transfer)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if isBig {
|
|
||||||
balances.NextTransferBatch++
|
|
||||||
}
|
|
||||||
if err := cache.PutNEP5Balances(toAddr, balances); err != nil {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
package state
|
package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NEP5Tracker contains info about a single account in a NEP5 contract.
|
// NEP5Tracker contains info about a single account in a NEP5 contract.
|
||||||
|
@ -148,6 +153,56 @@ func (t *NEP5Tracker) DecodeBinary(r *io.BinReader) {
|
||||||
t.LastUpdatedBlock = r.ReadU32LE()
|
t.LastUpdatedBlock = r.ReadU32LE()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseUint160(addr []byte) util.Uint160 {
|
||||||
|
if u, err := util.Uint160DecodeBytesBE(addr); err == nil {
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
return util.Uint160{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NEP5TransferFromNotification creates NEP5Transfer structure from the given
|
||||||
|
// notification (and using given context) if it's possible to parse it as
|
||||||
|
// NEP5 transfer.
|
||||||
|
func NEP5TransferFromNotification(ne NotificationEvent, txHash util.Uint256, height uint32, time uint32, index uint32) (*NEP5Transfer, error) {
|
||||||
|
arr, ok := ne.Item.Value().([]vm.StackItem)
|
||||||
|
if !ok || len(arr) != 4 {
|
||||||
|
return nil, errors.New("no array or wrong element count")
|
||||||
|
}
|
||||||
|
op, ok := arr[0].Value().([]byte)
|
||||||
|
if !ok || string(op) != "transfer" {
|
||||||
|
return nil, errors.New("not a 'transfer' event")
|
||||||
|
}
|
||||||
|
from, ok := arr[1].Value().([]byte)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("wrong 'from' type")
|
||||||
|
}
|
||||||
|
to, ok := arr[2].Value().([]byte)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("wrong 'to' type")
|
||||||
|
}
|
||||||
|
amount, ok := arr[3].Value().(*big.Int)
|
||||||
|
if !ok {
|
||||||
|
bs, ok := arr[3].Value().([]byte)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("wrong amount type")
|
||||||
|
}
|
||||||
|
amount = emit.BytesToInt(bs)
|
||||||
|
}
|
||||||
|
toAddr := parseUint160(to)
|
||||||
|
fromAddr := parseUint160(from)
|
||||||
|
transfer := &NEP5Transfer{
|
||||||
|
Asset: ne.ScriptHash,
|
||||||
|
From: fromAddr,
|
||||||
|
To: toAddr,
|
||||||
|
Amount: amount.Int64(),
|
||||||
|
Block: height,
|
||||||
|
Timestamp: time,
|
||||||
|
Tx: txHash,
|
||||||
|
Index: index,
|
||||||
|
}
|
||||||
|
return transfer, nil
|
||||||
|
}
|
||||||
|
|
||||||
// EncodeBinary implements io.Serializable interface.
|
// EncodeBinary implements io.Serializable interface.
|
||||||
// Note: change NEP5TransferSize constant when changing this function.
|
// Note: change NEP5TransferSize constant when changing this function.
|
||||||
func (t *NEP5Transfer) EncodeBinary(w *io.BinWriter) {
|
func (t *NEP5Transfer) EncodeBinary(w *io.BinWriter) {
|
||||||
|
|
|
@ -201,6 +201,30 @@ func (c *Client) GetBlockSysFee(index uint32) (util.Fixed8, error) {
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getBlockTransferTx is an internal version of GetBlockTransferTxByIndex/GetBlockTransferTxByHash.
|
||||||
|
func (c *Client) getBlockTransferTx(param interface{}) ([]result.TransferTx, error) {
|
||||||
|
var (
|
||||||
|
params = request.NewRawParams(param)
|
||||||
|
resp = new([]result.TransferTx)
|
||||||
|
)
|
||||||
|
if err := c.performRequest("getblocktransfertx", params, resp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return *resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlockTransferTxByIndex returns all transfer transactions from a block.
|
||||||
|
// It only works with neo-go 0.79.0+ servers.
|
||||||
|
func (c *Client) GetBlockTransferTxByIndex(index uint32) ([]result.TransferTx, error) {
|
||||||
|
return c.getBlockTransferTx(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlockTransferTxByHash returns all transfer transactions from a block.
|
||||||
|
// It only works with neo-go 0.79.0+ servers.
|
||||||
|
func (c *Client) GetBlockTransferTxByHash(hash util.Uint256) ([]result.TransferTx, error) {
|
||||||
|
return c.getBlockTransferTx(hash)
|
||||||
|
}
|
||||||
|
|
||||||
// GetClaimable returns tx outputs which can be claimed.
|
// GetClaimable returns tx outputs which can be claimed.
|
||||||
func (c *Client) GetClaimable(address string) (*result.ClaimableInfo, error) {
|
func (c *Client) GetClaimable(address string) (*result.ClaimableInfo, error) {
|
||||||
params := request.NewRawParams(address)
|
params := request.NewRawParams(address)
|
||||||
|
|
|
@ -83,10 +83,14 @@ type TransferTx struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TransferTxEvent is an event used for elements or events of TransferTx, it's
|
// TransferTxEvent is an event used for elements or events of TransferTx, it's
|
||||||
// either a single input/output, or a nep5 transfer.
|
// either a single input/output, or a nep5 transfer. The former always has
|
||||||
|
// Address and Type fields set with no From/To, the latter can either have
|
||||||
|
// From and To or Address and Type depending on particular RPC API function.
|
||||||
type TransferTxEvent struct {
|
type TransferTxEvent struct {
|
||||||
Address string `json:"address"`
|
Address string `json:"address,omitempty"`
|
||||||
Type string `json:"type"`
|
From string `json:"from,omitempty"`
|
||||||
|
To string `json:"to,omitempty"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
Value string `json:"value"`
|
Value string `json:"value"`
|
||||||
Asset string `json:"asset"`
|
Asset string `json:"asset"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,6 +90,7 @@ var rpcHandlers = map[string]func(*Server, request.Params) (interface{}, *respon
|
||||||
"getblockhash": (*Server).getBlockHash,
|
"getblockhash": (*Server).getBlockHash,
|
||||||
"getblockheader": (*Server).getBlockHeader,
|
"getblockheader": (*Server).getBlockHeader,
|
||||||
"getblocksysfee": (*Server).getBlockSysFee,
|
"getblocksysfee": (*Server).getBlockSysFee,
|
||||||
|
"getblocktransfertx": (*Server).getBlockTransferTx,
|
||||||
"getclaimable": (*Server).getClaimable,
|
"getclaimable": (*Server).getClaimable,
|
||||||
"getconnectioncount": (*Server).getConnectionCount,
|
"getconnectioncount": (*Server).getConnectionCount,
|
||||||
"getcontractstate": (*Server).getContractState,
|
"getcontractstate": (*Server).getContractState,
|
||||||
|
@ -395,29 +396,34 @@ func (s *Server) getConnectionCount(_ request.Params) (interface{}, *response.Er
|
||||||
return s.coreServer.PeerCount(), nil
|
return s.coreServer.PeerCount(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) getBlock(reqParams request.Params) (interface{}, *response.Error) {
|
func (s *Server) getBlockHashFromParam(param *request.Param) (util.Uint256, *response.Error) {
|
||||||
var hash util.Uint256
|
var hash util.Uint256
|
||||||
|
|
||||||
param := reqParams.Value(0)
|
|
||||||
if param == nil {
|
if param == nil {
|
||||||
return nil, response.ErrInvalidParams
|
return hash, response.ErrInvalidParams
|
||||||
}
|
}
|
||||||
|
|
||||||
switch param.Type {
|
switch param.Type {
|
||||||
case request.StringT:
|
case request.StringT:
|
||||||
var err error
|
var err error
|
||||||
hash, err = param.GetUint256()
|
hash, err = param.GetUint256()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, response.ErrInvalidParams
|
return hash, response.ErrInvalidParams
|
||||||
}
|
}
|
||||||
case request.NumberT:
|
case request.NumberT:
|
||||||
num, err := s.blockHeightFromParam(param)
|
num, err := s.blockHeightFromParam(param)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, response.ErrInvalidParams
|
return hash, response.ErrInvalidParams
|
||||||
}
|
}
|
||||||
hash = s.chain.GetHeaderHash(num)
|
hash = s.chain.GetHeaderHash(num)
|
||||||
default:
|
default:
|
||||||
return nil, response.ErrInvalidParams
|
return hash, response.ErrInvalidParams
|
||||||
|
}
|
||||||
|
return hash, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) getBlock(reqParams request.Params) (interface{}, *response.Error) {
|
||||||
|
hash, respErr := s.getBlockHashFromParam(reqParams.Value(0))
|
||||||
|
if respErr != nil {
|
||||||
|
return nil, respErr
|
||||||
}
|
}
|
||||||
|
|
||||||
block, err := s.chain.GetBlock(hash)
|
block, err := s.chain.GetBlock(hash)
|
||||||
|
@ -829,6 +835,56 @@ func (s *Server) getNEP5Transfers(ps request.Params) (interface{}, *response.Err
|
||||||
return bs, nil
|
return bs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func appendUTXOToTransferTx(transfer *result.TransferTx, tx *transaction.Transaction, chain core.Blockchainer) *response.Error {
|
||||||
|
inouts, err := chain.References(tx)
|
||||||
|
if err != nil {
|
||||||
|
return response.NewInternalServerError("invalid tx", err)
|
||||||
|
}
|
||||||
|
for _, inout := range inouts {
|
||||||
|
var event result.TransferTxEvent
|
||||||
|
|
||||||
|
event.Address = address.Uint160ToString(inout.Out.ScriptHash)
|
||||||
|
event.Type = "input"
|
||||||
|
event.Value = inout.Out.Amount.String()
|
||||||
|
event.Asset = inout.Out.AssetID.StringLE()
|
||||||
|
transfer.Elements = append(transfer.Elements, event)
|
||||||
|
}
|
||||||
|
for _, out := range tx.Outputs {
|
||||||
|
var event result.TransferTxEvent
|
||||||
|
|
||||||
|
event.Address = address.Uint160ToString(out.ScriptHash)
|
||||||
|
event.Type = "output"
|
||||||
|
event.Value = out.Amount.String()
|
||||||
|
event.Asset = out.AssetID.StringLE()
|
||||||
|
transfer.Elements = append(transfer.Elements, event)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// uint160ToString converts given hash to address, unless it's zero and an empty
|
||||||
|
// string is returned then.
|
||||||
|
func uint160ToString(u util.Uint160) string {
|
||||||
|
if u.Equals(util.Uint160{}) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return address.Uint160ToString(u)
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendNEP5ToTransferTx(transfer *result.TransferTx, nepTr *state.NEP5Transfer) {
|
||||||
|
var event result.TransferTxEvent
|
||||||
|
event.Asset = nepTr.Asset.StringLE()
|
||||||
|
if nepTr.Amount > 0 { // token was received
|
||||||
|
event.Value = strconv.FormatInt(nepTr.Amount, 10)
|
||||||
|
event.Type = "receive"
|
||||||
|
event.Address = uint160ToString(nepTr.From)
|
||||||
|
} else {
|
||||||
|
event.Value = strconv.FormatInt(-nepTr.Amount, 10)
|
||||||
|
event.Type = "send"
|
||||||
|
event.Address = uint160ToString(nepTr.To)
|
||||||
|
}
|
||||||
|
transfer.Events = append(transfer.Events, event)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) getAllTransferTx(ps request.Params) (interface{}, *response.Error) {
|
func (s *Server) getAllTransferTx(ps request.Params) (interface{}, *response.Error) {
|
||||||
var respErr *response.Error
|
var respErr *response.Error
|
||||||
|
|
||||||
|
@ -937,50 +993,15 @@ func (s *Server) getAllTransferTx(ps request.Params) (interface{}, *response.Err
|
||||||
}
|
}
|
||||||
transfer.NetworkFee = s.chain.NetworkFee(tx).String()
|
transfer.NetworkFee = s.chain.NetworkFee(tx).String()
|
||||||
transfer.SystemFee = s.chain.SystemFee(tx).String()
|
transfer.SystemFee = s.chain.SystemFee(tx).String()
|
||||||
|
respErr = appendUTXOToTransferTx(&transfer, tx, s.chain)
|
||||||
inouts, err := s.chain.References(tx)
|
if respErr != nil {
|
||||||
if err != nil {
|
|
||||||
respErr = response.NewInternalServerError("invalid tx", err)
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
for _, inout := range inouts {
|
|
||||||
var event result.TransferTxEvent
|
|
||||||
|
|
||||||
event.Address = address.Uint160ToString(inout.Out.ScriptHash)
|
|
||||||
event.Type = "input"
|
|
||||||
event.Value = inout.Out.Amount.String()
|
|
||||||
event.Asset = inout.Out.AssetID.StringLE()
|
|
||||||
transfer.Elements = append(transfer.Elements, event)
|
|
||||||
}
|
|
||||||
for _, out := range tx.Outputs {
|
|
||||||
var event result.TransferTxEvent
|
|
||||||
|
|
||||||
event.Address = address.Uint160ToString(out.ScriptHash)
|
|
||||||
event.Type = "output"
|
|
||||||
event.Value = out.Amount.String()
|
|
||||||
event.Asset = out.AssetID.StringLE()
|
|
||||||
transfer.Elements = append(transfer.Elements, event)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Pick all NEP5 events for this transaction, if there are any.
|
// Pick all NEP5 events for this transaction, if there are any.
|
||||||
for haveNep5 && nep5Last.Tx.Equals(transfer.TxID) {
|
for haveNep5 && nep5Last.Tx.Equals(transfer.TxID) {
|
||||||
if !skipTx {
|
if !skipTx {
|
||||||
var event result.TransferTxEvent
|
appendNEP5ToTransferTx(&transfer, &nep5Last)
|
||||||
event.Asset = nep5Last.Asset.StringLE()
|
|
||||||
if nep5Last.Amount > 0 { // token was received
|
|
||||||
event.Value = strconv.FormatInt(nep5Last.Amount, 10)
|
|
||||||
event.Type = "receive"
|
|
||||||
if !nep5Last.From.Equals(util.Uint160{}) {
|
|
||||||
event.Address = address.Uint160ToString(nep5Last.From)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
event.Value = strconv.FormatInt(-nep5Last.Amount, 10)
|
|
||||||
event.Type = "send"
|
|
||||||
if !nep5Last.To.Equals(util.Uint160{}) {
|
|
||||||
event.Address = address.Uint160ToString(nep5Last.To)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
transfer.Events = append(transfer.Events, event)
|
|
||||||
}
|
}
|
||||||
nep5Last, haveNep5 = <-nep5Trs
|
nep5Last, haveNep5 = <-nep5Trs
|
||||||
if haveNep5 {
|
if haveNep5 {
|
||||||
|
@ -1236,6 +1257,73 @@ func (s *Server) getAccountStateAux(reqParams request.Params, unspents bool) (in
|
||||||
return results, resultsErr
|
return results, resultsErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) getBlockTransferTx(ps request.Params) (interface{}, *response.Error) {
|
||||||
|
var (
|
||||||
|
res = make([]result.TransferTx, 0)
|
||||||
|
respErr *response.Error
|
||||||
|
)
|
||||||
|
|
||||||
|
hash, respErr := s.getBlockHashFromParam(ps.Value(0))
|
||||||
|
if respErr != nil {
|
||||||
|
return nil, respErr
|
||||||
|
}
|
||||||
|
|
||||||
|
block, err := s.chain.GetBlock(hash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, response.NewInternalServerError(fmt.Sprintf("Problem locating block with hash: %s", hash), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tx := range block.Transactions {
|
||||||
|
var transfer = result.TransferTx{
|
||||||
|
TxID: tx.Hash(),
|
||||||
|
Timestamp: block.Timestamp,
|
||||||
|
Index: block.Index,
|
||||||
|
NetworkFee: s.chain.NetworkFee(tx).String(),
|
||||||
|
SystemFee: s.chain.SystemFee(tx).String(),
|
||||||
|
}
|
||||||
|
|
||||||
|
respErr = appendUTXOToTransferTx(&transfer, tx, s.chain)
|
||||||
|
if respErr != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if tx.Type == transaction.InvocationType {
|
||||||
|
execRes, err := s.chain.GetAppExecResult(tx.Hash())
|
||||||
|
if err != nil {
|
||||||
|
respErr = response.NewInternalServerError(fmt.Sprintf("no application log for invocation tx %s", tx.Hash()), err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if execRes.VMState != "HALT" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var index uint32
|
||||||
|
for _, note := range execRes.Events {
|
||||||
|
nepTr, err := state.NEP5TransferFromNotification(note, tx.Hash(), block.Index, block.Timestamp, index)
|
||||||
|
// It's OK for event to be something different from NEP5 transfer.
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
transfer.Events = append(transfer.Events, result.TransferTxEvent{
|
||||||
|
Asset: nepTr.Asset.StringLE(),
|
||||||
|
From: uint160ToString(nepTr.From),
|
||||||
|
To: uint160ToString(nepTr.To),
|
||||||
|
Value: strconv.FormatInt(nepTr.Amount, 10),
|
||||||
|
})
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(transfer.Elements) != 0 || len(transfer.Events) != 0 {
|
||||||
|
res = append(res, transfer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if respErr != nil {
|
||||||
|
return nil, respErr
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
// getBlockSysFee returns the system fees of the block, based on the specified index.
|
// getBlockSysFee returns the system fees of the block, based on the specified index.
|
||||||
func (s *Server) getBlockSysFee(reqParams request.Params) (interface{}, *response.Error) {
|
func (s *Server) getBlockSysFee(reqParams request.Params) (interface{}, *response.Error) {
|
||||||
param := reqParams.ValueWithType(0, request.NumberT)
|
param := reqParams.ValueWithType(0, request.NumberT)
|
||||||
|
|
|
@ -1325,6 +1325,88 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
t.Run("getblocktransfertx", func(t *testing.T) {
|
||||||
|
bNeo, err := e.chain.GetBlock(e.chain.GetHeaderHash(206))
|
||||||
|
require.NoError(t, err)
|
||||||
|
txNeoTo1 := bNeo.Transactions[1].Hash()
|
||||||
|
|
||||||
|
body := doRPCCall(`{"jsonrpc": "2.0", "id": 1, "method": "getblocktransfertx", "params": [206]}`, httpSrv.URL, t)
|
||||||
|
res := checkErrGetResult(t, body, false)
|
||||||
|
actualp := new([]result.TransferTx)
|
||||||
|
require.NoError(t, json.Unmarshal(res, actualp))
|
||||||
|
expected := []result.TransferTx{
|
||||||
|
result.TransferTx{
|
||||||
|
TxID: txNeoTo1,
|
||||||
|
Timestamp: bNeo.Timestamp,
|
||||||
|
Index: bNeo.Index,
|
||||||
|
SystemFee: "0",
|
||||||
|
NetworkFee: "0",
|
||||||
|
Elements: []result.TransferTxEvent{
|
||||||
|
result.TransferTxEvent{
|
||||||
|
Address: "AKkkumHbBipZ46UMZJoFynJMXzSRnBvKcs",
|
||||||
|
Type: "input",
|
||||||
|
Value: "99999000",
|
||||||
|
Asset: "c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b",
|
||||||
|
},
|
||||||
|
result.TransferTxEvent{
|
||||||
|
Address: "AWLYWXB8C9Lt1nHdDZJnC5cpYJjgRDLk17",
|
||||||
|
Type: "output",
|
||||||
|
Value: "1000",
|
||||||
|
Asset: "c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b",
|
||||||
|
},
|
||||||
|
result.TransferTxEvent{
|
||||||
|
Address: "AKkkumHbBipZ46UMZJoFynJMXzSRnBvKcs",
|
||||||
|
Type: "output",
|
||||||
|
Value: "99998000",
|
||||||
|
Asset: "c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
require.Equal(t, expected, *actualp)
|
||||||
|
|
||||||
|
bNep5, err := e.chain.GetBlock(e.chain.GetHeaderHash(207))
|
||||||
|
require.NoError(t, err)
|
||||||
|
txNep5Init := bNep5.Transactions[1].Hash()
|
||||||
|
txNep5Transfer := bNep5.Transactions[2].Hash()
|
||||||
|
|
||||||
|
body = doRPCCall(`{"jsonrpc": "2.0", "id": 1, "method": "getblocktransfertx", "params": [207]}`, httpSrv.URL, t)
|
||||||
|
res = checkErrGetResult(t, body, false)
|
||||||
|
actualp = new([]result.TransferTx)
|
||||||
|
require.NoError(t, json.Unmarshal(res, actualp))
|
||||||
|
expected = []result.TransferTx{
|
||||||
|
result.TransferTx{
|
||||||
|
TxID: txNep5Init,
|
||||||
|
Timestamp: bNep5.Timestamp,
|
||||||
|
Index: bNep5.Index,
|
||||||
|
SystemFee: "0",
|
||||||
|
NetworkFee: "0",
|
||||||
|
Events: []result.TransferTxEvent{
|
||||||
|
result.TransferTxEvent{
|
||||||
|
To: "AeEc6DNaiVZSNJfTJ72rAFFqVKAMR5B7i3",
|
||||||
|
Value: "1000000",
|
||||||
|
Asset: testContractHashOld,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
result.TransferTx{
|
||||||
|
TxID: txNep5Transfer,
|
||||||
|
Timestamp: bNep5.Timestamp,
|
||||||
|
Index: bNep5.Index,
|
||||||
|
SystemFee: "0",
|
||||||
|
NetworkFee: "0",
|
||||||
|
Events: []result.TransferTxEvent{
|
||||||
|
result.TransferTxEvent{
|
||||||
|
From: "AeEc6DNaiVZSNJfTJ72rAFFqVKAMR5B7i3",
|
||||||
|
To: "AKkkumHbBipZ46UMZJoFynJMXzSRnBvKcs",
|
||||||
|
Value: "1000",
|
||||||
|
Asset: testContractHashOld,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
require.Equal(t, expected, *actualp)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tc rpcTestCase) getResultPair(e *executor) (expected interface{}, res interface{}) {
|
func (tc rpcTestCase) getResultPair(e *executor) (expected interface{}, res interface{}) {
|
||||||
|
|
Loading…
Reference in a new issue