commit
1ff1cd797e
15 changed files with 407 additions and 158 deletions
22
docs/rpc.md
22
docs/rpc.md
|
@ -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.
|
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
|
#### Websocket server
|
||||||
|
|
||||||
This server accepts websocket connections on `ws://$BASE_URL/ws` address. You
|
This server accepts websocket connections on `ws://$BASE_URL/ws` address. You
|
||||||
|
|
|
@ -133,6 +133,19 @@ func NewService(cfg Config) (Service, error) {
|
||||||
return nil, err
|
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()
|
defer srv.wallet.Close()
|
||||||
|
|
||||||
srv.dbft = dbft.New(
|
srv.dbft = dbft.New(
|
||||||
|
|
|
@ -784,20 +784,23 @@ func (bc *Blockchain) processNEP5Transfer(cache *dao.Cached, h util.Uint256, b *
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForEachNEP5Transfer executes f for each nep5 transfer in log.
|
// 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)
|
balances, err := bc.dao.GetNEP5Balances(acc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
for i := uint32(0); i <= balances.NextTransferBatch; i++ {
|
for i := int(balances.NextTransferBatch); i >= 0; i-- {
|
||||||
lg, err := bc.dao.GetNEP5TransferLog(acc, i)
|
lg, err := bc.dao.GetNEP5TransferLog(acc, uint32(i))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
err = lg.ForEach(f)
|
cont, err := lg.ForEach(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if !cont {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ type Blockchainer interface {
|
||||||
GetContractScriptHash(id int32) (util.Uint160, error)
|
GetContractScriptHash(id int32) (util.Uint160, error)
|
||||||
GetEnrollments() ([]state.Validator, error)
|
GetEnrollments() ([]state.Validator, error)
|
||||||
GetGoverningTokenBalance(acc util.Uint160) (*big.Int, uint32)
|
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
|
GetHeaderHash(int) util.Uint256
|
||||||
GetHeader(hash util.Uint256) (*block.Header, error)
|
GetHeader(hash util.Uint256) (*block.Header, error)
|
||||||
CurrentHeaderHash() util.Uint256
|
CurrentHeaderHash() util.Uint256
|
||||||
|
|
|
@ -95,7 +95,7 @@ func (cd *Cached) AppendNEP5Transfer(acc util.Uint160, index uint32, tr *state.N
|
||||||
if err := lg.Append(tr); err != nil {
|
if err := lg.Append(tr); err != nil {
|
||||||
return false, err
|
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
|
// Persist flushes all the changes made into the (supposedly) persistent
|
||||||
|
|
|
@ -208,8 +208,6 @@ func (dao *Simple) putNEP5Balances(acc util.Uint160, bs *state.NEP5Balances, buf
|
||||||
|
|
||||||
// -- start transfer log.
|
// -- start transfer log.
|
||||||
|
|
||||||
const nep5TransferBatchSize = 128
|
|
||||||
|
|
||||||
func getNEP5TransferLogKey(acc util.Uint160, index uint32) []byte {
|
func getNEP5TransferLogKey(acc util.Uint160, index uint32) []byte {
|
||||||
key := make([]byte, 1+util.Uint160Size+4)
|
key := make([]byte, 1+util.Uint160Size+4)
|
||||||
key[0] = byte(storage.STNEP5Transfers)
|
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 {
|
if err := lg.Append(tr); err != nil {
|
||||||
return false, err
|
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.
|
// -- end transfer log.
|
||||||
|
|
|
@ -36,7 +36,7 @@ func (h *HashNode) Hash() util.Uint256 {
|
||||||
return h.hash
|
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 }
|
func (h *HashNode) IsEmpty() bool { return !h.hashValid }
|
||||||
|
|
||||||
// Bytes returns serialized HashNode.
|
// Bytes returns serialized HashNode.
|
||||||
|
|
|
@ -8,6 +8,9 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"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.
|
// NEP5Tracker contains info about a single account in a NEP5 contract.
|
||||||
type NEP5Tracker struct {
|
type NEP5Tracker struct {
|
||||||
// Balance is the current balance of the account.
|
// 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.
|
// NEP5TransferLog is a log of NEP5 token transfers for the specific command.
|
||||||
type NEP5TransferLog struct {
|
type NEP5TransferLog struct {
|
||||||
Raw []byte
|
Raw []byte
|
||||||
// size is the number of NEP5Transfers written into Raw
|
|
||||||
size int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NEP5Transfer represents a single NEP5 Transfer event.
|
// 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.
|
// Append appends single transfer to a log.
|
||||||
func (lg *NEP5TransferLog) Append(tr *NEP5Transfer) error {
|
func (lg *NEP5TransferLog) Append(tr *NEP5Transfer) error {
|
||||||
w := io.NewBufBinWriter()
|
w := io.NewBufBinWriter()
|
||||||
|
// The first entry, set up counter.
|
||||||
|
if len(lg.Raw) == 0 {
|
||||||
|
w.WriteB(1)
|
||||||
|
}
|
||||||
tr.EncodeBinary(w.BinWriter)
|
tr.EncodeBinary(w.BinWriter)
|
||||||
if w.Err != nil {
|
if w.Err != nil {
|
||||||
return w.Err
|
return w.Err
|
||||||
}
|
}
|
||||||
|
if len(lg.Raw) != 0 {
|
||||||
|
lg.Raw[0]++
|
||||||
|
}
|
||||||
lg.Raw = append(lg.Raw, w.Bytes()...)
|
lg.Raw = append(lg.Raw, w.Bytes()...)
|
||||||
lg.size++
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForEach iterates over transfer log returning on first error.
|
// ForEach iterates over transfer log returning on first error.
|
||||||
func (lg *NEP5TransferLog) ForEach(f func(*NEP5Transfer) error) error {
|
func (lg *NEP5TransferLog) ForEach(f func(*NEP5Transfer) (bool, error)) (bool, error) {
|
||||||
if lg == nil {
|
if lg == nil || len(lg.Raw) == 0 {
|
||||||
return nil
|
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 {
|
if r.Err != nil {
|
||||||
return r.Err
|
return false, r.Err
|
||||||
} else if err := f(tr); err != nil {
|
}
|
||||||
return nil
|
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.
|
// Size returns an amount of transfer written in log.
|
||||||
func (lg *NEP5TransferLog) Size() int {
|
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.
|
// EncodeBinary implements io.Serializable interface.
|
||||||
|
@ -138,27 +154,18 @@ func (t *NEP5Transfer) EncodeBinary(w *io.BinWriter) {
|
||||||
w.WriteBytes(t.To[:])
|
w.WriteBytes(t.To[:])
|
||||||
w.WriteU32LE(t.Block)
|
w.WriteU32LE(t.Block)
|
||||||
w.WriteU64LE(t.Timestamp)
|
w.WriteU64LE(t.Timestamp)
|
||||||
amountBytes := bigint.ToBytes(&t.Amount)
|
amount := bigint.ToBytes(&t.Amount)
|
||||||
w.WriteU64LE(uint64(len(amountBytes)))
|
w.WriteVarBytes(amount)
|
||||||
w.WriteBytes(amountBytes)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecodeBinary implements io.Serializable interface.
|
// DecodeBinary implements io.Serializable interface.
|
||||||
func (t *NEP5Transfer) DecodeBinary(r *io.BinReader) {
|
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())
|
t.Asset = int32(r.ReadU32LE())
|
||||||
r.ReadBytes(t.Tx[:])
|
r.ReadBytes(t.Tx[:])
|
||||||
r.ReadBytes(t.From[:])
|
r.ReadBytes(t.From[:])
|
||||||
r.ReadBytes(t.To[:])
|
r.ReadBytes(t.To[:])
|
||||||
t.Block = r.ReadU32LE()
|
t.Block = r.ReadU32LE()
|
||||||
t.Timestamp = r.ReadU64LE()
|
t.Timestamp = r.ReadU64LE()
|
||||||
amountLen := r.ReadU64LE()
|
amount := r.ReadVarBytes(bigint.MaxBytesLen)
|
||||||
amountBytes := make([]byte, amountLen)
|
t.Amount = *bigint.FromBytes(amount)
|
||||||
r.ReadBytes(amountBytes)
|
|
||||||
t.Amount = *bigint.FromBytes(amountBytes)
|
|
||||||
return 4 + util.Uint160Size*2 + 8 + 4 + (8 + len(amountBytes)) + +util.Uint256Size
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/internal/random"
|
"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/internal/testserdes"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
@ -29,14 +28,14 @@ func TestNEP5TransferLog_Append(t *testing.T) {
|
||||||
|
|
||||||
require.Equal(t, len(expected), lg.Size())
|
require.Equal(t, len(expected), lg.Size())
|
||||||
|
|
||||||
i := 0
|
i := len(expected) - 1
|
||||||
err := lg.ForEach(func(tr *NEP5Transfer) error {
|
cont, err := lg.ForEach(func(tr *NEP5Transfer) (bool, error) {
|
||||||
require.Equal(t, expected[i], tr)
|
require.Equal(t, expected[i], tr)
|
||||||
i++
|
i--
|
||||||
return nil
|
return true, nil
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
require.True(t, cont)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNEP5Tracker_EncodeBinary(t *testing.T) {
|
func TestNEP5Tracker_EncodeBinary(t *testing.T) {
|
||||||
|
@ -62,18 +61,6 @@ func TestNEP5Transfer_DecodeBinary(t *testing.T) {
|
||||||
testserdes.EncodeDecodeBinary(t, expected, new(NEP5Transfer))
|
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 {
|
func randomTransfer(r *rand.Rand) *NEP5Transfer {
|
||||||
return &NEP5Transfer{
|
return &NEP5Transfer{
|
||||||
Amount: *big.NewInt(int64(r.Uint64())),
|
Amount: *big.NewInt(int64(r.Uint64())),
|
||||||
|
|
|
@ -6,8 +6,12 @@ import (
|
||||||
"math/bits"
|
"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.`
|
// 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
|
// FromBytes converts data in little-endian format to
|
||||||
// an integer.
|
// an integer.
|
||||||
|
|
|
@ -95,7 +95,7 @@ func (chain testChain) GetHeader(hash util.Uint256) (*block.Header, error) {
|
||||||
func (chain testChain) GetNextBlockValidators() ([]*keys.PublicKey, error) {
|
func (chain testChain) GetNextBlockValidators() ([]*keys.PublicKey, error) {
|
||||||
panic("TODO")
|
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")
|
panic("TODO")
|
||||||
}
|
}
|
||||||
func (chain testChain) GetNEP5Balances(util.Uint160) *state.NEP5Balances {
|
func (chain testChain) GetNEP5Balances(util.Uint160) *state.NEP5Balances {
|
||||||
|
|
|
@ -214,9 +214,31 @@ func (c *Client) GetNEP5Balances(address util.Uint160) (*result.NEP5Balances, er
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNEP5Transfers is a wrapper for getnep5transfers RPC.
|
// GetNEP5Transfers is a wrapper for getnep5transfers RPC. Address parameter
|
||||||
func (c *Client) GetNEP5Transfers(address string) (*result.NEP5Transfers, error) {
|
// 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)
|
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)
|
resp := new(result.NEP5Transfers)
|
||||||
if err := c.performRequest("getnep5transfers", params, resp); err != nil {
|
if err := c.performRequest("getnep5transfers", params, resp); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -420,7 +420,7 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
|
||||||
{
|
{
|
||||||
name: "positive",
|
name: "positive",
|
||||||
invoke: func(c *Client) (interface{}, error) {
|
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"}}`,
|
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{} {
|
result: func(c *Client) interface{} {
|
||||||
|
@ -948,7 +948,30 @@ var rpcClientErrorCases = map[string][]rpcClientErrorCase{
|
||||||
{
|
{
|
||||||
name: "getnep5transfers_invalid_params_error",
|
name: "getnep5transfers_invalid_params_error",
|
||||||
invoke: func(c *Client) (interface{}, 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",
|
name: "getnep5transfers_unmarshalling_error",
|
||||||
invoke: func(c *Client) (interface{}, error) {
|
invoke: func(c *Client) (interface{}, error) {
|
||||||
return c.GetNEP5Transfers("")
|
return c.GetNEP5Transfers("", nil, nil, nil, nil)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -77,6 +77,9 @@ const (
|
||||||
// treated like subscriber, so technically it's a limit on websocket
|
// treated like subscriber, so technically it's a limit on websocket
|
||||||
// connections.
|
// connections.
|
||||||
maxSubscribers = 64
|
maxSubscribers = 64
|
||||||
|
|
||||||
|
// Maximum number of elements for get*transfers requests.
|
||||||
|
maxTransfersLimit = 1000
|
||||||
)
|
)
|
||||||
|
|
||||||
var rpcHandlers = map[string]func(*Server, request.Params) (interface{}, *response.Error){
|
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
|
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
|
var start, end uint64
|
||||||
if p1 != nil {
|
var limit, page int
|
||||||
val, err := p1.GetInt()
|
|
||||||
|
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 {
|
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 {
|
page = p
|
||||||
val, err := p2.GetInt()
|
}
|
||||||
|
if pLimit != nil {
|
||||||
|
l, err := pLimit.GetInt()
|
||||||
if err != nil {
|
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)
|
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) {
|
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
|
return nil, response.ErrInvalidParams
|
||||||
}
|
}
|
||||||
|
|
||||||
p1, p2 := ps.Value(1), ps.Value(2)
|
start, end, limit, page, err := getTimestampsAndLimit(ps, 1)
|
||||||
start, end, err := getTimestamps(p1, p2)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, response.NewInvalidParamsError(err.Error(), err)
|
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{
|
bs := &result.NEP5Transfers{
|
||||||
Address: address.Uint160ToString(u),
|
Address: address.Uint160ToString(u),
|
||||||
|
@ -588,14 +615,29 @@ func (s *Server) getNEP5Transfers(ps request.Params) (interface{}, *response.Err
|
||||||
Sent: []result.NEP5Transfer{},
|
Sent: []result.NEP5Transfer{},
|
||||||
}
|
}
|
||||||
cache := make(map[int32]decimals)
|
cache := make(map[int32]decimals)
|
||||||
err = s.chain.ForEachNEP5Transfer(u, func(tr *state.NEP5Transfer) error {
|
var resCount, frameCount int
|
||||||
if tr.Timestamp < start || tr.Timestamp > end {
|
err = s.chain.ForEachNEP5Transfer(u, func(tr *state.NEP5Transfer) (bool, error) {
|
||||||
return nil
|
// 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)
|
d, err := s.getDecimals(tr.Asset, cache)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
transfer := result.NEP5Transfer{
|
transfer := result.NEP5Transfer{
|
||||||
Timestamp: tr.Timestamp,
|
Timestamp: tr.Timestamp,
|
||||||
Asset: d.Hash,
|
Asset: d.Hash,
|
||||||
|
@ -608,15 +650,20 @@ func (s *Server) getNEP5Transfers(ps request.Params) (interface{}, *response.Err
|
||||||
transfer.Address = address.Uint160ToString(tr.From)
|
transfer.Address = address.Uint160ToString(tr.From)
|
||||||
}
|
}
|
||||||
bs.Received = append(bs.Received, transfer)
|
bs.Received = append(bs.Received, transfer)
|
||||||
return nil
|
} else {
|
||||||
}
|
|
||||||
|
|
||||||
transfer.Amount = amountToString(new(big.Int).Neg(&tr.Amount), d.Value)
|
transfer.Amount = amountToString(new(big.Int).Neg(&tr.Amount), d.Value)
|
||||||
if !tr.To.Equals(util.Uint160{}) {
|
if !tr.To.Equals(util.Uint160{}) {
|
||||||
transfer.Address = address.Uint160ToString(tr.To)
|
transfer.Address = address.Uint160ToString(tr.To)
|
||||||
}
|
}
|
||||||
bs.Sent = append(bs.Sent, transfer)
|
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 {
|
if err != nil {
|
||||||
return nil, response.NewInternalServerError("invalid NEP5 transfer log", err)
|
return nil, response.NewInternalServerError("invalid NEP5 transfer log", err)
|
||||||
|
|
|
@ -160,6 +160,36 @@ var rpcTestCases = map[string][]rpcTestCase{
|
||||||
params: `["` + testchain.PrivateKeyByID(0).Address() + `", "notanumber"]`,
|
params: `["` + testchain.PrivateKeyByID(0).Address() + `", "notanumber"]`,
|
||||||
fail: true,
|
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",
|
name: "positive",
|
||||||
params: `["` + testchain.PrivateKeyByID(0).Address() + `", 0]`,
|
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) {
|
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() + `"`}
|
ps := []string{`"` + testchain.PrivateKeyByID(0).Address() + `"`}
|
||||||
h, err := e.chain.GetHeader(e.chain.GetHeaderHash(4))
|
if start != 0 {
|
||||||
require.NoError(t, err)
|
h, err := e.chain.GetHeader(e.chain.GetHeaderHash(start))
|
||||||
ps = append(ps, strconv.FormatUint(h.Timestamp, 10))
|
var ts uint64
|
||||||
h, err = e.chain.GetHeader(e.chain.GetHeaderHash(5))
|
if err == nil {
|
||||||
require.NoError(t, err)
|
ts = h.Timestamp
|
||||||
ps = append(ps, strconv.FormatUint(h.Timestamp, 10))
|
} 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, ", ")
|
p := strings.Join(ps, ", ")
|
||||||
rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "getnep5transfers", "params": [%s]}`, p)
|
rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "getnep5transfers", "params": [%s]}`, p)
|
||||||
body := doRPCCall(rpc, httpSrv.URL, t)
|
body := doRPCCall(rpc, httpSrv.URL, t)
|
||||||
res := checkErrGetResult(t, body, false)
|
res := checkErrGetResult(t, body, false)
|
||||||
actual := new(result.NEP5Transfers)
|
actual := new(result.NEP5Transfers)
|
||||||
require.NoError(t, json.Unmarshal(res, actual))
|
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{}) {
|
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)
|
res, ok := acc.(*result.NEP5Transfers)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
rublesHash, err := util.Uint160DecodeStringLE(testContractHash)
|
rublesHash, err := util.Uint160DecodeStringLE(testContractHash)
|
||||||
require.NoError(t, err)
|
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))
|
blockSendRubles, err := e.chain.GetBlock(e.chain.GetHeaderHash(6))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(blockSendRubles.Transactions))
|
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))
|
blockReceiveRubles, err := e.chain.GetBlock(e.chain.GetHeaderHash(5))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 2, len(blockReceiveRubles.Transactions))
|
require.Equal(t, 2, len(blockReceiveRubles.Transactions))
|
||||||
txReceiveRublesHash := blockReceiveRubles.Transactions[1].Hash()
|
txInitCall := blockReceiveRubles.Transactions[0]
|
||||||
blockReceiveGAS, err := e.chain.GetBlock(e.chain.GetHeaderHash(1))
|
txReceiveRubles := blockReceiveRubles.Transactions[1]
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, 2, len(blockReceiveGAS.Transactions))
|
|
||||||
txReceiveNEOHash := blockReceiveGAS.Transactions[0].Hash()
|
|
||||||
txReceiveGASHash := blockReceiveGAS.Transactions[1].Hash()
|
|
||||||
blockSendNEO, err := e.chain.GetBlock(e.chain.GetHeaderHash(4))
|
blockSendNEO, err := e.chain.GetBlock(e.chain.GetHeaderHash(4))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(blockSendNEO.Transactions))
|
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{
|
expected := result.NEP5Transfers{
|
||||||
Sent: []result.NEP5Transfer{
|
Sent: []result.NEP5Transfer{
|
||||||
{
|
{
|
||||||
Timestamp: blockSendNEO.Timestamp,
|
Timestamp: blockDeploy2.Timestamp,
|
||||||
Asset: e.chain.GoverningTokenHash(),
|
Asset: e.chain.UtilityTokenHash(),
|
||||||
Address: testchain.PrivateKeyByID(1).Address(),
|
Address: "", // burn
|
||||||
Amount: "1000",
|
Amount: amountToString(big.NewInt(txDeploy2.SystemFee+txDeploy2.NetworkFee), 8),
|
||||||
Index: 4,
|
Index: 7,
|
||||||
NotifyIndex: 0,
|
TxHash: blockDeploy2.Hash(),
|
||||||
TxHash: txSendNEOHash,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Timestamp: blockSendRubles.Timestamp,
|
Timestamp: blockSendRubles.Timestamp,
|
||||||
|
@ -1071,7 +1153,64 @@ func checkNep5TransfersAux(t *testing.T, e *executor, acc interface{}, start, en
|
||||||
Amount: "1.23",
|
Amount: "1.23",
|
||||||
Index: 6,
|
Index: 6,
|
||||||
NotifyIndex: 0,
|
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{
|
Received: []result.NEP5Transfer{
|
||||||
|
@ -1082,7 +1221,7 @@ func checkNep5TransfersAux(t *testing.T, e *executor, acc interface{}, start, en
|
||||||
Amount: "10",
|
Amount: "10",
|
||||||
Index: 5,
|
Index: 5,
|
||||||
NotifyIndex: 0,
|
NotifyIndex: 0,
|
||||||
TxHash: txReceiveRublesHash,
|
TxHash: txReceiveRubles.Hash(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Timestamp: blockSendNEO.Timestamp,
|
Timestamp: blockSendNEO.Timestamp,
|
||||||
|
@ -1091,7 +1230,7 @@ func checkNep5TransfersAux(t *testing.T, e *executor, acc interface{}, start, en
|
||||||
Amount: "17.99982000",
|
Amount: "17.99982000",
|
||||||
Index: 4,
|
Index: 4,
|
||||||
NotifyIndex: 0,
|
NotifyIndex: 0,
|
||||||
TxHash: txSendNEOHash,
|
TxHash: txSendNEO.Hash(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Timestamp: blockReceiveGAS.Timestamp,
|
Timestamp: blockReceiveGAS.Timestamp,
|
||||||
|
@ -1100,7 +1239,7 @@ func checkNep5TransfersAux(t *testing.T, e *executor, acc interface{}, start, en
|
||||||
Amount: "1000",
|
Amount: "1000",
|
||||||
Index: 1,
|
Index: 1,
|
||||||
NotifyIndex: 0,
|
NotifyIndex: 0,
|
||||||
TxHash: txReceiveGASHash,
|
TxHash: txReceiveGAS.Hash(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Timestamp: blockReceiveGAS.Timestamp,
|
Timestamp: blockReceiveGAS.Timestamp,
|
||||||
|
@ -1109,49 +1248,33 @@ func checkNep5TransfersAux(t *testing.T, e *executor, acc interface{}, start, en
|
||||||
Amount: "99999000",
|
Amount: "99999000",
|
||||||
Index: 1,
|
Index: 1,
|
||||||
NotifyIndex: 0,
|
NotifyIndex: 0,
|
||||||
TxHash: txReceiveNEOHash,
|
TxHash: txReceiveNEO.Hash(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Address: testchain.PrivateKeyByID(0).Address(),
|
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)
|
require.Equal(t, expected.Address, res.Address)
|
||||||
|
|
||||||
arr := make([]result.NEP5Transfer, 0, len(expected.Sent))
|
arr := make([]result.NEP5Transfer, 0, len(expected.Sent))
|
||||||
for i := range 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])
|
arr = append(arr, expected.Sent[i])
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
require.ElementsMatch(t, arr, res.Sent)
|
}
|
||||||
|
require.Equal(t, arr, res.Sent)
|
||||||
|
|
||||||
arr = arr[:0]
|
arr = arr[:0]
|
||||||
for i := range expected.Received {
|
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])
|
arr = append(arr, expected.Received[i])
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
require.ElementsMatch(t, arr, res.Received)
|
}
|
||||||
|
require.Equal(t, arr, res.Received)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue