core: store NEP-11 transfers, add accessor functions

This commit is contained in:
Roman Khimov 2021-11-16 23:09:04 +03:00
parent 7f40a0cfd8
commit 125e4231b0
7 changed files with 147 additions and 47 deletions

View file

@ -276,13 +276,23 @@ func (chain *FakeChain) GetNextBlockValidators() ([]*keys.PublicKey, error) {
panic("TODO") panic("TODO")
} }
// GetNEP17Contracts implements Blockchainer interface.
func (chain *FakeChain) GetNEP11Contracts() []util.Uint160 {
panic("TODO")
}
// GetNEP17Contracts implements Blockchainer interface. // GetNEP17Contracts implements Blockchainer interface.
func (chain *FakeChain) GetNEP17Contracts() []util.Uint160 { func (chain *FakeChain) GetNEP17Contracts() []util.Uint160 {
panic("TODO") panic("TODO")
} }
// GetNEP17LastUpdated implements Blockchainer interface. // GetNEP17LastUpdated implements Blockchainer interface.
func (chain *FakeChain) GetNEP17LastUpdated(acc util.Uint160) (map[int32]uint32, error) { func (chain *FakeChain) GetTokenLastUpdated(acc util.Uint160) (map[int32]uint32, error) {
panic("TODO")
}
// ForEachNEP17Transfer implements Blockchainer interface.
func (chain *FakeChain) ForEachNEP11Transfer(util.Uint160, func(*state.NEP11Transfer) (bool, error)) error {
panic("TODO") panic("TODO")
} }

View file

@ -45,7 +45,7 @@ import (
// Tuning parameters. // Tuning parameters.
const ( const (
headerBatchCount = 2000 headerBatchCount = 2000
version = "0.1.4" version = "0.1.5"
defaultInitialGAS = 52000000_00000000 defaultInitialGAS = 52000000_00000000
defaultMemPoolSize = 50000 defaultMemPoolSize = 50000
@ -195,7 +195,8 @@ type bcEvent struct {
// transferData is used for transfer caching during storeBlock. // transferData is used for transfer caching during storeBlock.
type transferData struct { type transferData struct {
Info state.TokenTransferInfo Info state.TokenTransferInfo
Log state.TokenTransferLog Log11 state.TokenTransferLog
Log17 state.TokenTransferLog
} }
// NewBlockchain returns a new blockchain object the will use the // NewBlockchain returns a new blockchain object the will use the
@ -1009,7 +1010,12 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
aerdone <- err aerdone <- err
return return
} }
err = kvcache.PutTokenTransferLog(acc, trData.Info.NextNEP17Batch, &trData.Log) err = kvcache.PutTokenTransferLog(acc, trData.Info.NextNEP11Batch, true, &trData.Log11)
if err != nil {
aerdone <- err
return
}
err = kvcache.PutTokenTransferLog(acc, trData.Info.NextNEP17Batch, false, &trData.Log17)
if err != nil { if err != nil {
aerdone <- err aerdone <- err
return return
@ -1246,7 +1252,7 @@ func (bc *Blockchain) handleNotification(note *state.NotificationEvent, d dao.DA
return return
} }
arr, ok := note.Item.Value().([]stackitem.Item) arr, ok := note.Item.Value().([]stackitem.Item)
if !ok || len(arr) != 3 { if !ok || !(len(arr) == 3 || len(arr) == 4) {
return return
} }
from, err := parseUint160(arr[0]) from, err := parseUint160(arr[0])
@ -1261,7 +1267,14 @@ func (bc *Blockchain) handleNotification(note *state.NotificationEvent, d dao.DA
if err != nil { if err != nil {
return return
} }
bc.processNEP17Transfer(d, transCache, h, b, note.ScriptHash, from, to, amount) var id []byte
if len(arr) == 4 {
id, err = arr[3].TryBytes()
if err != nil || len(id) > storage.MaxStorageKeyLen {
return
}
}
bc.processTokenTransfer(d, transCache, h, b, note.ScriptHash, from, to, amount, id)
} }
func parseUint160(itm stackitem.Item) (util.Uint160, error) { func parseUint160(itm stackitem.Item) (util.Uint160, error) {
@ -1276,8 +1289,9 @@ func parseUint160(itm stackitem.Item) (util.Uint160, error) {
return util.Uint160DecodeBytesBE(bytes) return util.Uint160DecodeBytesBE(bytes)
} }
func (bc *Blockchain) processNEP17Transfer(cache dao.DAO, transCache map[util.Uint160]transferData, func (bc *Blockchain) processTokenTransfer(cache dao.DAO, transCache map[util.Uint160]transferData,
h util.Uint256, b *block.Block, sc util.Uint160, from util.Uint160, to util.Uint160, amount *big.Int) { h util.Uint256, b *block.Block, sc util.Uint160, from util.Uint160, to util.Uint160,
amount *big.Int, tokenID []byte) {
var id int32 var id int32
nativeContract := bc.contracts.ByHash(sc) nativeContract := bc.contracts.ByHash(sc)
if nativeContract != nil { if nativeContract != nil {
@ -1289,7 +1303,11 @@ func (bc *Blockchain) processNEP17Transfer(cache dao.DAO, transCache map[util.Ui
} }
id = assetContract.ID id = assetContract.ID
} }
transfer := &state.NEP17Transfer{ var transfer io.Serializable
var nep17xfer *state.NEP17Transfer
var isNEP11 = (tokenID != nil)
if !isNEP11 {
nep17xfer = &state.NEP17Transfer{
Asset: id, Asset: id,
From: from, From: from,
To: to, To: to,
@ -1297,47 +1315,85 @@ func (bc *Blockchain) processNEP17Transfer(cache dao.DAO, transCache map[util.Ui
Timestamp: b.Timestamp, Timestamp: b.Timestamp,
Tx: h, Tx: h,
} }
transfer = nep17xfer
} else {
nep11xfer := &state.NEP11Transfer{
NEP17Transfer: state.NEP17Transfer{
Asset: id,
From: from,
To: to,
Block: b.Index,
Timestamp: b.Timestamp,
Tx: h,
},
ID: tokenID,
}
transfer = nep11xfer
nep17xfer = &nep11xfer.NEP17Transfer
}
if !from.Equals(util.Uint160{}) { if !from.Equals(util.Uint160{}) {
_ = transfer.Amount.Neg(amount) // We already have the Int. _ = nep17xfer.Amount.Neg(amount) // We already have the Int.
if appendNEP17Transfer(cache, transCache, from, transfer) != nil { if appendTokenTransfer(cache, transCache, from, transfer, id, b.Index, isNEP11) != nil {
return return
} }
} }
if !to.Equals(util.Uint160{}) { if !to.Equals(util.Uint160{}) {
_ = transfer.Amount.Set(amount) // We already have the Int. _ = nep17xfer.Amount.Set(amount) // We already have the Int.
_ = appendNEP17Transfer(cache, transCache, to, transfer) // Nothing useful we can do. _ = appendTokenTransfer(cache, transCache, to, transfer, id, b.Index, isNEP11) // Nothing useful we can do.
} }
} }
func appendNEP17Transfer(cache dao.DAO, transCache map[util.Uint160]transferData, addr util.Uint160, transfer *state.NEP17Transfer) error { func appendTokenTransfer(cache dao.DAO, transCache map[util.Uint160]transferData, addr util.Uint160, transfer io.Serializable,
token int32, bIndex uint32, isNEP11 bool) error {
transferData, ok := transCache[addr] transferData, ok := transCache[addr]
if !ok { if !ok {
balances, err := cache.GetTokenTransferInfo(addr) balances, err := cache.GetTokenTransferInfo(addr)
if err != nil { if err != nil {
return err return err
} }
if !balances.NewNEP17Batch { if !balances.NewNEP11Batch {
trLog, err := cache.GetTokenTransferLog(addr, balances.NextNEP17Batch) trLog, err := cache.GetTokenTransferLog(addr, balances.NextNEP11Batch, true)
if err != nil { if err != nil {
return err return err
} }
transferData.Log = *trLog transferData.Log11 = *trLog
}
if !balances.NewNEP17Batch {
trLog, err := cache.GetTokenTransferLog(addr, balances.NextNEP17Batch, false)
if err != nil {
return err
}
transferData.Log17 = *trLog
} }
transferData.Info = *balances transferData.Info = *balances
} }
err := transferData.Log.Append(transfer) var (
log *state.TokenTransferLog
newBatch *bool
nextBatch *uint32
)
if !isNEP11 {
log = &transferData.Log17
newBatch = &transferData.Info.NewNEP17Batch
nextBatch = &transferData.Info.NextNEP17Batch
} else {
log = &transferData.Log11
newBatch = &transferData.Info.NewNEP11Batch
nextBatch = &transferData.Info.NextNEP11Batch
}
err := log.Append(transfer)
if err != nil { if err != nil {
return err return err
} }
transferData.Info.LastUpdated[transfer.Asset] = transfer.Block transferData.Info.LastUpdated[token] = bIndex
transferData.Info.NewNEP17Batch = transferData.Log.Size() >= state.TokenTransferBatchSize *newBatch = log.Size() >= state.TokenTransferBatchSize
if transferData.Info.NewNEP17Batch { if *newBatch {
err = cache.PutTokenTransferLog(addr, transferData.Info.NextNEP17Batch, &transferData.Log) err = cache.PutTokenTransferLog(addr, *nextBatch, isNEP11, log)
if err != nil { if err != nil {
return err return err
} }
transferData.Info.NextNEP17Batch++ *nextBatch++
transferData.Log = state.TokenTransferLog{} *log = state.TokenTransferLog{}
} }
transCache[addr] = transferData transCache[addr] = transferData
return nil return nil
@ -1350,7 +1406,7 @@ func (bc *Blockchain) ForEachNEP17Transfer(acc util.Uint160, f func(*state.NEP17
return nil return nil
} }
for i := int(balances.NextNEP17Batch); i >= 0; i-- { for i := int(balances.NextNEP17Batch); i >= 0; i-- {
lg, err := bc.dao.GetTokenTransferLog(acc, uint32(i)) lg, err := bc.dao.GetTokenTransferLog(acc, uint32(i), false)
if err != nil { if err != nil {
return nil return nil
} }
@ -1365,15 +1421,42 @@ func (bc *Blockchain) ForEachNEP17Transfer(acc util.Uint160, f func(*state.NEP17
return nil return nil
} }
// ForEachNEP11Transfer executes f for each nep11 transfer in log.
func (bc *Blockchain) ForEachNEP11Transfer(acc util.Uint160, f func(*state.NEP11Transfer) (bool, error)) error {
balances, err := bc.dao.GetTokenTransferInfo(acc)
if err != nil {
return nil
}
for i := int(balances.NextNEP11Batch); i >= 0; i-- {
lg, err := bc.dao.GetTokenTransferLog(acc, uint32(i), true)
if err != nil {
return nil
}
cont, err := lg.ForEachNEP11(f)
if err != nil {
return err
}
if !cont {
break
}
}
return nil
}
// GetNEP17Contracts returns the list of deployed NEP17 contracts. // GetNEP17Contracts returns the list of deployed NEP17 contracts.
func (bc *Blockchain) GetNEP17Contracts() []util.Uint160 { func (bc *Blockchain) GetNEP17Contracts() []util.Uint160 {
return bc.contracts.Management.GetNEP17Contracts() return bc.contracts.Management.GetNEP17Contracts()
} }
// GetNEP17LastUpdated returns a set of contract ids with the corresponding last updated // GetNEP11Contracts returns the list of deployed NEP11 contracts.
func (bc *Blockchain) GetNEP11Contracts() []util.Uint160 {
return bc.contracts.Management.GetNEP11Contracts()
}
// GetTokenLastUpdated returns a set of contract ids with the corresponding last updated
// block indexes. In case of an empty account, latest stored state synchronisation point // block indexes. In case of an empty account, latest stored state synchronisation point
// is returned under Math.MinInt32 key. // is returned under Math.MinInt32 key.
func (bc *Blockchain) GetNEP17LastUpdated(acc util.Uint160) (map[int32]uint32, error) { func (bc *Blockchain) GetTokenLastUpdated(acc util.Uint160) (map[int32]uint32, error) {
info, err := bc.dao.GetTokenTransferInfo(acc) info, err := bc.dao.GetTokenTransferInfo(acc)
if err != nil { if err != nil {
return nil, err return nil, err

View file

@ -34,6 +34,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)
ForEachNEP11Transfer(util.Uint160, func(*state.NEP11Transfer) (bool, error)) error
ForEachNEP17Transfer(util.Uint160, func(*state.NEP17Transfer) (bool, error)) error ForEachNEP17Transfer(util.Uint160, func(*state.NEP17Transfer) (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)
@ -47,8 +48,9 @@ type Blockchainer interface {
GetNativeContractScriptHash(string) (util.Uint160, error) GetNativeContractScriptHash(string) (util.Uint160, error)
GetNatives() []state.NativeContract GetNatives() []state.NativeContract
GetNextBlockValidators() ([]*keys.PublicKey, error) GetNextBlockValidators() ([]*keys.PublicKey, error)
GetNEP11Contracts() []util.Uint160
GetNEP17Contracts() []util.Uint160 GetNEP17Contracts() []util.Uint160
GetNEP17LastUpdated(acc util.Uint160) (map[int32]uint32, error) GetTokenLastUpdated(acc util.Uint160) (map[int32]uint32, error)
GetNotaryContractScriptHash() util.Uint160 GetNotaryContractScriptHash() util.Uint160
GetNotaryBalance(acc util.Uint160) *big.Int GetNotaryBalance(acc util.Uint160) *big.Int
GetPolicer() Policer GetPolicer() Policer

View file

@ -43,7 +43,7 @@ type DAO interface {
GetCurrentHeaderHeight() (i uint32, h util.Uint256, err error) GetCurrentHeaderHeight() (i uint32, h util.Uint256, err error)
GetHeaderHashes() ([]util.Uint256, error) GetHeaderHashes() ([]util.Uint256, error)
GetTokenTransferInfo(acc util.Uint160) (*state.TokenTransferInfo, error) GetTokenTransferInfo(acc util.Uint160) (*state.TokenTransferInfo, error)
GetTokenTransferLog(acc util.Uint160, index uint32) (*state.TokenTransferLog, error) GetTokenTransferLog(acc util.Uint160, index uint32, isNEP11 bool) (*state.TokenTransferLog, error)
GetStateSyncPoint() (uint32, error) GetStateSyncPoint() (uint32, error)
GetStateSyncCurrentBlockHeight() (uint32, error) GetStateSyncCurrentBlockHeight() (uint32, error)
GetStorageItem(id int32, key []byte) state.StorageItem GetStorageItem(id int32, key []byte) state.StorageItem
@ -58,7 +58,7 @@ type DAO interface {
PutContractID(id int32, hash util.Uint160) error PutContractID(id int32, hash util.Uint160) error
PutCurrentHeader(hashAndIndex []byte) error PutCurrentHeader(hashAndIndex []byte) error
PutTokenTransferInfo(acc util.Uint160, bs *state.TokenTransferInfo) error PutTokenTransferInfo(acc util.Uint160, bs *state.TokenTransferInfo) error
PutTokenTransferLog(acc util.Uint160, index uint32, lg *state.TokenTransferLog) error PutTokenTransferLog(acc util.Uint160, index uint32, isNEP11 bool, lg *state.TokenTransferLog) error
PutStateSyncPoint(p uint32) error PutStateSyncPoint(p uint32) error
PutStateSyncCurrentBlockHeight(h uint32) error PutStateSyncCurrentBlockHeight(h uint32) error
PutStorageItem(id int32, key []byte, si state.StorageItem) error PutStorageItem(id int32, key []byte, si state.StorageItem) error
@ -176,17 +176,21 @@ func (dao *Simple) putTokenTransferInfo(acc util.Uint160, bs *state.TokenTransfe
// -- start transfer log. // -- start transfer log.
func getTokenTransferLogKey(acc util.Uint160, index uint32) []byte { func getTokenTransferLogKey(acc util.Uint160, index uint32, isNEP11 bool) []byte {
key := make([]byte, 1+util.Uint160Size+4) key := make([]byte, 1+util.Uint160Size+4)
if isNEP11 {
key[0] = byte(storage.STNEP11Transfers)
} else {
key[0] = byte(storage.STNEP17Transfers) key[0] = byte(storage.STNEP17Transfers)
}
copy(key[1:], acc.BytesBE()) copy(key[1:], acc.BytesBE())
binary.LittleEndian.PutUint32(key[util.Uint160Size:], index) binary.LittleEndian.PutUint32(key[util.Uint160Size:], index)
return key return key
} }
// GetTokenTransferLog retrieves transfer log from the cache. // GetTokenTransferLog retrieves transfer log from the cache.
func (dao *Simple) GetTokenTransferLog(acc util.Uint160, index uint32) (*state.TokenTransferLog, error) { func (dao *Simple) GetTokenTransferLog(acc util.Uint160, index uint32, isNEP11 bool) (*state.TokenTransferLog, error) {
key := getTokenTransferLogKey(acc, index) key := getTokenTransferLogKey(acc, index, isNEP11)
value, err := dao.Store.Get(key) value, err := dao.Store.Get(key)
if err != nil { if err != nil {
if err == storage.ErrKeyNotFound { if err == storage.ErrKeyNotFound {
@ -198,8 +202,8 @@ func (dao *Simple) GetTokenTransferLog(acc util.Uint160, index uint32) (*state.T
} }
// PutTokenTransferLog saves given transfer log in the cache. // PutTokenTransferLog saves given transfer log in the cache.
func (dao *Simple) PutTokenTransferLog(acc util.Uint160, index uint32, lg *state.TokenTransferLog) error { func (dao *Simple) PutTokenTransferLog(acc util.Uint160, index uint32, isNEP11 bool, lg *state.TokenTransferLog) error {
key := getTokenTransferLogKey(acc, index) key := getTokenTransferLogKey(acc, index, isNEP11)
return dao.Store.Put(key, lg.Raw) return dao.Store.Put(key, lg.Raw)
} }

View file

@ -15,7 +15,7 @@ func TestGAS_Roundtrip(t *testing.T) {
bc := newTestChain(t) bc := newTestChain(t)
getUtilityTokenBalance := func(bc *Blockchain, acc util.Uint160) (*big.Int, uint32) { getUtilityTokenBalance := func(bc *Blockchain, acc util.Uint160) (*big.Int, uint32) {
lub, err := bc.GetNEP17LastUpdated(acc) lub, err := bc.GetTokenLastUpdated(acc)
require.NoError(t, err) require.NoError(t, err)
return bc.GetUtilityTokenBalance(acc), lub[bc.contracts.GAS.ID] return bc.GetUtilityTokenBalance(acc), lub[bc.contracts.GAS.ID]
} }

View file

@ -20,8 +20,9 @@ const (
// STStorage prefix. Once state exchange process is completed, all items with // STStorage prefix. Once state exchange process is completed, all items with
// STStorage prefix will be replaced with STTempStorage-prefixed ones. // STStorage prefix will be replaced with STTempStorage-prefixed ones.
STTempStorage KeyPrefix = 0x71 STTempStorage KeyPrefix = 0x71
STNEP17Transfers KeyPrefix = 0x72 STNEP11Transfers KeyPrefix = 0x72
STNEP17TransferInfo KeyPrefix = 0x73 STNEP17Transfers KeyPrefix = 0x73
STTokenTransferInfo KeyPrefix = 0x74
IXHeaderHashList KeyPrefix = 0x80 IXHeaderHashList KeyPrefix = 0x80
SYSCurrentBlock KeyPrefix = 0xc0 SYSCurrentBlock KeyPrefix = 0xc0
SYSCurrentHeader KeyPrefix = 0xc1 SYSCurrentHeader KeyPrefix = 0xc1

View file

@ -661,7 +661,7 @@ func (s *Server) getNEP17Balances(ps request.Params) (interface{}, *response.Err
Address: address.Uint160ToString(u), Address: address.Uint160ToString(u),
Balances: []result.NEP17Balance{}, Balances: []result.NEP17Balance{},
} }
lastUpdated, err := s.chain.GetNEP17LastUpdated(u) lastUpdated, err := s.chain.GetTokenLastUpdated(u)
if err != nil { if err != nil {
return nil, response.NewRPCError("Failed to get NEP17 last updated block", err.Error(), err) return nil, response.NewRPCError("Failed to get NEP17 last updated block", err.Error(), err)
} }