Merge pull request #1419 from nspcc-dev/port-from-2.x

Port from 2.x
This commit is contained in:
Roman Khimov 2020-09-22 21:56:29 +03:00 committed by GitHub
commit 1ff1cd797e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 407 additions and 158 deletions

View file

@ -101,6 +101,28 @@ 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
`getnep5transfers` RPC call never returns 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 NbTiM6h8r99kpRtb428XcsUk1TzKed2gTc
within 0-1600094189 timestamps:
```json
{ "jsonrpc": "2.0", "id": 5, "method": "getnep5transfers", "params":
["NbTiM6h8r99kpRtb428XcsUk1TzKed2gTc", 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":
["NbTiM6h8r99kpRtb428XcsUk1TzKed2gTc", 0, 1600094189, 10, 1] }
```
#### Websocket server
This server accepts websocket connections on `ws://$BASE_URL/ws` address. You

View file

@ -133,6 +133,19 @@ func NewService(cfg Config) (Service, error) {
return nil, err
}
// Check that wallet password is correct for at least one account.
var ok bool
for _, acc := range srv.wallet.Accounts {
err := acc.Decrypt(srv.Config.Wallet.Password)
if err == nil {
ok = true
break
}
}
if !ok {
return nil, errors.New("no account with provided password was found")
}
defer srv.wallet.Close()
srv.dbft = dbft.New(

View file

@ -784,20 +784,23 @@ func (bc *Blockchain) processNEP5Transfer(cache *dao.Cached, h util.Uint256, b *
}
// ForEachNEP5Transfer executes f for each nep5 transfer in log.
func (bc *Blockchain) ForEachNEP5Transfer(acc util.Uint160, f func(*state.NEP5Transfer) error) error {
func (bc *Blockchain) ForEachNEP5Transfer(acc util.Uint160, f func(*state.NEP5Transfer) (bool, error)) error {
balances, err := bc.dao.GetNEP5Balances(acc)
if err != nil {
return nil
}
for i := uint32(0); i <= balances.NextTransferBatch; i++ {
lg, err := bc.dao.GetNEP5TransferLog(acc, i)
for i := int(balances.NextTransferBatch); i >= 0; i-- {
lg, err := bc.dao.GetNEP5TransferLog(acc, uint32(i))
if err != nil {
return nil
}
err = lg.ForEach(f)
cont, err := lg.ForEach(f)
if err != nil {
return err
}
if !cont {
break
}
}
return nil
}

View file

@ -31,7 +31,7 @@ type Blockchainer interface {
GetContractScriptHash(id int32) (util.Uint160, error)
GetEnrollments() ([]state.Validator, error)
GetGoverningTokenBalance(acc util.Uint160) (*big.Int, uint32)
ForEachNEP5Transfer(util.Uint160, func(*state.NEP5Transfer) error) error
ForEachNEP5Transfer(util.Uint160, func(*state.NEP5Transfer) (bool, error)) error
GetHeaderHash(int) util.Uint256
GetHeader(hash util.Uint256) (*block.Header, error)
CurrentHeaderHash() util.Uint256

View file

@ -95,7 +95,7 @@ func (cd *Cached) AppendNEP5Transfer(acc util.Uint160, index uint32, tr *state.N
if err := lg.Append(tr); err != nil {
return false, err
}
return lg.Size() >= nep5TransferBatchSize, cd.PutNEP5TransferLog(acc, index, lg)
return lg.Size() >= state.NEP5TransferBatchSize, cd.PutNEP5TransferLog(acc, index, lg)
}
// Persist flushes all the changes made into the (supposedly) persistent

View file

@ -208,8 +208,6 @@ func (dao *Simple) putNEP5Balances(acc util.Uint160, bs *state.NEP5Balances, buf
// -- start transfer log.
const nep5TransferBatchSize = 128
func getNEP5TransferLogKey(acc util.Uint160, index uint32) []byte {
key := make([]byte, 1+util.Uint160Size+4)
key[0] = byte(storage.STNEP5Transfers)
@ -250,7 +248,7 @@ func (dao *Simple) AppendNEP5Transfer(acc util.Uint160, index uint32, tr *state.
if err := lg.Append(tr); err != nil {
return false, err
}
return lg.Size() >= nep5TransferBatchSize, dao.PutNEP5TransferLog(acc, index, lg)
return lg.Size() >= state.NEP5TransferBatchSize, dao.PutNEP5TransferLog(acc, index, lg)
}
// -- end transfer log.

View file

@ -36,7 +36,7 @@ func (h *HashNode) Hash() util.Uint256 {
return h.hash
}
// IsEmpty returns true iff h is an empty node i.e. contains no hash.
// IsEmpty returns true if h is an empty node i.e. contains no hash.
func (h *HashNode) IsEmpty() bool { return !h.hashValid }
// Bytes returns serialized HashNode.

View file

@ -8,6 +8,9 @@ import (
"github.com/nspcc-dev/neo-go/pkg/util"
)
// NEP5TransferBatchSize is the maximum number of entries for NEP5TransferLog.
const NEP5TransferBatchSize = 128
// NEP5Tracker contains info about a single account in a NEP5 contract.
type NEP5Tracker struct {
// Balance is the current balance of the account.
@ -20,8 +23,6 @@ type NEP5Tracker struct {
// NEP5TransferLog is a log of NEP5 token transfers for the specific command.
type NEP5TransferLog struct {
Raw []byte
// size is the number of NEP5Transfers written into Raw
size int
}
// NEP5Transfer represents a single NEP5 Transfer event.
@ -85,37 +86,52 @@ func (bs *NEP5Balances) EncodeBinary(w *io.BinWriter) {
// Append appends single transfer to a log.
func (lg *NEP5TransferLog) Append(tr *NEP5Transfer) error {
w := io.NewBufBinWriter()
// The first entry, set up counter.
if len(lg.Raw) == 0 {
w.WriteB(1)
}
tr.EncodeBinary(w.BinWriter)
if w.Err != nil {
return w.Err
}
if len(lg.Raw) != 0 {
lg.Raw[0]++
}
lg.Raw = append(lg.Raw, w.Bytes()...)
lg.size++
return nil
}
// ForEach iterates over transfer log returning on first error.
func (lg *NEP5TransferLog) ForEach(f func(*NEP5Transfer) error) error {
if lg == nil {
return nil
func (lg *NEP5TransferLog) ForEach(f func(*NEP5Transfer) (bool, error)) (bool, error) {
if lg == nil || len(lg.Raw) == 0 {
return true, nil
}
transfers := make([]NEP5Transfer, lg.Size())
r := io.NewBinReaderFromBuf(lg.Raw[1:])
for i := 0; i < lg.Size(); i++ {
transfers[i].DecodeBinary(r)
}
tr := new(NEP5Transfer)
var bytesRead int
for i := 0; i < len(lg.Raw); i += bytesRead {
r := io.NewBinReaderFromBuf(lg.Raw[i:])
bytesRead = tr.DecodeBinaryReturnCount(r)
if r.Err != nil {
return r.Err
} else if err := f(tr); err != nil {
return nil
return false, r.Err
}
for i := len(transfers) - 1; i >= 0; i-- {
cont, err := f(&transfers[i])
if err != nil {
return false, err
}
if !cont {
return false, nil
}
}
return nil
return true, nil
}
// Size returns an amount of transfer written in log.
func (lg *NEP5TransferLog) Size() int {
return lg.size
if len(lg.Raw) == 0 {
return 0
}
return int(lg.Raw[0])
}
// EncodeBinary implements io.Serializable interface.
@ -138,27 +154,18 @@ func (t *NEP5Transfer) EncodeBinary(w *io.BinWriter) {
w.WriteBytes(t.To[:])
w.WriteU32LE(t.Block)
w.WriteU64LE(t.Timestamp)
amountBytes := bigint.ToBytes(&t.Amount)
w.WriteU64LE(uint64(len(amountBytes)))
w.WriteBytes(amountBytes)
amount := bigint.ToBytes(&t.Amount)
w.WriteVarBytes(amount)
}
// DecodeBinary implements io.Serializable interface.
func (t *NEP5Transfer) DecodeBinary(r *io.BinReader) {
_ = t.DecodeBinaryReturnCount(r)
}
// DecodeBinaryReturnCount decodes NEP5Transfer and returns the number of bytes read.
func (t *NEP5Transfer) DecodeBinaryReturnCount(r *io.BinReader) int {
t.Asset = int32(r.ReadU32LE())
r.ReadBytes(t.Tx[:])
r.ReadBytes(t.From[:])
r.ReadBytes(t.To[:])
t.Block = r.ReadU32LE()
t.Timestamp = r.ReadU64LE()
amountLen := r.ReadU64LE()
amountBytes := make([]byte, amountLen)
r.ReadBytes(amountBytes)
t.Amount = *bigint.FromBytes(amountBytes)
return 4 + util.Uint160Size*2 + 8 + 4 + (8 + len(amountBytes)) + +util.Uint256Size
amount := r.ReadVarBytes(bigint.MaxBytesLen)
t.Amount = *bigint.FromBytes(amount)
}

View file

@ -8,7 +8,6 @@ import (
"github.com/nspcc-dev/neo-go/pkg/internal/random"
"github.com/nspcc-dev/neo-go/pkg/internal/testserdes"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/stretchr/testify/require"
)
@ -29,14 +28,14 @@ func TestNEP5TransferLog_Append(t *testing.T) {
require.Equal(t, len(expected), lg.Size())
i := 0
err := lg.ForEach(func(tr *NEP5Transfer) error {
i := len(expected) - 1
cont, err := lg.ForEach(func(tr *NEP5Transfer) (bool, error) {
require.Equal(t, expected[i], tr)
i++
return nil
i--
return true, nil
})
require.NoError(t, err)
require.True(t, cont)
}
func TestNEP5Tracker_EncodeBinary(t *testing.T) {
@ -62,18 +61,6 @@ func TestNEP5Transfer_DecodeBinary(t *testing.T) {
testserdes.EncodeDecodeBinary(t, expected, new(NEP5Transfer))
}
func TestNEP5TransferSize(t *testing.T) {
tr := randomTransfer(rand.New(rand.NewSource(0)))
size := io.GetVarSize(tr)
w := io.NewBufBinWriter()
tr.EncodeBinary(w.BinWriter)
require.NoError(t, w.Err)
r := io.NewBinReaderFromBuf(w.Bytes())
actualTr := &NEP5Transfer{}
actual := actualTr.DecodeBinaryReturnCount(r)
require.EqualValues(t, actual, size)
}
func randomTransfer(r *rand.Rand) *NEP5Transfer {
return &NEP5Transfer{
Amount: *big.NewInt(int64(r.Uint64())),

View file

@ -6,8 +6,12 @@ import (
"math/bits"
)
const (
// MaxBytesLen is the maximum length of serialized integer suitable for Neo VM.
MaxBytesLen = 33 // 32 bytes for 256-bit integer plus 1 if padding needed
// wordSizeBytes is a size of a big.Word (uint) in bytes.`
const wordSizeBytes = bits.UintSize / 8
wordSizeBytes = bits.UintSize / 8
)
// FromBytes converts data in little-endian format to
// an integer.

View file

@ -95,7 +95,7 @@ func (chain testChain) GetHeader(hash util.Uint256) (*block.Header, error) {
func (chain testChain) GetNextBlockValidators() ([]*keys.PublicKey, error) {
panic("TODO")
}
func (chain testChain) ForEachNEP5Transfer(util.Uint160, func(*state.NEP5Transfer) error) error {
func (chain testChain) ForEachNEP5Transfer(util.Uint160, func(*state.NEP5Transfer) (bool, error)) error {
panic("TODO")
}
func (chain testChain) GetNEP5Balances(util.Uint160) *state.NEP5Balances {

View file

@ -214,9 +214,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

View file

@ -420,7 +420,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,"assethash":"600c4f5200db36177e3e8a09e9f18e2fc7d12a0f","transferaddress":"AYwgBNMepiv5ocGcyNT4mA8zPLTQ8pDBis","amount":"1000000","blockindex":436036,"transfernotifyindex":0,"txhash":"df7683ece554ecfb85cf41492c5f143215dd43ef9ec61181a28f922da06aba58"}],"address":"AbHgdBaWEnHkCiLtDZXjhvhaAK2cwFh5pF"}}`,
result: func(c *Client) interface{} {
@ -948,7 +948,30 @@ 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)
},
},
{
name: "getnep5transfers_invalid_params_error 2",
invoke: func(c *Client) (interface{}, error) {
var stop uint32
return c.GetNEP5Transfers("NbTiM6h8r99kpRtb428XcsUk1TzKed2gTc", nil, &stop, nil, nil)
},
},
{
name: "getnep5transfers_invalid_params_error 3",
invoke: func(c *Client) (interface{}, error) {
var start uint32
var limit int
return c.GetNEP5Transfers("NbTiM6h8r99kpRtb428XcsUk1TzKed2gTc", &start, nil, &limit, nil)
},
},
{
name: "getnep5transfers_invalid_params_error 4",
invoke: func(c *Client) (interface{}, error) {
var start, stop uint32
var page int
return c.GetNEP5Transfers("NbTiM6h8r99kpRtb428XcsUk1TzKed2gTc", &start, &stop, nil, &page)
},
},
{
@ -1112,7 +1135,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

@ -77,6 +77,9 @@ 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){
@ -545,23 +548,54 @@ func (s *Server) getNEP5Balances(ps request.Params) (interface{}, *response.Erro
return bs, nil
}
func getTimestamps(p1, p2 *request.Param) (uint64, uint64, error) {
func getTimestampsAndLimit(ps request.Params, index int) (uint64, uint64, int, int, error) {
var start, end uint64
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, err
return 0, 0, 0, 0, err
}
start = uint64(val)
if p < 0 {
return 0, 0, 0, 0, errors.New("can't use negative page")
}
if p2 != nil {
val, err := p2.GetInt()
page = p
}
if pLimit != nil {
l, err := pLimit.GetInt()
if err != nil {
return 0, 0, err
return 0, 0, 0, 0, err
}
if l <= 0 {
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
}
if pEnd != nil {
val, err := pEnd.GetInt()
if err != nil {
return 0, 0, 0, 0, err
}
end = uint64(val)
} else {
end = uint64(time.Now().Unix() * 1000)
}
return start, end, nil
if pStart != nil {
val, err := pStart.GetInt()
if err != nil {
return 0, 0, 0, 0, err
}
start = uint64(val)
} else {
start = uint64(time.Now().Add(-time.Hour*24*7).Unix() * 1000)
}
return start, end, limit, page, nil
}
func (s *Server) getNEP5Transfers(ps request.Params) (interface{}, *response.Error) {
@ -570,17 +604,10 @@ func (s *Server) getNEP5Transfers(ps request.Params) (interface{}, *response.Err
return nil, response.ErrInvalidParams
}
p1, p2 := ps.Value(1), ps.Value(2)
start, end, err := getTimestamps(p1, p2)
start, end, limit, page, err := getTimestampsAndLimit(ps, 1)
if err != nil {
return nil, response.NewInvalidParamsError(err.Error(), err)
}
if p2 == nil {
end = uint64(time.Now().Unix() * 1000)
if p1 == nil {
start = uint64(time.Now().Add(-time.Hour*24*7).Unix() * 1000)
}
}
bs := &result.NEP5Transfers{
Address: address.Uint160ToString(u),
@ -588,14 +615,29 @@ func (s *Server) getNEP5Transfers(ps request.Params) (interface{}, *response.Err
Sent: []result.NEP5Transfer{},
}
cache := make(map[int32]decimals)
err = s.chain.ForEachNEP5Transfer(u, func(tr *state.NEP5Transfer) error {
if tr.Timestamp < start || tr.Timestamp > end {
return nil
var resCount, frameCount int
err = s.chain.ForEachNEP5Transfer(u, func(tr *state.NEP5Transfer) (bool, error) {
// 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
}
frameCount++
// Using limits, not yet reached required page.
if limit != 0 && page*limit >= frameCount {
return true, nil
}
d, err := s.getDecimals(tr.Asset, cache)
if err != nil {
return nil
return false, err
}
transfer := result.NEP5Transfer{
Timestamp: tr.Timestamp,
Asset: d.Hash,
@ -608,15 +650,20 @@ func (s *Server) getNEP5Transfers(ps request.Params) (interface{}, *response.Err
transfer.Address = address.Uint160ToString(tr.From)
}
bs.Received = append(bs.Received, transfer)
return nil
}
} else {
transfer.Amount = amountToString(new(big.Int).Neg(&tr.Amount), d.Value)
if !tr.To.Equals(util.Uint160{}) {
transfer.Address = address.Uint160ToString(tr.To)
}
bs.Sent = append(bs.Sent, transfer)
return nil
}
resCount++
// Using limits, reached limit.
if limit != 0 && resCount >= limit {
return false, nil
}
return true, nil
})
if err != nil {
return nil, response.NewInternalServerError("invalid NEP5 transfer log", err)

View file

@ -160,6 +160,36 @@ var rpcTestCases = map[string][]rpcTestCase{
params: `["` + testchain.PrivateKeyByID(0).Address() + `", "notanumber"]`,
fail: true,
},
{
name: "invalid stop timestamp",
params: `["` + testchain.PrivateKeyByID(0).Address() + `", "1", "blah"]`,
fail: true,
},
{
name: "invalid limit",
params: `["` + testchain.PrivateKeyByID(0).Address() + `", "1", "2", "0"]`,
fail: true,
},
{
name: "invalid limit 2",
params: `["` + testchain.PrivateKeyByID(0).Address() + `", "1", "2", "bleh"]`,
fail: true,
},
{
name: "invalid limit 3",
params: `["` + testchain.PrivateKeyByID(0).Address() + `", "1", "2", "100500"]`,
fail: true,
},
{
name: "invalid page",
params: `["` + testchain.PrivateKeyByID(0).Address() + `", "1", "2", "3", "-1"]`,
fail: true,
},
{
name: "invalid page 2",
params: `["` + testchain.PrivateKeyByID(0).Address() + `", "1", "2", "3", "jajaja"]`,
fail: true,
},
{
name: "positive",
params: `["` + testchain.PrivateKeyByID(0).Address() + `", 0]`,
@ -914,21 +944,48 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
})
t.Run("getnep5transfers", func(t *testing.T) {
testNEP5T := func(t *testing.T, start, stop, limit, page int, sent, rcvd []int) {
ps := []string{`"` + testchain.PrivateKeyByID(0).Address() + `"`}
h, err := e.chain.GetHeader(e.chain.GetHeaderHash(4))
require.NoError(t, err)
ps = append(ps, strconv.FormatUint(h.Timestamp, 10))
h, err = e.chain.GetHeader(e.chain.GetHeaderHash(5))
require.NoError(t, err)
ps = append(ps, strconv.FormatUint(h.Timestamp, 10))
if start != 0 {
h, err := e.chain.GetHeader(e.chain.GetHeaderHash(start))
var ts uint64
if err == nil {
ts = h.Timestamp
} else {
ts = uint64(time.Now().UnixNano() / 1_000_000)
}
ps = append(ps, strconv.FormatUint(ts, 10))
}
if stop != 0 {
h, err := e.chain.GetHeader(e.chain.GetHeaderHash(stop))
var ts uint64
if err == nil {
ts = h.Timestamp
} else {
ts = uint64(time.Now().UnixNano() / 1_000_000)
}
ps = append(ps, strconv.FormatUint(ts, 10))
}
if limit != 0 {
ps = append(ps, strconv.FormatInt(int64(limit), 10))
}
if page != 0 {
ps = append(ps, strconv.FormatInt(int64(page), 10))
}
p := strings.Join(ps, ", ")
rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "getnep5transfers", "params": [%s]}`, p)
body := doRPCCall(rpc, httpSrv.URL, t)
res := checkErrGetResult(t, body, false)
actual := new(result.NEP5Transfers)
require.NoError(t, json.Unmarshal(res, actual))
checkNep5TransfersAux(t, e, actual, 4, 5)
checkNep5TransfersAux(t, e, actual, sent, rcvd)
}
t.Run("time frame only", func(t *testing.T) { testNEP5T(t, 4, 5, 0, 0, []int{3, 4, 5, 6}, []int{0, 1}) })
t.Run("no res", func(t *testing.T) { testNEP5T(t, 100, 100, 0, 0, []int{}, []int{}) })
t.Run("limit", func(t *testing.T) { testNEP5T(t, 1, 7, 3, 0, []int{0, 1, 2}, []int{}) })
t.Run("limit 2", func(t *testing.T) { testNEP5T(t, 4, 5, 2, 0, []int{3}, []int{0}) })
t.Run("limit with page", func(t *testing.T) { testNEP5T(t, 1, 7, 3, 1, []int{3, 4}, []int{0}) })
t.Run("limit with page 2", func(t *testing.T) { testNEP5T(t, 1, 7, 3, 2, []int{5, 6}, []int{1}) })
})
}
@ -1028,41 +1085,66 @@ func checkNep5Balances(t *testing.T, e *executor, acc interface{}) {
}
func checkNep5Transfers(t *testing.T, e *executor, acc interface{}) {
checkNep5TransfersAux(t, e, acc, 0, e.chain.HeaderHeight())
checkNep5TransfersAux(t, e, acc, []int{0, 1, 2, 3, 4, 5, 6, 7, 8}, []int{0, 1, 2, 3})
}
func checkNep5TransfersAux(t *testing.T, e *executor, acc interface{}, start, end uint32) {
func checkNep5TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rcvd []int) {
res, ok := acc.(*result.NEP5Transfers)
require.True(t, ok)
rublesHash, err := util.Uint160DecodeStringLE(testContractHash)
require.NoError(t, err)
blockDeploy2, err := e.chain.GetBlock(e.chain.GetHeaderHash(7))
require.NoError(t, err)
require.Equal(t, 1, len(blockDeploy2.Transactions))
txDeploy2 := blockDeploy2.Transactions[0]
blockSendRubles, err := e.chain.GetBlock(e.chain.GetHeaderHash(6))
require.NoError(t, err)
require.Equal(t, 1, len(blockSendRubles.Transactions))
txSendRublesHash := blockSendRubles.Transactions[0].Hash()
txSendRubles := blockSendRubles.Transactions[0]
blockReceiveRubles, err := e.chain.GetBlock(e.chain.GetHeaderHash(5))
require.NoError(t, err)
require.Equal(t, 2, len(blockReceiveRubles.Transactions))
txReceiveRublesHash := blockReceiveRubles.Transactions[1].Hash()
blockReceiveGAS, err := e.chain.GetBlock(e.chain.GetHeaderHash(1))
require.NoError(t, err)
require.Equal(t, 2, len(blockReceiveGAS.Transactions))
txReceiveNEOHash := blockReceiveGAS.Transactions[0].Hash()
txReceiveGASHash := blockReceiveGAS.Transactions[1].Hash()
txInitCall := blockReceiveRubles.Transactions[0]
txReceiveRubles := blockReceiveRubles.Transactions[1]
blockSendNEO, err := e.chain.GetBlock(e.chain.GetHeaderHash(4))
require.NoError(t, err)
require.Equal(t, 1, len(blockSendNEO.Transactions))
txSendNEOHash := blockSendNEO.Transactions[0].Hash()
txSendNEO := blockSendNEO.Transactions[0]
blockCtrInv1, err := e.chain.GetBlock(e.chain.GetHeaderHash(3))
require.NoError(t, err)
require.Equal(t, 1, len(blockCtrInv1.Transactions))
txCtrInv1 := blockCtrInv1.Transactions[0]
blockCtrDeploy, err := e.chain.GetBlock(e.chain.GetHeaderHash(2))
require.NoError(t, err)
require.Equal(t, 1, len(blockCtrDeploy.Transactions))
txCtrDeploy := blockCtrDeploy.Transactions[0]
blockReceiveGAS, err := e.chain.GetBlock(e.chain.GetHeaderHash(1))
require.NoError(t, err)
require.Equal(t, 2, len(blockReceiveGAS.Transactions))
txReceiveNEO := blockReceiveGAS.Transactions[0]
txReceiveGAS := blockReceiveGAS.Transactions[1]
// These are laid out here explicitly for 2 purposes:
// * to be able to reference any particular event for paging
// * to check chain events consistency
// Technically these could be retrieved from application log, but that would almost
// duplicate the Server method.
expected := result.NEP5Transfers{
Sent: []result.NEP5Transfer{
{
Timestamp: blockSendNEO.Timestamp,
Asset: e.chain.GoverningTokenHash(),
Address: testchain.PrivateKeyByID(1).Address(),
Amount: "1000",
Index: 4,
NotifyIndex: 0,
TxHash: txSendNEOHash,
Timestamp: blockDeploy2.Timestamp,
Asset: e.chain.UtilityTokenHash(),
Address: "", // burn
Amount: amountToString(big.NewInt(txDeploy2.SystemFee+txDeploy2.NetworkFee), 8),
Index: 7,
TxHash: blockDeploy2.Hash(),
},
{
Timestamp: blockSendRubles.Timestamp,
@ -1071,7 +1153,64 @@ func checkNep5TransfersAux(t *testing.T, e *executor, acc interface{}, start, en
Amount: "1.23",
Index: 6,
NotifyIndex: 0,
TxHash: txSendRublesHash,
TxHash: txSendRubles.Hash(),
},
{
Timestamp: blockSendRubles.Timestamp,
Asset: e.chain.UtilityTokenHash(),
Address: "", // burn
Amount: amountToString(big.NewInt(txSendRubles.SystemFee+txSendRubles.NetworkFee), 8),
Index: 6,
TxHash: blockSendRubles.Hash(),
},
{
Timestamp: blockReceiveRubles.Timestamp,
Asset: e.chain.UtilityTokenHash(),
Address: "", // burn
Amount: amountToString(big.NewInt(txReceiveRubles.SystemFee+txReceiveRubles.NetworkFee), 8),
Index: 5,
TxHash: blockReceiveRubles.Hash(),
},
{
Timestamp: blockReceiveRubles.Timestamp,
Asset: e.chain.UtilityTokenHash(),
Address: "", // burn
Amount: amountToString(big.NewInt(txInitCall.SystemFee+txInitCall.NetworkFee), 8),
Index: 5,
TxHash: blockReceiveRubles.Hash(),
},
{
Timestamp: blockSendNEO.Timestamp,
Asset: e.chain.GoverningTokenHash(),
Address: testchain.PrivateKeyByID(1).Address(),
Amount: "1000",
Index: 4,
NotifyIndex: 0,
TxHash: txSendNEO.Hash(),
},
{
Timestamp: blockSendNEO.Timestamp,
Asset: e.chain.UtilityTokenHash(),
Address: "", // burn
Amount: amountToString(big.NewInt(txSendNEO.SystemFee+txSendNEO.NetworkFee), 8),
Index: 4,
TxHash: blockSendNEO.Hash(),
},
{
Timestamp: blockCtrInv1.Timestamp,
Asset: e.chain.UtilityTokenHash(),
Address: "", // burn has empty receiver
Amount: amountToString(big.NewInt(txCtrInv1.SystemFee+txCtrInv1.NetworkFee), 8),
Index: 3,
TxHash: blockCtrInv1.Hash(),
},
{
Timestamp: blockCtrDeploy.Timestamp,
Asset: e.chain.UtilityTokenHash(),
Address: "", // burn has empty receiver
Amount: amountToString(big.NewInt(txCtrDeploy.SystemFee+txCtrDeploy.NetworkFee), 8),
Index: 2,
TxHash: blockCtrDeploy.Hash(),
},
},
Received: []result.NEP5Transfer{
@ -1082,7 +1221,7 @@ func checkNep5TransfersAux(t *testing.T, e *executor, acc interface{}, start, en
Amount: "10",
Index: 5,
NotifyIndex: 0,
TxHash: txReceiveRublesHash,
TxHash: txReceiveRubles.Hash(),
},
{
Timestamp: blockSendNEO.Timestamp,
@ -1091,7 +1230,7 @@ func checkNep5TransfersAux(t *testing.T, e *executor, acc interface{}, start, en
Amount: "17.99982000",
Index: 4,
NotifyIndex: 0,
TxHash: txSendNEOHash,
TxHash: txSendNEO.Hash(),
},
{
Timestamp: blockReceiveGAS.Timestamp,
@ -1100,7 +1239,7 @@ func checkNep5TransfersAux(t *testing.T, e *executor, acc interface{}, start, en
Amount: "1000",
Index: 1,
NotifyIndex: 0,
TxHash: txReceiveGASHash,
TxHash: txReceiveGAS.Hash(),
},
{
Timestamp: blockReceiveGAS.Timestamp,
@ -1109,49 +1248,33 @@ func checkNep5TransfersAux(t *testing.T, e *executor, acc interface{}, start, en
Amount: "99999000",
Index: 1,
NotifyIndex: 0,
TxHash: txReceiveNEOHash,
TxHash: txReceiveNEO.Hash(),
},
},
Address: testchain.PrivateKeyByID(0).Address(),
}
// take burned gas into account
u := testchain.PrivateKeyByID(0).GetScriptHash()
for i := 0; i <= int(e.chain.BlockHeight()); i++ {
var netFee int64
h := e.chain.GetHeaderHash(i)
b, err := e.chain.GetBlock(h)
require.NoError(t, err)
for j := range b.Transactions {
if u.Equals(b.Transactions[j].Sender()) {
amount := b.Transactions[j].SystemFee + b.Transactions[j].NetworkFee
expected.Sent = append(expected.Sent, result.NEP5Transfer{
Timestamp: b.Timestamp,
Asset: e.chain.UtilityTokenHash(),
Address: "", // burn has empty receiver
Amount: amountToString(big.NewInt(amount), 8),
Index: b.Index,
TxHash: b.Hash(),
})
}
netFee += b.Transactions[j].NetworkFee
}
}
require.Equal(t, expected.Address, res.Address)
arr := make([]result.NEP5Transfer, 0, len(expected.Sent))
for i := range expected.Sent {
if expected.Sent[i].Index >= start && expected.Sent[i].Index <= end {
for _, j := range sent {
if i == j {
arr = append(arr, expected.Sent[i])
break
}
}
require.ElementsMatch(t, arr, res.Sent)
}
require.Equal(t, arr, res.Sent)
arr = arr[:0]
for i := range expected.Received {
if expected.Received[i].Index >= start && expected.Received[i].Index <= end {
for _, j := range rcvd {
if i == j {
arr = append(arr, expected.Received[i])
break
}
}
require.ElementsMatch(t, arr, res.Received)
}
require.Equal(t, arr, res.Received)
}