forked from TrueCloudLab/neoneo-go
Merge pull request #748 from nspcc-dev/feature/splitnep5
core: store NEP5 balances separately from account
This commit is contained in:
commit
d9a83373ed
11 changed files with 236 additions and 80 deletions
|
@ -29,7 +29,7 @@ import (
|
||||||
// Tuning parameters.
|
// Tuning parameters.
|
||||||
const (
|
const (
|
||||||
headerBatchCount = 2000
|
headerBatchCount = 2000
|
||||||
version = "0.0.7"
|
version = "0.0.8"
|
||||||
|
|
||||||
// This one comes from C# code and it's different from the constant used
|
// This one comes from C# code and it's different from the constant used
|
||||||
// when creating an asset with Neo.Asset.Create interop call. It looks
|
// when creating an asset with Neo.Asset.Create interop call. It looks
|
||||||
|
@ -763,44 +763,46 @@ func (bc *Blockchain) processNEP5Transfer(cache *cachedDao, tx *transaction.Tran
|
||||||
Tx: tx.Hash(),
|
Tx: tx.Hash(),
|
||||||
}
|
}
|
||||||
if !fromAddr.Equals(util.Uint160{}) {
|
if !fromAddr.Equals(util.Uint160{}) {
|
||||||
acc, err := cache.GetAccountStateOrNew(fromAddr)
|
balances, err := cache.GetNEP5Balances(fromAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
bs := acc.NEP5Balances[sc]
|
bs := balances.Trackers[sc]
|
||||||
if bs == nil {
|
|
||||||
bs = new(state.NEP5Tracker)
|
|
||||||
acc.NEP5Balances[sc] = bs
|
|
||||||
}
|
|
||||||
bs.Balance -= amount
|
bs.Balance -= amount
|
||||||
bs.LastUpdatedBlock = b.Index
|
bs.LastUpdatedBlock = b.Index
|
||||||
if err := cache.PutAccountState(acc); err != nil {
|
balances.Trackers[sc] = bs
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
transfer.Amount = -amount
|
transfer.Amount = -amount
|
||||||
if err := cache.AppendNEP5Transfer(fromAddr, transfer); err != nil {
|
isBig, err := cache.AppendNEP5Transfer(fromAddr, balances.NextTransferBatch, transfer)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if isBig {
|
||||||
|
balances.NextTransferBatch++
|
||||||
|
}
|
||||||
|
if err := cache.PutNEP5Balances(fromAddr, balances); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !toAddr.Equals(util.Uint160{}) {
|
if !toAddr.Equals(util.Uint160{}) {
|
||||||
acc, err := cache.GetAccountStateOrNew(toAddr)
|
balances, err := cache.GetNEP5Balances(toAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
bs := acc.NEP5Balances[sc]
|
bs := balances.Trackers[sc]
|
||||||
if bs == nil {
|
|
||||||
bs = new(state.NEP5Tracker)
|
|
||||||
acc.NEP5Balances[sc] = bs
|
|
||||||
}
|
|
||||||
bs.Balance += amount
|
bs.Balance += amount
|
||||||
bs.LastUpdatedBlock = b.Index
|
bs.LastUpdatedBlock = b.Index
|
||||||
if err := cache.PutAccountState(acc); err != nil {
|
balances.Trackers[sc] = bs
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
transfer.Amount = amount
|
transfer.Amount = amount
|
||||||
if err := cache.AppendNEP5Transfer(toAddr, transfer); err != nil {
|
isBig, err := cache.AppendNEP5Transfer(toAddr, balances.NextTransferBatch, transfer)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if isBig {
|
||||||
|
balances.NextTransferBatch++
|
||||||
|
}
|
||||||
|
if err := cache.PutNEP5Balances(toAddr, balances); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -808,11 +810,28 @@ func (bc *Blockchain) processNEP5Transfer(cache *cachedDao, tx *transaction.Tran
|
||||||
|
|
||||||
// GetNEP5TransferLog returns NEP5 transfer log for the acc.
|
// GetNEP5TransferLog returns NEP5 transfer log for the acc.
|
||||||
func (bc *Blockchain) GetNEP5TransferLog(acc util.Uint160) *state.NEP5TransferLog {
|
func (bc *Blockchain) GetNEP5TransferLog(acc util.Uint160) *state.NEP5TransferLog {
|
||||||
lg, err := bc.dao.GetNEP5TransferLog(acc)
|
balances, err := bc.dao.GetNEP5Balances(acc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return lg
|
result := new(state.NEP5TransferLog)
|
||||||
|
for i := uint32(0); i <= balances.NextTransferBatch; i++ {
|
||||||
|
lg, err := bc.dao.GetNEP5TransferLog(acc, i)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
result.Raw = append(result.Raw, lg.Raw...)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNEP5Balances returns NEP5 balances for the acc.
|
||||||
|
func (bc *Blockchain) GetNEP5Balances(acc util.Uint160) *state.NEP5Balances {
|
||||||
|
bs, err := bc.dao.GetNEP5Balances(acc)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return bs
|
||||||
}
|
}
|
||||||
|
|
||||||
// LastBatch returns last persisted storage batch.
|
// LastBatch returns last persisted storage batch.
|
||||||
|
|
|
@ -36,6 +36,7 @@ type Blockchainer interface {
|
||||||
GetAccountState(util.Uint160) *state.Account
|
GetAccountState(util.Uint160) *state.Account
|
||||||
GetAppExecResult(util.Uint256) (*state.AppExecResult, error)
|
GetAppExecResult(util.Uint256) (*state.AppExecResult, error)
|
||||||
GetNEP5TransferLog(util.Uint160) *state.NEP5TransferLog
|
GetNEP5TransferLog(util.Uint160) *state.NEP5TransferLog
|
||||||
|
GetNEP5Balances(util.Uint160) *state.NEP5Balances
|
||||||
GetValidators(txes ...*transaction.Transaction) ([]*keys.PublicKey, error)
|
GetValidators(txes ...*transaction.Transaction) ([]*keys.PublicKey, error)
|
||||||
GetScriptHashesForVerifying(*transaction.Transaction) ([]util.Uint160, error)
|
GetScriptHashesForVerifying(*transaction.Transaction) ([]util.Uint160, error)
|
||||||
GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem
|
GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem
|
||||||
|
|
|
@ -14,6 +14,8 @@ type cachedDao struct {
|
||||||
accounts map[util.Uint160]*state.Account
|
accounts map[util.Uint160]*state.Account
|
||||||
contracts map[util.Uint160]*state.Contract
|
contracts map[util.Uint160]*state.Contract
|
||||||
unspents map[util.Uint256]*state.UnspentCoin
|
unspents map[util.Uint256]*state.UnspentCoin
|
||||||
|
balances map[util.Uint160]*state.NEP5Balances
|
||||||
|
transfers map[util.Uint160]map[uint32]*state.NEP5TransferLog
|
||||||
}
|
}
|
||||||
|
|
||||||
// newCachedDao returns new cachedDao wrapping around given backing store.
|
// newCachedDao returns new cachedDao wrapping around given backing store.
|
||||||
|
@ -21,7 +23,9 @@ func newCachedDao(backend storage.Store) *cachedDao {
|
||||||
accs := make(map[util.Uint160]*state.Account)
|
accs := make(map[util.Uint160]*state.Account)
|
||||||
ctrs := make(map[util.Uint160]*state.Contract)
|
ctrs := make(map[util.Uint160]*state.Contract)
|
||||||
unspents := make(map[util.Uint256]*state.UnspentCoin)
|
unspents := make(map[util.Uint256]*state.UnspentCoin)
|
||||||
return &cachedDao{*newDao(backend), accs, ctrs, unspents}
|
balances := make(map[util.Uint160]*state.NEP5Balances)
|
||||||
|
transfers := make(map[util.Uint160]map[uint32]*state.NEP5TransferLog)
|
||||||
|
return &cachedDao{*newDao(backend), accs, ctrs, unspents, balances, transfers}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAccountStateOrNew retrieves Account from cache or underlying Store
|
// GetAccountStateOrNew retrieves Account from cache or underlying Store
|
||||||
|
@ -85,6 +89,52 @@ func (cd *cachedDao) PutUnspentCoinState(hash util.Uint256, ucs *state.UnspentCo
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetNEP5Balances retrieves NEP5Balances for the acc.
|
||||||
|
func (cd *cachedDao) GetNEP5Balances(acc util.Uint160) (*state.NEP5Balances, error) {
|
||||||
|
if bs := cd.balances[acc]; bs != nil {
|
||||||
|
return bs, nil
|
||||||
|
}
|
||||||
|
return cd.dao.GetNEP5Balances(acc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PutNEP5Balances saves NEP5Balances for the acc.
|
||||||
|
func (cd *cachedDao) PutNEP5Balances(acc util.Uint160, bs *state.NEP5Balances) error {
|
||||||
|
cd.balances[acc] = bs
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNEP5TransferLog retrieves NEP5TransferLog for the acc.
|
||||||
|
func (cd *cachedDao) GetNEP5TransferLog(acc util.Uint160, index uint32) (*state.NEP5TransferLog, error) {
|
||||||
|
ts := cd.transfers[acc]
|
||||||
|
if ts != nil && ts[index] != nil {
|
||||||
|
return ts[index], nil
|
||||||
|
}
|
||||||
|
return cd.dao.GetNEP5TransferLog(acc, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PutNEP5TransferLog saves NEP5TransferLog for the acc.
|
||||||
|
func (cd *cachedDao) PutNEP5TransferLog(acc util.Uint160, index uint32, bs *state.NEP5TransferLog) error {
|
||||||
|
ts := cd.transfers[acc]
|
||||||
|
if ts == nil {
|
||||||
|
ts = make(map[uint32]*state.NEP5TransferLog, 2)
|
||||||
|
cd.transfers[acc] = ts
|
||||||
|
}
|
||||||
|
ts[index] = bs
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendNEP5Transfer appends new transfer to a transfer event log.
|
||||||
|
func (cd *cachedDao) AppendNEP5Transfer(acc util.Uint160, index uint32, tr *state.NEP5Transfer) (bool, error) {
|
||||||
|
lg, err := cd.GetNEP5TransferLog(acc, index)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if err := lg.Append(tr); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return lg.Size() >= nep5TransferBatchSize, cd.PutNEP5TransferLog(acc, index, lg)
|
||||||
|
}
|
||||||
|
|
||||||
// Persist flushes all the changes made into the (supposedly) persistent
|
// Persist flushes all the changes made into the (supposedly) persistent
|
||||||
// underlying store.
|
// underlying store.
|
||||||
func (cd *cachedDao) Persist() (int, error) {
|
func (cd *cachedDao) Persist() (int, error) {
|
||||||
|
@ -100,5 +150,19 @@ func (cd *cachedDao) Persist() (int, error) {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for acc, bs := range cd.balances {
|
||||||
|
err := cd.dao.PutNEP5Balances(acc, bs)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for acc, ts := range cd.transfers {
|
||||||
|
for ind, lg := range ts {
|
||||||
|
err := cd.dao.PutNEP5TransferLog(acc, ind, lg)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return cd.dao.Persist()
|
return cd.dao.Persist()
|
||||||
}
|
}
|
||||||
|
|
|
@ -135,11 +135,42 @@ func (dao *dao) DeleteContractState(hash util.Uint160) error {
|
||||||
|
|
||||||
// -- end contracts.
|
// -- end contracts.
|
||||||
|
|
||||||
|
// -- start nep5 balances.
|
||||||
|
|
||||||
|
// GetNEP5Balances retrieves nep5 balances from the cache.
|
||||||
|
func (dao *dao) GetNEP5Balances(acc util.Uint160) (*state.NEP5Balances, error) {
|
||||||
|
key := storage.AppendPrefix(storage.STNEP5Balances, acc.BytesBE())
|
||||||
|
bs := state.NewNEP5Balances()
|
||||||
|
err := dao.GetAndDecode(bs, key)
|
||||||
|
if err != nil && err != storage.ErrKeyNotFound {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return bs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNEP5Balances saves nep5 balances from the cache.
|
||||||
|
func (dao *dao) PutNEP5Balances(acc util.Uint160, bs *state.NEP5Balances) error {
|
||||||
|
key := storage.AppendPrefix(storage.STNEP5Balances, acc.BytesBE())
|
||||||
|
return dao.Put(bs, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- end nep5 balances.
|
||||||
|
|
||||||
// -- start transfer log.
|
// -- start transfer log.
|
||||||
|
|
||||||
|
const nep5TransferBatchSize = 128
|
||||||
|
|
||||||
|
func getNEP5TransferLogKey(acc util.Uint160, index uint32) []byte {
|
||||||
|
key := make([]byte, 1+util.Uint160Size+4)
|
||||||
|
key[0] = byte(storage.STNEP5Transfers)
|
||||||
|
copy(key[1:], acc.BytesBE())
|
||||||
|
binary.LittleEndian.PutUint32(key[util.Uint160Size:], index)
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
// GetNEP5TransferLog retrieves transfer log from the cache.
|
// GetNEP5TransferLog retrieves transfer log from the cache.
|
||||||
func (dao *dao) GetNEP5TransferLog(acc util.Uint160) (*state.NEP5TransferLog, error) {
|
func (dao *dao) GetNEP5TransferLog(acc util.Uint160, index uint32) (*state.NEP5TransferLog, error) {
|
||||||
key := storage.AppendPrefix(storage.STNEP5Transfers, acc.BytesBE())
|
key := getNEP5TransferLogKey(acc, index)
|
||||||
value, err := dao.store.Get(key)
|
value, err := dao.store.Get(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == storage.ErrKeyNotFound {
|
if err == storage.ErrKeyNotFound {
|
||||||
|
@ -151,24 +182,25 @@ func (dao *dao) GetNEP5TransferLog(acc util.Uint160) (*state.NEP5TransferLog, er
|
||||||
}
|
}
|
||||||
|
|
||||||
// PutNEP5TransferLog saves given transfer log in the cache.
|
// PutNEP5TransferLog saves given transfer log in the cache.
|
||||||
func (dao *dao) PutNEP5TransferLog(acc util.Uint160, lg *state.NEP5TransferLog) error {
|
func (dao *dao) PutNEP5TransferLog(acc util.Uint160, index uint32, lg *state.NEP5TransferLog) error {
|
||||||
key := storage.AppendPrefix(storage.STNEP5Transfers, acc.BytesBE())
|
key := getNEP5TransferLogKey(acc, index)
|
||||||
return dao.store.Put(key, lg.Raw)
|
return dao.store.Put(key, lg.Raw)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AppendNEP5Transfer appends a single NEP5 transfer to a log.
|
// AppendNEP5Transfer appends a single NEP5 transfer to a log.
|
||||||
func (dao *dao) AppendNEP5Transfer(acc util.Uint160, tr *state.NEP5Transfer) error {
|
// First return value signalizes that log size has exceeded batch size.
|
||||||
lg, err := dao.GetNEP5TransferLog(acc)
|
func (dao *dao) AppendNEP5Transfer(acc util.Uint160, index uint32, tr *state.NEP5Transfer) (bool, error) {
|
||||||
|
lg, err := dao.GetNEP5TransferLog(acc, index)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != storage.ErrKeyNotFound {
|
if err != storage.ErrKeyNotFound {
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
lg = new(state.NEP5TransferLog)
|
lg = new(state.NEP5TransferLog)
|
||||||
}
|
}
|
||||||
if err := lg.Append(tr); err != nil {
|
if err := lg.Append(tr); err != nil {
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
return dao.PutNEP5TransferLog(acc, lg)
|
return lg.Size() >= nep5TransferBatchSize, dao.PutNEP5TransferLog(acc, index, lg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- end transfer log.
|
// -- end transfer log.
|
||||||
|
|
|
@ -35,9 +35,6 @@ type Account struct {
|
||||||
Votes []*keys.PublicKey
|
Votes []*keys.PublicKey
|
||||||
Balances map[util.Uint256][]UnspentBalance
|
Balances map[util.Uint256][]UnspentBalance
|
||||||
Unclaimed []UnclaimedBalance
|
Unclaimed []UnclaimedBalance
|
||||||
// NEP5Balances is a map of the NEP5 contract hashes
|
|
||||||
// to the corresponding structures.
|
|
||||||
NEP5Balances map[util.Uint160]*NEP5Tracker
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAccount returns a new Account object.
|
// NewAccount returns a new Account object.
|
||||||
|
@ -49,8 +46,6 @@ func NewAccount(scriptHash util.Uint160) *Account {
|
||||||
Votes: []*keys.PublicKey{},
|
Votes: []*keys.PublicKey{},
|
||||||
Balances: make(map[util.Uint256][]UnspentBalance),
|
Balances: make(map[util.Uint256][]UnspentBalance),
|
||||||
Unclaimed: []UnclaimedBalance{},
|
Unclaimed: []UnclaimedBalance{},
|
||||||
|
|
||||||
NEP5Balances: make(map[util.Uint160]*NEP5Tracker),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,16 +70,6 @@ func (s *Account) DecodeBinary(br *io.BinReader) {
|
||||||
}
|
}
|
||||||
|
|
||||||
br.ReadArray(&s.Unclaimed)
|
br.ReadArray(&s.Unclaimed)
|
||||||
|
|
||||||
lenBalances = br.ReadVarUint()
|
|
||||||
s.NEP5Balances = make(map[util.Uint160]*NEP5Tracker, lenBalances)
|
|
||||||
for i := 0; i < int(lenBalances); i++ {
|
|
||||||
var key util.Uint160
|
|
||||||
var tr NEP5Tracker
|
|
||||||
br.ReadBytes(key[:])
|
|
||||||
tr.DecodeBinary(br)
|
|
||||||
s.NEP5Balances[key] = &tr
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncodeBinary encodes Account to the given BinWriter.
|
// EncodeBinary encodes Account to the given BinWriter.
|
||||||
|
@ -104,12 +89,6 @@ func (s *Account) EncodeBinary(bw *io.BinWriter) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bw.WriteArray(s.Unclaimed)
|
bw.WriteArray(s.Unclaimed)
|
||||||
|
|
||||||
bw.WriteVarUint(uint64(len(s.NEP5Balances)))
|
|
||||||
for k, v := range s.NEP5Balances {
|
|
||||||
bw.WriteBytes(k[:])
|
|
||||||
v.EncodeBinary(bw)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecodeBinary implements io.Serializable interface.
|
// DecodeBinary implements io.Serializable interface.
|
||||||
|
|
|
@ -41,6 +41,46 @@ type NEP5Transfer struct {
|
||||||
Tx util.Uint256
|
Tx util.Uint256
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NEP5Balances is a map of the NEP5 contract hashes
|
||||||
|
// to the corresponding structures.
|
||||||
|
type NEP5Balances struct {
|
||||||
|
Trackers map[util.Uint160]NEP5Tracker
|
||||||
|
// NextTransferBatch stores an index of the next transfer batch.
|
||||||
|
NextTransferBatch uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNEP5Balances returns new NEP5Balances.
|
||||||
|
func NewNEP5Balances() *NEP5Balances {
|
||||||
|
return &NEP5Balances{
|
||||||
|
Trackers: make(map[util.Uint160]NEP5Tracker),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeBinary implements io.Serializable interface.
|
||||||
|
func (bs *NEP5Balances) DecodeBinary(r *io.BinReader) {
|
||||||
|
bs.NextTransferBatch = r.ReadU32LE()
|
||||||
|
lenBalances := r.ReadVarUint()
|
||||||
|
m := make(map[util.Uint160]NEP5Tracker, lenBalances)
|
||||||
|
for i := 0; i < int(lenBalances); i++ {
|
||||||
|
var key util.Uint160
|
||||||
|
var tr NEP5Tracker
|
||||||
|
r.ReadBytes(key[:])
|
||||||
|
tr.DecodeBinary(r)
|
||||||
|
m[key] = tr
|
||||||
|
}
|
||||||
|
bs.Trackers = m
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeBinary implements io.Serializable interface.
|
||||||
|
func (bs *NEP5Balances) EncodeBinary(w *io.BinWriter) {
|
||||||
|
w.WriteU32LE(bs.NextTransferBatch)
|
||||||
|
w.WriteVarUint(uint64(len(bs.Trackers)))
|
||||||
|
for k, v := range bs.Trackers {
|
||||||
|
w.WriteBytes(k[:])
|
||||||
|
v.EncodeBinary(w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Append appends single transfer to a log.
|
// Append appends single transfer to a log.
|
||||||
func (lg *NEP5TransferLog) Append(tr *NEP5Transfer) error {
|
func (lg *NEP5TransferLog) Append(tr *NEP5Transfer) error {
|
||||||
w := io.NewBufBinWriter()
|
w := io.NewBufBinWriter()
|
||||||
|
@ -70,6 +110,11 @@ func (lg *NEP5TransferLog) ForEach(f func(*NEP5Transfer) error) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Size returns an amount of transfer written in log.
|
||||||
|
func (lg *NEP5TransferLog) Size() int {
|
||||||
|
return len(lg.Raw) / NEP5TransferSize
|
||||||
|
}
|
||||||
|
|
||||||
// EncodeBinary implements io.Serializable interface.
|
// EncodeBinary implements io.Serializable interface.
|
||||||
func (t *NEP5Tracker) EncodeBinary(w *io.BinWriter) {
|
func (t *NEP5Tracker) EncodeBinary(w *io.BinWriter) {
|
||||||
w.WriteU64LE(uint64(t.Balance))
|
w.WriteU64LE(uint64(t.Balance))
|
||||||
|
|
|
@ -25,6 +25,8 @@ func TestNEP5TransferLog_Append(t *testing.T) {
|
||||||
require.NoError(t, lg.Append(tr))
|
require.NoError(t, lg.Append(tr))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
require.Equal(t, len(expected), lg.Size())
|
||||||
|
|
||||||
i := 0
|
i := 0
|
||||||
err := lg.ForEach(func(tr *NEP5Transfer) error {
|
err := lg.ForEach(func(tr *NEP5Transfer) error {
|
||||||
require.Equal(t, expected[i], tr)
|
require.Equal(t, expected[i], tr)
|
||||||
|
|
|
@ -18,6 +18,7 @@ const (
|
||||||
STContract KeyPrefix = 0x50
|
STContract KeyPrefix = 0x50
|
||||||
STStorage KeyPrefix = 0x70
|
STStorage KeyPrefix = 0x70
|
||||||
STNEP5Transfers KeyPrefix = 0x72
|
STNEP5Transfers KeyPrefix = 0x72
|
||||||
|
STNEP5Balances KeyPrefix = 0x73
|
||||||
IXHeaderHashList KeyPrefix = 0x80
|
IXHeaderHashList KeyPrefix = 0x80
|
||||||
IXValidatorsCount KeyPrefix = 0x90
|
IXValidatorsCount KeyPrefix = 0x90
|
||||||
SYSCurrentBlock KeyPrefix = 0xc0
|
SYSCurrentBlock KeyPrefix = 0xc0
|
||||||
|
|
|
@ -94,6 +94,9 @@ func (chain testChain) GetAccountState(util.Uint160) *state.Account {
|
||||||
func (chain testChain) GetNEP5TransferLog(util.Uint160) *state.NEP5TransferLog {
|
func (chain testChain) GetNEP5TransferLog(util.Uint160) *state.NEP5TransferLog {
|
||||||
panic("TODO")
|
panic("TODO")
|
||||||
}
|
}
|
||||||
|
func (chain testChain) GetNEP5Balances(util.Uint160) *state.NEP5Balances {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
func (chain testChain) GetValidators(...*transaction.Transaction) ([]*keys.PublicKey, error) {
|
func (chain testChain) GetValidators(...*transaction.Transaction) ([]*keys.PublicKey, error) {
|
||||||
panic("TODO")
|
panic("TODO")
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
@ -23,9 +22,9 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpc/request"
|
"github.com/nspcc-dev/neo-go/pkg/rpc/request"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpc/response"
|
"github.com/nspcc-dev/neo-go/pkg/rpc/response"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
|
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
@ -424,11 +423,14 @@ func (s *Server) getNEP5Balances(ps request.Params) (interface{}, error) {
|
||||||
return nil, response.ErrInvalidParams
|
return nil, response.ErrInvalidParams
|
||||||
}
|
}
|
||||||
|
|
||||||
as := s.chain.GetAccountState(u)
|
as := s.chain.GetNEP5Balances(u)
|
||||||
bs := &result.NEP5Balances{Address: address.Uint160ToString(u)}
|
bs := &result.NEP5Balances{
|
||||||
|
Address: address.Uint160ToString(u),
|
||||||
|
Balances: []result.NEP5Balance{},
|
||||||
|
}
|
||||||
if as != nil {
|
if as != nil {
|
||||||
cache := make(map[util.Uint160]int64)
|
cache := make(map[util.Uint160]int64)
|
||||||
for h, bal := range as.NEP5Balances {
|
for h, bal := range as.Trackers {
|
||||||
dec, err := s.getDecimals(h, cache)
|
dec, err := s.getDecimals(h, cache)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
|
@ -454,7 +456,11 @@ func (s *Server) getNEP5Transfers(ps request.Params) (interface{}, error) {
|
||||||
return nil, response.ErrInvalidParams
|
return nil, response.ErrInvalidParams
|
||||||
}
|
}
|
||||||
|
|
||||||
bs := &result.NEP5Transfers{Address: address.Uint160ToString(u)}
|
bs := &result.NEP5Transfers{
|
||||||
|
Address: address.Uint160ToString(u),
|
||||||
|
Received: []result.NEP5Transfer{},
|
||||||
|
Sent: []result.NEP5Transfer{},
|
||||||
|
}
|
||||||
lg := s.chain.GetNEP5TransferLog(u)
|
lg := s.chain.GetNEP5TransferLog(u)
|
||||||
cache := make(map[util.Uint160]int64)
|
cache := make(map[util.Uint160]int64)
|
||||||
err = lg.ForEach(func(tr *state.NEP5Transfer) error {
|
err = lg.ForEach(func(tr *state.NEP5Transfer) error {
|
||||||
|
@ -508,29 +514,33 @@ func (s *Server) getDecimals(h util.Uint160, cache map[util.Uint160]int64) (int6
|
||||||
if d, ok := cache[h]; ok {
|
if d, ok := cache[h]; ok {
|
||||||
return d, nil
|
return d, nil
|
||||||
}
|
}
|
||||||
w := io.NewBufBinWriter()
|
script, err := request.CreateFunctionInvocationScript(h, request.Params{
|
||||||
emit.Int(w.BinWriter, 0)
|
{
|
||||||
emit.Opcode(w.BinWriter, opcode.NEWARRAY)
|
Type: request.StringT,
|
||||||
emit.String(w.BinWriter, "decimals")
|
Value: "decimals",
|
||||||
emit.AppCall(w.BinWriter, h, true)
|
},
|
||||||
v, _ := s.chain.GetTestVM()
|
{
|
||||||
v.LoadScript(w.Bytes())
|
Type: request.ArrayT,
|
||||||
if err := v.Run(); err != nil {
|
Value: []request.Param{},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
res := v.PopResult()
|
res := s.runScriptInVM(script)
|
||||||
if res == nil {
|
if res == nil || res.State != "HALT" || len(res.Stack) == 0 {
|
||||||
|
return 0, errors.New("execution error")
|
||||||
|
}
|
||||||
|
|
||||||
|
var d int64
|
||||||
|
switch item := res.Stack[len(res.Stack)-1]; item.Type {
|
||||||
|
case smartcontract.IntegerType:
|
||||||
|
d = item.Value.(int64)
|
||||||
|
case smartcontract.ByteArrayType:
|
||||||
|
d = emit.BytesToInt(item.Value.([]byte)).Int64()
|
||||||
|
default:
|
||||||
return 0, errors.New("invalid result")
|
return 0, errors.New("invalid result")
|
||||||
}
|
}
|
||||||
bi, ok := res.(*big.Int)
|
|
||||||
if !ok {
|
|
||||||
bs, ok := res.([]byte)
|
|
||||||
if !ok {
|
|
||||||
return 0, errors.New("invalid result")
|
|
||||||
}
|
|
||||||
bi = emit.BytesToInt(bs)
|
|
||||||
}
|
|
||||||
d := bi.Int64()
|
|
||||||
if d < 0 {
|
if d < 0 {
|
||||||
return 0, errors.New("negative decimals")
|
return 0, errors.New("negative decimals")
|
||||||
}
|
}
|
||||||
|
|
|
@ -623,7 +623,7 @@ var rpcTestCases = map[string][]rpcTestCase{
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
assert.Equal(t, res.Available, util.Fixed8FromInt64(8))
|
assert.Equal(t, res.Available, util.Fixed8FromInt64(8))
|
||||||
assert.True(t, res.Unavailable > 0)
|
assert.True(t, res.Unavailable > 0)
|
||||||
assert.Equal(t, res.Available + res.Unavailable, res.Unclaimed)
|
assert.Equal(t, res.Available+res.Unavailable, res.Unclaimed)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue