Merge branch 'develop' into fix-requests-hash-in-sdk

This commit is contained in:
colin 2025-06-16 15:48:21 +08:00 committed by GitHub
commit cba908db07
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
85 changed files with 2153 additions and 486 deletions

6
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"

View file

@ -14,11 +14,19 @@ 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 }}
# note: this is needed by git-auto-commit-action below
persist-credentials: true
- name: check diff
id: check_diff
run: |
@ -27,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]
@ -45,17 +53,19 @@ 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
uses: actions/setup-node@v4
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
# 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

View file

@ -11,6 +11,8 @@ on:
jobs:
build-and-push-arm64-image:
runs-on: ubuntu-latest
permissions: {}
strategy:
matrix:
arch:
@ -18,21 +20,29 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v4
with:
persist-credentials: false
- 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@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
with:
cache-binary: false
- name: Login to Docker Hub
uses: docker/login-action@v2
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 #v3.4.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: .

View file

@ -10,16 +10,25 @@ on:
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions: {}
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@v2
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.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: |
@ -27,13 +36,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@74a5d142397b4f367a81961eba4e8cd7edddf772 #v3.4.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

View file

@ -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,104 +13,144 @@ on:
- reopened
- synchronize
- 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
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
- 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
permissions: {}
steps:
- name: Install Go
uses: actions/setup-go@v2
uses: actions/setup-go@v5
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
uses: actions/checkout@v4
with:
persist-credentials: false
- 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
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
- name: Lint
run: |
rm -rf $HOME/.cache/golangci-lint
make lint
goimports-lint:
if: github.event.pull_request.draft == false
runs-on: ubuntu-latest
permissions: {}
steps:
- name: Install Go
uses: actions/setup-go@v2
uses: actions/setup-go@v5
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
uses: actions/checkout@v4
with:
persist-credentials: false
- 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
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
- 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
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
- name: Test
run: |
make test
- name: Upload coverage report
run: bash <(curl -s https://codecov.io/bash)

View file

@ -14,11 +14,14 @@ 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 }}
container:
image: returntocorp/semgrep
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
persist-credentials: false
- run: semgrep ci

34
.github/workflows/zizmor.yml vendored Normal file
View file

@ -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

View file

@ -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)

View file

@ -140,18 +140,32 @@ 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)
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
}
}
// Validate <256bit fields
switch {

View file

@ -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"
@ -133,6 +134,7 @@ var (
utils.MinerNoVerifyFlag,
utils.MinerStoreSkippedTxTracesFlag,
utils.MinerMaxAccountsNumFlag,
utils.MinerAllowEmptyFlag,
utils.NATFlag,
utils.NoDiscoverFlag,
utils.DiscoveryV5Flag,
@ -452,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)

View file

@ -161,6 +161,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{
utils.RPCGlobalEVMTimeoutFlag,
utils.RPCGlobalTxFeeCapFlag,
utils.AllowUnprotectedTxs,
utils.MaxBlockRangeFlag,
utils.JSpathFlag,
utils.ExecFlag,
utils.PreloadJSFlag,
@ -197,6 +198,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{
utils.MinerNoVerifyFlag,
utils.MinerStoreSkippedTxTracesFlag,
utils.MinerMaxAccountsNumFlag,
utils.MinerAllowEmptyFlag,
},
},
{
@ -226,6 +228,28 @@ 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.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{

View file

@ -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!")
}

View file

@ -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) {

View file

@ -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))

View file

@ -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.

View file

@ -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))

View file

@ -19,14 +19,66 @@ 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.
var (
// 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 <nil>")
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 <nil>")
return
}
lock.Lock()
defer lock.Unlock()
baseFeeScalar.Set(newScalar)
}
// VerifyEip1559Header verifies some header attributes which were changed in EIP-1559,
// - gas limit check
@ -54,18 +106,22 @@ 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(38200000) // 0.0382 Gwei
// L1_base_fee * 0.00017
verificationFee := parentL1BaseFee
verificationFee = new(big.Int).Mul(verificationFee, big.NewInt(17))
verificationFee = new(big.Int).Div(verificationFee, big.NewInt(100000))
scalar, overhead := ReadL2BaseFeeCoefficients()
return calcBaseFee(scalar, overhead, parentL1BaseFee)
}
baseFee := big.NewInt(0)
baseFee.Add(baseFee, l2SequencerFee)
baseFee.Add(baseFee, provingFee)
baseFee.Add(baseFee, verificationFee)
// 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))
}
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)
@ -73,3 +129,60 @@ func CalcBaseFee(config *params.ChainConfig, parent *types.Header, parentL1BaseF
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,
}}
}

View file

