Skip to content

Commit

Permalink
feat: limit cert by estimated size (#217)
Browse files Browse the repository at this point in the history
This set a limit on certificate size that can be set on config file (the size of certificate it's an estimation)
There are a special situation that is going to send a certificate bigger than maximium: 
- [ initial_block, end_block ] -> size exceed
- [ initial_block, (end_block -1) ] -> no bridges, so we have to send previous one even that exceed the maximum size

## Config
MaxCertSize: max size in bytes of certificate. Default 8Mb. Currently the maximum on Agglayer is 10Mb
```
[AggSender]
MaxCertSize = 8388608
```
  • Loading branch information
joanestebanr authored Dec 3, 2024
1 parent f792176 commit 158cfd2
Show file tree
Hide file tree
Showing 5 changed files with 262 additions and 30 deletions.
65 changes: 50 additions & 15 deletions aggsender/aggsender.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,10 +191,21 @@ func (a *AggSender) sendCertificate(ctx context.Context) (*agglayer.SignedCertif
if err != nil {
return nil, fmt.Errorf("error getting claims: %w", err)
}
certificateParams := &types.CertificateBuildParams{
FromBlock: fromBlock,
ToBlock: toBlock,
Bridges: bridges,
Claims: claims,
}

a.log.Infof("building certificate for block: %d to block: %d", fromBlock, toBlock)
certificateParams, err = a.limitCertSize(certificateParams)
if err != nil {
return nil, fmt.Errorf("error limitCertSize: %w", err)
}
a.log.Infof("building certificate for %s estimatedSize=%d",
certificateParams.String(), certificateParams.EstimatedSize())

certificate, err := a.buildCertificate(ctx, bridges, claims, lastSentCertificateInfo, toBlock)
certificate, err := a.buildCertificate(ctx, certificateParams, lastSentCertificateInfo)
if err != nil {
return nil, fmt.Errorf("error building certificate: %w", err)
}
Expand Down Expand Up @@ -265,6 +276,36 @@ func (a *AggSender) saveCertificateToStorage(ctx context.Context, cert types.Cer
return nil
}

func (a *AggSender) limitCertSize(fullCert *types.CertificateBuildParams) (*types.CertificateBuildParams, error) {
currentCert := fullCert
var previousCert *types.CertificateBuildParams
var err error
for {
if currentCert.NumberOfBridges() == 0 {
// We can't reduce more the certificate, so this is the minium size
a.log.Warnf("We reach the minium size of bridge.Certificate size: %d >max size: %d",
previousCert.EstimatedSize(), a.cfg.MaxCertSize)
return previousCert, nil
}

if a.cfg.MaxCertSize == 0 || currentCert.EstimatedSize() < a.cfg.MaxCertSize {
return currentCert, nil
}

// Minimum size of the certificate
if currentCert.NumberOfBlocks() <= 1 {
a.log.Warnf("reach the minium num blocks [%d to %d].Certificate size: %d >max size: %d",
currentCert.FromBlock, currentCert.ToBlock, currentCert.EstimatedSize(), a.cfg.MaxCertSize)
return currentCert, nil
}
previousCert = currentCert
currentCert, err = currentCert.Range(currentCert.FromBlock, currentCert.ToBlock-1)
if err != nil {
return nil, fmt.Errorf("error reducing certificate: %w", err)
}
}
}

// saveCertificate saves the certificate to a tmp file
func (a *AggSender) saveCertificateToFile(signedCertificate *agglayer.SignedCertificate) {
if signedCertificate == nil || a.cfg.SaveCertificatesToFilesPath == "" {
Expand Down Expand Up @@ -329,25 +370,19 @@ func (a *AggSender) getNextHeightAndPreviousLER(

// buildCertificate builds a certificate from the bridge events
func (a *AggSender) buildCertificate(ctx context.Context,
bridges []bridgesync.Bridge,
claims []bridgesync.Claim,
lastSentCertificateInfo *types.CertificateInfo,
toBlock uint64) (*agglayer.Certificate, error) {
if len(bridges) == 0 && len(claims) == 0 {
certParams *types.CertificateBuildParams,
lastSentCertificateInfo *types.CertificateInfo) (*agglayer.Certificate, error) {
if certParams.IsEmpty() {
return nil, errNoBridgesAndClaims
}

bridgeExits := a.getBridgeExits(bridges)
importedBridgeExits, err := a.getImportedBridgeExits(ctx, claims)
bridgeExits := a.getBridgeExits(certParams.Bridges)
importedBridgeExits, err := a.getImportedBridgeExits(ctx, certParams.Claims)
if err != nil {
return nil, fmt.Errorf("error getting imported bridge exits: %w", err)
}

var depositCount uint32

if len(bridges) > 0 {
depositCount = bridges[len(bridges)-1].DepositCount
}
depositCount := certParams.MaxDepoitCount()

exitRoot, err := a.l2Syncer.GetExitRootByIndex(ctx, depositCount)
if err != nil {
Expand All @@ -366,7 +401,7 @@ func (a *AggSender) buildCertificate(ctx context.Context,
BridgeExits: bridgeExits,
ImportedBridgeExits: importedBridgeExits,
Height: height,
Metadata: createCertificateMetadata(toBlock),
Metadata: createCertificateMetadata(certParams.ToBlock),
}, nil
}

Expand Down
121 changes: 106 additions & 15 deletions aggsender/aggsender_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -787,7 +787,13 @@ func TestBuildCertificate(t *testing.T) {
l1infoTreeSyncer: mockL1InfoTreeSyncer,
log: log.WithFields("test", "unittest"),
}
cert, err := aggSender.buildCertificate(context.Background(), tt.bridges, tt.claims, &tt.lastSentCertificateInfo, tt.toBlock)

certParam := &aggsendertypes.CertificateBuildParams{
ToBlock: tt.toBlock,
Bridges: tt.bridges,
Claims: tt.claims,
}
cert, err := aggSender.buildCertificate(context.Background(), certParam, &tt.lastSentCertificateInfo)

if tt.expectedError {
require.Error(t, err)
Expand Down Expand Up @@ -993,7 +999,7 @@ func TestSendCertificate(t *testing.T) {
mockL2Syncer.On("GetLastProcessedBlock", mock.Anything).Return(cfg.lastL2BlockProcessed...).Once()

if cfg.getBridges != nil {
mockL2Syncer.On("GetBridgesPublished", mock.Anything, mock.Anything, mock.Anything).Return(cfg.getBridges...).Once()
mockL2Syncer.On("GetBridgesPublished", mock.Anything, mock.Anything, mock.Anything).Return(cfg.getBridges...)
}

if cfg.getClaims != nil {
Expand Down Expand Up @@ -1643,8 +1649,6 @@ func TestGetNextHeightAndPreviousLER(t *testing.T) {
}

func TestSendCertificate_NoClaims(t *testing.T) {
t.Parallel()

privateKey, err := crypto.GenerateKey()
require.NoError(t, err)

Expand Down Expand Up @@ -1690,8 +1694,8 @@ func TestSendCertificate_NoClaims(t *testing.T) {
Metadata: []byte("metadata"),
DepositCount: 1,
},
}, nil).Once()
mockL2Syncer.On("GetClaims", mock.Anything, uint64(11), uint64(50)).Return([]bridgesync.Claim{}, nil).Once()
}, nil)
mockL2Syncer.On("GetClaims", mock.Anything, uint64(11), uint64(50)).Return([]bridgesync.Claim{}, nil)
mockL2Syncer.On("GetExitRootByIndex", mock.Anything, uint32(1)).Return(treeTypes.Root{}, nil).Once()
mockL2Syncer.On("OriginNetwork").Return(uint32(1), nil).Once()
mockAggLayerClient.On("SendCertificate", mock.Anything).Return(common.Hash{}, nil).Once()
Expand Down Expand Up @@ -1795,7 +1799,7 @@ func TestCheckLastCertificateFromAgglayer_Case2NoCertLocalCertRemote(t *testing.
testData := newAggsenderTestData(t, testDataFlagNone)
testData.l2syncerMock.EXPECT().OriginNetwork().Return(networkIDTest).Once()
testData.agglayerClientMock.EXPECT().GetLatestKnownCertificateHeader(networkIDTest).
Return(certInfoToCertHeader(&testData.testCerts[0], networkIDTest), nil).Once()
Return(certInfoToCertHeader(t, &testData.testCerts[0], networkIDTest), nil).Once()

err := testData.sut.checkLastCertificateFromAgglayer(testData.ctx)

Expand All @@ -1811,7 +1815,7 @@ func TestCheckLastCertificateFromAgglayer_Case2NoCertLocalCertRemoteErrorStorage
testData := newAggsenderTestData(t, testDataFlagMockStorage)
testData.l2syncerMock.EXPECT().OriginNetwork().Return(networkIDTest).Once()
testData.agglayerClientMock.EXPECT().GetLatestKnownCertificateHeader(networkIDTest).
Return(certInfoToCertHeader(&testData.testCerts[0], networkIDTest), nil).Once()
Return(certInfoToCertHeader(t, &testData.testCerts[0], networkIDTest), nil).Once()
testData.storageMock.EXPECT().GetLastSentCertificate().Return(nil, nil)
testData.storageMock.EXPECT().SaveLastSentCertificate(mock.Anything, mock.Anything).Return(errTest).Once()
err := testData.sut.checkLastCertificateFromAgglayer(testData.ctx)
Expand All @@ -1837,7 +1841,7 @@ func TestCheckLastCertificateFromAgglayer_Case3_1LessHeight(t *testing.T) {
testData := newAggsenderTestData(t, testDataFlagMockStorage)
testData.l2syncerMock.EXPECT().OriginNetwork().Return(networkIDTest).Once()
testData.agglayerClientMock.EXPECT().GetLatestKnownCertificateHeader(networkIDTest).
Return(certInfoToCertHeader(&testData.testCerts[0], networkIDTest), nil).Once()
Return(certInfoToCertHeader(t, &testData.testCerts[0], networkIDTest), nil).Once()
testData.storageMock.EXPECT().GetLastSentCertificate().Return(&testData.testCerts[1], nil)

err := testData.sut.checkLastCertificateFromAgglayer(testData.ctx)
Expand All @@ -1850,7 +1854,7 @@ func TestCheckLastCertificateFromAgglayer_Case3_2Mismatch(t *testing.T) {
testData := newAggsenderTestData(t, testDataFlagMockStorage)
testData.l2syncerMock.EXPECT().OriginNetwork().Return(networkIDTest).Once()
testData.agglayerClientMock.EXPECT().GetLatestKnownCertificateHeader(networkIDTest).
Return(certInfoToCertHeader(&testData.testCerts[1], networkIDTest), nil).Once()
Return(certInfoToCertHeader(t, &testData.testCerts[1], networkIDTest), nil).Once()
testData.storageMock.EXPECT().GetLastSentCertificate().Return(&testData.testCerts[0], nil)
testData.storageMock.EXPECT().SaveLastSentCertificate(mock.Anything, mock.Anything).Return(nil).Once()

Expand All @@ -1864,7 +1868,7 @@ func TestCheckLastCertificateFromAgglayer_Case4Mismatch(t *testing.T) {
testData := newAggsenderTestData(t, testDataFlagMockStorage)
testData.l2syncerMock.EXPECT().OriginNetwork().Return(networkIDTest).Once()
testData.agglayerClientMock.EXPECT().GetLatestKnownCertificateHeader(networkIDTest).
Return(certInfoToCertHeader(&testData.testCerts[0], networkIDTest), nil).Once()
Return(certInfoToCertHeader(t, &testData.testCerts[0], networkIDTest), nil).Once()
testData.storageMock.EXPECT().GetLastSentCertificate().Return(&testData.testCerts[1], nil)

err := testData.sut.checkLastCertificateFromAgglayer(testData.ctx)
Expand All @@ -1877,7 +1881,7 @@ func TestCheckLastCertificateFromAgglayer_Case5SameStatus(t *testing.T) {
testData := newAggsenderTestData(t, testDataFlagMockStorage)
testData.l2syncerMock.EXPECT().OriginNetwork().Return(networkIDTest).Once()
testData.agglayerClientMock.EXPECT().GetLatestKnownCertificateHeader(networkIDTest).
Return(certInfoToCertHeader(&testData.testCerts[0], networkIDTest), nil).Once()
Return(certInfoToCertHeader(t, &testData.testCerts[0], networkIDTest), nil).Once()
testData.storageMock.EXPECT().GetLastSentCertificate().Return(&testData.testCerts[0], nil)

err := testData.sut.checkLastCertificateFromAgglayer(testData.ctx)
Expand All @@ -1889,7 +1893,7 @@ func TestCheckLastCertificateFromAgglayer_Case5SameStatus(t *testing.T) {
func TestCheckLastCertificateFromAgglayer_Case5UpdateStatus(t *testing.T) {
testData := newAggsenderTestData(t, testDataFlagMockStorage)
testData.l2syncerMock.EXPECT().OriginNetwork().Return(networkIDTest).Once()
aggLayerCert := certInfoToCertHeader(&testData.testCerts[0], networkIDTest)
aggLayerCert := certInfoToCertHeader(t, &testData.testCerts[0], networkIDTest)
aggLayerCert.Status = agglayer.Settled
testData.agglayerClientMock.EXPECT().GetLatestKnownCertificateHeader(networkIDTest).
Return(aggLayerCert, nil).Once()
Expand All @@ -1905,7 +1909,7 @@ func TestCheckLastCertificateFromAgglayer_Case5UpdateStatus(t *testing.T) {
func TestCheckLastCertificateFromAgglayer_Case4ErrorUpdateStatus(t *testing.T) {
testData := newAggsenderTestData(t, testDataFlagMockStorage)
testData.l2syncerMock.EXPECT().OriginNetwork().Return(networkIDTest).Once()
aggLayerCert := certInfoToCertHeader(&testData.testCerts[0], networkIDTest)
aggLayerCert := certInfoToCertHeader(t, &testData.testCerts[0], networkIDTest)
aggLayerCert.Status = agglayer.Settled
testData.agglayerClientMock.EXPECT().GetLatestKnownCertificateHeader(networkIDTest).
Return(aggLayerCert, nil).Once()
Expand All @@ -1917,6 +1921,57 @@ func TestCheckLastCertificateFromAgglayer_Case4ErrorUpdateStatus(t *testing.T) {
require.Error(t, err)
}

func TestLimitSize_FirstOneFit(t *testing.T) {
testData := newAggsenderTestData(t, testDataFlagMockStorage)
certParams := &aggsendertypes.CertificateBuildParams{
FromBlock: uint64(1),
ToBlock: uint64(20),
Bridges: NewBridgesData(t, 1, []uint64{1}),
}
newCert, err := testData.sut.limitCertSize(certParams)
require.NoError(t, err)
require.Equal(t, certParams, newCert)
}

func TestLimitSize_FirstMinusOneFit(t *testing.T) {
testData := newAggsenderTestData(t, testDataFlagMockStorage)
testData.sut.cfg.MaxCertSize = (aggsendertypes.EstimatedSizeBridgeExit * 3) + 1
certParams := &aggsendertypes.CertificateBuildParams{
FromBlock: uint64(1),
ToBlock: uint64(20),
Bridges: NewBridgesData(t, 0, []uint64{19, 19, 19, 20}),
}
newCert, err := testData.sut.limitCertSize(certParams)
require.NoError(t, err)
require.Equal(t, uint64(19), newCert.ToBlock)
}

func TestLimitSize_NoWayToFitInMaxSize(t *testing.T) {
testData := newAggsenderTestData(t, testDataFlagMockStorage)
testData.sut.cfg.MaxCertSize = (aggsendertypes.EstimatedSizeBridgeExit * 2) + 1
certParams := &aggsendertypes.CertificateBuildParams{
FromBlock: uint64(1),
ToBlock: uint64(20),
Bridges: NewBridgesData(t, 0, []uint64{19, 19, 19, 20}),
}
newCert, err := testData.sut.limitCertSize(certParams)
require.NoError(t, err)
require.Equal(t, uint64(19), newCert.ToBlock)
}

func TestLimitSize_MinNumBlocks(t *testing.T) {
testData := newAggsenderTestData(t, testDataFlagMockStorage)
testData.sut.cfg.MaxCertSize = (aggsendertypes.EstimatedSizeBridgeExit * 2) + 1
certParams := &aggsendertypes.CertificateBuildParams{
FromBlock: uint64(1),
ToBlock: uint64(2),
Bridges: NewBridgesData(t, 0, []uint64{1, 1, 1, 2, 2, 2}),
}
newCert, err := testData.sut.limitCertSize(certParams)
require.NoError(t, err)
require.Equal(t, uint64(1), newCert.ToBlock)
}

type testDataFlags = int

const (
Expand All @@ -1934,7 +1989,40 @@ type aggsenderTestData struct {
testCerts []aggsendertypes.CertificateInfo
}

func certInfoToCertHeader(certInfo *aggsendertypes.CertificateInfo, networkID uint32) *agglayer.CertificateHeader {
func NewBridgesData(t *testing.T, num int, blockNum []uint64) []bridgesync.Bridge {
t.Helper()
if num == 0 {
num = len(blockNum)
}
res := make([]bridgesync.Bridge, 0)
for i := 0; i < num; i++ {
res = append(res, bridgesync.Bridge{
BlockNum: blockNum[i%len(blockNum)],
BlockPos: 0,
LeafType: agglayer.LeafTypeAsset.Uint8(),
OriginNetwork: 1,
})
}
return res
}

func NewClaimData(t *testing.T, num int, blockNum []uint64) []bridgesync.Claim {
t.Helper()
if num == 0 {
num = len(blockNum)
}
res := make([]bridgesync.Claim, 0)
for i := 0; i < num; i++ {
res = append(res, bridgesync.Claim{
BlockNum: blockNum[i%len(blockNum)],
BlockPos: 0,
})
}
return res
}

func certInfoToCertHeader(t *testing.T, certInfo *aggsendertypes.CertificateInfo, networkID uint32) *agglayer.CertificateHeader {
t.Helper()
if certInfo == nil {
return nil
}
Expand Down Expand Up @@ -1979,6 +2067,9 @@ func newAggsenderTestData(t *testing.T, creationFlags testDataFlags) *aggsenderT
aggLayerClient: agglayerClientMock,
storage: storage,
l1infoTreeSyncer: l1InfoTreeSyncerMock,
cfg: Config{
MaxCertSize: 1024 * 1024,
},
}
testCerts := []aggsendertypes.CertificateInfo{
{
Expand Down
3 changes: 3 additions & 0 deletions aggsender/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ type Config struct {
DelayBeetweenRetries types.Duration `mapstructure:"DelayBeetweenRetries"`
// KeepCertificatesHistory is a flag to keep the certificates history on storage
KeepCertificatesHistory bool `mapstructure:"KeepCertificatesHistory"`
// MaxCertSize is the maximum size of the certificate (the emitted certificate can be bigger that this size)
// 0 is infinite
MaxCertSize uint `mapstructure:"MaxCertSize"`
}

// String returns a string representation of the Config
Expand Down
Loading

0 comments on commit 158cfd2

Please sign in to comment.