Skip to content

Commit 94bee19

Browse files
feat(bridge-history): support codecv7 (#1609)
Co-authored-by: jonastheis <[email protected]>
1 parent b7e7d1a commit 94bee19

23 files changed

+313
-78
lines changed

bridge-history-api/Makefile

+3-3
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,6 @@ reset-env:
3737
go build -o $(PWD)/build/bin/bridgehistoryapi-db-cli ./cmd/db_cli && $(PWD)/build/bin/bridgehistoryapi-db-cli reset
3838

3939
bridgehistoryapi-docker:
40-
DOCKER_BUILDKIT=1 docker build -t scrolltech/bridgehistoryapi-fetcher:${IMAGE_VERSION} ${REPO_ROOT_DIR}/ -f ${REPO_ROOT_DIR}/build/dockerfiles/bridgehistoryapi-fetcher.Dockerfile
41-
DOCKER_BUILDKIT=1 docker build -t scrolltech/bridgehistoryapi-api:${IMAGE_VERSION} ${REPO_ROOT_DIR}/ -f ${REPO_ROOT_DIR}/build/dockerfiles/bridgehistoryapi-api.Dockerfile
42-
DOCKER_BUILDKIT=1 docker build -t scrolltech/bridgehistoryapi-db-cli:${IMAGE_VERSION} ${REPO_ROOT_DIR}/ -f ${REPO_ROOT_DIR}/build/dockerfiles/bridgehistoryapi-db-cli.Dockerfile
40+
DOCKER_BUILDKIT=1 docker build -t scrolltech/bridgehistoryapi-fetcher:${IMAGE_VERSION} ${REPO_ROOT_DIR}/ -f ${REPO_ROOT_DIR}/build/dockerfiles/bridgehistoryapi-fetcher.Dockerfile --platform=linux/amd64
41+
DOCKER_BUILDKIT=1 docker build -t scrolltech/bridgehistoryapi-api:${IMAGE_VERSION} ${REPO_ROOT_DIR}/ -f ${REPO_ROOT_DIR}/build/dockerfiles/bridgehistoryapi-api.Dockerfile --platform=linux/amd64
42+
DOCKER_BUILDKIT=1 docker build -t scrolltech/bridgehistoryapi-db-cli:${IMAGE_VERSION} ${REPO_ROOT_DIR}/ -f ${REPO_ROOT_DIR}/build/dockerfiles/bridgehistoryapi-db-cli.Dockerfile --platform=linux/amd64

bridge-history-api/abi/backend_abi.go

+11-4
Large diffs are not rendered by default.
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package backendabi
2+
3+
import (
4+
"testing"
5+
6+
"github.com/scroll-tech/go-ethereum/crypto"
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestEventSignatures(t *testing.T) {
11+
assert.Equal(t, crypto.Keccak256Hash([]byte("RevertBatch(uint256,bytes32)")), L1RevertBatchV0EventSig)
12+
assert.Equal(t, crypto.Keccak256Hash([]byte("RevertBatch(uint256,uint256)")), L1RevertBatchV7EventSig)
13+
}

bridge-history-api/cmd/fetcher/app/app.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,10 @@ func action(ctx *cli.Context) error {
6868

6969
observability.Server(ctx, db)
7070

71-
l1MessageFetcher := fetcher.NewL1MessageFetcher(subCtx, cfg.L1, db, l1Client)
71+
l1MessageFetcher, err := fetcher.NewL1MessageFetcher(subCtx, cfg.L1, db, l1Client)
72+
if err != nil {
73+
log.Crit("failed to create L1MessageFetcher", "err", err)
74+
}
7275
go l1MessageFetcher.Start()
7376

7477
l2MessageFetcher := fetcher.NewL2MessageFetcher(subCtx, cfg.L2, db, l2Client)

bridge-history-api/conf/config.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@
1919
"ScrollChainAddr": "0xa13BAF47339d63B743e7Da8741db5456DAc1E556",
2020
"GatewayRouterAddr": "0xF8B1378579659D8F7EE5f3C929c2f3E332E41Fd6",
2121
"MessageQueueAddr": "0x0d7E906BD9cAFa154b048cFa766Cc1E54E39AF9B",
22+
"MessageQueueV2Addr": "0x0000000000000000000000000000000000000000",
2223
"BatchBridgeGatewayAddr": "0x5Bcfd99c34cf7E06fc756f6f5aE7400504852bc4",
2324
"GasTokenGatewayAddr": "0x0000000000000000000000000000000000000000",
24-
"WrappedTokenGatewayAddr": "0x0000000000000000000000000000000000000000"
25+
"WrappedTokenGatewayAddr": "0x0000000000000000000000000000000000000000",
26+
"BlobScanAPIEndpoint": "https://api.blobscan.com/blobs/"
2527
},
2628
"L2": {
2729
"confirmation": 0,

bridge-history-api/go.mod

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ require (
1010
github.com/go-redis/redis/v8 v8.11.5
1111
github.com/pressly/goose/v3 v3.16.0
1212
github.com/prometheus/client_golang v1.19.0
13-
github.com/scroll-tech/go-ethereum v1.10.14-0.20250304112538-3c454e7101a3
13+
github.com/scroll-tech/da-codec v0.1.3-0.20250226072559-f8a8d3898f54
14+
github.com/scroll-tech/go-ethereum v1.10.14-0.20250305084331-57148478e950 // It's a hotfix for the header hash compatibility issue, pls change this with caution
1415
github.com/stretchr/testify v1.9.0
1516
github.com/urfave/cli/v2 v2.25.7
1617
golang.org/x/sync v0.11.0
@@ -90,7 +91,6 @@ require (
9091
github.com/rjeczalik/notify v0.9.1 // indirect
9192
github.com/rs/cors v1.7.0 // indirect
9293
github.com/russross/blackfriday/v2 v2.1.0 // indirect
93-
github.com/scroll-tech/da-codec v0.1.3-0.20250226072559-f8a8d3898f54 // indirect
9494
github.com/scroll-tech/zktrie v0.8.4 // indirect
9595
github.com/sethvargo/go-retry v0.2.4 // indirect
9696
github.com/shirou/gopsutil v3.21.11+incompatible // indirect

bridge-history-api/go.sum

+2-2
Original file line numberDiff line numberDiff line change
@@ -311,8 +311,8 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf
311311
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
312312
github.com/scroll-tech/da-codec v0.1.3-0.20250226072559-f8a8d3898f54 h1:qVpsVu1J91opTn6HYeuzWcBRVhQmPR8g05i+PlOjlI4=
313313
github.com/scroll-tech/da-codec v0.1.3-0.20250226072559-f8a8d3898f54/go.mod h1:xECEHZLVzbdUn+tNbRJhRIjLGTOTmnFQuTgUTeVLX58=
314-
github.com/scroll-tech/go-ethereum v1.10.14-0.20250304112538-3c454e7101a3 h1:y0L8+O3anBJIpboVWhNlUx3VC59he+0fd4b8PnDR16w=
315-
github.com/scroll-tech/go-ethereum v1.10.14-0.20250304112538-3c454e7101a3/go.mod h1:OblWe1+QrZwdpwO0j/LY3BSGuKT3YPUFBDQQgvvfStQ=
314+
github.com/scroll-tech/go-ethereum v1.10.14-0.20250305084331-57148478e950 h1:qfOaRflvH1vtnFWloB7BveKlP/VqYgMqLJ6e9TlBJ/8=
315+
github.com/scroll-tech/go-ethereum v1.10.14-0.20250305084331-57148478e950/go.mod h1:OblWe1+QrZwdpwO0j/LY3BSGuKT3YPUFBDQQgvvfStQ=
316316
github.com/scroll-tech/zktrie v0.8.4 h1:UagmnZ4Z3ITCk+aUq9NQZJNAwnWl4gSxsLb2Nl7IgRE=
317317
github.com/scroll-tech/zktrie v0.8.4/go.mod h1:XvNo7vAk8yxNyTjBDj5WIiFzYW4bx/gJ78+NK6Zn6Uk=
318318
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=

bridge-history-api/internal/config/config.go

+5
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,14 @@ type FetcherConfig struct {
3030
ScrollChainAddr string `json:"ScrollChainAddr"`
3131
GatewayRouterAddr string `json:"GatewayRouterAddr"`
3232
MessageQueueAddr string `json:"MessageQueueAddr"`
33+
MessageQueueV2Addr string `json:"MessageQueueV2Addr"`
3334
BatchBridgeGatewayAddr string `json:"BatchBridgeGatewayAddr"`
3435
GasTokenGatewayAddr string `json:"GasTokenGatewayAddr"`
3536
WrappedTokenGatewayAddr string `json:"WrappedTokenGatewayAddr"`
37+
38+
BeaconNodeAPIEndpoint string `json:"BeaconNodeAPIEndpoint"`
39+
BlobScanAPIEndpoint string `json:"BlobScanAPIEndpoint"`
40+
BlockNativeAPIEndpoint string `json:"BlockNativeAPIEndpoint"`
3641
}
3742

3843
// RedisConfig redis config

bridge-history-api/internal/controller/fetcher/l1_fetcher.go

+24-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package fetcher
22

33
import (
44
"context"
5+
"fmt"
56
"math/big"
67
"time"
78

@@ -10,6 +11,7 @@ import (
1011
"github.com/scroll-tech/go-ethereum/common"
1112
"github.com/scroll-tech/go-ethereum/ethclient"
1213
"github.com/scroll-tech/go-ethereum/log"
14+
"github.com/scroll-tech/go-ethereum/rollup/da_syncer/blob_client"
1315
"gorm.io/gorm"
1416

1517
"scroll-tech/bridge-history-api/internal/config"
@@ -35,13 +37,32 @@ type L1MessageFetcher struct {
3537
}
3638

3739
// NewL1MessageFetcher creates a new L1MessageFetcher instance.
38-
func NewL1MessageFetcher(ctx context.Context, cfg *config.FetcherConfig, db *gorm.DB, client *ethclient.Client) *L1MessageFetcher {
40+
func NewL1MessageFetcher(ctx context.Context, cfg *config.FetcherConfig, db *gorm.DB, client *ethclient.Client) (*L1MessageFetcher, error) {
41+
blobClient := blob_client.NewBlobClients()
42+
if cfg.BeaconNodeAPIEndpoint != "" {
43+
beaconNodeClient, err := blob_client.NewBeaconNodeClient(cfg.BeaconNodeAPIEndpoint)
44+
if err != nil {
45+
log.Warn("failed to create BeaconNodeClient", "err", err)
46+
} else {
47+
blobClient.AddBlobClient(beaconNodeClient)
48+
}
49+
}
50+
if cfg.BlobScanAPIEndpoint != "" {
51+
blobClient.AddBlobClient(blob_client.NewBlobScanClient(cfg.BlobScanAPIEndpoint))
52+
}
53+
if cfg.BlockNativeAPIEndpoint != "" {
54+
blobClient.AddBlobClient(blob_client.NewBlockNativeClient(cfg.BlockNativeAPIEndpoint))
55+
}
56+
if blobClient.Size() == 0 {
57+
return nil, fmt.Errorf("no blob client is configured")
58+
}
59+
3960
c := &L1MessageFetcher{
4061
ctx: ctx,
4162
cfg: cfg,
4263
client: client,
4364
eventUpdateLogic: logic.NewEventUpdateLogic(db, true),
44-
l1FetcherLogic: logic.NewL1FetcherLogic(cfg, db, client),
65+
l1FetcherLogic: logic.NewL1FetcherLogic(cfg, db, client, blobClient),
4566
}
4667

4768
reg := prometheus.DefaultRegisterer
@@ -58,7 +79,7 @@ func NewL1MessageFetcher(ctx context.Context, cfg *config.FetcherConfig, db *gor
5879
Help: "Latest blockchain height the L1 message fetcher has synced with.",
5980
})
6081

61-
return c
82+
return c, nil
6283
}
6384

6485
// Start starts the L1 message fetching process.

bridge-history-api/internal/logic/l1_event_parser.go

+112-9
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@ package logic
22

33
import (
44
"context"
5+
"fmt"
56
"math/big"
67

8+
"github.com/scroll-tech/da-codec/encoding"
79
"github.com/scroll-tech/go-ethereum/common"
810
"github.com/scroll-tech/go-ethereum/core/types"
911
"github.com/scroll-tech/go-ethereum/crypto"
1012
"github.com/scroll-tech/go-ethereum/ethclient"
1113
"github.com/scroll-tech/go-ethereum/log"
14+
"github.com/scroll-tech/go-ethereum/rollup/da_syncer/blob_client"
1215

1316
backendabi "scroll-tech/bridge-history-api/abi"
1417
"scroll-tech/bridge-history-api/internal/config"
@@ -19,15 +22,17 @@ import (
1922

2023
// L1EventParser the l1 event parser
2124
type L1EventParser struct {
22-
cfg *config.FetcherConfig
23-
client *ethclient.Client
25+
cfg *config.FetcherConfig
26+
client *ethclient.Client
27+
blobClient blob_client.BlobClient
2428
}
2529

2630
// NewL1EventParser creates l1 event parser
27-
func NewL1EventParser(cfg *config.FetcherConfig, client *ethclient.Client) *L1EventParser {
31+
func NewL1EventParser(cfg *config.FetcherConfig, client *ethclient.Client, blobClient blob_client.BlobClient) *L1EventParser {
2832
return &L1EventParser{
29-
cfg: cfg,
30-
client: client,
33+
cfg: cfg,
34+
client: client,
35+
blobClient: blobClient,
3136
}
3237
}
3338

@@ -232,7 +237,20 @@ func (e *L1EventParser) ParseL1SingleCrossChainEventLogs(ctx context.Context, lo
232237
}
233238

234239
// ParseL1BatchEventLogs parses L1 watched batch events.
235-
func (e *L1EventParser) ParseL1BatchEventLogs(ctx context.Context, logs []types.Log, client *ethclient.Client) ([]*orm.BatchEvent, error) {
240+
func (e *L1EventParser) ParseL1BatchEventLogs(ctx context.Context, logs []types.Log, client *ethclient.Client, blockTimestampsMap map[uint64]uint64) ([]*orm.BatchEvent, error) {
241+
// Since CodecV7 introduced multiple CommitBatch events per transaction,
242+
// each CommitBatch event corresponds to an individual blob containing block range data.
243+
// To correctly process these events, we need to:
244+
// 1. Parsing the associated blob data to extract the block range for each event
245+
// 2. Tracking the parent batch hash for each processed CommitBatch event, to:
246+
// - Validate the batch hash
247+
// - Derive the index of the current batch
248+
// In commitBatches and commitAndFinalizeBatch, the parent batch hash is passed in calldata,
249+
// so that we can use it to get the first batch's parent batch hash.
250+
// The index map serves this purpose with:
251+
// Key: commit transaction hash
252+
// Value: parent batch hashes (in order) for each processed CommitBatch event in the transaction
253+
txBlobIndexMap := make(map[common.Hash][]common.Hash)
236254
var l1BatchEvents []*orm.BatchEvent
237255
for _, vlog := range logs {
238256
switch vlog.Topics[0] {
@@ -247,11 +265,59 @@ func (e *L1EventParser) ParseL1BatchEventLogs(ctx context.Context, logs []types.
247265
log.Error("Failed to get commit batch tx or the tx is still pending", "err", err, "isPending", isPending)
248266
return nil, err
249267
}
250-
startBlock, endBlock, err := utils.GetBatchRangeFromCalldata(commitTx.Data())
268+
version, startBlock, endBlock, err := utils.GetBatchVersionAndBlockRangeFromCalldata(commitTx.Data())
251269
if err != nil {
252270
log.Error("Failed to get batch range from calldata", "hash", commitTx.Hash().String(), "height", vlog.BlockNumber)
253271
return nil, err
254272
}
273+
if version >= 7 { // It's a batch with version >= 7.
274+
codec, err := encoding.CodecFromVersion(encoding.CodecVersion(version))
275+
if err != nil {
276+
return nil, fmt.Errorf("unsupported codec version: %v, err: %w", version, err)
277+
}
278+
279+
// we append the batch hash to the slice for the current commit transaction after processing the batch.
280+
// that means the current index of the batch within the transaction is len(txBlobIndexMap[vlog.TxHash]).
281+
currentIndex := len(txBlobIndexMap[vlog.TxHash])
282+
if currentIndex >= len(commitTx.BlobHashes()) {
283+
return nil, fmt.Errorf("commit transaction %s has %d blobs, but trying to access index %d (batch index %d)",
284+
vlog.TxHash.String(), len(commitTx.BlobHashes()), currentIndex, event.BatchIndex.Uint64())
285+
}
286+
blobVersionedHash := commitTx.BlobHashes()[currentIndex]
287+
288+
// validate the batch hash
289+
var parentBatchHash common.Hash
290+
if currentIndex == 0 {
291+
parentBatchHash, err = utils.GetParentBatchHashFromCalldata(commitTx.Data())
292+
if err != nil {
293+
return nil, fmt.Errorf("failed to get parent batch header from calldata, tx hash: %s, err: %w", vlog.TxHash.String(), err)
294+
}
295+
} else {
296+
// here we need to subtract 1 from the current index to get the parent batch hash.
297+
parentBatchHash = txBlobIndexMap[vlog.TxHash][currentIndex-1]
298+
}
299+
calculatedBatch, err := codec.NewDABatchFromParams(event.BatchIndex.Uint64(), blobVersionedHash, parentBatchHash)
300+
if err != nil {
301+
return nil, fmt.Errorf("failed to create new DA batch from params, batch index: %d, err: %w", event.BatchIndex.Uint64(), err)
302+
}
303+
if calculatedBatch.Hash() != event.BatchHash {
304+
return nil, fmt.Errorf("batch hash mismatch for batch %d, expected: %s, got: %s", event.BatchIndex, event.BatchHash.String(), calculatedBatch.Hash().String())
305+
}
306+
307+
blocks, err := e.getBatchBlockRangeFromBlob(ctx, codec, blobVersionedHash, blockTimestampsMap[vlog.BlockNumber])
308+
if err != nil {
309+
return nil, fmt.Errorf("failed to process versioned blob, blobVersionedHash: %s, block number: %d, blob index: %d, err: %w",
310+
blobVersionedHash.String(), vlog.BlockNumber, currentIndex, err)
311+
}
312+
if len(blocks) == 0 {
313+
return nil, fmt.Errorf("no blocks found in the blob, blobVersionedHash: %s, block number: %d, blob index: %d",
314+
blobVersionedHash.String(), vlog.BlockNumber, currentIndex)
315+
}
316+
startBlock = blocks[0].Number()
317+
endBlock = blocks[len(blocks)-1].Number()
318+
319+
txBlobIndexMap[vlog.TxHash] = append(txBlobIndexMap[vlog.TxHash], event.BatchHash)
320+
}
255321
l1BatchEvents = append(l1BatchEvents, &orm.BatchEvent{
256322
BatchStatus: int(btypes.BatchStatusTypeCommitted),
257323
BatchIndex: event.BatchIndex.Uint64(),
@@ -260,8 +326,8 @@ func (e *L1EventParser) ParseL1BatchEventLogs(ctx context.Context, logs []types.
260326
EndBlockNumber: endBlock,
261327
L1BlockNumber: vlog.BlockNumber,
262328
})
263-
case backendabi.L1RevertBatchEventSig:
264-
event := backendabi.L1RevertBatchEvent{}
329+
case backendabi.L1RevertBatchV0EventSig:
330+
event := backendabi.L1RevertBatchV0Event{}
265331
if err := utils.UnpackLog(backendabi.IScrollChainABI, &event, "RevertBatch", vlog); err != nil {
266332
log.Error("Failed to unpack RevertBatch event", "err", err)
267333
return nil, err
@@ -272,6 +338,19 @@ func (e *L1EventParser) ParseL1BatchEventLogs(ctx context.Context, logs []types.
272338
BatchHash: event.BatchHash.String(),
273339
L1BlockNumber: vlog.BlockNumber,
274340
})
341+
case backendabi.L1RevertBatchV7EventSig:
342+
event := backendabi.L1RevertBatchV7Event{}
343+
if err := utils.UnpackLog(backendabi.IScrollChainABI, &event, "RevertBatch0", vlog); err != nil {
344+
log.Error("Failed to unpack RevertBatch event", "err", err)
345+
return nil, err
346+
}
347+
for i := event.StartBatchIndex.Uint64(); i <= event.FinishBatchIndex.Uint64(); i++ {
348+
l1BatchEvents = append(l1BatchEvents, &orm.BatchEvent{
349+
BatchStatus: int(btypes.BatchStatusTypeReverted),
350+
BatchIndex: i,
351+
L1BlockNumber: vlog.BlockNumber,
352+
})
353+
}
275354
case backendabi.L1FinalizeBatchEventSig:
276355
event := backendabi.L1FinalizeBatchEvent{}
277356
if err := utils.UnpackLog(backendabi.IScrollChainABI, &event, "FinalizeBatch", vlog); err != nil {
@@ -389,3 +468,27 @@ func getRealFromAddress(ctx context.Context, eventSender common.Address, eventMe
389468
}
390469
return sender.String(), nil
391470
}
471+
472+
func (e *L1EventParser) getBatchBlockRangeFromBlob(ctx context.Context, codec encoding.Codec, versionedHash common.Hash, l1BlockTime uint64) ([]encoding.DABlock, error) {
473+
blob, err := e.blobClient.GetBlobByVersionedHashAndBlockTime(ctx, versionedHash, l1BlockTime)
474+
if err != nil {
475+
return nil, fmt.Errorf("failed to get blob %s: %w", versionedHash.Hex(), err)
476+
}
477+
if blob == nil {
478+
return nil, fmt.Errorf("blob %s not found", versionedHash.Hex())
479+
}
480+
481+
blobPayload, err := codec.DecodeBlob(blob)
482+
if err != nil {
483+
return nil, fmt.Errorf("blob %s decode error: %w", versionedHash.Hex(), err)
484+
}
485+
486+
blocks := blobPayload.Blocks()
487+
if len(blocks) == 0 {
488+
return nil, fmt.Errorf("empty blocks in blob %s", versionedHash.Hex())
489+
}
490+
491+
log.Debug("Successfully processed blob", "versionedHash", versionedHash.Hex(), "blocksCount", len(blocks))
492+
493+
return blocks, nil
494+
}

0 commit comments

Comments
 (0)