2020-03-25 15:30:21 +00:00
package config
import (
2022-01-19 23:59:40 +00:00
"errors"
"fmt"
2022-01-21 02:33:06 +00:00
"sort"
2022-12-02 16:10:45 +00:00
"time"
2022-01-19 23:59:40 +00:00
2020-06-14 07:34:50 +00:00
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
2022-01-19 23:59:40 +00:00
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
2021-07-20 12:54:56 +00:00
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
2020-03-25 15:30:21 +00:00
)
// ProtocolConfiguration represents the protocol config.
type (
ProtocolConfiguration struct {
2022-01-21 02:33:06 +00:00
// CommitteeHistory stores committee size change history (height: size).
CommitteeHistory map [ uint32 ] int ` yaml:"CommitteeHistory" `
2022-01-29 08:28:29 +00:00
// GarbageCollectionPeriod sets the number of blocks to wait before
// starting the next MPT garbage collection cycle when RemoveUntraceableBlocks
// option is used.
GarbageCollectionPeriod uint32 ` yaml:"GarbageCollectionPeriod" `
Magic netmode . Magic ` yaml:"Magic" `
MemPoolSize int ` yaml:"MemPoolSize" `
2021-07-20 12:54:56 +00:00
2022-05-06 12:19:17 +00:00
// Hardforks is a map of hardfork names that enables version-specific application
// logic dependent on the specified height.
Hardforks map [ string ] uint32 ` yaml:"Hardforks" `
2021-07-20 12:54:56 +00:00
// InitialGASSupply is the amount of GAS generated in the genesis block.
InitialGASSupply fixedn . Fixed8 ` yaml:"InitialGASSupply" `
2020-11-27 10:55:48 +00:00
// P2PNotaryRequestPayloadPoolSize specifies the memory pool size for P2PNotaryRequestPayloads.
// It is valid only if P2PSigExtensions are enabled.
P2PNotaryRequestPayloadPoolSize int ` yaml:"P2PNotaryRequestPayloadPoolSize" `
2022-04-20 18:30:09 +00:00
// KeepOnlyLatestState specifies if MPT should only store the latest state.
2020-10-21 13:58:41 +00:00
// 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 ` yaml:"KeepOnlyLatestState" `
2022-01-29 08:28:29 +00:00
// RemoveUntraceableBlocks specifies if old data should be removed.
2020-11-24 09:07:58 +00:00
RemoveUntraceableBlocks bool ` yaml:"RemoveUntraceableBlocks" `
2021-03-15 10:00:04 +00:00
// MaxBlockSize is the maximum block size in bytes.
MaxBlockSize uint32 ` yaml:"MaxBlockSize" `
2021-03-15 10:51:07 +00:00
// MaxBlockSystemFee is the maximum overall system fee per block.
MaxBlockSystemFee int64 ` yaml:"MaxBlockSystemFee" `
2020-11-05 19:01:12 +00:00
// MaxTraceableBlocks is the length of the chain accessible to smart contracts.
MaxTraceableBlocks uint32 ` yaml:"MaxTraceableBlocks" `
2021-02-17 15:22:57 +00:00
// MaxTransactionsPerBlock is the maximum amount of transactions per block.
MaxTransactionsPerBlock uint16 ` yaml:"MaxTransactionsPerBlock" `
2021-05-17 08:07:08 +00:00
// 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" `
2022-04-20 18:30:09 +00:00
// NativeUpdateHistories is a list of histories of native contracts updates.
2021-03-11 11:39:51 +00:00
NativeUpdateHistories map [ string ] [ ] uint32 ` yaml:"NativeActivations" `
2020-11-27 10:55:48 +00:00
// P2PSigExtensions enables additional signature-related logic.
2020-10-14 16:07:16 +00:00
P2PSigExtensions bool ` yaml:"P2PSigExtensions" `
2021-06-15 08:09:35 +00:00
// P2PStateExchangeExtensions enables additional P2P MPT state data exchange logic.
P2PStateExchangeExtensions bool ` yaml:"P2PStateExchangeExtensions" `
2020-10-15 10:06:22 +00:00
// ReservedAttributes allows to have reserved attributes range for experimental or private purposes.
ReservedAttributes bool ` yaml:"ReservedAttributes" `
2020-03-25 15:30:21 +00:00
// SaveStorageBatch enables storage batch saving before every persist.
2022-12-02 16:10:45 +00:00
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.
2020-08-03 14:07:24 +00:00
SecondsPerBlock int ` yaml:"SecondsPerBlock" `
SeedList [ ] string ` yaml:"SeedList" `
StandbyCommittee [ ] string ` yaml:"StandbyCommittee" `
2020-11-17 12:57:50 +00:00
// StateRooInHeader enables storing state root in block header.
StateRootInHeader bool ` yaml:"StateRootInHeader" `
2021-06-15 08:09:35 +00:00
// 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" `
2022-12-02 16:10:45 +00:00
// 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" `
2022-01-21 02:33:06 +00:00
// Validators stores history of changes to consensus node number (height: number).
ValidatorsHistory map [ uint32 ] int ` yaml:"ValidatorsHistory" `
2020-03-25 15:30:21 +00:00
// Whether to verify received blocks.
VerifyBlocks bool ` yaml:"VerifyBlocks" `
2022-04-20 18:30:09 +00:00
// Whether to verify transactions in the received blocks.
2020-03-25 15:30:21 +00:00
VerifyTransactions bool ` yaml:"VerifyTransactions" `
}
)
2022-01-19 23:59:40 +00:00
2022-01-21 02:33:06 +00:00
// heightNumber is an auxiliary structure for configuration checks.
type heightNumber struct {
h uint32
n int
}
2022-01-19 23:59:40 +00:00
// Validate checks ProtocolConfiguration for internal consistency and returns
2022-04-20 18:30:09 +00:00
// an error if anything inappropriate found. Other methods can rely on protocol
2022-01-19 23:59:40 +00:00
// validity after this.
func ( p * ProtocolConfiguration ) Validate ( ) error {
2022-01-21 02:33:06 +00:00
var err error
2022-07-14 11:13:38 +00:00
if p . P2PStateExchangeExtensions && p . KeepOnlyLatestState && ! p . RemoveUntraceableBlocks {
2022-07-15 09:52:21 +00:00
return fmt . Errorf ( "P2PStateExchangeExtensions can be enabled either on MPT-complete node (KeepOnlyLatestState=false) or on light GC-enabled node (RemoveUntraceableBlocks=true)" )
2022-02-08 18:47:17 +00:00
}
2022-12-02 16:10:45 +00:00
if p . TimePerBlock % time . Millisecond != 0 {
return errors . New ( "TimePerBlock must be an integer number of milliseconds" )
}
2022-01-19 23:59:40 +00:00
for name := range p . NativeUpdateHistories {
if ! nativenames . IsValid ( name ) {
return fmt . Errorf ( "NativeActivations configuration section contains unexpected native contract name: %s" , name )
}
}
2022-05-06 12:19:17 +00:00
for name := range p . Hardforks {
if ! IsHardforkValid ( name ) {
return fmt . Errorf ( "Hardforks configuration section contains unexpected hardfork: %s" , name )
}
}
2022-01-21 02:33:06 +00:00
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 )
}
}
}
2022-01-19 23:59:40 +00:00
return nil
}
2022-01-21 02:33:06 +00:00
// 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
}
2022-07-26 12:18:30 +00:00
// 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 ||
2022-12-02 16:10:45 +00:00
p . TimePerBlock != o . TimePerBlock ||
2022-07-26 12:18:30 +00:00
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
}