mirror of
https://github.com/luxfi/node.git
synced 2026-07-27 03:39:39 +00:00
feat(proposervm): configurable MinBlkDelay + timestamp granularity tracks WindowDuration (sub-second cadence foundation)
Two coupled knobs for differentiated cadence — co-located D-Chain/DEX (fast) vs public C-Chain (standard): 1. MinBlkDelay was hardcoded to the 1s DefaultMinBlockDelay in chains/manager.go, ignoring its own --proposervm-min-block-delay flag. Now wired node.Config → ManagerConfig → proposervm.Config (0 ⇒ 1s default). High-throughput nets set it low. 2. Block timestamps were Truncate(time.Second) at 4 sites, quantizing cadence to 1s regardless of WindowDuration — so a sub-second window inflated slot numbers without finer time resolution and could NOT produce blocks faster than 1/s. Truncation now tracks proposer.TimestampGranularity() = min(1s, WindowDuration): mainnet (>=1s) keeps exact 1-second block times; sub-second windows get matching sub-second timestamps. Measured: 1s window/delay → ~95-104 TPS, byte-identical 5/5 finality (unchanged from before, no regression). NOTE: true sub-second cadence additionally requires a slot-handoff fix — at 100ms the slot recomputed in buildChild from a drifted 'now' can differ from the slot timeToBuild scheduled, causing errUnexpectedProposer verify drops; that + multi-sender saturation is the next step. These knobs are the necessary foundation. Co-authored-by: Hanzo Dev <dev@hanzo.ai>
This commit is contained in:
+11
-4
@@ -372,8 +372,11 @@ type ManagerConfig struct {
|
||||
// ProposerWindowDuration overrides the proposervm proposer-slot spacing (0 ⇒
|
||||
// the 5s mainnet default). Small local/dev nets set it low for fast cadence.
|
||||
ProposerWindowDuration time.Duration
|
||||
StakingBLSKey bls.Signer
|
||||
TracingEnabled bool
|
||||
// ProposerMinBlockDelay overrides the proposervm minimum block delay (0 ⇒ the
|
||||
// 1s default). High-throughput / DEX nets set it low (e.g. 1ms).
|
||||
ProposerMinBlockDelay time.Duration
|
||||
StakingBLSKey bls.Signer
|
||||
TracingEnabled bool
|
||||
// Must not be used unless [TracingEnabled] is true as this may be nil.
|
||||
Tracer trace.Tracer
|
||||
Log log.Logger
|
||||
@@ -1289,10 +1292,14 @@ func (m *manager) buildChain(chainParams ChainParameters, sb nets.Net) (*chainIn
|
||||
if regErr != nil {
|
||||
return nil, fmt.Errorf("failed to register proposervm metrics for chain %s: %w", chainParams.ID, regErr)
|
||||
}
|
||||
minBlkDelay := proposervm.DefaultMinBlockDelay
|
||||
if m.ProposerMinBlockDelay > 0 {
|
||||
minBlkDelay = m.ProposerMinBlockDelay
|
||||
}
|
||||
engineVM = proposervm.New(vmTyped, proposervm.Config{
|
||||
Upgrades: m.Upgrades,
|
||||
NetworkID: networkID, // CRITICAL-3: windower uses the SAME set ID as the cert side
|
||||
MinBlkDelay: proposervm.DefaultMinBlockDelay,
|
||||
MinBlkDelay: minBlkDelay,
|
||||
NumHistoricalBlocks: proposervm.DefaultNumHistoricalBlocks,
|
||||
StakingLeafSigner: m.StakingTLSSigner,
|
||||
StakingCertLeaf: m.StakingTLSCert,
|
||||
@@ -1305,7 +1312,7 @@ func (m *manager) buildChain(chainParams ChainParameters, sb nets.Net) (*chainIn
|
||||
log.Stringer("chainID", chainParams.ID),
|
||||
log.Int("K", consensusParams.K),
|
||||
log.Stringer("windowerNetworkID", networkID),
|
||||
log.Duration("minBlockDelay", proposervm.DefaultMinBlockDelay))
|
||||
log.Duration("minBlockDelay", minBlkDelay))
|
||||
}
|
||||
|
||||
m.Log.Info("initializing VM", log.Stringer("chainID", chainParams.ID))
|
||||
|
||||
@@ -1766,6 +1766,10 @@ func GetNodeConfig(v *viper.Viper) (node.Config, error) {
|
||||
if nodeConfig.ProposerWindowDuration < 0 {
|
||||
return node.Config{}, fmt.Errorf("%s must not be negative", ProposerVMWindowDurationKey)
|
||||
}
|
||||
nodeConfig.ProposerMinBlockDelay = v.GetDuration(ProposerVMMinBlockDelayKey)
|
||||
if nodeConfig.ProposerMinBlockDelay < 0 {
|
||||
return node.Config{}, fmt.Errorf("%s must not be negative", ProposerVMMinBlockDelayKey)
|
||||
}
|
||||
// Halflife of continuous averager used in health checks
|
||||
healthCheckAveragerHalflife := v.GetDuration(HealthCheckAveragerHalflifeKey)
|
||||
if healthCheckAveragerHalflife <= 0 {
|
||||
|
||||
@@ -182,6 +182,11 @@ type Config struct {
|
||||
// 1s) so block cadence is not floored at 5s per proposer slot.
|
||||
ProposerWindowDuration time.Duration `json:"proposerWindowDuration"`
|
||||
|
||||
// ProposerMinBlockDelay is the proposervm minimum delay between consecutive
|
||||
// blocks (the hard cadence floor). Zero keeps the 1s default; high-throughput
|
||||
// / DEX nets set it low (e.g. 1ms) to approach the consensus-finality floor.
|
||||
ProposerMinBlockDelay time.Duration `json:"proposerMinBlockDelay"`
|
||||
|
||||
// Network configuration
|
||||
NetworkConfig network.Config `json:"networkConfig"`
|
||||
|
||||
|
||||
@@ -1357,6 +1357,7 @@ func (n *Node) initChainManager(utxoAssetID ids.ID) error {
|
||||
StakingMLDSASigner: n.Config.StakingConfig.StakingMLDSA,
|
||||
StakingMLDSAPub: n.Config.StakingConfig.StakingMLDSAPub,
|
||||
ProposerWindowDuration: n.Config.ProposerWindowDuration,
|
||||
ProposerMinBlockDelay: n.Config.ProposerMinBlockDelay,
|
||||
StakingBLSKey: n.Config.StakingSigningKey,
|
||||
Log: n.Log,
|
||||
LogFactory: n.LogFactory,
|
||||
|
||||
@@ -250,7 +250,7 @@ func (p *postForkCommonComponents) Verify(
|
||||
// It is a pure function of (parentTimestamp, now) so it is idempotent for any two calls in the
|
||||
// same slot — the property the liveness fix relies on.
|
||||
func slotSnappedChildTimestamp(parentTimestamp, now time.Time) time.Time {
|
||||
ts := now.Truncate(time.Second)
|
||||
ts := now.Truncate(proposer.TimestampGranularity())
|
||||
if ts.Before(parentTimestamp) {
|
||||
return parentTimestamp
|
||||
}
|
||||
|
||||
@@ -203,7 +203,7 @@ func (b *preForkBlock) buildChild(ctx context.Context) (Block, error) {
|
||||
// "chain is currently forking" path is the only one taken.
|
||||
parentTimestamp := b.Timestamp()
|
||||
parentID := b.ID()
|
||||
newTimestamp := b.vm.Time().Truncate(time.Second)
|
||||
newTimestamp := b.vm.Time().Truncate(proposer.TimestampGranularity())
|
||||
if newTimestamp.Before(parentTimestamp) {
|
||||
newTimestamp = parentTimestamp
|
||||
}
|
||||
|
||||
@@ -42,6 +42,20 @@ var (
|
||||
MaxLookAheadWindow = MaxLookAheadSlots * WindowDuration // 1h at the default
|
||||
)
|
||||
|
||||
// TimestampGranularity is the resolution block timestamps are truncated to. It
|
||||
// tracks WindowDuration so the proposer-slot clock (TimeToSlot = elapsed /
|
||||
// WindowDuration) and the block-timestamp clock advance at the SAME rate: with a
|
||||
// coarser 1s truncation, a sub-second WindowDuration would inflate slot numbers
|
||||
// without finer time resolution, so blocks could not actually be produced faster
|
||||
// than 1/s. Capped at 1s so mainnet (WindowDuration >= 1s) keeps the original
|
||||
// 1-second block-time granularity exactly.
|
||||
func TimestampGranularity() time.Duration {
|
||||
if WindowDuration > time.Second {
|
||||
return time.Second
|
||||
}
|
||||
return WindowDuration
|
||||
}
|
||||
|
||||
// SetWindowDuration overrides the proposer-slot spacing. It MUST be called at
|
||||
// startup, before any chain begins block production: WindowDuration is read by
|
||||
// both the windower delay math and TimeToSlot, so changing it mid-flight would
|
||||
|
||||
@@ -551,7 +551,7 @@ func (vm *VM) timeToBuild(ctx context.Context) (time.Time, bool, error) {
|
||||
parentTimestamp = blk.Timestamp()
|
||||
nextStartTime time.Time
|
||||
)
|
||||
currentTime := vm.Clock.Time().Truncate(time.Second)
|
||||
currentTime := vm.Clock.Time().Truncate(proposer.TimestampGranularity())
|
||||
if nextStartTime, err = vm.getPostDurangoSlotTime(
|
||||
ctx,
|
||||
childBlockHeight,
|
||||
@@ -602,7 +602,7 @@ func (vm *VM) timeToBuildPreForkTransitionLocked(ctx context.Context) (time.Time
|
||||
var (
|
||||
parentTimestamp = pre.Timestamp()
|
||||
childHeight = pre.Height() + 1
|
||||
currentTime = vm.Clock.Time().Truncate(time.Second)
|
||||
currentTime = vm.Clock.Time().Truncate(proposer.TimestampGranularity())
|
||||
slot = proposer.TimeToSlot(parentTimestamp, currentTime)
|
||||
)
|
||||
// MinDelayForProposer returns the delay until THIS node's earliest slot in the
|
||||
|
||||
Reference in New Issue
Block a user