Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
be36d4a
poc change block finality to safe for l1 info tree syncer, because of…
Stefan-Ethernal Oct 23, 2025
9be5018
implement GetLatestL1InfoGER function and use it in aggoracle
Stefan-Ethernal Oct 24, 2025
da06086
fix tests
Stefan-Ethernal Oct 28, 2025
f8e4a15
test GetLatestL1InfoGER
Stefan-Ethernal Oct 29, 2025
14b4401
simplify keccak256 hash calculations by relying on go-ethereum helper…
Stefan-Ethernal Oct 29, 2025
908dc60
increase coverage
Stefan-Ethernal Oct 29, 2025
c4da964
Update l1infotreesync/l1infotreesync.go
Stefan-Ethernal Oct 29, 2025
16ef395
Update aggoracle/oracle.go
Stefan-Ethernal Oct 29, 2025
a2e490b
formatting
Stefan-Ethernal Oct 29, 2025
c19fe13
re-introduce block finality config param to l1 info tree syncer
Stefan-Ethernal Oct 30, 2025
8f05911
remove unused URLRPCL1 config params from aggoracle and l1infotreesync
Stefan-Ethernal Oct 30, 2025
56f66c2
update comment
Stefan-Ethernal Oct 30, 2025
30af17d
re-introduce reorg detector for l1 info tree sync
Stefan-Ethernal Oct 30, 2025
256b6d9
pass TestE2E
Stefan-Ethernal Oct 31, 2025
b5ce304
pass the TestStressAndReorg
Stefan-Ethernal Oct 31, 2025
dbc9e47
pass TestWithReorgs
Stefan-Ethernal Oct 31, 2025
361e57e
increase coverage
Stefan-Ethernal Nov 3, 2025
7485d4c
address comments
Stefan-Ethernal Nov 3, 2025
e1cd884
Merge branch 'develop' into poc/reduce-block-finality-for-aggoracle
Stefan-Ethernal Nov 3, 2025
9f03f0f
introduce merkle tree abstractions
Stefan-Ethernal Nov 3, 2025
7a3bc5a
use introduced abstractions in L1 info tree syncer
Stefan-Ethernal Nov 3, 2025
c09c1d9
increase coverage in l1 info tree sync package
Stefan-Ethernal Nov 3, 2025
6bbe2a6
test TreeGetRootByHash
Stefan-Ethernal Nov 3, 2025
6f3866c
format mockery file
Stefan-Ethernal Nov 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion aggoracle/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ var (

type Config struct {
TargetChainType TargetChainType `mapstructure:"TargetChainType"`
URLRPCL1 string `mapstructure:"URLRPCL1"`
WaitPeriodNextGER types.Duration `mapstructure:"WaitPeriodNextGER"`
EVMSender chaingersender.EVMConfig `mapstructure:"EVMSender"`
EnableAggOracleCommittee bool `mapstructure:"EnableAggOracleCommittee"`
Expand Down
2 changes: 1 addition & 1 deletion aggoracle/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func TestEVM_GERInjection(t *testing.T) {
cfg.AggOracleCommitteeCfg.EnableAggOracleCommittee = tt.enableAggOracleCommittee
l1, l2 := helpers.NewSimulatedEVMEnvironment(t, cfg)

for i := 0; i < 10; i++ {
for i := range 10 {
rootHash := common.HexToHash(strconv.Itoa(i))
_, err := l1.GERContract.UpdateExitRoot(l1.Auth, rootHash)
require.NoError(t, err)
Expand Down
8 changes: 3 additions & 5 deletions aggoracle/oracle.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (

// L1InfoTreeSyncer is an interface that defines the methods required to interact with the L1 info tree syncer
type L1InfoTreeSyncer interface {
GetLatestL1InfoLeaf(ctx context.Context) (*l1infotreesync.L1InfoTreeLeaf, error)
GetLatestL1InfoGER(ctx context.Context) (common.Hash, error)
}

// ChainSender is an interface that defines the methods required to send Global Exit Roots (GERs) to the chain
Expand Down Expand Up @@ -71,14 +71,12 @@ func (a *AggOracle) Start(ctx context.Context) {
func (a *AggOracle) processLatestGER(ctx context.Context) error {
a.logger.Debugf("checking for new GERs...")
// Fetch the latest GER
latestL1InfoLeaf, err := a.l1Info.GetLatestL1InfoLeaf(ctx)
latestGER, err := a.l1Info.GetLatestL1InfoGER(ctx)
if err != nil {
return err
}

a.logger.Debugf("latest l1 info leaf retrieved: %s", latestL1InfoLeaf.String())

latestGER := latestL1InfoLeaf.GlobalExitRoot
a.logger.Debugf("latest GER retrieved: %s", latestGER.String())

go func() {
err := a.chainSender.ProcessGER(ctx, latestGER)
Expand Down
23 changes: 13 additions & 10 deletions cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import (
"github.com/agglayer/aggkit/pprof"
"github.com/agglayer/aggkit/prometheus"
"github.com/agglayer/aggkit/reorgdetector"
aggkitsync "github.com/agglayer/aggkit/sync"
aggkittypes "github.com/agglayer/aggkit/types"
"github.com/ethereum/go-ethereum/common"
"github.com/prometheus/client_golang/prometheus/promhttp"
Expand Down Expand Up @@ -107,7 +108,7 @@ func start(cliCtx *cli.Context) error {
// Create WaitGroup for backfill goroutines synchronization
var backfillWg sync.WaitGroup
var rpcServices []jRPC.Service
l1InfoTreeSync := runL1InfoTreeSyncerIfNeeded(ctx, components, *cfg, l1Client)
l1InfoTreeSync := runL1InfoTreeSyncerIfNeeded(ctx, components, *cfg, reorgDetectorL1, l1Client)
if l1InfoTreeSync != nil {
rpcServices = append(rpcServices, l1InfoTreeSync.GetRPCServices()...)
}
Expand Down Expand Up @@ -374,14 +375,14 @@ func createAggSender(
}

func createAggoracle(
ethermanClient *etherman.RollupDataQuerier,
rollupDataQuerier *etherman.RollupDataQuerier,
cfg config.Config,
l1Client,
l1Client aggkittypes.BaseEthereumClienter,
l2Client aggkittypes.BaseEthereumClienter,
l1InfoTreeSyncer *l1infotreesync.L1InfoTreeSync,
l1InfoTreeSyncer aggoracle.L1InfoTreeSyncer,
) *aggoracle.AggOracle {
logger := log.WithFields("module", aggkitcommon.AGGORACLE)
l2ChainID, err := ethermanClient.GetRollupChainID()
l2ChainID, err := rollupDataQuerier.GetRollupChainID()
if err != nil {
logger.Errorf("Failed to retrieve L2ChainID: %v", err)
}
Expand Down Expand Up @@ -515,6 +516,7 @@ func runL1InfoTreeSyncerIfNeeded(
ctx context.Context,
components []string,
cfg config.Config,
reorgDetectorL1 aggkitsync.ReorgDetector,
l1Client aggkittypes.BaseEthereumClienter,
) *l1infotreesync.L1InfoTreeSync {
if !isNeeded([]string{
Expand All @@ -526,10 +528,9 @@ func runL1InfoTreeSyncerIfNeeded(
l1InfoTreeSync, err := l1infotreesync.New(
ctx,
cfg.L1InfoTreeSync,
aggkittypes.FinalizedBlock,
l1Client,
reorgDetectorL1,
l1infotreesync.FlagNone,
aggkittypes.FinalizedBlock,
)
if err != nil {
log.Fatal(err)
Expand Down Expand Up @@ -595,12 +596,14 @@ func runReorgDetectorL1IfNeeded(
cfg *reorgdetector.Config,
) (*reorgdetector.ReorgDetector, chan error) {
if !isNeeded([]string{
aggkitcommon.BRIDGE, aggkitcommon.L1BRIDGESYNC},
aggkitcommon.AGGORACLE, aggkitcommon.AGGSENDER, aggkitcommon.AGGSENDERVALIDATOR,
aggkitcommon.BRIDGE, aggkitcommon.L1BRIDGESYNC, aggkitcommon.L1INFOTREESYNC,
aggkitcommon.L2GERSYNC, aggkitcommon.AGGCHAINPROOFGEN},
components) {
return nil, nil
}
rd := newReorgDetector(cfg, l1Client, reorgdetector.L1)

rd := newReorgDetector(cfg, l1Client, reorgdetector.L1)
errChan := make(chan error)
go func() {
if err := rd.Start(ctx); err != nil {
Expand Down Expand Up @@ -674,7 +677,7 @@ func runBridgeSyncL1IfNeeded(
ctx context.Context,
components []string,
cfg bridgesync.Config,
reorgDetectorL1 *reorgdetector.ReorgDetector,
reorgDetectorL1 bridgesync.ReorgDetector,
l1Client aggkittypes.EthClienter,
rollupID uint32,
wg *sync.WaitGroup,
Expand Down
5 changes: 0 additions & 5 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ const (
delayBetweenRetriesHint = "AggSender.DelayBeetweenRetries is deprecated, " +
"use AggSender.DelayBetweenRetries instead"
aggOracleBlockFinalityDeprecated = "AggOracle.BlockFinality is deprecated, remove it from configuration"
l1InfoTreeSyncBlockFinalityDeprecated = "L1InfoTreeSync.BlockFinality is deprecated, remove it from configuration"
lastGERSyncDeprecatedHint = "LastGERSync is deprecated, use L2GERSync instead"
lastGERSyncSyncModeDeprecatedHint = "LastGERSync.SyncMode is deprecated, remove it from configuration"
l1NetworkConfigURLDeprecatedHint = "L1NetworkConfig.URL is deprecated, use L1NetworkConfig.RPC.URL instead"
Expand Down Expand Up @@ -191,10 +190,6 @@ var (
FieldNamePattern: "AggOracle.BlockFinality",
Reason: aggOracleBlockFinalityDeprecated,
},
{
FieldNamePattern: "L1InfoTreeSync.BlockFinality",
Reason: l1InfoTreeSyncBlockFinalityDeprecated,
},
{
FieldNamePattern: "LastGERSync.SyncMode",
Reason: lastGERSyncSyncModeDeprecatedHint,
Expand Down
4 changes: 0 additions & 4 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,6 @@ func TestLoadConfigWithDeprecatedFields(t *testing.T) {
PolAddr="{{L1Config.polTokenAddress}}"
ZkEVMAddr="{{L1Config.polygonZkEVMAddress}}"

[L1InfoTreeSync]
BlockFinality = "LatestBlock"

[Etherman]
URL = "{{L1URL}}"
[Etherman.EthermanConfig]
Expand Down Expand Up @@ -198,7 +195,6 @@ func TestLoadConfigWithDeprecatedFields(t *testing.T) {
require.ErrorContains(t, err, l1NetworkConfigUseRollupAddrHint)
require.ErrorContains(t, err, delayBetweenRetriesHint)
require.ErrorContains(t, err, aggOracleBlockFinalityDeprecated)
require.ErrorContains(t, err, l1InfoTreeSyncBlockFinalityDeprecated)
require.ErrorContains(t, err, lastGERSyncDeprecatedHint)
require.ErrorContains(t, err, lastGERSyncSyncModeDeprecatedHint)
require.ErrorContains(t, err, l1NetworkConfigURLDeprecatedHint)
Expand Down
3 changes: 1 addition & 2 deletions config/default.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,8 @@ FinalizedBlock = "LatestBlock"
DBPath = "{{PathRWData}}/L1InfoTreeSync.sqlite"
GlobalExitRootAddr = "{{L1NetworkConfig.GlobalExitRootManagerAddr}}"
RollupManagerAddr = "{{L1NetworkConfig.RollupManagerAddr}}"
BlockFinality = "SafeBlock"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are we sure that we want Safe as default? the situation of changing to Safe is for always or just an specific use case?

Copy link
Contributor Author

@Stefan-Ethernal Stefan-Ethernal Nov 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original requirement was to make the safe, in order to make the aggoracle runs faster (and consequentially be able to inject GERs at a higher frequency). Also I understand this is just an intermediary approach until syncers and reorg detector is refactored. Note that, in the near future, we are aiming to have GERs injected on every 6th L1 block, which is considered as good enough (and thus probably unlikely to be reorged). As you can see on the Etherscan, the reorgs are happening, but are often pretty shallow (most often the fork is only 1 block deep)

As it is highly unlikely that the safe block gets reorged, I think it makes sense to run on this BlockFinality by default. If for any reason we experience some issues, we can always override it through a config.

IMO it would be an overkill at this point to refactor this, and create a separate configuration for the aggoracle on the Kurtosis CDK and what not.

cc @arnaubennassar

Copy link
Contributor

@vcastellm vcastellm Nov 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm in for setting this as Default, the "highly unlikely" in the safe definition is quite high.

No, safe blocks have not been reorged in Ethereum. A "safe" block is one that has received attestations from two-thirds of the validator set, making it highly unlikely to be reorganized under normal network conditions. Reorganizations of "safe" blocks would require a catastrophic attack on the network, as it is understood to be crypto-economically secure.

SyncBlockChunkSize = 100
URLRPCL1 = "{{L1URL}}"
WaitForNewBlocksPeriod = "100ms"
InitialBlock = {{genesisBlockNumber}}
RetryAfterErrorPeriod = "1s"
Expand All @@ -103,7 +103,6 @@ RequireStorageContentCompatibility = {{RequireStorageContentCompatibility}}

[AggOracle]
TargetChainType = "EVM"
URLRPCL1 = "{{L1URL}}"
WaitPeriodNextGER = "10s"
EnableAggOracleCommittee = false
[AggOracle.EVMSender]
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ require (
github.com/urfave/cli/v2 v2.27.7
github.com/valyala/fasttemplate v1.2.2
go.uber.org/zap v1.27.0
golang.org/x/crypto v0.43.0
golang.org/x/net v0.46.0
golang.org/x/sync v0.17.0
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b
Expand Down Expand Up @@ -210,6 +209,7 @@ require (
go.yaml.in/yaml/v2 v2.4.2 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/arch v0.20.0 // indirect
golang.org/x/crypto v0.43.0 // indirect
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect
golang.org/x/mod v0.28.0 // indirect
Expand Down
28 changes: 19 additions & 9 deletions l1infotreesync/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,29 @@ package l1infotreesync

import (
"github.com/agglayer/aggkit/config/types"
aggkittypes "github.com/agglayer/aggkit/types"
"github.com/ethereum/go-ethereum/common"
)

type Config struct {
DBPath string `mapstructure:"DBPath"`
GlobalExitRootAddr common.Address `mapstructure:"GlobalExitRootAddr"`
RollupManagerAddr common.Address `mapstructure:"RollupManagerAddr"`
SyncBlockChunkSize uint64 `mapstructure:"SyncBlockChunkSize"`
URLRPCL1 string `mapstructure:"URLRPCL1"`
WaitForNewBlocksPeriod types.Duration `mapstructure:"WaitForNewBlocksPeriod"`
InitialBlock uint64 `mapstructure:"InitialBlock"`
RetryAfterErrorPeriod types.Duration `mapstructure:"RetryAfterErrorPeriod"`
MaxRetryAttemptsAfterError int `mapstructure:"MaxRetryAttemptsAfterError"`
// DBPath is the path of the database where the L1 Info Tree data will be stored
DBPath string `mapstructure:"DBPath"`
// GlobalExitRootAddr is the address of the GlobalExitRoot manager contract on L1
GlobalExitRootAddr common.Address `mapstructure:"GlobalExitRootAddr"`
// RollupManagerAddr is the address of the RollupManager/AgglayerManager contract
RollupManagerAddr common.Address `mapstructure:"RollupManagerAddr"`
// Possible values: LatestBlock, SafeBlock, PendingBlock, FinalizedBlock, EarliestBlock
BlockFinality aggkittypes.BlockNumberFinality `jsonschema:"enum=LatestBlock,enum=SafeBlock,enum=PendingBlock,enum=FinalizedBlock,enum=EarliestBlock" mapstructure:"BlockFinality"` //nolint:lll
// SyncBlockChunkSize is the amount of blocks that will be queried to the client on each request
SyncBlockChunkSize uint64 `mapstructure:"SyncBlockChunkSize"`
// WaitForNewBlocksPeriod time that will be waited when the synchronizer has queries for new blocks
WaitForNewBlocksPeriod types.Duration `mapstructure:"WaitForNewBlocksPeriod"`
// InitialBlock is the first block that will be queried when starting the synchronization from scratch
InitialBlock uint64 `mapstructure:"InitialBlock"`
// RetryAfterErrorPeriod is the time that will be waited when an unexpected error happens before retry
RetryAfterErrorPeriod types.Duration `mapstructure:"RetryAfterErrorPeriod"`
// MaxRetryAttemptsAfterError is the maximum number of consecutive attempts that will happen before panicing
MaxRetryAttemptsAfterError int `mapstructure:"MaxRetryAttemptsAfterError"`
// RequireStorageContentCompatibility is true it's mandatory that data stored in the database
// is compatible with the running environment
RequireStorageContentCompatibility bool `mapstructure:"RequireStorageContentCompatibility"`
Expand Down
Loading
Loading