Merge pull request #1239 from nspcc-dev/store_nep5_with_id

core: store contract IDs instead of hashes for NEP5Balances and Transfers
This commit is contained in:
fyrchik 2020-07-30 12:56:19 +03:00 committed by GitHub
commit e4fc655115
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 106 additions and 111 deletions

View file

@ -742,8 +742,19 @@ func parseUint160(addr []byte) util.Uint160 {
func (bc *Blockchain) processNEP5Transfer(cache *dao.Cached, h util.Uint256, b *block.Block, sc util.Uint160, from, to []byte, amount *big.Int) {
toAddr := parseUint160(to)
fromAddr := parseUint160(from)
var id int32
nativeContract := bc.contracts.ByHash(sc)
if nativeContract != nil {
id = nativeContract.Metadata().ContractID
} else {
assetContract := bc.GetContractState(sc)
if assetContract == nil {
return
}
id = assetContract.ID
}
transfer := &state.NEP5Transfer{
Asset: sc,
Asset: id,
From: fromAddr,
To: toAddr,
Block: b.Index,
@ -755,10 +766,10 @@ func (bc *Blockchain) processNEP5Transfer(cache *dao.Cached, h util.Uint256, b *
if err != nil {
return
}
bs := balances.Trackers[sc]
bs := balances.Trackers[id]
bs.Balance = *new(big.Int).Sub(&bs.Balance, amount)
bs.LastUpdatedBlock = b.Index
balances.Trackers[sc] = bs
balances.Trackers[id] = bs
transfer.Amount = *new(big.Int).Sub(&transfer.Amount, amount)
isBig, err := cache.AppendNEP5Transfer(fromAddr, balances.NextTransferBatch, transfer)
if err != nil {
@ -776,10 +787,10 @@ func (bc *Blockchain) processNEP5Transfer(cache *dao.Cached, h util.Uint256, b *
if err != nil {
return
}
bs := balances.Trackers[sc]
bs := balances.Trackers[id]
bs.Balance = *new(big.Int).Add(&bs.Balance, amount)
bs.LastUpdatedBlock = b.Index
balances.Trackers[sc] = bs
balances.Trackers[id] = bs
transfer.Amount = *amount
isBig, err := cache.AppendNEP5Transfer(toAddr, balances.NextTransferBatch, transfer)
@ -827,7 +838,7 @@ func (bc *Blockchain) GetUtilityTokenBalance(acc util.Uint160) *big.Int {
if err != nil {
return big.NewInt(0)
}
balance := bs.Trackers[bc.contracts.GAS.Hash].Balance
balance := bs.Trackers[bc.contracts.GAS.ContractID].Balance
return &balance
}
@ -838,7 +849,7 @@ func (bc *Blockchain) GetGoverningTokenBalance(acc util.Uint160) (*big.Int, uint
if err != nil {
return big.NewInt(0), 0
}
neo := bs.Trackers[bc.contracts.NEO.Hash]
neo := bs.Trackers[bc.contracts.NEO.ContractID]
return &neo.Balance, neo.LastUpdatedBlock
}
@ -1017,6 +1028,11 @@ func (bc *Blockchain) GetContractState(hash util.Uint160) *state.Contract {
return contract
}
// GetContractScriptHash returns contract script hash by its ID.
func (bc *Blockchain) GetContractScriptHash(id int32) (util.Uint160, error) {
return bc.dao.GetContractScriptHash(id)
}
// GetAccountState returns the account state from its script hash.
func (bc *Blockchain) GetAccountState(scriptHash util.Uint160) *state.Account {
as, err := bc.dao.GetAccountState(scriptHash)

View file

@ -27,6 +27,7 @@ type Blockchainer interface {
HeaderHeight() uint32
GetBlock(hash util.Uint256) (*block.Block, error)
GetContractState(hash util.Uint160) *state.Contract
GetContractScriptHash(id int32) (util.Uint160, error)
GetEnrollments() ([]state.Validator, error)
GetGoverningTokenBalance(acc util.Uint160) (*big.Int, uint32)
GetHeaderHash(int) util.Uint256

View file

@ -1,11 +1,9 @@
package dao
import (
"bytes"
"errors"
"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/io"
"github.com/nspcc-dev/neo-go/pkg/util"
)
@ -125,65 +123,6 @@ func (cd *Cached) AppendNEP5Transfer(acc util.Uint160, index uint32, tr *state.N
return lg.Size() >= nep5TransferBatchSize, cd.PutNEP5TransferLog(acc, index, lg)
}
// MigrateNEP5Balances migrates NEP5 balances from old contract to the new one.
func (cd *Cached) MigrateNEP5Balances(from, to util.Uint160) error {
var (
simpleDAO *Simple
cachedDAO = cd
ok bool
w = io.NewBufBinWriter()
)
for simpleDAO == nil {
simpleDAO, ok = cachedDAO.DAO.(*Simple)
if !ok {
cachedDAO, ok = cachedDAO.DAO.(*Cached)
if !ok {
panic("uknown DAO")
}
}
}
for acc, bs := range cd.balances {
err := simpleDAO.putNEP5Balances(acc, bs, w)
if err != nil {
return err
}
w.Reset()
}
cd.dropNEP5Cache = true
var store = simpleDAO.Store
// Create another layer of cache because we can't change original storage
// while seeking.
var upStore = storage.NewMemCachedStore(store)
store.Seek([]byte{byte(storage.STNEP5Balances)}, func(k, v []byte) {
if !bytes.Contains(v, from[:]) {
return
}
bs := state.NewNEP5Balances()
reader := io.NewBinReaderFromBuf(v)
bs.DecodeBinary(reader)
if reader.Err != nil {
panic("bad nep5 balances")
}
tr, ok := bs.Trackers[from]
if !ok {
return
}
delete(bs.Trackers, from)
bs.Trackers[to] = tr
w.Reset()
bs.EncodeBinary(w.BinWriter)
if w.Err != nil {
panic("error on nep5 balance encoding")
}
err := upStore.Put(k, w.Bytes())
if err != nil {
panic("can't put value in the DB")
}
})
_, err := upStore.Persist()
return err
}
// Persist flushes all the changes made into the (supposedly) persistent
// underlying store.
func (cd *Cached) Persist() (int, error) {

View file

@ -28,6 +28,7 @@ type DAO interface {
GetBatch() *storage.MemBatch
GetBlock(hash util.Uint256) (*block.Block, error)
GetContractState(hash util.Uint160) (*state.Contract, error)
GetContractScriptHash(id int32) (util.Uint160, error)
GetCurrentBlockHeight() (uint32, error)
GetCurrentHeaderHeight() (i uint32, h util.Uint256, err error)
GetCurrentStateRootHeight() (uint32, error)
@ -171,7 +172,10 @@ func (dao *Simple) GetContractState(hash util.Uint160) (*state.Contract, error)
// PutContractState puts given contract state into the given store.
func (dao *Simple) PutContractState(cs *state.Contract) error {
key := storage.AppendPrefix(storage.STContract, cs.ScriptHash().BytesBE())
return dao.Put(cs, key)
if err := dao.Put(cs, key); err != nil {
return err
}
return dao.putContractScriptHash(cs)
}
// DeleteContractState deletes given contract state in the given store.
@ -195,6 +199,29 @@ func (dao *Simple) GetAndUpdateNextContractID() (int32, error) {
return id, dao.Store.Put(key, data)
}
// putContractScriptHash puts given contract script hash into the given store.
// It's a private method because it should be used after PutContractState to keep
// ID-Hash pair always up-to-date.
func (dao *Simple) putContractScriptHash(cs *state.Contract) error {
key := make([]byte, 5)
key[0] = byte(storage.STContractID)
binary.LittleEndian.PutUint32(key[1:], uint32(cs.ID))
return dao.Store.Put(key, cs.ScriptHash().BytesBE())
}
// GetContractScriptHash returns script hash of the contract with the specified ID.
// Contract with the script hash may be destroyed.
func (dao *Simple) GetContractScriptHash(id int32) (util.Uint160, error) {
key := make([]byte, 5)
key[0] = byte(storage.STContractID)
binary.LittleEndian.PutUint32(key[1:], uint32(id))
data := &util.Uint160{}
if err := dao.GetAndDecode(data, key); err != nil {
return *data, err
}
return *data, nil
}
// -- end contracts.
// -- start nep5 balances.

View file

@ -154,7 +154,6 @@ func contractUpdate(ic *interop.Context, v *vm.VM) error {
if err := ic.DAO.DeleteContractState(oldHash); err != nil {
return fmt.Errorf("failed to update script: %v", err)
}
ic.DAO.MigrateNEP5Balances(oldHash, newHash)
}
// if manifest was provided, update the old contract manifest and check associated
// storage items if needed

View file

@ -210,7 +210,7 @@ func (n *NEO) unclaimedGas(ic *interop.Context, args []stackitem.Item) stackitem
if err != nil {
panic(err)
}
tr := bs.Trackers[n.Hash]
tr := bs.Trackers[n.ContractID]
gen := ic.Chain.CalculateClaimable(&tr.Balance, tr.LastUpdatedBlock, end)
return stackitem.NewBigInteger(gen)

View file

@ -210,7 +210,7 @@ func (c *nep5TokenNative) balanceOf(ic *interop.Context, args []stackitem.Item)
if err != nil {
panic(err)
}
balance := bs.Trackers[c.Hash].Balance
balance := bs.Trackers[c.ContractID].Balance
return stackitem.NewBigInteger(&balance)
}

View file

@ -26,8 +26,8 @@ type NEP5TransferLog struct {
// NEP5Transfer represents a single NEP5 Transfer event.
type NEP5Transfer struct {
// Asset is a NEP5 contract hash.
Asset util.Uint160
// Asset is a NEP5 contract ID.
Asset int32
// Address is the address of the sender.
From util.Uint160
// To is the address of the receiver.
@ -43,10 +43,10 @@ type NEP5Transfer struct {
Tx util.Uint256
}
// NEP5Balances is a map of the NEP5 contract hashes
// NEP5Balances is a map of the NEP5 contract IDs
// to the corresponding structures.
type NEP5Balances struct {
Trackers map[util.Uint160]NEP5Tracker
Trackers map[int32]NEP5Tracker
// NextTransferBatch stores an index of the next transfer batch.
NextTransferBatch uint32
}
@ -54,7 +54,7 @@ type NEP5Balances struct {
// NewNEP5Balances returns new NEP5Balances.
func NewNEP5Balances() *NEP5Balances {
return &NEP5Balances{
Trackers: make(map[util.Uint160]NEP5Tracker),
Trackers: make(map[int32]NEP5Tracker),
}
}
@ -62,11 +62,10 @@ func NewNEP5Balances() *NEP5Balances {
func (bs *NEP5Balances) DecodeBinary(r *io.BinReader) {
bs.NextTransferBatch = r.ReadU32LE()
lenBalances := r.ReadVarUint()
m := make(map[util.Uint160]NEP5Tracker, lenBalances)
m := make(map[int32]NEP5Tracker, lenBalances)
for i := 0; i < int(lenBalances); i++ {
var key util.Uint160
key := int32(r.ReadU32LE())
var tr NEP5Tracker
r.ReadBytes(key[:])
tr.DecodeBinary(r)
m[key] = tr
}
@ -78,7 +77,7 @@ 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[:])
w.WriteU32LE(uint32(k))
v.EncodeBinary(w)
}
}
@ -133,7 +132,7 @@ func (t *NEP5Tracker) DecodeBinary(r *io.BinReader) {
// EncodeBinary implements io.Serializable interface.
func (t *NEP5Transfer) EncodeBinary(w *io.BinWriter) {
w.WriteBytes(t.Asset[:])
w.WriteU32LE(uint32(t.Asset))
w.WriteBytes(t.Tx[:])
w.WriteBytes(t.From[:])
w.WriteBytes(t.To[:])
@ -151,7 +150,7 @@ func (t *NEP5Transfer) DecodeBinary(r *io.BinReader) {
// DecodeBinaryReturnCount decodes NEP5Transfer and returns the number of bytes read.
func (t *NEP5Transfer) DecodeBinaryReturnCount(r *io.BinReader) int {
r.ReadBytes(t.Asset[:])
t.Asset = int32(r.ReadU32LE())
r.ReadBytes(t.Tx[:])
r.ReadBytes(t.From[:])
r.ReadBytes(t.To[:])
@ -161,5 +160,5 @@ func (t *NEP5Transfer) DecodeBinaryReturnCount(r *io.BinReader) int {
amountBytes := make([]byte, amountLen)
r.ReadBytes(amountBytes)
t.Amount = *bigint.FromBytes(amountBytes)
return util.Uint160Size*3 + 8 + 4 + (8 + len(amountBytes)) + +util.Uint256Size
return 4 + util.Uint160Size*2 + 8 + 4 + (8 + len(amountBytes)) + +util.Uint256Size
}

View file

@ -50,7 +50,7 @@ func TestNEP5Tracker_EncodeBinary(t *testing.T) {
func TestNEP5Transfer_DecodeBinary(t *testing.T) {
expected := &NEP5Transfer{
Asset: util.Uint160{1, 2, 3},
Asset: 123,
From: util.Uint160{5, 6, 7},
To: util.Uint160{8, 9, 10},
Amount: *big.NewInt(42),
@ -78,7 +78,7 @@ func randomTransfer(r *rand.Rand) *NEP5Transfer {
return &NEP5Transfer{
Amount: *big.NewInt(int64(r.Uint64())),
Block: r.Uint32(),
Asset: random.Uint160(),
Asset: int32(random.Int(10, 10000000)),
From: random.Uint160(),
To: random.Uint160(),
Tx: random.Uint256(),

View file

@ -13,6 +13,7 @@ const (
STAccount KeyPrefix = 0x40
STNotification KeyPrefix = 0x4d
STContract KeyPrefix = 0x50
STContractID KeyPrefix = 0x51
STStorage KeyPrefix = 0x70
STNEP5Transfers KeyPrefix = 0x72
STNEP5Balances KeyPrefix = 0x73

View file

@ -70,6 +70,9 @@ func (chain testChain) GetBlock(hash util.Uint256) (*block.Block, error) {
func (chain testChain) GetContractState(hash util.Uint160) *state.Contract {
panic("TODO")
}
func (chain testChain) GetContractScriptHash(id int32) (util.Uint160, error) {
panic("TODO")
}
func (chain testChain) GetHeaderHash(int) util.Uint256 {
return util.Uint256{}
}

View file

@ -514,15 +514,15 @@ func (s *Server) getNEP5Balances(ps request.Params) (interface{}, *response.Erro
Balances: []result.NEP5Balance{},
}
if as != nil {
cache := make(map[util.Uint160]int64)
for h, bal := range as.Trackers {
dec, err := s.getDecimals(h, cache)
cache := make(map[int32]decimals)
for id, bal := range as.Trackers {
dec, err := s.getDecimals(id, cache)
if err != nil {
continue
}
amount := amountToString(&bal.Balance, dec)
amount := amountToString(&bal.Balance, dec.Value)
bs.Balances = append(bs.Balances, result.NEP5Balance{
Asset: h,
Asset: dec.Hash,
Amount: amount,
LastUpdated: bal.LastUpdatedBlock,
})
@ -543,20 +543,20 @@ func (s *Server) getNEP5Transfers(ps request.Params) (interface{}, *response.Err
Sent: []result.NEP5Transfer{},
}
lg := s.chain.GetNEP5TransferLog(u)
cache := make(map[util.Uint160]int64)
cache := make(map[int32]decimals)
err = lg.ForEach(func(tr *state.NEP5Transfer) error {
transfer := result.NEP5Transfer{
Timestamp: tr.Timestamp,
Asset: tr.Asset,
Index: tr.Block,
TxHash: tr.Tx,
}
d, err := s.getDecimals(tr.Asset, cache)
if err != nil {
return nil
}
transfer := result.NEP5Transfer{
Timestamp: tr.Timestamp,
Asset: d.Hash,
Index: tr.Block,
TxHash: tr.Tx,
}
if tr.Amount.Sign() > 0 { // token was received
transfer.Amount = amountToString(&tr.Amount, d)
transfer.Amount = amountToString(&tr.Amount, d.Value)
if !tr.From.Equals(util.Uint160{}) {
transfer.Address = address.Uint160ToString(tr.From)
}
@ -564,7 +564,7 @@ func (s *Server) getNEP5Transfers(ps request.Params) (interface{}, *response.Err
return nil
}
transfer.Amount = amountToString(new(big.Int).Neg(&tr.Amount), d)
transfer.Amount = amountToString(new(big.Int).Neg(&tr.Amount), d.Value)
if !tr.To.Equals(util.Uint160{}) {
transfer.Address = address.Uint160ToString(tr.To)
}
@ -590,10 +590,20 @@ func amountToString(amount *big.Int, decimals int64) string {
return fmt.Sprintf(fs, q, r)
}
func (s *Server) getDecimals(h util.Uint160, cache map[util.Uint160]int64) (int64, *response.Error) {
if d, ok := cache[h]; ok {
// decimals represents decimals value for the contract with the specified scripthash.
type decimals struct {
Hash util.Uint160
Value int64
}
func (s *Server) getDecimals(contractID int32, cache map[int32]decimals) (decimals, error) {
if d, ok := cache[contractID]; ok {
return d, nil
}
h, err := s.chain.GetContractScriptHash(contractID)
if err != nil {
return decimals{}, err
}
script, err := request.CreateFunctionInvocationScript(h, request.Params{
{
Type: request.StringT,
@ -605,26 +615,26 @@ func (s *Server) getDecimals(h util.Uint160, cache map[util.Uint160]int64) (int6
},
})
if err != nil {
return 0, response.NewInternalServerError("Can't create script", err)
return decimals{}, fmt.Errorf("can't create script: %v", err)
}
res := s.runScriptInVM(script, nil)
if res == nil || res.State != "HALT" || len(res.Stack) == 0 {
return 0, response.NewInternalServerError("execution error", errors.New("no result"))
return decimals{}, errors.New("execution error : no result")
}
var d int64
d := decimals{Hash: h}
switch item := res.Stack[len(res.Stack)-1]; item.Type {
case smartcontract.IntegerType:
d = item.Value.(int64)
d.Value = item.Value.(int64)
case smartcontract.ByteArrayType:
d = bigint.FromBytes(item.Value.([]byte)).Int64()
d.Value = bigint.FromBytes(item.Value.([]byte)).Int64()
default:
return 0, response.NewInternalServerError("invalid result", errors.New("not an integer"))
return d, errors.New("invalid result: not an integer")
}
if d < 0 {
return 0, response.NewInternalServerError("incorrect result", errors.New("negative result"))
if d.Value < 0 {
return d, errors.New("incorrect result: negative result")
}
cache[h] = d
cache[contractID] = d
return d, nil
}