config/core: allow to change the number of validators

Fixes #2320.
This commit is contained in:
Roman Khimov 2022-01-21 05:33:06 +03:00
parent 60d6fa1125
commit e621f746a7
12 changed files with 344 additions and 38 deletions

View file

@ -197,6 +197,7 @@ protocol-related settings described in the table below.
| Section | Type | Default value | Description | Notes | | Section | Type | Default value | Description | Notes |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| CommitteeHistory | map[uint32]int | none | Number of committee members after given height, for example `{0: 1, 20: 4}` sets up a chain with one committee member since the genesis and then changes the setting to 4 committee members at the height of 20. `StandbyCommittee` committee setting must have the number of keys equal or exceeding the highest value in this option. Blocks numbers where the change happens must be divisble by the old and by the new values simultaneously. If not set, committee size is derived from the `StandbyCommittee` setting and never changes. |
| KeepOnlyLatestState | `bool` | `false` | Specifies if MPT should only store latest state. If true, DB size will be smaller, but older roots won't be accessible. This value should remain the same for the same database. | | KeepOnlyLatestState | `bool` | `false` | Specifies if MPT should only store latest state. If true, DB size will be smaller, but older roots won't be accessible. This value should remain the same for the same database. |
| Magic | `uint32` | `0` | Magic number which uniquely identifies NEO network. | | Magic | `uint32` | `0` | Magic number which uniquely identifies NEO network. |
| MaxBlockSize | `uint32` | `262144` | Maximum block size in bytes. | | MaxBlockSize | `uint32` | `262144` | Maximum block size in bytes. |
@ -216,6 +217,7 @@ protocol-related settings described in the table below.
| StandbyCommittee | `[]string` | [] | List of public keys of standby committee validators are chosen from. | | StandbyCommittee | `[]string` | [] | List of public keys of standby committee validators are chosen from. |
| StateRootInHeader | `bool` | `false` | Enables storing state root in block header. | Experimental protocol extension! | | StateRootInHeader | `bool` | `false` | Enables storing state root in block header. | Experimental protocol extension! |
| StateSyncInterval | `int` | `40000` | The number of blocks between state heights available for MPT state data synchronization. | `P2PStateExchangeExtensions` should be enabled to use this setting. | | StateSyncInterval | `int` | `40000` | The number of blocks between state heights available for MPT state data synchronization. | `P2PStateExchangeExtensions` should be enabled to use this setting. |
| ValidatorsCount | `int` | `0` | Number of validators. | | ValidatorsCount | `int` | `0` | Number of validators set for the whole network lifetime, can't be set if `ValidatorsHistory` setting is used. |
| ValidatorsHistory | map[uint32]int | none | Number of consensus nodes to use after given height (see `CommitteeHistory` also). Heights where the change occurs must be divisible by the number of committee members at that height. Can't be used with `ValidatorsCount` not equal to zero. |
| VerifyBlocks | `bool` | `false` | Denotes whether to verify received blocks. | | VerifyBlocks | `bool` | `false` | Denotes whether to verify received blocks. |
| VerifyTransactions | `bool` | `false` | Denotes whether to verify transactions in received blocks. | | VerifyTransactions | `bool` | `false` | Denotes whether to verify transactions in received blocks. |

View file

