From 125e4231b019d41cfbd77e38e7c53b5a3a76976b Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Tue, 16 Nov 2021 23:09:04 +0300 Subject: [PATCH] core: store NEP-11 transfers, add accessor functions --- internal/fakechain/fakechain.go | 12 ++- pkg/core/blockchain.go | 149 ++++++++++++++++++++------ pkg/core/blockchainer/blockchainer.go | 4 +- pkg/core/dao/dao.go | 20 ++-- pkg/core/native_gas_test.go | 2 +- pkg/core/storage/store.go | 5 +- pkg/rpc/server/server.go | 2 +- 7 files changed, 147 insertions(+), 47 deletions(-) diff --git a/internal/fakechain/fakechain.go b/internal/fakechain/fakechain.go index 10e762dcc..d24004722 100644 --- a/internal/fakechain/fakechain.go +++ b/internal/fakechain/fakechain.go @@ -276,13 +276,23 @@ func (chain *FakeChain) GetNextBlockValidators() ([]*keys.PublicKey, error) { panic("TODO") } +// GetNEP17Contracts implements Blockchainer interface. +func (chain *FakeChain) GetNEP11Contracts() []util.Uint160 { + panic("TODO") +} + // GetNEP17Contracts implements Blockchainer interface. func (chain *FakeChain) GetNEP17Contracts() []util.Uint160 { panic("TODO") } // 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") } diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 38b2dacb4..3b5b23bdb 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -45,7 +45,7 @@ import ( // Tuning parameters. const ( headerBatchCount = 2000 - version = "0.1.4" + version = "0.1.5" defaultInitialGAS = 52000000_00000000 defaultMemPoolSize = 50000 @@ -194,8 +194,9 @@ type bcEvent struct { // transferData is used for transfer caching during storeBlock. type transferData struct { - Info state.TokenTransferInfo - Log state.TokenTransferLog + Info state.TokenTransferInfo + Log11 state.TokenTransferLog + Log17 state.TokenTransferLog } // 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 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 { aerdone <- err return @@ -1246,7 +1252,7 @@ func (bc *Blockchain) handleNotification(note *state.NotificationEvent, d dao.DA return } arr, ok := note.Item.Value().([]stackitem.Item) - if !ok || len(arr) != 3 { + if !ok || !(len(arr) == 3 || len(arr) == 4) { return } from, err := parseUint160(arr[0]) @@ -1261,7 +1267,14 @@ func (bc *Blockchain) handleNotification(note *state.NotificationEvent, d dao.DA if err != nil { 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) { @@ -1276,8 +1289,9 @@ func parseUint160(itm stackitem.Item) (util.Uint160, error) { return util.Uint160DecodeBytesBE(bytes) } -func (bc *Blockchain) processNEP17Transfer(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) { +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, tokenID []byte) { var id int32 nativeContract := bc.contracts.ByHash(sc) if nativeContract != nil { @@ -1289,55 +1303,97 @@ func (bc *Blockchain) processNEP17Transfer(cache dao.DAO, transCache map[util.Ui } id = assetContract.ID } - transfer := &state.NEP17Transfer{ - Asset: id, - From: from, - To: to, - Block: b.Index, - Timestamp: b.Timestamp, - Tx: h, + var transfer io.Serializable + var nep17xfer *state.NEP17Transfer + var isNEP11 = (tokenID != nil) + if !isNEP11 { + nep17xfer = &state.NEP17Transfer{ + Asset: id, + From: from, + To: to, + Block: b.Index, + Timestamp: b.Timestamp, + 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{}) { - _ = transfer.Amount.Neg(amount) // We already have the Int. - if appendNEP17Transfer(cache, transCache, from, transfer) != nil { + _ = nep17xfer.Amount.Neg(amount) // We already have the Int. + if appendTokenTransfer(cache, transCache, from, transfer, id, b.Index, isNEP11) != nil { return } } if !to.Equals(util.Uint160{}) { - _ = transfer.Amount.Set(amount) // We already have the Int. - _ = appendNEP17Transfer(cache, transCache, to, transfer) // Nothing useful we can do. + _ = nep17xfer.Amount.Set(amount) // We already have the Int. + _ = 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] if !ok { balances, err := cache.GetTokenTransferInfo(addr) if err != nil { return err } - if !balances.NewNEP17Batch { - trLog, err := cache.GetTokenTransferLog(addr, balances.NextNEP17Batch) + if !balances.NewNEP11Batch { + trLog, err := cache.GetTokenTransferLog(addr, balances.NextNEP11Batch, true) if err != nil { 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 } - 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 { return err } - transferData.Info.LastUpdated[transfer.Asset] = transfer.Block - transferData.Info.NewNEP17Batch = transferData.Log.Size() >= state.TokenTransferBatchSize - if transferData.Info.NewNEP17Batch { - err = cache.PutTokenTransferLog(addr, transferData.Info.NextNEP17Batch, &transferData.Log) + transferData.Info.LastUpdated[token] = bIndex + *newBatch = log.Size() >= state.TokenTransferBatchSize + if *newBatch { + err = cache.PutTokenTransferLog(addr, *nextBatch, isNEP11, log) if err != nil { return err } - transferData.Info.NextNEP17Batch++ - transferData.Log = state.TokenTransferLog{} + *nextBatch++ + *log = state.TokenTransferLog{} } transCache[addr] = transferData return nil @@ -1350,7 +1406,7 @@ func (bc *Blockchain) ForEachNEP17Transfer(acc util.Uint160, f func(*state.NEP17 return nil } 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 { return nil } @@ -1365,15 +1421,42 @@ func (bc *Blockchain) ForEachNEP17Transfer(acc util.Uint160, f func(*state.NEP17 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. func (bc *Blockchain) GetNEP17Contracts() []util.Uint160 { 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 // 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) if err != nil { return nil, err diff --git a/pkg/core/blockchainer/blockchainer.go b/pkg/core/blockchainer/blockchainer.go index c4c24573f..1b12cd9c6 100644 --- a/pkg/core/blockchainer/blockchainer.go +++ b/pkg/core/blockchainer/blockchainer.go @@ -34,6 +34,7 @@ type Blockchainer interface { GetContractScriptHash(id int32) (util.Uint160, error) GetEnrollments() ([]state.Validator, error) 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 GetHeaderHash(int) util.Uint256 GetHeader(hash util.Uint256) (*block.Header, error) @@ -47,8 +48,9 @@ type Blockchainer interface { GetNativeContractScriptHash(string) (util.Uint160, error) GetNatives() []state.NativeContract GetNextBlockValidators() ([]*keys.PublicKey, error) + GetNEP11Contracts() []util.Uint160 GetNEP17Contracts() []util.Uint160 - GetNEP17LastUpdated(acc util.Uint160) (map[int32]uint32, error) + GetTokenLastUpdated(acc util.Uint160) (map[int32]uint32, error) GetNotaryContractScriptHash() util.Uint160 GetNotaryBalance(acc util.Uint160) *big.Int GetPolicer() Policer diff --git a/pkg/core/dao/dao.go b/pkg/core/dao/dao.go index 4e5d609dd..131f3c6fd 100644 --- a/pkg/core/dao/dao.go +++ b/pkg/core/dao/dao.go @@ -43,7 +43,7 @@ type DAO interface { GetCurrentHeaderHeight() (i uint32, h util.Uint256, err error) GetHeaderHashes() ([]util.Uint256, 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) GetStateSyncCurrentBlockHeight() (uint32, error) GetStorageItem(id int32, key []byte) state.StorageItem @@ -58,7 +58,7 @@ type DAO interface { PutContractID(id int32, hash util.Uint160) error PutCurrentHeader(hashAndIndex []byte) 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 PutStateSyncCurrentBlockHeight(h uint32) 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. -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[0] = byte(storage.STNEP17Transfers) + if isNEP11 { + key[0] = byte(storage.STNEP11Transfers) + } else { + key[0] = byte(storage.STNEP17Transfers) + } copy(key[1:], acc.BytesBE()) binary.LittleEndian.PutUint32(key[util.Uint160Size:], index) return key } // GetTokenTransferLog retrieves transfer log from the cache. -func (dao *Simple) GetTokenTransferLog(acc util.Uint160, index uint32) (*state.TokenTransferLog, error) { - key := getTokenTransferLogKey(acc, index) +func (dao *Simple) GetTokenTransferLog(acc util.Uint160, index uint32, isNEP11 bool) (*state.TokenTransferLog, error) { + key := getTokenTransferLogKey(acc, index, isNEP11) value, err := dao.Store.Get(key) if err != nil { 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. -func (dao *Simple) PutTokenTransferLog(acc util.Uint160, index uint32, lg *state.TokenTransferLog) error { - key := getTokenTransferLogKey(acc, index) +func (dao *Simple) PutTokenTransferLog(acc util.Uint160, index uint32, isNEP11 bool, lg *state.TokenTransferLog) error { + key := getTokenTransferLogKey(acc, index, isNEP11) return dao.Store.Put(key, lg.Raw) } diff --git a/pkg/core/native_gas_test.go b/pkg/core/native_gas_test.go index eb64c70f6..6c6afdfc4 100644 --- a/pkg/core/native_gas_test.go +++ b/pkg/core/native_gas_test.go @@ -15,7 +15,7 @@ func TestGAS_Roundtrip(t *testing.T) { bc := newTestChain(t) getUtilityTokenBalance := func(bc *Blockchain, acc util.Uint160) (*big.Int, uint32) { - lub, err := bc.GetNEP17LastUpdated(acc) + lub, err := bc.GetTokenLastUpdated(acc) require.NoError(t, err) return bc.GetUtilityTokenBalance(acc), lub[bc.contracts.GAS.ID] } diff --git a/pkg/core/storage/store.go b/pkg/core/storage/store.go index 0888b9d38..ff9abd3c3 100644 --- a/pkg/core/storage/store.go +++ b/pkg/core/storage/store.go @@ -20,8 +20,9 @@ const ( // STStorage prefix. Once state exchange process is completed, all items with // STStorage prefix will be replaced with STTempStorage-prefixed ones. STTempStorage KeyPrefix = 0x71 - STNEP17Transfers KeyPrefix = 0x72 - STNEP17TransferInfo KeyPrefix = 0x73 + STNEP11Transfers KeyPrefix = 0x72 + STNEP17Transfers KeyPrefix = 0x73 + STTokenTransferInfo KeyPrefix = 0x74 IXHeaderHashList KeyPrefix = 0x80 SYSCurrentBlock KeyPrefix = 0xc0 SYSCurrentHeader KeyPrefix = 0xc1 diff --git a/pkg/rpc/server/server.go b/pkg/rpc/server/server.go index 415eb8f3b..99b02c382 100644 --- a/pkg/rpc/server/server.go +++ b/pkg/rpc/server/server.go @@ -661,7 +661,7 @@ func (s *Server) getNEP17Balances(ps request.Params) (interface{}, *response.Err Address: address.Uint160ToString(u), Balances: []result.NEP17Balance{}, } - lastUpdated, err := s.chain.GetNEP17LastUpdated(u) + lastUpdated, err := s.chain.GetTokenLastUpdated(u) if err != nil { return nil, response.NewRPCError("Failed to get NEP17 last updated block", err.Error(), err) }