Skip to content

Commit 8c36c4b

Browse files
feat: track certificates settlement
1 parent 435fdf8 commit 8c36c4b

File tree

5 files changed

+182
-9
lines changed

5 files changed

+182
-9
lines changed

aggsender/aggsender.go

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
aggsendertypes "github.com/0xPolygon/cdk/aggsender/types"
1212
"github.com/0xPolygon/cdk/bridgesync"
1313
cdkcommon "github.com/0xPolygon/cdk/common"
14-
"github.com/0xPolygon/cdk/config/types"
1514
"github.com/0xPolygon/cdk/etherman"
1615
"github.com/0xPolygon/cdk/l1infotreesync"
1716
"github.com/0xPolygon/cdk/log"
@@ -48,7 +47,7 @@ type AggSender struct {
4847
storage db.AggSenderStorage
4948
aggLayerClient agglayer.AgglayerClientInterface
5049

51-
sendInterval types.Duration
50+
cfg Config
5251

5352
sequencerKey *ecdsa.PrivateKey
5453
}
@@ -73,25 +72,26 @@ func New(
7372
}
7473

7574
return &AggSender{
75+
cfg: cfg,
7676
log: logger,
7777
storage: storage,
7878
l2Syncer: l2Syncer,
7979
l2Client: l2Client,
8080
aggLayerClient: aggLayerClient,
8181
l1infoTreeSyncer: l1InfoTreeSyncer,
8282
sequencerKey: sequencerPrivateKey,
83-
sendInterval: cfg.CertificateSendInterval,
8483
}, nil
8584
}
8685

8786
// Start starts the AggSender
8887
func (a *AggSender) Start(ctx context.Context) {
89-
a.sendCertificates(ctx)
88+
go a.sendCertificates(ctx)
89+
go a.checkIfCertificatesAreSettled(ctx)
9090
}
9191