@ -3,6 +3,7 @@ package config
import ( import (
"errors" "errors"
"fmt" "fmt"
"sort"
"github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/config/netmode"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
@ -12,8 +13,10 @@ import (
// ProtocolConfiguration represents the protocol config. // ProtocolConfiguration represents the protocol config.
type ( type (
ProtocolConfiguration struct { ProtocolConfiguration struct {
Magic netmode.Magic `yaml:"Magic"` // CommitteeHistory stores committee size change history (height: size).
MemPoolSize int `yaml:"MemPoolSize"` CommitteeHistory map[uint32]int `yaml:"CommitteeHistory"`
Magic netmode.Magic `yaml:"Magic"`
MemPoolSize int `yaml:"MemPoolSize"`
// InitialGASSupply is the amount of GAS generated in the genesis block. // InitialGASSupply is the amount of GAS generated in the genesis block.
InitialGASSupply fixedn.Fixed8 `yaml:"InitialGASSupply"` InitialGASSupply fixedn.Fixed8 `yaml:"InitialGASSupply"`
@ -57,6 +60,8 @@ type (
// It is valid only if P2PStateExchangeExtensions are enabled. // It is valid only if P2PStateExchangeExtensions are enabled.
StateSyncInterval int `yaml:"StateSyncInterval"` StateSyncInterval int `yaml:"StateSyncInterval"`
ValidatorsCount int `yaml:"ValidatorsCount"` ValidatorsCount int `yaml:"ValidatorsCount"`
// Validators stores history of changes to consensus node number (height: number).
ValidatorsHistory map[uint32]int `yaml:"ValidatorsHistory"`
// Whether to verify received blocks. // Whether to verify received blocks.
VerifyBlocks bool `yaml:"VerifyBlocks"` VerifyBlocks bool `yaml:"VerifyBlocks"`
// Whether to verify transactions in received blocks. // Whether to verify transactions in received blocks.
@ -64,17 +69,114 @@ type (
} }
) )
// heightNumber is an auxiliary structure for configuration checks.
type heightNumber struct {
h uint32
n int
}
// Validate checks ProtocolConfiguration for internal consistency and returns // Validate checks ProtocolConfiguration for internal consistency and returns
// error if anything inappropriate found. Other methods can rely on protocol // error if anything inappropriate found. Other methods can rely on protocol
// validity after this. // validity after this.
func (p *ProtocolConfiguration) Validate() error { func (p *ProtocolConfiguration) Validate() error {
if len(p.StandbyCommittee) < p.ValidatorsCount { var err error
return errors.New("validators count can't exceed the size of StandbyCommittee")
}
for name := range p.NativeUpdateHistories { for name := range p.NativeUpdateHistories {
if !nativenames.IsValid(name) { if !nativenames.IsValid(name) {
return fmt.Errorf("NativeActivations configuration section contains unexpected native contract name: %s", name) return fmt.Errorf("NativeActivations configuration section contains unexpected native contract name: %s", name)
} }
} }
if p.ValidatorsCount != 0 && len(p.ValidatorsHistory) != 0 {
return errors.New("configuration should either have ValidatorsCount or ValidatorsHistory, not both")
}
if len(p.StandbyCommittee) < p.ValidatorsCount {
return errors.New("validators count can't exceed the size of StandbyCommittee")
}
var arr = make([]heightNumber, 0, len(p.CommitteeHistory))
for h, n := range p.CommitteeHistory {
if int(n) > len(p.StandbyCommittee) {
return fmt.Errorf("too small StandbyCommittee for required number of committee members at %d", h)
}
arr = append(arr, heightNumber{h, n})
}
if len(arr) != 0 {
err = sortCheckZero(arr, "CommitteeHistory")
if err != nil {
return err
}
for i, hn := range arr[1:] {
if int64(hn.h)%int64(hn.n) != 0 || int64(hn.h)%int64(arr[i].n) != 0 {
return fmt.Errorf("invalid CommitteeHistory: bad %d height for %d and %d committee", hn.h, hn.n, arr[i].n)
}
}
}
arr = arr[:0]
for h, n := range p.ValidatorsHistory {
if int(n) > len(p.StandbyCommittee) {
return fmt.Errorf("too small StandbyCommittee for required number of validators at %d", h)
}
arr = append(arr, heightNumber{h, n})
}
if len(arr) != 0 {
err = sortCheckZero(arr, "ValidatorsHistory")
if err != nil {
return err
}
for _, hn := range arr {
if int64(hn.n) > int64(p.GetCommitteeSize(hn.h)) {
return fmt.Errorf("requested number of validators is too big: %d at %d", hn.n, hn.h)
}
if int64(hn.h)%int64(p.GetCommitteeSize(hn.h)) != 0 {
return fmt.Errorf("validators number change is not aligned with committee change at %d", hn.h)
}
}
}
return nil return nil
} }
// sortCheckZero sorts heightNumber array and checks for zero height presence.
func sortCheckZero(arr []heightNumber, field string) error {
sort.Slice(arr, func(i, j int) bool {
return arr[i].h < arr[j].h
})
if arr[0].h != 0 {
return fmt.Errorf("invalid %s: no height 0 specified", field)
}
return nil
}
// GetCommitteeSize returns the committee size for the given height. It implies
// valid configuration file.
func (p *ProtocolConfiguration) GetCommitteeSize(height uint32) int {
if len(p.CommitteeHistory) == 0 {
return len(p.StandbyCommittee)
}
return getBestFromMap(p.CommitteeHistory, height)
}
func getBestFromMap(dict map[uint32]int, height uint32) int {
var res int
var bestH = uint32(0)
for h, n := range dict {
if h >= bestH && h <= height {
res = n
bestH = h
}
}
return res
}
// GetNumOfCNs returns the number of validators for the given height.
// It implies valid configuration file.
func (p *ProtocolConfiguration) GetNumOfCNs(height uint32) int {
if len(p.ValidatorsHistory) == 0 {
return p.ValidatorsCount
}
return getBestFromMap(p.ValidatorsHistory, height)
}
// ShouldUpdateCommitteeAt answers the question of whether the committee
// should be updated at the given height.
func (p *ProtocolConfiguration) ShouldUpdateCommitteeAt(height uint32) bool {
return height%uint32(p.GetCommitteeSize(height)) == 0
}

View file

@ -13,8 +13,118 @@ func TestProtocolConfigurationValidation(t *testing.T) {
require.Error(t, p.Validate()) require.Error(t, p.Validate())
p = &ProtocolConfiguration{ p = &ProtocolConfiguration{
NativeUpdateHistories: map[string][]uint32{ NativeUpdateHistories: map[string][]uint32{
"someContract": []uint32{0, 10}, "someContract": {0, 10},
}, },
} }
require.Error(t, p.Validate()) require.Error(t, p.Validate())
p = &ProtocolConfiguration{
StandbyCommittee: []string{
"02b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc2",
"02103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e",
},
ValidatorsCount: 3,
}
require.Error(t, p.Validate())
p = &ProtocolConfiguration{
StandbyCommittee: []string{
"02b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc2",
"02103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e",
"03d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699",
"02a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd62",
},
ValidatorsCount: 4,
ValidatorsHistory: map[uint32]int{0: 4},
}
require.Error(t, p.Validate())
p = &ProtocolConfiguration{
StandbyCommittee: []string{
"02b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc2",
"02103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e",
"03d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699",
"02a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd62",
},
CommitteeHistory: map[uint32]int{0: 4},
ValidatorsHistory: map[uint32]int{0: 4, 1000: 5},
}
require.Error(t, p.Validate())
p = &ProtocolConfiguration{
StandbyCommittee: []string{
"02b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc2",
"02103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e",
"03d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699",
"02a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd62",
},
CommitteeHistory: map[uint32]int{0: 4, 1000: 5},
ValidatorsHistory: map[uint32]int{0: 4},
}
require.Error(t, p.Validate())
p = &ProtocolConfiguration{
StandbyCommittee: []string{
"02b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc2",
"02103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e",
"03d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699",
"02a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd62",
},
CommitteeHistory: map[uint32]int{0: 1, 999: 4},
ValidatorsHistory: map[uint32]int{0: 1},
}
require.Error(t, p.Validate())
p = &ProtocolConfiguration{
StandbyCommittee: []string{
"02b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc2",
"02103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e",
"03d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699",
"02a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd62",
},
CommitteeHistory: map[uint32]int{0: 1, 1000: 4},
ValidatorsHistory: map[uint32]int{0: 1, 999: 4},
}
require.Error(t, p.Validate())
p = &ProtocolConfiguration{
StandbyCommittee: []string{
"02b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc2",
"02103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e",
"03d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699",
"02a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd62",
},
CommitteeHistory: map[uint32]int{0: 1, 100: 4},
ValidatorsHistory: map[uint32]int{0: 4, 100: 4},
}
require.Error(t, p.Validate())
p = &ProtocolConfiguration{
StandbyCommittee: []string{
"02b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc2",
"02103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e",
"03d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699",
"02a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd62",
},
CommitteeHistory: map[uint32]int{0: 1, 100: 4},
ValidatorsHistory: map[uint32]int{0: 1, 100: 4},
}
require.NoError(t, p.Validate())
}
func TestGetCommitteeAndCNs(t *testing.T) {
p := &ProtocolConfiguration{
StandbyCommittee: []string{
"02b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc2",
"02103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e",
"03d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699",
"02a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd62",
},
CommitteeHistory: map[uint32]int{0: 1, 100: 4},
ValidatorsHistory: map[uint32]int{0: 1, 200: 4},
}
require.Equal(t, 1, p.GetCommitteeSize(0))
require.Equal(t, 1, p.GetCommitteeSize(99))
require.Equal(t, 4, p.GetCommitteeSize(100))
require.Equal(t, 4, p.GetCommitteeSize(101))
require.Equal(t, 4, p.GetCommitteeSize(200))
require.Equal(t, 4, p.GetCommitteeSize(201))
require.Equal(t, 1, p.GetNumOfCNs(0))
require.Equal(t, 1, p.GetNumOfCNs(100))
require.Equal(t, 1, p.GetNumOfCNs(101))
require.Equal(t, 1, p.GetNumOfCNs(199))
require.Equal(t, 4, p.GetNumOfCNs(200))
require.Equal(t, 4, p.GetNumOfCNs(201))
} }

View file

@ -16,7 +16,6 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/blockchainer" "github.com/nspcc-dev/neo-go/pkg/core/blockchainer"
"github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/mempool" "github.com/nspcc-dev/neo-go/pkg/core/mempool"
"github.com/nspcc-dev/neo-go/pkg/core/native"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
@ -48,6 +47,7 @@ const Category = "dBFT"
type Ledger interface { type Ledger interface {
AddBlock(block *coreb.Block) error AddBlock(block *coreb.Block) error
ApplyPolicyToTxSet([]*transaction.Transaction) []*transaction.Transaction ApplyPolicyToTxSet([]*transaction.Transaction) []*transaction.Transaction
GetConfig() config.ProtocolConfiguration
GetMemPool() *mempool.Pool GetMemPool() *mempool.Pool
GetNextBlockValidators() ([]*keys.PublicKey, error) GetNextBlockValidators() ([]*keys.PublicKey, error)
GetStateModule() blockchainer.StateRoot GetStateModule() blockchainer.StateRoot
@ -676,7 +676,8 @@ func (s *service) newBlockFromContext(ctx *dbft.Context) block.Block {
var validators keys.PublicKeys var validators keys.PublicKeys
var err error var err error
if native.ShouldUpdateCommittee(ctx.BlockIndex, s.Chain) { cfg := s.Chain.GetConfig()
if cfg.ShouldUpdateCommitteeAt(ctx.BlockIndex) {
validators, err = s.Chain.GetValidators() validators, err = s.Chain.GetValidators()
} else { } else {
validators, err = s.Chain.GetNextBlockValidators() validators, err = s.Chain.GetNextBlockValidators()
@ -684,7 +685,7 @@ func (s *service) newBlockFromContext(ctx *dbft.Context) block.Block {
if err != nil { if err != nil {
s.log.Fatal(fmt.Sprintf("failed to get validators: %s", err.Error())) s.log.Fatal(fmt.Sprintf("failed to get validators: %s", err.Error()))
} }
script, err := smartcontract.CreateMultiSigRedeemScript(s.dbft.Context.M(), 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

@ -88,7 +88,8 @@ func initServiceNextConsensus(t *testing.T, newAcc *wallet.Account, offset uint3
require.NoError(t, bc.PoolTx(tx)) require.NoError(t, bc.PoolTx(tx))
srv.dbft.OnTimeout(timer.HV{Height: srv.dbft.Context.BlockIndex}) srv.dbft.OnTimeout(timer.HV{Height: srv.dbft.Context.BlockIndex})
for i := srv.dbft.BlockIndex; !native.ShouldUpdateCommittee(i+offset, bc); i++ { cfg := bc.GetConfig()
for i := srv.dbft.BlockIndex; !cfg.ShouldUpdateCommitteeAt(i + offset); i++ {
srv.dbft.OnTimeout(timer.HV{Height: srv.dbft.Context.BlockIndex}) srv.dbft.OnTimeout(timer.HV{Height: srv.dbft.Context.BlockIndex})
} }

View file

@ -162,8 +162,11 @@ type Blockchain struct {
extensible atomic.Value extensible atomic.Value
// knownValidatorsCount is the latest known validators count used
// for defaultBlockWitness.
knownValidatorsCount atomic.Value
// defaultBlockWitness stores transaction.Witness with m out of n multisig, // defaultBlockWitness stores transaction.Witness with m out of n multisig,
// where n = ValidatorsCount. // where n = knownValidatorsCount.
defaultBlockWitness atomic.Value defaultBlockWitness atomic.Value
stateRoot *stateroot.Module stateRoot *stateroot.Module
@ -1177,7 +1180,7 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
} }
func (bc *Blockchain) updateExtensibleWhitelist(height uint32) error { func (bc *Blockchain) updateExtensibleWhitelist(height uint32) error {
updateCommittee := native.ShouldUpdateCommittee(height, bc) updateCommittee := bc.config.ShouldUpdateCommitteeAt(height)
stateVals, sh, err := bc.contracts.Designate.GetDesignatedByRole(bc.dao, noderoles.StateValidator, height) stateVals, sh, err := bc.contracts.Designate.GetDesignatedByRole(bc.dao, noderoles.StateValidator, height)
if err != nil { if err != nil {
return err return err
@ -1787,14 +1790,17 @@ func (bc *Blockchain) ApplyPolicyToTxSet(txes []*transaction.Transaction) []*tra
} }
maxBlockSize := bc.config.MaxBlockSize maxBlockSize := bc.config.MaxBlockSize
maxBlockSysFee := bc.config.MaxBlockSystemFee maxBlockSysFee := bc.config.MaxBlockSystemFee
oldVC := bc.knownValidatorsCount.Load()
defaultWitness := bc.defaultBlockWitness.Load() defaultWitness := bc.defaultBlockWitness.Load()
if defaultWitness == nil { curVC := bc.config.GetNumOfCNs(bc.BlockHeight() + 1)
m := smartcontract.GetDefaultHonestNodeCount(bc.config.ValidatorsCount) if oldVC == nil || oldVC != curVC {
m := smartcontract.GetDefaultHonestNodeCount(curVC)
verification, _ := smartcontract.CreateDefaultMultiSigRedeemScript(bc.contracts.NEO.GetNextBlockValidatorsInternal()) verification, _ := smartcontract.CreateDefaultMultiSigRedeemScript(bc.contracts.NEO.GetNextBlockValidatorsInternal())
defaultWitness = transaction.Witness{ defaultWitness = transaction.Witness{
InvocationScript: make([]byte, 66*m), InvocationScript: make([]byte, 66*m),
VerificationScript: verification, VerificationScript: verification,
} }
bc.knownValidatorsCount.Store(curVC)
bc.defaultBlockWitness.Store(defaultWitness) bc.defaultBlockWitness.Store(defaultWitness)
} }
var ( var (
@ -2085,7 +2091,7 @@ func (bc *Blockchain) PoolTxWithData(t *transaction.Transaction, data interface{
// GetStandByValidators returns validators from the configuration. // GetStandByValidators returns validators from the configuration.
func (bc *Blockchain) GetStandByValidators() keys.PublicKeys { func (bc *Blockchain) GetStandByValidators() keys.PublicKeys {
return bc.sbCommittee[:bc.config.ValidatorsCount].Copy() return bc.sbCommittee[:bc.config.GetNumOfCNs(bc.BlockHeight())].Copy()
} }
// GetStandByCommittee returns standby committee from the configuration. // GetStandByCommittee returns standby committee from the configuration.

View file

@ -1828,6 +1828,81 @@ func TestBlockchain_InitWithIncompleteStateJump(t *testing.T) {
} }
} }
func TestChainWithVolatileNumOfValidators(t *testing.T) {
bc := newTestChainWithCustomCfg(t, func(c *config.Config) {
c.ProtocolConfiguration.ValidatorsCount = 0
c.ProtocolConfiguration.CommitteeHistory = map[uint32]int{
0: 1,
4: 4,
24: 6,
}
c.ProtocolConfiguration.ValidatorsHistory = map[uint32]int{
0: 1,
4: 4,
}
require.NoError(t, c.ProtocolConfiguration.Validate())
})
require.Equal(t, uint32(0), bc.BlockHeight())
priv0 := testchain.PrivateKeyByID(0)
vals, err := bc.GetValidators()
require.NoError(t, err)
script, err := smartcontract.CreateDefaultMultiSigRedeemScript(vals)
require.NoError(t, err)
curWit := transaction.Witness{
VerificationScript: script,
}
for i := 1; i < 26; i++ {
comm, err := bc.GetCommittee()
require.NoError(t, err)
if i < 5 {
require.Equal(t, 1, len(comm))
} else if i < 25 {
require.Equal(t, 4, len(comm))
} else {
require.Equal(t, 6, len(comm))
}
// Mimic consensus.
if bc.config.ShouldUpdateCommitteeAt(uint32(i)) {
vals, err = bc.GetValidators()
} else {
vals, err = bc.GetNextBlockValidators()
}
require.NoError(t, err)
if i < 4 {
require.Equalf(t, 1, len(vals), "at %d", i)
} else {
require.Equalf(t, 4, len(vals), "at %d", i)
}
require.NoError(t, err)
script, err := smartcontract.CreateDefaultMultiSigRedeemScript(vals)
require.NoError(t, err)
nextWit := transaction.Witness{
VerificationScript: script,
}
b := &block.Block{
Header: block.Header{
NextConsensus: nextWit.ScriptHash(),
Script: curWit,
},
}
curWit = nextWit
b.PrevHash = bc.GetHeaderHash(i - 1)
b.Timestamp = uint64(time.Now().UTC().Unix())*1000 + uint64(i)
b.Index = uint32(i)
b.RebuildMerkleRoot()
if i < 5 {
signa := priv0.SignHashable(uint32(bc.config.Magic), b)
b.Script.InvocationScript = append([]byte{byte(opcode.PUSHDATA1), byte(len(signa))}, signa...)
} else {
b.Script.InvocationScript = testchain.Sign(b)
}
err = bc.AddBlock(b)
require.NoErrorf(t, err, "at %d", i)
}
}
func setSigner(tx *transaction.Transaction, h util.Uint160) { func setSigner(tx *transaction.Transaction, h util.Uint160) {
tx.Signers = []transaction.Signer{{ tx.Signers = []transaction.Signer{{
Account: h, Account: h,

View file

@ -273,7 +273,8 @@ func (n *NEO) updateCache(cvs keysWithVotes, bc interop.Ledger) error {
} }
n.committeeHash.Store(hash.Hash160(script)) n.committeeHash.Store(hash.Hash160(script))
nextVals := committee[:bc.GetConfig().ValidatorsCount].Copy() cfg := bc.GetConfig()
nextVals := committee[:cfg.GetNumOfCNs(bc.BlockHeight()+1)].Copy()
sort.Sort(nextVals) sort.Sort(nextVals)
n.nextValidators.Store(nextVals) n.nextValidators.Store(nextVals)
return nil return nil
@ -298,16 +299,16 @@ func (n *NEO) updateCommittee(ic *interop.Context) error {
return ic.DAO.PutStorageItem(n.ID, prefixCommittee, cvs.Bytes()) return ic.DAO.PutStorageItem(n.ID, prefixCommittee, cvs.Bytes())
} }
// ShouldUpdateCommittee returns true if committee is updated at block h.
func ShouldUpdateCommittee(h uint32, bc interop.Ledger) bool {
cfg := bc.GetConfig()
r := len(cfg.StandbyCommittee)
return h%uint32(r) == 0
}
// 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 ShouldUpdateCommittee(ic.Block.Index, ic.Chain) { cfg := ic.Chain.GetConfig()
if cfg.ShouldUpdateCommitteeAt(ic.Block.Index) {
oldKeys := n.nextValidators.Load().(keys.PublicKeys)
oldCom := n.committee.Load().(keysWithVotes)
if cfg.GetNumOfCNs(ic.Block.Index) != len(oldKeys) ||
cfg.GetCommitteeSize(ic.Block.Index) != len(oldCom) {
n.votesChanged.Store(true)
}
if err := n.updateCommittee(ic); err != nil { if err := n.updateCommittee(ic); err != nil {
return err return err
} }
@ -319,16 +320,17 @@ func (n *NEO) OnPersist(ic *interop.Context) error {
func (n *NEO) PostPersist(ic *interop.Context) error { func (n *NEO) PostPersist(ic *interop.Context) error {
gas := n.GetGASPerBlock(ic.DAO, ic.Block.Index) gas := n.GetGASPerBlock(ic.DAO, ic.Block.Index)
pubs := n.GetCommitteeMembers() pubs := n.GetCommitteeMembers()
committeeSize := len(ic.Chain.GetConfig().StandbyCommittee) cfg := ic.Chain.GetConfig()
committeeSize := cfg.GetCommitteeSize(ic.Block.Index)
index := int(ic.Block.Index) % committeeSize index := int(ic.Block.Index) % committeeSize
committeeReward := new(big.Int).Mul(gas, bigCommitteeRewardRatio) committeeReward := new(big.Int).Mul(gas, bigCommitteeRewardRatio)
n.GAS.mint(ic, pubs[index].GetScriptHash(), committeeReward.Div(committeeReward, big100), false) n.GAS.mint(ic, pubs[index].GetScriptHash(), committeeReward.Div(committeeReward, big100), false)
if ShouldUpdateCommittee(ic.Block.Index, ic.Chain) { if cfg.ShouldUpdateCommitteeAt(ic.Block.Index) {
var voterReward = new(big.Int).Set(bigVoterRewardRatio) var voterReward = new(big.Int).Set(bigVoterRewardRatio)
voterReward.Mul(voterReward, gas) voterReward.Mul(voterReward, gas)
voterReward.Mul(voterReward, big.NewInt(voterRewardFactor*int64(committeeSize))) voterReward.Mul(voterReward, big.NewInt(voterRewardFactor*int64(committeeSize)))
var validatorsCount = ic.Chain.GetConfig().ValidatorsCount var validatorsCount = cfg.GetNumOfCNs(ic.Block.Index)
voterReward.Div(voterReward, big.NewInt(int64(committeeSize+validatorsCount))) voterReward.Div(voterReward, big.NewInt(int64(committeeSize+validatorsCount)))
voterReward.Div(voterReward, big100) voterReward.Div(voterReward, big100)
@ -938,14 +940,16 @@ func (n *NEO) getAccountState(ic *interop.Context, args []stackitem.Item) stacki
// ComputeNextBlockValidators returns an actual list of current validators. // ComputeNextBlockValidators returns an actual list of current validators.
func (n *NEO) ComputeNextBlockValidators(bc interop.Ledger, d dao.DAO) (keys.PublicKeys, error) { func (n *NEO) ComputeNextBlockValidators(bc interop.Ledger, d dao.DAO) (keys.PublicKeys, error) {
if vals := n.validators.Load().(keys.PublicKeys); vals != nil { cfg := bc.GetConfig()
numOfCNs := cfg.GetNumOfCNs(bc.BlockHeight() + 1)
if vals := n.validators.Load().(keys.PublicKeys); vals != nil && numOfCNs == len(vals) {
return vals.Copy(), nil return vals.Copy(), nil
} }
result, _, err := n.computeCommitteeMembers(bc, d) result, _, err := n.computeCommitteeMembers(bc, d)
if err != nil { if err != nil {
return nil, err return nil, err
} }
result = result[:bc.GetConfig().ValidatorsCount] result = result[:numOfCNs]
sort.Sort(result) sort.Sort(result)
n.validators.Store(result) n.validators.Store(result)
return result, nil return result, nil
@ -1007,7 +1011,9 @@ func (n *NEO) computeCommitteeMembers(bc interop.Ledger, d dao.DAO) (keys.Public
voterTurnout := votersCount.Div(votersCount, totalSupply) voterTurnout := votersCount.Div(votersCount, totalSupply)
sbVals := bc.GetStandByCommittee() sbVals := bc.GetStandByCommittee()
count := len(sbVals) cfg := bc.GetConfig()
count := cfg.GetCommitteeSize(bc.BlockHeight() + 1)
sbVals = sbVals[:count]
cs, err := n.getCandidates(d, false) cs, err := n.getCandidates(d, false)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err

View file

@ -392,9 +392,10 @@ func (n *Notary) GetMaxNotValidBeforeDelta(dao dao.DAO) uint32 {
// setMaxNotValidBeforeDelta is Notary contract method and sets the maximum NotValidBefore delta. // setMaxNotValidBeforeDelta is Notary contract method and sets the maximum NotValidBefore delta.
func (n *Notary) setMaxNotValidBeforeDelta(ic *interop.Context, args []stackitem.Item) stackitem.Item { func (n *Notary) setMaxNotValidBeforeDelta(ic *interop.Context, args []stackitem.Item) stackitem.Item {
value := toUint32(args[0]) value := toUint32(args[0])
maxInc := ic.Chain.GetConfig().MaxValidUntilBlockIncrement cfg := ic.Chain.GetConfig()
if value > maxInc/2 || value < uint32(ic.Chain.GetConfig().ValidatorsCount) { maxInc := cfg.MaxValidUntilBlockIncrement
panic(fmt.Errorf("MaxNotValidBeforeDelta cannot be more than %d or less than %d", maxInc/2, ic.Chain.GetConfig().ValidatorsCount)) if value > maxInc/2 || value < uint32(cfg.GetNumOfCNs(ic.Chain.BlockHeight())) {
panic(fmt.Errorf("MaxNotValidBeforeDelta cannot be more than %d or less than %d", maxInc/2, cfg.GetNumOfCNs(ic.Chain.BlockHeight())))
} }
if !n.NEO.checkCommittee(ic) { if !n.NEO.checkCommittee(ic) {
panic("invalid committee signature") panic("invalid committee signature")

View file

@ -53,7 +53,7 @@ func validatorsFromConfig(cfg config.ProtocolConfiguration) ([]*keys.PublicKey,
if err != nil { if err != nil {
return nil, err return nil, err
} }
return vs[:cfg.ValidatorsCount], nil return vs[:cfg.GetNumOfCNs(0)], nil
} }
func committeeFromConfig(cfg config.ProtocolConfiguration) ([]*keys.PublicKey, error) { func committeeFromConfig(cfg config.ProtocolConfiguration) ([]*keys.PublicKey, error) {

View file

@ -1393,8 +1393,10 @@ func (s *Server) broadcastTxHashes(hs []util.Uint256) {
// initStaleMemPools initializes mempools for stale tx/payload processing. // initStaleMemPools initializes mempools for stale tx/payload processing.
func (s *Server) initStaleMemPools() { func (s *Server) initStaleMemPools() {
threshold := 5 threshold := 5
if s.config.ValidatorsCount*2 > threshold { // Not perfect, can change over time, but should be sufficient.
threshold = s.config.ValidatorsCount * 2 numOfCNs := s.config.GetNumOfCNs(s.chain.BlockHeight())
if numOfCNs*2 > threshold {
threshold = numOfCNs * 2
} }
s.mempool.SetResendThreshold(uint32(threshold), s.broadcastTX) s.mempool.SetResendThreshold(uint32(threshold), s.broadcastTX)

View file

@ -533,7 +533,7 @@ func (s *Server) getVersion(_ request.Params) (interface{}, *response.Error) {
MaxValidUntilBlockIncrement: cfg.MaxValidUntilBlockIncrement, MaxValidUntilBlockIncrement: cfg.MaxValidUntilBlockIncrement,
MaxTransactionsPerBlock: cfg.MaxTransactionsPerBlock, MaxTransactionsPerBlock: cfg.MaxTransactionsPerBlock,
MemoryPoolMaxTransactions: cfg.MemPoolSize, MemoryPoolMaxTransactions: cfg.MemPoolSize,
ValidatorsCount: byte(cfg.ValidatorsCount), ValidatorsCount: byte(cfg.GetNumOfCNs(s.chain.BlockHeight())),
InitialGasDistribution: cfg.InitialGASSupply, InitialGasDistribution: cfg.InitialGASSupply,
StateRootInHeader: cfg.StateRootInHeader, StateRootInHeader: cfg.StateRootInHeader,
}, },