mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-26 06:36:43 +00:00
Merge branch 'develop' into fix-requests-hash-in-sdk
This commit is contained in:
commit
cba908db07
85 changed files with 2153 additions and 486 deletions
6
.github/dependabot.yml
vendored
Normal file
6
.github/dependabot.yml
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
18
.github/workflows/bump_version.yml
vendored
18
.github/workflows/bump_version.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
18
.github/workflows/docker-arm64.yaml
vendored
18
.github/workflows/docker-arm64.yaml
vendored
|
|
@ -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: .
|
||||
|
|
|
|||
23
.github/workflows/docker.yaml
vendored
23
.github/workflows/docker.yaml
vendored
|
|
@ -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
|
||||
|
|
|
|||
70
.github/workflows/l2geth_ci.yml
vendored
70
.github/workflows/l2geth_ci.yml
vendored
|
|
@ -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)
|
||||
|
|
|
|||
7
.github/workflows/semgrep.yml
vendored
7
.github/workflows/semgrep.yml
vendored
|
|
@ -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
34
.github/workflows/zizmor.yml
vendored
Normal 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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -140,15 +140,29 @@ func Transaction(ctx *cli.Context) error {
|
|||
r.Address = sender
|
||||
}
|
||||
// Check intrinsic gas
|
||||
if gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil,
|
||||
chainConfig.IsHomestead(new(big.Int)), chainConfig.IsIstanbul(new(big.Int)), chainConfig.IsShanghai(new(big.Int))); err != nil {
|
||||
rules := chainConfig.Rules(common.Big0, 0)
|
||||
gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai)
|
||||
if err != nil {
|
||||
r.Error = err
|
||||
results = append(results, r)
|
||||
continue
|
||||
} else {
|
||||
r.IntrinsicGas = gas
|
||||
if tx.Gas() < gas {
|
||||
r.Error = fmt.Errorf("%w: have %d, want %d", core.ErrIntrinsicGas, tx.Gas(), gas)
|
||||
}
|
||||
r.IntrinsicGas = gas
|
||||
if tx.Gas() < gas {
|
||||
r.Error = fmt.Errorf("%w: have %d, want %d", core.ErrIntrinsicGas, tx.Gas(), gas)
|
||||
results = append(results, r)
|
||||
continue
|
||||
}
|
||||
// For Feynman txs, validate the floor data gas.
|
||||
if rules.IsFeynman {
|
||||
floorDataGas, err := core.FloorDataGas(tx.Data())
|
||||
if err != nil {
|
||||
r.Error = err
|
||||
results = append(results, r)
|
||||
continue
|
||||
}
|
||||
if tx.Gas() < floorDataGas {
|
||||
r.Error = fmt.Errorf("%w: have %d, want %d", core.ErrFloorDataGas, tx.Gas(), floorDataGas)
|
||||
results = append(results, r)
|
||||
continue
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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!")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,20 +202,17 @@ func (s *SystemContract) verifyCascadingFields(chain consensus.ChainHeaderReader
|
|||
return err
|
||||
}
|
||||
|
||||
// only if block header has NOT been requested, then verify the signature against the current signer
|
||||
if !header.Requested {
|
||||
signer, err := ecrecover(header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
signer, err := ecrecover(header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
if signer != s.signerAddressL1 {
|
||||
log.Error("Unauthorized signer", "Got", signer, "Expected:", s.signerAddressL1)
|
||||
return ErrUnauthorizedSigner
|
||||
}
|
||||
if signer != s.signerAddressL1 {
|
||||
log.Error("Unauthorized signer", "Got", signer, "Expected:", s.signerAddressL1)
|
||||
return ErrUnauthorizedSigner
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,13 +3,11 @@ package system_contract
|
|||
import (
|
||||
"context"
|
||||
"math/big"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum"
|
||||
"github.com/scroll-tech/go-ethereum/accounts"
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
"github.com/scroll-tech/go-ethereum/core/types"
|
||||
|
|
@ -19,14 +17,14 @@ import (
|
|||
"github.com/scroll-tech/go-ethereum/trie"
|
||||
)
|
||||
|
||||
var _ sync_service.EthClient = &fakeEthClient{}
|
||||
var _ sync_service.EthClient = &FakeEthClient{}
|
||||
|
||||
func TestSystemContract_FetchSigner(t *testing.T) {
|
||||
log.Root().SetHandler(log.DiscardHandler())
|
||||
|
||||
expectedSigner := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678")
|
||||
|
||||
fakeClient := &fakeEthClient{value: expectedSigner}
|
||||
fakeClient := &FakeEthClient{Value: expectedSigner}
|
||||
|
||||
config := ¶ms.SystemContractConfig{
|
||||
SystemContractAddress: common.HexToAddress("0xFAKE"),
|
||||
|
|
@ -55,7 +53,7 @@ func TestSystemContract_AuthorizeCheck(t *testing.T) {
|
|||
|
||||
expectedSigner := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678")
|
||||
|
||||
fakeClient := &fakeEthClient{value: expectedSigner}
|
||||
fakeClient := &FakeEthClient{Value: expectedSigner}
|
||||
config := ¶ms.SystemContractConfig{
|
||||
SystemContractAddress: common.HexToAddress("0xFAKE"),
|
||||
Period: 10,
|
||||
|
|
@ -106,8 +104,8 @@ func TestSystemContract_SignsAfterUpdate(t *testing.T) {
|
|||
updatedSigner := common.HexToAddress("0x2222222222222222222222222222222222222222")
|
||||
|
||||
// Create a fake client that starts by returning the wrong signer.
|
||||
fakeClient := &fakeEthClient{
|
||||
value: oldSigner,
|
||||
fakeClient := &FakeEthClient{
|
||||
Value: oldSigner,
|
||||
}
|
||||
|
||||
config := ¶ms.SystemContractConfig{
|
||||
|
|
@ -130,7 +128,7 @@ func TestSystemContract_SignsAfterUpdate(t *testing.T) {
|
|||
|
||||
// Now, simulate an update: change the fake client's returned value to updatedSigner.
|
||||
fakeClient.mu.Lock()
|
||||
fakeClient.value = updatedSigner
|
||||
fakeClient.Value = updatedSigner
|
||||
fakeClient.mu.Unlock()
|
||||
|
||||
// fetch new value from L1 (simulating a background poll)
|
||||
|
|
@ -171,53 +169,3 @@ func TestSystemContract_SignsAfterUpdate(t *testing.T) {
|
|||
t.Fatal("Timed out waiting for Seal to return a sealed block")
|
||||
}
|
||||
}
|
||||
|
||||
// fakeEthClient implements a minimal version of sync_service.EthClient for testing purposes.
|
||||
type fakeEthClient struct {
|
||||
mu sync.Mutex
|
||||
// value is the fixed value to return from StorageAt.
|
||||
// We'll assume StorageAt returns a 32-byte value representing an Ethereum address.
|
||||
value common.Address
|
||||
}
|
||||
|
||||
// BlockNumber returns 0.
|
||||
func (f *fakeEthClient) BlockNumber(ctx context.Context) (uint64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// ChainID returns a zero-value chain ID.
|
||||
func (f *fakeEthClient) ChainID(ctx context.Context) (*big.Int, error) {
|
||||
return big.NewInt(0), nil
|
||||
}
|
||||
|
||||
// FilterLogs returns an empty slice of logs.
|
||||
func (f *fakeEthClient) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) {
|
||||
return []types.Log{}, nil
|
||||
}
|
||||
|
||||
// HeaderByNumber returns nil.
|
||||
func (f *fakeEthClient) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// SubscribeFilterLogs returns a nil subscription.
|
||||
func (f *fakeEthClient) SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// TransactionByHash returns (nil, false, nil).
|
||||
func (f *fakeEthClient) TransactionByHash(ctx context.Context, txHash common.Hash) (*types.Transaction, bool, error) {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
// BlockByHash returns nil.
|
||||
func (f *fakeEthClient) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// StorageAt returns the byte representation of f.value.
|
||||
func (f *fakeEthClient) StorageAt(ctx context.Context, account common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
return f.value.Bytes(), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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++ {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -94,6 +94,10 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
|
|||
blockContext := NewEVMBlockContext(header, p.bc, p.config, nil)
|
||||
vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg)
|
||||
processorBlockTransactionGauge.Update(int64(block.Transactions().Len()))
|
||||
// Apply EIP-2935
|
||||
if p.config.IsFeynman(block.Time()) {
|
||||
ProcessParentBlockHash(block.ParentHash(), vmenv, statedb)
|
||||
}
|
||||
// Iterate over and process the individual transactions
|
||||
for i, tx := range block.Transactions() {
|
||||
msg, err := tx.AsMessage(types.MakeSigner(p.config, header.Number, header.Time), header.BaseFee)
|
||||
|
|
@ -199,3 +203,30 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo
|
|||
vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, config, cfg)
|
||||
return applyTransaction(msg, config, bc, author, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv)
|
||||
}
|
||||
|
||||
// ProcessParentBlockHash stores the parent block hash in the history storage contract
|
||||
// as per EIP-2935.
|
||||
func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM, statedb *state.StateDB) {
|
||||
msg := types.NewMessage(
|
||||
params.SystemAddress, // from
|
||||
¶ms.HistoryStorageAddress, // to
|
||||
0, // nonce
|
||||
common.Big0, // amount
|
||||
30_000_000, // gasLimit
|
||||
common.Big0, // gasPrice
|
||||
common.Big0, // gasFeeCap
|
||||
common.Big0, // gasTipCap
|
||||
prevHash.Bytes(), // data
|
||||
nil, // accessList
|
||||
false, // isFake
|
||||
nil, // setCodeAuthorizations
|
||||
)
|
||||
|
||||
evm.Reset(NewEVMTxContext(msg), statedb)
|
||||
statedb.AddAddressToAccessList(params.HistoryStorageAddress)
|
||||
_, _, err := evm.Call(vm.AccountRef(msg.From()), *msg.To(), msg.Data(), 30_000_000, common.Big0, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
statedb.Finalise(true)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 = ¶ms.ChainConfig{
|
||||
ChainID: big.NewInt(1),
|
||||
HomesteadBlock: big.NewInt(0),
|
||||
EIP150Block: big.NewInt(0),
|
||||
EIP155Block: big.NewInt(0),
|
||||
EIP158Block: big.NewInt(0),
|
||||
ByzantiumBlock: big.NewInt(0),
|
||||
ConstantinopleBlock: big.NewInt(0),
|
||||
PetersburgBlock: big.NewInt(0),
|
||||
IstanbulBlock: big.NewInt(0),
|
||||
MuirGlacierBlock: big.NewInt(0),
|
||||
BerlinBlock: big.NewInt(0),
|
||||
LondonBlock: big.NewInt(0),
|
||||
ShanghaiBlock: big.NewInt(0),
|
||||
BernoulliBlock: big.NewInt(0),
|
||||
CurieBlock: big.NewInt(0),
|
||||
DarwinTime: new(uint64),
|
||||
DarwinV2Time: new(uint64),
|
||||
EuclidTime: new(uint64),
|
||||
EuclidV2Time: new(uint64),
|
||||
FeynmanTime: new(uint64),
|
||||
Ethash: new(params.EthashConfig),
|
||||
}
|
||||
hashA = common.Hash{0x01}
|
||||
hashB = common.Hash{0x02}
|
||||
header = &types.Header{ParentHash: hashA, Number: big.NewInt(2), Difficulty: big.NewInt(0)}
|
||||
parent = &types.Header{ParentHash: hashB, Number: big.NewInt(1), Difficulty: big.NewInt(0)}
|
||||
coinbase = common.Address{}
|
||||
)
|
||||
test := func(statedb *state.StateDB) {
|
||||
statedb.SetNonce(params.HistoryStorageAddress, 1)
|
||||
statedb.SetCode(params.HistoryStorageAddress, params.HistoryStorageCode)
|
||||
statedb.IntermediateRoot(true)
|
||||
|
||||
vmContext := NewEVMBlockContext(header, nil, chainConfig, &coinbase)
|
||||
evm := vm.NewEVM(vmContext, vm.TxContext{}, statedb, chainConfig, vm.Config{})
|
||||
ProcessParentBlockHash(header.ParentHash, evm, statedb)
|
||||
|
||||
vmContext = NewEVMBlockContext(parent, nil, chainConfig, &coinbase)
|
||||
evm = vm.NewEVM(vmContext, vm.TxContext{}, statedb, chainConfig, vm.Config{})
|
||||
ProcessParentBlockHash(parent.ParentHash, evm, statedb)
|
||||
|
||||
// make sure that the state is correct
|
||||
if have := getParentBlockHash(statedb, 1); have != hashA {
|
||||
t.Errorf("want parent hash %v, have %v", hashA, have)
|
||||
}
|
||||
if have := getParentBlockHash(statedb, 0); have != hashB {
|
||||
t.Errorf("want parent hash %v, have %v", hashB, have)
|
||||
}
|
||||
}
|
||||
t.Run("MPT", func(t *testing.T) {
|
||||
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil)
|
||||
test(statedb)
|
||||
})
|
||||
}
|
||||
|
||||
func getParentBlockHash(statedb *state.StateDB, number uint64) common.Hash {
|
||||
ringIndex := number % params.HistoryServeWindow
|
||||
var key common.Hash
|
||||
binary.BigEndian.PutUint64(key[24:], ringIndex)
|
||||
return statedb.GetState(params.HistoryStorageAddress, key)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
215
core/tx_pool.go
215
core/tx_pool.go
|
|
@ -79,10 +79,6 @@ var (
|
|||
// with a different one without the required price bump.
|
||||
ErrReplaceUnderpriced = errors.New("replacement transaction underpriced")
|
||||
|
||||
// ErrAccountLimitExceeded is returned if a transaction would exceed the number
|
||||
// allowed by a pool for a single account.
|
||||
ErrAccountLimitExceeded = errors.New("account limit exceeded")
|
||||
|
||||
// ErrGasLimit is returned if a transaction's requested gas limit exceeds the
|
||||
// maximum allowance of the current block.
|
||||
ErrGasLimit = errors.New("exceeds block gas limit")
|
||||
|
|
@ -96,6 +92,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
|
||||
log.Debug("Reinjecting stale transactions", "count", len(reinject))
|
||||
if len(reinject) > 0 {
|
||||
log.Debug("Reinjecting stale transactions", "count", len(reinject))
|
||||
}
|
||||
senderCacher.recover(pool.signer, reinject)
|
||||
pool.addTxsLocked(reinject, false)
|
||||
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -74,6 +74,8 @@ func setDefaults(cfg *Config) {
|
|||
CurieBlock: new(big.Int),
|
||||
DarwinTime: new(uint64),
|
||||
DarwinV2Time: new(uint64),
|
||||
EuclidV2Time: new(uint64),
|
||||
FeynmanTime: new(uint64),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
14
core/vm/testdata/precompiles/bn256Pairing.json
vendored
14
core/vm/testdata/precompiles/bn256Pairing.json
vendored
|
|
@ -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",
|
||||
|
|
|
|||
45
eth/api.go
45
eth/api.go
|
|
@ -29,6 +29,8 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
"github.com/scroll-tech/go-ethereum/common/hexutil"
|
||||
"github.com/scroll-tech/go-ethereum/core"
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
2
go.mod
|
|
@ -51,7 +51,7 @@ require (
|
|||
github.com/prometheus/tsdb v0.7.1
|
||||
github.com/rjeczalik/notify v0.9.1
|
||||
github.com/rs/cors v1.7.0
|
||||
github.com/scroll-tech/da-codec v0.1.3-0.20250226072559-f8a8d3898f54
|
||||
github.com/scroll-tech/da-codec v0.1.3-0.20250313120912-344f2d5e33e1
|
||||
github.com/scroll-tech/zktrie v0.8.4
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible
|
||||
github.com/sourcegraph/conc v0.3.0
|
||||
|
|
|
|||
4
go.sum
4
go.sum
|
|
@ -396,8 +396,8 @@ github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncj
|
|||
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
|
||||
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/scroll-tech/da-codec v0.1.3-0.20250226072559-f8a8d3898f54 h1:qVpsVu1J91opTn6HYeuzWcBRVhQmPR8g05i+PlOjlI4=
|
||||
github.com/scroll-tech/da-codec v0.1.3-0.20250226072559-f8a8d3898f54/go.mod h1:xECEHZLVzbdUn+tNbRJhRIjLGTOTmnFQuTgUTeVLX58=
|
||||
github.com/scroll-tech/da-codec v0.1.3-0.20250313120912-344f2d5e33e1 h1:Dhd58LE1D+dnoxpgLVeQBMF9uweL/fhQfZHWtWSiOlE=
|
||||
github.com/scroll-tech/da-codec v0.1.3-0.20250313120912-344f2d5e33e1/go.mod h1:yhTS9OVC0xQGhg7DN5iV5KZJvnSIlFWAxDdp+6jxQtY=
|
||||
github.com/scroll-tech/zktrie v0.8.4 h1:UagmnZ4Z3ITCk+aUq9NQZJNAwnWl4gSxsLb2Nl7IgRE=
|
||||
github.com/scroll-tech/zktrie v0.8.4/go.mod h1:XvNo7vAk8yxNyTjBDj5WIiFzYW4bx/gJ78+NK6Zn6Uk=
|
||||
github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo=
|
||||
|
|
|
|||
|
|
@ -692,7 +692,6 @@ func (s *PublicBlockChainAPI) GetProof(ctx context.Context, address common.Addre
|
|||
storageHash = types.EmptyRootHash
|
||||
}
|
||||
keccakCodeHash := state.GetKeccakCodeHash(address)
|
||||
poseidonCodeHash := state.GetPoseidonCodeHash(address)
|
||||
storageProof := make([]StorageResult, len(storageKeys))
|
||||
|
||||
// if we have a storageTrie, (which means the account exists), we can update the storagehash
|
||||
|
|
@ -701,7 +700,6 @@ func (s *PublicBlockChainAPI) GetProof(ctx context.Context, address common.Addre
|
|||
} else {
|
||||
// no storageTrie means the account does not exist, so the codeHash is the hash of an empty bytearray.
|
||||
keccakCodeHash = codehash.EmptyKeccakCodeHash
|
||||
poseidonCodeHash = codehash.EmptyPoseidonCodeHash
|
||||
}
|
||||
|
||||
// create the proof for the storageKeys
|
||||
|
|
@ -723,17 +721,26 @@ func (s *PublicBlockChainAPI) GetProof(ctx context.Context, address common.Addre
|
|||
return nil, proofErr
|
||||
}
|
||||
|
||||
return &AccountResult{
|
||||
Address: address,
|
||||
AccountProof: toHexSlice(accountProof),
|
||||
Balance: (*hexutil.Big)(state.GetBalance(address)),
|
||||
KeccakCodeHash: keccakCodeHash,
|
||||
PoseidonCodeHash: poseidonCodeHash,
|
||||
CodeSize: hexutil.Uint64(state.GetCodeSize(address)),
|
||||
Nonce: hexutil.Uint64(state.GetNonce(address)),
|
||||
StorageHash: storageHash,
|
||||
StorageProof: storageProof,
|
||||
}, state.Error()
|
||||
result := &AccountResult{
|
||||
Address: address,
|
||||
AccountProof: toHexSlice(accountProof),
|
||||
Balance: (*hexutil.Big)(state.GetBalance(address)),
|
||||
KeccakCodeHash: keccakCodeHash,
|
||||
CodeSize: hexutil.Uint64(state.GetCodeSize(address)),
|
||||
Nonce: hexutil.Uint64(state.GetNonce(address)),
|
||||
StorageHash: storageHash,
|
||||
StorageProof: storageProof,
|
||||
}
|
||||
|
||||
if state.IsZktrie() {
|
||||
if storageTrie != nil {
|
||||
result.PoseidonCodeHash = state.GetPoseidonCodeHash(address)
|
||||
} else {
|
||||
result.PoseidonCodeHash = codehash.EmptyPoseidonCodeHash
|
||||
}
|
||||
}
|
||||
|
||||
return result, state.Error()
|
||||
}
|
||||
|
||||
// GetHeaderByNumber returns the requested canonical block header.
|
||||
|
|
@ -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
43
internal/ethapi/dbapi.go
Normal 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()
|
||||
}
|
||||
|
|
@ -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',
|
||||
}),
|
||||
]
|
||||
});
|
||||
`
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = ¶ms.CliqueConfig{Period: 1, Epoch: 30000}
|
||||
chainConfig.SystemContract = ¶ms.SystemContractConfig{Period: 1}
|
||||
|
||||
// init worker
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
cliqueEngine := clique.New(chainConfig.Clique, db)
|
||||
sysEngine := system_contract.New(context.Background(), chainConfig.SystemContract, &system_contract.FakeEthClient{Value: testBankAddress})
|
||||
engine := wrapper.NewUpgradableEngine(chainConfig.IsEuclidV2, cliqueEngine, sysEngine)
|
||||
w, b := newTestWorkerWithEmptyBlock(t, chainConfig, engine, db, 0)
|
||||
defer w.close()
|
||||
b.genesis.MustCommit(db)
|
||||
|
||||
// collect mined blocks
|
||||
sub := w.mux.Subscribe(core.NewMinedBlockEvent{})
|
||||
defer sub.Unsubscribe()
|
||||
w.start()
|
||||
|
||||
blocks := []*types.Block{}
|
||||
headers := []*types.Header{}
|
||||
|
||||
for i := 0; i < 6; i++ {
|
||||
select {
|
||||
case ev := <-sub.Chan():
|
||||
// activate EuclidV2 at next block
|
||||
if i == 2 {
|
||||
timeCount = int64(*chainConfig.EuclidV2Time)
|
||||
}
|
||||
|
||||
block := ev.Data.(core.NewMinedBlockEvent).Block
|
||||
blocks = append(blocks, block)
|
||||
headers = append(headers, block.Header())
|
||||
|
||||
case <-time.After(3 * time.Second):
|
||||
t.Fatalf("timeout")
|
||||
}
|
||||
}
|
||||
|
||||
// sanity check: we generated the EuclidV2 transition block
|
||||
assert.False(t, chainConfig.IsEuclidV2(headers[0].Time))
|
||||
assert.True(t, chainConfig.IsEuclidV2(headers[len(headers)-1].Time))
|
||||
|
||||
// import headers into new chain
|
||||
chainDb := rawdb.NewMemoryDatabase()
|
||||
b.genesis.MustCommit(chainDb)
|
||||
chain, err := core.NewBlockChain(chainDb, nil, b.chain.Config(), engine, vm.Config{}, nil, nil)
|
||||
assert.NoError(t, err)
|
||||
defer chain.Stop()
|
||||
|
||||
// previously this would fail with `unknown ancestor`
|
||||
_, err = chain.InsertHeaderChain(headers, 0)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// import headers into new chain
|
||||
chainDb = rawdb.NewMemoryDatabase()
|
||||
b.genesis.MustCommit(chainDb)
|
||||
chain, err = core.NewBlockChain(chainDb, nil, b.chain.Config(), engine, vm.Config{}, nil, nil)
|
||||
assert.NoError(t, err)
|
||||
defer chain.Stop()
|
||||
|
||||
// previously this would fail with `unknown ancestor`
|
||||
_, err = chain.InsertChain(blocks)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -328,20 +328,31 @@ 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,
|
||||
MaxTxPayloadBytesPerBlock: &ScrollMaxTxPayloadBytesPerBlock,
|
||||
FeeVaultAddress: &rcfg.ScrollFeeVaultAddress,
|
||||
L1Config: &L1Config{
|
||||
L1ChainId: 11155111,
|
||||
L1MessageQueueAddress: common.HexToAddress("0xF0B2293F5D834eAe920c6974D50957A1732de763"),
|
||||
NumL1MessagesPerBlock: 10,
|
||||
ScrollChainAddress: common.HexToAddress("0x2D567EcE699Eabe5afCd141eDB7A4f2D0D6ce8a0"),
|
||||
L1ChainId: 11155111,
|
||||
L1MessageQueueAddress: common.HexToAddress("0xF0B2293F5D834eAe920c6974D50957A1732de763"),
|
||||
L1MessageQueueV2Address: common.HexToAddress("0xA0673eC0A48aa924f067F1274EcD281A10c5f19F"),
|
||||
L1MessageQueueV2DeploymentBlock: 7773746,
|
||||
NumL1MessagesPerBlock: 10,
|
||||
ScrollChainAddress: common.HexToAddress("0x2D567EcE699Eabe5afCd141eDB7A4f2D0D6ce8a0"),
|
||||
L2SystemConfigAddress: common.HexToAddress("0xF444cF06A3E3724e20B35c2989d3942ea8b59124"),
|
||||
},
|
||||
GenesisStateRoot: &ScrollSepoliaGenesisState,
|
||||
},
|
||||
|
|
@ -369,20 +380,31 @@ 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,
|
||||
MaxTxPayloadBytesPerBlock: &ScrollMaxTxPayloadBytesPerBlock,
|
||||
FeeVaultAddress: &rcfg.ScrollFeeVaultAddress,
|
||||
L1Config: &L1Config{
|
||||
L1ChainId: 1,
|
||||
L1MessageQueueAddress: common.HexToAddress("0x0d7E906BD9cAFa154b048cFa766Cc1E54E39AF9B"),
|
||||
NumL1MessagesPerBlock: 10,
|
||||
ScrollChainAddress: common.HexToAddress("0xa13BAF47339d63B743e7Da8741db5456DAc1E556"),
|
||||
L1ChainId: 1,
|
||||
L1MessageQueueAddress: common.HexToAddress("0x0d7E906BD9cAFa154b048cFa766Cc1E54E39AF9B"),
|
||||
L1MessageQueueV2Address: common.HexToAddress("0x56971da63A3C0205184FEF096E9ddFc7A8C2D18a"),
|
||||
L1MessageQueueV2DeploymentBlock: 22088276,
|
||||
NumL1MessagesPerBlock: 10,
|
||||
ScrollChainAddress: common.HexToAddress("0xa13BAF47339d63B743e7Da8741db5456DAc1E556"),
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
54
rollup/l2_system_config/abi.go
Normal file
54
rollup/l2_system_config/abi.go
Normal 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
|
||||
}
|
||||
51
rollup/l2_system_config/abi_test.go
Normal file
51
rollup/l2_system_config/abi_test.go
Normal 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)
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue