Skip to content

Commit e3e3f6a

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 e3e3f6a

File tree

2 files changed

+128
-29
lines changed

2 files changed

+128
-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: 72 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,43 @@ 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+
securityParam = uint64(byronGenesis.ProtocolConsts.K) //nolint:gosec
422+
return securityParam // Byron uses k directly
423+
}
424+
425+
// Shelley+ eras
426+
k := shelleyGenesis.SecurityParam
427+
if k < 0 {
428+
ls.config.Logger.Warn("invalid negative security parameter", "k", k)
429+
return blockfetchBatchSlotThresholdDefault
430+
}
431+
securityParam = uint64(k)
432+
433+
// Calculate 3k/f
434+
if shelleyGenesis.ActiveSlotsCoeff.Rat == nil {
435+
ls.config.Logger.Warn("ActiveSlotsCoeff.Rat is nil")
436+
return blockfetchBatchSlotThresholdDefault
437+
}
438+
439+
result := new(big.Rat).Quo(
440+
big.NewRat(int64(3*securityParam), 1), //nolint:gosec
441+
shelleyGenesis.ActiveSlotsCoeff.Rat,
442+
)
443+
return new(big.Int).Div(result.Num(), result.Denom()).Uint64()
444+
}
445+
409446
type readChainResult struct {
410447
rollbackPoint ocommon.Point
411448
blocks []ledger.Block
@@ -594,22 +631,42 @@ func (ls *LedgerState) ledgerProcessBlocks() {
594631
// Enable validation using the k-slot window from ShelleyGenesis.
595632
if !shouldValidate && i == 0 {
596633
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-
)
634+
// Get security parameter based on era
635+
var securityParam uint64
636+
if ls.currentEra.Id == 0 { // Byron era
637+
byronGenesis := ls.config.CardanoNodeConfig.ByronGenesis()
638+
if byronGenesis == nil {
639+
return errors.New(
640+
"failed to get Byron Genesis config",
641+
)
642+
}
643+
k := byronGenesis.ProtocolConsts.K
644+
if k < 0 {
645+
return fmt.Errorf(
646+
"k parameter must be non-negative: %d",
647+
k,
648+
)
649+
}
650+
// Safe conversion: K is non-negative
651+
securityParam = uint64(k) // #nosec G115
652+
} else { // Shelley and later eras
653+
shelleyGenesis := ls.config.CardanoNodeConfig.ShelleyGenesis()
654+
if shelleyGenesis == nil {
655+
return errors.New(
656+
"failed to get Shelley Genesis config",
657+
)
658+
}
659+
// Get security parameter (k)
660+
k := shelleyGenesis.SecurityParam
661+
if k < 0 {
662+
return fmt.Errorf(
663+
"security param must be non-negative: %d",
664+
k,
665+
)
666+
}
667+
// Safe conversion: SecurityParam is non-negative
668+
securityParam = uint64(k) // #nosec G115
611669
}
612-
securityParam := uint64(k)
613670
currentTipSlot := ls.currentTip.Point.Slot
614671
blockSlot := next.SlotNumber()
615672
if currentTipSlot >= securityParam {

0 commit comments

Comments
 (0)