Merge pull request #1399 from nspcc-dev/getalltransfertx-2.x

Getalltransfertx 2.x
This commit is contained in:
Roman Khimov 2020-09-15 19:01:24 +03:00 committed by GitHub
commit ec631984d0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 686 additions and 106 deletions

View file

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

View file

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

View file

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

View file

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

View file

@ -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"`
}

View file

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

View file

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