core: track NEP5 transfers

This commit is contained in:
Evgenii Stratonikov 2020-03-05 17:11:58 +03:00
parent e8c4179a9c
commit f92fd3c948
5 changed files with 137 additions and 7 deletions

View file

@ -754,18 +754,26 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
return nil
}
func parseUint160(addr []byte) *util.Uint160 {
func parseUint160(addr []byte) util.Uint160 {
if u, err := util.Uint160DecodeBytesBE(addr); err == nil {
return &u
return u
}
return nil
return util.Uint160{}
}
func (bc *Blockchain) processNEP5Transfer(cache *cachedDao, tx *transaction.Transaction, b *block.Block, sc util.Uint160, from, to []byte, amount int64) {
toAddr := parseUint160(to)
fromAddr := parseUint160(from)
if fromAddr != nil {
acc, err := cache.GetAccountStateOrNew(*fromAddr)
transfer := &state.NEP5Transfer{
Asset: sc,
From: fromAddr,
To: toAddr,
Block: b.Index,
Timestamp: b.Timestamp,
Tx: tx.Hash(),
}
if !fromAddr.Equals(util.Uint160{}) {
acc, err := cache.GetAccountStateOrNew(fromAddr)
if err != nil {
return
}
@ -779,9 +787,14 @@ func (bc *Blockchain) processNEP5Transfer(cache *cachedDao, tx *transaction.Tran
if err := cache.PutAccountState(acc); err != nil {
return
}
transfer.Amount = -amount
if err := cache.AppendNEP5Transfer(fromAddr, transfer); err != nil {
return
}
}
if toAddr != nil {
acc, err := cache.GetAccountStateOrNew(*toAddr)
if !toAddr.Equals(util.Uint160{}) {
acc, err := cache.GetAccountStateOrNew(toAddr)
if err != nil {
return
}
@ -795,6 +808,11 @@ func (bc *Blockchain) processNEP5Transfer(cache *cachedDao, tx *transaction.Tran
if err := cache.PutAccountState(acc); err != nil {
return
}
transfer.Amount = amount
if err := cache.AppendNEP5Transfer(fromAddr, transfer); err != nil {
return
}
}
}

View file

@ -135,6 +135,41 @@ func (dao *dao) DeleteContractState(hash util.Uint160) error {
// -- end contracts.
// -- start transfer log.
// GetNEP5TransferLog retrieves transfer log from the cache.
func (dao *dao) GetNEP5TransferLog(acc util.Uint160) (*state.NEP5TransferLog, error) {
key := storage.AppendPrefix(storage.STNEP5Transfers, acc.BytesBE())
value, err := dao.store.Get(key)
if err != nil {
return nil, err
}
return &state.NEP5TransferLog{Raw: value}, nil
}
// PutNEP5TransferLog saves given transfer log in the cache.
func (dao *dao) PutNEP5TransferLog(acc util.Uint160, lg *state.NEP5TransferLog) error {
key := storage.AppendPrefix(storage.STNEP5Transfers, acc.BytesBE())
return dao.store.Put(key, lg.Raw)
}
// AppendNEP5Transfer appends a single NEP5 transfer to a log.
func (dao *dao) AppendNEP5Transfer(acc util.Uint160, tr *state.NEP5Transfer) error {
lg, err := dao.GetNEP5TransferLog(acc)
if err != nil {
if err != storage.ErrKeyNotFound {
return err
}
lg = new(state.NEP5TransferLog)
}
if err := lg.Append(tr); err != nil {
return err
}
return dao.PutNEP5TransferLog(acc, lg)
}
// -- end transfer log.
// -- start unspent coins.
// GetUnspentCoinStateOrNew gets UnspentCoinState from temporary or persistent Store

View file

@ -2,6 +2,7 @@ package state
import (
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/util"
)
// NEP5Tracker contains info about a single account in a NEP5 contract.
@ -13,6 +14,44 @@ type NEP5Tracker struct {
LastUpdatedBlock uint32
}
// NEP5TransferLog is a log of NEP5 token transfers for the specific command.
type NEP5TransferLog struct {
Raw []byte
}
// NEP5TransferSize is a size of a marshaled NEP5Transfer struct in bytes.
const NEP5TransferSize = util.Uint160Size*3 + 8 + 4 + 4 + util.Uint256Size
// NEP5Transfer represents a single NEP5 Transfer event.
type NEP5Transfer struct {
// Asset is a NEP5 contract hash.
Asset util.Uint160
// Address is the address of the sender.
From util.Uint160
// To is the address of the receiver.
To util.Uint160
// Amount is the amount of tokens transferred.
// It is negative when tokens are sent and positive if they are received.
Amount int64
// Block is a number of block when the event occured.
Block uint32
// Timestamp is the timestamp of the block where transfer occured.
Timestamp uint32
// Tx is a hash the transaction.
Tx util.Uint256
}
// Append appends single transfer to a log.
func (lg *NEP5TransferLog) Append(tr *NEP5Transfer) error {
w := io.NewBufBinWriter()
tr.EncodeBinary(w.BinWriter)
if w.Err != nil {
return w.Err
}
lg.Raw = append(lg.Raw, w.Bytes()...)
return nil
}
// EncodeBinary implements io.Serializable interface.
func (t *NEP5Tracker) EncodeBinary(w *io.BinWriter) {
w.WriteU64LE(uint64(t.Balance))
@ -24,3 +63,26 @@ func (t *NEP5Tracker) DecodeBinary(r *io.BinReader) {
t.Balance = int64(r.ReadU64LE())
t.LastUpdatedBlock = r.ReadU32LE()
}
// EncodeBinary implements io.Serializable interface.
// Note: change NEP5TransferSize constant when changing this function.
func (t *NEP5Transfer) EncodeBinary(w *io.BinWriter) {
w.WriteBytes(t.Asset[:])
w.WriteBytes(t.Tx[:])
w.WriteBytes(t.From[:])
w.WriteBytes(t.To[:])
w.WriteU32LE(t.Block)
w.WriteU32LE(t.Timestamp)
w.WriteU64LE(uint64(t.Amount))
}
// DecodeBinary implements io.Serializable interface.
func (t *NEP5Transfer) DecodeBinary(r *io.BinReader) {
r.ReadBytes(t.Asset[:])
r.ReadBytes(t.Tx[:])
r.ReadBytes(t.From[:])
r.ReadBytes(t.To[:])
t.Block = r.ReadU32LE()
t.Timestamp = r.ReadU32LE()
t.Amount = int64(r.ReadU64LE())
}

View file

@ -18,6 +18,20 @@ func TestNEP5Tracker_EncodeBinary(t *testing.T) {
testEncodeDecode(t, expected, new(NEP5Tracker))
}
func TestNEP5Transfer_DecodeBinary(t *testing.T) {
expected := &NEP5Transfer{
Asset: util.Uint160{1, 2, 3},
From: util.Uint160{5, 6, 7},
To: util.Uint160{8, 9, 10},
Amount: 42,
Block: 12345,
Timestamp: 54321,
Tx: util.Uint256{8, 5, 3},
}
testEncodeDecode(t, expected, new(NEP5Transfer))
}
func testEncodeDecode(t *testing.T, expected, actual io.Serializable) {
w := io.NewBufBinWriter()
expected.EncodeBinary(w.BinWriter)

View file

@ -17,6 +17,7 @@ const (
STNotification KeyPrefix = 0x4d
STContract KeyPrefix = 0x50
STStorage KeyPrefix = 0x70
STNEP5Transfers KeyPrefix = 0x72
IXHeaderHashList KeyPrefix = 0x80
IXValidatorsCount KeyPrefix = 0x90
SYSCurrentBlock KeyPrefix = 0xc0