Merge branch 'ethereum:master' into patch-1

This commit is contained in:
DAVID OKEAMAH 2024-10-14 20:52:05 +01:00 committed by GitHub
commit 96f5a1244a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
299 changed files with 12823 additions and 2133 deletions

1
.github/CODEOWNERS vendored
View file

@ -20,5 +20,6 @@ les/ @zsfelfoldi @rjl493456442
light/ @zsfelfoldi @rjl493456442 light/ @zsfelfoldi @rjl493456442
node/ @fjl node/ @fjl
p2p/ @fjl @zsfelfoldi p2p/ @fjl @zsfelfoldi
params/ @fjl @holiman @karalabe @gballet @rjl493456442 @zsfelfoldi
rpc/ @fjl @holiman rpc/ @fjl @holiman
signer/ @holiman signer/ @holiman

View file

@ -21,10 +21,14 @@ linters:
- staticcheck - staticcheck
- bidichk - bidichk
- durationcheck - durationcheck
- exportloopref - copyloopvar
- whitespace - whitespace
- revive # only certain checks enabled - revive # only certain checks enabled
- durationcheck
- gocheckcompilerdirectives
- reassign
- mirror
- tenv
### linters we tried and will not be using: ### linters we tried and will not be using:
### ###
# - structcheck # lots of false positives # - structcheck # lots of false positives

View file

@ -9,8 +9,7 @@ jobs:
- azure-osx - azure-osx
include: include:
# These builders create the Docker sub-images for multi-arch push and each # This builder create and push the Docker images for all architectures
# will attempt to push the multi-arch image if they are the last builder
- stage: build - stage: build
if: type = push if: type = push
os: linux os: linux
@ -26,24 +25,7 @@ jobs:
before_install: before_install:
- export DOCKER_CLI_EXPERIMENTAL=enabled - export DOCKER_CLI_EXPERIMENTAL=enabled
script: script:
- go run build/ci.go docker -image -manifest amd64,arm64 -upload ethereum/client-go - go run build/ci.go dockerx -platform "linux/amd64,linux/arm64" -upload ethereum/client-go
- stage: build
if: type = push
os: linux
arch: arm64
dist: focal
go: 1.23.x
env:
- docker
services:
- docker
git:
submodules: false # avoid cloning ethereum/tests
before_install:
- export DOCKER_CLI_EXPERIMENTAL=enabled
script:
- go run build/ci.go docker -image -manifest amd64,arm64 -upload ethereum/client-go
# This builder does the Linux Azure uploads # This builder does the Linux Azure uploads
- stage: build - stage: build
@ -85,12 +67,13 @@ jobs:
if: type = push if: type = push
os: osx os: osx
osx_image: xcode14.2 osx_image: xcode14.2
go: 1.23.x go: 1.23.1 # See https://github.com/ethereum/go-ethereum/pull/30478
env: env:
- azure-osx - azure-osx
git: git:
submodules: false # avoid cloning ethereum/tests submodules: false # avoid cloning ethereum/tests
script: script:
- ln -sf /Users/travis/gopath/bin/go1.23.1 /usr/local/bin/go # Work around travis go-setup bug
- go run build/ci.go install -dlgo - go run build/ci.go install -dlgo
- go run build/ci.go archive -type tar -signer OSX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds - go run build/ci.go archive -type tar -signer OSX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds
- go run build/ci.go install -dlgo -arch arm64 - go run build/ci.go install -dlgo -arch arm64

View file

@ -4,7 +4,7 @@ ARG VERSION=""
ARG BUILDNUM="" ARG BUILDNUM=""
# Build Geth in a stock Go builder container # Build Geth in a stock Go builder container
FROM golang:1.23-alpine as builder FROM golang:1.23-alpine AS builder
RUN apk add --no-cache gcc musl-dev linux-headers git RUN apk add --no-cache gcc musl-dev linux-headers git

View file

@ -4,7 +4,7 @@ ARG VERSION=""
ARG BUILDNUM="" ARG BUILDNUM=""
# Build Geth in a stock Go builder container # Build Geth in a stock Go builder container
FROM golang:1.23-alpine as builder FROM golang:1.23-alpine AS builder
RUN apk add --no-cache gcc musl-dev linux-headers git RUN apk add --no-cache gcc musl-dev linux-headers git
@ -14,6 +14,13 @@ COPY go.sum /go-ethereum/
RUN cd /go-ethereum && go mod download RUN cd /go-ethereum && go mod download
ADD . /go-ethereum ADD . /go-ethereum
# This is not strictly necessary, but it matches the "Dockerfile" steps, thus
# makes it so that under certain circumstances, the docker layer can be cached,
# and the builder can jump to the next (build all) command, with the go cache fully loaded.
#
RUN cd /go-ethereum && go run build/ci.go install -static ./cmd/geth
RUN cd /go-ethereum && go run build/ci.go install -static RUN cd /go-ethereum && go run build/ci.go install -static
# Pull all binaries into a second stage deploy alpine container # Pull all binaries into a second stage deploy alpine container

View file

@ -1199,7 +1199,6 @@ func TestUnpackRevert(t *testing.T) {
{"4e487b7100000000000000000000000000000000000000000000000000000000000000ff", "unknown panic code: 0xff", nil}, {"4e487b7100000000000000000000000000000000000000000000000000000000000000ff", "unknown panic code: 0xff", nil},
} }
for index, c := range cases { for index, c := range cases {
index, c := index, c
t.Run(fmt.Sprintf("case %d", index), func(t *testing.T) { t.Run(fmt.Sprintf("case %d", index), func(t *testing.T) {
t.Parallel() t.Parallel()
got, err := UnpackRevert(common.Hex2Bytes(c.input)) got, err := UnpackRevert(common.Hex2Bytes(c.input))

View file

@ -252,7 +252,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
} }
// Parse library references. // Parse library references.
for pattern, name := range libs { for pattern, name := range libs {
matched, err := regexp.Match("__\\$"+pattern+"\\$__", []byte(contracts[types[i]].InputBin)) matched, err := regexp.MatchString("__\\$"+pattern+"\\$__", contracts[types[i]].InputBin)
if err != nil { if err != nil {
log.Error("Could not search for pattern", "pattern", pattern, "contract", contracts[types[i]], "err", err) log.Error("Could not search for pattern", "pattern", pattern, "contract", contracts[types[i]], "err", err)
} }

View file

@ -331,7 +331,6 @@ func TestEventTupleUnpack(t *testing.T) {
for _, tc := range testCases { for _, tc := range testCases {
assert := assert.New(t) assert := assert.New(t)
tc := tc
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
err := unpackTestEventData(tc.dest, tc.data, tc.jsonLog, assert) err := unpackTestEventData(tc.dest, tc.data, tc.jsonLog, assert)
if tc.error == "" { if tc.error == "" {

View file

@ -34,7 +34,6 @@ import (
func TestPack(t *testing.T) { func TestPack(t *testing.T) {
t.Parallel() t.Parallel()
for i, test := range packUnpackTests { for i, test := range packUnpackTests {
i, test := i, test
t.Run(strconv.Itoa(i), func(t *testing.T) { t.Run(strconv.Itoa(i), func(t *testing.T) {
t.Parallel() t.Parallel()
encb, err := hex.DecodeString(test.packed) encb, err := hex.DecodeString(test.packed)

View file

@ -172,7 +172,6 @@ var reflectTests = []reflectTest{
func TestReflectNameToStruct(t *testing.T) { func TestReflectNameToStruct(t *testing.T) {
t.Parallel() t.Parallel()
for _, test := range reflectTests { for _, test := range reflectTests {
test := test
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
t.Parallel() t.Parallel()
m, err := mapArgNamesToStructFields(test.args, reflect.ValueOf(test.struc)) m, err := mapArgNamesToStructFields(test.args, reflect.ValueOf(test.struc))

View file

@ -137,7 +137,6 @@ func TestMakeTopics(t *testing.T) {
}, },
} }
for _, tt := range tests { for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
t.Parallel() t.Parallel()
got, err := MakeTopics(tt.args.query...) got, err := MakeTopics(tt.args.query...)
@ -373,7 +372,6 @@ func TestParseTopics(t *testing.T) {
tests := setupTopicsTests() tests := setupTopicsTests()
for _, tt := range tests { for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
t.Parallel() t.Parallel()
createObj := tt.args.createObj() createObj := tt.args.createObj()
@ -393,7 +391,6 @@ func TestParseTopicsIntoMap(t *testing.T) {
tests := setupTopicsTests() tests := setupTopicsTests()
for _, tt := range tests { for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
t.Parallel() t.Parallel()
outMap := make(map[string]interface{}) outMap := make(map[string]interface{})

View file

@ -389,7 +389,6 @@ func TestMethodMultiReturn(t *testing.T) {
"Can not unpack into a slice with wrong types", "Can not unpack into a slice with wrong types",
}} }}
for _, tc := range testCases { for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
require := require.New(t) require := require.New(t)
err := abi.UnpackIntoInterface(tc.dest, "multi", data) err := abi.UnpackIntoInterface(tc.dest, "multi", data)
@ -947,7 +946,7 @@ func TestOOMMaliciousInput(t *testing.T) {
} }
encb, err := hex.DecodeString(test.enc) encb, err := hex.DecodeString(test.enc)
if err != nil { if err != nil {
t.Fatalf("invalid hex: %s" + test.enc) t.Fatalf("invalid hex: %s", test.enc)
} }
_, err = abi.Methods["method"].Outputs.UnpackValues(encb) _, err = abi.Methods["method"].Outputs.UnpackValues(encb)
if err == nil { if err == nil {

View file

@ -92,7 +92,7 @@ func (ec *engineClient) updateLoop(headCh <-chan types.ChainHeadEvent) {
} }
func (ec *engineClient) callNewPayload(fork string, event types.ChainHeadEvent) (string, error) { func (ec *engineClient) callNewPayload(fork string, event types.ChainHeadEvent) (string, error) {
execData := engine.BlockToExecutableData(event.Block, nil, nil).ExecutionPayload execData := engine.BlockToExecutableData(event.Block, nil, nil, nil).ExecutionPayload
var ( var (
method string method string

View file

@ -34,7 +34,6 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) {
Withdrawals []*types.Withdrawal `json:"withdrawals"` Withdrawals []*types.Withdrawal `json:"withdrawals"`
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"` BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"` ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
Deposits types.Deposits `json:"depositRequests"`
ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"` ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"`
} }
var enc ExecutableData var enc ExecutableData
@ -60,7 +59,6 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) {
enc.Withdrawals = e.Withdrawals enc.Withdrawals = e.Withdrawals
enc.BlobGasUsed = (*hexutil.Uint64)(e.BlobGasUsed) enc.BlobGasUsed = (*hexutil.Uint64)(e.BlobGasUsed)
enc.ExcessBlobGas = (*hexutil.Uint64)(e.ExcessBlobGas) enc.ExcessBlobGas = (*hexutil.Uint64)(e.ExcessBlobGas)
enc.Deposits = e.Deposits
enc.ExecutionWitness = e.ExecutionWitness enc.ExecutionWitness = e.ExecutionWitness
return json.Marshal(&enc) return json.Marshal(&enc)
} }
@ -85,7 +83,6 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error {
Withdrawals []*types.Withdrawal `json:"withdrawals"` Withdrawals []*types.Withdrawal `json:"withdrawals"`
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"` BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"` ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
Deposits *types.Deposits `json:"depositRequests"`
ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"` ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"`
} }
var dec ExecutableData var dec ExecutableData
@ -160,9 +157,6 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error {
if dec.ExcessBlobGas != nil { if dec.ExcessBlobGas != nil {
e.ExcessBlobGas = (*uint64)(dec.ExcessBlobGas) e.ExcessBlobGas = (*uint64)(dec.ExcessBlobGas)
} }
if dec.Deposits != nil {
e.Deposits = *dec.Deposits
}
if dec.ExecutionWitness != nil { if dec.ExecutionWitness != nil {
e.ExecutionWitness = dec.ExecutionWitness e.ExecutionWitness = dec.ExecutionWitness
} }

View file

@ -18,13 +18,22 @@ func (e ExecutionPayloadEnvelope) MarshalJSON() ([]byte, error) {
ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"` ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"`
BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"` BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"`
BlobsBundle *BlobsBundleV1 `json:"blobsBundle"` BlobsBundle *BlobsBundleV1 `json:"blobsBundle"`
Requests []hexutil.Bytes `json:"executionRequests"`
Override bool `json:"shouldOverrideBuilder"` Override bool `json:"shouldOverrideBuilder"`
Witness *hexutil.Bytes `json:"witness"`
} }
var enc ExecutionPayloadEnvelope var enc ExecutionPayloadEnvelope
enc.ExecutionPayload = e.ExecutionPayload enc.ExecutionPayload = e.ExecutionPayload
enc.BlockValue = (*hexutil.Big)(e.BlockValue) enc.BlockValue = (*hexutil.Big)(e.BlockValue)
enc.BlobsBundle = e.BlobsBundle enc.BlobsBundle = e.BlobsBundle
if e.Requests != nil {
enc.Requests = make([]hexutil.Bytes, len(e.Requests))
for k, v := range e.Requests {
enc.Requests[k] = v
}
}
enc.Override = e.Override enc.Override = e.Override
enc.Witness = e.Witness
return json.Marshal(&enc) return json.Marshal(&enc)
} }
@ -34,7 +43,9 @@ func (e *ExecutionPayloadEnvelope) UnmarshalJSON(input []byte) error {
ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"` ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"`
BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"` BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"`
BlobsBundle *BlobsBundleV1 `json:"blobsBundle"` BlobsBundle *BlobsBundleV1 `json:"blobsBundle"`
Requests []hexutil.Bytes `json:"executionRequests"`
Override *bool `json:"shouldOverrideBuilder"` Override *bool `json:"shouldOverrideBuilder"`
Witness *hexutil.Bytes `json:"witness"`
} }
var dec ExecutionPayloadEnvelope var dec ExecutionPayloadEnvelope
if err := json.Unmarshal(input, &dec); err != nil { if err := json.Unmarshal(input, &dec); err != nil {
@ -51,8 +62,17 @@ func (e *ExecutionPayloadEnvelope) UnmarshalJSON(input []byte) error {
if dec.BlobsBundle != nil { if dec.BlobsBundle != nil {
e.BlobsBundle = dec.BlobsBundle e.BlobsBundle = dec.BlobsBundle
} }
if dec.Requests != nil {
e.Requests = make([][]byte, len(dec.Requests))
for k, v := range dec.Requests {
e.Requests[k] = v
}
}
if dec.Override != nil { if dec.Override != nil {
e.Override = *dec.Override e.Override = *dec.Override
} }
if dec.Witness != nil {
e.Witness = dec.Witness
}
return nil return nil
} }

View file

@ -36,7 +36,6 @@ var (
PayloadV1 PayloadVersion = 0x1 PayloadV1 PayloadVersion = 0x1
PayloadV2 PayloadVersion = 0x2 PayloadV2 PayloadVersion = 0x2
PayloadV3 PayloadVersion = 0x3 PayloadV3 PayloadVersion = 0x3
PayloadV4 PayloadVersion = 0x4
) )
//go:generate go run github.com/fjl/gencodec -type PayloadAttributes -field-override payloadAttributesMarshaling -out gen_blockparams.go //go:generate go run github.com/fjl/gencodec -type PayloadAttributes -field-override payloadAttributesMarshaling -out gen_blockparams.go
@ -77,7 +76,6 @@ type ExecutableData struct {
Withdrawals []*types.Withdrawal `json:"withdrawals"` Withdrawals []*types.Withdrawal `json:"withdrawals"`
BlobGasUsed *uint64 `json:"blobGasUsed"` BlobGasUsed *uint64 `json:"blobGasUsed"`
ExcessBlobGas *uint64 `json:"excessBlobGas"` ExcessBlobGas *uint64 `json:"excessBlobGas"`
Deposits types.Deposits `json:"depositRequests"`
ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"` ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"`
} }
@ -95,13 +93,23 @@ type executableDataMarshaling struct {
ExcessBlobGas *hexutil.Uint64 ExcessBlobGas *hexutil.Uint64
} }
// StatelessPayloadStatusV1 is the result of a stateless payload execution.
type StatelessPayloadStatusV1 struct {
Status string `json:"status"`
StateRoot common.Hash `json:"stateRoot"`
ReceiptsRoot common.Hash `json:"receiptsRoot"`
ValidationError *string `json:"validationError"`
}
//go:generate go run github.com/fjl/gencodec -type ExecutionPayloadEnvelope -field-override executionPayloadEnvelopeMarshaling -out gen_epe.go //go:generate go run github.com/fjl/gencodec -type ExecutionPayloadEnvelope -field-override executionPayloadEnvelopeMarshaling -out gen_epe.go
type ExecutionPayloadEnvelope struct { type ExecutionPayloadEnvelope struct {
ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"` ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"`
BlockValue *big.Int `json:"blockValue" gencodec:"required"` BlockValue *big.Int `json:"blockValue" gencodec:"required"`
BlobsBundle *BlobsBundleV1 `json:"blobsBundle"` BlobsBundle *BlobsBundleV1 `json:"blobsBundle"`
Requests [][]byte `json:"executionRequests"`
Override bool `json:"shouldOverrideBuilder"` Override bool `json:"shouldOverrideBuilder"`
Witness *hexutil.Bytes `json:"witness"`
} }
type BlobsBundleV1 struct { type BlobsBundleV1 struct {
@ -113,12 +121,14 @@ type BlobsBundleV1 struct {
// JSON type overrides for ExecutionPayloadEnvelope. // JSON type overrides for ExecutionPayloadEnvelope.
type executionPayloadEnvelopeMarshaling struct { type executionPayloadEnvelopeMarshaling struct {
BlockValue *hexutil.Big BlockValue *hexutil.Big
Requests []hexutil.Bytes
} }
type PayloadStatusV1 struct { type PayloadStatusV1 struct {
Status string `json:"status"` Status string `json:"status"`
LatestValidHash *common.Hash `json:"latestValidHash"` Witness *hexutil.Bytes `json:"witness"`
ValidationError *string `json:"validationError"` LatestValidHash *common.Hash `json:"latestValidHash"`
ValidationError *string `json:"validationError"`
} }
type TransitionConfigurationV1 struct { type TransitionConfigurationV1 struct {
@ -198,7 +208,21 @@ func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) {
// and that the blockhash of the constructed block matches the parameters. Nil // and that the blockhash of the constructed block matches the parameters. Nil
// Withdrawals value will propagate through the returned block. Empty // Withdrawals value will propagate through the returned block. Empty
// Withdrawals value must be passed via non-nil, length 0 value in data. // Withdrawals value must be passed via non-nil, length 0 value in data.
func ExecutableDataToBlock(data ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (*types.Block, error) { func ExecutableDataToBlock(data ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, requests [][]byte) (*types.Block, error) {
block, err := ExecutableDataToBlockNoHash(data, versionedHashes, beaconRoot, requests)
if err != nil {
return nil, err
}
if block.Hash() != data.BlockHash {
return nil, fmt.Errorf("blockhash mismatch, want %x, got %x", data.BlockHash, block.Hash())
}
return block, nil
}
// ExecutableDataToBlockNoHash is analogous to ExecutableDataToBlock, but is used
// for stateless execution, so it skips checking if the executable data hashes to
// the requested hash (stateless has to *compute* the root hash, it's not given).
func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, requests [][]byte) (*types.Block, error) {
txs, err := decodeTransactions(data.Transactions) txs, err := decodeTransactions(data.Transactions)
if err != nil { if err != nil {
return nil, err return nil, err
@ -233,19 +257,21 @@ func ExecutableDataToBlock(data ExecutableData, versionedHashes []common.Hash, b
h := types.DeriveSha(types.Withdrawals(data.Withdrawals), trie.NewStackTrie(nil)) h := types.DeriveSha(types.Withdrawals(data.Withdrawals), trie.NewStackTrie(nil))
withdrawalsRoot = &h withdrawalsRoot = &h
} }
// Compute requestsHash if any requests are non-nil.
var ( var requestsHash *common.Hash
requestsHash *common.Hash if requests != nil {
requests types.Requests // Put back request type byte.
) typedRequests := make([][]byte, len(requests))
if data.Deposits != nil { for i, reqdata := range requests {
requests = make(types.Requests, 0) typedReqdata := make([]byte, len(reqdata)+1)
for _, d := range data.Deposits { typedReqdata[0] = byte(i)
requests = append(requests, types.NewRequest(d)) copy(typedReqdata[1:], reqdata)
typedRequests[i] = typedReqdata
} }
h := types.DeriveSha(requests, trie.NewStackTrie(nil)) h := types.CalcRequestsHash(typedRequests)
requestsHash = &h requestsHash = &h
} }
header := &types.Header{ header := &types.Header{
ParentHash: data.ParentHash, ParentHash: data.ParentHash,
UncleHash: types.EmptyUncleHash, UncleHash: types.EmptyUncleHash,
@ -268,18 +294,15 @@ func ExecutableDataToBlock(data ExecutableData, versionedHashes []common.Hash, b
ParentBeaconRoot: beaconRoot, ParentBeaconRoot: beaconRoot,
RequestsHash: requestsHash, RequestsHash: requestsHash,
} }
block := types.NewBlockWithHeader(header) return types.NewBlockWithHeader(header).
block = block.WithBody(types.Body{Transactions: txs, Uncles: nil, Withdrawals: data.Withdrawals, Requests: requests}) WithBody(types.Body{Transactions: txs, Uncles: nil, Withdrawals: data.Withdrawals}).
block = block.WithWitness(data.ExecutionWitness) WithWitness(data.ExecutionWitness),
if block.Hash() != data.BlockHash { nil
return nil, fmt.Errorf("blockhash mismatch, want %x, got %x", data.BlockHash, block.Hash())
}
return block, nil
} }
// BlockToExecutableData constructs the ExecutableData structure by filling the // BlockToExecutableData constructs the ExecutableData structure by filling the
// fields from the given block. It assumes the given block is post-merge block. // fields from the given block. It assumes the given block is post-merge block.
func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types.BlobTxSidecar) *ExecutionPayloadEnvelope { func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types.BlobTxSidecar, requests [][]byte) *ExecutionPayloadEnvelope {
data := &ExecutableData{ data := &ExecutableData{
BlockHash: block.Hash(), BlockHash: block.Hash(),
ParentHash: block.ParentHash(), ParentHash: block.ParentHash(),
@ -300,6 +323,8 @@ func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types.
ExcessBlobGas: block.ExcessBlobGas(), ExcessBlobGas: block.ExcessBlobGas(),
ExecutionWitness: block.ExecutionWitness(), ExecutionWitness: block.ExecutionWitness(),
} }
// Add blobs.
bundle := BlobsBundleV1{ bundle := BlobsBundleV1{
Commitments: make([]hexutil.Bytes, 0), Commitments: make([]hexutil.Bytes, 0),
Blobs: make([]hexutil.Bytes, 0), Blobs: make([]hexutil.Bytes, 0),
@ -312,30 +337,29 @@ func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types.
bundle.Proofs = append(bundle.Proofs, hexutil.Bytes(sidecar.Proofs[j][:])) bundle.Proofs = append(bundle.Proofs, hexutil.Bytes(sidecar.Proofs[j][:]))
} }
} }
setRequests(block.Requests(), data)
return &ExecutionPayloadEnvelope{ExecutionPayload: data, BlockValue: fees, BlobsBundle: &bundle, Override: false}
}
// setRequests differentiates the different request types and // Remove type byte in requests.
// assigns them to the associated fields in ExecutableData. var plainRequests [][]byte
func setRequests(requests types.Requests, data *ExecutableData) {
if requests != nil { if requests != nil {
// If requests is non-nil, it means deposits are available in block and we plainRequests = make([][]byte, len(requests))
// should return an empty slice instead of nil if there are no deposits. for i, reqdata := range requests {
data.Deposits = make(types.Deposits, 0) plainRequests[i] = reqdata[1:]
}
for _, r := range requests {
if d, ok := r.Inner().(*types.Deposit); ok {
data.Deposits = append(data.Deposits, d)
} }
} }
return &ExecutionPayloadEnvelope{
ExecutionPayload: data,
BlockValue: fees,
BlobsBundle: &bundle,
Requests: plainRequests,
Override: false,
}
} }
// ExecutionPayloadBody is used in the response to GetPayloadBodiesByHash and GetPayloadBodiesByRange // ExecutionPayloadBody is used in the response to GetPayloadBodiesByHash and GetPayloadBodiesByRange
type ExecutionPayloadBody struct { type ExecutionPayloadBody struct {
TransactionData []hexutil.Bytes `json:"transactions"` TransactionData []hexutil.Bytes `json:"transactions"`
Withdrawals []*types.Withdrawal `json:"withdrawals"` Withdrawals []*types.Withdrawal `json:"withdrawals"`
Deposits types.Deposits `json:"depositRequests"`
} }
// Client identifiers to support ClientVersionV1. // Client identifiers to support ClientVersionV1.

View file

@ -24,6 +24,7 @@ import (
"io" "io"
"net/http" "net/http"
"net/url" "net/url"
"strconv"
"sync" "sync"
"time" "time"
@ -121,8 +122,8 @@ func NewBeaconLightApi(url string, customHeaders map[string]string) *BeaconLight
} }
} }
func (api *BeaconLightApi) httpGet(path string) ([]byte, error) { func (api *BeaconLightApi) httpGet(path string, params url.Values) ([]byte, error) {
uri, err := api.buildURL(path, nil) uri, err := api.buildURL(path, params)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -150,17 +151,16 @@ func (api *BeaconLightApi) httpGet(path string) ([]byte, error) {
} }
} }
func (api *BeaconLightApi) httpGetf(format string, params ...any) ([]byte, error) {
return api.httpGet(fmt.Sprintf(format, params...))
}
// GetBestUpdatesAndCommittees fetches and validates LightClientUpdate for given // GetBestUpdatesAndCommittees fetches and validates LightClientUpdate for given
// period and full serialized committee for the next period (committee root hash // period and full serialized committee for the next period (committee root hash
// equals update.NextSyncCommitteeRoot). // equals update.NextSyncCommitteeRoot).
// Note that the results are validated but the update signature should be verified // Note that the results are validated but the update signature should be verified
// by the caller as its validity depends on the update chain. // by the caller as its validity depends on the update chain.
func (api *BeaconLightApi) GetBestUpdatesAndCommittees(firstPeriod, count uint64) ([]*types.LightClientUpdate, []*types.SerializedSyncCommittee, error) { func (api *BeaconLightApi) GetBestUpdatesAndCommittees(firstPeriod, count uint64) ([]*types.LightClientUpdate, []*types.SerializedSyncCommittee, error) {
resp, err := api.httpGetf("/eth/v1/beacon/light_client/updates?start_period=%d&count=%d", firstPeriod, count) resp, err := api.httpGet("/eth/v1/beacon/light_client/updates", map[string][]string{
"start_period": {strconv.FormatUint(firstPeriod, 10)},
"count": {strconv.FormatUint(count, 10)},
})
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -197,7 +197,7 @@ func (api *BeaconLightApi) GetBestUpdatesAndCommittees(firstPeriod, count uint64
// See data structure definition here: // See data structure definition here:
// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientoptimisticupdate // https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientoptimisticupdate
func (api *BeaconLightApi) GetOptimisticUpdate() (types.OptimisticUpdate, error) { func (api *BeaconLightApi) GetOptimisticUpdate() (types.OptimisticUpdate, error) {
resp, err := api.httpGet("/eth/v1/beacon/light_client/optimistic_update") resp, err := api.httpGet("/eth/v1/beacon/light_client/optimistic_update", nil)
if err != nil { if err != nil {
return types.OptimisticUpdate{}, err return types.OptimisticUpdate{}, err
} }
@ -250,7 +250,7 @@ func decodeOptimisticUpdate(enc []byte) (types.OptimisticUpdate, error) {
// See data structure definition here: // See data structure definition here:
// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientfinalityupdate // https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientfinalityupdate
func (api *BeaconLightApi) GetFinalityUpdate() (types.FinalityUpdate, error) { func (api *BeaconLightApi) GetFinalityUpdate() (types.FinalityUpdate, error) {
resp, err := api.httpGet("/eth/v1/beacon/light_client/finality_update") resp, err := api.httpGet("/eth/v1/beacon/light_client/finality_update", nil)
if err != nil { if err != nil {
return types.FinalityUpdate{}, err return types.FinalityUpdate{}, err
} }
@ -316,7 +316,7 @@ func (api *BeaconLightApi) GetHeader(blockRoot common.Hash) (types.Header, bool,
} else { } else {
blockId = blockRoot.Hex() blockId = blockRoot.Hex()
} }
resp, err := api.httpGetf("/eth/v1/beacon/headers/%s", blockId) resp, err := api.httpGet(fmt.Sprintf("/eth/v1/beacon/headers/%s", blockId), nil)
if err != nil { if err != nil {
return types.Header{}, false, false, err return types.Header{}, false, false, err
} }
@ -347,7 +347,7 @@ func (api *BeaconLightApi) GetHeader(blockRoot common.Hash) (types.Header, bool,
// GetCheckpointData fetches and validates bootstrap data belonging to the given checkpoint. // GetCheckpointData fetches and validates bootstrap data belonging to the given checkpoint.
func (api *BeaconLightApi) GetCheckpointData(checkpointHash common.Hash) (*types.BootstrapData, error) { func (api *BeaconLightApi) GetCheckpointData(checkpointHash common.Hash) (*types.BootstrapData, error) {
resp, err := api.httpGetf("/eth/v1/beacon/light_client/bootstrap/0x%x", checkpointHash[:]) resp, err := api.httpGet(fmt.Sprintf("/eth/v1/beacon/light_client/bootstrap/0x%x", checkpointHash[:]), nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -389,7 +389,7 @@ func (api *BeaconLightApi) GetCheckpointData(checkpointHash common.Hash) (*types
} }
func (api *BeaconLightApi) GetBeaconBlock(blockRoot common.Hash) (*types.BeaconBlock, error) { func (api *BeaconLightApi) GetBeaconBlock(blockRoot common.Hash) (*types.BeaconBlock, error) {
resp, err := api.httpGetf("/eth/v2/beacon/blocks/0x%x", blockRoot) resp, err := api.httpGet(fmt.Sprintf("/eth/v2/beacon/blocks/0x%x", blockRoot), nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -69,12 +69,13 @@ func (h *HeadTracker) ValidatedFinality() (types.FinalityUpdate, bool) {
// slot or same slot and more signers) then ValidatedOptimistic is updated. // slot or same slot and more signers) then ValidatedOptimistic is updated.
// The boolean return flag signals if ValidatedOptimistic has been changed. // The boolean return flag signals if ValidatedOptimistic has been changed.
func (h *HeadTracker) ValidateOptimistic(update types.OptimisticUpdate) (bool, error) { func (h *HeadTracker) ValidateOptimistic(update types.OptimisticUpdate) (bool, error) {
h.lock.Lock()
defer h.lock.Unlock()
if err := update.Validate(); err != nil { if err := update.Validate(); err != nil {
return false, err return false, err
} }
h.lock.Lock()
defer h.lock.Unlock()
replace, err := h.validate(update.SignedHeader(), h.optimisticUpdate.SignedHeader()) replace, err := h.validate(update.SignedHeader(), h.optimisticUpdate.SignedHeader())
if replace { if replace {
h.optimisticUpdate, h.hasOptimisticUpdate = update, true h.optimisticUpdate, h.hasOptimisticUpdate = update, true
@ -88,12 +89,13 @@ func (h *HeadTracker) ValidateOptimistic(update types.OptimisticUpdate) (bool, e
// slot or same slot and more signers) then ValidatedFinality is updated. // slot or same slot and more signers) then ValidatedFinality is updated.
// The boolean return flag signals if ValidatedFinality has been changed. // The boolean return flag signals if ValidatedFinality has been changed.
func (h *HeadTracker) ValidateFinality(update types.FinalityUpdate) (bool, error) { func (h *HeadTracker) ValidateFinality(update types.FinalityUpdate) (bool, error) {
h.lock.Lock()
defer h.lock.Unlock()
if err := update.Validate(); err != nil { if err := update.Validate(); err != nil {
return false, err return false, err
} }
h.lock.Lock()
defer h.lock.Unlock()
replace, err := h.validate(update.SignedHeader(), h.finalityUpdate.SignedHeader()) replace, err := h.validate(update.SignedHeader(), h.finalityUpdate.SignedHeader())
if replace { if replace {
h.finalityUpdate, h.hasFinalityUpdate = update, true h.finalityUpdate, h.hasFinalityUpdate = update, true

View file

@ -5,88 +5,88 @@
# https://github.com/ethereum/execution-spec-tests/releases/download/v2.1.0/ # https://github.com/ethereum/execution-spec-tests/releases/download/v2.1.0/
ca89c76851b0900bfcc3cbb9a26cbece1f3d7c64a3bed38723e914713290df6c fixtures_develop.tar.gz ca89c76851b0900bfcc3cbb9a26cbece1f3d7c64a3bed38723e914713290df6c fixtures_develop.tar.gz
# version:golang 1.23.1 # version:golang 1.23.2
# https://go.dev/dl/ # https://go.dev/dl/
6ee44e298379d146a5e5aa6b1c5b5d5f5d0a3365eabdd70741e6e21340ec3b0d go1.23.1.src.tar.gz 36930162a93df417d90bd22c6e14daff4705baac2b02418edda671cdfa9cd07f go1.23.2.src.tar.gz
f17f2791717c15728ec63213a014e244c35f9c8846fb29f5a1b63d0c0556f756 go1.23.1.aix-ppc64.tar.gz 025d77f1780906142023a364c31a572afd7d56d3a3be1e4e562e367ca88d3267 go1.23.2.freebsd-amd64.tar.gz
dd9e772686ed908bcff94b6144322d4e2473a7dcd7c696b7e8b6d12f23c887fd go1.23.1.darwin-amd64.pkg 0d50bade977b84e173cb350946087f5de8c75f8df19456c3b60c5d58e186089d go1.23.2.windows-arm64.zip
488d9e4ca3e3ed513ee4edd91bef3a2360c65fa6d6be59cf79640bf840130a58 go1.23.1.darwin-amd64.tar.gz 0edd985dbd6de64d9c88dbc8835bae21203c58444bf26fce0739cbec4eb1b610 go1.23.2.windows-arm64.msi
be34b488157ec69d94e26e1554558219a2c90789bcb7e3686965a7f9c8cfcbe7 go1.23.1.darwin-arm64.pkg 2283d12dfe7c8c8a46a41bbf7d11fe007434e7590cd1b89e221e478640b7ee3a go1.23.2.linux-mips64le.tar.gz
e223795ca340e285a760a6446ce57a74500b30e57469a4109961d36184d3c05a go1.23.1.darwin-arm64.tar.gz 2293c5c3ffc595418308b4059ce214b99f0383cba83232e47a1a8c3b710c24e8 go1.23.2.linux-loong64.tar.gz
6af626176923a6ae6c5de6dc1c864f38365793c0e4ecd0d6eab847bdc23953e5 go1.23.1.dragonfly-amd64.tar.gz 23b93144e754bbcf5eda700e9decbdbd44d29ceedb1bf1de75f95e8a6ea986bb go1.23.2.openbsd-arm64.tar.gz
cc957c1a019702e6cdc2e257202d42799011ebc1968b6c3bcd6b1965952607d5 go1.23.1.freebsd-386.tar.gz 2734a5b54905cea45f136c28249e626d0241b865b0637fa1db64bf533d9d843e go1.23.2.netbsd-amd64.tar.gz
a7d57781c50bb80886a8f04066791956d45aa3eea0f83070c5268b6223afb2ff go1.23.1.freebsd-amd64.tar.gz 28af3c40687afdda6b33b300833b6d662716cc2d624fb9fd61a49bdad44cd869 go1.23.2.freebsd-arm.tar.gz
c7b09f3fef456048e596db9bea746eb66796aeb82885622b0388feee18f36a3e go1.23.1.freebsd-arm.tar.gz 367d522b47c7ce7761a671efcb8b12c8af8f509db1cd6160c91f410ef3201987 go1.23.2.windows-arm.msi
b05cd6a77995a0c8439d88df124811c725fb78b942d0b6dd1643529d7ba62f1f go1.23.1.freebsd-arm64.tar.gz 36b7228bae235eee6c8193f5a956e1a9a17874955affb86b3564709b0fab5874 go1.23.2.linux-mipsle.tar.gz
56236ae70be1613f2915943b94f53c96be5bffc0719314078facd778a89bc57e go1.23.1.freebsd-riscv64.tar.gz 3bd1130a08195d23960b154d2e6eaa80ac7325ebd9d01d74c58b6d12580e6b12 go1.23.2.linux-mips.tar.gz
8644c52df4e831202114fd67c9fcaf1f7233ad27bf945ac53fa7217cf1a0349f go1.23.1.illumos-amd64.tar.gz 3bf66879b38a233c5cbb5d2eb982004117f05d6bf06279e886e087d7c504427d go1.23.2.openbsd-riscv64.tar.gz
cdee2f4e2efa001f7ee75c90f2efc310b63346cfbba7b549987e9139527c6b17 go1.23.1.linux-386.tar.gz 3e80b943d70c7e1633822b42c1aa7234e61da14f13ff8efff7ee6e1347f37648 go1.23.2.netbsd-arm64.tar.gz
49bbb517cfa9eee677e1e7897f7cf9cfdbcf49e05f61984a2789136de359f9bd go1.23.1.linux-amd64.tar.gz 40c0b61971a1a74fd4566c536f682c9d4976fa71d40d9daabc875c06113d0fee go1.23.2.darwin-amd64.pkg
faec7f7f8ae53fda0f3d408f52182d942cc89ef5b7d3d9f23ff117437d4b2d2f go1.23.1.linux-arm64.tar.gz 445c0ef19d8692283f4c3a92052cc0568f5a048f4e546105f58e991d4aea54f5 go1.23.2.darwin-amd64.tar.gz
6c7832c7dcd8fb6d4eb308f672a725393403c74ee7be1aeccd8a443015df99de go1.23.1.linux-armv6l.tar.gz 542d3c1705f1c6a1c5a80d5dc62e2e45171af291e755d591c5e6531ef63b454e go1.23.2.linux-amd64.tar.gz
649ce3856ddc808c00b14a46232eab0bf95e7911cdf497010b17d76656f5ca4e go1.23.1.linux-loong64.tar.gz 560aff7fe1eeadc32248db35ed5c0a81e190d171b6ecec404cf46d808c13e92f go1.23.2.aix-ppc64.tar.gz
201911048f234e5a0c51ec94b1a11d4e47062fee4398b1d2faa6c820dc026724 go1.23.1.linux-mips.tar.gz 5611cd648f5100b73a7d6fd85589a481af18fdbaf9c153a92de9a8e39a6e061f go1.23.2.darwin-arm64.pkg
2bce3743df463915e45d2612f9476ffb03d0b3750b1cb3879347de08715b5fc6 go1.23.1.linux-mips64.tar.gz 695aac64532da8d9a243601ffa0411cd763be891fcf7fd2e857eea4ab10b8bcc go1.23.2.plan9-386.tar.gz
54e301f266e33431b0703136e0bbd4cf02461b1ecedd37b7cbd90cb862a98e5f go1.23.1.linux-mips64le.tar.gz 69b31edcd3d4f7d8bbf9aee2b25cafba30b444ef19bc7a033e15026f7d0cc5c2 go1.23.2.netbsd-arm.tar.gz
8efd495e93d17408c0803595cdc3bf13cb28e0f957aeabd9cc18245fb8e64019 go1.23.1.linux-mipsle.tar.gz 6ffa4ac1f4368a3121a032917577a4e0a3feaf696c3e98f213b74ac04c318bc4 go1.23.2.plan9-arm.tar.gz
52bd68689095831ad9af7160844c23b28bb8d0acd268de7e300ff5f0662b7a07 go1.23.1.linux-ppc64.tar.gz 72a6def70300cc804c70073d8b579603d9b39b39b02b3b5d340968d9e7e0e9d4 go1.23.2.windows-386.msi
042888cae54b5fbfd9dd1e3b6bc4a5134879777fe6497fc4c62ec394b5ecf2da go1.23.1.linux-ppc64le.tar.gz 791ca685ee5ca0f6fe849dc078145cb1323d0ea9dd308e9cca9ba2e7186dbb3d go1.23.2.linux-ppc64.tar.gz
1a4a609f0391bea202d9095453cbfaf7368fa88a04c206bf9dd715a738664dc3 go1.23.1.linux-riscv64.tar.gz 86b5de91fdf7bd9b52c77c62f8762518cf3fc256fe912af9bbff1d073054aa5b go1.23.2.plan9-amd64.tar.gz
47dc49ad45c45e192efa0df7dc7bc5403f5f2d15b5d0dc74ef3018154b616f4d go1.23.1.linux-s390x.tar.gz 8734c7cd464a0620f6605bd3f9256bed062f262d0d58e4f45099c329a08ed966 go1.23.2.openbsd-amd64.tar.gz
fbfbd5efa6a5d581ea7f5e65015f927db0e52135cab057e43d39d5482da54b61 go1.23.1.netbsd-386.tar.gz 980ceb889915695d94b166ca1300250dba76fa37a2d41eca2c5e7727dcb4fb7f go1.23.2.openbsd-arm.tar.gz
e96e1cc5cf36113ee6099d1a7306b22cd9c3f975a36bdff954c59f104f22b853 go1.23.1.netbsd-amd64.tar.gz a0cf25f236a0fa0a465816fe7f5c930f3b0b90c5c247b09c43a6adeff654e6ae go1.23.2.linux-mips64.tar.gz
c394dfc06bfc276a591209a37e09cd39089ec9a9cc3db30b94814ce2e39eb1d4 go1.23.1.netbsd-arm.tar.gz a13cc0d621af4f35afd90b886c60b1bf66f771939d226dc36fa61a337d90eb30 go1.23.2.openbsd-ppc64.tar.gz
b3b35d64f32821a68b3e2994032dbefb81978f2ec3f218c7a770623b82d36b8e go1.23.1.netbsd-arm64.tar.gz b29ff163b34cb4943c521fcfc1d956eaa6286561089042051a3fab22e79e9283 go1.23.2.windows-arm.zip
3c775c4c16c182e33c2c4ac090d9a247a93b3fb18a3df01d87d490f29599faff go1.23.1.openbsd-386.tar.gz bc28fe3002cd65cec65d0e4f6000584dacb8c71bfaff8801dfb532855ca42513 go1.23.2.windows-amd64.zip
5edbe53b47c57b32707fd7154536fbe9eaa79053fea01650c93b54cdba13fc0f go1.23.1.openbsd-amd64.tar.gz c164ce7d894b10fd861d7d7b96f1dbea3f993663d9f0c30bc4f8ae3915db8b0c go1.23.2.linux-ppc64le.tar.gz
c30903dd8fa98b8aca8e9db0962ce9f55502aed93e0ef41e5ae148aaa0088de1 go1.23.1.openbsd-arm.tar.gz c4ae1087dce4daf45a837f5fca36ac0e29a02ada9addf857f1c426e60bce6f21 go1.23.2.netbsd-386.tar.gz
12da183489e58f9c6b357bc1b626f85ed7d4220cab31a49d6a49e6ac6a718b67 go1.23.1.openbsd-arm64.tar.gz c80cbc5e66d6fb8b0c3300b0dda1fe925c429e199954d3327da2933d9870b041 go1.23.2.windows-amd64.msi
9cc9aad37696a4a10c31dcec9e35a308de0b369dad354d54cf07406ac6fa7c6f go1.23.1.openbsd-ppc64.tar.gz cb1ed4410f68d8be1156cee0a74fcfbdcd9bca377c83db3a9e1b07eebc6d71ef go1.23.2.linux-386.tar.gz
e1d740dda062ce5a276a0c3ed7d8b6353238bc8ff405f63e2e3480bfd26a5ec5 go1.23.1.openbsd-riscv64.tar.gz d1fde255843fec1f7f0611d468effd98e1f4309f589ac13037db07b032f9da35 go1.23.2.openbsd-386.tar.gz
da2a37f9987f01f096859230aa13ecc4ad2e7884465bce91004bc78c64435d65 go1.23.1.plan9-386.tar.gz d47e40366cd6c6b6ee14b811554cd7dde0351309f4a8a4569ec5ba2bd7689437 go1.23.2.illumos-amd64.tar.gz
fd8fff8b0697d55c4a4d02a8dc998192b80a9dc2a057647373d6ff607cad29de go1.23.1.plan9-amd64.tar.gz d87031194fe3e01abdcaf3c7302148ade97a7add6eac3fec26765bcb3207b80f go1.23.2.darwin-arm64.tar.gz
52efbc5804c1c86ba7868aa8ebbc31cc8c2a27b62a60fd57944970d48fc67525 go1.23.1.plan9-arm.tar.gz de1f94d7dd3548ba3036de1ea97eb8243881c22a88fcc04cc08c704ded769e02 go1.23.2.linux-s390x.tar.gz
f54205f21e2143f2ada1bf1c00ddf64590f5139d5c3fb77cc06175f0d8cc7567 go1.23.1.solaris-amd64.tar.gz e3286bdde186077e65e961cbe18874d42a461e5b9c472c26572b8d4a98d15c40 go1.23.2.linux-armv6l.tar.gz
369a17f0cfd29e5c848e58ffe0d772da20abe334d1c7ca01dbcd55bb3db0b440 go1.23.1.windows-386.msi e4d9a1319dfdaa827407855e406c43e85c878a1f93f4f3984c85dce969c8bf70 go1.23.2.freebsd-386.tar.gz
ab866f47d7be56e6b1c67f1d529bf4c23331a339fb0785f435a0552d352cb257 go1.23.1.windows-386.zip ea8ab49c5c04c9f94a3f4894d1b030fbce8d10413905fa399f6c39c0a44d5556 go1.23.2.linux-riscv64.tar.gz
e99dac215ee437b9bb8f8b14bbfe0e8756882c1ed291f30818e8363bc9c047a5 go1.23.1.windows-amd64.msi eaa3bc377badbdcae144633f8b29bf2680475b72dcd4c135343d3bdc0ba7671e go1.23.2.windows-386.zip
32dedf277c86610e380e1765593edb66876f00223df71690bd6be68ee17675c0 go1.23.1.windows-amd64.zip f11b9b4d4a0679909202fc5e88093d6ff720a8a417bfe6a34d502c3862367039 go1.23.2.freebsd-riscv64.tar.gz
23169c79dc6b54e0dffb25be6b67425ad9759392a58309bc057430a9bf4c8f6a go1.23.1.windows-arm.msi f163b99b03e4bbc64cd30363f1694a08fcd44094415db1f092f13f9d1bb7c28e go1.23.2.dragonfly-amd64.tar.gz
1a57615a09f13534f88e9f2d7efd5743535d1a5719b19e520eef965a634f8efb go1.23.1.windows-arm.zip f45af3e1434175ff85620a74c07fb41d6844655f1f2cd2389c5fca6de000f58c go1.23.2.freebsd-arm64.tar.gz
313e1a543931ad8735b4df8969e00f5f4c2ef07be21f54015ede961a70263d35 go1.23.1.windows-arm64.msi f626cdd92fc21a88b31c1251f419c17782933a42903db87a174ce74eeecc66a9 go1.23.2.linux-arm64.tar.gz
64ad0954d2c33f556fb1018d62de091254aa6e3a94f1c8a8b16af0d3701d194e go1.23.1.windows-arm64.zip fa70d39ddeb6b55241a30b48d7af4e681c6a7d7104e8326c3bc1b12a75e091cc go1.23.2.solaris-amd64.tar.gz
# version:golangci 1.59.0 # version:golangci 1.61.0
# https://github.com/golangci/golangci-lint/releases/ # https://github.com/golangci/golangci-lint/releases/
# https://github.com/golangci/golangci-lint/releases/download/v1.59.0/ # https://github.com/golangci/golangci-lint/releases/download/v1.61.0/
418acf7e255ddc0783e97129c9b03d9311b77826a5311d425a01c708a86417e7 golangci-lint-1.59.0-darwin-amd64.tar.gz 5c280ef3284f80c54fd90d73dc39ca276953949da1db03eb9dd0fbf868cc6e55 golangci-lint-1.61.0-darwin-amd64.tar.gz
5f6a1d95a6dd69f6e328eb56dd311a38e04cfab79a1305fbf4957f4e203f47b6 golangci-lint-1.59.0-darwin-arm64.tar.gz 544334890701e4e04a6e574bc010bea8945205c08c44cced73745a6378012d36 golangci-lint-1.61.0-darwin-arm64.tar.gz
8899bf589185d49f747f3e5db9f0bde8a47245a100c64a3dd4d65e8e92cfc4f2 golangci-lint-1.59.0-freebsd-386.tar.gz e885a6f561092055930ebd298914d80e8fd2e10d2b1e9942836c2c6a115301fa golangci-lint-1.61.0-freebsd-386.tar.gz
658212f138d9df2ac89427e22115af34bf387c0871d70f2a25101718946a014f golangci-lint-1.59.0-freebsd-amd64.tar.gz b13f6a3f11f65e7ff66b734d7554df3bbae0f485768848424e7554ed289e19c2 golangci-lint-1.61.0-freebsd-amd64.tar.gz
4c6395ea40f314d3b6fa17d8997baab93464d5d1deeaab513155e625473bd03a golangci-lint-1.59.0-freebsd-armv6.tar.gz cd8e7bbe5b8f33ed1597aa1cc588da96a3b9f22e1b9ae60d93511eae1a0ee8c5 golangci-lint-1.61.0-freebsd-armv6.tar.gz
ff37da4fbaacdb6bbae70fdbdbb1ba932a859956f788c82822fa06bef5b7c6b3 golangci-lint-1.59.0-freebsd-armv7.tar.gz 7ade524dbd88bd250968f45e190af90e151fa5ee63dd6aa7f7bb90e8155db61d golangci-lint-1.61.0-freebsd-armv7.tar.gz
439739469ed2bda182b1ec276d40c40e02f195537f78e3672996741ad223d6b6 golangci-lint-1.59.0-illumos-amd64.tar.gz 0fe3cd8a1ed8d9f54f48670a5af3df056d6040d94017057f0f4d65c930660ad9 golangci-lint-1.61.0-illumos-amd64.tar.gz
940801d46790e40d0a097d8fee34e2606f0ef148cd039654029b0b8750a15ed6 golangci-lint-1.59.0-linux-386.tar.gz b463fc5053a612abd26393ebaff1d85d7d56058946f4f0f7bf25ed44ea899415 golangci-lint-1.61.0-linux-386.tar.gz
3b14a439f33c4fff83dbe0349950d984042b9a1feb6c62f82787b598fc3ab5f4 golangci-lint-1.59.0-linux-amd64.tar.gz 77cb0af99379d9a21d5dc8c38364d060e864a01bd2f3e30b5e8cc550c3a54111 golangci-lint-1.61.0-linux-amd64.tar.gz
c57e6c0b0fa03089a2611dceddd5bc5d206716cccdff8b149da8baac598719a1 golangci-lint-1.59.0-linux-arm64.tar.gz af60ac05566d9351615cb31b4cc070185c25bf8cbd9b09c1873aa5ec6f3cc17e golangci-lint-1.61.0-linux-arm64.tar.gz
93149e2d3b25ac754df9a23172403d8aa6d021a7e0d9c090a12f51897f68c9a0 golangci-lint-1.59.0-linux-armv6.tar.gz 1f307f2fcc5d7d674062a967a0d83a7091e300529aa237ec6ad2b3dd14c897f5 golangci-lint-1.61.0-linux-armv6.tar.gz
d10ac38239d9efee3ee87b55c96cdf3fa09e1a525babe3ffdaaf65ccc48cf3dc golangci-lint-1.59.0-linux-armv7.tar.gz 3ad8cbaae75a547450844811300f99c4cd290277398e43d22b9eb1792d15af4c golangci-lint-1.61.0-linux-armv7.tar.gz
047338114b4f0d5f08f0fb9a397b03cc171916ed0960be7dfb355c2320cd5e9c golangci-lint-1.59.0-linux-loong64.tar.gz 9be2ca67d961d7699079739cf6f7c8291c5183d57e34d1677de21ca19d0bd3ed golangci-lint-1.61.0-linux-loong64.tar.gz
5632df0f7f8fc03a80a266130faef0b5902d280cf60621f1b2bdc1aef6d97ee9 golangci-lint-1.59.0-linux-mips64.tar.gz 90d005e1648115ebf0861b408eab9c936079a24763e883058b0a227cd3135d31 golangci-lint-1.61.0-linux-mips64.tar.gz
71dd638c82fa4439171e7126d2c7a32b5d103bfdef282cea40c83632cb3d1f4b golangci-lint-1.59.0-linux-mips64le.tar.gz 6d2ed4f49407115460b8c10ccfc40fd177e0887a48864a2879dd16e84ba2a48c golangci-lint-1.61.0-linux-mips64le.tar.gz
6cf9ea0d34e91669948483f9ae7f07da319a879344373a1981099fbd890cde00 golangci-lint-1.59.0-linux-ppc64le.tar.gz 633089589af5a58b7430afb6eee107d4e9c99e8d91711ddc219eb13a07e8d3b8 golangci-lint-1.61.0-linux-ppc64le.tar.gz
af0205fa6fbab197cee613c359947711231739095d21b5c837086233b36ad971 golangci-lint-1.59.0-linux-riscv64.tar.gz 4c1a097d9e0d1b4a8144dae6a1f5583a38d662f3bdc1498c4e954b6ed856be98 golangci-lint-1.61.0-linux-riscv64.tar.gz
a9d2fb93f3c688ebccef94f5dc96c0b07c4d20bf6556cddebd8442159b0c80f6 golangci-lint-1.59.0-linux-s390x.tar.gz 30581d3c987d287b7064617f1a2694143e10dffc40bc25be6636006ee82d7e1c golangci-lint-1.61.0-linux-s390x.tar.gz
68ab4c57a847b8ace9679887f2f8b2b6760e57ee29dcde8c3f40dd8bb2654fa2 golangci-lint-1.59.0-netbsd-386.tar.gz 42530bf8100bd43c07f5efe6d92148ba6c5a7a712d510c6f24be85af6571d5eb golangci-lint-1.61.0-netbsd-386.tar.gz
d277b8b435c19406d00de4d509eadf5a024a5782878332e9a1b7c02bb76e87a7 golangci-lint-1.59.0-netbsd-amd64.tar.gz b8bb07c920f6601edf718d5e82ec0784fd590b0992b42b6ec18da99f26013ed4 golangci-lint-1.61.0-netbsd-amd64.tar.gz
83211656be8dcfa1545af4f92894409f412d1f37566798cb9460a526593ad62c golangci-lint-1.59.0-netbsd-arm64.tar.gz 353a51527c60bd0776b0891b03f247c791986f625fca689d121972c624e54198 golangci-lint-1.61.0-netbsd-arm64.tar.gz
6c6866d28bf79fa9817a0f7d2b050890ed109cae80bdb4dfa39536a7226da237 golangci-lint-1.59.0-netbsd-armv6.tar.gz 957a6272c3137910514225704c5dac0723b9c65eb7d9587366a997736e2d7580 golangci-lint-1.61.0-netbsd-armv6.tar.gz
11587566363bd03ca586b7df9776ccaed569fcd1f3489930ac02f9375b307503 golangci-lint-1.59.0-netbsd-armv7.tar.gz a89eb28ff7f18f5cd52b914739360fa95cf2f643de4adeca46e26bec3a07e8d8 golangci-lint-1.61.0-netbsd-armv7.tar.gz
466181a8967bafa495e41494f93a0bec829c2cf715de874583b0460b3b8ae2b8 golangci-lint-1.59.0-windows-386.zip d8d74c43600b271393000717a4ed157d7a15bb85bab7db2efad9b63a694d4634 golangci-lint-1.61.0-windows-386.zip
3317d8a87a99a49a0a1321d295c010790e6dbf43ee96b318f4b8bb23eae7a565 golangci-lint-1.59.0-windows-amd64.zip e7bc2a81929a50f830244d6d2e657cce4f19a59aff49fa9000176ff34fda64ce golangci-lint-1.61.0-windows-amd64.zip
b3af955c7fceac8220a36fc799e1b3f19d3b247d32f422caac5f9845df8f7316 golangci-lint-1.59.0-windows-arm64.zip ed97c221596dd771e3dd9344872c140340bee2e819cd7a90afa1de752f1f2e0f golangci-lint-1.61.0-windows-arm64.zip
6f083c7d0c764e5a0e5bde46ee3e91ae357d80c194190fe1d9754392e9064c7e golangci-lint-1.59.0-windows-armv6.zip 4b365233948b13d02d45928a5c390045e00945e919747b9887b5f260247541ae golangci-lint-1.61.0-windows-armv6.zip
3709b4dd425deadab27748778d08e03c0f804d7748f7dd5b6bb488d98aa031c7 golangci-lint-1.59.0-windows-armv7.zip 595538fb64d152173959d28f6235227f9cd969a828e5af0c4e960d02af4ffd0e golangci-lint-1.61.0-windows-armv7.zip
# This is the builder on PPA that will build Go itself (inception-y), don't modify! # This is the builder on PPA that will build Go itself (inception-y), don't modify!
# #

View file

@ -50,7 +50,6 @@ import (
"path" "path"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strconv"
"strings" "strings"
"time" "time"
@ -121,11 +120,12 @@ var (
// Distros for which packages are created // Distros for which packages are created
debDistros = []string{ debDistros = []string{
"xenial", // 16.04, EOL: 04/2026 "xenial", // 16.04, EOL: 04/2026
"bionic", // 18.04, EOL: 04/2028 "bionic", // 18.04, EOL: 04/2028
"focal", // 20.04, EOL: 04/2030 "focal", // 20.04, EOL: 04/2030
"jammy", // 22.04, EOL: 04/2032 "jammy", // 22.04, EOL: 04/2032
"noble", // 24.04, EOL: 04/2034 "noble", // 24.04, EOL: 04/2034
"oracular", // 24.10, EOL: 07/2025
} }
// This is where the tests should be unpacked. // This is where the tests should be unpacked.
@ -159,8 +159,8 @@ func main() {
doLint(os.Args[2:]) doLint(os.Args[2:])
case "archive": case "archive":
doArchive(os.Args[2:]) doArchive(os.Args[2:])
case "docker": case "dockerx":
doDocker(os.Args[2:]) doDockerBuildx(os.Args[2:])
case "debsrc": case "debsrc":
doDebianSource(os.Args[2:]) doDebianSource(os.Args[2:])
case "nsis": case "nsis":
@ -723,10 +723,9 @@ func maybeSkipArchive(env build.Environment) {
} }
// Builds the docker images and optionally uploads them to Docker Hub. // Builds the docker images and optionally uploads them to Docker Hub.
func doDocker(cmdline []string) { func doDockerBuildx(cmdline []string) {
var ( var (
image = flag.Bool("image", false, `Whether to build and push an arch specific docker image`) platform = flag.String("platform", "", `Push a multi-arch docker image for the specified architectures (usually "linux/amd64,linux/arm64")`)
manifest = flag.String("manifest", "", `Push a multi-arch docker image for the specified architectures (usually "amd64,arm64")`)
upload = flag.String("upload", "", `Where to upload the docker image (usually "ethereum/client-go")`) upload = flag.String("upload", "", `Where to upload the docker image (usually "ethereum/client-go")`)
) )
flag.CommandLine.Parse(cmdline) flag.CommandLine.Parse(cmdline)
@ -761,129 +760,26 @@ func doDocker(cmdline []string) {
case strings.HasPrefix(env.Tag, "v1."): case strings.HasPrefix(env.Tag, "v1."):
tags = []string{"stable", fmt.Sprintf("release-1.%d", params.VersionMinor), "v" + params.Version} tags = []string{"stable", fmt.Sprintf("release-1.%d", params.VersionMinor), "v" + params.Version}
} }
// If architecture specific image builds are requested, build and push them // Need to create a mult-arch builder
if *image { build.MustRunCommand("docker", "buildx", "create", "--use", "--name", "multi-arch-builder", "--platform", *platform)
build.MustRunCommand("docker", "build", "--build-arg", "COMMIT="+env.Commit, "--build-arg", "VERSION="+params.VersionWithMeta, "--build-arg", "BUILDNUM="+env.Buildnum, "--tag", fmt.Sprintf("%s:TAG", *upload), ".")
build.MustRunCommand("docker", "build", "--build-arg", "COMMIT="+env.Commit, "--build-arg", "VERSION="+params.VersionWithMeta, "--build-arg", "BUILDNUM="+env.Buildnum, "--tag", fmt.Sprintf("%s:alltools-TAG", *upload), "-f", "Dockerfile.alltools", ".")
// Tag and upload the images to Docker Hub for _, spec := range []struct {
for _, tag := range tags { file string
gethImage := fmt.Sprintf("%s:%s-%s", *upload, tag, runtime.GOARCH) base string
toolImage := fmt.Sprintf("%s:alltools-%s-%s", *upload, tag, runtime.GOARCH) }{
{file: "Dockerfile", base: fmt.Sprintf("%s:", *upload)},
// If the image already exists (non version tag), check the build {file: "Dockerfile.alltools", base: fmt.Sprintf("%s:alltools-", *upload)},
// number to prevent overwriting a newer commit if concurrent builds } {
// are running. This is still a tiny bit racey if two published are for _, tag := range tags { // latest, stable etc
// done at the same time, but that's extremely unlikely even on the gethImage := fmt.Sprintf("%s%s", spec.base, tag)
// master branch. build.MustRunCommand("docker", "buildx", "build",
for _, img := range []string{gethImage, toolImage} { "--build-arg", "COMMIT="+env.Commit,
if exec.Command("docker", "pull", img).Run() != nil { "--build-arg", "VERSION="+params.VersionWithMeta,
continue // Generally the only failure is a missing image, which is good "--build-arg", "BUILDNUM="+env.Buildnum,
} "--tag", gethImage,
buildnum, err := exec.Command("docker", "inspect", "--format", "{{index .Config.Labels \"buildnum\"}}", img).CombinedOutput() "--platform", *platform,
if err != nil { "--push",
log.Fatalf("Failed to inspect container: %v\nOutput: %s", err, string(buildnum)) "--file", spec.file, ".")
}
buildnum = bytes.TrimSpace(buildnum)
if len(buildnum) > 0 && len(env.Buildnum) > 0 {
oldnum, err := strconv.Atoi(string(buildnum))
if err != nil {
log.Fatalf("Failed to parse old image build number: %v", err)
}
newnum, err := strconv.Atoi(env.Buildnum)
if err != nil {
log.Fatalf("Failed to parse current build number: %v", err)
}
if oldnum > newnum {
log.Fatalf("Current build number %d not newer than existing %d", newnum, oldnum)
} else {
log.Printf("Updating %s from build %d to %d", img, oldnum, newnum)
}
}
}
build.MustRunCommand("docker", "image", "tag", fmt.Sprintf("%s:TAG", *upload), gethImage)
build.MustRunCommand("docker", "image", "tag", fmt.Sprintf("%s:alltools-TAG", *upload), toolImage)
build.MustRunCommand("docker", "push", gethImage)
build.MustRunCommand("docker", "push", toolImage)
}
}
// If multi-arch image manifest push is requested, assemble it
if len(*manifest) != 0 {
// Since different architectures are pushed by different builders, wait
// until all required images are updated.
var mismatch bool
for i := 0; i < 2; i++ { // 2 attempts, second is race check
mismatch = false // hope there's no mismatch now
for _, tag := range tags {
for _, arch := range strings.Split(*manifest, ",") {
gethImage := fmt.Sprintf("%s:%s-%s", *upload, tag, arch)
toolImage := fmt.Sprintf("%s:alltools-%s-%s", *upload, tag, arch)
for _, img := range []string{gethImage, toolImage} {
if out, err := exec.Command("docker", "pull", img).CombinedOutput(); err != nil {
log.Printf("Required image %s unavailable: %v\nOutput: %s", img, err, out)
mismatch = true
break
}
buildnum, err := exec.Command("docker", "inspect", "--format", "{{index .Config.Labels \"buildnum\"}}", img).CombinedOutput()
if err != nil {
log.Fatalf("Failed to inspect container: %v\nOutput: %s", err, string(buildnum))
}
buildnum = bytes.TrimSpace(buildnum)
if string(buildnum) != env.Buildnum {
log.Printf("Build number mismatch on %s: want %s, have %s", img, env.Buildnum, buildnum)
mismatch = true
break
}
}
if mismatch {
break
}
}
if mismatch {
break
}
}
if mismatch {
// Build numbers mismatching, retry in a short time to
// avoid concurrent fails in both publisher images. If
// however the retry failed too, it means the concurrent
// builder is still crunching, let that do the publish.
if i == 0 {
time.Sleep(30 * time.Second)
}
continue
}
break
}
if mismatch {
log.Println("Relinquishing publish to other builder")
return
}
// Assemble and push the Geth manifest image
for _, tag := range tags {
gethImage := fmt.Sprintf("%s:%s", *upload, tag)
var gethSubImages []string
for _, arch := range strings.Split(*manifest, ",") {
gethSubImages = append(gethSubImages, gethImage+"-"+arch)
}
build.MustRunCommand("docker", append([]string{"manifest", "create", gethImage}, gethSubImages...)...)
build.MustRunCommand("docker", "manifest", "push", gethImage)
}
// Assemble and push the alltools manifest image
for _, tag := range tags {
toolImage := fmt.Sprintf("%s:alltools-%s", *upload, tag)
var toolSubImages []string
for _, arch := range strings.Split(*manifest, ",") {
toolSubImages = append(toolSubImages, toolImage+"-"+arch)
}
build.MustRunCommand("docker", append([]string{"manifest", "create", toolImage}, toolSubImages...)...)
build.MustRunCommand("docker", "manifest", "push", toolImage)
} }
} }
} }

View file

@ -100,7 +100,6 @@ func (c *Chain) AccountsInHashOrder() []state.DumpAccount {
list := make([]state.DumpAccount, len(c.state)) list := make([]state.DumpAccount, len(c.state))
i := 0 i := 0
for addr, acc := range c.state { for addr, acc := range c.state {
addr := addr
list[i] = acc list[i] = acc
list[i].Address = &addr list[i].Address = &addr
if len(acc.AddressHash) != 32 { if len(acc.AddressHash) != 32 {

View file

@ -286,7 +286,6 @@ a key before startingHash (wrong order). The server should return the first avai
} }
for i, tc := range tests { for i, tc := range tests {
tc := tc
if i > 0 { if i > 0 {
t.Log("\n") t.Log("\n")
} }
@ -429,7 +428,6 @@ of the test account. The server should return slots [2,3] (i.e. the 'next availa
} }
for i, tc := range tests { for i, tc := range tests {
tc := tc
if i > 0 { if i > 0 {
t.Log("\n") t.Log("\n")
} }
@ -526,7 +524,6 @@ func (s *Suite) TestSnapGetByteCodes(t *utesting.T) {
} }
for i, tc := range tests { for i, tc := range tests {
tc := tc
if i > 0 { if i > 0 {
t.Log("\n") t.Log("\n")
} }
@ -723,7 +720,6 @@ The server should reject the request.`,
} }
for i, tc := range tests { for i, tc := range tests {
tc := tc
if i > 0 { if i > 0 {
t.Log("\n") t.Log("\n")
} }

200
cmd/evm/eofparse.go Normal file
View file

@ -0,0 +1,200 @@
// Copyright 2023 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"bufio"
"encoding/hex"
"encoding/json"
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli/v2"
)
func init() {
jt = vm.NewPragueEOFInstructionSetForTesting()
}
var (
jt vm.JumpTable
initcode = "INITCODE"
)
func eofParseAction(ctx *cli.Context) error {
// If `--test` is set, parse and validate the reference test at the provided path.
if ctx.IsSet(refTestFlag.Name) {
var (
file = ctx.String(refTestFlag.Name)
executedTests int
passedTests int
)
err := filepath.Walk(file, func(path string, info fs.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil
}
log.Debug("Executing test", "name", info.Name())
passed, tot, err := executeTest(path)
passedTests += passed
executedTests += tot
return err
})
if err != nil {
return err
}
log.Info("Executed tests", "passed", passedTests, "total executed", executedTests)
return nil
}
// If `--hex` is set, parse and validate the hex string argument.
if ctx.IsSet(hexFlag.Name) {
if _, err := parseAndValidate(ctx.String(hexFlag.Name), false); err != nil {
return fmt.Errorf("err: %w", err)
}
fmt.Println("OK")
return nil
}
// If neither are passed in, read input from stdin.
scanner := bufio.NewScanner(os.Stdin)
scanner.Buffer(make([]byte, 1024*1024), 10*1024*1024)
for scanner.Scan() {
l := strings.TrimSpace(scanner.Text())
if strings.HasPrefix(l, "#") || l == "" {
continue
}
if _, err := parseAndValidate(l, false); err != nil {
fmt.Printf("err: %v\n", err)
} else {
fmt.Println("OK")
}
}
if err := scanner.Err(); err != nil {
fmt.Println(err.Error())
}
return nil
}
type refTests struct {
Vectors map[string]eOFTest `json:"vectors"`
}
type eOFTest struct {
Code string `json:"code"`
Results map[string]etResult `json:"results"`
ContainerKind string `json:"containerKind"`
}
type etResult struct {
Result bool `json:"result"`
Exception string `json:"exception,omitempty"`
}
func executeTest(path string) (int, int, error) {
src, err := os.ReadFile(path)
if err != nil {
return 0, 0, err
}
var testsByName map[string]refTests
if err := json.Unmarshal(src, &testsByName); err != nil {
return 0, 0, err
}
passed, total := 0, 0
for testsName, tests := range testsByName {
for name, tt := range tests.Vectors {
for fork, r := range tt.Results {
total++
_, err := parseAndValidate(tt.Code, tt.ContainerKind == initcode)
if r.Result && err != nil {
log.Error("Test failure, expected validation success", "name", testsName, "idx", name, "fork", fork, "err", err)
continue
}
if !r.Result && err == nil {
log.Error("Test failure, expected validation error", "name", testsName, "idx", name, "fork", fork, "have err", r.Exception, "err", err)
continue
}
passed++
}
}
}
return passed, total, nil
}
func parseAndValidate(s string, isInitCode bool) (*vm.Container, error) {
if len(s) >= 2 && strings.HasPrefix(s, "0x") {
s = s[2:]
}
b, err := hex.DecodeString(s)
if err != nil {
return nil, fmt.Errorf("unable to decode data: %w", err)
}
return parse(b, isInitCode)
}
func parse(b []byte, isInitCode bool) (*vm.Container, error) {
var c vm.Container
if err := c.UnmarshalBinary(b, isInitCode); err != nil {
return nil, err
}
if err := c.ValidateCode(&jt, isInitCode); err != nil {
return nil, err
}
return &c, nil
}
func eofDumpAction(ctx *cli.Context) error {
// If `--hex` is set, parse and validate the hex string argument.
if ctx.IsSet(hexFlag.Name) {
return eofDump(ctx.String(hexFlag.Name))
}
// Otherwise read from stdin
scanner := bufio.NewScanner(os.Stdin)
scanner.Buffer(make([]byte, 1024*1024), 10*1024*1024)
for scanner.Scan() {
l := strings.TrimSpace(scanner.Text())
if strings.HasPrefix(l, "#") || l == "" {
continue
}
if err := eofDump(l); err != nil {
return err
}
fmt.Println("")
}
return scanner.Err()
}
func eofDump(hexdata string) error {
if len(hexdata) >= 2 && strings.HasPrefix(hexdata, "0x") {
hexdata = hexdata[2:]
}
b, err := hex.DecodeString(hexdata)
if err != nil {
return fmt.Errorf("unable to decode data: %w", err)
}
var c vm.Container
if err := c.UnmarshalBinary(b, false); err != nil {
return err
}
fmt.Println(c.String())
return nil
}

166
cmd/evm/eofparse_test.go Normal file
View file

@ -0,0 +1,166 @@
package main
import (
"bufio"
"bytes"
"encoding/hex"
"fmt"
"os"
"strings"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm"
)
func FuzzEofParsing(f *testing.F) {
// Seed with corpus from execution-spec-tests
for i := 0; ; i++ {
fname := fmt.Sprintf("testdata/eof/eof_corpus_%d.txt", i)
corpus, err := os.Open(fname)
if err != nil {
break
}
f.Logf("Reading seed data from %v", fname)
scanner := bufio.NewScanner(corpus)
scanner.Buffer(make([]byte, 1024), 10*1024*1024)
for scanner.Scan() {
s := scanner.Text()
if len(s) >= 2 && strings.HasPrefix(s, "0x") {
s = s[2:]
}
b, err := hex.DecodeString(s)
if err != nil {
panic(err) // rotten corpus
}
f.Add(b)
}
corpus.Close()
if err := scanner.Err(); err != nil {
panic(err) // rotten corpus
}
}
// And do the fuzzing
f.Fuzz(func(t *testing.T, data []byte) {
var (
jt = vm.NewPragueEOFInstructionSetForTesting()
c vm.Container
)
cpy := common.CopyBytes(data)
if err := c.UnmarshalBinary(data, true); err == nil {
c.ValidateCode(&jt, true)
if have := c.MarshalBinary(); !bytes.Equal(have, data) {
t.Fatal("Unmarshal-> Marshal failure!")
}
}
if err := c.UnmarshalBinary(data, false); err == nil {
c.ValidateCode(&jt, false)
if have := c.MarshalBinary(); !bytes.Equal(have, data) {
t.Fatal("Unmarshal-> Marshal failure!")
}
}
if !bytes.Equal(cpy, data) {
panic("data modified during unmarshalling")
}
})
}
func TestEofParseInitcode(t *testing.T) {
testEofParse(t, true, "testdata/eof/results.initcode.txt")
}
func TestEofParseRegular(t *testing.T) {
testEofParse(t, false, "testdata/eof/results.regular.txt")
}
func testEofParse(t *testing.T, isInitCode bool, wantFile string) {
var wantFn func() string
var wantLoc = 0
{ // Configure the want-reader
wants, err := os.Open(wantFile)
if err != nil {
t.Fatal(err)
}
scanner := bufio.NewScanner(wants)
scanner.Buffer(make([]byte, 1024), 10*1024*1024)
wantFn = func() string {
if scanner.Scan() {
wantLoc++
return scanner.Text()
}
return "end of file reached"
}
}
for i := 0; ; i++ {
fname := fmt.Sprintf("testdata/eof/eof_corpus_%d.txt", i)
corpus, err := os.Open(fname)
if err != nil {
break
}
t.Logf("# Reading seed data from %v", fname)
scanner := bufio.NewScanner(corpus)
scanner.Buffer(make([]byte, 1024), 10*1024*1024)
line := 1
for scanner.Scan() {
s := scanner.Text()
if len(s) >= 2 && strings.HasPrefix(s, "0x") {
s = s[2:]
}
b, err := hex.DecodeString(s)
if err != nil {
panic(err) // rotten corpus
}
have := "OK"
if _, err := parse(b, isInitCode); err != nil {
have = fmt.Sprintf("ERR: %v", err)
}
if false { // Change this to generate the want-output
fmt.Printf("%v\n", have)
} else {
want := wantFn()
if have != want {
if len(want) > 100 {
want = want[:100]
}
if len(b) > 100 {
b = b[:100]
}
t.Errorf("%v:%d\n%v\ninput %x\nisInit: %v\nhave: %q\nwant: %q\n",
fname, line, fmt.Sprintf("%v:%d", wantFile, wantLoc), b, isInitCode, have, want)
}
}
line++
}
corpus.Close()
}
}
func BenchmarkEofParse(b *testing.B) {
corpus, err := os.Open("testdata/eof/eof_benches.txt")
if err != nil {
b.Fatal(err)
}
defer corpus.Close()
scanner := bufio.NewScanner(corpus)
scanner.Buffer(make([]byte, 1024), 10*1024*1024)
line := 1
for scanner.Scan() {
s := scanner.Text()
if len(s) >= 2 && strings.HasPrefix(s, "0x") {
s = s[2:]
}
data, err := hex.DecodeString(s)
if err != nil {
b.Fatal(err) // rotten corpus
}
b.Run(fmt.Sprintf("test-%d", line), func(b *testing.B) {
b.ReportAllocs()
b.SetBytes(int64(len(data)))
for i := 0; i < b.N; i++ {
_, _ = parse(data, false)
}
})
line++
}
}

View file

@ -23,6 +23,7 @@ import (
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/consensus/misc" "github.com/ethereum/go-ethereum/consensus/misc"
@ -50,6 +51,8 @@ type Prestate struct {
Pre types.GenesisAlloc `json:"pre"` Pre types.GenesisAlloc `json:"pre"`
} }
//go:generate go run github.com/fjl/gencodec -type ExecutionResult -field-override executionResultMarshaling -out gen_execresult.go
// ExecutionResult contains the execution status after running a state test, any // ExecutionResult contains the execution status after running a state test, any
// error that might have occurred and a dump of the final state if requested. // error that might have occurred and a dump of the final state if requested.
type ExecutionResult struct { type ExecutionResult struct {
@ -66,8 +69,12 @@ type ExecutionResult struct {
WithdrawalsRoot *common.Hash `json:"withdrawalsRoot,omitempty"` WithdrawalsRoot *common.Hash `json:"withdrawalsRoot,omitempty"`
CurrentExcessBlobGas *math.HexOrDecimal64 `json:"currentExcessBlobGas,omitempty"` CurrentExcessBlobGas *math.HexOrDecimal64 `json:"currentExcessBlobGas,omitempty"`
CurrentBlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed,omitempty"` CurrentBlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed,omitempty"`
RequestsHash *common.Hash `json:"requestsRoot,omitempty"` RequestsHash *common.Hash `json:"requestsHash,omitempty"`
DepositRequests *types.Deposits `json:"depositRequests,omitempty"` Requests [][]byte `json:"requests,omitempty"`
}
type executionResultMarshaling struct {
Requests []hexutil.Bytes `json:"requests,omitempty"`
} }
type ommer struct { type ommer struct {
@ -354,6 +361,28 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
amount := new(big.Int).Mul(new(big.Int).SetUint64(w.Amount), big.NewInt(params.GWei)) amount := new(big.Int).Mul(new(big.Int).SetUint64(w.Amount), big.NewInt(params.GWei))
statedb.AddBalance(w.Address, uint256.MustFromBig(amount), tracing.BalanceIncreaseWithdrawal) statedb.AddBalance(w.Address, uint256.MustFromBig(amount), tracing.BalanceIncreaseWithdrawal)
} }
// Gather the execution-layer triggered requests.
var requests [][]byte
if chainConfig.IsPrague(vmContext.BlockNumber, vmContext.Time) {
// EIP-6110 deposits
var allLogs []*types.Log
for _, receipt := range receipts {
allLogs = append(allLogs, receipt.Logs...)
}
depositRequests, err := core.ParseDepositLogs(allLogs, chainConfig)
if err != nil {
return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not parse requests logs: %v", err))
}
requests = append(requests, depositRequests)
// create EVM for system calls
vmenv := vm.NewEVM(vmContext, vm.TxContext{}, statedb, chainConfig, vm.Config{})
// EIP-7002 withdrawals
requests = append(requests, core.ProcessWithdrawalQueue(vmenv, statedb))
// EIP-7251 consolidations
requests = append(requests, core.ProcessConsolidationQueue(vmenv, statedb))
}
// Commit block // Commit block
root, err := statedb.Commit(vmContext.BlockNumber.Uint64(), chainConfig.IsEIP158(vmContext.BlockNumber)) root, err := statedb.Commit(vmContext.BlockNumber.Uint64(), chainConfig.IsEIP158(vmContext.BlockNumber))
if err != nil { if err != nil {
@ -379,28 +408,17 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
execRs.CurrentExcessBlobGas = (*math.HexOrDecimal64)(&excessBlobGas) execRs.CurrentExcessBlobGas = (*math.HexOrDecimal64)(&excessBlobGas)
execRs.CurrentBlobGasUsed = (*math.HexOrDecimal64)(&blobGasUsed) execRs.CurrentBlobGasUsed = (*math.HexOrDecimal64)(&blobGasUsed)
} }
if chainConfig.IsPrague(vmContext.BlockNumber, vmContext.Time) { if requests != nil {
// Parse the requests from the logs // Set requestsHash on block.
var allLogs []*types.Log h := types.CalcRequestsHash(requests)
for _, receipt := range receipts {
allLogs = append(allLogs, receipt.Logs...)
}
requests, err := core.ParseDepositLogs(allLogs, chainConfig)
if err != nil {
return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not parse requests logs: %v", err))
}
// Calculate the requests root
h := types.DeriveSha(requests, trie.NewStackTrie(nil))
execRs.RequestsHash = &h execRs.RequestsHash = &h
// Get the deposits from the requests for i := range requests {
deposits := make(types.Deposits, 0) // remove prefix
for _, req := range requests { requests[i] = requests[i][1:]
if dep, ok := req.Inner().(*types.Deposit); ok {
deposits = append(deposits, dep)
}
} }
execRs.DepositRequests = &deposits execRs.Requests = requests
} }
// Re-create statedb instance with new root upon the updated database // Re-create statedb instance with new root upon the updated database
// for accessing latest states. // for accessing latest states.
statedb, err = state.New(root, statedb.Database()) statedb, err = state.New(root, statedb.Database())

View file

@ -0,0 +1,134 @@
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
package t8ntool
import (
"encoding/json"
"errors"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/types"
)
var _ = (*executionResultMarshaling)(nil)
// MarshalJSON marshals as JSON.
func (e ExecutionResult) MarshalJSON() ([]byte, error) {
type ExecutionResult struct {
StateRoot common.Hash `json:"stateRoot"`
TxRoot common.Hash `json:"txRoot"`
ReceiptRoot common.Hash `json:"receiptsRoot"`
LogsHash common.Hash `json:"logsHash"`
Bloom types.Bloom `json:"logsBloom" gencodec:"required"`
Receipts types.Receipts `json:"receipts"`
Rejected []*rejectedTx `json:"rejected,omitempty"`
Difficulty *math.HexOrDecimal256 `json:"currentDifficulty" gencodec:"required"`
GasUsed math.HexOrDecimal64 `json:"gasUsed"`
BaseFee *math.HexOrDecimal256 `json:"currentBaseFee,omitempty"`
WithdrawalsRoot *common.Hash `json:"withdrawalsRoot,omitempty"`
CurrentExcessBlobGas *math.HexOrDecimal64 `json:"currentExcessBlobGas,omitempty"`
CurrentBlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed,omitempty"`
RequestsHash *common.Hash `json:"requestsHash,omitempty"`
Requests []hexutil.Bytes `json:"requests,omitempty"`
}
var enc ExecutionResult
enc.StateRoot = e.StateRoot
enc.TxRoot = e.TxRoot
enc.ReceiptRoot = e.ReceiptRoot
enc.LogsHash = e.LogsHash
enc.Bloom = e.Bloom
enc.Receipts = e.Receipts
enc.Rejected = e.Rejected
enc.Difficulty = e.Difficulty
enc.GasUsed = e.GasUsed
enc.BaseFee = e.BaseFee
enc.WithdrawalsRoot = e.WithdrawalsRoot
enc.CurrentExcessBlobGas = e.CurrentExcessBlobGas
enc.CurrentBlobGasUsed = e.CurrentBlobGasUsed
enc.RequestsHash = e.RequestsHash
if e.Requests != nil {
enc.Requests = make([]hexutil.Bytes, len(e.Requests))
for k, v := range e.Requests {
enc.Requests[k] = v
}
}
return json.Marshal(&enc)
}
// UnmarshalJSON unmarshals from JSON.
func (e *ExecutionResult) UnmarshalJSON(input []byte) error {
type ExecutionResult struct {
StateRoot *common.Hash `json:"stateRoot"`
TxRoot *common.Hash `json:"txRoot"`
ReceiptRoot *common.Hash `json:"receiptsRoot"`
LogsHash *common.Hash `json:"logsHash"`
Bloom *types.Bloom `json:"logsBloom" gencodec:"required"`
Receipts *types.Receipts `json:"receipts"`
Rejected []*rejectedTx `json:"rejected,omitempty"`
Difficulty *math.HexOrDecimal256 `json:"currentDifficulty" gencodec:"required"`
GasUsed *math.HexOrDecimal64 `json:"gasUsed"`
BaseFee *math.HexOrDecimal256 `json:"currentBaseFee,omitempty"`
WithdrawalsRoot *common.Hash `json:"withdrawalsRoot,omitempty"`
CurrentExcessBlobGas *math.HexOrDecimal64 `json:"currentExcessBlobGas,omitempty"`
CurrentBlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed,omitempty"`
RequestsHash *common.Hash `json:"requestsHash,omitempty"`
Requests []hexutil.Bytes `json:"requests,omitempty"`
}
var dec ExecutionResult
if err := json.Unmarshal(input, &dec); err != nil {
return err
}
if dec.StateRoot != nil {
e.StateRoot = *dec.StateRoot
}
if dec.TxRoot != nil {
e.TxRoot = *dec.TxRoot
}
if dec.ReceiptRoot != nil {
e.ReceiptRoot = *dec.ReceiptRoot
}
if dec.LogsHash != nil {
e.LogsHash = *dec.LogsHash
}
if dec.Bloom == nil {
return errors.New("missing required field 'logsBloom' for ExecutionResult")
}
e.Bloom = *dec.Bloom
if dec.Receipts != nil {
e.Receipts = *dec.Receipts
}
if dec.Rejected != nil {
e.Rejected = dec.Rejected
}
if dec.Difficulty == nil {
return errors.New("missing required field 'currentDifficulty' for ExecutionResult")
}
e.Difficulty = dec.Difficulty
if dec.GasUsed != nil {
e.GasUsed = *dec.GasUsed
}
if dec.BaseFee != nil {
e.BaseFee = dec.BaseFee
}
if dec.WithdrawalsRoot != nil {
e.WithdrawalsRoot = dec.WithdrawalsRoot
}
if dec.CurrentExcessBlobGas != nil {
e.CurrentExcessBlobGas = dec.CurrentExcessBlobGas
}
if dec.CurrentBlobGasUsed != nil {
e.CurrentBlobGasUsed = dec.CurrentBlobGasUsed
}
if dec.RequestsHash != nil {
e.RequestsHash = dec.RequestsHash
}
if dec.Requests != nil {
e.Requests = make([][]byte, len(dec.Requests))
for k, v := range dec.Requests {
e.Requests[k] = v
}
}
return nil
}

View file

@ -138,61 +138,90 @@ var (
Usage: "enable return data output", Usage: "enable return data output",
Category: flags.VMCategory, Category: flags.VMCategory,
} }
refTestFlag = &cli.StringFlag{
Name: "test",
Usage: "Path to EOF validation reference test.",
}
hexFlag = &cli.StringFlag{
Name: "hex",
Usage: "single container data parse and validation",
}
) )
var stateTransitionCommand = &cli.Command{ var (
Name: "transition", stateTransitionCommand = &cli.Command{
Aliases: []string{"t8n"}, Name: "transition",
Usage: "Executes a full state transition", Aliases: []string{"t8n"},
Action: t8ntool.Transition, Usage: "Executes a full state transition",
Flags: []cli.Flag{ Action: t8ntool.Transition,
t8ntool.TraceFlag, Flags: []cli.Flag{
t8ntool.TraceTracerFlag, t8ntool.TraceFlag,
t8ntool.TraceTracerConfigFlag, t8ntool.TraceTracerFlag,
t8ntool.TraceEnableMemoryFlag, t8ntool.TraceTracerConfigFlag,
t8ntool.TraceDisableStackFlag, t8ntool.TraceEnableMemoryFlag,
t8ntool.TraceEnableReturnDataFlag, t8ntool.TraceDisableStackFlag,
t8ntool.TraceEnableCallFramesFlag, t8ntool.TraceEnableReturnDataFlag,
t8ntool.OutputBasedir, t8ntool.TraceEnableCallFramesFlag,
t8ntool.OutputAllocFlag, t8ntool.OutputBasedir,
t8ntool.OutputResultFlag, t8ntool.OutputAllocFlag,
t8ntool.OutputBodyFlag, t8ntool.OutputResultFlag,
t8ntool.InputAllocFlag, t8ntool.OutputBodyFlag,
t8ntool.InputEnvFlag, t8ntool.InputAllocFlag,
t8ntool.InputTxsFlag, t8ntool.InputEnvFlag,
t8ntool.ForknameFlag, t8ntool.InputTxsFlag,
t8ntool.ChainIDFlag, t8ntool.ForknameFlag,
t8ntool.RewardFlag, t8ntool.ChainIDFlag,
}, t8ntool.RewardFlag,
} },
}
var transactionCommand = &cli.Command{ transactionCommand = &cli.Command{
Name: "transaction", Name: "transaction",
Aliases: []string{"t9n"}, Aliases: []string{"t9n"},
Usage: "Performs transaction validation", Usage: "Performs transaction validation",
Action: t8ntool.Transaction, Action: t8ntool.Transaction,
Flags: []cli.Flag{ Flags: []cli.Flag{
t8ntool.InputTxsFlag, t8ntool.InputTxsFlag,
t8ntool.ChainIDFlag, t8ntool.ChainIDFlag,
t8ntool.ForknameFlag, t8ntool.ForknameFlag,
}, },
} }
var blockBuilderCommand = &cli.Command{ blockBuilderCommand = &cli.Command{
Name: "block-builder", Name: "block-builder",
Aliases: []string{"b11r"}, Aliases: []string{"b11r"},
Usage: "Builds a block", Usage: "Builds a block",
Action: t8ntool.BuildBlock, Action: t8ntool.BuildBlock,
Flags: []cli.Flag{ Flags: []cli.Flag{
t8ntool.OutputBasedir, t8ntool.OutputBasedir,
t8ntool.OutputBlockFlag, t8ntool.OutputBlockFlag,
t8ntool.InputHeaderFlag, t8ntool.InputHeaderFlag,
t8ntool.InputOmmersFlag, t8ntool.InputOmmersFlag,
t8ntool.InputWithdrawalsFlag, t8ntool.InputWithdrawalsFlag,
t8ntool.InputTxsRlpFlag, t8ntool.InputTxsRlpFlag,
t8ntool.SealCliqueFlag, t8ntool.SealCliqueFlag,
}, },
} }
eofParseCommand = &cli.Command{
Name: "eofparse",
Aliases: []string{"eof"},
Usage: "Parses hex eof container and returns validation errors (if any)",
Action: eofParseAction,
Flags: []cli.Flag{
hexFlag,
refTestFlag,
},
}
eofDumpCommand = &cli.Command{
Name: "eofdump",
Usage: "Parses hex eof container and prints out human-readable representation of the container.",
Action: eofDumpAction,
Flags: []cli.Flag{
hexFlag,
},
}
)
// vmFlags contains flags related to running the EVM. // vmFlags contains flags related to running the EVM.
var vmFlags = []cli.Flag{ var vmFlags = []cli.Flag{
@ -235,6 +264,8 @@ func init() {
stateTransitionCommand, stateTransitionCommand,
transactionCommand, transactionCommand,
blockBuilderCommand, blockBuilderCommand,
eofParseCommand,
eofDumpCommand,
} }
app.Before = func(ctx *cli.Context) error { app.Before = func(ctx *cli.Context) error {
flags.MigrateGlobalFlags(ctx) flags.MigrateGlobalFlags(ctx)

View file

@ -524,7 +524,7 @@ func TestT9n(t *testing.T) {
ok, err := cmpJson(have, want) ok, err := cmpJson(have, want)
switch { switch {
case err != nil: case err != nil:
t.Logf(string(have)) t.Log(string(have))
t.Fatalf("test %d, json parsing failed: %v", i, err) t.Fatalf("test %d, json parsing failed: %v", i, err)
case !ok: case !ok:
t.Fatalf("test %d: output wrong, have \n%v\nwant\n%v\n", i, string(have), string(want)) t.Fatalf("test %d: output wrong, have \n%v\nwant\n%v\n", i, string(have), string(want))
@ -659,7 +659,7 @@ func TestB11r(t *testing.T) {
ok, err := cmpJson(have, want) ok, err := cmpJson(have, want)
switch { switch {
case err != nil: case err != nil:
t.Logf(string(have)) t.Log(string(have))
t.Fatalf("test %d, json parsing failed: %v", i, err) t.Fatalf("test %d, json parsing failed: %v", i, err)
case !ok: case !ok:
t.Fatalf("test %d: output wrong, have \n%v\nwant\n%v\n", i, string(have), string(want)) t.Fatalf("test %d: output wrong, have \n%v\nwant\n%v\n", i, string(have), string(want))

19
cmd/evm/testdata/eof/eof_benches.txt vendored Normal file

File diff suppressed because one or more lines are too long

1986
cmd/evm/testdata/eof/eof_corpus_0.txt vendored Normal file

File diff suppressed because one or more lines are too long

350
cmd/evm/testdata/eof/eof_corpus_1.txt vendored Normal file

File diff suppressed because one or more lines are too long

2336
cmd/evm/testdata/eof/results.initcode.txt vendored Normal file

File diff suppressed because it is too large Load diff

2336
cmd/evm/testdata/eof/results.regular.txt vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -113,7 +113,6 @@ func TestAccountImport(t *testing.T) {
}, },
} }
for _, test := range tests { for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
t.Parallel() t.Parallel()
importAccountWithExpect(t, test.key, test.output) importAccountWithExpect(t, test.key, test.output)

View file

@ -221,22 +221,23 @@ func initGenesis(ctx *cli.Context) error {
v := ctx.Uint64(utils.OverrideVerkle.Name) v := ctx.Uint64(utils.OverrideVerkle.Name)
overrides.OverrideVerkle = &v overrides.OverrideVerkle = &v
} }
for _, name := range []string{"chaindata", "lightchaindata"} {
chaindb, err := stack.OpenDatabaseWithFreezer(name, 0, 0, ctx.String(utils.AncientFlag.Name), "", false)
if err != nil {
utils.Fatalf("Failed to open database: %v", err)
}
defer chaindb.Close()
triedb := utils.MakeTrieDatabase(ctx, chaindb, ctx.Bool(utils.CachePreimagesFlag.Name), false, genesis.IsVerkle()) chaindb, err := stack.OpenDatabaseWithFreezer("chaindata", 0, 0, ctx.String(utils.AncientFlag.Name), "", false)
defer triedb.Close() if err != nil {
utils.Fatalf("Failed to open database: %v", err)
_, hash, err := core.SetupGenesisBlockWithOverride(chaindb, triedb, genesis, &overrides)
if err != nil {
utils.Fatalf("Failed to write genesis block: %v", err)
}
log.Info("Successfully wrote genesis state", "database", name, "hash", hash)
} }
defer chaindb.Close()
triedb := utils.MakeTrieDatabase(ctx, chaindb, ctx.Bool(utils.CachePreimagesFlag.Name), false, genesis.IsVerkle())
defer triedb.Close()
_, hash, err := core.SetupGenesisBlockWithOverride(chaindb, triedb, genesis, &overrides)
if err != nil {
utils.Fatalf("Failed to write genesis block: %v", err)
}
log.Info("Successfully wrote genesis state", "database", "chaindata", "hash", hash)
return nil return nil
} }
@ -258,29 +259,22 @@ func dumpGenesis(ctx *cli.Context) error {
// dump whatever already exists in the datadir // dump whatever already exists in the datadir
stack, _ := makeConfigNode(ctx) stack, _ := makeConfigNode(ctx)
for _, name := range []string{"chaindata", "lightchaindata"} {
db, err := stack.OpenDatabase(name, 0, 0, "", true)
if err != nil {
if !os.IsNotExist(err) {
return err
}
continue
}
genesis, err := core.ReadGenesis(db)
if err != nil {
utils.Fatalf("failed to read genesis: %s", err)
}
db.Close()
if err := json.NewEncoder(os.Stdout).Encode(*genesis); err != nil { db, err := stack.OpenDatabase("chaindata", 0, 0, "", true)
utils.Fatalf("could not encode stored genesis: %s", err) if err != nil {
} return err
return nil
} }
if ctx.IsSet(utils.DataDirFlag.Name) { defer db.Close()
utils.Fatalf("no existing datadir at %s", stack.Config().DataDir)
genesis, err = core.ReadGenesis(db)
if err != nil {
utils.Fatalf("failed to read genesis: %s", err)
} }
utils.Fatalf("no network preset provided, and no genesis exists in the default datadir")
if err := json.NewEncoder(os.Stdout).Encode(*genesis); err != nil {
utils.Fatalf("could not encode stored genesis: %s", err)
}
return nil return nil
} }
@ -557,7 +551,7 @@ func parseDumpConfig(ctx *cli.Context, db ethdb.Database) (*state.DumpConfig, co
default: default:
return nil, common.Hash{}, fmt.Errorf("invalid start argument: %x. 20 or 32 hex-encoded bytes required", startArg) return nil, common.Hash{}, fmt.Errorf("invalid start argument: %x. 20 or 32 hex-encoded bytes required", startArg)
} }
var conf = &state.DumpConfig{ conf := &state.DumpConfig{
SkipCode: ctx.Bool(utils.ExcludeCodeFlag.Name), SkipCode: ctx.Bool(utils.ExcludeCodeFlag.Name),
SkipStorage: ctx.Bool(utils.ExcludeStorageFlag.Name), SkipStorage: ctx.Bool(utils.ExcludeStorageFlag.Name),
OnlyWithAddresses: !ctx.Bool(utils.IncludeIncompletesFlag.Name), OnlyWithAddresses: !ctx.Bool(utils.IncludeIncompletesFlag.Name),

View file

@ -152,7 +152,7 @@ func remoteConsole(ctx *cli.Context) error {
func ephemeralConsole(ctx *cli.Context) error { func ephemeralConsole(ctx *cli.Context) error {
var b strings.Builder var b strings.Builder
for _, file := range ctx.Args().Slice() { for _, file := range ctx.Args().Slice() {
b.Write([]byte(fmt.Sprintf("loadScript('%s');", file))) b.WriteString(fmt.Sprintf("loadScript('%s');", file))
} }
utils.Fatalf(`The "js" command is deprecated. Please use the following instead: utils.Fatalf(`The "js" command is deprecated. Please use the following instead:
geth --exec "%s" console`, b.String()) geth --exec "%s" console`, b.String())

View file

@ -156,7 +156,6 @@ var (
utils.BeaconGenesisRootFlag, utils.BeaconGenesisRootFlag,
utils.BeaconGenesisTimeFlag, utils.BeaconGenesisTimeFlag,
utils.BeaconCheckpointFlag, utils.BeaconCheckpointFlag,
utils.CollectWitnessFlag,
}, utils.NetworkFlags, utils.DatabaseFlags) }, utils.NetworkFlags, utils.DatabaseFlags)
rpcFlags = []cli.Flag{ rpcFlags = []cli.Flag{

View file

@ -170,7 +170,6 @@ func TestKeyID(t *testing.T) {
{"third key", args{id: extractKeyId(gethPubKeys[2])}, "FD9813B2D2098484"}, {"third key", args{id: extractKeyId(gethPubKeys[2])}, "FD9813B2D2098484"},
} }
for _, tt := range tests { for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
t.Parallel() t.Parallel()
if got := keyID(tt.args.id); got != tt.want { if got := keyID(tt.args.id); got != tt.want {

View file

@ -142,7 +142,7 @@ func dump(in *inStream, s *rlp.Stream, depth int, out io.Writer) error {
s.List() s.List()
defer s.ListEnd() defer s.ListEnd()
if size == 0 { if size == 0 {
fmt.Fprintf(out, ws(depth)+"[]") fmt.Fprint(out, ws(depth)+"[]")
} else { } else {
fmt.Fprintln(out, ws(depth)+"[") fmt.Fprintln(out, ws(depth)+"[")
for i := 0; ; i++ { for i := 0; ; i++ {

View file

@ -600,11 +600,6 @@ var (
Usage: "Disables db compaction after import", Usage: "Disables db compaction after import",
Category: flags.LoggingCategory, Category: flags.LoggingCategory,
} }
CollectWitnessFlag = &cli.BoolFlag{
Name: "collectwitness",
Usage: "Enable state witness generation during block execution. Work in progress flag, don't use.",
Category: flags.MiscCategory,
}
// MISC settings // MISC settings
SyncTargetFlag = &cli.StringFlag{ SyncTargetFlag = &cli.StringFlag{
@ -1312,7 +1307,6 @@ func MakeAddress(ks *keystore.KeyStore, account string) (accounts.Account, error
func setEtherbase(ctx *cli.Context, cfg *ethconfig.Config) { func setEtherbase(ctx *cli.Context, cfg *ethconfig.Config) {
if ctx.IsSet(MinerEtherbaseFlag.Name) { if ctx.IsSet(MinerEtherbaseFlag.Name) {
log.Warn("Option --miner.etherbase is deprecated as the etherbase is set by the consensus client post-merge") log.Warn("Option --miner.etherbase is deprecated as the etherbase is set by the consensus client post-merge")
return
} }
if !ctx.IsSet(MinerPendingFeeRecipientFlag.Name) { if !ctx.IsSet(MinerPendingFeeRecipientFlag.Name) {
return return
@ -1767,9 +1761,6 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
// TODO(fjl): force-enable this in --dev mode // TODO(fjl): force-enable this in --dev mode
cfg.EnablePreimageRecording = ctx.Bool(VMEnableDebugFlag.Name) cfg.EnablePreimageRecording = ctx.Bool(VMEnableDebugFlag.Name)
} }
if ctx.IsSet(CollectWitnessFlag.Name) {
cfg.EnableWitnessCollection = ctx.Bool(CollectWitnessFlag.Name)
}
if ctx.IsSet(RPCGlobalGasCapFlag.Name) { if ctx.IsSet(RPCGlobalGasCapFlag.Name) {
cfg.RPCGasCap = ctx.Uint64(RPCGlobalGasCapFlag.Name) cfg.RPCGasCap = ctx.Uint64(RPCGlobalGasCapFlag.Name)
@ -1934,7 +1925,7 @@ func SetDNSDiscoveryDefaults(cfg *ethconfig.Config, genesis common.Hash) {
// RegisterEthService adds an Ethereum client to the stack. // RegisterEthService adds an Ethereum client to the stack.
// The second return value is the full node instance. // The second return value is the full node instance.
func RegisterEthService(stack *node.Node, cfg *ethconfig.Config) (ethapi.Backend, *eth.Ethereum) { func RegisterEthService(stack *node.Node, cfg *ethconfig.Config) (*eth.EthAPIBackend, *eth.Ethereum) {
backend, err := eth.New(stack, cfg) backend, err := eth.New(stack, cfg)
if err != nil { if err != nil {
Fatalf("Failed to register the Ethereum service: %v", err) Fatalf("Failed to register the Ethereum service: %v", err)
@ -1944,7 +1935,7 @@ func RegisterEthService(stack *node.Node, cfg *ethconfig.Config) (ethapi.Backend
} }
// RegisterEthStatsService configures the Ethereum Stats daemon and adds it to the node. // RegisterEthStatsService configures the Ethereum Stats daemon and adds it to the node.
func RegisterEthStatsService(stack *node.Node, backend ethapi.Backend, url string) { func RegisterEthStatsService(stack *node.Node, backend *eth.EthAPIBackend, url string) {
if err := ethstats.New(stack, backend, backend.Engine(), url); err != nil { if err := ethstats.New(stack, backend, backend.Engine(), url); err != nil {
Fatalf("Failed to register the Ethereum Stats service: %v", err) Fatalf("Failed to register the Ethereum Stats service: %v", err)
} }
@ -2070,8 +2061,6 @@ func MakeChainDatabase(ctx *cli.Context, stack *node.Node, readonly bool) ethdb.
break break
} }
chainDb = remotedb.New(client) chainDb = remotedb.New(client)
case ctx.String(SyncModeFlag.Name) == "light":
chainDb, err = stack.OpenDatabase("lightchaindata", cache, handles, "", readonly)
default: default:
chainDb, err = stack.OpenDatabaseWithFreezer("chaindata", cache, handles, ctx.String(AncientFlag.Name), "", readonly) chainDb, err = stack.OpenDatabaseWithFreezer("chaindata", cache, handles, ctx.String(AncientFlag.Name), "", readonly)
} }
@ -2194,7 +2183,6 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh
} }
vmcfg := vm.Config{ vmcfg := vm.Config{
EnablePreimageRecording: ctx.Bool(VMEnableDebugFlag.Name), EnablePreimageRecording: ctx.Bool(VMEnableDebugFlag.Name),
EnableWitnessCollection: ctx.Bool(CollectWitnessFlag.Name),
} }
if ctx.IsSet(VMTraceFlag.Name) { if ctx.IsSet(VMTraceFlag.Name) {
if name := ctx.String(VMTraceFlag.Name); name != "" { if name := ctx.String(VMTraceFlag.Name); name != "" {

View file

@ -56,7 +56,6 @@ func Test_SplitTagsFlag(t *testing.T) {
}, },
} }
for _, tt := range tests { for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
t.Parallel() t.Parallel()
if got := SplitTagsFlag(tt.args); !reflect.DeepEqual(got, tt.want) { if got := SplitTagsFlag(tt.args); !reflect.DeepEqual(got, tt.want) {

View file

@ -66,7 +66,6 @@ func TestGetPassPhraseWithList(t *testing.T) {
}, },
} }
for _, tt := range tests { for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
t.Parallel() t.Parallel()
if got := GetPassPhraseWithList(tt.args.text, tt.args.confirmation, tt.args.index, tt.args.passwords); got != tt.want { if got := GetPassPhraseWithList(tt.args.text, tt.args.confirmation, tt.args.index, tt.args.passwords); got != tt.want {

View file

@ -26,12 +26,13 @@ import (
// Iterator for disassembled EVM instructions // Iterator for disassembled EVM instructions
type instructionIterator struct { type instructionIterator struct {
code []byte code []byte
pc uint64 pc uint64
arg []byte arg []byte
op vm.OpCode op vm.OpCode
error error error error
started bool started bool
eofEnabled bool
} }
// NewInstructionIterator creates a new instruction iterator. // NewInstructionIterator creates a new instruction iterator.
@ -41,6 +42,13 @@ func NewInstructionIterator(code []byte) *instructionIterator {
return it return it
} }
// NewEOFInstructionIterator creates a new instruction iterator for EOF-code.
func NewEOFInstructionIterator(code []byte) *instructionIterator {
it := NewInstructionIterator(code)
it.eofEnabled = true
return it
}
// Next returns true if there is a next instruction and moves on. // Next returns true if there is a next instruction and moves on.
func (it *instructionIterator) Next() bool { func (it *instructionIterator) Next() bool {
if it.error != nil || uint64(len(it.code)) <= it.pc { if it.error != nil || uint64(len(it.code)) <= it.pc {
@ -63,13 +71,26 @@ func (it *instructionIterator) Next() bool {
// We reached the end. // We reached the end.
return false return false
} }
it.op = vm.OpCode(it.code[it.pc]) it.op = vm.OpCode(it.code[it.pc])
if it.op.IsPush() { var a int
a := uint64(it.op) - uint64(vm.PUSH0) if !it.eofEnabled { // Legacy code
u := it.pc + 1 + a if it.op.IsPush() {
a = int(it.op) - int(vm.PUSH0)
}
} else { // EOF code
if it.op == vm.RJUMPV {
// RJUMPV is unique as it has a variable sized operand. The total size is
// determined by the count byte which immediately follows RJUMPV.
maxIndex := int(it.code[it.pc+1])
a = (maxIndex+1)*2 + 1
} else {
a = vm.Immediates(it.op)
}
}
if a > 0 {
u := it.pc + 1 + uint64(a)
if uint64(len(it.code)) <= it.pc || uint64(len(it.code)) < u { if uint64(len(it.code)) <= it.pc || uint64(len(it.code)) < u {
it.error = fmt.Errorf("incomplete push instruction at %v", it.pc) it.error = fmt.Errorf("incomplete instruction at %v", it.pc)
return false return false
} }
it.arg = it.code[it.pc+1 : u] it.arg = it.code[it.pc+1 : u]
@ -105,7 +126,6 @@ func PrintDisassembled(code string) error {
if err != nil { if err != nil {
return err return err
} }
it := NewInstructionIterator(script) it := NewInstructionIterator(script)
for it.Next() { for it.Next() {
if it.Arg() != nil && 0 < len(it.Arg()) { if it.Arg() != nil && 0 < len(it.Arg()) {

View file

@ -17,42 +17,78 @@
package asm package asm
import ( import (
"testing"
"encoding/hex" "encoding/hex"
"fmt"
"strings"
"testing"
) )
// Tests disassembling instructions // Tests disassembling instructions
func TestInstructionIterator(t *testing.T) { func TestInstructionIterator(t *testing.T) {
for i, tc := range []struct { for i, tc := range []struct {
want int code string
code string legacyWant string
wantErr string eofWant string
}{ }{
{2, "61000000", ""}, // valid code {"", "", ""}, // empty
{0, "6100", "incomplete push instruction at 0"}, // invalid code {"6100", `err: incomplete instruction at 0`, `err: incomplete instruction at 0`},
{2, "5900", ""}, // push0 {"61000000", `
{0, "", ""}, // empty 00000: PUSH2 0x0000
00003: STOP`, `
00000: PUSH2 0x0000
00003: STOP`},
{"5F00", `
00000: PUSH0
00001: STOP`, `
00000: PUSH0
00001: STOP`},
{"d1aabb00", `00000: DATALOADN
00001: opcode 0xaa not defined
00002: opcode 0xbb not defined
00003: STOP`, `
00000: DATALOADN 0xaabb
00003: STOP`}, // DATALOADN(aabb),STOP
{"d1aa", `
00000: DATALOADN
00001: opcode 0xaa not defined`, "err: incomplete instruction at 0\n"}, // DATALOADN(aa) invalid
{"e20211223344556600", `
00000: RJUMPV
00001: MUL
00002: GT
00003: opcode 0x22 not defined
00004: CALLER
00005: DIFFICULTY
00006: SSTORE
err: incomplete instruction at 7`, `
00000: RJUMPV 0x02112233445566
00008: STOP`}, // RJUMPV( 6 bytes), STOP
} { } {
var ( var (
have int
code, _ = hex.DecodeString(tc.code) code, _ = hex.DecodeString(tc.code)
it = NewInstructionIterator(code) legacy = strings.TrimSpace(disassembly(NewInstructionIterator(code)))
eof = strings.TrimSpace(disassembly(NewEOFInstructionIterator(code)))
) )
for it.Next() { if want := strings.TrimSpace(tc.legacyWant); legacy != want {
have++ t.Errorf("test %d: wrong (legacy) output. have:\n%q\nwant:\n%q\n", i, legacy, want)
} }
var haveErr = "" if want := strings.TrimSpace(tc.eofWant); eof != want {
if it.Error() != nil { t.Errorf("test %d: wrong (eof) output. have:\n%q\nwant:\n%q\n", i, eof, want)
haveErr = it.Error().Error()
}
if haveErr != tc.wantErr {
t.Errorf("test %d: encountered error: %q want %q", i, haveErr, tc.wantErr)
continue
}
if have != tc.want {
t.Errorf("wrong instruction count, have %d want %d", have, tc.want)
} }
} }
} }
func disassembly(it *instructionIterator) string {
var out = new(strings.Builder)
for it.Next() {
if it.Arg() != nil && 0 < len(it.Arg()) {
fmt.Fprintf(out, "%05x: %v %#x\n", it.PC(), it.Op(), it.Arg())
} else {
fmt.Fprintf(out, "%05x: %v\n", it.PC(), it.Op())
}
}
if err := it.Error(); err != nil {
fmt.Fprintf(out, "err: %v\n", err)
}
return out.String()
}

View file

@ -20,10 +20,8 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/stateless"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
@ -123,7 +121,7 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
// such as amount of used gas, the receipt roots and the state root itself. // such as amount of used gas, the receipt roots and the state root itself.
func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateDB, res *ProcessResult, stateless bool) error { func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateDB, res *ProcessResult, stateless bool) error {
if res == nil { if res == nil {
return fmt.Errorf("nil ProcessResult value") return errors.New("nil ProcessResult value")
} }
header := block.Header() header := block.Header()
if block.GasUsed() != res.GasUsed { if block.GasUsed() != res.GasUsed {
@ -147,10 +145,12 @@ func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateD
} }
// Validate the parsed requests match the expected header value. // Validate the parsed requests match the expected header value.
if header.RequestsHash != nil { if header.RequestsHash != nil {
depositSha := types.DeriveSha(res.Requests, trie.NewStackTrie(nil)) reqhash := types.CalcRequestsHash(res.Requests)
if depositSha != *header.RequestsHash { if reqhash != *header.RequestsHash {
return fmt.Errorf("invalid deposit root hash (remote: %x local: %x)", *header.RequestsHash, depositSha) return fmt.Errorf("invalid requests hash (remote: %x local: %x)", *header.RequestsHash, reqhash)
} }
} else if res.Requests != nil {
return errors.New("block has requests before prague fork")
} }
// Validate the state root against the received state root and throw // Validate the state root against the received state root and throw
// an error if they don't match. // an error if they don't match.
@ -160,28 +160,6 @@ func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateD
return nil return nil
} }
// ValidateWitness cross validates a block execution with stateless remote clients.
//
// Normally we'd distribute the block witness to remote cross validators, wait
// for them to respond and then merge the results. For now, however, it's only
// Geth, so do an internal stateless run.
func (v *BlockValidator) ValidateWitness(witness *stateless.Witness, receiptRoot common.Hash, stateRoot common.Hash) error {
// Run the cross client stateless execution
// TODO(karalabe): Self-stateless for now, swap with other clients
crossReceiptRoot, crossStateRoot, err := ExecuteStateless(v.config, witness)
if err != nil {
return fmt.Errorf("stateless execution failed: %v", err)
}
// Stateless cross execution suceeeded, validate the withheld computed fields
if crossReceiptRoot != receiptRoot {
return fmt.Errorf("cross validator receipt root mismatch (cross: %x local: %x)", crossReceiptRoot, receiptRoot)
}
if crossStateRoot != stateRoot {
return fmt.Errorf("cross validator state root mismatch (cross: %x local: %x)", crossStateRoot, stateRoot)
}
return nil
}
// CalcGasLimit computes the gas limit of the next block after parent. It aims // CalcGasLimit computes the gas limit of the next block after parent. It aims
// to keep the baseline gas close to the provided target, and increase it towards // to keep the baseline gas close to the provided target, and increase it towards
// the target if the baseline gas is lower. // the target if the baseline gas is lower.

View file

@ -201,7 +201,7 @@ func testHeaderVerificationForMerging(t *testing.T, isClique bool) {
t.Fatalf("post-block %d: unexpected result returned: %v", i, result) t.Fatalf("post-block %d: unexpected result returned: %v", i, result)
case <-time.After(25 * time.Millisecond): case <-time.After(25 * time.Millisecond):
} }
chain.InsertBlockWithoutSetHead(postBlocks[i]) chain.InsertBlockWithoutSetHead(postBlocks[i], false)
} }
// Verify the blocks with pre-merge blocks and post-merge blocks // Verify the blocks with pre-merge blocks and post-merge blocks

View file

@ -78,10 +78,11 @@ var (
snapshotCommitTimer = metrics.NewRegisteredResettingTimer("chain/snapshot/commits", nil) snapshotCommitTimer = metrics.NewRegisteredResettingTimer("chain/snapshot/commits", nil)
triedbCommitTimer = metrics.NewRegisteredResettingTimer("chain/triedb/commits", nil) triedbCommitTimer = metrics.NewRegisteredResettingTimer("chain/triedb/commits", nil)
blockInsertTimer = metrics.NewRegisteredResettingTimer("chain/inserts", nil) blockInsertTimer = metrics.NewRegisteredResettingTimer("chain/inserts", nil)
blockValidationTimer = metrics.NewRegisteredResettingTimer("chain/validation", nil) blockValidationTimer = metrics.NewRegisteredResettingTimer("chain/validation", nil)
blockExecutionTimer = metrics.NewRegisteredResettingTimer("chain/execution", nil) blockCrossValidationTimer = metrics.NewRegisteredResettingTimer("chain/crossvalidation", nil)
blockWriteTimer = metrics.NewRegisteredResettingTimer("chain/write", nil) blockExecutionTimer = metrics.NewRegisteredResettingTimer("chain/execution", nil)
blockWriteTimer = metrics.NewRegisteredResettingTimer("chain/write", nil)
blockReorgMeter = metrics.NewRegisteredMeter("chain/reorg/executes", nil) blockReorgMeter = metrics.NewRegisteredMeter("chain/reorg/executes", nil)
blockReorgAddMeter = metrics.NewRegisteredMeter("chain/reorg/add", nil) blockReorgAddMeter = metrics.NewRegisteredMeter("chain/reorg/add", nil)
@ -1598,7 +1599,9 @@ func (bc *BlockChain) InsertChain(chain types.Blocks) (int, error) {
return 0, errChainStopped return 0, errChainStopped
} }
defer bc.chainmu.Unlock() defer bc.chainmu.Unlock()
return bc.insertChain(chain, true)
_, n, err := bc.insertChain(chain, true, false) // No witness collection for mass inserts (would get super large)
return n, err
} }
// insertChain is the internal implementation of InsertChain, which assumes that // insertChain is the internal implementation of InsertChain, which assumes that
@ -1609,10 +1612,10 @@ func (bc *BlockChain) InsertChain(chain types.Blocks) (int, error) {
// racey behaviour. If a sidechain import is in progress, and the historic state // racey behaviour. If a sidechain import is in progress, and the historic state
// is imported, but then new canon-head is added before the actual sidechain // is imported, but then new canon-head is added before the actual sidechain
// completes, then the historic state could be pruned again // completes, then the historic state could be pruned again
func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) { func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness bool) (*stateless.Witness, int, error) {
// If the chain is terminating, don't even bother starting up. // If the chain is terminating, don't even bother starting up.
if bc.insertStopped() { if bc.insertStopped() {
return 0, nil return nil, 0, nil
} }
// Start a parallel signature recovery (signer will fluke on fork transition, minimal perf loss) // Start a parallel signature recovery (signer will fluke on fork transition, minimal perf loss)
SenderCacher.RecoverFromBlocks(types.MakeSigner(bc.chainConfig, chain[0].Number(), chain[0].Time()), chain) SenderCacher.RecoverFromBlocks(types.MakeSigner(bc.chainConfig, chain[0].Number(), chain[0].Time()), chain)
@ -1667,7 +1670,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
for block != nil && bc.skipBlock(err, it) { for block != nil && bc.skipBlock(err, it) {
log.Debug("Writing previously known block", "number", block.Number(), "hash", block.Hash()) log.Debug("Writing previously known block", "number", block.Number(), "hash", block.Hash())
if err := bc.writeKnownBlock(block); err != nil { if err := bc.writeKnownBlock(block); err != nil {
return it.index, err return nil, it.index, err
} }
lastCanon = block lastCanon = block
@ -1681,12 +1684,12 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
if setHead { if setHead {
// First block is pruned, insert as sidechain and reorg only if TD grows enough // First block is pruned, insert as sidechain and reorg only if TD grows enough
log.Debug("Pruned ancestor, inserting as sidechain", "number", block.Number(), "hash", block.Hash()) log.Debug("Pruned ancestor, inserting as sidechain", "number", block.Number(), "hash", block.Hash())
return bc.insertSideChain(block, it) return bc.insertSideChain(block, it, makeWitness)
} else { } else {
// We're post-merge and the parent is pruned, try to recover the parent state // We're post-merge and the parent is pruned, try to recover the parent state
log.Debug("Pruned ancestor", "number", block.Number(), "hash", block.Hash()) log.Debug("Pruned ancestor", "number", block.Number(), "hash", block.Hash())
_, err := bc.recoverAncestors(block) _, err := bc.recoverAncestors(block, makeWitness)
return it.index, err return nil, it.index, err
} }
// Some other error(except ErrKnownBlock) occurred, abort. // Some other error(except ErrKnownBlock) occurred, abort.
// ErrKnownBlock is allowed here since some known blocks // ErrKnownBlock is allowed here since some known blocks
@ -1694,7 +1697,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
case err != nil && !errors.Is(err, ErrKnownBlock): case err != nil && !errors.Is(err, ErrKnownBlock):
stats.ignored += len(it.chain) stats.ignored += len(it.chain)
bc.reportBlock(block, nil, err) bc.reportBlock(block, nil, err)
return it.index, err return nil, it.index, err
} }
// No validation errors for the first block (or chain prefix skipped) // No validation errors for the first block (or chain prefix skipped)
var activeState *state.StateDB var activeState *state.StateDB
@ -1708,6 +1711,9 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
} }
}() }()
// Track the singleton witness from this chain insertion (if any)
var witness *stateless.Witness
for ; block != nil && err == nil || errors.Is(err, ErrKnownBlock); block, err = it.next() { for ; block != nil && err == nil || errors.Is(err, ErrKnownBlock); block, err = it.next() {
// If the chain is terminating, stop processing blocks // If the chain is terminating, stop processing blocks
if bc.insertStopped() { if bc.insertStopped() {
@ -1744,7 +1750,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
"hash", block.Hash(), "number", block.NumberU64()) "hash", block.Hash(), "number", block.NumberU64())
} }
if err := bc.writeKnownBlock(block); err != nil { if err := bc.writeKnownBlock(block); err != nil {
return it.index, err return nil, it.index, err
} }
stats.processed++ stats.processed++
if bc.logger != nil && bc.logger.OnSkippedBlock != nil { if bc.logger != nil && bc.logger.OnSkippedBlock != nil {
@ -1755,13 +1761,11 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
Safe: bc.CurrentSafeBlock(), Safe: bc.CurrentSafeBlock(),
}) })
} }
// We can assume that logs are empty here, since the only way for consecutive // We can assume that logs are empty here, since the only way for consecutive
// Clique blocks to have the same state is if there are no transactions. // Clique blocks to have the same state is if there are no transactions.
lastCanon = block lastCanon = block
continue continue
} }
// Retrieve the parent block and it's state to execute on top // Retrieve the parent block and it's state to execute on top
start := time.Now() start := time.Now()
parent := it.previous() parent := it.previous()
@ -1770,7 +1774,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
} }
statedb, err := state.New(parent.Root, bc.statedb) statedb, err := state.New(parent.Root, bc.statedb)
if err != nil { if err != nil {
return it.index, err return nil, it.index, err
} }
statedb.SetLogger(bc.logger) statedb.SetLogger(bc.logger)
@ -1778,11 +1782,13 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
// while processing transactions. Before Byzantium the prefetcher is mostly // while processing transactions. Before Byzantium the prefetcher is mostly
// useless due to the intermediate root hashing after each transaction. // useless due to the intermediate root hashing after each transaction.
if bc.chainConfig.IsByzantium(block.Number()) { if bc.chainConfig.IsByzantium(block.Number()) {
var witness *stateless.Witness // Generate witnesses either if we're self-testing, or if it's the
if bc.vmConfig.EnableWitnessCollection { // only block being inserted. A bit crude, but witnesses are huge,
witness, err = stateless.NewWitness(bc, block) // so we refuse to make an entire chain of them.
if bc.vmConfig.StatelessSelfValidation || (makeWitness && len(chain) == 1) {
witness, err = stateless.NewWitness(block.Header(), bc)
if err != nil { if err != nil {
return it.index, err return nil, it.index, err
} }
} }
statedb.StartPrefetcher("chain", witness) statedb.StartPrefetcher("chain", witness)
@ -1814,7 +1820,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
res, err := bc.processBlock(block, statedb, start, setHead) res, err := bc.processBlock(block, statedb, start, setHead)
followupInterrupt.Store(true) followupInterrupt.Store(true)
if err != nil { if err != nil {
return it.index, err return nil, it.index, err
} }
// Report the import stats before returning the various results // Report the import stats before returning the various results
stats.processed++ stats.processed++
@ -1831,7 +1837,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
// After merge we expect few side chains. Simply count // After merge we expect few side chains. Simply count
// all blocks the CL gives us for GC processing time // all blocks the CL gives us for GC processing time
bc.gcproc += res.procTime bc.gcproc += res.procTime
return it.index, nil // Direct block insertion of a single block return witness, it.index, nil // Direct block insertion of a single block
} }
switch res.status { switch res.status {
case CanonStatTy: case CanonStatTy:
@ -1861,7 +1867,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
} }
} }
stats.ignored += it.remaining() stats.ignored += it.remaining()
return it.index, err return witness, it.index, err
} }
// blockProcessingResult is a summary of block processing // blockProcessingResult is a summary of block processing
@ -1906,13 +1912,36 @@ func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, s
} }
vtime := time.Since(vstart) vtime := time.Since(vstart)
if witness := statedb.Witness(); witness != nil { // If witnesses was generated and stateless self-validation requested, do
if err = bc.validator.ValidateWitness(witness, block.ReceiptHash(), block.Root()); err != nil { // that now. Self validation should *never* run in production, it's more of
bc.reportBlock(block, res, err) // a tight integration to enable running *all* consensus tests through the
return nil, fmt.Errorf("cross verification failed: %v", err) // witness builder/runner, which would otherwise be impossible due to the
// various invalid chain states/behaviors being contained in those tests.
xvstart := time.Now()
if witness := statedb.Witness(); witness != nil && bc.vmConfig.StatelessSelfValidation {
log.Warn("Running stateless self-validation", "block", block.Number(), "hash", block.Hash())
// Remove critical computed fields from the block to force true recalculation
context := block.Header()
context.Root = common.Hash{}
context.ReceiptHash = common.Hash{}
task := types.NewBlockWithHeader(context).WithBody(*block.Body())
// Run the stateless self-cross-validation
crossStateRoot, crossReceiptRoot, err := ExecuteStateless(bc.chainConfig, task, witness)
if err != nil {
return nil, fmt.Errorf("stateless self-validation failed: %v", err)
}
if crossStateRoot != block.Root() {
return nil, fmt.Errorf("stateless self-validation root mismatch (cross: %x local: %x)", crossStateRoot, block.Root())
}
if crossReceiptRoot != block.ReceiptHash() {
return nil, fmt.Errorf("stateless self-validation receipt root mismatch (cross: %x local: %x)", crossReceiptRoot, block.ReceiptHash())
} }
} }
proctime := time.Since(start) // processing + validation xvtime := time.Since(xvstart)
proctime := time.Since(start) // processing + validation + cross validation
// Update the metrics touched during block processing and validation // Update the metrics touched during block processing and validation
accountReadTimer.Update(statedb.AccountReads) // Account reads are complete(in processing) accountReadTimer.Update(statedb.AccountReads) // Account reads are complete(in processing)
@ -1930,6 +1959,7 @@ func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, s
trieUpdate := statedb.AccountUpdates + statedb.StorageUpdates // The time spent on tries update trieUpdate := statedb.AccountUpdates + statedb.StorageUpdates // The time spent on tries update
blockExecutionTimer.Update(ptime - (statedb.AccountReads + statedb.StorageReads)) // The time spent on EVM processing blockExecutionTimer.Update(ptime - (statedb.AccountReads + statedb.StorageReads)) // The time spent on EVM processing
blockValidationTimer.Update(vtime - (triehash + trieUpdate)) // The time spent on block validation blockValidationTimer.Update(vtime - (triehash + trieUpdate)) // The time spent on block validation
blockCrossValidationTimer.Update(xvtime) // The time spent on stateless cross validation
// Write the block to the chain and get the status. // Write the block to the chain and get the status.
var ( var (
@ -1964,7 +1994,7 @@ func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, s
// The method writes all (header-and-body-valid) blocks to disk, then tries to // The method writes all (header-and-body-valid) blocks to disk, then tries to
// switch over to the new chain if the TD exceeded the current chain. // switch over to the new chain if the TD exceeded the current chain.
// insertSideChain is only used pre-merge. // insertSideChain is only used pre-merge.
func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (int, error) { func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator, makeWitness bool) (*stateless.Witness, int, error) {
var ( var (
externTd *big.Int externTd *big.Int
current = bc.CurrentBlock() current = bc.CurrentBlock()
@ -2000,7 +2030,7 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (i
// If someone legitimately side-mines blocks, they would still be imported as usual. However, // If someone legitimately side-mines blocks, they would still be imported as usual. However,
// we cannot risk writing unverified blocks to disk when they obviously target the pruning // we cannot risk writing unverified blocks to disk when they obviously target the pruning
// mechanism. // mechanism.
return it.index, errors.New("sidechain ghost-state attack") return nil, it.index, errors.New("sidechain ghost-state attack")
} }
} }
if externTd == nil { if externTd == nil {
@ -2011,7 +2041,7 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (i
if !bc.HasBlock(block.Hash(), block.NumberU64()) { if !bc.HasBlock(block.Hash(), block.NumberU64()) {
start := time.Now() start := time.Now()
if err := bc.writeBlockWithoutState(block, externTd); err != nil { if err := bc.writeBlockWithoutState(block, externTd); err != nil {
return it.index, err return nil, it.index, err
} }
log.Debug("Injected sidechain block", "number", block.Number(), "hash", block.Hash(), log.Debug("Injected sidechain block", "number", block.Number(), "hash", block.Hash(),
"diff", block.Difficulty(), "elapsed", common.PrettyDuration(time.Since(start)), "diff", block.Difficulty(), "elapsed", common.PrettyDuration(time.Since(start)),
@ -2028,7 +2058,7 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (i
for parent != nil && !bc.HasState(parent.Root) { for parent != nil && !bc.HasState(parent.Root) {
if bc.stateRecoverable(parent.Root) { if bc.stateRecoverable(parent.Root) {
if err := bc.triedb.Recover(parent.Root); err != nil { if err := bc.triedb.Recover(parent.Root); err != nil {
return 0, err return nil, 0, err
} }
break break
} }
@ -2038,7 +2068,7 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (i
parent = bc.GetHeader(parent.ParentHash, parent.Number.Uint64()-1) parent = bc.GetHeader(parent.ParentHash, parent.Number.Uint64()-1)
} }
if parent == nil { if parent == nil {
return it.index, errors.New("missing parent") return nil, it.index, errors.New("missing parent")
} }
// Import all the pruned blocks to make the state available // Import all the pruned blocks to make the state available
var ( var (
@ -2057,30 +2087,30 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (i
// memory here. // memory here.
if len(blocks) >= 2048 || memory > 64*1024*1024 { if len(blocks) >= 2048 || memory > 64*1024*1024 {
log.Info("Importing heavy sidechain segment", "blocks", len(blocks), "start", blocks[0].NumberU64(), "end", block.NumberU64()) log.Info("Importing heavy sidechain segment", "blocks", len(blocks), "start", blocks[0].NumberU64(), "end", block.NumberU64())
if _, err := bc.insertChain(blocks, true); err != nil { if _, _, err := bc.insertChain(blocks, true, false); err != nil {
return 0, err return nil, 0, err
} }
blocks, memory = blocks[:0], 0 blocks, memory = blocks[:0], 0
// If the chain is terminating, stop processing blocks // If the chain is terminating, stop processing blocks
if bc.insertStopped() { if bc.insertStopped() {
log.Debug("Abort during blocks processing") log.Debug("Abort during blocks processing")
return 0, nil return nil, 0, nil
} }
} }
} }
if len(blocks) > 0 { if len(blocks) > 0 {
log.Info("Importing sidechain segment", "start", blocks[0].NumberU64(), "end", blocks[len(blocks)-1].NumberU64()) log.Info("Importing sidechain segment", "start", blocks[0].NumberU64(), "end", blocks[len(blocks)-1].NumberU64())
return bc.insertChain(blocks, true) return bc.insertChain(blocks, true, makeWitness)
} }
return 0, nil return nil, 0, nil
} }
// recoverAncestors finds the closest ancestor with available state and re-execute // recoverAncestors finds the closest ancestor with available state and re-execute
// all the ancestor blocks since that. // all the ancestor blocks since that.
// recoverAncestors is only used post-merge. // recoverAncestors is only used post-merge.
// We return the hash of the latest block that we could correctly validate. // We return the hash of the latest block that we could correctly validate.
func (bc *BlockChain) recoverAncestors(block *types.Block) (common.Hash, error) { func (bc *BlockChain) recoverAncestors(block *types.Block, makeWitness bool) (common.Hash, error) {
// Gather all the sidechain hashes (full blocks may be memory heavy) // Gather all the sidechain hashes (full blocks may be memory heavy)
var ( var (
hashes []common.Hash hashes []common.Hash
@ -2120,7 +2150,7 @@ func (bc *BlockChain) recoverAncestors(block *types.Block) (common.Hash, error)
} else { } else {
b = bc.GetBlock(hashes[i], numbers[i]) b = bc.GetBlock(hashes[i], numbers[i])
} }
if _, err := bc.insertChain(types.Blocks{b}, false); err != nil { if _, _, err := bc.insertChain(types.Blocks{b}, false, makeWitness && i == 0); err != nil {
return b.ParentHash(), err return b.ParentHash(), err
} }
} }
@ -2336,14 +2366,14 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Block) error {
// The key difference between the InsertChain is it won't do the canonical chain // The key difference between the InsertChain is it won't do the canonical chain
// updating. It relies on the additional SetCanonical call to finalize the entire // updating. It relies on the additional SetCanonical call to finalize the entire
// procedure. // procedure.
func (bc *BlockChain) InsertBlockWithoutSetHead(block *types.Block) error { func (bc *BlockChain) InsertBlockWithoutSetHead(block *types.Block, makeWitness bool) (*stateless.Witness, error) {
if !bc.chainmu.TryLock() { if !bc.chainmu.TryLock() {
return errChainStopped return nil, errChainStopped
} }
defer bc.chainmu.Unlock() defer bc.chainmu.Unlock()
_, err := bc.insertChain(types.Blocks{block}, false) witness, _, err := bc.insertChain(types.Blocks{block}, false, makeWitness)
return err return witness, err
} }
// SetCanonical rewinds the chain to set the new head block as the specified // SetCanonical rewinds the chain to set the new head block as the specified
@ -2357,7 +2387,7 @@ func (bc *BlockChain) SetCanonical(head *types.Block) (common.Hash, error) {
// Re-execute the reorged chain in case the head state is missing. // Re-execute the reorged chain in case the head state is missing.
if !bc.HasState(head.Root()) { if !bc.HasState(head.Root()) {
if latestValidHash, err := bc.recoverAncestors(head); err != nil { if latestValidHash, err := bc.recoverAncestors(head, false); err != nil {
return latestValidHash, err return latestValidHash, err
} }
log.Info("Recovered head state", "number", head.Number(), "hash", head.Hash()) log.Info("Recovered head state", "number", head.Number(), "hash", head.Hash())

View file

@ -1757,8 +1757,8 @@ func testRepair(t *testing.T, tt *rewindTest, snapshots bool) {
func testRepairWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme string) { func testRepairWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme string) {
// It's hard to follow the test case, visualize the input // It's hard to follow the test case, visualize the input
// log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) // log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, true)))
// fmt.Println(tt.dump(true)) // fmt.Println(tt.dump(false))
// Create a temporary persistent database // Create a temporary persistent database
datadir := t.TempDir() datadir := t.TempDir()
@ -1908,7 +1908,7 @@ func TestIssue23496(t *testing.T) {
func testIssue23496(t *testing.T, scheme string) { func testIssue23496(t *testing.T, scheme string) {
// It's hard to follow the test case, visualize the input // It's hard to follow the test case, visualize the input
//log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) // log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, true)))
// Create a temporary persistent database // Create a temporary persistent database
datadir := t.TempDir() datadir := t.TempDir()

View file

@ -1961,7 +1961,7 @@ func testSetHead(t *testing.T, tt *rewindTest, snapshots bool) {
func testSetHeadWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme string) { func testSetHeadWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme string) {
// It's hard to follow the test case, visualize the input // It's hard to follow the test case, visualize the input
// log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) // log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, true)))
// fmt.Println(tt.dump(false)) // fmt.Println(tt.dump(false))
// Create a temporary persistent database // Create a temporary persistent database

View file

@ -222,8 +222,8 @@ type snapshotTest struct {
func (snaptest *snapshotTest) test(t *testing.T) { func (snaptest *snapshotTest) test(t *testing.T) {
// It's hard to follow the test case, visualize the input // It's hard to follow the test case, visualize the input
// log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) // log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, true)))
// fmt.Println(tt.dump()) // fmt.Println(snaptest.dump())
chain, blocks := snaptest.prepare(t) chain, blocks := snaptest.prepare(t)
// Restart the chain normally // Restart the chain normally
@ -245,8 +245,8 @@ type crashSnapshotTest struct {
func (snaptest *crashSnapshotTest) test(t *testing.T) { func (snaptest *crashSnapshotTest) test(t *testing.T) {
// It's hard to follow the test case, visualize the input // It's hard to follow the test case, visualize the input
// log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) // log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, true)))
// fmt.Println(tt.dump()) // fmt.Println(snaptest.dump())
chain, blocks := snaptest.prepare(t) chain, blocks := snaptest.prepare(t)
// Pull the plug on the database, simulating a hard crash // Pull the plug on the database, simulating a hard crash
@ -297,8 +297,8 @@ type gappedSnapshotTest struct {
func (snaptest *gappedSnapshotTest) test(t *testing.T) { func (snaptest *gappedSnapshotTest) test(t *testing.T) {
// It's hard to follow the test case, visualize the input // It's hard to follow the test case, visualize the input
// log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) // log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, true)))
// fmt.Println(tt.dump()) // fmt.Println(snaptest.dump())
chain, blocks := snaptest.prepare(t) chain, blocks := snaptest.prepare(t)
// Insert blocks without enabling snapshot if gapping is required. // Insert blocks without enabling snapshot if gapping is required.
@ -341,8 +341,8 @@ type setHeadSnapshotTest struct {
func (snaptest *setHeadSnapshotTest) test(t *testing.T) { func (snaptest *setHeadSnapshotTest) test(t *testing.T) {
// It's hard to follow the test case, visualize the input // It's hard to follow the test case, visualize the input
// log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) // log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, true)))
// fmt.Println(tt.dump()) // fmt.Println(snaptest.dump())
chain, blocks := snaptest.prepare(t) chain, blocks := snaptest.prepare(t)
// Rewind the chain if setHead operation is required. // Rewind the chain if setHead operation is required.
@ -370,8 +370,8 @@ type wipeCrashSnapshotTest struct {
func (snaptest *wipeCrashSnapshotTest) test(t *testing.T) { func (snaptest *wipeCrashSnapshotTest) test(t *testing.T) {
// It's hard to follow the test case, visualize the input // It's hard to follow the test case, visualize the input
// log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) // log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, true)))
// fmt.Println(tt.dump()) // fmt.Println(snaptest.dump())
chain, blocks := snaptest.prepare(t) chain, blocks := snaptest.prepare(t)
// Firstly, stop the chain properly, with all snapshot journal // Firstly, stop the chain properly, with all snapshot journal

View file

@ -2126,9 +2126,9 @@ func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommon
// [ Cn, Cn+1, Cc, Sn+3 ... Sm] // [ Cn, Cn+1, Cc, Sn+3 ... Sm]
// ^ ^ ^ pruned // ^ ^ ^ pruned
func TestPrunedImportSide(t *testing.T) { func TestPrunedImportSide(t *testing.T) {
//glogger := log.NewGlogHandler(log.StreamHandler(os.Stdout, log.TerminalFormat(false))) // glogger := log.NewGlogHandler(log.NewTerminalHandler(os.Stderr, false))
//glogger.Verbosity(3) // glogger.Verbosity(3)
//log.Root().SetHandler(log.Handler(glogger)) // log.SetDefault(log.NewLogger(glogger))
testSideImport(t, 3, 3, -1) testSideImport(t, 3, 3, -1)
testSideImport(t, 3, -3, -1) testSideImport(t, 3, -3, -1)
testSideImport(t, 10, 0, -1) testSideImport(t, 10, 0, -1)
@ -2137,9 +2137,9 @@ func TestPrunedImportSide(t *testing.T) {
} }
func TestPrunedImportSideWithMerging(t *testing.T) { func TestPrunedImportSideWithMerging(t *testing.T) {
//glogger := log.NewGlogHandler(log.StreamHandler(os.Stdout, log.TerminalFormat(false))) // glogger := log.NewGlogHandler(log.NewTerminalHandler(os.Stderr, false))
//glogger.Verbosity(3) // glogger.Verbosity(3)
//log.Root().SetHandler(log.Handler(glogger)) // log.SetDefault(log.NewLogger(glogger))
testSideImport(t, 3, 3, 0) testSideImport(t, 3, 3, 0)
testSideImport(t, 3, -3, 0) testSideImport(t, 3, -3, 0)
testSideImport(t, 10, 0, 0) testSideImport(t, 10, 0, 0)
@ -3629,7 +3629,7 @@ func TestSetCanonical(t *testing.T) {
} }
func testSetCanonical(t *testing.T, scheme string) { func testSetCanonical(t *testing.T, scheme string) {
//log.Root().SetHandler(log.LvlFilterHandler(log.LvlDebug, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) // log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, true)))
var ( var (
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
@ -3674,7 +3674,7 @@ func testSetCanonical(t *testing.T, scheme string) {
gen.AddTx(tx) gen.AddTx(tx)
}) })
for _, block := range side { for _, block := range side {
err := chain.InsertBlockWithoutSetHead(block) _, err := chain.InsertBlockWithoutSetHead(block, false)
if err != nil { if err != nil {
t.Fatalf("Failed to insert into chain: %v", err) t.Fatalf("Failed to insert into chain: %v", err)
} }
@ -4228,56 +4228,81 @@ func TestEIP3651(t *testing.T) {
} }
} }
func TestEIP6110(t *testing.T) { // Simple deposit generator, source: https://gist.github.com/lightclient/54abb2af2465d6969fa6d1920b9ad9d7
var depositsGeneratorCode = common.FromHex("6080604052366103aa575f603067ffffffffffffffff811115610025576100246103ae565b5b6040519080825280601f01601f1916602001820160405280156100575781602001600182028036833780820191505090505b5090505f8054906101000a900460ff1660f81b815f8151811061007d5761007c6103db565b5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690815f1a9053505f602067ffffffffffffffff8111156100c7576100c66103ae565b5b6040519080825280601f01601f1916602001820160405280156100f95781602001600182028036833780820191505090505b5090505f8054906101000a900460ff1660f81b815f8151811061011f5761011e6103db565b5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690815f1a9053505f600867ffffffffffffffff811115610169576101686103ae565b5b6040519080825280601f01601f19166020018201604052801561019b5781602001600182028036833780820191505090505b5090505f8054906101000a900460ff1660f81b815f815181106101c1576101c06103db565b5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690815f1a9053505f606067ffffffffffffffff81111561020b5761020a6103ae565b5b6040519080825280601f01601f19166020018201604052801561023d5781602001600182028036833780820191505090505b5090505f8054906101000a900460ff1660f81b815f81518110610263576102626103db565b5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690815f1a9053505f600867ffffffffffffffff8111156102ad576102ac6103ae565b5b6040519080825280601f01601f1916602001820160405280156102df5781602001600182028036833780820191505090505b5090505f8054906101000a900460ff1660f81b815f81518110610305576103046103db565b5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690815f1a9053505f8081819054906101000a900460ff168092919061035090610441565b91906101000a81548160ff021916908360ff160217905550507f649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c585858585856040516103a09594939291906104d9565b60405180910390a1005b5f80fd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f60ff82169050919050565b5f61044b82610435565b915060ff820361045e5761045d610408565b5b600182019050919050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f6104ab82610469565b6104b58185610473565b93506104c5818560208601610483565b6104ce81610491565b840191505092915050565b5f60a0820190508181035f8301526104f181886104a1565b9050818103602083015261050581876104a1565b9050818103604083015261051981866104a1565b9050818103606083015261052d81856104a1565b9050818103608083015261054181846104a1565b9050969550505050505056fea26469706673582212208569967e58690162d7d6fe3513d07b393b4c15e70f41505cbbfd08f53eba739364736f6c63430008190033")
// This is a smoke test for EIP-7685 requests added in the Prague fork. The test first
// creates a block containing requests, and then inserts it into the chain to run
// validation.
func TestPragueRequests(t *testing.T) {
var ( var (
engine = beacon.NewFaker() key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
addr1 = crypto.PubkeyToAddress(key1.PublicKey)
// A sender who makes transactions, has some funds config = *params.MergedTestChainConfig
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") signer = types.LatestSigner(&config)
addr = crypto.PubkeyToAddress(key.PublicKey) engine = beacon.NewFaker()
funds = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether))
config = *params.AllEthashProtocolChanges
gspec = &Genesis{
Config: &config,
Alloc: types.GenesisAlloc{
addr: {Balance: funds},
config.DepositContractAddress: {
// Simple deposit generator, source: https://gist.github.com/lightclient/54abb2af2465d6969fa6d1920b9ad9d7
Code: common.Hex2Bytes("6080604052366103aa575f603067ffffffffffffffff811115610025576100246103ae565b5b6040519080825280601f01601f1916602001820160405280156100575781602001600182028036833780820191505090505b5090505f8054906101000a900460ff1660f81b815f8151811061007d5761007c6103db565b5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690815f1a9053505f602067ffffffffffffffff8111156100c7576100c66103ae565b5b6040519080825280601f01601f1916602001820160405280156100f95781602001600182028036833780820191505090505b5090505f8054906101000a900460ff1660f81b815f8151811061011f5761011e6103db565b5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690815f1a9053505f600867ffffffffffffffff811115610169576101686103ae565b5b6040519080825280601f01601f19166020018201604052801561019b5781602001600182028036833780820191505090505b5090505f8054906101000a900460ff1660f81b815f815181106101c1576101c06103db565b5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690815f1a9053505f606067ffffffffffffffff81111561020b5761020a6103ae565b5b6040519080825280601f01601f19166020018201604052801561023d5781602001600182028036833780820191505090505b5090505f8054906101000a900460ff1660f81b815f81518110610263576102626103db565b5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690815f1a9053505f600867ffffffffffffffff8111156102ad576102ac6103ae565b5b6040519080825280601f01601f1916602001820160405280156102df5781602001600182028036833780820191505090505b5090505f8054906101000a900460ff1660f81b815f81518110610305576103046103db565b5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690815f1a9053505f8081819054906101000a900460ff168092919061035090610441565b91906101000a81548160ff021916908360ff160217905550507f649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c585858585856040516103a09594939291906104d9565b60405180910390a1005b5f80fd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f60ff82169050919050565b5f61044b82610435565b915060ff820361045e5761045d610408565b5b600182019050919050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f6104ab82610469565b6104b58185610473565b93506104c5818560208601610483565b6104ce81610491565b840191505092915050565b5f60a0820190508181035f8301526104f181886104a1565b9050818103602083015261050581876104a1565b9050818103604083015261051981866104a1565b9050818103606083015261052d81856104a1565b9050818103608083015261054181846104a1565b9050969550505050505056fea26469706673582212208569967e58690162d7d6fe3513d07b393b4c15e70f41505cbbfd08f53eba739364736f6c63430008190033"),
Nonce: 0,
Balance: big.NewInt(0),
},
},
}
) )
gspec := &Genesis{
gspec.Config.BerlinBlock = common.Big0 Config: &config,
gspec.Config.LondonBlock = common.Big0 Alloc: types.GenesisAlloc{
gspec.Config.TerminalTotalDifficulty = common.Big0 addr1: {Balance: big.NewInt(9999900000000000)},
gspec.Config.TerminalTotalDifficultyPassed = true config.DepositContractAddress: {Code: depositsGeneratorCode},
gspec.Config.ShanghaiTime = u64(0) params.WithdrawalQueueAddress: {Code: params.WithdrawalQueueCode},
gspec.Config.CancunTime = u64(0) params.ConsolidationQueueAddress: {Code: params.ConsolidationQueueCode},
gspec.Config.PragueTime = u64(0) },
signer := types.LatestSigner(gspec.Config) }
_, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) { _, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) {
for i := 0; i < 5; i++ { // create deposit
txdata := &types.DynamicFeeTx{ depositTx := types.MustSignNewTx(key1, signer, &types.DynamicFeeTx{
ChainID: gspec.Config.ChainID, ChainID: gspec.Config.ChainID,
Nonce: uint64(i), Nonce: 0,
To: &config.DepositContractAddress, To: &config.DepositContractAddress,
Gas: 500000, Gas: 500_000,
GasFeeCap: newGwei(5), GasFeeCap: newGwei(5),
GasTipCap: big.NewInt(2), GasTipCap: big.NewInt(2),
AccessList: nil, })
Data: []byte{}, b.AddTx(depositTx)
}
tx := types.NewTx(txdata) // create withdrawal request
tx, _ = types.SignTx(tx, signer, key) withdrawalTx := types.MustSignNewTx(key1, signer, &types.DynamicFeeTx{
b.AddTx(tx) ChainID: gspec.Config.ChainID,
} Nonce: 1,
To: &params.WithdrawalQueueAddress,
Gas: 500_000,
GasFeeCap: newGwei(5),
GasTipCap: big.NewInt(2),
Value: newGwei(1),
Data: common.FromHex("b917cfdc0d25b72d55cf94db328e1629b7f4fde2c30cdacf873b664416f76a0c7f7cc50c9f72a3cb84be88144cde91250000000000000d80"),
})
b.AddTx(withdrawalTx)
// create consolidation request
consolidationTx := types.MustSignNewTx(key1, signer, &types.DynamicFeeTx{
ChainID: gspec.Config.ChainID,
Nonce: 2,
To: &params.ConsolidationQueueAddress,
Gas: 500_000,
GasFeeCap: newGwei(5),
GasTipCap: big.NewInt(2),
Value: newGwei(1),
Data: common.FromHex("b917cfdc0d25b72d55cf94db328e1629b7f4fde2c30cdacf873b664416f76a0c7f7cc50c9f72a3cb84be88144cde9125b9812f7d0b1f2f969b52bbb2d316b0c2fa7c9dba85c428c5e6c27766bcc4b0c6e874702ff1eb1c7024b08524a9771601"),
})
b.AddTx(consolidationTx)
}) })
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{Tracer: logger.NewMarkdownLogger(&logger.Config{DisableStack: true}, os.Stderr).Hooks()}, nil)
// Check block has the correct requests hash.
rh := blocks[0].RequestsHash()
if rh == nil {
t.Fatal("block has nil requests hash")
}
expectedRequestsHash := common.HexToHash("0x06ffb72b9f0823510b128bca6cd4f96f59b745de6791e9fc350b596e7605101e")
if *rh != expectedRequestsHash {
t.Fatalf("block has wrong requestsHash %v, want %v", *rh, expectedRequestsHash)
}
// Insert block to check validation.
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{}, nil)
if err != nil { if err != nil {
t.Fatalf("failed to create tester chain: %v", err) t.Fatalf("failed to create tester chain: %v", err)
} }
@ -4285,32 +4310,4 @@ func TestEIP6110(t *testing.T) {
if n, err := chain.InsertChain(blocks); err != nil { if n, err := chain.InsertChain(blocks); err != nil {
t.Fatalf("block %d: failed to insert into chain: %v", n, err) t.Fatalf("block %d: failed to insert into chain: %v", n, err)
} }
block := chain.GetBlockByNumber(1)
if len(block.Requests()) != 5 {
t.Fatalf("failed to retrieve deposits: have %d, want %d", len(block.Requests()), 5)
}
// Verify each index is correct.
for want, req := range block.Requests() {
d, ok := req.Inner().(*types.Deposit)
if !ok {
t.Fatalf("expected deposit object")
}
if got := int(d.PublicKey[0]); got != want {
t.Fatalf("invalid pubkey: have %d, want %d", got, want)
}
if got := int(d.WithdrawalCredentials[0]); got != want {
t.Fatalf("invalid withdrawal credentials: have %d, want %d", got, want)
}
if d.Amount != uint64(want) {
t.Fatalf("invalid amounbt: have %d, want %d", d.Amount, want)
}
if got := int(d.Signature[0]); got != want {
t.Fatalf("invalid signature: have %d, want %d", got, want)
}
if d.Index != uint64(want) {
t.Fatalf("invalid index: have %d, want %d", d.Index, want)
}
}
} }

View file

@ -346,18 +346,34 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
gen(i, b) gen(i, b)
} }
var requests types.Requests var requests [][]byte
if config.IsPrague(b.header.Number, b.header.Time) { if config.IsPrague(b.header.Number, b.header.Time) {
// EIP-6110 deposits
var blockLogs []*types.Log
for _, r := range b.receipts { for _, r := range b.receipts {
d, err := ParseDepositLogs(r.Logs, config) blockLogs = append(blockLogs, r.Logs...)
if err != nil {
panic(fmt.Sprintf("failed to parse deposit log: %v", err))
}
requests = append(requests, d...)
} }
depositRequests, err := ParseDepositLogs(blockLogs, config)
if err != nil {
panic(fmt.Sprintf("failed to parse deposit log: %v", err))
}
requests = append(requests, depositRequests)
// create EVM for system calls
blockContext := NewEVMBlockContext(b.header, cm, &b.header.Coinbase)
vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, cm.config, vm.Config{})
// EIP-7002 withdrawals
withdrawalRequests := ProcessWithdrawalQueue(vmenv, statedb)
requests = append(requests, withdrawalRequests)
// EIP-7251 consolidations
consolidationRequests := ProcessConsolidationQueue(vmenv, statedb)
requests = append(requests, consolidationRequests)
}
if requests != nil {
reqHash := types.CalcRequestsHash(requests)
b.header.RequestsHash = &reqHash
} }
body := types.Body{Transactions: b.txs, Uncles: b.uncles, Withdrawals: b.withdrawals, Requests: requests} body := types.Body{Transactions: b.txs, Uncles: b.uncles, Withdrawals: b.withdrawals}
block, err := b.engine.FinalizeAndAssemble(cm, b.header, statedb, &body, b.receipts) block, err := b.engine.FinalizeAndAssemble(cm, b.header, statedb, &body, b.receipts)
if err != nil { if err != nil {
panic(err) panic(err)
@ -446,16 +462,15 @@ func GenerateVerkleChain(config *params.ChainConfig, parent *types.Block, engine
// Save pre state for proof generation // Save pre state for proof generation
// preState := statedb.Copy() // preState := statedb.Copy()
// TODO uncomment when the 2935 PR is merged // Pre-execution system calls.
// if config.IsPrague(b.header.Number, b.header.Time) { if config.IsPrague(b.header.Number, b.header.Time) {
// if !config.IsPrague(b.parent.Number(), b.parent.Time()) { // EIP-2935
// Transition case: insert all 256 ancestors blockContext := NewEVMBlockContext(b.header, cm, &b.header.Coinbase)
// InsertBlockHashHistoryAtEip2935Fork(statedb, b.header.Number.Uint64()-1, b.header.ParentHash, chainreader) vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, cm.config, vm.Config{})
// } else { ProcessParentBlockHash(b.header.ParentHash, vmenv, statedb)
// ProcessParentBlockHash(statedb, b.header.Number.Uint64()-1, b.header.ParentHash) }
// }
// } // Execute any user modifications to the block.
// Execute any user modifications to the block
if gen != nil { if gen != nil {
gen(i, b) gen(i, b)
} }
@ -469,7 +484,7 @@ func GenerateVerkleChain(config *params.ChainConfig, parent *types.Block, engine
panic(err) panic(err)
} }
// Write state changes to db // Write state changes to DB.
root, err := statedb.Commit(b.header.Number.Uint64(), config.IsEIP158(b.header.Number)) root, err := statedb.Commit(b.header.Number.Uint64(), config.IsEIP158(b.header.Number))
if err != nil { if err != nil {
panic(fmt.Sprintf("state write error: %v", err)) panic(fmt.Sprintf("state write error: %v", err))

View file

@ -449,7 +449,6 @@ func (g *Genesis) toBlockWithRoot(root common.Hash) *types.Block {
} }
var ( var (
withdrawals []*types.Withdrawal withdrawals []*types.Withdrawal
requests types.Requests
) )
if conf := g.Config; conf != nil { if conf := g.Config; conf != nil {
num := big.NewInt(int64(g.Number)) num := big.NewInt(int64(g.Number))
@ -473,11 +472,12 @@ func (g *Genesis) toBlockWithRoot(root common.Hash) *types.Block {
} }
} }
if conf.IsPrague(num, g.Timestamp) { if conf.IsPrague(num, g.Timestamp) {
head.RequestsHash = &types.EmptyRequestsHash emptyRequests := [][]byte{{0x00}, {0x01}, {0x02}}
requests = make(types.Requests, 0) rhash := types.CalcRequestsHash(emptyRequests)
head.RequestsHash = &rhash
} }
} }
return types.NewBlock(head, &types.Body{Withdrawals: withdrawals, Requests: requests}, nil, trie.NewStackTrie(nil)) return types.NewBlock(head, &types.Body{Withdrawals: withdrawals}, nil, trie.NewStackTrie(nil))
} }
// Commit writes the block and state of a genesis specification to the database. // Commit writes the block and state of a genesis specification to the database.
@ -588,10 +588,11 @@ func DeveloperGenesisBlock(gasLimit uint64, faucet *common.Address) *Genesis {
common.BytesToAddress([]byte{7}): {Balance: big.NewInt(1)}, // ECScalarMul common.BytesToAddress([]byte{7}): {Balance: big.NewInt(1)}, // ECScalarMul
common.BytesToAddress([]byte{8}): {Balance: big.NewInt(1)}, // ECPairing common.BytesToAddress([]byte{8}): {Balance: big.NewInt(1)}, // ECPairing
common.BytesToAddress([]byte{9}): {Balance: big.NewInt(1)}, // BLAKE2b common.BytesToAddress([]byte{9}): {Balance: big.NewInt(1)}, // BLAKE2b
// Pre-deploy EIP-4788 system contract // Pre-deploy system contracts
params.BeaconRootsAddress: {Nonce: 1, Code: params.BeaconRootsCode, Balance: common.Big0}, params.BeaconRootsAddress: {Nonce: 1, Code: params.BeaconRootsCode, Balance: common.Big0},
// Pre-deploy EIP-2935 history contract. params.HistoryStorageAddress: {Nonce: 1, Code: params.HistoryStorageCode, Balance: common.Big0},
params.HistoryStorageAddress: {Nonce: 1, Code: params.HistoryStorageCode}, params.WithdrawalQueueAddress: {Nonce: 1, Code: params.WithdrawalQueueCode, Balance: common.Big0},
params.ConsolidationQueueAddress: {Nonce: 1, Code: params.ConsolidationQueueCode, Balance: common.Big0},
}, },
} }
if faucet != nil { if faucet != nil {

View file

@ -388,10 +388,10 @@ func TestBlockReceiptStorage(t *testing.T) {
// Insert the receipt slice into the database and check presence // Insert the receipt slice into the database and check presence
WriteReceipts(db, hash, 0, receipts) WriteReceipts(db, hash, 0, receipts)
if rs := ReadReceipts(db, hash, 0, 0, params.TestChainConfig); len(rs) == 0 { if rs := ReadReceipts(db, hash, 0, 0, params.TestChainConfig); len(rs) == 0 {
t.Fatalf("no receipts returned") t.Fatal("no receipts returned")
} else { } else {
if err := checkReceiptsRLP(rs, receipts); err != nil { if err := checkReceiptsRLP(rs, receipts); err != nil {
t.Fatalf(err.Error()) t.Fatal(err)
} }
} }
// Delete the body and ensure that the receipts are no longer returned (metadata can't be recomputed) // Delete the body and ensure that the receipts are no longer returned (metadata can't be recomputed)
@ -401,7 +401,7 @@ func TestBlockReceiptStorage(t *testing.T) {
} }
// Ensure that receipts without metadata can be returned without the block body too // Ensure that receipts without metadata can be returned without the block body too
if err := checkReceiptsRLP(ReadRawReceipts(db, hash, 0), receipts); err != nil { if err := checkReceiptsRLP(ReadRawReceipts(db, hash, 0), receipts); err != nil {
t.Fatalf(err.Error()) t.Fatal(err)
} }
// Sanity check that body alone without the receipt is a full purge // Sanity check that body alone without the receipt is a full purge
WriteBody(db, hash, 0, body) WriteBody(db, hash, 0, body)

View file

@ -302,6 +302,10 @@ func ParseStateScheme(provided string, disk ethdb.Database) (string, error) {
log.Info("State scheme set to already existing", "scheme", stored) log.Info("State scheme set to already existing", "scheme", stored)
return stored, nil // reuse scheme of persistent scheme return stored, nil // reuse scheme of persistent scheme
} }
// If state scheme is specified, ensure it's valid.
if provided != HashScheme && provided != PathScheme {
return "", fmt.Errorf("invalid state scheme %s", provided)
}
// If state scheme is specified, ensure it's compatible with // If state scheme is specified, ensure it's compatible with
// persistent state. // persistent state.
if stored == "" || provided == stored { if stored == "" || provided == stored {

View file

@ -52,13 +52,11 @@ var (
// freezerTableSize defines the maximum size of freezer data files. // freezerTableSize defines the maximum size of freezer data files.
const freezerTableSize = 2 * 1000 * 1000 * 1000 const freezerTableSize = 2 * 1000 * 1000 * 1000
// Freezer is a memory mapped append-only database to store immutable ordered // Freezer is an append-only database to store immutable ordered data into
// data into flat files: // flat files:
// //
// - The append-only nature ensures that disk writes are minimized. // - The append-only nature ensures that disk writes are minimized.
// - The memory mapping ensures we can max out system memory for caching without // - The in-order data ensures that disk reads are always optimized.
// reserving it for go-ethereum. This would also reduce the memory requirements
// of Geth, and thus also GC overhead.
type Freezer struct { type Freezer struct {
frozen atomic.Uint64 // Number of items already frozen frozen atomic.Uint64 // Number of items already frozen
tail atomic.Uint64 // Number of the first stored item in the freezer tail atomic.Uint64 // Number of the first stored item in the freezer
@ -152,7 +150,7 @@ func NewFreezer(datadir string, namespace string, readonly bool, maxTableSize ui
return freezer, nil return freezer, nil
} }
// Close terminates the chain freezer, unmapping all the data files. // Close terminates the chain freezer, closing all the data files.
func (f *Freezer) Close() error { func (f *Freezer) Close() error {
f.writeLock.Lock() f.writeLock.Lock()
defer f.writeLock.Unlock() defer f.writeLock.Unlock()

View file

@ -180,10 +180,10 @@ func (batch *freezerTableBatch) maybeCommit() error {
return nil return nil
} }
// commit writes the batched items to the backing freezerTable. // commit writes the batched items to the backing freezerTable. Note index
// file isn't fsync'd after the file write, the recent write can be lost
// after the power failure.
func (batch *freezerTableBatch) commit() error { func (batch *freezerTableBatch) commit() error {
// Write data. The head file is fsync'd after write to ensure the
// data is truly transferred to disk.
_, err := batch.t.head.Write(batch.dataBuffer) _, err := batch.t.head.Write(batch.dataBuffer)
if err != nil { if err != nil {
return err return err
@ -194,15 +194,10 @@ func (batch *freezerTableBatch) commit() error {
dataSize := int64(len(batch.dataBuffer)) dataSize := int64(len(batch.dataBuffer))
batch.dataBuffer = batch.dataBuffer[:0] batch.dataBuffer = batch.dataBuffer[:0]
// Write indices. The index file is fsync'd after write to ensure the
// data indexes are truly transferred to disk.
_, err = batch.t.index.Write(batch.indexBuffer) _, err = batch.t.index.Write(batch.indexBuffer)
if err != nil { if err != nil {
return err return err
} }
if err := batch.t.index.Sync(); err != nil {
return err
}
indexSize := int64(len(batch.indexBuffer)) indexSize := int64(len(batch.indexBuffer))
batch.indexBuffer = batch.indexBuffer[:0] batch.indexBuffer = batch.indexBuffer[:0]

View file

@ -17,6 +17,7 @@
package rawdb package rawdb
import ( import (
"bufio"
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"errors" "errors"
@ -26,6 +27,7 @@ import (
"path/filepath" "path/filepath"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
@ -219,7 +221,13 @@ func (t *freezerTable) repair() error {
return err return err
} // New file can't trigger this path } // New file can't trigger this path
} }
// Retrieve the file sizes and prepare for truncation // Validate the index file as it might contain some garbage data after the
// power failures.
if err := t.repairIndex(); err != nil {
return err
}
// Retrieve the file sizes and prepare for truncation. Note the file size
// might be changed after index validation.
if stat, err = t.index.Stat(); err != nil { if stat, err = t.index.Stat(); err != nil {
return err return err
} }
@ -364,6 +372,133 @@ func (t *freezerTable) repair() error {
return nil return nil
} }
// repairIndex validates the integrity of the index file. According to the design,
// the initial entry in the file denotes the earliest data file along with the
// count of deleted items. Following this, all subsequent entries in the file must
// be in order. This function identifies any corrupted entries and truncates items
// occurring after the corruption point.
//
// corruption can occur because of the power failure. In the Linux kernel, the
// file metadata update and data update are not necessarily performed at the
// same time. Typically, the metadata will be flushed/journalled ahead of the file
// data. Therefore, we make the pessimistic assumption that the file is first
// extended with invalid "garbage" data (normally zero bytes) and that afterwards
// the correct data replaces the garbage. As all the items in index file are
// supposed to be in-order, the leftover garbage must be truncated before the
// index data is utilized.
//
// It's important to note an exception that's unfortunately undetectable: when
// all index entries in the file are zero. Distinguishing whether they represent
// leftover garbage or if all items in the table have zero size is impossible.
// In such instances, the file will remain unchanged to prevent potential data
// loss or misinterpretation.
func (t *freezerTable) repairIndex() error {
// Retrieve the file sizes and prepare for validation
stat, err := t.index.Stat()
if err != nil {
return err
}
size := stat.Size()
// Move the read cursor to the beginning of the file
_, err = t.index.Seek(0, io.SeekStart)
if err != nil {
return err
}
fr := bufio.NewReader(t.index)
var (
start = time.Now()
buff = make([]byte, indexEntrySize)
prev indexEntry
head indexEntry
read = func() (indexEntry, error) {
n, err := io.ReadFull(fr, buff)
if err != nil {
return indexEntry{}, err
}
if n != indexEntrySize {
return indexEntry{}, fmt.Errorf("failed to read from index, n: %d", n)
}
var entry indexEntry
entry.unmarshalBinary(buff)
return entry, nil
}
truncate = func(offset int64) error {
if t.readonly {
return fmt.Errorf("index file is corrupted at %d, size: %d", offset, size)
}
if err := truncateFreezerFile(t.index, offset); err != nil {
return err
}
log.Warn("Truncated index file", "offset", offset, "truncated", size-offset)
return nil
}
)
for offset := int64(0); offset < size; offset += indexEntrySize {
entry, err := read()
if err != nil {
return err
}
if offset == 0 {
head = entry
continue
}
// Ensure that the first non-head index refers to the earliest file,
// or the next file if the earliest file has no space to place the
// first item.
if offset == indexEntrySize {
if entry.filenum != head.filenum && entry.filenum != head.filenum+1 {
log.Error("Corrupted index item detected", "earliest", head.filenum, "filenumber", entry.filenum)
return truncate(offset)
}
prev = entry
continue
}
// ensure two consecutive index items are in order
if err := t.checkIndexItems(prev, entry); err != nil {
log.Error("Corrupted index item detected", "err", err)
return truncate(offset)
}
prev = entry
}
// Move the read cursor to the end of the file. While theoretically, the
// cursor should reach the end by reading all the items in the file, perform
// the seek operation anyway as a precaution.
_, err = t.index.Seek(0, io.SeekEnd)
if err != nil {
return err
}
log.Debug("Verified index file", "items", size/indexEntrySize, "elapsed", common.PrettyDuration(time.Since(start)))
return nil
}
// checkIndexItems validates the correctness of two consecutive index items based
// on the following rules:
//
// - The file number of two consecutive index items must either be the same or
// increase monotonically. If the file number decreases or skips in a
// non-sequential manner, the index item is considered invalid.
//
// - For index items with the same file number, the data offset must be in
// non-decreasing order. Note: Two index items with the same file number
// and the same data offset are permitted if the entry size is zero.
//
// - The first index item in a new data file must not have a zero data offset.
func (t *freezerTable) checkIndexItems(a, b indexEntry) error {
if b.filenum != a.filenum && b.filenum != a.filenum+1 {
return fmt.Errorf("index items with inconsistent file number, prev: %d, next: %d", a.filenum, b.filenum)
}
if b.filenum == a.filenum && b.offset < a.offset {
return fmt.Errorf("index items with unordered offset, prev: %d, next: %d", a.offset, b.offset)
}
if b.filenum == a.filenum+1 && b.offset == 0 {
return fmt.Errorf("index items with zero offset, file number: %d", b.filenum)
}
return nil
}
// preopen opens all files that the freezer will need. This method should be called from an init-context, // preopen opens all files that the freezer will need. This method should be called from an init-context,
// since it assumes that it doesn't have to bother with locking // since it assumes that it doesn't have to bother with locking
// The rationale for doing preopen is to not have to do it from within Retrieve, thus not needing to ever // The rationale for doing preopen is to not have to do it from within Retrieve, thus not needing to ever

View file

@ -1367,3 +1367,69 @@ func TestRandom(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
} }
func TestIndexValidation(t *testing.T) {
const (
items = 30
dataSize = 10
)
garbage := indexEntry{
filenum: 100,
offset: 200,
}
var cases = []struct {
offset int64
data []byte
expItems int
}{
// extend index file with zero bytes at the end
{
offset: (items + 1) * indexEntrySize,
data: make([]byte, indexEntrySize),
expItems: 30,
},
// write garbage in the first non-head item
{
offset: indexEntrySize,
data: garbage.append(nil),
expItems: 0,
},
// write garbage in the first non-head item
{
offset: (items/2 + 1) * indexEntrySize,
data: garbage.append(nil),
expItems: items / 2,
},
}
for _, c := range cases {
fn := fmt.Sprintf("t-%d", rand.Uint64())
f, err := newTable(os.TempDir(), fn, metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge(), 100, true, false)
if err != nil {
t.Fatal(err)
}
writeChunks(t, f, items, dataSize)
// write corrupted data
f.index.WriteAt(c.data, c.offset)
f.Close()
// reopen the table, corruption should be truncated
f, err = newTable(os.TempDir(), fn, metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge(), 100, true, false)
if err != nil {
t.Fatal(err)
}
for i := 0; i < c.expItems; i++ {
exp := getChunk(10, i)
got, err := f.Retrieve(uint64(i))
if err != nil {
t.Fatalf("Failed to read from table, %v", err)
}
if !bytes.Equal(exp, got) {
t.Fatalf("Unexpected item data, want: %v, got: %v", exp, got)
}
}
if f.items.Load() != uint64(c.expItems) {
t.Fatalf("Unexpected item number, want: %d, got: %d", c.expItems, f.items.Load())
}
}
}

View file

@ -28,8 +28,8 @@ import (
// mode specifies how a tree location has been accessed // mode specifies how a tree location has been accessed
// for the byte value: // for the byte value:
// * the first bit is set if the branch has been edited // * the first bit is set if the branch has been read
// * the second bit is set if the branch has been read // * the second bit is set if the branch has been edited
type mode byte type mode byte
const ( const (
@ -117,11 +117,23 @@ func (ae *AccessEvents) ValueTransferGas(callerAddr, targetAddr common.Address)
return gas return gas
} }
// ContractCreateCPreheck charges access costs before
// a contract creation is initiated. It is just reads, because the
// address collision is done before the transfer, and so no write
// are guaranteed to happen at this point.
func (ae *AccessEvents) ContractCreatePreCheckGas(addr common.Address) uint64 {
var gas uint64
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, false)
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, false)
return gas
}
// ContractCreateInitGas returns the access gas costs for the initialization of // ContractCreateInitGas returns the access gas costs for the initialization of
// a contract creation. // a contract creation.
func (ae *AccessEvents) ContractCreateInitGas(addr common.Address, createSendsValue bool) uint64 { func (ae *AccessEvents) ContractCreateInitGas(addr common.Address) uint64 {
var gas uint64 var gas uint64
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, true) gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, true)
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, true)
return gas return gas
} }

View file

@ -100,13 +100,13 @@ func TestContractCreateInitGas(t *testing.T) {
} }
// Check cold read cost, without a value // Check cold read cost, without a value
gas := ae.ContractCreateInitGas(testAddr, false) gas := ae.ContractCreateInitGas(testAddr)
if want := params.WitnessBranchWriteCost + params.WitnessBranchReadCost + params.WitnessChunkWriteCost + params.WitnessChunkReadCost; gas != want { if want := params.WitnessBranchWriteCost + params.WitnessBranchReadCost + 2*params.WitnessChunkWriteCost + 2*params.WitnessChunkReadCost; gas != want {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, want) t.Fatalf("incorrect gas computed, got %d, want %d", gas, want)
} }
// Check warm read cost // Check warm read cost
gas = ae.ContractCreateInitGas(testAddr, false) gas = ae.ContractCreateInitGas(testAddr)
if gas != 0 { if gas != 0 {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
} }

View file

@ -1265,7 +1265,7 @@ func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool) (*stateU
} }
if !ret.empty() { if !ret.empty() {
// If snapshotting is enabled, update the snapshot tree with this new version // If snapshotting is enabled, update the snapshot tree with this new version
if snap := s.db.Snapshot(); snap != nil { if snap := s.db.Snapshot(); snap != nil && snap.Snapshot(ret.originRoot) != nil {
start := time.Now() start := time.Now()
if err := snap.Update(ret.root, ret.originRoot, ret.destructs, ret.accounts, ret.storages); err != nil { if err := snap.Update(ret.root, ret.originRoot, ret.destructs, ret.accounts, ret.storages); err != nil {
log.Warn("Failed to update snapshot tree", "from", ret.originRoot, "to", ret.root, "err", err) log.Warn("Failed to update snapshot tree", "from", ret.originRoot, "to", ret.root, "err", err)

View file

@ -282,7 +282,6 @@ func (sf *subfetcher) schedule(keys [][]byte, read bool) error {
// Append the tasks to the current queue // Append the tasks to the current queue
sf.lock.Lock() sf.lock.Lock()
for _, key := range keys { for _, key := range keys {
key := key // closure for the append below
sf.tasks = append(sf.tasks, &subfetcherTask{read: read, key: key}) sf.tasks = append(sf.tasks, &subfetcherTask{read: read, key: key})
} }
sf.lock.Unlock() sf.lock.Unlock()

View file

@ -71,8 +71,9 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
var ( var (
context vm.BlockContext context vm.BlockContext
signer = types.MakeSigner(p.config, header.Number, header.Time) signer = types.MakeSigner(p.config, header.Number, header.Time)
err error
) )
// Apply pre-execution system calls.
context = NewEVMBlockContext(header, p.chain, nil) context = NewEVMBlockContext(header, p.chain, nil)
vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, p.config, cfg) vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, p.config, cfg)
if beaconRoot := block.BeaconRoot(); beaconRoot != nil { if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
@ -81,6 +82,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
if p.config.IsPrague(block.Number(), block.Time()) { if p.config.IsPrague(block.Number(), block.Time()) {
ProcessParentBlockHash(block.ParentHash(), vmenv, statedb) ProcessParentBlockHash(block.ParentHash(), vmenv, statedb)
} }
// Iterate over and process the individual transactions // Iterate over and process the individual transactions
for i, tx := range block.Transactions() { for i, tx := range block.Transactions() {
msg, err := TransactionToMessage(tx, signer, header.BaseFee) msg, err := TransactionToMessage(tx, signer, header.BaseFee)
@ -96,13 +98,22 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
receipts = append(receipts, receipt) receipts = append(receipts, receipt)
allLogs = append(allLogs, receipt.Logs...) allLogs = append(allLogs, receipt.Logs...)
} }
// Read requests if Prague is enabled. // Read requests if Prague is enabled.
var requests types.Requests var requests [][]byte
if p.config.IsPrague(block.Number(), block.Time()) { if p.config.IsPrague(block.Number(), block.Time()) {
requests, err = ParseDepositLogs(allLogs, p.config) // EIP-6110 deposits
depositRequests, err := ParseDepositLogs(allLogs, p.config)
if err != nil { if err != nil {
return nil, err return nil, err
} }
requests = append(requests, depositRequests)
// EIP-7002 withdrawals
withdrawalRequests := ProcessWithdrawalQueue(vmenv, statedb)
requests = append(requests, withdrawalRequests)
// EIP-7251 consolidations
consolidationRequests := ProcessConsolidationQueue(vmenv, statedb)
requests = append(requests, consolidationRequests)
} }
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards) // Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
@ -215,9 +226,6 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, vmenv *vm.EVM, statedb *stat
defer tracer.OnSystemCallEnd() defer tracer.OnSystemCallEnd()
} }
} }
// If EIP-4788 is enabled, we need to invoke the beaconroot storage contract with
// the new root
msg := &Message{ msg := &Message{
From: params.SystemAddress, From: params.SystemAddress,
GasLimit: 30_000_000, GasLimit: 30_000_000,
@ -244,7 +252,6 @@ func ProcessParentBlockHash(prevHash common.Hash, vmenv *vm.EVM, statedb *state.
defer tracer.OnSystemCallEnd() defer tracer.OnSystemCallEnd()
} }
} }
msg := &Message{ msg := &Message{
From: params.SystemAddress, From: params.SystemAddress,
GasLimit: 30_000_000, GasLimit: 30_000_000,
@ -260,17 +267,59 @@ func ProcessParentBlockHash(prevHash common.Hash, vmenv *vm.EVM, statedb *state.
statedb.Finalise(true) statedb.Finalise(true)
} }
// ProcessWithdrawalQueue calls the EIP-7002 withdrawal queue contract.
// It returns the opaque request data returned by the contract.
func ProcessWithdrawalQueue(vmenv *vm.EVM, statedb *state.StateDB) []byte {
return processRequestsSystemCall(vmenv, statedb, 0x01, params.WithdrawalQueueAddress)
}
// ProcessConsolidationQueue calls the EIP-7251 consolidation queue contract.
// It returns the opaque request data returned by the contract.
func ProcessConsolidationQueue(vmenv *vm.EVM, statedb *state.StateDB) []byte {
return processRequestsSystemCall(vmenv, statedb, 0x02, params.ConsolidationQueueAddress)
}
func processRequestsSystemCall(vmenv *vm.EVM, statedb *state.StateDB, requestType byte, addr common.Address) []byte {
if tracer := vmenv.Config.Tracer; tracer != nil {
if tracer.OnSystemCallStart != nil {
tracer.OnSystemCallStart()
}
if tracer.OnSystemCallEnd != nil {
defer tracer.OnSystemCallEnd()
}
}
msg := &Message{
From: params.SystemAddress,
GasLimit: 30_000_000,
GasPrice: common.Big0,
GasFeeCap: common.Big0,
GasTipCap: common.Big0,
To: &addr,
}
vmenv.Reset(NewEVMTxContext(msg), statedb)
statedb.AddAddressToAccessList(addr)
ret, _, _ := vmenv.Call(vm.AccountRef(msg.From), *msg.To, msg.Data, 30_000_000, common.U2560)
statedb.Finalise(true)
// Create withdrawals requestsData with prefix 0x01
requestsData := make([]byte, len(ret)+1)
requestsData[0] = requestType
copy(requestsData[1:], ret)
return requestsData
}
// ParseDepositLogs extracts the EIP-6110 deposit values from logs emitted by // ParseDepositLogs extracts the EIP-6110 deposit values from logs emitted by
// BeaconDepositContract. // BeaconDepositContract.
func ParseDepositLogs(logs []*types.Log, config *params.ChainConfig) (types.Requests, error) { func ParseDepositLogs(logs []*types.Log, config *params.ChainConfig) ([]byte, error) {
deposits := make(types.Requests, 0) deposits := make([]byte, 1) // note: first byte is 0x00 (== deposit request type)
for _, log := range logs { for _, log := range logs {
if log.Address == config.DepositContractAddress { if log.Address == config.DepositContractAddress {
d, err := types.UnpackIntoDeposit(log.Data) request, err := types.DepositLogToRequest(log.Data)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to parse deposit data: %v", err) return nil, fmt.Errorf("unable to parse deposit data: %v", err)
} }
deposits = append(deposits, types.NewRequest(d)) deposits = append(deposits, request...)
} }
} }
return deposits, nil return deposits, nil

View file

@ -25,21 +25,30 @@ import (
"github.com/ethereum/go-ethereum/core/stateless" "github.com/ethereum/go-ethereum/core/stateless"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/triedb" "github.com/ethereum/go-ethereum/triedb"
) )
// ExecuteStateless runs a stateless execution based on a witness, verifies // ExecuteStateless runs a stateless execution based on a witness, verifies
// everything it can locally and returns the two computed fields that need the // everything it can locally and returns the state root and receipt root, that
// other side to explicitly check. // need the other side to explicitly check.
// //
// This method is a bit of a sore thumb here, but: // This method is a bit of a sore thumb here, but:
// - It cannot be placed in core/stateless, because state.New prodces a circular dep // - It cannot be placed in core/stateless, because state.New prodces a circular dep
// - It cannot be placed outside of core, because it needs to construct a dud headerchain // - It cannot be placed outside of core, because it needs to construct a dud headerchain
// //
// TODO(karalabe): Would be nice to resolve both issues above somehow and move it. // TODO(karalabe): Would be nice to resolve both issues above somehow and move it.
func ExecuteStateless(config *params.ChainConfig, witness *stateless.Witness) (common.Hash, common.Hash, error) { func ExecuteStateless(config *params.ChainConfig, block *types.Block, witness *stateless.Witness) (common.Hash, common.Hash, error) {
// Sanity check if the supplied block accidentally contains a set root or
// receipt hash. If so, be very loud, but still continue.
if block.Root() != (common.Hash{}) {
log.Error("stateless runner received state root it's expected to calculate (faulty consensus client)", "block", block.Number())
}
if block.ReceiptHash() != (common.Hash{}) {
log.Error("stateless runner received receipt root it's expected to calculate (faulty consensus client)", "block", block.Number())
}
// Create and populate the state database to serve as the stateless backend // Create and populate the state database to serve as the stateless backend
memdb := witness.MakeHashDB() memdb := witness.MakeHashDB()
db, err := state.New(witness.Root(), state.NewDatabase(triedb.NewDatabase(memdb, triedb.HashDefaults), nil)) db, err := state.New(witness.Root(), state.NewDatabase(triedb.NewDatabase(memdb, triedb.HashDefaults), nil))
@ -57,16 +66,15 @@ func ExecuteStateless(config *params.ChainConfig, witness *stateless.Witness) (c
validator := NewBlockValidator(config, nil) // No chain, we only validate the state, not the block validator := NewBlockValidator(config, nil) // No chain, we only validate the state, not the block
// Run the stateless blocks processing and self-validate certain fields // Run the stateless blocks processing and self-validate certain fields
res, err := processor.Process(witness.Block, db, vm.Config{}) res, err := processor.Process(block, db, vm.Config{})
if err != nil { if err != nil {
return common.Hash{}, common.Hash{}, err return common.Hash{}, common.Hash{}, err
} }
if err = validator.ValidateState(witness.Block, db, res, true); err != nil { if err = validator.ValidateState(block, db, res, true); err != nil {
return common.Hash{}, common.Hash{}, err return common.Hash{}, common.Hash{}, err
} }
// Almost everything validated, but receipt and state root needs to be returned // Almost everything validated, but receipt and state root needs to be returned
receiptRoot := types.DeriveSha(res.Receipts, trie.NewStackTrie(nil)) receiptRoot := types.DeriveSha(res.Receipts, trie.NewStackTrie(nil))
stateRoot := db.IntermediateRoot(config.IsEIP158(witness.Block.Number())) stateRoot := db.IntermediateRoot(config.IsEIP158(block.Number()))
return stateRoot, receiptRoot, nil
return receiptRoot, stateRoot, nil
} }

View file

@ -26,6 +26,13 @@ import (
// MakeHashDB imports tries, codes and block hashes from a witness into a new // MakeHashDB imports tries, codes and block hashes from a witness into a new
// hash-based memory db. We could eventually rewrite this into a pathdb, but // hash-based memory db. We could eventually rewrite this into a pathdb, but
// simple is better for now. // simple is better for now.
//
// Note, this hashdb approach is quite strictly self-validating:
// - Headers are persisted keyed by hash, so blockhash will error on junk
// - Codes are persisted keyed by hash, so bytecode lookup will error on junk
// - Trie nodes are persisted keyed by hash, so trie expansion will error on junk
//
// Acceleration structures built would need to explicitly validate the witness.
func (w *Witness) MakeHashDB() ethdb.Database { func (w *Witness) MakeHashDB() ethdb.Database {
var ( var (
memdb = rawdb.NewMemoryDatabase() memdb = rawdb.NewMemoryDatabase()

View file

@ -17,42 +17,31 @@
package stateless package stateless
import ( import (
"bytes"
"errors"
"fmt"
"io" "io"
"slices"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
) )
//go:generate go run github.com/fjl/gencodec -type extWitness -field-override extWitnessMarshalling -out gen_encoding_json.go
// toExtWitness converts our internal witness representation to the consensus one. // toExtWitness converts our internal witness representation to the consensus one.
func (w *Witness) toExtWitness() *extWitness { func (w *Witness) toExtWitness() *extWitness {
ext := &extWitness{ ext := &extWitness{
Block: w.Block,
Headers: w.Headers, Headers: w.Headers,
} }
ext.Codes = make([][]byte, 0, len(w.Codes)) ext.Codes = make([][]byte, 0, len(w.Codes))
for code := range w.Codes { for code := range w.Codes {
ext.Codes = append(ext.Codes, []byte(code)) ext.Codes = append(ext.Codes, []byte(code))
} }
slices.SortFunc(ext.Codes, bytes.Compare)
ext.State = make([][]byte, 0, len(w.State)) ext.State = make([][]byte, 0, len(w.State))
for node := range w.State { for node := range w.State {
ext.State = append(ext.State, []byte(node)) ext.State = append(ext.State, []byte(node))
} }
slices.SortFunc(ext.State, bytes.Compare)
return ext return ext
} }
// fromExtWitness converts the consensus witness format into our internal one. // fromExtWitness converts the consensus witness format into our internal one.
func (w *Witness) fromExtWitness(ext *extWitness) error { func (w *Witness) fromExtWitness(ext *extWitness) error {
w.Block, w.Headers = ext.Block, ext.Headers w.Headers = ext.Headers
w.Codes = make(map[string]struct{}, len(ext.Codes)) w.Codes = make(map[string]struct{}, len(ext.Codes))
for _, code := range ext.Codes { for _, code := range ext.Codes {
@ -62,12 +51,7 @@ func (w *Witness) fromExtWitness(ext *extWitness) error {
for _, node := range ext.State { for _, node := range ext.State {
w.State[string(node)] = struct{}{} w.State[string(node)] = struct{}{}
} }
return w.sanitize() return nil
}
// MarshalJSON marshals a witness as JSON.
func (w *Witness) MarshalJSON() ([]byte, error) {
return w.toExtWitness().MarshalJSON()
} }
// EncodeRLP serializes a witness as RLP. // EncodeRLP serializes a witness as RLP.
@ -75,15 +59,6 @@ func (w *Witness) EncodeRLP(wr io.Writer) error {
return rlp.Encode(wr, w.toExtWitness()) return rlp.Encode(wr, w.toExtWitness())
} }
// UnmarshalJSON unmarshals from JSON.
func (w *Witness) UnmarshalJSON(input []byte) error {
var ext extWitness
if err := ext.UnmarshalJSON(input); err != nil {
return err
}
return w.fromExtWitness(&ext)
}
// DecodeRLP decodes a witness from RLP. // DecodeRLP decodes a witness from RLP.
func (w *Witness) DecodeRLP(s *rlp.Stream) error { func (w *Witness) DecodeRLP(s *rlp.Stream) error {
var ext extWitness var ext extWitness
@ -93,37 +68,9 @@ func (w *Witness) DecodeRLP(s *rlp.Stream) error {
return w.fromExtWitness(&ext) return w.fromExtWitness(&ext)
} }
// sanitize checks for some mandatory fields in the witness after decoding so
// the rest of the code can assume invariants and doesn't have to deal with
// corrupted data.
func (w *Witness) sanitize() error {
// Verify that the "parent" header (i.e. index 0) is available, and is the
// true parent of the block-to-be executed, since we use that to link the
// current block to the pre-state.
if len(w.Headers) == 0 {
return errors.New("parent header (for pre-root hash) missing")
}
for i, header := range w.Headers {
if header == nil {
return fmt.Errorf("witness header nil at position %d", i)
}
}
if w.Headers[0].Hash() != w.Block.ParentHash() {
return fmt.Errorf("parent hash different: witness %v, block parent %v", w.Headers[0].Hash(), w.Block.ParentHash())
}
return nil
}
// extWitness is a witness RLP encoding for transferring across clients. // extWitness is a witness RLP encoding for transferring across clients.
type extWitness struct { type extWitness struct {
Block *types.Block `json:"block" gencodec:"required"` Headers []*types.Header
Headers []*types.Header `json:"headers" gencodec:"required"` Codes [][]byte
Codes [][]byte `json:"codes"` State [][]byte
State [][]byte `json:"state"`
}
// extWitnessMarshalling defines the hex marshalling types for a witness.
type extWitnessMarshalling struct {
Codes []hexutil.Bytes
State []hexutil.Bytes
} }

View file

@ -1,74 +0,0 @@
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
package stateless
import (
"encoding/json"
"errors"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
)
var _ = (*extWitnessMarshalling)(nil)
// MarshalJSON marshals as JSON.
func (e extWitness) MarshalJSON() ([]byte, error) {
type extWitness struct {
Block *types.Block `json:"block" gencodec:"required"`
Headers []*types.Header `json:"headers" gencodec:"required"`
Codes []hexutil.Bytes `json:"codes"`
State []hexutil.Bytes `json:"state"`
}
var enc extWitness
enc.Block = e.Block
enc.Headers = e.Headers
if e.Codes != nil {
enc.Codes = make([]hexutil.Bytes, len(e.Codes))
for k, v := range e.Codes {
enc.Codes[k] = v
}
}
if e.State != nil {
enc.State = make([]hexutil.Bytes, len(e.State))
for k, v := range e.State {
enc.State[k] = v
}
}
return json.Marshal(&enc)
}
// UnmarshalJSON unmarshals from JSON.
func (e *extWitness) UnmarshalJSON(input []byte) error {
type extWitness struct {
Block *types.Block `json:"block" gencodec:"required"`
Headers []*types.Header `json:"headers" gencodec:"required"`
Codes []hexutil.Bytes `json:"codes"`
State []hexutil.Bytes `json:"state"`
}
var dec extWitness
if err := json.Unmarshal(input, &dec); err != nil {
return err
}
if dec.Block == nil {
return errors.New("missing required field 'block' for extWitness")
}
e.Block = dec.Block
if dec.Headers == nil {
return errors.New("missing required field 'headers' for extWitness")
}
e.Headers = dec.Headers
if dec.Codes != nil {
e.Codes = make([][]byte, len(dec.Codes))
for k, v := range dec.Codes {
e.Codes[k] = v
}
}
if dec.State != nil {
e.State = make([][]byte, len(dec.State))
for k, v := range dec.State {
e.State[k] = v
}
}
return nil
}

View file

@ -17,16 +17,13 @@
package stateless package stateless
import ( import (
"bytes"
"errors" "errors"
"fmt"
"maps" "maps"
"slices" "slices"
"sync" "sync"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
) )
// HeaderReader is an interface to pull in headers in place of block hashes for // HeaderReader is an interface to pull in headers in place of block hashes for
@ -36,10 +33,11 @@ type HeaderReader interface {
GetHeader(hash common.Hash, number uint64) *types.Header GetHeader(hash common.Hash, number uint64) *types.Header
} }
// Witness encompasses a block, state and any other chain data required to apply // Witness encompasses the state required to apply a set of transactions and
// a set of transactions and derive a post state/receipt root. // derive a post state/receipt root.
type Witness struct { type Witness struct {
Block *types.Block // Current block with rootHash and receiptHash zeroed out context *types.Header // Header to which this witness belongs to, with rootHash and receiptHash zeroed out
Headers []*types.Header // Past headers in reverse order (0=parent, 1=parent's-parent, etc). First *must* be set. Headers []*types.Header // Past headers in reverse order (0=parent, 1=parent's-parent, etc). First *must* be set.
Codes map[string]struct{} // Set of bytecodes ran or accessed Codes map[string]struct{} // Set of bytecodes ran or accessed
State map[string]struct{} // Set of MPT state trie nodes (account and storage together) State map[string]struct{} // Set of MPT state trie nodes (account and storage together)
@ -49,24 +47,23 @@ type Witness struct {
} }
// NewWitness creates an empty witness ready for population. // NewWitness creates an empty witness ready for population.
func NewWitness(chain HeaderReader, block *types.Block) (*Witness, error) { func NewWitness(context *types.Header, chain HeaderReader) (*Witness, error) {
// Zero out the result fields to avoid accidentally sending them to the verifier // When building witnesses, retrieve the parent header, which will *always*
header := block.Header() // be included to act as a trustless pre-root hash container
header.Root = common.Hash{} var headers []*types.Header
header.ReceiptHash = common.Hash{} if chain != nil {
parent := chain.GetHeader(context.ParentHash, context.Number.Uint64()-1)
// Retrieve the parent header, which will *always* be included to act as a if parent == nil {
// trustless pre-root hash container return nil, errors.New("failed to retrieve parent header")
parent := chain.GetHeader(block.ParentHash(), block.NumberU64()-1) }
if parent == nil { headers = append(headers, parent)
return nil, errors.New("failed to retrieve parent header")
} }
// Create the wtness with a reconstructed gutted out block // Create the wtness with a reconstructed gutted out block
return &Witness{ return &Witness{
Block: types.NewBlockWithHeader(header).WithBody(*block.Body()), context: context,
Headers: headers,
Codes: make(map[string]struct{}), Codes: make(map[string]struct{}),
State: make(map[string]struct{}), State: make(map[string]struct{}),
Headers: []*types.Header{parent},
chain: chain, chain: chain,
}, nil }, nil
} }
@ -76,11 +73,8 @@ func NewWitness(chain HeaderReader, block *types.Block) (*Witness, error) {
// the chain to cover the block being added. // the chain to cover the block being added.
func (w *Witness) AddBlockHash(number uint64) { func (w *Witness) AddBlockHash(number uint64) {
// Keep pulling in headers until this hash is populated // Keep pulling in headers until this hash is populated
for int(w.Block.NumberU64()-number) > len(w.Headers) { for int(w.context.Number.Uint64()-number) > len(w.Headers) {
tail := w.Block.Header() tail := w.Headers[len(w.Headers)-1]
if len(w.Headers) > 0 {
tail = w.Headers[len(w.Headers)-1]
}
w.Headers = append(w.Headers, w.chain.GetHeader(tail.ParentHash, tail.Number.Uint64()-1)) w.Headers = append(w.Headers, w.chain.GetHeader(tail.ParentHash, tail.Number.Uint64()-1))
} }
} }
@ -101,53 +95,22 @@ func (w *Witness) AddState(nodes map[string]struct{}) {
w.lock.Lock() w.lock.Lock()
defer w.lock.Unlock() defer w.lock.Unlock()
for node := range nodes { maps.Copy(w.State, nodes)
w.State[node] = struct{}{}
}
} }
// Copy deep-copies the witness object. Witness.Block isn't deep-copied as it // Copy deep-copies the witness object. Witness.Block isn't deep-copied as it
// is never mutated by Witness // is never mutated by Witness
func (w *Witness) Copy() *Witness { func (w *Witness) Copy() *Witness {
return &Witness{ cpy := &Witness{
Block: w.Block,
Headers: slices.Clone(w.Headers), Headers: slices.Clone(w.Headers),
Codes: maps.Clone(w.Codes), Codes: maps.Clone(w.Codes),
State: maps.Clone(w.State), State: maps.Clone(w.State),
chain: w.chain,
} }
} if w.context != nil {
cpy.context = types.CopyHeader(w.context)
// String prints a human-readable summary containing the total size of the
// witness and the sizes of the underlying components
func (w *Witness) String() string {
blob, _ := rlp.EncodeToBytes(w)
bytesTotal := len(blob)
blob, _ = rlp.EncodeToBytes(w.Block)
bytesBlock := len(blob)
bytesHeaders := 0
for _, header := range w.Headers {
blob, _ = rlp.EncodeToBytes(header)
bytesHeaders += len(blob)
} }
bytesCodes := 0 return cpy
for code := range w.Codes {
bytesCodes += len(code)
}
bytesState := 0
for node := range w.State {
bytesState += len(node)
}
buf := new(bytes.Buffer)
fmt.Fprintf(buf, "Witness #%d: %v\n", w.Block.Number(), common.StorageSize(bytesTotal))
fmt.Fprintf(buf, " block (%4d txs): %10v\n", len(w.Block.Transactions()), common.StorageSize(bytesBlock))
fmt.Fprintf(buf, "%4d headers: %10v\n", len(w.Headers), common.StorageSize(bytesHeaders))
fmt.Fprintf(buf, "%4d trie nodes: %10v\n", len(w.State), common.StorageSize(bytesState))
fmt.Fprintf(buf, "%4d codes: %10v\n", len(w.Codes), common.StorageSize(bytesCodes))
return buf.String()
} }
// Root returns the pre-state root from the first header. // Root returns the pre-state root from the first header.

View file

@ -2,6 +2,24 @@
All notable changes to the tracing interface will be documented in this file. All notable changes to the tracing interface will be documented in this file.
## [Unreleased]
### Modified types
- `GasChangeReason` has been extended with the following reasons which will be enabled only post-Verkle. There shouldn't be any gas changes with those reasons prior to the fork.
- `GasChangeWitnessContractCollisionCheck` flags the event of adding to the witness when checking for contract address collision.
## [v1.14.4]
This release contained only minor extensions to the tracing interface.
### Modified types
- `GasChangeReason` has been extended with the following reasons that will only be active post-Verkle.
- `GasChangeWitnessContractInit` flags the event of adding to the witness during the contract creation initialization step.
- `GasChangeWitnessContractCreation` flags the event of adding to the witness during the contract creation finalization step.
- `GasChangeWitnessCodeChunk` flags the event of adding one or more contract code chunks to the witness.
## [v1.14.3] ## [v1.14.3]
There have been minor backwards-compatible changes to the tracing interface to explicitly mark the execution of **system** contracts. As of now the only system call updates the parent beacon block root as per [EIP-4788](https://eips.ethereum.org/EIPS/eip-4788). Other system calls are being considered for the future hardfork. There have been minor backwards-compatible changes to the tracing interface to explicitly mark the execution of **system** contracts. As of now the only system call updates the parent beacon block root as per [EIP-4788](https://eips.ethereum.org/EIPS/eip-4788). Other system calls are being considered for the future hardfork.
@ -75,6 +93,7 @@ The hooks `CaptureStart` and `CaptureEnd` have been removed. These hooks signale
- `CaptureState` -> `OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error)`. `op` is of type `byte` which can be cast to `vm.OpCode` when necessary. A `*vm.ScopeContext` is not passed anymore. It is replaced by `tracing.OpContext` which offers access to the memory, stack and current contract. - `CaptureState` -> `OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error)`. `op` is of type `byte` which can be cast to `vm.OpCode` when necessary. A `*vm.ScopeContext` is not passed anymore. It is replaced by `tracing.OpContext` which offers access to the memory, stack and current contract.
- `CaptureFault` -> `OnFault(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, depth int, err error)`. Similar to above. - `CaptureFault` -> `OnFault(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, depth int, err error)`. Similar to above.
[unreleased]: https://github.com/ethereum/go-ethereum/compare/v1.14.0...master [unreleased]: https://github.com/ethereum/go-ethereum/compare/v1.14.8...master
[v1.14.0]: https://github.com/ethereum/go-ethereum/releases/tag/v1.14.0 [v1.14.0]: https://github.com/ethereum/go-ethereum/releases/tag/v1.14.0
[v1.14.3]: https://github.com/ethereum/go-ethereum/releases/tag/v1.14.3 [v1.14.3]: https://github.com/ethereum/go-ethereum/releases/tag/v1.14.3
[v1.14.4]: https://github.com/ethereum/go-ethereum/releases/tag/v1.14.4

View file

@ -34,6 +34,7 @@ type OpContext interface {
Address() common.Address Address() common.Address
CallValue() *uint256.Int CallValue() *uint256.Int
CallInput() []byte CallInput() []byte
ContractCode() []byte
} }
// StateDB gives tracers access to the whole state. // StateDB gives tracers access to the whole state.
@ -42,6 +43,7 @@ type StateDB interface {
GetNonce(common.Address) uint64 GetNonce(common.Address) uint64
GetCode(common.Address) []byte GetCode(common.Address) []byte
GetState(common.Address, common.Hash) common.Hash GetState(common.Address, common.Hash) common.Hash
GetTransientState(common.Address, common.Hash) common.Hash
Exist(common.Address) bool Exist(common.Address) bool
GetRefund() uint64 GetRefund() uint64
} }
@ -300,12 +302,14 @@ const (
GasChangeCallStorageColdAccess GasChangeReason = 13 GasChangeCallStorageColdAccess GasChangeReason = 13
// GasChangeCallFailedExecution is the burning of the remaining gas when the execution failed without a revert. // GasChangeCallFailedExecution is the burning of the remaining gas when the execution failed without a revert.
GasChangeCallFailedExecution GasChangeReason = 14 GasChangeCallFailedExecution GasChangeReason = 14
// GasChangeWitnessContractInit is the amount charged for adding to the witness during the contract creation initialization step // GasChangeWitnessContractInit flags the event of adding to the witness during the contract creation initialization step.
GasChangeWitnessContractInit GasChangeReason = 15 GasChangeWitnessContractInit GasChangeReason = 15
// GasChangeWitnessContractCreation is the amount charged for adding to the witness during the contract creation finalization step // GasChangeWitnessContractCreation flags the event of adding to the witness during the contract creation finalization step.
GasChangeWitnessContractCreation GasChangeReason = 16 GasChangeWitnessContractCreation GasChangeReason = 16
// GasChangeWitnessCodeChunk is the amount charged for touching one or more contract code chunks // GasChangeWitnessCodeChunk flags the event of adding one or more contract code chunks to the witness.
GasChangeWitnessCodeChunk GasChangeReason = 17 GasChangeWitnessCodeChunk GasChangeReason = 17
// GasChangeWitnessContractCollisionCheck flags the event of adding to the witness when checking for contract address collision.
GasChangeWitnessContractCollisionCheck GasChangeReason = 18
// GasChangeIgnored is a special value that can be used to indicate that the gas change should be ignored as // GasChangeIgnored is a special value that can be used to indicate that the gas change should be ignored as
// it will be "manually" tracked by a direct emit of the gas change event. // it will be "manually" tracked by a direct emit of the gas change event.

View file

@ -478,7 +478,7 @@ func (p *BlobPool) parseTransaction(id uint64, size uint32, blob []byte) error {
log.Error("Rejecting duplicate blob pool entry", "id", id, "hash", tx.Hash()) log.Error("Rejecting duplicate blob pool entry", "id", id, "hash", tx.Hash())
return errors.New("duplicate blob entry") return errors.New("duplicate blob entry")
} }
sender, err := p.signer.Sender(tx) sender, err := types.Sender(p.signer, tx)
if err != nil { if err != nil {
// This path is impossible unless the signature validity changes across // This path is impossible unless the signature validity changes across
// restarts. For that ever improbable case, recover gracefully by ignoring // restarts. For that ever improbable case, recover gracefully by ignoring
@ -566,7 +566,7 @@ func (p *BlobPool) recheck(addr common.Address, inclusions map[common.Hash]uint6
ids []uint64 ids []uint64
nonces []uint64 nonces []uint64
) )
for txs[0].nonce < next { for len(txs) > 0 && txs[0].nonce < next {
ids = append(ids, txs[0].id) ids = append(ids, txs[0].id)
nonces = append(nonces, txs[0].nonce) nonces = append(nonces, txs[0].nonce)
@ -892,7 +892,7 @@ func (p *BlobPool) reorg(oldHead, newHead *types.Header) (map[common.Address][]*
// and accumulate the transactors and transactions // and accumulate the transactors and transactions
for rem.NumberU64() > add.NumberU64() { for rem.NumberU64() > add.NumberU64() {
for _, tx := range rem.Transactions() { for _, tx := range rem.Transactions() {
from, _ := p.signer.Sender(tx) from, _ := types.Sender(p.signer, tx)
discarded[from] = append(discarded[from], tx) discarded[from] = append(discarded[from], tx)
transactors[from] = struct{}{} transactors[from] = struct{}{}
@ -904,7 +904,7 @@ func (p *BlobPool) reorg(oldHead, newHead *types.Header) (map[common.Address][]*
} }
for add.NumberU64() > rem.NumberU64() { for add.NumberU64() > rem.NumberU64() {
for _, tx := range add.Transactions() { for _, tx := range add.Transactions() {
from, _ := p.signer.Sender(tx) from, _ := types.Sender(p.signer, tx)
included[from] = append(included[from], tx) included[from] = append(included[from], tx)
inclusions[tx.Hash()] = add.NumberU64() inclusions[tx.Hash()] = add.NumberU64()
@ -917,7 +917,7 @@ func (p *BlobPool) reorg(oldHead, newHead *types.Header) (map[common.Address][]*
} }
for rem.Hash() != add.Hash() { for rem.Hash() != add.Hash() {
for _, tx := range rem.Transactions() { for _, tx := range rem.Transactions() {
from, _ := p.signer.Sender(tx) from, _ := types.Sender(p.signer, tx)
discarded[from] = append(discarded[from], tx) discarded[from] = append(discarded[from], tx)
transactors[from] = struct{}{} transactors[from] = struct{}{}
@ -927,7 +927,7 @@ func (p *BlobPool) reorg(oldHead, newHead *types.Header) (map[common.Address][]*
return nil, nil return nil, nil
} }
for _, tx := range add.Transactions() { for _, tx := range add.Transactions() {
from, _ := p.signer.Sender(tx) from, _ := types.Sender(p.signer, tx)
included[from] = append(included[from], tx) included[from] = append(included[from], tx)
inclusions[tx.Hash()] = add.NumberU64() inclusions[tx.Hash()] = add.NumberU64()
@ -940,7 +940,7 @@ func (p *BlobPool) reorg(oldHead, newHead *types.Header) (map[common.Address][]*
} }
// Generate the set of transactions per address to pull back into the pool, // Generate the set of transactions per address to pull back into the pool,
// also updating the rest along the way // also updating the rest along the way
reinject := make(map[common.Address][]*types.Transaction) reinject := make(map[common.Address][]*types.Transaction, len(transactors))
for addr := range transactors { for addr := range transactors {
// Generate the set that was lost to reinject into the pool // Generate the set that was lost to reinject into the pool
lost := make([]*types.Transaction, 0, len(discarded[addr])) lost := make([]*types.Transaction, 0, len(discarded[addr]))
@ -1127,7 +1127,7 @@ func (p *BlobPool) validateTx(tx *types.Transaction) error {
// If the transaction replaces an existing one, ensure that price bumps are // If the transaction replaces an existing one, ensure that price bumps are
// adhered to. // adhered to.
var ( var (
from, _ = p.signer.Sender(tx) // already validated above from, _ = types.Sender(p.signer, tx) // already validated above
next = p.state.GetNonce(from) next = p.state.GetNonce(from)
) )
if uint64(len(p.index[from])) > tx.Nonce()-next { if uint64(len(p.index[from])) > tx.Nonce()-next {

View file

@ -27,7 +27,6 @@ import (
"path/filepath" "path/filepath"
"sync" "sync"
"testing" "testing"
"time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/misc/eip1559" "github.com/ethereum/go-ethereum/consensus/misc/eip1559"
@ -39,7 +38,6 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/holiman/billy" "github.com/holiman/billy"
@ -53,25 +51,14 @@ var (
emptyBlobVHash = kzg4844.CalcBlobHashV1(sha256.New(), &emptyBlobCommit) emptyBlobVHash = kzg4844.CalcBlobHashV1(sha256.New(), &emptyBlobCommit)
) )
// Chain configuration with Cancun enabled.
//
// TODO(karalabe): replace with params.MainnetChainConfig after Cancun.
var testChainConfig *params.ChainConfig
func init() {
testChainConfig = new(params.ChainConfig)
*testChainConfig = *params.MainnetChainConfig
testChainConfig.CancunTime = new(uint64)
*testChainConfig.CancunTime = uint64(time.Now().Unix())
}
// testBlockChain is a mock of the live chain for testing the pool. // testBlockChain is a mock of the live chain for testing the pool.
type testBlockChain struct { type testBlockChain struct {
config *params.ChainConfig config *params.ChainConfig
basefee *uint256.Int basefee *uint256.Int
blobfee *uint256.Int blobfee *uint256.Int
statedb *state.StateDB statedb *state.StateDB
blocks map[uint64]*types.Block
} }
func (bc *testBlockChain) Config() *params.ChainConfig { func (bc *testBlockChain) Config() *params.ChainConfig {
@ -79,7 +66,7 @@ func (bc *testBlockChain) Config() *params.ChainConfig {
} }
func (bc *testBlockChain) CurrentBlock() *types.Header { func (bc *testBlockChain) CurrentBlock() *types.Header {
// Yolo, life is too short to invert mist.CalcBaseFee and misc.CalcBlobFee, // Yolo, life is too short to invert misc.CalcBaseFee and misc.CalcBlobFee,
// just binary search it them. // just binary search it them.
// The base fee at 5714 ETH translates into the 21000 base gas higher than // The base fee at 5714 ETH translates into the 21000 base gas higher than
@ -142,7 +129,14 @@ func (bc *testBlockChain) CurrentFinalBlock() *types.Header {
} }
func (bc *testBlockChain) GetBlock(hash common.Hash, number uint64) *types.Block { func (bc *testBlockChain) GetBlock(hash common.Hash, number uint64) *types.Block {
return nil // This is very yolo for the tests. If the number is the origin block we use
// to init the pool, return an empty block with the correct starting header.
//
// If it is something else, return some baked in mock data.
if number == bc.config.LondonBlock.Uint64()+1 {
return types.NewBlockWithHeader(bc.CurrentBlock())
}
return bc.blocks[number]
} }
func (bc *testBlockChain) StateAt(common.Hash) (*state.StateDB, error) { func (bc *testBlockChain) StateAt(common.Hash) (*state.StateDB, error) {
@ -181,14 +175,14 @@ func makeAddressReserver() txpool.AddressReserver {
// the blob pool. // the blob pool.
func makeTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCap uint64, key *ecdsa.PrivateKey) *types.Transaction { func makeTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCap uint64, key *ecdsa.PrivateKey) *types.Transaction {
blobtx := makeUnsignedTx(nonce, gasTipCap, gasFeeCap, blobFeeCap) blobtx := makeUnsignedTx(nonce, gasTipCap, gasFeeCap, blobFeeCap)
return types.MustSignNewTx(key, types.LatestSigner(testChainConfig), blobtx) return types.MustSignNewTx(key, types.LatestSigner(params.MainnetChainConfig), blobtx)
} }
// makeUnsignedTx is a utility method to construct a random blob transaction // makeUnsignedTx is a utility method to construct a random blob transaction
// without signing it. // without signing it.
func makeUnsignedTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCap uint64) *types.BlobTx { func makeUnsignedTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCap uint64) *types.BlobTx {
return &types.BlobTx{ return &types.BlobTx{
ChainID: uint256.MustFromBig(testChainConfig.ChainID), ChainID: uint256.MustFromBig(params.MainnetChainConfig.ChainID),
Nonce: nonce, Nonce: nonce,
GasTipCap: uint256.NewInt(gasTipCap), GasTipCap: uint256.NewInt(gasTipCap),
GasFeeCap: uint256.NewInt(gasFeeCap), GasFeeCap: uint256.NewInt(gasFeeCap),
@ -229,13 +223,17 @@ func verifyPoolInternals(t *testing.T, pool *BlobPool) {
for hash := range seen { for hash := range seen {
t.Errorf("indexed transaction hash #%x missing from lookup table", hash) t.Errorf("indexed transaction hash #%x missing from lookup table", hash)
} }
// Verify that transactions are sorted per account and contain no nonce gaps // Verify that transactions are sorted per account and contain no nonce gaps,
// and that the first nonce is the next expected one based on the state.
for addr, txs := range pool.index { for addr, txs := range pool.index {
for i := 1; i < len(txs); i++ { for i := 1; i < len(txs); i++ {
if txs[i].nonce != txs[i-1].nonce+1 { if txs[i].nonce != txs[i-1].nonce+1 {
t.Errorf("addr %v, tx %d nonce mismatch: have %d, want %d", addr, i, txs[i].nonce, txs[i-1].nonce+1) t.Errorf("addr %v, tx %d nonce mismatch: have %d, want %d", addr, i, txs[i].nonce, txs[i-1].nonce+1)
} }
} }
if txs[0].nonce != pool.state.GetNonce(addr) {
t.Errorf("addr %v, first tx nonce mismatch: have %d, want %d", addr, txs[0].nonce, pool.state.GetNonce(addr))
}
} }
// Verify that calculated evacuation thresholds are correct // Verify that calculated evacuation thresholds are correct
for addr, txs := range pool.index { for addr, txs := range pool.index {
@ -315,7 +313,7 @@ func verifyPoolInternals(t *testing.T, pool *BlobPool) {
// - 8. Fully duplicate transactions (matching hash) must be dropped // - 8. Fully duplicate transactions (matching hash) must be dropped
// - 9. Duplicate nonces from the same account must be dropped // - 9. Duplicate nonces from the same account must be dropped
func TestOpenDrops(t *testing.T) { func TestOpenDrops(t *testing.T) {
log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true))) //log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true)))
// Create a temporary folder for the persistent backend // Create a temporary folder for the persistent backend
storage, _ := os.MkdirTemp("", "blobpool-") storage, _ := os.MkdirTemp("", "blobpool-")
@ -331,7 +329,7 @@ func TestOpenDrops(t *testing.T) {
// Insert a transaction with a bad signature to verify that stale junk after // Insert a transaction with a bad signature to verify that stale junk after
// potential hard-forks can get evicted (case 2) // potential hard-forks can get evicted (case 2)
tx := types.NewTx(&types.BlobTx{ tx := types.NewTx(&types.BlobTx{
ChainID: uint256.MustFromBig(testChainConfig.ChainID), ChainID: uint256.MustFromBig(params.MainnetChainConfig.ChainID),
GasTipCap: new(uint256.Int), GasTipCap: new(uint256.Int),
GasFeeCap: new(uint256.Int), GasFeeCap: new(uint256.Int),
Gas: 0, Gas: 0,
@ -560,7 +558,7 @@ func TestOpenDrops(t *testing.T) {
statedb.Commit(0, true) statedb.Commit(0, true)
chain := &testBlockChain{ chain := &testBlockChain{
config: testChainConfig, config: params.MainnetChainConfig,
basefee: uint256.NewInt(params.InitialBaseFee), basefee: uint256.NewInt(params.InitialBaseFee),
blobfee: uint256.NewInt(params.BlobTxMinBlobGasprice), blobfee: uint256.NewInt(params.BlobTxMinBlobGasprice),
statedb: statedb, statedb: statedb,
@ -638,7 +636,7 @@ func TestOpenDrops(t *testing.T) {
// - 2. Eviction thresholds are calculated correctly for the sequences // - 2. Eviction thresholds are calculated correctly for the sequences
// - 3. Balance usage of an account is totals across all transactions // - 3. Balance usage of an account is totals across all transactions
func TestOpenIndex(t *testing.T) { func TestOpenIndex(t *testing.T) {
log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true))) //log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true)))
// Create a temporary folder for the persistent backend // Create a temporary folder for the persistent backend
storage, _ := os.MkdirTemp("", "blobpool-") storage, _ := os.MkdirTemp("", "blobpool-")
@ -679,7 +677,7 @@ func TestOpenIndex(t *testing.T) {
statedb.Commit(0, true) statedb.Commit(0, true)
chain := &testBlockChain{ chain := &testBlockChain{
config: testChainConfig, config: params.MainnetChainConfig,
basefee: uint256.NewInt(params.InitialBaseFee), basefee: uint256.NewInt(params.InitialBaseFee),
blobfee: uint256.NewInt(params.BlobTxMinBlobGasprice), blobfee: uint256.NewInt(params.BlobTxMinBlobGasprice),
statedb: statedb, statedb: statedb,
@ -727,7 +725,7 @@ func TestOpenIndex(t *testing.T) {
// Tests that after indexing all the loaded transactions from disk, a price heap // Tests that after indexing all the loaded transactions from disk, a price heap
// is correctly constructed based on the head basefee and blobfee. // is correctly constructed based on the head basefee and blobfee.
func TestOpenHeap(t *testing.T) { func TestOpenHeap(t *testing.T) {
log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true))) //log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true)))
// Create a temporary folder for the persistent backend // Create a temporary folder for the persistent backend
storage, _ := os.MkdirTemp("", "blobpool-") storage, _ := os.MkdirTemp("", "blobpool-")
@ -781,7 +779,7 @@ func TestOpenHeap(t *testing.T) {
statedb.Commit(0, true) statedb.Commit(0, true)
chain := &testBlockChain{ chain := &testBlockChain{
config: testChainConfig, config: params.MainnetChainConfig,
basefee: uint256.NewInt(1050), basefee: uint256.NewInt(1050),
blobfee: uint256.NewInt(105), blobfee: uint256.NewInt(105),
statedb: statedb, statedb: statedb,
@ -814,7 +812,7 @@ func TestOpenHeap(t *testing.T) {
// Tests that after the pool's previous state is loaded back, any transactions // Tests that after the pool's previous state is loaded back, any transactions
// over the new storage cap will get dropped. // over the new storage cap will get dropped.
func TestOpenCap(t *testing.T) { func TestOpenCap(t *testing.T) {
log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true))) //log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true)))
// Create a temporary folder for the persistent backend // Create a temporary folder for the persistent backend
storage, _ := os.MkdirTemp("", "blobpool-") storage, _ := os.MkdirTemp("", "blobpool-")
@ -861,7 +859,7 @@ func TestOpenCap(t *testing.T) {
statedb.Commit(0, true) statedb.Commit(0, true)
chain := &testBlockChain{ chain := &testBlockChain{
config: testChainConfig, config: params.MainnetChainConfig,
basefee: uint256.NewInt(1050), basefee: uint256.NewInt(1050),
blobfee: uint256.NewInt(105), blobfee: uint256.NewInt(105),
statedb: statedb, statedb: statedb,
@ -906,15 +904,14 @@ func TestOpenCap(t *testing.T) {
// specific to the blob pool. It does not do an exhaustive transaction validity // specific to the blob pool. It does not do an exhaustive transaction validity
// check. // check.
func TestAdd(t *testing.T) { func TestAdd(t *testing.T) {
log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true))) //log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true)))
// seed is a helper tumpe to seed an initial state db and pool // seed is a helper tuple to seed an initial state db and pool
type seed struct { type seed struct {
balance uint64 balance uint64
nonce uint64 nonce uint64
txs []*types.BlobTx txs []*types.BlobTx
} }
// addtx is a helper sender/tx tuple to represent a new tx addition // addtx is a helper sender/tx tuple to represent a new tx addition
type addtx struct { type addtx struct {
from string from string
@ -925,6 +922,7 @@ func TestAdd(t *testing.T) {
tests := []struct { tests := []struct {
seeds map[string]seed seeds map[string]seed
adds []addtx adds []addtx
block []addtx
}{ }{
// Transactions from new accounts should be accepted if their initial // Transactions from new accounts should be accepted if their initial
// nonce matches the expected one from the statedb. Higher or lower must // nonce matches the expected one from the statedb. Higher or lower must
@ -1250,6 +1248,25 @@ func TestAdd(t *testing.T) {
}, },
}, },
}, },
// Tests issue #30518 where a refactor broke internal state invariants,
// causing included transactions not to be properly accounted and thus
// account states going our of sync with the chain.
{
seeds: map[string]seed{
"alice": {
balance: 1000000,
txs: []*types.BlobTx{
makeUnsignedTx(0, 1, 1, 1),
},
},
},
block: []addtx{
{
from: "alice",
tx: makeUnsignedTx(0, 1, 1, 1),
},
},
},
} }
for i, tt := range tests { for i, tt := range tests {
// Create a temporary folder for the persistent backend // Create a temporary folder for the persistent backend
@ -1276,7 +1293,7 @@ func TestAdd(t *testing.T) {
// Sign the seed transactions and store them in the data store // Sign the seed transactions and store them in the data store
for _, tx := range seed.txs { for _, tx := range seed.txs {
signed := types.MustSignNewTx(keys[acc], types.LatestSigner(testChainConfig), tx) signed := types.MustSignNewTx(keys[acc], types.LatestSigner(params.MainnetChainConfig), tx)
blob, _ := rlp.EncodeToBytes(signed) blob, _ := rlp.EncodeToBytes(signed)
store.Put(blob) store.Put(blob)
} }
@ -1286,7 +1303,7 @@ func TestAdd(t *testing.T) {
// Create a blob pool out of the pre-seeded dats // Create a blob pool out of the pre-seeded dats
chain := &testBlockChain{ chain := &testBlockChain{
config: testChainConfig, config: params.MainnetChainConfig,
basefee: uint256.NewInt(1050), basefee: uint256.NewInt(1050),
blobfee: uint256.NewInt(105), blobfee: uint256.NewInt(105),
statedb: statedb, statedb: statedb,
@ -1299,14 +1316,40 @@ func TestAdd(t *testing.T) {
// Add each transaction one by one, verifying the pool internals in between // Add each transaction one by one, verifying the pool internals in between
for j, add := range tt.adds { for j, add := range tt.adds {
signed, _ := types.SignNewTx(keys[add.from], types.LatestSigner(testChainConfig), add.tx) signed, _ := types.SignNewTx(keys[add.from], types.LatestSigner(params.MainnetChainConfig), add.tx)
if err := pool.add(signed); !errors.Is(err, add.err) { if err := pool.add(signed); !errors.Is(err, add.err) {
t.Errorf("test %d, tx %d: adding transaction error mismatch: have %v, want %v", i, j, err, add.err) t.Errorf("test %d, tx %d: adding transaction error mismatch: have %v, want %v", i, j, err, add.err)
} }
verifyPoolInternals(t, pool) verifyPoolInternals(t, pool)
} }
// Verify the pool internals and close down the test
verifyPoolInternals(t, pool) verifyPoolInternals(t, pool)
// If the test contains a chain head event, run that and gain verify the internals
if tt.block != nil {
// Fake a header for the new set of transactions
header := &types.Header{
Number: big.NewInt(int64(chain.CurrentBlock().Number.Uint64() + 1)),
BaseFee: chain.CurrentBlock().BaseFee, // invalid, but nothing checks it, yolo
}
// Inject the fake block into the chain
txs := make([]*types.Transaction, len(tt.block))
for j, inc := range tt.block {
txs[j] = types.MustSignNewTx(keys[inc.from], types.LatestSigner(params.MainnetChainConfig), inc.tx)
}
chain.blocks = map[uint64]*types.Block{
header.Number.Uint64(): types.NewBlockWithHeader(header).WithBody(types.Body{
Transactions: txs,
}),
}
// Apply the nonce updates to the state db
for _, tx := range txs {
sender, _ := types.Sender(types.LatestSigner(params.MainnetChainConfig), tx)
chain.statedb.SetNonce(sender, tx.Nonce()+1)
}
pool.Reset(chain.CurrentBlock(), header)
verifyPoolInternals(t, pool)
}
// Close down the test
pool.Close() pool.Close()
} }
} }
@ -1325,10 +1368,10 @@ func benchmarkPoolPending(b *testing.B, datacap uint64) {
var ( var (
basefee = uint64(1050) basefee = uint64(1050)
blobfee = uint64(105) blobfee = uint64(105)
signer = types.LatestSigner(testChainConfig) signer = types.LatestSigner(params.MainnetChainConfig)
statedb, _ = state.New(types.EmptyRootHash, state.NewDatabaseForTesting()) statedb, _ = state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
chain = &testBlockChain{ chain = &testBlockChain{
config: testChainConfig, config: params.MainnetChainConfig,
basefee: uint256.NewInt(basefee), basefee: uint256.NewInt(basefee),
blobfee: uint256.NewInt(blobfee), blobfee: uint256.NewInt(blobfee),
statedb: statedb, statedb: statedb,

View file

@ -19,9 +19,7 @@ package core
import ( import (
"sync/atomic" "sync/atomic"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/stateless"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
) )
@ -35,9 +33,6 @@ type Validator interface {
// ValidateState validates the given statedb and optionally the process result. // ValidateState validates the given statedb and optionally the process result.
ValidateState(block *types.Block, state *state.StateDB, res *ProcessResult, stateless bool) error ValidateState(block *types.Block, state *state.StateDB, res *ProcessResult, stateless bool) error
// ValidateWitness cross validates a block execution with stateless remote clients.
ValidateWitness(witness *stateless.Witness, receiptRoot common.Hash, stateRoot common.Hash) error
} }
// Prefetcher is an interface for pre-caching transaction signatures and state. // Prefetcher is an interface for pre-caching transaction signatures and state.
@ -59,7 +54,7 @@ type Processor interface {
// ProcessResult contains the values computed by Process. // ProcessResult contains the values computed by Process.
type ProcessResult struct { type ProcessResult struct {
Receipts types.Receipts Receipts types.Receipts
Requests types.Requests Requests [][]byte
Logs []*types.Log Logs []*types.Log
GasUsed uint64 GasUsed uint64
} }

View file

@ -18,6 +18,7 @@
package types package types
import ( import (
"crypto/sha256"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"io" "io"
@ -168,9 +169,8 @@ func (h *Header) SanityCheck() error {
func (h *Header) EmptyBody() bool { func (h *Header) EmptyBody() bool {
var ( var (
emptyWithdrawals = h.WithdrawalsHash == nil || *h.WithdrawalsHash == EmptyWithdrawalsHash emptyWithdrawals = h.WithdrawalsHash == nil || *h.WithdrawalsHash == EmptyWithdrawalsHash
emptyRequests = h.RequestsHash == nil || *h.RequestsHash == EmptyReceiptsHash
) )
return h.TxHash == EmptyTxsHash && h.UncleHash == EmptyUncleHash && emptyWithdrawals && emptyRequests return h.TxHash == EmptyTxsHash && h.UncleHash == EmptyUncleHash && emptyWithdrawals
} }
// EmptyReceipts returns true if there are no receipts for this header/block. // EmptyReceipts returns true if there are no receipts for this header/block.
@ -184,7 +184,6 @@ type Body struct {
Transactions []*Transaction Transactions []*Transaction
Uncles []*Header Uncles []*Header
Withdrawals []*Withdrawal `rlp:"optional"` Withdrawals []*Withdrawal `rlp:"optional"`
Requests []*Request `rlp:"optional"`
} }
// Block represents an Ethereum block. // Block represents an Ethereum block.
@ -209,7 +208,6 @@ type Block struct {
uncles []*Header uncles []*Header
transactions Transactions transactions Transactions
withdrawals Withdrawals withdrawals Withdrawals
requests Requests
// witness is not an encoded part of the block body. // witness is not an encoded part of the block body.
// It is held in Block in order for easy relaying to the places // It is held in Block in order for easy relaying to the places
@ -232,7 +230,6 @@ type extblock struct {
Txs []*Transaction Txs []*Transaction
Uncles []*Header Uncles []*Header
Withdrawals []*Withdrawal `rlp:"optional"` Withdrawals []*Withdrawal `rlp:"optional"`
Requests []*Request `rlp:"optional"`
} }
// NewBlock creates a new block. The input data is copied, changes to header and to the // NewBlock creates a new block. The input data is copied, changes to header and to the
@ -249,7 +246,6 @@ func NewBlock(header *Header, body *Body, receipts []*Receipt, hasher TrieHasher
txs = body.Transactions txs = body.Transactions
uncles = body.Uncles uncles = body.Uncles
withdrawals = body.Withdrawals withdrawals = body.Withdrawals
requests = body.Requests
) )
if len(txs) == 0 { if len(txs) == 0 {
@ -288,17 +284,6 @@ func NewBlock(header *Header, body *Body, receipts []*Receipt, hasher TrieHasher
b.withdrawals = slices.Clone(withdrawals) b.withdrawals = slices.Clone(withdrawals)
} }
if requests == nil {
b.header.RequestsHash = nil
} else if len(requests) == 0 {
b.header.RequestsHash = &EmptyRequestsHash
b.requests = Requests{}
} else {
h := DeriveSha(Requests(requests), hasher)
b.header.RequestsHash = &h
b.requests = slices.Clone(requests)
}
return b return b
} }
@ -348,7 +333,7 @@ func (b *Block) DecodeRLP(s *rlp.Stream) error {
if err := s.Decode(&eb); err != nil { if err := s.Decode(&eb); err != nil {
return err return err
} }
b.header, b.uncles, b.transactions, b.withdrawals, b.requests = eb.Header, eb.Uncles, eb.Txs, eb.Withdrawals, eb.Requests b.header, b.uncles, b.transactions, b.withdrawals = eb.Header, eb.Uncles, eb.Txs, eb.Withdrawals
b.size.Store(rlp.ListSize(size)) b.size.Store(rlp.ListSize(size))
return nil return nil
} }
@ -360,14 +345,13 @@ func (b *Block) EncodeRLP(w io.Writer) error {
Txs: b.transactions, Txs: b.transactions,
Uncles: b.uncles, Uncles: b.uncles,
Withdrawals: b.withdrawals, Withdrawals: b.withdrawals,
Requests: b.requests,
}) })
} }
// Body returns the non-header content of the block. // Body returns the non-header content of the block.
// Note the returned data is not an independent copy. // Note the returned data is not an independent copy.
func (b *Block) Body() *Body { func (b *Block) Body() *Body {
return &Body{b.transactions, b.uncles, b.withdrawals, b.requests} return &Body{b.transactions, b.uncles, b.withdrawals}
} }
// Accessors for body data. These do not return a copy because the content // Accessors for body data. These do not return a copy because the content
@ -376,7 +360,6 @@ func (b *Block) Body() *Body {
func (b *Block) Uncles() []*Header { return b.uncles } func (b *Block) Uncles() []*Header { return b.uncles }
func (b *Block) Transactions() Transactions { return b.transactions } func (b *Block) Transactions() Transactions { return b.transactions }
func (b *Block) Withdrawals() Withdrawals { return b.withdrawals } func (b *Block) Withdrawals() Withdrawals { return b.withdrawals }
func (b *Block) Requests() Requests { return b.requests }
func (b *Block) Transaction(hash common.Hash) *Transaction { func (b *Block) Transaction(hash common.Hash) *Transaction {
for _, transaction := range b.transactions { for _, transaction := range b.transactions {
@ -419,7 +402,8 @@ func (b *Block) BaseFee() *big.Int {
return new(big.Int).Set(b.header.BaseFee) return new(big.Int).Set(b.header.BaseFee)
} }
func (b *Block) BeaconRoot() *common.Hash { return b.header.ParentBeaconRoot } func (b *Block) BeaconRoot() *common.Hash { return b.header.ParentBeaconRoot }
func (b *Block) RequestsHash() *common.Hash { return b.header.RequestsHash }
func (b *Block) ExcessBlobGas() *uint64 { func (b *Block) ExcessBlobGas() *uint64 {
var excessBlobGas *uint64 var excessBlobGas *uint64
@ -474,6 +458,19 @@ func CalcUncleHash(uncles []*Header) common.Hash {
return rlpHash(uncles) return rlpHash(uncles)
} }
// CalcRequestsHash creates the block requestsHash value for a list of requests.
func CalcRequestsHash(requests [][]byte) common.Hash {
h1, h2 := sha256.New(), sha256.New()
var buf common.Hash
for _, item := range requests {
h1.Reset()
h1.Write(item)
h2.Write(h1.Sum(buf[:0]))
}
h2.Sum(buf[:0])
return buf
}
// NewBlockWithHeader creates a block with the given header data. The // NewBlockWithHeader creates a block with the given header data. The
// header data is copied, changes to header and to the field values // header data is copied, changes to header and to the field values
// will not affect the block. // will not affect the block.
@ -501,7 +498,6 @@ func (b *Block) WithBody(body Body) *Block {
transactions: slices.Clone(body.Transactions), transactions: slices.Clone(body.Transactions),
uncles: make([]*Header, len(body.Uncles)), uncles: make([]*Header, len(body.Uncles)),
withdrawals: slices.Clone(body.Withdrawals), withdrawals: slices.Clone(body.Withdrawals),
requests: slices.Clone(body.Requests),
witness: b.witness, witness: b.witness,
} }
for i := range body.Uncles { for i := range body.Uncles {
@ -516,7 +512,6 @@ func (b *Block) WithWitness(witness *ExecutionWitness) *Block {
transactions: b.transactions, transactions: b.transactions,
uncles: b.uncles, uncles: b.uncles,
withdrawals: b.withdrawals, withdrawals: b.withdrawals,
requests: b.requests,
witness: witness, witness: witness,
} }
} }

View file

@ -17,52 +17,27 @@
package types package types
import ( import (
"bytes"
"encoding/binary"
"fmt" "fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/rlp"
) )
//go:generate go run github.com/fjl/gencodec -type Deposit -field-override depositMarshaling -out gen_deposit_json.go const (
depositRequestSize = 192
// Deposit contains EIP-6110 deposit data. )
type Deposit struct {
PublicKey [48]byte `json:"pubkey"` // public key of validator
WithdrawalCredentials common.Hash `json:"withdrawalCredentials"` // beneficiary of the validator funds
Amount uint64 `json:"amount"` // deposit size in Gwei
Signature [96]byte `json:"signature"` // signature over deposit msg
Index uint64 `json:"index"` // deposit count value
}
// field type overrides for gencodec
type depositMarshaling struct {
PublicKey hexutil.Bytes
WithdrawalCredentials hexutil.Bytes
Amount hexutil.Uint64
Signature hexutil.Bytes
Index hexutil.Uint64
}
// Deposits implements DerivableList for requests.
type Deposits []*Deposit
// Len returns the length of s.
func (s Deposits) Len() int { return len(s) }
// EncodeIndex encodes the i'th deposit to s.
func (s Deposits) EncodeIndex(i int, w *bytes.Buffer) {
rlp.Encode(w, s[i])
}
// UnpackIntoDeposit unpacks a serialized DepositEvent. // UnpackIntoDeposit unpacks a serialized DepositEvent.
func UnpackIntoDeposit(data []byte) (*Deposit, error) { func DepositLogToRequest(data []byte) ([]byte, error) {
if len(data) != 576 { if len(data) != 576 {
return nil, fmt.Errorf("deposit wrong length: want 576, have %d", len(data)) return nil, fmt.Errorf("deposit wrong length: want 576, have %d", len(data))
} }
var d Deposit
request := make([]byte, depositRequestSize)
const (
pubkeyOffset = 0
withdrawalCredOffset = pubkeyOffset + 48
amountOffset = withdrawalCredOffset + 32
signatureOffset = amountOffset + 8
indexOffset = signatureOffset + 96
)
// The ABI encodes the position of dynamic elements first. Since there are 5 // The ABI encodes the position of dynamic elements first. Since there are 5
// elements, skip over the positional data. The first 32 bytes of dynamic // elements, skip over the positional data. The first 32 bytes of dynamic
// elements also encode their actual length. Skip over that value too. // elements also encode their actual length. Skip over that value too.
@ -70,34 +45,20 @@ func UnpackIntoDeposit(data []byte) (*Deposit, error) {
// PublicKey is the first element. ABI encoding pads values to 32 bytes, so // PublicKey is the first element. ABI encoding pads values to 32 bytes, so
// despite BLS public keys being length 48, the value length here is 64. Then // despite BLS public keys being length 48, the value length here is 64. Then
// skip over the next length value. // skip over the next length value.
copy(d.PublicKey[:], data[b:b+48]) copy(request[pubkeyOffset:], data[b:b+48])
b += 48 + 16 + 32 b += 48 + 16 + 32
// WithdrawalCredentials is 32 bytes. Read that value then skip over next // WithdrawalCredentials is 32 bytes. Read that value then skip over next
// length. // length.
copy(d.WithdrawalCredentials[:], data[b:b+32]) copy(request[withdrawalCredOffset:], data[b:b+32])
b += 32 + 32 b += 32 + 32
// Amount is 8 bytes, but it is padded to 32. Skip over it and the next // Amount is 8 bytes, but it is padded to 32. Skip over it and the next
// length. // length.
d.Amount = binary.LittleEndian.Uint64(data[b : b+8]) copy(request[amountOffset:], data[b:b+8])
b += 8 + 24 + 32 b += 8 + 24 + 32
// Signature is 96 bytes. Skip over it and the next length. // Signature is 96 bytes. Skip over it and the next length.
copy(d.Signature[:], data[b:b+96]) copy(request[signatureOffset:], data[b:b+96])
b += 96 + 32 b += 96 + 32
// Amount is 8 bytes. // Index is 8 bytes.
d.Index = binary.LittleEndian.Uint64(data[b : b+8]) copy(request[indexOffset:], data[b:b+8])
return request, nil
return &d, nil
}
func (d *Deposit) requestType() byte { return DepositRequestType }
func (d *Deposit) encode(b *bytes.Buffer) error { return rlp.Encode(b, d) }
func (d *Deposit) decode(input []byte) error { return rlp.DecodeBytes(input, d) }
func (d *Deposit) copy() RequestData {
return &Deposit{
PublicKey: d.PublicKey,
WithdrawalCredentials: d.WithdrawalCredentials,
Amount: d.Amount,
Signature: d.Signature,
Index: d.Index,
}
} }

View file

@ -17,8 +17,7 @@
package types package types
import ( import (
"encoding/binary" "bytes"
"reflect"
"testing" "testing"
"github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi"
@ -71,23 +70,26 @@ func FuzzUnpackIntoDeposit(f *testing.F) {
copy(sig[:], s) copy(sig[:], s)
copy(index[:], i) copy(index[:], i)
want := Deposit{ var enc []byte
PublicKey: pubkey, enc = append(enc, pubkey[:]...)
WithdrawalCredentials: wxCred, enc = append(enc, wxCred[:]...)
Amount: binary.LittleEndian.Uint64(amount[:]), enc = append(enc, amount[:]...)
Signature: sig, enc = append(enc, sig[:]...)
Index: binary.LittleEndian.Uint64(index[:]), enc = append(enc, index[:]...)
}
out, err := depositABI.Pack("DepositEvent", want.PublicKey[:], want.WithdrawalCredentials[:], amount[:], want.Signature[:], index[:]) out, err := depositABI.Pack("DepositEvent", pubkey[:], wxCred[:], amount[:], sig[:], index[:])
if err != nil { if err != nil {
t.Fatalf("error packing deposit: %v", err) t.Fatalf("error packing deposit: %v", err)
} }
got, err := UnpackIntoDeposit(out[4:]) got, err := DepositLogToRequest(out[4:])
if err != nil { if err != nil {
t.Errorf("error unpacking deposit: %v", err) t.Errorf("error unpacking deposit: %v", err)
} }
if !reflect.DeepEqual(want, *got) { if len(got) != depositRequestSize {
t.Errorf("roundtrip failed: want %v, got %v", want, got) t.Errorf("wrong output size: %d, want %d", len(got), depositRequestSize)
}
if !bytes.Equal(enc, got) {
t.Errorf("roundtrip failed: want %x, got %x", enc, got)
} }
}) })
} }

View file

@ -1,70 +0,0 @@
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
package types
import (
"encoding/json"
"errors"
"github.com/ethereum/go-ethereum/common/hexutil"
)
var _ = (*depositMarshaling)(nil)
// MarshalJSON marshals as JSON.
func (d Deposit) MarshalJSON() ([]byte, error) {
type Deposit struct {
PublicKey hexutil.Bytes `json:"pubkey"`
WithdrawalCredentials hexutil.Bytes `json:"withdrawalCredentials"`
Amount hexutil.Uint64 `json:"amount"`
Signature hexutil.Bytes `json:"signature"`
Index hexutil.Uint64 `json:"index"`
}
var enc Deposit
enc.PublicKey = d.PublicKey[:]
enc.WithdrawalCredentials = d.WithdrawalCredentials[:]
enc.Amount = hexutil.Uint64(d.Amount)
enc.Signature = d.Signature[:]
enc.Index = hexutil.Uint64(d.Index)
return json.Marshal(&enc)
}
// UnmarshalJSON unmarshals from JSON.
func (d *Deposit) UnmarshalJSON(input []byte) error {
type Deposit struct {
PublicKey *hexutil.Bytes `json:"pubkey"`
WithdrawalCredentials *hexutil.Bytes `json:"withdrawalCredentials"`
Amount *hexutil.Uint64 `json:"amount"`
Signature *hexutil.Bytes `json:"signature"`
Index *hexutil.Uint64 `json:"index"`
}
var dec Deposit
if err := json.Unmarshal(input, &dec); err != nil {
return err
}
if dec.PublicKey != nil {
if len(*dec.PublicKey) != len(d.PublicKey) {
return errors.New("field 'pubkey' has wrong length, need 48 items")
}
copy(d.PublicKey[:], *dec.PublicKey)
}
if dec.WithdrawalCredentials != nil {
if len(*dec.WithdrawalCredentials) != len(d.WithdrawalCredentials) {
return errors.New("field 'withdrawalCredentials' has wrong length, need 32 items")
}
copy(d.WithdrawalCredentials[:], *dec.WithdrawalCredentials)
}
if dec.Amount != nil {
d.Amount = uint64(*dec.Amount)
}
if dec.Signature != nil {
if len(*dec.Signature) != len(d.Signature) {
return errors.New("field 'signature' has wrong length, need 96 items")
}
copy(d.Signature[:], *dec.Signature)
}
if dec.Index != nil {
d.Index = uint64(*dec.Index)
}
return nil
}

View file

@ -41,9 +41,6 @@ var (
// EmptyWithdrawalsHash is the known hash of the empty withdrawal set. // EmptyWithdrawalsHash is the known hash of the empty withdrawal set.
EmptyWithdrawalsHash = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") EmptyWithdrawalsHash = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
// EmptyRequestsHash is the known hash of the empty requests set.
EmptyRequestsHash = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
// EmptyVerkleHash is the known hash of an empty verkle trie. // EmptyVerkleHash is the known hash of an empty verkle trie.
EmptyVerkleHash = common.Hash{} EmptyVerkleHash = common.Hash{}
) )

View file

@ -1,157 +0,0 @@
// Copyright 2024 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 types
import (
"bytes"
"errors"
"fmt"
"io"
"github.com/ethereum/go-ethereum/rlp"
)
var (
ErrRequestTypeNotSupported = errors.New("request type not supported")
errShortTypedRequest = errors.New("typed request too short")
)
// Request types.
const (
DepositRequestType = 0x00
)
// Request is an EIP-7685 request object. It represents execution layer
// triggered messages bound for the consensus layer.
type Request struct {
inner RequestData
}
// Type returns the EIP-7685 type of the request.
func (r *Request) Type() byte {
return r.inner.requestType()
}
// Inner returns the inner request data.
func (r *Request) Inner() RequestData {
return r.inner
}
// NewRequest creates a new request.
func NewRequest(inner RequestData) *Request {
req := new(Request)
req.inner = inner.copy()
return req
}
// Requests implements DerivableList for requests.
type Requests []*Request
// Len returns the length of s.
func (s Requests) Len() int { return len(s) }
// EncodeIndex encodes the i'th request to s.
func (s Requests) EncodeIndex(i int, w *bytes.Buffer) {
s[i].encode(w)
}
// RequestData is the underlying data of a request.
type RequestData interface {
requestType() byte
encode(*bytes.Buffer) error
decode([]byte) error
copy() RequestData // creates a deep copy and initializes all fields
}
// EncodeRLP implements rlp.Encoder
func (r *Request) EncodeRLP(w io.Writer) error {
buf := encodeBufferPool.Get().(*bytes.Buffer)
defer encodeBufferPool.Put(buf)
buf.Reset()
if err := r.encode(buf); err != nil {
return err
}
return rlp.Encode(w, buf.Bytes())
}
// encode writes the canonical encoding of a request to w.
func (r *Request) encode(w *bytes.Buffer) error {
w.WriteByte(r.Type())
return r.inner.encode(w)
}
// MarshalBinary returns the canonical encoding of the request.
func (r *Request) MarshalBinary() ([]byte, error) {
var buf bytes.Buffer
err := r.encode(&buf)
return buf.Bytes(), err
}
// DecodeRLP implements rlp.Decoder
func (r *Request) DecodeRLP(s *rlp.Stream) error {
kind, size, err := s.Kind()
switch {
case err != nil:
return err
case kind == rlp.List:
return fmt.Errorf("untyped request")
case kind == rlp.Byte:
return errShortTypedRequest
default:
// First read the request payload bytes into a temporary buffer.
b, buf, err := getPooledBuffer(size)
if err != nil {
return err
}
defer encodeBufferPool.Put(buf)
if err := s.ReadBytes(b); err != nil {
return err
}
// Now decode the inner request.
inner, err := r.decode(b)
if err == nil {
r.inner = inner
}
return err
}
}
// UnmarshalBinary decodes the canonical encoding of requests.
func (r *Request) UnmarshalBinary(b []byte) error {
inner, err := r.decode(b)
if err != nil {
return err
}
r.inner = inner
return nil
}
// decode decodes a request from the canonical format.
func (r *Request) decode(b []byte) (RequestData, error) {
if len(b) <= 1 {
return nil, errShortTypedRequest
}
var inner RequestData
switch b[0] {
case DepositRequestType:
inner = new(Deposit)
default:
return nil, ErrRequestTypeNotSupported
}
err := inner.decode(b[1:])
return inner, err
}

View file

@ -560,7 +560,7 @@ func (s Transactions) EncodeIndex(i int, w *bytes.Buffer) {
func TxDifference(a, b Transactions) Transactions { func TxDifference(a, b Transactions) Transactions {
keep := make(Transactions, 0, len(a)) keep := make(Transactions, 0, len(a))
remove := make(map[common.Hash]struct{}) remove := make(map[common.Hash]struct{}, b.Len())
for _, tx := range b { for _, tx := range b {
remove[tx.Hash()] = struct{}{} remove[tx.Hash()] = struct{}{}
} }

View file

@ -64,21 +64,24 @@ func MakeSigner(config *params.ChainConfig, blockNumber *big.Int, blockTime uint
// Use this in transaction-handling code where the current block number is unknown. If you // Use this in transaction-handling code where the current block number is unknown. If you
// have the current block number available, use MakeSigner instead. // have the current block number available, use MakeSigner instead.
func LatestSigner(config *params.ChainConfig) Signer { func LatestSigner(config *params.ChainConfig) Signer {
var signer Signer
if config.ChainID != nil { if config.ChainID != nil {
if config.CancunTime != nil { switch {
return NewCancunSigner(config.ChainID) case config.CancunTime != nil:
} signer = NewCancunSigner(config.ChainID)
if config.LondonBlock != nil { case config.LondonBlock != nil:
return NewLondonSigner(config.ChainID) signer = NewLondonSigner(config.ChainID)
} case config.BerlinBlock != nil:
if config.BerlinBlock != nil { signer = NewEIP2930Signer(config.ChainID)
return NewEIP2930Signer(config.ChainID) case config.EIP155Block != nil:
} signer = NewEIP155Signer(config.ChainID)
if config.EIP155Block != nil { default:
return NewEIP155Signer(config.ChainID) signer = HomesteadSigner{}
} }
} else {
signer = HomesteadSigner{}
} }
return HomesteadSigner{} return signer
} }
// LatestSignerForChainID returns the 'most permissive' Signer available. Specifically, // LatestSignerForChainID returns the 'most permissive' Signer available. Specifically,
@ -89,10 +92,13 @@ func LatestSigner(config *params.ChainConfig) Signer {
// configuration are unknown. If you have a ChainConfig, use LatestSigner instead. // configuration are unknown. If you have a ChainConfig, use LatestSigner instead.
// If you have a ChainConfig and know the current block number, use MakeSigner instead. // If you have a ChainConfig and know the current block number, use MakeSigner instead.
func LatestSignerForChainID(chainID *big.Int) Signer { func LatestSignerForChainID(chainID *big.Int) Signer {
if chainID == nil { var signer Signer
return HomesteadSigner{} if chainID != nil {
signer = NewCancunSigner(chainID)
} else {
signer = HomesteadSigner{}
} }
return NewCancunSigner(chainID) return signer
} }
// SignTx signs the transaction using the given signer and private key. // SignTx signs the transaction using the given signer and private key.

View file

@ -546,9 +546,7 @@ func TestYParityJSONUnmarshalling(t *testing.T) {
DynamicFeeTxType, DynamicFeeTxType,
BlobTxType, BlobTxType,
} { } {
txType := txType
for _, test := range tests { for _, test := range tests {
test := test
t.Run(fmt.Sprintf("txType=%d: %s", txType, test.name), func(t *testing.T) { t.Run(fmt.Sprintf("txType=%d: %s", txType, test.name), func(t *testing.T) {
// Copy the base json // Copy the base json
testJson := maps.Clone(baseJson) testJson := maps.Clone(baseJson)

98
core/vm/analysis_eof.go Normal file
View file

@ -0,0 +1,98 @@
// Copyright 2024 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 vm
// eofCodeBitmap collects data locations in code.
func eofCodeBitmap(code []byte) bitvec {
// The bitmap is 4 bytes longer than necessary, in case the code
// ends with a PUSH32, the algorithm will push zeroes onto the
// bitvector outside the bounds of the actual code.
bits := make(bitvec, len(code)/8+1+4)
return eofCodeBitmapInternal(code, bits)
}
// eofCodeBitmapInternal is the internal implementation of codeBitmap for EOF
// code validation.
func eofCodeBitmapInternal(code, bits bitvec) bitvec {
for pc := uint64(0); pc < uint64(len(code)); {
var (
op = OpCode(code[pc])
numbits uint16
)
pc++
if op == RJUMPV {
// RJUMPV is unique as it has a variable sized operand.
// The total size is determined by the count byte which
// immediate follows RJUMPV. Truncation will be caught
// in other validation steps -- for now, just return a
// valid bitmap for as much of the code as is
// available.
end := uint64(len(code))
if pc >= end {
// Count missing, no more bits to mark.
return bits
}
numbits = uint16(code[pc])*2 + 3
if pc+uint64(numbits) > end {
// Jump table is truncated, mark as many bits
// as possible.
numbits = uint16(end - pc)
}
} else {
numbits = uint16(Immediates(op))
if numbits == 0 {
continue
}
}
if numbits >= 8 {
for ; numbits >= 16; numbits -= 16 {
bits.set16(pc)
pc += 16
}
for ; numbits >= 8; numbits -= 8 {
bits.set8(pc)
pc += 8
}
}
switch numbits {
case 1:
bits.set1(pc)
pc += 1
case 2:
bits.setN(set2BitsMask, pc)
pc += 2
case 3:
bits.setN(set3BitsMask, pc)
pc += 3
case 4:
bits.setN(set4BitsMask, pc)
pc += 4
case 5:
bits.setN(set5BitsMask, pc)
pc += 5
case 6:
bits.setN(set6BitsMask, pc)
pc += 6
case 7:
bits.setN(set7BitsMask, pc)
pc += 7
}
}
return bits
}

View file

@ -105,3 +105,31 @@ func BenchmarkJumpdestOpAnalysis(bench *testing.B) {
op = STOP op = STOP
bench.Run(op.String(), bencher) bench.Run(op.String(), bencher)
} }
func BenchmarkJumpdestOpEOFAnalysis(bench *testing.B) {
var op OpCode
bencher := func(b *testing.B) {
code := make([]byte, analysisCodeSize)
b.SetBytes(analysisCodeSize)
for i := range code {
code[i] = byte(op)
}
bits := make(bitvec, len(code)/8+1+4)
b.ResetTimer()
for i := 0; i < b.N; i++ {
clear(bits)
eofCodeBitmapInternal(code, bits)
}
}
for op = PUSH1; op <= PUSH32; op++ {
bench.Run(op.String(), bencher)
}
op = JUMPDEST
bench.Run(op.String(), bencher)
op = STOP
bench.Run(op.String(), bencher)
op = RJUMPV
bench.Run(op.String(), bencher)
op = EOFCREATE
bench.Run(op.String(), bencher)
}

View file

@ -533,3 +533,173 @@ func enable4762(jt *JumpTable) {
} }
} }
} }
// enableEOF applies the EOF changes.
// OBS! For EOF, there are two changes:
// 1. Two separate jumptables are required. One, EOF-jumptable, is used by
// eof contracts. This one contains things like RJUMP.
// 2. The regular non-eof jumptable also needs to be modified, specifically to
// modify how EXTCODECOPY works under the hood.
//
// This method _only_ deals with case 1.
func enableEOF(jt *JumpTable) {
// Deprecate opcodes
undefined := &operation{
execute: opUndefined,
constantGas: 0,
minStack: minStack(0, 0),
maxStack: maxStack(0, 0),
undefined: true,
}
jt[CALL] = undefined
jt[CALLCODE] = undefined
jt[DELEGATECALL] = undefined
jt[STATICCALL] = undefined
jt[SELFDESTRUCT] = undefined
jt[JUMP] = undefined
jt[JUMPI] = undefined
jt[PC] = undefined
jt[CREATE] = undefined
jt[CREATE2] = undefined
jt[CODESIZE] = undefined
jt[CODECOPY] = undefined
jt[EXTCODESIZE] = undefined
jt[EXTCODECOPY] = undefined
jt[EXTCODEHASH] = undefined
jt[GAS] = undefined
// Allow 0xFE to terminate sections
jt[INVALID] = &operation{
execute: opUndefined,
constantGas: 0,
minStack: minStack(0, 0),
maxStack: maxStack(0, 0),
}
// New opcodes
jt[RJUMP] = &operation{
execute: opRjump,
constantGas: GasQuickStep,
minStack: minStack(0, 0),
maxStack: maxStack(0, 0),
}
jt[RJUMPI] = &operation{
execute: opRjumpi,
constantGas: GasFastishStep,
minStack: minStack(1, 0),
maxStack: maxStack(1, 0),
}
jt[RJUMPV] = &operation{
execute: opRjumpv,
constantGas: GasFastishStep,
minStack: minStack(1, 0),
maxStack: maxStack(1, 0),
}
jt[CALLF] = &operation{
execute: opCallf,
constantGas: GasFastStep,
minStack: minStack(0, 0),
maxStack: maxStack(0, 0),
}
jt[RETF] = &operation{
execute: opRetf,
constantGas: GasFastestStep,
minStack: minStack(0, 0),
maxStack: maxStack(0, 0),
}
jt[JUMPF] = &operation{
execute: opJumpf,
constantGas: GasFastStep,
minStack: minStack(0, 0),
maxStack: maxStack(0, 0),
}
jt[EOFCREATE] = &operation{
execute: opEOFCreate,
constantGas: params.Create2Gas,
dynamicGas: gasEOFCreate,
minStack: minStack(4, 1),
maxStack: maxStack(4, 1),
memorySize: memoryEOFCreate,
}
jt[RETURNCONTRACT] = &operation{
execute: opReturnContract,
// returncontract has zero constant gas cost
dynamicGas: pureMemoryGascost,
minStack: minStack(2, 0),
maxStack: maxStack(2, 0),
memorySize: memoryReturnContract,
}
jt[DATALOAD] = &operation{
execute: opDataLoad,
constantGas: GasFastishStep,
minStack: minStack(1, 1),
maxStack: maxStack(1, 1),
}
jt[DATALOADN] = &operation{
execute: opDataLoadN,
constantGas: GasFastestStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
}
jt[DATASIZE] = &operation{
execute: opDataSize,
constantGas: GasQuickStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
}
jt[DATACOPY] = &operation{
execute: opDataCopy,
constantGas: GasFastestStep,
dynamicGas: memoryCopierGas(2),
minStack: minStack(3, 0),
maxStack: maxStack(3, 0),
memorySize: memoryDataCopy,
}
jt[DUPN] = &operation{
execute: opDupN,
constantGas: GasFastestStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
}
jt[SWAPN] = &operation{
execute: opSwapN,
constantGas: GasFastestStep,
minStack: minStack(0, 0),
maxStack: maxStack(0, 0),
}
jt[EXCHANGE] = &operation{
execute: opExchange,
constantGas: GasFastestStep,
minStack: minStack(0, 0),
maxStack: maxStack(0, 0),
}
jt[RETURNDATALOAD] = &operation{
execute: opReturnDataLoad,
constantGas: GasFastestStep,
minStack: minStack(1, 1),
maxStack: maxStack(1, 1),
}
jt[EXTCALL] = &operation{
execute: opExtCall,
constantGas: params.WarmStorageReadCostEIP2929,
dynamicGas: makeCallVariantGasCallEIP2929(gasExtCall, 0),
minStack: minStack(4, 1),
maxStack: maxStack(4, 1),
memorySize: memoryExtCall,
}
jt[EXTDELEGATECALL] = &operation{
execute: opExtDelegateCall,
dynamicGas: makeCallVariantGasCallEIP2929(gasExtDelegateCall, 0),
constantGas: params.WarmStorageReadCostEIP2929,
minStack: minStack(3, 1),
maxStack: maxStack(3, 1),
memorySize: memoryExtCall,
}
jt[EXTSTATICCALL] = &operation{
execute: opExtStaticCall,
constantGas: params.WarmStorageReadCostEIP2929,
dynamicGas: makeCallVariantGasCallEIP2929(gasExtStaticCall, 0),
minStack: minStack(3, 1),
maxStack: maxStack(3, 1),
memorySize: memoryExtCall,
}
}

501
core/vm/eof.go Normal file
View file

@ -0,0 +1,501 @@
// Copyright 2024 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 vm
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"strings"
"github.com/ethereum/go-ethereum/params"
)
const (
offsetVersion = 2
offsetTypesKind = 3
offsetCodeKind = 6
kindTypes = 1
kindCode = 2
kindContainer = 3
kindData = 4
eofFormatByte = 0xef
eof1Version = 1
maxInputItems = 127
maxOutputItems = 128
maxStackHeight = 1023
maxContainerSections = 256
)
var eofMagic = []byte{0xef, 0x00}
// HasEOFByte returns true if code starts with 0xEF byte
func HasEOFByte(code []byte) bool {
return len(code) != 0 && code[0] == eofFormatByte
}
// hasEOFMagic returns true if code starts with magic defined by EIP-3540
func hasEOFMagic(code []byte) bool {
return len(eofMagic) <= len(code) && bytes.Equal(eofMagic, code[0:len(eofMagic)])
}
// isEOFVersion1 returns true if the code's version byte equals eof1Version. It
// does not verify the EOF magic is valid.
func isEOFVersion1(code []byte) bool {
return 2 < len(code) && code[2] == byte(eof1Version)
}
// Container is an EOF container object.
type Container struct {
types []*functionMetadata
codeSections [][]byte
subContainers []*Container
subContainerCodes [][]byte
data []byte
dataSize int // might be more than len(data)
}
// functionMetadata is an EOF function signature.
type functionMetadata struct {
inputs uint8
outputs uint8
maxStackHeight uint16
}
// stackDelta returns the #outputs - #inputs
func (meta *functionMetadata) stackDelta() int {
return int(meta.outputs) - int(meta.inputs)
}
// checkInputs checks the current minimum stack (stackMin) against the required inputs
// of the metadata, and returns an error if the stack is too shallow.
func (meta *functionMetadata) checkInputs(stackMin int) error {
if int(meta.inputs) > stackMin {
return ErrStackUnderflow{stackLen: stackMin, required: int(meta.inputs)}
}
return nil
}
// checkStackMax checks the if current maximum stack combined with the
// functin max stack will result in a stack overflow, and if so returns an error.
func (meta *functionMetadata) checkStackMax(stackMax int) error {
newMaxStack := stackMax + int(meta.maxStackHeight) - int(meta.inputs)
if newMaxStack > int(params.StackLimit) {
return ErrStackOverflow{stackLen: newMaxStack, limit: int(params.StackLimit)}
}
return nil
}
// MarshalBinary encodes an EOF container into binary format.
func (c *Container) MarshalBinary() []byte {
// Build EOF prefix.
b := make([]byte, 2)
copy(b, eofMagic)
b = append(b, eof1Version)
// Write section headers.
b = append(b, kindTypes)
b = binary.BigEndian.AppendUint16(b, uint16(len(c.types)*4))
b = append(b, kindCode)
b = binary.BigEndian.AppendUint16(b, uint16(len(c.codeSections)))
for _, codeSection := range c.codeSections {
b = binary.BigEndian.AppendUint16(b, uint16(len(codeSection)))
}
var encodedContainer [][]byte
if len(c.subContainers) != 0 {
b = append(b, kindContainer)
b = binary.BigEndian.AppendUint16(b, uint16(len(c.subContainers)))
for _, section := range c.subContainers {
encoded := section.MarshalBinary()
b = binary.BigEndian.AppendUint16(b, uint16(len(encoded)))
encodedContainer = append(encodedContainer, encoded)
}
}
b = append(b, kindData)
b = binary.BigEndian.AppendUint16(b, uint16(c.dataSize))
b = append(b, 0) // terminator
// Write section contents.
for _, ty := range c.types {
b = append(b, []byte{ty.inputs, ty.outputs, byte(ty.maxStackHeight >> 8), byte(ty.maxStackHeight & 0x00ff)}...)
}
for _, code := range c.codeSections {
b = append(b, code...)
}
for _, section := range encodedContainer {
b = append(b, section...)
}
b = append(b, c.data...)
return b
}
// UnmarshalBinary decodes an EOF container.
func (c *Container) UnmarshalBinary(b []byte, isInitcode bool) error {
return c.unmarshalContainer(b, isInitcode, true)
}
// UnmarshalSubContainer decodes an EOF container that is inside another container.
func (c *Container) UnmarshalSubContainer(b []byte, isInitcode bool) error {
return c.unmarshalContainer(b, isInitcode, false)
}
func (c *Container) unmarshalContainer(b []byte, isInitcode bool, topLevel bool) error {
if !hasEOFMagic(b) {
return fmt.Errorf("%w: want %x", errInvalidMagic, eofMagic)
}
if len(b) < 14 {
return io.ErrUnexpectedEOF
}
if len(b) > params.MaxInitCodeSize {
return ErrMaxInitCodeSizeExceeded
}
if !isEOFVersion1(b) {
return fmt.Errorf("%w: have %d, want %d", errInvalidVersion, b[2], eof1Version)
}
var (
kind, typesSize, dataSize int
codeSizes []int
err error
)
// Parse type section header.
kind, typesSize, err = parseSection(b, offsetTypesKind)
if err != nil {
return err
}
if kind != kindTypes {
return fmt.Errorf("%w: found section kind %x instead", errMissingTypeHeader, kind)
}
if typesSize < 4 || typesSize%4 != 0 {
return fmt.Errorf("%w: type section size must be divisible by 4, have %d", errInvalidTypeSize, typesSize)
}
if typesSize/4 > 1024 {
return fmt.Errorf("%w: type section must not exceed 4*1024, have %d", errInvalidTypeSize, typesSize)
}
// Parse code section header.
kind, codeSizes, err = parseSectionList(b, offsetCodeKind)
if err != nil {
return err
}
if kind != kindCode {
return fmt.Errorf("%w: found section kind %x instead", errMissingCodeHeader, kind)
}
if len(codeSizes) != typesSize/4 {
return fmt.Errorf("%w: mismatch of code sections found and type signatures, types %d, code %d", errInvalidCodeSize, typesSize/4, len(codeSizes))
}
// Parse (optional) container section header.
var containerSizes []int
offset := offsetCodeKind + 2 + 2*len(codeSizes) + 1
if offset < len(b) && b[offset] == kindContainer {
kind, containerSizes, err = parseSectionList(b, offset)
if err != nil {
return err
}
if kind != kindContainer {
panic("somethings wrong")
}
if len(containerSizes) == 0 {
return fmt.Errorf("%w: total container count must not be zero", errInvalidContainerSectionSize)
}
offset = offset + 2 + 2*len(containerSizes) + 1
}
// Parse data section header.
kind, dataSize, err = parseSection(b, offset)
if err != nil {
return err
}
if kind != kindData {
return fmt.Errorf("%w: found section %x instead", errMissingDataHeader, kind)
}
c.dataSize = dataSize
// Check for terminator.
offsetTerminator := offset + 3
if len(b) < offsetTerminator {
return fmt.Errorf("%w: invalid offset terminator", io.ErrUnexpectedEOF)
}
if b[offsetTerminator] != 0 {
return fmt.Errorf("%w: have %x", errMissingTerminator, b[offsetTerminator])
}
// Verify overall container size.
expectedSize := offsetTerminator + typesSize + sum(codeSizes) + dataSize + 1
if len(containerSizes) != 0 {
expectedSize += sum(containerSizes)
}
if len(b) < expectedSize-dataSize {
return fmt.Errorf("%w: have %d, want %d", errInvalidContainerSize, len(b), expectedSize)
}
// Only check that the expected size is not exceed on non-initcode
if (!topLevel || !isInitcode) && len(b) > expectedSize {
return fmt.Errorf("%w: have %d, want %d", errInvalidContainerSize, len(b), expectedSize)
}
// Parse types section.
idx := offsetTerminator + 1
var types = make([]*functionMetadata, 0, typesSize/4)
for i := 0; i < typesSize/4; i++ {
sig := &functionMetadata{
inputs: b[idx+i*4],
outputs: b[idx+i*4+1],
maxStackHeight: binary.BigEndian.Uint16(b[idx+i*4+2:]),
}
if sig.inputs > maxInputItems {
return fmt.Errorf("%w for section %d: have %d", errTooManyInputs, i, sig.inputs)
}
if sig.outputs > maxOutputItems {
return fmt.Errorf("%w for section %d: have %d", errTooManyOutputs, i, sig.outputs)
}
if sig.maxStackHeight > maxStackHeight {
return fmt.Errorf("%w for section %d: have %d", errTooLargeMaxStackHeight, i, sig.maxStackHeight)
}
types = append(types, sig)
}
if types[0].inputs != 0 || types[0].outputs != 0x80 {
return fmt.Errorf("%w: have %d, %d", errInvalidSection0Type, types[0].inputs, types[0].outputs)
}
c.types = types
// Parse code sections.
idx += typesSize
codeSections := make([][]byte, len(codeSizes))
for i, size := range codeSizes {
if size == 0 {
return fmt.Errorf("%w for section %d: size must not be 0", errInvalidCodeSize, i)
}
codeSections[i] = b[idx : idx+size]
idx += size
}
c.codeSections = codeSections
// Parse the optional container sizes.
if len(containerSizes) != 0 {
if len(containerSizes) > maxContainerSections {
return fmt.Errorf("%w number of container section exceed: %v: have %v", errInvalidContainerSectionSize, maxContainerSections, len(containerSizes))
}
subContainerCodes := make([][]byte, 0, len(containerSizes))
subContainers := make([]*Container, 0, len(containerSizes))
for i, size := range containerSizes {
if size == 0 || idx+size > len(b) {
return fmt.Errorf("%w for section %d: size must not be 0", errInvalidContainerSectionSize, i)
}
subC := new(Container)
end := min(idx+size, len(b))
if err := subC.unmarshalContainer(b[idx:end], isInitcode, false); err != nil {
if topLevel {
return fmt.Errorf("%w in sub container %d", err, i)
}
return err
}
subContainers = append(subContainers, subC)
subContainerCodes = append(subContainerCodes, b[idx:end])
idx += size
}
c.subContainers = subContainers
c.subContainerCodes = subContainerCodes
}
//Parse data section.
end := len(b)
if !isInitcode {
end = min(idx+dataSize, len(b))
}
if topLevel && len(b) != idx+dataSize {
return errTruncatedTopLevelContainer
}
c.data = b[idx:end]
return nil
}
// ValidateCode validates each code section of the container against the EOF v1
// rule set.
func (c *Container) ValidateCode(jt *JumpTable, isInitCode bool) error {
refBy := notRefByEither
if isInitCode {
refBy = refByEOFCreate
}
return c.validateSubContainer(jt, refBy)
}
func (c *Container) validateSubContainer(jt *JumpTable, refBy int) error {
visited := make(map[int]struct{})
subContainerVisited := make(map[int]int)
toVisit := []int{0}
for len(toVisit) > 0 {
// TODO check if this can be used as a DOS
// Theres and edge case here where we mark something as visited that we visit before,
// This should not trigger a re-visit
// e.g. 0 -> 1, 2, 3
// 1 -> 2, 3
// should not mean 2 and 3 should be visited twice
var (
index = toVisit[0]
code = c.codeSections[index]
)
if _, ok := visited[index]; !ok {
res, err := validateCode(code, index, c, jt, refBy == refByEOFCreate)
if err != nil {
return err
}
visited[index] = struct{}{}
// Mark all sections that can be visited from here.
for idx := range res.visitedCode {
if _, ok := visited[idx]; !ok {
toVisit = append(toVisit, idx)
}
}
// Mark all subcontainer that can be visited from here.
for idx, reference := range res.visitedSubContainers {
// Make sure subcontainers are only ever referenced by either EOFCreate or ReturnContract
if ref, ok := subContainerVisited[idx]; ok && ref != reference {
return errors.New("section referenced by both EOFCreate and ReturnContract")
}
subContainerVisited[idx] = reference
}
if refBy == refByReturnContract && res.isInitCode {
return errIncompatibleContainerKind
}
if refBy == refByEOFCreate && res.isRuntime {
return errIncompatibleContainerKind
}
}
toVisit = toVisit[1:]
}
// Make sure every code section is visited at least once.
if len(visited) != len(c.codeSections) {
return errUnreachableCode
}
for idx, container := range c.subContainers {
reference, ok := subContainerVisited[idx]
if !ok {
return errOrphanedSubcontainer
}
if err := container.validateSubContainer(jt, reference); err != nil {
return err
}
}
return nil
}
// parseSection decodes a (kind, size) pair from an EOF header.
func parseSection(b []byte, idx int) (kind, size int, err error) {
if idx+3 >= len(b) {
return 0, 0, io.ErrUnexpectedEOF
}
kind = int(b[idx])
size = int(binary.BigEndian.Uint16(b[idx+1:]))
return kind, size, nil
}
// parseSectionList decodes a (kind, len, []codeSize) section list from an EOF
// header.
func parseSectionList(b []byte, idx int) (kind int, list []int, err error) {
if idx >= len(b) {
return 0, nil, io.ErrUnexpectedEOF
}
kind = int(b[idx])
list, err = parseList(b, idx+1)
if err != nil {
return 0, nil, err
}
return kind, list, nil
}
// parseList decodes a list of uint16..
func parseList(b []byte, idx int) ([]int, error) {
if len(b) < idx+2 {
return nil, io.ErrUnexpectedEOF
}
count := binary.BigEndian.Uint16(b[idx:])
if len(b) <= idx+2+int(count)*2 {
return nil, io.ErrUnexpectedEOF
}
list := make([]int, count)
for i := 0; i < int(count); i++ {
list[i] = int(binary.BigEndian.Uint16(b[idx+2+2*i:]))
}
return list, nil
}
// parseUint16 parses a 16 bit unsigned integer.
func parseUint16(b []byte) (int, error) {
if len(b) < 2 {
return 0, io.ErrUnexpectedEOF
}
return int(binary.BigEndian.Uint16(b)), nil
}
// parseInt16 parses a 16 bit signed integer.
func parseInt16(b []byte) int {
return int(int16(b[1]) | int16(b[0])<<8)
}
// sum computes the sum of a slice.
func sum(list []int) (s int) {
for _, n := range list {
s += n
}
return
}
func (c *Container) String() string {
var output = []string{
"Header",
fmt.Sprintf(" - EOFMagic: %02x", eofMagic),
fmt.Sprintf(" - EOFVersion: %02x", eof1Version),
fmt.Sprintf(" - KindType: %02x", kindTypes),
fmt.Sprintf(" - TypesSize: %04x", len(c.types)*4),
fmt.Sprintf(" - KindCode: %02x", kindCode),
fmt.Sprintf(" - KindData: %02x", kindData),
fmt.Sprintf(" - DataSize: %04x", len(c.data)),
fmt.Sprintf(" - Number of code sections: %d", len(c.codeSections)),
}
for i, code := range c.codeSections {
output = append(output, fmt.Sprintf(" - Code section %d length: %04x", i, len(code)))
}
output = append(output, fmt.Sprintf(" - Number of subcontainers: %d", len(c.subContainers)))
if len(c.subContainers) > 0 {
for i, section := range c.subContainers {
output = append(output, fmt.Sprintf(" - subcontainer %d length: %04x\n", i, len(section.MarshalBinary())))
}
}
output = append(output, "Body")
for i, typ := range c.types {
output = append(output, fmt.Sprintf(" - Type %v: %x", i,
[]byte{typ.inputs, typ.outputs, byte(typ.maxStackHeight >> 8), byte(typ.maxStackHeight & 0x00ff)}))
}
for i, code := range c.codeSections {
output = append(output, fmt.Sprintf(" - Code section %d: %#x", i, code))
}
for i, section := range c.subContainers {
output = append(output, fmt.Sprintf(" - Subcontainer %d: %x", i, section.MarshalBinary()))
}
output = append(output, fmt.Sprintf(" - Data: %#x", c.data))
return strings.Join(output, "\n")
}

235
core/vm/eof_control_flow.go Normal file
View file

@ -0,0 +1,235 @@
// Copyright 2024 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package vm
import (
"fmt"
"github.com/ethereum/go-ethereum/params"
)
func validateControlFlow(code []byte, section int, metadata []*functionMetadata, jt *JumpTable) (int, error) {
var (
maxStackHeight = int(metadata[section].inputs)
visitCount = 0
next = make([]int, 0, 1)
)
var (
stackBoundsMax = make([]uint16, len(code))
stackBoundsMin = make([]uint16, len(code))
)
setBounds := func(pos, min, maxi int) {
// The stackboundMax slice is a bit peculiar. We use `0` to denote
// not set. Therefore, we use `1` to represent the value `0`, and so on.
// So if the caller wants to store `1` as max bound, we internally store it as
// `2`.
if stackBoundsMax[pos] == 0 { // Not yet set
visitCount++
}
if maxi < 65535 {
stackBoundsMax[pos] = uint16(maxi + 1)
}
stackBoundsMin[pos] = uint16(min)
maxStackHeight = max(maxStackHeight, maxi)
}
getStackMaxMin := func(pos int) (ok bool, min, max int) {
maxi := stackBoundsMax[pos]
if maxi == 0 { // Not yet set
return false, 0, 0
}
return true, int(stackBoundsMin[pos]), int(maxi - 1)
}
// set the initial stack bounds
setBounds(0, int(metadata[section].inputs), int(metadata[section].inputs))
qualifiedExit := false
for pos := 0; pos < len(code); pos++ {
op := OpCode(code[pos])
ok, currentStackMin, currentStackMax := getStackMaxMin(pos)
if !ok {
return 0, errUnreachableCode
}
switch op {
case CALLF:
arg, _ := parseUint16(code[pos+1:])
newSection := metadata[arg]
if err := newSection.checkInputs(currentStackMin); err != nil {
return 0, fmt.Errorf("%w: at pos %d", err, pos)
}
if err := newSection.checkStackMax(currentStackMax); err != nil {
return 0, fmt.Errorf("%w: at pos %d", err, pos)
}
delta := newSection.stackDelta()
currentStackMax += delta
currentStackMin += delta
case RETF:
/* From the spec:
> for RETF the following must hold: stack_height_max == stack_height_min == types[current_code_index].outputs,
In other words: RETF must unambiguously return all items remaining on the stack.
*/
if currentStackMax != currentStackMin {
return 0, fmt.Errorf("%w: max %d, min %d, at pos %d", errInvalidOutputs, currentStackMax, currentStackMin, pos)
}
numOutputs := int(metadata[section].outputs)
if numOutputs >= maxOutputItems {
return 0, fmt.Errorf("%w: at pos %d", errInvalidNonReturningFlag, pos)
}
if numOutputs != currentStackMin {
return 0, fmt.Errorf("%w: have %d, want %d, at pos %d", errInvalidOutputs, numOutputs, currentStackMin, pos)
}
qualifiedExit = true
case JUMPF:
arg, _ := parseUint16(code[pos+1:])
newSection := metadata[arg]
if err := newSection.checkStackMax(currentStackMax); err != nil {
return 0, fmt.Errorf("%w: at pos %d", err, pos)
}
if newSection.outputs == 0x80 {
if err := newSection.checkInputs(currentStackMin); err != nil {
return 0, fmt.Errorf("%w: at pos %d", err, pos)
}
} else {
if currentStackMax != currentStackMin {
return 0, fmt.Errorf("%w: max %d, min %d, at pos %d", errInvalidOutputs, currentStackMax, currentStackMin, pos)
}
wantStack := int(metadata[section].outputs) - newSection.stackDelta()
if currentStackMax != wantStack {
return 0, fmt.Errorf("%w: at pos %d", errInvalidOutputs, pos)
}
}
qualifiedExit = qualifiedExit || newSection.outputs < maxOutputItems
case DUPN:
arg := int(code[pos+1]) + 1
if want, have := arg, currentStackMin; want > have {
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
}
case SWAPN:
arg := int(code[pos+1]) + 1
if want, have := arg+1, currentStackMin; want > have {
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
}
case EXCHANGE:
arg := int(code[pos+1])
n := arg>>4 + 1
m := arg&0x0f + 1
if want, have := n+m+1, currentStackMin; want > have {
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
}
default:
if want, have := jt[op].minStack, currentStackMin; want > have {
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
}
}
if !terminals[op] && op != CALLF {
change := int(params.StackLimit) - jt[op].maxStack
currentStackMax += change
currentStackMin += change
}
next = next[:0]
switch op {
case RJUMP:
nextPos := pos + 2 + parseInt16(code[pos+1:])
next = append(next, nextPos)
// We set the stack bounds of the destination
// and skip the argument, only for RJUMP, all other opcodes are handled later
if nextPos+1 < pos {
ok, nextMin, nextMax := getStackMaxMin(nextPos + 1)
if !ok {
return 0, errInvalidBackwardJump
}
if nextMax != currentStackMax || nextMin != currentStackMin {
return 0, errInvalidMaxStackHeight
}
} else {
ok, nextMin, nextMax := getStackMaxMin(nextPos + 1)
if !ok {
setBounds(nextPos+1, currentStackMin, currentStackMax)
} else {
setBounds(nextPos+1, min(nextMin, currentStackMin), max(nextMax, currentStackMax))
}
}
case RJUMPI:
arg := parseInt16(code[pos+1:])
next = append(next, pos+2)
next = append(next, pos+2+arg)
case RJUMPV:
count := int(code[pos+1]) + 1
next = append(next, pos+1+2*count)
for i := 0; i < count; i++ {
arg := parseInt16(code[pos+2+2*i:])
next = append(next, pos+1+2*count+arg)
}
default:
if imm := int(immediates[op]); imm != 0 {
next = append(next, pos+imm)
} else {
// Simple op, no operand.
next = append(next, pos)
}
}
if op != RJUMP && !terminals[op] {
for _, instr := range next {
nextPC := instr + 1
if nextPC >= len(code) {
return 0, fmt.Errorf("%w: end with %s, pos %d", errInvalidCodeTermination, op, pos)
}
if nextPC > pos {
// target reached via forward jump or seq flow
ok, nextMin, nextMax := getStackMaxMin(nextPC)
if !ok {
setBounds(nextPC, currentStackMin, currentStackMax)
} else {
setBounds(nextPC, min(nextMin, currentStackMin), max(nextMax, currentStackMax))
}
} else {
// target reached via backwards jump
ok, nextMin, nextMax := getStackMaxMin(nextPC)
if !ok {
return 0, errInvalidBackwardJump
}
if currentStackMax != nextMax {
return 0, fmt.Errorf("%w want %d as current max got %d at pos %d,", errInvalidBackwardJump, currentStackMax, nextMax, pos)
}
if currentStackMin != nextMin {
return 0, fmt.Errorf("%w want %d as current min got %d at pos %d,", errInvalidBackwardJump, currentStackMin, nextMin, pos)
}
}
}
}
if op == RJUMP {
pos += 2 // skip the immediate
} else {
pos = next[0]
}
}
if qualifiedExit != (metadata[section].outputs < maxOutputItems) {
return 0, fmt.Errorf("%w no RETF or qualified JUMPF", errInvalidNonReturningFlag)
}
if maxStackHeight >= int(params.StackLimit) {
return 0, ErrStackOverflow{maxStackHeight, int(params.StackLimit)}
}
if maxStackHeight != int(metadata[section].maxStackHeight) {
return 0, fmt.Errorf("%w in code section %d: have %d, want %d", errInvalidMaxStackHeight, section, maxStackHeight, metadata[section].maxStackHeight)
}
return visitCount, nil
}

70
core/vm/eof_immediates.go Normal file
View file

@ -0,0 +1,70 @@
// Copyright 2024 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 vm
// immediate denotes how many immediate bytes an operation uses. This information
// is not required during runtime, only during EOF-validation, so is not
// places into the op-struct in the instruction table.
// Note: the immediates is fork-agnostic, and assumes that validity of opcodes at
// the given time is performed elsewhere.
var immediates [256]uint8
// terminals denotes whether instructions can be the final opcode in a code section.
// Note: the terminals is fork-agnostic, and assumes that validity of opcodes at
// the given time is performed elsewhere.
var terminals [256]bool
func init() {
// The legacy pushes
for i := uint8(1); i < 33; i++ {
immediates[int(PUSH0)+int(i)] = i
}
// And new eof opcodes.
immediates[DATALOADN] = 2
immediates[RJUMP] = 2
immediates[RJUMPI] = 2
immediates[RJUMPV] = 3
immediates[CALLF] = 2
immediates[JUMPF] = 2
immediates[DUPN] = 1
immediates[SWAPN] = 1
immediates[EXCHANGE] = 1
immediates[EOFCREATE] = 1
immediates[RETURNCONTRACT] = 1
// Define the terminals.
terminals[STOP] = true
terminals[RETF] = true
terminals[JUMPF] = true
terminals[RETURNCONTRACT] = true
terminals[RETURN] = true
terminals[REVERT] = true
terminals[INVALID] = true
}
// Immediates returns the number bytes of immediates (argument not from
// stack but from code) a given opcode has.
// OBS:
// - This function assumes EOF instruction-set. It cannot be upon in
// a. pre-EOF code
// b. post-EOF but legacy code
// - RJUMPV is unique as it has a variable sized operand. The total size is
// determined by the count byte which immediately follows RJUMPV. This method
// will return '3' for RJUMPV, which is the minimum.
func Immediates(op OpCode) int {
return int(immediates[op])
}

112
core/vm/eof_instructions.go Normal file
View file

@ -0,0 +1,112 @@
// Copyright 2024 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 vm
// opRjump implements the RJUMP opcode.
func opRjump(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
}
// opRjumpi implements the RJUMPI opcode
func opRjumpi(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
}
// opRjumpv implements the RJUMPV opcode
func opRjumpv(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
}
// opCallf implements the CALLF opcode
func opCallf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
}
// opRetf implements the RETF opcode
func opRetf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
}
// opJumpf implements the JUMPF opcode
func opJumpf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
}
// opEOFCreate implements the EOFCREATE opcode
func opEOFCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
}
// opReturnContract implements the RETURNCONTRACT opcode
func opReturnContract(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
}
// opDataLoad implements the DATALOAD opcode
func opDataLoad(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
}
// opDataLoadN implements the DATALOADN opcode
func opDataLoadN(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
}
// opDataSize implements the DATASIZE opcode
func opDataSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
}
// opDataCopy implements the DATACOPY opcode
func opDataCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
}
// opDupN implements the DUPN opcode
func opDupN(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
}
// opSwapN implements the SWAPN opcode
func opSwapN(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
}
// opExchange implements the EXCHANGE opcode
func opExchange(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
}
// opReturnDataLoad implements the RETURNDATALOAD opcode
func opReturnDataLoad(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
}
// opExtCall implements the EOFCREATE opcode
func opExtCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
}
// opExtDelegateCall implements the EXTDELEGATECALL opcode
func opExtDelegateCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
}
// opExtStaticCall implements the EXTSTATICCALL opcode
func opExtStaticCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
}

119
core/vm/eof_test.go Normal file
View file

@ -0,0 +1,119 @@
// 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 vm
import (
"encoding/hex"
"fmt"
"reflect"
"testing"
"github.com/ethereum/go-ethereum/common"
)
func TestEOFMarshaling(t *testing.T) {
for i, test := range []struct {
want Container
err error
}{
{
want: Container{
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
codeSections: [][]byte{common.Hex2Bytes("604200")},
data: []byte{0x01, 0x02, 0x03},
dataSize: 3,
},
},
{
want: Container{
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
codeSections: [][]byte{common.Hex2Bytes("604200")},
data: []byte{0x01, 0x02, 0x03},
dataSize: 3,
},
},
{
want: Container{
types: []*functionMetadata{
{inputs: 0, outputs: 0x80, maxStackHeight: 1},
{inputs: 2, outputs: 3, maxStackHeight: 4},
{inputs: 1, outputs: 1, maxStackHeight: 1},
},
codeSections: [][]byte{
common.Hex2Bytes("604200"),
common.Hex2Bytes("6042604200"),
common.Hex2Bytes("00"),
},
data: []byte{},
},
},
} {
var (
b = test.want.MarshalBinary()
got Container
)
t.Logf("b: %#x", b)
if err := got.UnmarshalBinary(b, true); err != nil && err != test.err {
t.Fatalf("test %d: got error \"%v\", want \"%v\"", i, err, test.err)
}
if !reflect.DeepEqual(got, test.want) {
t.Fatalf("test %d: got %+v, want %+v", i, got, test.want)
}
}
}
func TestEOFSubcontainer(t *testing.T) {
var subcontainer = new(Container)
if err := subcontainer.UnmarshalBinary(common.Hex2Bytes("ef000101000402000100010400000000800000fe"), true); err != nil {
t.Fatal(err)
}
container := Container{
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
codeSections: [][]byte{common.Hex2Bytes("604200")},
subContainers: []*Container{subcontainer},
data: []byte{0x01, 0x02, 0x03},
dataSize: 3,
}
var (
b = container.MarshalBinary()
got Container
)
if err := got.UnmarshalBinary(b, true); err != nil {
t.Fatal(err)
}
fmt.Print(got)
if res := got.MarshalBinary(); !reflect.DeepEqual(res, b) {
t.Fatalf("invalid marshalling, want %v got %v", b, res)
}
}
func TestMarshaling(t *testing.T) {
tests := []string{
"EF000101000402000100040400000000800000E0000000",
"ef0001010004020001000d04000000008000025fe100055f5fe000035f600100",
}
for i, test := range tests {
s, err := hex.DecodeString(test)
if err != nil {
t.Fatalf("test %d: error decoding: %v", i, err)
}
var got Container
if err := got.UnmarshalBinary(s, true); err != nil {
t.Fatalf("test %d: got error %v", i, err)
}
}
}

255
core/vm/eof_validation.go Normal file
View file

@ -0,0 +1,255 @@
// Copyright 2024 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 vm
import (
"errors"
"fmt"
"io"
)
// Below are all possible errors that can occur during validation of
// EOF containers.
var (
errInvalidMagic = errors.New("invalid magic")
errUndefinedInstruction = errors.New("undefined instruction")
errTruncatedImmediate = errors.New("truncated immediate")
errInvalidSectionArgument = errors.New("invalid section argument")
errInvalidCallArgument = errors.New("callf into non-returning section")
errInvalidDataloadNArgument = errors.New("invalid dataloadN argument")
errInvalidJumpDest = errors.New("invalid jump destination")
errInvalidBackwardJump = errors.New("invalid backward jump")
errInvalidOutputs = errors.New("invalid number of outputs")
errInvalidMaxStackHeight = errors.New("invalid max stack height")
errInvalidCodeTermination = errors.New("invalid code termination")
errEOFCreateWithTruncatedSection = errors.New("eofcreate with truncated section")
errOrphanedSubcontainer = errors.New("subcontainer not referenced at all")
errIncompatibleContainerKind = errors.New("incompatible container kind")
errStopAndReturnContract = errors.New("Stop/Return and Returncontract in the same code section")
errStopInInitCode = errors.New("initcode contains a RETURN or STOP opcode")
errTruncatedTopLevelContainer = errors.New("truncated top level container")
errUnreachableCode = errors.New("unreachable code")
errInvalidNonReturningFlag = errors.New("invalid non-returning flag, bad RETF")
errInvalidVersion = errors.New("invalid version")
errMissingTypeHeader = errors.New("missing type header")
errInvalidTypeSize = errors.New("invalid type section size")
errMissingCodeHeader = errors.New("missing code header")
errInvalidCodeSize = errors.New("invalid code size")
errInvalidContainerSectionSize = errors.New("invalid container section size")
errMissingDataHeader = errors.New("missing data header")
errMissingTerminator = errors.New("missing header terminator")
errTooManyInputs = errors.New("invalid type content, too many inputs")
errTooManyOutputs = errors.New("invalid type content, too many outputs")
errInvalidSection0Type = errors.New("invalid section 0 type, input and output should be zero and non-returning (0x80)")
errTooLargeMaxStackHeight = errors.New("invalid type content, max stack height exceeds limit")
errInvalidContainerSize = errors.New("invalid container size")
)
const (
notRefByEither = iota
refByReturnContract
refByEOFCreate
)
type validationResult struct {
visitedCode map[int]struct{}
visitedSubContainers map[int]int
isInitCode bool
isRuntime bool
}
// validateCode validates the code parameter against the EOF v1 validity requirements.
func validateCode(code []byte, section int, container *Container, jt *JumpTable, isInitCode bool) (*validationResult, error) {
var (
i = 0
// Tracks the number of actual instructions in the code (e.g.
// non-immediate values). This is used at the end to determine
// if each instruction is reachable.
count = 0
op OpCode
analysis bitvec
visitedCode map[int]struct{}
visitedSubcontainers map[int]int
hasReturnContract bool
hasStop bool
)
// This loop visits every single instruction and verifies:
// * if the instruction is valid for the given jump table.
// * if the instruction has an immediate value, it is not truncated.
// * if performing a relative jump, all jump destinations are valid.
// * if changing code sections, the new code section index is valid and
// will not cause a stack overflow.
for i < len(code) {
count++
op = OpCode(code[i])
if jt[op].undefined {
return nil, fmt.Errorf("%w: op %s, pos %d", errUndefinedInstruction, op, i)
}
size := int(immediates[op])
if size != 0 && len(code) <= i+size {
return nil, fmt.Errorf("%w: op %s, pos %d", errTruncatedImmediate, op, i)
}
switch op {
case RJUMP, RJUMPI:
if err := checkDest(code, &analysis, i+1, i+3, len(code)); err != nil {
return nil, err
}
case RJUMPV:
max_size := int(code[i+1])
length := max_size + 1
if len(code) <= i+length {
return nil, fmt.Errorf("%w: jump table truncated, op %s, pos %d", errTruncatedImmediate, op, i)
}
offset := i + 2
for j := 0; j < length; j++ {
if err := checkDest(code, &analysis, offset+j*2, offset+(length*2), len(code)); err != nil {
return nil, err
}
}
i += 2 * max_size
case CALLF:
arg, _ := parseUint16(code[i+1:])
if arg >= len(container.types) {
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", errInvalidSectionArgument, arg, len(container.types), i)
}
if container.types[arg].outputs == 0x80 {
return nil, fmt.Errorf("%w: section %v", errInvalidCallArgument, arg)
}
if visitedCode == nil {
visitedCode = make(map[int]struct{})
}
visitedCode[arg] = struct{}{}
case JUMPF:
arg, _ := parseUint16(code[i+1:])
if arg >= len(container.types) {
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", errInvalidSectionArgument, arg, len(container.types), i)
}
if container.types[arg].outputs != 0x80 && container.types[arg].outputs > container.types[section].outputs {
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", errInvalidOutputs, arg, len(container.types), i)
}
if visitedCode == nil {
visitedCode = make(map[int]struct{})
}
visitedCode[arg] = struct{}{}
case DATALOADN:
arg, _ := parseUint16(code[i+1:])
// TODO why are we checking this? We should just pad
if arg+32 > len(container.data) {
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", errInvalidDataloadNArgument, arg, len(container.data), i)
}
case RETURNCONTRACT:
if !isInitCode {
return nil, errIncompatibleContainerKind
}
arg := int(code[i+1])
if arg >= len(container.subContainers) {
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", errUnreachableCode, arg, len(container.subContainers), i)
}
if visitedSubcontainers == nil {
visitedSubcontainers = make(map[int]int)
}
// We need to store per subcontainer how it was referenced
if v, ok := visitedSubcontainers[arg]; ok && v != refByReturnContract {
return nil, fmt.Errorf("section already referenced, arg :%d", arg)
}
if hasStop {
return nil, errStopAndReturnContract
}
hasReturnContract = true
visitedSubcontainers[arg] = refByReturnContract
case EOFCREATE:
arg := int(code[i+1])
if arg >= len(container.subContainers) {
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", errUnreachableCode, arg, len(container.subContainers), i)
}
if ct := container.subContainers[arg]; len(ct.data) != ct.dataSize {
return nil, fmt.Errorf("%w: container %d, have %d, claimed %d, pos %d", errEOFCreateWithTruncatedSection, arg, len(ct.data), ct.dataSize, i)
}
if visitedSubcontainers == nil {
visitedSubcontainers = make(map[int]int)
}
// We need to store per subcontainer how it was referenced
if v, ok := visitedSubcontainers[arg]; ok && v != refByEOFCreate {
return nil, fmt.Errorf("section already referenced, arg :%d", arg)
}
visitedSubcontainers[arg] = refByEOFCreate
case STOP, RETURN:
if isInitCode {
return nil, errStopInInitCode
}
if hasReturnContract {
return nil, errStopAndReturnContract
}
hasStop = true
}
i += size + 1
}
// Code sections may not "fall through" and require proper termination.
// Therefore, the last instruction must be considered terminal or RJUMP.
if !terminals[op] && op != RJUMP {
return nil, fmt.Errorf("%w: end with %s, pos %d", errInvalidCodeTermination, op, i)
}
if paths, err := validateControlFlow(code, section, container.types, jt); err != nil {
return nil, err
} else if paths != count {
// TODO(matt): return actual position of unreachable code
return nil, errUnreachableCode
}
return &validationResult{
visitedCode: visitedCode,
visitedSubContainers: visitedSubcontainers,
isInitCode: hasReturnContract,
isRuntime: hasStop,
}, nil
}
// checkDest parses a relative offset at code[0:2] and checks if it is a valid jump destination.
func checkDest(code []byte, analysis *bitvec, imm, from, length int) error {
if len(code) < imm+2 {
return io.ErrUnexpectedEOF
}
if analysis != nil && *analysis == nil {
*analysis = eofCodeBitmap(code)
}
offset := parseInt16(code[imm:])
dest := from + offset
if dest < 0 || dest >= length {
return fmt.Errorf("%w: out-of-bounds offset: offset %d, dest %d, pos %d", errInvalidJumpDest, offset, dest, imm)
}
if !analysis.codeSegment(uint64(dest)) {
return fmt.Errorf("%w: offset into immediate: offset %d, dest %d, pos %d", errInvalidJumpDest, offset, dest, imm)
}
return nil
}
//// disasm is a helper utility to show a sequence of comma-separated operations,
//// with immediates shown inline,
//// e.g: PUSH1(0x00),EOFCREATE(0x00),
//func disasm(code []byte) string {
// var ops []string
// for i := 0; i < len(code); i++ {
// var op string
// if args := immediates[code[i]]; args > 0 {
// op = fmt.Sprintf("%v(%#x)", OpCode(code[i]).String(), code[i+1:i+1+int(args)])
// i += int(args)
// } else {
// op = OpCode(code[i]).String()
// }
// ops = append(ops, op)
// }
// return strings.Join(ops, ",")
//}

View file

@ -0,0 +1,517 @@
// Copyright 2024 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 vm
import (
"encoding/binary"
"errors"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/params"
)
func TestValidateCode(t *testing.T) {
for i, test := range []struct {
code []byte
section int
metadata []*functionMetadata
err error
}{
{
code: []byte{
byte(CALLER),
byte(POP),
byte(STOP),
},
section: 0,
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
},
{
code: []byte{
byte(CALLF), 0x00, 0x00,
byte(RETF),
},
section: 0,
metadata: []*functionMetadata{{inputs: 0, outputs: 0, maxStackHeight: 0}},
},
{
code: []byte{
byte(ADDRESS),
byte(CALLF), 0x00, 0x00,
byte(POP),
byte(RETF),
},
section: 0,
metadata: []*functionMetadata{{inputs: 0, outputs: 0, maxStackHeight: 1}},
},
{
code: []byte{
byte(CALLER),
byte(POP),
},
section: 0,
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
err: errInvalidCodeTermination,
},
{
code: []byte{
byte(RJUMP),
byte(0x00),
byte(0x01),
byte(CALLER),
byte(STOP),
},
section: 0,
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 0}},
err: errUnreachableCode,
},
{
code: []byte{
byte(PUSH1),
byte(0x42),
byte(ADD),
byte(STOP),
},
section: 0,
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
err: ErrStackUnderflow{stackLen: 1, required: 2},
},
{
code: []byte{
byte(PUSH1),
byte(0x42),
byte(POP),
byte(STOP),
},
section: 0,
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 2}},
err: errInvalidMaxStackHeight,
},
{
code: []byte{
byte(PUSH0),
byte(RJUMPI),
byte(0x00),
byte(0x01),
byte(PUSH1),
byte(0x42), // jumps to here
byte(POP),
byte(STOP),
},
section: 0,
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
err: errInvalidJumpDest,
},
{
code: []byte{
byte(PUSH0),
byte(RJUMPV),
byte(0x01),
byte(0x00),
byte(0x01),
byte(0x00),
byte(0x02),
byte(PUSH1),
byte(0x42), // jumps to here
byte(POP), // and here
byte(STOP),
},
section: 0,
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
err: errInvalidJumpDest,
},
{
code: []byte{
byte(PUSH0),
byte(RJUMPV),
byte(0x00),
byte(STOP),
},
section: 0,
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
err: errTruncatedImmediate,
},
{
code: []byte{
byte(RJUMP), 0x00, 0x03,
byte(JUMPDEST), // this code is unreachable to forward jumps alone
byte(JUMPDEST),
byte(RETURN),
byte(PUSH1), 20,
byte(PUSH1), 39,
byte(PUSH1), 0x00,
byte(DATACOPY),
byte(PUSH1), 20,
byte(PUSH1), 0x00,
byte(RJUMP), 0xff, 0xef,
},
section: 0,
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 3}},
err: errUnreachableCode,
},
{
code: []byte{
byte(PUSH1), 1,
byte(RJUMPI), 0x00, 0x03,
byte(JUMPDEST),
byte(JUMPDEST),
byte(STOP),
byte(PUSH1), 20,
byte(PUSH1), 39,
byte(PUSH1), 0x00,
byte(DATACOPY),
byte(PUSH1), 20,
byte(PUSH1), 0x00,
byte(RETURN),
},
section: 0,
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 3}},
},
{
code: []byte{
byte(PUSH1), 1,
byte(RJUMPV), 0x01, 0x00, 0x03, 0xff, 0xf8,
byte(JUMPDEST),
byte(JUMPDEST),
byte(STOP),
byte(PUSH1), 20,
byte(PUSH1), 39,
byte(PUSH1), 0x00,
byte(DATACOPY),
byte(PUSH1), 20,
byte(PUSH1), 0x00,
byte(RETURN),
},
section: 0,
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 3}},
},
{
code: []byte{
byte(STOP),
byte(STOP),
byte(INVALID),
},
section: 0,
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 0}},
err: errUnreachableCode,
},
{
code: []byte{
byte(RETF),
},
section: 0,
metadata: []*functionMetadata{{inputs: 0, outputs: 1, maxStackHeight: 0}},
err: errInvalidOutputs,
},
{
code: []byte{
byte(RETF),
},
section: 0,
metadata: []*functionMetadata{{inputs: 3, outputs: 3, maxStackHeight: 3}},
},
{
code: []byte{
byte(CALLF), 0x00, 0x01,
byte(POP),
byte(STOP),
},
section: 0,
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}, {inputs: 0, outputs: 1, maxStackHeight: 0}},
},
{
code: []byte{
byte(ORIGIN),
byte(ORIGIN),
byte(CALLF), 0x00, 0x01,
byte(POP),
byte(RETF),
},
section: 0,
metadata: []*functionMetadata{{inputs: 0, outputs: 0, maxStackHeight: 2}, {inputs: 2, outputs: 1, maxStackHeight: 2}},
},
} {
container := &Container{
types: test.metadata,
data: make([]byte, 0),
subContainers: make([]*Container, 0),
}
_, err := validateCode(test.code, test.section, container, &pragueEOFInstructionSet, false)
if !errors.Is(err, test.err) {
t.Errorf("test %d (%s): unexpected error (want: %v, got: %v)", i, common.Bytes2Hex(test.code), test.err, err)
}
}
}
// BenchmarkRJUMPI tries to benchmark the RJUMPI opcode validation
// For this we do a bunch of RJUMPIs that jump backwards (in a potential infinite loop).
func BenchmarkRJUMPI(b *testing.B) {
snippet := []byte{
byte(PUSH0),
byte(RJUMPI), 0xFF, 0xFC,
}
code := []byte{}
for i := 0; i < params.MaxCodeSize/len(snippet)-1; i++ {
code = append(code, snippet...)
}
code = append(code, byte(STOP))
container := &Container{
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
data: make([]byte, 0),
subContainers: make([]*Container, 0),
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := validateCode(code, 0, container, &pragueEOFInstructionSet, false)
if err != nil {
b.Fatal(err)
}
}
}
// BenchmarkRJUMPV tries to benchmark the validation of the RJUMPV opcode
// for this we set up as many RJUMPV opcodes with a full jumptable (containing 0s) as possible.
func BenchmarkRJUMPV(b *testing.B) {
snippet := []byte{
byte(PUSH0),
byte(RJUMPV),
0xff, // count
0x00, 0x00,
}
for i := 0; i < 255; i++ {
snippet = append(snippet, []byte{0x00, 0x00}...)
}
code := []byte{}
for i := 0; i < 24576/len(snippet)-1; i++ {
code = append(code, snippet...)
}
code = append(code, byte(PUSH0))
code = append(code, byte(STOP))
container := &Container{
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
data: make([]byte, 0),
subContainers: make([]*Container, 0),
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := validateCode(code, 0, container, &pragueEOFInstructionSet, false)
if err != nil {
b.Fatal(err)
}
}
}
// BenchmarkEOFValidation tries to benchmark the code validation for the CALLF/RETF operation.
// For this we set up code that calls into 1024 code sections which can either
// - just contain a RETF opcode
// - or code to again call into 1024 code sections.
// We can't have all code sections calling each other, otherwise we would exceed 48KB.
func BenchmarkEOFValidation(b *testing.B) {
var container Container
var code []byte
maxSections := 1024
for i := 0; i < maxSections; i++ {
code = append(code, byte(CALLF))
code = binary.BigEndian.AppendUint16(code, uint16(i%(maxSections-1))+1)
}
// First container
container.codeSections = append(container.codeSections, append(code, byte(STOP)))
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 0})
inner := []byte{
byte(RETF),
}
for i := 0; i < 1023; i++ {
container.codeSections = append(container.codeSections, inner)
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0, maxStackHeight: 0})
}
for i := 0; i < 12; i++ {
container.codeSections[i+1] = append(code, byte(RETF))
}
bin := container.MarshalBinary()
if len(bin) > 48*1024 {
b.Fatal("Exceeds 48Kb")
}
var container2 Container
b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := container2.UnmarshalBinary(bin, true); err != nil {
b.Fatal(err)
}
if err := container2.ValidateCode(&pragueEOFInstructionSet, false); err != nil {
b.Fatal(err)
}
}
}
// BenchmarkEOFValidation tries to benchmark the code validation for the CALLF/RETF operation.
// For this we set up code that calls into 1024 code sections which
// - contain calls to some other code sections.
// We can't have all code sections calling each other, otherwise we would exceed 48KB.
func BenchmarkEOFValidation2(b *testing.B) {
var container Container
var code []byte
maxSections := 1024
for i := 0; i < maxSections; i++ {
code = append(code, byte(CALLF))
code = binary.BigEndian.AppendUint16(code, uint16(i%(maxSections-1))+1)
}
code = append(code, byte(STOP))
// First container
container.codeSections = append(container.codeSections, code)
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 0})
inner := []byte{
byte(CALLF), 0x03, 0xE8,
byte(CALLF), 0x03, 0xE9,
byte(CALLF), 0x03, 0xF0,
byte(CALLF), 0x03, 0xF1,
byte(CALLF), 0x03, 0xF2,
byte(CALLF), 0x03, 0xF3,
byte(CALLF), 0x03, 0xF4,
byte(CALLF), 0x03, 0xF5,
byte(CALLF), 0x03, 0xF6,
byte(CALLF), 0x03, 0xF7,
byte(CALLF), 0x03, 0xF8,
byte(CALLF), 0x03, 0xF,
byte(RETF),
}
for i := 0; i < 1023; i++ {
container.codeSections = append(container.codeSections, inner)
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0, maxStackHeight: 0})
}
bin := container.MarshalBinary()
if len(bin) > 48*1024 {
b.Fatal("Exceeds 48Kb")
}
var container2 Container
b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := container2.UnmarshalBinary(bin, true); err != nil {
b.Fatal(err)
}
if err := container2.ValidateCode(&pragueEOFInstructionSet, false); err != nil {
b.Fatal(err)
}
}
}
// BenchmarkEOFValidation3 tries to benchmark the code validation for the CALLF/RETF and RJUMPI/V operations.
// For this we set up code that calls into 1024 code sections which either
// - contain an RJUMP opcode
// - contain calls to other code sections
// We can't have all code sections calling each other, otherwise we would exceed 48KB.
func BenchmarkEOFValidation3(b *testing.B) {
var container Container
var code []byte
snippet := []byte{
byte(PUSH0),
byte(RJUMPV),
0xff, // count
0x00, 0x00,
}
for i := 0; i < 255; i++ {
snippet = append(snippet, []byte{0x00, 0x00}...)
}
code = append(code, snippet...)
// First container, calls into all other containers
maxSections := 1024
for i := 0; i < maxSections; i++ {
code = append(code, byte(CALLF))
code = binary.BigEndian.AppendUint16(code, uint16(i%(maxSections-1))+1)
}
code = append(code, byte(STOP))
container.codeSections = append(container.codeSections, code)
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 1})
// Other containers
for i := 0; i < 1023; i++ {
container.codeSections = append(container.codeSections, []byte{byte(RJUMP), 0x00, 0x00, byte(RETF)})
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0, maxStackHeight: 0})
}
// Other containers
for i := 0; i < 68; i++ {
container.codeSections[i+1] = append(snippet, byte(RETF))
container.types[i+1] = &functionMetadata{inputs: 0, outputs: 0, maxStackHeight: 1}
}
bin := container.MarshalBinary()
if len(bin) > 48*1024 {
b.Fatal("Exceeds 48Kb")
}
b.ResetTimer()
b.ReportMetric(float64(len(bin)), "bytes")
for i := 0; i < b.N; i++ {
for k := 0; k < 40; k++ {
var container2 Container
if err := container2.UnmarshalBinary(bin, true); err != nil {
b.Fatal(err)
}
if err := container2.ValidateCode(&pragueEOFInstructionSet, false); err != nil {
b.Fatal(err)
}
}
}
}
func BenchmarkRJUMPI_2(b *testing.B) {
code := []byte{
byte(PUSH0),
byte(RJUMPI), 0xFF, 0xFC,
}
for i := 0; i < params.MaxCodeSize/4-1; i++ {
code = append(code, byte(PUSH0))
x := -4 * i
code = append(code, byte(RJUMPI))
code = binary.BigEndian.AppendUint16(code, uint16(x))
}
code = append(code, byte(STOP))
container := &Container{
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
data: make([]byte, 0),
subContainers: make([]*Container, 0),
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := validateCode(code, 0, container, &pragueEOFInstructionSet, false)
if err != nil {
b.Fatal(err)
}
}
}
func FuzzUnmarshalBinary(f *testing.F) {
f.Fuzz(func(_ *testing.T, input []byte) {
var container Container
container.UnmarshalBinary(input, true)
})
}
func FuzzValidate(f *testing.F) {
f.Fuzz(func(_ *testing.T, code []byte, maxStack uint16) {
var container Container
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: maxStack})
validateCode(code, 0, &container, &pragueEOFInstructionSet, true)
})
}

View file

@ -51,10 +51,14 @@ type ErrStackUnderflow struct {
required int required int
} }
func (e *ErrStackUnderflow) Error() string { func (e ErrStackUnderflow) Error() string {
return fmt.Sprintf("stack underflow (%d <=> %d)", e.stackLen, e.required) return fmt.Sprintf("stack underflow (%d <=> %d)", e.stackLen, e.required)
} }
func (e ErrStackUnderflow) Unwrap() error {
return errors.New("stack underflow")
}
// ErrStackOverflow wraps an evm error when the items on the stack exceeds // ErrStackOverflow wraps an evm error when the items on the stack exceeds
// the maximum allowance. // the maximum allowance.
type ErrStackOverflow struct { type ErrStackOverflow struct {
@ -62,10 +66,14 @@ type ErrStackOverflow struct {
limit int limit int
} }
func (e *ErrStackOverflow) Error() string { func (e ErrStackOverflow) Error() string {
return fmt.Sprintf("stack limit reached %d (%d)", e.stackLen, e.limit) return fmt.Sprintf("stack limit reached %d (%d)", e.stackLen, e.limit)
} }
func (e ErrStackOverflow) Unwrap() error {
return errors.New("stack overflow")
}
// ErrInvalidOpCode wraps an evm error when an invalid opcode is encountered. // ErrInvalidOpCode wraps an evm error when an invalid opcode is encountered.
type ErrInvalidOpCode struct { type ErrInvalidOpCode struct {
opcode OpCode opcode OpCode

View file

@ -448,6 +448,18 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
} }
evm.StateDB.SetNonce(caller.Address(), nonce+1) evm.StateDB.SetNonce(caller.Address(), nonce+1)
// Charge the contract creation init gas in verkle mode
if evm.chainRules.IsEIP4762 {
statelessGas := evm.AccessEvents.ContractCreatePreCheckGas(address)
if statelessGas > gas {
return nil, common.Address{}, 0, ErrOutOfGas
}
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
evm.Config.Tracer.OnGasChange(gas, gas-statelessGas, tracing.GasChangeWitnessContractCollisionCheck)
}
gas = gas - statelessGas
}
// We add this to the access list _before_ taking a snapshot. Even if the // We add this to the access list _before_ taking a snapshot. Even if the
// creation fails, the access-list change should not be rolled back. // creation fails, the access-list change should not be rolled back.
if evm.chainRules.IsEIP2929 { if evm.chainRules.IsEIP2929 {
@ -484,6 +496,17 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
if evm.chainRules.IsEIP158 { if evm.chainRules.IsEIP158 {
evm.StateDB.SetNonce(address, 1) evm.StateDB.SetNonce(address, 1)
} }
// Charge the contract creation init gas in verkle mode
if evm.chainRules.IsEIP4762 {
statelessGas := evm.AccessEvents.ContractCreateInitGas(address)
if statelessGas > gas {
return nil, common.Address{}, 0, ErrOutOfGas
}
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
evm.Config.Tracer.OnGasChange(gas, gas-statelessGas, tracing.GasChangeWitnessContractInit)
}
gas = gas - statelessGas
}
evm.Context.Transfer(evm.StateDB, caller.Address(), address, value) evm.Context.Transfer(evm.StateDB, caller.Address(), address, value)
// Initialise a new contract and set the code that is to be used by the EVM. // Initialise a new contract and set the code that is to be used by the EVM.
@ -505,13 +528,6 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
// initNewContract runs a new contract's creation code, performs checks on the // initNewContract runs a new contract's creation code, performs checks on the
// resulting code that is to be deployed, and consumes necessary gas. // resulting code that is to be deployed, and consumes necessary gas.
func (evm *EVM) initNewContract(contract *Contract, address common.Address, value *uint256.Int) ([]byte, error) { func (evm *EVM) initNewContract(contract *Contract, address common.Address, value *uint256.Int) ([]byte, error) {
// Charge the contract creation init gas in verkle mode
if evm.chainRules.IsEIP4762 {
if !contract.UseGas(evm.AccessEvents.ContractCreateInitGas(address, value.Sign() != 0), evm.Config.Tracer, tracing.GasChangeWitnessContractInit) {
return nil, ErrOutOfGas
}
}
ret, err := evm.interpreter.Run(contract, nil, false) ret, err := evm.interpreter.Run(contract, nil, false)
if err != nil { if err != nil {
return ret, err return ret, err
@ -533,11 +549,6 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address, valu
return ret, ErrCodeStoreOutOfGas return ret, ErrCodeStoreOutOfGas
} }
} else { } else {
// Contract creation completed, touch the missing fields in the contract
if !contract.UseGas(evm.AccessEvents.AddAccount(address, true), evm.Config.Tracer, tracing.GasChangeWitnessContractCreation) {
return ret, ErrCodeStoreOutOfGas
}
if len(ret) > 0 && !contract.UseGas(evm.AccessEvents.CodeChunksRangeGas(address, 0, uint64(len(ret)), uint64(len(ret)), true), evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk) { if len(ret) > 0 && !contract.UseGas(evm.AccessEvents.CodeChunksRangeGas(address, 0, uint64(len(ret)), uint64(len(ret)), true), evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk) {
return ret, ErrCodeStoreOutOfGas return ret, ErrCodeStoreOutOfGas
} }

View file

@ -24,6 +24,7 @@ import (
const ( const (
GasQuickStep uint64 = 2 GasQuickStep uint64 = 2
GasFastestStep uint64 = 3 GasFastestStep uint64 = 3
GasFastishStep uint64 = 4
GasFastStep uint64 = 5 GasFastStep uint64 = 5
GasMidStep uint64 = 8 GasMidStep uint64 = 8
GasSlowStep uint64 = 10 GasSlowStep uint64 = 10

View file

@ -502,3 +502,20 @@ func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me
} }
return gas, nil return gas, nil
} }
func gasExtCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
panic("not implemented")
}
func gasExtDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
panic("not implemented")
}
func gasExtStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
panic("not implemented")
}
// gasEOFCreate returns the gas-cost for EOF-Create. Hashing charge needs to be
// deducted in the opcode itself, since it depends on the immediate
func gasEOFCreate(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
panic("not implemented")
}

View file

@ -33,7 +33,8 @@ type Config struct {
NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls) NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls)
EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages
ExtraEips []int // Additional EIPS that are to be enabled ExtraEips []int // Additional EIPS that are to be enabled
EnableWitnessCollection bool // true if witness collection is enabled
StatelessSelfValidation bool // Generate execution witnesses and self-check against them (testing purpose)
} }
// ScopeContext contains the things that are per-call, such as stack and memory, // ScopeContext contains the things that are per-call, such as stack and memory,
@ -83,6 +84,11 @@ func (ctx *ScopeContext) CallInput() []byte {
return ctx.Contract.Input return ctx.Contract.Input
} }
// ContractCode returns the code of the contract being executed.
func (ctx *ScopeContext) ContractCode() []byte {
return ctx.Contract.Code
}
// EVMInterpreter represents an EVM interpreter // EVMInterpreter represents an EVM interpreter
type EVMInterpreter struct { type EVMInterpreter struct {
evm *EVM evm *EVM

View file

@ -42,6 +42,9 @@ type operation struct {
// memorySize returns the memory size required for the operation // memorySize returns the memory size required for the operation
memorySize memorySizeFunc memorySize memorySizeFunc
// undefined denotes if the instruction is not officially defined in the jump table
undefined bool
} }
var ( var (
@ -58,6 +61,7 @@ var (
shanghaiInstructionSet = newShanghaiInstructionSet() shanghaiInstructionSet = newShanghaiInstructionSet()
cancunInstructionSet = newCancunInstructionSet() cancunInstructionSet = newCancunInstructionSet()
verkleInstructionSet = newVerkleInstructionSet() verkleInstructionSet = newVerkleInstructionSet()
pragueEOFInstructionSet = newPragueEOFInstructionSet()
) )
// JumpTable contains the EVM opcodes supported at a given fork. // JumpTable contains the EVM opcodes supported at a given fork.
@ -87,6 +91,16 @@ func newVerkleInstructionSet() JumpTable {
return validate(instructionSet) return validate(instructionSet)
} }
func NewPragueEOFInstructionSetForTesting() JumpTable {
return newPragueEOFInstructionSet()
}
func newPragueEOFInstructionSet() JumpTable {
instructionSet := newCancunInstructionSet()
enableEOF(&instructionSet)
return validate(instructionSet)
}
func newCancunInstructionSet() JumpTable { func newCancunInstructionSet() JumpTable {
instructionSet := newShanghaiInstructionSet() instructionSet := newShanghaiInstructionSet()
enable4844(&instructionSet) // EIP-4844 (BLOBHASH opcode) enable4844(&instructionSet) // EIP-4844 (BLOBHASH opcode)
@ -1059,12 +1073,17 @@ func newFrontierInstructionSet() JumpTable {
minStack: minStack(1, 0), minStack: minStack(1, 0),
maxStack: maxStack(1, 0), maxStack: maxStack(1, 0),
}, },
INVALID: {
execute: opUndefined,
minStack: minStack(0, 0),
maxStack: maxStack(0, 0),
},
} }
// Fill all unassigned slots with opUndefined. // Fill all unassigned slots with opUndefined.
for i, entry := range tbl { for i, entry := range tbl {
if entry == nil { if entry == nil {
tbl[i] = &operation{execute: opUndefined, maxStack: maxStack(0, 0)} tbl[i] = &operation{execute: opUndefined, maxStack: maxStack(0, 0), undefined: true}
} }
} }

Some files were not shown because too many files have changed in this diff Show more