From 801f109a042cbbcd605837a83d143299ddc43968 Mon Sep 17 00:00:00 2001 From: colinlyguo Date: Thu, 20 Jul 2023 03:59:05 +0800 Subject: [PATCH] feat(relayer): estimate l1 batch commit gas --- .../controller/watcher/batch_proposer.go | 37 +++++++++++++++- .../controller/watcher/chunk_proposer.go | 25 +++++++++++ bridge/internal/orm/chunk.go | 15 ++++++- common/types/block.go | 44 ++++--------------- common/version/version.go | 2 +- 5 files changed, 83 insertions(+), 40 deletions(-) diff --git a/bridge/internal/controller/watcher/batch_proposer.go b/bridge/internal/controller/watcher/batch_proposer.go index b72ef72d91..454afeab68 100644 --- a/bridge/internal/controller/watcher/batch_proposer.go +++ b/bridge/internal/controller/watcher/batch_proposer.go @@ -2,6 +2,7 @@ package watcher import ( "context" + "errors" "fmt" "time" @@ -99,7 +100,32 @@ func (p *BatchProposer) proposeBatchChunks() ([]*orm.Chunk, error) { firstChunk := dbChunks[0] totalL1CommitCalldataSize := firstChunk.TotalL1CommitCalldataSize totalL1CommitGas := firstChunk.TotalL1CommitGas - var totalChunks uint64 = 1 + totalChunks := uint64(1) + totalL1MessagePopped := firstChunk.TotalL1MessagesPoppedBefore + uint64(firstChunk.TotalL1MessagesPoppedInChunk) + + parentBatch, err := p.batchOrm.GetLatestBatch(p.ctx) + if err != nil && !errors.Is(errors.Unwrap(err), gorm.ErrRecordNotFound) { + return nil, err + } + + getKeccakGas := func(size uint64) uint64 { + return 30 + 6*((size+31)/32) // 30 + 6 * ceil(size / 32) + } + + // Add extra gas costs + totalL1CommitGas += 4 * 2100 // 4 one-time cold sload for commitBatch + totalL1CommitGas += 20000 // 1 time sstore + // adjusting gas: + // add 1 time cold sload (2100 gas) for L1MessageQueue + // add 1 time cold address access (2600 gas) for L1MessageQueue + // minus 1 time warm sload (100 gas) & 1 time warm address access (100 gas) + totalL1CommitGas += (2100 + 2600 - 100 - 100) + totalL1CommitGas += getKeccakGas(32 * totalChunks) // batch data hash + if parentBatch != nil { // parent batch header hash + totalL1CommitGas += getKeccakGas(uint64(len(parentBatch.BatchHeader))) + } + // batch header size: 89 + 32 * ceil(l1MessagePopped / 256) + totalL1CommitGas += getKeccakGas(89 + 32*(totalL1MessagePopped+255)/256) // Check if the first chunk breaks hard limits. // If so, it indicates there are bugs in chunk-proposer, manual fix is needed. @@ -124,9 +150,16 @@ func (p *BatchProposer) proposeBatchChunks() ([]*orm.Chunk, error) { } for i, chunk := range dbChunks[1:] { - totalChunks++ totalL1CommitCalldataSize += chunk.TotalL1CommitCalldataSize totalL1CommitGas += chunk.TotalL1CommitGas + // adjust batch data hash gas cost: add one chunk + totalL1CommitGas -= getKeccakGas(32 * totalChunks) + totalChunks++ + totalL1CommitGas += getKeccakGas(32 * totalChunks) + // adjust batch header hash gas cost: adjust totalL1MessagePopped in calculating header length + totalL1CommitGas -= getKeccakGas(89 + 32*(totalL1MessagePopped+255)/256) + totalL1MessagePopped += uint64(chunk.TotalL1MessagesPoppedInChunk) + totalL1CommitGas += getKeccakGas(89 + 32*(totalL1MessagePopped+255)/256) if totalChunks > p.maxChunkNumPerBatch || totalL1CommitCalldataSize > p.maxL1CommitCalldataSizePerBatch || totalL1CommitGas > p.maxL1CommitGasPerBatch { diff --git a/bridge/internal/controller/watcher/chunk_proposer.go b/bridge/internal/controller/watcher/chunk_proposer.go index d4a536da72..c28f5be7fa 100644 --- a/bridge/internal/controller/watcher/chunk_proposer.go +++ b/bridge/internal/controller/watcher/chunk_proposer.go @@ -2,6 +2,7 @@ package watcher import ( "context" + "errors" "fmt" "time" @@ -88,11 +89,29 @@ func (p *ChunkProposer) proposeChunk() (*types.Chunk, error) { return nil, nil } + parentChunk, err := p.chunkOrm.GetLatestChunk(p.ctx) + if err != nil && !errors.Is(errors.Unwrap(err), gorm.ErrRecordNotFound) { + return nil, err + } + firstBlock := blocks[0] totalTxGasUsed := firstBlock.Header.GasUsed totalL2TxNum := firstBlock.L2TxsNum() totalL1CommitCalldataSize := firstBlock.EstimateL1CommitCalldataSize() + totalBlocks := uint64(1) totalL1CommitGas := firstBlock.EstimateL1CommitGas() + totalL1CommitGas += 100 // warm sload per block + + getKeccakGas := func(size uint64) uint64 { + return 30 + 6*((size+31)/32) // 30 + 6 * ceil(size / 32) + } + + var totalL1MessagesPoppedBefore uint64 + if parentChunk != nil { + totalL1MessagesPoppedBefore = parentChunk.TotalL1MessagesPoppedBefore + uint64(parentChunk.TotalL1MessagesPoppedInChunk) + } + totalL1MessagesPoppedInChunk := firstBlock.NumL1Messages(totalL1MessagesPoppedBefore) + totalL1CommitGas += getKeccakGas(58*totalBlocks + 32*(totalL1MessagesPoppedInChunk+totalL2TxNum)) // Check if the first block breaks hard limits. // If so, it indicates there are bugs in sequencer, manual fix is needed. @@ -138,6 +157,12 @@ func (p *ChunkProposer) proposeChunk() (*types.Chunk, error) { totalL2TxNum += block.L2TxsNum() totalL1CommitCalldataSize += block.EstimateL1CommitCalldataSize() totalL1CommitGas += block.EstimateL1CommitGas() + totalL1CommitGas += 100 // warm sload per block + // adjust chunk hash gas cost: add one block + totalL1CommitGas -= getKeccakGas(58*totalBlocks + 32*(totalL1MessagesPoppedInChunk+totalL2TxNum)) + totalBlocks++ + totalL1MessagesPoppedInChunk += block.NumL1Messages(totalL1MessagesPoppedBefore + totalL1MessagesPoppedInChunk) + totalL1CommitGas += getKeccakGas(58*totalBlocks + 32*(totalL1MessagesPoppedInChunk+totalL2TxNum)) if totalTxGasUsed > p.maxTxGasPerChunk || totalL2TxNum > p.maxL2TxNumPerChunk || totalL1CommitCalldataSize > p.maxL1CommitCalldataSizePerChunk || diff --git a/bridge/internal/orm/chunk.go b/bridge/internal/orm/chunk.go index 4d884e134f..82ba017d24 100644 --- a/bridge/internal/orm/chunk.go +++ b/bridge/internal/orm/chunk.go @@ -149,7 +149,18 @@ func (o *Chunk) InsertChunk(ctx context.Context, chunk *types.Chunk, dbTX ...*go totalL1CommitGas += block.EstimateL1CommitGas() } - numBlocks := len(chunk.Blocks) + numBlocks := uint64(len(chunk.Blocks)) + + // Calculate additional gas costs + totalL1CommitGas += numBlocks * 100 // numBlocks times warm sload + + getKeccakGas := func(size uint64) uint64 { + return 30 + 6*((size+31)/32) // 30 + 6 * ceil(size / 32) + } + + totalL1MessagesPoppedInChunk := chunk.NumL1Messages(totalL1MessagePoppedBefore) + totalL1CommitGas += getKeccakGas(58*numBlocks + 32*(totalL1MessagesPoppedInChunk+totalL2TxNum)) // chunk hash + newChunk := Chunk{ Index: chunkIndex, Hash: hash.Hex(), @@ -163,7 +174,7 @@ func (o *Chunk) InsertChunk(ctx context.Context, chunk *types.Chunk, dbTX ...*go TotalL1CommitGas: totalL1CommitGas, StartBlockTime: chunk.Blocks[0].Header.Time, TotalL1MessagesPoppedBefore: totalL1MessagePoppedBefore, - TotalL1MessagesPoppedInChunk: uint32(chunk.NumL1Messages(totalL1MessagePoppedBefore)), + TotalL1MessagesPoppedInChunk: uint32(totalL1MessagesPoppedInChunk), ProvingStatus: int16(types.ProvingTaskUnassigned), } diff --git a/common/types/block.go b/common/types/block.go index 5955cd5c34..7940f8c3b3 100644 --- a/common/types/block.go +++ b/common/types/block.go @@ -6,7 +6,6 @@ import ( "math" "github.com/scroll-tech/go-ethereum/common" - "github.com/scroll-tech/go-ethereum/common/hexutil" "github.com/scroll-tech/go-ethereum/core/types" ) @@ -79,48 +78,23 @@ func (w *WrappedBlock) EstimateL1CommitCalldataSize() uint64 { } // EstimateL1CommitGas calculates the calldata gas in l1 commit approximately. -// TODO: This will need to be adjusted. -// The part added here is only the calldata cost, -// but we have execution cost for verifying blocks / chunks / batches and storing the batch hash. func (w *WrappedBlock) EstimateL1CommitGas() uint64 { var total uint64 + var numL1Messages uint64 for _, txData := range w.Transactions { if txData.Type == types.L1MessageTxType { + numL1Messages++ continue } - data, _ := hexutil.Decode(txData.Data) - tx := types.NewTx(&types.LegacyTx{ - Nonce: txData.Nonce, - To: txData.To, - Value: txData.Value.ToInt(), - Gas: txData.Gas, - GasPrice: txData.GasPrice.ToInt(), - Data: data, - V: txData.V.ToInt(), - R: txData.R.ToInt(), - S: txData.S.ToInt(), - }) - rlpTxData, _ := tx.MarshalBinary() - - for _, b := range rlpTxData { - if b == 0 { - total += zeroByteGas - } else { - total += nonZeroByteGas - } - } + } - var txLen [4]byte - binary.BigEndian.PutUint32(txLen[:], uint32(len(rlpTxData))) + // sload + total += numL1Messages * 2100 // numL1Messages times cold sload in L1MessageQueue + + // staticcall + total += numL1Messages * 100 // numL1Messages times call to L1MessageQueue + total += numL1Messages * 100 // numL1Messages times warm address access to L1MessageQueue - for _, b := range txLen { - if b == 0 { - total += zeroByteGas - } else { - total += nonZeroByteGas - } - } - } return total } diff --git a/common/version/version.go b/common/version/version.go index c3711186eb..936178cfc8 100644 --- a/common/version/version.go +++ b/common/version/version.go @@ -5,7 +5,7 @@ import ( "runtime/debug" ) -var tag = "v4.0.23" +var tag = "v4.0.24" var commit = func() string { if info, ok := debug.ReadBuildInfo(); ok {