@ -112,17 +112,55 @@ 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, 1},
{1000000000, 1},
{2000000000, 1},
{100000000000, 2},
{111111111111, 2},
{2164000000000, 22},
{644149677419355, 6442},
}
for i, test := range tests {
config := config()
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)
}
}
testsWithDefaults := []struct {
parentL1BaseFee int64
expectedL2BaseFee int64
}{
{0, 15680000},
{1000000000, 15714000},
{2000000000, 15748000},
{100000000000, 19080000},
{111111111111, 19457777},
{2164000000000, 89256000},
{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)
}
}
}
// TestMinBaseFee assumes all blocks are 1559-blocks
func TestMinBaseFee(t *testing.T) {
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)
}
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)
}
}

View file

@ -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)
}
@ -130,7 +135,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,8 +202,6 @@ 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
@ -211,7 +214,6 @@ func (s *SystemContract) verifyCascadingFields(chain consensus.ChainHeaderReader
log.Error("Unauthorized signer", "Got", signer, "Expected:", s.signerAddressL1)
return ErrUnauthorizedSigner
}
}
return nil
}

View file

@ -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"
@ -48,8 +51,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 +84,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 +98,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())
@ -131,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
}

View file

@ -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 := &params.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 := &params.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 := &params.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
}

View file

@ -2,7 +2,6 @@ package wrapper
import (
"math/big"
"time"
"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/consensus"
@ -61,7 +60,7 @@ func (ue *UpgradableEngine) VerifyHeader(chain consensus.ChainHeaderReader, head
// 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))
@ -77,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
@ -110,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++ {
@ -124,14 +123,13 @@ 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)
// 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++ {

View file

@ -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

View file

@ -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"
)
@ -53,8 +55,10 @@ 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)
l2BaseFeeUpdateTimer = metrics.NewRegisteredTimer("chain/fees/updates", nil)
accountReadTimer = metrics.NewRegisteredTimer("chain/account/reads", nil)
accountHashTimer = metrics.NewRegisteredTimer("chain/account/hashes", nil)
@ -148,6 +152,51 @@ 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
}
}
}
// 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.
//
@ -422,6 +471,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
}
@ -1253,6 +1305,12 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
l2BaseFeeGauge.Update(0)
}
// 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.
@ -1283,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
@ -1527,7 +1585,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
@ -1680,7 +1738,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
@ -1817,7 +1875,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()

View file

@ -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

View file

@ -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))},
},

View file

@ -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

View file

@ -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
}

View file

@ -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

View file

@ -18,9 +18,11 @@
package state
import (
"bytes"
"errors"
"fmt"
"math/big"
"slices"
"sort"
"time"
@ -160,18 +162,25 @@ 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
}
// 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.
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 +976,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

View file

@ -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
&params.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)
}

View file

@ -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,15 +391,15 @@ 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 [0xa31de6e26bd5ffba0ca91a2bc29fc2eaad6a6cfc5ad9ab6ffb69cac121e0125c]: 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{
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 [0x98e54c5ecfa7986a66480d65ba32f2c6a2a6aedc3a67abb91b1e118b0717ed2d]: intrinsic gas too low: have 54299, want 54300",
},
} {
block := GenerateBadBlock(genesis, ethash.NewFaker(), tt.txs, gspec.Config)
@ -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 = &params.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)
}

View file

@ -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 {
@ -269,8 +281,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 +316,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.
@ -373,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
@ -448,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
@ -479,7 +515,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
}
@ -538,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)

View file

@ -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,14 @@ 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")
// 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.
@ -106,7 +110,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 (
@ -293,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
@ -420,6 +425,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()
@ -434,6 +441,16 @@ 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())
}
}
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.
@ -467,7 +484,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 +500,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 +563,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))
@ -809,19 +827,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 {
@ -835,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 {
@ -849,6 +861,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
}
@ -863,13 +885,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
}
@ -880,7 +902,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
}
@ -888,7 +910,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
}
@ -908,7 +930,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
}
@ -916,7 +938,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)
}
@ -927,11 +949,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)
@ -941,7 +965,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()
@ -963,7 +987,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
}
@ -980,11 +1004,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())
@ -1034,6 +1060,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)
@ -1044,6 +1071,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)
@ -1052,6 +1080,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)
@ -1060,7 +1089,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)
@ -1233,7 +1262,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
@ -1463,10 +1492,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())
}
}
}
@ -1535,6 +1564,10 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) {
}
}
reinject = types.TxDifference(discarded, included)
for _, tx := range reinject {
log.Debug("TX is removed from old chain due to reorg", "hash", tx.Hash().Hex())
}
}
}
}
@ -1552,7 +1585,9 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) {
pool.currentMaxGas = newHead.GasLimit
// Inject any transactions discarded due to reorgs
if len(reinject) > 0 {
log.Debug("Reinjecting stale transactions", "count", len(reinject))
}
senderCacher.recover(pool.signer, reinject)
pool.addTxsLocked(reinject, false)
@ -1564,6 +1599,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
@ -1588,6 +1624,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))
@ -1598,6 +1637,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)))
@ -1621,7 +1661,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)))
}
@ -1679,7 +1719,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)))
@ -1732,7 +1772,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)))
@ -1760,7 +1800,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)))
@ -1803,6 +1843,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
@ -1812,6 +1853,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)
@ -1837,22 +1879,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)
@ -1866,7 +1910,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)
@ -1895,6 +1939,57 @@ 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 err := pool.checkDelegationLimit(from, tx); err != nil {
return err
}
// 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 {
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
}
}
}
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 +2227,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 +2235,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 +2291,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 +2314,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
}

