Skip to content

Commit ff90cf9

Browse files
feat: mechanism to calculate last settled block by not using metadata (#838)
## 🔄 Changes Summary This PR introduces a new interface called `CertificateQuerier` which, for now, has only one function called `GetLastSettledCertificateToBlock` which calculates the last settled block from a previously settled certificate. This will be used on the validator, to calculate what was the last settled block, so we can figure out from which block a new certificate that is being verified is built. It will also be used on startup from scratch, when `aggsender` syncs its state with `agglayer`, so we can save the `ToBlock` of the last settled certificate in the `db`. The motivation behind this PR is to not use the `Metadata` field on the certificate, since it will not be signed by the proposer and validators, hence, it can be faked, but deduce the block range from other certificate data. ## How the last settled block is calculated The last settled block in the `CertificateQuerier` is calculated like this: - First it will check the `NewLocalExitRoot` field on the certificate, if it is not the first `empty` LER, meaning, there were some bridge exits in the certificate, or in certificates before it, it will get the block num on which the `NewLocalExitRoot` hash was added on `L2` by calling the `L2 bridge syncer`. - Secondly, it will call the `agglayer` endpoint to get the latest settled imported bridge exit. Note that this PR added only the endpoint sceleton, since this is not yet implemented on `agglayer`. This endpoint will return the `GlobalIndex` of the last settled imported bridge exit (claim), and we will use that to get the claim from the `L2 bridge syncer` since `GlobalIndex` is unique per claim. - Thirdly, it will ping the `AggchainFEP` rollup contract to get the last settled `L2` block from it (which is gotten from the `aggchain proof` in the `FEP` certificates). If the network is not `FEP` this call will just return 0. - At the end, it will compare the 3 blocks gotten in previous steps, and choose the max value from it. We need all three values because, PP certificates can either contain only bridge exits, or only imported bridge exits, and FEP certificates can be empty. **IMPORTANT NOTE**: This code is not used anywhere. It will be used in subsequent PRs. ## ⚠️ Breaking Changes NA ## 📋 Config Updates NA ## ✅ Testing - 🤖 **Automatic**: aggkit CI
1 parent 0b8cd6a commit ff90cf9

29 files changed

+1403
-72
lines changed

.mockery.yaml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ packages:
99
github.com/agglayer/aggkit/agglayer:
1010
config:
1111
inpackage: true
12-
dir: "{{ .InterfaceDir }}"
13-
outpkg: "{{ .PackageName }}"
12+
dir: "{{ .InterfaceDir }}/mocks"
1413
interfaces:
1514
AgglayerClientInterface:
1615
config:

agglayer/agglayer_client_cache.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,10 @@ func (c *AgglayerClientCache) GetLatestPendingCertificateHeader(ctx context.Cont
125125
networkID uint32) (*agglayertypes.CertificateHeader, error) {
126126
return c.agglayerClient.GetLatestPendingCertificateHeader(ctx, networkID)
127127
}
128+
129+
// GetLatestSettledImportedBridgeExit retrieves the latest settled
130+
// imported bridge exit from the Agglayer client. (no cache)
131+
func (c *AgglayerClientCache) GetLatestSettledImportedBridgeExit(
132+
ctx context.Context) (*agglayertypes.GlobalIndex, error) {
133+
return c.agglayerClient.GetLatestSettledImportedBridgeExit(ctx)
134+
}

agglayer/agglayer_client_cache_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"testing"
77
"time"
88

9+
"github.com/agglayer/aggkit/agglayer/mocks"
910
agglayertypes "github.com/agglayer/aggkit/agglayer/types"
1011
"github.com/ethereum/go-ethereum/common"
1112
"github.com/jellydator/ttlcache/v3"
@@ -40,7 +41,7 @@ func TestGetCertificateHeader(t *testing.T) {
4041
Status: agglayertypes.Settled,
4142
}
4243

43-
mockAgglayerClient := NewAgglayerClientMock(t)
44+
mockAgglayerClient := mocks.NewAgglayerClientMock(t)
4445
certCache := NewCertificateCache(mockAgglayerClient, ttl, capacity)
4546

4647
// Test cache doesn't have the certificate header initially
@@ -88,7 +89,7 @@ func TestExpiration(t *testing.T) {
8889
Status: agglayertypes.Settled,
8990
}
9091

91-
mockAgglayerClient := NewAgglayerClientMock(t)
92+
mockAgglayerClient := mocks.NewAgglayerClientMock(t)
9293
certCache := NewCertificateCache(mockAgglayerClient, ttl, capacity)
9394

9495
mockAgglayerClient.EXPECT().GetCertificateHeader(t.Context(), certificateID).Return(certificateHeader, nil).Times(2)

agglayer/client.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ type AggLayerClientCertificateIDQuerier interface {
3030
type AgglayerClientInterface interface {
3131
SendCertificate(ctx context.Context, certificate *types.Certificate, validatorSignature []byte) (common.Hash, error)
3232
GetCertificateHeader(ctx context.Context, certificateHash common.Hash) (*types.CertificateHeader, error)
33+
GetLatestSettledImportedBridgeExit(ctx context.Context) (*types.GlobalIndex, error)
3334
AggLayerClientGetEpochConfiguration
3435
AggLayerClientRecoveryQuerier
3536
AggLayerClientCertificateIDQuerier

agglayer/grpc/agglayer_grpc_client.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,11 @@ func (a *AgglayerGRPCClient) GetCertificateHeader(
156156
return convertProtoCertificateHeader(response.CertificateHeader), nil
157157
}
158158

159+
func (a *AgglayerGRPCClient) GetLatestSettledImportedBridgeExit(ctx context.Context) (*types.GlobalIndex, error) {
160+
// TODO - implement this method once agglayer supports it
161+
return &types.GlobalIndex{}, nil
162+
}
163+
159164
// ConvertCertToProtoCertificate converts a types.Certificate to a grpc v1nodetypes.Certificate
160165
func ConvertCertToProtoCertificate(
161166
certificate *types.Certificate,

agglayer/mock_agglayer_client.go renamed to agglayer/mocks/mock_agglayer_client.go

Lines changed: 59 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

agglayer/types/types.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,11 @@ type GlobalIndex struct {
471471
LeafIndex uint32 `json:"leaf_index"`
472472
}
473473

474+
// ToBigInt converts the GlobalIndex to a big.Int representation
475+
func (g *GlobalIndex) ToBigInt() *big.Int {
476+
return bridgesync.GenerateGlobalIndex(g.MainnetFlag, g.RollupIndex, g.LeafIndex)
477+
}
478+
474479
func (g *GlobalIndex) Validate() error {
475480
if g == nil {
476481
return errors.New("globalIndex is nil")

aggsender/aggsender_test.go

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"time"
1515

1616
"github.com/agglayer/aggkit/agglayer"
17+
agglayermocks "github.com/agglayer/aggkit/agglayer/mocks"
1718
agglayertypes "github.com/agglayer/aggkit/agglayer/types"
1819
"github.com/agglayer/aggkit/aggsender/config"
1920
"github.com/agglayer/aggkit/aggsender/db"
@@ -72,7 +73,7 @@ func TestConfigString(t *testing.T) {
7273
}
7374

7475
func TestAggSenderStart(t *testing.T) {
75-
aggLayerMock := agglayer.NewAgglayerClientMock(t)
76+
aggLayerMock := agglayermocks.NewAgglayerClientMock(t)
7677
epochNotifierMock := mocks.NewEpochNotifier(t)
7778
bridgeL2SyncerMock := mocks.NewL2BridgeSyncer(t)
7879
rollupQuerierMock := mocks.NewRollupDataQuerier(t)
@@ -204,7 +205,7 @@ func TestSendCertificate_NoClaims(t *testing.T) {
204205
mockStorage := mocks.NewAggSenderStorage(t)
205206
mockL2BridgeQuerier := mocks.NewBridgeQuerier(t)
206207
mockL1Querier := mocks.NewL1InfoTreeDataQuerier(t)
207-
mockAggLayerClient := agglayer.NewAgglayerClientMock(t)
208+
mockAggLayerClient := agglayermocks.NewAgglayerClientMock(t)
208209
mockEpochNotifier := mocks.NewEpochNotifier(t)
209210
mockLERQuerier := mocks.NewLERQuerier(t)
210211
logger := log.WithFields("aggsender-test", "no claims test")
@@ -313,15 +314,15 @@ func TestSendCertificate(t *testing.T) {
313314

314315
testCases := []struct {
315316
name string
316-
mockFn func(*mocks.AggSenderStorage, *mocks.AggsenderFlow, *agglayer.AgglayerClientMock)
317+
mockFn func(*mocks.AggSenderStorage, *mocks.AggsenderFlow, *agglayermocks.AgglayerClientMock)
317318
mockValidatorFn func() *mocks.CertificateValidateAndSigner
318319
expectedError string
319320
}{
320321
{
321322
name: "error getting certificate build params",
322323
mockFn: func(mockStorage *mocks.AggSenderStorage,
323324
mockFlow *mocks.AggsenderFlow,
324-
mockAgglayerClient *agglayer.AgglayerClientMock) {
325+
mockAgglayerClient *agglayermocks.AgglayerClientMock) {
325326
mockFlow.EXPECT().GetCertificateBuildParams(mock.Anything).Return(nil, errors.New("some error")).Once()
326327
},
327328
expectedError: "error getting certificate build params",
@@ -330,15 +331,15 @@ func TestSendCertificate(t *testing.T) {
330331
name: "no new blocks consumed",
331332
mockFn: func(mockStorage *mocks.AggSenderStorage,
332333
mockFlow *mocks.AggsenderFlow,
333-
mockAgglayerClient *agglayer.AgglayerClientMock) {
334+
mockAgglayerClient *agglayermocks.AgglayerClientMock) {
334335
mockFlow.EXPECT().GetCertificateBuildParams(mock.Anything).Return(nil, nil).Once()
335336
},
336337
},
337338
{
338339
name: "error building certificate",
339340
mockFn: func(mockStorage *mocks.AggSenderStorage,
340341
mockFlow *mocks.AggsenderFlow,
341-
mockAgglayerClient *agglayer.AgglayerClientMock) {
342+
mockAgglayerClient *agglayermocks.AgglayerClientMock) {
342343
mockFlow.EXPECT().GetCertificateBuildParams(mock.Anything).Return(&aggsendertypes.CertificateBuildParams{
343344
Bridges: []bridgesync.Bridge{{}},
344345
}, nil).Once()
@@ -350,7 +351,7 @@ func TestSendCertificate(t *testing.T) {
350351
name: "error sending certificate",
351352
mockFn: func(mockStorage *mocks.AggSenderStorage,
352353
mockFlow *mocks.AggsenderFlow,
353-
mockAgglayerClient *agglayer.AgglayerClientMock) {
354+
mockAgglayerClient *agglayermocks.AgglayerClientMock) {
354355
mockFlow.EXPECT().GetCertificateBuildParams(mock.Anything).Return(&aggsendertypes.CertificateBuildParams{
355356
Bridges: []bridgesync.Bridge{{}},
356357
}, nil).Once()
@@ -369,7 +370,7 @@ func TestSendCertificate(t *testing.T) {
369370
name: "error saving certificate to storage",
370371
mockFn: func(mockStorage *mocks.AggSenderStorage,
371372
mockFlow *mocks.AggsenderFlow,
372-
mockAgglayerClient *agglayer.AgglayerClientMock) {
373+
mockAgglayerClient *agglayermocks.AgglayerClientMock) {
373374
mockFlow.EXPECT().GetCertificateBuildParams(mock.Anything).Return(&aggsendertypes.CertificateBuildParams{
374375
Bridges: []bridgesync.Bridge{{}},
375376
}, nil).Once()
@@ -388,7 +389,7 @@ func TestSendCertificate(t *testing.T) {
388389
name: "error getting validator signature",
389390
mockFn: func(mockStorage *mocks.AggSenderStorage,
390391
mockFlow *mocks.AggsenderFlow,
391-
mockAgglayerClient *agglayer.AgglayerClientMock) {
392+
mockAgglayerClient *agglayermocks.AgglayerClientMock) {
392393
mockFlow.EXPECT().GetCertificateBuildParams(mock.Anything).Return(&aggsendertypes.CertificateBuildParams{
393394
Bridges: []bridgesync.Bridge{{}},
394395
}, nil).Once()
@@ -411,7 +412,7 @@ func TestSendCertificate(t *testing.T) {
411412
name: "successful validation and sending of a certificate",
412413
mockFn: func(mockStorage *mocks.AggSenderStorage,
413414
mockFlow *mocks.AggsenderFlow,
414-
mockAgglayerClient *agglayer.AgglayerClientMock) {
415+
mockAgglayerClient *agglayermocks.AgglayerClientMock) {
415416
mockFlow.EXPECT().GetCertificateBuildParams(mock.Anything).Return(&aggsendertypes.CertificateBuildParams{
416417
Bridges: []bridgesync.Bridge{{}},
417418
}, nil).Once()
@@ -434,7 +435,7 @@ func TestSendCertificate(t *testing.T) {
434435
name: "successful sending and saving of a certificate",
435436
mockFn: func(mockStorage *mocks.AggSenderStorage,
436437
mockFlow *mocks.AggsenderFlow,
437-
mockAgglayerClient *agglayer.AgglayerClientMock) {
438+
mockAgglayerClient *agglayermocks.AgglayerClientMock) {
438439
mockFlow.EXPECT().GetCertificateBuildParams(mock.Anything).Return(&aggsendertypes.CertificateBuildParams{
439440
Bridges: []bridgesync.Bridge{{}},
440441
}, nil).Once()
@@ -456,7 +457,7 @@ func TestSendCertificate(t *testing.T) {
456457

457458
mockStorage := mocks.NewAggSenderStorage(t)
458459
mockAggsenderFlow := mocks.NewAggsenderFlow(t)
459-
mockAgglayerClient := agglayer.NewAgglayerClientMock(t)
460+
mockAgglayerClient := agglayermocks.NewAgglayerClientMock(t)
460461
mockEpochNotifier := mocks.NewEpochNotifier(t)
461462
tt.mockFn(mockStorage, mockAggsenderFlow, mockAgglayerClient)
462463

@@ -699,7 +700,7 @@ const (
699700

700701
type aggsenderTestData struct {
701702
ctx context.Context
702-
agglayerClientMock *agglayer.AgglayerClientMock
703+
agglayerClientMock *agglayermocks.AgglayerClientMock
703704
l1InfoQuerier *mocks.L1InfoTreeDataQuerier
704705
l2BridgeQuerier *mocks.BridgeQuerier
705706
storageMock *mocks.AggSenderStorage
@@ -745,7 +746,7 @@ func NewClaimData(t *testing.T, num int, blockNum []uint64) []bridgesync.Claim {
745746
func newAggsenderTestData(t *testing.T, creationFlags testDataFlags) *aggsenderTestData {
746747
t.Helper()
747748
l2BridgeQuerier := mocks.NewBridgeQuerier(t)
748-
agglayerClientMock := agglayer.NewAgglayerClientMock(t)
749+
agglayerClientMock := agglayermocks.NewAgglayerClientMock(t)
749750
l1InfoTreeQuerierMock := mocks.NewL1InfoTreeDataQuerier(t)
750751
lerQuerier := mocks.NewLERQuerier(t)
751752
epochNotifierMock := mocks.NewEpochNotifier(t)

aggsender/epoch_notifier_per_block_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import (
55
"fmt"
66
"testing"
77

8-
"github.com/agglayer/aggkit/agglayer"
8+
agglayermocks "github.com/agglayer/aggkit/agglayer/mocks"
99
agglayertypes "github.com/agglayer/aggkit/agglayer/types"
1010
"github.com/agglayer/aggkit/aggsender/mocks"
1111
"github.com/agglayer/aggkit/aggsender/types"
@@ -118,7 +118,7 @@ func TestNewConfigEpochNotifierPerBlock(t *testing.T) {
118118
ctx := context.Background()
119119
_, err := NewConfigEpochNotifierPerBlock(ctx, nil, 1)
120120
require.Error(t, err)
121-
aggLayerMock := agglayer.NewAgglayerClientMock(t)
121+
aggLayerMock := agglayermocks.NewAgglayerClientMock(t)
122122
aggLayerMock.On("GetEpochConfiguration", mock.Anything).Return(nil, fmt.Errorf("error")).Once()
123123
_, err = NewConfigEpochNotifierPerBlock(ctx, aggLayerMock, 1)
124124
require.Error(t, err)

aggsender/flows/factory.go

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,6 @@ import (
1919
)
2020

2121
var (
22-
// funcGetL2StartBlock is a intermediate func that allow to override this call in UT
23-
funcGetL2StartBlock = getL2StartBlock
24-
2522
// l2GERReaderFactory is a factory function to create L2 GER reader
2623
l2GERReaderFactory = l2gersync.NewL2EVMGERReader
2724
)
@@ -87,10 +84,6 @@ func NewFlow(
8784

8885
l1InfoTreeQuerier := query.NewL1InfoTreeDataQuerier(l1Client, l1InfoTreeSyncer)
8986

90-
startL2Block, err := funcGetL2StartBlock(cfg.SovereignRollupAddr, l1Client)
91-
if err != nil {
92-
return nil, fmt.Errorf("aggchainProverFlow - error reading sovereign rollup: %w", err)
93-
}
9487
optimisticSigner, optimisticModeQuerier, err := optimistic.NewOptimistic(
9588
ctx, logger, l1Client, cfg.OptimisticModeConfig)
9689
if err != nil {
@@ -103,10 +96,15 @@ func NewFlow(
10396
return nil, fmt.Errorf("aggchainProverFlow - error creating LER data querier: %w", err)
10497
}
10598

99+
aggchainFEPQuerier, err := query.NewAggchainFEPQuerier(logger, cfg.SovereignRollupAddr, l1Client)
100+
if err != nil {
101+
return nil, fmt.Errorf("aggchainProverFlow - error creating aggchain FEP querier: %w", err)
102+
}
103+
106104
l2BridgeQuerier := query.NewBridgeDataQuerier(logger, l2Syncer, cfg.DelayBetweenRetries.Duration)
107105
baseFlow := NewBaseFlow(
108106
logger, l2BridgeQuerier, storage, l1InfoTreeQuerier, lerQuerier,
109-
NewBaseFlowConfig(cfg.MaxCertSize, startL2Block, cfg.RequireNoFEPBlockGap),
107+
NewBaseFlowConfig(cfg.MaxCertSize, aggchainFEPQuerier.StartL2Block(), cfg.RequireNoFEPBlockGap),
110108
)
111109

112110
l2GERReader, err := l2GERReaderFactory(cfg.GlobalExitRootL2Addr, l2Client, l1InfoTreeSyncer)

0 commit comments

Comments
 (0)