mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-22 19:29:39 +00:00
rpc: implement getnep5transfers RPC
This commit is contained in:
parent
2757882d26
commit
95a8fa234f
13 changed files with 199 additions and 3 deletions
|
@ -48,7 +48,7 @@ which would yield the response:
|
||||||
| `getconnectioncount` | Yes |
|
| `getconnectioncount` | Yes |
|
||||||
| `getcontractstate` | Yes |
|
| `getcontractstate` | Yes |
|
||||||
| `getnep5balances` | Yes |
|
| `getnep5balances` | Yes |
|
||||||
| `getnep5transfers` | No (#498) |
|
| `getnep5transfers` | Yes |
|
||||||
| `getpeers` | Yes |
|
| `getpeers` | Yes |
|
||||||
| `getrawmempool` | Yes |
|
| `getrawmempool` | Yes |
|
||||||
| `getrawtransaction` | Yes |
|
| `getrawtransaction` | Yes |
|
||||||
|
|
|
@ -810,12 +810,21 @@ func (bc *Blockchain) processNEP5Transfer(cache *cachedDao, tx *transaction.Tran
|
||||||
}
|
}
|
||||||
|
|
||||||
transfer.Amount = amount
|
transfer.Amount = amount
|
||||||
if err := cache.AppendNEP5Transfer(fromAddr, transfer); err != nil {
|
if err := cache.AppendNEP5Transfer(toAddr, transfer); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetNEP5TransferLog returns NEP5 transfer log for the acc.
|
||||||
|
func (bc *Blockchain) GetNEP5TransferLog(acc util.Uint160) *state.NEP5TransferLog {
|
||||||
|
lg, err := bc.dao.GetNEP5TransferLog(acc)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return lg
|
||||||
|
}
|
||||||
|
|
||||||
// LastBatch returns last persisted storage batch.
|
// LastBatch returns last persisted storage batch.
|
||||||
func (bc *Blockchain) LastBatch() *storage.MemBatch {
|
func (bc *Blockchain) LastBatch() *storage.MemBatch {
|
||||||
return bc.lastBatch
|
return bc.lastBatch
|
||||||
|
|
|
@ -34,6 +34,7 @@ type Blockchainer interface {
|
||||||
GetAssetState(util.Uint256) *state.Asset
|
GetAssetState(util.Uint256) *state.Asset
|
||||||
GetAccountState(util.Uint160) *state.Account
|
GetAccountState(util.Uint160) *state.Account
|
||||||
GetAppExecResult(util.Uint256) (*state.AppExecResult, error)
|
GetAppExecResult(util.Uint256) (*state.AppExecResult, error)
|
||||||
|
GetNEP5TransferLog(util.Uint160) *state.NEP5TransferLog
|
||||||
GetValidators(txes ...*transaction.Transaction) ([]*keys.PublicKey, error)
|
GetValidators(txes ...*transaction.Transaction) ([]*keys.PublicKey, error)
|
||||||
GetScriptHashesForVerifying(*transaction.Transaction) ([]util.Uint160, error)
|
GetScriptHashesForVerifying(*transaction.Transaction) ([]util.Uint160, error)
|
||||||
GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem
|
GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem
|
||||||
|
|
|
@ -142,6 +142,9 @@ func (dao *dao) GetNEP5TransferLog(acc util.Uint160) (*state.NEP5TransferLog, er
|
||||||
key := storage.AppendPrefix(storage.STNEP5Transfers, acc.BytesBE())
|
key := storage.AppendPrefix(storage.STNEP5Transfers, acc.BytesBE())
|
||||||
value, err := dao.store.Get(key)
|
value, err := dao.store.Get(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if err == storage.ErrKeyNotFound {
|
||||||
|
return new(state.NEP5TransferLog), nil
|
||||||
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &state.NEP5TransferLog{Raw: value}, nil
|
return &state.NEP5TransferLog{Raw: value}, nil
|
||||||
|
|
|
@ -52,6 +52,24 @@ func (lg *NEP5TransferLog) Append(tr *NEP5Transfer) error {
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
tr := new(NEP5Transfer)
|
||||||
|
for i := 0; i < len(lg.Raw); i += NEP5TransferSize {
|
||||||
|
r := io.NewBinReaderFromBuf(lg.Raw[i : i+NEP5TransferSize])
|
||||||
|
tr.DecodeBinary(r)
|
||||||
|
if r.Err != nil {
|
||||||
|
return r.Err
|
||||||
|
} else if err := f(tr); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// EncodeBinary implements io.Serializable interface.
|
// EncodeBinary implements io.Serializable interface.
|
||||||
func (t *NEP5Tracker) EncodeBinary(w *io.BinWriter) {
|
func (t *NEP5Tracker) EncodeBinary(w *io.BinWriter) {
|
||||||
w.WriteU64LE(uint64(t.Balance))
|
w.WriteU64LE(uint64(t.Balance))
|
||||||
|
|
|
@ -1,14 +1,40 @@
|
||||||
package state
|
package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
gio "io"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestNEP5TransferLog_Append(t *testing.T) {
|
||||||
|
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
|
expected := []*NEP5Transfer{
|
||||||
|
randomTransfer(t, r),
|
||||||
|
randomTransfer(t, r),
|
||||||
|
randomTransfer(t, r),
|
||||||
|
randomTransfer(t, r),
|
||||||
|
}
|
||||||
|
|
||||||
|
lg := new(NEP5TransferLog)
|
||||||
|
for _, tr := range expected {
|
||||||
|
require.NoError(t, lg.Append(tr))
|
||||||
|
}
|
||||||
|
|
||||||
|
i := 0
|
||||||
|
err := lg.ForEach(func(tr *NEP5Transfer) error {
|
||||||
|
require.Equal(t, expected[i], tr)
|
||||||
|
i++
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func TestNEP5Tracker_EncodeBinary(t *testing.T) {
|
func TestNEP5Tracker_EncodeBinary(t *testing.T) {
|
||||||
expected := &NEP5Tracker{
|
expected := &NEP5Tracker{
|
||||||
Balance: int64(rand.Uint64()),
|
Balance: int64(rand.Uint64()),
|
||||||
|
@ -32,6 +58,25 @@ func TestNEP5Transfer_DecodeBinary(t *testing.T) {
|
||||||
testEncodeDecode(t, expected, new(NEP5Transfer))
|
testEncodeDecode(t, expected, new(NEP5Transfer))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func randomTransfer(t *testing.T, r *rand.Rand) *NEP5Transfer {
|
||||||
|
tr := &NEP5Transfer{
|
||||||
|
Amount: int64(r.Uint64()),
|
||||||
|
Block: r.Uint32(),
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
_, err = gio.ReadFull(r, tr.Asset[:])
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = gio.ReadFull(r, tr.From[:])
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = gio.ReadFull(r, tr.To[:])
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = gio.ReadFull(r, tr.Tx[:])
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return tr
|
||||||
|
}
|
||||||
|
|
||||||
func testEncodeDecode(t *testing.T, expected, actual io.Serializable) {
|
func testEncodeDecode(t *testing.T, expected, actual io.Serializable) {
|
||||||
w := io.NewBufBinWriter()
|
w := io.NewBufBinWriter()
|
||||||
expected.EncodeBinary(w.BinWriter)
|
expected.EncodeBinary(w.BinWriter)
|
||||||
|
|
|
@ -92,6 +92,9 @@ func (chain testChain) GetAssetState(util.Uint256) *state.Asset {
|
||||||
func (chain testChain) GetAccountState(util.Uint160) *state.Account {
|
func (chain testChain) GetAccountState(util.Uint160) *state.Account {
|
||||||
panic("TODO")
|
panic("TODO")
|
||||||
}
|
}
|
||||||
|
func (chain testChain) GetNEP5TransferLog(util.Uint160) *state.NEP5TransferLog {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
func (chain testChain) GetValidators(...*transaction.Transaction) ([]*keys.PublicKey, error) {
|
func (chain testChain) GetValidators(...*transaction.Transaction) ([]*keys.PublicKey, error) {
|
||||||
panic("TODO")
|
panic("TODO")
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ Supported methods
|
||||||
getblock
|
getblock
|
||||||
getclaimable
|
getclaimable
|
||||||
getnep5balances
|
getnep5balances
|
||||||
|
getnep5transfers
|
||||||
getrawtransaction
|
getrawtransaction
|
||||||
getunspents
|
getunspents
|
||||||
invoke
|
invoke
|
||||||
|
@ -44,7 +45,6 @@ Unsupported methods
|
||||||
getconnectioncount
|
getconnectioncount
|
||||||
getcontractstate
|
getcontractstate
|
||||||
getmetricblocktimestamp
|
getmetricblocktimestamp
|
||||||
getnep5transfers
|
|
||||||
getnewaddress
|
getnewaddress
|
||||||
getpeers
|
getpeers
|
||||||
getrawmempool
|
getrawmempool
|
||||||
|
|
|
@ -103,6 +103,16 @@ func (c *Client) GetNEP5Balances(address util.Uint160) (*result.NEP5Balances, er
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetNEP5Transfers is a wrapper for getnep5transfers RPC.
|
||||||
|
func (c *Client) GetNEP5Transfers(address string) (*result.NEP5Transfers, error) {
|
||||||
|
params := request.NewRawParams(address)
|
||||||
|
resp := new(result.NEP5Transfers)
|
||||||
|
if err := c.performRequest("getnep5transfers", params, resp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetRawTransaction returns a transaction by hash.
|
// GetRawTransaction returns a transaction by hash.
|
||||||
func (c *Client) GetRawTransaction(hash util.Uint256) (*transaction.Transaction, error) {
|
func (c *Client) GetRawTransaction(hash util.Uint256) (*transaction.Transaction, error) {
|
||||||
var (
|
var (
|
||||||
|
|
|
@ -14,3 +14,21 @@ type NEP5Balance struct {
|
||||||
Amount string `json:"amount"`
|
Amount string `json:"amount"`
|
||||||
LastUpdated uint32 `json:"last_updated_block"`
|
LastUpdated uint32 `json:"last_updated_block"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NEP5Transfers is a result for the getnep5transfers RPC.
|
||||||
|
type NEP5Transfers struct {
|
||||||
|
Sent []NEP5Transfer `json:"sent"`
|
||||||
|
Received []NEP5Transfer `json:"received"`
|
||||||
|
Address string `json:"address"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NEP5Transfer represents single NEP5 transfer event.
|
||||||
|
type NEP5Transfer struct {
|
||||||
|
Timestamp uint32 `json:"timestamp"`
|
||||||
|
Asset util.Uint160 `json:"asset_hash"`
|
||||||
|
Address string `json:"transfer_address,omitempty"`
|
||||||
|
Amount string `json:"amount"`
|
||||||
|
Index uint32 `json:"block_index"`
|
||||||
|
NotifyIndex uint32 `json:"transfer_notify_index"`
|
||||||
|
TxHash util.Uint256 `json:"tx_hash"`
|
||||||
|
}
|
||||||
|
|
|
@ -83,6 +83,14 @@ var (
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
getnep5transfersCalled = prometheus.NewCounter(
|
||||||
|
prometheus.CounterOpts{
|
||||||
|
Help: "Number of calls to getnep5transfers rpc endpoint",
|
||||||
|
Name: "getnep5transfers_called",
|
||||||
|
Namespace: "neogo",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
getversionCalled = prometheus.NewCounter(
|
getversionCalled = prometheus.NewCounter(
|
||||||
prometheus.CounterOpts{
|
prometheus.CounterOpts{
|
||||||
Help: "Number of calls to getversion rpc endpoint",
|
Help: "Number of calls to getversion rpc endpoint",
|
||||||
|
|
|
@ -202,6 +202,10 @@ Methods:
|
||||||
getnep5balancesCalled.Inc()
|
getnep5balancesCalled.Inc()
|
||||||
results, resultsErr = s.getNEP5Balances(reqParams)
|
results, resultsErr = s.getNEP5Balances(reqParams)
|
||||||
|
|
||||||
|
case "getnep5transfers":
|
||||||
|
getnep5transfersCalled.Inc()
|
||||||
|
results, resultsErr = s.getNEP5Transfers(reqParams)
|
||||||
|
|
||||||
case "getversion":
|
case "getversion":
|
||||||
getversionCalled.Inc()
|
getversionCalled.Inc()
|
||||||
results = result.Version{
|
results = result.Version{
|
||||||
|
@ -410,6 +414,47 @@ func (s *Server) getNEP5Balances(ps request.Params) (interface{}, error) {
|
||||||
return bs, nil
|
return bs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) getNEP5Transfers(ps request.Params) (interface{}, error) {
|
||||||
|
p, ok := ps.ValueWithType(0, request.StringT)
|
||||||
|
if !ok {
|
||||||
|
return nil, response.ErrInvalidParams
|
||||||
|
}
|
||||||
|
u, err := p.GetUint160FromAddress()
|
||||||
|
if err != nil {
|
||||||
|
return nil, response.ErrInvalidParams
|
||||||
|
}
|
||||||
|
|
||||||
|
bs := &result.NEP5Transfers{Address: address.Uint160ToString(u)}
|
||||||
|
lg := s.chain.GetNEP5TransferLog(u)
|
||||||
|
err = lg.ForEach(func(tr *state.NEP5Transfer) error {
|
||||||
|
transfer := result.NEP5Transfer{
|
||||||
|
Timestamp: tr.Timestamp,
|
||||||
|
Asset: tr.Asset,
|
||||||
|
Index: tr.Block,
|
||||||
|
TxHash: tr.Tx,
|
||||||
|
}
|
||||||
|
if tr.Amount > 0 { // token was received
|
||||||
|
transfer.Amount = strconv.FormatInt(tr.Amount, 10)
|
||||||
|
if !tr.From.Equals(util.Uint160{}) {
|
||||||
|
transfer.Address = address.Uint160ToString(tr.From)
|
||||||
|
}
|
||||||
|
bs.Received = append(bs.Received, transfer)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
transfer.Amount = strconv.FormatInt(-tr.Amount, 10)
|
||||||
|
if !tr.From.Equals(util.Uint160{}) {
|
||||||
|
transfer.Address = address.Uint160ToString(tr.To)
|
||||||
|
}
|
||||||
|
bs.Sent = append(bs.Sent, transfer)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, response.NewInternalServerError("invalid NEP5 transfer log", err)
|
||||||
|
}
|
||||||
|
return bs, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) getStorage(ps request.Params) (interface{}, error) {
|
func (s *Server) getStorage(ps request.Params) (interface{}, error) {
|
||||||
param, ok := ps.Value(0)
|
param, ok := ps.Value(0)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core"
|
"github.com/nspcc-dev/neo-go/pkg/core"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||||
"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/rpc/response"
|
"github.com/nspcc-dev/neo-go/pkg/rpc/response"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
|
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
|
||||||
|
@ -170,6 +171,41 @@ var rpcTestCases = map[string][]rpcTestCase{
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"getnep5transfers": {
|
||||||
|
{
|
||||||
|
name: "no params",
|
||||||
|
params: `[]`,
|
||||||
|
fail: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid address",
|
||||||
|
params: `["notahex"]`,
|
||||||
|
fail: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "positive",
|
||||||
|
params: `["AKkkumHbBipZ46UMZJoFynJMXzSRnBvKcs"]`,
|
||||||
|
result: func(e *executor) interface{} { return &result.NEP5Transfers{} },
|
||||||
|
check: func(t *testing.T, e *executor, acc interface{}) {
|
||||||
|
res, ok := acc.(*result.NEP5Transfers)
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Equal(t, "AKkkumHbBipZ46UMZJoFynJMXzSRnBvKcs", res.Address)
|
||||||
|
|
||||||
|
assetHash, err := util.Uint160DecodeStringLE("d864728bdbc88da799bc43862ae6aaa62adc3a87")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, 1, len(res.Received))
|
||||||
|
require.Equal(t, "1000", res.Received[0].Amount)
|
||||||
|
require.Equal(t, assetHash, res.Received[0].Asset)
|
||||||
|
require.Equal(t, address.Uint160ToString(assetHash), res.Received[0].Address)
|
||||||
|
|
||||||
|
require.Equal(t, 1, len(res.Sent))
|
||||||
|
require.Equal(t, "123", res.Sent[0].Amount)
|
||||||
|
require.Equal(t, assetHash, res.Sent[0].Asset)
|
||||||
|
require.Equal(t, "AWLYWXB8C9Lt1nHdDZJnC5cpYJjgRDLk17", res.Sent[0].Address)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
"getstorage": {
|
"getstorage": {
|
||||||
{
|
{
|
||||||
name: "positive",
|
name: "positive",
|
||||||
|
|
Loading…
Reference in a new issue