From f92fd3c9482feef6b5f4280027d8eb5705c80fcb Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Thu, 5 Mar 2020 17:11:58 +0300 Subject: [PATCH] core: track NEP5 transfers --- pkg/core/blockchain.go | 32 ++++++++++++++----- pkg/core/dao.go | 35 +++++++++++++++++++++ pkg/core/state/nep5.go | 62 +++++++++++++++++++++++++++++++++++++ pkg/core/state/nep5_test.go | 14 +++++++++ pkg/core/storage/store.go | 1 + 5 files changed, 137 insertions(+), 7 deletions(-) diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 665b93cf6..d1b6a4433 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -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 + } } } diff --git a/pkg/core/dao.go b/pkg/core/dao.go index 34e4dab1e..482eadbee 100644 --- a/pkg/core/dao.go +++ b/pkg/core/dao.go @@ -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 diff --git a/pkg/core/state/nep5.go b/pkg/core/state/nep5.go index 989c0b14f..e2143d13d 100644 --- a/pkg/core/state/nep5.go +++ b/pkg/core/state/nep5.go @@ -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()) +} diff --git a/pkg/core/state/nep5_test.go b/pkg/core/state/nep5_test.go index cf187d729..e97a37c8f 100644 --- a/pkg/core/state/nep5_test.go +++ b/pkg/core/state/nep5_test.go @@ -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) diff --git a/pkg/core/storage/store.go b/pkg/core/storage/store.go index 5ec0f9d67..a36523f4d 100644 --- a/pkg/core/storage/store.go +++ b/pkg/core/storage/store.go @@ -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