mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-17 10:20:44 +00:00
Merge branch 'ethereum:master' into patch-1
This commit is contained in:
commit
96f5a1244a
299 changed files with 12823 additions and 2133 deletions
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
25
.travis.yml
25
.travis.yml
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 == "" {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
|
|
||||||
|
|
@ -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{})
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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!
|
||||||
#
|
#
|
||||||
|
|
|
||||||
162
build/ci.go
162
build/ci.go
|
|
@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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
200
cmd/evm/eofparse.go
Normal 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
166
cmd/evm/eofparse_test.go
Normal 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++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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())
|
||||||
|
|
|
||||||
134
cmd/evm/internal/t8ntool/gen_execresult.go
Normal file
134
cmd/evm/internal/t8ntool/gen_execresult.go
Normal 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
|
||||||
|
}
|
||||||
133
cmd/evm/main.go
133
cmd/evm/main.go
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
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
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
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
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
2336
cmd/evm/testdata/eof/results.regular.txt
vendored
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
|
|
||||||
|
|
@ -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())
|
||||||
|
|
|
||||||
|
|
@ -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{
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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++ {
|
||||||
|
|
|
||||||
|
|
@ -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 != "" {
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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()) {
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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())
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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: ¶ms.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: ¶ms.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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -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{}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -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{}{}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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
98
core/vm/analysis_eof.go
Normal 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
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
170
core/vm/eips.go
170
core/vm/eips.go
|
|
@ -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
501
core/vm/eof.go
Normal 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
235
core/vm/eof_control_flow.go
Normal 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
70
core/vm/eof_immediates.go
Normal 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
112
core/vm/eof_instructions.go
Normal 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
119
core/vm/eof_test.go
Normal 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
255
core/vm/eof_validation.go
Normal 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, ",")
|
||||||
|
//}
|
||||||
517
core/vm/eof_validation_test.go
Normal file
517
core/vm/eof_validation_test.go
Normal 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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
Loading…
Reference in a new issue