core: switch to the new voting system (via native NEO contract)

It has all the methods required now, so you can register, vote and get
voting results. Fixes #865.
This commit is contained in:
Roman Khimov 2020-04-26 20:04:16 +03:00
parent 2fa3bdf6a9
commit b83e84ca08
13 changed files with 42 additions and 399 deletions

View file

@ -479,22 +479,12 @@ func (s *service) getVerifiedTx(count int) []block.Transaction {
return res
}
func (s *service) getValidators(txx ...block.Transaction) []crypto.PublicKey {
func (s *service) getValidators(_ ...block.Transaction) []crypto.PublicKey {
var (
pKeys []*keys.PublicKey
err error
)
if len(txx) == 0 {
pKeys, err = s.Chain.GetValidators()
} else {
ntxx := make([]*transaction.Transaction, len(txx))
for i := range ntxx {
ntxx[i] = txx[i].(*transaction.Transaction)
}
pKeys, err = s.Chain.GetValidators(ntxx...)
}
pKeys, err = s.Chain.GetValidators()
if err != nil {
s.log.Error("error while trying to get validators", zap.Error(err))
}

View file

@ -630,14 +630,6 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
return err
}
}
case *transaction.EnrollmentTX:
if err := processEnrollmentTX(cache, t); err != nil {
return err
}
case *transaction.StateTX:
if err := bc.processStateTX(cache, tx, t); err != nil {
return err
}
case *transaction.InvocationTX:
systemInterop := bc.newInteropContext(trigger.Application, cache, block, tx)
v := SpawnVM(systemInterop)
@ -839,26 +831,6 @@ func processOutputs(tx *transaction.Transaction, dao *dao.Cached) error {
return nil
}
func processValidatorStateDescriptor(descriptor *transaction.StateDescriptor, dao *dao.Cached) error {
publicKey := &keys.PublicKey{}
err := publicKey.DecodeBytes(descriptor.Key)
if err != nil {
return err
}
validatorState, err := dao.GetValidatorStateOrNew(publicKey)
if err != nil {
return err
}
if descriptor.Field == "Registered" {
if len(descriptor.Value) == 1 {
validatorState.Registered = descriptor.Value[0] != 0
return dao.PutValidatorState(validatorState)
}
return errors.New("bad descriptor value")
}
return nil
}
func (bc *Blockchain) processAccountStateDescriptor(descriptor *transaction.StateDescriptor, t *transaction.Transaction, dao *dao.Cached) error {
hash, err := util.Uint160DecodeBytesBE(descriptor.Key)
if err != nil {
@ -1335,7 +1307,7 @@ func (bc *Blockchain) verifyTx(t *transaction.Transaction, block *block.Block) e
for _, k := range votes {
var isRegistered bool
for i := range validators {
if k.Equal(validators[i].PublicKey) {
if k.Equal(validators[i].Key) {
isRegistered = true
break
}
@ -1631,128 +1603,14 @@ func (bc *Blockchain) GetStandByValidators() (keys.PublicKeys, error) {
return getValidators(bc.config)
}
// GetValidators returns validators.
// Golang implementation of GetValidators method in C# (https://github.com/neo-project/neo/blob/c64748ecbac3baeb8045b16af0d518398a6ced24/neo/Persistence/Snapshot.cs#L182)
func (bc *Blockchain) GetValidators(txes ...*transaction.Transaction) ([]*keys.PublicKey, error) {
cache := dao.NewCached(bc.dao)
if len(txes) > 0 {
for _, tx := range txes {
// iterate through outputs
for index, output := range tx.Outputs {
accountState, err := cache.GetAccountStateOrNew(output.ScriptHash)
if err != nil {
return nil, err
}
accountState.Balances[output.AssetID] = append(accountState.Balances[output.AssetID], state.UnspentBalance{
Tx: tx.Hash(),
Index: uint16(index),
Value: output.Amount,
})
if err := cache.PutAccountState(accountState); err != nil {
return nil, err
}
}
// group inputs by the same previous hash and iterate through inputs
group := make(map[util.Uint256][]*transaction.Input)
for i := range tx.Inputs {
hash := tx.Inputs[i].PrevHash
group[hash] = append(group[hash], &tx.Inputs[i])
}
for hash, inputs := range group {
unspent, err := cache.GetUnspentCoinState(hash)
if err != nil {
return nil, err
}
// process inputs
for _, input := range inputs {
prevOutput := &unspent.States[input.PrevIndex].Output
accountState, err := cache.GetAccountStateOrNew(prevOutput.ScriptHash)
if err != nil {
return nil, err
}
delete(accountState.Balances, prevOutput.AssetID)
if err = cache.PutAccountState(accountState); err != nil {
return nil, err
}
}
}
switch t := tx.Data.(type) {
case *transaction.EnrollmentTX:
if err := processEnrollmentTX(cache, t); err != nil {
return nil, err
}
case *transaction.StateTX:
if err := bc.processStateTX(cache, tx, t); err != nil {
return nil, err
}
}
}
}
return bc.contracts.NEO.GetValidatorsInternal(bc, cache)
// GetValidators returns next block validators.
func (bc *Blockchain) GetValidators() ([]*keys.PublicKey, error) {
return bc.contracts.NEO.GetNextBlockValidatorsInternal(bc, bc.dao)
}
// GetEnrollments returns all registered validators and non-registered SB validators
func (bc *Blockchain) GetEnrollments() ([]*state.Validator, error) {
validators := bc.dao.GetValidators()
standByValidators, err := bc.GetStandByValidators()
if err != nil {
return nil, err
}
uniqueSBValidators := standByValidators.Unique()
var result []*state.Validator
for _, validator := range validators {
if validator.Registered {
result = append(result, validator)
}
}
for _, sBValidator := range uniqueSBValidators {
isAdded := false
for _, v := range result {
if v.PublicKey == sBValidator {
isAdded = true
break
}
}
if !isAdded {
result = append(result, &state.Validator{
PublicKey: sBValidator,
Registered: false,
Votes: 0,
})
}
}
return result, nil
}
func (bc *Blockchain) processStateTX(dao *dao.Cached, t *transaction.Transaction, tx *transaction.StateTX) error {
for _, desc := range tx.Descriptors {
switch desc.Type {
case transaction.Account:
if err := bc.processAccountStateDescriptor(desc, t, dao); err != nil {
return err
}
case transaction.Validator:
if err := processValidatorStateDescriptor(desc, dao); err != nil {
return err
}
}
}
return nil
}
func processEnrollmentTX(dao *dao.Cached, tx *transaction.EnrollmentTX) error {
validatorState, err := dao.GetValidatorStateOrNew(&tx.PublicKey)
if err != nil {
return err
}
validatorState.Registered = true
return dao.PutValidatorState(validatorState)
// GetEnrollments returns all registered validators.
func (bc *Blockchain) GetEnrollments() ([]state.Validator, error) {
return bc.contracts.NEO.GetRegisteredValidators(bc.dao)
}
// GetScriptHashesForVerifying returns all the ScriptHashes of a transaction which will be use

View file

@ -24,7 +24,7 @@ type Blockchainer interface {
HeaderHeight() uint32
GetBlock(hash util.Uint256) (*block.Block, error)
GetContractState(hash util.Uint160) *state.Contract
GetEnrollments() ([]*state.Validator, error)
GetEnrollments() ([]state.Validator, error)
GetHeaderHash(int) util.Uint256
GetHeader(hash util.Uint256) (*block.Header, error)
CurrentHeaderHash() util.Uint256
@ -36,7 +36,7 @@ type Blockchainer interface {
GetAppExecResult(util.Uint256) (*state.AppExecResult, error)
GetNEP5TransferLog(util.Uint160) *state.NEP5TransferLog
GetNEP5Balances(util.Uint160) *state.NEP5Balances
GetValidators(txes ...*transaction.Transaction) ([]*keys.PublicKey, error)
GetValidators() ([]*keys.PublicKey, error)
GetStandByValidators() (keys.PublicKeys, error)
GetScriptHashesForVerifying(*transaction.Transaction) ([]util.Uint160, error)
GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem

View file

@ -10,7 +10,6 @@ import (
"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/keys"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/util"
)
@ -20,7 +19,6 @@ type DAO interface {
AppendNEP5Transfer(acc util.Uint160, index uint32, tr *state.NEP5Transfer) (bool, error)
DeleteContractState(hash util.Uint160) error
DeleteStorageItem(scripthash util.Uint160, key []byte) error
DeleteValidatorState(vs *state.Validator) error
GetAccountState(hash util.Uint160) (*state.Account, error)
GetAccountStateOrNew(hash util.Uint160) (*state.Account, error)
GetAndDecode(entity io.Serializable, key []byte) error
@ -39,9 +37,6 @@ type DAO interface {
GetStorageItemsWithPrefix(hash util.Uint160, prefix []byte) (map[string]*state.StorageItem, error)
GetTransaction(hash util.Uint256) (*transaction.Transaction, uint32, error)
GetUnspentCoinState(hash util.Uint256) (*state.UnspentCoin, error)
GetValidatorState(publicKey *keys.PublicKey) (*state.Validator, error)
GetValidatorStateOrNew(publicKey *keys.PublicKey) (*state.Validator, error)
GetValidators() []*state.Validator
GetVersion() (string, error)
GetWrapped() DAO
HasTransaction(hash util.Uint256) bool
@ -57,7 +52,6 @@ type DAO interface {
PutNEP5TransferLog(acc util.Uint160, index uint32, lg *state.NEP5TransferLog) error
PutStorageItem(scripthash util.Uint160, key []byte, si *state.StorageItem) error
PutUnspentCoinState(hash util.Uint256, ucs *state.UnspentCoin) error
PutValidatorState(vs *state.Validator) error
PutVersion(v string) error
StoreAsBlock(block *block.Block, sysFee uint32) error
StoreAsCurrentBlock(block *block.Block) error
@ -307,61 +301,6 @@ func (dao *Simple) putUnspentCoinState(hash util.Uint256, ucs *state.UnspentCoin
// -- end unspent coins.
// -- start validator.
// GetValidatorStateOrNew gets validator from store or created new one in case of error.
func (dao *Simple) GetValidatorStateOrNew(publicKey *keys.PublicKey) (*state.Validator, error) {
validatorState, err := dao.GetValidatorState(publicKey)
if err != nil {
if err != storage.ErrKeyNotFound {
return nil, err
}
validatorState = &state.Validator{PublicKey: publicKey}
}
return validatorState, nil
}
// GetValidators returns all validators from store.
func (dao *Simple) GetValidators() []*state.Validator {
var validators []*state.Validator
dao.Store.Seek(storage.STValidator.Bytes(), func(k, v []byte) {
r := io.NewBinReaderFromBuf(v)
validator := &state.Validator{}
validator.DecodeBinary(r)
if r.Err != nil {
return
}
validators = append(validators, validator)
})
return validators
}
// GetValidatorState returns validator by publicKey.
func (dao *Simple) GetValidatorState(publicKey *keys.PublicKey) (*state.Validator, error) {
validatorState := &state.Validator{}
key := storage.AppendPrefix(storage.STValidator, publicKey.Bytes())
err := dao.GetAndDecode(validatorState, key)
if err != nil {
return nil, err
}
return validatorState, nil
}
// PutValidatorState puts given Validator into the given store.
func (dao *Simple) PutValidatorState(vs *state.Validator) error {
key := storage.AppendPrefix(storage.STValidator, vs.PublicKey.Bytes())
return dao.Put(vs, key)
}
// DeleteValidatorState deletes given Validator into the given store.
func (dao *Simple) DeleteValidatorState(vs *state.Validator) error {
key := storage.AppendPrefix(storage.STValidator, vs.PublicKey.Bytes())
return dao.Store.Delete(key)
}
// -- end validator.
// -- start notification event.
// GetAppExecResult gets application execution result from the

View file

@ -113,61 +113,6 @@ func TestPutGetUnspentCoinState(t *testing.T) {
require.Equal(t, unspentCoinState, gotUnspentCoinState)
}
func TestGetValidatorStateOrNew_New(t *testing.T) {
dao := NewSimple(storage.NewMemoryStore())
publicKey := &keys.PublicKey{}
validatorState, err := dao.GetValidatorStateOrNew(publicKey)
require.NoError(t, err)
require.NotNil(t, validatorState)
}
func TestPutGetValidatorState(t *testing.T) {
dao := NewSimple(storage.NewMemoryStore())
publicKey := &keys.PublicKey{}
validatorState := &state.Validator{
PublicKey: publicKey,
Registered: false,
Votes: 0,
}
err := dao.PutValidatorState(validatorState)
require.NoError(t, err)
gotValidatorState, err := dao.GetValidatorState(publicKey)
require.NoError(t, err)
require.Equal(t, validatorState, gotValidatorState)
}
func TestDeleteValidatorState(t *testing.T) {
dao := NewSimple(storage.NewMemoryStore())
publicKey := &keys.PublicKey{}
validatorState := &state.Validator{
PublicKey: publicKey,
Registered: false,
Votes: 0,
}
err := dao.PutValidatorState(validatorState)
require.NoError(t, err)
err = dao.DeleteValidatorState(validatorState)
require.NoError(t, err)
gotValidatorState, err := dao.GetValidatorState(publicKey)
require.Error(t, err)
require.Nil(t, gotValidatorState)
}
func TestGetValidators(t *testing.T) {
dao := NewSimple(storage.NewMemoryStore())
publicKey := &keys.PublicKey{}
validatorState := &state.Validator{
PublicKey: publicKey,
Registered: false,
Votes: 0,
}
err := dao.PutValidatorState(validatorState)
require.NoError(t, err)
validators := dao.GetValidators()
require.Equal(t, validatorState, validators[0])
require.Len(t, validators, 1)
}
func TestPutGetAppExecResult(t *testing.T) {
dao := NewSimple(storage.NewMemoryStore())
hash := random.Uint256()

View file

@ -236,13 +236,6 @@ func witnessGetVerificationScript(ic *interop.Context, v *vm.VM) error {
return nil
}
// bcGetValidators returns validators.
func bcGetValidators(ic *interop.Context, v *vm.VM) error {
validators := ic.DAO.GetValidators()
v.Estack().PushVal(validators)
return nil
}
// popInputFromVM returns transaction.Input from the first estack element.
func popInputFromVM(v *vm.VM) (*transaction.Input, error) {
inInterface := v.Estack().Pop().Value()

View file

@ -139,7 +139,6 @@ var neoInterops = []interop.Function{
{Name: "Neo.Blockchain.GetHeight", Func: bcGetHeight, Price: 1},
{Name: "Neo.Blockchain.GetTransaction", Func: bcGetTransaction, Price: 100},
{Name: "Neo.Blockchain.GetTransactionHeight", Func: bcGetTransactionHeight, Price: 100},
{Name: "Neo.Blockchain.GetValidators", Func: bcGetValidators, Price: 200},
{Name: "Neo.Contract.Create", Func: contractCreate, Price: 0},
{Name: "Neo.Contract.Destroy", Func: contractDestroy, Price: 1},
{Name: "Neo.Contract.GetScript", Func: contractGetScript, Price: 1},
@ -225,7 +224,6 @@ var neoInterops = []interop.Function{
{Name: "AntShares.Blockchain.GetHeader", Func: bcGetHeader, Price: 100},
{Name: "AntShares.Blockchain.GetHeight", Func: bcGetHeight, Price: 1},
{Name: "AntShares.Blockchain.GetTransaction", Func: bcGetTransaction, Price: 100},
{Name: "AntShares.Blockchain.GetValidators", Func: bcGetValidators, Price: 200},
{Name: "AntShares.Contract.Create", Func: contractCreate, Price: 0},
{Name: "AntShares.Contract.Destroy", Func: contractDestroy, Price: 1},
{Name: "AntShares.Contract.GetScript", Func: contractGetScript, Price: 1},

View file

@ -31,12 +31,6 @@ type keyWithVotes struct {
Votes *big.Int
}
// pkeyWithVotes is a deserialized key with votes balance.
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.
@ -349,6 +343,24 @@ func (n *NEO) getRegisteredValidators(d dao.DAO) ([]keyWithVotes, error) {
return arr, nil
}
// GetRegisteredValidators returns current registered validators list with keys
// and votes.
func (n *NEO) GetRegisteredValidators(d dao.DAO) ([]state.Validator, error) {
kvs, err := n.getRegisteredValidators(d)
if err != nil {
return nil, err
}
arr := make([]state.Validator, len(kvs))
for i := range kvs {
arr[i].Key, err = keys.NewPublicKeyFromBytes([]byte(kvs[i].Key))
if err != nil {
return nil, err
}
arr[i].Votes = kvs[i].Votes
}
return arr, nil
}
func (n *NEO) getRegisteredValidatorsCall(ic *interop.Context, _ []vm.StackItem) vm.StackItem {
validators, err := n.getRegisteredValidators(ic.DAO)
if err != nil {
@ -374,18 +386,10 @@ func (n *NEO) GetValidatorsInternal(bc blockchainer.Blockchainer, d dao.DAO) (ke
if err != nil {
return nil, err
}
validatorsBytes, err := n.getRegisteredValidators(d)
validators, err := n.GetRegisteredValidators(d)
if err != nil {
return nil, err
}
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.
cmp := validators[i].Votes.Cmp(validators[j].Votes)
@ -446,7 +450,7 @@ func (n *NEO) getNextBlockValidators(ic *interop.Context, _ []vm.StackItem) vm.S
func (n *NEO) GetNextBlockValidatorsInternal(bc blockchainer.Blockchainer, d dao.DAO) (keys.PublicKeys, error) {
si := d.GetStorageItem(n.Hash, nextValidatorsKey)
if si == nil {
return bc.GetStandByValidators()
return n.GetValidatorsInternal(bc, d)
}
pubs := keys.PublicKeys{}
err := pubs.DecodeBytes(si.Value)

View file

@ -1,39 +1,13 @@
package state
import (
"math/big"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/util"
)
// Validator holds the state of a validator.
// Validator holds the state of a validator (its key and votes balance).
type Validator struct {
PublicKey *keys.PublicKey
Registered bool
Votes util.Fixed8
}
// RegisteredAndHasVotes returns true or false whether Validator is registered and has votes.
func (vs *Validator) RegisteredAndHasVotes() bool {
return vs.Registered && vs.Votes > util.Fixed8(0)
}
// UnregisteredAndHasNoVotes returns true when Validator is not registered and has no votes.
func (vs *Validator) UnregisteredAndHasNoVotes() bool {
return !vs.Registered && vs.Votes == 0
}
// EncodeBinary encodes Validator to the given BinWriter.
func (vs *Validator) EncodeBinary(bw *io.BinWriter) {
vs.PublicKey.EncodeBinary(bw)
bw.WriteBool(vs.Registered)
vs.Votes.EncodeBinary(bw)
}
// DecodeBinary decodes Validator from the given BinReader.
func (vs *Validator) DecodeBinary(reader *io.BinReader) {
vs.PublicKey = &keys.PublicKey{}
vs.PublicKey.DecodeBinary(reader)
vs.Registered = reader.ReadBool()
vs.Votes.DecodeBinary(reader)
Key *keys.PublicKey
Votes *big.Int
}

View file

@ -1,57 +0,0 @@
package state
import (
"math/big"
"testing"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/internal/testserdes"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/stretchr/testify/require"
)
func TestValidatorState_DecodeEncodeBinary(t *testing.T) {
state := &Validator{
PublicKey: &keys.PublicKey{},
Registered: false,
Votes: util.Fixed8(10),
}
testserdes.EncodeDecodeBinary(t, state, new(Validator))
}
func TestRegisteredAndHasVotes_Registered(t *testing.T) {
state := &Validator{
PublicKey: &keys.PublicKey{
X: big.NewInt(1),
Y: big.NewInt(1),
},
Registered: true,
Votes: 0,
}
require.False(t, state.RegisteredAndHasVotes())
}
func TestRegisteredAndHasVotes_RegisteredWithVotes(t *testing.T) {
state := &Validator{
PublicKey: &keys.PublicKey{
X: big.NewInt(1),
Y: big.NewInt(1),
},
Registered: true,
Votes: 1,
}
require.True(t, state.RegisteredAndHasVotes())
}
func TestRegisteredAndHasVotes_NotRegisteredWithVotes(t *testing.T) {
state := &Validator{
PublicKey: &keys.PublicKey{
X: big.NewInt(1),
Y: big.NewInt(1),
},
Registered: false,
Votes: 1,
}
require.False(t, state.RegisteredAndHasVotes())
}

View file

@ -96,13 +96,13 @@ func (chain testChain) GetNEP5TransferLog(util.Uint160) *state.NEP5TransferLog {
func (chain testChain) GetNEP5Balances(util.Uint160) *state.NEP5Balances {
panic("TODO")
}
func (chain testChain) GetValidators(...*transaction.Transaction) ([]*keys.PublicKey, error) {
func (chain testChain) GetValidators() ([]*keys.PublicKey, error) {
panic("TODO")
}
func (chain testChain) GetStandByValidators() (keys.PublicKeys, error) {
panic("TODO")
}
func (chain testChain) GetEnrollments() ([]*state.Validator, error) {
func (chain testChain) GetEnrollments() ([]state.Validator, error) {
panic("TODO")
}
func (chain testChain) GetScriptHashesForVerifying(*transaction.Transaction) ([]util.Uint160, error) {

View file

@ -2,13 +2,12 @@ package result
import (
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/util"
)
// Validator used for the representation of
// state.Validator on the RPC Server.
type Validator struct {
PublicKey keys.PublicKey `json:"publickey"`
Votes util.Fixed8 `json:"votes"`
Votes int64 `json:"votes,string"`
Active bool `json:"active"`
}

View file

@ -815,9 +815,9 @@ func (s *Server) getValidators(_ request.Params) (interface{}, error) {
var res []result.Validator
for _, v := range enrollments {
res = append(res, result.Validator{
PublicKey: *v.PublicKey,
Votes: v.Votes,
Active: validators.Contains(v.PublicKey),
PublicKey: *v.Key,
Votes: v.Votes.Int64(),
Active: validators.Contains(v.Key),
})
}
return res, nil