go-ethereum/rollup/sync_service/bridge_client.go
Jonas Theis b4f36b2489
feat(MessageQueueV2): add reading of MessageQueueV2 and transition logic from V1 to V2 for EuclidV2 (#1108)
* port changes from #1013

* port changes from #1068

* go.mod tidy

* fix compile error

* fix goimports

* fix log

* address review comments

* upgrade golang.org/x/net to 0.23.0

* port changes from #1018

* fix tests and linter errors

* address review comments

* refactor rollup sync service / verifier to use CalldataBlobSource to retrieve data from L1

* add configuration and initialize blob clients

* fix unit tests

* remove unused code

* address review comments

* address more review comments

* implement first version of new da-codec and to handle multiple batches submitted in one transaction

* add CommitBatchDAV7 and handle multiple commit events submitted in a single transactions

* fix bug due to previous batch being empty when processing the first batch within a set of batches

* Allow using MPT

* implement reading of QueueTransaction from  L1MessageQueueV1 and V2

* implement access to V1 and V2 messages and replace usage so that V1 is used before EuclidV2 fork and V2 afterward

* add tests

* update to latest da-codec

* add field to CommittedBatchMeta to store LastL1MessageQueueHash for CodecV7 batches

* adjust rollup verifier to support CodecV7 batches

* address review comments

* consume all L1 messages before EuclidV2 fork

* address review comments

* address review comments

* fix issues after merge

* go mod tidy

* fix unit tests

* update da-codec

* add test TestValidateBatchCodecV7

* go mod tidy

* do not log error on shutdown

* add flag to explicitly disable L1MessageQueueV2

* add TestEuclidV2HardForkMessageQueue to scroll worker

* add sanity check for version to deserialization of committedBatchMetaV7

* add mechanism for nodes to retrieve V2 messages even if they upgrade after L1MessageQueueV2 is deployed

* chore: auto version bump [bot]

* address review comments

* Update core/rawdb/accessors_l1_message.go

Co-authored-by: colin <102356659+colinlyguo@users.noreply.github.com>

* address review comments

---------

Co-authored-by: Ömer Faruk Irmak <omerfirmak@gmail.com>
Co-authored-by: Thegaram <Thegaram@users.noreply.github.com>
Co-authored-by: colin <102356659+colinlyguo@users.noreply.github.com>
Co-authored-by: Péter Garamvölgyi <peter@scroll.io>
2025-02-14 12:57:07 +01:00

194 lines
6 KiB
Go

package sync_service
import (
"context"
"errors"
"fmt"
"math/big"
"github.com/scroll-tech/go-ethereum/accounts/abi/bind"
"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/log"
"github.com/scroll-tech/go-ethereum/rpc"
)
// BridgeClient is a wrapper around EthClient that adds
// methods for conveniently collecting L1 messages.
type BridgeClient struct {
client EthClient
confirmations rpc.BlockNumber
l1MessageQueueV1Address common.Address
filtererV1 *L1MessageQueueFilterer
enableMessageQueueV2 bool
l1MessageQueueV2Address common.Address
filtererV2 *L1MessageQueueFilterer
}
func newBridgeClient(ctx context.Context, l1Client EthClient, l1ChainId uint64, confirmations rpc.BlockNumber, l1MessageQueueV1Address common.Address, enableMessageQueueV2 bool, l1MessageQueueV2Address common.Address) (*BridgeClient, error) {
if l1MessageQueueV1Address == (common.Address{}) {
return nil, errors.New("must pass non-zero l1MessageQueueV1Address to BridgeClient")
}
if enableMessageQueueV2 && l1MessageQueueV2Address == (common.Address{}) {
return nil, errors.New("must pass non-zero l1MessageQueueV2Address to BridgeClient")
}
// sanity check: compare chain IDs
got, err := l1Client.ChainID(ctx)
if err != nil {
return nil, fmt.Errorf("failed to query L1 chain ID, err = %w", err)
}
if got.Cmp(big.NewInt(0).SetUint64(l1ChainId)) != 0 {
return nil, fmt.Errorf("unexpected chain ID, expected = %v, got = %v", l1ChainId, got)
}
filtererV1, err := NewL1MessageQueueFilterer(l1MessageQueueV1Address, l1Client)
if err != nil {
return nil, fmt.Errorf("failed to initialize L1MessageQueueV1Filterer, err = %w", err)
}
var filtererV2 *L1MessageQueueFilterer
if enableMessageQueueV2 {
filtererV2, err = NewL1MessageQueueFilterer(l1MessageQueueV2Address, l1Client)
if err != nil {
return nil, fmt.Errorf("failed to initialize L1MessageQueueV2Filterer, err = %w", err)
}
}
client := BridgeClient{
client: l1Client,
confirmations: confirmations,
l1MessageQueueV1Address: l1MessageQueueV1Address,
filtererV1: filtererV1,
enableMessageQueueV2: enableMessageQueueV2,
l1MessageQueueV2Address: l1MessageQueueV2Address,
filtererV2: filtererV2,
}
return &client, nil
}
// fetchMessagesInRange retrieves and parses all L1 messages between the
// provided from and to L1 block numbers (inclusive).
func (c *BridgeClient) fetchMessagesInRange(ctx context.Context, from, to uint64, queryL1MessagesV1 bool) ([]types.L1MessageTx, []types.L1MessageTx, error) {
log.Trace("BridgeClient fetchMessagesInRange", "fromBlock", from, "toBlock", to)
var msgsV1, msgsV2 []types.L1MessageTx
opts := bind.FilterOpts{
Start: from,
End: &to,
Context: ctx,
}
// Query L1MessageQueueV1 if enabled. We disable querying of L1MessageQueueV1 once L1MessageQueueV2 is enabled,
// and we have received the first QueueTransaction event from L1MessageQueueV2.
if queryL1MessagesV1 {
it, err := c.filtererV1.FilterQueueTransaction(&opts, nil, nil)
if err != nil {
return nil, nil, err
}
for it.Next() {
event := it.Event
log.Trace("Received new L1 QueueTransaction event from L1MessageQueueV1", "event", event)
if !event.GasLimit.IsUint64() {
return nil, nil, fmt.Errorf("invalid QueueTransaction event: QueueIndex = %v, GasLimit = %v", event.QueueIndex, event.GasLimit)
}
msgsV1 = append(msgsV1, types.L1MessageTx{
QueueIndex: event.QueueIndex,
Gas: event.GasLimit.Uint64(),
To: &event.Target,
Value: event.Value,
Data: event.Data,
Sender: event.Sender,
})
}
if err = it.Error(); err != nil {
return nil, nil, err
}
}
// We allow to explicitly enable/disable querying of L1MessageQueueV2. This is useful for running the node without
// MessageQueueV2 available on L1 or for testing purposes.
if !c.enableMessageQueueV2 {
return msgsV1, nil, nil
}
// We always query L1MessageQueueV2. Before EuclidV2 L1 upgrade tx this will return an empty list as we don't use
// L1MessageQueueV2 to enqueue L1 messages before EuclidV2.
it, err := c.filtererV2.FilterQueueTransaction(&opts, nil, nil)
if err != nil {
return nil, nil, err
}
for it.Next() {
event := it.Event
log.Trace("Received new L1 QueueTransaction event from L1MessageQueueV2", "event", event)
if !event.GasLimit.IsUint64() {
return nil, nil, fmt.Errorf("invalid QueueTransaction event: QueueIndex = %v, GasLimit = %v", event.QueueIndex, event.GasLimit)
}
msgsV2 = append(msgsV2, types.L1MessageTx{
QueueIndex: event.QueueIndex,
Gas: event.GasLimit.Uint64(),
To: &event.Target,
Value: event.Value,
Data: event.Data,
Sender: event.Sender,
})
}
if err = it.Error(); err != nil {
return nil, nil, err
}
return msgsV1, msgsV2, nil
}
func (c *BridgeClient) getLatestConfirmedBlockNumber(ctx context.Context) (uint64, error) {
// confirmation based on "safe" or "finalized" block tag
if c.confirmations == rpc.SafeBlockNumber || c.confirmations == rpc.FinalizedBlockNumber {
tag := big.NewInt(int64(c.confirmations))
header, err := c.client.HeaderByNumber(ctx, tag)
if err != nil {
return 0, err
}
if !header.Number.IsInt64() {
return 0, fmt.Errorf("received unexpected block number in BridgeClient: %v", header.Number)
}
return header.Number.Uint64(), nil
}
// confirmation based on latest block number
if c.confirmations == rpc.LatestBlockNumber {
number, err := c.client.BlockNumber(ctx)
if err != nil {
return 0, err
}
return number, nil
}
// confirmation based on a certain number of blocks
if c.confirmations.Int64() >= 0 {
number, err := c.client.BlockNumber(ctx)
if err != nil {
return 0, err
}
confirmations := uint64(c.confirmations.Int64())
if number >= confirmations {
return number - confirmations, nil
}
return 0, nil
}
return 0, fmt.Errorf("unknown confirmation type: %v", c.confirmations)
}