diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index ec94e57e4..660381f27 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -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. diff --git a/pkg/core/native/native_gas.go b/pkg/core/native/native_gas.go index 524d6879d..310131086 100644 --- a/pkg/core/native/native_gas.go +++ b/pkg/core/native/native_gas.go @@ -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 { diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index 8e0185a8a..88e908985 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -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 { diff --git a/pkg/core/native_neo_test.go b/pkg/core/native_neo_test.go index 9917123ee..d4d049370 100644 --- a/pkg/core/native_neo_test.go +++ b/pkg/core/native_neo_test.go @@ -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)