From 6954c73459382d47329135705afeb3f733bad9c2 Mon Sep 17 00:00:00 2001 From: Jonas Theis <4181434+jonastheis@users.noreply.github.com> Date: Wed, 5 Mar 2025 18:00:31 +0800 Subject: [PATCH 01/45] fix(sync service): skip messages with lower index (#1134) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * skip messages with lower index * address review comments * chore: auto version bump [bot] --------- Co-authored-by: Thegaram --- params/version.go | 2 +- rollup/sync_service/sync_service.go | 32 ++++++++++++++++++++++++++--- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/params/version.go b/params/version.go index 1e641d4b57..6badac58f8 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 5 // Major version component of the current release VersionMinor = 8 // Minor version component of the current release - VersionPatch = 20 // Patch version component of the current release + VersionPatch = 21 // Patch version component of the current release VersionMeta = "mainnet" // Version metadata to append to the version string ) diff --git a/rollup/sync_service/sync_service.go b/rollup/sync_service/sync_service.go index 65743b5e7e..a8edba793b 100644 --- a/rollup/sync_service/sync_service.go +++ b/rollup/sync_service/sync_service.go @@ -1,6 +1,7 @@ package sync_service import ( + "bytes" "context" "fmt" "reflect" @@ -15,6 +16,7 @@ import ( "github.com/scroll-tech/go-ethereum/metrics" "github.com/scroll-tech/go-ethereum/node" "github.com/scroll-tech/go-ethereum/params" + "github.com/scroll-tech/go-ethereum/rlp" ) const ( @@ -272,19 +274,43 @@ func (s *SyncService) fetchMessages() { if len(msgs) > 0 { log.Debug("Received new L1 events", "fromBlock", from, "toBlock", to, "count", len(msgs)) - rawdb.WriteL1Messages(batchWriter, msgs) // collect messages in memory - numMsgsCollected += len(msgs) } for _, msg := range msgs { if msg.QueueIndex > 0 { queueIndex++ } + // check if received queue index matches expected queue index - if msg.QueueIndex != queueIndex { + if msg.QueueIndex > queueIndex { log.Error("Unexpected queue index in SyncService", "expected", queueIndex, "got", msg.QueueIndex, "msg", msg) return // do not flush inconsistent data to disk } + + // compare with stored message in database, abort if not equal, ignore if already exists + if msg.QueueIndex < queueIndex { + log.Warn("Duplicate queue index in SyncService", "expected", queueIndex, "got", msg.QueueIndex) + + receivedMsgBytes, err := rlp.EncodeToBytes(msg) + if err != nil { + log.Error("Failed to encode message", "err", err) + return + } + storedMsgBytes := rawdb.ReadL1MessageRLP(s.db, msg.QueueIndex) + if !bytes.Equal(storedMsgBytes, receivedMsgBytes) { + storedL1Message := rawdb.ReadL1Message(s.db, msg.QueueIndex) + log.Error("Stored message at same queue index does not match received message", "queueIndex", msg.QueueIndex, "expected", storedL1Message, "got", msg) + return + } + + // already exists, ignore + queueIndex-- + continue + } + + // store message to database (collected in memory and flushed periodically) + rawdb.WriteL1Message(batchWriter, msg) + numMsgsCollected++ } numBlocksPendingDbWrite += to - from + 1 From e62c6f08e2cb233ca0e14d01bc3f41f2edaea369 Mon Sep 17 00:00:00 2001 From: Jonas Theis <4181434+jonastheis@users.noreply.github.com> Date: Wed, 5 Mar 2025 18:26:41 +0800 Subject: [PATCH 02/45] fix: add ABI with removed functions from contract (#1135) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add ABI with removed functions from contract * chore: auto version bump [bot] --------- Co-authored-by: Péter Garamvölgyi Co-authored-by: Thegaram --- params/version.go | 2 +- rollup/da_syncer/batch_queue.go | 2 +- rollup/l1/abi.go | 2 +- rollup/l1/abi_test.go | 7 +++++++ 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/params/version.go b/params/version.go index 6badac58f8..478902b22a 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 5 // Major version component of the current release VersionMinor = 8 // Minor version component of the current release - VersionPatch = 21 // Patch version component of the current release + VersionPatch = 22 // Patch version component of the current release VersionMeta = "mainnet" // Version metadata to append to the version string ) diff --git a/rollup/da_syncer/batch_queue.go b/rollup/da_syncer/batch_queue.go index 1850f53c19..b0b2614004 100644 --- a/rollup/da_syncer/batch_queue.go +++ b/rollup/da_syncer/batch_queue.go @@ -159,7 +159,7 @@ func (bq *BatchQueue) processAndDeleteBatch(batch da.Entry) da.EntryWithBlocks { // In case of a restart or crash we can continue from the last processed batch (and its metadata). rawdb.WriteDAProcessedBatchMeta(bq.db, bq.previousBatch) - log.Info("processing batch", "batchIndex", entryWithBlocks.BatchIndex(), "L1BlockNumber", entryWithBlocks.L1BlockNumber(), "totalL1MessagesPopped", entryWithBlocks.TotalL1MessagesPopped(), "previousBatch", bq.previousBatch.BatchIndex, "previousL1BlockNumber", bq.previousBatch.L1BlockNumber, "previous TotalL1MessagesPopped", bq.previousBatch.TotalL1MessagesPopped) + log.Debug("processing batch", "batchIndex", entryWithBlocks.BatchIndex(), "L1BlockNumber", entryWithBlocks.L1BlockNumber(), "totalL1MessagesPopped", entryWithBlocks.TotalL1MessagesPopped(), "previousBatch", bq.previousBatch.BatchIndex, "previousL1BlockNumber", bq.previousBatch.L1BlockNumber, "previous TotalL1MessagesPopped", bq.previousBatch.TotalL1MessagesPopped) bq.previousBatch = &rawdb.DAProcessedBatchMeta{ L1BlockNumber: entryWithBlocks.L1BlockNumber(), diff --git a/rollup/l1/abi.go b/rollup/l1/abi.go index bcc35b5a75..e0d710bc84 100644 --- a/rollup/l1/abi.go +++ b/rollup/l1/abi.go @@ -24,7 +24,7 @@ func init() { // ScrollChainMetaData contains ABI of the ScrollChain contract. var ScrollChainMetaData = &bind.MetaData{ - ABI: "[{\"type\":\"constructor\",\"inputs\":[{\"name\":\"_chainId\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"_messageQueueV1\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"_messageQueueV2\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"_verifier\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"_systemConfig\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"addProver\",\"inputs\":[{\"name\":\"_account\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"addSequencer\",\"inputs\":[{\"name\":\"_account\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"commitAndFinalizeBatch\",\"inputs\":[{\"name\":\"version\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"parentBatchHash\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"finalizeStruct\",\"type\":\"tuple\",\"internalType\":\"struct IScrollChain.FinalizeStruct\",\"components\":[{\"name\":\"batchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"lastProcessedQueueIndex\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"postStateRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"withdrawRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"zkProof\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"commitBatchWithBlobProof\",\"inputs\":[{\"name\":\"_version\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"_parentBatchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"_chunks\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"},{\"name\":\"_skippedL1MessageBitmap\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"_blobDataProof\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"commitBatches\",\"inputs\":[{\"name\":\"version\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"parentBatchHash\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"lastBatchHash\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"committedBatches\",\"inputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"disableEnforcedBatchMode\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"finalizeBundlePostEuclidV2\",\"inputs\":[{\"name\":\"batchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"lastProcessedQueueIndex\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"postStateRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"withdrawRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"aggrProof\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"finalizeBundleWithProof\",\"inputs\":[{\"name\":\"batchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"postStateRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"withdrawRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"aggrProof\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"finalizedStateRoots\",\"inputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"importGenesisBatch\",\"inputs\":[{\"name\":\"_batchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"_stateRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"initialEuclidBatchIndex\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"initialize\",\"inputs\":[{\"name\":\"_messageQueue\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"_verifier\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"_maxNumTxInChunk\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"initializeV2\",\"inputs\":[],\"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\":\"isEnforcedModeEnabled\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"isProver\",\"inputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"isSequencer\",\"inputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"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\":\"maxNumTxInChunk\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"messageQueueV1\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"messageQueueV2\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"miscData\",\"inputs\":[],\"outputs\":[{\"name\":\"lastCommittedBatchIndex\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"lastFinalizedBatchIndex\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"lastFinalizeTimestamp\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"flags\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"reserved\",\"type\":\"uint88\",\"internalType\":\"uint88\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"owner\",\"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\":\"removeProver\",\"inputs\":[{\"name\":\"_account\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"removeSequencer\",\"inputs\":[{\"name\":\"_account\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"renounceOwnership\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"revertBatch\",\"inputs\":[{\"name\":\"batchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setPause\",\"inputs\":[{\"name\":\"_status\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"systemConfig\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"transferOwnership\",\"inputs\":[{\"name\":\"newOwner\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"updateMaxNumTxInChunk\",\"inputs\":[{\"name\":\"_maxNumTxInChunk\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"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\":\"OwnershipTransferred\",\"inputs\":[{\"name\":\"previousOwner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"newOwner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Paused\",\"inputs\":[{\"name\":\"account\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"RevertBatch\",\"inputs\":[{\"name\":\"batchIndex\",\"type\":\"uint256\",\"indexed\":true,\"internalType\":\"uint256\"},{\"name\":\"batchHash\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"}],\"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\":\"Unpaused\",\"inputs\":[{\"name\":\"account\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"UpdateEnforcedBatchMode\",\"inputs\":[{\"name\":\"enabled\",\"type\":\"bool\",\"indexed\":false,\"internalType\":\"bool\"},{\"name\":\"lastCommittedBatchIndex\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"UpdateMaxNumTxInChunk\",\"inputs\":[{\"name\":\"oldMaxNumTxInChunk\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"},{\"name\":\"newMaxNumTxInChunk\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"UpdateProver\",\"inputs\":[{\"name\":\"account\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"status\",\"type\":\"bool\",\"indexed\":false,\"internalType\":\"bool\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"UpdateSequencer\",\"inputs\":[{\"name\":\"account\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"status\",\"type\":\"bool\",\"indexed\":false,\"internalType\":\"bool\"}],\"anonymous\":false},{\"type\":\"error\",\"name\":\"ErrorAccountIsNotEOA\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorBatchHeaderV0LengthTooSmall\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorBatchHeaderV1LengthTooSmall\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorBatchHeaderV3LengthMismatch\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorBatchHeaderV7LengthMismatch\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorBatchIsAlreadyCommitted\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorBatchIsAlreadyVerified\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorBatchIsEmpty\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorBatchNotCommitted\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorCallPointEvaluationPrecompileFailed\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorCallerIsNotProver\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorCallerIsNotSequencer\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorCannotDowngradeVersion\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorEuclidForkEnabled\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorFinalizePreAndPostEuclidBatchInOneBundle\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorFoundMultipleBlobs\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorGenesisBatchHasNonZeroField\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorGenesisBatchImported\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorGenesisDataHashIsZero\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorGenesisParentBatchHashIsNonZero\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorInEnforcedBatchMode\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorIncorrectBatchHash\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorIncorrectBatchVersion\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorIncorrectBitmapLength\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorIncorrectBitmapLengthV0\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorIncorrectBitmapLengthV1\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorIncorrectChunkLengthV1\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorLastL1MessageSkipped\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorNoBlobFound\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorNoBlockInChunkV1\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorNotAllV1MessagesAreFinalized\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorNotInEnforcedBatchMode\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorNumTxsLessThanNumL1Msgs\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorRevertFinalizedBatch\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorStateRootIsZero\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorTooManyTxsInOneChunk\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorUnexpectedPointEvaluationPrecompileOutput\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorV5BatchContainsTransactions\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorV5BatchNotContainsOnlyOneBlock\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorV5BatchNotContainsOnlyOneChunk\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ErrorZeroAddress\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InconsistentBatchHash\",\"inputs\":[{\"name\":\"batchIndex\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"expected\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"actual\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}]}]", + ABI: "[{\"type\":\"function\",\"name\":\"commitAndFinalizeBatch\",\"inputs\":[{\"name\":\"version\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"parentBatchHash\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"finalizeStruct\",\"type\":\"tuple\",\"internalType\":\"struct ScrollChainInterface.FinalizeStruct\",\"components\":[{\"name\":\"batchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"totalL1MessagesPoppedOverall\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"postStateRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"withdrawRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"zkProof\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"commitBatch\",\"inputs\":[{\"name\":\"version\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"parentBatchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"chunks\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"},{\"name\":\"skippedL1MessageBitmap\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"commitBatchWithBlobProof\",\"inputs\":[{\"name\":\"version\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"parentBatchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"chunks\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"},{\"name\":\"skippedL1MessageBitmap\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"blobDataProof\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"commitBatches\",\"inputs\":[{\"name\":\"version\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"parentBatchHash\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"lastBatchHash\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"finalizeBatch\",\"inputs\":[{\"name\":\"_batchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"_prevStateRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"_postStateRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"_withdrawRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"finalizeBatch4844\",\"inputs\":[{\"name\":\"_batchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"_postStateRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"_withdrawRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"_blobDataProof\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"finalizeBatchWithProof\",\"inputs\":[{\"name\":\"batchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"prevStateRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"postStateRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"withdrawRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"aggrProof\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"finalizeBatchWithProof4844\",\"inputs\":[{\"name\":\"batchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"prevStateRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"postStateRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"withdrawRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"blobDataProof\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"aggrProof\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"finalizeBundle\",\"inputs\":[{\"name\":\"batchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"postStateRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"withdrawRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"finalizeBundlePostEuclidV2\",\"inputs\":[{\"name\":\"batchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"totalL1MessagesPoppedOverall\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"postStateRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"withdrawRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"aggrProof\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"finalizeBundlePostEuclidV2NoProof\",\"inputs\":[{\"name\":\"batchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"totalL1MessagesPoppedOverall\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"postStateRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"withdrawRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"finalizeBundleWithProof\",\"inputs\":[{\"name\":\"batchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"postStateRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"withdrawRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"aggrProof\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"finalizeEuclidInitialBatch\",\"inputs\":[{\"name\":\"postStateRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"revertBatch\",\"inputs\":[{\"name\":\"batchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"count\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"revertBatch\",\"inputs\":[{\"name\":\"batchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"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\":\"RevertBatch\",\"inputs\":[{\"name\":\"batchIndex\",\"type\":\"uint256\",\"indexed\":true,\"internalType\":\"uint256\"},{\"name\":\"batchHash\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"}],\"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\":\"UpdateEnforcedBatchMode\",\"inputs\":[{\"name\":\"enabled\",\"type\":\"bool\",\"indexed\":false,\"internalType\":\"bool\"},{\"name\":\"lastCommittedBatchIndex\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false}]", } // L1MessageQueueMetaDataManual contains all meta data concerning the L1MessageQueue contract. diff --git a/rollup/l1/abi_test.go b/rollup/l1/abi_test.go index 59fcb21ae5..7ad874d3d8 100644 --- a/rollup/l1/abi_test.go +++ b/rollup/l1/abi_test.go @@ -18,6 +18,13 @@ func TestEventSignatures(t *testing.T) { assert.Equal(t, crypto.Keccak256Hash([]byte("FinalizeBatch(uint256,bytes32,bytes32,bytes32)")), ScrollChainABI.Events[finalizeBatchEventName].ID) } +func TestMethodSignatures(t *testing.T) { + assert.Equal(t, crypto.Keccak256Hash([]byte("commitBatch(uint8,bytes,bytes[],bytes)")).Bytes()[:4], ScrollChainABI.Methods[commitBatchMethodName].ID) + assert.Equal(t, crypto.Keccak256Hash([]byte("commitBatchWithBlobProof(uint8,bytes,bytes[],bytes,bytes)")).Bytes()[:4], ScrollChainABI.Methods[commitBatchWithBlobProofMethodName].ID) + assert.Equal(t, crypto.Keccak256Hash([]byte("commitBatches(uint8,bytes32,bytes32)")).Bytes()[:4], ScrollChainABI.Methods[commitBatchesV7MethodName].ID) + assert.Equal(t, crypto.Keccak256Hash([]byte("finalizeBundlePostEuclidV2(bytes,uint256,bytes32,bytes32,bytes)")).Bytes()[:4], ScrollChainABI.Methods[finalizeBundlePostEuclidV2MethodName].ID) +} + func TestUnpackLog(t *testing.T) { mockBatchIndex := big.NewInt(123) finishMockBatchIndex := big.NewInt(125) From 478940e79601af7b041b4521139815bb5360a51b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Wed, 5 Mar 2025 16:10:38 +0100 Subject: [PATCH 03/45] feat: Euclid release on Scroll Sepolia (#1122) * feat: Euclid release on Scroll Sepolia (wip) * update system contract consensus params * update timestamp * improve logs * fix * adjust logs * undo some changes --- consensus/system_contract/system_contract.go | 21 +++++++++++----- params/config.go | 25 +++++++++++++------- params/version.go | 2 +- 3 files changed, 33 insertions(+), 15 deletions(-) diff --git a/consensus/system_contract/system_contract.go b/consensus/system_contract/system_contract.go index 8e99c9cfa3..a93968e873 100644 --- a/consensus/system_contract/system_contract.go +++ b/consensus/system_contract/system_contract.go @@ -48,8 +48,9 @@ func New(ctx context.Context, config *params.SystemContractConfig, client sync_s } if err := s.fetchAddressFromL1(); err != nil { - log.Error("failed to fetch signer address from L1", "err", err) + log.Error("Failed to fetch signer address from L1", "err", err) } + return s } @@ -80,7 +81,7 @@ func (s *SystemContract) Start() { return case <-syncTicker.C: if err := s.fetchAddressFromL1(); err != nil { - log.Error("failed to fetch signer address from L1", "err", err) + log.Error("Failed to fetch signer address from L1", "err", err) } } } @@ -94,16 +95,24 @@ func (s *SystemContract) fetchAddressFromL1() error { } bAddress := common.BytesToAddress(address) + s.lock.Lock() + defer s.lock.Unlock() + // Validate the address is not empty if bAddress == (common.Address{}) { - return fmt.Errorf("retrieved empty signer address from L1 System Contract: contract=%s, slot=%s", s.config.SystemContractAddress.Hex(), s.config.SystemContractSlot.Hex()) + log.Debug("Retrieved empty signer address from L1 System Contract", "contract", s.config.SystemContractAddress.Hex(), "slot", s.config.SystemContractSlot.Hex()) + + // Not initialized yet -- we don't consider this an error + if s.signerAddressL1 == (common.Address{}) { + log.Warn("System Contract signer address not initialized") + return nil + } + + return fmt.Errorf("retrieved empty signer address from L1 System Contract") } log.Debug("Read address from system contract", "address", bAddress.Hex()) - s.lock.Lock() - defer s.lock.Unlock() - if s.signerAddressL1 != bAddress { s.signerAddressL1 = bAddress log.Info("Updated new signer from L1 system contract", "signer", bAddress.Hex()) diff --git a/params/config.go b/params/config.go index 1684a1bc3f..d955dd161b 100644 --- a/params/config.go +++ b/params/config.go @@ -328,20 +328,29 @@ var ( CurieBlock: big.NewInt(4740239), DarwinTime: newUint64(1723622400), DarwinV2Time: newUint64(1724832000), + EuclidTime: newUint64(1741680000), + EuclidV2Time: newUint64(1741852800), Clique: &CliqueConfig{ Period: 3, Epoch: 30000, }, + SystemContract: &SystemContractConfig{ + Period: 3, + SystemContractAddress: common.HexToAddress("0xC706Ba9fa4fedF4507CB7A898b4766c1bbf9be57"), + SystemContractSlot: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000067"), + }, Scroll: ScrollConfig{ UseZktrie: true, MaxTxPerBlock: &ScrollMaxTxPerBlock, MaxTxPayloadBytesPerBlock: &ScrollMaxTxPayloadBytesPerBlock, FeeVaultAddress: &rcfg.ScrollFeeVaultAddress, L1Config: &L1Config{ - L1ChainId: 11155111, - L1MessageQueueAddress: common.HexToAddress("0xF0B2293F5D834eAe920c6974D50957A1732de763"), - NumL1MessagesPerBlock: 10, - ScrollChainAddress: common.HexToAddress("0x2D567EcE699Eabe5afCd141eDB7A4f2D0D6ce8a0"), + L1ChainId: 11155111, + L1MessageQueueAddress: common.HexToAddress("0xF0B2293F5D834eAe920c6974D50957A1732de763"), + L1MessageQueueV2Address: common.HexToAddress("0xA0673eC0A48aa924f067F1274EcD281A10c5f19F"), + L1MessageQueueV2DeploymentBlock: 7773746, + NumL1MessagesPerBlock: 10, + ScrollChainAddress: common.HexToAddress("0x2D567EcE699Eabe5afCd141eDB7A4f2D0D6ce8a0"), }, GenesisStateRoot: &ScrollSepoliaGenesisState, }, @@ -692,8 +701,8 @@ type ScrollConfig struct { type L1Config struct { L1ChainId uint64 `json:"l1ChainId,string,omitempty"` L1MessageQueueAddress common.Address `json:"l1MessageQueueAddress,omitempty"` - L1MessageQueueV2Address common.Address `json:"l1MessageQueueV2Address,omitempty"` // TODO: set address once known - L1MessageQueueV2DeploymentBlock uint64 `json:"l1MessageQueueV2DeploymentBlock,omitempty"` // TODO: set block number once known + L1MessageQueueV2Address common.Address `json:"l1MessageQueueV2Address,omitempty"` + L1MessageQueueV2DeploymentBlock uint64 `json:"l1MessageQueueV2DeploymentBlock,omitempty"` NumL1MessagesPerBlock uint64 `json:"numL1MessagesPerBlock,string,omitempty"` ScrollChainAddress common.Address `json:"scrollChainAddress,omitempty"` } @@ -703,8 +712,8 @@ func (c *L1Config) String() string { return "" } - return fmt.Sprintf("{l1ChainId: %v, l1MessageQueueAddress: %v, numL1MessagesPerBlock: %v, ScrollChainAddress: %v}", - c.L1ChainId, c.L1MessageQueueAddress.Hex(), c.NumL1MessagesPerBlock, c.ScrollChainAddress.Hex()) + return fmt.Sprintf("{l1ChainId: %v, l1MessageQueueAddress: %v, l1MessageQueueV2Address: %v, l1MessageQueueV2DeploymentBlock: %v, numL1MessagesPerBlock: %v, ScrollChainAddress: %v}", + c.L1ChainId, c.L1MessageQueueAddress.Hex(), c.L1MessageQueueV2Address.Hex(), c.L1MessageQueueV2DeploymentBlock, c.NumL1MessagesPerBlock, c.ScrollChainAddress.Hex()) } func (s ScrollConfig) FeeVaultEnabled() bool { diff --git a/params/version.go b/params/version.go index 478902b22a..70874fe4f1 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 5 // Major version component of the current release VersionMinor = 8 // Minor version component of the current release - VersionPatch = 22 // Patch version component of the current release + VersionPatch = 23 // Patch version component of the current release VersionMeta = "mainnet" // Version metadata to append to the version string ) From 61cfff3bc5ccfc6b665bec116cac7c0184575cbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Thu, 6 Mar 2025 13:08:59 +0100 Subject: [PATCH 04/45] fix: print genesisStateRoot on startup (#1136) --- params/config.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/params/config.go b/params/config.go index d955dd161b..a563041d23 100644 --- a/params/config.go +++ b/params/config.go @@ -739,8 +739,13 @@ func (s ScrollConfig) String() string { maxTxPayloadBytesPerBlock = fmt.Sprintf("%v", *s.MaxTxPayloadBytesPerBlock) } - return fmt.Sprintf("{useZktrie: %v, maxTxPerBlock: %v, MaxTxPayloadBytesPerBlock: %v, feeVaultAddress: %v, l1Config: %v}", - s.UseZktrie, maxTxPerBlock, maxTxPayloadBytesPerBlock, s.FeeVaultAddress, s.L1Config.String()) + genesisStateRoot := "" + if s.GenesisStateRoot != nil { + genesisStateRoot = fmt.Sprintf("%v", *s.GenesisStateRoot) + } + + return fmt.Sprintf("{useZktrie: %v, maxTxPerBlock: %v, MaxTxPayloadBytesPerBlock: %v, feeVaultAddress: %v, l1Config: %v, genesisStateRoot: %v}", + s.UseZktrie, maxTxPerBlock, maxTxPayloadBytesPerBlock, s.FeeVaultAddress, s.L1Config.String(), genesisStateRoot) } // IsValidTxCount returns whether the given block's transaction count is below the limit. From 974cfcd726c3270ccc20fa8f0c19b6ced1974ba3 Mon Sep 17 00:00:00 2001 From: Jonas Theis <4181434+jonastheis@users.noreply.github.com> Date: Wed, 12 Mar 2025 13:34:56 +0800 Subject: [PATCH 05/45] feat(L1 follower): add `commitAndFinalizeBatch` method (#1140) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add commitAndFinalizeBatch method to L1Reader to support enforced batches in L1 follower and rollup verifier * chore: auto version bump [bot] * update ABI --------- Co-authored-by: jonastheis --- params/version.go | 2 +- rollup/l1/abi.go | 19 ++++++++++++++++++- rollup/l1/abi_test.go | 1 + rollup/l1/reader.go | 26 +++++++++++++++++++++++++- 4 files changed, 45 insertions(+), 3 deletions(-) diff --git a/params/version.go b/params/version.go index 70874fe4f1..519324c809 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 5 // Major version component of the current release VersionMinor = 8 // Minor version component of the current release - VersionPatch = 23 // Patch version component of the current release + VersionPatch = 24 // Patch version component of the current release VersionMeta = "mainnet" // Version metadata to append to the version string ) diff --git a/rollup/l1/abi.go b/rollup/l1/abi.go index e0d710bc84..17b00c7cd1 100644 --- a/rollup/l1/abi.go +++ b/rollup/l1/abi.go @@ -24,7 +24,7 @@ func init() { // ScrollChainMetaData contains ABI of the ScrollChain contract. var ScrollChainMetaData = &bind.MetaData{ - ABI: "[{\"type\":\"function\",\"name\":\"commitAndFinalizeBatch\",\"inputs\":[{\"name\":\"version\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"parentBatchHash\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"finalizeStruct\",\"type\":\"tuple\",\"internalType\":\"struct ScrollChainInterface.FinalizeStruct\",\"components\":[{\"name\":\"batchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"totalL1MessagesPoppedOverall\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"postStateRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"withdrawRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"zkProof\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"commitBatch\",\"inputs\":[{\"name\":\"version\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"parentBatchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"chunks\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"},{\"name\":\"skippedL1MessageBitmap\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"commitBatchWithBlobProof\",\"inputs\":[{\"name\":\"version\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"parentBatchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"chunks\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"},{\"name\":\"skippedL1MessageBitmap\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"blobDataProof\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"commitBatches\",\"inputs\":[{\"name\":\"version\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"parentBatchHash\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"lastBatchHash\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"finalizeBatch\",\"inputs\":[{\"name\":\"_batchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"_prevStateRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"_postStateRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"_withdrawRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"finalizeBatch4844\",\"inputs\":[{\"name\":\"_batchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"_postStateRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"_withdrawRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"_blobDataProof\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"finalizeBatchWithProof\",\"inputs\":[{\"name\":\"batchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"prevStateRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"postStateRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"withdrawRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"aggrProof\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"finalizeBatchWithProof4844\",\"inputs\":[{\"name\":\"batchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"prevStateRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"postStateRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"withdrawRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"blobDataProof\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"aggrProof\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"finalizeBundle\",\"inputs\":[{\"name\":\"batchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"postStateRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"withdrawRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"finalizeBundlePostEuclidV2\",\"inputs\":[{\"name\":\"batchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"totalL1MessagesPoppedOverall\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"postStateRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"withdrawRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"aggrProof\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"finalizeBundlePostEuclidV2NoProof\",\"inputs\":[{\"name\":\"batchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"totalL1MessagesPoppedOverall\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"postStateRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"withdrawRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"finalizeBundleWithProof\",\"inputs\":[{\"name\":\"batchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"postStateRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"withdrawRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"aggrProof\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"finalizeEuclidInitialBatch\",\"inputs\":[{\"name\":\"postStateRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"revertBatch\",\"inputs\":[{\"name\":\"batchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"count\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"revertBatch\",\"inputs\":[{\"name\":\"batchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"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\":\"RevertBatch\",\"inputs\":[{\"name\":\"batchIndex\",\"type\":\"uint256\",\"indexed\":true,\"internalType\":\"uint256\"},{\"name\":\"batchHash\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"}],\"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\":\"UpdateEnforcedBatchMode\",\"inputs\":[{\"name\":\"enabled\",\"type\":\"bool\",\"indexed\":false,\"internalType\":\"bool\"},{\"name\":\"lastCommittedBatchIndex\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false}]", + ABI: "[{\"type\":\"function\",\"name\":\"commitAndFinalizeBatch\",\"inputs\":[{\"name\":\"version\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"parentBatchHash\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"finalizeStruct\",\"type\":\"tuple\",\"internalType\":\"struct ScrollChainInterface.FinalizeStruct\",\"components\":[{\"name\":\"batchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"totalL1MessagesPoppedOverall\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"postStateRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"withdrawRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"zkProof\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"commitBatch\",\"inputs\":[{\"name\":\"version\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"parentBatchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"chunks\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"},{\"name\":\"skippedL1MessageBitmap\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"commitBatchWithBlobProof\",\"inputs\":[{\"name\":\"version\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"parentBatchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"chunks\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"},{\"name\":\"skippedL1MessageBitmap\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"blobDataProof\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"commitBatches\",\"inputs\":[{\"name\":\"version\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"parentBatchHash\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"lastBatchHash\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"committedBatches\",\"inputs\":[{\"name\":\"batchIndex\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"finalizeBatch\",\"inputs\":[{\"name\":\"_batchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"_prevStateRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"_postStateRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"_withdrawRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"finalizeBatch4844\",\"inputs\":[{\"name\":\"_batchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"_postStateRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"_withdrawRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"_blobDataProof\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"finalizeBatchWithProof\",\"inputs\":[{\"name\":\"batchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"prevStateRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"postStateRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"withdrawRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"aggrProof\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"finalizeBatchWithProof4844\",\"inputs\":[{\"name\":\"batchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"prevStateRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"postStateRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"withdrawRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"blobDataProof\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"aggrProof\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"finalizeBundle\",\"inputs\":[{\"name\":\"batchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"postStateRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"withdrawRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"finalizeBundlePostEuclidV2\",\"inputs\":[{\"name\":\"batchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"totalL1MessagesPoppedOverall\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"postStateRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"withdrawRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"aggrProof\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"finalizeBundlePostEuclidV2NoProof\",\"inputs\":[{\"name\":\"batchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"totalL1MessagesPoppedOverall\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"postStateRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"withdrawRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"finalizeBundleWithProof\",\"inputs\":[{\"name\":\"batchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"postStateRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"withdrawRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"aggrProof\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"finalizeEuclidInitialBatch\",\"inputs\":[{\"name\":\"postStateRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"finalizedStateRoots\",\"inputs\":[{\"name\":\"batchIndex\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"importGenesisBatch\",\"inputs\":[{\"name\":\"_batchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"_stateRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"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\":\"lastFinalizedBatchIndex\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"revertBatch\",\"inputs\":[{\"name\":\"batchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"count\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"revertBatch\",\"inputs\":[{\"name\":\"batchHeader\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"withdrawRoots\",\"inputs\":[{\"name\":\"batchIndex\",\"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\":\"RevertBatch\",\"inputs\":[{\"name\":\"batchIndex\",\"type\":\"uint256\",\"indexed\":true,\"internalType\":\"uint256\"},{\"name\":\"batchHash\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"}],\"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\":\"UpdateEnforcedBatchMode\",\"inputs\":[{\"name\":\"enabled\",\"type\":\"bool\",\"indexed\":false,\"internalType\":\"bool\"},{\"name\":\"lastCommittedBatchIndex\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false}]", } // L1MessageQueueMetaDataManual contains all meta data concerning the L1MessageQueue contract. @@ -45,6 +45,7 @@ const ( commitBatchMethodName = "commitBatch" commitBatchWithBlobProofMethodName = "commitBatchWithBlobProof" commitBatchesV7MethodName = "commitBatches" + commitAndFinalizeBatch = "commitAndFinalizeBatch" finalizeBundlePostEuclidV2MethodName = "finalizeBundlePostEuclidV2" @@ -362,3 +363,19 @@ func newFinalizeBatchArgs(method *abi.Method, values []any) (*FinalizeBatchArgs, } return &args, nil } + +type commitAndFinalizeBatchArgs struct { + Version uint8 + ParentBatchHash common.Hash + FinalizeStruct FinalizeBatchArgs +} + +func newCommitAndFinalizeBatchArgs(method *abi.Method, values []any) (*commitAndFinalizeBatchArgs, error) { + var args commitAndFinalizeBatchArgs + err := method.Inputs.Copy(&args, values) + if err != nil { + return nil, err + } + + return &args, nil +} diff --git a/rollup/l1/abi_test.go b/rollup/l1/abi_test.go index 7ad874d3d8..43533bd68e 100644 --- a/rollup/l1/abi_test.go +++ b/rollup/l1/abi_test.go @@ -22,6 +22,7 @@ func TestMethodSignatures(t *testing.T) { assert.Equal(t, crypto.Keccak256Hash([]byte("commitBatch(uint8,bytes,bytes[],bytes)")).Bytes()[:4], ScrollChainABI.Methods[commitBatchMethodName].ID) assert.Equal(t, crypto.Keccak256Hash([]byte("commitBatchWithBlobProof(uint8,bytes,bytes[],bytes,bytes)")).Bytes()[:4], ScrollChainABI.Methods[commitBatchWithBlobProofMethodName].ID) assert.Equal(t, crypto.Keccak256Hash([]byte("commitBatches(uint8,bytes32,bytes32)")).Bytes()[:4], ScrollChainABI.Methods[commitBatchesV7MethodName].ID) + assert.Equal(t, crypto.Keccak256Hash([]byte("commitAndFinalizeBatch(uint8,bytes32,(bytes,uint256,bytes32,bytes32,bytes))")).Bytes()[:4], ScrollChainABI.Methods[commitAndFinalizeBatch].ID) assert.Equal(t, crypto.Keccak256Hash([]byte("finalizeBundlePostEuclidV2(bytes,uint256,bytes32,bytes32,bytes)")).Bytes()[:4], ScrollChainABI.Methods[finalizeBundlePostEuclidV2MethodName].ID) } diff --git a/rollup/l1/reader.go b/rollup/l1/reader.go index 6c39621d91..f2b5ce6c7e 100644 --- a/rollup/l1/reader.go +++ b/rollup/l1/reader.go @@ -6,6 +6,8 @@ import ( "fmt" "math/big" + "github.com/scroll-tech/da-codec/encoding" + "github.com/scroll-tech/go-ethereum" "github.com/scroll-tech/go-ethereum/accounts/abi" "github.com/scroll-tech/go-ethereum/common" @@ -97,7 +99,7 @@ func (r *Reader) FinalizedL1MessageQueueIndex(blockNumber uint64) (uint64, error return next - 1, nil } -func (r *Reader) LatestFinalizedBatch(blockNumber uint64) (uint64, error) { +func (r *Reader) LatestFinalizedBatchIndex(blockNumber uint64) (uint64, error) { data, err := r.scrollChainABI.Pack(lastFinalizedBatchIndex) if err != nil { return 0, fmt.Errorf("failed to pack %s: %w", lastFinalizedBatchIndex, err) @@ -398,6 +400,28 @@ func (r *Reader) FetchCommitTxData(commitEvent *CommitBatchEvent) (*CommitBatchA if err != nil { return nil, fmt.Errorf("failed to decode calldata into commitBatch args %s, values: %+v, err: %w", commitBatchesV7MethodName, values, err) } + } else if method.Name == commitAndFinalizeBatch { + commitAndFinalizeArgs, err := newCommitAndFinalizeBatchArgs(method, values) + if err != nil { + return nil, fmt.Errorf("failed to decode calldata into commitAndFinalizeBatch args %s, values: %+v, err: %w", commitAndFinalizeBatch, values, err) + } + + // in commitAndFinalizeBatch, the last batch hash is encoded in the finalize struct as this is the only batch we're + // committing when calling this function. + codec, err := encoding.CodecFromVersion(encoding.CodecVersion(commitAndFinalizeArgs.Version)) + if err != nil { + return nil, fmt.Errorf("failed to get codec from version %d, err: %w", commitAndFinalizeArgs.Version, err) + } + daBatch, err := codec.NewDABatchFromBytes(commitAndFinalizeArgs.FinalizeStruct.BatchHeader) + if err != nil { + return nil, fmt.Errorf("failed to decode daBatch from bytes, err: %w", err) + } + + args = &CommitBatchArgs{ + Version: commitAndFinalizeArgs.Version, + ParentBatchHash: commitAndFinalizeArgs.ParentBatchHash, + LastBatchHash: daBatch.Hash(), + } } else { return nil, fmt.Errorf("unknown method name for commit transaction: %s", method.Name) } From aec00892714ad6ba7a7cd6b0078e47186a621158 Mon Sep 17 00:00:00 2001 From: Jonas Theis <4181434+jonastheis@users.noreply.github.com> Date: Thu, 13 Mar 2025 23:16:34 +0800 Subject: [PATCH 06/45] fix(verifier,l1follower): update da-codec to latest version (#1143) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * update da-codec to latest version * fix nodes db * chore: auto version bump [bot] * fix comment * fix L1 block number * fix comment * fix error trigger * adjust comment --------- Co-authored-by: jonastheis --- go.mod | 2 +- go.sum | 4 ++-- params/version.go | 2 +- .../rollup_sync_service.go | 19 +++++++++++++++++++ 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index ea4204d370..1c3397b960 100644 --- a/go.mod +++ b/go.mod @@ -51,7 +51,7 @@ require ( github.com/prometheus/tsdb v0.7.1 github.com/rjeczalik/notify v0.9.1 github.com/rs/cors v1.7.0 - github.com/scroll-tech/da-codec v0.1.3-0.20250226072559-f8a8d3898f54 + github.com/scroll-tech/da-codec v0.1.3-0.20250313120912-344f2d5e33e1 github.com/scroll-tech/zktrie v0.8.4 github.com/shirou/gopsutil v3.21.11+incompatible github.com/sourcegraph/conc v0.3.0 diff --git a/go.sum b/go.sum index 6e028c2e64..a6c9f2ad2a 100644 --- a/go.sum +++ b/go.sum @@ -396,8 +396,8 @@ github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncj github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/scroll-tech/da-codec v0.1.3-0.20250226072559-f8a8d3898f54 h1:qVpsVu1J91opTn6HYeuzWcBRVhQmPR8g05i+PlOjlI4= -github.com/scroll-tech/da-codec v0.1.3-0.20250226072559-f8a8d3898f54/go.mod h1:xECEHZLVzbdUn+tNbRJhRIjLGTOTmnFQuTgUTeVLX58= +github.com/scroll-tech/da-codec v0.1.3-0.20250313120912-344f2d5e33e1 h1:Dhd58LE1D+dnoxpgLVeQBMF9uweL/fhQfZHWtWSiOlE= +github.com/scroll-tech/da-codec v0.1.3-0.20250313120912-344f2d5e33e1/go.mod h1:yhTS9OVC0xQGhg7DN5iV5KZJvnSIlFWAxDdp+6jxQtY= github.com/scroll-tech/zktrie v0.8.4 h1:UagmnZ4Z3ITCk+aUq9NQZJNAwnWl4gSxsLb2Nl7IgRE= github.com/scroll-tech/zktrie v0.8.4/go.mod h1:XvNo7vAk8yxNyTjBDj5WIiFzYW4bx/gJ78+NK6Zn6Uk= github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= diff --git a/params/version.go b/params/version.go index 519324c809..d085d9e9b8 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 5 // Major version component of the current release VersionMinor = 8 // Minor version component of the current release - VersionPatch = 24 // Patch version component of the current release + VersionPatch = 25 // Patch version component of the current release VersionMeta = "mainnet" // Version metadata to append to the version string ) diff --git a/rollup/rollup_sync_service/rollup_sync_service.go b/rollup/rollup_sync_service/rollup_sync_service.go index c0d79d8339..8b65d0f8b3 100644 --- a/rollup/rollup_sync_service/rollup_sync_service.go +++ b/rollup/rollup_sync_service/rollup_sync_service.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "os" + "strings" "sync" "time" @@ -42,6 +43,8 @@ const ( defaultLogInterval = 5 * time.Minute ) +var ErrShouldResetSyncHeight = errors.New("ErrShouldResetSyncHeight") + // RollupSyncService collects ScrollChain batch commit/revert/finalize events and stores metadata into db. type RollupSyncService struct { ctx context.Context @@ -205,6 +208,12 @@ func (s *RollupSyncService) fetchRollupEvents() error { } if err = s.updateRollupEvents(daEntries); err != nil { + if errors.Is(err, ErrShouldResetSyncHeight) { + log.Warn("Resetting sync height to L1 block 7892668 to fix L1 message queue hash calculation") + s.callDataBlobSource.SetL1Height(7892668) + + return nil + } // Reset the L1 height to the previous value to retry fetching the same data. s.callDataBlobSource.SetL1Height(prevL1Height) return fmt.Errorf("failed to parse and update rollup event logs: %w", err) @@ -535,6 +544,16 @@ func validateBatch(batchIndex uint64, event *l1.FinalizeBatchEvent, parentFinali daBatch, err := codec.NewDABatch(batch) if err != nil { + // This is hotfix for the L1 message hash mismatch issue which lead to wrong committedBatchMeta.PostL1MessageQueueHash hashes. + // These in turn lead to a wrongly computed batch hash locally. This happened after upgrading to EuclidV2 + // where da-codec was not updated to the latest version in l2geth. + // If the error message due to mismatching PostL1MessageQueueHash contains the same hash as the hardcoded one, + // this means the node ran into this issue. + // We need to reset the sync height to 1 block before the L1 block in which the last batch in CodecV6 was committed. + // The node will overwrite the wrongly computed message queue hashes. + if strings.Contains(err.Error(), "0xaa16faf2a1685fe1d7e0f2810b1a0e98c2841aef96596d10456a6d0f00000000") { + return 0, nil, ErrShouldResetSyncHeight + } return 0, nil, fmt.Errorf("failed to create DA batch, batch index: %v, codec version: %v, err: %w", batchIndex, codecVersion, err) } localBatchHash := daBatch.Hash() From 2f0adcf7a8053b35eca69a918196d0b77cb0fd49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Fri, 14 Mar 2025 11:13:20 +0100 Subject: [PATCH 07/45] ci: fix TOB-SCREUC-6, disable cache-binary option (#1137) fix(ci): disable cache-binary option --- .github/workflows/docker-arm64.yaml | 2 ++ .github/workflows/docker.yaml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/docker-arm64.yaml b/.github/workflows/docker-arm64.yaml index 5e68adfb05..93305ff290 100644 --- a/.github/workflows/docker-arm64.yaml +++ b/.github/workflows/docker-arm64.yaml @@ -26,6 +26,8 @@ jobs: - name: Set up Docker Buildx id: buildx uses: docker/setup-buildx-action@v2 + with: + cache-binary: false - name: Login to Docker Hub uses: docker/login-action@v2 with: diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml index 64c415b6f3..34a2295d76 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/docker.yaml @@ -17,6 +17,8 @@ jobs: uses: docker/setup-qemu-action@v2 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 + with: + cache-binary: false - name: Extract docker metadata id: meta uses: docker/metadata-action@v3 From 080afd438196ffaa644bea4fff3c9e7b00434504 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Fri, 14 Mar 2025 11:26:14 +0100 Subject: [PATCH 08/45] ci: fix TOB-SCREUC-7, pin 3rd-party actions (#1138) * fix(ci): disable cache-binary option * ci: pin 3rd-party actions --- .github/dependabot.yml | 6 ++++++ .github/workflows/docker-arm64.yaml | 9 ++++++--- .github/workflows/docker.yaml | 14 +++++++++----- .github/workflows/l2geth_ci.yml | 25 ++++++++++++++++++++++++- 4 files changed, 45 insertions(+), 9 deletions(-) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..5ace4600a1 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/docker-arm64.yaml b/.github/workflows/docker-arm64.yaml index 93305ff290..f58d57c1c0 100644 --- a/.github/workflows/docker-arm64.yaml +++ b/.github/workflows/docker-arm64.yaml @@ -19,22 +19,25 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v2 + - name: Set up QEMU run: | docker run --rm --privileged multiarch/qemu-user-static --reset -p yes docker buildx create --name multiarch --driver docker-container --use + - name: Set up Docker Buildx id: buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@885d1462b80bc1c1c7f0b00334ad271f09369c55 # v2.10.0 with: cache-binary: false - name: Login to Docker Hub - uses: docker/login-action@v2 + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 #v3.3.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Build docker image - uses: docker/build-push-action@v2 + uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0 with: platforms: linux/arm64 context: . diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml index 34a2295d76..8c252a32cd 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/docker.yaml @@ -13,15 +13,17 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v2 + - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@2b82ce82d56a2a04d2637cd93a637ae1b359c0a7 # v2.2.0 + - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@885d1462b80bc1c1c7f0b00334ad271f09369c55 # v2.10.0 with: cache-binary: false - name: Extract docker metadata id: meta - uses: docker/metadata-action@v3 + uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0 with: images: scrolltech/l2geth tags: | @@ -29,13 +31,15 @@ jobs: type=raw,value=latest,enable=${{ github.event_name == 'release' }} flavor: | latest=false + - name: Login to Docker Hub - uses: docker/login-action@v2 + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 #v3.3.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Build docker image - uses: docker/build-push-action@v2 + uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0 with: context: . file: Dockerfile diff --git a/.github/workflows/l2geth_ci.yml b/.github/workflows/l2geth_ci.yml index d196128a69..7881e3a535 100644 --- a/.github/workflows/l2geth_ci.yml +++ b/.github/workflows/l2geth_ci.yml @@ -13,6 +13,7 @@ on: - ready_for_review name: CI jobs: + build-mock-ccc-geth: # build geth with mock circuit capacity checker if: github.event.pull_request.draft == false runs-on: ubuntu-latest @@ -21,11 +22,14 @@ jobs: uses: actions/setup-go@v2 with: go-version: 1.21.x + - name: Checkout code uses: actions/checkout@v2 + - name: Build run: | make nccc_geth + build-geth: # build geth with circuit capacity checker if: github.event_name == 'push' # will only be triggered when pushing to main & staging & develop & alpha runs-on: ubuntu-latest @@ -34,19 +38,23 @@ jobs: uses: actions/setup-go@v2 with: go-version: 1.21.x + - name: Install rust - uses: actions-rs/toolchain@v1 + uses: actions-rust-lang/setup-rust-toolchain@9399c7bb15d4c7d47b27263d024f0a4978346ba4 # v1.11.0 with: toolchain: nightly-2023-12-03 override: true components: rustfmt, clippy + - name: Checkout code uses: actions/checkout@v2 + - name: Build run: | make libzkp sudo cp ./rollup/ccc/libzkp/libzkp.so /usr/local/lib/ make geth + check: if: github.event.pull_request.draft == false runs-on: ubuntu-latest @@ -55,12 +63,15 @@ jobs: uses: actions/setup-go@v2 with: go-version: 1.21.x + - name: Checkout code uses: actions/checkout@v2 + - name: Lint run: | rm -rf $HOME/.cache/golangci-lint make lint + goimports-lint: if: github.event.pull_request.draft == false runs-on: ubuntu-latest @@ -69,17 +80,22 @@ jobs: uses: actions/setup-go@v2 with: go-version: 1.18.x + - name: Install goimports run: go install golang.org/x/tools/cmd/goimports@v0.24.0 + - name: Checkout code uses: actions/checkout@v2 + - run: goimports -local github.com/scroll-tech/go-ethereum/ -w . + # If there are any diffs from goimports, fail. - name: Verify no changes from goimports run: | if [ -n "$(git status --porcelain)" ]; then exit 1 fi + go-mod-tidy-lint: if: github.event.pull_request.draft == false runs-on: ubuntu-latest @@ -88,15 +104,19 @@ jobs: uses: actions/setup-go@v2 with: go-version: 1.21.x + - name: Checkout code uses: actions/checkout@v2 + - run: go mod tidy + # If there are any diffs from go mod tidy, fail. - name: Verify no changes from go mod tidy run: | if [ -n "$(git status --porcelain)" ]; then exit 1 fi + test: if: github.event.pull_request.draft == false runs-on: ubuntu-latest @@ -105,10 +125,13 @@ jobs: uses: actions/setup-go@v2 with: go-version: 1.21.x + - name: Checkout code uses: actions/checkout@v2 + - name: Test run: | make test + - name: Upload coverage report run: bash <(curl -s https://codecov.io/bash) From 94fcd7d9cef479548e790239052e2a4b6cb081f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Fri, 14 Mar 2025 11:36:32 +0100 Subject: [PATCH 09/45] ci: fix TOB-SCREUC-8, do not persist git credentials (#1139) * fix(ci): disable cache-binary option * ci: pin 3rd-party actions * ci: do not persist git credentials --- .github/workflows/bump_version.yml | 4 ++++ .github/workflows/docker-arm64.yaml | 2 ++ .github/workflows/docker.yaml | 3 +++ .github/workflows/l2geth_ci.yml | 12 ++++++++++++ .github/workflows/semgrep.yml | 2 ++ 5 files changed, 23 insertions(+) diff --git a/.github/workflows/bump_version.yml b/.github/workflows/bump_version.yml index 61c4e85bd7..39bdbc979f 100644 --- a/.github/workflows/bump_version.yml +++ b/.github/workflows/bump_version.yml @@ -19,6 +19,8 @@ jobs: uses: actions/checkout@v3 with: ref: ${{ github.head_ref }} + persist-credentials: false + - name: check diff id: check_diff run: | @@ -45,11 +47,13 @@ jobs: echo '> yes' echo "result=bump" >> "$GITHUB_OUTPUT" fi + - name: Install Node.js 16 if: steps.check_diff.outputs.result == 'bump' uses: actions/setup-node@v3 with: node-version: 16 + - name: bump version in params/version.go if: steps.check_diff.outputs.result == 'bump' run: node .github/scripts/bump_version_dot_go.mjs diff --git a/.github/workflows/docker-arm64.yaml b/.github/workflows/docker-arm64.yaml index f58d57c1c0..55c299d6a2 100644 --- a/.github/workflows/docker-arm64.yaml +++ b/.github/workflows/docker-arm64.yaml @@ -19,6 +19,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v2 + with: + persist-credentials: false - name: Set up QEMU run: | diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml index 8c252a32cd..46ad8e00d7 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/docker.yaml @@ -13,6 +13,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v2 + with: + persist-credentials: false - name: Set up QEMU uses: docker/setup-qemu-action@2b82ce82d56a2a04d2637cd93a637ae1b359c0a7 # v2.2.0 @@ -21,6 +23,7 @@ jobs: uses: docker/setup-buildx-action@885d1462b80bc1c1c7f0b00334ad271f09369c55 # v2.10.0 with: cache-binary: false + - name: Extract docker metadata id: meta uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0 diff --git a/.github/workflows/l2geth_ci.yml b/.github/workflows/l2geth_ci.yml index 7881e3a535..f7efb4d9ac 100644 --- a/.github/workflows/l2geth_ci.yml +++ b/.github/workflows/l2geth_ci.yml @@ -25,6 +25,8 @@ jobs: - name: Checkout code uses: actions/checkout@v2 + with: + persist-credentials: false - name: Build run: | @@ -48,6 +50,8 @@ jobs: - name: Checkout code uses: actions/checkout@v2 + with: + persist-credentials: false - name: Build run: | @@ -66,6 +70,8 @@ jobs: - name: Checkout code uses: actions/checkout@v2 + with: + persist-credentials: false - name: Lint run: | @@ -86,6 +92,8 @@ jobs: - name: Checkout code uses: actions/checkout@v2 + with: + persist-credentials: false - run: goimports -local github.com/scroll-tech/go-ethereum/ -w . @@ -107,6 +115,8 @@ jobs: - name: Checkout code uses: actions/checkout@v2 + with: + persist-credentials: false - run: go mod tidy @@ -128,6 +138,8 @@ jobs: - name: Checkout code uses: actions/checkout@v2 + with: + persist-credentials: false - name: Test run: | diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml index 6d7663154a..0e8e8fba03 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/semgrep.yml @@ -21,4 +21,6 @@ jobs: image: returntocorp/semgrep steps: - uses: actions/checkout@v3 + with: + persist-credentials: false - run: semgrep ci From 12536ac34b2f99106092a69462dd22416fa54a9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Sat, 15 Mar 2025 13:49:30 +0100 Subject: [PATCH 10/45] ci: add zizmor (#1146) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ci: add zizmor * trigger ci * adjust permissions * typo * adjust * adjust * chore: auto version bump [bot] * adjust * adjust * fix * chore: auto version bump [bot] * undo bump version * fix branch --------- Co-authored-by: Thegaram <7571518+Thegaram@users.noreply.github.com> --- .github/workflows/bump_version.yml | 14 ++++++++---- .github/workflows/docker-arm64.yaml | 3 +++ .github/workflows/docker.yaml | 2 ++ .github/workflows/l2geth_ci.yml | 11 ++++++++-- .github/workflows/semgrep.yml | 1 + .github/workflows/zizmor.yml | 34 +++++++++++++++++++++++++++++ 6 files changed, 59 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/zizmor.yml diff --git a/.github/workflows/bump_version.yml b/.github/workflows/bump_version.yml index 39bdbc979f..fd5fc98b1f 100644 --- a/.github/workflows/bump_version.yml +++ b/.github/workflows/bump_version.yml @@ -14,12 +14,18 @@ jobs: try-to-bump: if: contains(github.event.pull_request.labels.*.name, 'bump-version') runs-on: ubuntu-latest + permissions: + # Give the default GITHUB_TOKEN write permission to commit and push the + # added or changed files to the repository. + contents: write + steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{ github.head_ref }} - persist-credentials: false + # note: this is needed by git-auto-commit-action below + persist-credentials: true - name: check diff id: check_diff @@ -29,7 +35,7 @@ jobs: # fetch develop branch so that we can diff against later git fetch origin develop - echo 'checking verion changes in diff...' + echo 'checking version changes in diff...' # check if version changed in version.go # note: the grep will fail if use \d instead of [0-9] @@ -59,7 +65,7 @@ jobs: run: node .github/scripts/bump_version_dot_go.mjs # Commits made by this Action do not trigger new Workflow runs - - uses: stefanzweifel/git-auto-commit-action@3ea6ae190baf489ba007f7c92608f33ce20ef04a + - uses: stefanzweifel/git-auto-commit-action@e348103e9026cc0eee72ae06630dbe30c8bf7a79 # v5.1.0 if: steps.check_diff.outputs.result == 'bump' with: skip_fetch: true # already did fetch in check diff diff --git a/.github/workflows/docker-arm64.yaml b/.github/workflows/docker-arm64.yaml index 55c299d6a2..9962e17bab 100644 --- a/.github/workflows/docker-arm64.yaml +++ b/.github/workflows/docker-arm64.yaml @@ -11,6 +11,8 @@ on: jobs: build-and-push-arm64-image: runs-on: ubuntu-latest + permissions: {} + strategy: matrix: arch: @@ -32,6 +34,7 @@ jobs: uses: docker/setup-buildx-action@885d1462b80bc1c1c7f0b00334ad271f09369c55 # v2.10.0 with: cache-binary: false + - name: Login to Docker Hub uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 #v3.3.0 with: diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml index 46ad8e00d7..3f1f5f3ad7 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/docker.yaml @@ -10,6 +10,8 @@ on: jobs: build-and-push: runs-on: ubuntu-latest + permissions: {} + steps: - name: Checkout code uses: actions/checkout@v2 diff --git a/.github/workflows/l2geth_ci.yml b/.github/workflows/l2geth_ci.yml index f7efb4d9ac..8cb0541926 100644 --- a/.github/workflows/l2geth_ci.yml +++ b/.github/workflows/l2geth_ci.yml @@ -1,3 +1,5 @@ +name: CI + on: push: branches: # we keep this to avoid triggering `push` & `pull_request` every time we update a PR @@ -11,12 +13,12 @@ on: - reopened - synchronize - ready_for_review -name: CI -jobs: +jobs: build-mock-ccc-geth: # build geth with mock circuit capacity checker if: github.event.pull_request.draft == false runs-on: ubuntu-latest + permissions: {} steps: - name: Install Go uses: actions/setup-go@v2 @@ -35,6 +37,7 @@ jobs: build-geth: # build geth with circuit capacity checker if: github.event_name == 'push' # will only be triggered when pushing to main & staging & develop & alpha runs-on: ubuntu-latest + permissions: {} steps: - name: Install Go uses: actions/setup-go@v2 @@ -62,6 +65,7 @@ jobs: check: if: github.event.pull_request.draft == false runs-on: ubuntu-latest + permissions: {} steps: - name: Install Go uses: actions/setup-go@v2 @@ -81,6 +85,7 @@ jobs: goimports-lint: if: github.event.pull_request.draft == false runs-on: ubuntu-latest + permissions: {} steps: - name: Install Go uses: actions/setup-go@v2 @@ -107,6 +112,7 @@ jobs: go-mod-tidy-lint: if: github.event.pull_request.draft == false runs-on: ubuntu-latest + permissions: {} steps: - name: Install Go uses: actions/setup-go@v2 @@ -130,6 +136,7 @@ jobs: test: if: github.event.pull_request.draft == false runs-on: ubuntu-latest + permissions: {} steps: - name: Install Go uses: actions/setup-go@v2 diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml index 0e8e8fba03..d889f76141 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/semgrep.yml @@ -15,6 +15,7 @@ jobs: semgrep: name: semgrep/ci runs-on: ubuntu-20.04 + permissions: {} env: SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} container: diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml new file mode 100644 index 0000000000..689f2e66ca --- /dev/null +++ b/.github/workflows/zizmor.yml @@ -0,0 +1,34 @@ +name: zizmor GA Security Analysis + +on: + push: + branches: ["develop"] + pull_request: + branches: ["**"] + +jobs: + zizmor: + name: zizmor + runs-on: ubuntu-latest + permissions: + security-events: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Install the latest version of uv + uses: astral-sh/setup-uv@f94ec6bedd8674c4426838e6b50417d36b6ab231 # v5.3.1 + + - name: Run zizmor + run: uvx zizmor --format sarif . > results.sarif + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload SARIF file + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: results.sarif + category: zizmor From b3933861aec450bc2035f2b70fdd1714e95dab06 Mon Sep 17 00:00:00 2001 From: colin <102356659+colinlyguo@users.noreply.github.com> Date: Wed, 19 Mar 2025 16:41:05 +0800 Subject: [PATCH 11/45] fix: add EIP-3607 check in L1 messages (#1149) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: add EIP-3607 check in l1 message * Update core/state_transition.go Co-authored-by: Péter Garamvölgyi --------- Co-authored-by: Péter Garamvölgyi --- core/state_transition.go | 21 ++++++++++++++------- params/version.go | 2 +- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/core/state_transition.go b/core/state_transition.go index 0f480d52a3..1a7b3cea4b 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -269,8 +269,21 @@ func (st *StateTransition) buyGas() error { } func (st *StateTransition) preCheck() error { + // Only check transactions that are not fake + if !st.msg.IsFake() { + // Make sure the sender is an EOA + code := st.state.GetCode(st.msg.From()) + _, delegated := types.ParseDelegation(code) + if len(code) > 0 && !delegated { + // If the sender on L1 is a (delegated) EOA, then it must be a (delegated) EOA on L2, too. + // If the sender on L1 is a contract, then we apply address aliasing. + // The probability that the aliased address happens to be a smart contract on L2 is negligible. + return fmt.Errorf("%w: address %v, len(code): %d", ErrSenderNoEOA, st.msg.From().Hex(), len(code)) + } + } + if st.msg.IsL1MessageTx() { - // No fee fields to check, no nonce to check, and no need to check if EOA (L1 already verified it for us) + // No fee fields to check, no nonce to check // Gas is free, but no refunds! st.gas += st.msg.Gas() st.initialGas = st.msg.Gas() @@ -291,12 +304,6 @@ func (st *StateTransition) preCheck() error { return fmt.Errorf("%w: address %v, nonce: %d", ErrNonceMax, st.msg.From().Hex(), stNonce) } - // Make sure the sender is an EOA - code := st.state.GetCode(st.msg.From()) - _, delegated := types.ParseDelegation(code) - if len(code) > 0 && !delegated { - return fmt.Errorf("%w: address %v, len(code): %d", ErrSenderNoEOA, st.msg.From().Hex(), len(code)) - } } // Make sure that transaction gasFeeCap is greater than the baseFee (post london) // Note: Logically, this should be `IsCurie`, but we keep `IsLondon` to ensure backward compatibility. diff --git a/params/version.go b/params/version.go index d085d9e9b8..e90b162754 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 5 // Major version component of the current release VersionMinor = 8 // Minor version component of the current release - VersionPatch = 25 // Patch version component of the current release + VersionPatch = 26 // Patch version component of the current release VersionMeta = "mainnet" // Version metadata to append to the version string ) From 12ceadaa6c242354daa453ef0f6840901358a43e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Thu, 20 Mar 2025 13:05:38 +0100 Subject: [PATCH 12/45] feat: track latest relayed l1 message (#1150) --- core/blockchain.go | 12 ++++++++++++ params/version.go | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/core/blockchain.go b/core/blockchain.go index aa3d74197f..f3b150c4cb 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -53,6 +53,7 @@ var ( headHeaderGauge = metrics.NewRegisteredGauge("chain/head/header", nil) headFastBlockGauge = metrics.NewRegisteredGauge("chain/head/receipt", nil) headTimeGapGauge = metrics.NewRegisteredGauge("chain/head/timegap", nil) + headL1MessageGauge = metrics.NewRegisteredGauge("chain/head/l1msg", nil) l2BaseFeeGauge = metrics.NewRegisteredGauge("chain/fees/l2basefee", nil) @@ -1253,6 +1254,17 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. l2BaseFeeGauge.Update(0) } + // Note the latest relayed L1 message queue index (if any) + for _, tx := range block.Transactions() { + if msg := tx.AsL1MessageTx(); msg != nil { + // Queue index is guaranteed to fit into int64. + headL1MessageGauge.Update(int64(msg.QueueIndex)) + } else { + // No more L1 messages in this block. + break + } + } + parent := bc.GetHeaderByHash(block.ParentHash()) // block.Time is guaranteed to be larger than parent.Time, // and the time gap should fit into int64. diff --git a/params/version.go b/params/version.go index e90b162754..133999bae3 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 5 // Major version component of the current release VersionMinor = 8 // Minor version component of the current release - VersionPatch = 26 // Patch version component of the current release + VersionPatch = 27 // Patch version component of the current release VersionMeta = "mainnet" // Version metadata to append to the version string ) From e1e578d99d6560f3ffc42fa9f8e1d8afe1a8f580 Mon Sep 17 00:00:00 2001 From: colin <102356659+colinlyguo@users.noreply.github.com> Date: Thu, 20 Mar 2025 20:19:01 +0800 Subject: [PATCH 13/45] feat(tx-pool): migrate upstream setcode transaction changes (#1148) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Péter Garamvölgyi --- core/tx_pool.go | 98 ++++++++++++++-------------- core/tx_pool_test.go | 131 ++++++++++++++++++++++++++++++++++++-- core/types/transaction.go | 12 +++- params/version.go | 2 +- 4 files changed, 182 insertions(+), 61 deletions(-) diff --git a/core/tx_pool.go b/core/tx_pool.go index b9828789cd..0109d56776 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -79,10 +79,6 @@ var ( // with a different one without the required price bump. ErrReplaceUnderpriced = errors.New("replacement transaction underpriced") - // ErrAccountLimitExceeded is returned if a transaction would exceed the number - // allowed by a pool for a single account. - ErrAccountLimitExceeded = errors.New("account limit exceeded") - // ErrGasLimit is returned if a transaction's requested gas limit exceeds the // maximum allowance of the current block. ErrGasLimit = errors.New("exceeds block gas limit") @@ -96,6 +92,10 @@ var ( // making the transaction invalid, rather a DOS protection. ErrOversizedData = errors.New("oversized data") + // ErrInflightTxLimitReached is returned when the maximum number of in-flight + // transactions is reached for specific accounts. + ErrInflightTxLimitReached = errors.New("in-flight transaction limit reached for delegated accounts") + // ErrAuthorityReserved is returned if a transaction has an authorization // signed by an address which already has in-flight transactions known to the // pool. @@ -809,19 +809,8 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error { if pool.currentState.GetBalance(from).Cmp(tx.Cost()) < 0 { return ErrInsufficientFunds } - list := pool.pending[from] - if list == nil || !list.Overlaps(tx) { - // Transaction takes a new nonce value out of the pool. Ensure it doesn't - // overflow the number of permitted transactions from a single account - // (i.e. max cancellable via out-of-bound transaction). - if used, left := usedAndLeftSlots(pool, from); left <= 0 { - return fmt.Errorf("%w: pooled %d txs", ErrAccountLimitExceeded, used) - } - // Verify no authorizations will invalidate existing transactions known to - // the pool. - if conflicts := knownConflicts(pool, tx.SetCodeAuthorities()); len(conflicts) > 0 { - return fmt.Errorf("%w: authorization conflicts with other known tx", ErrAuthorityReserved) - } + if err := pool.validateAuth(from, tx); err != nil { + return err } if tx.Type() == types.SetCodeTxType { if len(tx.SetCodeAuthorizations()) == 0 { @@ -1895,6 +1884,43 @@ func (pool *TxPool) calculateTxsLifecycle(txs types.Transactions, t time.Time) { } } +// validateAuth verifies that the transaction complies with code authorization +// restrictions brought by SetCode transaction type. +func (pool *TxPool) validateAuth(from common.Address, tx *types.Transaction) error { + // Allow at most one in-flight tx for delegated accounts or those with a + // pending authorization. + if pool.currentState.GetKeccakCodeHash(from) != codehash.EmptyKeccakCodeHash || len(pool.all.auths[from]) != 0 { + var ( + count int + exists bool + ) + pending := pool.pending[from] + if pending != nil { + count += pending.Len() + exists = pending.Overlaps(tx) + } + queue := pool.queue[from] + if queue != nil { + count += queue.Len() + exists = exists || queue.Overlaps(tx) + } + // Replace the existing in-flight transaction for delegated accounts + // are still supported + if count >= 1 && !exists { + return ErrInflightTxLimitReached + } + } + // Authorities cannot conflict with any pending or queued transactions. + if auths := tx.SetCodeAuthorities(); len(auths) > 0 { + for _, auth := range auths { + if pool.pending[auth] != nil || pool.queue[auth] != nil { + return ErrAuthorityReserved + } + } + } + return nil +} + // PauseReorgs stops any new reorg jobs to be started but doesn't interrupt any existing ones that are in flight // Keep in mind this function might block, although it is not expected to block for any significant amount of time func (pool *TxPool) PauseReorgs() { @@ -2132,7 +2158,6 @@ func (t *txLookup) Remove(hash common.Hash) { t.lock.Lock() defer t.lock.Unlock() - t.removeAuthorities(hash) tx, ok := t.locals[hash] if !ok { tx, ok = t.remotes[hash] @@ -2141,6 +2166,7 @@ func (t *txLookup) Remove(hash common.Hash) { log.Error("No transaction found to be deleted", "hash", hash) return } + t.removeAuthorities(tx) t.slots -= numSlots(tx) slotsGauge.Update(int64(t.slots)) @@ -2196,8 +2222,9 @@ func (t *txLookup) addAuthorities(tx *types.Transaction) { // removeAuthorities stops tracking the supplied tx in relation to its // authorities. -func (t *txLookup) removeAuthorities(hash common.Hash) { - for addr := range t.auths { +func (t *txLookup) removeAuthorities(tx *types.Transaction) { + hash := tx.Hash() + for _, addr := range tx.SetCodeAuthorities() { list := t.auths[addr] // Remove tx from tracker. if i := slices.Index(list, hash); i >= 0 { @@ -2218,34 +2245,3 @@ func (t *txLookup) removeAuthorities(hash common.Hash) { func numSlots(tx *types.Transaction) int { return int((tx.Size() + txSlotSize - 1) / txSlotSize) } - -// usedAndLeftSlots returns the number of slots used and left for the given address. -func usedAndLeftSlots(pool *TxPool, addr common.Address) (int, int) { - var have int - if list := pool.pending[addr]; list != nil { - have += list.Len() - } - if list := pool.queue[addr]; list != nil { - have += list.Len() - } - if pool.currentState.GetKeccakCodeHash(addr) != codehash.EmptyKeccakCodeHash || len(pool.all.auths[addr]) != 0 { - // Allow at most one in-flight tx for delegated accounts or those with - // a pending authorization. - return have, max(0, 1-have) - } - return have, math.MaxInt -} - -// knownConflicts returns a list of addresses that conflict with the given authorities. -func knownConflicts(pool *TxPool, auths []common.Address) []common.Address { - var conflicts []common.Address - // Authorities cannot conflict with any pending or queued transactions. - for _, addr := range auths { - if list := pool.pending[addr]; list != nil { - conflicts = append(conflicts, addr) - } else if list := pool.queue[addr]; list != nil { - conflicts = append(conflicts, addr) - } - } - return conflicts -} diff --git a/core/tx_pool_test.go b/core/tx_pool_test.go index e97b8be96a..f7d3b298db 100644 --- a/core/tx_pool_test.go +++ b/core/tx_pool_test.go @@ -24,6 +24,7 @@ import ( "math/big" "math/rand" "os" + "slices" "sync/atomic" "testing" "time" @@ -156,6 +157,10 @@ func pricedSetCodeTx(nonce uint64, gaslimit uint64, gasFee, tip *uint256.Int, ke }) authList = append(authList, auth) } + return pricedSetCodeTxWithAuth(nonce, gaslimit, gasFee, tip, key, authList) +} + +func pricedSetCodeTxWithAuth(nonce uint64, gaslimit uint64, gasFee, tip *uint256.Int, key *ecdsa.PrivateKey, authList []types.SetCodeAuthorization) *types.Transaction { return types.MustSignNewTx(key, types.LatestSignerForChainID(params.TestChainConfig.ChainID), &types.SetCodeTx{ ChainID: uint256.MustFromBig(params.TestChainConfig.ChainID), Nonce: nonce, @@ -214,6 +219,34 @@ func validateTxPoolInternals(pool *TxPool) error { return fmt.Errorf("pending nonce mismatch: have %v, want %v", nonce, last+1) } } + // Ensure all auths in pool are tracked + for _, tx := range pool.all.locals { + for _, addr := range tx.SetCodeAuthorities() { + list := pool.all.auths[addr] + if i := slices.Index(list, tx.Hash()); i < 0 { + return fmt.Errorf("authority not tracked: addr %s, tx %s", addr, tx.Hash()) + } + } + } + for _, tx := range pool.all.remotes { + for _, addr := range tx.SetCodeAuthorities() { + list := pool.all.auths[addr] + if i := slices.Index(list, tx.Hash()); i < 0 { + return fmt.Errorf("authority not tracked: addr %s, tx %s", addr, tx.Hash()) + } + } + } + // Ensure all auths in pool have an associated tx in locals or remotes. + for addr, hashes := range pool.all.auths { + for _, hash := range hashes { + _, inLocals := pool.all.locals[hash] + _, inRemotes := pool.all.remotes[hash] + + if !inLocals && !inRemotes { + return fmt.Errorf("dangling authority, missing originating tx: addr %s, hash %s", addr, hash.Hex()) + } + } + } return nil } @@ -2701,12 +2734,12 @@ func TestSetCodeTransactions(t *testing.T) { if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), keyA)); err != nil { t.Fatalf("%s: failed to add remote transaction: %v", name, err) } - if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1), keyA)); !errors.Is(err, ErrAccountLimitExceeded) { - t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrAccountLimitExceeded, err) + if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1), keyA)); !errors.Is(err, ErrInflightTxLimitReached) { + t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrInflightTxLimitReached, err) } // Also check gapped transaction. - if err := pool.addRemoteSync(pricedTransaction(2, 100000, big.NewInt(1), keyA)); !errors.Is(err, ErrAccountLimitExceeded) { - t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrAccountLimitExceeded, err) + if err := pool.addRemoteSync(pricedTransaction(2, 100000, big.NewInt(1), keyA)); !errors.Is(err, ErrInflightTxLimitReached) { + t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrInflightTxLimitReached, err) } // Replace by fee. if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(10), keyA)); err != nil { @@ -2740,8 +2773,8 @@ func TestSetCodeTransactions(t *testing.T) { t.Fatalf("%s: failed to add with pending delegation: %v", name, err) } // Also check gapped transaction is rejected. - if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1), keyC)); !errors.Is(err, ErrAccountLimitExceeded) { - t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrAccountLimitExceeded, err) + if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1), keyC)); !errors.Is(err, ErrInflightTxLimitReached) { + t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrInflightTxLimitReached, err) } }, }, @@ -2815,7 +2848,7 @@ func TestSetCodeTransactions(t *testing.T) { if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1000), keyC)); err != nil { t.Fatalf("%s: failed to added single pooled for account with pending delegation: %v", name, err) } - if err, want := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1000), keyC)), ErrAccountLimitExceeded; !errors.Is(err, want) { + if err, want := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1000), keyC)), ErrInflightTxLimitReached; !errors.Is(err, want) { t.Fatalf("%s: error mismatch: want %v, have %v", name, want, err) } }, @@ -2846,6 +2879,32 @@ func TestSetCodeTransactions(t *testing.T) { } }, }, + { + name: "remove-hash-from-authority-tracker", + pending: 10, + run: func(name string, pool *TxPool, statedb *state.StateDB) { + var keys []*ecdsa.PrivateKey + for i := 0; i < 30; i++ { + key, _ := crypto.GenerateKey() + keys = append(keys, key) + addr := crypto.PubkeyToAddress(key.PublicKey) + testAddBalance(pool, addr, big.NewInt(params.Ether)) + } + // Create a transactions with 3 unique auths so the lookup's auth map is + // filled with addresses. + for i := 0; i < 30; i += 3 { + if err := pool.addRemoteSync(pricedSetCodeTx(0, 250000, uint256.NewInt(10), uint256.NewInt(3), keys[i], []unsignedAuth{{0, keys[i]}, {0, keys[i+1]}, {0, keys[i+2]}})); err != nil { + t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err) + } + } + // Replace one of the transactions with a normal transaction so that the + // original hash is removed from the tracker. The hash should be + // associated with 3 different authorities. + if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1000), keys[0])); err != nil { + t.Fatalf("%s: failed to replace with remote transaction: %v", name, err) + } + }, + }, } { // Create the pool to test the status retrievals with statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) @@ -2875,6 +2934,64 @@ func TestSetCodeTransactions(t *testing.T) { } } +func TestSetCodeTransactionsReorg(t *testing.T) { + t.Parallel() + + // Create the pool to test the status retrievals with + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + blockchain := &testBlockChain{1000000, statedb, new(event.Feed)} + + pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) + defer pool.Stop() + + // Create the test accounts + var ( + keyA, _ = crypto.GenerateKey() + addrA = crypto.PubkeyToAddress(keyA.PublicKey) + ) + testAddBalance(pool, addrA, big.NewInt(params.Ether)) + // Send an authorization for 0x42 + var authList []types.SetCodeAuthorization + auth, _ := types.SignSetCode(keyA, types.SetCodeAuthorization{ + ChainID: *uint256.MustFromBig(params.TestChainConfig.ChainID), + Address: common.Address{0x42}, + Nonce: 0, + }) + authList = append(authList, auth) + if err := pool.addRemoteSync(pricedSetCodeTxWithAuth(0, 250000, uint256.NewInt(10), uint256.NewInt(3), keyA, authList)); err != nil { + t.Fatalf("failed to add with remote setcode transaction: %v", err) + } + // Simulate the chain moving + blockchain.statedb.SetNonce(addrA, 1) + blockchain.statedb.SetCode(addrA, types.AddressToDelegation(auth.Address)) + <-pool.requestReset(nil, nil) + // Set an authorization for 0x00 + auth, _ = types.SignSetCode(keyA, types.SetCodeAuthorization{ + ChainID: *uint256.MustFromBig(params.TestChainConfig.ChainID), + Address: common.Address{}, + Nonce: 0, + }) + authList = append(authList, auth) + if err := pool.addRemoteSync(pricedSetCodeTxWithAuth(1, 250000, uint256.NewInt(10), uint256.NewInt(3), keyA, authList)); err != nil { + t.Fatalf("failed to add with remote setcode transaction: %v", err) + } + // Try to add a transactions in + if err := pool.addRemoteSync(pricedTransaction(2, 100000, big.NewInt(1000), keyA)); !errors.Is(err, ErrInflightTxLimitReached) { + t.Fatalf("unexpected error %v, expecting %v", err, ErrInflightTxLimitReached) + } + // Simulate the chain moving + blockchain.statedb.SetNonce(addrA, 2) + blockchain.statedb.SetCode(addrA, nil) + <-pool.requestReset(nil, nil) + // Now send two transactions from addrA + if err := pool.addRemoteSync(pricedTransaction(2, 100000, big.NewInt(1000), keyA)); err != nil { + t.Fatalf("failed to added single transaction: %v", err) + } + if err := pool.addRemoteSync(pricedTransaction(3, 100000, big.NewInt(1000), keyA)); err != nil { + t.Fatalf("failed to added single transaction: %v", err) + } +} + // Benchmarks the speed of validating the contents of the pending queue of the // transaction pool. func BenchmarkPendingDemotion100(b *testing.B) { benchmarkPendingDemotion(b, 100) } diff --git a/core/types/transaction.go b/core/types/transaction.go index 6a5a50f8b2..4f88df833d 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -477,15 +477,23 @@ func (tx *Transaction) SetCodeAuthorizations() []SetCodeAuthorization { return setcodetx.AuthList } -// SetCodeAuthorities returns a list of each authorization's corresponding authority. +// SetCodeAuthorities returns a list of unique authorities from the +// authorization list. func (tx *Transaction) SetCodeAuthorities() []common.Address { setcodetx, ok := tx.inner.(*SetCodeTx) if !ok { return nil } - auths := make([]common.Address, 0, len(setcodetx.AuthList)) + var ( + marks = make(map[common.Address]bool) + auths = make([]common.Address, 0, len(setcodetx.AuthList)) + ) for _, auth := range setcodetx.AuthList { if addr, err := auth.Authority(); err == nil { + if marks[addr] { + continue + } + marks[addr] = true auths = append(auths, addr) } } diff --git a/params/version.go b/params/version.go index 133999bae3..99a9625f3f 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 5 // Major version component of the current release VersionMinor = 8 // Minor version component of the current release - VersionPatch = 27 // Patch version component of the current release + VersionPatch = 28 // Patch version component of the current release VersionMeta = "mainnet" // Version metadata to append to the version string ) From 019e52cb0c8bcfa41f6a9c01d31c96ac15da5e81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Fri, 21 Mar 2025 13:44:59 +0100 Subject: [PATCH 14/45] feat(worker): allow empty blocks (#1153) --- cmd/geth/main.go | 1 + cmd/geth/usage.go | 1 + cmd/utils/flags.go | 7 +++++++ miner/miner.go | 1 + miner/scroll_worker.go | 3 +++ params/version.go | 2 +- 6 files changed, 14 insertions(+), 1 deletion(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index b1b1e92642..dcb7d12fc1 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -133,6 +133,7 @@ var ( utils.MinerNoVerifyFlag, utils.MinerStoreSkippedTxTracesFlag, utils.MinerMaxAccountsNumFlag, + utils.MinerAllowEmptyFlag, utils.NATFlag, utils.NoDiscoverFlag, utils.DiscoveryV5Flag, diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 0e1fce891a..8686dbc3df 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -197,6 +197,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{ utils.MinerNoVerifyFlag, utils.MinerStoreSkippedTxTracesFlag, utils.MinerMaxAccountsNumFlag, + utils.MinerAllowEmptyFlag, }, }, { diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 8d8bdf6773..7371a37700 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -515,6 +515,10 @@ var ( Usage: "Maximum number of accounts that miner will fetch the pending transactions of when building a new block", Value: math.MaxInt, } + MinerAllowEmptyFlag = cli.BoolFlag{ + Name: "miner.allowempty", + Usage: "Allow sealing empty blocks", + } // Account settings UnlockedAccountFlag = cli.StringFlag{ Name: "unlock", @@ -1620,6 +1624,9 @@ func setMiner(ctx *cli.Context, cfg *miner.Config) { if ctx.GlobalIsSet(MinerMaxAccountsNumFlag.Name) { cfg.MaxAccountsNum = ctx.GlobalInt(MinerMaxAccountsNumFlag.Name) } + if ctx.GlobalIsSet(MinerAllowEmptyFlag.Name) { + cfg.AllowEmpty = ctx.GlobalBool(MinerAllowEmptyFlag.Name) + } if ctx.GlobalIsSet(LegacyMinerGasTargetFlag.Name) { log.Warn("The generic --miner.gastarget flag is deprecated and will be removed in the future!") } diff --git a/miner/miner.go b/miner/miner.go index 0d483643df..a7b5b0cc81 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -60,6 +60,7 @@ type Config struct { StoreSkippedTxTraces bool // Whether store the wrapped traces when storing a skipped tx MaxAccountsNum int // Maximum number of accounts that miner will fetch the pending transactions of when building a new block CCCMaxWorkers int // Maximum number of workers to use for async CCC tasks + AllowEmpty bool // If true, then we allow sealing empty blocks SigningDisabled bool // Whether to disable signing blocks with consensus enginek } diff --git a/miner/scroll_worker.go b/miner/scroll_worker.go index c2b97b5fbf..b95fb6027a 100644 --- a/miner/scroll_worker.go +++ b/miner/scroll_worker.go @@ -400,6 +400,9 @@ func (w *worker) mainLoop() { w.current.deadlineReached = true if len(w.current.txs) > 0 { _, err = w.commit() + } else if w.config.AllowEmpty { + log.Warn("Committing empty block", "number", w.current.header.Number) + _, err = w.commit() } case ev := <-w.txsCh: idleTimer.UpdateSince(idleStart) diff --git a/params/version.go b/params/version.go index 99a9625f3f..d3e1cbbc50 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 5 // Major version component of the current release VersionMinor = 8 // Minor version component of the current release - VersionPatch = 28 // Patch version component of the current release + VersionPatch = 29 // Patch version component of the current release VersionMeta = "mainnet" // Version metadata to append to the version string ) From 048db622672f5055bf9d9238216d8957117212c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Fri, 21 Mar 2025 14:19:59 +0100 Subject: [PATCH 15/45] ci: fix dependabot alerts (#1154) --- .github/workflows/bump_version.yml | 2 +- .github/workflows/docker-arm64.yaml | 6 +++--- .github/workflows/docker.yaml | 8 ++++---- .github/workflows/l2geth_ci.yml | 24 ++++++++++++------------ .github/workflows/semgrep.yml | 2 +- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/.github/workflows/bump_version.yml b/.github/workflows/bump_version.yml index fd5fc98b1f..c0b21ab156 100644 --- a/.github/workflows/bump_version.yml +++ b/.github/workflows/bump_version.yml @@ -56,7 +56,7 @@ jobs: - name: Install Node.js 16 if: steps.check_diff.outputs.result == 'bump' - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: 16 diff --git a/.github/workflows/docker-arm64.yaml b/.github/workflows/docker-arm64.yaml index 9962e17bab..00127188fa 100644 --- a/.github/workflows/docker-arm64.yaml +++ b/.github/workflows/docker-arm64.yaml @@ -20,7 +20,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: persist-credentials: false @@ -31,12 +31,12 @@ jobs: - name: Set up Docker Buildx id: buildx - uses: docker/setup-buildx-action@885d1462b80bc1c1c7f0b00334ad271f09369c55 # v2.10.0 + uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 with: cache-binary: false - name: Login to Docker Hub - uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 #v3.3.0 + uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 #v3.4.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml index 3f1f5f3ad7..c76bdda76a 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/docker.yaml @@ -14,15 +14,15 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: persist-credentials: false - name: Set up QEMU - uses: docker/setup-qemu-action@2b82ce82d56a2a04d2637cd93a637ae1b359c0a7 # v2.2.0 + uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@885d1462b80bc1c1c7f0b00334ad271f09369c55 # v2.10.0 + uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 with: cache-binary: false @@ -38,7 +38,7 @@ jobs: latest=false - name: Login to Docker Hub - uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 #v3.3.0 + uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 #v3.4.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} diff --git a/.github/workflows/l2geth_ci.yml b/.github/workflows/l2geth_ci.yml index 8cb0541926..da1f73c058 100644 --- a/.github/workflows/l2geth_ci.yml +++ b/.github/workflows/l2geth_ci.yml @@ -21,12 +21,12 @@ jobs: permissions: {} steps: - name: Install Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v5 with: go-version: 1.21.x - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: persist-credentials: false @@ -40,7 +40,7 @@ jobs: permissions: {} steps: - name: Install Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v5 with: go-version: 1.21.x @@ -52,7 +52,7 @@ jobs: components: rustfmt, clippy - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: persist-credentials: false @@ -68,12 +68,12 @@ jobs: permissions: {} steps: - name: Install Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v5 with: go-version: 1.21.x - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: persist-credentials: false @@ -88,7 +88,7 @@ jobs: permissions: {} steps: - name: Install Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v5 with: go-version: 1.18.x @@ -96,7 +96,7 @@ jobs: run: go install golang.org/x/tools/cmd/goimports@v0.24.0 - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: persist-credentials: false @@ -115,12 +115,12 @@ jobs: permissions: {} steps: - name: Install Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v5 with: go-version: 1.21.x - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: persist-credentials: false @@ -139,12 +139,12 @@ jobs: permissions: {} steps: - name: Install Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v5 with: go-version: 1.21.x - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: persist-credentials: false diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml index d889f76141..393b7a7fad 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/semgrep.yml @@ -21,7 +21,7 @@ jobs: container: image: returntocorp/semgrep steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: persist-credentials: false - run: semgrep ci From c529171c9bf55fcc8dbe5c7b9869388f8fcc256c Mon Sep 17 00:00:00 2001 From: colin <102356659+colinlyguo@users.noreply.github.com> Date: Tue, 25 Mar 2025 02:16:43 +0800 Subject: [PATCH 16/45] fix: add sload witness when calculating dynamic gas (#1157) * add sload witness when calculating dynamic gas * change sstore * tweaks * fix * move get sload witness only when in cold sload * remove a fake comment * tweak logs * nit --- core/vm/gas_table.go | 18 ++++++++++++------ core/vm/operations_acl.go | 22 +++++++++++++++++----- params/version.go | 2 +- 3 files changed, 30 insertions(+), 12 deletions(-) diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index fe7c34759c..cc1cdcee39 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -100,6 +100,10 @@ func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySi y, x = stack.Back(1), stack.Back(0) current = evm.StateDB.GetState(contract.Address(), x.Bytes32()) ) + + // Try updating the witness of SSTORE at first to align with reth's witness implementation. + original := evm.StateDB.GetCommittedState(contract.Address(), x.Bytes32()) + // The legacy gas metering only takes into consideration the current state // Legacy rules should be applied if we are in Petersburg (removal of EIP-1283) // OR Constantinople is not active @@ -137,7 +141,6 @@ func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySi if current == value { // noop (1) return params.NetSstoreNoopGas, nil } - original := evm.StateDB.GetCommittedState(contract.Address(), x.Bytes32()) if original == current { if original == (common.Hash{}) { // create slot (2.1.1) return params.NetSstoreInitGas, nil @@ -178,10 +181,6 @@ func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySi // 2.2.2.1. If original value is 0, add SSTORE_SET_GAS - SLOAD_GAS to refund counter. // 2.2.2.2. Otherwise, add SSTORE_RESET_GAS - SLOAD_GAS gas to refund counter. func gasSStoreEIP2200(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - // If we fail the minimum gas availability invariant, fail (0) - if contract.Gas <= params.SstoreSentryGasEIP2200 { - return 0, errors.New("not enough gas for reentrancy sentry") - } // Gas sentry honoured, do the actual gas calculation based on the stored value var ( y, x = stack.Back(1), stack.Back(0) @@ -189,10 +188,17 @@ func gasSStoreEIP2200(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m ) value := common.Hash(y.Bytes32()) + // Try updating the witness of SSTORE at first to align with reth's witness implementation. + original := evm.StateDB.GetCommittedState(contract.Address(), x.Bytes32()) + + // If we fail the minimum gas availability invariant, fail (0) + if contract.Gas <= params.SstoreSentryGasEIP2200 { + return 0, errors.New("not enough gas for reentrancy sentry") + } + if current == value { // noop (1) return params.SloadGasEIP2200, nil } - original := evm.StateDB.GetCommittedState(contract.Address(), x.Bytes32()) if original == current { if original == (common.Hash{}) { // create slot (2.1.1) return params.SstoreSetGasEIP2200, nil diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index d9a8b44102..0a53d62ada 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -27,10 +27,6 @@ import ( func makeGasSStoreFunc(clearingRefund uint64) gasFunc { return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - // If we fail the minimum gas availability invariant, fail (0) - if contract.Gas <= params.SstoreSentryGasEIP2200 { - return 0, errors.New("not enough gas for reentrancy sentry") - } // Gas sentry honoured, do the actual gas calculation based on the stored value var ( y, x = stack.Back(1), stack.peek() @@ -38,6 +34,15 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc { current = evm.StateDB.GetState(contract.Address(), slot) cost = uint64(0) ) + + // Try updating the witness of SSTORE at first to align with reth's witness implementation. + original := evm.StateDB.GetCommittedState(contract.Address(), x.Bytes32()) + + // If we fail the minimum gas availability invariant, fail (0) + if contract.Gas <= params.SstoreSentryGasEIP2200 { + return 0, errors.New("not enough gas for reentrancy sentry") + } + // Check slot presence in the access list if addrPresent, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent { cost = params.ColdSloadCostEIP2929 @@ -57,7 +62,6 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc { // return params.SloadGasEIP2200, nil return cost + params.WarmStorageReadCostEIP2929, nil // SLOAD_GAS } - original := evm.StateDB.GetCommittedState(contract.Address(), x.Bytes32()) if original == current { if original == (common.Hash{}) { // create slot (2.1.1) return cost + params.SstoreSetGasEIP2200, nil @@ -109,6 +113,14 @@ func gasSLoadEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me // If the caller cannot afford the cost, this change will be rolled back // If he does afford it, we can skip checking the same thing later on, during execution evm.StateDB.AddSlotToAccessList(contract.Address(), slot) + + // Try updating the witness of SLOAD to align with reth's witness implementation. + // Another place that needs to change is when calculating the gas cost after Frontier and before EIP-2929. + // Frontier gas cost simply uses params.SloadGasFrontier (i.e., 50 gas), so changing the gas cost there + // might affect code cleanliness. Usually, this won't be a problem because EIP-2929 is enabled by default. + // Thus, adding the SLOAD witness before EIP-2929 is left as a TODO here. + evm.StateDB.GetCommittedState(contract.Address(), loc.Bytes32()) + return params.ColdSloadCostEIP2929, nil } return params.WarmStorageReadCostEIP2929, nil diff --git a/params/version.go b/params/version.go index d3e1cbbc50..2a7eb3c126 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 5 // Major version component of the current release VersionMinor = 8 // Minor version component of the current release - VersionPatch = 29 // Patch version component of the current release + VersionPatch = 30 // Patch version component of the current release VersionMeta = "mainnet" // Version metadata to append to the version string ) From 6451c80d5506c542587c7e0ced59fbb35c8aa94f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Mon, 24 Mar 2025 19:23:08 +0100 Subject: [PATCH 17/45] fix: correctly initialize latest l1msg gauge (#1155) --- core/blockchain.go | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index f3b150c4cb..2b881ceed2 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -149,6 +149,18 @@ var defaultCacheConfig = &CacheConfig{ SnapshotWait: true, } +func updateHeadL1msgGauge(block *types.Block) { + for _, tx := range block.Transactions() { + if msg := tx.AsL1MessageTx(); msg != nil { + // Queue index is guaranteed to fit into int64. + headL1MessageGauge.Update(int64(msg.QueueIndex)) + } else { + // No more L1 messages in this block. + break + } + } +} + // BlockChain represents the canonical chain given a database with a genesis // block. The Blockchain manages chain imports, reverts, chain reorganisations. // @@ -423,6 +435,9 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par triedb.SaveCachePeriodically(bc.cacheConfig.TrieCleanJournal, bc.cacheConfig.TrieCleanRejournal, bc.quit) }() } + + updateHeadL1msgGauge(bc.CurrentBlock()) + return bc, nil } @@ -1255,15 +1270,7 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. } // Note the latest relayed L1 message queue index (if any) - for _, tx := range block.Transactions() { - if msg := tx.AsL1MessageTx(); msg != nil { - // Queue index is guaranteed to fit into int64. - headL1MessageGauge.Update(int64(msg.QueueIndex)) - } else { - // No more L1 messages in this block. - break - } - } + updateHeadL1msgGauge(block) parent := bc.GetHeaderByHash(block.ParentHash()) // block.Time is guaranteed to be larger than parent.Time, From ca9d53938ba95f1b0770744efd25c96771134b61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Mon, 24 Mar 2025 19:34:43 +0100 Subject: [PATCH 18/45] feat: mainnet Euclid params (#1152) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: mainnet Euclid params * update params * adjust timestamp * chore: auto version bump [bot] --------- Co-authored-by: Thegaram <7571518+Thegaram@users.noreply.github.com> --- params/config.go | 17 +++++++++++++---- params/version.go | 2 +- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/params/config.go b/params/config.go index a563041d23..d5f1a2166c 100644 --- a/params/config.go +++ b/params/config.go @@ -378,20 +378,29 @@ var ( CurieBlock: big.NewInt(7096836), DarwinTime: newUint64(1724227200), DarwinV2Time: newUint64(1725264000), + EuclidTime: newUint64(1744815600), + EuclidV2Time: newUint64(1745305200), Clique: &CliqueConfig{ Period: 3, Epoch: 30000, }, + SystemContract: &SystemContractConfig{ + Period: 3, + SystemContractAddress: common.HexToAddress("0x8432728A257646449245558B8b7Dbe51A16c7a4D"), + SystemContractSlot: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000067"), + }, Scroll: ScrollConfig{ UseZktrie: true, MaxTxPerBlock: &ScrollMaxTxPerBlock, MaxTxPayloadBytesPerBlock: &ScrollMaxTxPayloadBytesPerBlock, FeeVaultAddress: &rcfg.ScrollFeeVaultAddress, L1Config: &L1Config{ - L1ChainId: 1, - L1MessageQueueAddress: common.HexToAddress("0x0d7E906BD9cAFa154b048cFa766Cc1E54E39AF9B"), - NumL1MessagesPerBlock: 10, - ScrollChainAddress: common.HexToAddress("0xa13BAF47339d63B743e7Da8741db5456DAc1E556"), + L1ChainId: 1, + L1MessageQueueAddress: common.HexToAddress("0x0d7E906BD9cAFa154b048cFa766Cc1E54E39AF9B"), + L1MessageQueueV2Address: common.HexToAddress("0x56971da63A3C0205184FEF096E9ddFc7A8C2D18a"), + L1MessageQueueV2DeploymentBlock: 22088276, + NumL1MessagesPerBlock: 10, + ScrollChainAddress: common.HexToAddress("0xa13BAF47339d63B743e7Da8741db5456DAc1E556"), }, GenesisStateRoot: &ScrollMainnetGenesisState, }, diff --git a/params/version.go b/params/version.go index 2a7eb3c126..3b6ac95466 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 5 // Major version component of the current release VersionMinor = 8 // Minor version component of the current release - VersionPatch = 30 // Patch version component of the current release + VersionPatch = 31 // Patch version component of the current release VersionMeta = "mainnet" // Version metadata to append to the version string ) From 2ff8403065527745cae784c3c7d0fda3fa9cab81 Mon Sep 17 00:00:00 2001 From: colin <102356659+colinlyguo@users.noreply.github.com> Date: Wed, 26 Mar 2025 17:44:41 +0800 Subject: [PATCH 19/45] fix: add failure retries in testWitness (#1158) * add more logs in testWitness * bump version * add more logs * use statedb.IntermediateRoot * add retries * add more retries * remove root compute retries * tweak * add a fixme * print error --- eth/api.go | 43 +++++++++++++++++++++++++++++++++++-------- params/version.go | 2 +- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/eth/api.go b/eth/api.go index 8a0f094eef..0b380a57b2 100644 --- a/eth/api.go +++ b/eth/api.go @@ -368,36 +368,63 @@ func generateWitness(blockchain *core.BlockChain, block *types.Block) (*stateles if err := blockchain.Validator().ValidateState(block, statedb, receipts, usedGas); err != nil { return nil, fmt.Errorf("failed to validate block %d: %w", block.Number(), err) } - return witness, testWitness(blockchain, block, witness) + + // FIXME: testWitness will fail from time to time, the problem is caused by occasional state root mismatch + // after processing the block based on witness. We need to investigate the root cause and fix it. + for retries := 0; retries < 5; retries++ { + if err = testWitness(blockchain, block, witness); err == nil { + return witness, nil + } else { + log.Warn("Failed to validate witness", "block", block.Number(), "error", err) + } + } + return witness, err } func testWitness(blockchain *core.BlockChain, block *types.Block, witness *stateless.Witness) error { stateRoot := witness.Root() - if diskRoot, _ := rawdb.ReadDiskStateRoot(blockchain.Database(), stateRoot); diskRoot != (common.Hash{}) { + diskRoot, err := rawdb.ReadDiskStateRoot(blockchain.Database(), stateRoot) + if err != nil { + return fmt.Errorf("failed to read disk state root for stateRoot %s: %w", stateRoot.Hex(), err) + } + if diskRoot != (common.Hash{}) { + log.Debug("Using disk root for state root", "stateRoot", stateRoot.Hex(), "diskRoot", diskRoot.Hex()) stateRoot = diskRoot } // Create and populate the state database to serve as the stateless backend statedb, err := state.New(stateRoot, state.NewDatabase(witness.MakeHashDB()), nil) if err != nil { - return fmt.Errorf("failed to create state database: %w", err) + return fmt.Errorf("failed to create state database with stateRoot %s: %w", stateRoot.Hex(), err) } receipts, _, usedGas, err := blockchain.Processor().Process(block, statedb, *blockchain.GetVMConfig()) if err != nil { - return fmt.Errorf("failed to process block %d: %w", block.Number(), err) + return fmt.Errorf("failed to process block %d (hash: %s): %w", block.Number(), block.Hash().Hex(), err) } if err := blockchain.Validator().ValidateState(block, statedb, receipts, usedGas); err != nil { - return fmt.Errorf("failed to validate block %d: %w", block.Number(), err) + return fmt.Errorf("failed to validate block %d (hash: %s): %w", block.Number(), block.Hash().Hex(), err) } postStateRoot := block.Root() - if diskRoot, _ := rawdb.ReadDiskStateRoot(blockchain.Database(), postStateRoot); diskRoot != (common.Hash{}) { + diskRoot, err = rawdb.ReadDiskStateRoot(blockchain.Database(), postStateRoot) + if err != nil { + return fmt.Errorf("failed to read disk state root for postStateRoot %s: %w", postStateRoot.Hex(), err) + } + if diskRoot != (common.Hash{}) { + log.Debug("Using disk root for post state root", "postStateRoot", postStateRoot.Hex(), "diskRoot", diskRoot.Hex()) postStateRoot = diskRoot } - if statedb.GetRootHash() != postStateRoot { - return fmt.Errorf("failed to commit statelessly %d: %w", block.Number(), err) + computedRoot := statedb.GetRootHash() + if computedRoot != postStateRoot { + log.Debug("State root mismatch", "block", block.Number(), "expected", postStateRoot.Hex(), "got", computedRoot) + executionWitness := ToExecutionWitness(witness) + jsonStr, err := json.Marshal(executionWitness) + if err != nil { + return fmt.Errorf("state root mismatch after processing block %d (hash: %s): expected %s, got %s, but failed to marshal witness: %w", block.Number(), block.Hash().Hex(), postStateRoot.Hex(), computedRoot, err) + } + return fmt.Errorf("state root mismatch after processing block %d (hash: %s): expected %s, got %s, witness: %s", block.Number(), block.Hash().Hex(), postStateRoot.Hex(), computedRoot, string(jsonStr)) } return nil } diff --git a/params/version.go b/params/version.go index 3b6ac95466..4716919647 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 5 // Major version component of the current release VersionMinor = 8 // Minor version component of the current release - VersionPatch = 31 // Patch version component of the current release + VersionPatch = 32 // Patch version component of the current release VersionMeta = "mainnet" // Version metadata to append to the version string ) From 24757865c6bbd9becb0256e97e8492d1f73987d9 Mon Sep 17 00:00:00 2001 From: colin <102356659+colinlyguo@users.noreply.github.com> Date: Wed, 26 Mar 2025 19:55:02 +0800 Subject: [PATCH 20/45] fix(api): eth_getProof crash (#1159) * fix(api): eth_getProof crash * return PoseidonCodeHash for zktrie * fix --- internal/ethapi/api.go | 33 ++++++++++++++++++++------------- params/version.go | 2 +- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 83b04f4a61..78e7dbe09c 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -692,7 +692,6 @@ func (s *PublicBlockChainAPI) GetProof(ctx context.Context, address common.Addre storageHash = types.EmptyRootHash } keccakCodeHash := state.GetKeccakCodeHash(address) - poseidonCodeHash := state.GetPoseidonCodeHash(address) storageProof := make([]StorageResult, len(storageKeys)) // if we have a storageTrie, (which means the account exists), we can update the storagehash @@ -701,7 +700,6 @@ func (s *PublicBlockChainAPI) GetProof(ctx context.Context, address common.Addre } else { // no storageTrie means the account does not exist, so the codeHash is the hash of an empty bytearray. keccakCodeHash = codehash.EmptyKeccakCodeHash - poseidonCodeHash = codehash.EmptyPoseidonCodeHash } // create the proof for the storageKeys @@ -723,17 +721,26 @@ func (s *PublicBlockChainAPI) GetProof(ctx context.Context, address common.Addre return nil, proofErr } - return &AccountResult{ - Address: address, - AccountProof: toHexSlice(accountProof), - Balance: (*hexutil.Big)(state.GetBalance(address)), - KeccakCodeHash: keccakCodeHash, - PoseidonCodeHash: poseidonCodeHash, - CodeSize: hexutil.Uint64(state.GetCodeSize(address)), - Nonce: hexutil.Uint64(state.GetNonce(address)), - StorageHash: storageHash, - StorageProof: storageProof, - }, state.Error() + result := &AccountResult{ + Address: address, + AccountProof: toHexSlice(accountProof), + Balance: (*hexutil.Big)(state.GetBalance(address)), + KeccakCodeHash: keccakCodeHash, + CodeSize: hexutil.Uint64(state.GetCodeSize(address)), + Nonce: hexutil.Uint64(state.GetNonce(address)), + StorageHash: storageHash, + StorageProof: storageProof, + } + + if state.IsZktrie() { + if storageTrie != nil { + result.PoseidonCodeHash = state.GetPoseidonCodeHash(address) + } else { + result.PoseidonCodeHash = codehash.EmptyPoseidonCodeHash + } + } + + return result, state.Error() } // GetHeaderByNumber returns the requested canonical block header. diff --git a/params/version.go b/params/version.go index 4716919647..d986e37759 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 5 // Major version component of the current release VersionMinor = 8 // Minor version component of the current release - VersionPatch = 32 // Patch version component of the current release + VersionPatch = 33 // Patch version component of the current release VersionMeta = "mainnet" // Version metadata to append to the version string ) From bbe9809b1203b6ac400ee8eb4ccf02e5411e885a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Faruk=20Irmak?= Date: Wed, 2 Apr 2025 16:17:42 +0300 Subject: [PATCH 21/45] fix: check ancient header hash after decoding (#1162) --- core/rawdb/accessors_chain.go | 15 ++++++++++----- params/version.go | 2 +- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index d89a7d2e21..d9aceeca09 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -26,7 +26,6 @@ import ( "github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/core/types" - "github.com/scroll-tech/go-ethereum/crypto" "github.com/scroll-tech/go-ethereum/ethdb" "github.com/scroll-tech/go-ethereum/log" "github.com/scroll-tech/go-ethereum/params" @@ -301,11 +300,9 @@ func WriteFastTxLookupLimit(db ethdb.KeyValueWriter, number uint64) { func ReadHeaderRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawValue { var data []byte db.ReadAncients(func(reader ethdb.AncientReader) error { - // First try to look up the data in ancient database. Extra hash - // comparison is necessary since ancient database only maintains - // the canonical data. + // First try to look up the data in ancient database. data, _ = reader.Ancient(freezerHeaderTable, number) - if len(data) > 0 && crypto.Keccak256Hash(data) == hash { + if len(data) > 0 { return nil } // If not, try reading from leveldb @@ -337,6 +334,14 @@ func ReadHeader(db ethdb.Reader, hash common.Hash, number uint64) *types.Header log.Error("Invalid block header RLP", "hash", hash, "err", err) return nil } + + // Extra hash + // comparison is necessary since ancient database only maintains + // the canonical data. + if header.Hash() != hash { + return nil + } + return header } diff --git a/params/version.go b/params/version.go index d986e37759..b9cfc5ce6b 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 5 // Major version component of the current release VersionMinor = 8 // Minor version component of the current release - VersionPatch = 33 // Patch version component of the current release + VersionPatch = 34 // Patch version component of the current release VersionMeta = "mainnet" // Version metadata to append to the version string ) From d6b4c96bf1b66e3f5a806875782a47ca498f7440 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Faruk=20Irmak?= Date: Thu, 3 Apr 2025 01:18:49 +0300 Subject: [PATCH 22/45] fix: ignore key-not-found errors in testWitness (#1164) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: ignore key-not-found errors in testWitness * chore: auto version bump [bot] --------- Co-authored-by: omerfirmak <10325815+omerfirmak@users.noreply.github.com> --- eth/api.go | 5 +++-- params/version.go | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/eth/api.go b/eth/api.go index 0b380a57b2..cd8df1111e 100644 --- a/eth/api.go +++ b/eth/api.go @@ -44,6 +44,7 @@ import ( "github.com/scroll-tech/go-ethereum/rollup/rcfg" "github.com/scroll-tech/go-ethereum/rpc" "github.com/scroll-tech/go-ethereum/trie" + "github.com/syndtr/goleveldb/leveldb" ) // PublicEthereumAPI provides an API to access Ethereum full node-related @@ -384,7 +385,7 @@ func generateWitness(blockchain *core.BlockChain, block *types.Block) (*stateles func testWitness(blockchain *core.BlockChain, block *types.Block, witness *stateless.Witness) error { stateRoot := witness.Root() diskRoot, err := rawdb.ReadDiskStateRoot(blockchain.Database(), stateRoot) - if err != nil { + if err != nil && !errors.Is(err, leveldb.ErrNotFound) { return fmt.Errorf("failed to read disk state root for stateRoot %s: %w", stateRoot.Hex(), err) } if diskRoot != (common.Hash{}) { @@ -409,7 +410,7 @@ func testWitness(blockchain *core.BlockChain, block *types.Block, witness *state postStateRoot := block.Root() diskRoot, err = rawdb.ReadDiskStateRoot(blockchain.Database(), postStateRoot) - if err != nil { + if err != nil && !errors.Is(err, leveldb.ErrNotFound) { return fmt.Errorf("failed to read disk state root for postStateRoot %s: %w", postStateRoot.Hex(), err) } if diskRoot != (common.Hash{}) { diff --git a/params/version.go b/params/version.go index b9cfc5ce6b..4b80652f5b 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 5 // Major version component of the current release VersionMinor = 8 // Minor version component of the current release - VersionPatch = 34 // Patch version component of the current release + VersionPatch = 35 // Patch version component of the current release VersionMeta = "mainnet" // Version metadata to append to the version string ) From d482b2fdb46e4883dd222dae9281d7ab47ec5f5f Mon Sep 17 00:00:00 2001 From: georgehao Date: Thu, 3 Apr 2025 11:46:07 +0800 Subject: [PATCH 23/45] add non congested gas fee to fee history (#1163) * add non congested gas fee to fee history * address comment --- eth/gasprice/feehistory.go | 45 ++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/eth/gasprice/feehistory.go b/eth/gasprice/feehistory.go index 0d38d7628e..571dfa3d74 100644 --- a/eth/gasprice/feehistory.go +++ b/eth/gasprice/feehistory.go @@ -84,7 +84,7 @@ func (s sortGasAndReward) Less(i, j int) bool { // processBlock takes a blockFees structure with the blockNumber, the header and optionally // the block field filled in, retrieves the block from the backend if not present yet and // fills in the rest of the fields. -func (oracle *Oracle) processBlock(bf *blockFees, percentiles []float64) { +func (oracle *Oracle) processBlock(bf *blockFees, percentiles []float64, nonCongestedPrice *big.Int) { chainconfig := oracle.backend.ChainConfig() if bf.results.baseFee = bf.header.BaseFee; bf.results.baseFee == nil { bf.results.baseFee = new(big.Int) @@ -121,7 +121,12 @@ func (oracle *Oracle) processBlock(bf *blockFees, percentiles []float64) { sorter := make(sortGasAndReward, len(bf.block.Transactions())) for i, tx := range bf.block.Transactions() { - reward, _ := tx.EffectiveGasTip(bf.block.BaseFee()) + var reward *big.Int + if nonCongestedPrice != nil { + reward = nonCongestedPrice + } else { + reward, _ = tx.EffectiveGasTip(bf.block.BaseFee()) + } sorter[i] = txGasAndReward{gasUsed: bf.receipts[i].GasUsed, reward: reward} } sort.Sort(sorter) @@ -144,23 +149,25 @@ func (oracle *Oracle) processBlock(bf *blockFees, percentiles []float64) { // also returned if requested and available. // Note: an error is only returned if retrieving the head header has failed. If there are no // retrievable blocks in the specified range then zero block count is returned with no error. -func (oracle *Oracle) resolveBlockRange(ctx context.Context, lastBlock rpc.BlockNumber, blocks int) (*types.Block, []*types.Receipt, uint64, int, error) { +func (oracle *Oracle) resolveBlockRange(ctx context.Context, lastBlock rpc.BlockNumber, blocks int) (*types.Block, []*types.Receipt, uint64, int, *types.Header, error) { var ( headBlock rpc.BlockNumber pendingBlock *types.Block pendingReceipts types.Receipts + headHeader *types.Header ) // query either pending block or head header and set headBlock if lastBlock == rpc.PendingBlockNumber { if pendingBlock, pendingReceipts = oracle.backend.PendingBlockAndReceipts(); pendingBlock != nil { lastBlock = rpc.BlockNumber(pendingBlock.NumberU64()) headBlock = lastBlock - 1 + headHeader = pendingBlock.Header() } else { // pending block not supported by backend, process until latest block lastBlock = rpc.LatestBlockNumber blocks-- if blocks == 0 { - return nil, nil, 0, 0, nil + return nil, nil, 0, 0, nil, nil } } } @@ -168,27 +175,28 @@ func (oracle *Oracle) resolveBlockRange(ctx context.Context, lastBlock rpc.Block // if pending block is not fetched then we retrieve the head header to get the head block number if latestHeader, err := oracle.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber); err == nil { headBlock = rpc.BlockNumber(latestHeader.Number.Uint64()) + headHeader = latestHeader } else { - return nil, nil, 0, 0, err + return nil, nil, 0, 0, nil, err } } if lastBlock == rpc.LatestBlockNumber { lastBlock = headBlock } else if pendingBlock == nil && lastBlock > headBlock { - return nil, nil, 0, 0, fmt.Errorf("%w: requested %d, head %d", errRequestBeyondHead, lastBlock, headBlock) + return nil, nil, 0, 0, nil, fmt.Errorf("%w: requested %d, head %d", errRequestBeyondHead, lastBlock, headBlock) } if lastBlock == rpc.FinalizedBlockNumber { if latestFinalizedHeader, err := oracle.backend.HeaderByNumber(ctx, rpc.FinalizedBlockNumber); err == nil { lastBlock = rpc.BlockNumber(latestFinalizedHeader.Number.Uint64()) } else { - return nil, nil, 0, 0, err + return nil, nil, 0, 0, nil, err } } // ensure not trying to retrieve before genesis if rpc.BlockNumber(blocks) > lastBlock+1 { blocks = int(lastBlock + 1) } - return pendingBlock, pendingReceipts, uint64(lastBlock), blocks, nil + return pendingBlock, pendingReceipts, uint64(lastBlock), blocks, headHeader, nil } // FeeHistory returns data relevant for fee estimation based on the specified range of blocks. @@ -230,12 +238,26 @@ func (oracle *Oracle) FeeHistory(ctx context.Context, blocks int, unresolvedLast pendingReceipts []*types.Receipt err error ) - pendingBlock, pendingReceipts, lastBlock, blocks, err := oracle.resolveBlockRange(ctx, unresolvedLastBlock, blocks) + pendingBlock, pendingReceipts, lastBlock, blocks, headHeader, err := oracle.resolveBlockRange(ctx, unresolvedLastBlock, blocks) if err != nil || blocks == 0 { return common.Big0, nil, nil, nil, err } oldestBlock := lastBlock + 1 - uint64(blocks) + // If pending txs are less than oracle.congestedThreshold, we consider the network to be non-congested and suggest + // a minimal tip cap. This is to prevent users from overpaying for gas when the network is not congested and a few + // high-priced txs are causing the suggested tip cap to be high. + var nonCongestedPrice *big.Int + pendingTxCount, _ := oracle.backend.StatsWithMinBaseFee(headHeader.BaseFee) + if pendingTxCount < oracle.congestedThreshold { + // Before Curie (EIP-1559), we need to return the total suggested gas price. After Curie we return defaultGasTipCap wei as the tip cap, + // as the base fee is set separately or added manually for legacy transactions. + nonCongestedPrice = oracle.defaultGasTipCap + if !oracle.backend.ChainConfig().IsCurie(headHeader.Number) { + nonCongestedPrice = oracle.defaultBasePrice + } + } + var ( next = oldestBlock results = make(chan *blockFees, blocks) @@ -244,6 +266,7 @@ func (oracle *Oracle) FeeHistory(ctx context.Context, blocks int, unresolvedLast for i, p := range rewardPercentiles { binary.LittleEndian.PutUint64(percentileKey[i*8:(i+1)*8], math.Float64bits(p)) } + for i := 0; i < maxBlockFetchers && i < blocks; i++ { go func() { for { @@ -257,7 +280,7 @@ func (oracle *Oracle) FeeHistory(ctx context.Context, blocks int, unresolvedLast if pendingBlock != nil && blockNumber >= pendingBlock.NumberU64() { fees.block, fees.receipts = pendingBlock, pendingReceipts fees.header = fees.block.Header() - oracle.processBlock(fees, rewardPercentiles) + oracle.processBlock(fees, rewardPercentiles, nonCongestedPrice) results <- fees } else { cacheKey := struct { @@ -279,7 +302,7 @@ func (oracle *Oracle) FeeHistory(ctx context.Context, blocks int, unresolvedLast fees.header, fees.err = oracle.backend.HeaderByNumber(ctx, rpc.BlockNumber(blockNumber)) } if fees.header != nil && fees.err == nil { - oracle.processBlock(fees, rewardPercentiles) + oracle.processBlock(fees, rewardPercentiles, nonCongestedPrice) if fees.err == nil { oracle.historyCache.Add(cacheKey, fees.results) } From ad017f50ec699103861162d5ef8ba5d65df59d4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Faruk=20Irmak?= Date: Tue, 8 Apr 2025 11:32:25 +0300 Subject: [PATCH 24/45] fix: an attempt at making witness generation more stable (#1166) --- core/blockchain.go | 4 ++-- core/state/state_object.go | 12 +++++++++++- core/state/statedb.go | 23 ++++++++++++++++++----- eth/api.go | 19 ++++++------------- params/version.go | 2 +- rollup/pipeline/pipeline.go | 2 +- 6 files changed, 39 insertions(+), 23 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 2b881ceed2..1a46a315c2 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1699,7 +1699,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er } // Enable prefetching to pull in trie node paths while processing transactions - statedb.StartPrefetcher("chain", nil) + statedb.StartPrefetcher("chain") activeState = statedb // If we have a followup block, run that against the current state to pre-cache @@ -1836,7 +1836,7 @@ func (bc *BlockChain) BuildAndWriteBlock(parentBlock *types.Block, header *types return nil, NonStatTy, err } - statedb.StartPrefetcher("l1sync", nil) + statedb.StartPrefetcher("l1sync") defer statedb.StopPrefetcher() header.ParentHash = parentBlock.Hash() diff --git a/core/state/state_object.go b/core/state/state_object.go index 4fb9e82c2e..2b615d9408 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -21,6 +21,7 @@ import ( "fmt" "io" "math/big" + "slices" "time" "github.com/scroll-tech/go-ethereum/common" @@ -344,8 +345,17 @@ func (s *stateObject) updateTrie(db Database) Trie { tr := s.getTrie(db) hasher := s.db.hasher + sortedPendingStorages := make([]common.Hash, 0, len(s.pendingStorage)) + for key := range s.pendingStorage { + sortedPendingStorages = append(sortedPendingStorages, key) + } + slices.SortFunc(sortedPendingStorages, func(a, b common.Hash) int { + return bytes.Compare(a[:], b[:]) + }) + usedStorage := make([][]byte, 0, len(s.pendingStorage)) - for key, value := range s.pendingStorage { + for _, key := range sortedPendingStorages { + value := s.pendingStorage[key] // Skip noop changes, persist actual changes if value == s.originStorage[key] { continue diff --git a/core/state/statedb.go b/core/state/statedb.go index 7affd81fc4..94a89349af 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -18,9 +18,11 @@ package state import ( + "bytes" "errors" "fmt" "math/big" + "slices" "sort" "time" @@ -160,18 +162,20 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) return sdb, nil } +// WithWitness sets the witness for the state database. +func (s *StateDB) WithWitness(witness *stateless.Witness) { + s.witness = witness +} + // StartPrefetcher initializes a new trie prefetcher to pull in nodes from the // state trie concurrently while the state is mutated so that when we reach the // commit phase, most of the needed data is already hot. -func (s *StateDB) StartPrefetcher(namespace string, witness *stateless.Witness) { +func (s *StateDB) StartPrefetcher(namespace string) { if s.prefetcher != nil { s.prefetcher.close() s.prefetcher = nil } - // Enable witness collection if requested - s.witness = witness - if s.snap != nil { s.prefetcher = newTriePrefetcher(s.db, s.originalRoot, namespace) } @@ -967,8 +971,17 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { s.trie = trie } } - usedAddrs := make([][]byte, 0, len(s.stateObjectsPending)) + + sortedPendingAddrs := make([]common.Address, 0, len(s.stateObjectsPending)) for addr := range s.stateObjectsPending { + sortedPendingAddrs = append(sortedPendingAddrs, addr) + } + slices.SortFunc(sortedPendingAddrs, func(a, b common.Address) int { + return bytes.Compare(a[:], b[:]) + }) + + usedAddrs := make([][]byte, 0, len(s.stateObjectsPending)) + for _, addr := range sortedPendingAddrs { if obj := s.stateObjects[addr]; obj.deleted { s.deleteStateObject(obj) s.AccountDeleted += 1 diff --git a/eth/api.go b/eth/api.go index cd8df1111e..e8b66b9e4e 100644 --- a/eth/api.go +++ b/eth/api.go @@ -29,6 +29,8 @@ import ( "strings" "time" + "github.com/syndtr/goleveldb/leveldb" + "github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/common/hexutil" "github.com/scroll-tech/go-ethereum/core" @@ -44,7 +46,6 @@ import ( "github.com/scroll-tech/go-ethereum/rollup/rcfg" "github.com/scroll-tech/go-ethereum/rpc" "github.com/scroll-tech/go-ethereum/trie" - "github.com/syndtr/goleveldb/leveldb" ) // PublicEthereumAPI provides an API to access Ethereum full node-related @@ -354,13 +355,11 @@ func generateWitness(blockchain *core.BlockChain, block *types.Block) (*stateles if err != nil { return nil, fmt.Errorf("failed to retrieve parent state: %w", err) } + statedb.WithWitness(witness) // Collect storage locations that prover needs but sequencer might not touch necessarily statedb.GetState(rcfg.L2MessageQueueAddress, rcfg.WithdrawTrieRootSlot) - statedb.StartPrefetcher("debug_execution_witness", witness) - defer statedb.StopPrefetcher() - receipts, _, usedGas, err := blockchain.Processor().Process(block, statedb, *blockchain.GetVMConfig()) if err != nil { return nil, fmt.Errorf("failed to process block %d: %w", block.Number(), err) @@ -370,16 +369,10 @@ func generateWitness(blockchain *core.BlockChain, block *types.Block) (*stateles return nil, fmt.Errorf("failed to validate block %d: %w", block.Number(), err) } - // FIXME: testWitness will fail from time to time, the problem is caused by occasional state root mismatch - // after processing the block based on witness. We need to investigate the root cause and fix it. - for retries := 0; retries < 5; retries++ { - if err = testWitness(blockchain, block, witness); err == nil { - return witness, nil - } else { - log.Warn("Failed to validate witness", "block", block.Number(), "error", err) - } + if err = testWitness(blockchain, block, witness); err != nil { + return nil, err } - return witness, err + return witness, nil } func testWitness(blockchain *core.BlockChain, block *types.Block, witness *stateless.Witness) error { diff --git a/params/version.go b/params/version.go index 4b80652f5b..8744e190e3 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 5 // Major version component of the current release VersionMinor = 8 // Minor version component of the current release - VersionPatch = 35 // Patch version component of the current release + VersionPatch = 36 // Patch version component of the current release VersionMeta = "mainnet" // Version metadata to append to the version string ) diff --git a/rollup/pipeline/pipeline.go b/rollup/pipeline/pipeline.go index 77ac3aee24..90c6149b38 100644 --- a/rollup/pipeline/pipeline.go +++ b/rollup/pipeline/pipeline.go @@ -228,7 +228,7 @@ func sendCancellable[T any, C comparable](resCh chan T, msg T, cancelCh <-chan C } func (p *Pipeline) traceAndApplyStage(txsIn <-chan *types.Transaction) (<-chan error, <-chan *BlockCandidate, error) { - p.state.StartPrefetcher("miner", nil) + p.state.StartPrefetcher("miner") downstreamCh := make(chan *BlockCandidate, p.downstreamChCapacity()) resCh := make(chan error) p.wg.Add(1) From 8a150b44ffa24c7a332164bbc98d669f9c5ea003 Mon Sep 17 00:00:00 2001 From: colin <102356659+colinlyguo@users.noreply.github.com> Date: Wed, 9 Apr 2025 20:43:13 +0800 Subject: [PATCH 25/45] feat(txpool): more txn tracing logs (#1167) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(txpool): increase level of txn tracing logs * simplify * tweak logs * chore: auto version bump [bot] --- core/tx_pool.go | 75 ++++++++++++++++++++++++++++++++--------------- params/version.go | 2 +- 2 files changed, 52 insertions(+), 25 deletions(-) diff --git a/core/tx_pool.go b/core/tx_pool.go index 0109d56776..0b27de7a08 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -106,7 +106,7 @@ var ( evictionInterval = time.Minute // Time interval to check for evictable transactions statsReportInterval = 8 * time.Second // Time interval to report transaction pool stats - dumpReorgTxHashThreshold = 100 // Number of transaction hashse to dump when runReorg + dumpReorgTxHashThreshold = 100 // Number of transaction hashes to dump when runReorg ) var ( @@ -467,7 +467,7 @@ func (pool *TxPool) loop() { if time.Since(pool.beats[addr]) > pool.config.Lifetime { list := pool.queue[addr].Flatten() for _, tx := range list { - log.Trace("Evicting queued transaction due to timeout", "account", addr.Hex(), "hash", tx.Hash().Hex(), "lifetime sec", time.Since(pool.beats[addr]).Seconds(), "lifetime limit sec", pool.config.Lifetime.Seconds()) + log.Debug("Evicting queued transaction due to timeout", "account", addr.Hex(), "hash", tx.Hash().Hex(), "lifetime sec", time.Since(pool.beats[addr]).Seconds(), "lifetime limit sec", pool.config.Lifetime.Seconds()) pool.removeTx(tx.Hash(), true) } queuedEvictionMeter.Mark(int64(len(list))) @@ -483,7 +483,7 @@ func (pool *TxPool) loop() { if time.Since(pool.beats[addr]) > pool.config.Lifetime { list := pool.pending[addr].Flatten() for _, tx := range list { - log.Trace("Evicting pending transaction due to timeout", "account", addr.Hex(), "hash", tx.Hash().Hex(), "lifetime sec", time.Since(pool.beats[addr]).Seconds(), "lifetime limit sec", pool.config.Lifetime.Seconds()) + log.Debug("Evicting pending transaction due to timeout", "account", addr.Hex(), "hash", tx.Hash().Hex(), "lifetime sec", time.Since(pool.beats[addr]).Seconds(), "lifetime limit sec", pool.config.Lifetime.Seconds()) pool.removeTx(tx.Hash(), true) } pendingEvictionMeter.Mark(int64(len(list))) @@ -546,6 +546,7 @@ func (pool *TxPool) SetGasPrice(price *big.Int) { // pool.priced is sorted by GasFeeCap, so we have to iterate through pool.all instead drop := pool.all.RemotesBelowTip(price) for _, tx := range drop { + log.Debug("Removing transaction below price limit", "hash", tx.Hash().Hex(), "price", tx.GasFeeCap().String(), "limit", price.String()) pool.removeTx(tx.Hash(), false) } pool.priced.Removed(len(drop)) @@ -852,13 +853,13 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e // If the transaction is already known, discard it hash := tx.Hash() if pool.all.Get(hash) != nil { - log.Trace("Discarding already known transaction", "hash", hash.Hex()) + log.Debug("Discarding already known transaction when adding the transaction", "hash", hash.Hex()) knownTxMeter.Mark(1) return false, ErrAlreadyKnown } if pool.IsMiner() && rawdb.IsSkippedTransaction(pool.chain.Database(), hash) { - log.Trace("Discarding already known skipped transaction", "hash", hash.Hex()) + log.Debug("Discarding already known skipped transaction when adding the transaction", "hash", hash.Hex()) knownSkippedTxMeter.Mark(1) return false, ErrAlreadyKnown } @@ -869,7 +870,7 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e // If the transaction fails basic validation, discard it if err := pool.validateTx(tx, isLocal); err != nil { - log.Trace("Discarding invalid transaction", "hash", hash.Hex(), "err", err) + log.Debug("Discarding invalid transaction when adding the transaction", "hash", hash.Hex(), "err", err) invalidTxMeter.Mark(1) return false, err } @@ -877,7 +878,7 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e if uint64(pool.all.Slots()+numSlots(tx)) > pool.config.GlobalSlots+pool.config.GlobalQueue { // If the new transaction is underpriced, don't accept it if !isLocal && pool.priced.Underpriced(tx) { - log.Trace("Discarding underpriced transaction", "hash", hash.Hex(), "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap()) + log.Debug("Discarding underpriced transaction when transaction pool is full", "hash", hash.Hex(), "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap()) underpricedTxMeter.Mark(1) return false, ErrUnderpriced } @@ -897,7 +898,7 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e // Special case, we still can't make the room for the new remote one. if !isLocal && !success { - log.Trace("Discarding overflown transaction", "hash", hash.Hex()) + log.Debug("Discarding overflown transaction when transaction pool is full", "hash", hash.Hex(), "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap()) overflowedTxMeter.Mark(1) return false, ErrTxPoolOverflow } @@ -905,7 +906,7 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e pool.changesSinceReorg += len(drop) // Kick out the underpriced remote transactions. for _, tx := range drop { - log.Trace("Discarding freshly underpriced transaction", "hash", tx.Hash(), "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap()) + log.Debug("Discarding underpriced transaction when transaction pool is full", "hash", tx.Hash().Hex(), "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap()) underpricedTxMeter.Mark(1) pool.removeTx(tx.Hash(), false) } @@ -916,11 +917,13 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e // Nonce already pending, check if required price bump is met inserted, old := list.Add(tx, pool.currentState, pool.config.PriceBump, pool.chainconfig, pool.currentHead) if !inserted { + log.Debug("Discarding underpriced pending transaction", "hash", hash.Hex(), "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap()) pendingDiscardMeter.Mark(1) return false, ErrReplaceUnderpriced } // New transaction is better, replace old one if old != nil { + log.Debug("Replacing pending transaction", "hash", tx.Hash().Hex(), "old", old.Hash().Hex()) pool.all.Remove(old.Hash()) pool.calculateTxsLifecycle(types.Transactions{old}, time.Now()) pool.priced.Removed(1) @@ -930,7 +933,7 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e pool.priced.Put(tx, isLocal) pool.journalTx(from, tx) pool.queueTxEvent(tx) - log.Trace("Pooled new executable transaction", "hash", hash.Hex(), "from", from.Hex(), "to", tx.To()) + log.Debug("Pooled new executable transaction", "hash", hash.Hex(), "from", from.Hex(), "to", tx.To(), "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap()) // Successful promotion, bump the heartbeat pool.beats[from] = time.Now() @@ -952,7 +955,7 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e } pool.journalTx(from, tx) - log.Trace("Pooled new future transaction", "hash", hash.Hex(), "from", from.Hex(), "to", tx.To()) + log.Debug("Pooled new future transaction", "hash", hash.Hex(), "from", from.Hex(), "to", tx.To(), "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap()) return replaced, nil } @@ -969,11 +972,13 @@ func (pool *TxPool) enqueueTx(hash common.Hash, tx *types.Transaction, local boo inserted, old := pool.queue[from].Add(tx, pool.currentState, pool.config.PriceBump, pool.chainconfig, pool.currentHead) if !inserted { // An older transaction was better, discard this + log.Debug("Discarding underpriced queued transaction", "hash", hash.Hex(), "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap()) queuedDiscardMeter.Mark(1) return false, ErrReplaceUnderpriced } // Discard any previous transaction and mark this if old != nil { + log.Debug("Replacing queued transaction", "hash", tx.Hash().Hex(), "old", old.Hash().Hex()) pool.all.Remove(old.Hash()) pool.priced.Removed(1) pool.calculateTxsLifecycle(types.Transactions{old}, time.Now()) @@ -1023,6 +1028,7 @@ func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.T // Account pending list is full if uint64(list.Len()) >= pool.config.AccountPendingLimit { + log.Debug("Discarding pending transaction when account pending limit is reached", "hash", hash.Hex(), "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap()) pool.all.Remove(hash) pool.calculateTxsLifecycle(types.Transactions{tx}, time.Now()) pool.priced.Removed(1) @@ -1033,6 +1039,7 @@ func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.T inserted, old := list.Add(tx, pool.currentState, pool.config.PriceBump, pool.chainconfig, pool.currentHead) if !inserted { // An older transaction was better, discard this + log.Debug("Discarding underpriced pending transaction", "hash", hash.Hex(), "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap()) pool.all.Remove(hash) pool.calculateTxsLifecycle(types.Transactions{tx}, time.Now()) pool.priced.Removed(1) @@ -1041,6 +1048,7 @@ func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.T } // Otherwise discard any previous transaction and mark this if old != nil { + log.Debug("Replacing pending transaction", "hash", tx.Hash().Hex(), "old", old.Hash().Hex()) pool.all.Remove(old.Hash()) pool.calculateTxsLifecycle(types.Transactions{old}, time.Now()) pool.priced.Removed(1) @@ -1049,7 +1057,7 @@ func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.T // Nothing was replaced, bump the pending counter pendingGauge.Inc(1) } - log.Trace("Transaction promoted from future queue to pending", "hash", hash.Hex(), "from", addr.Hex(), "to", tx.To()) + log.Debug("Transaction promoted from future queue to pending", "hash", hash.Hex(), "from", addr.Hex(), "to", tx.To()) // Set the potentially new pending nonce and notify any subsystems of the new tx pool.pendingNonces.set(addr, tx.Nonce()+1) @@ -1222,7 +1230,7 @@ func (pool *TxPool) removeTx(hash common.Hash, outofbound bool) { return } - log.Trace("Removing transaction from pool", "hash", hash.Hex()) + log.Debug("Removing transaction from pool", "hash", hash.Hex()) addr, _ := types.Sender(pool.signer, tx) // already validated during insertion @@ -1452,10 +1460,10 @@ func (pool *TxPool) runReorg(done chan struct{}, reset *txpoolResetRequest, dirt } pool.txFeed.Send(NewTxsEvent{txs}) - log.Debug("runReorg", "len(txs)", len(txs)) + log.Trace("runReorg", "len(txs)", len(txs)) if len(txs) > dumpReorgTxHashThreshold { for _, txs := range txs { - log.Debug("dumping runReorg tx hashes", "txHash", txs.Hash().Hex()) + log.Trace("dumping runReorg tx hashes", "txHash", txs.Hash().Hex()) } } } @@ -1524,6 +1532,15 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) { } } reinject = types.TxDifference(discarded, included) + + for _, tx := range discarded { + log.Debug("TXPOOL_REORG: TX removed from old chain", "hash", tx.Hash().Hex()) + } + + for _, tx := range included { + log.Debug("TXPOOL_REORG: TX added in the chain", "hash", tx.Hash().Hex()) + } + } } } @@ -1541,7 +1558,9 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) { pool.currentMaxGas = newHead.GasLimit // Inject any transactions discarded due to reorgs - log.Debug("Reinjecting stale transactions", "count", len(reinject)) + if len(reinject) > 0 { + log.Debug("Reinjecting stale transactions", "count", len(reinject)) + } senderCacher.recover(pool.signer, reinject) pool.addTxsLocked(reinject, false) @@ -1577,6 +1596,9 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) []*types.Trans hash := tx.Hash() pool.all.Remove(hash) pool.calculateTxsLifecycle(types.Transactions{tx}, time.Now()) + // This is a special case where the transaction was already included in a block, + // thus no need to mark it as removed, setting a trace log level to avoid confusion. + log.Trace("Removed queued transaction with low nonce", "hash", hash.Hex()) } log.Trace("Removed old queued transactions", "count", len(forwards)) @@ -1587,6 +1609,7 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) []*types.Trans hash := tx.Hash() pool.all.Remove(hash) pool.calculateTxsLifecycle(types.Transactions{tx}, time.Now()) + log.Debug("Removed unpayable queued transaction", "hash", hash.Hex()) } log.Trace("Removed unpayable queued transactions", "count", len(drops)) queuedNofundsMeter.Mark(int64(len(drops))) @@ -1610,7 +1633,7 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) []*types.Trans hash := tx.Hash() pool.all.Remove(hash) pool.calculateTxsLifecycle(types.Transactions{tx}, time.Now()) - log.Trace("Removed cap-exceeding queued transaction", "hash", hash.Hex()) + log.Debug("Removed cap-exceeding queued transaction", "hash", hash.Hex()) } queuedRateLimitMeter.Mark(int64(len(caps))) } @@ -1668,7 +1691,7 @@ func (pool *TxPool) truncatePending() { // Update the account nonce to the dropped transaction // note: this will set pending nonce to the min nonce from the discarded txs pool.pendingNonces.setIfLower(addr, tx.Nonce()) - log.Trace("Removed pending transaction to comply with hard limit", "hash", hash.Hex()) + log.Debug("Removed pending transaction to comply with hard limit", "hash", hash.Hex()) } pool.priced.Removed(len(caps)) pendingGauge.Dec(int64(len(caps))) @@ -1721,7 +1744,7 @@ func (pool *TxPool) truncatePending() { // Update the account nonce to the dropped transaction pool.pendingNonces.setIfLower(offenders[i], tx.Nonce()) - log.Trace("Removed fairness-exceeding pending transaction", "hash", hash.Hex()) + log.Debug("Removed fairness-exceeding pending transaction", "hash", hash.Hex()) } pool.priced.Removed(len(caps)) pendingGauge.Dec(int64(len(caps))) @@ -1749,7 +1772,7 @@ func (pool *TxPool) truncatePending() { // Update the account nonce to the dropped transaction pool.pendingNonces.setIfLower(addr, tx.Nonce()) - log.Trace("Removed fairness-exceeding pending transaction", "hash", hash) + log.Debug("Removed fairness-exceeding pending transaction", "hash", hash.Hex()) } pool.priced.Removed(len(caps)) pendingGauge.Dec(int64(len(caps))) @@ -1792,6 +1815,7 @@ func (pool *TxPool) truncateQueue() { // Drop all transactions if they are less than the overflow if size := uint64(list.Len()); size <= drop { for _, tx := range list.Flatten() { + log.Debug("Removed fairness-exceeding queued transaction", "hash", tx.Hash().Hex()) pool.removeTx(tx.Hash(), true) } drop -= size @@ -1801,6 +1825,7 @@ func (pool *TxPool) truncateQueue() { // Otherwise drop only last few transactions txs := list.Flatten() for i := len(txs) - 1; i >= 0 && drop > 0; i-- { + log.Debug("Removed fairness-exceeding queued transaction", "hash", txs[i].Hash().Hex()) pool.removeTx(txs[i].Hash(), true) drop-- queuedRateLimitMeter.Mark(1) @@ -1826,22 +1851,24 @@ func (pool *TxPool) demoteUnexecutables() { hash := tx.Hash() pool.all.Remove(hash) pool.calculateTxsLifecycle(types.Transactions{tx}, time.Now()) - log.Trace("Removed old pending transaction", "hash", hash.Hex()) + // This is a special case where the transaction was already included in a block, + // thus no need to mark it as removed, setting a trace log level to avoid confusion. + log.Trace("Removed pending transaction with low nonce", "hash", hash.Hex()) } // Drop all transactions that are too costly (low balance or out of gas), and queue any invalids back for later costLimit := pool.currentState.GetBalance(addr) drops, invalids := list.FilterF(costLimit, pool.currentMaxGas, pool.executableTxFilter(costLimit)) for _, tx := range drops { hash := tx.Hash() - log.Trace("Removed unpayable pending transaction", "hash", hash.Hex()) pool.all.Remove(hash) pool.calculateTxsLifecycle(types.Transactions{tx}, time.Now()) + log.Debug("Removed unpayable pending transaction", "hash", hash.Hex()) } pendingNofundsMeter.Mark(int64(len(drops))) for _, tx := range invalids { hash := tx.Hash() - log.Trace("Demoting pending transaction", "hash", hash.Hex()) + log.Debug("Demoting pending transaction", "hash", hash.Hex()) // Internal shuffle shouldn't touch the lookup set. pool.enqueueTx(hash, tx, false, false) @@ -1855,7 +1882,7 @@ func (pool *TxPool) demoteUnexecutables() { gapped := list.Cap(0) for _, tx := range gapped { hash := tx.Hash() - log.Error("Demoting invalidated transaction", "hash", hash) + log.Error("Demoting invalidated transaction", "hash", hash.Hex()) // Internal shuffle shouldn't touch the lookup set. pool.enqueueTx(hash, tx, false, false) diff --git a/params/version.go b/params/version.go index 8744e190e3..23f6311392 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 5 // Major version component of the current release VersionMinor = 8 // Minor version component of the current release - VersionPatch = 36 // Patch version component of the current release + VersionPatch = 37 // Patch version component of the current release VersionMeta = "mainnet" // Version metadata to append to the version string ) From bda20891ae174bfa7553d35ad5360a32b39d2a40 Mon Sep 17 00:00:00 2001 From: colin <102356659+colinlyguo@users.noreply.github.com> Date: Thu, 10 Apr 2025 16:16:03 +0800 Subject: [PATCH 26/45] fix(txpool): tracing txn executed status (#1168) * fix(txpool): tx journey executed status tracing * fix * bump version * tweak --- core/tx_pool.go | 12 +++++------- params/version.go | 2 +- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/core/tx_pool.go b/core/tx_pool.go index 0b27de7a08..bf36bf281a 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -434,6 +434,9 @@ func (pool *TxPool) loop() { if ev.Block != nil { pool.requestReset(head.Header(), ev.Block.Header()) head = ev.Block + for _, tx := range head.Transactions() { + log.Debug("TX is included in a block", "hash", tx.Hash().Hex()) + } } // System shutdown. @@ -1533,14 +1536,9 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) { } reinject = types.TxDifference(discarded, included) - for _, tx := range discarded { - log.Debug("TXPOOL_REORG: TX removed from old chain", "hash", tx.Hash().Hex()) + for _, tx := range reinject { + log.Debug("TX is removed from old chain due to reorg", "hash", tx.Hash().Hex()) } - - for _, tx := range included { - log.Debug("TXPOOL_REORG: TX added in the chain", "hash", tx.Hash().Hex()) - } - } } } diff --git a/params/version.go b/params/version.go index 23f6311392..94e8f3fe02 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 5 // Major version component of the current release VersionMinor = 8 // Minor version component of the current release - VersionPatch = 37 // Patch version component of the current release + VersionPatch = 38 // Patch version component of the current release VersionMeta = "mainnet" // Version metadata to append to the version string ) From d178b00fb2511a43e35919a73fdf0779c88ba40f Mon Sep 17 00:00:00 2001 From: Morty <70688412+yiweichi@users.noreply.github.com> Date: Fri, 18 Apr 2025 15:17:25 +0800 Subject: [PATCH 27/45] feat: update L2 base fee formula (#1169) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: update L2 base fee formula * chore: auto version bump [bot] * fix: lint * fix: upgrade ci runner * update multiplier --- .github/workflows/semgrep.yml | 2 +- consensus/misc/eip1559.go | 8 ++++---- consensus/misc/eip1559_test.go | 14 +++++++------- core/state_processor_test.go | 4 ++-- params/version.go | 2 +- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml index 393b7a7fad..8b3f139d47 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/semgrep.yml @@ -14,7 +14,7 @@ name: Semgrep jobs: semgrep: name: semgrep/ci - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 permissions: {} env: SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} diff --git a/consensus/misc/eip1559.go b/consensus/misc/eip1559.go index 483878fc0e..382ad2ada5 100644 --- a/consensus/misc/eip1559.go +++ b/consensus/misc/eip1559.go @@ -55,12 +55,12 @@ func CalcBaseFee(config *params.ChainConfig, parent *types.Header, parentL1BaseF return big.NewInt(10000000) // 0.01 Gwei } l2SequencerFee := big.NewInt(1000000) // 0.001 Gwei - provingFee := big.NewInt(38200000) // 0.0382 Gwei + provingFee := big.NewInt(14680000) // 0.01468 Gwei - // L1_base_fee * 0.00017 + // L1_base_fee * 0.000034 verificationFee := parentL1BaseFee - verificationFee = new(big.Int).Mul(verificationFee, big.NewInt(17)) - verificationFee = new(big.Int).Div(verificationFee, big.NewInt(100000)) + verificationFee = new(big.Int).Mul(verificationFee, big.NewInt(34)) + verificationFee = new(big.Int).Div(verificationFee, big.NewInt(1000000)) baseFee := big.NewInt(0) baseFee.Add(baseFee, l2SequencerFee) diff --git a/consensus/misc/eip1559_test.go b/consensus/misc/eip1559_test.go index 3b48c2e581..12aa2b17c2 100644 --- a/consensus/misc/eip1559_test.go +++ b/consensus/misc/eip1559_test.go @@ -112,13 +112,13 @@ func TestCalcBaseFee(t *testing.T) { parentL1BaseFee int64 expectedL2BaseFee int64 }{ - {0, 39200000}, - {1000000000, 39370000}, - {2000000000, 39540000}, - {100000000000, 56200000}, - {111111111111, 58088888}, - {2164000000000, 407080000}, - {58592942000000, 10000000000}, // cap at max L2 base fee + {0, 15680000}, + {1000000000, 15714000}, + {2000000000, 15748000}, + {100000000000, 19080000}, + {111111111111, 19457777}, + {2164000000000, 89256000}, + {644149677419355, 10000000000}, // cap at max L2 base fee } for i, test := range tests { if have, want := CalcBaseFee(config(), nil, big.NewInt(test.parentL1BaseFee)), big.NewInt(test.expectedL2BaseFee); have.Cmp(want) != 0 { diff --git a/core/state_processor_test.go b/core/state_processor_test.go index a2585941ce..ac5babcac3 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -389,13 +389,13 @@ func TestStateProcessorErrors(t *testing.T) { txs: []*types.Transaction{ mkDynamicCreationTx(0, 500000, common.Big0, misc.CalcBaseFee(config, genesis.Header(), parentL1BaseFee), tooBigInitCode[:]), }, - want: "could not apply tx 0 [0xa31de6e26bd5ffba0ca91a2bc29fc2eaad6a6cfc5ad9ab6ffb69cac121e0125c]: max initcode size exceeded: code size 49153 limit 49152", + want: "could not apply tx 0 [0x9fff9d187a68f9dce9664475ed9a01a5178992f15b44ce88ee7b1129a183e6af]: max initcode size exceeded: code size 49153 limit 49152", }, { // ErrIntrinsicGas: Not enough gas to cover init code txs: []*types.Transaction{ mkDynamicCreationTx(0, 54299, common.Big0, misc.CalcBaseFee(config, genesis.Header(), parentL1BaseFee), smallInitCode[:]), }, - want: "could not apply tx 0 [0xf36b7d68cf239f956f7c36be26688a97aaa317ea5f5230d109bb30dbc8598ccb]: intrinsic gas too low: have 54299, want 54300", + want: "could not apply tx 0 [0x272eefb0eeb3b973e933ae5dba17e7ecf6bfded5ce358f2a78426153c247f677]: intrinsic gas too low: have 54299, want 54300", }, } { block := GenerateBadBlock(genesis, ethash.NewFaker(), tt.txs, gspec.Config) diff --git a/params/version.go b/params/version.go index 94e8f3fe02..265cf80503 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 5 // Major version component of the current release VersionMinor = 8 // Minor version component of the current release - VersionPatch = 38 // Patch version component of the current release + VersionPatch = 39 // Patch version component of the current release VersionMeta = "mainnet" // Version metadata to append to the version string ) From 5e956fe2649e313f9af14863a1418f5d36738a38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Faruk=20Irmak?= Date: Thu, 24 Apr 2025 10:59:28 +0300 Subject: [PATCH 28/45] fix: handle empty callstacks in call tracers (#1171) --- eth/tracers/native/call.go | 12 +++++++----- eth/tracers/native/call_flat.go | 8 +++++--- params/version.go | 2 +- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/eth/tracers/native/call.go b/eth/tracers/native/call.go index b2bebb15ce..36a3417d8f 100644 --- a/eth/tracers/native/call.go +++ b/eth/tracers/native/call.go @@ -18,7 +18,6 @@ package native import ( "encoding/json" - "errors" "math/big" "strconv" "strings" @@ -60,21 +59,21 @@ type callTracer struct { func newCallTracer(ctx *tracers.Context) tracers.Tracer { // First callframe contains tx context info // and is populated on start and end. - t := &callTracer{callstack: make([]callFrame, 1)} + t := &callTracer{callstack: make([]callFrame, 0, 1)} return t } // CaptureStart implements the EVMLogger interface to initialize the tracing operation. func (t *callTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int, authorizationResults []types.AuthorizationResult) { t.env = env - t.callstack[0] = callFrame{ + t.callstack = append(t.callstack, callFrame{ Type: "CALL", From: addrToHex(from), To: addrToHex(to), Input: bytesToHex(input), Gas: uintToHex(gas), Value: bigToHex(value), - } + }) if create { t.callstack[0].Type = "CREATE" } @@ -152,7 +151,10 @@ func (t *callTracer) CaptureExit(output []byte, gasUsed uint64, err error) { // error arising from the encoding or forceful termination (via `Stop`). func (t *callTracer) GetResult() (json.RawMessage, error) { if len(t.callstack) != 1 { - return nil, errors.New("incorrect number of top-level calls") + // If the callstack is empty, return an empty JSON object instead of erroring + // Callstack can be empty due to an edge case where an L1 message is reverted even + // before entering the first call. + return json.RawMessage("{}"), nil } res, err := json.Marshal(t.callstack[0]) if err != nil { diff --git a/eth/tracers/native/call_flat.go b/eth/tracers/native/call_flat.go index b82398c78a..721882b0c9 100644 --- a/eth/tracers/native/call_flat.go +++ b/eth/tracers/native/call_flat.go @@ -18,7 +18,6 @@ package native import ( "encoding/json" - "errors" "fmt" "math/big" "strings" @@ -115,7 +114,7 @@ type flatCallTracer struct { // newFlatCallTracer returns a new flatCallTracer. func newFlatCallTracer(ctx *tracers.Context) tracers.Tracer { - t := &callTracer{callstack: make([]callFrame, 1)} + t := &callTracer{callstack: make([]callFrame, 0, 1)} return &flatCallTracer{callTracer: t, ctx: ctx} @@ -133,7 +132,10 @@ func (t *flatCallTracer) CaptureEnter(typ vm.OpCode, from common.Address, to com // error arising from the encoding or forceful termination (via `Stop`). func (t *flatCallTracer) GetResult() (json.RawMessage, error) { if len(t.callTracer.callstack) < 1 { - return nil, errors.New("invalid number of calls") + // If the callstack is empty, return an empty JSON list instead of erroring + // Callstack can be empty due to an edge case where an L1 message is reverted even + // before entering the first call. + return json.RawMessage("[]"), nil } flat, err := flatFromNested(&t.callTracer.callstack[0], []int{}, true, t.ctx) diff --git a/params/version.go b/params/version.go index 265cf80503..d3a7166505 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 5 // Major version component of the current release VersionMinor = 8 // Minor version component of the current release - VersionPatch = 39 // Patch version component of the current release + VersionPatch = 40 // Patch version component of the current release VersionMeta = "mainnet" // Version metadata to append to the version string ) From eaf06e30a037dd7d206404c8b2022db892308acb Mon Sep 17 00:00:00 2001 From: Jonas Theis <4181434+jonastheis@users.noreply.github.com> Date: Mon, 28 Apr 2025 15:10:10 +0800 Subject: [PATCH 29/45] feat(L1Reader): change to NextUnfinalizedL1MessageQueueIndex and add GetFinalizedStateRootByBatchIndex (#1160) * change FinalizedL1MessageQueueIndex to NextUnfinalizedL1MessageQueueIndex * feat(L1Reader): add GetFinalizedStateRootByBatchIndex (#1165) * feat(L1Reader): add GetFinalizedStateRootByBatchIndex * fix: FinalizedL1MessageQueueIndex return type * fix: get state root batchIndex type --------- Co-authored-by: Morty <70688412+yiweichi@users.noreply.github.com> --- rollup/l1/reader.go | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/rollup/l1/reader.go b/rollup/l1/reader.go index f2b5ce6c7e..e479b7dd9d 100644 --- a/rollup/l1/reader.go +++ b/rollup/l1/reader.go @@ -23,6 +23,7 @@ const ( finalizeBatchEventName = "FinalizeBatch" nextUnfinalizedQueueIndex = "nextUnfinalizedQueueIndex" lastFinalizedBatchIndex = "lastFinalizedBatchIndex" + finalizedStateRoots = "finalizedStateRoots" defaultRollupEventsFetchBlockRange = 100 ) @@ -72,7 +73,7 @@ func NewReader(ctx context.Context, config Config, l1Client Client) (*Reader, er return &reader, nil } -func (r *Reader) FinalizedL1MessageQueueIndex(blockNumber uint64) (uint64, error) { +func (r *Reader) NextUnfinalizedL1MessageQueueIndex(blockNumber uint64) (uint64, error) { data, err := r.l1MessageQueueABI.Pack(nextUnfinalizedQueueIndex) if err != nil { return 0, fmt.Errorf("failed to pack %s: %w", nextUnfinalizedQueueIndex, err) @@ -92,11 +93,7 @@ func (r *Reader) FinalizedL1MessageQueueIndex(blockNumber uint64) (uint64, error } next := parsedResult.Uint64() - if next == 0 { - return 0, nil - } - - return next - 1, nil + return next, nil } func (r *Reader) LatestFinalizedBatchIndex(blockNumber uint64) (uint64, error) { @@ -121,6 +118,28 @@ func (r *Reader) LatestFinalizedBatchIndex(blockNumber uint64) (uint64, error) { return parsedResult.Uint64(), nil } +func (r *Reader) GetFinalizedStateRootByBatchIndex(blockNumber uint64, batchIndex uint64) (common.Hash, error) { + data, err := r.scrollChainABI.Pack(finalizedStateRoots, big.NewInt(int64(batchIndex))) + if err != nil { + return common.Hash{}, fmt.Errorf("failed to pack %s: %w", finalizedStateRoots, err) + } + + result, err := r.client.CallContract(r.ctx, ethereum.CallMsg{ + To: &r.config.ScrollChainAddress, + Data: data, + }, new(big.Int).SetUint64(blockNumber)) + if err != nil { + return common.Hash{}, fmt.Errorf("failed to call %s: %w", finalizedStateRoots, err) + } + + var parsedResult common.Hash + if err = r.scrollChainABI.UnpackIntoInterface(&parsedResult, finalizedStateRoots, result); err != nil { + return common.Hash{}, fmt.Errorf("failed to unpack result: %w", err) + } + + return parsedResult, nil +} + // GetLatestFinalizedBlockNumber fetches the block number of the latest finalized block from the L1 chain. func (r *Reader) GetLatestFinalizedBlockNumber() (uint64, error) { header, err := r.client.HeaderByNumber(r.ctx, big.NewInt(int64(rpc.FinalizedBlockNumber))) From a4109fd8a95fbddce819868110898d80561f1e5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Wed, 30 Apr 2025 13:55:29 +0200 Subject: [PATCH 30/45] feat: add debug_db* methods (#1177) --- common/bytes.go | 12 +++++++++++ internal/ethapi/dbapi.go | 43 +++++++++++++++++++++++++++++++++++++ internal/web3ext/web3ext.go | 15 +++++++++++++ params/version.go | 2 +- 4 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 internal/ethapi/dbapi.go diff --git a/common/bytes.go b/common/bytes.go index 7827bb572e..ae45aa18c6 100644 --- a/common/bytes.go +++ b/common/bytes.go @@ -19,6 +19,9 @@ package common import ( "encoding/hex" + "errors" + + "github.com/scroll-tech/go-ethereum/common/hexutil" ) // FromHex returns the bytes represented by the hexadecimal string s. @@ -92,6 +95,15 @@ func Hex2BytesFixed(str string, flen int) []byte { return hh } +// ParseHexOrString tries to hexdecode b, but if the prefix is missing, it instead just returns the raw bytes +func ParseHexOrString(str string) ([]byte, error) { + b, err := hexutil.Decode(str) + if errors.Is(err, hexutil.ErrMissingPrefix) { + return []byte(str), nil + } + return b, err +} + // RightPadBytes zero-pads slice to the right up to length l. func RightPadBytes(slice []byte, l int) []byte { if l <= len(slice) { diff --git a/internal/ethapi/dbapi.go b/internal/ethapi/dbapi.go new file mode 100644 index 0000000000..d8433c2abe --- /dev/null +++ b/internal/ethapi/dbapi.go @@ -0,0 +1,43 @@ +// Copyright 2022 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package ethapi + +import ( + "github.com/scroll-tech/go-ethereum/common" + "github.com/scroll-tech/go-ethereum/common/hexutil" +) + +// DbGet returns the raw value of a key stored in the database. +func (api *PublicDebugAPI) DbGet(key string) (hexutil.Bytes, error) { + blob, err := common.ParseHexOrString(key) + if err != nil { + return nil, err + } + return api.b.ChainDb().Get(blob) +} + +// DbAncient retrieves an ancient binary blob from the append-only immutable files. +// It is a mapping to the `AncientReaderOp.Ancient` method +func (api *PublicDebugAPI) DbAncient(kind string, number uint64) (hexutil.Bytes, error) { + return api.b.ChainDb().Ancient(kind, number) +} + +// DbAncients returns the ancient item numbers in the ancient store. +// It is a mapping to the `AncientReaderOp.Ancients` method +func (api *PublicDebugAPI) DbAncients() (uint64, error) { + return api.b.ChainDb().Ancients() +} diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 3255100341..5b1e768dd3 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -482,6 +482,21 @@ web3._extend({ params: 2, inputFormatter:[web3._extend.formatters.inputBlockNumberFormatter, web3._extend.formatters.inputBlockNumberFormatter], }), + new web3._extend.Method({ + name: 'dbGet', + call: 'debug_dbGet', + params: 1 + }), + new web3._extend.Method({ + name: 'dbAncient', + call: 'debug_dbAncient', + params: 2 + }), + new web3._extend.Method({ + name: 'dbAncients', + call: 'debug_dbAncients', + params: 0 + }), new web3._extend.Method({ name: 'executionWitness', call: 'debug_executionWitness', diff --git a/params/version.go b/params/version.go index d3a7166505..c3e76c74e6 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 5 // Major version component of the current release VersionMinor = 8 // Minor version component of the current release - VersionPatch = 40 // Patch version component of the current release + VersionPatch = 41 // Patch version component of the current release VersionMeta = "mainnet" // Version metadata to append to the version string ) From 7b56ff1d664c342eb21e88863b5f90325d52b94c Mon Sep 17 00:00:00 2001 From: Morty <70688412+yiweichi@users.noreply.github.com> Date: Wed, 30 Apr 2025 22:29:22 +0800 Subject: [PATCH 31/45] fix: system config consensus (#1180) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: system config to check sign in any case * chore: auto version bump [bot] --- consensus/system_contract/consensus.go | 23 ++++++++++------------- params/version.go | 2 +- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/consensus/system_contract/consensus.go b/consensus/system_contract/consensus.go index 797297870d..c01bec805a 100644 --- a/consensus/system_contract/consensus.go +++ b/consensus/system_contract/consensus.go @@ -130,7 +130,7 @@ func (s *SystemContract) verifyHeader(chain consensus.ChainHeaderReader, header return errInvalidNonce } // Check that the BlockSignature contains signature if block is not requested - if !header.Requested && header.Number.Cmp(big.NewInt(0)) != 0 && len(header.BlockSignature) != extraSeal { + if header.Number.Cmp(big.NewInt(0)) != 0 && len(header.BlockSignature) != extraSeal { return errMissingSignature } // Ensure that the mix digest is zero @@ -197,20 +197,17 @@ func (s *SystemContract) verifyCascadingFields(chain consensus.ChainHeaderReader return err } - // only if block header has NOT been requested, then verify the signature against the current signer - if !header.Requested { - signer, err := ecrecover(header) - if err != nil { - return err - } + signer, err := ecrecover(header) + if err != nil { + return err + } - s.lock.RLock() - defer s.lock.RUnlock() + s.lock.RLock() + defer s.lock.RUnlock() - if signer != s.signerAddressL1 { - log.Error("Unauthorized signer", "Got", signer, "Expected:", s.signerAddressL1) - return ErrUnauthorizedSigner - } + if signer != s.signerAddressL1 { + log.Error("Unauthorized signer", "Got", signer, "Expected:", s.signerAddressL1) + return ErrUnauthorizedSigner } return nil diff --git a/params/version.go b/params/version.go index c3e76c74e6..e663a587e8 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 5 // Major version component of the current release VersionMinor = 8 // Minor version component of the current release - VersionPatch = 41 // Patch version component of the current release + VersionPatch = 42 // Patch version component of the current release VersionMeta = "mainnet" // Version metadata to append to the version string ) From 0d72990d8e86f1b7a4c3f229928a0d73f547a951 Mon Sep 17 00:00:00 2001 From: colin <102356659+colinlyguo@users.noreply.github.com> Date: Wed, 7 May 2025 17:05:11 +0800 Subject: [PATCH 32/45] feat: migrate setcode tx upstream changes (#1175) * internal/ethapi: exclude 7702 authorities from result in eth_createAccessList * txpool changes * bump version * migrate a comment change --- core/state_transition.go | 2 +- core/tx_pool.go | 62 ++++++++----- core/tx_pool_test.go | 169 +++++++++++++++++++++++++--------- core/vm/access_list_tracer.go | 12 +-- internal/ethapi/api.go | 29 +++++- params/version.go | 2 +- 6 files changed, 197 insertions(+), 79 deletions(-) diff --git a/core/state_transition.go b/core/state_transition.go index 1a7b3cea4b..cc75301f14 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -486,7 +486,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { // validateAuthorization validates an EIP-7702 authorization against the state. func (st *StateTransition) validateAuthorization(auth *types.SetCodeAuthorization) (authority common.Address, preCode []byte, err error) { - // Verify chain ID is 0 or equal to current chain ID. + // Verify chain ID is null or equal to current chain ID. if !auth.ChainID.IsZero() && auth.ChainID.CmpBig(st.evm.ChainConfig().ChainID) != 0 { return authority, nil, ErrAuthorizationWrongChainID } diff --git a/core/tx_pool.go b/core/tx_pool.go index bf36bf281a..9a8c011c2a 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -96,6 +96,10 @@ var ( // transactions is reached for specific accounts. ErrInflightTxLimitReached = errors.New("in-flight transaction limit reached for delegated accounts") + // ErrOutOfOrderTxFromDelegated is returned when the transaction with gapped + // nonce received from the accounts with delegation or pending delegation. + ErrOutOfOrderTxFromDelegated = errors.New("gapped-nonce tx from delegated accounts") + // ErrAuthorityReserved is returned if a transaction has an authorization // signed by an address which already has in-flight transactions known to the // pool. @@ -1909,36 +1913,50 @@ func (pool *TxPool) calculateTxsLifecycle(txs types.Transactions, t time.Time) { } } +// checkDelegationLimit determines if the tx sender is delegated or has a +// pending delegation, and if so, ensures they have at most one in-flight +// **executable** transaction, e.g. disallow stacked and gapped transactions +// from the account. +func (pool *TxPool) checkDelegationLimit(from common.Address, tx *types.Transaction) error { + // Short circuit if the sender has neither delegation nor pending delegation. + if pool.currentState.GetKeccakCodeHash(from) == codehash.EmptyKeccakCodeHash && len(pool.all.auths[from]) == 0 { + return nil + } + pending := pool.pending[from] + if pending == nil { + // Transaction with gapped nonce is not supported for delegated accounts + if pool.pendingNonces.get(from) != tx.Nonce() { + return ErrOutOfOrderTxFromDelegated + } + return nil + } + // Transaction replacement is supported + if pending.Overlaps(tx) { + return nil + } + return ErrInflightTxLimitReached +} + // validateAuth verifies that the transaction complies with code authorization // restrictions brought by SetCode transaction type. func (pool *TxPool) validateAuth(from common.Address, tx *types.Transaction) error { // Allow at most one in-flight tx for delegated accounts or those with a // pending authorization. - if pool.currentState.GetKeccakCodeHash(from) != codehash.EmptyKeccakCodeHash || len(pool.all.auths[from]) != 0 { - var ( - count int - exists bool - ) - pending := pool.pending[from] - if pending != nil { - count += pending.Len() - exists = pending.Overlaps(tx) - } - queue := pool.queue[from] - if queue != nil { - count += queue.Len() - exists = exists || queue.Overlaps(tx) - } - // Replace the existing in-flight transaction for delegated accounts - // are still supported - if count >= 1 && !exists { - return ErrInflightTxLimitReached - } + if err := pool.checkDelegationLimit(from, tx); err != nil { + return err } - // Authorities cannot conflict with any pending or queued transactions. + // For symmetry, allow at most one in-flight tx for any authority with a + // pending transaction. if auths := tx.SetCodeAuthorities(); len(auths) > 0 { for _, auth := range auths { - if pool.pending[auth] != nil || pool.queue[auth] != nil { + var count int + if pending := pool.pending[auth]; pending != nil { + count += pending.Len() + } + if queue := pool.queue[auth]; queue != nil { + count += queue.Len() + } + if count > 1 { return ErrAuthorityReserved } } diff --git a/core/tx_pool_test.go b/core/tx_pool_test.go index f7d3b298db..7d9501a807 100644 --- a/core/tx_pool_test.go +++ b/core/tx_pool_test.go @@ -2722,22 +2722,27 @@ func TestSetCodeTransactions(t *testing.T) { }{ { // Check that only one in-flight transaction is allowed for accounts - // with delegation set. Also verify the accepted transaction can be - // replaced by fee. - name: "only-one-in-flight", + // with delegation set. + name: "accept-one-inflight-tx-of-delegated-account", pending: 1, run: func(name string, pool *TxPool, statedb *state.StateDB) { aa := common.Address{0xaa, 0xaa} statedb.SetCode(addrA, append(types.DelegationPrefix, aa.Bytes()...)) statedb.SetCode(aa, []byte{byte(vm.ADDRESS), byte(vm.PUSH0), byte(vm.SSTORE)}) + + // Send gapped transaction, it should be rejected. + if err := pool.addRemoteSync(pricedTransaction(2, 100000, big.NewInt(1), keyA)); !errors.Is(err, ErrOutOfOrderTxFromDelegated) { + t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrOutOfOrderTxFromDelegated, err) + } // Send transactions. First is accepted, second is rejected. if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), keyA)); err != nil { t.Fatalf("%s: failed to add remote transaction: %v", name, err) } + // Second and further transactions shall be rejected if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1), keyA)); !errors.Is(err, ErrInflightTxLimitReached) { t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrInflightTxLimitReached, err) } - // Also check gapped transaction. + // Check gapped transaction again. if err := pool.addRemoteSync(pricedTransaction(2, 100000, big.NewInt(1), keyA)); !errors.Is(err, ErrInflightTxLimitReached) { t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrInflightTxLimitReached, err) } @@ -2745,6 +2750,70 @@ func TestSetCodeTransactions(t *testing.T) { if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(10), keyA)); err != nil { t.Fatalf("%s: failed to replace with remote transaction: %v", name, err) } + + // Reset the delegation, avoid leaking state into the other tests + statedb.SetCode(addrA, nil) + }, + }, + { + // This test is analogous to the previous one, but the delegation is pending + // instead of set. + name: "allow-one-tx-from-pooled-delegation", + pending: 2, + run: func(name string, pool *TxPool, statedb *state.StateDB) { + // Create a pending delegation request from B. + if err := pool.addRemoteSync(setCodeTx(0, keyA, []unsignedAuth{{0, keyB}})); err != nil { + t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err) + } + // First transaction from B is accepted. + if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), keyB)); err != nil { + t.Fatalf("%s: failed to add remote transaction: %v", name, err) + } + // Second transaction fails due to limit. + if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1), keyB)); !errors.Is(err, ErrInflightTxLimitReached) { + t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrInflightTxLimitReached, err) + } + // Replace by fee for first transaction from B works. + if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(2), keyB)); err != nil { + t.Fatalf("%s: failed to add remote transaction: %v", name, err) + } + }, + }, + { + // This is the symmetric case of the previous one, where the delegation request + // is received after the transaction. The resulting state shall be the same. + name: "accept-authorization-from-sender-of-one-inflight-tx", + pending: 2, + run: func(name string, pool *TxPool, statedb *state.StateDB) { + // The first in-flight transaction is accepted. + if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), keyB)); err != nil { + t.Fatalf("%s: failed to add with pending delegation: %v", name, err) + } + // Delegation is accepted. + if err := pool.addRemoteSync(setCodeTx(0, keyA, []unsignedAuth{{0, keyB}})); err != nil { + t.Fatalf("%s: failed to add remote transaction: %v", name, err) + } + // The second in-flight transaction is rejected. + if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1), keyB)); !errors.Is(err, ErrInflightTxLimitReached) { + t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrInflightTxLimitReached, err) + } + }, + }, + { + name: "reject-authorization-from-sender-with-more-than-one-inflight-tx", + pending: 2, + run: func(name string, pool *TxPool, statedb *state.StateDB) { + // Submit two transactions. + if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), keyB)); err != nil { + t.Fatalf("%s: failed to add with pending delegation: %v", name, err) + } + if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1), keyB)); err != nil { + t.Fatalf("%s: failed to add with pending delegation: %v", name, err) + } + // Delegation rejected since two txs are already in-flight. + if err := pool.addRemoteSync(setCodeTx(0, keyA, []unsignedAuth{{0, keyB}})); !errors.Is(err, ErrAuthorityReserved) { + t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrAuthorityReserved, err) + } }, }, { @@ -2752,7 +2821,7 @@ func TestSetCodeTransactions(t *testing.T) { pending: 2, run: func(name string, pool *TxPool, statedb *state.StateDB) { // Send two transactions where the first has no conflicting delegations and - // the second should be allowed despite conflicting with the authorities in 1). + // the second should be allowed despite conflicting with the authorities in the first. if err := pool.addRemoteSync(setCodeTx(0, keyA, []unsignedAuth{{1, keyC}})); err != nil { t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err) } @@ -2761,28 +2830,10 @@ func TestSetCodeTransactions(t *testing.T) { } }, }, - { - name: "allow-one-tx-from-pooled-delegation", - pending: 2, - run: func(name string, pool *TxPool, statedb *state.StateDB) { - // Verify C cannot originate another transaction when it has a pooled delegation. - if err := pool.addRemoteSync(setCodeTx(0, keyA, []unsignedAuth{{0, keyC}})); err != nil { - t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err) - } - if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), keyC)); err != nil { - t.Fatalf("%s: failed to add with pending delegation: %v", name, err) - } - // Also check gapped transaction is rejected. - if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1), keyC)); !errors.Is(err, ErrInflightTxLimitReached) { - t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrInflightTxLimitReached, err) - } - }, - }, { name: "replace-by-fee-setcode-tx", pending: 1, run: func(name string, pool *TxPool, statedb *state.StateDB) { - // 4. Fee bump the setcode tx send. if err := pool.addRemoteSync(setCodeTx(0, keyB, []unsignedAuth{{1, keyC}})); err != nil { t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err) } @@ -2793,42 +2844,87 @@ func TestSetCodeTransactions(t *testing.T) { }, { name: "allow-tx-from-replaced-authority", - pending: 2, + pending: 3, run: func(name string, pool *TxPool, statedb *state.StateDB) { - // Fee bump with a different auth list. Make sure that unlocks the authorities. + // Send transaction from A with B as an authority. if err := pool.addRemoteSync(pricedSetCodeTx(0, 250000, uint256.NewInt(10), uint256.NewInt(3), keyA, []unsignedAuth{{0, keyB}})); err != nil { t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err) } + // Replace transaction with another having C as an authority. if err := pool.addRemoteSync(pricedSetCodeTx(0, 250000, uint256.NewInt(3000), uint256.NewInt(300), keyA, []unsignedAuth{{0, keyC}})); err != nil { t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err) } - // Now send a regular tx from B. + // B should not be considred as having an in-flight delegation, so + // should allow more than one pooled transaction. if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(10), keyB)); err != nil { t.Fatalf("%s: failed to replace with remote transaction: %v", name, err) } + if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(10), keyB)); err != nil { + t.Fatalf("%s: failed to replace with remote transaction: %v", name, err) + } }, }, { + // This test is analogous to the previous one, but the the replaced + // transaction is self-sponsored. name: "allow-tx-from-replaced-self-sponsor-authority", - pending: 2, + pending: 3, run: func(name string, pool *TxPool, statedb *state.StateDB) { + // Reset the delegation + statedb.SetCode(addrA, nil) + + // Send transaction from A with A as an authority. if err := pool.addRemoteSync(pricedSetCodeTx(0, 250000, uint256.NewInt(10), uint256.NewInt(3), keyA, []unsignedAuth{{0, keyA}})); err != nil { t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err) } + // Replace transaction with a transaction with B as an authority. if err := pool.addRemoteSync(pricedSetCodeTx(0, 250000, uint256.NewInt(30), uint256.NewInt(30), keyA, []unsignedAuth{{0, keyB}})); err != nil { t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err) } - // Now send a regular tx from keyA. - if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1000), keyA)); err != nil { + // The one in-flight transaction limit from A no longer applies, so we + // can stack a second transaction for the account. + if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1000), keyA)); err != nil { t.Fatalf("%s: failed to replace with remote transaction: %v", name, err) } - // Make sure we can still send from keyB. + // B should still be able to send transactions. if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1000), keyB)); err != nil { t.Fatalf("%s: failed to replace with remote transaction: %v", name, err) } + // However B still has the limitation to one in-flight transaction. + if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1), keyB)); !errors.Is(err, ErrInflightTxLimitReached) { + t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrInflightTxLimitReached, err) + } }, }, { + name: "replacements-respect-inflight-tx-count", + pending: 2, + run: func(name string, pool *TxPool, statedb *state.StateDB) { + // Send transaction from A with B as an authority. + if err := pool.addRemoteSync(pricedSetCodeTx(0, 250000, uint256.NewInt(10), uint256.NewInt(3), keyA, []unsignedAuth{{0, keyB}})); err != nil { + t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err) + } + // Send two transactions from B. Only the first should be accepted due + // to in-flight limit. + if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), keyB)); err != nil { + t.Fatalf("%s: failed to add remote transaction: %v", name, err) + } + if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1), keyB)); !errors.Is(err, ErrInflightTxLimitReached) { + t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrInflightTxLimitReached, err) + } + // Replace the in-flight transaction from B. + if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(30), keyB)); err != nil { + t.Fatalf("%s: failed to replace with remote transaction: %v", name, err) + } + // Ensure the in-flight limit for B is still in place. + if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1), keyB)); !errors.Is(err, ErrInflightTxLimitReached) { + t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrInflightTxLimitReached, err) + } + }, + }, + { + // Since multiple authorizations can be pending simultaneously, replacing + // one of them should not break the one in-flight-transaction limit. name: "track-multiple-conflicting-delegations", pending: 3, run: func(name string, pool *TxPool, statedb *state.StateDB) { @@ -2853,19 +2949,6 @@ func TestSetCodeTransactions(t *testing.T) { } }, }, - { - name: "reject-delegation-from-pending-account", - pending: 1, - run: func(name string, pool *TxPool, statedb *state.StateDB) { - // Attempt to submit a delegation from an account with a pending tx. - if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1000), keyC)); err != nil { - t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err) - } - if err, want := pool.addRemoteSync(setCodeTx(0, keyA, []unsignedAuth{{1, keyC}})), ErrAuthorityReserved; !errors.Is(err, want) { - t.Fatalf("%s: error mismatch: want %v, have %v", name, want, err) - } - }, - }, { name: "nonce-gapped-invalid-auth-does-not-block-pending-tx", pending: 1, diff --git a/core/vm/access_list_tracer.go b/core/vm/access_list_tracer.go index 997b169ec5..6342dfd3ad 100644 --- a/core/vm/access_list_tracer.go +++ b/core/vm/access_list_tracer.go @@ -115,16 +115,10 @@ type AccessListTracer struct { // NewAccessListTracer creates a new tracer that can generate AccessLists. // An optional AccessList can be specified to occupy slots and addresses in // the resulting accesslist. -func NewAccessListTracer(acl types.AccessList, from, to common.Address, precompiles []common.Address) *AccessListTracer { - excl := map[common.Address]struct{}{ - from: {}, to: {}, - } - for _, addr := range precompiles { - excl[addr] = struct{}{} - } +func NewAccessListTracer(acl types.AccessList, addressesToExclude map[common.Address]struct{}) *AccessListTracer { list := newAccessList() for _, al := range acl { - if _, ok := excl[al.Address]; !ok { + if _, ok := addressesToExclude[al.Address]; !ok { list.addAddress(al.Address) } for _, slot := range al.StorageKeys { @@ -132,7 +126,7 @@ func NewAccessListTracer(acl types.AccessList, from, to common.Address, precompi } } return &AccessListTracer{ - excl: excl, + excl: addressesToExclude, list: list, } } diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 78e7dbe09c..d9978b7ac5 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1536,10 +1536,33 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH // Retrieve the precompiles since they don't need to be added to the access list precompiles := vm.ActivePrecompiles(b.ChainConfig().Rules(header.Number, header.Time)) + // addressesToExclude contains sender, receiver, precompiles and valid authorizations + addressesToExclude := map[common.Address]struct{}{args.from(): {}, to: {}} + for _, addr := range precompiles { + addressesToExclude[addr] = struct{}{} + } + + // Prevent redundant operations if args contain more authorizations than EVM may handle + maxAuthorizations := uint64(*args.Gas) / params.CallNewAccountGas + if uint64(len(args.AuthorizationList)) > maxAuthorizations { + return nil, 0, nil, errors.New("insufficient gas to process all authorizations") + } + + for _, auth := range args.AuthorizationList { + // Duplicating stateTransition.validateAuthorization() logic + if (!auth.ChainID.IsZero() && auth.ChainID.CmpBig(b.ChainConfig().ChainID) != 0) || auth.Nonce+1 < auth.Nonce { + continue + } + + if authority, err := auth.Authority(); err == nil { + addressesToExclude[authority] = struct{}{} + } + } + // Create an initial tracer - prevTracer := vm.NewAccessListTracer(nil, args.from(), to, precompiles) + prevTracer := vm.NewAccessListTracer(nil, addressesToExclude) if args.AccessList != nil { - prevTracer = vm.NewAccessListTracer(*args.AccessList, args.from(), to, precompiles) + prevTracer = vm.NewAccessListTracer(*args.AccessList, addressesToExclude) } for { // Retrieve the current access list to expand @@ -1565,7 +1588,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH } // Apply the transaction with the access list tracer - tracer := vm.NewAccessListTracer(accessList, args.from(), to, precompiles) + tracer := vm.NewAccessListTracer(accessList, addressesToExclude) config := vm.Config{Tracer: tracer, Debug: true, NoBaseFee: true} vmenv, _, err := b.GetEVM(ctx, msg, statedb, header, &config) if err != nil { diff --git a/params/version.go b/params/version.go index e663a587e8..0b9983d968 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 5 // Major version component of the current release VersionMinor = 8 // Minor version component of the current release - VersionPatch = 42 // Patch version component of the current release + VersionPatch = 43 // Patch version component of the current release VersionMeta = "mainnet" // Version metadata to append to the version string ) From 3edb331d88aa5d40609f40c6979623b6df5cf142 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Fri, 9 May 2025 17:32:01 +0200 Subject: [PATCH 33/45] feat(rollup-verifier): make withdraw root check optional (#1182) --- params/version.go | 2 +- .../rollup_sync_service.go | 22 ++++++++++++------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/params/version.go b/params/version.go index 0b9983d968..7105fe6f63 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 5 // Major version component of the current release VersionMinor = 8 // Minor version component of the current release - VersionPatch = 43 // Patch version component of the current release + VersionPatch = 44 // Patch version component of the current release VersionMeta = "mainnet" // Version metadata to append to the version string ) diff --git a/rollup/rollup_sync_service/rollup_sync_service.go b/rollup/rollup_sync_service/rollup_sync_service.go index 8b65d0f8b3..190fd0810e 100644 --- a/rollup/rollup_sync_service/rollup_sync_service.go +++ b/rollup/rollup_sync_service/rollup_sync_service.go @@ -394,16 +394,20 @@ func (s *RollupSyncService) getLocalChunksForBatch(chunkBlockRanges []*rawdb.Chu return nil, fmt.Errorf("failed to get block by number: %v", i) } txData := encoding.TxsToTxsData(block.Transactions()) - state, err := s.bc.StateAt(block.Root()) - if err != nil { - return nil, fmt.Errorf("failed to get block state, block: %v, err: %w", block.Hash().Hex(), err) - } - withdrawRoot := withdrawtrie.ReadWTRSlot(rcfg.L2MessageQueueAddress, state) chunks[i].Blocks[j-cr.StartBlockNumber] = &encoding.Block{ Header: block.Header(), Transactions: txData, - WithdrawRoot: withdrawRoot, } + + // read withdraw root, if available + // note: historical state is not available on full nodes + state, err := s.bc.StateAt(block.Root()) + if err != nil { + log.Trace("State is not available, skipping withdraw trie validation", "blockNumber", block.NumberU64(), "blockHash", block.Hash().Hex(), "err", err) + continue + } + withdrawRoot := withdrawtrie.ReadWTRSlot(rcfg.L2MessageQueueAddress, state) + chunks[i].Blocks[j-cr.StartBlockNumber].WithdrawRoot = withdrawRoot } } @@ -574,7 +578,9 @@ func validateBatch(batchIndex uint64, event *l1.FinalizeBatchEvent, parentFinali os.Exit(1) } - if localWithdrawRoot != event.WithdrawRoot() { + // note: this check is optional, + // withdraw root correctness is already implied by state root correctness. + if localWithdrawRoot != (common.Hash{}) && localWithdrawRoot != event.WithdrawRoot() { log.Error("Withdraw root mismatch", "batch index", event.BatchIndex().Uint64(), "start block", startBlock.Header.Number.Uint64(), "end block", endBlock.Header.Number.Uint64(), "parent batch hash", parentFinalizedBatchMeta.BatchHash.Hex(), "l1 finalized withdraw root", event.WithdrawRoot().Hex(), "l2 withdraw root", localWithdrawRoot.Hex()) stack.Close() os.Exit(1) @@ -603,7 +609,7 @@ func validateBatch(batchIndex uint64, event *l1.FinalizeBatchEvent, parentFinali BatchHash: localBatchHash, TotalL1MessagePopped: totalL1MessagePopped, StateRoot: localStateRoot, - WithdrawRoot: localWithdrawRoot, + WithdrawRoot: event.WithdrawRoot(), } return endBlock.Header.Number.Uint64(), finalizedBatchMeta, nil } From 0b6f8f188949c3e40a9941871fda9adb993ebc62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Wed, 14 May 2025 12:41:19 +0200 Subject: [PATCH 34/45] feat: update base fee via cli (#1183) * feat: update base fee via cli * fix * improve comments --- cmd/geth/main.go | 2 ++ cmd/geth/usage.go | 25 +++++++++++++++++++++ cmd/utils/flags.go | 37 ++++++++++++++++++++++++++++++ consensus/misc/eip1559.go | 41 +++++++++++++++++++++++++--------- consensus/misc/eip1559_test.go | 34 +++++++++++++++++++++++++++- eth/backend.go | 7 ++++++ eth/ethconfig/config.go | 4 ++++ params/config.go | 19 ++++++++++++++-- params/version.go | 2 +- 9 files changed, 157 insertions(+), 14 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index dcb7d12fc1..0377609ceb 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -185,6 +185,8 @@ var ( utils.DARecoverySignBlocksFlag, utils.DARecoveryL2EndBlockFlag, utils.DARecoveryProduceBlocksFlag, + utils.L2BaseFeeScalarFlag, + utils.L2BaseFeeOverheadFlag, } rpcFlags = []cli.Flag{ diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 8686dbc3df..df99d6eed0 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -161,6 +161,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{ utils.RPCGlobalEVMTimeoutFlag, utils.RPCGlobalTxFeeCapFlag, utils.AllowUnprotectedTxs, + utils.MaxBlockRangeFlag, utils.JSpathFlag, utils.ExecFlag, utils.PreloadJSFlag, @@ -227,6 +228,30 @@ var AppHelpFlagGroups = []flags.FlagGroup{ Name: "METRICS AND STATS", Flags: metricsFlags, }, + { + Name: "ROLLUP", + Flags: []cli.Flag{ + utils.L1EndpointFlag, + utils.L1ConfirmationsFlag, + utils.L1DeploymentBlockFlag, + utils.L1DisableMessageQueueV2Flag, + utils.RollupVerifyEnabledFlag, + utils.L2BaseFeeScalarFlag, + utils.L2BaseFeeOverheadFlag, + utils.DASyncEnabledFlag, + utils.DABlobScanAPIEndpointFlag, + utils.DABlockNativeAPIEndpointFlag, + utils.DABeaconNodeAPIEndpointFlag, + utils.DARecoveryModeFlag, + utils.DARecoveryInitialL1BlockFlag, + utils.DARecoveryInitialBatchFlag, + utils.DARecoverySignBlocksFlag, + utils.DARecoveryL2EndBlockFlag, + utils.DARecoveryProduceBlocksFlag, + utils.CircuitCapacityCheckEnabledFlag, + utils.CircuitCapacityCheckWorkersFlag, + }, + }, { Name: "ALIASED (deprecated)", Flags: []cli.Flag{ diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 7371a37700..10c8a60e53 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -47,6 +47,7 @@ import ( "github.com/scroll-tech/go-ethereum/consensus" "github.com/scroll-tech/go-ethereum/consensus/clique" "github.com/scroll-tech/go-ethereum/consensus/ethash" + "github.com/scroll-tech/go-ethereum/consensus/misc" "github.com/scroll-tech/go-ethereum/core" "github.com/scroll-tech/go-ethereum/core/rawdb" "github.com/scroll-tech/go-ethereum/core/vm" @@ -934,6 +935,18 @@ var ( Name: "da.recovery.produceblocks", Usage: "Produce unsigned blocks after L1 recovery for permissionless batch submission", } + + // L2 base fee settings + L2BaseFeeScalarFlag = BigFlag{ + Name: "basefee.scalar", + Usage: "Scalar used in the l2 base fee formula. Signer nodes will use this for computing the next block's base fee. Follower nodes will use this in RPC.", + Value: misc.DefaultBaseFeeScalar, + } + L2BaseFeeOverheadFlag = BigFlag{ + Name: "basefee.overhead", + Usage: "Overhead used in the l2 base fee formula. Signer nodes will use this for computing the next block's base fee. Follower nodes will use this in RPC.", + Value: misc.DefaultBaseFeeOverhead, + } ) // MakeDataDir retrieves the currently requested data directory, terminating @@ -1709,6 +1722,29 @@ func setDA(ctx *cli.Context, cfg *ethconfig.Config) { } } +func setBaseFee(ctx *cli.Context, cfg *ethconfig.Config) { + cfg.BaseFeeScalar = misc.DefaultBaseFeeScalar + if ctx.GlobalIsSet(L2BaseFeeScalarFlag.Name) { + cfg.BaseFeeScalar = GlobalBig(ctx, L2BaseFeeScalarFlag.Name) + } + cfg.BaseFeeOverhead = misc.DefaultBaseFeeOverhead + if ctx.GlobalIsSet(L2BaseFeeOverheadFlag.Name) { + cfg.BaseFeeOverhead = GlobalBig(ctx, L2BaseFeeOverheadFlag.Name) + } + + log.Info("L2 base fee coefficients", "scalar", cfg.BaseFeeScalar, "overhead", cfg.BaseFeeOverhead) + + var minBaseFee uint64 + if fee := misc.MinBaseFee(cfg.BaseFeeScalar, cfg.BaseFeeOverhead); fee.IsUint64() { + minBaseFee = fee.Uint64() + } + + if cfg.TxPool.PriceLimit < minBaseFee { + log.Warn("Updating txpool price limit to min L2 base fee", "provided", cfg.TxPool.PriceLimit, "updated", minBaseFee) + cfg.TxPool.PriceLimit = minBaseFee + } +} + func setMaxBlockRange(ctx *cli.Context, cfg *ethconfig.Config) { if ctx.GlobalIsSet(MaxBlockRangeFlag.Name) { cfg.MaxBlockRange = ctx.GlobalInt64(MaxBlockRangeFlag.Name) @@ -1785,6 +1821,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { setCircuitCapacityCheck(ctx, cfg) setEnableRollupVerify(ctx, cfg) setDA(ctx, cfg) + setBaseFee(ctx, cfg) setMaxBlockRange(ctx, cfg) if ctx.GlobalIsSet(ShadowforkPeersFlag.Name) { cfg.ShadowForkPeerIDs = ctx.GlobalStringSlice(ShadowforkPeersFlag.Name) diff --git a/consensus/misc/eip1559.go b/consensus/misc/eip1559.go index 382ad2ada5..b6443e76b0 100644 --- a/consensus/misc/eip1559.go +++ b/consensus/misc/eip1559.go @@ -28,6 +28,16 @@ import ( // We would only go above this if L1 base fee hits 2931 Gwei. const MaximumL2BaseFee = 10000000000 +// L2 base fee formula constants and defaults. +// l2BaseFee = (l1BaseFee * scalar) / PRECISION + overhead. +// `scalar` accounts for finalization costs. `overhead` accounts for sequencing and proving costs. +// we use 1e18 for precision to match the contract implementation. +var ( + BaseFeePrecision = new(big.Int).SetUint64(1e18) + DefaultBaseFeeScalar = new(big.Int).SetUint64(34000000000000) + DefaultBaseFeeOverhead = new(big.Int).SetUint64(15680000) +) + // VerifyEip1559Header verifies some header attributes which were changed in EIP-1559, // - gas limit check // - basefee check @@ -54,18 +64,29 @@ func CalcBaseFee(config *params.ChainConfig, parent *types.Header, parentL1BaseF if config.Clique != nil && config.Clique.ShadowForkHeight != 0 && parent.Number.Uint64() >= config.Clique.ShadowForkHeight { return big.NewInt(10000000) // 0.01 Gwei } - l2SequencerFee := big.NewInt(1000000) // 0.001 Gwei - provingFee := big.NewInt(14680000) // 0.01468 Gwei - // L1_base_fee * 0.000034 - verificationFee := parentL1BaseFee - verificationFee = new(big.Int).Mul(verificationFee, big.NewInt(34)) - verificationFee = new(big.Int).Div(verificationFee, big.NewInt(1000000)) + scalar := config.Scroll.BaseFeeScalar + if scalar == nil { + scalar = DefaultBaseFeeScalar + } + overhead := config.Scroll.BaseFeeOverhead + if overhead == nil { + overhead = DefaultBaseFeeOverhead + } - baseFee := big.NewInt(0) - baseFee.Add(baseFee, l2SequencerFee) - baseFee.Add(baseFee, provingFee) - baseFee.Add(baseFee, verificationFee) + return calcBaseFee(scalar, overhead, parentL1BaseFee) +} + +// MinBaseFee calculates the minimum L2 base fee based on the configured coefficients. +func MinBaseFee(scalar, overhead *big.Int) *big.Int { + return calcBaseFee(scalar, overhead, big.NewInt(0)) +} + +func calcBaseFee(scalar, overhead, parentL1BaseFee *big.Int) *big.Int { + baseFee := new(big.Int).Set(parentL1BaseFee) + baseFee.Mul(baseFee, scalar) + baseFee.Div(baseFee, BaseFeePrecision) + baseFee.Add(baseFee, overhead) if baseFee.Cmp(big.NewInt(MaximumL2BaseFee)) > 0 { baseFee = big.NewInt(MaximumL2BaseFee) diff --git a/consensus/misc/eip1559_test.go b/consensus/misc/eip1559_test.go index 12aa2b17c2..bab8c4e496 100644 --- a/consensus/misc/eip1559_test.go +++ b/consensus/misc/eip1559_test.go @@ -111,6 +111,27 @@ func TestCalcBaseFee(t *testing.T) { tests := []struct { parentL1BaseFee int64 expectedL2BaseFee int64 + }{ + {0, 1}, + {1000000000, 1}, + {2000000000, 1}, + {100000000000, 2}, + {111111111111, 2}, + {2164000000000, 22}, + {644149677419355, 6442}, + } + for i, test := range tests { + config := config() + config.Scroll.BaseFeeScalar = big.NewInt(10000000) + config.Scroll.BaseFeeOverhead = big.NewInt(1) + if have, want := CalcBaseFee(config, nil, big.NewInt(test.parentL1BaseFee)), big.NewInt(test.expectedL2BaseFee); have.Cmp(want) != 0 { + t.Errorf("test %d: have %d want %d, ", i, have, want) + } + } + + testsWithDefaults := []struct { + parentL1BaseFee int64 + expectedL2BaseFee int64 }{ {0, 15680000}, {1000000000, 15714000}, @@ -120,9 +141,20 @@ func TestCalcBaseFee(t *testing.T) { {2164000000000, 89256000}, {644149677419355, 10000000000}, // cap at max L2 base fee } - for i, test := range tests { + for i, test := range testsWithDefaults { if have, want := CalcBaseFee(config(), nil, big.NewInt(test.parentL1BaseFee)), big.NewInt(test.expectedL2BaseFee); have.Cmp(want) != 0 { t.Errorf("test %d: have %d want %d, ", i, have, want) } } } + +// TestMinBaseFee assumes all blocks are 1559-blocks +func TestMinBaseFee(t *testing.T) { + if have, want := MinBaseFee(DefaultBaseFeeScalar, DefaultBaseFeeOverhead), big.NewInt(15680000); have.Cmp(want) != 0 { + t.Errorf("have %d want %d, ", have, want) + } + + if have, want := MinBaseFee(big.NewInt(10000000), big.NewInt(1)), big.NewInt(1); have.Cmp(want) != 0 { + t.Errorf("have %d want %d, ", have, want) + } +} diff --git a/eth/backend.go b/eth/backend.go index d6902f089d..99d35828d2 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -148,6 +148,13 @@ func New(stack *node.Node, config *ethconfig.Config, l1Client l1.Client) (*Ether if _, ok := genesisErr.(*params.ConfigCompatError); genesisErr != nil && !ok { return nil, genesisErr } + + // Hacky workaround: + // It's hard to pass these fields to `CalcBaseFee`, etc. + // So pass them as part of the genesis config instead. + chainConfig.Scroll.BaseFeeScalar = config.BaseFeeScalar + chainConfig.Scroll.BaseFeeOverhead = config.BaseFeeOverhead + log.Info("Initialised chain configuration", "config", chainConfig) if err := pruner.RecoverPruning(stack.ResolvePath(""), chainDb, stack.ResolvePath(config.TrieCleanCacheJournal)); err != nil { diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 94fa46c34c..f50109cb3f 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -230,6 +230,10 @@ type Config struct { // DA syncer options DA da_syncer.Config + + // L2 base fee coefficients for the formula: `l2BaseFee = (l1BaseFee * scalar) / PRECISION + overhead`. + BaseFeeScalar *big.Int + BaseFeeOverhead *big.Int } // CreateConsensusEngine creates a consensus engine for the given chain configuration. diff --git a/params/config.go b/params/config.go index d5f1a2166c..28eb863ba6 100644 --- a/params/config.go +++ b/params/config.go @@ -704,6 +704,11 @@ type ScrollConfig struct { // Genesis State Root for MPT clients GenesisStateRoot *common.Hash `json:"genesisStateRoot,omitempty"` + + // L2 base fee coefficients for the formula: `l2BaseFee = (l1BaseFee * scalar) / PRECISION + overhead`. + // These fields are populated from configuration flags, and passed to `CalcBaseFee`. + BaseFeeScalar *big.Int `json:"-"` + BaseFeeOverhead *big.Int `json:"-"` } // L1Config contains the l1 parameters needed to sync l1 contract events (e.g., l1 messages, commit/revert/finalize batches) in the sequencer @@ -753,8 +758,18 @@ func (s ScrollConfig) String() string { genesisStateRoot = fmt.Sprintf("%v", *s.GenesisStateRoot) } - return fmt.Sprintf("{useZktrie: %v, maxTxPerBlock: %v, MaxTxPayloadBytesPerBlock: %v, feeVaultAddress: %v, l1Config: %v, genesisStateRoot: %v}", - s.UseZktrie, maxTxPerBlock, maxTxPayloadBytesPerBlock, s.FeeVaultAddress, s.L1Config.String(), genesisStateRoot) + baseFeeScalar := "" + if s.BaseFeeScalar != nil { + baseFeeScalar = s.BaseFeeScalar.String() + } + + baseFeeOverhead := "" + if s.BaseFeeOverhead != nil { + baseFeeOverhead = s.BaseFeeOverhead.String() + } + + return fmt.Sprintf("{useZktrie: %v, maxTxPerBlock: %v, MaxTxPayloadBytesPerBlock: %v, feeVaultAddress: %v, l1Config: %v, genesisStateRoot: %v, baseFeeScalar: %v, baseFeeOverhead: %v}", + s.UseZktrie, maxTxPerBlock, maxTxPayloadBytesPerBlock, s.FeeVaultAddress, s.L1Config.String(), genesisStateRoot, baseFeeScalar, baseFeeOverhead) } // IsValidTxCount returns whether the given block's transaction count is below the limit. diff --git a/params/version.go b/params/version.go index 7105fe6f63..07af53cefd 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 5 // Major version component of the current release VersionMinor = 8 // Minor version component of the current release - VersionPatch = 44 // Patch version component of the current release + VersionPatch = 45 // Patch version component of the current release VersionMeta = "mainnet" // Version metadata to append to the version string ) From 2500647c49ac7bcf54dd8c9f54f127ea10591361 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Wed, 14 May 2025 15:34:00 +0200 Subject: [PATCH 35/45] fix: address race condition during EuclidV2 header chain verification (#1186) --- consensus/wrapper/consensus.go | 33 +++++++++++++++++++++++++++++---- params/version.go | 2 +- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/consensus/wrapper/consensus.go b/consensus/wrapper/consensus.go index 3c5aa5cb3e..bfabd0ffcc 100644 --- a/consensus/wrapper/consensus.go +++ b/consensus/wrapper/consensus.go @@ -59,6 +59,31 @@ func (ue *UpgradableEngine) VerifyHeader(chain consensus.ChainHeaderReader, head return ue.chooseEngine(header.Time).VerifyHeader(chain, header, seal) } +func waitForHeader(chain consensus.ChainHeaderReader, header *types.Header) { + hash, number := header.Hash(), header.Number.Uint64() + + // poll every 2 seconds, should succeed after a few tries + ticker := time.NewTicker(2 * time.Second) + defer ticker.Stop() + + // we give up after 2 minutes + timeout := time.After(120 * time.Second) + + for { + select { + case <-ticker.C: + // try reading from chain, if the header is present then we are ready + if h := chain.GetHeader(hash, number); h != nil { + return + } + + case <-timeout: + log.Warn("Unable to find last pre-EuclidV2 header in chain", "hash", hash.Hex(), "number", number) + return + } + } +} + // VerifyHeaders verifies a batch of headers concurrently. In our use-case, // headers can only be all system, all clique, or start with clique and then switch once to system. func (ue *UpgradableEngine) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) { @@ -124,10 +149,10 @@ func (ue *UpgradableEngine) VerifyHeaders(chain consensus.ChainHeaderReader, hea } } - // Not sure why we need this here, but without this we get err="unknown ancestor" - // at the 1st Euclid block. It seems that `VerifyHeaders` start processing the next - // header before the previous one was written into `chain`. - time.Sleep(2 * time.Second) + // `VerifyHeader` will try to call `chain.GetHeader`, which will race with `InsertHeaderChain`. + // This might result in "unknown ancestor" header validation error. + // This should be temporary, so we solve this by waiting here. + waitForHeader(chain, cliqueHeaders[len(cliqueHeaders)-1]) // Verify system contract headers. log.Info("Start EuclidV2 transition verification in SystemContract section", "startBlockNumber", systemHeaders[0].Number, "endBlockNumber", systemHeaders[len(systemHeaders)-1].Number) diff --git a/params/version.go b/params/version.go index 07af53cefd..2522b38243 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 5 // Major version component of the current release VersionMinor = 8 // Minor version component of the current release - VersionPatch = 45 // Patch version component of the current release + VersionPatch = 46 // Patch version component of the current release VersionMeta = "mainnet" // Version metadata to append to the version string ) From aba2ddda86ea3d28f8ff622c46434a7cff768f4d Mon Sep 17 00:00:00 2001 From: Morty <70688412+yiweichi@users.noreply.github.com> Date: Thu, 15 May 2025 16:29:34 +0800 Subject: [PATCH 36/45] feat: add metrics finalized block (#1172) --- rollup/rollup_sync_service/rollup_sync_service.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/rollup/rollup_sync_service/rollup_sync_service.go b/rollup/rollup_sync_service/rollup_sync_service.go index 190fd0810e..844147e28c 100644 --- a/rollup/rollup_sync_service/rollup_sync_service.go +++ b/rollup/rollup_sync_service/rollup_sync_service.go @@ -17,6 +17,7 @@ import ( "github.com/scroll-tech/go-ethereum/core/rawdb" "github.com/scroll-tech/go-ethereum/ethdb" "github.com/scroll-tech/go-ethereum/log" + "github.com/scroll-tech/go-ethereum/metrics" "github.com/scroll-tech/go-ethereum/node" "github.com/scroll-tech/go-ethereum/params" "github.com/scroll-tech/go-ethereum/rollup/da_syncer" @@ -43,7 +44,10 @@ const ( defaultLogInterval = 5 * time.Minute ) -var ErrShouldResetSyncHeight = errors.New("ErrShouldResetSyncHeight") +var ( + finalizedBlockGauge = metrics.NewRegisteredGauge("chain/head/finalized", nil) + ErrShouldResetSyncHeight = errors.New("ErrShouldResetSyncHeight") +) // RollupSyncService collects ScrollChain batch commit/revert/finalize events and stores metadata into db. type RollupSyncService struct { @@ -135,6 +139,11 @@ func (s *RollupSyncService) Start() { log.Info("Starting rollup event sync background service", "latest processed block", s.callDataBlobSource.L1Height()) + finalizedBlockHeightPtr := rawdb.ReadFinalizedL2BlockNumber(s.db) + if finalizedBlockHeightPtr != nil { + finalizedBlockGauge.Update(int64(*finalizedBlockHeightPtr)) + } + go func() { syncTicker := time.NewTicker(defaultSyncInterval) defer syncTicker.Stop() @@ -321,6 +330,7 @@ func (s *RollupSyncService) updateRollupEvents(daEntries da.Entries) error { return fmt.Errorf("failed to batch write finalized batch meta to database: %w", err) } rawdb.WriteFinalizedL2BlockNumber(s.db, highestFinalizedBlockNumber) + finalizedBlockGauge.Update(int64(highestFinalizedBlockNumber)) rawdb.WriteLastFinalizedBatchIndex(s.db, batchIndex) log.Debug("write finalized l2 block number", "batch index", batchIndex, "finalized l2 block height", highestFinalizedBlockNumber) From ad6cced99df72e5863171193fd0c09bc8f78abb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Fri, 16 May 2025 09:27:46 +0200 Subject: [PATCH 37/45] fix: provide parent header during EuclidV2 transition verification (#1187) * fix: provide parent header during EuclidV2 transition verification * add test --- consensus/clique/clique.go | 2 +- consensus/consensus.go | 2 +- consensus/ethash/consensus.go | 2 +- consensus/system_contract/consensus.go | 9 +- consensus/system_contract/system_contract.go | 53 ++++++++ .../system_contract/system_contract_test.go | 64 +--------- consensus/wrapper/consensus.go | 43 ++----- core/block_validator_test.go | 10 +- core/blockchain.go | 2 +- core/headerchain.go | 2 +- miner/scroll_worker_test.go | 116 +++++++++++++++++- params/version.go | 2 +- 12 files changed, 199 insertions(+), 108 deletions(-) diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index 68fadec813..8a4c8bda4a 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -226,7 +226,7 @@ func (c *Clique) VerifyHeader(chain consensus.ChainHeaderReader, header *types.H // VerifyHeaders is similar to VerifyHeader, but verifies a batch of headers. The // method returns a quit channel to abort the operations and a results channel to // retrieve the async verifications (the order is that of the input slice). -func (c *Clique) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) { +func (c *Clique) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool, parent *types.Header) (chan<- struct{}, <-chan error) { abort := make(chan struct{}) results := make(chan error, len(headers)) diff --git a/consensus/consensus.go b/consensus/consensus.go index 6416460684..5ed80dff4d 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -71,7 +71,7 @@ type Engine interface { // concurrently. The method returns a quit channel to abort the operations and // a results channel to retrieve the async verifications (the order is that of // the input slice). - VerifyHeaders(chain ChainHeaderReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) + VerifyHeaders(chain ChainHeaderReader, headers []*types.Header, seals []bool, parent *types.Header) (chan<- struct{}, <-chan error) // VerifyUncles verifies that the given block's uncles conform to the consensus // rules of a given engine. diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index a24ec37f67..d90cf613f3 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -119,7 +119,7 @@ func (ethash *Ethash) VerifyHeader(chain consensus.ChainHeaderReader, header *ty // VerifyHeaders is similar to VerifyHeader, but verifies a batch of headers // concurrently. The method returns a quit channel to abort the operations and // a results channel to retrieve the async verifications. -func (ethash *Ethash) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) { +func (ethash *Ethash) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool, parent *types.Header) (chan<- struct{}, <-chan error) { // If we're running a full engine faking, accept any input as valid if ethash.config.PowMode == ModeFullFake || len(headers) == 0 { abort, results := make(chan struct{}), make(chan error, len(headers)) diff --git a/consensus/system_contract/consensus.go b/consensus/system_contract/consensus.go index c01bec805a..01d7be1850 100644 --- a/consensus/system_contract/consensus.go +++ b/consensus/system_contract/consensus.go @@ -86,13 +86,18 @@ func (s *SystemContract) VerifyHeader(chain consensus.ChainHeaderReader, header // concurrently. The method returns a quit channel to abort the operations and // a results channel to retrieve the async verifications (the order is that of // the input slice). -func (s *SystemContract) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) { +func (s *SystemContract) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool, parent *types.Header) (chan<- struct{}, <-chan error) { abort := make(chan struct{}) results := make(chan error, len(headers)) go func() { for i, header := range headers { - err := s.verifyHeader(chain, header, headers[:i]) + parents := headers[:i] + if len(parents) == 0 && parent != nil { + parents = []*types.Header{parent} + } + + err := s.verifyHeader(chain, header, parents) if err != nil { log.Error("Error verifying headers", "err", err) } diff --git a/consensus/system_contract/system_contract.go b/consensus/system_contract/system_contract.go index a93968e873..aef24a00ba 100644 --- a/consensus/system_contract/system_contract.go +++ b/consensus/system_contract/system_contract.go @@ -3,10 +3,13 @@ package system_contract import ( "context" "fmt" + "math/big" "sync" "time" + "github.com/scroll-tech/go-ethereum" "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/params" "github.com/scroll-tech/go-ethereum/rollup/sync_service" @@ -140,3 +143,53 @@ func (s *SystemContract) localSignerAddress() common.Address { return s.signer } + +// FakeEthClient implements a minimal version of sync_service.EthClient for testing purposes. +type FakeEthClient struct { + mu sync.Mutex + // Value is the fixed Value to return from StorageAt. + // We'll assume StorageAt returns a 32-byte Value representing an Ethereum address. + Value common.Address +} + +// BlockNumber returns 0. +func (f *FakeEthClient) BlockNumber(ctx context.Context) (uint64, error) { + return 0, nil +} + +// ChainID returns a zero-value chain ID. +func (f *FakeEthClient) ChainID(ctx context.Context) (*big.Int, error) { + return big.NewInt(0), nil +} + +// FilterLogs returns an empty slice of logs. +func (f *FakeEthClient) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) { + return []types.Log{}, nil +} + +// HeaderByNumber returns nil. +func (f *FakeEthClient) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { + return nil, nil +} + +// SubscribeFilterLogs returns a nil subscription. +func (f *FakeEthClient) SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) { + return nil, nil +} + +// TransactionByHash returns (nil, false, nil). +func (f *FakeEthClient) TransactionByHash(ctx context.Context, txHash common.Hash) (*types.Transaction, bool, error) { + return nil, false, nil +} + +// BlockByHash returns nil. +func (f *FakeEthClient) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { + return nil, nil +} + +// StorageAt returns the byte representation of f.value. +func (f *FakeEthClient) StorageAt(ctx context.Context, account common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) { + f.mu.Lock() + defer f.mu.Unlock() + return f.Value.Bytes(), nil +} diff --git a/consensus/system_contract/system_contract_test.go b/consensus/system_contract/system_contract_test.go index ddd6a0fc8d..fa344f5c9e 100644 --- a/consensus/system_contract/system_contract_test.go +++ b/consensus/system_contract/system_contract_test.go @@ -3,13 +3,11 @@ package system_contract import ( "context" "math/big" - "sync" "testing" "time" "github.com/stretchr/testify/require" - "github.com/scroll-tech/go-ethereum" "github.com/scroll-tech/go-ethereum/accounts" "github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/core/types" @@ -19,14 +17,14 @@ import ( "github.com/scroll-tech/go-ethereum/trie" ) -var _ sync_service.EthClient = &fakeEthClient{} +var _ sync_service.EthClient = &FakeEthClient{} func TestSystemContract_FetchSigner(t *testing.T) { log.Root().SetHandler(log.DiscardHandler()) expectedSigner := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678") - fakeClient := &fakeEthClient{value: expectedSigner} + fakeClient := &FakeEthClient{Value: expectedSigner} config := ¶ms.SystemContractConfig{ SystemContractAddress: common.HexToAddress("0xFAKE"), @@ -55,7 +53,7 @@ func TestSystemContract_AuthorizeCheck(t *testing.T) { expectedSigner := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678") - fakeClient := &fakeEthClient{value: expectedSigner} + fakeClient := &FakeEthClient{Value: expectedSigner} config := ¶ms.SystemContractConfig{ SystemContractAddress: common.HexToAddress("0xFAKE"), Period: 10, @@ -106,8 +104,8 @@ func TestSystemContract_SignsAfterUpdate(t *testing.T) { updatedSigner := common.HexToAddress("0x2222222222222222222222222222222222222222") // Create a fake client that starts by returning the wrong signer. - fakeClient := &fakeEthClient{ - value: oldSigner, + fakeClient := &FakeEthClient{ + Value: oldSigner, } config := ¶ms.SystemContractConfig{ @@ -130,7 +128,7 @@ func TestSystemContract_SignsAfterUpdate(t *testing.T) { // Now, simulate an update: change the fake client's returned value to updatedSigner. fakeClient.mu.Lock() - fakeClient.value = updatedSigner + fakeClient.Value = updatedSigner fakeClient.mu.Unlock() // fetch new value from L1 (simulating a background poll) @@ -171,53 +169,3 @@ func TestSystemContract_SignsAfterUpdate(t *testing.T) { t.Fatal("Timed out waiting for Seal to return a sealed block") } } - -// fakeEthClient implements a minimal version of sync_service.EthClient for testing purposes. -type fakeEthClient struct { - mu sync.Mutex - // value is the fixed value to return from StorageAt. - // We'll assume StorageAt returns a 32-byte value representing an Ethereum address. - value common.Address -} - -// BlockNumber returns 0. -func (f *fakeEthClient) BlockNumber(ctx context.Context) (uint64, error) { - return 0, nil -} - -// ChainID returns a zero-value chain ID. -func (f *fakeEthClient) ChainID(ctx context.Context) (*big.Int, error) { - return big.NewInt(0), nil -} - -// FilterLogs returns an empty slice of logs. -func (f *fakeEthClient) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) { - return []types.Log{}, nil -} - -// HeaderByNumber returns nil. -func (f *fakeEthClient) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { - return nil, nil -} - -// SubscribeFilterLogs returns a nil subscription. -func (f *fakeEthClient) SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) { - return nil, nil -} - -// TransactionByHash returns (nil, false, nil). -func (f *fakeEthClient) TransactionByHash(ctx context.Context, txHash common.Hash) (*types.Transaction, bool, error) { - return nil, false, nil -} - -// BlockByHash returns nil. -func (f *fakeEthClient) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { - return nil, nil -} - -// StorageAt returns the byte representation of f.value. -func (f *fakeEthClient) StorageAt(ctx context.Context, account common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) { - f.mu.Lock() - defer f.mu.Unlock() - return f.value.Bytes(), nil -} diff --git a/consensus/wrapper/consensus.go b/consensus/wrapper/consensus.go index bfabd0ffcc..16ea46f0d8 100644 --- a/consensus/wrapper/consensus.go +++ b/consensus/wrapper/consensus.go @@ -2,7 +2,6 @@ package wrapper import ( "math/big" - "time" "github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/consensus" @@ -59,34 +58,9 @@ func (ue *UpgradableEngine) VerifyHeader(chain consensus.ChainHeaderReader, head return ue.chooseEngine(header.Time).VerifyHeader(chain, header, seal) } -func waitForHeader(chain consensus.ChainHeaderReader, header *types.Header) { - hash, number := header.Hash(), header.Number.Uint64() - - // poll every 2 seconds, should succeed after a few tries - ticker := time.NewTicker(2 * time.Second) - defer ticker.Stop() - - // we give up after 2 minutes - timeout := time.After(120 * time.Second) - - for { - select { - case <-ticker.C: - // try reading from chain, if the header is present then we are ready - if h := chain.GetHeader(hash, number); h != nil { - return - } - - case <-timeout: - log.Warn("Unable to find last pre-EuclidV2 header in chain", "hash", hash.Hex(), "number", number) - return - } - } -} - // VerifyHeaders verifies a batch of headers concurrently. In our use-case, // headers can only be all system, all clique, or start with clique and then switch once to system. -func (ue *UpgradableEngine) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) { +func (ue *UpgradableEngine) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool, parent *types.Header) (chan<- struct{}, <-chan error) { abort := make(chan struct{}) results := make(chan error, len(headers)) @@ -102,12 +76,12 @@ func (ue *UpgradableEngine) VerifyHeaders(chain consensus.ChainHeaderReader, hea // If the first header is system, then all headers must be system. if firstEngine == ue.system { - return firstEngine.VerifyHeaders(chain, headers, seals) + return firstEngine.VerifyHeaders(chain, headers, seals, nil) } // If first and last headers are both clique, then all headers are clique. if firstEngine == lastEngine { - return firstEngine.VerifyHeaders(chain, headers, seals) + return firstEngine.VerifyHeaders(chain, headers, seals, nil) } // Otherwise, headers start as clique then switch to system. Since we assume @@ -135,7 +109,7 @@ func (ue *UpgradableEngine) VerifyHeaders(chain consensus.ChainHeaderReader, hea // Verify clique headers. log.Info("Start EuclidV2 transition verification in Clique section", "startBlockNumber", cliqueHeaders[0].Number, "endBlockNumber", cliqueHeaders[len(cliqueHeaders)-1].Number) - abortClique, cliqueResults := ue.clique.VerifyHeaders(chain, cliqueHeaders, cliqueSeals) + abortClique, cliqueResults := ue.clique.VerifyHeaders(chain, cliqueHeaders, cliqueSeals, nil) // Note: cliqueResults is not closed so we cannot directly iterate over it for i := 0; i < len(cliqueHeaders); i++ { @@ -149,14 +123,13 @@ func (ue *UpgradableEngine) VerifyHeaders(chain consensus.ChainHeaderReader, hea } } - // `VerifyHeader` will try to call `chain.GetHeader`, which will race with `InsertHeaderChain`. - // This might result in "unknown ancestor" header validation error. - // This should be temporary, so we solve this by waiting here. - waitForHeader(chain, cliqueHeaders[len(cliqueHeaders)-1]) + // Since the Clique part of the header chain might not yet be stored in the local chain, + // provide a hint to the SystemContract consensus engine. + lastCliqueHeader := cliqueHeaders[len(cliqueHeaders)-1] // Verify system contract headers. log.Info("Start EuclidV2 transition verification in SystemContract section", "startBlockNumber", systemHeaders[0].Number, "endBlockNumber", systemHeaders[len(systemHeaders)-1].Number) - abortSystem, systemResults := ue.system.VerifyHeaders(chain, systemHeaders, systemSeals) + abortSystem, systemResults := ue.system.VerifyHeaders(chain, systemHeaders, systemSeals, lastCliqueHeader) // Note: systemResults is not closed so we cannot directly iterate over it for i := 0; i < len(systemHeaders); i++ { diff --git a/core/block_validator_test.go b/core/block_validator_test.go index c94132d001..12e599921c 100644 --- a/core/block_validator_test.go +++ b/core/block_validator_test.go @@ -51,10 +51,10 @@ func TestHeaderVerification(t *testing.T) { if valid { engine := ethash.NewFaker() - _, results = engine.VerifyHeaders(chain, []*types.Header{headers[i]}, []bool{true}) + _, results = engine.VerifyHeaders(chain, []*types.Header{headers[i]}, []bool{true}, nil) } else { engine := ethash.NewFakeFailer(headers[i].Number.Uint64()) - _, results = engine.VerifyHeaders(chain, []*types.Header{headers[i]}, []bool{true}) + _, results = engine.VerifyHeaders(chain, []*types.Header{headers[i]}, []bool{true}, nil) } // Wait for the verification result select { @@ -107,11 +107,11 @@ func testHeaderConcurrentVerification(t *testing.T, threads int) { if valid { chain, _ := NewBlockChain(testdb, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, nil) - _, results = chain.engine.VerifyHeaders(chain, headers, seals) + _, results = chain.engine.VerifyHeaders(chain, headers, seals, nil) chain.Stop() } else { chain, _ := NewBlockChain(testdb, nil, params.TestChainConfig, ethash.NewFakeFailer(uint64(len(headers)-1)), vm.Config{}, nil, nil) - _, results = chain.engine.VerifyHeaders(chain, headers, seals) + _, results = chain.engine.VerifyHeaders(chain, headers, seals, nil) chain.Stop() } // Wait for all the verification results @@ -176,7 +176,7 @@ func testHeaderConcurrentAbortion(t *testing.T, threads int) { chain, _ := NewBlockChain(testdb, nil, params.TestChainConfig, ethash.NewFakeDelayer(time.Millisecond), vm.Config{}, nil, nil) defer chain.Stop() - abort, results := chain.engine.VerifyHeaders(chain, headers, seals) + abort, results := chain.engine.VerifyHeaders(chain, headers, seals, nil) close(abort) // Deplete the results channel diff --git a/core/blockchain.go b/core/blockchain.go index 1a46a315c2..821564769e 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1546,7 +1546,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er headers[i] = block.Header() seals[i] = verifySeals } - abort, results := bc.engine.VerifyHeaders(bc, headers, seals) + abort, results := bc.engine.VerifyHeaders(bc, headers, seals, nil) defer close(abort) // Peek the error for the first block to decide the directing import logic diff --git a/core/headerchain.go b/core/headerchain.go index f047d654d9..c01eb7ba8c 100644 --- a/core/headerchain.go +++ b/core/headerchain.go @@ -339,7 +339,7 @@ func (hc *HeaderChain) ValidateHeaderChain(chain []*types.Header, checkFreq int) seals[len(seals)-1] = true } - abort, results := hc.engine.VerifyHeaders(hc, chain, seals) + abort, results := hc.engine.VerifyHeaders(hc, chain, seals, nil) defer close(abort) // Iterate over the headers and ensure they all check out diff --git a/miner/scroll_worker_test.go b/miner/scroll_worker_test.go index db18e86738..cbbf82f32a 100644 --- a/miner/scroll_worker_test.go +++ b/miner/scroll_worker_test.go @@ -17,6 +17,7 @@ package miner import ( + "context" "fmt" "math" "math/big" @@ -33,6 +34,8 @@ import ( "github.com/scroll-tech/go-ethereum/consensus" "github.com/scroll-tech/go-ethereum/consensus/clique" "github.com/scroll-tech/go-ethereum/consensus/ethash" + "github.com/scroll-tech/go-ethereum/consensus/system_contract" + "github.com/scroll-tech/go-ethereum/consensus/wrapper" "github.com/scroll-tech/go-ethereum/core" "github.com/scroll-tech/go-ethereum/core/rawdb" "github.com/scroll-tech/go-ethereum/core/types" @@ -77,6 +80,14 @@ var ( MaxAccountsNum: math.MaxInt, CCCMaxWorkers: 2, } + + testConfigAllowEmpty = &Config{ + Recommit: time.Second, + GasCeil: params.GenesisGasLimit, + MaxAccountsNum: math.MaxInt, + CCCMaxWorkers: 2, + AllowEmpty: true, + } ) func init() { @@ -138,6 +149,15 @@ func newTestWorkerBackend(t *testing.T, chainConfig *params.ChainConfig, engine e.Authorize(testBankAddress, func(account accounts.Account, s string, data []byte) ([]byte, error) { return crypto.Sign(crypto.Keccak256(data), testBankKey) }) + case *wrapper.UpgradableEngine: + gspec.ExtraData = make([]byte, 32+common.AddressLength+crypto.SignatureLength) + gspec.Timestamp = uint64(time.Now().Unix()) + copy(gspec.ExtraData[32:32+common.AddressLength], testBankAddress.Bytes()) + e.Authorize(testBankAddress, func(account accounts.Account, s string, data []byte) ([]byte, error) { + return crypto.Sign(crypto.Keccak256(data), testBankKey) + }, func(account accounts.Account, s string, data []byte) ([]byte, error) { + return crypto.Sign(crypto.Keccak256(data), testBankKey) + }) case *ethash.Ethash: default: t.Fatalf("unexpected consensus engine type: %T", engine) @@ -207,14 +227,26 @@ func (b *testWorkerBackend) newRandomTx(creation bool) *types.Transaction { return tx } -func newTestWorker(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, db ethdb.Database, blocks int) (*worker, *testWorkerBackend) { +func testWorker(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, db ethdb.Database, blocks int, allowEmpty bool) (*worker, *testWorkerBackend) { backend := newTestWorkerBackend(t, chainConfig, engine, db, blocks) backend.txPool.AddLocals(pendingTxs) - w := newWorker(testConfig, chainConfig, engine, backend, new(event.TypeMux), nil, false, false) + config := testConfig + if allowEmpty { + config = testConfigAllowEmpty + } + w := newWorker(config, chainConfig, engine, backend, new(event.TypeMux), nil, false, false) w.setEtherbase(testBankAddress) return w, backend } +func newTestWorker(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, db ethdb.Database, blocks int) (*worker, *testWorkerBackend) { + return testWorker(t, chainConfig, engine, db, blocks, false) +} + +func newTestWorkerWithEmptyBlock(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, db ethdb.Database, blocks int) (*worker, *testWorkerBackend) { + return testWorker(t, chainConfig, engine, db, blocks, true) +} + func TestGenerateBlockAndImportClique(t *testing.T) { testGenerateBlockAndImport(t, true) } @@ -1355,3 +1387,83 @@ func TestEuclidV2HardForkMessageQueue(t *testing.T) { } } } + +// TestEuclidV2TransitionVerification tests that the upgradable consensus engine +// can successfully verify the EuclidV2 transition chain. +func TestEuclidV2TransitionVerification(t *testing.T) { + // patch time.Now() to be able to simulate hard fork time + patches := gomonkey.NewPatches() + defer patches.Reset() + var timeCount int64 + patches.ApplyFunc(time.Now, func() time.Time { + timeCount++ + return time.Unix(timeCount, 0) + }) + + // init chain config + chainConfig := params.AllCliqueProtocolChanges.Clone() + chainConfig.EuclidTime = newUint64(0) + chainConfig.EuclidV2Time = newUint64(10000) + chainConfig.Clique = ¶ms.CliqueConfig{Period: 1, Epoch: 30000} + chainConfig.SystemContract = ¶ms.SystemContractConfig{Period: 1} + + // init worker + db := rawdb.NewMemoryDatabase() + cliqueEngine := clique.New(chainConfig.Clique, db) + sysEngine := system_contract.New(context.Background(), chainConfig.SystemContract, &system_contract.FakeEthClient{Value: testBankAddress}) + engine := wrapper.NewUpgradableEngine(chainConfig.IsEuclidV2, cliqueEngine, sysEngine) + w, b := newTestWorkerWithEmptyBlock(t, chainConfig, engine, db, 0) + defer w.close() + b.genesis.MustCommit(db) + + // collect mined blocks + sub := w.mux.Subscribe(core.NewMinedBlockEvent{}) + defer sub.Unsubscribe() + w.start() + + blocks := []*types.Block{} + headers := []*types.Header{} + + for i := 0; i < 6; i++ { + select { + case ev := <-sub.Chan(): + // activate EuclidV2 at next block + if i == 2 { + timeCount = int64(*chainConfig.EuclidV2Time) + } + + block := ev.Data.(core.NewMinedBlockEvent).Block + blocks = append(blocks, block) + headers = append(headers, block.Header()) + + case <-time.After(3 * time.Second): + t.Fatalf("timeout") + } + } + + // sanity check: we generated the EuclidV2 transition block + assert.False(t, chainConfig.IsEuclidV2(headers[0].Time)) + assert.True(t, chainConfig.IsEuclidV2(headers[len(headers)-1].Time)) + + // import headers into new chain + chainDb := rawdb.NewMemoryDatabase() + b.genesis.MustCommit(chainDb) + chain, err := core.NewBlockChain(chainDb, nil, b.chain.Config(), engine, vm.Config{}, nil, nil) + assert.NoError(t, err) + defer chain.Stop() + + // previously this would fail with `unknown ancestor` + _, err = chain.InsertHeaderChain(headers, 0) + assert.NoError(t, err) + + // import headers into new chain + chainDb = rawdb.NewMemoryDatabase() + b.genesis.MustCommit(chainDb) + chain, err = core.NewBlockChain(chainDb, nil, b.chain.Config(), engine, vm.Config{}, nil, nil) + assert.NoError(t, err) + defer chain.Stop() + + // previously this would fail with `unknown ancestor` + _, err = chain.InsertChain(blocks) + assert.NoError(t, err) +} diff --git a/params/version.go b/params/version.go index 2522b38243..dfae47a034 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 5 // Major version component of the current release VersionMinor = 8 // Minor version component of the current release - VersionPatch = 46 // Patch version component of the current release + VersionPatch = 47 // Patch version component of the current release VersionMeta = "mainnet" // Version metadata to append to the version string ) From c66a003b88bd9ec7ff8a08fbf9c871c56f808aae Mon Sep 17 00:00:00 2001 From: colin <102356659+colinlyguo@users.noreply.github.com> Date: Mon, 19 May 2025 19:38:51 +0800 Subject: [PATCH 38/45] feat: add logs to track tx and block propagation delay (#1184) --- eth/fetcher/tx_fetcher.go | 2 +- eth/handler.go | 11 +++++++---- eth/protocols/eth/handlers.go | 24 ++++++++++++++---------- eth/protocols/eth/peer.go | 2 +- params/version.go | 2 +- 5 files changed, 24 insertions(+), 17 deletions(-) diff --git a/eth/fetcher/tx_fetcher.go b/eth/fetcher/tx_fetcher.go index 24fec71020..dba52701f9 100644 --- a/eth/fetcher/tx_fetcher.go +++ b/eth/fetcher/tx_fetcher.go @@ -794,7 +794,7 @@ func (f *TxFetcher) scheduleFetches(timer *mclock.Timer, timeout chan struct{}, return true // continue in the for-each }) - log.Debug("Scheduling transaction retrieval", "peer", peer, "len(f.announces[peer])", len(f.announces[peer]), "len(hashes)", len(hashes)) + log.Trace("Scheduling transaction retrieval", "peer", peer, "len(f.announces[peer])", len(f.announces[peer]), "len(hashes)", len(hashes)) peerAnnounceTxsLenGauge.Update(int64(len(f.announces[peer]))) peerRetrievalTxsLenGauge.Update(int64(len(hashes))) diff --git a/eth/handler.go b/eth/handler.go index f77de69d0b..4755a0c7b7 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -455,6 +455,8 @@ func (h *handler) BroadcastBlock(block *types.Block, propagate bool) { hash := block.Hash() peers := onlyShadowForkPeers(h.shadowForkPeerIDs, h.peers.peersWithoutBlock(hash)) + log.Debug("Broadcasting block", "hash", hash.Hex(), "number", block.NumberU64(), "size", block.Size()) + // If propagation is requested, send to a subset of the peer if propagate { // Calculate the TD of the block (it's not imported yet, so block.Td is not valid) @@ -470,7 +472,7 @@ func (h *handler) BroadcastBlock(block *types.Block, propagate bool) { for _, peer := range transfer { peer.AsyncSendNewBlock(block, td) } - log.Trace("Propagated block", "hash", hash, "recipients", len(transfer), "duration", common.PrettyDuration(time.Since(block.ReceivedAt))) + log.Trace("Propagated block", "hash", hash.Hex(), "recipients", len(transfer), "duration", common.PrettyDuration(time.Since(block.ReceivedAt))) return } // Otherwise if the block is indeed in out own chain, announce it @@ -478,7 +480,7 @@ func (h *handler) BroadcastBlock(block *types.Block, propagate bool) { for _, peer := range peers { peer.AsyncSendNewBlockHash(block) } - log.Trace("Announced block", "hash", hash, "recipients", len(peers), "duration", common.PrettyDuration(time.Since(block.ReceivedAt))) + log.Trace("Announced block", "hash", hash.Hex(), "recipients", len(peers), "duration", common.PrettyDuration(time.Since(block.ReceivedAt))) } } @@ -503,6 +505,7 @@ func (h *handler) BroadcastTransactions(txs types.Transactions) { if tx.IsL1MessageTx() { continue } + log.Debug("Broadcasting transaction", "hash", tx.Hash().Hex(), "size", tx.Size()) peers := onlyShadowForkPeers(h.shadowForkPeerIDs, h.peers.peersWithoutTransaction(tx.Hash())) // Send the tx unconditionally to a subset of our peers numDirect := int(math.Sqrt(float64(len(peers)))) @@ -518,13 +521,13 @@ func (h *handler) BroadcastTransactions(txs types.Transactions) { directPeers++ directCount += len(hashes) peer.AsyncSendTransactions(hashes) - log.Debug("Transactions being broadcasted to", "peer", peer.String(), "len", len(hashes)) + log.Trace("Transactions being broadcasted to", "peer", peer.String(), "len", len(hashes)) } for peer, hashes := range annos { annoPeers++ annoCount += len(hashes) peer.AsyncSendPooledTransactionHashes(hashes) - log.Debug("Transactions being announced to", "peer", peer.String(), "len", len(hashes)) + log.Trace("Transactions being announced to", "peer", peer.String(), "len", len(hashes)) } log.Debug("Transaction broadcast", "txs", len(txs), "announce packs", annoPeers, "announced hashes", annoCount, diff --git a/eth/protocols/eth/handlers.go b/eth/protocols/eth/handlers.go index fd10725968..c0b8f2c9dc 100644 --- a/eth/protocols/eth/handlers.go +++ b/eth/protocols/eth/handlers.go @@ -294,6 +294,7 @@ func handleNewBlock(backend Backend, msg Decoder, peer *Peer) error { // Mark the peer as owning the block peer.markBlock(ann.Block.Hash()) + log.Debug("Received new block via gossip", "blockHash", ann.Block.Hash().Hex(), "blockNumber", ann.Block.NumberU64(), "peer", peer.String()) return backend.Handle(peer, ann) } @@ -362,12 +363,12 @@ func handleNewPooledTransactionHashes(backend Backend, msg Decoder, peer *Peer) } ann := new(NewPooledTransactionHashesPacket) if err := msg.Decode(ann); err != nil { - log.Debug("Failed to decode `NewPooledTransactionHashesPacket`", "peer", peer.String(), "err", err) + log.Trace("Failed to decode `NewPooledTransactionHashesPacket`", "peer", peer.String(), "err", err) newPooledTxHashesFailMeter.Mark(1) return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) } // Schedule all the unknown hashes for retrieval - log.Debug("handleNewPooledTransactionHashes", "peer", peer.String(), "len(ann)", len(*ann)) + log.Trace("handleNewPooledTransactionHashes", "peer", peer.String(), "len(ann)", len(*ann)) newPooledTxHashesLenGauge.Update(int64(len(*ann))) for _, hash := range *ann { peer.markTransaction(hash) @@ -379,12 +380,15 @@ func handleGetPooledTransactions66(backend Backend, msg Decoder, peer *Peer) err // Decode the pooled transactions retrieval message var query GetPooledTransactionsPacket66 if err := msg.Decode(&query); err != nil { - log.Debug("Failed to decode `GetPooledTransactionsPacket66`", "peer", peer.String(), "err", err) + log.Trace("Failed to decode `GetPooledTransactionsPacket66`", "peer", peer.String(), "err", err) getPooledTxsFailMeter.Mark(1) return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) } hashes, txs := answerGetPooledTransactions(backend, query.GetPooledTransactionsPacket, peer) - log.Debug("handleGetPooledTransactions", "peer", peer.String(), "RequestId", query.RequestId, "len(query)", len(query.GetPooledTransactionsPacket), "retrieved", len(hashes)) + log.Trace("handleGetPooledTransactions", "peer", peer.String(), "RequestId", query.RequestId, "len(query)", len(query.GetPooledTransactionsPacket), "retrieved", len(hashes)) + for _, hash := range hashes { + log.Debug("Received new pooled transaction", "hash", hash.Hex(), "peer", peer.String()) + } getPooledTxsQueryLenGauge.Update(int64(len(query.GetPooledTransactionsPacket))) getPooledTxsRetrievedLenGauge.Update(int64(len(hashes))) return peer.ReplyPooledTransactionsRLP(query.RequestId, hashes, txs) @@ -427,16 +431,16 @@ func handleTransactions(backend Backend, msg Decoder, peer *Peer) error { var txs TransactionsPacket if err := msg.Decode(&txs); err != nil { handleTxsFailMeter.Mark(1) - log.Debug("Failed to decode `TransactionsPacket`", "peer", peer.String(), "err", err) + log.Trace("Failed to decode `TransactionsPacket`", "peer", peer.String(), "err", err) return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) } - log.Debug("handleTransactions", "peer", peer.String(), "len(txs)", len(txs)) + log.Trace("handleTransactions", "peer", peer.String(), "len(txs)", len(txs)) handleTxsLenGauge.Update(int64(len(txs))) for i, tx := range txs { // Validate and mark the remote transaction if tx == nil { handleTxsNilMeter.Mark(1) - log.Debug("handleTransactions: transaction is nil", "peer", peer.String(), "i", i) + log.Trace("handleTransactions: transaction is nil", "peer", peer.String(), "i", i) return fmt.Errorf("%w: transaction %d is nil", errDecode, i) } peer.markTransaction(tx.Hash()) @@ -453,16 +457,16 @@ func handlePooledTransactions66(backend Backend, msg Decoder, peer *Peer) error var txs PooledTransactionsPacket66 if err := msg.Decode(&txs); err != nil { pooledTxs66FailMeter.Mark(1) - log.Debug("Failed to decode `PooledTransactionsPacket66`", "peer", peer.String(), "err", err) + log.Trace("Failed to decode `PooledTransactionsPacket66`", "peer", peer.String(), "err", err) return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) } - log.Debug("handlePooledTransactions66", "peer", peer.String(), "len(txs)", len(txs.PooledTransactionsPacket)) + log.Trace("handlePooledTransactions66", "peer", peer.String(), "len(txs)", len(txs.PooledTransactionsPacket)) pooledTxs66LenGauge.Update(int64(len(txs.PooledTransactionsPacket))) for i, tx := range txs.PooledTransactionsPacket { // Validate and mark the remote transaction if tx == nil { pooledTxs66NilMeter.Mark(1) - log.Debug("handlePooledTransactions: transaction is nil", "peer", peer.String(), "i", i) + log.Trace("handlePooledTransactions: transaction is nil", "peer", peer.String(), "i", i) return fmt.Errorf("%w: transaction %d is nil", errDecode, i) } peer.markTransaction(tx.Hash()) diff --git a/eth/protocols/eth/peer.go b/eth/protocols/eth/peer.go index d7ab789227..838232051c 100644 --- a/eth/protocols/eth/peer.go +++ b/eth/protocols/eth/peer.go @@ -436,7 +436,7 @@ func (p *Peer) RequestTxs(hashes []common.Hash) error { p.Log().Debug("Fetching batch of transactions", "count", len(hashes)) id := rand.Uint64() - log.Debug("Requesting transactions", "RequestId", id, "Peer.id", p.id, "count", len(hashes)) + log.Trace("Requesting transactions", "RequestId", id, "Peer.id", p.id, "count", len(hashes)) peerRequestTxsCntGauge.Update(int64(len(hashes))) requestTracker.Track(p.id, p.version, GetPooledTransactionsMsg, PooledTransactionsMsg, id) diff --git a/params/version.go b/params/version.go index dfae47a034..7d8730ddd8 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 5 // Major version component of the current release VersionMinor = 8 // Minor version component of the current release - VersionPatch = 47 // Patch version component of the current release + VersionPatch = 48 // Patch version component of the current release VersionMeta = "mainnet" // Version metadata to append to the version string ) From 141a8df143f0b32c1a7fa8c353ec213f9eec9795 Mon Sep 17 00:00:00 2001 From: Jonas Theis <4181434+jonastheis@users.noreply.github.com> Date: Wed, 21 May 2025 16:36:14 +0800 Subject: [PATCH 39/45] fix(rollup verifier): nil pointer due to missing `CommittedBatchMeta` (#1188) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix nil pointer due to CommittedBatchMeta not being found in the DB and add rewind of L1 sync height to recover from missing CommittedBatchMeta * chore: auto version bump [bot] * formatting * increase rewindL1height to 100 * make sure no underflow --- params/version.go | 2 +- .../rollup_sync_service.go | 35 +++++++++++++++++-- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/params/version.go b/params/version.go index 7d8730ddd8..de0b4211f6 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 5 // Major version component of the current release VersionMinor = 8 // Minor version component of the current release - VersionPatch = 48 // Patch version component of the current release + VersionPatch = 49 // Patch version component of the current release VersionMeta = "mainnet" // Version metadata to append to the version string ) diff --git a/rollup/rollup_sync_service/rollup_sync_service.go b/rollup/rollup_sync_service/rollup_sync_service.go index 844147e28c..8aba65a1d4 100644 --- a/rollup/rollup_sync_service/rollup_sync_service.go +++ b/rollup/rollup_sync_service/rollup_sync_service.go @@ -42,11 +42,15 @@ const ( // defaultLogInterval is the frequency at which we print the latest processed block. defaultLogInterval = 5 * time.Minute + + // rewindL1Height is the number of blocks to rewind the L1 sync height when a missing batch event is detected. + rewindL1Height = 100 ) var ( finalizedBlockGauge = metrics.NewRegisteredGauge("chain/head/finalized", nil) ErrShouldResetSyncHeight = errors.New("ErrShouldResetSyncHeight") + ErrMissingBatchEvent = errors.New("ErrMissingBatchEvent") ) // RollupSyncService collects ScrollChain batch commit/revert/finalize events and stores metadata into db. @@ -223,6 +227,22 @@ func (s *RollupSyncService) fetchRollupEvents() error { return nil } + if errors.Is(err, ErrMissingBatchEvent) { + // make sure no underflow + var rewindTo uint64 + if prevL1Height > rewindL1Height { + rewindTo = prevL1Height - rewindL1Height + } + + // If there's a missing batch event, rewind the L1 sync height by some blocks to re-fetch from L1 RPC and + // replay creating corresponding CommittedBatchMeta in local DB. + // This happens recursively until the missing event has been recovered as we will call fetchRollupEvents again + // with the `L1Height = prevL1Height - rewindL1Height`. + s.callDataBlobSource.SetL1Height(rewindTo) + + return fmt.Errorf("missing batch event, rewinding L1 sync height by %d blocks to %d: %w", rewindL1Height, rewindTo, err) + } + // Reset the L1 height to the previous value to retry fetching the same data. s.callDataBlobSource.SetL1Height(prevL1Height) return fmt.Errorf("failed to parse and update rollup event logs: %w", err) @@ -297,12 +317,18 @@ func (s *RollupSyncService) updateRollupEvents(daEntries da.Entries) error { var err error if index > 0 { if parentCommittedBatchMeta, err = rawdb.ReadCommittedBatchMeta(s.db, index-1); err != nil { - return fmt.Errorf("failed to read parent committed batch meta, batch index: %v, err: %w", index-1, err) + return fmt.Errorf("failed to read parent committed batch meta, batch index: %v, err: %w", index-1, errors.Join(ErrMissingBatchEvent, err)) + } + if parentCommittedBatchMeta == nil { + return fmt.Errorf("parent committed batch meta = nil, batch index: %v, err: %w", index-1, ErrMissingBatchEvent) } } committedBatchMeta, err := rawdb.ReadCommittedBatchMeta(s.db, index) if err != nil { - return fmt.Errorf("failed to read committed batch meta, batch index: %v, err: %w", index, err) + return fmt.Errorf("failed to read committed batch meta, batch index: %v, err: %w", index, errors.Join(ErrMissingBatchEvent, err)) + } + if committedBatchMeta == nil { + return fmt.Errorf("committed batch meta = nil, batch index: %v, err: %w", index, ErrMissingBatchEvent) } chunks, err := s.getLocalChunksForBatch(committedBatchMeta.ChunkBlockRanges) @@ -447,7 +473,10 @@ func (s *RollupSyncService) getCommittedBatchMeta(commitedBatch da.EntryWithBloc if commitedBatch.Version() == encoding.CodecV7 { parentCommittedBatchMeta, err := rawdb.ReadCommittedBatchMeta(s.db, commitedBatch.BatchIndex()-1) if err != nil { - return nil, fmt.Errorf("failed to read parent committed batch meta, batch index: %v, err: %w", commitedBatch.BatchIndex()-1, err) + return nil, fmt.Errorf("failed to read parent committed batch meta, batch index: %v, err: %w", commitedBatch.BatchIndex()-1, errors.Join(ErrMissingBatchEvent, err)) + } + if parentCommittedBatchMeta == nil { + return nil, fmt.Errorf("parent committed batch meta = nil, batch index: %v, err: %w", commitedBatch.BatchIndex()-1, ErrMissingBatchEvent) } // If parent batch has a lower version this means this is the first batch of CodecV7. From d8f4932bf202f5d7f405c1f82bd1de72d87dc84a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Wed, 28 May 2025 11:19:33 +0200 Subject: [PATCH 40/45] fix: configure default timeout for blob clients (#1191) * feat: configure default timeout for blob clients * fix typo --- params/version.go | 2 +- .../da_syncer/blob_client/beacon_node_client.go | 15 ++++++++++++--- rollup/da_syncer/blob_client/blob_scan_client.go | 7 ++++++- .../da_syncer/blob_client/block_native_client.go | 9 ++++++++- 4 files changed, 27 insertions(+), 6 deletions(-) diff --git a/params/version.go b/params/version.go index de0b4211f6..12c6206f54 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 5 // Major version component of the current release VersionMinor = 8 // Minor version component of the current release - VersionPatch = 49 // Patch version component of the current release + VersionPatch = 50 // Patch version component of the current release VersionMeta = "mainnet" // Version metadata to append to the version string ) diff --git a/rollup/da_syncer/blob_client/beacon_node_client.go b/rollup/da_syncer/blob_client/beacon_node_client.go index adb61a4199..f7129af76d 100644 --- a/rollup/da_syncer/blob_client/beacon_node_client.go +++ b/rollup/da_syncer/blob_client/beacon_node_client.go @@ -9,12 +9,18 @@ import ( "net/http" "net/url" "strconv" + "time" "github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/crypto/kzg4844" ) +const ( + BeaconNodeDefaultTimeout = 15 * time.Second +) + type BeaconNodeClient struct { + client *http.Client apiEndpoint string genesisTime uint64 secondsPerSlot uint64 @@ -27,12 +33,14 @@ var ( ) func NewBeaconNodeClient(apiEndpoint string) (*BeaconNodeClient, error) { + client := &http.Client{Timeout: BeaconNodeDefaultTimeout} + // get genesis time genesisPath, err := url.JoinPath(apiEndpoint, beaconNodeGenesisEndpoint) if err != nil { return nil, fmt.Errorf("failed to join path, err: %w", err) } - resp, err := http.Get(genesisPath) + resp, err := client.Get(genesisPath) if err != nil { return nil, fmt.Errorf("cannot do request, err: %w", err) } @@ -62,7 +70,7 @@ func NewBeaconNodeClient(apiEndpoint string) (*BeaconNodeClient, error) { if err != nil { return nil, fmt.Errorf("failed to join path, err: %w", err) } - resp, err = http.Get(specPath) + resp, err = client.Get(specPath) if err != nil { return nil, fmt.Errorf("cannot do request, err: %w", err) } @@ -91,6 +99,7 @@ func NewBeaconNodeClient(apiEndpoint string) (*BeaconNodeClient, error) { } return &BeaconNodeClient{ + client: client, apiEndpoint: apiEndpoint, genesisTime: genesisTime, secondsPerSlot: secondsPerSlot, @@ -105,7 +114,7 @@ func (c *BeaconNodeClient) GetBlobByVersionedHashAndBlockTime(ctx context.Contex if err != nil { return nil, fmt.Errorf("failed to join path, err: %w", err) } - resp, err := http.Get(blobSidecarPath) + resp, err := c.client.Get(blobSidecarPath) if err != nil { return nil, fmt.Errorf("cannot do request, err: %w", err) } diff --git a/rollup/da_syncer/blob_client/blob_scan_client.go b/rollup/da_syncer/blob_client/blob_scan_client.go index 0185cc9dc9..75fe4fdfbd 100644 --- a/rollup/da_syncer/blob_client/blob_scan_client.go +++ b/rollup/da_syncer/blob_client/blob_scan_client.go @@ -8,12 +8,17 @@ import ( "fmt" "net/http" "net/url" + "time" "github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/common/hexutil" "github.com/scroll-tech/go-ethereum/crypto/kzg4844" ) +const ( + BlobScanDefaultTimeout = 15 * time.Second +) + type BlobScanClient struct { client *http.Client apiEndpoint string @@ -21,7 +26,7 @@ type BlobScanClient struct { func NewBlobScanClient(apiEndpoint string) *BlobScanClient { return &BlobScanClient{ - client: http.DefaultClient, + client: &http.Client{Timeout: BlobScanDefaultTimeout}, apiEndpoint: apiEndpoint, } } diff --git a/rollup/da_syncer/blob_client/block_native_client.go b/rollup/da_syncer/blob_client/block_native_client.go index 1fe6efbbab..d96f54a3cf 100644 --- a/rollup/da_syncer/blob_client/block_native_client.go +++ b/rollup/da_syncer/blob_client/block_native_client.go @@ -8,18 +8,25 @@ import ( "fmt" "net/http" "net/url" + "time" "github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/common/hexutil" "github.com/scroll-tech/go-ethereum/crypto/kzg4844" ) +const ( + BlockNativeDefaultTimeout = 15 * time.Second +) + type BlockNativeClient struct { + client *http.Client apiEndpoint string } func NewBlockNativeClient(apiEndpoint string) *BlockNativeClient { return &BlockNativeClient{ + client: &http.Client{Timeout: BlockNativeDefaultTimeout}, apiEndpoint: apiEndpoint, } } @@ -34,7 +41,7 @@ func (c *BlockNativeClient) GetBlobByVersionedHashAndBlockTime(ctx context.Conte if err != nil { return nil, fmt.Errorf("cannot create request, err: %w", err) } - resp, err := http.DefaultClient.Do(req) + resp, err := c.client.Do(req) if err != nil { return nil, fmt.Errorf("cannot do request, err: %w", err) } From cdd77c165907e6c5e26fe0b59b5fcf2e8a5c5384 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Wed, 28 May 2025 14:51:00 +0200 Subject: [PATCH 41/45] feat: update base fee via contract (#1189) * Revert "feat: update base fee via cli (#1183)" This reverts commit 0b6f8f188949c3e40a9941871fda9adb993ebc62. * feat: update l2 base fee via system contract * nit * bump version * fix tests * update contract abi * init coefficients on startup * nil check * update test * update txpool gas price * init with default value * update scroll sepolia and mainnet contract addresses * nit * update txpool min price during startup * print stack trace for worker panic * fix event parsing * fix log * improve txpool update logic * move initializeL2BaseFeeCoefficients to misc package * bump version * add API to query L2 base fee coefficients via console * fix code review suggestion --- cmd/geth/main.go | 8 +- cmd/geth/usage.go | 2 - cmd/utils/flags.go | 37 -------- consensus/misc/eip1559.go | 128 ++++++++++++++++++++++++---- consensus/misc/eip1559_test.go | 14 ++- core/blockchain.go | 43 +++++++++- core/state_processor_test.go | 4 +- core/tx_pool.go | 9 ++ eth/api.go | 2 + eth/backend.go | 27 +++--- eth/ethconfig/config.go | 4 - internal/web3ext/web3ext.go | 4 + miner/scroll_worker.go | 2 + params/config.go | 34 ++++---- params/version.go | 2 +- rollup/l2_system_config/abi.go | 54 ++++++++++++ rollup/l2_system_config/abi_test.go | 51 +++++++++++ rollup/rcfg/config.go | 17 ++++ 18 files changed, 338 insertions(+), 104 deletions(-) create mode 100644 rollup/l2_system_config/abi.go create mode 100644 rollup/l2_system_config/abi_test.go diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 0377609ceb..c1fbb08746 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -29,6 +29,7 @@ import ( "github.com/scroll-tech/go-ethereum/accounts/keystore" "github.com/scroll-tech/go-ethereum/cmd/utils" "github.com/scroll-tech/go-ethereum/common" + "github.com/scroll-tech/go-ethereum/consensus/misc" "github.com/scroll-tech/go-ethereum/console/prompt" "github.com/scroll-tech/go-ethereum/eth" "github.com/scroll-tech/go-ethereum/eth/downloader" @@ -185,8 +186,6 @@ var ( utils.DARecoverySignBlocksFlag, utils.DARecoveryL2EndBlockFlag, utils.DARecoveryProduceBlocksFlag, - utils.L2BaseFeeScalarFlag, - utils.L2BaseFeeOverheadFlag, } rpcFlags = []cli.Flag{ @@ -455,8 +454,9 @@ func startNode(ctx *cli.Context, stack *node.Node, backend ethapi.Backend) { utils.Fatalf("Ethereum service not running") } // Set the gas price to the limits from the CLI and start mining - gasprice := utils.GlobalBig(ctx, utils.MinerGasPriceFlag.Name) - ethBackend.TxPool().SetGasPrice(gasprice) + // gasprice := utils.GlobalBig(ctx, utils.MinerGasPriceFlag.Name) + // ethBackend.TxPool().SetGasPrice(gasprice) + ethBackend.TxPool().SetGasPrice(misc.MinBaseFee()) // override configured min gas price ethBackend.TxPool().SetIsMiner(true) // start mining threads := ctx.GlobalInt(utils.MinerThreadsFlag.Name) diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index df99d6eed0..1d299d339f 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -236,8 +236,6 @@ var AppHelpFlagGroups = []flags.FlagGroup{ utils.L1DeploymentBlockFlag, utils.L1DisableMessageQueueV2Flag, utils.RollupVerifyEnabledFlag, - utils.L2BaseFeeScalarFlag, - utils.L2BaseFeeOverheadFlag, utils.DASyncEnabledFlag, utils.DABlobScanAPIEndpointFlag, utils.DABlockNativeAPIEndpointFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 10c8a60e53..7371a37700 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -47,7 +47,6 @@ import ( "github.com/scroll-tech/go-ethereum/consensus" "github.com/scroll-tech/go-ethereum/consensus/clique" "github.com/scroll-tech/go-ethereum/consensus/ethash" - "github.com/scroll-tech/go-ethereum/consensus/misc" "github.com/scroll-tech/go-ethereum/core" "github.com/scroll-tech/go-ethereum/core/rawdb" "github.com/scroll-tech/go-ethereum/core/vm" @@ -935,18 +934,6 @@ var ( Name: "da.recovery.produceblocks", Usage: "Produce unsigned blocks after L1 recovery for permissionless batch submission", } - - // L2 base fee settings - L2BaseFeeScalarFlag = BigFlag{ - Name: "basefee.scalar", - Usage: "Scalar used in the l2 base fee formula. Signer nodes will use this for computing the next block's base fee. Follower nodes will use this in RPC.", - Value: misc.DefaultBaseFeeScalar, - } - L2BaseFeeOverheadFlag = BigFlag{ - Name: "basefee.overhead", - Usage: "Overhead used in the l2 base fee formula. Signer nodes will use this for computing the next block's base fee. Follower nodes will use this in RPC.", - Value: misc.DefaultBaseFeeOverhead, - } ) // MakeDataDir retrieves the currently requested data directory, terminating @@ -1722,29 +1709,6 @@ func setDA(ctx *cli.Context, cfg *ethconfig.Config) { } } -func setBaseFee(ctx *cli.Context, cfg *ethconfig.Config) { - cfg.BaseFeeScalar = misc.DefaultBaseFeeScalar - if ctx.GlobalIsSet(L2BaseFeeScalarFlag.Name) { - cfg.BaseFeeScalar = GlobalBig(ctx, L2BaseFeeScalarFlag.Name) - } - cfg.BaseFeeOverhead = misc.DefaultBaseFeeOverhead - if ctx.GlobalIsSet(L2BaseFeeOverheadFlag.Name) { - cfg.BaseFeeOverhead = GlobalBig(ctx, L2BaseFeeOverheadFlag.Name) - } - - log.Info("L2 base fee coefficients", "scalar", cfg.BaseFeeScalar, "overhead", cfg.BaseFeeOverhead) - - var minBaseFee uint64 - if fee := misc.MinBaseFee(cfg.BaseFeeScalar, cfg.BaseFeeOverhead); fee.IsUint64() { - minBaseFee = fee.Uint64() - } - - if cfg.TxPool.PriceLimit < minBaseFee { - log.Warn("Updating txpool price limit to min L2 base fee", "provided", cfg.TxPool.PriceLimit, "updated", minBaseFee) - cfg.TxPool.PriceLimit = minBaseFee - } -} - func setMaxBlockRange(ctx *cli.Context, cfg *ethconfig.Config) { if ctx.GlobalIsSet(MaxBlockRangeFlag.Name) { cfg.MaxBlockRange = ctx.GlobalInt64(MaxBlockRangeFlag.Name) @@ -1821,7 +1785,6 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { setCircuitCapacityCheck(ctx, cfg) setEnableRollupVerify(ctx, cfg) setDA(ctx, cfg) - setBaseFee(ctx, cfg) setMaxBlockRange(ctx, cfg) if ctx.GlobalIsSet(ShadowforkPeersFlag.Name) { cfg.ShadowForkPeerIDs = ctx.GlobalStringSlice(ShadowforkPeersFlag.Name) diff --git a/consensus/misc/eip1559.go b/consensus/misc/eip1559.go index b6443e76b0..9990df04a7 100644 --- a/consensus/misc/eip1559.go +++ b/consensus/misc/eip1559.go @@ -19,25 +19,67 @@ package misc import ( "fmt" "math/big" + "sync" + "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/params" + "github.com/scroll-tech/go-ethereum/rollup/rcfg" + "github.com/scroll-tech/go-ethereum/rpc" ) -// Protocol-enforced maximum L2 base fee. -// We would only go above this if L1 base fee hits 2931 Gwei. -const MaximumL2BaseFee = 10000000000 +const ( + // Protocol-enforced maximum L2 base fee. + // We would only go above this if L1 base fee hits 2931 Gwei. + MaximumL2BaseFee = 10000000000 + + // L2 base fee fallback values, in case the L2 system contract + // is not deployed on not configured yet. + DefaultBaseFeeOverhead = 15680000 + DefaultBaseFeeScalar = 34000000000000 +) // L2 base fee formula constants and defaults. // l2BaseFee = (l1BaseFee * scalar) / PRECISION + overhead. // `scalar` accounts for finalization costs. `overhead` accounts for sequencing and proving costs. -// we use 1e18 for precision to match the contract implementation. var ( - BaseFeePrecision = new(big.Int).SetUint64(1e18) - DefaultBaseFeeScalar = new(big.Int).SetUint64(34000000000000) - DefaultBaseFeeOverhead = new(big.Int).SetUint64(15680000) + // We use 1e18 for precision to match the contract implementation. + BaseFeePrecision = new(big.Int).SetUint64(1e18) + + // scalar and overhead are updated automatically in `Blockchain.writeBlockWithState`. + baseFeeScalar = big.NewInt(0) + baseFeeOverhead = big.NewInt(0) + + lock sync.RWMutex ) +func ReadL2BaseFeeCoefficients() (scalar *big.Int, overhead *big.Int) { + lock.RLock() + defer lock.RUnlock() + return new(big.Int).Set(baseFeeScalar), new(big.Int).Set(baseFeeOverhead) +} + +func UpdateL2BaseFeeOverhead(newOverhead *big.Int) { + if newOverhead == nil { + log.Error("Failed to set L2 base fee overhead, new value is ") + return + } + lock.Lock() + defer lock.Unlock() + baseFeeOverhead.Set(newOverhead) +} + +func UpdateL2BaseFeeScalar(newScalar *big.Int) { + if newScalar == nil { + log.Error("Failed to set L2 base fee scalar, new value is ") + return + } + lock.Lock() + defer lock.Unlock() + baseFeeScalar.Set(newScalar) +} + // VerifyEip1559Header verifies some header attributes which were changed in EIP-1559, // - gas limit check // - basefee check @@ -65,20 +107,13 @@ func CalcBaseFee(config *params.ChainConfig, parent *types.Header, parentL1BaseF return big.NewInt(10000000) // 0.01 Gwei } - scalar := config.Scroll.BaseFeeScalar - if scalar == nil { - scalar = DefaultBaseFeeScalar - } - overhead := config.Scroll.BaseFeeOverhead - if overhead == nil { - overhead = DefaultBaseFeeOverhead - } - + scalar, overhead := ReadL2BaseFeeCoefficients() return calcBaseFee(scalar, overhead, parentL1BaseFee) } -// MinBaseFee calculates the minimum L2 base fee based on the configured coefficients. -func MinBaseFee(scalar, overhead *big.Int) *big.Int { +// MinBaseFee calculates the minimum L2 base fee based on the current coefficients. +func MinBaseFee() *big.Int { + scalar, overhead := ReadL2BaseFeeCoefficients() return calcBaseFee(scalar, overhead, big.NewInt(0)) } @@ -94,3 +129,60 @@ func calcBaseFee(scalar, overhead, parentL1BaseFee *big.Int) *big.Int { return baseFee } + +type State interface { + GetState(addr common.Address, hash common.Hash) common.Hash +} + +func InitializeL2BaseFeeCoefficients(chainConfig *params.ChainConfig, state State) error { + overhead := common.Big0 + scalar := common.Big0 + + if l2SystemConfig := chainConfig.Scroll.L2SystemConfigAddress(); l2SystemConfig != (common.Address{}) { + overhead = state.GetState(l2SystemConfig, rcfg.L2BaseFeeOverheadSlot).Big() + scalar = state.GetState(l2SystemConfig, rcfg.L2BaseFeeScalarSlot).Big() + } else { + log.Warn("L2SystemConfig address is not configured") + } + + // fallback to default if contract is not deployed or configured yet + if overhead.Cmp(common.Big0) == 0 { + overhead = big.NewInt(DefaultBaseFeeOverhead) + } + if scalar.Cmp(common.Big0) == 0 { + scalar = big.NewInt(DefaultBaseFeeScalar) + } + + // update local view of coefficients + lock.Lock() + defer lock.Unlock() + baseFeeOverhead.Set(overhead) + baseFeeScalar.Set(scalar) + log.Info("Initialized L2 base fee coefficients", "overhead", overhead, "scalar", scalar) + return nil +} + +type API struct{} + +type L2BaseFeeConfig struct { + Scalar *big.Int `json:"scalar,omitempty"` + Overhead *big.Int `json:"overhead,omitempty"` +} + +func (api *API) GetL2BaseFeeConfig() *L2BaseFeeConfig { + scalar, overhead := ReadL2BaseFeeCoefficients() + + return &L2BaseFeeConfig{ + Scalar: scalar, + Overhead: overhead, + } +} + +func APIs() []rpc.API { + return []rpc.API{{ + Namespace: "scroll", + Version: "1.0", + Service: &API{}, + Public: false, + }} +} diff --git a/consensus/misc/eip1559_test.go b/consensus/misc/eip1559_test.go index bab8c4e496..d41d3e5966 100644 --- a/consensus/misc/eip1559_test.go +++ b/consensus/misc/eip1559_test.go @@ -122,8 +122,8 @@ func TestCalcBaseFee(t *testing.T) { } for i, test := range tests { config := config() - config.Scroll.BaseFeeScalar = big.NewInt(10000000) - config.Scroll.BaseFeeOverhead = big.NewInt(1) + UpdateL2BaseFeeScalar(big.NewInt(10000000)) + UpdateL2BaseFeeOverhead(big.NewInt(1)) if have, want := CalcBaseFee(config, nil, big.NewInt(test.parentL1BaseFee)), big.NewInt(test.expectedL2BaseFee); have.Cmp(want) != 0 { t.Errorf("test %d: have %d want %d, ", i, have, want) } @@ -142,6 +142,8 @@ func TestCalcBaseFee(t *testing.T) { {644149677419355, 10000000000}, // cap at max L2 base fee } for i, test := range testsWithDefaults { + UpdateL2BaseFeeScalar(big.NewInt(34000000000000)) + UpdateL2BaseFeeOverhead(big.NewInt(15680000)) if have, want := CalcBaseFee(config(), nil, big.NewInt(test.parentL1BaseFee)), big.NewInt(test.expectedL2BaseFee); have.Cmp(want) != 0 { t.Errorf("test %d: have %d want %d, ", i, have, want) } @@ -150,11 +152,15 @@ func TestCalcBaseFee(t *testing.T) { // TestMinBaseFee assumes all blocks are 1559-blocks func TestMinBaseFee(t *testing.T) { - if have, want := MinBaseFee(DefaultBaseFeeScalar, DefaultBaseFeeOverhead), big.NewInt(15680000); have.Cmp(want) != 0 { + UpdateL2BaseFeeScalar(big.NewInt(34000000000000)) + UpdateL2BaseFeeOverhead(big.NewInt(15680000)) + if have, want := MinBaseFee(), big.NewInt(15680000); have.Cmp(want) != 0 { t.Errorf("have %d want %d, ", have, want) } - if have, want := MinBaseFee(big.NewInt(10000000), big.NewInt(1)), big.NewInt(1); have.Cmp(want) != 0 { + UpdateL2BaseFeeScalar(big.NewInt(10000000)) + UpdateL2BaseFeeOverhead(big.NewInt(1)) + if have, want := MinBaseFee(), big.NewInt(1); have.Cmp(want) != 0 { t.Errorf("have %d want %d, ", have, want) } } diff --git a/core/blockchain.go b/core/blockchain.go index 821564769e..57a803198f 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -34,6 +34,7 @@ import ( "github.com/scroll-tech/go-ethereum/common/mclock" "github.com/scroll-tech/go-ethereum/common/prque" "github.com/scroll-tech/go-ethereum/consensus" + "github.com/scroll-tech/go-ethereum/consensus/misc" "github.com/scroll-tech/go-ethereum/core/rawdb" "github.com/scroll-tech/go-ethereum/core/state" "github.com/scroll-tech/go-ethereum/core/state/snapshot" @@ -45,6 +46,7 @@ import ( "github.com/scroll-tech/go-ethereum/log" "github.com/scroll-tech/go-ethereum/metrics" "github.com/scroll-tech/go-ethereum/params" + "github.com/scroll-tech/go-ethereum/rollup/l2_system_config" "github.com/scroll-tech/go-ethereum/trie" ) @@ -55,7 +57,8 @@ var ( headTimeGapGauge = metrics.NewRegisteredGauge("chain/head/timegap", nil) headL1MessageGauge = metrics.NewRegisteredGauge("chain/head/l1msg", nil) - l2BaseFeeGauge = metrics.NewRegisteredGauge("chain/fees/l2basefee", nil) + l2BaseFeeGauge = metrics.NewRegisteredGauge("chain/fees/l2basefee", nil) + l2BaseFeeUpdateTimer = metrics.NewRegisteredTimer("chain/fees/updates", nil) accountReadTimer = metrics.NewRegisteredTimer("chain/account/reads", nil) accountHashTimer = metrics.NewRegisteredTimer("chain/account/hashes", nil) @@ -161,6 +164,39 @@ func updateHeadL1msgGauge(block *types.Block) { } } +// updateL2BaseFeeCoefficients updates the global L2 base fee coefficients. +// Coefficient updates are written into L2 state and emit an event. +// We could use either here; we read from the event to avoid state reads. +// In the future, if the base fee setting becomes part of block validation, +// reading from state will be more appropriate. +func updateL2BaseFeeCoefficients(l2SystemConfigAddress common.Address, logs []*types.Log) { + defer func(start time.Time) { l2BaseFeeUpdateTimer.Update(time.Since(start)) }(time.Now()) + + for _, l := range logs { + if l.Address != l2SystemConfigAddress { + continue + } + switch l.Topics[0] { + case l2_system_config.BaseFeeOverheadUpdatedTopic: + event, err := l2_system_config.UnpackBaseFeeOverheadUpdatedEvent(*l) + if err != nil { + log.Error("failed to unpack base fee overhead updated event log", "err", err, "log", *l) + break // break from switch, continue loop + } + misc.UpdateL2BaseFeeOverhead(event.NewBaseFeeOverhead) + log.Info("Updated L2 base fee overhead", "blockNumber", l.BlockNumber, "blockHash", l.BlockHash.Hex(), "old", event.OldBaseFeeOverhead, "new", event.NewBaseFeeOverhead) + case l2_system_config.BaseFeeScalarUpdatedTopic: + event, err := l2_system_config.UnpackBaseFeeScalarUpdatedEvent(*l) + if err != nil { + log.Error("failed to unpack base fee scalar updated event log", "err", err, "log", *l) + break // break from switch, continue loop + } + misc.UpdateL2BaseFeeScalar(event.NewBaseFeeScalar) + log.Info("Updated L2 base fee scalar", "blockNumber", l.BlockNumber, "blockHash", l.BlockHash.Hex(), "old", event.OldBaseFeeScalar, "new", event.NewBaseFeeScalar) + } + } +} + // BlockChain represents the canonical chain given a database with a genesis // block. The Blockchain manages chain imports, reverts, chain reorganisations. // @@ -1272,6 +1308,9 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. // Note the latest relayed L1 message queue index (if any) updateHeadL1msgGauge(block) + // Execute L2 base fee coefficient updates (if any) + updateL2BaseFeeCoefficients(bc.Config().Scroll.L2SystemConfigAddress(), logs) + parent := bc.GetHeaderByHash(block.ParentHash()) // block.Time is guaranteed to be larger than parent.Time, // and the time gap should fit into int64. @@ -1302,7 +1341,7 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. if queueIndex == nil { // We expect that we only insert contiguous chain segments, // so the parent will always be inserted first. - log.Crit("Queue index in DB is nil", "parent", block.ParentHash(), "hash", block.Hash()) + log.Crit("Queue index in DB is nil", "parent", block.ParentHash().Hex(), "hash", block.Hash().Hex()) } numProcessed := uint64(block.NumL1MessagesProcessed(*queueIndex)) // do not overwrite the index written by the miner worker diff --git a/core/state_processor_test.go b/core/state_processor_test.go index ac5babcac3..a37e90e93c 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -389,13 +389,13 @@ func TestStateProcessorErrors(t *testing.T) { txs: []*types.Transaction{ mkDynamicCreationTx(0, 500000, common.Big0, misc.CalcBaseFee(config, genesis.Header(), parentL1BaseFee), tooBigInitCode[:]), }, - want: "could not apply tx 0 [0x9fff9d187a68f9dce9664475ed9a01a5178992f15b44ce88ee7b1129a183e6af]: max initcode size exceeded: code size 49153 limit 49152", + want: "could not apply tx 0 [0x7b33776d375660694a23ef992c090265682f3687607e0099b14503fdb65d73e3]: max initcode size exceeded: code size 49153 limit 49152", }, { // ErrIntrinsicGas: Not enough gas to cover init code txs: []*types.Transaction{ mkDynamicCreationTx(0, 54299, common.Big0, misc.CalcBaseFee(config, genesis.Header(), parentL1BaseFee), smallInitCode[:]), }, - want: "could not apply tx 0 [0x272eefb0eeb3b973e933ae5dba17e7ecf6bfded5ce358f2a78426153c247f677]: intrinsic gas too low: have 54299, want 54300", + want: "could not apply tx 0 [0x98e54c5ecfa7986a66480d65ba32f2c6a2a6aedc3a67abb91b1e118b0717ed2d]: intrinsic gas too low: have 54299, want 54300", }, } { block := GenerateBadBlock(genesis, ethash.NewFaker(), tt.txs, gspec.Config) diff --git a/core/tx_pool.go b/core/tx_pool.go index 9a8c011c2a..67f3ee1012 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -424,6 +424,8 @@ func (pool *TxPool) loop() { journal = time.NewTicker(pool.config.Rejournal) // Track the previous head headers for transaction reorgs head = pool.chain.CurrentBlock() + // Track the min L2 base fee + minBaseFee = big.NewInt(0) ) defer report.Stop() defer evict.Stop() @@ -443,6 +445,13 @@ func (pool *TxPool) loop() { } } + newMinBaseFee := misc.MinBaseFee() + if minBaseFee.Cmp(newMinBaseFee) != 0 { + log.Debug("Updating min base fee in txpool", "old", minBaseFee, "new", newMinBaseFee) + minBaseFee = newMinBaseFee + pool.SetGasPrice(minBaseFee) + } + // System shutdown. case <-pool.chainHeadSub.Err(): close(pool.reorgShutdownCh) diff --git a/eth/api.go b/eth/api.go index e8b66b9e4e..0752a3cb15 100644 --- a/eth/api.go +++ b/eth/api.go @@ -133,6 +133,8 @@ func (api *PrivateMinerAPI) SetGasPrice(gasPrice hexutil.Big) bool { api.e.gasPrice = (*big.Int)(&gasPrice) api.e.lock.Unlock() + // This will override the min base fee configuration. + // That is fine, it only happens if we explicitly set gas price via the console. api.e.txPool.SetGasPrice((*big.Int)(&gasPrice)) return true } diff --git a/eth/backend.go b/eth/backend.go index 99d35828d2..ad765b835b 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -32,6 +32,7 @@ import ( "github.com/scroll-tech/go-ethereum/common/hexutil" "github.com/scroll-tech/go-ethereum/consensus" "github.com/scroll-tech/go-ethereum/consensus/clique" + "github.com/scroll-tech/go-ethereum/consensus/misc" "github.com/scroll-tech/go-ethereum/consensus/system_contract" "github.com/scroll-tech/go-ethereum/consensus/wrapper" "github.com/scroll-tech/go-ethereum/core" @@ -148,13 +149,6 @@ func New(stack *node.Node, config *ethconfig.Config, l1Client l1.Client) (*Ether if _, ok := genesisErr.(*params.ConfigCompatError); genesisErr != nil && !ok { return nil, genesisErr } - - // Hacky workaround: - // It's hard to pass these fields to `CalcBaseFee`, etc. - // So pass them as part of the genesis config instead. - chainConfig.Scroll.BaseFeeScalar = config.BaseFeeScalar - chainConfig.Scroll.BaseFeeOverhead = config.BaseFeeOverhead - log.Info("Initialised chain configuration", "config", chainConfig) if err := pruner.RecoverPruning(stack.ResolvePath(""), chainDb, stack.ResolvePath(config.TrieCleanCacheJournal)); err != nil { @@ -220,6 +214,12 @@ func New(stack *node.Node, config *ethconfig.Config, l1Client l1.Client) (*Ether eth.blockchain.Validator().WithAsyncValidator(eth.asyncChecker.Check) } + state, err := eth.blockchain.State() + if err != nil { + return nil, err + } + misc.InitializeL2BaseFeeCoefficients(chainConfig, state) + // Rewind the chain in case of an incompatible config upgrade. if compat, ok := genesisErr.(*params.ConfigCompatError); ok { log.Warn("Rewinding chain to upgrade configuration", "err", compat) @@ -232,6 +232,7 @@ func New(stack *node.Node, config *ethconfig.Config, l1Client l1.Client) (*Ether config.TxPool.Journal = stack.ResolvePath(config.TxPool.Journal) } eth.txPool = core.NewTxPool(config.TxPool, chainConfig, eth.blockchain) + eth.txPool.SetGasPrice(misc.MinBaseFee()) // Initialize and start DA syncing pipeline before SyncService as SyncService is blocking until all L1 messages are loaded. // We need SyncService to load the L1 messages for DA syncing, but since both sync from last known L1 state, we can @@ -361,6 +362,9 @@ func (s *Ethereum) APIs() []rpc.API { // Append any APIs exposed explicitly by the consensus engine apis = append(apis, s.engine.APIs(s.BlockChain())...) + // Append L2 base fee APIs. + apis = append(apis, misc.APIs()...) + if !s.config.EnableDASyncing { apis = append(apis, rpc.API{ Namespace: "eth", @@ -527,10 +531,11 @@ func (s *Ethereum) StartMining(threads int) error { // If the miner was not running, initialize it if !s.IsMining() { // Propagate the initial price point to the transaction pool - s.lock.RLock() - price := s.gasPrice - s.lock.RUnlock() - s.txPool.SetGasPrice(price) + // Disabled, we now update min gas price automatically via L2 base fee. + // s.lock.RLock() + // price := s.gasPrice + // s.lock.RUnlock() + // s.txPool.SetGasPrice(price) // Configure the local mining address eb, err := s.Etherbase() diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index f50109cb3f..94fa46c34c 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -230,10 +230,6 @@ type Config struct { // DA syncer options DA da_syncer.Config - - // L2 base fee coefficients for the formula: `l2BaseFee = (l1BaseFee * scalar) / PRECISION + overhead`. - BaseFeeScalar *big.Int - BaseFeeOverhead *big.Int } // CreateConsensusEngine creates a consensus engine for the given chain configuration. diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 5b1e768dd3..0fc93fec84 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -998,6 +998,10 @@ web3._extend({ name: 'localSigner', getter: 'scroll_getLocalSigner', }), + new web3._extend.Property({ + name: 'l2BaseFeeConfig', + getter: 'scroll_getL2BaseFeeConfig', + }), ] }); ` diff --git a/miner/scroll_worker.go b/miner/scroll_worker.go index b95fb6027a..e58c6360e7 100644 --- a/miner/scroll_worker.go +++ b/miner/scroll_worker.go @@ -21,6 +21,7 @@ import ( "fmt" "math" "math/big" + "runtime/debug" "sync" "sync/atomic" "time" @@ -341,6 +342,7 @@ func (w *worker) mainLoop() { p := recover() if p != nil { log.Error("worker mainLoop panic", "panic", p) + fmt.Println("stacktrace from panic: \n" + string(debug.Stack())) } }() diff --git a/params/config.go b/params/config.go index 28eb863ba6..96b77e04a7 100644 --- a/params/config.go +++ b/params/config.go @@ -351,6 +351,7 @@ var ( L1MessageQueueV2DeploymentBlock: 7773746, NumL1MessagesPerBlock: 10, ScrollChainAddress: common.HexToAddress("0x2D567EcE699Eabe5afCd141eDB7A4f2D0D6ce8a0"), + L2SystemConfigAddress: common.HexToAddress("0xF444cF06A3E3724e20B35c2989d3942ea8b59124"), }, GenesisStateRoot: &ScrollSepoliaGenesisState, }, @@ -401,6 +402,7 @@ var ( L1MessageQueueV2DeploymentBlock: 22088276, NumL1MessagesPerBlock: 10, ScrollChainAddress: common.HexToAddress("0xa13BAF47339d63B743e7Da8741db5456DAc1E556"), + L2SystemConfigAddress: common.HexToAddress("0x331A873a2a85219863d80d248F9e2978fE88D0Ea"), }, GenesisStateRoot: &ScrollMainnetGenesisState, }, @@ -704,11 +706,6 @@ type ScrollConfig struct { // Genesis State Root for MPT clients GenesisStateRoot *common.Hash `json:"genesisStateRoot,omitempty"` - - // L2 base fee coefficients for the formula: `l2BaseFee = (l1BaseFee * scalar) / PRECISION + overhead`. - // These fields are populated from configuration flags, and passed to `CalcBaseFee`. - BaseFeeScalar *big.Int `json:"-"` - BaseFeeOverhead *big.Int `json:"-"` } // L1Config contains the l1 parameters needed to sync l1 contract events (e.g., l1 messages, commit/revert/finalize batches) in the sequencer @@ -719,6 +716,7 @@ type L1Config struct { L1MessageQueueV2DeploymentBlock uint64 `json:"l1MessageQueueV2DeploymentBlock,omitempty"` NumL1MessagesPerBlock uint64 `json:"numL1MessagesPerBlock,string,omitempty"` ScrollChainAddress common.Address `json:"scrollChainAddress,omitempty"` + L2SystemConfigAddress common.Address `json:"l2SystemConfigAddress,omitempty"` } func (c *L1Config) String() string { @@ -726,8 +724,8 @@ func (c *L1Config) String() string { return "" } - return fmt.Sprintf("{l1ChainId: %v, l1MessageQueueAddress: %v, l1MessageQueueV2Address: %v, l1MessageQueueV2DeploymentBlock: %v, numL1MessagesPerBlock: %v, ScrollChainAddress: %v}", - c.L1ChainId, c.L1MessageQueueAddress.Hex(), c.L1MessageQueueV2Address.Hex(), c.L1MessageQueueV2DeploymentBlock, c.NumL1MessagesPerBlock, c.ScrollChainAddress.Hex()) + return fmt.Sprintf("{l1ChainId: %v, l1MessageQueueAddress: %v, l1MessageQueueV2Address: %v, l1MessageQueueV2DeploymentBlock: %v, numL1MessagesPerBlock: %v, ScrollChainAddress: %v, L2SystemConfigAddress: %v}", + c.L1ChainId, c.L1MessageQueueAddress.Hex(), c.L1MessageQueueV2Address.Hex(), c.L1MessageQueueV2DeploymentBlock, c.NumL1MessagesPerBlock, c.ScrollChainAddress.Hex(), c.L2SystemConfigAddress.Hex()) } func (s ScrollConfig) FeeVaultEnabled() bool { @@ -758,18 +756,8 @@ func (s ScrollConfig) String() string { genesisStateRoot = fmt.Sprintf("%v", *s.GenesisStateRoot) } - baseFeeScalar := "" - if s.BaseFeeScalar != nil { - baseFeeScalar = s.BaseFeeScalar.String() - } - - baseFeeOverhead := "" - if s.BaseFeeOverhead != nil { - baseFeeOverhead = s.BaseFeeOverhead.String() - } - - return fmt.Sprintf("{useZktrie: %v, maxTxPerBlock: %v, MaxTxPayloadBytesPerBlock: %v, feeVaultAddress: %v, l1Config: %v, genesisStateRoot: %v, baseFeeScalar: %v, baseFeeOverhead: %v}", - s.UseZktrie, maxTxPerBlock, maxTxPayloadBytesPerBlock, s.FeeVaultAddress, s.L1Config.String(), genesisStateRoot, baseFeeScalar, baseFeeOverhead) + return fmt.Sprintf("{useZktrie: %v, maxTxPerBlock: %v, MaxTxPayloadBytesPerBlock: %v, feeVaultAddress: %v, l1Config: %v, genesisStateRoot: %v}", + s.UseZktrie, maxTxPerBlock, maxTxPayloadBytesPerBlock, s.FeeVaultAddress, s.L1Config.String(), genesisStateRoot) } // IsValidTxCount returns whether the given block's transaction count is below the limit. @@ -788,6 +776,14 @@ func (s ScrollConfig) IsValidBlockSizeForMining(size common.StorageSize) bool { return s.IsValidBlockSize(size * (1.0 / 0.95)) } +// L2SystemConfigAddress returns the configured l2 system config address, or the zero address if it is not configured. +func (s ScrollConfig) L2SystemConfigAddress() common.Address { + if s.L1Config == nil { + return common.Address{} // only in tests + } + return s.L1Config.L2SystemConfigAddress +} + // EthashConfig is the consensus engine configs for proof-of-work based sealing. type EthashConfig struct{} diff --git a/params/version.go b/params/version.go index 12c6206f54..dd5c54baa4 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 5 // Major version component of the current release VersionMinor = 8 // Minor version component of the current release - VersionPatch = 50 // Patch version component of the current release + VersionPatch = 51 // Patch version component of the current release VersionMeta = "mainnet" // Version metadata to append to the version string ) diff --git a/rollup/l2_system_config/abi.go b/rollup/l2_system_config/abi.go new file mode 100644 index 0000000000..b30361f079 --- /dev/null +++ b/rollup/l2_system_config/abi.go @@ -0,0 +1,54 @@ +package l2_system_config + +import ( + "math/big" + + "github.com/scroll-tech/go-ethereum/accounts/abi" + "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/rollup/l1" +) + +var ( + l2SystemConfigABI *abi.ABI + + baseFeeOverheadUpdatedEventName = "BaseFeeOverheadUpdated" + baseFeeScalarUpdatedEventName = "BaseFeeScalarUpdated" + + BaseFeeOverheadUpdatedTopic common.Hash + BaseFeeScalarUpdatedTopic common.Hash +) + +func init() { + l2SystemConfigABI, _ = l2SystemConfigMetaData.GetAbi() + + BaseFeeOverheadUpdatedTopic = l2SystemConfigABI.Events[baseFeeOverheadUpdatedEventName].ID + BaseFeeScalarUpdatedTopic = l2SystemConfigABI.Events[baseFeeScalarUpdatedEventName].ID +} + +var l2SystemConfigMetaData = &bind.MetaData{ + ABI: "[{\"type\":\"event\",\"name\":\"BaseFeeOverheadUpdated\",\"inputs\":[{\"name\":\"oldBaseFeeOverhead\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"},{\"name\":\"newBaseFeeOverhead\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"BaseFeeScalarUpdated\",\"inputs\":[{\"name\":\"oldBaseFeeScalar\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"},{\"name\":\"newBaseFeeScalar\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false}]", +} + +type BaseFeeOverheadUpdatedEventUnpacked struct { + OldBaseFeeOverhead *big.Int + NewBaseFeeOverhead *big.Int +} + +type BaseFeeScalarUpdatedEventUnpacked struct { + OldBaseFeeScalar *big.Int + NewBaseFeeScalar *big.Int +} + +func UnpackBaseFeeOverheadUpdatedEvent(log types.Log) (*BaseFeeOverheadUpdatedEventUnpacked, error) { + event := &BaseFeeOverheadUpdatedEventUnpacked{} + err := l1.UnpackLog(l2SystemConfigABI, event, baseFeeOverheadUpdatedEventName, log) + return event, err +} + +func UnpackBaseFeeScalarUpdatedEvent(log types.Log) (*BaseFeeScalarUpdatedEventUnpacked, error) { + event := &BaseFeeScalarUpdatedEventUnpacked{} + err := l1.UnpackLog(l2SystemConfigABI, event, baseFeeScalarUpdatedEventName, log) + return event, err +} diff --git a/rollup/l2_system_config/abi_test.go b/rollup/l2_system_config/abi_test.go new file mode 100644 index 0000000000..db4059599b --- /dev/null +++ b/rollup/l2_system_config/abi_test.go @@ -0,0 +1,51 @@ +package l2_system_config + +import ( + "math/big" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/scroll-tech/go-ethereum/common" + "github.com/scroll-tech/go-ethereum/core/types" + "github.com/scroll-tech/go-ethereum/crypto" +) + +func TestEventSignatures(t *testing.T) { + assert.Equal(t, crypto.Keccak256Hash([]byte("BaseFeeOverheadUpdated(uint256,uint256)")), BaseFeeOverheadUpdatedTopic) + assert.Equal(t, crypto.Keccak256Hash([]byte("BaseFeeScalarUpdated(uint256,uint256)")), BaseFeeScalarUpdatedTopic) +} + +func bigToBytesPadded(num *big.Int) []byte { + return common.BigToHash(num).Bytes() +} + +func TestUnpackBaseFeeOverheadUpdatedLog(t *testing.T) { + old := common.Big1 + new := common.Big2 + + log := types.Log{ + Topics: []common.Hash{BaseFeeOverheadUpdatedTopic}, + Data: append(bigToBytesPadded(old), bigToBytesPadded(new)...), + } + + event, err := UnpackBaseFeeOverheadUpdatedEvent(log) + assert.NoError(t, err) + assert.Equal(t, common.Big1, event.OldBaseFeeOverhead) + assert.Equal(t, common.Big2, event.NewBaseFeeOverhead) +} + +func TestUnpackBaseFeeScalarUpdatedLog(t *testing.T) { + old := common.Big32 + new := common.Big256 + + log := types.Log{ + Topics: []common.Hash{BaseFeeScalarUpdatedTopic}, + Data: append(bigToBytesPadded(old), bigToBytesPadded(new)...), + } + + event, err := UnpackBaseFeeScalarUpdatedEvent(log) + assert.NoError(t, err) + assert.Equal(t, common.Big32, event.OldBaseFeeScalar) + assert.Equal(t, common.Big256, event.NewBaseFeeScalar) +} diff --git a/rollup/rcfg/config.go b/rollup/rcfg/config.go index 8f7931b745..3bac185c83 100644 --- a/rollup/rcfg/config.go +++ b/rollup/rcfg/config.go @@ -49,3 +49,20 @@ var ( // cat artifacts/src/L1GasPriceOracle.sol/L1GasPriceOracle.json | jq -r .deployedBytecode.object CurieL1GasPriceOracleBytecode = common.Hex2Bytes("608060405234801561000f575f80fd5b5060043610610132575f3560e01c8063715018a6116100b4578063a911d77f11610079578063a911d77f1461024c578063bede39b514610254578063de26c4a114610267578063e88a60ad1461027a578063f2fde38b1461028d578063f45e65d8146102a0575f80fd5b8063715018a6146101eb57806384189161146101f35780638da5cb5b146101fc57806393e59dc114610226578063944b247f14610239575f80fd5b80633d0f963e116100fa5780633d0f963e146101a057806349948e0e146101b3578063519b4bd3146101c65780636a5e67e5146101cf57806370465597146101d8575f80fd5b80630c18c1621461013657806313dad5be1461015257806323e524ac1461016f5780633577afc51461017857806339455d3a1461018d575b5f80fd5b61013f60025481565b6040519081526020015b60405180910390f35b60085461015f9060ff1681565b6040519015158152602001610149565b61013f60065481565b61018b6101863660046109b3565b6102a9565b005b61018b61019b3660046109ca565b61033b565b61018b6101ae3660046109ea565b610438565b61013f6101c1366004610a2b565b6104bb565b61013f60015481565b61013f60075481565b61018b6101e63660046109b3565b6104e0565b61018b61056e565b61013f60055481565b5f5461020e906001600160a01b031681565b6040516001600160a01b039091168152602001610149565b60045461020e906001600160a01b031681565b61018b6102473660046109b3565b6105a2565b61018b61062e565b61018b6102623660046109b3565b61068a565b61013f610275366004610a2b565b610747565b61018b6102883660046109b3565b610764565b61018b61029b3660046109ea565b6107f0565b61013f60035481565b5f546001600160a01b031633146102db5760405162461bcd60e51b81526004016102d290610ad6565b60405180910390fd5b621c9c388111156102ff57604051635742c80560e11b815260040160405180910390fd5b60028190556040518181527f32740b35c0ea213650f60d44366b4fb211c9033b50714e4a1d34e65d5beb9bb4906020015b60405180910390a150565b6004805460405163efc7840160e01b815233928101929092526001600160a01b03169063efc7840190602401602060405180830381865afa158015610382573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103a69190610b0d565b6103c3576040516326b3506d60e11b815260040160405180910390fd5b600182905560058190556040518281527f351fb23757bb5ea0546c85b7996ddd7155f96b939ebaa5ff7bc49c75f27f2c449060200160405180910390a16040518181527f9a14bfb5d18c4c3cf14cae19c23d7cf1bcede357ea40ca1f75cd49542c71c214906020015b60405180910390a15050565b5f546001600160a01b031633146104615760405162461bcd60e51b81526004016102d290610ad6565b600480546001600160a01b038381166001600160a01b031983168117909355604080519190921680825260208201939093527f22d1c35fe072d2e42c3c8f9bd4a0d34aa84a0101d020a62517b33fdb3174e5f7910161042c565b6008545f9060ff16156104d7576104d18261087b565b92915050565b6104d1826108c1565b5f546001600160a01b031633146105095760405162461bcd60e51b81526004016102d290610ad6565b610519633b9aca006103e8610b40565b81111561053957604051631e44fdeb60e11b815260040160405180910390fd5b60038190556040518181527f3336cd9708eaf2769a0f0dc0679f30e80f15dcd88d1921b5a16858e8b85c591a90602001610330565b5f546001600160a01b031633146105975760405162461bcd60e51b81526004016102d290610ad6565b6105a05f610904565b565b5f546001600160a01b031633146105cb5760405162461bcd60e51b81526004016102d290610ad6565b6105d9633b9aca0080610b40565b8111156105f95760405163874f603160e01b815260040160405180910390fd5b60068190556040518181527f2ab3f5a4ebbcbf3c24f62f5454f52f10e1a8c9dcc5acac8f19199ce881a6a10890602001610330565b5f546001600160a01b031633146106575760405162461bcd60e51b81526004016102d290610ad6565b60085460ff161561067b576040516379f9c57560e01b815260040160405180910390fd5b6008805460ff19166001179055565b6004805460405163efc7840160e01b815233928101929092526001600160a01b03169063efc7840190602401602060405180830381865afa1580156106d1573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106f59190610b0d565b610712576040516326b3506d60e11b815260040160405180910390fd5b60018190556040518181527f351fb23757bb5ea0546c85b7996ddd7155f96b939ebaa5ff7bc49c75f27f2c4490602001610330565b6008545f9060ff161561075b57505f919050565b6104d182610953565b5f546001600160a01b0316331461078d5760405162461bcd60e51b81526004016102d290610ad6565b61079b633b9aca0080610b40565b8111156107bb5760405163f37ec21560e01b815260040160405180910390fd5b60078190556040518181527f6b332a036d8c3ead57dcb06c87243bd7a2aed015ddf2d0528c2501dae56331aa90602001610330565b5f546001600160a01b031633146108195760405162461bcd60e51b81526004016102d290610ad6565b6001600160a01b03811661086f5760405162461bcd60e51b815260206004820152601d60248201527f6e6577206f776e657220697320746865207a65726f206164647265737300000060448201526064016102d2565b61087881610904565b50565b5f633b9aca0060055483516007546108939190610b40565b61089d9190610b40565b6001546006546108ad9190610b40565b6108b79190610b57565b6104d19190610b6a565b5f806108cc83610953565b90505f600154826108dd9190610b40565b9050633b9aca00600354826108f29190610b40565b6108fc9190610b6a565b949350505050565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b80515f908190815b818110156109a45784818151811061097557610975610b89565b01602001516001600160f81b0319165f036109955760048301925061099c565b6010830192505b60010161095b565b50506002540160400192915050565b5f602082840312156109c3575f80fd5b5035919050565b5f80604083850312156109db575f80fd5b50508035926020909101359150565b5f602082840312156109fa575f80fd5b81356001600160a01b0381168114610a10575f80fd5b9392505050565b634e487b7160e01b5f52604160045260245ffd5b5f60208284031215610a3b575f80fd5b813567ffffffffffffffff80821115610a52575f80fd5b818401915084601f830112610a65575f80fd5b813581811115610a7757610a77610a17565b604051601f8201601f19908116603f01168101908382118183101715610a9f57610a9f610a17565b81604052828152876020848701011115610ab7575f80fd5b826020860160208301375f928101602001929092525095945050505050565b60208082526017908201527f63616c6c6572206973206e6f7420746865206f776e6572000000000000000000604082015260600190565b5f60208284031215610b1d575f80fd5b81518015158114610a10575f80fd5b634e487b7160e01b5f52601160045260245ffd5b80820281158282048414176104d1576104d1610b2c565b808201808211156104d1576104d1610b2c565b5f82610b8457634e487b7160e01b5f52601260045260245ffd5b500490565b634e487b7160e01b5f52603260045260245ffdfea26469706673582212200c2ac583f18be4f94ab169ae6f2ea3a708a7c0d4424746b120b177adb39e626064736f6c63430008180033") ) + +// L2SystemConfig constants. +var ( + // > forge inspect src/L2/L2SystemConfig.sol:L2SystemConfig storageLayout + // ╭-----------------+-------------+------+--------+-------+------------------------------------------╮ + // | Name | Type | Slot | Offset | Bytes | Contract | + // +==================================================================================================+ + // ... + // |-----------------+-------------+------+--------+-------+------------------------------------------| + // | baseFeeOverhead | uint256 | 101 | 0 | 32 | src/L2/L2SystemConfig.sol:L2SystemConfig | + // |-----------------+-------------+------+--------+-------+------------------------------------------| + // | baseFeeScalar | uint256 | 102 | 0 | 32 | src/L2/L2SystemConfig.sol:L2SystemConfig | + // ╰-----------------+-------------+------+--------+-------+------------------------------------------╯ + + L2BaseFeeOverheadSlot = common.BigToHash(big.NewInt(101)) + L2BaseFeeScalarSlot = common.BigToHash(big.NewInt(102)) +) From 7fbc35db183651f2f26cd7e56eebc9a3ca5f369e Mon Sep 17 00:00:00 2001 From: Jonas Theis <4181434+jonastheis@users.noreply.github.com> Date: Thu, 5 Jun 2025 17:20:54 +0800 Subject: [PATCH 42/45] fix(blob clients): nil pointer with unexpected blob client response (#1195) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix nil pointer with unexpected blob client response * chore: auto version bump [bot] --- params/version.go | 2 +- rollup/da_syncer/blob_client/blob_scan_client.go | 6 ++++++ rollup/da_syncer/blob_client/block_native_client.go | 6 ++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/params/version.go b/params/version.go index dd5c54baa4..7a5726b9fd 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 5 // Major version component of the current release VersionMinor = 8 // Minor version component of the current release - VersionPatch = 51 // Patch version component of the current release + VersionPatch = 52 // Patch version component of the current release VersionMeta = "mainnet" // Version metadata to append to the version string ) diff --git a/rollup/da_syncer/blob_client/blob_scan_client.go b/rollup/da_syncer/blob_client/blob_scan_client.go index 75fe4fdfbd..c4756ed695 100644 --- a/rollup/da_syncer/blob_client/blob_scan_client.go +++ b/rollup/da_syncer/blob_client/blob_scan_client.go @@ -64,6 +64,12 @@ func (c *BlobScanClient) GetBlobByVersionedHashAndBlockTime(ctx context.Context, if err != nil { return nil, fmt.Errorf("failed to decode result into struct, err: %w", err) } + + // check that blob data is not empty + if len(result.Data) < 2 { + return nil, fmt.Errorf("blob data is too short to be valid, expected at least 2 characters, got: %s, versioned hash: %s", result.Data, versionedHash.String()) + } + blobBytes, err := hex.DecodeString(result.Data[2:]) if err != nil { return nil, fmt.Errorf("failed to decode data to bytes, err: %w", err) diff --git a/rollup/da_syncer/blob_client/block_native_client.go b/rollup/da_syncer/blob_client/block_native_client.go index d96f54a3cf..b3ec31ba62 100644 --- a/rollup/da_syncer/blob_client/block_native_client.go +++ b/rollup/da_syncer/blob_client/block_native_client.go @@ -59,6 +59,12 @@ func (c *BlockNativeClient) GetBlobByVersionedHashAndBlockTime(ctx context.Conte if err != nil { return nil, fmt.Errorf("failed to decode result into struct, err: %w", err) } + + // check that blob data is not empty + if len(result.Blob.Data) < 2 { + return nil, fmt.Errorf("blob data is too short to be valid, expected at least 2 characters, got: %s, versioned hash: %s", result.Blob.Data, versionedHash.String()) + } + blobBytes, err := hex.DecodeString(result.Blob.Data[2:]) if err != nil { return nil, fmt.Errorf("failed to decode data to bytes, err: %w", err) From 00c5c2bd3e65df8a34aa7150937646efa9c03adf Mon Sep 17 00:00:00 2001 From: colin <102356659+colinlyguo@users.noreply.github.com> Date: Tue, 10 Jun 2025 17:03:37 +0800 Subject: [PATCH 43/45] fix: update blob-related parameters to Prague configuration (#1202) --- params/protocol_params.go | 4 ++-- params/version.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/params/protocol_params.go b/params/protocol_params.go index a541c86a0f..7aab650058 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -165,9 +165,9 @@ const ( BlobTxBlobGasPerBlob = 1 << 17 // Gas consumption of a single data blob (== blob byte size) BlobTxMinBlobGasprice = 1 // Minimum gas price for data blobs - BlobTxBlobGaspriceUpdateFraction = 3338477 // Controls the maximum rate of change for blob gas price + BlobTxBlobGaspriceUpdateFraction = 5007716 // Controls the maximum rate of change for blob gas price, using Prague parameters - BlobTxTargetBlobGasPerBlock = 3 * BlobTxBlobGasPerBlob // Target consumable blob gas for data blobs per block (for 1559-like pricing) + BlobTxTargetBlobGasPerBlock = 6 * BlobTxBlobGasPerBlob // Target consumable blob gas for data blobs per block (for 1559-like pricing), using Prague parameters ) // Gas discount table for BLS12-381 G1 and G2 multi exponentiation operations diff --git a/params/version.go b/params/version.go index 7a5726b9fd..8f10685252 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 5 // Major version component of the current release VersionMinor = 8 // Minor version component of the current release - VersionPatch = 52 // Patch version component of the current release + VersionPatch = 53 // Patch version component of the current release VersionMeta = "mainnet" // Version metadata to append to the version string ) From 51482290826b722e69ee1698e1e877d39c87ca73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Wed, 11 Jun 2025 14:34:40 +0200 Subject: [PATCH 44/45] feat: feynman upgrade vm changes (#1193) * add Feynman to chain spec * remove input size limit from ecPairing * restore original behavior for blockhash opcode * eip-2935: manually cherry-pick upstream commit a223efc * eip-7623: manually cherry-pick upstream commit 0e1a19d * add note about L1 messages * nit * add history storage to dev genesis * EIP-2935: adopt tracer changes from upstream a223efcf * remove todos * update protocol params * add block hash to witness, plus various cleanups --- cmd/evm/internal/t8ntool/execution.go | 9 +++ cmd/evm/internal/t8ntool/transaction.go | 26 +++++-- core/error.go | 4 + core/genesis.go | 2 + core/state/statedb.go | 5 ++ core/state_processor.go | 31 ++++++++ core/state_processor_test.go | 73 +++++++++++++++++- core/state_transition.go | 74 ++++++++++++++----- core/tx_pool.go | 12 +++ core/vm/contracts.go | 46 +++++++++--- core/vm/contracts_test.go | 3 +- core/vm/evm.go | 2 + core/vm/instructions.go | 26 +++++++ core/vm/interface.go | 3 + core/vm/interpreter.go | 2 + core/vm/jump_table.go | 13 ++++ core/vm/runtime/runtime.go | 2 + core/vm/runtime/runtime_test.go | 14 ++-- .../vm/testdata/precompiles/bn256Pairing.json | 14 ++++ eth/state_accessor.go | 6 ++ eth/tracers/api.go | 22 ++++++ miner/scroll_worker.go | 10 +++ miner/scroll_worker_test.go | 1 + params/config.go | 18 ++++- params/protocol_params.go | 21 +++++- params/version.go | 2 +- 26 files changed, 393 insertions(+), 48 deletions(-) diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index 393129a3ac..922d10648e 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -152,6 +152,15 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, if chainConfig.CurieBlock != nil && chainConfig.CurieBlock.Cmp(new(big.Int).SetUint64(pre.Env.Number)) == 0 { misc.ApplyCurieHardFork(statedb) } + // Apply EIP-2935 + if pre.Env.BlockHashes != nil && chainConfig.IsFeynman(pre.Env.Timestamp) { + var ( + prevNumber = pre.Env.Number - 1 + prevHash = pre.Env.BlockHashes[math.HexOrDecimal64(prevNumber)] + evm = vm.NewEVM(vmContext, vm.TxContext{}, statedb, chainConfig, vmConfig) + ) + core.ProcessParentBlockHash(prevHash, evm, statedb) + } for i, tx := range txs { msg, err := tx.AsMessage(signer, pre.Env.BaseFee) diff --git a/cmd/evm/internal/t8ntool/transaction.go b/cmd/evm/internal/t8ntool/transaction.go index 1a1535a875..d7a8002f61 100644 --- a/cmd/evm/internal/t8ntool/transaction.go +++ b/cmd/evm/internal/t8ntool/transaction.go @@ -140,15 +140,29 @@ func Transaction(ctx *cli.Context) error { r.Address = sender } // Check intrinsic gas - if gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, - chainConfig.IsHomestead(new(big.Int)), chainConfig.IsIstanbul(new(big.Int)), chainConfig.IsShanghai(new(big.Int))); err != nil { + rules := chainConfig.Rules(common.Big0, 0) + gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai) + if err != nil { r.Error = err results = append(results, r) continue - } else { - r.IntrinsicGas = gas - if tx.Gas() < gas { - r.Error = fmt.Errorf("%w: have %d, want %d", core.ErrIntrinsicGas, tx.Gas(), gas) + } + r.IntrinsicGas = gas + if tx.Gas() < gas { + r.Error = fmt.Errorf("%w: have %d, want %d", core.ErrIntrinsicGas, tx.Gas(), gas) + results = append(results, r) + continue + } + // For Feynman txs, validate the floor data gas. + if rules.IsFeynman { + floorDataGas, err := core.FloorDataGas(tx.Data()) + if err != nil { + r.Error = err + results = append(results, r) + continue + } + if tx.Gas() < floorDataGas { + r.Error = fmt.Errorf("%w: have %d, want %d", core.ErrFloorDataGas, tx.Gas(), floorDataGas) results = append(results, r) continue } diff --git a/core/error.go b/core/error.go index 54ec1b188d..98a637e958 100644 --- a/core/error.go +++ b/core/error.go @@ -81,6 +81,10 @@ var ( // than required to start the invocation. ErrIntrinsicGas = errors.New("intrinsic gas too low") + // ErrFloorDataGas is returned if the transaction is specified to use less gas + // than required for the data floor cost. + ErrFloorDataGas = errors.New("insufficient gas for floor data gas cost") + // ErrTxTypeNotSupported is returned if a transaction is not supported in the // current network configuration. ErrTxTypeNotSupported = types.ErrTxTypeNotSupported diff --git a/core/genesis.go b/core/genesis.go index 9554be0ac3..a5eea6f30c 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -500,6 +500,8 @@ func DeveloperGenesisBlock(period uint64, gasLimit uint64, faucet common.Address common.BytesToAddress([]byte{7}): {Balance: big.NewInt(1)}, // ECScalarMul common.BytesToAddress([]byte{8}): {Balance: big.NewInt(1)}, // ECPairing common.BytesToAddress([]byte{9}): {Balance: big.NewInt(1)}, // BLAKE2b + // Pre-deploy EIP-2935 history contract. + params.HistoryStorageAddress: {Nonce: 1, Code: params.HistoryStorageCode, Balance: common.Big0}, // LSH 250 due to finite field limitation faucet: {Balance: new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 250), big.NewInt(9))}, }, diff --git a/core/state/statedb.go b/core/state/statedb.go index 94a89349af..5302263d36 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -167,6 +167,11 @@ func (s *StateDB) WithWitness(witness *stateless.Witness) { s.witness = witness } +// Witness returns the current witness for the state database. +func (s *StateDB) Witness() *stateless.Witness { + return s.witness +} + // StartPrefetcher initializes a new trie prefetcher to pull in nodes from the // state trie concurrently while the state is mutated so that when we reach the // commit phase, most of the needed data is already hot. diff --git a/core/state_processor.go b/core/state_processor.go index 7a6c5d0d97..ddbc9d43bd 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -94,6 +94,10 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg blockContext := NewEVMBlockContext(header, p.bc, p.config, nil) vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg) processorBlockTransactionGauge.Update(int64(block.Transactions().Len())) + // Apply EIP-2935 + if p.config.IsFeynman(block.Time()) { + ProcessParentBlockHash(block.ParentHash(), vmenv, statedb) + } // Iterate over and process the individual transactions for i, tx := range block.Transactions() { msg, err := tx.AsMessage(types.MakeSigner(p.config, header.Number, header.Time), header.BaseFee) @@ -199,3 +203,30 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, config, cfg) return applyTransaction(msg, config, bc, author, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv) } + +// ProcessParentBlockHash stores the parent block hash in the history storage contract +// as per EIP-2935. +func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM, statedb *state.StateDB) { + msg := types.NewMessage( + params.SystemAddress, // from + ¶ms.HistoryStorageAddress, // to + 0, // nonce + common.Big0, // amount + 30_000_000, // gasLimit + common.Big0, // gasPrice + common.Big0, // gasFeeCap + common.Big0, // gasTipCap + prevHash.Bytes(), // data + nil, // accessList + false, // isFake + nil, // setCodeAuthorizations + ) + + evm.Reset(NewEVMTxContext(msg), statedb) + statedb.AddAddressToAccessList(params.HistoryStorageAddress) + _, _, err := evm.Call(vm.AccountRef(msg.From()), *msg.To(), msg.Data(), 30_000_000, common.Big0, nil) + if err != nil { + panic(err) + } + statedb.Finalise(true) +} diff --git a/core/state_processor_test.go b/core/state_processor_test.go index a37e90e93c..7fbc812cb6 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -18,6 +18,7 @@ package core import ( "crypto/ecdsa" + "encoding/binary" "math/big" "testing" @@ -31,9 +32,11 @@ import ( "github.com/scroll-tech/go-ethereum/consensus/ethash" "github.com/scroll-tech/go-ethereum/consensus/misc" "github.com/scroll-tech/go-ethereum/core/rawdb" + "github.com/scroll-tech/go-ethereum/core/state" "github.com/scroll-tech/go-ethereum/core/types" "github.com/scroll-tech/go-ethereum/core/vm" "github.com/scroll-tech/go-ethereum/crypto" + "github.com/scroll-tech/go-ethereum/ethdb/memorydb" "github.com/scroll-tech/go-ethereum/params" "github.com/scroll-tech/go-ethereum/trie" ) @@ -64,6 +67,7 @@ func TestStateProcessorErrors(t *testing.T) { DarwinV2Time: new(uint64), EuclidTime: new(uint64), EuclidV2Time: new(uint64), + FeynmanTime: new(uint64), Ethash: new(params.EthashConfig), } signer = types.LatestSigner(config) @@ -387,9 +391,9 @@ func TestStateProcessorErrors(t *testing.T) { }{ { // ErrMaxInitCodeSizeExceeded txs: []*types.Transaction{ - mkDynamicCreationTx(0, 500000, common.Big0, misc.CalcBaseFee(config, genesis.Header(), parentL1BaseFee), tooBigInitCode[:]), + mkDynamicCreationTx(0, 520000, common.Big0, misc.CalcBaseFee(config, genesis.Header(), parentL1BaseFee), tooBigInitCode[:]), }, - want: "could not apply tx 0 [0x7b33776d375660694a23ef992c090265682f3687607e0099b14503fdb65d73e3]: max initcode size exceeded: code size 49153 limit 49152", + want: "could not apply tx 0 [0xe0d03426cecc04467410064cb4de02012fc069d2462282735d7dfcb9dea9f63b]: max initcode size exceeded: code size 49153 limit 49152", }, { // ErrIntrinsicGas: Not enough gas to cover init code txs: []*types.Transaction{ @@ -453,3 +457,68 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr // Assemble and return the final block for sealing return types.NewBlock(header, txs, nil, receipts, trie.NewStackTrie(nil)) } + +func TestProcessParentBlockHash(t *testing.T) { + var ( + chainConfig = ¶ms.ChainConfig{ + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + LondonBlock: big.NewInt(0), + ShanghaiBlock: big.NewInt(0), + BernoulliBlock: big.NewInt(0), + CurieBlock: big.NewInt(0), + DarwinTime: new(uint64), + DarwinV2Time: new(uint64), + EuclidTime: new(uint64), + EuclidV2Time: new(uint64), + FeynmanTime: new(uint64), + Ethash: new(params.EthashConfig), + } + hashA = common.Hash{0x01} + hashB = common.Hash{0x02} + header = &types.Header{ParentHash: hashA, Number: big.NewInt(2), Difficulty: big.NewInt(0)} + parent = &types.Header{ParentHash: hashB, Number: big.NewInt(1), Difficulty: big.NewInt(0)} + coinbase = common.Address{} + ) + test := func(statedb *state.StateDB) { + statedb.SetNonce(params.HistoryStorageAddress, 1) + statedb.SetCode(params.HistoryStorageAddress, params.HistoryStorageCode) + statedb.IntermediateRoot(true) + + vmContext := NewEVMBlockContext(header, nil, chainConfig, &coinbase) + evm := vm.NewEVM(vmContext, vm.TxContext{}, statedb, chainConfig, vm.Config{}) + ProcessParentBlockHash(header.ParentHash, evm, statedb) + + vmContext = NewEVMBlockContext(parent, nil, chainConfig, &coinbase) + evm = vm.NewEVM(vmContext, vm.TxContext{}, statedb, chainConfig, vm.Config{}) + ProcessParentBlockHash(parent.ParentHash, evm, statedb) + + // make sure that the state is correct + if have := getParentBlockHash(statedb, 1); have != hashA { + t.Errorf("want parent hash %v, have %v", hashA, have) + } + if have := getParentBlockHash(statedb, 0); have != hashB { + t.Errorf("want parent hash %v, have %v", hashB, have) + } + } + t.Run("MPT", func(t *testing.T) { + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil) + test(statedb) + }) +} + +func getParentBlockHash(statedb *state.StateDB, number uint64) common.Hash { + ringIndex := number % params.HistoryServeWindow + var key common.Hash + binary.BigEndian.PutUint64(key[24:], ringIndex) + return statedb.GetState(params.HistoryStorageAddress, key) +} diff --git a/core/state_transition.go b/core/state_transition.go index cc75301f14..0b01945927 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -17,6 +17,7 @@ package core import ( + "bytes" "fmt" "math" "math/big" @@ -141,12 +142,9 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set // Bump the required gas by the amount of transactional data if dataLen > 0 { // Zero and non-zero bytes are priced differently - var nz uint64 - for _, byt := range data { - if byt != 0 { - nz++ - } - } + z := uint64(bytes.Count(data, []byte{0})) + nz := dataLen - z + // Make sure we don't exceed uint64 for all data combinations nonZeroGas := params.TxDataNonZeroGasFrontier if isEIP2028 { @@ -157,7 +155,6 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set } gas += nz * nonZeroGas - z := dataLen - nz if (math.MaxUint64-gas)/params.TxDataZeroGas < z { return 0, ErrGasUintOverflow } @@ -181,6 +178,21 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set return gas, nil } +// FloorDataGas computes the minimum gas required for a transaction based on its data tokens (EIP-7623). +func FloorDataGas(data []byte) (uint64, error) { + var ( + z = uint64(bytes.Count(data, []byte{0})) + nz = uint64(len(data)) - z + tokens = nz*params.TxTokenPerNonZeroByte + z + ) + // Check for overflow + if (math.MaxUint64-params.TxGas)/params.TxCostFloorPerToken < tokens { + return 0, ErrGasUintOverflow + } + // Minimum gas required for a transaction based on its data tokens (EIP-7623). + return params.TxGas + tokens*params.TxCostFloorPerToken, nil +} + // toWordSize returns the ceiled word size required for init code payment calculation. func toWordSize(size uint64) uint64 { if size > math.MaxUint64-31 { @@ -380,16 +392,30 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { sender = vm.AccountRef(msg.From()) rules = st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber, st.evm.Context.Time.Uint64()) contractCreation = msg.To() == nil + floorDataGas uint64 ) // Check clauses 4-5, subtract intrinsic gas if everything is correct gas, err := IntrinsicGas(st.data, st.msg.AccessList(), st.msg.SetCodeAuthorizations(), contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai) if err != nil { + // Note: The L1 message queue contract ensures that this cannot happen for L1 messages. return nil, err } if st.gas < gas { + // Note: The L1 message queue contract ensures that this cannot happen for L1 messages. return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gas, gas) } + // Gas limit suffices for the floor data cost (EIP-7623) + if rules.IsFeynman { + floorDataGas, err = FloorDataGas(st.data) + if err != nil { + return nil, err + } + if st.gas < floorDataGas { + // Note: The L1 message queue contract ensures that this cannot happen for L1 messages. + return nil, fmt.Errorf("%w: have %d, want %d", ErrFloorDataGas, st.gas, floorDataGas) + } + } st.gas -= gas // Check clause 6 @@ -455,13 +481,16 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { }, nil } - if !rules.IsLondon { - // Before EIP-3529: refunds were capped to gasUsed / 2 - st.refundGas(params.RefundQuotient) - } else { - // After EIP-3529: refunds are capped to gasUsed / 5 - st.refundGas(params.RefundQuotientEIP3529) + // Compute refund counter, capped to a refund quotient. + st.gas += st.calcRefund() + if rules.IsFeynman { + // After EIP-7623: Data-heavy transactions pay the floor gas. + if st.gasUsed() < floorDataGas { + st.gas = st.initialGas - floorDataGas + } } + st.returnGas() + effectiveTip := st.gasPrice // only burn the base fee if the fee vault is not enabled @@ -545,14 +574,25 @@ func (st *StateTransition) applyAuthorization(auth *types.SetCodeAuthorization) return types.AuthorizationResult{Authority: authority, PreCode: preCode, Success: true} } -func (st *StateTransition) refundGas(refundQuotient uint64) { - // Apply refund counter, capped to a refund quotient - refund := st.gasUsed() / refundQuotient +// calcRefund computes refund counter, capped to a refund quotient. +func (st *StateTransition) calcRefund() uint64 { + var refund uint64 + if !st.evm.ChainConfig().IsLondon(st.evm.Context.BlockNumber) { + // Before EIP-3529: refunds were capped to gasUsed / 2 + refund = st.gasUsed() / params.RefundQuotient + } else { + // After EIP-3529: refunds are capped to gasUsed / 5 + refund = st.gasUsed() / params.RefundQuotientEIP3529 + } if refund > st.state.GetRefund() { refund = st.state.GetRefund() } - st.gas += refund + return refund +} +// returnGas returns ETH for remaining gas, +// exchanged at the original rate. +func (st *StateTransition) returnGas() { // Return ETH for remaining gas, exchanged at the original rate. remaining := new(big.Int).Mul(new(big.Int).SetUint64(st.gas), st.gasPrice) st.state.AddBalance(st.msg.From(), remaining) diff --git a/core/tx_pool.go b/core/tx_pool.go index 67f3ee1012..5a4afcb1a4 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -297,6 +297,7 @@ type TxPool struct { eip1559 bool // Fork indicator whether we are using EIP-1559 type transactions. shanghai bool // Fork indicator whether we are in the Shanghai stage. eip7702 bool // Fork indicator whether we are using EIP-7702 type transactions. + feynman bool // Fork indicator whether we are in the Feynman stage. currentState *state.StateDB // Current state in the blockchain head currentHead *big.Int // Current blockchain head @@ -855,6 +856,16 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error { if tx.Gas() < intrGas { return ErrIntrinsicGas } + // Ensure the transaction can cover floor data gas. + if pool.feynman { + floorDataGas, err := FloorDataGas(tx.Data()) + if err != nil { + return err + } + if tx.Gas() < floorDataGas { + return fmt.Errorf("%w: gas %v, minimum needed %v", ErrFloorDataGas, tx.Gas(), floorDataGas) + } + } return nil } @@ -1583,6 +1594,7 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) { pool.eip1559 = pool.chainconfig.IsCurie(next) pool.shanghai = pool.chainconfig.IsShanghai(next) pool.eip7702 = pool.chainconfig.IsEuclidV2(newHead.Time) + pool.feynman = pool.chainconfig.IsFeynman(newHead.Time) // Update current head pool.currentHead = next diff --git a/core/vm/contracts.go b/core/vm/contracts.go index d115182d49..97b2297700 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -80,7 +80,7 @@ var PrecompiledContractsIstanbul = map[common.Address]PrecompiledContract{ common.BytesToAddress([]byte{5}): &bigModExp{eip2565: false}, common.BytesToAddress([]byte{6}): &bn256AddIstanbul{}, common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, - common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, + common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{limitInputLength: false}, common.BytesToAddress([]byte{9}): &blake2F{}, } @@ -94,7 +94,7 @@ var PrecompiledContractsBerlin = map[common.Address]PrecompiledContract{ common.BytesToAddress([]byte{5}): &bigModExp{eip2565: true}, common.BytesToAddress([]byte{6}): &bn256AddIstanbul{}, common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, - common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, + common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{limitInputLength: false}, common.BytesToAddress([]byte{9}): &blake2F{}, } @@ -108,7 +108,7 @@ var PrecompiledContractsArchimedes = map[common.Address]PrecompiledContract{ common.BytesToAddress([]byte{5}): &bigModExp{eip2565: true}, common.BytesToAddress([]byte{6}): &bn256AddIstanbul{}, common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, - common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, + common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{limitInputLength: true}, common.BytesToAddress([]byte{9}): &blake2FDisabled{}, } @@ -122,7 +122,7 @@ var PrecompiledContractsBernoulli = map[common.Address]PrecompiledContract{ common.BytesToAddress([]byte{5}): &bigModExp{eip2565: true}, common.BytesToAddress([]byte{6}): &bn256AddIstanbul{}, common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, - common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, + common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{limitInputLength: true}, common.BytesToAddress([]byte{9}): &blake2FDisabled{}, } @@ -136,7 +136,22 @@ var PrecompiledContractsEuclidV2 = map[common.Address]PrecompiledContract{ common.BytesToAddress([]byte{5}): &bigModExp{eip2565: true}, common.BytesToAddress([]byte{6}): &bn256AddIstanbul{}, common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, - common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, + common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{limitInputLength: true}, + common.BytesToAddress([]byte{9}): &blake2FDisabled{}, + common.BytesToAddress([]byte{0x01, 0x00}): &p256Verify{}, +} + +// PrecompiledContractsFeynman contains the default set of pre-compiled Ethereum +// contracts used in the Feynman release. +var PrecompiledContractsFeynman = map[common.Address]PrecompiledContract{ + common.BytesToAddress([]byte{1}): &ecrecover{}, + common.BytesToAddress([]byte{2}): &sha256hash{}, + common.BytesToAddress([]byte{3}): &ripemd160hashDisabled{}, + common.BytesToAddress([]byte{4}): &dataCopy{}, + common.BytesToAddress([]byte{5}): &bigModExp{eip2565: true}, + common.BytesToAddress([]byte{6}): &bn256AddIstanbul{}, + common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, + common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{limitInputLength: false}, common.BytesToAddress([]byte{9}): &blake2FDisabled{}, common.BytesToAddress([]byte{0x01, 0x00}): &p256Verify{}, } @@ -156,6 +171,7 @@ var PrecompiledContractsBLS = map[common.Address]PrecompiledContract{ } var ( + PrecompiledAddressesFeynman []common.Address PrecompiledAddressesEuclidV2 []common.Address PrecompiledAddressesBernoulli []common.Address PrecompiledAddressesArchimedes []common.Address @@ -187,11 +203,16 @@ func init() { for k := range PrecompiledContractsEuclidV2 { PrecompiledAddressesEuclidV2 = append(PrecompiledAddressesEuclidV2, k) } + for k := range PrecompiledContractsFeynman { + PrecompiledAddressesFeynman = append(PrecompiledAddressesFeynman, k) + } } // ActivePrecompiles returns the precompiles enabled with the current configuration. func ActivePrecompiles(rules params.Rules) []common.Address { switch { + case rules.IsFeynman: + return PrecompiledAddressesFeynman case rules.IsEuclidV2: return PrecompiledAddressesEuclidV2 case rules.IsBernoulli: @@ -600,10 +621,6 @@ var ( // runBn256Pairing implements the Bn256Pairing precompile, referenced by both // Byzantium and Istanbul operations. func runBn256Pairing(input []byte) ([]byte, error) { - // Allow at most 4 inputs - if len(input) > 4*192 { - return nil, errBadPairingInput - } // Handle some corner cases cheaply if len(input)%192 > 0 { return nil, errBadPairingInput @@ -634,7 +651,9 @@ func runBn256Pairing(input []byte) ([]byte, error) { // bn256PairingIstanbul implements a pairing pre-compile for the bn256 curve // conforming to Istanbul consensus rules. -type bn256PairingIstanbul struct{} +type bn256PairingIstanbul struct { + limitInputLength bool +} // RequiredGas returns the gas required to execute the pre-compiled contract. func (c *bn256PairingIstanbul) RequiredGas(input []byte) uint64 { @@ -642,6 +661,13 @@ func (c *bn256PairingIstanbul) RequiredGas(input []byte) uint64 { } func (c *bn256PairingIstanbul) Run(input []byte) ([]byte, error) { + if c.limitInputLength { + // Allow at most 4 inputs + if len(input) > 4*192 { + return nil, errBadPairingInput + } + } + return runBn256Pairing(input) } diff --git a/core/vm/contracts_test.go b/core/vm/contracts_test.go index e028d45f65..f6217b0ffd 100644 --- a/core/vm/contracts_test.go +++ b/core/vm/contracts_test.go @@ -65,6 +65,7 @@ var allPrecompiles = map[common.Address]PrecompiledContract{ common.BytesToAddress([]byte{16}): &bls12381Pairing{}, common.BytesToAddress([]byte{17}): &bls12381MapG1{}, common.BytesToAddress([]byte{18}): &bls12381MapG2{}, + common.BytesToAddress([]byte{0xf8}): &bn256PairingIstanbul{limitInputLength: true}, common.BytesToAddress([]byte{0x01, 0x00}): &p256Verify{}, } @@ -264,7 +265,7 @@ func BenchmarkPrecompiledBn256ScalarMul(b *testing.B) { benchJson("bn256ScalarMu // Tests the sample inputs from the elliptic curve pairing check EIP 197. func TestPrecompiledBn256Pairing(t *testing.T) { testJson("bn256Pairing", "08", t) } -func TestPrecompiledBn256PairingFail(t *testing.T) { testJsonFail("bn256Pairing", "08", t) } +func TestPrecompiledBn256PairingFail(t *testing.T) { testJsonFail("bn256Pairing", "f8", t) } func BenchmarkPrecompiledBn256Pairing(b *testing.B) { benchJson("bn256Pairing", "08", b) } func TestPrecompiledBlake2F(t *testing.T) { testJson("blake2F", "09", t) } diff --git a/core/vm/evm.go b/core/vm/evm.go index afb5c7cb39..0514677c61 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -47,6 +47,8 @@ type ( func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) { var precompiles map[common.Address]PrecompiledContract switch { + case evm.chainRules.IsFeynman: + precompiles = PrecompiledContractsFeynman case evm.chainRules.IsEuclidV2: precompiles = PrecompiledContractsEuclidV2 case evm.chainRules.IsBernoulli: diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 36a467a8f3..8027afae76 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -470,6 +470,32 @@ func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ( return nil, nil } +func opBlockhashPostFeynman(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + num := scope.Stack.peek() + num64, overflow := num.Uint64WithOverflow() + if overflow { + num.Clear() + return nil, nil + } + var upper, lower uint64 + upper = interpreter.evm.Context.BlockNumber.Uint64() + if upper < 257 { + lower = 0 + } else { + lower = upper - 256 + } + if num64 >= lower && num64 < upper { + res := interpreter.evm.Context.GetHash(num64) + if witness := interpreter.evm.StateDB.Witness(); witness != nil { + witness.AddBlockHash(num64) + } + num.SetBytes(res[:]) + } else { + num.Clear() + } + return nil, nil +} + func opCoinbase(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { scope.Stack.push(new(uint256.Int).SetBytes(interpreter.evm.FeeRecipient().Bytes())) return nil, nil diff --git a/core/vm/interface.go b/core/vm/interface.go index fe27905e91..2d19515c37 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -20,6 +20,7 @@ import ( "math/big" "github.com/scroll-tech/go-ethereum/common" + "github.com/scroll-tech/go-ethereum/core/stateless" "github.com/scroll-tech/go-ethereum/core/types" "github.com/scroll-tech/go-ethereum/params" ) @@ -85,6 +86,8 @@ type StateDB interface { AddPreimage(common.Hash, []byte) ForEachStorage(common.Address, func(common.Hash, common.Hash) bool) error + + Witness() *stateless.Witness } // CallContext provides a basic interface for the EVM calling conventions. The EVM diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 81de5f3c05..28b5972e7d 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -74,6 +74,8 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter { if cfg.JumpTable[STOP] == nil { var jt JumpTable switch { + case evm.chainRules.IsFeynman: + jt = feynmanInstructionSet case evm.chainRules.IsEuclidV2: jt = euclidV2InstructionSet case evm.chainRules.IsDarwin: diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index 5c46758dbf..92a1a8c790 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -62,11 +62,24 @@ var ( curieInstructionSet = newCurieInstructionSet() darwinInstructionSet = newDarwinInstructionSet() euclidV2InstructionSet = newEuclidV2InstructionSet() + feynmanInstructionSet = newFeynmanInstructionSet() ) // JumpTable contains the EVM opcodes supported at a given fork. type JumpTable [256]*operation +// newFeynmanInstructionSet returns the frontier, homestead, byzantium, +// contantinople, istanbul, petersburg, berlin, london, shanghai, curie, darwin, euclidV2, +// and feynman instructions. +func newFeynmanInstructionSet() JumpTable { + instructionSet := newEuclidV2InstructionSet() + + // change block hash opcode implementation + instructionSet[BLOCKHASH].execute = opBlockhashPostFeynman + + return instructionSet +} + // newEuclidV2InstructionSet returns the frontier, homestead, byzantium, // contantinople, istanbul, petersburg, berlin, london, shanghai, curie, darwin and euclidV2 instructions. func newEuclidV2InstructionSet() JumpTable { diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index 7e4029aec4..2d21fd37c1 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -74,6 +74,8 @@ func setDefaults(cfg *Config) { CurieBlock: new(big.Int), DarwinTime: new(uint64), DarwinV2Time: new(uint64), + EuclidV2Time: new(uint64), + FeynmanTime: new(uint64), } } diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index fa1556fa59..a1b5a9d5b2 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -337,17 +337,13 @@ func TestBlockhash(t *testing.T) { if zero.BitLen() != 0 { t.Fatalf("expected zeroes, got %x", ret[0:32]) } - firstExpectedHash := new(big.Int) - firstExpectedHash.SetString("13215081625009140218242111988507489764601005198286886925088730931502473149599", 10) - if first.Uint64() != firstExpectedHash.Uint64() { - t.Fatalf("first hash should be 13215081625009140218242111988507489764601005198286886925088730931502473149599, got %d (%x)", first, ret[32:64]) + if first.Uint64() != 999 { + t.Fatalf("second block should be 999, got %d (%x)", first, ret[32:64]) } - lastExpectedHash := new(big.Int) - lastExpectedHash.SetString("2851160567348483005169712516804956111111231427377973738952179767509712807467", 10) - if last.Uint64() != lastExpectedHash.Uint64() { - t.Fatalf("last hash should be 2851160567348483005169712516804956111111231427377973738952179767509712807467, got %d (%x)", last, ret[64:96]) + if last.Uint64() != 744 { + t.Fatalf("last block should be 744, got %d (%x)", last, ret[64:96]) } - if exp, got := 0, chain.counter; exp != got { + if exp, got := 255, chain.counter; exp != got { t.Errorf("suboptimal; too much chain iteration, expected %d, got %d", exp, got) } } diff --git a/core/vm/testdata/precompiles/bn256Pairing.json b/core/vm/testdata/precompiles/bn256Pairing.json index bd06969aa8..3fbed6b87c 100644 --- a/core/vm/testdata/precompiles/bn256Pairing.json +++ b/core/vm/testdata/precompiles/bn256Pairing.json @@ -76,6 +76,20 @@ "Gas": 113000, "NoBenchmark": false }, + { + "Input": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "ten_point_match_1", + "Gas": 385000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002203e205db4f19b37b60121b83a7333706db86431c6d835849957ed8c3928ad7927dc7234fd11d3e8c36c59277c3e6f149d5cd3cfa9a62aee49f8130962b4b3b9195e8aa5b7827463722b8c153931579d3505566b4edf48d498e185f0509de15204bb53b8977e5f92a0bc372742c4830944a59b4fe6b1c0466e2a6dad122b5d2e030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd31a76dae6d3272396d0cbe61fced2bc532edac647851e3ac53ce1cc9c7e645a83198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002203e205db4f19b37b60121b83a7333706db86431c6d835849957ed8c3928ad7927dc7234fd11d3e8c36c59277c3e6f149d5cd3cfa9a62aee49f8130962b4b3b9195e8aa5b7827463722b8c153931579d3505566b4edf48d498e185f0509de15204bb53b8977e5f92a0bc372742c4830944a59b4fe6b1c0466e2a6dad122b5d2e030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd31a76dae6d3272396d0cbe61fced2bc532edac647851e3ac53ce1cc9c7e645a83198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002203e205db4f19b37b60121b83a7333706db86431c6d835849957ed8c3928ad7927dc7234fd11d3e8c36c59277c3e6f149d5cd3cfa9a62aee49f8130962b4b3b9195e8aa5b7827463722b8c153931579d3505566b4edf48d498e185f0509de15204bb53b8977e5f92a0bc372742c4830944a59b4fe6b1c0466e2a6dad122b5d2e030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd31a76dae6d3272396d0cbe61fced2bc532edac647851e3ac53ce1cc9c7e645a83198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002203e205db4f19b37b60121b83a7333706db86431c6d835849957ed8c3928ad7927dc7234fd11d3e8c36c59277c3e6f149d5cd3cfa9a62aee49f8130962b4b3b9195e8aa5b7827463722b8c153931579d3505566b4edf48d498e185f0509de15204bb53b8977e5f92a0bc372742c4830944a59b4fe6b1c0466e2a6dad122b5d2e030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd31a76dae6d3272396d0cbe61fced2bc532edac647851e3ac53ce1cc9c7e645a83198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002203e205db4f19b37b60121b83a7333706db86431c6d835849957ed8c3928ad7927dc7234fd11d3e8c36c59277c3e6f149d5cd3cfa9a62aee49f8130962b4b3b9195e8aa5b7827463722b8c153931579d3505566b4edf48d498e185f0509de15204bb53b8977e5f92a0bc372742c4830944a59b4fe6b1c0466e2a6dad122b5d2e030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd31a76dae6d3272396d0cbe61fced2bc532edac647851e3ac53ce1cc9c7e645a83198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "ten_point_match_2", + "Gas": 385000, + "NoBenchmark": false + }, { "Input": "105456a333e6d636854f987ea7bb713dfd0ae8371a72aea313ae0c32c0bf10160cf031d41b41557f3e7e3ba0c51bebe5da8e6ecd855ec50fc87efcdeac168bcc0476be093a6d2b4bbf907172049874af11e1b6267606e00804d3ff0037ec57fd3010c68cb50161b7d1d96bb71edfec9880171954e56871abf3d93cc94d745fa114c059d74e5b6c4ec14ae5864ebe23a71781d86c29fb8fb6cce94f70d3de7a2101b33461f39d9e887dbb100f170a2345dde3c07e256d1dfa2b657ba5cd030427000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000021a2c3013d2ea92e13c800cde68ef56a294b883f6ac35d25f587c09b1b3c635f7290158a80cd3d66530f74dc94c94adb88f5cdb481acca997b6e60071f08a115f2f997f3dbd66a7afe07fe7862ce239edba9e05c5afff7f8a1259c9733b2dfbb929d1691530ca701b4a106054688728c9972c8512e9789e9567aae23e302ccd75", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", diff --git a/eth/state_accessor.go b/eth/state_accessor.go index c610edd85d..13b80ded03 100644 --- a/eth/state_accessor.go +++ b/eth/state_accessor.go @@ -176,6 +176,12 @@ func (eth *Ethereum) stateAtTransaction(block *types.Block, txIndex int, reexec if err != nil { return nil, vm.BlockContext{}, nil, err } + // If feynman hardfork, insert parent block hash in the state as per EIP-2935. + if eth.blockchain.Config().IsFeynman(block.Time()) { + context := core.NewEVMBlockContext(block.Header(), eth.blockchain, eth.blockchain.Config(), nil) + vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, eth.blockchain.Config(), vm.Config{}) + core.ProcessParentBlockHash(block.ParentHash(), vmenv, statedb) + } if txIndex == 0 && len(block.Transactions()) == 0 { return nil, vm.BlockContext{}, statedb, nil } diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 271ddf9632..7b66326c09 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -277,6 +277,11 @@ func (api *API) traceChain(ctx context.Context, start, end *types.Block, config for task := range tasks { signer := types.MakeSigner(api.backend.ChainConfig(), task.block.Number(), task.block.Time()) blockCtx := core.NewEVMBlockContext(task.block.Header(), api.chainContext(localctx), api.backend.ChainConfig(), nil) + // EIP-2935: Insert parent hash in history contract. + if api.backend.ChainConfig().IsFeynman(task.block.Time()) { + evm := vm.NewEVM(blockCtx, vm.TxContext{}, task.statedb, api.backend.ChainConfig(), vm.Config{}) + core.ProcessParentBlockHash(task.block.ParentHash(), evm, task.statedb) + } // Trace all the transactions contained within for i, tx := range task.block.Transactions() { msg, _ := tx.AsMessage(signer, task.block.BaseFee()) @@ -539,6 +544,11 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), api.backend.ChainConfig(), nil) deleteEmptyObjects = chainConfig.IsEIP158(block.Number()) ) + // EIP-2935: Insert parent hash in history contract. + if api.backend.ChainConfig().IsFeynman(block.Time()) { + vmenv := vm.NewEVM(vmctx, vm.TxContext{}, statedb, chainConfig, vm.Config{}) + core.ProcessParentBlockHash(block.ParentHash(), vmenv, statedb) + } for i, tx := range block.Transactions() { var ( msg, _ = tx.AsMessage(signer, block.BaseFee()) @@ -614,6 +624,11 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac threads = len(txs) } blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), api.backend.ChainConfig(), nil) + // EIP-2935: Insert parent hash in history contract. + if api.backend.ChainConfig().IsFeynman(block.Time()) { + evm := vm.NewEVM(blockCtx, vm.TxContext{}, statedb, api.backend.ChainConfig(), vm.Config{}) + core.ProcessParentBlockHash(block.ParentHash(), evm, statedb) + } blockHash := block.Hash() blockNumber := block.NumberU64() for th := 0; th < threads; th++ { @@ -748,6 +763,13 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block canon = false } } + + // EIP-2935: Insert parent hash in history contract. + if api.backend.ChainConfig().IsFeynman(block.Time()) { + evm := vm.NewEVM(vmctx, vm.TxContext{}, statedb, api.backend.ChainConfig(), vm.Config{}) + core.ProcessParentBlockHash(block.ParentHash(), evm, statedb) + } + for i, tx := range block.Transactions() { // Prepare the trasaction for un-traced execution var ( diff --git a/miner/scroll_worker.go b/miner/scroll_worker.go index e58c6360e7..8af97f1cd7 100644 --- a/miner/scroll_worker.go +++ b/miner/scroll_worker.go @@ -627,15 +627,25 @@ func (w *worker) tryCommitNewWork(now time.Time, parent common.Hash, reorging bo // handleForks func (w *worker) handleForks() (bool, error) { + // Apply Curie predeployed contract update if w.chainConfig.CurieBlock != nil && w.chainConfig.CurieBlock.Cmp(w.current.header.Number) == 0 { misc.ApplyCurieHardFork(w.current.state) return true, nil } + // Stop/start miner at Euclid fork boundary on zktrie/mpt nodes if w.chainConfig.IsEuclid(w.current.header.Time) { parent := w.chain.GetBlockByHash(w.current.header.ParentHash) return parent != nil && !w.chainConfig.IsEuclid(parent.Time()), nil } + + // Apply EIP-2935 + if w.chainConfig.IsFeynman(w.current.header.Time) { + context := core.NewEVMBlockContext(w.current.header, w.chain, w.chainConfig, nil) + vmenv := vm.NewEVM(context, vm.TxContext{}, w.current.state, w.chainConfig, vm.Config{}) + core.ProcessParentBlockHash(w.current.header.ParentHash, vmenv, w.current.state) + } + return false, nil } diff --git a/miner/scroll_worker_test.go b/miner/scroll_worker_test.go index cbbf82f32a..74f1371c91 100644 --- a/miner/scroll_worker_test.go +++ b/miner/scroll_worker_test.go @@ -1404,6 +1404,7 @@ func TestEuclidV2TransitionVerification(t *testing.T) { chainConfig := params.AllCliqueProtocolChanges.Clone() chainConfig.EuclidTime = newUint64(0) chainConfig.EuclidV2Time = newUint64(10000) + chainConfig.FeynmanTime = nil chainConfig.Clique = ¶ms.CliqueConfig{Period: 1, Epoch: 30000} chainConfig.SystemContract = ¶ms.SystemContractConfig{Period: 1} diff --git a/params/config.go b/params/config.go index 96b77e04a7..eda4340e80 100644 --- a/params/config.go +++ b/params/config.go @@ -330,6 +330,7 @@ var ( DarwinV2Time: newUint64(1724832000), EuclidTime: newUint64(1741680000), EuclidV2Time: newUint64(1741852800), + FeynmanTime: nil, Clique: &CliqueConfig{ Period: 3, Epoch: 30000, @@ -381,6 +382,7 @@ var ( DarwinV2Time: newUint64(1725264000), EuclidTime: newUint64(1744815600), EuclidV2Time: newUint64(1745305200), + FeynmanTime: nil, Clique: &CliqueConfig{ Period: 3, Epoch: 30000, @@ -521,6 +523,7 @@ var ( DarwinV2Time: new(uint64), EuclidTime: new(uint64), EuclidV2Time: new(uint64), + FeynmanTime: new(uint64), TerminalTotalDifficulty: nil, Ethash: new(EthashConfig), Clique: nil, @@ -662,6 +665,7 @@ type ChainConfig struct { DarwinV2Time *uint64 `json:"darwinv2Time,omitempty"` // DarwinV2 switch time (nil = no fork, 0 = already on darwinv2) EuclidTime *uint64 `json:"euclidTime,omitempty"` // Euclid switch time (nil = no fork, 0 = already on euclid) EuclidV2Time *uint64 `json:"euclidv2Time,omitempty"` // EuclidV2 switch time (nil = no fork, 0 = already on euclidv2) + FeynmanTime *uint64 `json:"feynmanTime,omitempty"` // Feynman switch time (nil = no fork, 0 = already on feynman) // TerminalTotalDifficulty is the amount of total difficulty reached by // the network that triggers the consensus upgrade. @@ -852,7 +856,11 @@ func (c *ChainConfig) String() string { if c.EuclidV2Time != nil { euclidV2Time = fmt.Sprintf("@%v", *c.EuclidV2Time) } - return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, Berlin: %v, London: %v, Arrow Glacier: %v, Archimedes: %v, Shanghai: %v, Bernoulli: %v, Curie: %v, Darwin: %v, DarwinV2: %v, Euclid: %v, EuclidV2: %v, Engine: %v, Scroll config: %v}", + feynmanTime := "" + if c.FeynmanTime != nil { + feynmanTime = fmt.Sprintf("@%v", *c.FeynmanTime) + } + return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, Berlin: %v, London: %v, Arrow Glacier: %v, Archimedes: %v, Shanghai: %v, Bernoulli: %v, Curie: %v, Darwin: %v, DarwinV2: %v, Euclid: %v, EuclidV2: %v, Feynman: %v, Engine: %v, Scroll config: %v}", c.ChainID, c.HomesteadBlock, c.DAOForkBlock, @@ -876,6 +884,7 @@ func (c *ChainConfig) String() string { darwinV2Time, euclidTime, euclidV2Time, + feynmanTime, engine, c.Scroll, ) @@ -988,6 +997,11 @@ func (c *ChainConfig) IsEuclidV2(now uint64) bool { return isForkedTime(now, c.EuclidV2Time) } +// IsFeynman returns whether time is either equal to the Feynman fork time or greater. +func (c *ChainConfig) IsFeynman(now uint64) bool { + return isForkedTime(now, c.FeynmanTime) +} + // IsTerminalPoWBlock returns whether the given block is the last block of PoW stage. func (c *ChainConfig) IsTerminalPoWBlock(parentTotalDiff *big.Int, totalDiff *big.Int) bool { if c.TerminalTotalDifficulty == nil { @@ -1201,6 +1215,7 @@ type Rules struct { IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool IsBerlin, IsLondon, IsArchimedes, IsShanghai bool IsBernoulli, IsCurie, IsDarwin, IsEuclid, IsEuclidV2 bool + IsFeynman bool } // Rules ensures c's ChainID is not nil. @@ -1228,5 +1243,6 @@ func (c *ChainConfig) Rules(num *big.Int, time uint64) Rules { IsDarwin: c.IsDarwin(time), IsEuclid: c.IsEuclid(time), IsEuclidV2: c.IsEuclidV2(time), + IsFeynman: c.IsFeynman(time), } } diff --git a/params/protocol_params.go b/params/protocol_params.go index 7aab650058..7fa848db83 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -16,7 +16,11 @@ package params -import "math/big" +import ( + "math/big" + + "github.com/scroll-tech/go-ethereum/common" +) const ( GasLimitBoundDivisor uint64 = 1024 // The bound divisor of the gas limit, used in update calculations. @@ -87,6 +91,8 @@ const ( TxDataNonZeroGasFrontier uint64 = 68 // Per byte of data attached to a transaction that is not equal to zero. NOTE: Not payable on data of calls between transactions. TxDataNonZeroGasEIP2028 uint64 = 16 // Per byte of non zero data attached to a transaction after EIP 2028 (part in Istanbul) + TxTokenPerNonZeroByte uint64 = 4 // Token cost per non-zero byte as specified by EIP-7623. + TxCostFloorPerToken uint64 = 10 // Cost floor per byte of data as specified by EIP-7623. TxAccessListAddressGas uint64 = 2400 // Per address specified in EIP 2930 access list TxAccessListStorageKeyGas uint64 = 1900 // Per storage key specified in EIP 2930 access list TxAuthTupleGas uint64 = 12500 // Per auth tuple code specified in EIP-7702 @@ -168,14 +174,27 @@ const ( BlobTxBlobGaspriceUpdateFraction = 5007716 // Controls the maximum rate of change for blob gas price, using Prague parameters BlobTxTargetBlobGasPerBlock = 6 * BlobTxBlobGasPerBlob // Target consumable blob gas for data blobs per block (for 1559-like pricing), using Prague parameters + + HistoryServeWindow = 8192 // Number of blocks to serve historical block hashes for, EIP-2935. ) // Gas discount table for BLS12-381 G1 and G2 multi exponentiation operations var Bls12381MultiExpDiscountTable = [128]uint64{1200, 888, 764, 641, 594, 547, 500, 453, 438, 423, 408, 394, 379, 364, 349, 334, 330, 326, 322, 318, 314, 310, 306, 302, 298, 294, 289, 285, 281, 277, 273, 269, 268, 266, 265, 263, 262, 260, 259, 257, 256, 254, 253, 251, 250, 248, 247, 245, 244, 242, 241, 239, 238, 236, 235, 233, 232, 231, 229, 228, 226, 225, 223, 222, 221, 220, 219, 219, 218, 217, 216, 216, 215, 214, 213, 213, 212, 211, 211, 210, 209, 208, 208, 207, 206, 205, 205, 204, 203, 202, 202, 201, 200, 199, 199, 198, 197, 196, 196, 195, 194, 193, 193, 192, 191, 191, 190, 189, 188, 188, 187, 186, 185, 185, 184, 183, 182, 182, 181, 180, 179, 179, 178, 177, 176, 176, 175, 174} +// Difficulty parameters. var ( DifficultyBoundDivisor = big.NewInt(2048) // The bound divisor of the difficulty, used in the update calculations. GenesisDifficulty = big.NewInt(131072) // Difficulty of the Genesis block. MinimumDifficulty = big.NewInt(131072) // The minimum that the difficulty may ever be. DurationLimit = big.NewInt(13) // The decision boundary on the blocktime duration used to determine whether difficulty should go up or not. ) + +// System contracts. +var ( + // SystemAddress is where the system-transaction is sent from as per EIP-4788 + SystemAddress = common.HexToAddress("0xfffffffffffffffffffffffffffffffffffffffe") + + // EIP-2935 - Serve historical block hashes from state + HistoryStorageAddress = common.HexToAddress("0x0000F90827F1C53a10cb7A02335B175320002935") + HistoryStorageCode = common.FromHex("3373fffffffffffffffffffffffffffffffffffffffe14604657602036036042575f35600143038111604257611fff81430311604257611fff9006545f5260205ff35b5f5ffd5b5f35611fff60014303065500") +) diff --git a/params/version.go b/params/version.go index 8f10685252..4e4e9f19f3 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 5 // Major version component of the current release VersionMinor = 8 // Minor version component of the current release - VersionPatch = 53 // Patch version component of the current release + VersionPatch = 54 // Patch version component of the current release VersionMeta = "mainnet" // Version metadata to append to the version string ) From cf3d22ef8707aa1d22ee9a849e3aad9aa5c3e0dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Wed, 11 Jun 2025 16:15:28 +0200 Subject: [PATCH 45/45] feat: reject txs with max l1 data fee (#1203) * feat: reject txs with max l1 data fee * hide mutable constant using getter function * nit --- core/tx_pool.go | 5 +++++ params/version.go | 2 +- rollup/fees/rollup_fee.go | 11 +++++++++-- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/core/tx_pool.go b/core/tx_pool.go index 5a4afcb1a4..3d49800e01 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -842,6 +842,11 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error { if err != nil { return fmt.Errorf("failed to calculate L1 data fee, err: %w", err) } + // Reject transactions that require the max data fee amount. + // This can only happen if the L1 gas oracle is updated incorrectly. + if l1DataFee.Cmp(fees.MaxL1DataFee()) >= 0 { + return errors.New("invalid transaction: invalid L1 data fee") + } // Transactor should have enough funds to cover the costs // cost == L1 data fee + V + GP * GL if b := pool.currentState.GetBalance(from); b.Cmp(new(big.Int).Add(tx.Cost(), l1DataFee)) < 0 { diff --git a/params/version.go b/params/version.go index 4e4e9f19f3..01c2565f15 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 5 // Major version component of the current release VersionMinor = 8 // Minor version component of the current release - VersionPatch = 54 // Patch version component of the current release + VersionPatch = 55 // Patch version component of the current release VersionMeta = "mainnet" // Version metadata to append to the version string ) diff --git a/rollup/fees/rollup_fee.go b/rollup/fees/rollup_fee.go index 2c46aeedbf..ac5fa9fa81 100644 --- a/rollup/fees/rollup_fee.go +++ b/rollup/fees/rollup_fee.go @@ -20,8 +20,15 @@ var ( // to be non-zero. // - tx length prefix: 4 bytes txExtraDataBytes = uint64(4) + + // L1 data fee cap. + l1DataFeeCap = new(big.Int).SetUint64(math.MaxUint64) ) +func MaxL1DataFee() *big.Int { + return new(big.Int).Set(l1DataFeeCap) +} + // Message represents the interface of a message. // It should be a subset of the methods found on // types.Message @@ -248,8 +255,8 @@ func CalculateL1DataFee(tx *types.Transaction, state StateDB, config *params.Cha // ensure l1DataFee fits into uint64 for circuit compatibility // (note: in practice this value should never be this big) - if !l1DataFee.IsUint64() { - l1DataFee.SetUint64(math.MaxUint64) + if l1DataFee.Cmp(l1DataFeeCap) > 0 { + l1DataFee = new(big.Int).Set(l1DataFeeCap) } return l1DataFee, nil