neoneo-go/pkg/config/protocol_config.go
Anna Shaleva 6cd0f78649 config: do not allow zero numbers for validators/committee history
Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
2023-08-01 17:22:33 +03:00

300 lines
12 KiB
Go

package config
import (
"errors"
"fmt"
"sort"
"time"
"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/encoding/fixedn"
)
// ProtocolConfiguration represents the protocol config.
type (
ProtocolConfiguration struct {
// CommitteeHistory stores committee size change history (height: size).
CommitteeHistory map[uint32]uint32 `yaml:"CommitteeHistory"`
// GarbageCollectionPeriod sets the number of blocks to wait before
// starting the next MPT garbage collection cycle when RemoveUntraceableBlocks
// option is used.
//
// Deprecated: please use the same setting in the ApplicationConfiguration, this field will be removed in future versions.
GarbageCollectionPeriod uint32 `yaml:"GarbageCollectionPeriod"`
Magic netmode.Magic `yaml:"Magic"`
MemPoolSize int `yaml:"MemPoolSize"`
// Hardforks is a map of hardfork names that enables version-specific application
// logic dependent on the specified height.
Hardforks map[string]uint32 `yaml:"Hardforks"`
// InitialGASSupply is the amount of GAS generated in the genesis block.
InitialGASSupply fixedn.Fixed8 `yaml:"InitialGASSupply"`
// P2PNotaryRequestPayloadPoolSize specifies the memory pool size for P2PNotaryRequestPayloads.
// It is valid only if P2PSigExtensions are enabled.
P2PNotaryRequestPayloadPoolSize int `yaml:"P2PNotaryRequestPayloadPoolSize"`
// KeepOnlyLatestState specifies if MPT should only store the 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.
//
// Deprecated: please use the same setting in the ApplicationConfiguration, this field will be removed in future versions.
KeepOnlyLatestState bool `yaml:"KeepOnlyLatestState"`
// RemoveUntraceableBlocks specifies if old data should be removed.
//
// Deprecated: please use the same setting in the ApplicationConfiguration, this field will be removed in future versions.
RemoveUntraceableBlocks bool `yaml:"RemoveUntraceableBlocks"`
// MaxBlockSize is the maximum block size in bytes.
MaxBlockSize uint32 `yaml:"MaxBlockSize"`
// MaxBlockSystemFee is the maximum overall system fee per block.
MaxBlockSystemFee int64 `yaml:"MaxBlockSystemFee"`
// MaxTraceableBlocks is the length of the chain accessible to smart contracts.
MaxTraceableBlocks uint32 `yaml:"MaxTraceableBlocks"`
// MaxTransactionsPerBlock is the maximum amount of transactions per block.
MaxTransactionsPerBlock uint16 `yaml:"MaxTransactionsPerBlock"`
// MaxValidUntilBlockIncrement is the upper increment size of blockchain height in blocks
// exceeding that a transaction should fail validation. It is set to estimated daily number
// of blocks with 15s interval.
MaxValidUntilBlockIncrement uint32 `yaml:"MaxValidUntilBlockIncrement"`
// NativeUpdateHistories is a list of histories of native contracts updates.
NativeUpdateHistories map[string][]uint32 `yaml:"NativeActivations"`
// P2PSigExtensions enables additional signature-related logic.
P2PSigExtensions bool `yaml:"P2PSigExtensions"`
// P2PStateExchangeExtensions enables additional P2P MPT state data exchange logic.
P2PStateExchangeExtensions bool `yaml:"P2PStateExchangeExtensions"`
// ReservedAttributes allows to have reserved attributes range for experimental or private purposes.
ReservedAttributes bool `yaml:"ReservedAttributes"`
// SaveStorageBatch enables storage batch saving before every persist.
//
// Deprecated: please use the same setting in the ApplicationConfiguration, this field will be removed in future versions.
SaveStorageBatch bool `yaml:"SaveStorageBatch"`
// SecondsPerBlock is the time interval (in seconds) between blocks that consensus nodes work with.
//
// Deprecated: replaced by TimePerBlock, to be removed in future versions.
SecondsPerBlock int `yaml:"SecondsPerBlock"`
SeedList []string `yaml:"SeedList"`
StandbyCommittee []string `yaml:"StandbyCommittee"`
// StateRooInHeader enables storing state root in block header.
StateRootInHeader bool `yaml:"StateRootInHeader"`
// StateSyncInterval is the number of blocks between state heights available for MPT state data synchronization.
// It is valid only if P2PStateExchangeExtensions are enabled.
StateSyncInterval int `yaml:"StateSyncInterval"`
// TimePerBlock is the time interval between blocks that consensus nodes work with.
// It must be an integer number of milliseconds.
TimePerBlock time.Duration `yaml:"TimePerBlock"`
ValidatorsCount int `yaml:"ValidatorsCount"`
// Validators stores history of changes to consensus node number (height: number).
ValidatorsHistory map[uint32]uint32 `yaml:"ValidatorsHistory"`
// Whether to verify received blocks.
//
// Deprecated: please use the same setting in the ApplicationConfiguration, this field will be removed in future versions.
VerifyBlocks bool `yaml:"VerifyBlocks"`
// Whether to verify transactions in the received blocks.
VerifyTransactions bool `yaml:"VerifyTransactions"`
}
)
// heightNumber is an auxiliary structure for configuration checks.
type heightNumber struct {
h uint32
n uint32
}
// Validate checks ProtocolConfiguration for internal consistency and returns
// an error if anything inappropriate found. Other methods can rely on protocol
// validity after this.
func (p *ProtocolConfiguration) Validate() error {
var err error
if p.P2PStateExchangeExtensions && p.KeepOnlyLatestState && !p.RemoveUntraceableBlocks {
return fmt.Errorf("P2PStateExchangeExtensions can be enabled either on MPT-complete node (KeepOnlyLatestState=false) or on light GC-enabled node (RemoveUntraceableBlocks=true)")
}
if p.TimePerBlock%time.Millisecond != 0 {
return errors.New("TimePerBlock must be an integer number of milliseconds")
}
for name := range p.NativeUpdateHistories {
if !nativenames.IsValid(name) {
return fmt.Errorf("NativeActivations configuration section contains unexpected native contract name: %s", name)
}
}
for name := range p.Hardforks {
if !IsHardforkValid(name) {
return fmt.Errorf("Hardforks configuration section contains unexpected hardfork: %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 n == 0 {
return fmt.Errorf("invalid CommitteeHistory: bad members count (%d) for height %d", n, h)
}
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 n == 0 {
return fmt.Errorf("invalid ValidatorsHistory: bad members count (%d) for height %d", n, h)
}
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
}
// 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 int(getBestFromMap(p.CommitteeHistory, height))
}
func getBestFromMap(dict map[uint32]uint32, height uint32) uint32 {
var res uint32
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 int(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
}
// Equals allows to compare two ProtocolConfiguration instances, returns true if
// they're equal.
func (p *ProtocolConfiguration) Equals(o *ProtocolConfiguration) bool {
if p.GarbageCollectionPeriod != o.GarbageCollectionPeriod ||
p.InitialGASSupply != o.InitialGASSupply ||
p.KeepOnlyLatestState != o.KeepOnlyLatestState ||
p.Magic != o.Magic ||
p.MaxBlockSize != o.MaxBlockSize ||
p.MaxBlockSystemFee != o.MaxBlockSystemFee ||
p.MaxTraceableBlocks != o.MaxTraceableBlocks ||
p.MaxTransactionsPerBlock != o.MaxTransactionsPerBlock ||
p.MaxValidUntilBlockIncrement != o.MaxValidUntilBlockIncrement ||
p.MemPoolSize != o.MemPoolSize ||
p.P2PNotaryRequestPayloadPoolSize != o.P2PNotaryRequestPayloadPoolSize ||
p.P2PSigExtensions != o.P2PSigExtensions ||
p.P2PStateExchangeExtensions != o.P2PStateExchangeExtensions ||
p.RemoveUntraceableBlocks != o.RemoveUntraceableBlocks ||
p.ReservedAttributes != o.ReservedAttributes ||
p.SaveStorageBatch != o.SaveStorageBatch ||
p.SecondsPerBlock != o.SecondsPerBlock ||
p.StateRootInHeader != o.StateRootInHeader ||
p.StateSyncInterval != o.StateSyncInterval ||
p.TimePerBlock != o.TimePerBlock ||
p.ValidatorsCount != o.ValidatorsCount ||
p.VerifyBlocks != o.VerifyBlocks ||
p.VerifyTransactions != o.VerifyTransactions ||
len(p.CommitteeHistory) != len(o.CommitteeHistory) ||
len(p.Hardforks) != len(o.Hardforks) ||
len(p.NativeUpdateHistories) != len(o.NativeUpdateHistories) ||
len(p.SeedList) != len(o.SeedList) ||
len(p.StandbyCommittee) != len(o.StandbyCommittee) ||
len(p.ValidatorsHistory) != len(o.ValidatorsHistory) {
return false
}
for k, v := range p.CommitteeHistory {
vo, ok := o.CommitteeHistory[k]
if !ok || v != vo {
return false
}
}
for k, v := range p.Hardforks {
vo, ok := o.Hardforks[k]
if !ok || v != vo {
return false
}
}
for k, v := range p.NativeUpdateHistories {
vo := o.NativeUpdateHistories[k]
if len(v) != len(vo) {
return false
}
for i := range v {
if v[i] != vo[i] {
return false
}
}
}
for i := range p.SeedList {
if p.SeedList[i] != o.SeedList[i] {
return false
}
}
for i := range p.StandbyCommittee {
if p.StandbyCommittee[i] != o.StandbyCommittee[i] {
return false
}
}
for k, v := range p.ValidatorsHistory {
vo, ok := o.ValidatorsHistory[k]
if !ok || v != vo {
return false
}
}
return true
}