mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-12-04 19:19:44 +00:00
Merge pull request #1399 from nspcc-dev/getalltransfertx-2.x
Getalltransfertx 2.x
This commit is contained in:
commit
ec631984d0
7 changed files with 686 additions and 106 deletions
159
docs/rpc.md
159
docs/rpc.md
|
@ -56,6 +56,7 @@ which would yield the response:
|
|||
| `gettxout` |
|
||||
| `getunclaimed` |
|
||||
| `getunspents` |
|
||||
| `getutxotransfers` |
|
||||
| `getvalidators` |
|
||||
| `getversion` |
|
||||
| `invoke` |
|
||||
|
@ -102,6 +103,164 @@ and we're not accepting issues related to them.
|
|||
|
||||
Some additional extensions are implemented as a part of this RPC server.
|
||||
|
||||
#### Limits and paging for getnep5transfers and getutxotransfers
|
||||
|
||||
Both `getnep5transfers` and `getutxotransfers` RPC calls never return more than
|
||||
1000 results for one request (within specified time frame). You can pass your
|
||||
own limit via an additional parameter and then use paging to request the next
|
||||
batch of transfers.
|
||||
|
||||
Example requesting 10 events for address AYC7wn4xb8SEeYpgPXHHjLr3gBuWbgAC3Q
|
||||
within 0-1600094189 timestamps:
|
||||
|
||||
```json
|
||||
{ "jsonrpc": "2.0", "id": 5, "method": "getnep5transfers", "params":
|
||||
["AYC7wn4xb8SEeYpgPXHHjLr3gBuWbgAC3Q", 0, 1600094189, 10] }
|
||||
```
|
||||
|
||||
Get the next 10 transfers for the same account within the same time frame:
|
||||
|
||||
```json
|
||||
{ "jsonrpc": "2.0", "id": 5, "method": "getnep5transfers", "params":
|
||||
["AYC7wn4xb8SEeYpgPXHHjLr3gBuWbgAC3Q", 0, 1600094189, 10, 1] }
|
||||
```
|
||||
|
||||
#### getalltransfertx call
|
||||
|
||||
In addition to regular `getnep5transfers` and `getutxotransfers` RPC calls
|
||||
`getalltransfertx` is provided to return both NEP5 and UTXO events for account
|
||||
in a single stream of events. These events are grouped by transaction and an
|
||||
additional metadata like fees is provided. It has the same parameters as
|
||||
`getnep5transfers`, but limits and paging is applied to transactions instead
|
||||
of transfer events. UTXO inputs and outputs are provided by `elements` array,
|
||||
while NEP5 transfer events are contained in `events` array.
|
||||
|
||||
Example request:
|
||||
|
||||
```json
|
||||
{ "jsonrpc": "2.0", "id": 5, "method": "getalltransfertx", "params":
|
||||
["AYC7wn4xb8SEeYpgPXHHjLr3gBuWbgAC3Q", 0, 1600094189, 2] }
|
||||
|
||||
```
|
||||
|
||||
Reply:
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc" : "2.0",
|
||||
"result" : [
|
||||
{
|
||||
"txid" : "0x1cb7e089bb52cabb35c480de9d99c41c6fea7f5a276b41d71ab3fc7c470dcb74",
|
||||
"events" : [
|
||||
{
|
||||
"type" : "send",
|
||||
"asset" : "3a4acd3647086e7c44398aac0349802e6a171129",
|
||||
"value" : "20000000000",
|
||||
"address" : "ALuZLuuDssJqG2E4foANKwbLamYHuffFjg"
|
||||
}
|
||||
],
|
||||
"net_fee" : 0,
|
||||
"block_index" : 6163114,
|
||||
"timestamp" : 1600094117,
|
||||
"sys_fee" : 0
|
||||
},
|
||||
{
|
||||
"block_index" : 6162995,
|
||||
"net_fee" : 0,
|
||||
"timestamp" : 1600092165,
|
||||
"events" : [
|
||||
{
|
||||
"address" : "ALuZLuuDssJqG2E4foANKwbLamYHuffFjg",
|
||||
"value" : "20000000000",
|
||||
"type" : "receive",
|
||||
"asset" : "3a4acd3647086e7c44398aac0349802e6a171129"
|
||||
}
|
||||
],
|
||||
"txid" : "0xc8b45480ade5395a4a239bb44eea6d86113f32090c4854b0c4aeee1b9485edab",
|
||||
"sys_fee" : 0
|
||||
}
|
||||
],
|
||||
"id" : 5
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Another request:
|
||||
|
||||
```json
|
||||
{ "jsonrpc": "2.0", "id": 5, "method": "getalltransfertx", "params":
|
||||
["AKJL9HwrFGdic9GTTXrdaHuNYa5oxqioRY", 0, 1600079056, 2, 13] }
|
||||
```
|
||||
|
||||
Reply:
|
||||
|
||||
```json
|
||||
{
|
||||
"result" : [
|
||||
{
|
||||
"timestamp" : 1561566911,
|
||||
"net_fee" : 1,
|
||||
"events" : [
|
||||
{
|
||||
"address" : "AZCcft1uYtmZXxzHPr5tY7L6M85zG7Dsrv",
|
||||
"asset" : "1578103c13e39df15d0d29826d957e85d770d8c9",
|
||||
"type" : "receive",
|
||||
"value" : "2380844141430"
|
||||
}
|
||||
],
|
||||
"elements" : [
|
||||
{
|
||||
"asset" : "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7",
|
||||
"address" : "AZCcft1uYtmZXxzHPr5tY7L6M85zG7Dsrv",
|
||||
"value" : "0.00000831",
|
||||
"type" : "input"
|
||||
},
|
||||
{
|
||||
"address" : "AZCcft1uYtmZXxzHPr5tY7L6M85zG7Dsrv",
|
||||
"asset" : "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7",
|
||||
"type" : "output",
|
||||
"value" : "0.0000083"
|
||||
}
|
||||
],
|
||||
"sys_fee" : 0,
|
||||
"txid" : "0xb4f1bdb466d8bd3524502008a0bc1f9342356b4eea67be19d384845c670442a6",
|
||||
"block_index" : 3929554
|
||||
},
|
||||
{
|
||||
"elements" : [
|
||||
{
|
||||
"value" : "0.00000838",
|
||||
"type" : "input",
|
||||
"asset" : "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7",
|
||||
"address" : "AZCcft1uYtmZXxzHPr5tY7L6M85zG7Dsrv"
|
||||
},
|
||||
{
|
||||
"asset" : "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7",
|
||||
"address" : "AZCcft1uYtmZXxzHPr5tY7L6M85zG7Dsrv",
|
||||
"value" : "0.00000837",
|
||||
"type" : "output"
|
||||
}
|
||||
],
|
||||
"events" : [
|
||||
{
|
||||
"asset" : "1578103c13e39df15d0d29826d957e85d770d8c9",
|
||||
"address" : "AZCcft1uYtmZXxzHPr5tY7L6M85zG7Dsrv",
|
||||
"value" : "2100000000",
|
||||
"type" : "receive"
|
||||
}
|
||||
],
|
||||
"timestamp" : 1561566300,
|
||||
"net_fee" : 1,
|
||||
"block_index" : 3929523,
|
||||
"sys_fee" : 0,
|
||||
"txid" : "0xc045c0612b34218b7e5eaee973114af3eff925f859adf23cf953930f667cdc93"
|
||||
}
|
||||
],
|
||||
"id" : 5,
|
||||
"jsonrpc" : "2.0"
|
||||
}
|
||||
```
|
||||
|
||||
#### Websocket server
|
||||
|
||||
This server accepts websocket connections on `ws://$BASE_URL/ws` address. You
|
||||
|
|
|
@ -19,6 +19,7 @@ TODO:
|
|||
Supported methods
|
||||
|
||||
getaccountstate
|
||||
getalltransfertx
|
||||
getapplicationlog
|
||||
getassetstate
|
||||
getbestblockhash
|
||||
|
@ -40,6 +41,7 @@ Supported methods
|
|||
gettxout
|
||||
getunclaimed
|
||||
getunspents
|
||||
getutxotransfers
|
||||
getvalidators
|
||||
getversion
|
||||
invoke
|
||||
|
|
|
@ -28,6 +28,20 @@ func (c *Client) GetAccountState(address string) (*result.AccountState, error) {
|
|||
return resp, nil
|
||||
}
|
||||
|
||||
// GetAllTransferTx returns all transfer transactions for a given account within
|
||||
// specified timestamps (by block time) with specified output limits and page. It
|
||||
// only works with neo-go 0.78.0+ servers.
|
||||
func (c *Client) GetAllTransferTx(acc util.Uint160, start, end uint32, limit, page int) ([]result.TransferTx, error) {
|
||||
var (
|
||||
params = request.NewRawParams(acc.StringLE(), start, end, limit, page)
|
||||
resp = new([]result.TransferTx)
|
||||
)
|
||||
if err := c.performRequest("getalltransfertx", params, resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return *resp, nil
|
||||
}
|
||||
|
||||
// GetApplicationLog returns the contract log based on the specified txid.
|
||||
func (c *Client) GetApplicationLog(hash util.Uint256) (*result.ApplicationLog, error) {
|
||||
var (
|
||||
|
@ -231,9 +245,31 @@ func (c *Client) GetNEP5Balances(address util.Uint160) (*result.NEP5Balances, er
|
|||
return resp, nil
|
||||
}
|
||||
|
||||
// GetNEP5Transfers is a wrapper for getnep5transfers RPC.
|
||||
func (c *Client) GetNEP5Transfers(address string) (*result.NEP5Transfers, error) {
|
||||
// GetNEP5Transfers is a wrapper for getnep5transfers RPC. Address parameter
|
||||
// is mandatory, while all the others are optional. Start and stop parameters
|
||||
// are supported since neo-go 0.77.0 and limit and page since neo-go 0.78.0.
|
||||
// These parameters are positional in the JSON-RPC call, you can't specify limit
|
||||
// and not specify start/stop for example.
|
||||
func (c *Client) GetNEP5Transfers(address string, start, stop *uint32, limit, page *int) (*result.NEP5Transfers, error) {
|
||||
params := request.NewRawParams(address)
|
||||
if start != nil {
|
||||
params.Values = append(params.Values, *start)
|
||||
if stop != nil {
|
||||
params.Values = append(params.Values, *stop)
|
||||
if limit != nil {
|
||||
params.Values = append(params.Values, *limit)
|
||||
if page != nil {
|
||||
params.Values = append(params.Values, *page)
|
||||
}
|
||||
} else if page != nil {
|
||||
return nil, errors.New("bad parameters")
|
||||
}
|
||||
} else if limit != nil || page != nil {
|
||||
return nil, errors.New("bad parameters")
|
||||
}
|
||||
} else if stop != nil || limit != nil || page != nil {
|
||||
return nil, errors.New("bad parameters")
|
||||
}
|
||||
resp := new(result.NEP5Transfers)
|
||||
if err := c.performRequest("getnep5transfers", params, resp); err != nil {
|
||||
return nil, err
|
||||
|
@ -368,6 +404,38 @@ func (c *Client) GetUnspents(address string) (*result.Unspents, error) {
|
|||
return resp, nil
|
||||
}
|
||||
|
||||
// GetUTXOTransfers is a wrapper for getutxoransfers RPC. Address parameter
|
||||
// is mandatory, while all the others are optional. It's only supported since
|
||||
// neo-go 0.77.0 with limit and page parameters only since neo-go 0.78.0.
|
||||
// These parameters are positional in the JSON-RPC call, you can't specify limit
|
||||
// and not specify start/stop for example.
|
||||
func (c *Client) GetUTXOTransfers(address string, start, stop *uint32, limit, page *int) (*result.GetUTXO, error) {
|
||||
params := request.NewRawParams(address)
|
||||
if start != nil {
|
||||
params.Values = append(params.Values, *start)
|
||||
if stop != nil {
|
||||
params.Values = append(params.Values, *stop)
|
||||
if limit != nil {
|
||||
params.Values = append(params.Values, *limit)
|
||||
if page != nil {
|
||||
params.Values = append(params.Values, *page)
|
||||
}
|
||||
} else if page != nil {
|
||||
return nil, errors.New("bad parameters")
|
||||
}
|
||||
} else if limit != nil || page != nil {
|
||||
return nil, errors.New("bad parameters")
|
||||
}
|
||||
} else if stop != nil || limit != nil || page != nil {
|
||||
return nil, errors.New("bad parameters")
|
||||
}
|
||||
resp := new(result.GetUTXO)
|
||||
if err := c.performRequest("getutxotransfers", params, resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetValidators returns the current NEO consensus nodes information and voting status.
|
||||
func (c *Client) GetValidators() ([]result.Validator, error) {
|
||||
var (
|
||||
|
|
|
@ -481,7 +481,7 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
|
|||
{
|
||||
name: "positive",
|
||||
invoke: func(c *Client) (interface{}, error) {
|
||||
return c.GetNEP5Transfers("AbHgdBaWEnHkCiLtDZXjhvhaAK2cwFh5pF")
|
||||
return c.GetNEP5Transfers("AbHgdBaWEnHkCiLtDZXjhvhaAK2cwFh5pF", nil, nil, nil, nil)
|
||||
},
|
||||
serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"sent":[],"received":[{"timestamp":1555651816,"asset_hash":"600c4f5200db36177e3e8a09e9f18e2fc7d12a0f","transfer_address":"AYwgBNMepiv5ocGcyNT4mA8zPLTQ8pDBis","amount":"1000000","block_index":436036,"transfer_notify_index":0,"tx_hash":"df7683ece554ecfb85cf41492c5f143215dd43ef9ec61181a28f922da06aba58"}],"address":"AbHgdBaWEnHkCiLtDZXjhvhaAK2cwFh5pF"}}`,
|
||||
result: func(c *Client) interface{} {
|
||||
|
@ -1138,7 +1138,7 @@ var rpcClientErrorCases = map[string][]rpcClientErrorCase{
|
|||
{
|
||||
name: "getnep5transfers_invalid_params_error",
|
||||
invoke: func(c *Client) (interface{}, error) {
|
||||
return c.GetNEP5Transfers("")
|
||||
return c.GetNEP5Transfers("", nil, nil, nil, nil)
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -1320,7 +1320,7 @@ var rpcClientErrorCases = map[string][]rpcClientErrorCase{
|
|||
{
|
||||
name: "getnep5transfers_unmarshalling_error",
|
||||
invoke: func(c *Client) (interface{}, error) {
|
||||
return c.GetNEP5Transfers("")
|
||||
return c.GetNEP5Transfers("", nil, nil, nil, nil)
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -69,3 +69,24 @@ func (b *NEP5Balance) UnmarshalJSON(data []byte) error {
|
|||
b.LastUpdated = s.LastUpdated
|
||||
return nil
|
||||
}
|
||||
|
||||
// TransferTx is a type used to represent and element of `getalltransfertx`
|
||||
// result. It combines transaction's inputs/outputs with NEP5 events.
|
||||
type TransferTx struct {
|
||||
TxID util.Uint256 `json:"txid"`
|
||||
Timestamp uint32 `json:"timestamp"`
|
||||
Index uint32 `json:"block_index"`
|
||||
SystemFee int64 `json:"sys_fee"`
|
||||
NetworkFee int64 `json:"net_fee"`
|
||||
Elements []TransferTxEvent `json:"elements,omitempty"`
|
||||
Events []TransferTxEvent `json:"events,omitempty"`
|
||||
}
|
||||
|
||||
// TransferTxEvent is an event used for elements or events of TransferTx, it's
|
||||
// either a single input/output, or a nep5 transfer.
|
||||
type TransferTxEvent struct {
|
||||
Address string `json:"address"`
|
||||
Type string `json:"type"`
|
||||
Value string `json:"value"`
|
||||
Asset string `json:"asset"`
|
||||
}
|
||||
|
|
|
@ -74,10 +74,14 @@ const (
|
|||
// treated like subscriber, so technically it's a limit on websocket
|
||||
// connections.
|
||||
maxSubscribers = 64
|
||||
|
||||
// Maximum number of elements for get*transfers requests.
|
||||
maxTransfersLimit = 1000
|
||||
)
|
||||
|
||||
var rpcHandlers = map[string]func(*Server, request.Params) (interface{}, *response.Error){
|
||||
"getaccountstate": (*Server).getAccountState,
|
||||
"getalltransfertx": (*Server).getAllTransferTx,
|
||||
"getapplicationlog": (*Server).getApplicationLog,
|
||||
"getassetstate": (*Server).getAssetState,
|
||||
"getbestblockhash": (*Server).getBestBlockHash,
|
||||
|
@ -450,34 +454,54 @@ func (s *Server) getVersion(_ request.Params) (interface{}, *response.Error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
func getTimestampsAndLimit(p1, p2, p3 *request.Param) (uint32, uint32, int, error) {
|
||||
func getTimestampsAndLimit(ps request.Params, index int) (uint32, uint32, int, int, error) {
|
||||
var start, end uint32
|
||||
var limit int
|
||||
if p1 != nil {
|
||||
val, err := p1.GetInt()
|
||||
var limit, page int
|
||||
|
||||
limit = maxTransfersLimit
|
||||
pStart, pEnd, pLimit, pPage := ps.Value(index), ps.Value(index+1), ps.Value(index+2), ps.Value(index+3)
|
||||
if pPage != nil {
|
||||
p, err := pPage.GetInt()
|
||||
if err != nil {
|
||||
return 0, 0, 0, err
|
||||
return 0, 0, 0, 0, err
|
||||
}
|
||||
start = uint32(val)
|
||||
}
|
||||
if p2 != nil {
|
||||
val, err := p2.GetInt()
|
||||
if err != nil {
|
||||
return 0, 0, 0, err
|
||||
if p < 0 {
|
||||
return 0, 0, 0, 0, errors.New("can't use negative page")
|
||||
}
|
||||
end = uint32(val)
|
||||
page = p
|
||||
}
|
||||
if p3 != nil {
|
||||
l, err := p3.GetInt()
|
||||
if pLimit != nil {
|
||||
l, err := pLimit.GetInt()
|
||||
if err != nil {
|
||||
return 0, 0, 0, err
|
||||
return 0, 0, 0, 0, err
|
||||
}
|
||||
if l <= 0 {
|
||||
return 0, 0, 0, errors.New("can't use negative or zero limit")
|
||||
return 0, 0, 0, 0, errors.New("can't use negative or zero limit")
|
||||
}
|
||||
if l > maxTransfersLimit {
|
||||
return 0, 0, 0, 0, errors.New("too big limit requested")
|
||||
}
|
||||
limit = l
|
||||
}
|
||||
return start, end, limit, nil
|
||||
if pEnd != nil {
|
||||
val, err := pEnd.GetInt()
|
||||
if err != nil {
|
||||
return 0, 0, 0, 0, err
|
||||
}
|
||||
end = uint32(val)
|
||||
} else {
|
||||
end = uint32(time.Now().Unix())
|
||||
}
|
||||
if pStart != nil {
|
||||
val, err := pStart.GetInt()
|
||||
if err != nil {
|
||||
return 0, 0, 0, 0, err
|
||||
}
|
||||
start = uint32(val)
|
||||
} else {
|
||||
start = uint32(time.Now().Add(-time.Hour * 24 * 7).Unix())
|
||||
}
|
||||
return start, end, limit, page, nil
|
||||
}
|
||||
|
||||
func getAssetMaps(name string) (map[util.Uint256]*result.AssetUTXO, map[util.Uint256]*result.AssetUTXO, error) {
|
||||
|
@ -528,38 +552,33 @@ func (s *Server) getUTXOTransfers(ps request.Params) (interface{}, *response.Err
|
|||
index++
|
||||
}
|
||||
|
||||
p1, p2, p3 := ps.Value(index), ps.Value(index+1), ps.Value(index+2)
|
||||
start, end, limit, err := getTimestampsAndLimit(p1, p2, p3)
|
||||
start, end, limit, page, err := getTimestampsAndLimit(ps, index)
|
||||
if err != nil {
|
||||
return nil, response.NewInvalidParamsError("", err)
|
||||
}
|
||||
if p2 == nil {
|
||||
end = uint32(time.Now().Unix())
|
||||
if p1 == nil {
|
||||
start = uint32(time.Now().Add(-time.Hour * 24 * 7).Unix())
|
||||
}
|
||||
}
|
||||
|
||||
sent, recv, err := getAssetMaps(assetName)
|
||||
if err != nil {
|
||||
return nil, response.NewInvalidParamsError("", err)
|
||||
}
|
||||
tr := new(state.Transfer)
|
||||
var resCount, frameCount int
|
||||
err = s.chain.ForEachTransfer(addr, tr, func() (bool, error) {
|
||||
// Iterating from newest to oldest, not yet reached required
|
||||
// time frame, continue looping.
|
||||
if tr.Timestamp > end {
|
||||
return true, nil
|
||||
}
|
||||
var count int
|
||||
for _, res := range sent {
|
||||
count += len(res.Transactions)
|
||||
}
|
||||
for _, res := range recv {
|
||||
count += len(res.Transactions)
|
||||
}
|
||||
if tr.Timestamp < start ||
|
||||
(limit != 0 && count >= limit) {
|
||||
// Iterating from newest to oldest, moved past required
|
||||
// time frame, stop looping.
|
||||
if tr.Timestamp < start {
|
||||
return false, nil
|
||||
}
|
||||
frameCount++
|
||||
// Using limits, not yet reached required page.
|
||||
if limit != 0 && page*limit >= frameCount {
|
||||
return true, nil
|
||||
}
|
||||
assetID := core.GoverningTokenID()
|
||||
if !tr.IsGoverning {
|
||||
assetID = core.UtilityTokenID()
|
||||
|
@ -578,6 +597,11 @@ func (s *Server) getUTXOTransfers(ps request.Params) (interface{}, *response.Err
|
|||
})
|
||||
a.TotalAmount += tr.Amount
|
||||
}
|
||||
resCount++
|
||||
// Using limits, reached limit.
|
||||
if limit != 0 && resCount >= limit {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -743,17 +767,10 @@ func (s *Server) getNEP5Transfers(ps request.Params) (interface{}, *response.Err
|
|||
return nil, response.ErrInvalidParams
|
||||
}
|
||||
|
||||
p1, p2, p3 := ps.Value(1), ps.Value(2), ps.Value(3)
|
||||
start, end, limit, err := getTimestampsAndLimit(p1, p2, p3)
|
||||
start, end, limit, page, err := getTimestampsAndLimit(ps, 1)
|
||||
if err != nil {
|
||||
return nil, response.NewInvalidParamsError("", err)
|
||||
}
|
||||
if p2 == nil {
|
||||
end = uint32(time.Now().Unix())
|
||||
if p1 == nil {
|
||||
start = uint32(time.Now().Add(-time.Hour * 24 * 7).Unix())
|
||||
}
|
||||
}
|
||||
|
||||
bs := &result.NEP5Transfers{
|
||||
Address: address.Uint160ToString(u),
|
||||
|
@ -761,14 +778,23 @@ func (s *Server) getNEP5Transfers(ps request.Params) (interface{}, *response.Err
|
|||
Sent: []result.NEP5Transfer{},
|
||||
}
|
||||
tr := new(state.NEP5Transfer)
|
||||
var resCount, frameCount int
|
||||
err = s.chain.ForEachNEP5Transfer(u, tr, func() (bool, error) {
|
||||
// Iterating from newest to oldest, not yet reached required
|
||||
// time frame, continue looping.
|
||||
if tr.Timestamp > end {
|
||||
return true, nil
|
||||
}
|
||||
if tr.Timestamp < start ||
|
||||
(limit != 0 && (len(bs.Received)+len(bs.Sent) >= limit)) {
|
||||
// Iterating from newest to oldest, moved past required
|
||||
// time frame, stop looping.
|
||||
if tr.Timestamp < start {
|
||||
return false, nil
|
||||
}
|
||||
frameCount++
|
||||
// Using limits, not yet reached required page.
|
||||
if limit != 0 && page*limit >= frameCount {
|
||||
return true, nil
|
||||
}
|
||||
transfer := result.NEP5Transfer{
|
||||
Timestamp: tr.Timestamp,
|
||||
Asset: tr.Asset,
|
||||
|
@ -783,14 +809,18 @@ func (s *Server) getNEP5Transfers(ps request.Params) (interface{}, *response.Err
|
|||
transfer.Address = address.Uint160ToString(tr.From)
|
||||
}
|
||||
bs.Received = append(bs.Received, transfer)
|
||||
return true, nil
|
||||
} else {
|
||||
transfer.Amount = strconv.FormatInt(-tr.Amount, 10)
|
||||
if !tr.To.Equals(util.Uint160{}) {
|
||||
transfer.Address = address.Uint160ToString(tr.To)
|
||||
}
|
||||
bs.Sent = append(bs.Sent, transfer)
|
||||
}
|
||||
|
||||
transfer.Amount = strconv.FormatInt(-tr.Amount, 10)
|
||||
if !tr.To.Equals(util.Uint160{}) {
|
||||
transfer.Address = address.Uint160ToString(tr.To)
|
||||
resCount++
|
||||
// Using limits, reached limit.
|
||||
if limit != 0 && resCount >= limit {
|
||||
return false, nil
|
||||
}
|
||||
bs.Sent = append(bs.Sent, transfer)
|
||||
return true, nil
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -799,6 +829,194 @@ func (s *Server) getNEP5Transfers(ps request.Params) (interface{}, *response.Err
|
|||
return bs, nil
|
||||
}
|
||||
|
||||
func (s *Server) getAllTransferTx(ps request.Params) (interface{}, *response.Error) {
|
||||
var respErr *response.Error
|
||||
|
||||
u, err := ps.Value(0).GetUint160FromAddressOrHex()
|
||||
if err != nil {
|
||||
return nil, response.ErrInvalidParams
|
||||
}
|
||||
|
||||
start, end, limit, page, err := getTimestampsAndLimit(ps, 1)
|
||||
if err != nil {
|
||||
return nil, response.NewInvalidParamsError("", err)
|
||||
}
|
||||
|
||||
var (
|
||||
utxoCont = make(chan bool)
|
||||
nep5Cont = make(chan bool)
|
||||
utxoTrs = make(chan state.Transfer)
|
||||
nep5Trs = make(chan state.NEP5Transfer)
|
||||
)
|
||||
|
||||
go func() {
|
||||
tr := new(state.Transfer)
|
||||
_ = s.chain.ForEachTransfer(u, tr, func() (bool, error) {
|
||||
var cont bool
|
||||
|
||||
// Iterating from newest to oldest, not yet reached required
|
||||
// time frame, continue looping.
|
||||
if tr.Timestamp > end {
|
||||
return true, nil
|
||||
}
|
||||
// Iterating from newest to oldest, moved past required
|
||||
// time frame, stop looping.
|
||||
if tr.Timestamp < start {
|
||||
return false, nil
|
||||
}
|
||||
utxoTrs <- *tr
|
||||
cont = <-utxoCont
|
||||
return cont, nil
|
||||
})
|
||||
close(utxoTrs)
|
||||
}()
|
||||
|
||||
go func() {
|
||||
tr := new(state.NEP5Transfer)
|
||||
_ = s.chain.ForEachNEP5Transfer(u, tr, func() (bool, error) {
|
||||
var cont bool
|
||||
|
||||
// Iterating from newest to oldest, not yet reached required
|
||||
// time frame, continue looping.
|
||||
if tr.Timestamp > end {
|
||||
return true, nil
|
||||
}
|
||||
// Iterating from newest to oldest, moved past required
|
||||
// time frame, stop looping.
|
||||
if tr.Timestamp < start {
|
||||
return false, nil
|
||||
}
|
||||
nep5Trs <- *tr
|
||||
cont = <-nep5Cont
|
||||
return cont, nil
|
||||
})
|
||||
close(nep5Trs)
|
||||
}()
|
||||
|
||||
var (
|
||||
res = make([]result.TransferTx, 0, limit)
|
||||
frameCount int
|
||||
utxoLast state.Transfer
|
||||
nep5Last state.NEP5Transfer
|
||||
haveUtxo, haveNep5 bool
|
||||
)
|
||||
|
||||
utxoLast, haveUtxo = <-utxoTrs
|
||||
if haveUtxo {
|
||||
utxoCont <- true
|
||||
}
|
||||
nep5Last, haveNep5 = <-nep5Trs
|
||||
if haveNep5 {
|
||||
nep5Cont <- true
|
||||
}
|
||||
for len(res) < limit {
|
||||
if !haveUtxo && !haveNep5 {
|
||||
break
|
||||
}
|
||||
var isNep5 = haveNep5 && (!haveUtxo || (nep5Last.Timestamp > utxoLast.Timestamp))
|
||||
var transfer result.TransferTx
|
||||
if isNep5 {
|
||||
transfer.TxID = nep5Last.Tx
|
||||
transfer.Timestamp = nep5Last.Timestamp
|
||||
transfer.Index = nep5Last.Block
|
||||
} else {
|
||||
transfer.TxID = utxoLast.Tx
|
||||
transfer.Timestamp = utxoLast.Timestamp
|
||||
transfer.Index = utxoLast.Block
|
||||
}
|
||||
frameCount++
|
||||
// Using limits, not yet reached required page. But still need
|
||||
// to drain inputs for this tx.
|
||||
skipTx := page*limit >= frameCount
|
||||
|
||||
if !skipTx {
|
||||
tx, _, err := s.chain.GetTransaction(transfer.TxID)
|
||||
if err != nil {
|
||||
respErr = response.NewInternalServerError("invalid NEP5 transfer log", err)
|
||||
break
|
||||
}
|
||||
transfer.NetworkFee = int64(s.chain.NetworkFee(tx))
|
||||
transfer.SystemFee = int64(s.chain.SystemFee(tx))
|
||||
|
||||
inouts, err := s.chain.References(tx)
|
||||
if err != nil {
|
||||
respErr = response.NewInternalServerError("invalid tx", err)
|
||||
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.
|
||||
for haveNep5 && nep5Last.Tx.Equals(transfer.TxID) {
|
||||
if !skipTx {
|
||||
var event result.TransferTxEvent
|
||||
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
|
||||
if haveNep5 {
|
||||
nep5Cont <- true
|
||||
}
|
||||
}
|
||||
|
||||
// Skip UTXO events, we've already got them from inputs and outputs.
|
||||
for haveUtxo && utxoLast.Tx.Equals(transfer.TxID) {
|
||||
utxoLast, haveUtxo = <-utxoTrs
|
||||
if haveUtxo {
|
||||
utxoCont <- true
|
||||
}
|
||||
}
|
||||
if !skipTx {
|
||||
res = append(res, transfer)
|
||||
}
|
||||
}
|
||||
if haveUtxo {
|
||||
_, ok := <-utxoTrs
|
||||
if ok {
|
||||
utxoCont <- false
|
||||
}
|
||||
}
|
||||
if haveNep5 {
|
||||
_, ok := <-nep5Trs
|
||||
if ok {
|
||||
nep5Cont <- false
|
||||
}
|
||||
}
|
||||
if respErr != nil {
|
||||
return nil, respErr
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *Server) getMinimumNetworkFee(ps request.Params) (interface{}, *response.Error) {
|
||||
return s.chain.GetConfig().MinimumNetworkFee, nil
|
||||
}
|
||||
|
|
|
@ -1164,24 +1164,28 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
|
|||
})
|
||||
|
||||
t.Run("getutxotransfers", func(t *testing.T) {
|
||||
testGetUTXO := func(t *testing.T, asset string, start, stop int) {
|
||||
testGetUTXO := func(t *testing.T, asset string, start, stop, limit, page int, present []int) {
|
||||
ps := []string{`"AKkkumHbBipZ46UMZJoFynJMXzSRnBvKcs"`}
|
||||
if asset != "" {
|
||||
ps = append(ps, fmt.Sprintf("%q", asset))
|
||||
}
|
||||
if start >= 0 {
|
||||
if start > int(e.chain.HeaderHeight()) {
|
||||
ps = append(ps, strconv.Itoa(int(time.Now().Unix())))
|
||||
} else {
|
||||
b, err := e.chain.GetHeader(e.chain.GetHeaderHash(start))
|
||||
require.NoError(t, err)
|
||||
ps = append(ps, strconv.Itoa(int(b.Timestamp)))
|
||||
}
|
||||
if stop != 0 {
|
||||
b, err := e.chain.GetHeader(e.chain.GetHeaderHash(stop))
|
||||
require.NoError(t, err)
|
||||
ps = append(ps, strconv.Itoa(int(b.Timestamp)))
|
||||
}
|
||||
if start > int(e.chain.HeaderHeight()) {
|
||||
ps = append(ps, strconv.Itoa(int(time.Now().Unix())))
|
||||
} else {
|
||||
b, err := e.chain.GetHeader(e.chain.GetHeaderHash(start))
|
||||
require.NoError(t, err)
|
||||
ps = append(ps, strconv.Itoa(int(b.Timestamp)))
|
||||
}
|
||||
if stop != 0 {
|
||||
b, err := e.chain.GetHeader(e.chain.GetHeaderHash(stop))
|
||||
require.NoError(t, err)
|
||||
ps = append(ps, strconv.Itoa(int(b.Timestamp)))
|
||||
}
|
||||
if limit != 0 {
|
||||
ps = append(ps, strconv.Itoa(limit))
|
||||
}
|
||||
if page != 0 {
|
||||
ps = append(ps, strconv.Itoa(page))
|
||||
}
|
||||
p := strings.Join(ps, ", ")
|
||||
rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "getutxotransfers", "params": [%s]}`, p)
|
||||
|
@ -1189,11 +1193,17 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
|
|||
res := checkErrGetResult(t, body, false)
|
||||
actual := new(result.GetUTXO)
|
||||
require.NoError(t, json.Unmarshal(res, actual))
|
||||
checkTransfers(t, e, actual, asset, start, stop)
|
||||
checkTransfers(t, e, actual, present)
|
||||
}
|
||||
t.Run("RestrictByAsset", func(t *testing.T) { testGetUTXO(t, "neo", 0, 0) })
|
||||
t.Run("TooBigStart", func(t *testing.T) { testGetUTXO(t, "", 300, 0) })
|
||||
t.Run("RestrictAll", func(t *testing.T) { testGetUTXO(t, "", 202, 203) })
|
||||
// See `checkTransfers` for the last parameter values.
|
||||
t.Run("All", func(t *testing.T) { testGetUTXO(t, "", 0, 207, 0, 0, []int{0, 1, 2, 3, 4, 5, 6, 7}) })
|
||||
t.Run("RestrictByAsset", func(t *testing.T) { testGetUTXO(t, "neo", 0, 0, 0, 0, []int{0, 1, 2, 6, 7}) })
|
||||
t.Run("TooBigStart", func(t *testing.T) { testGetUTXO(t, "", 300, 0, 0, 0, []int{}) })
|
||||
t.Run("RestrictAll", func(t *testing.T) { testGetUTXO(t, "", 202, 203, 0, 0, []int{1, 2, 3}) })
|
||||
t.Run("Limit", func(t *testing.T) { testGetUTXO(t, "neo", 0, 207, 2, 0, []int{7, 6}) })
|
||||
t.Run("Limit 2", func(t *testing.T) { testGetUTXO(t, "", 0, 204, 1, 0, []int{5}) })
|
||||
t.Run("Limit with page", func(t *testing.T) { testGetUTXO(t, "", 0, 204, 1, 1, []int{4}) })
|
||||
t.Run("Limit with page 2", func(t *testing.T) { testGetUTXO(t, "", 0, 204, 2, 2, []int{1, 0}) })
|
||||
})
|
||||
|
||||
t.Run("getnep5transfers", func(t *testing.T) {
|
||||
|
@ -1213,6 +1223,108 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
|
|||
require.NoError(t, json.Unmarshal(res, actual))
|
||||
checkNep5TransfersAux(t, e, actual, true)
|
||||
})
|
||||
|
||||
t.Run("getalltransfertx", func(t *testing.T) {
|
||||
testGetTxs := func(t *testing.T, asset string, start, stop, limit, page int, present []util.Uint256) {
|
||||
ps := []string{`"AKkkumHbBipZ46UMZJoFynJMXzSRnBvKcs"`}
|
||||
ps = append(ps, strconv.Itoa(start))
|
||||
ps = append(ps, strconv.Itoa(stop))
|
||||
if limit != 0 {
|
||||
ps = append(ps, strconv.Itoa(limit))
|
||||
}
|
||||
if page != 0 {
|
||||
ps = append(ps, strconv.Itoa(page))
|
||||
}
|
||||
p := strings.Join(ps, ", ")
|
||||
rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "getalltransfertx", "params": [%s]}`, p)
|
||||
body := doRPCCall(rpc, httpSrv.URL, t)
|
||||
res := checkErrGetResult(t, body, false)
|
||||
actualp := new([]result.TransferTx)
|
||||
require.NoError(t, json.Unmarshal(res, actualp))
|
||||
actual := *actualp
|
||||
require.Equal(t, len(present), len(actual))
|
||||
for _, id := range present {
|
||||
var isThere bool
|
||||
var ttx result.TransferTx
|
||||
for i := range actual {
|
||||
if id.Equals(actual[i].TxID) {
|
||||
ttx = actual[i]
|
||||
isThere = true
|
||||
break
|
||||
}
|
||||
}
|
||||
require.True(t, isThere)
|
||||
tx, h, err := e.chain.GetTransaction(id)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, h, ttx.Index)
|
||||
require.Equal(t, int64(e.chain.SystemFee(tx)), ttx.SystemFee)
|
||||
require.Equal(t, int64(e.chain.NetworkFee(tx)), ttx.NetworkFee)
|
||||
require.Equal(t, len(tx.Inputs)+len(tx.Outputs), len(ttx.Elements))
|
||||
}
|
||||
}
|
||||
b, err := e.chain.GetBlock(e.chain.GetHeaderHash(1))
|
||||
require.NoError(t, err)
|
||||
txMoveNeo := b.Transactions[1].Hash()
|
||||
b, err = e.chain.GetBlock(e.chain.GetHeaderHash(202))
|
||||
require.NoError(t, err)
|
||||
txNeoRT := b.Transactions[1].Hash()
|
||||
b, err = e.chain.GetBlock(e.chain.GetHeaderHash(203))
|
||||
require.NoError(t, err)
|
||||
txGasClaim := b.Transactions[1].Hash()
|
||||
b, err = e.chain.GetBlock(e.chain.GetHeaderHash(204))
|
||||
require.NoError(t, err)
|
||||
txDeploy := b.Transactions[1].Hash()
|
||||
ts204 := int(b.Timestamp)
|
||||
b, err = e.chain.GetBlock(e.chain.GetHeaderHash(206))
|
||||
require.NoError(t, err)
|
||||
txNeoTo1 := b.Transactions[1].Hash()
|
||||
b, err = e.chain.GetBlock(e.chain.GetHeaderHash(207))
|
||||
require.NoError(t, err)
|
||||
txNep5Tr := b.Transactions[2].Hash()
|
||||
b, err = e.chain.GetBlock(e.chain.GetHeaderHash(208))
|
||||
require.NoError(t, err)
|
||||
txNep5To1 := b.Transactions[1].Hash()
|
||||
ts208 := int(b.Timestamp)
|
||||
b, err = e.chain.GetBlock(e.chain.GetHeaderHash(209))
|
||||
require.NoError(t, err)
|
||||
txMigrate := b.Transactions[1].Hash()
|
||||
b, err = e.chain.GetBlock(e.chain.GetHeaderHash(210))
|
||||
require.NoError(t, err)
|
||||
txNep5To0 := b.Transactions[1].Hash()
|
||||
lastTs := int(b.Timestamp)
|
||||
t.Run("All", func(t *testing.T) {
|
||||
testGetTxs(t, "", 0, lastTs, 0, 0, []util.Uint256{
|
||||
txMoveNeo, txNeoRT, txGasClaim, txDeploy, txNeoTo1, txNep5Tr,
|
||||
txNep5To1, txMigrate, txNep5To0,
|
||||
})
|
||||
})
|
||||
t.Run("last 3", func(t *testing.T) {
|
||||
testGetTxs(t, "", 0, lastTs, 3, 0, []util.Uint256{
|
||||
txNep5To1, txMigrate, txNep5To0,
|
||||
})
|
||||
})
|
||||
t.Run("3, page 1", func(t *testing.T) {
|
||||
testGetTxs(t, "", 0, lastTs, 3, 1, []util.Uint256{
|
||||
txDeploy, txNeoTo1, txNep5Tr,
|
||||
})
|
||||
})
|
||||
t.Run("3, page 2", func(t *testing.T) {
|
||||
testGetTxs(t, "", 0, lastTs, 3, 2, []util.Uint256{
|
||||
txMoveNeo, txNeoRT, txGasClaim,
|
||||
})
|
||||
})
|
||||
t.Run("3, page 3", func(t *testing.T) {
|
||||
testGetTxs(t, "", 0, lastTs, 3, 3, []util.Uint256{})
|
||||
})
|
||||
t.Run("no dates", func(t *testing.T) {
|
||||
testGetTxs(t, "", 0, 1000000, 0, 0, []util.Uint256{})
|
||||
})
|
||||
t.Run("204-208", func(t *testing.T) {
|
||||
testGetTxs(t, "", ts204, ts208, 0, 0, []util.Uint256{
|
||||
txDeploy, txNeoTo1, txNep5Tr, txNep5To1,
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (tc rpcTestCase) getResultPair(e *executor) (expected interface{}, res interface{}) {
|
||||
|
@ -1312,43 +1424,43 @@ func checkNep5TransfersAux(t *testing.T, e *executor, acc interface{}, onlyFirst
|
|||
require.Equal(t, uint32(0), res.Sent[0].NotifyIndex)
|
||||
}
|
||||
|
||||
func checkTransfers(t *testing.T, e *executor, acc interface{}, asset string, start, stop int) {
|
||||
func checkTransfers(t *testing.T, e *executor, acc interface{}, checked []int) {
|
||||
type transfer struct {
|
||||
sent bool
|
||||
asset string
|
||||
index uint32
|
||||
amount int64
|
||||
}
|
||||
|
||||
var transfers = []transfer{
|
||||
{false, "neo", 1, 99999000}, // NEO to us.
|
||||
{false, "neo", 202, 99999000}, // NEO roundtrip for GAS claim.
|
||||
{true, "neo", 202, 99999000}, // NEO roundtrip for GAS claim.
|
||||
{false, "gas", 203, 160798392000}, // GAS claim.
|
||||
{false, "gas", 204, 150798392000}, // Remainder from contract deployment.
|
||||
{true, "gas", 204, 160798392000}, // Contract deployment.
|
||||
{false, "neo", 206, 99998000}, // Remainder of NEO sent.
|
||||
{true, "neo", 206, 99999000}, // NEO to another validator.
|
||||
}
|
||||
res := acc.(*result.GetUTXO)
|
||||
require.Equal(t, res.Address, "AKkkumHbBipZ46UMZJoFynJMXzSRnBvKcs")
|
||||
|
||||
// transfer from multisig address to us
|
||||
u := getUTXOForBlock(res, false, "neo", 1)
|
||||
if start <= 1 && (stop == 0 || stop >= 1) && (asset == "neo" || asset == "") {
|
||||
require.NotNil(t, u)
|
||||
require.EqualValues(t, int64(99999000), u.Amount)
|
||||
} else {
|
||||
require.Nil(t, u)
|
||||
}
|
||||
for i, tr := range transfers {
|
||||
var present bool
|
||||
|
||||
// gas claim
|
||||
u = getUTXOForBlock(res, false, "gas", 203)
|
||||
if start <= 203 && (stop == 0 || stop >= 203) && (asset == "gas" || asset == "") {
|
||||
require.NotNil(t, u)
|
||||
require.EqualValues(t, int64(160798392000), u.Amount)
|
||||
} else {
|
||||
require.Nil(t, u)
|
||||
}
|
||||
|
||||
// transfer from us to another validator
|
||||
u = getUTXOForBlock(res, true, "neo", 206)
|
||||
if start <= 206 && (stop == 0 || stop >= 206) && (asset == "neo" || asset == "") {
|
||||
require.NotNil(t, u)
|
||||
require.EqualValues(t, int64(99999000), u.Amount)
|
||||
} else {
|
||||
require.Nil(t, u)
|
||||
}
|
||||
|
||||
u = getUTXOForBlock(res, false, "neo", 206)
|
||||
if start <= 206 && (stop == 0 || stop >= 206) && (asset == "neo" || asset == "") {
|
||||
require.NotNil(t, u)
|
||||
require.EqualValues(t, int64(99998000), u.Amount)
|
||||
} else {
|
||||
require.Nil(t, u)
|
||||
u := getUTXOForBlock(res, tr.sent, tr.asset, tr.index)
|
||||
for j := range checked {
|
||||
if checked[j] == i {
|
||||
present = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if present {
|
||||
require.NotNil(t, u)
|
||||
require.EqualValues(t, tr.amount, u.Amount)
|
||||
} else {
|
||||
require.Nil(t, u)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue