Skip to content

Commit 8a15b9c

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 8a15b9c

File tree

2 files changed

+155
-26
lines changed

2 files changed

+155
-26
lines changed

ledger/chainsync.go

Lines changed: 120 additions & 11 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,80 @@ 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 := uint64(blockfetchBatchSlotThresholdDefault)
145+
byronGenesis := ls.config.CardanoNodeConfig.ByronGenesis()
146+
shelleyGenesis := ls.config.CardanoNodeConfig.ShelleyGenesis()
147+
if byronGenesis != nil && shelleyGenesis != nil {
148+
var securityParam uint64
149+
var validParams bool
150+
151+
// Use K parameter from Byron Genesis during Byron era, SecurityParam from Shelley Genesis for later eras
152+
if ls.currentEra.Id == 0 { // Byron era ID is 0
153+
k := byronGenesis.ProtocolConsts.K
154+
if k < 0 {
155+
ls.config.Logger.Warn("Byron K parameter is negative, using fallback", "k", k)
156+
validParams = false
157+
} else {
158+
// Safe conversion: K is non-negative
159+
securityParam = uint64(k) // #nosec G115
160+
validParams = true
161+
}
162+
} else {
163+
secParam := shelleyGenesis.SecurityParam
164+
if secParam < 0 {
165+
ls.config.Logger.Warn("Shelley SecurityParam is negative, using fallback", "securityParam", secParam)
166+
validParams = false
167+
} else {
168+
// Safe conversion: SecurityParam is non-negative
169+
securityParam = uint64(secParam) // #nosec G115
170+
validParams = true
171+
}
172+
}
173+
174+
if validParams {
175+
// Calculate stability window: 3k/f for Shelley+ eras, just k for Byron
176+
var stabilityWindow uint64
177+
if ls.currentEra.Id == 0 { // Byron era
178+
stabilityWindow = securityParam
179+
} else {
180+
// Validate ActiveSlotsCoeff before using it
181+
if shelleyGenesis.ActiveSlotsCoeff.Rat == nil {
182+
ls.config.Logger.Warn("Shelley ActiveSlotsCoeff.Rat is nil, using fallback")
183+
validParams = false
184+
} else {
185+
activeSlotsRat := shelleyGenesis.ActiveSlotsCoeff.Rat
186+
// Check for division by zero
187+
if activeSlotsRat.Num().Sign() == 0 {
188+
ls.config.Logger.Warn("Shelley ActiveSlotsCoeff numerator is zero, using fallback")
189+
validParams = false
190+
} else {
191+
// Compute stability window: floor((3 * securityParam) / f)
192+
// Using integer arithmetic to avoid floating point precision issues
193+
numerator := new(big.Int).SetUint64(3 * securityParam)
194+
quotient := new(big.Int).Div(
195+
new(big.Int).Mul(numerator, activeSlotsRat.Denom()),
196+
activeSlotsRat.Num(),
197+
)
198+
199+
// Check if result fits in uint64
200+
if !quotient.IsUint64() {
201+
ls.config.Logger.Warn("Stability window calculation result too large, using fallback")
202+
validParams = false
203+
} else {
204+
stabilityWindow = quotient.Uint64()
205+
}
206+
}
207+
}
208+
}
209+
210+
if validParams {
211+
slotThreshold = stabilityWindow
212+
}
213+
}
214+
}
144215
if e.Point.Slot < e.Tip.Point.Slot &&
145-
(e.Tip.Point.Slot-e.Point.Slot > blockfetchBatchSlotThreshold) &&
216+
(e.Tip.Point.Slot-e.Point.Slot > slotThreshold) &&
146217
(headerCount+1) < allowedHeaderCount {
147218
return nil
148219
}
@@ -272,13 +343,51 @@ func (ls *LedgerState) calculateEpochNonce(
272343
if byronGenesis == nil || shelleyGenesis == nil {
273344
return nil, errors.New("could not get genesis config")
274345
}
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()
346+
var securityParam uint64
347+
// Use K parameter from Byron Genesis during Byron era, SecurityParam from Shelley Genesis for later eras
348+
if ls.currentEra.Id == 0 { // Byron era ID is 0
349+
k := byronGenesis.ProtocolConsts.K
350+
if k < 0 {
351+
return nil, fmt.Errorf("k parameter must be non-negative: %d", k)
352+
}
353+
// Safe conversion: K is non-negative
354+
securityParam = uint64(k) // #nosec G115
355+
} else {
356+
secParam := shelleyGenesis.SecurityParam
357+
if secParam < 0 {
358+
return nil, fmt.Errorf("security param must be non-negative: %d", secParam)
359+
}
360+
// Safe conversion: SecurityParam is non-negative
361+
securityParam = uint64(secParam) // #nosec G115
362+
}
363+
// Calculate stability window: 3k/f for Shelley+ eras, just k for Byron
364+
var stabilityWindow uint64
365+
if ls.currentEra.Id == 0 { // Byron era
366+
stabilityWindow = securityParam
367+
} else {
368+
// Validate ActiveSlotsCoeff before using it
369+
if shelleyGenesis.ActiveSlotsCoeff.Rat == nil {
370+
return nil, errors.New("empty ActiveSlotsCoeff")
371+
}
372+
activeSlotsRat := shelleyGenesis.ActiveSlotsCoeff.Rat
373+
// Check for division by zero
374+
if activeSlotsRat.Num().Sign() == 0 {
375+
return nil, errors.New("divide by zero")
376+
}
377+
// Compute stability window: floor((3 * securityParam) / f)
378+
// Using integer arithmetic to avoid floating point precision issues
379+
numerator := new(big.Int).SetUint64(3 * securityParam)
380+
quotient := new(big.Int).Div(
381+
new(big.Int).Mul(numerator, activeSlotsRat.Denom()),
382+
activeSlotsRat.Num(),
383+
)
384+
385+
// Check if result fits in uint64
386+
if !quotient.IsUint64() {
387+
return nil, errors.New("stability window calculation result too large")
388+
}
389+
stabilityWindow = quotient.Uint64()
390+
}
282391
var stabilityWindowStartSlot uint64
283392
if epochStartSlot > stabilityWindow {
284393
stabilityWindowStartSlot = epochStartSlot - stabilityWindow

ledger/state.go

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -594,22 +594,42 @@ func (ls *LedgerState) ledgerProcessBlocks() {
594594
// Enable validation using the k-slot window from ShelleyGenesis.
595595
if !shouldValidate && i == 0 {
596596
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-
)
597+
// Get security parameter based on era
598+
var securityParam uint64
599+
if ls.currentEra.Id == 0 { // Byron era
600+
byronGenesis := ls.config.CardanoNodeConfig.ByronGenesis()
601+
if byronGenesis == nil {
602+
return errors.New(
603+
"failed to get Byron Genesis config",
604+
)
605+
}
606+
k := byronGenesis.ProtocolConsts.K
607+
if k < 0 {
608+
return fmt.Errorf(
609+
"k parameter must be non-negative: %d",
610+
k,
611+
)
612+
}
613+
// Safe conversion: K is non-negative
614+
securityParam = uint64(k) // #nosec G115
615+
} else { // Shelley and later eras
616+
shelleyGenesis := ls.config.CardanoNodeConfig.ShelleyGenesis()
617+
if shelleyGenesis == nil {
618+
return errors.New(
619+
"failed to get Shelley Genesis config",
620+
)
621+
}
622+
// Get security parameter (k)
623+
k := shelleyGenesis.SecurityParam
624+
if k < 0 {
625+
return fmt.Errorf(
626+
"security param must be non-negative: %d",
627+
k,
628+
)
629+
}
630+
// Safe conversion: SecurityParam is non-negative
631+
securityParam = uint64(k) // #nosec G115
611632
}
612-
securityParam := uint64(k)
613633
currentTipSlot := ls.currentTip.Point.Slot
614634
blockSlot := next.SlotNumber()
615635
if currentTipSlot >= securityParam {

0 commit comments

Comments
 (0)