Skip to content

Commit 2c15812

Browse files
committed
fix: detected failed next proving periods and adjust
- Add GetChallengeFinality to the smart-contract verifier interface and implementation. This exposes the on-chain challengeFinality setting. - Updated NextProvingPeriodTask to pull the stored challenge_window for each proof set, fetch the current tipset height and challenge finality, and detect when NextPDPChallengeWindowStart returns an epoch that violates challengeEpoch >= block.number + challengeFinality. In that case we now call adjustNextProveAt to compute a future epoch aligned with the next challenge window and log the adjustment. The corrected epoch is sent to nextProvingPeriod and persisted so the scheduler heals itself the next time it runs.
1 parent 22706c4 commit 2c15812

File tree

3 files changed

+68
-31
lines changed

3 files changed

+68
-31
lines changed

pkg/pdp/smartcontracts/verifier.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ var log = logging.Logger("smartcontracts")
2020
type Verifier interface {
2121
GetDataSetLeafCount(ctx context.Context, setId *big.Int) (*big.Int, error)
2222
GetNextChallengeEpoch(ctx context.Context, setId *big.Int) (*big.Int, error)
23+
GetChallengeFinality(ctx context.Context) (*big.Int, error)
2324
GetDataSetListener(ctx context.Context, setId *big.Int) (common.Address, error)
2425
GetDataSetStorageProvider(ctx context.Context, setId *big.Int) (common.Address, common.Address, error)
2526
GetChallengeRange(ctx context.Context, setId *big.Int) (*big.Int, error)
@@ -72,6 +73,10 @@ func (v *verifierContract) GetNextChallengeEpoch(ctx context.Context, setId *big
7273
return v.verifier.GetNextChallengeEpoch(&bind.CallOpts{Context: ctx}, setId)
7374
}
7475

76+
func (v *verifierContract) GetChallengeFinality(ctx context.Context) (*big.Int, error) {
77+
return v.verifier.GetChallengeFinality(&bind.CallOpts{Context: ctx})
78+
}
79+
7580
func (v *verifierContract) GetDataSetListener(ctx context.Context, setId *big.Int) (common.Address, error) {
7681
return v.verifier.GetDataSetListener(&bind.CallOpts{Context: ctx}, setId)
7782
}

pkg/pdp/tasks/next_pdp.go

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package tasks
22

33
import (
44
"context"
5-
"database/sql"
65
"errors"
76
"fmt"
87
"math/big"
@@ -23,8 +22,6 @@ import (
2322

2423
var _ scheduler.TaskInterface = &NextProvingPeriodTask{}
2524

26-
//var _ = scheduler.Reg(&NextProvingPeriodTask{})
27-
2825
type NextProvingPeriodTask struct {
2926
db *gorm.DB
3027
ethClient bind.ContractBackend
@@ -82,7 +79,7 @@ func NewNextProvingPeriodTask(
8279
Where("id = ? AND challenge_request_task_id IS NULL", ps.ProofSetID).
8380
Update("challenge_request_task_id", id)
8481
if result.Error != nil {
85-
return false, fmt.Errorf("failed to update pdp_proof_sets: %w", err)
82+
return false, fmt.Errorf("failed to update pdp_proof_sets: %w", result.Error)
8683
}
8784
if result.RowsAffected == 0 {
8885
// Someone else might have already scheduled the task
@@ -138,9 +135,9 @@ func (n *NextProvingPeriodTask) Do(taskID scheduler.TaskID) (done bool, err erro
138135
err = n.db.WithContext(ctx).
139136
Model(&models.PDPProofSet{}).
140137
Where("challenge_request_task_id = ? AND prove_at_epoch IS NOT NULL", taskID).
141-
Select("id").
138+
Select("id", "challenge_window").
142139
First(&pdp).Error
143-
if errors.Is(err, sql.ErrNoRows) {
140+
if errors.Is(err, gorm.ErrRecordNotFound) {
144141
// No matching proof set, task is done (something weird happened, and e.g another task was spawned in place of this one)
145142
return true, nil
146143
}
@@ -154,6 +151,37 @@ func (n *NextProvingPeriodTask) Do(taskID scheduler.TaskID) (done bool, err erro
154151
return false, fmt.Errorf("failed to get next challenge window start: %w", err)
155152
}
156153

154+
if pdp.ChallengeWindow == nil {
155+
return false, fmt.Errorf("proof set %d missing challenge window metadata", proofSetID)
156+
}
157+
158+
challengeFinality, err := n.verifier.GetChallengeFinality(ctx)
159+
if err != nil {
160+
return false, fmt.Errorf("failed to get challenge finality: %w", err)
161+
}
162+
163+
challengeWindow := big.NewInt(*pdp.ChallengeWindow)
164+
165+
ts, err := n.fil.ChainHead(ctx)
166+
if err != nil {
167+
return false, fmt.Errorf("failed to get chain head: %w", err)
168+
}
169+
170+
minEpoch := big.NewInt(int64(ts.Height()))
171+
minEpoch.Add(minEpoch, challengeFinality)
172+
173+
if nextProveAt.Cmp(minEpoch) < 0 {
174+
adjusted := adjustNextProveAt(int64(ts.Height()), challengeFinality, challengeWindow)
175+
log.Warnw("adjusting next prove epoch",
176+
"proof_set_id", proofSetID,
177+
"original_epoch", nextProveAt,
178+
"adjusted_epoch", adjusted,
179+
"current_height", ts.Height(),
180+
"challenge_window", challengeWindow,
181+
)
182+
nextProveAt = adjusted
183+
}
184+
157185
// Prepare the transaction data
158186
abiData, err := n.verifier.GetABI()
159187
if err != nil {
@@ -180,12 +208,6 @@ func (n *NextProvingPeriodTask) Do(taskID scheduler.TaskID) (done bool, err erro
180208
return false, fmt.Errorf("failed to get default sender address: %w", err)
181209
}
182210

183-
// Get the current tipset
184-
ts, err := n.fil.ChainHead(ctx)
185-
if err != nil {
186-
return false, fmt.Errorf("failed to get chain head: %w", err)
187-
}
188-
189211
// Send the transaction
190212
reason := "pdp-proving-period"
191213
log.Infow("Sending next proving period transaction", "task_id", taskID, "proof_set_id", proofSetID,
@@ -205,7 +227,7 @@ func (n *NextProvingPeriodTask) Do(taskID scheduler.TaskID) (done bool, err erro
205227
"prove_at_epoch": nextProveAt.Uint64(),
206228
})
207229
if result.Error != nil {
208-
return fmt.Errorf("failed to update pdp_proof_sets: %w", err)
230+
return fmt.Errorf("failed to update pdp_proof_sets: %w", result.Error)
209231
}
210232
if result.RowsAffected == 0 {
211233
return fmt.Errorf("pdp_proof_sets update affected 0 rows")

pkg/pdp/tasks/next_pdp_test.go

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,6 @@ func TestAdjustNextProveAt(t *testing.T) {
3030
expected: 2011, // minRequired=2002, next window=2010, result=2011 (2010+1)
3131
description: "When minRequired doesn't fall on boundary, find next window",
3232
},
33-
{
34-
name: "exact scenario from logs",
35-
currentHeight: 2685164,
36-
challengeFinality: big.NewInt(2),
37-
challengeWindow: big.NewInt(30),
38-
expected: 2685181, // minRequired=2685166, next window=2685180, result=2685181 (2685180+1)
39-
description: "Real scenario should produce predictable window placement",
40-
},
4133
{
4234
name: "falls exactly on window boundary",
4335
currentHeight: 100,
@@ -47,25 +39,43 @@ func TestAdjustNextProveAt(t *testing.T) {
4739
description: "When minRequired falls exactly on boundary, move to next window",
4840
},
4941
{
50-
name: "smart contract realistic values",
42+
name: "mainnet params inside current window",
43+
currentHeight: 5568958,
44+
challengeFinality: big.NewInt(150), // mainnet challengeFinality
45+
challengeWindow: big.NewInt(20), // mainnet challenge window size
46+
expected: 5569121, // minRequired=5569108, next window=5569120, result=5569121
47+
description: "Large finality relative to window should advance to the next window boundary",
48+
},
49+
{
50+
name: "mainnet params exact boundary",
51+
currentHeight: 1000010, // (height + finality) lands exactly on a 20-epoch boundary
52+
challengeFinality: big.NewInt(150), // mainnet challengeFinality
53+
challengeWindow: big.NewInt(20), // mainnet challenge window size
54+
expected: 1000181, // minRequired=1000160, boundary=1000160 => next window 1000180, result=1000181
55+
description: "Exact boundary must bump to the next window and add 1 epoch",
56+
},
57+
{
58+
name: "realistic small finality/window",
5159
currentHeight: 1000000,
52-
challengeFinality: big.NewInt(2), // MinConfidence from watcher_eth.go
53-
challengeWindow: big.NewInt(30), // Common challenge window from tests
54-
expected: 1000021, // minRequired=1000002, windowStart=999990+30=1000020, result=1000021 (1000020+1)
55-
description: "Realistic smart contract values with challenge window 30 and finality 2",
60+
challengeFinality: big.NewInt(2),
61+
challengeWindow: big.NewInt(30),
62+
expected: 1000021, // minRequired=1000002, next window=1000020, result=1000021
63+
description: "Small finality with 30-epoch windows advances to the immediate next window",
5664
},
5765
{
58-
name: "proving period scenario",
59-
currentHeight: 500000,
66+
name: "late in window still advances only one window",
67+
currentHeight: 2685164,
6068
challengeFinality: big.NewInt(2),
61-
challengeWindow: big.NewInt(60), // As requested - proving period of 60
62-
expected: 500041, // minRequired=500002, next window=500040, result=500041 (500040+1)
63-
description: "Scenario with proving period/challenge window of 60 epochs",
69+
challengeWindow: big.NewInt(30),
70+
expected: 2685181, // minRequired=2685166, next window=2685180, result=2685181
71+
description: "Late-window case advances to next boundary, not multiple windows ahead",
6472
},
6573
}
6674

6775
for _, tt := range tests {
76+
tt := tt
6877
t.Run(tt.name, func(t *testing.T) {
78+
t.Parallel()
6979
result := adjustNextProveAt(tt.currentHeight, tt.challengeFinality, tt.challengeWindow)
7080
resultInt := result.Int64()
7181

0 commit comments

Comments
 (0)