diff --git a/ROADMAP.md b/ROADMAP.md index c67048ceb..15215578e 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -92,3 +92,13 @@ used instead (that also have a counter). It's not a frequently used thing and it's easy to replace it, so removal of old counters is scheduled for January-February 2023 (~0.100.X release). + +## SecondsPerBlock protocol configuration + +With 0.100.0 version SecondsPerBlock protocol configuration setting was +deprecated and replaced by a bit more generic and precise TimePerBlock +(allowing for subsecond time). An informational message is printed on node +startup to inform about this, it's very easy to deal with this configuration +change, just replace one line. + +Removal of SecondsPerBlock is scheduled for May-June 2023 (~0.103.0 release). diff --git a/docs/node-configuration.md b/docs/node-configuration.md index 19fb01325..66cda204f 100644 --- a/docs/node-configuration.md +++ b/docs/node-configuration.md @@ -197,7 +197,7 @@ where: enable `SessionBackedByMPT`, see `SessionBackedByMPT` documentation for more details. - `SessionExpirationTime` is a lifetime of iterator session in seconds. It is set - to `SecondsPerBlock` seconds by default and is relevant only if `SessionEnabled` + to `TimePerBlock` seconds by default and is relevant only if `SessionEnabled` is set to `true`. - `SessionBackedByMPT` is a flag forcing JSON-RPC server into using MPT-backed storage for delayed iterator traversal. If `true`, then iterator resources got @@ -275,11 +275,12 @@ protocol-related settings described in the table below. | RemoveUntraceableBlocks | `bool`| `false` | Denotes whether old blocks should be removed from cache and database. If enabled, then only the last `MaxTraceableBlocks` are stored and accessible to smart contracts. Old MPT data is also deleted in accordance with `GarbageCollectionPeriod` setting. If enabled along with `P2PStateExchangeExtensions`, then old blocks and MPT states will be removed up to the second latest state synchronisation point (see `StateSyncInterval`). | | ReservedAttributes | `bool` | `false` | Allows to have reserved attributes range for experimental or private purposes. | | SaveStorageBatch | `bool` | `false` | Enables storage batch saving before every persist. It is similar to StorageDump plugin for C# node. | -| SecondsPerBlock | `int` | `15` | Minimal time that should pass before next block is accepted. | +| SecondsPerBlock | `int` | `15` | Minimal time that should pass before next block is accepted. Deprecated: please use TimePerBlock setting (which overrides anything set here), SecondsPerBlock will be removed in future versions. | | SeedList | `[]string` | [] | List of initial nodes addresses used to establish connectivity. | | 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! | | StateSyncInterval | `int` | `40000` | The number of blocks between state heights available for MPT state data synchronization. | `P2PStateExchangeExtensions` should be enabled to use this setting. | +| TimePerBlock | `Duration` | `15s` | Minimal (and targeted for) time interval between blocks. Must be an integer number of milliseconds. | | 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 the received blocks. | diff --git a/pkg/config/protocol_config.go b/pkg/config/protocol_config.go index 5cfc743df..014a2e288 100644 --- a/pkg/config/protocol_config.go +++ b/pkg/config/protocol_config.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "sort" + "time" "github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" @@ -58,7 +59,10 @@ type ( // ReservedAttributes allows to have reserved attributes range for experimental or private purposes. ReservedAttributes bool `yaml:"ReservedAttributes"` // SaveStorageBatch enables storage batch saving before every persist. - SaveStorageBatch bool `yaml:"SaveStorageBatch"` + 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"` @@ -67,7 +71,10 @@ type ( // 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"` - ValidatorsCount int `yaml:"ValidatorsCount"` + // 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]int `yaml:"ValidatorsHistory"` // Whether to verify received blocks. @@ -92,6 +99,9 @@ func (p *ProtocolConfiguration) Validate() 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) @@ -219,6 +229,7 @@ func (p *ProtocolConfiguration) Equals(o *ProtocolConfiguration) bool { 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 || diff --git a/pkg/config/protocol_config_test.go b/pkg/config/protocol_config_test.go index a2e976099..aa6e6d68b 100644 --- a/pkg/config/protocol_config_test.go +++ b/pkg/config/protocol_config_test.go @@ -3,6 +3,7 @@ package config import ( "path/filepath" "testing" + "time" "github.com/stretchr/testify/require" ) @@ -17,6 +18,14 @@ func TestProtocolConfigurationValidation(t *testing.T) { P2PStateExchangeExtensions: true, } require.Error(t, p.Validate()) + p = &ProtocolConfiguration{ + StandbyCommittee: []string{ + "02b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc2", + }, + ValidatorsCount: 1, + TimePerBlock: time.Microsecond, + } + require.Error(t, p.Validate()) p = &ProtocolConfiguration{ ValidatorsCount: 1, } diff --git a/pkg/consensus/consensus_test.go b/pkg/consensus/consensus_test.go index c3f3c6098..665cbf5dc 100644 --- a/pkg/consensus/consensus_test.go +++ b/pkg/consensus/consensus_test.go @@ -480,7 +480,7 @@ func newTestServiceWithChain(t *testing.T, bc *core.Blockchain) *service { ProtocolConfiguration: bc.GetConfig(), RequestTx: func(...util.Uint256) {}, StopTxFlow: func() {}, - TimePerBlock: time.Duration(bc.GetConfig().SecondsPerBlock) * time.Second, + TimePerBlock: bc.GetConfig().TimePerBlock, Wallet: &config.Wallet{ Path: "./testdata/wallet1.json", Password: "one", diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 490e6c699..44e6e6532 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -55,7 +55,7 @@ const ( defaultMaxBlockSystemFee = 900000000000 defaultMaxTraceableBlocks = 2102400 // 1 year of 15s blocks defaultMaxTransactionsPerBlock = 512 - defaultSecondsPerBlock = 15 + defaultTimePerBlock = 15 * time.Second // HeaderVerificationGasLimit is the maximum amount of GAS for block header verification. HeaderVerificationGasLimit = 3_00000000 // 3 GAS defaultStateSyncInterval = 40000 @@ -252,15 +252,21 @@ func NewBlockchain(s storage.Store, cfg config.ProtocolConfiguration, log *zap.L log.Info("MaxTransactionsPerBlock is not set or wrong, using default value", zap.Uint16("MaxTransactionsPerBlock", cfg.MaxTransactionsPerBlock)) } - if cfg.SecondsPerBlock == 0 { - cfg.SecondsPerBlock = defaultSecondsPerBlock - log.Info("SecondsPerBlock is not set or wrong, using default value", - zap.Int("SecondsPerBlock", cfg.SecondsPerBlock)) + if cfg.TimePerBlock <= 0 { + if cfg.SecondsPerBlock > 0 { //nolint:staticcheck // SA1019: cfg.SecondsPerBlock is deprecated + cfg.TimePerBlock = time.Duration(cfg.SecondsPerBlock) * time.Second //nolint:staticcheck // SA1019: cfg.SecondsPerBlock is deprecated + log.Info("TimePerBlock is not set, using deprecated SecondsPerBlock setting, consider updating your config", + zap.Duration("TimePerBlock", cfg.TimePerBlock)) + } else { + cfg.TimePerBlock = defaultTimePerBlock + log.Info("TimePerBlock is not set or wrong, using default value", + zap.Duration("TimePerBlock", cfg.TimePerBlock)) + } } if cfg.MaxValidUntilBlockIncrement == 0 { - const secondsPerDay = int(24 * time.Hour / time.Second) + const timePerDay = 24 * time.Hour - cfg.MaxValidUntilBlockIncrement = uint32(secondsPerDay / cfg.SecondsPerBlock) + cfg.MaxValidUntilBlockIncrement = uint32(timePerDay / cfg.TimePerBlock) log.Info("MaxValidUntilBlockIncrement is not set or wrong, using default value", zap.Uint32("MaxValidUntilBlockIncrement", cfg.MaxValidUntilBlockIncrement)) } @@ -2650,7 +2656,7 @@ func (bc *Blockchain) getFakeNextBlock(nextBlockHeight uint32) (*block.Block, er if err != nil { return nil, err } - b.Timestamp = hdr.Timestamp + uint64(bc.config.SecondsPerBlock*int(time.Second/time.Millisecond)) + b.Timestamp = hdr.Timestamp + uint64(bc.config.TimePerBlock/time.Millisecond) return b, nil } diff --git a/pkg/neotest/chain/chain.go b/pkg/neotest/chain/chain.go index 4e6de45e0..13a7fa174 100644 --- a/pkg/neotest/chain/chain.go +++ b/pkg/neotest/chain/chain.go @@ -4,6 +4,7 @@ import ( "encoding/hex" "sort" "testing" + "time" "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/config/netmode" @@ -22,9 +23,9 @@ const ( // We don't need a lot of traceable blocks for tests. MaxTraceableBlocks = 1000 - // SecondsPerBlock is the default SecondsPerBlock setting used for test chains. + // TimePerBlock is the default TimePerBlock setting used for test chains (1s). // Usually blocks are created by tests bypassing this setting. - SecondsPerBlock = 1 + TimePerBlock = time.Second ) const singleValidatorWIF = "KxyjQ8eUa4FHt3Gvioyt1Wz29cTUrE4eTqX3yFSk1YFCsPL8uNsY" @@ -117,7 +118,7 @@ func init() { // NewSingle creates a new blockchain instance with a single validator and // setups cleanup functions. The configuration used is with netmode.UnitTestNet -// magic and SecondsPerBlock/MaxTraceableBlocks options defined by constants in +// magic and TimePerBlock/MaxTraceableBlocks options defined by constants in // this package. MemoryStore is used as the backend storage, so all of the chain // contents is always in RAM. The Signer returned is the validator (and the committee at // the same time). @@ -140,7 +141,7 @@ func NewSingleWithCustomConfigAndStore(t testing.TB, f func(cfg *config.Protocol protoCfg := config.ProtocolConfiguration{ Magic: netmode.UnitTestNet, MaxTraceableBlocks: MaxTraceableBlocks, - SecondsPerBlock: SecondsPerBlock, + TimePerBlock: TimePerBlock, StandbyCommittee: []string{hex.EncodeToString(committeeAcc.PublicKey().Bytes())}, ValidatorsCount: 1, VerifyBlocks: true, @@ -196,7 +197,7 @@ func NewMultiWithCustomConfigAndStoreNoCheck(t testing.TB, f func(*config.Protoc protoCfg := config.ProtocolConfiguration{ Magic: netmode.UnitTestNet, MaxTraceableBlocks: MaxTraceableBlocks, - SecondsPerBlock: SecondsPerBlock, + TimePerBlock: TimePerBlock, StandbyCommittee: standByCommittee, ValidatorsCount: 4, VerifyBlocks: true, diff --git a/pkg/network/server_config.go b/pkg/network/server_config.go index c3eb3baa4..0983dfda2 100644 --- a/pkg/network/server_config.go +++ b/pkg/network/server_config.go @@ -89,6 +89,10 @@ type ( func NewServerConfig(cfg config.Config) ServerConfig { appConfig := cfg.ApplicationConfiguration protoConfig := cfg.ProtocolConfiguration + timePerBlock := protoConfig.TimePerBlock + if timePerBlock == 0 && protoConfig.SecondsPerBlock > 0 { //nolint:staticcheck // SA1019: protoConfig.SecondsPerBlock is deprecated + timePerBlock = time.Duration(protoConfig.SecondsPerBlock) * time.Second //nolint:staticcheck // SA1019: protoConfig.SecondsPerBlock is deprecated + } return ServerConfig{ UserAgent: cfg.GenerateUserAgent(), @@ -105,7 +109,7 @@ func NewServerConfig(cfg config.Config) ServerConfig { MaxPeers: appConfig.MaxPeers, AttemptConnPeers: appConfig.AttemptConnPeers, MinPeers: appConfig.MinPeers, - TimePerBlock: time.Duration(protoConfig.SecondsPerBlock) * time.Second, + TimePerBlock: timePerBlock, OracleCfg: appConfig.Oracle, P2PNotaryCfg: appConfig.P2PNotary, StateRootCfg: appConfig.StateRoot, diff --git a/pkg/network/tcp_peer.go b/pkg/network/tcp_peer.go index a3e44e67d..e297f669a 100644 --- a/pkg/network/tcp_peer.go +++ b/pkg/network/tcp_peer.go @@ -205,7 +205,7 @@ func (p *TCPPeer) handleQueues() { var p2pSkipCounter uint32 const p2pSkipDivisor = 4 - var writeTimeout = time.Duration(p.server.config.SecondsPerBlock) * time.Second + var writeTimeout = p.server.TimePerBlock for { var msg []byte diff --git a/pkg/services/rpcsrv/client_test.go b/pkg/services/rpcsrv/client_test.go index f9749d261..0bab941a3 100644 --- a/pkg/services/rpcsrv/client_test.go +++ b/pkg/services/rpcsrv/client_test.go @@ -1994,7 +1994,7 @@ func TestClient_Wait(t *testing.T) { select { case <-rcvr: break waitloop - case <-time.NewTimer(time.Duration(chain.GetConfig().SecondsPerBlock) * time.Second).C: + case <-time.NewTimer(chain.GetConfig().TimePerBlock).C: t.Fatal("transaction failed to be awaited") } } @@ -2070,7 +2070,7 @@ func TestWSClient_Wait(t *testing.T) { require.Equal(t, vmstate.Halt, aer.VMState) } break waitloop - case <-time.NewTimer(time.Duration(chain.GetConfig().SecondsPerBlock) * time.Second).C: + case <-time.NewTimer(chain.GetConfig().TimePerBlock).C: t.Fatalf("transaction from block %d failed to be awaited: deadline exceeded", b.Index) } } @@ -2232,7 +2232,7 @@ waitloop: require.Equal(t, trigger.Application, aer.Trigger) require.Equal(t, vmstate.Halt, aer.VMState) break waitloop - case <-time.NewTimer(time.Duration(chain.GetConfig().SecondsPerBlock) * time.Second).C: + case <-time.NewTimer(chain.GetConfig().TimePerBlock).C: t.Fatal("transaction failed to be awaited") } } diff --git a/pkg/services/rpcsrv/server.go b/pkg/services/rpcsrv/server.go index 536966c05..1a1a37ea3 100644 --- a/pkg/services/rpcsrv/server.go +++ b/pkg/services/rpcsrv/server.go @@ -268,8 +268,8 @@ func New(chain Ledger, conf config.RPC, coreServer *network.Server, protoCfg := chain.GetConfig() if conf.SessionEnabled { if conf.SessionExpirationTime <= 0 { - conf.SessionExpirationTime = protoCfg.SecondsPerBlock - log.Info("SessionExpirationTime is not set or wrong, setting default value", zap.Int("SessionExpirationTime", protoCfg.SecondsPerBlock)) + conf.SessionExpirationTime = int(protoCfg.TimePerBlock / time.Second) + log.Info("SessionExpirationTime is not set or wrong, setting default value", zap.Int("SessionExpirationTime", conf.SessionExpirationTime)) } if conf.SessionPoolSize <= 0 { conf.SessionPoolSize = defaultSessionPoolSize @@ -706,7 +706,7 @@ func (s *Server) getVersion(_ params.Params) (interface{}, *neorpc.Error) { Protocol: result.Protocol{ AddressVersion: address.NEO3Prefix, Network: cfg.Magic, - MillisecondsPerBlock: cfg.SecondsPerBlock * 1000, + MillisecondsPerBlock: int(cfg.TimePerBlock / time.Millisecond), MaxTraceableBlocks: cfg.MaxTraceableBlocks, MaxValidUntilBlockIncrement: cfg.MaxValidUntilBlockIncrement, MaxTransactionsPerBlock: cfg.MaxTransactionsPerBlock, diff --git a/pkg/services/rpcsrv/server_test.go b/pkg/services/rpcsrv/server_test.go index bae68f8d0..9746bc8f2 100644 --- a/pkg/services/rpcsrv/server_test.go +++ b/pkg/services/rpcsrv/server_test.go @@ -872,7 +872,7 @@ var rpcTestCases = map[string][]rpcTestCase{ cfg := e.chain.GetConfig() require.EqualValues(t, address.NEO3Prefix, resp.Protocol.AddressVersion) require.EqualValues(t, cfg.Magic, resp.Protocol.Network) - require.EqualValues(t, cfg.SecondsPerBlock*1000, resp.Protocol.MillisecondsPerBlock) + require.EqualValues(t, cfg.TimePerBlock/time.Millisecond, resp.Protocol.MillisecondsPerBlock) require.EqualValues(t, cfg.MaxTraceableBlocks, resp.Protocol.MaxTraceableBlocks) require.EqualValues(t, cfg.MaxValidUntilBlockIncrement, resp.Protocol.MaxValidUntilBlockIncrement) require.EqualValues(t, cfg.MaxTransactionsPerBlock, resp.Protocol.MaxTransactionsPerBlock) diff --git a/pkg/services/stateroot/service.go b/pkg/services/stateroot/service.go index 338ad4f3b..4bfd05206 100644 --- a/pkg/services/stateroot/service.go +++ b/pkg/services/stateroot/service.go @@ -87,7 +87,7 @@ func New(cfg config.StateRoot, sm *stateroot.Module, log *zap.Logger, bc Ledger, blockCh: make(chan *block.Block), stopCh: make(chan struct{}), done: make(chan struct{}), - timePerBlock: time.Duration(bcConf.SecondsPerBlock) * time.Second, + timePerBlock: bcConf.TimePerBlock, maxRetries: voteValidEndInc, relayExtensible: cb, }