native: make newEpochNextValidators always contain non-empty value

If it's the end of epoch, then it contains the updated validators list recalculated
during the last block's PostPersist. If it's middle of the epoch, then it contains
previously calculated value (value for the previous completed epoch) that is equal
to the current nextValidators cache value.

Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
This commit is contained in:
Anna Shaleva 2023-09-01 18:15:36 +03:00
parent f78f915071
commit d9644204ee
3 changed files with 40 additions and 54 deletions

View file

@ -683,16 +683,9 @@ func (s *service) getValidators(txes ...block.Transaction) []crypto.PublicKey {
pKeys, err = s.Chain.GetNextBlockValidators() pKeys, err = s.Chain.GetNextBlockValidators()
} else { } else {
// getValidators with non-empty args is used by dbft to fill block's // getValidators with non-empty args is used by dbft to fill block's
// NextConsensus field, thus should return proper value for NextConsensus. // NextConsensus field, ComputeNextBlockValidators will return proper
cfg := s.Chain.GetConfig().ProtocolConfiguration // value for NextConsensus wrt dBFT epoch start/end.
if cfg.ShouldUpdateCommitteeAt(s.dbft.Context.BlockIndex) { pKeys = s.Chain.ComputeNextBlockValidators()
// Calculate NextConsensus based on the most fresh chain state.
pKeys = s.Chain.ComputeNextBlockValidators()
} else {
// Take the cached validators that are relevant for the current dBFT epoch
// to make NextConsensus.
pKeys, err = s.Chain.GetNextBlockValidators()
}
} }
if err != nil { if err != nil {
s.log.Error("error while trying to get validators", zap.Error(err)) s.log.Error("error while trying to get validators", zap.Error(err))
@ -734,20 +727,7 @@ func (s *service) newBlockFromContext(ctx *dbft.Context) block.Block {
block.PrevStateRoot = sr.Root block.PrevStateRoot = sr.Root
} }
var validators keys.PublicKeys var validators = s.Chain.ComputeNextBlockValidators()
var err error
cfg := s.Chain.GetConfig().ProtocolConfiguration
if cfg.ShouldUpdateCommitteeAt(ctx.BlockIndex) {
// Calculate NextConsensus based on the most fresh chain state.
validators = s.Chain.ComputeNextBlockValidators()
} else {
// Take the cached validators that are relevant for the current dBFT epoch
// to make NextConsensus.
validators, err = s.Chain.GetNextBlockValidators()
}
if err != nil {
s.log.Fatal(fmt.Sprintf("failed to get validators: %s", err.Error()))
}
script, err := smartcontract.CreateDefaultMultiSigRedeemScript(validators) script, err := smartcontract.CreateDefaultMultiSigRedeemScript(validators)
if err != nil { if err != nil {
s.log.Fatal(fmt.Sprintf("failed to create multisignature script: %s", err.Error())) s.log.Fatal(fmt.Sprintf("failed to create multisignature script: %s", err.Error()))

View file

@ -2698,9 +2698,12 @@ func (bc *Blockchain) GetCommittee() (keys.PublicKeys, error) {
} }
// ComputeNextBlockValidators returns current validators. Validators list // ComputeNextBlockValidators returns current validators. Validators list
// returned from this method may be updated every block (depending on // returned from this method is updated once per CommitteeSize number of blocks.
// register/unregister/vote calls to NeoToken contract), not only once per // For the last block in the dBFT epoch this method returns the list of validators
// (committee size) number of blocks. // recalculated from the latest relevant information about NEO votes; in this case
// list of validators may differ from the one returned by GetNextBlockValidators.
// For the not-last block of dBFT epoch this method returns the same list as
// GetNextBlockValidators.
func (bc *Blockchain) ComputeNextBlockValidators() []*keys.PublicKey { func (bc *Blockchain) ComputeNextBlockValidators() []*keys.PublicKey {
return bc.contracts.NEO.ComputeNextBlockValidators(bc.dao) return bc.contracts.NEO.ComputeNextBlockValidators(bc.dao)
} }

View file

@ -139,9 +139,11 @@ func copyNeoCache(src, dst *NeoCache) {
// Can safely omit copying because the new array is created each time // Can safely omit copying because the new array is created each time
// newEpochNextValidators list, nextValidators and committee are updated. // newEpochNextValidators list, nextValidators and committee are updated.
dst.nextValidators = src.nextValidators dst.nextValidators = src.nextValidators
dst.newEpochNextValidators = src.newEpochNextValidators
dst.committee = src.committee dst.committee = src.committee
dst.committeeHash = src.committeeHash dst.committeeHash = src.committeeHash
dst.newEpochNextValidators = src.newEpochNextValidators
dst.newEpochCommittee = src.newEpochCommittee
dst.newEpochCommitteeHash = src.newEpochCommitteeHash
dst.registerPrice = src.registerPrice dst.registerPrice = src.registerPrice
@ -281,11 +283,6 @@ func (n *NEO) Initialize(ic *interop.Context) error {
cache := &NeoCache{ cache := &NeoCache{
gasPerVoteCache: make(map[string]big.Int), gasPerVoteCache: make(map[string]big.Int),
votesChanged: true, votesChanged: true,
// Will be updated in the last epoch block's PostPersist right before the committee update block.
// NeoToken's deployment (and initialization) isn't intended to be performed not-in-genesis block anyway.
newEpochNextValidators: nil,
newEpochCommittee: nil,
newEpochCommitteeHash: util.Uint160{},
} }
// We need cache to be present in DAO before the subsequent call to `mint`. // We need cache to be present in DAO before the subsequent call to `mint`.
@ -317,6 +314,11 @@ func (n *NEO) Initialize(ic *interop.Context) error {
setIntWithKey(n.ID, ic.DAO, []byte{prefixRegisterPrice}, DefaultRegisterPrice) setIntWithKey(n.ID, ic.DAO, []byte{prefixRegisterPrice}, DefaultRegisterPrice)
cache.registerPrice = int64(DefaultRegisterPrice) cache.registerPrice = int64(DefaultRegisterPrice)
var numOfCNs = n.cfg.GetNumOfCNs(ic.Block.Index + 1)
err = n.updateCachedNewEpochValues(ic.DAO, cache, ic.BlockHeight(), numOfCNs)
if err != nil {
return fmt.Errorf("failed to update next block newEpoch* cache: %w", err)
}
return nil return nil
} }
@ -328,13 +330,6 @@ func (n *NEO) InitializeCache(blockHeight uint32, d *dao.Simple) error {
cache := &NeoCache{ cache := &NeoCache{
gasPerVoteCache: make(map[string]big.Int), gasPerVoteCache: make(map[string]big.Int),
votesChanged: true, votesChanged: true,
// If it's a block in the middle of dBFT epoch, then no one must access
// these NewEpoch* fields, and they will be updated during the PostPersist of the last
// block in the current epoch. If it's the last block of the current epoch,
// then update this cache manually below.
newEpochNextValidators: nil,
newEpochCommittee: nil,
newEpochCommitteeHash: util.Uint160{},
} }
var committee = keysWithVotes{} var committee = keysWithVotes{}
@ -349,14 +344,21 @@ func (n *NEO) InitializeCache(blockHeight uint32, d *dao.Simple) error {
cache.gasPerBlock = n.getSortedGASRecordFromDAO(d) cache.gasPerBlock = n.getSortedGASRecordFromDAO(d)
cache.registerPrice = getIntWithKey(n.ID, d, []byte{prefixRegisterPrice}) cache.registerPrice = getIntWithKey(n.ID, d, []byte{prefixRegisterPrice})
// Update newEpoch* cache for external users if committee should be // Update newEpoch* cache for external users. It holds values for the previous
// updated in the next block. // dBFT epoch if the current one isn't yet finished.
if n.cfg.ShouldUpdateCommitteeAt(blockHeight + 1) { if n.cfg.ShouldUpdateCommitteeAt(blockHeight + 1) {
var numOfCNs = n.cfg.GetNumOfCNs(blockHeight + 1) var numOfCNs = n.cfg.GetNumOfCNs(blockHeight + 1)
err := n.updateCachedNewEpochValues(d, cache, blockHeight, numOfCNs) err := n.updateCachedNewEpochValues(d, cache, blockHeight, numOfCNs)
if err != nil { if err != nil {
return fmt.Errorf("failed to update next block newEpoch* cache: %w", err) return fmt.Errorf("failed to update next block newEpoch* cache: %w", err)
} }
} else {
// nextValidators, committee and committee hash are filled in by this moment
// via n.updateCache call.
cache.newEpochNextValidators = cache.nextValidators.Copy()
cache.newEpochCommittee = make(keysWithVotes, len(cache.committee))
copy(cache.newEpochCommittee, cache.committee)
cache.newEpochCommitteeHash = cache.committeeHash
} }
d.SetCache(n.ID, cache) d.SetCache(n.ID, cache)
@ -418,15 +420,10 @@ func (n *NEO) OnPersist(ic *interop.Context) error {
cache := ic.DAO.GetRWCache(n.ID).(*NeoCache) cache := ic.DAO.GetRWCache(n.ID).(*NeoCache)
// Cached newEpoch* values always have proper value set (either by PostPersist // Cached newEpoch* values always have proper value set (either by PostPersist
// during the last epoch block handling or by initialization code). // during the last epoch block handling or by initialization code).
oldKeys := cache.nextValidators cache.nextValidators = cache.newEpochNextValidators
oldCom := cache.committee cache.committee = cache.newEpochCommittee
if n.cfg.GetNumOfCNs(ic.Block.Index) != len(oldKeys) || cache.committeeHash = cache.newEpochCommitteeHash
n.cfg.GetCommitteeSize(ic.Block.Index) != len(oldCom) { cache.votesChanged = false
cache.nextValidators = cache.newEpochNextValidators
cache.committee = cache.newEpochCommittee
cache.committeeHash = cache.newEpochCommitteeHash
cache.votesChanged = false
}
// We need to put in storage anyway, as it affects dumps // We need to put in storage anyway, as it affects dumps
ic.DAO.PutStorageItem(n.ID, prefixCommittee, cache.committee.Bytes(ic.DAO.GetItemCtx())) ic.DAO.PutStorageItem(n.ID, prefixCommittee, cache.committee.Bytes(ic.DAO.GetItemCtx()))
@ -490,7 +487,9 @@ func (n *NEO) PostPersist(ic *interop.Context) error {
h = ic.Block.Index // consider persisting block as stored to get _next_ block newEpochNextValidators h = ic.Block.Index // consider persisting block as stored to get _next_ block newEpochNextValidators
numOfCNs = n.cfg.GetNumOfCNs(h + 1) numOfCNs = n.cfg.GetNumOfCNs(h + 1)
) )
if cache.newEpochNextValidators == nil || numOfCNs != len(cache.newEpochNextValidators) { if cache.votesChanged ||
numOfCNs != len(cache.newEpochNextValidators) ||
n.cfg.GetCommitteeSize(h+1) != len(cache.newEpochCommittee) {
if !isCacheRW { if !isCacheRW {
cache = ic.DAO.GetRWCache(n.ID).(*NeoCache) cache = ic.DAO.GetRWCache(n.ID).(*NeoCache)
} }
@ -839,7 +838,9 @@ func (n *NEO) UnregisterCandidateInternal(ic *interop.Context, pub *keys.PublicK
return nil return nil
} }
cache := ic.DAO.GetRWCache(n.ID).(*NeoCache) cache := ic.DAO.GetRWCache(n.ID).(*NeoCache)
cache.newEpochNextValidators = nil // Not only current committee/validators cache is interested in votesChanged, but also
// newEpoch cache, thus, modify votesChanged to update the latter.
cache.votesChanged = true
c := new(candidate).FromBytes(si) c := new(candidate).FromBytes(si)
emitEvent := c.Registered emitEvent := c.Registered
c.Registered = false c.Registered = false
@ -1120,7 +1121,9 @@ func (n *NEO) ComputeNextBlockValidators(d *dao.Simple) keys.PublicKeys {
return vals.Copy() return vals.Copy()
} }
// It's a caller's program error to call ComputeNextBlockValidators not having // It's a caller's program error to call ComputeNextBlockValidators not having
// the right value in lower cache. // the right value in lower cache. With the current scheme of handling
// newEpochNextValidators cache this code is expected to be unreachable, but
// let's have this panic here just in case.
panic("bug: unexpected external call to newEpochNextValidators cache") panic("bug: unexpected external call to newEpochNextValidators cache")
} }