core/native: store validators in NEO native contract state

This technically breaks voting with UTXO-based NEO (processTXWithValidators*),
but we're moving towards the new system.
This commit is contained in:
Roman Khimov 2020-04-26 00:23:30 +03:00
parent 4e8ee697ee
commit 3476a18fa9
3 changed files with 118 additions and 52 deletions

View file

@ -861,9 +861,9 @@ func processTXWithValidatorsSubtract(output *transaction.Output, account *state.
// modAccountVotes adds given value to given account voted validators. // modAccountVotes adds given value to given account voted validators.
func modAccountVotes(account *state.Account, dao *dao.Cached, value util.Fixed8) error { func modAccountVotes(account *state.Account, dao *dao.Cached, value util.Fixed8) error {
if err := native.ModifyAccountVotes(account, dao, value); err != nil { // if err := native.ModifyAccountVotes(account, dao, value); err != nil {
return err // return err
} // }
if len(account.Votes) > 0 { if len(account.Votes) > 0 {
vc, err := dao.GetValidatorsCount() vc, err := dao.GetValidatorsCount()
if err != nil { if err != nil {

View file

@ -37,6 +37,7 @@ type DAO interface {
GetNextBlockValidators() (keys.PublicKeys, error) GetNextBlockValidators() (keys.PublicKeys, error)
GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem
GetStorageItems(hash util.Uint160) (map[string]*state.StorageItem, error) GetStorageItems(hash util.Uint160) (map[string]*state.StorageItem, error)
GetStorageItemsWithPrefix(hash util.Uint160, prefix []byte) (map[string]*state.StorageItem, error)
GetTransaction(hash util.Uint256) (*transaction.Transaction, uint32, error) GetTransaction(hash util.Uint256) (*transaction.Transaction, uint32, error)
GetUnspentCoinState(hash util.Uint256) (*state.UnspentCoin, error) GetUnspentCoinState(hash util.Uint256) (*state.UnspentCoin, error)
GetValidatorState(publicKey *keys.PublicKey) (*state.Validator, error) GetValidatorState(publicKey *keys.PublicKey) (*state.Validator, error)
@ -471,9 +472,19 @@ func (dao *Simple) DeleteStorageItem(scripthash util.Uint160, key []byte) error
// GetStorageItems returns all storage items for a given scripthash. // GetStorageItems returns all storage items for a given scripthash.
func (dao *Simple) GetStorageItems(hash util.Uint160) (map[string]*state.StorageItem, error) { func (dao *Simple) GetStorageItems(hash util.Uint160) (map[string]*state.StorageItem, error) {
return dao.GetStorageItemsWithPrefix(hash, nil)
}
// GetStorageItemsWithPrefix returns all storage items with given prefix for a
// given scripthash.
func (dao *Simple) GetStorageItemsWithPrefix(hash util.Uint160, prefix []byte) (map[string]*state.StorageItem, error) {
var siMap = make(map[string]*state.StorageItem) var siMap = make(map[string]*state.StorageItem)
var err error var err error
lookupKey := storage.AppendPrefix(storage.STStorage, hash.BytesLE())
if prefix != nil {
lookupKey = append(lookupKey, prefix...)
}
saveToMap := func(k, v []byte) { saveToMap := func(k, v []byte) {
if err != nil { if err != nil {
return return
@ -487,9 +498,9 @@ func (dao *Simple) GetStorageItems(hash util.Uint160) (map[string]*state.Storage
} }
// Cut prefix and hash. // Cut prefix and hash.
siMap[string(k[21:])] = si siMap[string(k[len(lookupKey):])] = si
} }
dao.Store.Seek(storage.AppendPrefix(storage.STStorage, hash.BytesLE()), saveToMap) dao.Store.Seek(lookupKey, saveToMap)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -9,7 +9,6 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/interop/runtime" "github.com/nspcc-dev/neo-go/pkg/core/interop/runtime"
"github.com/nspcc-dev/neo-go/pkg/core/state" "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/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
@ -24,10 +23,37 @@ type NEO struct {
GAS *GAS GAS *GAS
} }
const neoSyscallName = "Neo.Native.Tokens.NEO" // keyWithVotes is a serialized key with votes balance. It's not deserialized
// because some uses of it imply serialized-only usage and converting to
// PublicKey is quite expensive.
type keyWithVotes struct {
Key string
Votes *big.Int
}
// NEOTotalSupply is the total amount of NEO in the system. // pkeyWithVotes is a deserialized key with votes balance.
const NEOTotalSupply = 100000000 type pkeyWithVotes struct {
Key *keys.PublicKey
Votes *big.Int
}
const (
neoSyscallName = "Neo.Native.Tokens.NEO"
// NEOTotalSupply is the total amount of NEO in the system.
NEOTotalSupply = 100000000
// prefixValidator is a prefix used to store validator's data.
prefixValidator = 33
)
// makeValidatorKey creates a key from account script hash.
func makeValidatorKey(key *keys.PublicKey) []byte {
b := key.Bytes()
// Don't create a new buffer.
b = append(b, 0)
copy(b[1:], b[0:])
b[0] = prefixValidator
return b
}
// NewNEO returns NEO native contract. // NewNEO returns NEO native contract.
func NewNEO() *NEO { func NewNEO() *NEO {
@ -57,7 +83,7 @@ func NewNEO() *NEO {
n.AddMethod(md, desc, false) n.AddMethod(md, desc, false)
desc = newDescriptor("getRegisteredValidators", smartcontract.ArrayType) desc = newDescriptor("getRegisteredValidators", smartcontract.ArrayType)
md = newMethodAndPrice(n.getRegisteredValidators, 1, smartcontract.NoneFlag) md = newMethodAndPrice(n.getRegisteredValidatorsCall, 1, smartcontract.NoneFlag)
n.AddMethod(md, desc, true) n.AddMethod(md, desc, true)
desc = newDescriptor("getValidators", smartcontract.ArrayType) desc = newDescriptor("getValidators", smartcontract.ArrayType)
@ -164,11 +190,17 @@ func (n *NEO) registerValidator(ic *interop.Context, args []vm.StackItem) vm.Sta
} }
func (n *NEO) registerValidatorInternal(ic *interop.Context, pub *keys.PublicKey) error { func (n *NEO) registerValidatorInternal(ic *interop.Context, pub *keys.PublicKey) error {
_, err := ic.DAO.GetValidatorState(pub) key := makeValidatorKey(pub)
if err == nil { si := ic.DAO.GetStorageItem(n.Hash, key)
return err if si != nil {
return errors.New("already registered")
} }
return ic.DAO.PutValidatorState(&state.Validator{PublicKey: pub}) si = new(state.StorageItem)
// It's the same simple counter, calling it `Votes` instead of `Balance`
// doesn't help a lot.
votes := state.NEP5BalanceState{}
si.Value = votes.Bytes()
return ic.DAO.PutStorageItem(n.Hash, key, si)
} }
func (n *NEO) vote(ic *interop.Context, args []vm.StackItem) vm.StackItem { func (n *NEO) vote(ic *interop.Context, args []vm.StackItem) vm.StackItem {
@ -210,19 +242,15 @@ func (n *NEO) VoteInternal(ic *interop.Context, h util.Uint160, pubs keys.Public
if err != nil { if err != nil {
return err return err
} }
balance := util.Fixed8(acc.Balance.Int64()) if err := n.ModifyAccountVotes(oldAcc, ic.DAO, new(big.Int).Neg(&acc.Balance)); err != nil {
if err := ModifyAccountVotes(oldAcc, ic.DAO, -balance); err != nil {
return err return err
} }
pubs = pubs.Unique() pubs = pubs.Unique()
// Check validators registration.
var newPubs keys.PublicKeys var newPubs keys.PublicKeys
for _, pub := range pubs { for _, pub := range pubs {
_, err := ic.DAO.GetValidatorState(pub) if ic.DAO.GetStorageItem(n.Hash, makeValidatorKey(pub)) == nil {
if err != nil { continue
if err == storage.ErrKeyNotFound {
continue
}
return err
} }
newPubs = append(newPubs, pub) newPubs = append(newPubs, pub)
} }
@ -232,50 +260,69 @@ func (n *NEO) VoteInternal(ic *interop.Context, h util.Uint160, pubs keys.Public
return err return err
} }
if lv > 0 { if lv > 0 {
vc[lv-1] -= balance vc[lv-1] -= util.Fixed8(acc.Balance.Int64())
} }
if len(newPubs) > 0 { if len(newPubs) > 0 {
vc[lp-1] += balance vc[lp-1] += util.Fixed8(acc.Balance.Int64())
} }
if err := ic.DAO.PutValidatorsCount(vc); err != nil { if err := ic.DAO.PutValidatorsCount(vc); err != nil {
return err return err
} }
} }
oldAcc.Votes = newPubs oldAcc.Votes = newPubs
if err := ModifyAccountVotes(oldAcc, ic.DAO, balance); err != nil { if err := n.ModifyAccountVotes(oldAcc, ic.DAO, &acc.Balance); err != nil {
return err return err
} }
return ic.DAO.PutAccountState(oldAcc) return ic.DAO.PutAccountState(oldAcc)
} }
// ModifyAccountVotes modifies votes of the specified account by value (can be negative). // ModifyAccountVotes modifies votes of the specified account by value (can be negative).
func ModifyAccountVotes(acc *state.Account, d dao.DAO, value util.Fixed8) error { func (n *NEO) ModifyAccountVotes(acc *state.Account, d dao.DAO, value *big.Int) error {
for _, vote := range acc.Votes { for _, vote := range acc.Votes {
validator, err := d.GetValidatorStateOrNew(vote) key := makeValidatorKey(vote)
si := d.GetStorageItem(n.Hash, key)
if si == nil {
return errors.New("invalid validator")
}
votes, err := state.NEP5BalanceStateFromBytes(si.Value)
if err != nil { if err != nil {
return err return err
} }
validator.Votes += value votes.Balance.Add(&votes.Balance, value)
if validator.UnregisteredAndHasNoVotes() { si.Value = votes.Bytes()
if err := d.DeleteValidatorState(validator); err != nil { if err := d.PutStorageItem(n.Hash, key, si); err != nil {
return err return err
}
} else {
if err := d.PutValidatorState(validator); err != nil {
return err
}
} }
} }
return nil return nil
} }
func (n *NEO) getRegisteredValidators(ic *interop.Context, _ []vm.StackItem) vm.StackItem { func (n *NEO) getRegisteredValidators(d dao.DAO) ([]keyWithVotes, error) {
vs := ic.DAO.GetValidators() siMap, err := d.GetStorageItemsWithPrefix(n.Hash, []byte{prefixValidator})
arr := make([]vm.StackItem, len(vs)) if err != nil {
for i := range vs { return nil, err
}
arr := make([]keyWithVotes, 0, len(siMap))
for key, si := range siMap {
votes, err := state.NEP5BalanceStateFromBytes(si.Value)
if err != nil {
return nil, err
}
arr = append(arr, keyWithVotes{key, &votes.Balance})
}
return arr, nil
}
func (n *NEO) getRegisteredValidatorsCall(ic *interop.Context, _ []vm.StackItem) vm.StackItem {
validators, err := n.getRegisteredValidators(ic.DAO)
if err != nil {
panic(err)
}
arr := make([]vm.StackItem, len(validators))
for i := range validators {
arr[i] = vm.NewStructItem([]vm.StackItem{ arr[i] = vm.NewStructItem([]vm.StackItem{
vm.NewByteArrayItem(vs[i].PublicKey.Bytes()), vm.NewByteArrayItem([]byte(validators[i].Key)),
vm.NewBigIntegerItem(big.NewInt(int64(vs[i].Votes))), vm.NewBigIntegerItem(validators[i].Votes),
}) })
} }
return vm.NewArrayItem(arr) return vm.NewArrayItem(arr)
@ -294,18 +341,26 @@ func (n *NEO) GetValidatorsInternal(bc blockchainer.Blockchainer, d dao.DAO) ([]
return sb, nil return sb, nil
} }
validators := d.GetValidators() validatorsBytes, err := n.getRegisteredValidators(d)
sort.Slice(validators, func(i, j int) bool { if err != nil {
// Unregistered validators go to the end of the list. return nil, err
if validators[i].Registered != validators[j].Registered { }
return validators[i].Registered validators := make([]pkeyWithVotes, len(validatorsBytes))
for i := range validatorsBytes {
validators[i].Key, err = keys.NewPublicKeyFromBytes([]byte(validatorsBytes[i].Key))
if err != nil {
return nil, err
} }
validators[i].Votes = validatorsBytes[i].Votes
}
sort.Slice(validators, func(i, j int) bool {
// The most-voted validators should end up in the front of the list. // The most-voted validators should end up in the front of the list.
if validators[i].Votes != validators[j].Votes { cmp := validators[i].Votes.Cmp(validators[j].Votes)
return validators[i].Votes > validators[j].Votes if cmp != 0 {
return cmp > 0
} }
// Ties are broken with public keys. // Ties are broken with public keys.
return validators[i].PublicKey.Cmp(validators[j].PublicKey) == -1 return validators[i].Key.Cmp(validators[j].Key) == -1
}) })
count := validatorsCount.GetWeightedAverage() count := validatorsCount.GetWeightedAverage()
@ -320,8 +375,8 @@ func (n *NEO) GetValidatorsInternal(bc blockchainer.Blockchainer, d dao.DAO) ([]
uniqueSBValidators := standByValidators.Unique() uniqueSBValidators := standByValidators.Unique()
result := keys.PublicKeys{} result := keys.PublicKeys{}
for _, validator := range validators { for _, validator := range validators {
if validator.RegisteredAndHasVotes() || uniqueSBValidators.Contains(validator.PublicKey) { if validator.Votes.Sign() > 0 || uniqueSBValidators.Contains(validator.Key) {
result = append(result, validator.PublicKey) result = append(result, validator.Key)
} }
} }