native: cache committee members

This commit is contained in:
Evgenii Stratonikov 2020-08-28 10:24:54 +03:00
parent 43b3e15330
commit 83e94d3bbc
4 changed files with 73 additions and 91 deletions

View file

@ -1212,10 +1212,7 @@ func (bc *Blockchain) verifyTxAttributes(tx *transaction.Transaction) error {
for i := range tx.Attributes {
switch tx.Attributes[i].Type {
case transaction.HighPriority:
pubs, err := bc.contracts.NEO.GetCommitteeMembers(bc, bc.dao)
if err != nil {
return err
}
pubs := bc.contracts.NEO.GetCommitteeMembers()
s, err := smartcontract.CreateMajorityMultiSigRedeemScript(pubs)
if err != nil {
return err
@ -1376,10 +1373,7 @@ func (bc *Blockchain) GetStandByCommittee() keys.PublicKeys {
// GetCommittee returns the sorted list of public keys of nodes in committee.
func (bc *Blockchain) GetCommittee() (keys.PublicKeys, error) {
pubs, err := bc.contracts.NEO.GetCommitteeMembers(bc, bc.dao)
if err != nil {
return nil, err
}
pubs := bc.contracts.NEO.GetCommitteeMembers()
sort.Sort(pubs)
return pubs, nil
}
@ -1391,7 +1385,7 @@ func (bc *Blockchain) GetValidators() ([]*keys.PublicKey, error) {
// GetNextBlockValidators returns next block validators.
func (bc *Blockchain) GetNextBlockValidators() ([]*keys.PublicKey, error) {
return bc.contracts.NEO.GetNextBlockValidatorsInternal(bc, bc.dao)
return bc.contracts.NEO.GetNextBlockValidatorsInternal(), nil
}
// GetEnrollments returns all registered validators.

View file

@ -2,7 +2,6 @@ package native
import (
"errors"
"fmt"
"math/big"
"github.com/nspcc-dev/neo-go/pkg/core/interop"
@ -89,10 +88,7 @@ func (g *GAS) OnPersist(ic *interop.Context) error {
absAmount := big.NewInt(tx.SystemFee + tx.NetworkFee)
g.burn(ic, tx.Sender(), absAmount)
}
validators, err := g.NEO.getNextBlockValidatorsInternal(ic.Chain, ic.DAO)
if err != nil {
return fmt.Errorf("can't get block validators: %w", err)
}
validators := g.NEO.GetNextBlockValidatorsInternal()
primary := validators[ic.Block.ConsensusData.PrimaryIndex].GetScriptHash()
var netFee int64
for _, tx := range ic.Block.Transactions {

View file

@ -30,6 +30,10 @@ type NEO struct {
votesChanged atomic.Value
nextValidators atomic.Value
validators atomic.Value
// committee contains cached committee members and
// is updated during block persist. It's value
// is always equal to value stored by `prefixCommittee`.
committee atomic.Value
}
// keyWithVotes is a serialized key with votes balance. It's not deserialized
@ -63,9 +67,8 @@ const (
)
var (
// nextValidatorsKey is a key used to store validators for the
// next block.
nextValidatorsKey = []byte{14}
// prefixCommittee is a key used to store committee.
prefixCommittee = []byte{14}
)
// makeValidatorKey creates a key from account script hash.
@ -93,6 +96,7 @@ func NewNEO() *NEO {
n.votesChanged.Store(true)
n.nextValidators.Store(keys.PublicKeys(nil))
n.validators.Store(keys.PublicKeys(nil))
n.committee.Store(keys.PublicKeys(nil))
onp := n.Methods["onPersist"]
onp.Func = getOnPersistWrapper(n.onPersist)
@ -158,6 +162,15 @@ func (n *NEO) Initialize(ic *interop.Context) error {
return errors.New("already initialized")
}
committee := ic.Chain.GetStandByCommittee()
n.committee.Store(committee)
n.updateNextValidators(committee, ic.Chain)
err := ic.DAO.PutStorageItem(n.ContractID, prefixCommittee, &state.StorageItem{Value: committee.Bytes()})
if err != nil {
return err
}
h, err := getStandbyValidatorsHash(ic)
if err != nil {
return err
@ -177,45 +190,47 @@ func (n *NEO) Initialize(ic *interop.Context) error {
return nil
}
func (n *NEO) updateNextValidators(committee keys.PublicKeys, bc blockchainer.Blockchainer) {
nextVals := committee[:bc.GetConfig().ValidatorsCount].Copy()
sort.Sort(nextVals)
n.nextValidators.Store(nextVals)
}
func (n *NEO) updateCommittee(ic *interop.Context) error {
votesChanged := n.votesChanged.Load().(bool)
if !votesChanged {
// We need to put in storage anyway, as it affects dumps
committee := n.committee.Load().(keys.PublicKeys)
si := &state.StorageItem{Value: committee.Bytes()}
return ic.DAO.PutStorageItem(n.ContractID, prefixCommittee, si)
}
committee, err := n.ComputeCommitteeMembers(ic.Chain, ic.DAO)
if err != nil {
return err
}
n.committee.Store(committee)
n.updateNextValidators(committee, ic.Chain)
n.votesChanged.Store(false)
si := &state.StorageItem{Value: committee.Bytes()}
return ic.DAO.PutStorageItem(n.ContractID, prefixCommittee, si)
}
// OnPersist implements Contract interface.
func (n *NEO) OnPersist(ic *interop.Context) error {
if err := n.updateCommittee(ic); err != nil {
return err
}
gas, err := n.GetGASPerBlock(ic, ic.Block.Index)
if err != nil {
return err
}
pubs, err := n.GetCommitteeMembers(ic.Chain, ic.DAO)
if err != nil {
return err
}
pubs := n.GetCommitteeMembers()
index := int(ic.Block.Index) % len(ic.Chain.GetConfig().StandbyCommittee)
gas.Mul(gas, big.NewInt(committeeRewardRatio))
n.GAS.mint(ic, pubs[index].GetScriptHash(), gas.Div(gas, big.NewInt(100)))
if !n.votesChanged.Load().(bool) {
return nil
}
pubs, err = n.GetValidatorsInternal(ic.Chain, ic.DAO)
if err != nil {
return err
}
prev := n.nextValidators.Load().(keys.PublicKeys)
if len(prev) == len(pubs) {
var needUpdate bool
for i := range pubs {
if !pubs[i].Equal(prev[i]) {
needUpdate = true
break
}
}
if !needUpdate {
return nil
}
}
n.votesChanged.Store(false)
n.nextValidators.Store(pubs)
si := new(state.StorageItem)
si.Value = pubs.Bytes()
return ic.DAO.PutStorageItem(n.ContractID, nextValidatorsKey, si)
return nil
}
func (n *NEO) increaseBalance(ic *interop.Context, h util.Uint160, si *state.StorageItem, amount *big.Int) error {
@ -304,10 +319,7 @@ func (n *NEO) GetGASPerBlock(ic *interop.Context, index uint32) (*big.Int, error
// GetCommitteeAddress returns address of the committee.
func (n *NEO) GetCommitteeAddress(bc blockchainer.Blockchainer, d dao.DAO) (util.Uint160, error) {
pubs, err := n.GetCommitteeMembers(bc, d)
if err != nil {
return util.Uint160{}, err
}
pubs := n.GetCommitteeMembers()
script, err := smartcontract.CreateMajorityMultiSigRedeemScript(pubs)
if err != nil {
return util.Uint160{}, err
@ -578,10 +590,7 @@ func (n *NEO) GetValidatorsInternal(bc blockchainer.Blockchainer, d dao.DAO) (ke
if vals := n.validators.Load().(keys.PublicKeys); vals != nil {
return vals.Copy(), nil
}
result, err := n.GetCommitteeMembers(bc, d)
if err != nil {
return nil, err
}
result := n.GetCommitteeMembers()
count := bc.GetConfig().ValidatorsCount
if len(result) < count {
count = len(result)
@ -601,10 +610,7 @@ func (n *NEO) getValidators(ic *interop.Context, _ []stackitem.Item) stackitem.I
}
func (n *NEO) getCommittee(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
pubs, err := n.GetCommitteeMembers(ic.Chain, ic.DAO)
if err != nil {
panic(err)
}
pubs := n.GetCommitteeMembers()
sort.Sort(pubs)
return pubsToArray(pubs)
}
@ -621,8 +627,13 @@ func (n *NEO) modifyVoterTurnout(d dao.DAO, amount *big.Int) error {
return d.PutStorageItem(n.ContractID, key, si)
}
// GetCommitteeMembers returns public keys of nodes in committee.
func (n *NEO) GetCommitteeMembers(bc blockchainer.Blockchainer, d dao.DAO) (keys.PublicKeys, error) {
// GetCommitteeMembers returns public keys of nodes in committee using cached value.
func (n *NEO) GetCommitteeMembers() keys.PublicKeys {
return n.committee.Load().(keys.PublicKeys).Copy()
}
// ComputeCommitteeMembers returns public keys of nodes in committee.
func (n *NEO) ComputeCommitteeMembers(bc blockchainer.Blockchainer, d dao.DAO) (keys.PublicKeys, error) {
key := []byte{prefixVotersCount}
si := d.GetStorageItem(n.ContractID, key)
if si == nil {
@ -633,7 +644,8 @@ func (n *NEO) GetCommitteeMembers(bc blockchainer.Blockchainer, d dao.DAO) (keys
votersCount.Mul(votersCount, big.NewInt(effectiveVoterTurnout))
voterTurnout := votersCount.Div(votersCount, n.getTotalSupply(d))
if voterTurnout.Sign() != 1 {
return bc.GetStandByCommittee(), nil
pubs := bc.GetStandByCommittee()
return pubs, nil
}
cs, err := n.getCandidates(d)
if err != nil {
@ -664,34 +676,13 @@ func (n *NEO) GetCommitteeMembers(bc blockchainer.Blockchainer, d dao.DAO) (keys
}
func (n *NEO) getNextBlockValidators(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
result, err := n.getNextBlockValidatorsInternal(ic.Chain, ic.DAO)
if err != nil {
panic(err)
}
result := n.GetNextBlockValidatorsInternal()
return pubsToArray(result)
}
// GetNextBlockValidatorsInternal returns next block validators.
func (n *NEO) GetNextBlockValidatorsInternal(bc blockchainer.Blockchainer, d dao.DAO) (keys.PublicKeys, error) {
pubs, err := n.getNextBlockValidatorsInternal(bc, d)
if err != nil {
return nil, err
}
return pubs.Copy(), nil
}
// getNextBlockValidatorsInternal returns next block validators.
func (n *NEO) getNextBlockValidatorsInternal(bc blockchainer.Blockchainer, d dao.DAO) (keys.PublicKeys, error) {
si := d.GetStorageItem(n.ContractID, nextValidatorsKey)
if si == nil {
return n.GetValidatorsInternal(bc, d)
}
pubs := keys.PublicKeys{}
err := pubs.DecodeBytes(si.Value)
if err != nil {
return nil, err
}
return pubs, nil
func (n *NEO) GetNextBlockValidatorsInternal() keys.PublicKeys {
return n.nextValidators.Load().(keys.PublicKeys).Copy()
}
func pubsToArray(pubs keys.PublicKeys) stackitem.Item {

View file

@ -68,14 +68,15 @@ func TestNEO_Vote(t *testing.T) {
require.NoError(t, neo.VoteInternal(ic, h, candidates[i]))
}
require.NoError(t, neo.OnPersist(ic))
// We still haven't voted enough validators in.
pubs, err = neo.GetValidatorsInternal(bc, ic.DAO)
require.NoError(t, err)
require.Equal(t, standBySorted, pubs)
require.NoError(t, neo.OnPersist(ic))
pubs, err = neo.GetNextBlockValidatorsInternal(bc, ic.DAO)
require.NoError(t, err)
pubs = neo.GetNextBlockValidatorsInternal()
require.EqualValues(t, standBySorted, pubs)
// Register and give some value to the last validator.
@ -91,19 +92,19 @@ func TestNEO_Vote(t *testing.T) {
require.NoError(t, neo.RegisterCandidateInternal(ic, priv.PublicKey()))
}
require.NoError(t, neo.OnPersist(ic))
pubs, err = neo.GetValidatorsInternal(bc, ic.DAO)
require.NoError(t, err)
sortedCandidates := candidates.Copy()
sort.Sort(sortedCandidates)
require.EqualValues(t, sortedCandidates, pubs)
require.NoError(t, neo.OnPersist(ic))
pubs, err = neo.GetNextBlockValidatorsInternal(bc, ic.DAO)
require.NoError(t, err)
pubs = neo.GetNextBlockValidatorsInternal()
require.EqualValues(t, sortedCandidates, pubs)
require.NoError(t, neo.UnregisterCandidateInternal(ic, candidates[0]))
require.Error(t, neo.VoteInternal(ic, h, candidates[0]))
require.NoError(t, neo.OnPersist(ic))
pubs, err = neo.GetValidatorsInternal(bc, ic.DAO)
require.NoError(t, err)