Skip to content

Commit 594a6f6

Browse files
committed
fix: use stability window for slot threshold
- Replace hard-coded blockfetchBatchSlotThreshold with dynamic calculation - Use correct security parameters based on current era: * Byron era: K parameter from ByronGenesis.ProtocolConsts.K * Shelley+ eras: SecurityParam from ShelleyGenesis.SecurityParam - Update stability window calculation in both chainsync and validation logic - Calculate stability window as 3k/f for Shelley+ eras, k for Byron era - Ensures blockfetch operations respect protocol-defined stability window Fixes the TODO to calculate slot threshold from protocol params Signed-off-by: Chris Gianelloni <wolf31o2@blinklabs.io>
1 parent 2b47f49 commit 594a6f6

File tree

2 files changed

+179
-29
lines changed

2 files changed

+179
-29
lines changed

ledger/chainsync.go

Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,8 @@ const (
3636
// This prevents us exceeding the configured recv queue size in the block-fetch protocol
3737
blockfetchBatchSize = 500
3838

39-
// TODO: calculate from protocol params
40-
// Number of slots from upstream tip to stop doing blockfetch batches
41-
blockfetchBatchSlotThreshold = 2500 * 20
39+
// Default/fallback slot threshold for blockfetch batches
40+
blockfetchBatchSlotThresholdDefault = 2500 * 20
4241

4342
// Timeout for updates on a blockfetch operation. This is based on a 2s BatchStart
4443
// and a 2s Block timeout for blockfetch
@@ -141,8 +140,10 @@ func (ls *LedgerState) handleEventChainsyncBlockHeader(e ChainsyncEvent) error {
141140
}
142141
// Wait for additional block headers before fetching block bodies if we're
143142
// far enough out from upstream tip
143+
// Use security window as slot threshold if available
144+
slotThreshold := ls.calculateStabilityWindow()
144145
if e.Point.Slot < e.Tip.Point.Slot &&
145-
(e.Tip.Point.Slot-e.Point.Slot > blockfetchBatchSlotThreshold) &&
146+
(e.Tip.Point.Slot-e.Point.Slot > slotThreshold) &&
146147
(headerCount+1) < allowedHeaderCount {
147148
return nil
148149
}
@@ -267,18 +268,59 @@ func (ls *LedgerState) calculateEpochNonce(
267268
return genesisHashBytes, nil
268269
}
269270
// Calculate stability window
270-
byronGenesis := ls.config.CardanoNodeConfig.ByronGenesis()
271271
shelleyGenesis := ls.config.CardanoNodeConfig.ShelleyGenesis()
272-
if byronGenesis == nil || shelleyGenesis == nil {
273-
return nil, errors.New("could not get genesis config")
272+
if shelleyGenesis == nil {
273+
return nil, errors.New("could not get Shelley genesis config")
274+
}
275+
var securityParam uint64
276+
// Use K parameter from Byron Genesis during Byron era, SecurityParam from Shelley Genesis for later eras
277+
if ls.currentEra.Id == 0 { // Byron era ID is 0
278+
byronGenesis := ls.config.CardanoNodeConfig.ByronGenesis()
279+
if byronGenesis == nil {
280+
return nil, errors.New("cloud not get Byron genesis config")
281+
}
282+
k := byronGenesis.ProtocolConsts.K
283+
if k < 0 {
284+
return nil, fmt.Errorf("k parameter must be non-negative: %d", k)
285+
}
286+
// Safe conversion: K is non-negative
287+
securityParam = uint64(k) // #nosec G115
288+
} else {
289+
secParam := shelleyGenesis.SecurityParam
290+
if secParam < 0 {
291+
return nil, fmt.Errorf("security param must be non-negative: %d", secParam)
292+
}
293+
// Safe conversion: SecurityParam is non-negative
294+
securityParam = uint64(secParam) // #nosec G115
295+
}
296+
// Calculate stability window: 3k/f for Shelley+ eras, just k for Byron
297+
var stabilityWindow uint64
298+
if ls.currentEra.Id == 0 { // Byron era
299+
stabilityWindow = securityParam
300+
} else {
301+
// Validate ActiveSlotsCoeff before using it
302+
if shelleyGenesis.ActiveSlotsCoeff.Rat == nil {
303+
return nil, errors.New("empty ActiveSlotsCoeff")
304+
}
305+
activeSlotsRat := shelleyGenesis.ActiveSlotsCoeff.Rat
306+
// Check for division by zero
307+
if activeSlotsRat.Num().Sign() == 0 {
308+
return nil, errors.New("divide by zero")
309+
}
310+
// Compute stability window: floor((3 * securityParam) / f)
311+
// Using integer arithmetic to avoid floating point precision issues
312+
numerator := new(big.Int).SetUint64(3 * securityParam)
313+
quotient := new(big.Int).Div(
314+
new(big.Int).Mul(numerator, activeSlotsRat.Denom()),
315+
activeSlotsRat.Num(),
316+
)
317+
318+
// Check if result fits in uint64
319+
if !quotient.IsUint64() {
320+
return nil, errors.New("stability window calculation result too large")
321+
}
322+
stabilityWindow = quotient.Uint64()
274323
}
275-
stabilityWindow := new(big.Rat).Quo(
276-
big.NewRat(
277-
int64(3*byronGenesis.ProtocolConsts.K),
278-
1,
279-
),
280-
shelleyGenesis.ActiveSlotsCoeff.Rat,
281-
).Num().Uint64()
282324
var stabilityWindowStartSlot uint64
283325
if epochStartSlot > stabilityWindow {
284326
stabilityWindowStartSlot = epochStartSlot - stabilityWindow

ledger/state.go

Lines changed: 123 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,94 @@ func (ls *LedgerState) consumeUtxo(
406406
)
407407
}
408408

409+
// calculateStabilityWindow returns the stability window based on the current era.
410+
// For Byron era, returns k. For Shelley+ eras, returns 3k/f.
411+
// Returns the default threshold if genesis data is unavailable or invalid.
412+
func (ls *LedgerState) calculateStabilityWindow() uint64 {
413+
byronGenesis := ls.config.CardanoNodeConfig.ByronGenesis()
414+
shelleyGenesis := ls.config.CardanoNodeConfig.ShelleyGenesis()
415+
if byronGenesis == nil || shelleyGenesis == nil {
416+
return blockfetchBatchSlotThresholdDefault
417+
}
418+
419+
var securityParam uint64
420+
if ls.currentEra.Id == 0 { // Byron era
421+
k := byronGenesis.ProtocolConsts.K
422+
if k < 0 {
423+
ls.config.Logger.Warn("invalid negative security parameter", "k", k)
424+
return blockfetchBatchSlotThresholdDefault
425+
}
426+
if k == 0 {
427+
ls.config.Logger.Warn("security parameter is zero", "k", k)
428+
return blockfetchBatchSlotThresholdDefault
429+
}
430+
securityParam = uint64(k) // #nosec G115
431+
return securityParam // Byron uses k directly
432+
}
433+
434+
// Shelley+ eras
435+
k := shelleyGenesis.SecurityParam
436+
if k < 0 {
437+
ls.config.Logger.Warn("invalid negative security parameter", "k", k)
438+
return blockfetchBatchSlotThresholdDefault
439+
}
440+
if k == 0 {
441+
ls.config.Logger.Warn("security parameter is zero", "k", k)
442+
return blockfetchBatchSlotThresholdDefault
443+
}
444+
securityParam = uint64(k)
445+
446+
// Calculate 3k/f
447+
activeSlotsCoeff := shelleyGenesis.ActiveSlotsCoeff.Rat
448+
if activeSlotsCoeff == nil {
449+
ls.config.Logger.Warn("ActiveSlotsCoeff.Rat is nil")
450+
return blockfetchBatchSlotThresholdDefault
451+
}
452+
453+
if activeSlotsCoeff.Num().Sign() <= 0 {
454+
ls.config.Logger.Warn(
455+
"ActiveSlotsCoeff must be positive",
456+
"active_slots_coeff",
457+
activeSlotsCoeff.String(),
458+
)
459+
return blockfetchBatchSlotThresholdDefault
460+
}
461+
462+
numerator := new(big.Int).SetUint64(securityParam)
463+
numerator.Mul(numerator, big.NewInt(3))
464+
numerator.Mul(numerator, activeSlotsCoeff.Denom())
465+
denominator := new(big.Int).Set(activeSlotsCoeff.Num())
466+
window, remainder := new(
467+
big.Int,
468+
).QuoRem(numerator, denominator, new(big.Int))
469+
if remainder.Sign() != 0 {
470+
window.Add(window, big.NewInt(1))
471+
}
472+
if window.Sign() <= 0 {
473+
ls.config.Logger.Warn(
474+
"stability window calculation produced non-positive result",
475+
"security_param",
476+
securityParam,
477+
"active_slots_coeff",
478+
activeSlotsCoeff.String(),
479+
)
480+
return blockfetchBatchSlotThresholdDefault
481+
}
482+
if !window.IsUint64() {
483+
ls.config.Logger.Warn(
484+
"stability window calculation overflowed uint64",
485+
"security_param",
486+
securityParam,
487+
"active_slots_coeff",
488+
activeSlotsCoeff.String(),
489+
"window_num",
490+
window.String(),
491+
)
492+
return blockfetchBatchSlotThresholdDefault
493+
}
494+
return window.Uint64()
495+
}
496+
409497
type readChainResult struct {
410498
rollbackPoint ocommon.Point
411499
blocks []ledger.Block
@@ -594,22 +682,42 @@ func (ls *LedgerState) ledgerProcessBlocks() {
594682
// Enable validation using the k-slot window from ShelleyGenesis.
595683
if !shouldValidate && i == 0 {
596684
var cutoffSlot uint64
597-
// Get parameters from Shelley Genesis
598-
shelleyGenesis := ls.config.CardanoNodeConfig.ShelleyGenesis()
599-
if shelleyGenesis == nil {
600-
return errors.New(
601-
"failed to get Shelley Genesis config",
602-
)
603-
}
604-
// Get security parameter (k)
605-
k := shelleyGenesis.SecurityParam
606-
if k < 0 {
607-
return fmt.Errorf(
608-
"security param must be non-negative: %d",
609-
k,
610-
)
685+
// Get security parameter based on era
686+
var securityParam uint64
687+
if ls.currentEra.Id == 0 { // Byron era
688+
byronGenesis := ls.config.CardanoNodeConfig.ByronGenesis()
689+
if byronGenesis == nil {
690+
return errors.New(
691+
"failed to get Byron Genesis config",
692+
)
693+
}
694+
k := byronGenesis.ProtocolConsts.K
695+
if k < 0 {
696+
return fmt.Errorf(
697+
"k parameter must be non-negative: %d",
698+
k,
699+
)
700+
}
701+
// Safe conversion: K is non-negative
702+
securityParam = uint64(k) // #nosec G115
703+
} else { // Shelley and later eras
704+
shelleyGenesis := ls.config.CardanoNodeConfig.ShelleyGenesis()
705+
if shelleyGenesis == nil {
706+
return errors.New(
707+
"failed to get Shelley Genesis config",
708+
)
709+
}
710+
// Get security parameter (k)
711+
k := shelleyGenesis.SecurityParam
712+
if k < 0 {
713+
return fmt.Errorf(
714+
"security param must be non-negative: %d",
715+
k,
716+
)
717+
}
718+
// Safe conversion: SecurityParam is non-negative
719+
securityParam = uint64(k) // #nosec G115
611720
}
612-
securityParam := uint64(k)
613721
currentTipSlot := ls.currentTip.Point.Slot
614722
blockSlot := next.SlotNumber()
615723
if currentTipSlot >= securityParam {

0 commit comments

Comments
 (0)