diff --git a/common/version/version.go b/common/version/version.go index db128cc5e8..dedecf6097 100644 --- a/common/version/version.go +++ b/common/version/version.go @@ -5,7 +5,7 @@ import ( "runtime/debug" ) -var tag = "v4.5.27" +var tag = "v4.5.29" var commit = func() string { if info, ok := debug.ReadBuildInfo(); ok { diff --git a/rollup/abi/validium_abi.go b/rollup/abi/validium_abi.go new file mode 100644 index 0000000000..e0a6bf9fbb --- /dev/null +++ b/rollup/abi/validium_abi.go @@ -0,0 +1,22 @@ +package bridgeabi + +import ( + "github.com/scroll-tech/go-ethereum/accounts/abi" + "github.com/scroll-tech/go-ethereum/accounts/abi/bind" +) + +var ( + // ValidiumABI holds information about Validium's context and available invokable methods. + ValidiumABI *abi.ABI +) + +func init() { + ValidiumABI, _ = ValidiumMetaData.GetAbi() +} + +// Generated manually from abigen. + +// ValidiumMetaData contains all meta data concerning the ScrollChainValidium contract. +var ValidiumMetaData = &bind.MetaData{ + ABI: "[{\"type\":\"constructor\",\"inputs\":[{\"name\":\"_chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"_messageQueueV2\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"_verifier\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"DEFAULT_ADMIN_ROLE\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"GENESIS_IMPORTER_ROLE\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"PROVER_ROLE\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"SEQUENCER_ROLE\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"commitBatch\",\"inputs\":[{\"name\":\"version\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"parentBatchHash\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"postStateRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"withdrawRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"commitment\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"committedBatches\",\"inputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"finalizeBundle\",\"inputs\":[{\"name\":\"batchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"totalL1MessagesPoppedOverall\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"aggrProof\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"getRoleAdmin\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"grantRole\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"account\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"hasRole\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"account\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"importGenesisBatch\",\"inputs\":[{\"name\":\"_batchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"initialize\",\"inputs\":[{\"name\":\"_admin\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"isBatchFinalized\",\"inputs\":[{\"name\":\"_batchIndex\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"lastCommittedBatchIndex\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"lastFinalizedBatchIndex\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"layer2ChainId\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"messageQueueV2\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"paused\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"renounceRole\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"account\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"revertBatch\",\"inputs\":[{\"name\":\"batchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"revokeRole\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"account\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setPause\",\"inputs\":[{\"name\":\"_status\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"stateRoots\",\"inputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"supportsInterface\",\"inputs\":[{\"name\":\"interfaceId\",\"type\":\"bytes4\",\"internalType\":\"bytes4\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"verifier\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"withdrawRoots\",\"inputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"event\",\"name\":\"CommitBatch\",\"inputs\":[{\"name\":\"batchIndex\",\"type\":\"uint256\",\"indexed\":true,\"internalType\":\"uint256\"},{\"name\":\"batchHash\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"FinalizeBatch\",\"inputs\":[{\"name\":\"batchIndex\",\"type\":\"uint256\",\"indexed\":true,\"internalType\":\"uint256\"},{\"name\":\"batchHash\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"stateRoot\",\"type\":\"bytes32\",\"indexed\":false,\"internalType\":\"bytes32\"},{\"name\":\"withdrawRoot\",\"type\":\"bytes32\",\"indexed\":false,\"internalType\":\"bytes32\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Initialized\",\"inputs\":[{\"name\":\"version\",\"type\":\"uint8\",\"indexed\":false,\"internalType\":\"uint8\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Paused\",\"inputs\":[{\"name\":\"account\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"RevertBatch\",\"inputs\":[{\"name\":\"startBatchIndex\",\"type\":\"uint256\",\"indexed\":true,\"internalType\":\"uint256\"},{\"name\":\"finishBatchIndex\",\"type\":\"uint256\",\"indexed\":true,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"RoleAdminChanged\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"previousAdminRole\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"newAdminRole\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"RoleGranted\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"account\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"sender\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"RoleRevoked\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"account\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"sender\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Unpaused\",\"inputs\":[{\"name\":\"account\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"error\",\"name\":\"ErrorBatchHeaderV0LengthTooSmall\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorBatchIsAlreadyVerified\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorBatchNotCommitted\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorGenesisBatchImported\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorIncorrectBatchHash\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorInvalidGenesisBatch\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorRevertFinalizedBatch\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorStateRootIsZero\",\"inputs\":[]}]", +} diff --git a/rollup/cmd/rollup_relayer/app/app.go b/rollup/cmd/rollup_relayer/app/app.go index 168b58aeed..96bb73c14d 100644 --- a/rollup/cmd/rollup_relayer/app/app.go +++ b/rollup/cmd/rollup_relayer/app/app.go @@ -107,7 +107,7 @@ func action(ctx *cli.Context) error { } chunkProposer := watcher.NewChunkProposer(subCtx, cfg.L2Config.ChunkProposerConfig, minCodecVersion, genesis.Config, db, registry) - batchProposer := watcher.NewBatchProposer(subCtx, cfg.L2Config.BatchProposerConfig, minCodecVersion, genesis.Config, db, registry) + batchProposer := watcher.NewBatchProposer(subCtx, cfg.L2Config.BatchProposerConfig, minCodecVersion, genesis.Config, db, cfg.L2Config.RelayerConfig.ValidiumMode, registry) bundleProposer := watcher.NewBundleProposer(subCtx, cfg.L2Config.BundleProposerConfig, minCodecVersion, genesis.Config, db, registry) l2watcher := watcher.NewL2WatcherClient(subCtx, l2client, cfg.L2Config.Confirmations, cfg.L2Config.L2MessageQueueAddress, cfg.L2Config.WithdrawTrieRootSlot, genesis.Config, db, registry) diff --git a/rollup/conf/config.json b/rollup/conf/config.json index b2e365221e..415de9eed8 100644 --- a/rollup/conf/config.json +++ b/rollup/conf/config.json @@ -36,6 +36,7 @@ "endpoint": "https://rpc.scroll.io", "l2_message_queue_address": "0x0000000000000000000000000000000000000000", "relayer_config": { + "validium_mode": false, "rollup_contract_address": "0x0000000000000000000000000000000000000000", "gas_price_oracle_address": "0x0000000000000000000000000000000000000000", "sender_config": { @@ -123,4 +124,4 @@ "maxOpenNum": 200, "maxIdleNum": 20 } -} \ No newline at end of file +} diff --git a/rollup/internal/config/relayer.go b/rollup/internal/config/relayer.go index bb1c7e2235..db2e274681 100644 --- a/rollup/internal/config/relayer.go +++ b/rollup/internal/config/relayer.go @@ -53,6 +53,8 @@ type ChainMonitor struct { // RelayerConfig loads relayer configuration items. // What we need to pay attention to is that type RelayerConfig struct { + // ValidiumMode indicates if the relayer is in validium mode. + ValidiumMode bool `json:"validium_mode"` // RollupContractAddress store the rollup contract address. RollupContractAddress common.Address `json:"rollup_contract_address,omitempty"` // GasPriceOracleContractAddress store the scroll messenger contract address. @@ -73,8 +75,6 @@ type RelayerConfig struct { // Indicates if bypass features specific to testing environments are enabled. EnableTestEnvBypassFeatures bool `json:"enable_test_env_bypass_features"` - // The timeout in seconds for finalizing a batch without proof, only used when EnableTestEnvBypassFeatures is true. - FinalizeBatchWithoutProofTimeoutSec uint64 `json:"finalize_batch_without_proof_timeout_sec"` // The timeout in seconds for finalizing a bundle without proof, only used when EnableTestEnvBypassFeatures is true. FinalizeBundleWithoutProofTimeoutSec uint64 `json:"finalize_bundle_without_proof_timeout_sec"` } diff --git a/rollup/internal/controller/relayer/l2_relayer.go b/rollup/internal/controller/relayer/l2_relayer.go index cf5f267663..18110d7b17 100644 --- a/rollup/internal/controller/relayer/l2_relayer.go +++ b/rollup/internal/controller/relayer/l2_relayer.go @@ -79,6 +79,7 @@ type Layer2Relayer struct { commitSender *sender.Sender finalizeSender *sender.Sender l1RollupABI *abi.ABI + validiumABI *abi.ABI l2GasOracleABI *abi.ABI @@ -172,6 +173,7 @@ func NewLayer2Relayer(ctx context.Context, l2Client *ethclient.Client, db *gorm. commitSender: commitSender, finalizeSender: finalizeSender, l1RollupABI: bridgeAbi.ScrollChainABI, + validiumABI: bridgeAbi.ValidiumABI, l2GasOracleABI: bridgeAbi.L2GasPriceOracleABI, batchStrategy: strategy, @@ -239,10 +241,11 @@ func (r *Layer2Relayer) initializeGenesis() error { TotalL1MessagePoppedBefore: 0, ParentBatchHash: common.Hash{}, Chunks: []*encoding.Chunk{chunk}, + Blocks: chunk.Blocks, } var dbBatch *orm.Batch - dbBatch, err = r.batchOrm.InsertBatch(r.ctx, batch, encoding.CodecV0, rutils.BatchMetrics{}, dbTX) + dbBatch, err = r.batchOrm.InsertBatch(r.ctx, batch, encoding.CodecV0, rutils.BatchMetrics{ValidiumMode: r.cfg.ValidiumMode}, dbTX) if err != nil { return fmt.Errorf("failed to insert batch: %v", err) } @@ -274,10 +277,23 @@ func (r *Layer2Relayer) initializeGenesis() error { } func (r *Layer2Relayer) commitGenesisBatch(batchHash string, batchHeader []byte, stateRoot common.Hash) error { - // encode "importGenesisBatch" transaction calldata - calldata, packErr := r.l1RollupABI.Pack("importGenesisBatch", batchHeader, stateRoot) - if packErr != nil { - return fmt.Errorf("failed to pack importGenesisBatch with batch header: %v and state root: %v. error: %v", common.Bytes2Hex(batchHeader), stateRoot, packErr) + var calldata []byte + var packErr error + + if r.cfg.ValidiumMode { + // validium mode: only pass batchHeader + calldata, packErr = r.validiumABI.Pack("importGenesisBatch", batchHeader) + if packErr != nil { + return fmt.Errorf("failed to pack validium importGenesisBatch with batch header: %v. error: %v", common.Bytes2Hex(batchHeader), packErr) + } + log.Info("Validium importGenesis", "calldata", common.Bytes2Hex(calldata)) + } else { + // rollup mode: pass batchHeader and stateRoot + calldata, packErr = r.l1RollupABI.Pack("importGenesisBatch", batchHeader, stateRoot) + if packErr != nil { + return fmt.Errorf("failed to pack rollup importGenesisBatch with batch header: %v and state root: %v. error: %v", common.Bytes2Hex(batchHeader), stateRoot, packErr) + } + log.Info("Rollup importGenesis", "calldata", common.Bytes2Hex(calldata), "stateRoot", stateRoot) } // submit genesis batch to L1 rollup contract @@ -285,7 +301,7 @@ func (r *Layer2Relayer) commitGenesisBatch(batchHash string, batchHeader []byte, if err != nil { return fmt.Errorf("failed to send import genesis batch tx to L1, error: %v", err) } - log.Info("importGenesisBatch transaction sent", "contract", r.cfg.RollupContractAddress, "txHash", txHash, "batchHash", batchHash) + log.Info("importGenesisBatch transaction sent", "contract", r.cfg.RollupContractAddress, "txHash", txHash, "batchHash", batchHash, "validium", r.cfg.ValidiumMode) // wait for confirmation // we assume that no other transactions are sent before initializeGenesis completes @@ -310,20 +326,23 @@ func (r *Layer2Relayer) commitGenesisBatch(batchHash string, batchHeader []byte, if !confirmation.IsSuccessful { return errors.New("import genesis batch tx failed") } - log.Info("Successfully committed genesis batch on L1", "txHash", confirmation.TxHash.String()) + log.Info("Successfully committed genesis batch on L1", "txHash", confirmation.TxHash.String(), "validium", r.cfg.ValidiumMode) return nil } } } // ProcessPendingBatches processes the pending batches by sending commitBatch transactions to layer 1. -// Pending batchess are submitted if one of the following conditions is met: +// Pending batches are submitted if one of the following conditions is met: // - the first batch is too old -> forceSubmit // - backlogCount > r.cfg.BatchSubmission.BacklogMax -> forceSubmit // - we have at least minBatches AND price hits a desired target price func (r *Layer2Relayer) ProcessPendingBatches() { + // Get effective batch limits based on whether validium mode is enabled. + minBatches, maxBatches := r.getEffectiveBatchLimits() + // get pending batches from database in ascending order by their index. - dbBatches, err := r.batchOrm.GetFailedAndPendingBatches(r.ctx, r.cfg.BatchSubmission.MaxBatches) + dbBatches, err := r.batchOrm.GetFailedAndPendingBatches(r.ctx, maxBatches) if err != nil { log.Error("Failed to fetch pending L2 batches", "err", err) return @@ -432,21 +451,21 @@ func (r *Layer2Relayer) ProcessPendingBatches() { break } - if batchesToSubmitLen < r.cfg.BatchSubmission.MaxBatches { + if batchesToSubmitLen < maxBatches { batchesToSubmit = append(batchesToSubmit, &dbBatchWithChunks{ Batch: dbBatch, Chunks: dbChunks, }) } - if len(batchesToSubmit) >= r.cfg.BatchSubmission.MaxBatches { + if len(batchesToSubmit) >= maxBatches { break } } // we only submit batches if we have a timeout or if we have enough batches to submit - if !forceSubmit && len(batchesToSubmit) < r.cfg.BatchSubmission.MinBatches { - log.Debug("Not enough batches to submit", "count", len(batchesToSubmit), "minBatches", r.cfg.BatchSubmission.MinBatches, "maxBatches", r.cfg.BatchSubmission.MaxBatches) + if !forceSubmit && len(batchesToSubmit) < minBatches { + log.Debug("Not enough batches to submit", "count", len(batchesToSubmit), "minBatches", minBatches, "maxBatches", maxBatches) return } @@ -466,10 +485,22 @@ func (r *Layer2Relayer) ProcessPendingBatches() { codecVersion := encoding.CodecVersion(firstBatch.CodecVersion) switch codecVersion { case encoding.CodecV7, encoding.CodecV8: - calldata, blobs, maxBlockHeight, totalGasUsed, err = r.constructCommitBatchPayloadCodecV7(batchesToSubmit, firstBatch, lastBatch) - if err != nil { - log.Error("failed to construct constructCommitBatchPayloadCodecV7 payload for V7", "codecVersion", codecVersion, "start index", firstBatch.Index, "end index", lastBatch.Index, "err", err) - return + if r.cfg.ValidiumMode { + if len(batchesToSubmit) != 1 { + log.Error("validium mode only supports committing one batch at a time", "codecVersion", codecVersion, "start index", firstBatch.Index, "end index", lastBatch.Index, "batches count", len(batchesToSubmit)) + return + } + calldata, maxBlockHeight, totalGasUsed, err = r.constructCommitBatchPayloadValidium(batchesToSubmit[0]) + if err != nil { + log.Error("failed to construct validium payload", "codecVersion", codecVersion, "index", batchesToSubmit[0].Batch.Index, "err", err) + return + } + } else { + calldata, blobs, maxBlockHeight, totalGasUsed, err = r.constructCommitBatchPayloadCodecV7(batchesToSubmit, firstBatch, lastBatch) + if err != nil { + log.Error("failed to construct normal payload", "codecVersion", codecVersion, "start index", firstBatch.Index, "end index", lastBatch.Index, "err", err) + return + } } default: log.Error("unsupported codec version in ProcessPendingBatches", "codecVersion", codecVersion, "start index", firstBatch, "end index", lastBatch.Index) @@ -522,6 +553,14 @@ func (r *Layer2Relayer) ProcessPendingBatches() { log.Info("Sent the commitBatches tx to layer1", "batches count", len(batchesToSubmit), "start index", firstBatch.Index, "start hash", firstBatch.Hash, "end index", lastBatch.Index, "end hash", lastBatch.Hash, "tx hash", txHash.String()) } +// getEffectiveBatchLimits returns the effective min and max batch limits based on whether validium mode is enabled. +func (r *Layer2Relayer) getEffectiveBatchLimits() (int, int) { + if r.cfg.ValidiumMode { + return 1, 1 // minBatches=1, maxBatches=1 + } + return r.cfg.BatchSubmission.MinBatches, r.cfg.BatchSubmission.MaxBatches +} + func (r *Layer2Relayer) contextIDFromBatches(codecVersion encoding.CodecVersion, batches []*dbBatchWithChunks) string { contextIDs := []string{fmt.Sprintf("v%d", codecVersion)} for _, batch := range batches { @@ -690,9 +729,16 @@ func (r *Layer2Relayer) finalizeBundle(bundle *orm.Bundle, withProof bool) error var calldata []byte switch encoding.CodecVersion(bundle.CodecVersion) { case encoding.CodecV7, encoding.CodecV8: - calldata, err = r.constructFinalizeBundlePayloadCodecV7(dbBatch, endChunk, aggProof) - if err != nil { - return fmt.Errorf("failed to construct finalizeBundle payload codecv7, bundle index: %v, last batch index: %v, err: %w", bundle.Index, dbBatch.Index, err) + if r.cfg.ValidiumMode { + calldata, err = r.constructFinalizeBundlePayloadValidium(dbBatch, endChunk, aggProof) + if err != nil { + return fmt.Errorf("failed to construct validium finalizeBundle payload, codec version: %v, bundle index: %v, last batch index: %v, err: %w", dbBatch.CodecVersion, bundle.Index, dbBatch.Index, err) + } + } else { + calldata, err = r.constructFinalizeBundlePayloadCodecV7(dbBatch, endChunk, aggProof) + if err != nil { + return fmt.Errorf("failed to construct normal finalizeBundle payload, codec version: %v, bundle index: %v, last batch index: %v, err: %w", dbBatch.CodecVersion, bundle.Index, dbBatch.Index, err) + } } default: return fmt.Errorf("unsupported codec version in finalizeBundle, bundle index: %v, version: %d", bundle.Index, bundle.CodecVersion) @@ -951,6 +997,35 @@ func (r *Layer2Relayer) constructCommitBatchPayloadCodecV7(batchesToSubmit []*db return calldata, blobs, maxBlockHeight, totalGasUsed, nil } +func (r *Layer2Relayer) constructCommitBatchPayloadValidium(batch *dbBatchWithChunks) ([]byte, uint64, uint64, error) { + // Calculate metrics + var maxBlockHeight uint64 + var totalGasUsed uint64 + for _, c := range batch.Chunks { + if c.EndBlockNumber > maxBlockHeight { + maxBlockHeight = c.EndBlockNumber + } + totalGasUsed += c.TotalL2TxGas + } + + // Get the commitment from the batch data: for validium mode, we use the last L2 block hash as the commitment to the off-chain data + // Get the last chunk from the batch to find the end block hash + // TODO: This is a temporary solution, we might use a larger commitment in the future + if len(batch.Chunks) == 0 { + return nil, 0, 0, fmt.Errorf("last batch has no chunks") + } + + lastChunk := batch.Chunks[len(batch.Chunks)-1] + commitment := common.HexToHash(lastChunk.EndBlockHash) + version := encoding.CodecVersion(batch.Batch.CodecVersion) + calldata, err := r.validiumABI.Pack("commitBatch", version, common.HexToHash(batch.Batch.ParentBatchHash), common.HexToHash(batch.Batch.StateRoot), common.HexToHash(batch.Batch.WithdrawRoot), commitment[:]) + if err != nil { + return nil, 0, 0, fmt.Errorf("failed to pack commitBatch: %w", err) + } + log.Info("Validium commitBatch", "maxBlockHeight", maxBlockHeight, "commitment", commitment.Hex()) + return calldata, maxBlockHeight, totalGasUsed, nil +} + func (r *Layer2Relayer) constructFinalizeBundlePayloadCodecV7(dbBatch *orm.Batch, endChunk *orm.Chunk, aggProof *message.OpenVMBundleProof) ([]byte, error) { if aggProof != nil { // finalizeBundle with proof. calldata, packErr := r.l1RollupABI.Pack( @@ -967,7 +1042,8 @@ func (r *Layer2Relayer) constructFinalizeBundlePayloadCodecV7(dbBatch *orm.Batch return calldata, nil } - fmt.Println("packing finalizeBundlePostEuclidV2NoProof", len(dbBatch.BatchHeader), dbBatch.CodecVersion, dbBatch.BatchHeader, new(big.Int).SetUint64(endChunk.TotalL1MessagesPoppedBefore+endChunk.TotalL1MessagesPoppedInChunk), common.HexToHash(dbBatch.StateRoot), common.HexToHash(dbBatch.WithdrawRoot)) + log.Info("Packing finalizeBundlePostEuclidV2NoProof", "batchHeaderLength", len(dbBatch.BatchHeader), "codecVersion", dbBatch.CodecVersion, "totalL1Messages", endChunk.TotalL1MessagesPoppedBefore+endChunk.TotalL1MessagesPoppedInChunk, "stateRoot", dbBatch.StateRoot, "withdrawRoot", dbBatch.WithdrawRoot) + // finalizeBundle without proof. calldata, packErr := r.l1RollupABI.Pack( "finalizeBundlePostEuclidV2NoProof", @@ -982,6 +1058,26 @@ func (r *Layer2Relayer) constructFinalizeBundlePayloadCodecV7(dbBatch *orm.Batch return calldata, nil } +func (r *Layer2Relayer) constructFinalizeBundlePayloadValidium(dbBatch *orm.Batch, endChunk *orm.Chunk, aggProof *message.OpenVMBundleProof) ([]byte, error) { + log.Info("Packing validium finalizeBundle", "batchHeaderLength", len(dbBatch.BatchHeader), "codecVersion", dbBatch.CodecVersion, "totalL1Messages", endChunk.TotalL1MessagesPoppedBefore+endChunk.TotalL1MessagesPoppedInChunk, "stateRoot", dbBatch.StateRoot, "withdrawRoot", dbBatch.WithdrawRoot, "withProof", aggProof != nil) + + var proof []byte + if aggProof != nil { + proof = aggProof.Proof() + } + + calldata, packErr := r.validiumABI.Pack( + "finalizeBundle", + dbBatch.BatchHeader, + new(big.Int).SetUint64(endChunk.TotalL1MessagesPoppedBefore+endChunk.TotalL1MessagesPoppedInChunk), + proof, + ) + if packErr != nil { + return nil, fmt.Errorf("failed to pack validium finalizeBundle: %w", packErr) + } + return calldata, nil +} + // StopSenders stops the senders of the rollup-relayer to prevent querying the removed pending_transaction table in unit tests. // for unit test func (r *Layer2Relayer) StopSenders() { diff --git a/rollup/internal/controller/watcher/batch_proposer.go b/rollup/internal/controller/watcher/batch_proposer.go index 76d8ea94bf..522ce07cb7 100644 --- a/rollup/internal/controller/watcher/batch_proposer.go +++ b/rollup/internal/controller/watcher/batch_proposer.go @@ -32,6 +32,7 @@ type BatchProposer struct { cfg *config.BatchProposerConfig replayMode bool + validiumMode bool minCodecVersion encoding.CodecVersion chainCfg *params.ChainConfig @@ -53,7 +54,7 @@ type BatchProposer struct { } // NewBatchProposer creates a new BatchProposer instance. -func NewBatchProposer(ctx context.Context, cfg *config.BatchProposerConfig, minCodecVersion encoding.CodecVersion, chainCfg *params.ChainConfig, db *gorm.DB, reg prometheus.Registerer) *BatchProposer { +func NewBatchProposer(ctx context.Context, cfg *config.BatchProposerConfig, minCodecVersion encoding.CodecVersion, chainCfg *params.ChainConfig, db *gorm.DB, validiumMode bool, reg prometheus.Registerer) *BatchProposer { log.Info("new batch proposer", "batchTimeoutSec", cfg.BatchTimeoutSec, "maxBlobSize", maxBlobSize, "maxUncompressedBatchBytesSize", cfg.MaxUncompressedBatchBytesSize) p := &BatchProposer{ @@ -63,7 +64,8 @@ func NewBatchProposer(ctx context.Context, cfg *config.BatchProposerConfig, minC chunkOrm: orm.NewChunk(db), l2BlockOrm: orm.NewL2Block(db), cfg: cfg, - replayMode: false, + replayMode: false, // default is false, set to true when using proposer tool + validiumMode: validiumMode, minCodecVersion: minCodecVersion, chainCfg: chainCfg, @@ -171,7 +173,7 @@ func (p *BatchProposer) updateDBBatchInfo(batch *encoding.Batch, codecVersion en // recalculate batch metrics after truncation var calcErr error - metrics, calcErr = utils.CalculateBatchMetrics(batch, codecVersion) + metrics, calcErr = utils.CalculateBatchMetrics(batch, codecVersion, p.validiumMode) if calcErr != nil { return fmt.Errorf("failed to calculate batch metrics, batch index: %v, error: %w", batch.Index, calcErr) } @@ -287,7 +289,7 @@ func (p *BatchProposer) proposeBatch() error { batch.Blocks = append(batch.Blocks, chunk.Blocks...) batch.PostL1MessageQueueHash = common.HexToHash(dbChunks[i].PostL1MessageQueueHash) - metrics, calcErr := utils.CalculateBatchMetrics(&batch, codec.Version()) + metrics, calcErr := utils.CalculateBatchMetrics(&batch, codec.Version(), p.validiumMode) if calcErr != nil { return fmt.Errorf("failed to calculate batch metrics: %w", calcErr) } @@ -312,7 +314,7 @@ func (p *BatchProposer) proposeBatch() error { batch.PostL1MessageQueueHash = common.HexToHash(dbChunks[i-1].PostL1MessageQueueHash) batch.Blocks = batch.Blocks[:len(batch.Blocks)-len(lastChunk.Blocks)] - metrics, err = utils.CalculateBatchMetrics(&batch, codec.Version()) + metrics, err = utils.CalculateBatchMetrics(&batch, codec.Version(), p.validiumMode) if err != nil { return fmt.Errorf("failed to calculate batch metrics: %w", err) } @@ -322,7 +324,7 @@ func (p *BatchProposer) proposeBatch() error { } } - metrics, calcErr := utils.CalculateBatchMetrics(&batch, codec.Version()) + metrics, calcErr := utils.CalculateBatchMetrics(&batch, codec.Version(), p.validiumMode) if calcErr != nil { return fmt.Errorf("failed to calculate batch metrics: %w", calcErr) } diff --git a/rollup/internal/controller/watcher/batch_proposer_test.go b/rollup/internal/controller/watcher/batch_proposer_test.go index c446315d6d..526c93655f 100644 --- a/rollup/internal/controller/watcher/batch_proposer_test.go +++ b/rollup/internal/controller/watcher/batch_proposer_test.go @@ -100,7 +100,7 @@ func testBatchProposerLimitsCodecV7(t *testing.T) { DarwinV2Time: new(uint64), EuclidTime: new(uint64), EuclidV2Time: new(uint64), - }, db, nil) + }, db, false /* rollup mode */, nil) bp.TryProposeBatch() batches, err := batchOrm.GetBatches(context.Background(), map[string]interface{}{}, []string{}, 0) @@ -178,7 +178,7 @@ func testBatchProposerBlobSizeLimitCodecV7(t *testing.T) { MaxChunksPerBatch: math.MaxInt32, BatchTimeoutSec: math.MaxUint32, MaxUncompressedBatchBytesSize: math.MaxUint64, - }, encoding.CodecV7, chainConfig, db, nil) + }, encoding.CodecV7, chainConfig, db, false /* rollup mode */, nil) for i := 0; i < 2; i++ { bp.TryProposeBatch() @@ -246,7 +246,7 @@ func testBatchProposerMaxChunkNumPerBatchLimitCodecV7(t *testing.T) { MaxChunksPerBatch: 45, BatchTimeoutSec: math.MaxUint32, MaxUncompressedBatchBytesSize: math.MaxUint64, - }, encoding.CodecV7, chainConfig, db, nil) + }, encoding.CodecV7, chainConfig, db, false /* rollup mode */, nil) bp.TryProposeBatch() batches, err := batchOrm.GetBatches(context.Background(), map[string]interface{}{}, []string{}, 0) @@ -335,7 +335,7 @@ func testBatchProposerUncompressedBatchBytesLimitCodecV8(t *testing.T) { MaxChunksPerBatch: math.MaxInt32, // No chunk count limit BatchTimeoutSec: math.MaxUint32, // No timeout limit MaxUncompressedBatchBytesSize: 4 * 1024, // 4KiB limit - }, encoding.CodecV8, chainConfig, db, nil) + }, encoding.CodecV8, chainConfig, db, false /* rollup mode */, nil) bp.TryProposeBatch() diff --git a/rollup/internal/controller/watcher/bundle_proposer_test.go b/rollup/internal/controller/watcher/bundle_proposer_test.go index f631a60a18..7e2f27da43 100644 --- a/rollup/internal/controller/watcher/bundle_proposer_test.go +++ b/rollup/internal/controller/watcher/bundle_proposer_test.go @@ -103,7 +103,7 @@ func testBundleProposerLimitsCodecV7(t *testing.T) { MaxChunksPerBatch: math.MaxInt32, BatchTimeoutSec: 0, MaxUncompressedBatchBytesSize: math.MaxUint64, - }, encoding.CodecV7, chainConfig, db, nil) + }, encoding.CodecV7, chainConfig, db, false /* rollup mode */, nil) cp.TryProposeChunk() // chunk1 contains block1 bap.TryProposeBatch() // batch1 contains chunk1 diff --git a/rollup/internal/controller/watcher/proposer_tool.go b/rollup/internal/controller/watcher/proposer_tool.go index 9ab53d91ce..b825073cfa 100644 --- a/rollup/internal/controller/watcher/proposer_tool.go +++ b/rollup/internal/controller/watcher/proposer_tool.go @@ -125,7 +125,7 @@ func NewProposerTool(ctx context.Context, cancel context.CancelFunc, cfg *config chunkProposer := NewChunkProposer(ctx, cfg.L2Config.ChunkProposerConfig, minCodecVersion, chainCfg, db, nil) chunkProposer.SetReplayDB(dbForReplay) - batchProposer := NewBatchProposer(ctx, cfg.L2Config.BatchProposerConfig, minCodecVersion, chainCfg, db, nil) + batchProposer := NewBatchProposer(ctx, cfg.L2Config.BatchProposerConfig, minCodecVersion, chainCfg, db, false /* rollup mode */, nil) batchProposer.SetReplayDB(dbForReplay) bundleProposer := NewBundleProposer(ctx, cfg.L2Config.BundleProposerConfig, minCodecVersion, chainCfg, db, nil) diff --git a/rollup/internal/orm/batch.go b/rollup/internal/orm/batch.go index 584792fe18..9bb699937f 100644 --- a/rollup/internal/orm/batch.go +++ b/rollup/internal/orm/batch.go @@ -285,7 +285,7 @@ func (o *Batch) InsertBatch(ctx context.Context, batch *encoding.Batch, codecVer startChunkIndex = parentBatch.EndChunkIndex + 1 } - batchMeta, err := rutils.GetBatchMetadata(batch, codecVersion) + batchMeta, err := rutils.GetBatchMetadata(batch, codecVersion, metrics.ValidiumMode) if err != nil { log.Error("failed to get batch metadata", "index", batch.Index, "total l1 message popped before", batch.TotalL1MessagePoppedBefore, "parent hash", batch.ParentBatchHash.Hex(), "number of chunks", numChunks, "err", err) diff --git a/rollup/internal/utils/utils.go b/rollup/internal/utils/utils.go index 82d3f4af90..9a55b93e21 100644 --- a/rollup/internal/utils/utils.go +++ b/rollup/internal/utils/utils.go @@ -1,11 +1,13 @@ package utils import ( + "encoding/binary" "fmt" "time" "github.com/scroll-tech/da-codec/encoding" "github.com/scroll-tech/go-ethereum/common" + "github.com/scroll-tech/go-ethereum/crypto" ) // ChunkMetrics indicates the metrics for proposing a chunk. @@ -60,15 +62,18 @@ type BatchMetrics struct { L1CommitBlobSize uint64 L1CommitUncompressedBatchBytesSize uint64 + ValidiumMode bool // default false: rollup mode + // timing metrics EstimateBlobSizeTime time.Duration } // CalculateBatchMetrics calculates batch metrics. -func CalculateBatchMetrics(batch *encoding.Batch, codecVersion encoding.CodecVersion) (*BatchMetrics, error) { +func CalculateBatchMetrics(batch *encoding.Batch, codecVersion encoding.CodecVersion, validiumMode bool) (*BatchMetrics, error) { metrics := &BatchMetrics{ NumChunks: uint64(len(batch.Chunks)), FirstBlockTimestamp: batch.Chunks[0].Blocks[0].Header.Time, + ValidiumMode: validiumMode, } codec, err := encoding.CodecFromVersion(codecVersion) @@ -119,8 +124,59 @@ type BatchMetadata struct { ChallengeDigest common.Hash } +// encodeBatchHeaderValidium encodes batch header for validium mode and returns both encoded bytes and hash +func encodeBatchHeaderValidium(b *encoding.Batch, codecVersion encoding.CodecVersion) ([]byte, common.Hash, error) { + if b == nil { + return nil, common.Hash{}, fmt.Errorf("batch is nil, version: %v, index: %v", codecVersion, b.Index) + } + + if len(b.Blocks) == 0 { + return nil, common.Hash{}, fmt.Errorf("batch contains no blocks, version: %v, index: %v", codecVersion, b.Index) + } + + // For validium mode, use the last block hash as commitment to the off-chain data + // TODO: This is a temporary solution, we might use a larger commitment in the future + lastBlock := b.Blocks[len(b.Blocks)-1] + commitment := lastBlock.Header.Hash() + + // Batch header field sizes + const ( + versionSize = 1 + indexSize = 8 + parentHashSize = 32 + stateRootSize = 32 + withdrawRootSize = 32 + commitmentSize = 32 // TODO: 32 bytes for now, might use larger commitment in the future + + // Total size of validium batch header + validiumBatchHeaderSize = versionSize + indexSize + parentHashSize + stateRootSize + withdrawRootSize + commitmentSize + ) + + batchBytes := make([]byte, validiumBatchHeaderSize) + + // Define offsets for each field + var ( + versionOffset = 0 + indexOffset = versionOffset + versionSize + parentHashOffset = indexOffset + indexSize + stateRootOffset = parentHashOffset + parentHashSize + withdrawRootOffset = stateRootOffset + stateRootSize + commitmentOffset = withdrawRootOffset + withdrawRootSize + ) + + batchBytes[versionOffset] = uint8(codecVersion) // version + binary.BigEndian.PutUint64(batchBytes[indexOffset:indexOffset+indexSize], b.Index) // batch index + copy(batchBytes[parentHashOffset:parentHashOffset+parentHashSize], b.ParentBatchHash[0:parentHashSize]) // parentBatchHash + copy(batchBytes[stateRootOffset:stateRootOffset+stateRootSize], b.StateRoot().Bytes()[0:stateRootSize]) // postStateRoot + copy(batchBytes[withdrawRootOffset:withdrawRootOffset+withdrawRootSize], b.WithdrawRoot().Bytes()[0:withdrawRootSize]) // postWithdrawRoot + copy(batchBytes[commitmentOffset:commitmentOffset+commitmentSize], commitment[0:commitmentSize]) // data commitment + + hash := crypto.Keccak256Hash(batchBytes) + return batchBytes, hash, nil +} + // GetBatchMetadata retrieves the metadata of a batch. -func GetBatchMetadata(batch *encoding.Batch, codecVersion encoding.CodecVersion) (*BatchMetadata, error) { +func GetBatchMetadata(batch *encoding.Batch, codecVersion encoding.CodecVersion, validiumMode bool) (*BatchMetadata, error) { codec, err := encoding.CodecFromVersion(codecVersion) if err != nil { return nil, fmt.Errorf("failed to get codec from version: %v, err: %w", codecVersion, err) @@ -139,9 +195,17 @@ func GetBatchMetadata(batch *encoding.Batch, codecVersion encoding.CodecVersion) ChallengeDigest: daBatch.ChallengeDigest(), } + // If this function is used in Validium, we encode the batch header differently. + if validiumMode { + batchMeta.BatchBytes, batchMeta.BatchHash, err = encodeBatchHeaderValidium(batch, codecVersion) + if err != nil { + return nil, fmt.Errorf("failed to encode batch header for validium, version: %v, index: %v, err: %w", codecVersion, batch.Index, err) + } + } + batchMeta.BatchBlobDataProof, err = daBatch.BlobDataProofForPointEvaluation() if err != nil { - return nil, fmt.Errorf("failed to get blob data proof, version: %v, err: %w", codecVersion, err) + return nil, fmt.Errorf("failed to get blob data proof, version: %v, index: %v, err: %w", codecVersion, batch.Index, err) } numChunks := len(batch.Chunks) diff --git a/rollup/tests/rollup_test.go b/rollup/tests/rollup_test.go index 14c1a22b31..4296d90146 100644 --- a/rollup/tests/rollup_test.go +++ b/rollup/tests/rollup_test.go @@ -128,7 +128,7 @@ func testCommitBatchAndFinalizeBundleCodecV7(t *testing.T) { MaxChunksPerBatch: math.MaxInt32, BatchTimeoutSec: 300, MaxUncompressedBatchBytesSize: math.MaxUint64, - }, encoding.CodecV7, chainConfig, db, nil) + }, encoding.CodecV7, chainConfig, db, false /* rollup mode */, nil) bup := watcher.NewBundleProposer(context.Background(), &config.BundleProposerConfig{ MaxBatchNumPerBundle: 2,