From 3b08b5b19b143e36c4c7ffbe7ea5a2499e6ae014 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Tue, 16 Nov 2021 13:28:59 +0300 Subject: [PATCH 01/11] native: add NEP-11 tracking to management contract --- pkg/core/native/management.go | 48 ++++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/pkg/core/native/management.go b/pkg/core/native/management.go index 7403559e7..5584d03fc 100644 --- a/pkg/core/native/management.go +++ b/pkg/core/native/management.go @@ -33,6 +33,8 @@ type Management struct { mtx sync.RWMutex contracts map[util.Uint160]*state.Contract + // nep11 is a map of NEP11-compliant contracts which is updated with every PostPersist. + nep11 map[util.Uint160]struct{} // nep17 is a map of NEP17-compliant contracts which is updated with every PostPersist. nep17 map[util.Uint160]struct{} } @@ -65,6 +67,7 @@ func newManagement() *Management { var m = &Management{ ContractMD: *interop.NewContractMD(nativenames.Management, ManagementContractID), contracts: make(map[util.Uint160]*state.Contract), + nep11: make(map[util.Uint160]struct{}), nep17: make(map[util.Uint160]struct{}), } defer m.UpdateHash() @@ -453,6 +456,18 @@ func (m *Management) Metadata() *interop.ContractMD { return &m.ContractMD } +// updateContractCache saves contract in the common and NEP-related caches. It's +// an internal method that must be called with m.mtx lock taken. +func (m *Management) updateContractCache(cs *state.Contract) { + m.contracts[cs.Hash] = cs + if cs.Manifest.IsStandardSupported(manifest.NEP11StandardName) { + m.nep11[cs.Hash] = struct{}{} + } + if cs.Manifest.IsStandardSupported(manifest.NEP17StandardName) { + m.nep17[cs.Hash] = struct{}{} + } +} + // OnPersist implements Contract interface. func (m *Management) OnPersist(ic *interop.Context) error { for _, native := range ic.Natives { @@ -473,10 +488,7 @@ func (m *Management) OnPersist(ic *interop.Context) error { return fmt.Errorf("initializing %s native contract: %w", md.Name, err) } m.mtx.Lock() - m.contracts[md.Hash] = cs - if md.Manifest.IsStandardSupported(manifest.NEP17StandardName) { - m.nep17[md.Hash] = struct{}{} - } + m.updateContractCache(cs) m.mtx.Unlock() } @@ -497,10 +509,7 @@ func (m *Management) InitializeCache(d dao.DAO) error { if initErr != nil { return } - m.contracts[cs.Hash] = cs - if cs.Manifest.IsStandardSupported(manifest.NEP17StandardName) { - m.nep17[cs.Hash] = struct{}{} - } + m.updateContractCache(cs) }) return initErr } @@ -512,24 +521,33 @@ func (m *Management) PostPersist(ic *interop.Context) error { if cs != nil { continue } + delete(m.nep11, h) + delete(m.nep17, h) newCs, err := m.getContractFromDAO(ic.DAO, h) if err != nil { // Contract was destroyed. delete(m.contracts, h) - delete(m.nep17, h) continue } - m.contracts[h] = newCs - if newCs.Manifest.IsStandardSupported(manifest.NEP17StandardName) { - m.nep17[h] = struct{}{} - } else { - delete(m.nep17, h) - } + m.updateContractCache(newCs) } m.mtx.Unlock() return nil } +// GetNEP11Contracts returns hashes of all deployed contracts that support NEP-11 standard. The list +// is updated every PostPersist, so until PostPersist is called, the result for the previous block +// is returned. +func (m *Management) GetNEP11Contracts() []util.Uint160 { + m.mtx.RLock() + result := make([]util.Uint160, 0, len(m.nep11)) + for h := range m.nep11 { + result = append(result, h) + } + m.mtx.RUnlock() + return result +} + // GetNEP17Contracts returns hashes of all deployed contracts that support NEP17 standard. The list // is updated every PostPersist, so until PostPersist is called, the result for the previous block // is returned. From c63aeb38bbfaa499dd6d42bd4ecc2c0e3c13d778 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Tue, 16 Nov 2021 19:18:06 +0300 Subject: [PATCH 02/11] state: prepare for NEP-11 data tracking, refactor/rename There is a lot of similarity, so try reusing common code and use more neutral naming. --- pkg/core/blockchain.go | 34 +++--- pkg/core/dao/dao.go | 54 +++++----- pkg/core/state/nep17_test.go | 84 --------------- pkg/core/state/{nep17.go => tokens.go} | 106 ++++++++++++++----- pkg/core/state/tokens_test.go | 141 +++++++++++++++++++++++++ 5 files changed, 262 insertions(+), 157 deletions(-) delete mode 100644 pkg/core/state/nep17_test.go rename pkg/core/state/{nep17.go => tokens.go} (50%) create mode 100644 pkg/core/state/tokens_test.go diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 38172577e..38b2dacb4 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -194,8 +194,8 @@ type bcEvent struct { // transferData is used for transfer caching during storeBlock. type transferData struct { - Info state.NEP17TransferInfo - Log state.NEP17TransferLog + Info state.TokenTransferInfo + Log state.TokenTransferLog } // NewBlockchain returns a new blockchain object the will use the @@ -1004,12 +1004,12 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error return } for acc, trData := range transCache { - err = kvcache.PutNEP17TransferInfo(acc, &trData.Info) + err = kvcache.PutTokenTransferInfo(acc, &trData.Info) if err != nil { aerdone <- err return } - err = kvcache.PutNEP17TransferLog(acc, trData.Info.NextTransferBatch, &trData.Log) + err = kvcache.PutTokenTransferLog(acc, trData.Info.NextNEP17Batch, &trData.Log) if err != nil { aerdone <- err return @@ -1312,12 +1312,12 @@ func (bc *Blockchain) processNEP17Transfer(cache dao.DAO, transCache map[util.Ui func appendNEP17Transfer(cache dao.DAO, transCache map[util.Uint160]transferData, addr util.Uint160, transfer *state.NEP17Transfer) error { transferData, ok := transCache[addr] if !ok { - balances, err := cache.GetNEP17TransferInfo(addr) + balances, err := cache.GetTokenTransferInfo(addr) if err != nil { return err } - if !balances.NewBatch { - trLog, err := cache.GetNEP17TransferLog(addr, balances.NextTransferBatch) + if !balances.NewNEP17Batch { + trLog, err := cache.GetTokenTransferLog(addr, balances.NextNEP17Batch) if err != nil { return err } @@ -1330,14 +1330,14 @@ func appendNEP17Transfer(cache dao.DAO, transCache map[util.Uint160]transferData return err } transferData.Info.LastUpdated[transfer.Asset] = transfer.Block - transferData.Info.NewBatch = transferData.Log.Size() >= state.NEP17TransferBatchSize - if transferData.Info.NewBatch { - err = cache.PutNEP17TransferLog(addr, transferData.Info.NextTransferBatch, &transferData.Log) + transferData.Info.NewNEP17Batch = transferData.Log.Size() >= state.TokenTransferBatchSize + if transferData.Info.NewNEP17Batch { + err = cache.PutTokenTransferLog(addr, transferData.Info.NextNEP17Batch, &transferData.Log) if err != nil { return err } - transferData.Info.NextTransferBatch++ - transferData.Log = state.NEP17TransferLog{} + transferData.Info.NextNEP17Batch++ + transferData.Log = state.TokenTransferLog{} } transCache[addr] = transferData return nil @@ -1345,16 +1345,16 @@ func appendNEP17Transfer(cache dao.DAO, transCache map[util.Uint160]transferData // ForEachNEP17Transfer executes f for each nep17 transfer in log. func (bc *Blockchain) ForEachNEP17Transfer(acc util.Uint160, f func(*state.NEP17Transfer) (bool, error)) error { - balances, err := bc.dao.GetNEP17TransferInfo(acc) + balances, err := bc.dao.GetTokenTransferInfo(acc) if err != nil { return nil } - for i := int(balances.NextTransferBatch); i >= 0; i-- { - lg, err := bc.dao.GetNEP17TransferLog(acc, uint32(i)) + for i := int(balances.NextNEP17Batch); i >= 0; i-- { + lg, err := bc.dao.GetTokenTransferLog(acc, uint32(i)) if err != nil { return nil } - cont, err := lg.ForEach(f) + cont, err := lg.ForEachNEP17(f) if err != nil { return err } @@ -1374,7 +1374,7 @@ func (bc *Blockchain) GetNEP17Contracts() []util.Uint160 { // 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) { - info, err := bc.dao.GetNEP17TransferInfo(acc) + info, err := bc.dao.GetTokenTransferInfo(acc) if err != nil { return nil, err } diff --git a/pkg/core/dao/dao.go b/pkg/core/dao/dao.go index b15a36e97..5eac7e505 100644 --- a/pkg/core/dao/dao.go +++ b/pkg/core/dao/dao.go @@ -43,8 +43,8 @@ type DAO interface { GetCurrentBlockHeight() (uint32, error) GetCurrentHeaderHeight() (i uint32, h util.Uint256, err error) GetHeaderHashes() ([]util.Uint256, error) - GetNEP17TransferInfo(acc util.Uint160) (*state.NEP17TransferInfo, error) - GetNEP17TransferLog(acc util.Uint160, index uint32) (*state.NEP17TransferLog, error) + GetTokenTransferInfo(acc util.Uint160) (*state.TokenTransferInfo, error) + GetTokenTransferLog(acc util.Uint160, index uint32) (*state.TokenTransferLog, error) GetStateSyncPoint() (uint32, error) GetStateSyncCurrentBlockHeight() (uint32, error) GetStorageItem(id int32, key []byte) state.StorageItem @@ -58,8 +58,8 @@ type DAO interface { PutAppExecResult(aer *state.AppExecResult, buf *io.BufBinWriter) error PutContractID(id int32, hash util.Uint160) error PutCurrentHeader(hashAndIndex []byte) error - PutNEP17TransferInfo(acc util.Uint160, bs *state.NEP17TransferInfo) error - PutNEP17TransferLog(acc util.Uint160, index uint32, lg *state.NEP17TransferLog) error + PutTokenTransferInfo(acc util.Uint160, bs *state.TokenTransferInfo) error + PutTokenTransferLog(acc util.Uint160, index uint32, lg *state.TokenTransferLog) error PutStateSyncPoint(p uint32) error PutStateSyncCurrentBlockHeight(h uint32) error PutStorageItem(id int32, key []byte, si state.StorageItem) error @@ -69,7 +69,7 @@ type DAO interface { StoreAsBlock(block *block.Block, buf *io.BufBinWriter) error StoreAsCurrentBlock(block *block.Block, buf *io.BufBinWriter) error StoreAsTransaction(tx *transaction.Transaction, index uint32, buf *io.BufBinWriter) error - putNEP17TransferInfo(acc util.Uint160, bs *state.NEP17TransferInfo, buf *io.BufBinWriter) error + putTokenTransferInfo(acc util.Uint160, bs *state.TokenTransferInfo, buf *io.BufBinWriter) error } // Simple is memCached wrapper around DB, simple DAO implementation. @@ -152,10 +152,10 @@ func (dao *Simple) GetContractScriptHash(id int32) (util.Uint160, error) { // -- start nep17 transfer info. -// GetNEP17TransferInfo retrieves nep17 transfer info from the cache. -func (dao *Simple) GetNEP17TransferInfo(acc util.Uint160) (*state.NEP17TransferInfo, error) { - key := storage.AppendPrefix(storage.STNEP17TransferInfo, acc.BytesBE()) - bs := state.NewNEP17TransferInfo() +// GetTokenTransferInfo retrieves nep17 transfer info from the cache. +func (dao *Simple) GetTokenTransferInfo(acc util.Uint160) (*state.TokenTransferInfo, error) { + key := storage.AppendPrefix(storage.STTokenTransferInfo, acc.BytesBE()) + bs := state.NewTokenTransferInfo() err := dao.GetAndDecode(bs, key) if err != nil && err != storage.ErrKeyNotFound { return nil, err @@ -163,13 +163,13 @@ func (dao *Simple) GetNEP17TransferInfo(acc util.Uint160) (*state.NEP17TransferI return bs, nil } -// PutNEP17TransferInfo saves nep17 transfer info in the cache. -func (dao *Simple) PutNEP17TransferInfo(acc util.Uint160, bs *state.NEP17TransferInfo) error { - return dao.putNEP17TransferInfo(acc, bs, io.NewBufBinWriter()) +// PutTokenTransferInfo saves nep17 transfer info in the cache. +func (dao *Simple) PutTokenTransferInfo(acc util.Uint160, bs *state.TokenTransferInfo) error { + return dao.putTokenTransferInfo(acc, bs, io.NewBufBinWriter()) } -func (dao *Simple) putNEP17TransferInfo(acc util.Uint160, bs *state.NEP17TransferInfo, buf *io.BufBinWriter) error { - key := storage.AppendPrefix(storage.STNEP17TransferInfo, acc.BytesBE()) +func (dao *Simple) putTokenTransferInfo(acc util.Uint160, bs *state.TokenTransferInfo, buf *io.BufBinWriter) error { + key := storage.AppendPrefix(storage.STTokenTransferInfo, acc.BytesBE()) return dao.putWithBuffer(bs, key, buf) } @@ -177,7 +177,7 @@ func (dao *Simple) putNEP17TransferInfo(acc util.Uint160, bs *state.NEP17Transfe // -- start transfer log. -func getNEP17TransferLogKey(acc util.Uint160, index uint32) []byte { +func getTokenTransferLogKey(acc util.Uint160, index uint32) []byte { key := make([]byte, 1+util.Uint160Size+4) key[0] = byte(storage.STNEP17Transfers) copy(key[1:], acc.BytesBE()) @@ -185,34 +185,34 @@ func getNEP17TransferLogKey(acc util.Uint160, index uint32) []byte { return key } -// GetNEP17TransferLog retrieves transfer log from the cache. -func (dao *Simple) GetNEP17TransferLog(acc util.Uint160, index uint32) (*state.NEP17TransferLog, error) { - key := getNEP17TransferLogKey(acc, index) +// GetTokenTransferLog retrieves transfer log from the cache. +func (dao *Simple) GetTokenTransferLog(acc util.Uint160, index uint32) (*state.TokenTransferLog, error) { + key := getTokenTransferLogKey(acc, index) value, err := dao.Store.Get(key) if err != nil { if err == storage.ErrKeyNotFound { - return new(state.NEP17TransferLog), nil + return new(state.TokenTransferLog), nil } return nil, err } - return &state.NEP17TransferLog{Raw: value}, nil + return &state.TokenTransferLog{Raw: value}, nil } -// PutNEP17TransferLog saves given transfer log in the cache. -func (dao *Simple) PutNEP17TransferLog(acc util.Uint160, index uint32, lg *state.NEP17TransferLog) error { - key := getNEP17TransferLogKey(acc, index) +// 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) return dao.Store.Put(key, lg.Raw) } // AppendNEP17Transfer appends a single NEP17 transfer to a log. // First return value signalizes that log size has exceeded batch size. func (dao *Simple) AppendNEP17Transfer(acc util.Uint160, index uint32, isNew bool, tr *state.NEP17Transfer) (bool, error) { - var lg *state.NEP17TransferLog + var lg *state.TokenTransferLog if isNew { - lg = new(state.NEP17TransferLog) + lg = new(state.TokenTransferLog) } else { var err error - lg, err = dao.GetNEP17TransferLog(acc, index) + lg, err = dao.GetTokenTransferLog(acc, index) if err != nil { return false, err } @@ -220,7 +220,7 @@ func (dao *Simple) AppendNEP17Transfer(acc util.Uint160, index uint32, isNew boo if err := lg.Append(tr); err != nil { return false, err } - return lg.Size() >= state.NEP17TransferBatchSize, dao.PutNEP17TransferLog(acc, index, lg) + return lg.Size() >= state.TokenTransferBatchSize, dao.PutTokenTransferLog(acc, index, lg) } // -- end transfer log. diff --git a/pkg/core/state/nep17_test.go b/pkg/core/state/nep17_test.go deleted file mode 100644 index 514cbe61a..000000000 --- a/pkg/core/state/nep17_test.go +++ /dev/null @@ -1,84 +0,0 @@ -package state - -import ( - "math/big" - "math/rand" - "testing" - "time" - - "github.com/nspcc-dev/neo-go/internal/random" - "github.com/nspcc-dev/neo-go/internal/testserdes" - "github.com/nspcc-dev/neo-go/pkg/util" - "github.com/stretchr/testify/require" -) - -func TestNEP17TransferLog_Append(t *testing.T) { - r := rand.New(rand.NewSource(time.Now().UnixNano())) - expected := []*NEP17Transfer{ - randomTransfer(r), - randomTransfer(r), - randomTransfer(r), - randomTransfer(r), - } - - lg := new(NEP17TransferLog) - for _, tr := range expected { - require.NoError(t, lg.Append(tr)) - } - - require.Equal(t, len(expected), lg.Size()) - - i := len(expected) - 1 - cont, err := lg.ForEach(func(tr *NEP17Transfer) (bool, error) { - require.Equal(t, expected[i], tr) - i-- - return true, nil - }) - require.NoError(t, err) - require.True(t, cont) -} - -func BenchmarkNEP17TransferLog_Append(b *testing.B) { - r := rand.New(rand.NewSource(time.Now().UnixNano())) - ts := make([]*NEP17Transfer, NEP17TransferBatchSize) - for i := range ts { - ts[i] = randomTransfer(r) - } - - lg := new(NEP17TransferLog) - b.ResetTimer() - b.ReportAllocs() - for i := 0; i < b.N; i++ { - for _, tr := range ts { - err := lg.Append(tr) - if err != nil { - b.FailNow() - } - } - } -} - -func TestNEP17Transfer_DecodeBinary(t *testing.T) { - expected := &NEP17Transfer{ - Asset: 123, - From: util.Uint160{5, 6, 7}, - To: util.Uint160{8, 9, 10}, - Amount: *big.NewInt(42), - Block: 12345, - Timestamp: 54321, - Tx: util.Uint256{8, 5, 3}, - } - - testserdes.EncodeDecodeBinary(t, expected, new(NEP17Transfer)) -} - -func randomTransfer(r *rand.Rand) *NEP17Transfer { - return &NEP17Transfer{ - Amount: *big.NewInt(int64(r.Uint64())), - Block: r.Uint32(), - Asset: int32(random.Int(10, 10000000)), - From: random.Uint160(), - To: random.Uint160(), - Tx: random.Uint256(), - } -} diff --git a/pkg/core/state/nep17.go b/pkg/core/state/tokens.go similarity index 50% rename from pkg/core/state/nep17.go rename to pkg/core/state/tokens.go index d61e05ba2..79dc8fce1 100644 --- a/pkg/core/state/nep17.go +++ b/pkg/core/state/tokens.go @@ -4,16 +4,17 @@ import ( "bytes" "math/big" + "github.com/nspcc-dev/neo-go/pkg/core/storage" "github.com/nspcc-dev/neo-go/pkg/encoding/bigint" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/util" ) -// NEP17TransferBatchSize is the maximum number of entries for NEP17TransferLog. -const NEP17TransferBatchSize = 128 +// TokenTransferBatchSize is the maximum number of entries for TokenTransferLog. +const TokenTransferBatchSize = 128 -// NEP17TransferLog is a log of NEP17 token transfers for the specific command. -type NEP17TransferLog struct { +// TokenTransferLog is a serialized log of token transfers. +type TokenTransferLog struct { Raw []byte } @@ -36,27 +37,41 @@ type NEP17Transfer struct { Tx util.Uint256 } -// NEP17TransferInfo stores map of the NEP17 contract IDs to the balance's last updated -// block trackers along with information about NEP17 transfer batch. -type NEP17TransferInfo struct { - LastUpdated map[int32]uint32 - // NextTransferBatch stores an index of the next transfer batch. - NextTransferBatch uint32 - // NewBatch is true if batch with the `NextTransferBatch` index should be created. - NewBatch bool +// NEP11Transfer represents a single NEP-11 Transfer event. +type NEP11Transfer struct { + NEP17Transfer + + // ID is a NEP-11 token ID. + ID []byte } -// NewNEP17TransferInfo returns new NEP17TransferInfo. -func NewNEP17TransferInfo() *NEP17TransferInfo { - return &NEP17TransferInfo{ +// TokenTransferInfo stores map of the contract IDs to the balance's last updated +// block trackers along with information about NEP-17 and NEP-11 transfer batch. +type TokenTransferInfo struct { + LastUpdated map[int32]uint32 + // NextNEP11Batch stores the index of the next NEP-17 transfer batch. + NextNEP11Batch uint32 + // NextNEP17Batch stores the index of the next NEP-17 transfer batch. + NextNEP17Batch uint32 + // NewNEP11Batch is true if batch with the `NextNEP11Batch` index should be created. + NewNEP11Batch bool + // NewNEP17Batch is true if batch with the `NextNEP17Batch` index should be created. + NewNEP17Batch bool +} + +// NewTokenTransferInfo returns new TokenTransferInfo. +func NewTokenTransferInfo() *TokenTransferInfo { + return &TokenTransferInfo{ LastUpdated: make(map[int32]uint32), } } // DecodeBinary implements io.Serializable interface. -func (bs *NEP17TransferInfo) DecodeBinary(r *io.BinReader) { - bs.NextTransferBatch = r.ReadU32LE() - bs.NewBatch = r.ReadBool() +func (bs *TokenTransferInfo) DecodeBinary(r *io.BinReader) { + bs.NextNEP11Batch = r.ReadU32LE() + bs.NextNEP17Batch = r.ReadU32LE() + bs.NewNEP11Batch = r.ReadBool() + bs.NewNEP17Batch = r.ReadBool() lenBalances := r.ReadVarUint() m := make(map[int32]uint32, lenBalances) for i := 0; i < int(lenBalances); i++ { @@ -67,9 +82,11 @@ func (bs *NEP17TransferInfo) DecodeBinary(r *io.BinReader) { } // EncodeBinary implements io.Serializable interface. -func (bs *NEP17TransferInfo) EncodeBinary(w *io.BinWriter) { - w.WriteU32LE(bs.NextTransferBatch) - w.WriteBool(bs.NewBatch) +func (bs *TokenTransferInfo) EncodeBinary(w *io.BinWriter) { + w.WriteU32LE(bs.NextNEP11Batch) + w.WriteU32LE(bs.NextNEP17Batch) + w.WriteBool(bs.NewNEP11Batch) + w.WriteBool(bs.NewNEP17Batch) w.WriteVarUint(uint64(len(bs.LastUpdated))) for k, v := range bs.LastUpdated { w.WriteU32LE(uint32(k)) @@ -78,7 +95,7 @@ func (bs *NEP17TransferInfo) EncodeBinary(w *io.BinWriter) { } // Append appends single transfer to a log. -func (lg *NEP17TransferLog) Append(tr *NEP17Transfer) error { +func (lg *TokenTransferLog) Append(tr io.Serializable) error { // The first entry, set up counter. if len(lg.Raw) == 0 { lg.Raw = append(lg.Raw, 0) @@ -96,8 +113,30 @@ func (lg *NEP17TransferLog) Append(tr *NEP17Transfer) error { return nil } -// ForEach iterates over transfer log returning on first error. -func (lg *NEP17TransferLog) ForEach(f func(*NEP17Transfer) (bool, error)) (bool, error) { +// ForEachNEP11 iterates over transfer log returning on first error. +func (lg *TokenTransferLog) ForEachNEP11(f func(*NEP11Transfer) (bool, error)) (bool, error) { + if lg == nil || len(lg.Raw) == 0 { + return true, nil + } + transfers := make([]NEP11Transfer, lg.Size()) + r := io.NewBinReaderFromBuf(lg.Raw[1:]) + for i := 0; i < lg.Size(); i++ { + transfers[i].DecodeBinary(r) + } + if r.Err != nil { + return false, r.Err + } + for i := len(transfers) - 1; i >= 0; i-- { + cont, err := f(&transfers[i]) + if err != nil || !cont { + return false, err + } + } + return true, nil +} + +// ForEachNEP17 iterates over transfer log returning on first error. +func (lg *TokenTransferLog) ForEachNEP17(f func(*NEP17Transfer) (bool, error)) (bool, error) { if lg == nil || len(lg.Raw) == 0 { return true, nil } @@ -111,18 +150,15 @@ func (lg *NEP17TransferLog) ForEach(f func(*NEP17Transfer) (bool, error)) (bool, } for i := len(transfers) - 1; i >= 0; i-- { cont, err := f(&transfers[i]) - if err != nil { + if err != nil || !cont { return false, err } - if !cont { - return false, nil - } } return true, nil } // Size returns an amount of transfer written in log. -func (lg *NEP17TransferLog) Size() int { +func (lg *TokenTransferLog) Size() int { if len(lg.Raw) == 0 { return 0 } @@ -152,3 +188,15 @@ func (t *NEP17Transfer) DecodeBinary(r *io.BinReader) { amount := r.ReadVarBytes(bigint.MaxBytesLen) t.Amount = *bigint.FromBytes(amount) } + +// EncodeBinary implements io.Serializable interface. +func (t *NEP11Transfer) EncodeBinary(w *io.BinWriter) { + t.NEP17Transfer.EncodeBinary(w) + w.WriteVarBytes(t.ID) +} + +// DecodeBinary implements io.Serializable interface. +func (t *NEP11Transfer) DecodeBinary(r *io.BinReader) { + t.NEP17Transfer.DecodeBinary(r) + t.ID = r.ReadVarBytes(storage.MaxStorageKeyLen) +} diff --git a/pkg/core/state/tokens_test.go b/pkg/core/state/tokens_test.go new file mode 100644 index 000000000..120b5a567 --- /dev/null +++ b/pkg/core/state/tokens_test.go @@ -0,0 +1,141 @@ +package state + +import ( + "math/big" + "math/rand" + "testing" + "time" + + "github.com/nspcc-dev/neo-go/internal/random" + "github.com/nspcc-dev/neo-go/internal/testserdes" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/stretchr/testify/require" +) + +func TestTokenTransferLog_Append17(t *testing.T) { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + expected := []*NEP17Transfer{ + random17Transfer(r), + random17Transfer(r), + random17Transfer(r), + random17Transfer(r), + } + + lg := new(TokenTransferLog) + for _, tr := range expected { + require.NoError(t, lg.Append(tr)) + } + + require.Equal(t, len(expected), lg.Size()) + + i := len(expected) - 1 + cont, err := lg.ForEachNEP17(func(tr *NEP17Transfer) (bool, error) { + require.Equal(t, expected[i], tr) + i-- + return true, nil + }) + require.NoError(t, err) + require.True(t, cont) +} + +func TestTokenTransferLog_Append(t *testing.T) { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + expected := []*NEP11Transfer{ + random11Transfer(r), + random11Transfer(r), + random11Transfer(r), + random11Transfer(r), + } + + lg := new(TokenTransferLog) + for _, tr := range expected { + require.NoError(t, lg.Append(tr)) + } + + require.Equal(t, len(expected), lg.Size()) + + i := len(expected) - 1 + cont, err := lg.ForEachNEP11(func(tr *NEP11Transfer) (bool, error) { + require.Equal(t, expected[i], tr) + i-- + return true, nil + }) + require.NoError(t, err) + require.True(t, cont) +} + +func BenchmarkTokenTransferLog_Append(b *testing.B) { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + ts := make([]*NEP17Transfer, TokenTransferBatchSize) + for i := range ts { + ts[i] = random17Transfer(r) + } + + lg := new(TokenTransferLog) + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + for _, tr := range ts { + err := lg.Append(tr) + if err != nil { + b.FailNow() + } + } + } +} + +func TestNEP17Transfer_DecodeBinary(t *testing.T) { + expected := &NEP17Transfer{ + Asset: 123, + From: util.Uint160{5, 6, 7}, + To: util.Uint160{8, 9, 10}, + Amount: *big.NewInt(42), + Block: 12345, + Timestamp: 54321, + Tx: util.Uint256{8, 5, 3}, + } + + testserdes.EncodeDecodeBinary(t, expected, new(NEP17Transfer)) +} + +func TestNEP11Transfer_DecodeBinary(t *testing.T) { + expected := &NEP11Transfer{ + NEP17Transfer: NEP17Transfer{ + Asset: 123, + From: util.Uint160{5, 6, 7}, + To: util.Uint160{8, 9, 10}, + Amount: *big.NewInt(42), + Block: 12345, + Timestamp: 54321, + Tx: util.Uint256{8, 5, 3}, + }, + ID: []byte{42, 42, 42}, + } + + testserdes.EncodeDecodeBinary(t, expected, new(NEP11Transfer)) +} + +func random17Transfer(r *rand.Rand) *NEP17Transfer { + return &NEP17Transfer{ + Amount: *big.NewInt(int64(r.Uint64())), + Block: r.Uint32(), + Asset: int32(random.Int(10, 10000000)), + From: random.Uint160(), + To: random.Uint160(), + Tx: random.Uint256(), + } +} + +func random11Transfer(r *rand.Rand) *NEP11Transfer { + return &NEP11Transfer{ + NEP17Transfer: NEP17Transfer{ + Amount: *big.NewInt(int64(r.Uint64())), + Block: r.Uint32(), + Asset: int32(random.Int(10, 10000000)), + From: random.Uint160(), + To: random.Uint160(), + Tx: random.Uint256(), + }, + ID: random.Uint256().BytesBE(), + } +} From 095ed3f64e2de185188a5d59bc2fb955cc238fa5 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Tue, 16 Nov 2021 22:51:04 +0300 Subject: [PATCH 03/11] dao: drop unused AppendNEP17Transfer() --- pkg/core/dao/dao.go | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/pkg/core/dao/dao.go b/pkg/core/dao/dao.go index 5eac7e505..4e5d609dd 100644 --- a/pkg/core/dao/dao.go +++ b/pkg/core/dao/dao.go @@ -31,7 +31,6 @@ var ( // DAO is a data access object. type DAO interface { AppendAppExecResult(aer *state.AppExecResult, buf *io.BufBinWriter) error - AppendNEP17Transfer(acc util.Uint160, index uint32, isNew bool, tr *state.NEP17Transfer) (bool, error) DeleteBlock(h util.Uint256, buf *io.BufBinWriter) error DeleteContractID(id int32) error DeleteStorageItem(id int32, key []byte) error @@ -204,25 +203,6 @@ func (dao *Simple) PutTokenTransferLog(acc util.Uint160, index uint32, lg *state return dao.Store.Put(key, lg.Raw) } -// AppendNEP17Transfer appends a single NEP17 transfer to a log. -// First return value signalizes that log size has exceeded batch size. -func (dao *Simple) AppendNEP17Transfer(acc util.Uint160, index uint32, isNew bool, tr *state.NEP17Transfer) (bool, error) { - var lg *state.TokenTransferLog - if isNew { - lg = new(state.TokenTransferLog) - } else { - var err error - lg, err = dao.GetTokenTransferLog(acc, index) - if err != nil { - return false, err - } - } - if err := lg.Append(tr); err != nil { - return false, err - } - return lg.Size() >= state.TokenTransferBatchSize, dao.PutTokenTransferLog(acc, index, lg) -} - // -- end transfer log. // -- start notification event. From 7f40a0cfd8f0cfdc8f66c2ca85e7d042db96f7fc Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Tue, 16 Nov 2021 22:52:10 +0300 Subject: [PATCH 04/11] state: prime token transfer info with the demand for new logs Setting NewNEPXXBatch avoids the need to get them from the database which is useful for newly tracked accounts. --- pkg/core/state/tokens.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/core/state/tokens.go b/pkg/core/state/tokens.go index 79dc8fce1..beae5109c 100644 --- a/pkg/core/state/tokens.go +++ b/pkg/core/state/tokens.go @@ -62,7 +62,9 @@ type TokenTransferInfo struct { // NewTokenTransferInfo returns new TokenTransferInfo. func NewTokenTransferInfo() *TokenTransferInfo { return &TokenTransferInfo{ - LastUpdated: make(map[int32]uint32), + NewNEP11Batch: true, + NewNEP17Batch: true, + LastUpdated: make(map[int32]uint32), } } From 125e4231b019d41cfbd77e38e7c53b5a3a76976b Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Tue, 16 Nov 2021 23:09:04 +0300 Subject: [PATCH 05/11] 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) } From f3278ea5b2c519dd053bd86aea042838e82e59de Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Tue, 16 Nov 2021 23:11:23 +0300 Subject: [PATCH 06/11] core: reuse the transfer log buffer Make it a bit more efficient. --- pkg/core/blockchain.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 3b5b23bdb..bc519e1cd 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -1393,7 +1393,8 @@ func appendTokenTransfer(cache dao.DAO, transCache map[util.Uint160]transferData return err } *nextBatch++ - *log = state.TokenTransferLog{} + // Put makes a copy of it anyway. + log.Raw = log.Raw[:0] } transCache[addr] = transferData return nil From b622c1934dc459f5cb9c60ab5f7ec424296f5fce Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Tue, 16 Nov 2021 23:16:54 +0300 Subject: [PATCH 07/11] core: only save token logs if there is something to save Makes no sense storing empty ones. --- pkg/core/blockchain.go | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index bc519e1cd..0745794e6 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -1010,15 +1010,19 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error aerdone <- err return } - err = kvcache.PutTokenTransferLog(acc, trData.Info.NextNEP11Batch, true, &trData.Log11) - if err != nil { - aerdone <- err - return + if !trData.Info.NewNEP11Batch { + 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 + if !trData.Info.NewNEP17Batch { + err = kvcache.PutTokenTransferLog(acc, trData.Info.NextNEP17Batch, false, &trData.Log17) + if err != nil { + aerdone <- err + return + } } } close(aerdone) From 7f48653e66cd98d17c1b53ae9261df784ab4de9f Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Wed, 17 Nov 2021 23:04:50 +0300 Subject: [PATCH 08/11] rpc: add server-side NEP-11 tracking API --- docs/node-configuration.md | 3 + docs/rpc.md | 51 +++--- pkg/config/config.go | 1 + pkg/rpc/response/result/nep17.go | 36 ---- pkg/rpc/response/result/tokens.go | 74 ++++++++ pkg/rpc/rpc_config.go | 1 + pkg/rpc/server/server.go | 285 ++++++++++++++++++++++++++---- pkg/rpc/server/server_test.go | 98 +++++++++- pkg/rpc/server/tokens.go | 27 +++ 9 files changed, 483 insertions(+), 93 deletions(-) delete mode 100644 pkg/rpc/response/result/nep17.go create mode 100644 pkg/rpc/response/result/tokens.go create mode 100644 pkg/rpc/server/tokens.go diff --git a/docs/node-configuration.md b/docs/node-configuration.md index dbbd5dcb3..792a41a19 100644 --- a/docs/node-configuration.md +++ b/docs/node-configuration.md @@ -134,6 +134,7 @@ RPC: MaxGasInvoke: 50 MaxIteratorResultItems: 100 MaxFindResultItems: 100 + MaxNEP11Tokens: 100 Port: 10332 TLSConfig: Address: "" @@ -154,6 +155,8 @@ where: `n`, only `n` iterations are returned and truncated is true, indicating that there is still data to be returned. - `MaxFindResultItems` - the maximum number of elements for `findstates` response. +- `MaxNEP11Tokens` - limit for the number of tokens returned from + `getnep11balances` call. - `Port` is an RPC server port it should be bound to. - `TLS` section configures TLS protocol. diff --git a/docs/rpc.md b/docs/rpc.md index 901bd06ca..ad22e75cb 100644 --- a/docs/rpc.md +++ b/docs/rpc.md @@ -48,6 +48,9 @@ which would yield the response: | `getconnectioncount` | | `getcontractstate` | | `getnativecontracts` | +| `getnep11balances` | +| `getnep11properties` | +| `getnep11transfers` | | `getnep17balances` | | `getnep17transfers` | | `getnextblockvalidators` | @@ -107,28 +110,32 @@ This method doesn't work for the Ledger contract, you can get data via regular the native contract by its name (case-insensitive), unlike the C# node where it only possible for index or hash. -#### `getnep17balances` +#### `getnep11balances` and `getnep17balances` +neo-go's implementation of `getnep11balances` and `getnep17balances` does not +perform tracking of NEP-11 and NEP-17 balances for each account as it is done +in the C# node. Instead, neo-go node maintains the list of standard-compliant +contracts, i.e. those contracts that have `NEP-11` or `NEP-17` declared in the +supported standards section of the manifest. Each time balances are queried, +neo-go node asks every NEP-11/NEP-17 contract for the account balance by +invoking `balanceOf` method with the corresponding args. Invocation GAS limit +is set to be 3 GAS. All non-zero balances are included in the RPC call result. -neo-go's implementation of `getnep17balances` does not perform tracking of NEP17 -balances for each account as it is done in the C# node. Instead, neo-go node -maintains the list of NEP17-compliant contracts, i.e. those contracts that have -`NEP-17` declared in the supported standards section of the manifest. Each time -`getnep17balances` is queried, neo-go node asks every NEP17 contract for the -account balance by invoking `balanceOf` method with the corresponding args. -Invocation GAS limit is set to be 3 GAS. All non-zero NEP17 balances are included -in the RPC call result. - -Thus, if NEP17 token contract doesn't have `NEP-17` standard declared in the list -of supported standards but emits proper NEP17 `Transfer` notifications, the token -balance won't be shown in the list of NEP17 balances returned by the neo-go node -(unlike the C# node behavior). However, transfer logs of such token are still -available via `getnep17transfers` RPC call. +Thus, if token contract doesn't have proper standard declared in the list of +supported standards but emits compliant NEP-11/NEP-17 `Transfer` +notifications, the token balance won't be shown in the list of balances +returned by the neo-go node (unlike the C# node behavior). However, transfer +logs of such tokens are still available via respective `getnepXXtransfers` RPC +calls. The behaviour of the `LastUpdatedBlock` tracking for archival nodes as far as for governing token balances matches the C# node's one. For non-archival nodes and -other NEP17-compliant tokens if transfer's `LastUpdatedBlock` is lower than the +other NEP-11/NEP-17 tokens if transfer's `LastUpdatedBlock` is lower than the latest state synchronization point P the node working against, then -`LastUpdatedBlock` equals P. +`LastUpdatedBlock` equals P. For NEP-11 NFTs `LastUpdatedBlock` is equal for +all tokens of the same asset. + +#### `getnep11transfers` and `getnep17transfers` +`transfernotifyindex` is not tracked by NeoGo, thus this field is always zero. ### Unsupported methods @@ -166,12 +173,12 @@ burned). This method can be used on P2P Notary enabled networks to submit new notary payloads to be relayed from RPC to P2P. -#### Limits and paging for getnep17transfers +#### Limits and paging for getnep11transfers and getnep17transfers -`getnep17transfers` RPC call never returns more than 1000 results for one -request (within specified time frame). You can pass your own limit via an -additional parameter and then use paging to request the next batch of -transfers. +`getnep11transfers` and `getnep17transfers` RPC calls never return more than +1000 results for one request (within specified time frame). You can pass your +own limit via an additional parameter and then use paging to request the next +batch of transfers. Example requesting 10 events for address NbTiM6h8r99kpRtb428XcsUk1TzKed2gTc within 0-1600094189000 timestamps: diff --git a/pkg/config/config.go b/pkg/config/config.go index 54fca42df..2b9445ecc 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -53,6 +53,7 @@ func LoadFile(configPath string) (Config, error) { RPC: rpc.Config{ MaxIteratorResultItems: 100, MaxFindResultItems: 100, + MaxNEP11Tokens: 100, }, }, } diff --git a/pkg/rpc/response/result/nep17.go b/pkg/rpc/response/result/nep17.go deleted file mode 100644 index 50c0550d8..000000000 --- a/pkg/rpc/response/result/nep17.go +++ /dev/null @@ -1,36 +0,0 @@ -package result - -import ( - "github.com/nspcc-dev/neo-go/pkg/util" -) - -// NEP17Balances is a result for the getnep17balances RPC call. -type NEP17Balances struct { - Balances []NEP17Balance `json:"balance"` - Address string `json:"address"` -} - -// NEP17Balance represents balance for the single token contract. -type NEP17Balance struct { - Asset util.Uint160 `json:"assethash"` - Amount string `json:"amount"` - LastUpdated uint32 `json:"lastupdatedblock"` -} - -// NEP17Transfers is a result for the getnep17transfers RPC. -type NEP17Transfers struct { - Sent []NEP17Transfer `json:"sent"` - Received []NEP17Transfer `json:"received"` - Address string `json:"address"` -} - -// NEP17Transfer represents single NEP17 transfer event. -type NEP17Transfer struct { - Timestamp uint64 `json:"timestamp"` - Asset util.Uint160 `json:"assethash"` - Address string `json:"transferaddress,omitempty"` - Amount string `json:"amount"` - Index uint32 `json:"blockindex"` - NotifyIndex uint32 `json:"transfernotifyindex"` - TxHash util.Uint256 `json:"txhash"` -} diff --git a/pkg/rpc/response/result/tokens.go b/pkg/rpc/response/result/tokens.go new file mode 100644 index 000000000..cb2ca0c82 --- /dev/null +++ b/pkg/rpc/response/result/tokens.go @@ -0,0 +1,74 @@ +package result + +import ( + "github.com/nspcc-dev/neo-go/pkg/util" +) + +// NEP11Balances is a result for the getnep11balances RPC call. +type NEP11Balances struct { + Balances []NEP11AssetBalance `json:"balance"` + Address string `json:"address"` +} + +// NEP11Balance is a structure holding balance of a NEP-11 asset. +type NEP11AssetBalance struct { + Asset util.Uint160 `json:"assethash"` + Tokens []NEP11TokenBalance `json:"tokens"` +} + +// NEP11TokenBalance represents balance of a single NFT. +type NEP11TokenBalance struct { + ID string `json:"tokenid"` + Amount string `json:"amount"` + LastUpdated uint32 `json:"lastupdatedblock"` +} + +// NEP17Balances is a result for the getnep17balances RPC call. +type NEP17Balances struct { + Balances []NEP17Balance `json:"balance"` + Address string `json:"address"` +} + +// NEP17Balance represents balance for the single token contract. +type NEP17Balance struct { + Asset util.Uint160 `json:"assethash"` + Amount string `json:"amount"` + LastUpdated uint32 `json:"lastupdatedblock"` +} + +// NEP11Transfers is a result for the getnep11transfers RPC. +type NEP11Transfers struct { + Sent []NEP11Transfer `json:"sent"` + Received []NEP11Transfer `json:"received"` + Address string `json:"address"` +} + +// NEP11Transfer represents single NEP-11 transfer event. +type NEP11Transfer struct { + Timestamp uint64 `json:"timestamp"` + Asset util.Uint160 `json:"assethash"` + Address string `json:"transferaddress,omitempty"` + ID string `json:"tokenid"` + Amount string `json:"amount"` + Index uint32 `json:"blockindex"` + NotifyIndex uint32 `json:"transfernotifyindex"` + TxHash util.Uint256 `json:"txhash"` +} + +// NEP17Transfers is a result for the getnep17transfers RPC. +type NEP17Transfers struct { + Sent []NEP17Transfer `json:"sent"` + Received []NEP17Transfer `json:"received"` + Address string `json:"address"` +} + +// NEP17Transfer represents single NEP17 transfer event. +type NEP17Transfer struct { + Timestamp uint64 `json:"timestamp"` + Asset util.Uint160 `json:"assethash"` + Address string `json:"transferaddress,omitempty"` + Amount string `json:"amount"` + Index uint32 `json:"blockindex"` + NotifyIndex uint32 `json:"transfernotifyindex"` + TxHash util.Uint256 `json:"txhash"` +} diff --git a/pkg/rpc/rpc_config.go b/pkg/rpc/rpc_config.go index 1785f80a8..6e241701f 100644 --- a/pkg/rpc/rpc_config.go +++ b/pkg/rpc/rpc_config.go @@ -15,6 +15,7 @@ type ( MaxGasInvoke fixedn.Fixed8 `yaml:"MaxGasInvoke"` MaxIteratorResultItems int `yaml:"MaxIteratorResultItems"` MaxFindResultItems int `yaml:"MaxFindResultItems"` + MaxNEP11Tokens int `yaml:"MaxNEP11Tokens"` Port uint16 `yaml:"Port"` TLSConfig TLSConfig `yaml:"TLSConfig"` } diff --git a/pkg/rpc/server/server.go b/pkg/rpc/server/server.go index 99b02c382..32965c688 100644 --- a/pkg/rpc/server/server.go +++ b/pkg/rpc/server/server.go @@ -5,6 +5,7 @@ import ( "context" "crypto/elliptic" "encoding/binary" + "encoding/hex" "encoding/json" "errors" "fmt" @@ -22,10 +23,12 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/block" "github.com/nspcc-dev/neo-go/pkg/core/blockchainer" "github.com/nspcc-dev/neo-go/pkg/core/fee" + "github.com/nspcc-dev/neo-go/pkg/core/interop/iterator" "github.com/nspcc-dev/neo-go/pkg/core/mempoolevent" "github.com/nspcc-dev/neo-go/pkg/core/mpt" "github.com/nspcc-dev/neo-go/pkg/core/native" "github.com/nspcc-dev/neo-go/pkg/core/state" + "github.com/nspcc-dev/neo-go/pkg/core/storage" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" @@ -115,6 +118,9 @@ var rpcHandlers = map[string]func(*Server, request.Params) (interface{}, *respon "getconnectioncount": (*Server).getConnectionCount, "getcontractstate": (*Server).getContractState, "getnativecontracts": (*Server).getNativeContracts, + "getnep11balances": (*Server).getNEP11Balances, + "getnep11properties": (*Server).getNEP11Properties, + "getnep11transfers": (*Server).getNEP11Transfers, "getnep17balances": (*Server).getNEP17Balances, "getnep17transfers": (*Server).getNEP17Transfers, "getpeers": (*Server).getPeers, @@ -153,6 +159,13 @@ var invalidBlockHeightError = func(index int, height int) *response.Error { // doesn't set any Error function. var upgrader = websocket.Upgrader{} +var knownNEP11Properties = map[string]bool{ + "description": true, + "image": true, + "name": true, + "tokenURI": true, +} + // New creates a new Server struct. func New(chain blockchainer.Blockchainer, conf rpc.Config, coreServer *network.Server, orc *oracle.Oracle, log *zap.Logger) Server { @@ -651,6 +664,141 @@ func (s *Server) getApplicationLog(reqParams request.Params) (interface{}, *resp return result.NewApplicationLog(hash, appExecResults, trig), nil } +func (s *Server) getNEP11Tokens(h util.Uint160, acc util.Uint160, bw *io.BufBinWriter) ([]stackitem.Item, error) { + item, finalize, err := s.invokeReadOnly(bw, h, "tokensOf", acc) + if err != nil { + return nil, err + } + defer finalize() + if (item.Type() == stackitem.InteropT) && iterator.IsIterator(item) { + vals, _ := iterator.Values(item, s.config.MaxNEP11Tokens) + return vals, nil + } + return nil, fmt.Errorf("invalid `tokensOf` result type %s", item.String()) +} + +func (s *Server) getNEP11Balances(ps request.Params) (interface{}, *response.Error) { + u, err := ps.Value(0).GetUint160FromAddressOrHex() + if err != nil { + return nil, response.ErrInvalidParams + } + + bs := &result.NEP11Balances{ + Address: address.Uint160ToString(u), + Balances: []result.NEP11AssetBalance{}, + } + lastUpdated, err := s.chain.GetTokenLastUpdated(u) + if err != nil { + return nil, response.NewRPCError("Failed to get NEP11 last updated block", err.Error(), err) + } + var count int + stateSyncPoint := lastUpdated[math.MinInt32] + bw := io.NewBufBinWriter() +contract_loop: + for _, h := range s.chain.GetNEP11Contracts() { + toks, err := s.getNEP11Tokens(h, u, bw) + if err != nil { + continue + } + if len(toks) == 0 { + continue + } + cs := s.chain.GetContractState(h) + if cs == nil { + continue + } + isDivisible := (cs.Manifest.ABI.GetMethod("balanceOf", 2) != nil) + lub, ok := lastUpdated[cs.ID] + if !ok { + cfg := s.chain.GetConfig() + if !cfg.P2PStateExchangeExtensions && cfg.RemoveUntraceableBlocks { + return nil, response.NewInternalServerError(fmt.Sprintf("failed to get LastUpdatedBlock for balance of %s token", cs.Hash.StringLE()), nil) + } + lub = stateSyncPoint + } + bs.Balances = append(bs.Balances, result.NEP11AssetBalance{ + Asset: h, + Tokens: make([]result.NEP11TokenBalance, 0, len(toks)), + }) + curAsset := &bs.Balances[len(bs.Balances)-1] + for i := range toks { + id, err := toks[i].TryBytes() + if err != nil || len(id) > storage.MaxStorageKeyLen { + continue + } + var amount = "1" + if isDivisible { + balance, err := s.getTokenBalance(h, u, id, bw) + if err != nil { + continue + } + if balance.Sign() == 0 { + continue + } + amount = balance.String() + } + count++ + curAsset.Tokens = append(curAsset.Tokens, result.NEP11TokenBalance{ + ID: hex.EncodeToString(id), + Amount: amount, + LastUpdated: lub, + }) + if count >= s.config.MaxNEP11Tokens { + break contract_loop + } + } + } + return bs, nil +} + +func (s *Server) invokeNEP11Properties(h util.Uint160, id []byte, bw *io.BufBinWriter) ([]stackitem.MapElement, error) { + item, finalize, err := s.invokeReadOnly(bw, h, "properties", id) + if err != nil { + return nil, err + } + defer finalize() + if item.Type() != stackitem.MapT { + return nil, fmt.Errorf("invalid `properties` result type %s", item.String()) + } + return item.Value().([]stackitem.MapElement), nil +} + +func (s *Server) getNEP11Properties(ps request.Params) (interface{}, *response.Error) { + asset, err := ps.Value(0).GetUint160FromAddressOrHex() + if err != nil { + return nil, response.ErrInvalidParams + } + token, err := ps.Value(1).GetBytesHex() + if err != nil { + return nil, response.ErrInvalidParams + } + props, err := s.invokeNEP11Properties(asset, token, nil) + if err != nil { + return nil, response.NewRPCError("failed to get NEP-11 properties", err.Error(), err) + } + res := make(map[string]interface{}) + for _, kv := range props { + key, err := kv.Key.TryBytes() + if err != nil { + continue + } + var val interface{} + if knownNEP11Properties[string(key)] || kv.Value.Type() != stackitem.AnyT { + v, err := kv.Value.TryBytes() + if err != nil { + continue + } + if knownNEP11Properties[string(key)] { + val = string(v) + } else { + val = v + } + } + res[string(key)] = val + } + return res, nil +} + func (s *Server) getNEP17Balances(ps request.Params) (interface{}, *response.Error) { u, err := ps.Value(0).GetUint160FromAddressOrHex() if err != nil { @@ -668,7 +816,7 @@ func (s *Server) getNEP17Balances(ps request.Params) (interface{}, *response.Err stateSyncPoint := lastUpdated[math.MinInt32] bw := io.NewBufBinWriter() for _, h := range s.chain.GetNEP17Contracts() { - balance, err := s.getNEP17Balance(h, u, bw) + balance, err := s.getTokenBalance(h, u, nil, bw) if err != nil { continue } @@ -696,30 +844,53 @@ func (s *Server) getNEP17Balances(ps request.Params) (interface{}, *response.Err return bs, nil } -func (s *Server) getNEP17Balance(h util.Uint160, acc util.Uint160, bw *io.BufBinWriter) (*big.Int, error) { +func (s *Server) invokeReadOnly(bw *io.BufBinWriter, h util.Uint160, method string, params ...interface{}) (stackitem.Item, func(), error) { if bw == nil { bw = io.NewBufBinWriter() } else { bw.Reset() } - emit.AppCall(bw.BinWriter, h, "balanceOf", callflag.ReadStates, acc) + emit.AppCall(bw.BinWriter, h, method, callflag.ReadStates|callflag.AllowCall, params...) if bw.Err != nil { - return nil, fmt.Errorf("failed to create `balanceOf` invocation script: %w", bw.Err) + return nil, nil, fmt.Errorf("failed to create `%s` invocation script: %w", method, bw.Err) } script := bw.Bytes() tx := &transaction.Transaction{Script: script} - v, finalize := s.chain.GetTestVM(trigger.Application, tx, nil) - defer finalize() + b, err := s.getFakeNextBlock() + if err != nil { + return nil, nil, err + } + v, finalize := s.chain.GetTestVM(trigger.Application, tx, b) v.GasLimit = core.HeaderVerificationGasLimit v.LoadScriptWithFlags(script, callflag.All) - err := v.Run() + err = v.Run() if err != nil { - return nil, fmt.Errorf("failed to run `balanceOf` for %s: %w", h.StringLE(), err) + finalize() + return nil, nil, fmt.Errorf("failed to run `%s` for %s: %w", method, h.StringLE(), err) } if v.Estack().Len() != 1 { - return nil, fmt.Errorf("invalid `balanceOf` return values count: expected 1, got %d", v.Estack().Len()) + finalize() + return nil, nil, fmt.Errorf("invalid `%s` return values count: expected 1, got %d", method, v.Estack().Len()) } - res, err := v.Estack().Pop().Item().TryInteger() + return v.Estack().Pop().Item(), finalize, nil +} + +func (s *Server) getTokenBalance(h util.Uint160, acc util.Uint160, id []byte, bw *io.BufBinWriter) (*big.Int, error) { + var ( + item stackitem.Item + finalize func() + err error + ) + if id == nil { // NEP-17 and NEP-11 generic. + item, finalize, err = s.invokeReadOnly(bw, h, "balanceOf", acc) + } else { // NEP-11 divisible. + item, finalize, err = s.invokeReadOnly(bw, h, "balanceOf", acc, id) + } + if err != nil { + return nil, err + } + finalize() + res, err := item.TryInteger() if err != nil { return nil, fmt.Errorf("unexpected `balanceOf` result type: %w", err) } @@ -776,7 +947,15 @@ func getTimestampsAndLimit(ps request.Params, index int) (uint64, uint64, int, i return start, end, limit, page, nil } +func (s *Server) getNEP11Transfers(ps request.Params) (interface{}, *response.Error) { + return s.getTokenTransfers(ps, true) +} + func (s *Server) getNEP17Transfers(ps request.Params) (interface{}, *response.Error) { + return s.getTokenTransfers(ps, false) +} + +func (s *Server) getTokenTransfers(ps request.Params, isNEP11 bool) (interface{}, *response.Error) { u, err := ps.Value(0).GetUint160FromAddressOrHex() if err != nil { return nil, response.ErrInvalidParams @@ -787,33 +966,37 @@ func (s *Server) getNEP17Transfers(ps request.Params) (interface{}, *response.Er return nil, response.NewInvalidParamsError(err.Error(), err) } - bs := &result.NEP17Transfers{ + bs := &tokenTransfers{ Address: address.Uint160ToString(u), - Received: []result.NEP17Transfer{}, - Sent: []result.NEP17Transfer{}, + Received: []interface{}{}, + Sent: []interface{}{}, } cache := make(map[int32]util.Uint160) var resCount, frameCount int - err = s.chain.ForEachNEP17Transfer(u, func(tr *state.NEP17Transfer) (bool, error) { + // handleTransfer returns items to be added into received and sent arrays + // along with a continue flag and error. + var handleTransfer = func(tr *state.NEP17Transfer) (*result.NEP17Transfer, *result.NEP17Transfer, bool, error) { + var received, sent *result.NEP17Transfer + // Iterating from newest to oldest, not yet reached required // time frame, continue looping. if tr.Timestamp > end { - return true, nil + return nil, nil, true, nil } // Iterating from newest to oldest, moved past required // time frame, stop looping. if tr.Timestamp < start { - return false, nil + return nil, nil, false, nil } frameCount++ // Using limits, not yet reached required page. if limit != 0 && page*limit >= frameCount { - return true, nil + return nil, nil, true, nil } h, err := s.getHash(tr.Asset, cache) if err != nil { - return false, err + return nil, nil, false, err } transfer := result.NEP17Transfer{ @@ -827,24 +1010,51 @@ func (s *Server) getNEP17Transfers(ps request.Params) (interface{}, *response.Er if !tr.From.Equals(util.Uint160{}) { transfer.Address = address.Uint160ToString(tr.From) } - bs.Received = append(bs.Received, transfer) + received = &result.NEP17Transfer{} + *received = transfer // Make a copy, transfer is to be modified below. } else { transfer.Amount = new(big.Int).Neg(&tr.Amount).String() if !tr.To.Equals(util.Uint160{}) { transfer.Address = address.Uint160ToString(tr.To) } - bs.Sent = append(bs.Sent, transfer) + sent = &result.NEP17Transfer{} + *sent = transfer } resCount++ - // Using limits, reached limit. - if limit != 0 && resCount >= limit { - return false, nil - } - return true, nil - }) + // Check limits for continue flag. + return received, sent, !(limit != 0 && resCount >= limit), nil + } + if !isNEP11 { + err = s.chain.ForEachNEP17Transfer(u, func(tr *state.NEP17Transfer) (bool, error) { + r, s, res, err := handleTransfer(tr) + if err == nil { + if r != nil { + bs.Received = append(bs.Received, r) + } + if s != nil { + bs.Sent = append(bs.Sent, s) + } + } + return res, err + }) + } else { + err = s.chain.ForEachNEP11Transfer(u, func(tr *state.NEP11Transfer) (bool, error) { + r, s, res, err := handleTransfer(&tr.NEP17Transfer) + if err == nil { + id := hex.EncodeToString(tr.ID) + if r != nil { + bs.Received = append(bs.Received, nep17TransferToNEP11(r, id)) + } + if s != nil { + bs.Sent = append(bs.Sent, nep17TransferToNEP11(s, id)) + } + } + return res, err + }) + } if err != nil { - return nil, response.NewInternalServerError("invalid NEP17 transfer log", err) + return nil, response.NewInternalServerError("invalid transfer log", err) } return bs, nil } @@ -1444,12 +1654,7 @@ func (s *Server) invokeContractVerify(reqParams request.Params) (interface{}, *r return s.runScriptInVM(trigger.Verification, invocationScript, scriptHash, tx) } -// runScriptInVM runs given script in a new test VM and returns the invocation -// result. The script is either a simple script in case of `application` trigger -// witness invocation script in case of `verification` trigger (it pushes `verify` -// arguments on stack before verification). In case of contract verification -// contractScriptHash should be specified. -func (s *Server) runScriptInVM(t trigger.Type, script []byte, contractScriptHash util.Uint160, tx *transaction.Transaction) (*result.Invoke, *response.Error) { +func (s *Server) getFakeNextBlock() (*block.Block, error) { // When transferring funds, script execution does no auto GAS claim, // because it depends on persisting tx height. // This is why we provide block here. @@ -1457,10 +1662,22 @@ func (s *Server) runScriptInVM(t trigger.Type, script []byte, contractScriptHash b.Index = s.chain.BlockHeight() + 1 hdr, err := s.chain.GetHeader(s.chain.GetHeaderHash(int(s.chain.BlockHeight()))) if err != nil { - return nil, response.NewInternalServerError("can't get last block", err) + return nil, err } b.Timestamp = hdr.Timestamp + uint64(s.chain.GetConfig().SecondsPerBlock*int(time.Second/time.Millisecond)) + return b, nil +} +// runScriptInVM runs given script in a new test VM and returns the invocation +// result. The script is either a simple script in case of `application` trigger +// witness invocation script in case of `verification` trigger (it pushes `verify` +// arguments on stack before verification). In case of contract verification +// contractScriptHash should be specified. +func (s *Server) runScriptInVM(t trigger.Type, script []byte, contractScriptHash util.Uint160, tx *transaction.Transaction) (*result.Invoke, *response.Error) { + b, err := s.getFakeNextBlock() + if err != nil { + return nil, response.NewInternalServerError("can't create fake block", err) + } vm, finalize := s.chain.GetTestVM(t, tx, b) vm.GasLimit = int64(s.config.MaxGasInvoke) if t == trigger.Verification { diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index af7c194f2..a380157f4 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -68,6 +68,20 @@ const invokescriptContractAVM = "VwIADBQBDAMOBQYMDQIODw0DDgcJAAAAANswcGhB+CfsjCG const nameServiceContractHash = "3a602b3e7cfd760850bfac44f4a9bb0ebad3e2dc" +var NNSHash = util.Uint160{0xdc, 0xe2, 0xd3, 0xba, 0x0e, 0xbb, 0xa9, 0xf4, 0x44, 0xac, 0xbf, 0x50, 0x08, 0x76, 0xfd, 0x7c, 0x3e, 0x2b, 0x60, 0x3a} + +var nep11Reg = &result.NEP11Balances{ + Address: "Nhfg3TbpwogLvDGVvAvqyThbsHgoSUKwtn", + Balances: []result.NEP11AssetBalance{{ + Asset: NNSHash, + Tokens: []result.NEP11TokenBalance{{ + ID: "6e656f2e636f6d", + Amount: "1", + LastUpdated: 14, + }}, + }}, +} + var rpcTestCases = map[string][]rpcTestCase{ "getapplicationlog": { { @@ -213,7 +227,89 @@ var rpcTestCases = map[string][]rpcTestCase{ fail: true, }, }, - + "getnep11balances": { + { + name: "no params", + params: `[]`, + fail: true, + }, + { + name: "invalid address", + params: `["notahex"]`, + fail: true, + }, + { + name: "positive", + params: `["` + testchain.PrivateKeyByID(0).GetScriptHash().StringLE() + `"]`, + result: func(e *executor) interface{} { return nep11Reg }, + }, + { + name: "positive_address", + params: `["` + address.Uint160ToString(testchain.PrivateKeyByID(0).GetScriptHash()) + `"]`, + result: func(e *executor) interface{} { return nep11Reg }, + }, + }, + "getnep11properties": { + { + name: "no params", + params: `[]`, + fail: true, + }, + { + name: "invalid address", + params: `["notahex"]`, + fail: true, + }, + { + name: "no token", + params: `["` + NNSHash.StringLE() + `"]`, + fail: true, + }, + { + name: "bad token", + params: `["` + NNSHash.StringLE() + `", "abcdef"]`, + fail: true, + }, + { + name: "positive", + params: `["` + NNSHash.StringLE() + `", "6e656f2e636f6d"]`, + result: func(e *executor) interface{} { + return &map[string]interface{}{ + "name": "neo.com", + "expiration": "bhORxoMB", + } + }, + }, + }, + "getnep11transfers": { + { + name: "no params", + params: `[]`, + fail: true, + }, + { + name: "invalid address", + params: `["notahex"]`, + fail: true, + }, + { + name: "invalid timestamp", + params: `["` + testchain.PrivateKeyByID(0).Address() + `", "notanumber"]`, + fail: true, + }, + { + name: "invalid stop timestamp", + params: `["` + testchain.PrivateKeyByID(0).Address() + `", "1", "blah"]`, + fail: true, + }, + { + name: "positive", + params: `["` + testchain.PrivateKeyByID(0).Address() + `", 0]`, + result: func(e *executor) interface{} { + return &result.NEP11Transfers{Sent: []result.NEP11Transfer{}, Received: []result.NEP11Transfer{{Timestamp: 0x17c6edfe76e, Asset: util.Uint160{0xdc, 0xe2, 0xd3, 0xba, 0xe, 0xbb, 0xa9, 0xf4, 0x44, 0xac, 0xbf, 0x50, 0x8, 0x76, 0xfd, 0x7c, 0x3e, 0x2b, 0x60, 0x3a}, Address: "", ID: "6e656f2e636f6d", Amount: "1", Index: 0xe, NotifyIndex: 0x0, TxHash: util.Uint256{0x5b, 0x5a, 0x5b, 0xae, 0xf2, 0xc5, 0x63, 0x8a, 0x2e, 0xcc, 0x77, 0x27, 0xd9, 0x6b, 0xb9, 0xda, 0x3a, 0x7f, 0x30, 0xaa, 0xcf, 0xda, 0x7f, 0x8a, 0x10, 0xd3, 0x23, 0xbf, 0xd, 0x1f, 0x28, 0x69}}}, Address: "Nhfg3TbpwogLvDGVvAvqyThbsHgoSUKwtn"} + }, + }, + }, "getnep17balances": { { name: "no params", diff --git a/pkg/rpc/server/tokens.go b/pkg/rpc/server/tokens.go new file mode 100644 index 000000000..608c0b818 --- /dev/null +++ b/pkg/rpc/server/tokens.go @@ -0,0 +1,27 @@ +package server + +import ( + "github.com/nspcc-dev/neo-go/pkg/rpc/response/result" +) + +// tokenTransfers is a generic type used to represent NEP-11 and NEP-17 transfers. +type tokenTransfers struct { + Sent []interface{} `json:"sent"` + Received []interface{} `json:"received"` + Address string `json:"address"` +} + +// nep17TransferToNEP11 adds an ID to provided NEP-17 transfer and returns a new +// NEP-11 structure. +func nep17TransferToNEP11(t17 *result.NEP17Transfer, id string) result.NEP11Transfer { + return result.NEP11Transfer{ + Timestamp: t17.Timestamp, + Asset: t17.Asset, + Address: t17.Address, + ID: id, + Amount: t17.Amount, + Index: t17.Index, + NotifyIndex: t17.NotifyIndex, + TxHash: t17.TxHash, + } +} From 1375918b63845758e0ecca13c69421f11916f7c2 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 18 Nov 2021 00:08:10 +0300 Subject: [PATCH 09/11] rpc: add client-side NEP-11 methods --- pkg/rpc/client/doc.go | 3 + pkg/rpc/client/rpc.go | 80 ++++++++++++++++-- pkg/rpc/client/rpc_test.go | 131 ++++++++++++++++++++++++++++++ pkg/rpc/response/result/tokens.go | 8 ++ pkg/rpc/server/server.go | 11 +-- 5 files changed, 217 insertions(+), 16 deletions(-) diff --git a/pkg/rpc/client/doc.go b/pkg/rpc/client/doc.go index 5fb0bc10b..5ad1eac12 100644 --- a/pkg/rpc/client/doc.go +++ b/pkg/rpc/client/doc.go @@ -29,6 +29,9 @@ Supported methods getconnectioncount getcontractstate getnativecontracts + getnep11balances + getnep11properties + getnep11transfers getnep17balances getnep17transfers getpeers diff --git a/pkg/rpc/client/rpc.go b/pkg/rpc/client/rpc.go index b0a02824e..da10e46d5 100644 --- a/pkg/rpc/client/rpc.go +++ b/pkg/rpc/client/rpc.go @@ -1,6 +1,8 @@ package client import ( + "encoding/base64" + "encoding/hex" "errors" "fmt" @@ -267,6 +269,16 @@ func (c *Client) GetNativeContracts() ([]state.NativeContract, error) { return resp, nil } +// GetNEP11Balances is a wrapper for getnep11balances RPC. +func (c *Client) GetNEP11Balances(address util.Uint160) (*result.NEP11Balances, error) { + params := request.NewRawParams(address.StringLE()) + resp := new(result.NEP11Balances) + if err := c.performRequest("getnep11balances", params, resp); err != nil { + return nil, err + } + return resp, nil +} + // GetNEP17Balances is a wrapper for getnep17balances RPC. func (c *Client) GetNEP17Balances(address util.Uint160) (*result.NEP17Balances, error) { params := request.NewRawParams(address.StringLE()) @@ -277,12 +289,53 @@ func (c *Client) GetNEP17Balances(address util.Uint160) (*result.NEP17Balances, return resp, nil } -// GetNEP17Transfers is a wrapper for getnep17transfers RPC. Address parameter -// is mandatory, while all the others are optional. Start and stop parameters -// are supported since neo-go 0.77.0 and limit and page since neo-go 0.78.0. -// These parameters are positional in the JSON-RPC call, you can't specify limit -// and not specify start/stop for example. -func (c *Client) GetNEP17Transfers(address string, start, stop *uint64, limit, page *int) (*result.NEP17Transfers, error) { +// GetNEP11Properties is a wrapper for getnep11properties RPC. We recommend using +// NEP11Properties method instead of this to receive and work with proper VM types, +// this method is provided mostly for the sake of completeness. For well-known +// attributes like "description", "image", "name" and "tokenURI" it returns strings, +// while for all other ones []byte (which can be nil). +func (c *Client) GetNEP11Properties(asset util.Uint160, token []byte) (map[string]interface{}, error) { + params := request.NewRawParams(asset.StringLE(), hex.EncodeToString(token)) + resp := make(map[string]interface{}) + if err := c.performRequest("getnep11properties", params, &resp); err != nil { + return nil, err + } + for k, v := range resp { + if v == nil { + continue + } + str, ok := v.(string) + if !ok { + return nil, errors.New("value is not a string") + } + if result.KnownNEP11Properties[k] { + continue + } + val, err := base64.StdEncoding.DecodeString(str) + if err != nil { + return nil, err + } + resp[k] = val + } + return resp, nil +} + +// GetNEP11Transfers is a wrapper for getnep11transfers RPC. Address parameter +// is mandatory, while all the others are optional. Limit and page parameters are +// only supported by NeoGo servers and can only be specified with start and stop. +func (c *Client) GetNEP11Transfers(address string, start, stop *uint64, limit, page *int) (*result.NEP11Transfers, error) { + params, err := packTransfersParams(address, start, stop, limit, page) + if err != nil { + return nil, err + } + resp := new(result.NEP11Transfers) + if err := c.performRequest("getnep11transfers", *params, resp); err != nil { + return nil, err + } + return resp, nil +} + +func packTransfersParams(address string, start, stop *uint64, limit, page *int) (*request.RawParams, error) { params := request.NewRawParams(address) if start != nil { params.Values = append(params.Values, *start) @@ -302,8 +355,21 @@ func (c *Client) GetNEP17Transfers(address string, start, stop *uint64, limit, p } else if stop != nil || limit != nil || page != nil { return nil, errors.New("bad parameters") } + return ¶ms, nil +} + +// GetNEP17Transfers is a wrapper for getnep17transfers RPC. Address parameter +// is mandatory, while all the others are optional. Start and stop parameters +// are supported since neo-go 0.77.0 and limit and page since neo-go 0.78.0. +// These parameters are positional in the JSON-RPC call, you can't specify limit +// and not specify start/stop for example. +func (c *Client) GetNEP17Transfers(address string, start, stop *uint64, limit, page *int) (*result.NEP17Transfers, error) { + params, err := packTransfersParams(address, start, stop, limit, page) + if err != nil { + return nil, err + } resp := new(result.NEP17Transfers) - if err := c.performRequest("getnep17transfers", params, resp); err != nil { + if err := c.performRequest("getnep17transfers", *params, resp); err != nil { return nil, err } return resp, nil diff --git a/pkg/rpc/client/rpc_test.go b/pkg/rpc/client/rpc_test.go index c14ddc130..a7a58a47d 100644 --- a/pkg/rpc/client/rpc_test.go +++ b/pkg/rpc/client/rpc_test.go @@ -532,6 +532,36 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ }, }, }, + "getnep11balances": { + { + name: "positive", + invoke: func(c *Client) (interface{}, error) { + hash, err := util.Uint160DecodeStringLE("1aada0032aba1ef6d1f07bbd8bec1d85f5380fb3") + if err != nil { + panic(err) + } + return c.GetNEP11Balances(hash) + }, + serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"balance":[{"assethash":"a48b6e1291ba24211ad11bb90ae2a10bf1fcd5a8","tokens":[{"tokenid":"abcdef","amount":"1","lastupdatedblock":251604}]}],"address":"NcEkNmgWmf7HQVQvzhxpengpnt4DXjmZLe"}}`, + result: func(c *Client) interface{} { + hash, err := util.Uint160DecodeStringLE("a48b6e1291ba24211ad11bb90ae2a10bf1fcd5a8") + if err != nil { + panic(err) + } + return &result.NEP11Balances{ + Balances: []result.NEP11AssetBalance{{ + Asset: hash, + Tokens: []result.NEP11TokenBalance{{ + ID: "abcdef", + Amount: "1", + LastUpdated: 251604, + }}, + }}, + Address: "NcEkNmgWmf7HQVQvzhxpengpnt4DXjmZLe", + } + }, + }, + }, "getnep17balances": { { name: "positive", @@ -559,6 +589,61 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ }, }, }, + "getnep11properties": { + { + name: "positive", + invoke: func(c *Client) (interface{}, error) { + hash, err := util.Uint160DecodeStringLE("1aada0032aba1ef6d1f07bbd8bec1d85f5380fb3") + if err != nil { + panic(err) + } + return c.GetNEP11Properties(hash, []byte("abcdef")) + }, // NcEkNmgWmf7HQVQvzhxpengpnt4DXjmZLe + serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"name":"sometoken","field1":"c29tZXRoaW5n","field2":null}}`, + result: func(c *Client) interface{} { + return map[string]interface{}{ + "name": "sometoken", + "field1": []byte("something"), + "field2": nil, + } + }, + }, + }, + "getnep11transfers": { + { + name: "positive", + invoke: func(c *Client) (interface{}, error) { + return c.GetNEP11Transfers("NcEkNmgWmf7HQVQvzhxpengpnt4DXjmZLe", nil, nil, nil, nil) + }, + serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"sent":[],"received":[{"timestamp":1555651816,"assethash":"600c4f5200db36177e3e8a09e9f18e2fc7d12a0f","transferaddress":"NfgHwwTi3wHAS8aFAN243C5vGbkYDpqLHP","amount":"1","tokenid":"abcdef","blockindex":436036,"transfernotifyindex":0,"txhash":"df7683ece554ecfb85cf41492c5f143215dd43ef9ec61181a28f922da06aba58"}],"address":"NcEkNmgWmf7HQVQvzhxpengpnt4DXjmZLe"}}`, + result: func(c *Client) interface{} { + assetHash, err := util.Uint160DecodeStringLE("600c4f5200db36177e3e8a09e9f18e2fc7d12a0f") + if err != nil { + panic(err) + } + txHash, err := util.Uint256DecodeStringLE("df7683ece554ecfb85cf41492c5f143215dd43ef9ec61181a28f922da06aba58") + if err != nil { + panic(err) + } + return &result.NEP11Transfers{ + Sent: []result.NEP11Transfer{}, + Received: []result.NEP11Transfer{ + { + Timestamp: 1555651816, + Asset: assetHash, + Address: "NfgHwwTi3wHAS8aFAN243C5vGbkYDpqLHP", + Amount: "1", + ID: "abcdef", + Index: 436036, + NotifyIndex: 0, + TxHash: txHash, + }, + }, + Address: "NcEkNmgWmf7HQVQvzhxpengpnt4DXjmZLe", + } + }, + }, + }, "getnep17transfers": { { name: "positive", @@ -1052,6 +1137,22 @@ type rpcClientErrorCase struct { } var rpcClientErrorCases = map[string][]rpcClientErrorCase{ + `{"jsonrpc":"2.0","id":1,"result":{"name":"name","bad":42}}`: { + { + name: "getnep11properties_unmarshalling_error", + invoke: func(c *Client) (interface{}, error) { + return c.GetNEP11Properties(util.Uint160{}, []byte{}) + }, + }, + }, + `{"jsonrpc":"2.0","id":1,"result":{"name":100500,"good":"c29tZXRoaW5n"}}`: { + { + name: "getnep11properties_unmarshalling_error", + invoke: func(c *Client) (interface{}, error) { + return c.GetNEP11Properties(util.Uint160{}, []byte{}) + }, + }, + }, `{"jsonrpc":"2.0","id":1,"result":"not-a-hex-string"}`: { { name: "getblock_not_a_hex_response", @@ -1229,12 +1330,30 @@ var rpcClientErrorCases = map[string][]rpcClientErrorCase{ return c.GetContractStateByHash(util.Uint160{}) }, }, + { + name: "getnep11balances_invalid_params_error", + invoke: func(c *Client) (interface{}, error) { + return c.GetNEP11Balances(util.Uint160{}) + }, + }, { name: "getnep17balances_invalid_params_error", invoke: func(c *Client) (interface{}, error) { return c.GetNEP17Balances(util.Uint160{}) }, }, + { + name: "getnep11properties_invalid_params_error", + invoke: func(c *Client) (interface{}, error) { + return c.GetNEP11Properties(util.Uint160{}, []byte{}) + }, + }, + { + name: "getnep11transfers_invalid_params_error", + invoke: func(c *Client) (interface{}, error) { + return c.GetNEP11Transfers("", nil, nil, nil, nil) + }, + }, { name: "getnep17transfers_invalid_params_error", invoke: func(c *Client) (interface{}, error) { @@ -1416,12 +1535,24 @@ var rpcClientErrorCases = map[string][]rpcClientErrorCase{ return c.GetContractStateByHash(util.Uint160{}) }, }, + { + name: "getnep11balances_unmarshalling_error", + invoke: func(c *Client) (interface{}, error) { + return c.GetNEP11Balances(util.Uint160{}) + }, + }, { name: "getnep17balances_unmarshalling_error", invoke: func(c *Client) (interface{}, error) { return c.GetNEP17Balances(util.Uint160{}) }, }, + { + name: "getnep11transfers_unmarshalling_error", + invoke: func(c *Client) (interface{}, error) { + return c.GetNEP11Transfers("", nil, nil, nil, nil) + }, + }, { name: "getnep17transfers_unmarshalling_error", invoke: func(c *Client) (interface{}, error) { diff --git a/pkg/rpc/response/result/tokens.go b/pkg/rpc/response/result/tokens.go index cb2ca0c82..1afc9fcdb 100644 --- a/pkg/rpc/response/result/tokens.go +++ b/pkg/rpc/response/result/tokens.go @@ -72,3 +72,11 @@ type NEP17Transfer struct { NotifyIndex uint32 `json:"transfernotifyindex"` TxHash util.Uint256 `json:"txhash"` } + +// KnownNEP11Properties contains a list of well-known NEP-11 token property names. +var KnownNEP11Properties = map[string]bool{ + "description": true, + "image": true, + "name": true, + "tokenURI": true, +} diff --git a/pkg/rpc/server/server.go b/pkg/rpc/server/server.go index 32965c688..70ec058a5 100644 --- a/pkg/rpc/server/server.go +++ b/pkg/rpc/server/server.go @@ -159,13 +159,6 @@ var invalidBlockHeightError = func(index int, height int) *response.Error { // doesn't set any Error function. var upgrader = websocket.Upgrader{} -var knownNEP11Properties = map[string]bool{ - "description": true, - "image": true, - "name": true, - "tokenURI": true, -} - // New creates a new Server struct. func New(chain blockchainer.Blockchainer, conf rpc.Config, coreServer *network.Server, orc *oracle.Oracle, log *zap.Logger) Server { @@ -783,12 +776,12 @@ func (s *Server) getNEP11Properties(ps request.Params) (interface{}, *response.E continue } var val interface{} - if knownNEP11Properties[string(key)] || kv.Value.Type() != stackitem.AnyT { + if result.KnownNEP11Properties[string(key)] || kv.Value.Type() != stackitem.AnyT { v, err := kv.Value.TryBytes() if err != nil { continue } - if knownNEP11Properties[string(key)] { + if result.KnownNEP11Properties[string(key)] { val = string(v) } else { val = v From ce9d0b22cfcb77ac6f9f17053bdf07644fb30972 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 18 Nov 2021 16:37:42 +0300 Subject: [PATCH 10/11] *: use NEP-XX naming consistently in docs/comments Standards are NEP-11 and NEP-17, not NEP11, not NEP17, not anything else. Variable/function names of course can use whatever fits, but documents and comments should be consistent wrt this. --- CHANGELOG.md | 20 ++++++++++---------- cli/nep11_test.go | 4 ++-- cli/nep17_test.go | 2 +- cli/testdata/verify.go | 4 ++-- cli/wallet/nep11.go | 26 +++++++++++++------------- cli/wallet/nep17.go | 16 ++++++++-------- cli/wallet/wallet.go | 4 ++-- docs/cli.md | 2 +- examples/README.md | 2 +- examples/nft-nd-nns/nns.go | 2 +- examples/nft-nd/nft.go | 2 +- pkg/core/blockchain.go | 10 +++++----- pkg/core/dao/dao.go | 8 ++++---- pkg/core/helper_test.go | 2 +- pkg/core/native/management.go | 4 ++-- pkg/core/native/management_test.go | 4 ++-- pkg/core/state/native_state.go | 2 +- pkg/core/state/tokens.go | 4 ++-- pkg/rpc/client/nep11.go | 26 +++++++++++++------------- pkg/rpc/client/nep17.go | 16 ++++++++-------- pkg/rpc/server/server.go | 4 ++-- pkg/smartcontract/manifest/manifest.go | 4 ++-- pkg/wallet/token_test.go | 6 +++--- 23 files changed, 87 insertions(+), 87 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca54b0783..82fca29cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -294,7 +294,7 @@ Improvements: StateRootInHeader option (#2028) * cached GAS per vote value leads to substantially improved block processing speed (#2032) - * failed native NEP17 transfers now still re-save the old value to the + * failed native NEP-17 transfers now still re-save the old value to the storage making state dumps compatible with C# (#2034) * refactored and renamed some stackitem package functions, added proper error values in all cases (#2045) @@ -412,30 +412,30 @@ Bugs fixed: ## 0.95.0 "Sharpness" (17 May 2021) This version mostly implements N3 RC2 protocol changes (and is fully -RC2-compatible), but also brings NEP11 CLI support and small improvements for +RC2-compatible), but also brings NEP-11 CLI support and small improvements for node operators. Please note that this release is incompatible with 0.94.1 and there won't be long-term support provided for it. New features: - * CLI command for NEP17 transfers now accepts `data` parameter for the + * CLI command for NEP-17 transfers now accepts `data` parameter for the transfer (#1906) * contract deployment CLI comand now also accepts `data` parameter for `_deploy` method (#1907) - * NEP11 and NEP17 transfers from CLI can now have multiple signers (#1914) + * NEP-11 and NEP-17 transfers from CLI can now have multiple signers (#1914) * `System.Runtime.BurnGas` interop was added to burn some GAS as well as `refuel` GAS contract method to add GAS to current execution environment (#1937) * port number announced via P2P can now differ from actual port node is bound to if new option is used (#1942) - * CLI now supports full set of NEP11 commands, including balance and + * CLI now supports full set of NEP-11 commands, including balance and transfers (#1918) * string split, memory search and compare functions added to stdlib (#1943) * MaxValidUntilBlockIncrement can now be configured (#1963) Behavior changes: - * `data` parameter is now passed in a different way to NEP17 RPC client + * `data` parameter is now passed in a different way to NEP-17 RPC client methods (#1906) * default (used if nothing else specified) signer scope is now `CalledByEntry` in CLI and RPC (#1909) @@ -454,7 +454,7 @@ Behavior changes: * node will reread TLS certificates (if any configured) on SIGHUP (#1945) * contract trusts are now expressed with permission descriptors in manifest (#1946) - * NEP11 transfers now also support `data` parameter (#1950) + * NEP-11 transfers now also support `data` parameter (#1950) * N3 RC2 testnet magic differs from N2 RC1 testnet (#1951, #1954) * stdlib encoding/decoding methods now only accept inputs no longer than 1024 bytes (#1943) @@ -485,7 +485,7 @@ Improvements: Bugs fixed: * `getproof` RPC request returned successful results in some cases where it should fail - * `Transfer` events with invalid numbers were not rejected by NEP17 tracking + * `Transfer` events with invalid numbers were not rejected by NEP-17 tracking code (#1902) * boolean function parameters were not accepted by `invokefunction` RPC call implementation (#1920) @@ -744,9 +744,9 @@ New features: Behavior changes: * VM CLI now supports and requires manifests to properly work with NEF files (#1642) - * NEP17-related CLI commands now output GAS balance as floating point numbers + * NEP-17-related CLI commands now output GAS balance as floating point numbers (#1654) - * `--from` parameter can be omitted now for NEP17 CLI transfers, the default + * `--from` parameter can be omitted now for NEP-17 CLI transfers, the default wallet address will be used in this case (#1655) * native contracts now use more specific types for methods arguments (#1657) * some native contract names and IDs have changed (#1622, #1660) diff --git a/cli/nep11_test.go b/cli/nep11_test.go index 576d7184c..a5852f25d 100644 --- a/cli/nep11_test.go +++ b/cli/nep11_test.go @@ -53,7 +53,7 @@ func TestNEP11Import(t *testing.T) { // already exists e.RunWithError(t, append(args, "--token", nnsContractHash.StringLE())...) - // not a NEP11 token + // not a NEP-11 token e.RunWithError(t, append(args, "--token", neoContractHash.StringLE())...) t.Run("Info", func(t *testing.T) { @@ -283,7 +283,7 @@ func TestNEP11_OwnerOf_BalanceOf_Transfer(t *testing.T) { e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...) checkBalanceResult(t, nftOwnerAddr, "1") // tokenID1 - // transfer: good, to NEP11-Payable contract, with data + // transfer: good, to NEP-11-Payable contract, with data verifyH := deployVerifyContract(t, e) cmdTransfer = []string{ "neo-go", "wallet", "nep11", "transfer", diff --git a/cli/nep17_test.go b/cli/nep17_test.go index bc86b0bca..d71c458cf 100644 --- a/cli/nep17_test.go +++ b/cli/nep17_test.go @@ -321,7 +321,7 @@ func TestNEP17ImportToken(t *testing.T) { "--wallet", walletPath, "--token", address.Uint160ToString(neoContractHash)) // try address instead of sh - // not a NEP17 token + // not a NEP-17 token e.RunWithError(t, "neo-go", "wallet", "nep17", "import", "--rpc-endpoint", "http://"+e.RPC.Addr, "--wallet", walletPath, diff --git a/cli/testdata/verify.go b/cli/testdata/verify.go index 0e6c51352..3f399c9f6 100644 --- a/cli/testdata/verify.go +++ b/cli/testdata/verify.go @@ -12,8 +12,8 @@ func Verify() bool { func OnNEP17Payment(from interop.Hash160, amount int, data interface{}) { } -// OnNEP11Payment notifies about NEP11 payment. You don't call this method directly, -// instead it's called by NEP11 contract when you transfer funds from your address +// OnNEP11Payment notifies about NEP-11 payment. You don't call this method directly, +// instead it's called by NEP-11 contract when you transfer funds from your address // to the address of this NFT contract. func OnNEP11Payment(from interop.Hash160, amount int, token []byte, data interface{}) { runtime.Notify("OnNEP11Payment", from, amount, token, data) diff --git a/cli/wallet/nep11.go b/cli/wallet/nep11.go index e0393484e..b22d30245 100644 --- a/cli/wallet/nep11.go +++ b/cli/wallet/nep11.go @@ -52,14 +52,14 @@ func newNEP11Commands() []cli.Command { }, { Name: "import", - Usage: "import NEP11 token to a wallet", + Usage: "import NEP-11 token to a wallet", UsageText: "import --wallet --rpc-endpoint --timeout