9292
// sendCertificates sends certificates to the aggLayer
9393
func (a *AggSender) sendCertificates(ctx context.Context) {
94-
ticker := time.NewTicker(a.sendInterval.Duration)
94+
ticker := time.NewTicker(a.cfg.CertificateSendInterval.Duration)
9595

9696
for {
9797
select {
@@ -408,3 +408,38 @@ func (a *AggSender) signCertificate(certificate *agglayer.Certificate) (*agglaye
408408
Signature: sig,
409409
}, nil
410410
}
411+
412+
// checkIfCertificatesAreSettled checks if certificates are settled
413+
func (a *AggSender) checkIfCertificatesAreSettled(ctx context.Context) {
414+
ticker := time.NewTicker(a.cfg.CertificateSendInterval.Duration)
415+
for {
416+
select {
417+
case <-ticker.C:
418+
pendingCertificates, err := a.storage.GetCertificatesByStatus(ctx, []agglayer.CertificateStatus{agglayer.Pending})
419+
if err != nil {
420+
a.log.Error("error getting pending certificates: %w", err)
421+
continue
422+
}
423+
424+
for _, certificate := range pendingCertificates {
425+
certificateHeader, err := a.aggLayerClient.GetCertificateHeader(certificate.CertificateID)
426+
if err != nil {
427+
a.log.Error("error getting header of certificate %s with height: %d from agglayer: %w",
428+
certificate.CertificateID, certificate.Height, err)
429+
continue
430+
}
431+
432+
if certificateHeader.Status == agglayer.Settled || certificateHeader.Status == agglayer.InError {
433+
certificate.Status = certificateHeader.Status
434+
435+
if err := a.storage.UpdateCertificateStatus(ctx, *certificate); err != nil {
436+
a.log.Error("error updating certificate status in storage: %w", err)
437+
continue
438+
}
439+
}
440+
}
441+
case <-ctx.Done():
442+
return
443+
}
444+
}
445+
}

aggsender/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ type Config struct {
99
DBPath string `mapstructure:"DBPath"`
1010
AggLayerURL string `mapstructure:"AggLayerURL"`
1111
CertificateSendInterval types.Duration `mapstructure:"CertificateSendInterval"`
12+
CheckSettledInterval types.Duration `mapstructure:"CheckSettledInterval"`
1213
SequencerPrivateKey types.KeystoreFileConfig `mapstructure:"SequencerPrivateKey"`
1314
URLRPCL2 string `mapstructure:"URLRPCL2"`
1415
}

aggsender/db/aggsender_db_storage.go

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import (
66
"errors"
77
"fmt"
88
"math"
9+
"strings"
910

11+
"github.com/0xPolygon/cdk/agglayer"
1012
"github.com/0xPolygon/cdk/aggsender/db/migrations"
1113
"github.com/0xPolygon/cdk/aggsender/types"
1214
"github.com/0xPolygon/cdk/db"
@@ -25,6 +27,10 @@ type AggSenderStorage interface {
2527
SaveLastSentCertificate(ctx context.Context, certificate types.CertificateInfo) error
2628
// DeleteCertificate deletes a certificate from the storage
2729
DeleteCertificate(ctx context.Context, certificateID common.Hash) error
30+
// GetCertificatesByStatus returns a list of certificates by their status
31+
GetCertificatesByStatus(ctx context.Context, status []agglayer.CertificateStatus) ([]*types.CertificateInfo, error)
32+
// UpdateCertificateStatus updates the status of a certificate
33+
UpdateCertificateStatus(ctx context.Context, certificate types.CertificateInfo) error
2834
}
2935

3036
var _ AggSenderStorage = (*AggSenderSQLStorage)(nil)
@@ -52,6 +58,45 @@ func NewAggSenderSQLStorage(logger *log.Logger, dbPath string) (*AggSenderSQLSto
5258
}, nil
5359
}
5460

61+
func (a *AggSenderSQLStorage) GetCertificatesByStatus(ctx context.Context,
62+
statuses []agglayer.CertificateStatus) ([]*types.CertificateInfo, error) {
63+
tx, err := a.db.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
64+
if err != nil {
65+
return nil, err
66+
}
67+
68+
defer func() {
69+
if err := tx.Rollback(); err != nil {
70+
a.logger.Warnf("error rolling back tx: %w", err)
71+
}
72+
}()
73+
74+
query := "SELECT * FROM certificate_info"
75+
args := make([]interface{}, len(statuses))
76+
77+
if len(statuses) > 0 {
78+
placeholders := make([]string, len(statuses))
79+
// Build the WHERE clause for status filtering
80+
for i := range statuses {
81+
placeholders[i] = fmt.Sprintf("$%d", i+1)
82+
args[i] = statuses[i]
83+
}
84+
85+
// Build the WHERE clause with the joined placeholders
86+
query += " WHERE status IN (" + strings.Join(placeholders, ", ") + ")"
87+
}
88+
89+
// Add ordering by creation date (oldest first)
90+
query += " ORDER BY height ASC"
91+
92+
var certificates []*types.CertificateInfo
93+
if err = meddler.QueryAll(a.db, &certificates, query, args...); err != nil {
94+
return nil, err
95+
}
96+
97+
return certificates, nil
98+
}
99+
55100
// GetCertificateByHeight returns a certificate by its height
56101
func (a *AggSenderSQLStorage) GetCertificateByHeight(ctx context.Context,
57102
height uint64) (types.CertificateInfo, error) {
@@ -79,7 +124,7 @@ func (a *AggSenderSQLStorage) GetCertificateByHeight(ctx context.Context,
79124
return certificateInfo, nil
80125
}
81126

82-
// GetLastSentCertificate returns the last certificate sent to the aggLayer
127+
// GetLastSentCertificate returns the last certificate sent to the aggLayer that is still Pending
83128
func (a *AggSenderSQLStorage) GetLastSentCertificate(ctx context.Context) (types.CertificateInfo, error) {
84129
tx, err := a.db.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
85130
if err != nil {
@@ -92,7 +137,8 @@ func (a *AggSenderSQLStorage) GetLastSentCertificate(ctx context.Context) (types
92137
}
93138
}()
94139

95-
rows, err := tx.Query(`SELECT * FROM certificate_info ORDER BY height DESC LIMIT 1;`)
140+
rows, err := tx.Query(`SELECT * FROM certificate_info WHERE status = $1 ORDER BY height DESC LIMIT 1;`,
141+
agglayer.Pending)
96142
if err != nil {
97143
return types.CertificateInfo{}, getSelectQueryError(math.MaxUint64, err) // force checking err not found
98144
}
@@ -157,6 +203,33 @@ func (a *AggSenderSQLStorage) DeleteCertificate(ctx context.Context, certificate
157203
return nil
158204
}
159205

206+
// UpdateCertificateStatus updates the status of a certificate
207+
func (a *AggSenderSQLStorage) UpdateCertificateStatus(ctx context.Context, certificate types.CertificateInfo) error {
208+
tx, err := db.NewTx(ctx, a.db)
209+
if err != nil {
210+
return err
211+
}
212+
defer func() {
213+
if err != nil {
214+
if errRllbck := tx.Rollback(); errRllbck != nil {
215+
a.logger.Errorf("error while rolling back tx %w", errRllbck)
216+
}
217+
}
218+
}()
219+
220+
if _, err := tx.Exec(`UPDATE certificate_info SET status = $1 WHERE certificate_id = $2;`,
221+
certificate.Status, certificate.CertificateID); err != nil {
222+
return fmt.Errorf("error updating certificate info: %w", err)
223+
}
224+
if err := tx.Commit(); err != nil {
225+
return err
226+
}
227+
228+
a.logger.Debugf("updated certificate status - CertificateID: %s", certificate.CertificateID)
229+
230+
return nil
231+
}
232+
160233
// clean deletes all the data from the storage
161234
// NOTE: Used only in tests
162235
func (a *AggSenderSQLStorage) clean() error {

aggsender/db/aggsender_db_storage_test.go

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ func Test_Storage(t *testing.T) {
7474
NewLocalExitRoot: common.HexToHash("0x6"),
7575
FromBlock: 5,
7676
ToBlock: 6,
77-
Status: agglayer.Settled,
77+
Status: agglayer.Pending,
7878
}
7979
require.NoError(t, storage.SaveLastSentCertificate(ctx, certificate))
8080

@@ -113,4 +113,68 @@ func Test_Storage(t *testing.T) {
113113
require.Equal(t, certificate, certificateFromDB)
114114
require.NoError(t, storage.clean())
115115
})
116+
117+
t.Run("GetCertificatesByStatus", func(t *testing.T) {
118+
// Insert some certificates with different statuses
119+
certificates := []*types.CertificateInfo{
120+
{
121+
Height: 7,
122+
CertificateID: common.HexToHash("0x7"),
123+
NewLocalExitRoot: common.HexToHash("0x8"),
124+
FromBlock: 7,
125+
ToBlock: 8,
126+
Status: agglayer.Settled,
127+
},
128+
{
129+
Height: 9,
130+
CertificateID: common.HexToHash("0x9"),
131+
NewLocalExitRoot: common.HexToHash("0xA"),
132+
FromBlock: 9,
133+
ToBlock: 10,
134+
Status: agglayer.Pending,
135+
},
136+
{
137+
Height: 11,
138+
CertificateID: common.HexToHash("0xB"),
139+
NewLocalExitRoot: common.HexToHash("0xC"),
140+
FromBlock: 11,
141+
ToBlock: 12,
142+
Status: agglayer.InError,
143+
},
144+
}
145+
146+
for _, cert := range certificates {
147+
require.NoError(t, storage.SaveLastSentCertificate(ctx, *cert))
148+
}
149+
150+
// Test fetching certificates with status Settled
151+
statuses := []agglayer.CertificateStatus{agglayer.Settled}
152+
certificatesFromDB, err := storage.GetCertificatesByStatus(ctx, statuses)
153+
require.NoError(t, err)
154+
require.Len(t, certificatesFromDB, 1)
155+
require.ElementsMatch(t, []*types.CertificateInfo{certificates[0]}, certificatesFromDB)
156+
157+
// Test fetching certificates with status Pending
158+
statuses = []agglayer.CertificateStatus{agglayer.Pending}
159+
certificatesFromDB, err = storage.GetCertificatesByStatus(ctx, statuses)
160+
require.NoError(t, err)
161+
require.Len(t, certificatesFromDB, 1)
162+
require.ElementsMatch(t, []*types.CertificateInfo{certificates[1]}, certificatesFromDB)
163+
164+
// Test fetching certificates with status InError
165+
statuses = []agglayer.CertificateStatus{agglayer.InError}
166+
certificatesFromDB, err = storage.GetCertificatesByStatus(ctx, statuses)
167+
require.NoError(t, err)
168+
require.Len(t, certificatesFromDB, 1)
169+
require.ElementsMatch(t, []*types.CertificateInfo{certificates[2]}, certificatesFromDB)
170+
171+
// Test fetching certificates with status InError and Pending
172+
statuses = []agglayer.CertificateStatus{agglayer.InError, agglayer.Pending}
173+
certificatesFromDB, err = storage.GetCertificatesByStatus(ctx, statuses)
174+
require.NoError(t, err)
175+
require.Len(t, certificatesFromDB, 2)
176+
require.ElementsMatch(t, []*types.CertificateInfo{certificates[1], certificates[2]}, certificatesFromDB)
177+
178+
require.NoError(t, storage.clean())
179+
})
116180
}

config/default.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,5 +276,5 @@ AggLayerURL = "http://zkevm-agglayer"
276276
SequencerPrivateKey = {Path = "/pk/sequencer.keystore", Password = "testonly"}
277277
CertificateSendInterval = "1m"
278278
URLRPCL2="http://test-aggoracle-l2:8545"
279-
279+
CheckSettledInterval = "5s"
280280
`

0 commit comments

Comments
 (0)