Skip to content

Commit 5161ad6

Browse files
committed
Write sequencer submission strategy with params for 2, 5 and 12 hours
1 parent 0d8b00c commit 5161ad6

File tree

3 files changed

+151
-5
lines changed

3 files changed

+151
-5
lines changed

rollup/conf/config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
"batch_submission": {
5555
"min_batches": 1,
5656
"max_batches": 6,
57-
"timeout": 300
57+
"timeout": 7200
5858
},
5959
"gas_oracle_config": {
6060
"min_gas_price": 0,

rollup/internal/controller/relayer/l2_relayer.go

Lines changed: 145 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"errors"
66
"fmt"
7+
"math"
78
"math/big"
89
"sort"
910
"strings"
@@ -46,6 +47,7 @@ type Layer2Relayer struct {
4647
batchOrm *orm.Batch
4748
chunkOrm *orm.Chunk
4849
l2BlockOrm *orm.L2Block
50+
l1BlockOrm *orm.L1Block
4951

5052
cfg *config.RelayerConfig
5153

@@ -63,6 +65,22 @@ type Layer2Relayer struct {
6365
chainCfg *params.ChainConfig
6466
}
6567

68+
// StrategyParams holds the per‐window fee‐submission rules.
69+
type StrategyParams struct {
70+
BaselineType string // "pct_min" or "ewma"
71+
BaselineParam float64 // percentile (0–1) or α for EWMA
72+
Gamma float64 // relaxation γ
73+
Beta float64 // relaxation β
74+
RelaxType string // "exponential" or "sigmoid"
75+
}
76+
77+
// bestParams maps your 2h/5h/12h windows to their best rules.
78+
var bestParams = map[uint64]StrategyParams{
79+
2 * 3600: {BaselineType: "pct_min", BaselineParam: 0.10, Gamma: 0.4, Beta: 8, RelaxType: "exponential"},
80+
5 * 3600: {BaselineType: "pct_min", BaselineParam: 0.30, Gamma: 0.6, Beta: 20, RelaxType: "sigmoid"},
81+
12 * 3600: {BaselineType: "pct_min", BaselineParam: 0.50, Gamma: 0.5, Beta: 20, RelaxType: "sigmoid"},
82+
}
83+
6684
// NewLayer2Relayer will return a new instance of Layer2RelayerClient
6785
func NewLayer2Relayer(ctx context.Context, l2Client *ethclient.Client, db *gorm.DB, cfg *config.RelayerConfig, chainCfg *params.ChainConfig, serviceType ServiceType, reg prometheus.Registerer) (*Layer2Relayer, error) {
6886
var commitSender, finalizeSender *sender.Sender
@@ -106,6 +124,7 @@ func NewLayer2Relayer(ctx context.Context, l2Client *ethclient.Client, db *gorm.
106124

107125
bundleOrm: orm.NewBundle(db),
108126
batchOrm: orm.NewBatch(db),
127+
l1BlockOrm: orm.NewL1Block(db),
109128
l2BlockOrm: orm.NewL2Block(db),
110129
chunkOrm: orm.NewChunk(db),
111130

@@ -268,12 +287,58 @@ func (r *Layer2Relayer) commitGenesisBatch(batchHash string, batchHeader []byte,
268287
// ProcessPendingBatches processes the pending batches by sending commitBatch transactions to layer 1.
269288
func (r *Layer2Relayer) ProcessPendingBatches() {
270289
// get pending batches from database in ascending order by their index.
271-
dbBatches, err := r.batchOrm.GetFailedAndPendingBatches(r.ctx, r.cfg.BatchSubmission.MaxBatches)
290+
allBatches, err := r.batchOrm.GetFailedAndPendingBatches(r.ctx, 0)
272291
if err != nil {
273292
log.Error("Failed to fetch pending L2 batches", "err", err)
274293
return
275294
}
276295

296+
// if backlog outgrow max size, force‐submit enough oldest batches
297+
backlogCount := len(allBatches)
298+
backlogMax := 75 //r.cfg.BatchSubmission.BacklogMax
299+
300+
if len(allBatches) < r.cfg.BatchSubmission.MinBatches || len(allBatches) == 0 {
301+
log.Debug("Not enough pending batches to submit", "count", len(allBatches), "minBatches", r.cfg.BatchSubmission.MinBatches, "maxBatches", r.cfg.BatchSubmission.MaxBatches)
302+
return
303+
}
304+
305+
// return if not hitting target price
306+
if backlogCount <= backlogMax {
307+
strat, ok := bestParams[uint64(r.cfg.BatchSubmission.TimeoutSec)]
308+
if !ok {
309+
log.Warn("unknown timeoutSec in bestParams, falling back to immediate submit",
310+
"windowSec", r.cfg.BatchSubmission.TimeoutSec)
311+
} else {
312+
// pull the blob‐fee history
313+
hist, err := r.fetchBlobFeeHistory(strat.WindowSec)
314+
if err != nil || len(hist) == 0 {
315+
log.Warn("blob-fee history unavailable or empty; fallback to immediate batch submission",
316+
"err", err, "history_length", len(hist))
317+
// Proceed immediately with batch submission without further checks
318+
} else {
319+
// compute the target
320+
oldest := allBatches[0].CreatedAt
321+
target := calculateTargetPrice(strat, oldest, hist)
322+
323+
// current = most recent sample
324+
current := hist[len(hist)-1]
325+
326+
// deadline
327+
deadline := time.Duration(strat.WindowSec) * time.Second
328+
if current.Cmp(target) > 0 && time.Since(oldest) < deadline {
329+
log.Debug("blob‐fee above target & window not yet passed; skipping submit",
330+
"current", current, "target", target, "age", time.Since(oldest))
331+
return
332+
}
333+
}
334+
}
335+
}
336+
337+
dbBatches := allBatches
338+
if len(allBatches) > r.cfg.BatchSubmission.MaxBatches {
339+
dbBatches = allBatches[:r.cfg.BatchSubmission.MaxBatches]
340+
}
341+
277342
var batchesToSubmit []*dbBatchWithChunksAndParent
278343
var forceSubmit bool
279344
for i, dbBatch := range dbBatches {
@@ -1120,6 +1185,85 @@ func (r *Layer2Relayer) StopSenders() {
11201185
}
11211186
}
11221187

1188+
// fetchBlobFeeHistory returns the last WindowSec seconds of blob‐fee samples,
1189+
// by reading L1Block table’s BlobBaseFee column.
1190+
func (r *Layer2Relayer) fetchBlobFeeHistory(windowSec uint64) ([]*big.Int, error) {
1191+
// how many blocks ago? ~12s per block
1192+
blocksAgo := windowSec / 12
1193+
latest, err := r.l1BlockOrm.GetLatestL1BlockHeight(r.ctx)
1194+
if err != nil {
1195+
return nil, fmt.Errorf("GetLatestL1BlockHeight: %w", err)
1196+
}
1197+
start := int64(latest) - int64(blocksAgo)
1198+
if start < 0 {
1199+
start = 0
1200+
}
1201+
1202+
// pull all L1Blocks in [start .. latest]
1203+
filters := map[string]interface{}{
1204+
"number >= ?": start,
1205+
"number <= ?": latest,
1206+
}
1207+
recs, err := r.l1BlockOrm.GetL1Blocks(r.ctx, filters)
1208+
if err != nil {
1209+
return nil, fmt.Errorf("GetL1Blocks: %w", err)
1210+
}
1211+
hist := make([]*big.Int, len(recs))
1212+
for i, b := range recs {
1213+
hist[i] = new(big.Int).SetUint64(b.BlobBaseFee)
1214+
}
1215+
return hist, nil
1216+
}
1217+
1218+
// calculateTargetPrice applies pct_min/ewma + relaxation to get a BigInt target
1219+
func calculateTargetPrice(strat StrategyParams, firstTime time.Time, history []*big.Int) *big.Int {
1220+
n := len(history)
1221+
if n == 0 {
1222+
return big.NewInt(0)
1223+
}
1224+
// convert to float64 Gwei
1225+
data := make([]float64, n)
1226+
for i, v := range history {
1227+
f, _ := new(big.Float).Quo(new(big.Float).SetInt(v), big.NewFloat(1e9)).Float64()
1228+
data[i] = f
1229+
}
1230+
var baseline float64
1231+
switch strat.BaselineType {
1232+
case "pct_min":
1233+
sort.Float64s(data)
1234+
idx := int(strat.BaselineParam * float64(n-1))
1235+
if idx < 0 {
1236+
idx = 0
1237+
}
1238+
baseline = data[idx]
1239+
case "ewma":
1240+
alpha := strat.BaselineParam
1241+
ewma := data[0]
1242+
for i := 1; i < n; i++ {
1243+
ewma = alpha*data[i] + (1-alpha)*ewma
1244+
}
1245+
baseline = ewma
1246+
default:
1247+
baseline = data[n-1]
1248+
}
1249+
// relaxation
1250+
age := time.Since(firstTime).Seconds()
1251+
frac := age / float64(strat.WindowSec)
1252+
var adjusted float64
1253+
switch strat.RelaxType {
1254+
case "exponential":
1255+
adjusted = baseline * (1 + strat.Gamma*math.Exp(strat.Beta*(frac-1)))
1256+
case "sigmoid":
1257+
adjusted = baseline * (1 + strat.Gamma/(1+math.Exp(-strat.Beta*(frac-0.5))))
1258+
default:
1259+
adjusted = baseline
1260+
}
1261+
// back to wei
1262+
f := new(big.Float).Mul(big.NewFloat(adjusted), big.NewFloat(1e9))
1263+
out, _ := f.Int(nil)
1264+
return out
1265+
}
1266+
11231267
func addrFromSignerConfig(config *config.SignerConfig) (common.Address, error) {
11241268
switch config.SignerType {
11251269
case sender.PrivateKeySignerType:

rollup/internal/orm/batch.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -221,15 +221,17 @@ func (o *Batch) GetRollupStatusByHashList(ctx context.Context, hashes []string)
221221
// GetFailedAndPendingBatches retrieves batches with failed or pending status up to the specified limit.
222222
// The returned batches are sorted in ascending order by their index.
223223
func (o *Batch) GetFailedAndPendingBatches(ctx context.Context, limit int) ([]*Batch, error) {
224-
if limit <= 0 {
225-
return nil, errors.New("limit must be greater than zero")
224+
if limit < 0 {
225+
return nil, errors.New("limit must be greater than or equal to zero")
226226
}
227227

228228
db := o.db.WithContext(ctx)
229229
db = db.Model(&Batch{})
230230
db = db.Where("rollup_status = ? OR rollup_status = ?", types.RollupCommitFailed, types.RollupPending)
231231
db = db.Order("index ASC")
232-
db = db.Limit(limit)
232+
if limit > 0 {
233+
db = db.Limit(limit)
234+
}
233235

234236
var batches []*Batch
235237
if err := db.Find(&batches).Error; err != nil {

0 commit comments

Comments
 (0)