View file

@ -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
}
@ -2689,29 +2722,98 @@ 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)
}
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)
// 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.
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)
// 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)
}
// Replace by fee.
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)
}
},
},
{
@ -2719,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)
}
@ -2728,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, ErrAccountLimitExceeded) {
t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrAccountLimitExceeded, 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)
}
@ -2760,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) {
@ -2815,20 +2944,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) {
t.Fatalf("%s: error mismatch: want %v, have %v", name, want, err)
}
},
},
{
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) {
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 +2962,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 +3017,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) }

View file

@ -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)
}
}

View file

@ -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,
}
}

View file

@ -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)
}

View file

@ -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) }

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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:

View file

@ -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 {

View file

@ -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

View file

@ -74,6 +74,8 @@ func setDefaults(cfg *Config) {
CurieBlock: new(big.Int),
DarwinTime: new(uint64),
DarwinV2Time: new(uint64),
EuclidV2Time: new(uint64),
FeynmanTime: new(uint64),
}
}

View file

@ -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)
}
}

View file

@ -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",

View file

@ -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"
@ -131,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
}
@ -353,13 +357,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)
@ -368,36 +370,57 @@ 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)
if err = testWitness(blockchain, block, witness); err != nil {
return nil, err
}
return witness, nil
}
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 && !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{}) {
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 && !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{}) {
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
}

View file

@ -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"
@ -213,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)
@ -225,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
@ -354,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",
@ -520,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()

View file

@ -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)))

View file

@ -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)
}

View file

@ -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,

View file

@ -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())

View file

@ -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)

View file

@ -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
}

View file

@ -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 (

View file

@ -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 {

View file

@ -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)

2
go.mod
View file

@ -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

4
go.sum
View file

@ -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=

View file

@ -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{
result := &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()
}
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.
@ -1529,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
@ -1558,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 {

43
internal/ethapi/dbapi.go Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
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()
}

View file

@ -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',
@ -983,6 +998,10 @@ web3._extend({
name: 'localSigner',
getter: 'scroll_getLocalSigner',
}),
new web3._extend.Property({
name: 'l2BaseFeeConfig',
getter: 'scroll_getL2BaseFeeConfig',
}),
]
});
`

View file

@ -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
}

View file

@ -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()))
}
}()
@ -400,6 +402,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)
@ -622,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
}

View file

@ -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,84 @@ 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.FeynmanTime = nil
chainConfig.Clique = &params.CliqueConfig{Period: 1, Epoch: 30000}
chainConfig.SystemContract = &params.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)
}

View file

@ -328,10 +328,18 @@ var (
CurieBlock: big.NewInt(4740239),
DarwinTime: newUint64(1723622400),
DarwinV2Time: newUint64(1724832000),
EuclidTime: newUint64(1741680000),
EuclidV2Time: newUint64(1741852800),
FeynmanTime: nil,
Clique: &CliqueConfig{
Period: 3,
Epoch: 30000,
},
SystemContract: &SystemContractConfig{
Period: 3,
SystemContractAddress: common.HexToAddress("0xC706Ba9fa4fedF4507CB7A898b4766c1bbf9be57"),
SystemContractSlot: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000067"),
},
Scroll: ScrollConfig{
UseZktrie: true,
MaxTxPerBlock: &ScrollMaxTxPerBlock,
@ -340,8 +348,11 @@ var (
L1Config: &L1Config{
L1ChainId: 11155111,
L1MessageQueueAddress: common.HexToAddress("0xF0B2293F5D834eAe920c6974D50957A1732de763"),
L1MessageQueueV2Address: common.HexToAddress("0xA0673eC0A48aa924f067F1274EcD281A10c5f19F"),
L1MessageQueueV2DeploymentBlock: 7773746,
NumL1MessagesPerBlock: 10,
ScrollChainAddress: common.HexToAddress("0x2D567EcE699Eabe5afCd141eDB7A4f2D0D6ce8a0"),
L2SystemConfigAddress: common.HexToAddress("0xF444cF06A3E3724e20B35c2989d3942ea8b59124"),
},
GenesisStateRoot: &ScrollSepoliaGenesisState,
},
@ -369,10 +380,18 @@ var (
CurieBlock: big.NewInt(7096836),
DarwinTime: newUint64(1724227200),
DarwinV2Time: newUint64(1725264000),
EuclidTime: newUint64(1744815600),
EuclidV2Time: newUint64(1745305200),
FeynmanTime: nil,
Clique: &CliqueConfig{
Period: 3,
Epoch: 30000,
},
SystemContract: &SystemContractConfig{
Period: 3,
SystemContractAddress: common.HexToAddress("0x8432728A257646449245558B8b7Dbe51A16c7a4D"),
SystemContractSlot: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000067"),
},
Scroll: ScrollConfig{
UseZktrie: true,
MaxTxPerBlock: &ScrollMaxTxPerBlock,
@ -381,8 +400,11 @@ var (
L1Config: &L1Config{
L1ChainId: 1,
L1MessageQueueAddress: common.HexToAddress("0x0d7E906BD9cAFa154b048cFa766Cc1E54E39AF9B"),
L1MessageQueueV2Address: common.HexToAddress("0x56971da63A3C0205184FEF096E9ddFc7A8C2D18a"),
L1MessageQueueV2DeploymentBlock: 22088276,
NumL1MessagesPerBlock: 10,
ScrollChainAddress: common.HexToAddress("0xa13BAF47339d63B743e7Da8741db5456DAc1E556"),
L2SystemConfigAddress: common.HexToAddress("0x331A873a2a85219863d80d248F9e2978fE88D0Ea"),
},
GenesisStateRoot: &ScrollMainnetGenesisState,
},
@ -501,6 +523,7 @@ var (
DarwinV2Time: new(uint64),
EuclidTime: new(uint64),
EuclidV2Time: new(uint64),
FeynmanTime: new(uint64),
TerminalTotalDifficulty: nil,
Ethash: new(EthashConfig),
Clique: nil,
@ -642,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.
@ -692,10 +716,11 @@ 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"`
L2SystemConfigAddress common.Address `json:"l2SystemConfigAddress,omitempty"`
}
func (c *L1Config) String() string {
@ -703,8 +728,8 @@ func (c *L1Config) String() string {
return "<nil>"
}
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, 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 {
@ -730,8 +755,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 := "<nil>"
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.
@ -750,6 +780,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{}
@ -818,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 := "<nil>"
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,
@ -842,6 +884,7 @@ func (c *ChainConfig) String() string {
darwinV2Time,
euclidTime,
euclidV2Time,
feynmanTime,
engine,
c.Scroll,
)
@ -954,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 {
@ -1167,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.
@ -1194,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),
}
}

View file

@ -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
@ -165,17 +171,30 @@ 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
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")
)

View file

@ -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 = 56 // Patch version component of the current release
VersionMeta = "mainnet" // Version metadata to append to the version string
)

View file

@ -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(),

View file

@ -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)
}

View file

@ -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,
}
}
@ -59,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)

View file

@ -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)
}
@ -52,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)

View file

@ -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

File diff suppressed because one or more lines are too long

View file

@ -18,6 +18,14 @@ 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("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)
}
func TestUnpackLog(t *testing.T) {
mockBatchIndex := big.NewInt(123)
finishMockBatchIndex := big.NewInt(125)

View file

@ -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"
@ -21,6 +23,7 @@ const (
finalizeBatchEventName = "FinalizeBatch"
nextUnfinalizedQueueIndex = "nextUnfinalizedQueueIndex"
lastFinalizedBatchIndex = "lastFinalizedBatchIndex"
finalizedStateRoots = "finalizedStateRoots"
defaultRollupEventsFetchBlockRange = 100
)
@ -70,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)
@ -90,14 +93,10 @@ 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) 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)
@ -119,6 +118,28 @@ func (r *Reader) LatestFinalizedBatch(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)))
@ -398,6 +419,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)
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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)

File diff suppressed because one or more lines are too long

View file

@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"os"
"strings"
"sync"
"time"
@ -16,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"
@ -40,6 +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.
@ -132,6 +143,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()
@ -205,6 +221,28 @@ 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
}
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)
@ -279,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)
@ -312,6 +356,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)
@ -385,16 +430,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
}
}
@ -424,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.
@ -535,6 +587,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()
@ -555,7 +617,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)
@ -584,7 +648,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
}

View file

@ -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