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

View file

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

View file

@ -30,6 +30,10 @@ type NEO struct {
votesChanged atomic.Value votesChanged atomic.Value
nextValidators atomic.Value nextValidators atomic.Value
validators 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 // keyWithVotes is a serialized key with votes balance. It's not deserialized
@ -63,9 +67,8 @@ const (
) )
var ( var (
// nextValidatorsKey is a key used to store validators for the // prefixCommittee is a key used to store committee.
// next block. prefixCommittee = []byte{14}
nextValidatorsKey = []byte{14}
) )
// makeValidatorKey creates a key from account script hash. // makeValidatorKey creates a key from account script hash.
@ -93,6 +96,7 @@ func NewNEO() *NEO {
n.votesChanged.Store(true) n.votesChanged.Store(true)
n.nextValidators.Store(keys.PublicKeys(nil)) n.nextValidators.Store(keys.PublicKeys(nil))
n.validators.Store(keys.PublicKeys(nil)) n.validators.Store(keys.PublicKeys(nil))
n.committee.Store(keys.PublicKeys(nil))
onp := n.Methods["onPersist"] onp := n.Methods["onPersist"]
onp.Func = getOnPersistWrapper(n.onPersist) onp.Func = getOnPersistWrapper(n.onPersist)
@ -158,6 +162,15 @@ func (n *NEO) Initialize(ic *interop.Context) error {
return errors.New("already initialized") 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) h, err := getStandbyValidatorsHash(ic)
if err != nil { if err != nil {
return err return err
@ -177,45 +190,47 @@ func (n *NEO) Initialize(ic *interop.Context) error {
return nil 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. // OnPersist implements Contract interface.
func (n *NEO) OnPersist(ic *interop.Context) error { 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) gas, err := n.GetGASPerBlock(ic, ic.Block.Index)
if err != nil { if err != nil {
return err return err
} }
pubs, err := n.GetCommitteeMembers(ic.Chain, ic.DAO) pubs := n.GetCommitteeMembers()
if err != nil {
return err
}
index := int(ic.Block.Index) % len(ic.Chain.GetConfig().StandbyCommittee) index := int(ic.Block.Index) % len(ic.Chain.GetConfig().StandbyCommittee)
gas.Mul(gas, big.NewInt(committeeRewardRatio)) gas.Mul(gas, big.NewInt(committeeRewardRatio))
n.GAS.mint(ic, pubs[index].GetScriptHash(), gas.Div(gas, big.NewInt(100))) n.GAS.mint(ic, pubs[index].GetScriptHash(), gas.Div(gas, big.NewInt(100)))
return nil
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)
} }
func (n *NEO) increaseBalance(ic *interop.Context, h util.Uint160, si *state.StorageItem, amount *big.Int) error { 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. // GetCommitteeAddress returns address of the committee.
func (n *NEO) GetCommitteeAddress(bc blockchainer.Blockchainer, d dao.DAO) (util.Uint160, error) { func (n *NEO) GetCommitteeAddress(bc blockchainer.Blockchainer, d dao.DAO) (util.Uint160, error) {
pubs, err := n.GetCommitteeMembers(bc, d) pubs := n.GetCommitteeMembers()
if err != nil {
return util.Uint160{}, err
}
script, err := smartcontract.CreateMajorityMultiSigRedeemScript(pubs) script, err := smartcontract.CreateMajorityMultiSigRedeemScript(pubs)
if err != nil { if err != nil {
return util.Uint160{}, err 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 { if vals := n.validators.Load().(keys.PublicKeys); vals != nil {
return vals.Copy(), nil return vals.Copy(), nil
} }
result, err := n.GetCommitteeMembers(bc, d) result := n.GetCommitteeMembers()
if err != nil {
return nil, err
}
count := bc.GetConfig().ValidatorsCount count := bc.GetConfig().ValidatorsCount
if len(result) < count { if len(result) < count {
count = len(result) 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 { func (n *NEO) getCommittee(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
pubs, err := n.GetCommitteeMembers(ic.Chain, ic.DAO) pubs := n.GetCommitteeMembers()
if err != nil {
panic(err)
}
sort.Sort(pubs) sort.Sort(pubs)
return pubsToArray(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) return d.PutStorageItem(n.ContractID, key, si)
} }
// GetCommitteeMembers returns public keys of nodes in committee. // GetCommitteeMembers returns public keys of nodes in committee using cached value.
func (n *NEO) GetCommitteeMembers(bc blockchainer.Blockchainer, d dao.DAO) (keys.PublicKeys, error) { 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} key := []byte{prefixVotersCount}
si := d.GetStorageItem(n.ContractID, key) si := d.GetStorageItem(n.ContractID, key)
if si == nil { if si == nil {
@ -633,7 +644,8 @@ func (n *NEO) GetCommitteeMembers(bc blockchainer.Blockchainer, d dao.DAO) (keys
votersCount.Mul(votersCount, big.NewInt(effectiveVoterTurnout)) votersCount.Mul(votersCount, big.NewInt(effectiveVoterTurnout))
voterTurnout := votersCount.Div(votersCount, n.getTotalSupply(d)) voterTurnout := votersCount.Div(votersCount, n.getTotalSupply(d))
if voterTurnout.Sign() != 1 { if voterTurnout.Sign() != 1 {
return bc.GetStandByCommittee(), nil pubs := bc.GetStandByCommittee()
return pubs, nil
} }
cs, err := n.getCandidates(d) cs, err := n.getCandidates(d)
if err != nil { 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 { func (n *NEO) getNextBlockValidators(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
result, err := n.getNextBlockValidatorsInternal(ic.Chain, ic.DAO) result := n.GetNextBlockValidatorsInternal()
if err != nil {
panic(err)
}
return pubsToArray(result) return pubsToArray(result)
} }
// GetNextBlockValidatorsInternal returns next block validators. // GetNextBlockValidatorsInternal returns next block validators.
func (n *NEO) GetNextBlockValidatorsInternal(bc blockchainer.Blockchainer, d dao.DAO) (keys.PublicKeys, error) { func (n *NEO) GetNextBlockValidatorsInternal() keys.PublicKeys {
pubs, err := n.getNextBlockValidatorsInternal(bc, d) return n.nextValidators.Load().(keys.PublicKeys).Copy()
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 pubsToArray(pubs keys.PublicKeys) stackitem.Item { 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.VoteInternal(ic, h, candidates[i]))
} }
require.NoError(t, neo.OnPersist(ic))
// We still haven't voted enough validators in. // We still haven't voted enough validators in.
pubs, err = neo.GetValidatorsInternal(bc, ic.DAO) pubs, err = neo.GetValidatorsInternal(bc, ic.DAO)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, standBySorted, pubs) require.Equal(t, standBySorted, pubs)
require.NoError(t, neo.OnPersist(ic)) require.NoError(t, neo.OnPersist(ic))
pubs, err = neo.GetNextBlockValidatorsInternal(bc, ic.DAO) pubs = neo.GetNextBlockValidatorsInternal()
require.NoError(t, err)
require.EqualValues(t, standBySorted, pubs) require.EqualValues(t, standBySorted, pubs)
// Register and give some value to the last validator. // 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.RegisterCandidateInternal(ic, priv.PublicKey()))
} }
require.NoError(t, neo.OnPersist(ic))
pubs, err = neo.GetValidatorsInternal(bc, ic.DAO) pubs, err = neo.GetValidatorsInternal(bc, ic.DAO)
require.NoError(t, err) require.NoError(t, err)
sortedCandidates := candidates.Copy() sortedCandidates := candidates.Copy()
sort.Sort(sortedCandidates) sort.Sort(sortedCandidates)
require.EqualValues(t, sortedCandidates, pubs) require.EqualValues(t, sortedCandidates, pubs)
require.NoError(t, neo.OnPersist(ic)) pubs = neo.GetNextBlockValidatorsInternal()
pubs, err = neo.GetNextBlockValidatorsInternal(bc, ic.DAO)
require.NoError(t, err)
require.EqualValues(t, sortedCandidates, pubs) require.EqualValues(t, sortedCandidates, pubs)
require.NoError(t, neo.UnregisterCandidateInternal(ic, candidates[0])) require.NoError(t, neo.UnregisterCandidateInternal(ic, candidates[0]))
require.Error(t, neo.VoteInternal(ic, h, candidates[0])) require.Error(t, neo.VoteInternal(ic, h, candidates[0]))
require.NoError(t, neo.OnPersist(ic))
pubs, err = neo.GetValidatorsInternal(bc, ic.DAO) pubs, err = neo.GetValidatorsInternal(bc, ic.DAO)
require.NoError(t, err) require.NoError(t, err)