mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-20 13:44:31 +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
|
||||
node/ @fjl
|
||||
p2p/ @fjl @zsfelfoldi
|
||||
params/ @fjl @holiman @karalabe @gballet @rjl493456442 @zsfelfoldi
|
||||
rpc/ @fjl @holiman
|
||||
signer/ @holiman
|
||||
|
|
|
|||
|
|
@ -21,10 +21,14 @@ linters:
|
|||
- staticcheck
|
||||
- bidichk
|
||||
- durationcheck
|
||||
- exportloopref
|
||||
- copyloopvar
|
||||
- whitespace
|
||||
- revive # only certain checks enabled
|
||||
|
||||
- durationcheck
|
||||
- gocheckcompilerdirectives
|
||||
- reassign
|
||||
- mirror
|
||||
- tenv
|
||||
### linters we tried and will not be using:
|
||||
###
|
||||
# - structcheck # lots of false positives
|
||||
|
|
|
|||
25
.travis.yml
25
.travis.yml
|
|
@ -9,8 +9,7 @@ jobs:
|
|||
- azure-osx
|
||||
|
||||
include:
|
||||
# These builders create the Docker sub-images for multi-arch push and each
|
||||
# will attempt to push the multi-arch image if they are the last builder
|
||||
# This builder create and push the Docker images for all architectures
|
||||
- stage: build
|
||||
if: type = push
|
||||
os: linux
|
||||
|
|
@ -26,24 +25,7 @@ jobs:
|
|||
before_install:
|
||||
- export DOCKER_CLI_EXPERIMENTAL=enabled
|
||||
script:
|
||||
- go run build/ci.go docker -image -manifest amd64,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
|
||||
- go run build/ci.go dockerx -platform "linux/amd64,linux/arm64" -upload ethereum/client-go
|
||||
|
||||
# This builder does the Linux Azure uploads
|
||||
- stage: build
|
||||
|
|
@ -85,12 +67,13 @@ jobs:
|
|||
if: type = push
|
||||
os: osx
|
||||
osx_image: xcode14.2
|
||||
go: 1.23.x
|
||||
go: 1.23.1 # See https://github.com/ethereum/go-ethereum/pull/30478
|
||||
env:
|
||||
- azure-osx
|
||||
git:
|
||||
submodules: false # avoid cloning ethereum/tests
|
||||
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 archive -type tar -signer OSX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds
|
||||
- go run build/ci.go install -dlgo -arch arm64
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ ARG VERSION=""
|
|||
ARG BUILDNUM=""
|
||||
|
||||
# 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
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ ARG VERSION=""
|
|||
ARG BUILDNUM=""
|
||||
|
||||
# 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
|
||||
|
||||
|
|
@ -14,6 +14,13 @@ COPY go.sum /go-ethereum/
|
|||
RUN cd /go-ethereum && go mod download
|
||||
|
||||
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
|
||||
|
||||
# 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},
|
||||
}
|
||||
for index, c := range cases {
|
||||
index, c := index, c
|
||||
t.Run(fmt.Sprintf("case %d", index), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
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.
|
||||
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 {
|
||||
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 {
|
||||
assert := assert.New(t)
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := unpackTestEventData(tc.dest, tc.data, tc.jsonLog, assert)
|
||||
if tc.error == "" {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ import (
|
|||
func TestPack(t *testing.T) {
|
||||
t.Parallel()
|
||||
for i, test := range packUnpackTests {
|
||||
i, test := i, test
|
||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
encb, err := hex.DecodeString(test.packed)
|
||||
|
|
|
|||
|
|
@ -172,7 +172,6 @@ var reflectTests = []reflectTest{
|
|||
func TestReflectNameToStruct(t *testing.T) {
|
||||
t.Parallel()
|
||||
for _, test := range reflectTests {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
m, err := mapArgNamesToStructFields(test.args, reflect.ValueOf(test.struc))
|
||||
|
|
|
|||
|
|
@ -137,7 +137,6 @@ func TestMakeTopics(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
got, err := MakeTopics(tt.args.query...)
|
||||
|
|
@ -373,7 +372,6 @@ func TestParseTopics(t *testing.T) {
|
|||
tests := setupTopicsTests()
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
createObj := tt.args.createObj()
|
||||
|
|
@ -393,7 +391,6 @@ func TestParseTopicsIntoMap(t *testing.T) {
|
|||
tests := setupTopicsTests()
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
outMap := make(map[string]interface{})
|
||||
|
|
|
|||
|
|
@ -389,7 +389,6 @@ func TestMethodMultiReturn(t *testing.T) {
|
|||
"Can not unpack into a slice with wrong types",
|
||||
}}
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
err := abi.UnpackIntoInterface(tc.dest, "multi", data)
|
||||
|
|
@ -947,7 +946,7 @@ func TestOOMMaliciousInput(t *testing.T) {
|
|||
}
|
||||
encb, err := hex.DecodeString(test.enc)
|
||||
if err != nil {
|
||||
t.Fatalf("invalid hex: %s" + test.enc)
|
||||
t.Fatalf("invalid hex: %s", test.enc)
|
||||
}
|
||||
_, err = abi.Methods["method"].Outputs.UnpackValues(encb)
|
||||
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) {
|
||||
execData := engine.BlockToExecutableData(event.Block, nil, nil).ExecutionPayload
|
||||
execData := engine.BlockToExecutableData(event.Block, nil, nil, nil).ExecutionPayload
|
||||
|
||||
var (
|
||||
method string
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) {
|
|||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
|
||||
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
|
||||
Deposits types.Deposits `json:"depositRequests"`
|
||||
ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"`
|
||||
}
|
||||
var enc ExecutableData
|
||||
|
|
@ -60,7 +59,6 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) {
|
|||
enc.Withdrawals = e.Withdrawals
|
||||
enc.BlobGasUsed = (*hexutil.Uint64)(e.BlobGasUsed)
|
||||
enc.ExcessBlobGas = (*hexutil.Uint64)(e.ExcessBlobGas)
|
||||
enc.Deposits = e.Deposits
|
||||
enc.ExecutionWitness = e.ExecutionWitness
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
|
@ -85,7 +83,6 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error {
|
|||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
|
||||
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
|
||||
Deposits *types.Deposits `json:"depositRequests"`
|
||||
ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"`
|
||||
}
|
||||
var dec ExecutableData
|
||||
|
|
@ -160,9 +157,6 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error {
|
|||
if dec.ExcessBlobGas != nil {
|
||||
e.ExcessBlobGas = (*uint64)(dec.ExcessBlobGas)
|
||||
}
|
||||
if dec.Deposits != nil {
|
||||
e.Deposits = *dec.Deposits
|
||||
}
|
||||
if dec.ExecutionWitness != nil {
|
||||
e.ExecutionWitness = dec.ExecutionWitness
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,13 +18,22 @@ func (e ExecutionPayloadEnvelope) MarshalJSON() ([]byte, error) {
|
|||
ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"`
|
||||
BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"`
|
||||
BlobsBundle *BlobsBundleV1 `json:"blobsBundle"`
|
||||
Requests []hexutil.Bytes `json:"executionRequests"`
|
||||
Override bool `json:"shouldOverrideBuilder"`
|
||||
Witness *hexutil.Bytes `json:"witness"`
|
||||
}
|
||||
var enc ExecutionPayloadEnvelope
|
||||
enc.ExecutionPayload = e.ExecutionPayload
|
||||
enc.BlockValue = (*hexutil.Big)(e.BlockValue)
|
||||
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.Witness = e.Witness
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
||||
|
|
@ -34,7 +43,9 @@ func (e *ExecutionPayloadEnvelope) UnmarshalJSON(input []byte) error {
|
|||
ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"`
|
||||
BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"`
|
||||
BlobsBundle *BlobsBundleV1 `json:"blobsBundle"`
|
||||
Requests []hexutil.Bytes `json:"executionRequests"`
|
||||
Override *bool `json:"shouldOverrideBuilder"`
|
||||
Witness *hexutil.Bytes `json:"witness"`
|
||||
}
|
||||
var dec ExecutionPayloadEnvelope
|
||||
if err := json.Unmarshal(input, &dec); err != nil {
|
||||
|
|
@ -51,8 +62,17 @@ func (e *ExecutionPayloadEnvelope) UnmarshalJSON(input []byte) error {
|
|||
if dec.BlobsBundle != nil {
|
||||
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 {
|
||||
e.Override = *dec.Override
|
||||
}
|
||||
if dec.Witness != nil {
|
||||
e.Witness = dec.Witness
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@ var (
|
|||
PayloadV1 PayloadVersion = 0x1
|
||||
PayloadV2 PayloadVersion = 0x2
|
||||
PayloadV3 PayloadVersion = 0x3
|
||||
PayloadV4 PayloadVersion = 0x4
|
||||
)
|
||||
|
||||
//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"`
|
||||
BlobGasUsed *uint64 `json:"blobGasUsed"`
|
||||
ExcessBlobGas *uint64 `json:"excessBlobGas"`
|
||||
Deposits types.Deposits `json:"depositRequests"`
|
||||
ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"`
|
||||
}
|
||||
|
||||
|
|
@ -95,13 +93,23 @@ type executableDataMarshaling struct {
|
|||
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
|
||||
|
||||
type ExecutionPayloadEnvelope struct {
|
||||
ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"`
|
||||
BlockValue *big.Int `json:"blockValue" gencodec:"required"`
|
||||
BlobsBundle *BlobsBundleV1 `json:"blobsBundle"`
|
||||
Requests [][]byte `json:"executionRequests"`
|
||||
Override bool `json:"shouldOverrideBuilder"`
|
||||
Witness *hexutil.Bytes `json:"witness"`
|
||||
}
|
||||
|
||||
type BlobsBundleV1 struct {
|
||||
|
|
@ -113,12 +121,14 @@ type BlobsBundleV1 struct {
|
|||
// JSON type overrides for ExecutionPayloadEnvelope.
|
||||
type executionPayloadEnvelopeMarshaling struct {
|
||||
BlockValue *hexutil.Big
|
||||
Requests []hexutil.Bytes
|
||||
}
|
||||
|
||||
type PayloadStatusV1 struct {
|
||||
Status string `json:"status"`
|
||||
LatestValidHash *common.Hash `json:"latestValidHash"`
|
||||
ValidationError *string `json:"validationError"`
|
||||
Status string `json:"status"`
|
||||
Witness *hexutil.Bytes `json:"witness"`
|
||||
LatestValidHash *common.Hash `json:"latestValidHash"`
|
||||
ValidationError *string `json:"validationError"`
|
||||
}
|
||||
|
||||
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
|
||||
// Withdrawals value will propagate through the returned block. Empty
|
||||
// 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)
|
||||
if err != nil {
|
||||
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))
|
||||
withdrawalsRoot = &h
|
||||
}
|
||||
// Compute requestsHash if any requests are non-nil.
|
||||
var (
|
||||
requestsHash *common.Hash
|
||||
requests types.Requests
|
||||
)
|
||||
if data.Deposits != nil {
|
||||
requests = make(types.Requests, 0)
|
||||
for _, d := range data.Deposits {
|
||||
requests = append(requests, types.NewRequest(d))
|
||||
|
||||
var requestsHash *common.Hash
|
||||
if requests != nil {
|
||||
// Put back request type byte.
|
||||
typedRequests := make([][]byte, len(requests))
|
||||
for i, reqdata := range requests {
|
||||
typedReqdata := make([]byte, len(reqdata)+1)
|
||||
typedReqdata[0] = byte(i)
|
||||
copy(typedReqdata[1:], reqdata)
|
||||
typedRequests[i] = typedReqdata
|
||||
}
|
||||
h := types.DeriveSha(requests, trie.NewStackTrie(nil))
|
||||
h := types.CalcRequestsHash(typedRequests)
|
||||
requestsHash = &h
|
||||
}
|
||||
|
||||
header := &types.Header{
|
||||
ParentHash: data.ParentHash,
|
||||
UncleHash: types.EmptyUncleHash,
|
||||
|
|
@ -268,18 +294,15 @@ func ExecutableDataToBlock(data ExecutableData, versionedHashes []common.Hash, b
|
|||
ParentBeaconRoot: beaconRoot,
|
||||
RequestsHash: requestsHash,
|
||||
}
|
||||
block := types.NewBlockWithHeader(header)
|
||||
block = block.WithBody(types.Body{Transactions: txs, Uncles: nil, Withdrawals: data.Withdrawals, Requests: requests})
|
||||
block = block.WithWitness(data.ExecutionWitness)
|
||||
if block.Hash() != data.BlockHash {
|
||||
return nil, fmt.Errorf("blockhash mismatch, want %x, got %x", data.BlockHash, block.Hash())
|
||||
}
|
||||
return block, nil
|
||||
return types.NewBlockWithHeader(header).
|
||||
WithBody(types.Body{Transactions: txs, Uncles: nil, Withdrawals: data.Withdrawals}).
|
||||
WithWitness(data.ExecutionWitness),
|
||||
nil
|
||||
}
|
||||
|
||||
// BlockToExecutableData constructs the ExecutableData structure by filling the
|
||||
// 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{
|
||||
BlockHash: block.Hash(),
|
||||
ParentHash: block.ParentHash(),
|
||||
|
|
@ -300,6 +323,8 @@ func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types.
|
|||
ExcessBlobGas: block.ExcessBlobGas(),
|
||||
ExecutionWitness: block.ExecutionWitness(),
|
||||
}
|
||||
|
||||
// Add blobs.
|
||||
bundle := BlobsBundleV1{
|
||||
Commitments: 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][:]))
|
||||
}
|
||||
}
|
||||
setRequests(block.Requests(), data)
|
||||
return &ExecutionPayloadEnvelope{ExecutionPayload: data, BlockValue: fees, BlobsBundle: &bundle, Override: false}
|
||||
}
|
||||
|
||||
// setRequests differentiates the different request types and
|
||||
// assigns them to the associated fields in ExecutableData.
|
||||
func setRequests(requests types.Requests, data *ExecutableData) {
|
||||
// Remove type byte in requests.
|
||||
var plainRequests [][]byte
|
||||
if requests != nil {
|
||||
// If requests is non-nil, it means deposits are available in block and we
|
||||
// should return an empty slice instead of nil if there are no deposits.
|
||||
data.Deposits = make(types.Deposits, 0)
|
||||
}
|
||||
for _, r := range requests {
|
||||
if d, ok := r.Inner().(*types.Deposit); ok {
|
||||
data.Deposits = append(data.Deposits, d)
|
||||
plainRequests = make([][]byte, len(requests))
|
||||
for i, reqdata := range requests {
|
||||
plainRequests[i] = reqdata[1:]
|
||||
}
|
||||
}
|
||||
|
||||
return &ExecutionPayloadEnvelope{
|
||||
ExecutionPayload: data,
|
||||
BlockValue: fees,
|
||||
BlobsBundle: &bundle,
|
||||
Requests: plainRequests,
|
||||
Override: false,
|
||||
}
|
||||
}
|
||||
|
||||
// ExecutionPayloadBody is used in the response to GetPayloadBodiesByHash and GetPayloadBodiesByRange
|
||||
type ExecutionPayloadBody struct {
|
||||
TransactionData []hexutil.Bytes `json:"transactions"`
|
||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||
Deposits types.Deposits `json:"depositRequests"`
|
||||
}
|
||||
|
||||
// Client identifiers to support ClientVersionV1.
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import (
|
|||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
|
@ -121,8 +122,8 @@ func NewBeaconLightApi(url string, customHeaders map[string]string) *BeaconLight
|
|||
}
|
||||
}
|
||||
|
||||
func (api *BeaconLightApi) httpGet(path string) ([]byte, error) {
|
||||
uri, err := api.buildURL(path, nil)
|
||||
func (api *BeaconLightApi) httpGet(path string, params url.Values) ([]byte, error) {
|
||||
uri, err := api.buildURL(path, params)
|
||||
if err != nil {
|
||||
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
|
||||
// period and full serialized committee for the next period (committee root hash
|
||||
// equals update.NextSyncCommitteeRoot).
|
||||
// Note that the results are validated but the update signature should be verified
|
||||
// by the caller as its validity depends on the update chain.
|
||||
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 {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
|
@ -197,7 +197,7 @@ func (api *BeaconLightApi) GetBestUpdatesAndCommittees(firstPeriod, count uint64
|
|||
// See data structure definition here:
|
||||
// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientoptimisticupdate
|
||||
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 {
|
||||
return types.OptimisticUpdate{}, err
|
||||
}
|
||||
|
|
@ -250,7 +250,7 @@ func decodeOptimisticUpdate(enc []byte) (types.OptimisticUpdate, error) {
|
|||
// See data structure definition here:
|
||||
// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientfinalityupdate
|
||||
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 {
|
||||
return types.FinalityUpdate{}, err
|
||||
}
|
||||
|
|
@ -316,7 +316,7 @@ func (api *BeaconLightApi) GetHeader(blockRoot common.Hash) (types.Header, bool,
|
|||
} else {
|
||||
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 {
|
||||
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.
|
||||
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 {
|
||||
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) {
|
||||
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 {
|
||||
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.
|
||||
// The boolean return flag signals if ValidatedOptimistic has been changed.
|
||||
func (h *HeadTracker) ValidateOptimistic(update types.OptimisticUpdate) (bool, error) {
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
|
||||
if err := update.Validate(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
|
||||
replace, err := h.validate(update.SignedHeader(), h.optimisticUpdate.SignedHeader())
|
||||
if replace {
|
||||
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.
|
||||
// The boolean return flag signals if ValidatedFinality has been changed.
|
||||
func (h *HeadTracker) ValidateFinality(update types.FinalityUpdate) (bool, error) {
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
|
||||
if err := update.Validate(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
|
||||
replace, err := h.validate(update.SignedHeader(), h.finalityUpdate.SignedHeader())
|
||||
if replace {
|
||||
h.finalityUpdate, h.hasFinalityUpdate = update, true
|
||||
|
|
|
|||
|
|
@ -5,88 +5,88 @@
|
|||
# https://github.com/ethereum/execution-spec-tests/releases/download/v2.1.0/
|
||||
ca89c76851b0900bfcc3cbb9a26cbece1f3d7c64a3bed38723e914713290df6c fixtures_develop.tar.gz
|
||||
|
||||
# version:golang 1.23.1
|
||||
# version:golang 1.23.2
|
||||
# https://go.dev/dl/
|
||||
6ee44e298379d146a5e5aa6b1c5b5d5f5d0a3365eabdd70741e6e21340ec3b0d go1.23.1.src.tar.gz
|
||||
f17f2791717c15728ec63213a014e244c35f9c8846fb29f5a1b63d0c0556f756 go1.23.1.aix-ppc64.tar.gz
|
||||
dd9e772686ed908bcff94b6144322d4e2473a7dcd7c696b7e8b6d12f23c887fd go1.23.1.darwin-amd64.pkg
|
||||
488d9e4ca3e3ed513ee4edd91bef3a2360c65fa6d6be59cf79640bf840130a58 go1.23.1.darwin-amd64.tar.gz
|
||||
be34b488157ec69d94e26e1554558219a2c90789bcb7e3686965a7f9c8cfcbe7 go1.23.1.darwin-arm64.pkg
|
||||
e223795ca340e285a760a6446ce57a74500b30e57469a4109961d36184d3c05a go1.23.1.darwin-arm64.tar.gz
|
||||
6af626176923a6ae6c5de6dc1c864f38365793c0e4ecd0d6eab847bdc23953e5 go1.23.1.dragonfly-amd64.tar.gz
|
||||
cc957c1a019702e6cdc2e257202d42799011ebc1968b6c3bcd6b1965952607d5 go1.23.1.freebsd-386.tar.gz
|
||||
a7d57781c50bb80886a8f04066791956d45aa3eea0f83070c5268b6223afb2ff go1.23.1.freebsd-amd64.tar.gz
|
||||
c7b09f3fef456048e596db9bea746eb66796aeb82885622b0388feee18f36a3e go1.23.1.freebsd-arm.tar.gz
|
||||
b05cd6a77995a0c8439d88df124811c725fb78b942d0b6dd1643529d7ba62f1f go1.23.1.freebsd-arm64.tar.gz
|
||||
56236ae70be1613f2915943b94f53c96be5bffc0719314078facd778a89bc57e go1.23.1.freebsd-riscv64.tar.gz
|
||||
8644c52df4e831202114fd67c9fcaf1f7233ad27bf945ac53fa7217cf1a0349f go1.23.1.illumos-amd64.tar.gz
|
||||
cdee2f4e2efa001f7ee75c90f2efc310b63346cfbba7b549987e9139527c6b17 go1.23.1.linux-386.tar.gz
|
||||
49bbb517cfa9eee677e1e7897f7cf9cfdbcf49e05f61984a2789136de359f9bd go1.23.1.linux-amd64.tar.gz
|
||||
faec7f7f8ae53fda0f3d408f52182d942cc89ef5b7d3d9f23ff117437d4b2d2f go1.23.1.linux-arm64.tar.gz
|
||||
6c7832c7dcd8fb6d4eb308f672a725393403c74ee7be1aeccd8a443015df99de go1.23.1.linux-armv6l.tar.gz
|
||||
649ce3856ddc808c00b14a46232eab0bf95e7911cdf497010b17d76656f5ca4e go1.23.1.linux-loong64.tar.gz
|
||||
201911048f234e5a0c51ec94b1a11d4e47062fee4398b1d2faa6c820dc026724 go1.23.1.linux-mips.tar.gz
|
||||
2bce3743df463915e45d2612f9476ffb03d0b3750b1cb3879347de08715b5fc6 go1.23.1.linux-mips64.tar.gz
|
||||
54e301f266e33431b0703136e0bbd4cf02461b1ecedd37b7cbd90cb862a98e5f go1.23.1.linux-mips64le.tar.gz
|
||||
8efd495e93d17408c0803595cdc3bf13cb28e0f957aeabd9cc18245fb8e64019 go1.23.1.linux-mipsle.tar.gz
|
||||
52bd68689095831ad9af7160844c23b28bb8d0acd268de7e300ff5f0662b7a07 go1.23.1.linux-ppc64.tar.gz
|
||||
042888cae54b5fbfd9dd1e3b6bc4a5134879777fe6497fc4c62ec394b5ecf2da go1.23.1.linux-ppc64le.tar.gz
|
||||
1a4a609f0391bea202d9095453cbfaf7368fa88a04c206bf9dd715a738664dc3 go1.23.1.linux-riscv64.tar.gz
|
||||
47dc49ad45c45e192efa0df7dc7bc5403f5f2d15b5d0dc74ef3018154b616f4d go1.23.1.linux-s390x.tar.gz
|
||||
fbfbd5efa6a5d581ea7f5e65015f927db0e52135cab057e43d39d5482da54b61 go1.23.1.netbsd-386.tar.gz
|
||||
e96e1cc5cf36113ee6099d1a7306b22cd9c3f975a36bdff954c59f104f22b853 go1.23.1.netbsd-amd64.tar.gz
|
||||
c394dfc06bfc276a591209a37e09cd39089ec9a9cc3db30b94814ce2e39eb1d4 go1.23.1.netbsd-arm.tar.gz
|
||||
b3b35d64f32821a68b3e2994032dbefb81978f2ec3f218c7a770623b82d36b8e go1.23.1.netbsd-arm64.tar.gz
|
||||
3c775c4c16c182e33c2c4ac090d9a247a93b3fb18a3df01d87d490f29599faff go1.23.1.openbsd-386.tar.gz
|
||||
5edbe53b47c57b32707fd7154536fbe9eaa79053fea01650c93b54cdba13fc0f go1.23.1.openbsd-amd64.tar.gz
|
||||
c30903dd8fa98b8aca8e9db0962ce9f55502aed93e0ef41e5ae148aaa0088de1 go1.23.1.openbsd-arm.tar.gz
|
||||
12da183489e58f9c6b357bc1b626f85ed7d4220cab31a49d6a49e6ac6a718b67 go1.23.1.openbsd-arm64.tar.gz
|
||||
9cc9aad37696a4a10c31dcec9e35a308de0b369dad354d54cf07406ac6fa7c6f go1.23.1.openbsd-ppc64.tar.gz
|
||||
e1d740dda062ce5a276a0c3ed7d8b6353238bc8ff405f63e2e3480bfd26a5ec5 go1.23.1.openbsd-riscv64.tar.gz
|
||||
da2a37f9987f01f096859230aa13ecc4ad2e7884465bce91004bc78c64435d65 go1.23.1.plan9-386.tar.gz
|
||||
fd8fff8b0697d55c4a4d02a8dc998192b80a9dc2a057647373d6ff607cad29de go1.23.1.plan9-amd64.tar.gz
|
||||
52efbc5804c1c86ba7868aa8ebbc31cc8c2a27b62a60fd57944970d48fc67525 go1.23.1.plan9-arm.tar.gz
|
||||
f54205f21e2143f2ada1bf1c00ddf64590f5139d5c3fb77cc06175f0d8cc7567 go1.23.1.solaris-amd64.tar.gz
|
||||
369a17f0cfd29e5c848e58ffe0d772da20abe334d1c7ca01dbcd55bb3db0b440 go1.23.1.windows-386.msi
|
||||
ab866f47d7be56e6b1c67f1d529bf4c23331a339fb0785f435a0552d352cb257 go1.23.1.windows-386.zip
|
||||
e99dac215ee437b9bb8f8b14bbfe0e8756882c1ed291f30818e8363bc9c047a5 go1.23.1.windows-amd64.msi
|
||||
32dedf277c86610e380e1765593edb66876f00223df71690bd6be68ee17675c0 go1.23.1.windows-amd64.zip
|
||||
23169c79dc6b54e0dffb25be6b67425ad9759392a58309bc057430a9bf4c8f6a go1.23.1.windows-arm.msi
|
||||
1a57615a09f13534f88e9f2d7efd5743535d1a5719b19e520eef965a634f8efb go1.23.1.windows-arm.zip
|
||||
313e1a543931ad8735b4df8969e00f5f4c2ef07be21f54015ede961a70263d35 go1.23.1.windows-arm64.msi
|
||||
64ad0954d2c33f556fb1018d62de091254aa6e3a94f1c8a8b16af0d3701d194e go1.23.1.windows-arm64.zip
|
||||
36930162a93df417d90bd22c6e14daff4705baac2b02418edda671cdfa9cd07f go1.23.2.src.tar.gz
|
||||
025d77f1780906142023a364c31a572afd7d56d3a3be1e4e562e367ca88d3267 go1.23.2.freebsd-amd64.tar.gz
|
||||
0d50bade977b84e173cb350946087f5de8c75f8df19456c3b60c5d58e186089d go1.23.2.windows-arm64.zip
|
||||
0edd985dbd6de64d9c88dbc8835bae21203c58444bf26fce0739cbec4eb1b610 go1.23.2.windows-arm64.msi
|
||||
2283d12dfe7c8c8a46a41bbf7d11fe007434e7590cd1b89e221e478640b7ee3a go1.23.2.linux-mips64le.tar.gz
|
||||
2293c5c3ffc595418308b4059ce214b99f0383cba83232e47a1a8c3b710c24e8 go1.23.2.linux-loong64.tar.gz
|
||||
23b93144e754bbcf5eda700e9decbdbd44d29ceedb1bf1de75f95e8a6ea986bb go1.23.2.openbsd-arm64.tar.gz
|
||||
2734a5b54905cea45f136c28249e626d0241b865b0637fa1db64bf533d9d843e go1.23.2.netbsd-amd64.tar.gz
|
||||
28af3c40687afdda6b33b300833b6d662716cc2d624fb9fd61a49bdad44cd869 go1.23.2.freebsd-arm.tar.gz
|
||||
367d522b47c7ce7761a671efcb8b12c8af8f509db1cd6160c91f410ef3201987 go1.23.2.windows-arm.msi
|
||||
36b7228bae235eee6c8193f5a956e1a9a17874955affb86b3564709b0fab5874 go1.23.2.linux-mipsle.tar.gz
|
||||
3bd1130a08195d23960b154d2e6eaa80ac7325ebd9d01d74c58b6d12580e6b12 go1.23.2.linux-mips.tar.gz
|
||||
3bf66879b38a233c5cbb5d2eb982004117f05d6bf06279e886e087d7c504427d go1.23.2.openbsd-riscv64.tar.gz
|
||||
3e80b943d70c7e1633822b42c1aa7234e61da14f13ff8efff7ee6e1347f37648 go1.23.2.netbsd-arm64.tar.gz
|
||||
40c0b61971a1a74fd4566c536f682c9d4976fa71d40d9daabc875c06113d0fee go1.23.2.darwin-amd64.pkg
|
||||
445c0ef19d8692283f4c3a92052cc0568f5a048f4e546105f58e991d4aea54f5 go1.23.2.darwin-amd64.tar.gz
|
||||
542d3c1705f1c6a1c5a80d5dc62e2e45171af291e755d591c5e6531ef63b454e go1.23.2.linux-amd64.tar.gz
|
||||
560aff7fe1eeadc32248db35ed5c0a81e190d171b6ecec404cf46d808c13e92f go1.23.2.aix-ppc64.tar.gz
|
||||
5611cd648f5100b73a7d6fd85589a481af18fdbaf9c153a92de9a8e39a6e061f go1.23.2.darwin-arm64.pkg
|
||||
695aac64532da8d9a243601ffa0411cd763be891fcf7fd2e857eea4ab10b8bcc go1.23.2.plan9-386.tar.gz
|
||||
69b31edcd3d4f7d8bbf9aee2b25cafba30b444ef19bc7a033e15026f7d0cc5c2 go1.23.2.netbsd-arm.tar.gz
|
||||
6ffa4ac1f4368a3121a032917577a4e0a3feaf696c3e98f213b74ac04c318bc4 go1.23.2.plan9-arm.tar.gz
|
||||
72a6def70300cc804c70073d8b579603d9b39b39b02b3b5d340968d9e7e0e9d4 go1.23.2.windows-386.msi
|
||||
791ca685ee5ca0f6fe849dc078145cb1323d0ea9dd308e9cca9ba2e7186dbb3d go1.23.2.linux-ppc64.tar.gz
|
||||
86b5de91fdf7bd9b52c77c62f8762518cf3fc256fe912af9bbff1d073054aa5b go1.23.2.plan9-amd64.tar.gz
|
||||
8734c7cd464a0620f6605bd3f9256bed062f262d0d58e4f45099c329a08ed966 go1.23.2.openbsd-amd64.tar.gz
|
||||
980ceb889915695d94b166ca1300250dba76fa37a2d41eca2c5e7727dcb4fb7f go1.23.2.openbsd-arm.tar.gz
|
||||
a0cf25f236a0fa0a465816fe7f5c930f3b0b90c5c247b09c43a6adeff654e6ae go1.23.2.linux-mips64.tar.gz
|
||||
a13cc0d621af4f35afd90b886c60b1bf66f771939d226dc36fa61a337d90eb30 go1.23.2.openbsd-ppc64.tar.gz
|
||||
b29ff163b34cb4943c521fcfc1d956eaa6286561089042051a3fab22e79e9283 go1.23.2.windows-arm.zip
|
||||
bc28fe3002cd65cec65d0e4f6000584dacb8c71bfaff8801dfb532855ca42513 go1.23.2.windows-amd64.zip
|
||||
c164ce7d894b10fd861d7d7b96f1dbea3f993663d9f0c30bc4f8ae3915db8b0c go1.23.2.linux-ppc64le.tar.gz
|
||||
c4ae1087dce4daf45a837f5fca36ac0e29a02ada9addf857f1c426e60bce6f21 go1.23.2.netbsd-386.tar.gz
|
||||
c80cbc5e66d6fb8b0c3300b0dda1fe925c429e199954d3327da2933d9870b041 go1.23.2.windows-amd64.msi
|
||||
cb1ed4410f68d8be1156cee0a74fcfbdcd9bca377c83db3a9e1b07eebc6d71ef go1.23.2.linux-386.tar.gz
|
||||
d1fde255843fec1f7f0611d468effd98e1f4309f589ac13037db07b032f9da35 go1.23.2.openbsd-386.tar.gz
|
||||
d47e40366cd6c6b6ee14b811554cd7dde0351309f4a8a4569ec5ba2bd7689437 go1.23.2.illumos-amd64.tar.gz
|
||||
d87031194fe3e01abdcaf3c7302148ade97a7add6eac3fec26765bcb3207b80f go1.23.2.darwin-arm64.tar.gz
|
||||
de1f94d7dd3548ba3036de1ea97eb8243881c22a88fcc04cc08c704ded769e02 go1.23.2.linux-s390x.tar.gz
|
||||
e3286bdde186077e65e961cbe18874d42a461e5b9c472c26572b8d4a98d15c40 go1.23.2.linux-armv6l.tar.gz
|
||||
e4d9a1319dfdaa827407855e406c43e85c878a1f93f4f3984c85dce969c8bf70 go1.23.2.freebsd-386.tar.gz
|
||||
ea8ab49c5c04c9f94a3f4894d1b030fbce8d10413905fa399f6c39c0a44d5556 go1.23.2.linux-riscv64.tar.gz
|
||||
eaa3bc377badbdcae144633f8b29bf2680475b72dcd4c135343d3bdc0ba7671e go1.23.2.windows-386.zip
|
||||
f11b9b4d4a0679909202fc5e88093d6ff720a8a417bfe6a34d502c3862367039 go1.23.2.freebsd-riscv64.tar.gz
|
||||
f163b99b03e4bbc64cd30363f1694a08fcd44094415db1f092f13f9d1bb7c28e go1.23.2.dragonfly-amd64.tar.gz
|
||||
f45af3e1434175ff85620a74c07fb41d6844655f1f2cd2389c5fca6de000f58c go1.23.2.freebsd-arm64.tar.gz
|
||||
f626cdd92fc21a88b31c1251f419c17782933a42903db87a174ce74eeecc66a9 go1.23.2.linux-arm64.tar.gz
|
||||
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/download/v1.59.0/
|
||||
418acf7e255ddc0783e97129c9b03d9311b77826a5311d425a01c708a86417e7 golangci-lint-1.59.0-darwin-amd64.tar.gz
|
||||
5f6a1d95a6dd69f6e328eb56dd311a38e04cfab79a1305fbf4957f4e203f47b6 golangci-lint-1.59.0-darwin-arm64.tar.gz
|
||||
8899bf589185d49f747f3e5db9f0bde8a47245a100c64a3dd4d65e8e92cfc4f2 golangci-lint-1.59.0-freebsd-386.tar.gz
|
||||
658212f138d9df2ac89427e22115af34bf387c0871d70f2a25101718946a014f golangci-lint-1.59.0-freebsd-amd64.tar.gz
|
||||
4c6395ea40f314d3b6fa17d8997baab93464d5d1deeaab513155e625473bd03a golangci-lint-1.59.0-freebsd-armv6.tar.gz
|
||||
ff37da4fbaacdb6bbae70fdbdbb1ba932a859956f788c82822fa06bef5b7c6b3 golangci-lint-1.59.0-freebsd-armv7.tar.gz
|
||||
439739469ed2bda182b1ec276d40c40e02f195537f78e3672996741ad223d6b6 golangci-lint-1.59.0-illumos-amd64.tar.gz
|
||||
940801d46790e40d0a097d8fee34e2606f0ef148cd039654029b0b8750a15ed6 golangci-lint-1.59.0-linux-386.tar.gz
|
||||
3b14a439f33c4fff83dbe0349950d984042b9a1feb6c62f82787b598fc3ab5f4 golangci-lint-1.59.0-linux-amd64.tar.gz
|
||||
c57e6c0b0fa03089a2611dceddd5bc5d206716cccdff8b149da8baac598719a1 golangci-lint-1.59.0-linux-arm64.tar.gz
|
||||
93149e2d3b25ac754df9a23172403d8aa6d021a7e0d9c090a12f51897f68c9a0 golangci-lint-1.59.0-linux-armv6.tar.gz
|
||||
d10ac38239d9efee3ee87b55c96cdf3fa09e1a525babe3ffdaaf65ccc48cf3dc golangci-lint-1.59.0-linux-armv7.tar.gz
|
||||
047338114b4f0d5f08f0fb9a397b03cc171916ed0960be7dfb355c2320cd5e9c golangci-lint-1.59.0-linux-loong64.tar.gz
|
||||
5632df0f7f8fc03a80a266130faef0b5902d280cf60621f1b2bdc1aef6d97ee9 golangci-lint-1.59.0-linux-mips64.tar.gz
|
||||
71dd638c82fa4439171e7126d2c7a32b5d103bfdef282cea40c83632cb3d1f4b golangci-lint-1.59.0-linux-mips64le.tar.gz
|
||||
6cf9ea0d34e91669948483f9ae7f07da319a879344373a1981099fbd890cde00 golangci-lint-1.59.0-linux-ppc64le.tar.gz
|
||||
af0205fa6fbab197cee613c359947711231739095d21b5c837086233b36ad971 golangci-lint-1.59.0-linux-riscv64.tar.gz
|
||||
a9d2fb93f3c688ebccef94f5dc96c0b07c4d20bf6556cddebd8442159b0c80f6 golangci-lint-1.59.0-linux-s390x.tar.gz
|
||||
68ab4c57a847b8ace9679887f2f8b2b6760e57ee29dcde8c3f40dd8bb2654fa2 golangci-lint-1.59.0-netbsd-386.tar.gz
|
||||
d277b8b435c19406d00de4d509eadf5a024a5782878332e9a1b7c02bb76e87a7 golangci-lint-1.59.0-netbsd-amd64.tar.gz
|
||||
83211656be8dcfa1545af4f92894409f412d1f37566798cb9460a526593ad62c golangci-lint-1.59.0-netbsd-arm64.tar.gz
|
||||
6c6866d28bf79fa9817a0f7d2b050890ed109cae80bdb4dfa39536a7226da237 golangci-lint-1.59.0-netbsd-armv6.tar.gz
|
||||
11587566363bd03ca586b7df9776ccaed569fcd1f3489930ac02f9375b307503 golangci-lint-1.59.0-netbsd-armv7.tar.gz
|
||||
466181a8967bafa495e41494f93a0bec829c2cf715de874583b0460b3b8ae2b8 golangci-lint-1.59.0-windows-386.zip
|
||||
3317d8a87a99a49a0a1321d295c010790e6dbf43ee96b318f4b8bb23eae7a565 golangci-lint-1.59.0-windows-amd64.zip
|
||||
b3af955c7fceac8220a36fc799e1b3f19d3b247d32f422caac5f9845df8f7316 golangci-lint-1.59.0-windows-arm64.zip
|
||||
6f083c7d0c764e5a0e5bde46ee3e91ae357d80c194190fe1d9754392e9064c7e golangci-lint-1.59.0-windows-armv6.zip
|
||||
3709b4dd425deadab27748778d08e03c0f804d7748f7dd5b6bb488d98aa031c7 golangci-lint-1.59.0-windows-armv7.zip
|
||||
# https://github.com/golangci/golangci-lint/releases/download/v1.61.0/
|
||||
5c280ef3284f80c54fd90d73dc39ca276953949da1db03eb9dd0fbf868cc6e55 golangci-lint-1.61.0-darwin-amd64.tar.gz
|
||||
544334890701e4e04a6e574bc010bea8945205c08c44cced73745a6378012d36 golangci-lint-1.61.0-darwin-arm64.tar.gz
|
||||
e885a6f561092055930ebd298914d80e8fd2e10d2b1e9942836c2c6a115301fa golangci-lint-1.61.0-freebsd-386.tar.gz
|
||||
b13f6a3f11f65e7ff66b734d7554df3bbae0f485768848424e7554ed289e19c2 golangci-lint-1.61.0-freebsd-amd64.tar.gz
|
||||
cd8e7bbe5b8f33ed1597aa1cc588da96a3b9f22e1b9ae60d93511eae1a0ee8c5 golangci-lint-1.61.0-freebsd-armv6.tar.gz
|
||||
7ade524dbd88bd250968f45e190af90e151fa5ee63dd6aa7f7bb90e8155db61d golangci-lint-1.61.0-freebsd-armv7.tar.gz
|
||||
0fe3cd8a1ed8d9f54f48670a5af3df056d6040d94017057f0f4d65c930660ad9 golangci-lint-1.61.0-illumos-amd64.tar.gz
|
||||
b463fc5053a612abd26393ebaff1d85d7d56058946f4f0f7bf25ed44ea899415 golangci-lint-1.61.0-linux-386.tar.gz
|
||||
77cb0af99379d9a21d5dc8c38364d060e864a01bd2f3e30b5e8cc550c3a54111 golangci-lint-1.61.0-linux-amd64.tar.gz
|
||||
af60ac05566d9351615cb31b4cc070185c25bf8cbd9b09c1873aa5ec6f3cc17e golangci-lint-1.61.0-linux-arm64.tar.gz
|
||||
1f307f2fcc5d7d674062a967a0d83a7091e300529aa237ec6ad2b3dd14c897f5 golangci-lint-1.61.0-linux-armv6.tar.gz
|
||||
3ad8cbaae75a547450844811300f99c4cd290277398e43d22b9eb1792d15af4c golangci-lint-1.61.0-linux-armv7.tar.gz
|
||||
9be2ca67d961d7699079739cf6f7c8291c5183d57e34d1677de21ca19d0bd3ed golangci-lint-1.61.0-linux-loong64.tar.gz
|
||||
90d005e1648115ebf0861b408eab9c936079a24763e883058b0a227cd3135d31 golangci-lint-1.61.0-linux-mips64.tar.gz
|
||||
6d2ed4f49407115460b8c10ccfc40fd177e0887a48864a2879dd16e84ba2a48c golangci-lint-1.61.0-linux-mips64le.tar.gz
|
||||
633089589af5a58b7430afb6eee107d4e9c99e8d91711ddc219eb13a07e8d3b8 golangci-lint-1.61.0-linux-ppc64le.tar.gz
|
||||
4c1a097d9e0d1b4a8144dae6a1f5583a38d662f3bdc1498c4e954b6ed856be98 golangci-lint-1.61.0-linux-riscv64.tar.gz
|
||||
30581d3c987d287b7064617f1a2694143e10dffc40bc25be6636006ee82d7e1c golangci-lint-1.61.0-linux-s390x.tar.gz
|
||||
42530bf8100bd43c07f5efe6d92148ba6c5a7a712d510c6f24be85af6571d5eb golangci-lint-1.61.0-netbsd-386.tar.gz
|
||||
b8bb07c920f6601edf718d5e82ec0784fd590b0992b42b6ec18da99f26013ed4 golangci-lint-1.61.0-netbsd-amd64.tar.gz
|
||||
353a51527c60bd0776b0891b03f247c791986f625fca689d121972c624e54198 golangci-lint-1.61.0-netbsd-arm64.tar.gz
|
||||
957a6272c3137910514225704c5dac0723b9c65eb7d9587366a997736e2d7580 golangci-lint-1.61.0-netbsd-armv6.tar.gz
|
||||
a89eb28ff7f18f5cd52b914739360fa95cf2f643de4adeca46e26bec3a07e8d8 golangci-lint-1.61.0-netbsd-armv7.tar.gz
|
||||
d8d74c43600b271393000717a4ed157d7a15bb85bab7db2efad9b63a694d4634 golangci-lint-1.61.0-windows-386.zip
|
||||
e7bc2a81929a50f830244d6d2e657cce4f19a59aff49fa9000176ff34fda64ce golangci-lint-1.61.0-windows-amd64.zip
|
||||
ed97c221596dd771e3dd9344872c140340bee2e819cd7a90afa1de752f1f2e0f golangci-lint-1.61.0-windows-arm64.zip
|
||||
4b365233948b13d02d45928a5c390045e00945e919747b9887b5f260247541ae golangci-lint-1.61.0-windows-armv6.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!
|
||||
#
|
||||
|
|
|
|||
162
build/ci.go
162
build/ci.go
|
|
@ -50,7 +50,6 @@ import (
|
|||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
|
@ -121,11 +120,12 @@ var (
|
|||
|
||||
// Distros for which packages are created
|
||||
debDistros = []string{
|
||||
"xenial", // 16.04, EOL: 04/2026
|
||||
"bionic", // 18.04, EOL: 04/2028
|
||||
"focal", // 20.04, EOL: 04/2030
|
||||
"jammy", // 22.04, EOL: 04/2032
|
||||
"noble", // 24.04, EOL: 04/2034
|
||||
"xenial", // 16.04, EOL: 04/2026
|
||||
"bionic", // 18.04, EOL: 04/2028
|
||||
"focal", // 20.04, EOL: 04/2030
|
||||
"jammy", // 22.04, EOL: 04/2032
|
||||
"noble", // 24.04, EOL: 04/2034
|
||||
"oracular", // 24.10, EOL: 07/2025
|
||||
}
|
||||
|
||||
// This is where the tests should be unpacked.
|
||||
|
|
@ -159,8 +159,8 @@ func main() {
|
|||
doLint(os.Args[2:])
|
||||
case "archive":
|
||||
doArchive(os.Args[2:])
|
||||
case "docker":
|
||||
doDocker(os.Args[2:])
|
||||
case "dockerx":
|
||||
doDockerBuildx(os.Args[2:])
|
||||
case "debsrc":
|
||||
doDebianSource(os.Args[2:])
|
||||
case "nsis":
|
||||
|
|
@ -723,10 +723,9 @@ func maybeSkipArchive(env build.Environment) {
|
|||
}
|
||||
|
||||
// Builds the docker images and optionally uploads them to Docker Hub.
|
||||
func doDocker(cmdline []string) {
|
||||
func doDockerBuildx(cmdline []string) {
|
||||
var (
|
||||
image = flag.Bool("image", false, `Whether to build and push an arch specific docker image`)
|
||||
manifest = flag.String("manifest", "", `Push a multi-arch docker image for the specified architectures (usually "amd64,arm64")`)
|
||||
platform = flag.String("platform", "", `Push a multi-arch docker image for the specified architectures (usually "linux/amd64,linux/arm64")`)
|
||||
upload = flag.String("upload", "", `Where to upload the docker image (usually "ethereum/client-go")`)
|
||||
)
|
||||
flag.CommandLine.Parse(cmdline)
|
||||
|
|
@ -761,129 +760,26 @@ func doDocker(cmdline []string) {
|
|||
case strings.HasPrefix(env.Tag, "v1."):
|
||||
tags = []string{"stable", fmt.Sprintf("release-1.%d", params.VersionMinor), "v" + params.Version}
|
||||
}
|
||||
// If architecture specific image builds are requested, build and push them
|
||||
if *image {
|
||||
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", ".")
|
||||
// Need to create a mult-arch builder
|
||||
build.MustRunCommand("docker", "buildx", "create", "--use", "--name", "multi-arch-builder", "--platform", *platform)
|
||||
|
||||
// Tag and upload the images to Docker Hub
|
||||
for _, tag := range tags {
|
||||
gethImage := fmt.Sprintf("%s:%s-%s", *upload, tag, runtime.GOARCH)
|
||||
toolImage := fmt.Sprintf("%s:alltools-%s-%s", *upload, tag, runtime.GOARCH)
|
||||
|
||||
// If the image already exists (non version tag), check the build
|
||||
// number to prevent overwriting a newer commit if concurrent builds
|
||||
// are running. This is still a tiny bit racey if two published are
|
||||
// done at the same time, but that's extremely unlikely even on the
|
||||
// master branch.
|
||||
for _, img := range []string{gethImage, toolImage} {
|
||||
if exec.Command("docker", "pull", img).Run() != nil {
|
||||
continue // Generally the only failure is a missing image, which is good
|
||||
}
|
||||
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 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)
|
||||
for _, spec := range []struct {
|
||||
file string
|
||||
base string
|
||||
}{
|
||||
{file: "Dockerfile", base: fmt.Sprintf("%s:", *upload)},
|
||||
{file: "Dockerfile.alltools", base: fmt.Sprintf("%s:alltools-", *upload)},
|
||||
} {
|
||||
for _, tag := range tags { // latest, stable etc
|
||||
gethImage := fmt.Sprintf("%s%s", spec.base, tag)
|
||||
build.MustRunCommand("docker", "buildx", "build",
|
||||
"--build-arg", "COMMIT="+env.Commit,
|
||||
"--build-arg", "VERSION="+params.VersionWithMeta,
|
||||
"--build-arg", "BUILDNUM="+env.Buildnum,
|
||||
"--tag", gethImage,
|
||||
"--platform", *platform,
|
||||
"--push",
|
||||
"--file", spec.file, ".")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,7 +100,6 @@ func (c *Chain) AccountsInHashOrder() []state.DumpAccount {
|
|||
list := make([]state.DumpAccount, len(c.state))
|
||||
i := 0
|
||||
for addr, acc := range c.state {
|
||||
addr := addr
|
||||
list[i] = acc
|
||||
list[i].Address = &addr
|
||||
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 {
|
||||
tc := tc
|
||||
if i > 0 {
|
||||
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 {
|
||||
tc := tc
|
||||
if i > 0 {
|
||||
t.Log("\n")
|
||||
}
|
||||
|
|
@ -526,7 +524,6 @@ func (s *Suite) TestSnapGetByteCodes(t *utesting.T) {
|
|||
}
|
||||
|
||||
for i, tc := range tests {
|
||||
tc := tc
|
||||
if i > 0 {
|
||||
t.Log("\n")
|
||||
}
|
||||
|
|
@ -723,7 +720,6 @@ The server should reject the request.`,
|
|||
}
|
||||
|
||||
for i, tc := range tests {
|
||||
tc := tc
|
||||
if i > 0 {
|
||||
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"
|
||||
|
||||
"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/consensus/ethash"
|
||||
"github.com/ethereum/go-ethereum/consensus/misc"
|
||||
|
|
@ -50,6 +51,8 @@ type Prestate struct {
|
|||
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
|
||||
// error that might have occurred and a dump of the final state if requested.
|
||||
type ExecutionResult struct {
|
||||
|
|
@ -66,8 +69,12 @@ type ExecutionResult struct {
|
|||
WithdrawalsRoot *common.Hash `json:"withdrawalsRoot,omitempty"`
|
||||
CurrentExcessBlobGas *math.HexOrDecimal64 `json:"currentExcessBlobGas,omitempty"`
|
||||
CurrentBlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed,omitempty"`
|
||||
RequestsHash *common.Hash `json:"requestsRoot,omitempty"`
|
||||
DepositRequests *types.Deposits `json:"depositRequests,omitempty"`
|
||||
RequestsHash *common.Hash `json:"requestsHash,omitempty"`
|
||||
Requests [][]byte `json:"requests,omitempty"`
|
||||
}
|
||||
|
||||
type executionResultMarshaling struct {
|
||||
Requests []hexutil.Bytes `json:"requests,omitempty"`
|
||||
}
|
||||
|
||||
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))
|
||||
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
|
||||
root, err := statedb.Commit(vmContext.BlockNumber.Uint64(), chainConfig.IsEIP158(vmContext.BlockNumber))
|
||||
if err != nil {
|
||||
|
|
@ -379,28 +408,17 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
|||
execRs.CurrentExcessBlobGas = (*math.HexOrDecimal64)(&excessBlobGas)
|
||||
execRs.CurrentBlobGasUsed = (*math.HexOrDecimal64)(&blobGasUsed)
|
||||
}
|
||||
if chainConfig.IsPrague(vmContext.BlockNumber, vmContext.Time) {
|
||||
// Parse the requests from the logs
|
||||
var allLogs []*types.Log
|
||||
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))
|
||||
if requests != nil {
|
||||
// Set requestsHash on block.
|
||||
h := types.CalcRequestsHash(requests)
|
||||
execRs.RequestsHash = &h
|
||||
// Get the deposits from the requests
|
||||
deposits := make(types.Deposits, 0)
|
||||
for _, req := range requests {
|
||||
if dep, ok := req.Inner().(*types.Deposit); ok {
|
||||
deposits = append(deposits, dep)
|
||||
}
|
||||
for i := range requests {
|
||||
// remove prefix
|
||||
requests[i] = requests[i][1:]
|
||||
}
|
||||
execRs.DepositRequests = &deposits
|
||||
execRs.Requests = requests
|
||||
}
|
||||
|
||||
// Re-create statedb instance with new root upon the updated database
|
||||
// for accessing latest states.
|
||||
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",
|
||||
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{
|
||||
Name: "transition",
|
||||
Aliases: []string{"t8n"},
|
||||
Usage: "Executes a full state transition",
|
||||
Action: t8ntool.Transition,
|
||||
Flags: []cli.Flag{
|
||||
t8ntool.TraceFlag,
|
||||
t8ntool.TraceTracerFlag,
|
||||
t8ntool.TraceTracerConfigFlag,
|
||||
t8ntool.TraceEnableMemoryFlag,
|
||||
t8ntool.TraceDisableStackFlag,
|
||||
t8ntool.TraceEnableReturnDataFlag,
|
||||
t8ntool.TraceEnableCallFramesFlag,
|
||||
t8ntool.OutputBasedir,
|
||||
t8ntool.OutputAllocFlag,
|
||||
t8ntool.OutputResultFlag,
|
||||
t8ntool.OutputBodyFlag,
|
||||
t8ntool.InputAllocFlag,
|
||||
t8ntool.InputEnvFlag,
|
||||
t8ntool.InputTxsFlag,
|
||||
t8ntool.ForknameFlag,
|
||||
t8ntool.ChainIDFlag,
|
||||
t8ntool.RewardFlag,
|
||||
},
|
||||
}
|
||||
var (
|
||||
stateTransitionCommand = &cli.Command{
|
||||
Name: "transition",
|
||||
Aliases: []string{"t8n"},
|
||||
Usage: "Executes a full state transition",
|
||||
Action: t8ntool.Transition,
|
||||
Flags: []cli.Flag{
|
||||
t8ntool.TraceFlag,
|
||||
t8ntool.TraceTracerFlag,
|
||||
t8ntool.TraceTracerConfigFlag,
|
||||
t8ntool.TraceEnableMemoryFlag,
|
||||
t8ntool.TraceDisableStackFlag,
|
||||
t8ntool.TraceEnableReturnDataFlag,
|
||||
t8ntool.TraceEnableCallFramesFlag,
|
||||
t8ntool.OutputBasedir,
|
||||
t8ntool.OutputAllocFlag,
|
||||
t8ntool.OutputResultFlag,
|
||||
t8ntool.OutputBodyFlag,
|
||||
t8ntool.InputAllocFlag,
|
||||
t8ntool.InputEnvFlag,
|
||||
t8ntool.InputTxsFlag,
|
||||
t8ntool.ForknameFlag,
|
||||
t8ntool.ChainIDFlag,
|
||||
t8ntool.RewardFlag,
|
||||
},
|
||||
}
|
||||
|
||||
var transactionCommand = &cli.Command{
|
||||
Name: "transaction",
|
||||
Aliases: []string{"t9n"},
|
||||
Usage: "Performs transaction validation",
|
||||
Action: t8ntool.Transaction,
|
||||
Flags: []cli.Flag{
|
||||
t8ntool.InputTxsFlag,
|
||||
t8ntool.ChainIDFlag,
|
||||
t8ntool.ForknameFlag,
|
||||
},
|
||||
}
|
||||
transactionCommand = &cli.Command{
|
||||
Name: "transaction",
|
||||
Aliases: []string{"t9n"},
|
||||
Usage: "Performs transaction validation",
|
||||
Action: t8ntool.Transaction,
|
||||
Flags: []cli.Flag{
|
||||
t8ntool.InputTxsFlag,
|
||||
t8ntool.ChainIDFlag,
|
||||
t8ntool.ForknameFlag,
|
||||
},
|
||||
}
|
||||
|
||||
var blockBuilderCommand = &cli.Command{
|
||||
Name: "block-builder",
|
||||
Aliases: []string{"b11r"},
|
||||
Usage: "Builds a block",
|
||||
Action: t8ntool.BuildBlock,
|
||||
Flags: []cli.Flag{
|
||||
t8ntool.OutputBasedir,
|
||||
t8ntool.OutputBlockFlag,
|
||||
t8ntool.InputHeaderFlag,
|
||||
t8ntool.InputOmmersFlag,
|
||||
t8ntool.InputWithdrawalsFlag,
|
||||
t8ntool.InputTxsRlpFlag,
|
||||
t8ntool.SealCliqueFlag,
|
||||
},
|
||||
}
|
||||
blockBuilderCommand = &cli.Command{
|
||||
Name: "block-builder",
|
||||
Aliases: []string{"b11r"},
|
||||
Usage: "Builds a block",
|
||||
Action: t8ntool.BuildBlock,
|
||||
Flags: []cli.Flag{
|
||||
t8ntool.OutputBasedir,
|
||||
t8ntool.OutputBlockFlag,
|
||||
t8ntool.InputHeaderFlag,
|
||||
t8ntool.InputOmmersFlag,
|
||||
t8ntool.InputWithdrawalsFlag,
|
||||
t8ntool.InputTxsRlpFlag,
|
||||
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.
|
||||
var vmFlags = []cli.Flag{
|
||||
|
|
@ -235,6 +264,8 @@ func init() {
|
|||
stateTransitionCommand,
|
||||
transactionCommand,
|
||||
blockBuilderCommand,
|
||||
eofParseCommand,
|
||||
eofDumpCommand,
|
||||
}
|
||||
app.Before = func(ctx *cli.Context) error {
|
||||
flags.MigrateGlobalFlags(ctx)
|
||||
|
|
|
|||
|
|
@ -524,7 +524,7 @@ func TestT9n(t *testing.T) {
|
|||
ok, err := cmpJson(have, want)
|
||||
switch {
|
||||
case err != nil:
|
||||
t.Logf(string(have))
|
||||
t.Log(string(have))
|
||||
t.Fatalf("test %d, json parsing failed: %v", i, err)
|
||||
case !ok:
|
||||
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)
|
||||
switch {
|
||||
case err != nil:
|
||||
t.Logf(string(have))
|
||||
t.Log(string(have))
|
||||
t.Fatalf("test %d, json parsing failed: %v", i, err)
|
||||
case !ok:
|
||||
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 {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
importAccountWithExpect(t, test.key, test.output)
|
||||
|
|
|
|||
|
|
@ -221,22 +221,23 @@ func initGenesis(ctx *cli.Context) error {
|
|||
v := ctx.Uint64(utils.OverrideVerkle.Name)
|
||||
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())
|
||||
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", name, "hash", hash)
|
||||
chaindb, err := stack.OpenDatabaseWithFreezer("chaindata", 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())
|
||||
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
|
||||
}
|
||||
|
||||
|
|
@ -258,29 +259,22 @@ func dumpGenesis(ctx *cli.Context) error {
|
|||
|
||||
// dump whatever already exists in the datadir
|
||||
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 {
|
||||
utils.Fatalf("could not encode stored genesis: %s", err)
|
||||
}
|
||||
return nil
|
||||
db, err := stack.OpenDatabase("chaindata", 0, 0, "", true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ctx.IsSet(utils.DataDirFlag.Name) {
|
||||
utils.Fatalf("no existing datadir at %s", stack.Config().DataDir)
|
||||
defer db.Close()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
|
@ -557,7 +551,7 @@ func parseDumpConfig(ctx *cli.Context, db ethdb.Database) (*state.DumpConfig, co
|
|||
default:
|
||||
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),
|
||||
SkipStorage: ctx.Bool(utils.ExcludeStorageFlag.Name),
|
||||
OnlyWithAddresses: !ctx.Bool(utils.IncludeIncompletesFlag.Name),
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ func remoteConsole(ctx *cli.Context) error {
|
|||
func ephemeralConsole(ctx *cli.Context) error {
|
||||
var b strings.Builder
|
||||
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:
|
||||
geth --exec "%s" console`, b.String())
|
||||
|
|
|
|||
|
|
@ -156,7 +156,6 @@ var (
|
|||
utils.BeaconGenesisRootFlag,
|
||||
utils.BeaconGenesisTimeFlag,
|
||||
utils.BeaconCheckpointFlag,
|
||||
utils.CollectWitnessFlag,
|
||||
}, utils.NetworkFlags, utils.DatabaseFlags)
|
||||
|
||||
rpcFlags = []cli.Flag{
|
||||
|
|
|
|||
|
|
@ -170,7 +170,6 @@ func TestKeyID(t *testing.T) {
|
|||
{"third key", args{id: extractKeyId(gethPubKeys[2])}, "FD9813B2D2098484"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
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()
|
||||
defer s.ListEnd()
|
||||
if size == 0 {
|
||||
fmt.Fprintf(out, ws(depth)+"[]")
|
||||
fmt.Fprint(out, ws(depth)+"[]")
|
||||
} else {
|
||||
fmt.Fprintln(out, ws(depth)+"[")
|
||||
for i := 0; ; i++ {
|
||||
|
|
|
|||
|
|
@ -600,11 +600,6 @@ var (
|
|||
Usage: "Disables db compaction after import",
|
||||
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
|
||||
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) {
|
||||
if ctx.IsSet(MinerEtherbaseFlag.Name) {
|
||||
log.Warn("Option --miner.etherbase is deprecated as the etherbase is set by the consensus client post-merge")
|
||||
return
|
||||
}
|
||||
if !ctx.IsSet(MinerPendingFeeRecipientFlag.Name) {
|
||||
return
|
||||
|
|
@ -1767,9 +1761,6 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
|
|||
// TODO(fjl): force-enable this in --dev mode
|
||||
cfg.EnablePreimageRecording = ctx.Bool(VMEnableDebugFlag.Name)
|
||||
}
|
||||
if ctx.IsSet(CollectWitnessFlag.Name) {
|
||||
cfg.EnableWitnessCollection = ctx.Bool(CollectWitnessFlag.Name)
|
||||
}
|
||||
|
||||
if ctx.IsSet(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.
|
||||
// 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)
|
||||
if err != nil {
|
||||
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.
|
||||
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 {
|
||||
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
|
||||
}
|
||||
chainDb = remotedb.New(client)
|
||||
case ctx.String(SyncModeFlag.Name) == "light":
|
||||
chainDb, err = stack.OpenDatabase("lightchaindata", cache, handles, "", readonly)
|
||||
default:
|
||||
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{
|
||||
EnablePreimageRecording: ctx.Bool(VMEnableDebugFlag.Name),
|
||||
EnableWitnessCollection: ctx.Bool(CollectWitnessFlag.Name),
|
||||
}
|
||||
if ctx.IsSet(VMTraceFlag.Name) {
|
||||
if name := ctx.String(VMTraceFlag.Name); name != "" {
|
||||
|
|
|
|||
|
|
@ -56,7 +56,6 @@ func Test_SplitTagsFlag(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if got := SplitTagsFlag(tt.args); !reflect.DeepEqual(got, tt.want) {
|
||||
|
|
|
|||
|
|
@ -66,7 +66,6 @@ func TestGetPassPhraseWithList(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
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
|
||||
type instructionIterator struct {
|
||||
code []byte
|
||||
pc uint64
|
||||
arg []byte
|
||||
op vm.OpCode
|
||||
error error
|
||||
started bool
|
||||
code []byte
|
||||
pc uint64
|
||||
arg []byte
|
||||
op vm.OpCode
|
||||
error error
|
||||
started bool
|
||||
eofEnabled bool
|
||||
}
|
||||
|
||||
// NewInstructionIterator creates a new instruction iterator.
|
||||
|
|
@ -41,6 +42,13 @@ func NewInstructionIterator(code []byte) *instructionIterator {
|
|||
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.
|
||||
func (it *instructionIterator) Next() bool {
|
||||
if it.error != nil || uint64(len(it.code)) <= it.pc {
|
||||
|
|
@ -63,13 +71,26 @@ func (it *instructionIterator) Next() bool {
|
|||
// We reached the end.
|
||||
return false
|
||||
}
|
||||
|
||||
it.op = vm.OpCode(it.code[it.pc])
|
||||
if it.op.IsPush() {
|
||||
a := uint64(it.op) - uint64(vm.PUSH0)
|
||||
u := it.pc + 1 + a
|
||||
var a int
|
||||
if !it.eofEnabled { // Legacy code
|
||||
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 {
|
||||
it.error = fmt.Errorf("incomplete push instruction at %v", it.pc)
|
||||
it.error = fmt.Errorf("incomplete instruction at %v", it.pc)
|
||||
return false
|
||||
}
|
||||
it.arg = it.code[it.pc+1 : u]
|
||||
|
|
@ -105,7 +126,6 @@ func PrintDisassembled(code string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
it := NewInstructionIterator(script)
|
||||
for it.Next() {
|
||||
if it.Arg() != nil && 0 < len(it.Arg()) {
|
||||
|
|
|
|||
|
|
@ -17,42 +17,78 @@
|
|||
package asm
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Tests disassembling instructions
|
||||
func TestInstructionIterator(t *testing.T) {
|
||||
for i, tc := range []struct {
|
||||
want int
|
||||
code string
|
||||
wantErr string
|
||||
code string
|
||||
legacyWant string
|
||||
eofWant string
|
||||
}{
|
||||
{2, "61000000", ""}, // valid code
|
||||
{0, "6100", "incomplete push instruction at 0"}, // invalid code
|
||||
{2, "5900", ""}, // push0
|
||||
{0, "", ""}, // empty
|
||||
{"", "", ""}, // empty
|
||||
{"6100", `err: incomplete instruction at 0`, `err: incomplete instruction at 0`},
|
||||
{"61000000", `
|
||||
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 (
|
||||
have int
|
||||
code, _ = hex.DecodeString(tc.code)
|
||||
it = NewInstructionIterator(code)
|
||||
legacy = strings.TrimSpace(disassembly(NewInstructionIterator(code)))
|
||||
eof = strings.TrimSpace(disassembly(NewEOFInstructionIterator(code)))
|
||||
)
|
||||
for it.Next() {
|
||||
have++
|
||||
if want := strings.TrimSpace(tc.legacyWant); legacy != want {
|
||||
t.Errorf("test %d: wrong (legacy) output. have:\n%q\nwant:\n%q\n", i, legacy, want)
|
||||
}
|
||||
var haveErr = ""
|
||||
if it.Error() != nil {
|
||||
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)
|
||||
if want := strings.TrimSpace(tc.eofWant); eof != want {
|
||||
t.Errorf("test %d: wrong (eof) output. have:\n%q\nwant:\n%q\n", i, eof, 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"
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"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/params"
|
||||
"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.
|
||||
func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateDB, res *ProcessResult, stateless bool) error {
|
||||
if res == nil {
|
||||
return fmt.Errorf("nil ProcessResult value")
|
||||
return errors.New("nil ProcessResult value")
|
||||
}
|
||||
header := block.Header()
|
||||
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.
|
||||
if header.RequestsHash != nil {
|
||||
depositSha := types.DeriveSha(res.Requests, trie.NewStackTrie(nil))
|
||||
if depositSha != *header.RequestsHash {
|
||||
return fmt.Errorf("invalid deposit root hash (remote: %x local: %x)", *header.RequestsHash, depositSha)
|
||||
reqhash := types.CalcRequestsHash(res.Requests)
|
||||
if reqhash != *header.RequestsHash {
|
||||
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
|
||||
// an error if they don't match.
|
||||
|
|
@ -160,28 +160,6 @@ func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateD
|
|||
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
|
||||
// to keep the baseline gas close to the provided target, and increase it towards
|
||||
// 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)
|
||||
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
|
||||
|
|
|
|||
|
|
@ -78,10 +78,11 @@ var (
|
|||
snapshotCommitTimer = metrics.NewRegisteredResettingTimer("chain/snapshot/commits", nil)
|
||||
triedbCommitTimer = metrics.NewRegisteredResettingTimer("chain/triedb/commits", nil)
|
||||
|
||||
blockInsertTimer = metrics.NewRegisteredResettingTimer("chain/inserts", nil)
|
||||
blockValidationTimer = metrics.NewRegisteredResettingTimer("chain/validation", nil)
|
||||
blockExecutionTimer = metrics.NewRegisteredResettingTimer("chain/execution", nil)
|
||||
blockWriteTimer = metrics.NewRegisteredResettingTimer("chain/write", nil)
|
||||
blockInsertTimer = metrics.NewRegisteredResettingTimer("chain/inserts", nil)
|
||||
blockValidationTimer = metrics.NewRegisteredResettingTimer("chain/validation", nil)
|
||||
blockCrossValidationTimer = metrics.NewRegisteredResettingTimer("chain/crossvalidation", nil)
|
||||
blockExecutionTimer = metrics.NewRegisteredResettingTimer("chain/execution", nil)
|
||||
blockWriteTimer = metrics.NewRegisteredResettingTimer("chain/write", nil)
|
||||
|
||||
blockReorgMeter = metrics.NewRegisteredMeter("chain/reorg/executes", nil)
|
||||
blockReorgAddMeter = metrics.NewRegisteredMeter("chain/reorg/add", nil)
|
||||
|
|
@ -1598,7 +1599,9 @@ func (bc *BlockChain) InsertChain(chain types.Blocks) (int, error) {
|
|||
return 0, errChainStopped
|
||||
}
|
||||
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
|
||||
|
|
@ -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
|
||||
// is imported, but then new canon-head is added before the actual sidechain
|
||||
// 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 bc.insertStopped() {
|
||||
return 0, nil
|
||||
return nil, 0, nil
|
||||
}
|
||||
// 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)
|
||||
|
|
@ -1667,7 +1670,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
|
|||
for block != nil && bc.skipBlock(err, it) {
|
||||
log.Debug("Writing previously known block", "number", block.Number(), "hash", block.Hash())
|
||||
if err := bc.writeKnownBlock(block); err != nil {
|
||||
return it.index, err
|
||||
return nil, it.index, err
|
||||
}
|
||||
lastCanon = block
|
||||
|
||||
|
|
@ -1681,12 +1684,12 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
|
|||
if setHead {
|
||||
// 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())
|
||||
return bc.insertSideChain(block, it)
|
||||
return bc.insertSideChain(block, it, makeWitness)
|
||||
} else {
|
||||
// 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())
|
||||
_, err := bc.recoverAncestors(block)
|
||||
return it.index, err
|
||||
_, err := bc.recoverAncestors(block, makeWitness)
|
||||
return nil, it.index, err
|
||||
}
|
||||
// Some other error(except ErrKnownBlock) occurred, abort.
|
||||
// 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):
|
||||
stats.ignored += len(it.chain)
|
||||
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)
|
||||
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() {
|
||||
// If the chain is terminating, stop processing blocks
|
||||
if bc.insertStopped() {
|
||||
|
|
@ -1744,7 +1750,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
|
|||
"hash", block.Hash(), "number", block.NumberU64())
|
||||
}
|
||||
if err := bc.writeKnownBlock(block); err != nil {
|
||||
return it.index, err
|
||||
return nil, it.index, err
|
||||
}
|
||||
stats.processed++
|
||||
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(),
|
||||
})
|
||||
}
|
||||
|
||||
// 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.
|
||||
lastCanon = block
|
||||
continue
|
||||
}
|
||||
|
||||
// Retrieve the parent block and it's state to execute on top
|
||||
start := time.Now()
|
||||
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)
|
||||
if err != nil {
|
||||
return it.index, err
|
||||
return nil, it.index, err
|
||||
}
|
||||
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
|
||||
// useless due to the intermediate root hashing after each transaction.
|
||||
if bc.chainConfig.IsByzantium(block.Number()) {
|
||||
var witness *stateless.Witness
|
||||
if bc.vmConfig.EnableWitnessCollection {
|
||||
witness, err = stateless.NewWitness(bc, block)
|
||||
// Generate witnesses either if we're self-testing, or if it's the
|
||||
// only block being inserted. A bit crude, but witnesses are huge,
|
||||
// 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 {
|
||||
return it.index, err
|
||||
return nil, it.index, err
|
||||
}
|
||||
}
|
||||
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)
|
||||
followupInterrupt.Store(true)
|
||||
if err != nil {
|
||||
return it.index, err
|
||||
return nil, it.index, err
|
||||
}
|
||||
// Report the import stats before returning the various results
|
||||
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
|
||||
// all blocks the CL gives us for GC processing time
|
||||
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 {
|
||||
case CanonStatTy:
|
||||
|
|
@ -1861,7 +1867,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
|
|||
}
|
||||
}
|
||||
stats.ignored += it.remaining()
|
||||
return it.index, err
|
||||
return witness, it.index, err
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
if witness := statedb.Witness(); witness != nil {
|
||||
if err = bc.validator.ValidateWitness(witness, block.ReceiptHash(), block.Root()); err != nil {
|
||||
bc.reportBlock(block, res, err)
|
||||
return nil, fmt.Errorf("cross verification failed: %v", err)
|
||||
// If witnesses was generated and stateless self-validation requested, do
|
||||
// that now. Self validation should *never* run in production, it's more of
|
||||
// a tight integration to enable running *all* consensus tests through the
|
||||
// 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
|
||||
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
|
||||
blockExecutionTimer.Update(ptime - (statedb.AccountReads + statedb.StorageReads)) // The time spent on EVM processing
|
||||
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.
|
||||
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
|
||||
// switch over to the new chain if the TD exceeded the current chain.
|
||||
// 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 (
|
||||
externTd *big.Int
|
||||
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,
|
||||
// we cannot risk writing unverified blocks to disk when they obviously target the pruning
|
||||
// mechanism.
|
||||
return it.index, errors.New("sidechain ghost-state attack")
|
||||
return nil, it.index, errors.New("sidechain ghost-state attack")
|
||||
}
|
||||
}
|
||||
if externTd == nil {
|
||||
|
|
@ -2011,7 +2041,7 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (i
|
|||
if !bc.HasBlock(block.Hash(), block.NumberU64()) {
|
||||
start := time.Now()
|
||||
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(),
|
||||
"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) {
|
||||
if bc.stateRecoverable(parent.Root) {
|
||||
if err := bc.triedb.Recover(parent.Root); err != nil {
|
||||
return 0, err
|
||||
return nil, 0, err
|
||||
}
|
||||
break
|
||||
}
|
||||
|
|
@ -2038,7 +2068,7 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (i
|
|||
parent = bc.GetHeader(parent.ParentHash, parent.Number.Uint64()-1)
|
||||
}
|
||||
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
|
||||
var (
|
||||
|
|
@ -2057,30 +2087,30 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (i
|
|||
// memory here.
|
||||
if len(blocks) >= 2048 || memory > 64*1024*1024 {
|
||||
log.Info("Importing heavy sidechain segment", "blocks", len(blocks), "start", blocks[0].NumberU64(), "end", block.NumberU64())
|
||||
if _, err := bc.insertChain(blocks, true); err != nil {
|
||||
return 0, err
|
||||
if _, _, err := bc.insertChain(blocks, true, false); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
blocks, memory = blocks[:0], 0
|
||||
|
||||
// If the chain is terminating, stop processing blocks
|
||||
if bc.insertStopped() {
|
||||
log.Debug("Abort during blocks processing")
|
||||
return 0, nil
|
||||
return nil, 0, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(blocks) > 0 {
|
||||
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
|
||||
// all the ancestor blocks since that.
|
||||
// recoverAncestors is only used post-merge.
|
||||
// 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)
|
||||
var (
|
||||
hashes []common.Hash
|
||||
|
|
@ -2120,7 +2150,7 @@ func (bc *BlockChain) recoverAncestors(block *types.Block) (common.Hash, error)
|
|||
} else {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
// updating. It relies on the additional SetCanonical call to finalize the entire
|
||||
// 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() {
|
||||
return errChainStopped
|
||||
return nil, errChainStopped
|
||||
}
|
||||
defer bc.chainmu.Unlock()
|
||||
|
||||
_, err := bc.insertChain(types.Blocks{block}, false)
|
||||
return err
|
||||
witness, _, err := bc.insertChain(types.Blocks{block}, false, makeWitness)
|
||||
return witness, err
|
||||
}
|
||||
|
||||
// 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.
|
||||
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
|
||||
}
|
||||
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) {
|
||||
// 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))))
|
||||
// fmt.Println(tt.dump(true))
|
||||
// log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, true)))
|
||||
// fmt.Println(tt.dump(false))
|
||||
|
||||
// Create a temporary persistent database
|
||||
datadir := t.TempDir()
|
||||
|
|
@ -1908,7 +1908,7 @@ func TestIssue23496(t *testing.T) {
|
|||
|
||||
func testIssue23496(t *testing.T, scheme string) {
|
||||
// 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
|
||||
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) {
|
||||
// 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))
|
||||
|
||||
// Create a temporary persistent database
|
||||
|
|
|
|||
|
|
@ -222,8 +222,8 @@ type snapshotTest struct {
|
|||
|
||||
func (snaptest *snapshotTest) test(t *testing.T) {
|
||||
// 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))))
|
||||
// fmt.Println(tt.dump())
|
||||
// log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, true)))
|
||||
// fmt.Println(snaptest.dump())
|
||||
chain, blocks := snaptest.prepare(t)
|
||||
|
||||
// Restart the chain normally
|
||||
|
|
@ -245,8 +245,8 @@ type crashSnapshotTest struct {
|
|||
|
||||
func (snaptest *crashSnapshotTest) test(t *testing.T) {
|
||||
// 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))))
|
||||
// fmt.Println(tt.dump())
|
||||
// log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, true)))
|
||||
// fmt.Println(snaptest.dump())
|
||||
chain, blocks := snaptest.prepare(t)
|
||||
|
||||
// Pull the plug on the database, simulating a hard crash
|
||||
|
|
@ -297,8 +297,8 @@ type gappedSnapshotTest struct {
|
|||
|
||||
func (snaptest *gappedSnapshotTest) test(t *testing.T) {
|
||||
// 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))))
|
||||
// fmt.Println(tt.dump())
|
||||
// log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, true)))
|
||||
// fmt.Println(snaptest.dump())
|
||||
chain, blocks := snaptest.prepare(t)
|
||||
|
||||
// Insert blocks without enabling snapshot if gapping is required.
|
||||
|
|
@ -341,8 +341,8 @@ type setHeadSnapshotTest struct {
|
|||
|
||||
func (snaptest *setHeadSnapshotTest) test(t *testing.T) {
|
||||
// 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))))
|
||||
// fmt.Println(tt.dump())
|
||||
// log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, true)))
|
||||
// fmt.Println(snaptest.dump())
|
||||
chain, blocks := snaptest.prepare(t)
|
||||
|
||||
// Rewind the chain if setHead operation is required.
|
||||
|
|
@ -370,8 +370,8 @@ type wipeCrashSnapshotTest struct {
|
|||
|
||||
func (snaptest *wipeCrashSnapshotTest) test(t *testing.T) {
|
||||
// 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))))
|
||||
// fmt.Println(tt.dump())
|
||||
// log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, true)))
|
||||
// fmt.Println(snaptest.dump())
|
||||
chain, blocks := snaptest.prepare(t)
|
||||
|
||||
// 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]
|
||||
// ^ ^ ^ pruned
|
||||
func TestPrunedImportSide(t *testing.T) {
|
||||
//glogger := log.NewGlogHandler(log.StreamHandler(os.Stdout, log.TerminalFormat(false)))
|
||||
//glogger.Verbosity(3)
|
||||
//log.Root().SetHandler(log.Handler(glogger))
|
||||
// glogger := log.NewGlogHandler(log.NewTerminalHandler(os.Stderr, false))
|
||||
// glogger.Verbosity(3)
|
||||
// log.SetDefault(log.NewLogger(glogger))
|
||||
testSideImport(t, 3, 3, -1)
|
||||
testSideImport(t, 3, -3, -1)
|
||||
testSideImport(t, 10, 0, -1)
|
||||
|
|
@ -2137,9 +2137,9 @@ func TestPrunedImportSide(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestPrunedImportSideWithMerging(t *testing.T) {
|
||||
//glogger := log.NewGlogHandler(log.StreamHandler(os.Stdout, log.TerminalFormat(false)))
|
||||
//glogger.Verbosity(3)
|
||||
//log.Root().SetHandler(log.Handler(glogger))
|
||||
// glogger := log.NewGlogHandler(log.NewTerminalHandler(os.Stderr, false))
|
||||
// glogger.Verbosity(3)
|
||||
// log.SetDefault(log.NewLogger(glogger))
|
||||
testSideImport(t, 3, 3, 0)
|
||||
testSideImport(t, 3, -3, 0)
|
||||
testSideImport(t, 10, 0, 0)
|
||||
|
|
@ -3629,7 +3629,7 @@ func TestSetCanonical(t *testing.T) {
|
|||
}
|
||||
|
||||
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 (
|
||||
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
|
|
@ -3674,7 +3674,7 @@ func testSetCanonical(t *testing.T, scheme string) {
|
|||
gen.AddTx(tx)
|
||||
})
|
||||
for _, block := range side {
|
||||
err := chain.InsertBlockWithoutSetHead(block)
|
||||
_, err := chain.InsertBlockWithoutSetHead(block, false)
|
||||
if err != nil {
|
||||
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 (
|
||||
engine = beacon.NewFaker()
|
||||
|
||||
// A sender who makes transactions, has some funds
|
||||
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
addr = crypto.PubkeyToAddress(key.PublicKey)
|
||||
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),
|
||||
},
|
||||
},
|
||||
}
|
||||
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
addr1 = crypto.PubkeyToAddress(key1.PublicKey)
|
||||
config = *params.MergedTestChainConfig
|
||||
signer = types.LatestSigner(&config)
|
||||
engine = beacon.NewFaker()
|
||||
)
|
||||
|
||||
gspec.Config.BerlinBlock = common.Big0
|
||||
gspec.Config.LondonBlock = common.Big0
|
||||
gspec.Config.TerminalTotalDifficulty = common.Big0
|
||||
gspec.Config.TerminalTotalDifficultyPassed = true
|
||||
gspec.Config.ShanghaiTime = u64(0)
|
||||
gspec.Config.CancunTime = u64(0)
|
||||
gspec.Config.PragueTime = u64(0)
|
||||
signer := types.LatestSigner(gspec.Config)
|
||||
gspec := &Genesis{
|
||||
Config: &config,
|
||||
Alloc: types.GenesisAlloc{
|
||||
addr1: {Balance: big.NewInt(9999900000000000)},
|
||||
config.DepositContractAddress: {Code: depositsGeneratorCode},
|
||||
params.WithdrawalQueueAddress: {Code: params.WithdrawalQueueCode},
|
||||
params.ConsolidationQueueAddress: {Code: params.ConsolidationQueueCode},
|
||||
},
|
||||
}
|
||||
|
||||
_, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) {
|
||||
for i := 0; i < 5; i++ {
|
||||
txdata := &types.DynamicFeeTx{
|
||||
ChainID: gspec.Config.ChainID,
|
||||
Nonce: uint64(i),
|
||||
To: &config.DepositContractAddress,
|
||||
Gas: 500000,
|
||||
GasFeeCap: newGwei(5),
|
||||
GasTipCap: big.NewInt(2),
|
||||
AccessList: nil,
|
||||
Data: []byte{},
|
||||
}
|
||||
tx := types.NewTx(txdata)
|
||||
tx, _ = types.SignTx(tx, signer, key)
|
||||
b.AddTx(tx)
|
||||
}
|
||||
// create deposit
|
||||
depositTx := types.MustSignNewTx(key1, signer, &types.DynamicFeeTx{
|
||||
ChainID: gspec.Config.ChainID,
|
||||
Nonce: 0,
|
||||
To: &config.DepositContractAddress,
|
||||
Gas: 500_000,
|
||||
GasFeeCap: newGwei(5),
|
||||
GasTipCap: big.NewInt(2),
|
||||
})
|
||||
b.AddTx(depositTx)
|
||||
|
||||
// create withdrawal request
|
||||
withdrawalTx := types.MustSignNewTx(key1, signer, &types.DynamicFeeTx{
|
||||
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 {
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
|
||||
var requests types.Requests
|
||||
var requests [][]byte
|
||||
if config.IsPrague(b.header.Number, b.header.Time) {
|
||||
// EIP-6110 deposits
|
||||
var blockLogs []*types.Log
|
||||
for _, r := range b.receipts {
|
||||
d, err := ParseDepositLogs(r.Logs, config)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to parse deposit log: %v", err))
|
||||
}
|
||||
requests = append(requests, d...)
|
||||
blockLogs = append(blockLogs, r.Logs...)
|
||||
}
|
||||
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)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
|
@ -446,16 +462,15 @@ func GenerateVerkleChain(config *params.ChainConfig, parent *types.Block, engine
|
|||
// Save pre state for proof generation
|
||||
// preState := statedb.Copy()
|
||||
|
||||
// TODO uncomment when the 2935 PR is merged
|
||||
// if config.IsPrague(b.header.Number, b.header.Time) {
|
||||
// if !config.IsPrague(b.parent.Number(), b.parent.Time()) {
|
||||
// Transition case: insert all 256 ancestors
|
||||
// InsertBlockHashHistoryAtEip2935Fork(statedb, b.header.Number.Uint64()-1, b.header.ParentHash, chainreader)
|
||||
// } else {
|
||||
// ProcessParentBlockHash(statedb, b.header.Number.Uint64()-1, b.header.ParentHash)
|
||||
// }
|
||||
// }
|
||||
// Execute any user modifications to the block
|
||||
// Pre-execution system calls.
|
||||
if config.IsPrague(b.header.Number, b.header.Time) {
|
||||
// EIP-2935
|
||||
blockContext := NewEVMBlockContext(b.header, cm, &b.header.Coinbase)
|
||||
vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, cm.config, vm.Config{})
|
||||
ProcessParentBlockHash(b.header.ParentHash, vmenv, statedb)
|
||||
}
|
||||
|
||||
// Execute any user modifications to the block.
|
||||
if gen != nil {
|
||||
gen(i, b)
|
||||
}
|
||||
|
|
@ -469,7 +484,7 @@ func GenerateVerkleChain(config *params.ChainConfig, parent *types.Block, engine
|
|||
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))
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("state write error: %v", err))
|
||||
|
|
|
|||
|
|
@ -449,7 +449,6 @@ func (g *Genesis) toBlockWithRoot(root common.Hash) *types.Block {
|
|||
}
|
||||
var (
|
||||
withdrawals []*types.Withdrawal
|
||||
requests types.Requests
|
||||
)
|
||||
if conf := g.Config; conf != nil {
|
||||
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) {
|
||||
head.RequestsHash = &types.EmptyRequestsHash
|
||||
requests = make(types.Requests, 0)
|
||||
emptyRequests := [][]byte{{0x00}, {0x01}, {0x02}}
|
||||
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.
|
||||
|
|
@ -588,10 +588,11 @@ func DeveloperGenesisBlock(gasLimit uint64, faucet *common.Address) *Genesis {
|
|||
common.BytesToAddress([]byte{7}): {Balance: big.NewInt(1)}, // ECScalarMul
|
||||
common.BytesToAddress([]byte{8}): {Balance: big.NewInt(1)}, // ECPairing
|
||||
common.BytesToAddress([]byte{9}): {Balance: big.NewInt(1)}, // BLAKE2b
|
||||
// Pre-deploy EIP-4788 system contract
|
||||
params.BeaconRootsAddress: {Nonce: 1, Code: params.BeaconRootsCode, Balance: common.Big0},
|
||||
// Pre-deploy EIP-2935 history contract.
|
||||
params.HistoryStorageAddress: {Nonce: 1, Code: params.HistoryStorageCode},
|
||||
// Pre-deploy system contracts
|
||||
params.BeaconRootsAddress: {Nonce: 1, Code: params.BeaconRootsCode, Balance: common.Big0},
|
||||
params.HistoryStorageAddress: {Nonce: 1, Code: params.HistoryStorageCode, Balance: common.Big0},
|
||||
params.WithdrawalQueueAddress: {Nonce: 1, Code: params.WithdrawalQueueCode, Balance: common.Big0},
|
||||
params.ConsolidationQueueAddress: {Nonce: 1, Code: params.ConsolidationQueueCode, Balance: common.Big0},
|
||||
},
|
||||
}
|
||||
if faucet != nil {
|
||||
|
|
|
|||
|
|
@ -388,10 +388,10 @@ func TestBlockReceiptStorage(t *testing.T) {
|
|||
// Insert the receipt slice into the database and check presence
|
||||
WriteReceipts(db, hash, 0, receipts)
|
||||
if rs := ReadReceipts(db, hash, 0, 0, params.TestChainConfig); len(rs) == 0 {
|
||||
t.Fatalf("no receipts returned")
|
||||
t.Fatal("no receipts returned")
|
||||
} else {
|
||||
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)
|
||||
|
|
@ -401,7 +401,7 @@ func TestBlockReceiptStorage(t *testing.T) {
|
|||
}
|
||||
// Ensure that receipts without metadata can be returned without the block body too
|
||||
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
|
||||
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)
|
||||
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
|
||||
// persistent state.
|
||||
if stored == "" || provided == stored {
|
||||
|
|
|
|||
|
|
@ -52,13 +52,11 @@ var (
|
|||
// freezerTableSize defines the maximum size of freezer data files.
|
||||
const freezerTableSize = 2 * 1000 * 1000 * 1000
|
||||
|
||||
// Freezer is a memory mapped append-only database to store immutable ordered
|
||||
// data into flat files:
|
||||
// Freezer is an append-only database to store immutable ordered data into
|
||||
// flat files:
|
||||
//
|
||||
// - The append-only nature ensures that disk writes are minimized.
|
||||
// - The memory mapping ensures we can max out system memory for caching without
|
||||
// reserving it for go-ethereum. This would also reduce the memory requirements
|
||||
// of Geth, and thus also GC overhead.
|
||||
// - The append-only nature ensures that disk writes are minimized.
|
||||
// - The in-order data ensures that disk reads are always optimized.
|
||||
type Freezer struct {
|
||||
frozen atomic.Uint64 // Number of items already frozen
|
||||
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
|
||||
}
|
||||
|
||||
// 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 {
|
||||
f.writeLock.Lock()
|
||||
defer f.writeLock.Unlock()
|
||||
|
|
|
|||
|
|
@ -180,10 +180,10 @@ func (batch *freezerTableBatch) maybeCommit() error {
|
|||
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 {
|
||||
// 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)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -194,15 +194,10 @@ func (batch *freezerTableBatch) commit() error {
|
|||
dataSize := int64(len(batch.dataBuffer))
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := batch.t.index.Sync(); err != nil {
|
||||
return err
|
||||
}
|
||||
indexSize := int64(len(batch.indexBuffer))
|
||||
batch.indexBuffer = batch.indexBuffer[:0]
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package rawdb
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
|
|
@ -26,6 +27,7 @@ import (
|
|||
"path/filepath"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
|
|
@ -219,7 +221,13 @@ func (t *freezerTable) repair() error {
|
|||
return err
|
||||
} // 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 {
|
||||
return err
|
||||
}
|
||||
|
|
@ -364,6 +372,133 @@ func (t *freezerTable) repair() error {
|
|||
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,
|
||||
// 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
|
||||
|
|
|
|||
|
|
@ -1367,3 +1367,69 @@ func TestRandom(t *testing.T) {
|
|||
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
|
||||
// for the byte value:
|
||||
// * the first bit is set if the branch has been edited
|
||||
// * the second bit is set if the branch has been read
|
||||
// * the first bit is set if the branch has been read
|
||||
// * the second bit is set if the branch has been edited
|
||||
type mode byte
|
||||
|
||||
const (
|
||||
|
|
@ -117,11 +117,23 @@ func (ae *AccessEvents) ValueTransferGas(callerAddr, targetAddr common.Address)
|
|||
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
|
||||
// a contract creation.
|
||||
func (ae *AccessEvents) ContractCreateInitGas(addr common.Address, createSendsValue bool) uint64 {
|
||||
func (ae *AccessEvents) ContractCreateInitGas(addr common.Address) uint64 {
|
||||
var gas uint64
|
||||
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, true)
|
||||
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, true)
|
||||
return gas
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -100,13 +100,13 @@ func TestContractCreateInitGas(t *testing.T) {
|
|||
}
|
||||
|
||||
// Check cold read cost, without a value
|
||||
gas := ae.ContractCreateInitGas(testAddr, false)
|
||||
if want := params.WitnessBranchWriteCost + params.WitnessBranchReadCost + params.WitnessChunkWriteCost + params.WitnessChunkReadCost; gas != want {
|
||||
gas := ae.ContractCreateInitGas(testAddr)
|
||||
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)
|
||||
}
|
||||
|
||||
// Check warm read cost
|
||||
gas = ae.ContractCreateInitGas(testAddr, false)
|
||||
gas = ae.ContractCreateInitGas(testAddr)
|
||||
if 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 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()
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -282,7 +282,6 @@ func (sf *subfetcher) schedule(keys [][]byte, read bool) error {
|
|||
// Append the tasks to the current queue
|
||||
sf.lock.Lock()
|
||||
for _, key := range keys {
|
||||
key := key // closure for the append below
|
||||
sf.tasks = append(sf.tasks, &subfetcherTask{read: read, key: key})
|
||||
}
|
||||
sf.lock.Unlock()
|
||||
|
|
|
|||
|
|
@ -71,8 +71,9 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
|
|||
var (
|
||||
context vm.BlockContext
|
||||
signer = types.MakeSigner(p.config, header.Number, header.Time)
|
||||
err error
|
||||
)
|
||||
|
||||
// Apply pre-execution system calls.
|
||||
context = NewEVMBlockContext(header, p.chain, nil)
|
||||
vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, p.config, cfg)
|
||||
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()) {
|
||||
ProcessParentBlockHash(block.ParentHash(), vmenv, statedb)
|
||||
}
|
||||
|
||||
// Iterate over and process the individual transactions
|
||||
for i, tx := range block.Transactions() {
|
||||
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)
|
||||
allLogs = append(allLogs, receipt.Logs...)
|
||||
}
|
||||
|
||||
// Read requests if Prague is enabled.
|
||||
var requests types.Requests
|
||||
var requests [][]byte
|
||||
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 {
|
||||
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)
|
||||
|
|
@ -215,9 +226,6 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, vmenv *vm.EVM, statedb *stat
|
|||
defer tracer.OnSystemCallEnd()
|
||||
}
|
||||
}
|
||||
|
||||
// If EIP-4788 is enabled, we need to invoke the beaconroot storage contract with
|
||||
// the new root
|
||||
msg := &Message{
|
||||
From: params.SystemAddress,
|
||||
GasLimit: 30_000_000,
|
||||
|
|
@ -244,7 +252,6 @@ func ProcessParentBlockHash(prevHash common.Hash, vmenv *vm.EVM, statedb *state.
|
|||
defer tracer.OnSystemCallEnd()
|
||||
}
|
||||
}
|
||||
|
||||
msg := &Message{
|
||||
From: params.SystemAddress,
|
||||
GasLimit: 30_000_000,
|
||||
|
|
@ -260,17 +267,59 @@ func ProcessParentBlockHash(prevHash common.Hash, vmenv *vm.EVM, statedb *state.
|
|||
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
|
||||
// BeaconDepositContract.
|
||||
func ParseDepositLogs(logs []*types.Log, config *params.ChainConfig) (types.Requests, error) {
|
||||
deposits := make(types.Requests, 0)
|
||||
func ParseDepositLogs(logs []*types.Log, config *params.ChainConfig) ([]byte, error) {
|
||||
deposits := make([]byte, 1) // note: first byte is 0x00 (== deposit request type)
|
||||
for _, log := range logs {
|
||||
if log.Address == config.DepositContractAddress {
|
||||
d, err := types.UnpackIntoDeposit(log.Data)
|
||||
request, err := types.DepositLogToRequest(log.Data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse deposit data: %v", err)
|
||||
}
|
||||
deposits = append(deposits, types.NewRequest(d))
|
||||
deposits = append(deposits, request...)
|
||||
}
|
||||
}
|
||||
return deposits, nil
|
||||
|
|
|
|||
|
|
@ -25,21 +25,30 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/stateless"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"github.com/ethereum/go-ethereum/triedb"
|
||||
)
|
||||
|
||||
// ExecuteStateless runs a stateless execution based on a witness, verifies
|
||||
// everything it can locally and returns the two computed fields that need the
|
||||
// other side to explicitly check.
|
||||
// everything it can locally and returns the state root and receipt root, that
|
||||
// need the other side to explicitly check.
|
||||
//
|
||||
// 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 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.
|
||||
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
|
||||
memdb := witness.MakeHashDB()
|
||||
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
|
||||
|
||||
// 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 {
|
||||
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
|
||||
}
|
||||
// Almost everything validated, but receipt and state root needs to be returned
|
||||
receiptRoot := types.DeriveSha(res.Receipts, trie.NewStackTrie(nil))
|
||||
stateRoot := db.IntermediateRoot(config.IsEIP158(witness.Block.Number()))
|
||||
|
||||
return receiptRoot, stateRoot, nil
|
||||
stateRoot := db.IntermediateRoot(config.IsEIP158(block.Number()))
|
||||
return stateRoot, receiptRoot, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,13 @@ import (
|
|||
// 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
|
||||
// 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 {
|
||||
var (
|
||||
memdb = rawdb.NewMemoryDatabase()
|
||||
|
|
|
|||
|
|
@ -17,42 +17,31 @@
|
|||
package stateless
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"slices"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"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.
|
||||
func (w *Witness) toExtWitness() *extWitness {
|
||||
ext := &extWitness{
|
||||
Block: w.Block,
|
||||
Headers: w.Headers,
|
||||
}
|
||||
ext.Codes = make([][]byte, 0, len(w.Codes))
|
||||
for code := range w.Codes {
|
||||
ext.Codes = append(ext.Codes, []byte(code))
|
||||
}
|
||||
slices.SortFunc(ext.Codes, bytes.Compare)
|
||||
|
||||
ext.State = make([][]byte, 0, len(w.State))
|
||||
for node := range w.State {
|
||||
ext.State = append(ext.State, []byte(node))
|
||||
}
|
||||
slices.SortFunc(ext.State, bytes.Compare)
|
||||
return ext
|
||||
}
|
||||
|
||||
// fromExtWitness converts the consensus witness format into our internal one.
|
||||
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))
|
||||
for _, code := range ext.Codes {
|
||||
|
|
@ -62,12 +51,7 @@ func (w *Witness) fromExtWitness(ext *extWitness) error {
|
|||
for _, node := range ext.State {
|
||||
w.State[string(node)] = struct{}{}
|
||||
}
|
||||
return w.sanitize()
|
||||
}
|
||||
|
||||
// MarshalJSON marshals a witness as JSON.
|
||||
func (w *Witness) MarshalJSON() ([]byte, error) {
|
||||
return w.toExtWitness().MarshalJSON()
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncodeRLP serializes a witness as RLP.
|
||||
|
|
@ -75,15 +59,6 @@ func (w *Witness) EncodeRLP(wr io.Writer) error {
|
|||
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.
|
||||
func (w *Witness) DecodeRLP(s *rlp.Stream) error {
|
||||
var ext extWitness
|
||||
|
|
@ -93,37 +68,9 @@ func (w *Witness) DecodeRLP(s *rlp.Stream) error {
|
|||
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.
|
||||
type extWitness struct {
|
||||
Block *types.Block `json:"block" gencodec:"required"`
|
||||
Headers []*types.Header `json:"headers" gencodec:"required"`
|
||||
Codes [][]byte `json:"codes"`
|
||||
State [][]byte `json:"state"`
|
||||
}
|
||||
|
||||
// extWitnessMarshalling defines the hex marshalling types for a witness.
|
||||
type extWitnessMarshalling struct {
|
||||
Codes []hexutil.Bytes
|
||||
State []hexutil.Bytes
|
||||
Headers []*types.Header
|
||||
Codes [][]byte
|
||||
State [][]byte
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"slices"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"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
|
||||
|
|
@ -36,10 +33,11 @@ type HeaderReader interface {
|
|||
GetHeader(hash common.Hash, number uint64) *types.Header
|
||||
}
|
||||
|
||||
// Witness encompasses a block, state and any other chain data required to apply
|
||||
// a set of transactions and derive a post state/receipt root.
|
||||
// Witness encompasses the state required to apply a set of transactions and
|
||||
// derive a post state/receipt root.
|
||||
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.
|
||||
Codes map[string]struct{} // Set of bytecodes ran or accessed
|
||||
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.
|
||||
func NewWitness(chain HeaderReader, block *types.Block) (*Witness, error) {
|
||||
// Zero out the result fields to avoid accidentally sending them to the verifier
|
||||
header := block.Header()
|
||||
header.Root = common.Hash{}
|
||||
header.ReceiptHash = common.Hash{}
|
||||
|
||||
// Retrieve the parent header, which will *always* be included to act as a
|
||||
// trustless pre-root hash container
|
||||
parent := chain.GetHeader(block.ParentHash(), block.NumberU64()-1)
|
||||
if parent == nil {
|
||||
return nil, errors.New("failed to retrieve parent header")
|
||||
func NewWitness(context *types.Header, chain HeaderReader) (*Witness, error) {
|
||||
// When building witnesses, retrieve the parent header, which will *always*
|
||||
// be included to act as a trustless pre-root hash container
|
||||
var headers []*types.Header
|
||||
if chain != nil {
|
||||
parent := chain.GetHeader(context.ParentHash, context.Number.Uint64()-1)
|
||||
if parent == nil {
|
||||
return nil, errors.New("failed to retrieve parent header")
|
||||
}
|
||||
headers = append(headers, parent)
|
||||
}
|
||||
// Create the wtness with a reconstructed gutted out block
|
||||
return &Witness{
|
||||
Block: types.NewBlockWithHeader(header).WithBody(*block.Body()),
|
||||
context: context,
|
||||
Headers: headers,
|
||||
Codes: make(map[string]struct{}),
|
||||
State: make(map[string]struct{}),
|
||||
Headers: []*types.Header{parent},
|
||||
chain: chain,
|
||||
}, nil
|
||||
}
|
||||
|
|
@ -76,11 +73,8 @@ func NewWitness(chain HeaderReader, block *types.Block) (*Witness, error) {
|
|||
// the chain to cover the block being added.
|
||||
func (w *Witness) AddBlockHash(number uint64) {
|
||||
// Keep pulling in headers until this hash is populated
|
||||
for int(w.Block.NumberU64()-number) > len(w.Headers) {
|
||||
tail := w.Block.Header()
|
||||
if len(w.Headers) > 0 {
|
||||
tail = w.Headers[len(w.Headers)-1]
|
||||
}
|
||||
for int(w.context.Number.Uint64()-number) > len(w.Headers) {
|
||||
tail := w.Headers[len(w.Headers)-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()
|
||||
defer w.lock.Unlock()
|
||||
|
||||
for node := range nodes {
|
||||
w.State[node] = struct{}{}
|
||||
}
|
||||
maps.Copy(w.State, nodes)
|
||||
}
|
||||
|
||||
// Copy deep-copies the witness object. Witness.Block isn't deep-copied as it
|
||||
// is never mutated by Witness
|
||||
func (w *Witness) Copy() *Witness {
|
||||
return &Witness{
|
||||
Block: w.Block,
|
||||
cpy := &Witness{
|
||||
Headers: slices.Clone(w.Headers),
|
||||
Codes: maps.Clone(w.Codes),
|
||||
State: maps.Clone(w.State),
|
||||
chain: w.chain,
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
if w.context != nil {
|
||||
cpy.context = types.CopyHeader(w.context)
|
||||
}
|
||||
bytesCodes := 0
|
||||
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()
|
||||
return cpy
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
||||
## [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]
|
||||
|
||||
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.
|
||||
- `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.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
|
||||
CallValue() *uint256.Int
|
||||
CallInput() []byte
|
||||
ContractCode() []byte
|
||||
}
|
||||
|
||||
// StateDB gives tracers access to the whole state.
|
||||
|
|
@ -42,6 +43,7 @@ type StateDB interface {
|
|||
GetNonce(common.Address) uint64
|
||||
GetCode(common.Address) []byte
|
||||
GetState(common.Address, common.Hash) common.Hash
|
||||
GetTransientState(common.Address, common.Hash) common.Hash
|
||||
Exist(common.Address) bool
|
||||
GetRefund() uint64
|
||||
}
|
||||
|
|
@ -300,12 +302,14 @@ const (
|
|||
GasChangeCallStorageColdAccess GasChangeReason = 13
|
||||
// GasChangeCallFailedExecution is the burning of the remaining gas when the execution failed without a revert.
|
||||
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
|
||||
// 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
|
||||
// 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
|
||||
// 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
|
||||
// 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())
|
||||
return errors.New("duplicate blob entry")
|
||||
}
|
||||
sender, err := p.signer.Sender(tx)
|
||||
sender, err := types.Sender(p.signer, tx)
|
||||
if err != nil {
|
||||
// This path is impossible unless the signature validity changes across
|
||||
// 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
|
||||
nonces []uint64
|
||||
)
|
||||
for txs[0].nonce < next {
|
||||
for len(txs) > 0 && txs[0].nonce < next {
|
||||
ids = append(ids, txs[0].id)
|
||||
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
|
||||
for rem.NumberU64() > add.NumberU64() {
|
||||
for _, tx := range rem.Transactions() {
|
||||
from, _ := p.signer.Sender(tx)
|
||||
from, _ := types.Sender(p.signer, tx)
|
||||
|
||||
discarded[from] = append(discarded[from], tx)
|
||||
transactors[from] = struct{}{}
|
||||
|
|
@ -904,7 +904,7 @@ func (p *BlobPool) reorg(oldHead, newHead *types.Header) (map[common.Address][]*
|
|||
}
|
||||
for add.NumberU64() > rem.NumberU64() {
|
||||
for _, tx := range add.Transactions() {
|
||||
from, _ := p.signer.Sender(tx)
|
||||
from, _ := types.Sender(p.signer, tx)
|
||||
|
||||
included[from] = append(included[from], tx)
|
||||
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 _, tx := range rem.Transactions() {
|
||||
from, _ := p.signer.Sender(tx)
|
||||
from, _ := types.Sender(p.signer, tx)
|
||||
|
||||
discarded[from] = append(discarded[from], tx)
|
||||
transactors[from] = struct{}{}
|
||||
|
|
@ -927,7 +927,7 @@ func (p *BlobPool) reorg(oldHead, newHead *types.Header) (map[common.Address][]*
|
|||
return nil, nil
|
||||
}
|
||||
for _, tx := range add.Transactions() {
|
||||
from, _ := p.signer.Sender(tx)
|
||||
from, _ := types.Sender(p.signer, tx)
|
||||
|
||||
included[from] = append(included[from], tx)
|
||||
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,
|
||||
// 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 {
|
||||
// Generate the set that was lost to reinject into the pool
|
||||
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
|
||||
// adhered to.
|
||||
var (
|
||||
from, _ = p.signer.Sender(tx) // already validated above
|
||||
from, _ = types.Sender(p.signer, tx) // already validated above
|
||||
next = p.state.GetNonce(from)
|
||||
)
|
||||
if uint64(len(p.index[from])) > tx.Nonce()-next {
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ import (
|
|||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"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/crypto"
|
||||
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/holiman/billy"
|
||||
|
|
@ -53,25 +51,14 @@ var (
|
|||
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.
|
||||
type testBlockChain struct {
|
||||
config *params.ChainConfig
|
||||
basefee *uint256.Int
|
||||
blobfee *uint256.Int
|
||||
statedb *state.StateDB
|
||||
|
||||
blocks map[uint64]*types.Block
|
||||
}
|
||||
|
||||
func (bc *testBlockChain) Config() *params.ChainConfig {
|
||||
|
|
@ -79,7 +66,7 @@ func (bc *testBlockChain) Config() *params.ChainConfig {
|
|||
}
|
||||
|
||||
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.
|
||||
|
||||
// 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 {
|
||||
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) {
|
||||
|
|
@ -181,14 +175,14 @@ func makeAddressReserver() txpool.AddressReserver {
|
|||
// the blob pool.
|
||||
func makeTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCap uint64, key *ecdsa.PrivateKey) *types.Transaction {
|
||||
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
|
||||
// without signing it.
|
||||
func makeUnsignedTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCap uint64) *types.BlobTx {
|
||||
return &types.BlobTx{
|
||||
ChainID: uint256.MustFromBig(testChainConfig.ChainID),
|
||||
ChainID: uint256.MustFromBig(params.MainnetChainConfig.ChainID),
|
||||
Nonce: nonce,
|
||||
GasTipCap: uint256.NewInt(gasTipCap),
|
||||
GasFeeCap: uint256.NewInt(gasFeeCap),
|
||||
|
|
@ -229,13 +223,17 @@ func verifyPoolInternals(t *testing.T, pool *BlobPool) {
|
|||
for hash := range seen {
|
||||
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 i := 1; i < len(txs); i++ {
|
||||
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)
|
||||
}
|
||||
}
|
||||
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
|
||||
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
|
||||
// - 9. Duplicate nonces from the same account must be dropped
|
||||
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
|
||||
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
|
||||
// potential hard-forks can get evicted (case 2)
|
||||
tx := types.NewTx(&types.BlobTx{
|
||||
ChainID: uint256.MustFromBig(testChainConfig.ChainID),
|
||||
ChainID: uint256.MustFromBig(params.MainnetChainConfig.ChainID),
|
||||
GasTipCap: new(uint256.Int),
|
||||
GasFeeCap: new(uint256.Int),
|
||||
Gas: 0,
|
||||
|
|
@ -560,7 +558,7 @@ func TestOpenDrops(t *testing.T) {
|
|||
statedb.Commit(0, true)
|
||||
|
||||
chain := &testBlockChain{
|
||||
config: testChainConfig,
|
||||
config: params.MainnetChainConfig,
|
||||
basefee: uint256.NewInt(params.InitialBaseFee),
|
||||
blobfee: uint256.NewInt(params.BlobTxMinBlobGasprice),
|
||||
statedb: statedb,
|
||||
|
|
@ -638,7 +636,7 @@ func TestOpenDrops(t *testing.T) {
|
|||
// - 2. Eviction thresholds are calculated correctly for the sequences
|
||||
// - 3. Balance usage of an account is totals across all transactions
|
||||
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
|
||||
storage, _ := os.MkdirTemp("", "blobpool-")
|
||||
|
|
@ -679,7 +677,7 @@ func TestOpenIndex(t *testing.T) {
|
|||
statedb.Commit(0, true)
|
||||
|
||||
chain := &testBlockChain{
|
||||
config: testChainConfig,
|
||||
config: params.MainnetChainConfig,
|
||||
basefee: uint256.NewInt(params.InitialBaseFee),
|
||||
blobfee: uint256.NewInt(params.BlobTxMinBlobGasprice),
|
||||
statedb: statedb,
|
||||
|
|
@ -727,7 +725,7 @@ func TestOpenIndex(t *testing.T) {
|
|||
// Tests that after indexing all the loaded transactions from disk, a price heap
|
||||
// is correctly constructed based on the head basefee and blobfee.
|
||||
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
|
||||
storage, _ := os.MkdirTemp("", "blobpool-")
|
||||
|
|
@ -781,7 +779,7 @@ func TestOpenHeap(t *testing.T) {
|
|||
statedb.Commit(0, true)
|
||||
|
||||
chain := &testBlockChain{
|
||||
config: testChainConfig,
|
||||
config: params.MainnetChainConfig,
|
||||
basefee: uint256.NewInt(1050),
|
||||
blobfee: uint256.NewInt(105),
|
||||
statedb: statedb,
|
||||
|
|
@ -814,7 +812,7 @@ func TestOpenHeap(t *testing.T) {
|
|||
// Tests that after the pool's previous state is loaded back, any transactions
|
||||
// over the new storage cap will get dropped.
|
||||
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
|
||||
storage, _ := os.MkdirTemp("", "blobpool-")
|
||||
|
|
@ -861,7 +859,7 @@ func TestOpenCap(t *testing.T) {
|
|||
statedb.Commit(0, true)
|
||||
|
||||
chain := &testBlockChain{
|
||||
config: testChainConfig,
|
||||
config: params.MainnetChainConfig,
|
||||
basefee: uint256.NewInt(1050),
|
||||
blobfee: uint256.NewInt(105),
|
||||
statedb: statedb,
|
||||
|
|
@ -906,15 +904,14 @@ func TestOpenCap(t *testing.T) {
|
|||
// specific to the blob pool. It does not do an exhaustive transaction validity
|
||||
// check.
|
||||
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 {
|
||||
balance uint64
|
||||
nonce uint64
|
||||
txs []*types.BlobTx
|
||||
}
|
||||
|
||||
// addtx is a helper sender/tx tuple to represent a new tx addition
|
||||
type addtx struct {
|
||||
from string
|
||||
|
|
@ -925,6 +922,7 @@ func TestAdd(t *testing.T) {
|
|||
tests := []struct {
|
||||
seeds map[string]seed
|
||||
adds []addtx
|
||||
block []addtx
|
||||
}{
|
||||
// Transactions from new accounts should be accepted if their initial
|
||||
// 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 {
|
||||
// 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
|
||||
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)
|
||||
store.Put(blob)
|
||||
}
|
||||
|
|
@ -1286,7 +1303,7 @@ func TestAdd(t *testing.T) {
|
|||
|
||||
// Create a blob pool out of the pre-seeded dats
|
||||
chain := &testBlockChain{
|
||||
config: testChainConfig,
|
||||
config: params.MainnetChainConfig,
|
||||
basefee: uint256.NewInt(1050),
|
||||
blobfee: uint256.NewInt(105),
|
||||
statedb: statedb,
|
||||
|
|
@ -1299,14 +1316,40 @@ func TestAdd(t *testing.T) {
|
|||
|
||||
// Add each transaction one by one, verifying the pool internals in between
|
||||
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) {
|
||||
t.Errorf("test %d, tx %d: adding transaction error mismatch: have %v, want %v", i, j, err, add.err)
|
||||
}
|
||||
verifyPoolInternals(t, pool)
|
||||
}
|
||||
// Verify the pool internals and close down the test
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
|
@ -1325,10 +1368,10 @@ func benchmarkPoolPending(b *testing.B, datacap uint64) {
|
|||
var (
|
||||
basefee = uint64(1050)
|
||||
blobfee = uint64(105)
|
||||
signer = types.LatestSigner(testChainConfig)
|
||||
signer = types.LatestSigner(params.MainnetChainConfig)
|
||||
statedb, _ = state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
|
||||
chain = &testBlockChain{
|
||||
config: testChainConfig,
|
||||
config: params.MainnetChainConfig,
|
||||
basefee: uint256.NewInt(basefee),
|
||||
blobfee: uint256.NewInt(blobfee),
|
||||
statedb: statedb,
|
||||
|
|
|
|||
|
|
@ -19,9 +19,7 @@ package core
|
|||
import (
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"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/vm"
|
||||
)
|
||||
|
|
@ -35,9 +33,6 @@ type Validator interface {
|
|||
|
||||
// ValidateState validates the given statedb and optionally the process result.
|
||||
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.
|
||||
|
|
@ -59,7 +54,7 @@ type Processor interface {
|
|||
// ProcessResult contains the values computed by Process.
|
||||
type ProcessResult struct {
|
||||
Receipts types.Receipts
|
||||
Requests types.Requests
|
||||
Requests [][]byte
|
||||
Logs []*types.Log
|
||||
GasUsed uint64
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
|
|
@ -168,9 +169,8 @@ func (h *Header) SanityCheck() error {
|
|||
func (h *Header) EmptyBody() bool {
|
||||
var (
|
||||
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.
|
||||
|
|
@ -184,7 +184,6 @@ type Body struct {
|
|||
Transactions []*Transaction
|
||||
Uncles []*Header
|
||||
Withdrawals []*Withdrawal `rlp:"optional"`
|
||||
Requests []*Request `rlp:"optional"`
|
||||
}
|
||||
|
||||
// Block represents an Ethereum block.
|
||||
|
|
@ -209,7 +208,6 @@ type Block struct {
|
|||
uncles []*Header
|
||||
transactions Transactions
|
||||
withdrawals Withdrawals
|
||||
requests Requests
|
||||
|
||||
// witness is not an encoded part of the block body.
|
||||
// It is held in Block in order for easy relaying to the places
|
||||
|
|
@ -232,7 +230,6 @@ type extblock struct {
|
|||
Txs []*Transaction
|
||||
Uncles []*Header
|
||||
Withdrawals []*Withdrawal `rlp:"optional"`
|
||||
Requests []*Request `rlp:"optional"`
|
||||
}
|
||||
|
||||
// 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
|
||||
uncles = body.Uncles
|
||||
withdrawals = body.Withdrawals
|
||||
requests = body.Requests
|
||||
)
|
||||
|
||||
if len(txs) == 0 {
|
||||
|
|
@ -288,17 +284,6 @@ func NewBlock(header *Header, body *Body, receipts []*Receipt, hasher TrieHasher
|
|||
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
|
||||
}
|
||||
|
||||
|
|
@ -348,7 +333,7 @@ func (b *Block) DecodeRLP(s *rlp.Stream) error {
|
|||
if err := s.Decode(&eb); err != nil {
|
||||
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))
|
||||
return nil
|
||||
}
|
||||
|
|
@ -360,14 +345,13 @@ func (b *Block) EncodeRLP(w io.Writer) error {
|
|||
Txs: b.transactions,
|
||||
Uncles: b.uncles,
|
||||
Withdrawals: b.withdrawals,
|
||||
Requests: b.requests,
|
||||
})
|
||||
}
|
||||
|
||||
// Body returns the non-header content of the block.
|
||||
// Note the returned data is not an independent copy.
|
||||
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
|
||||
|
|
@ -376,7 +360,6 @@ func (b *Block) Body() *Body {
|
|||
func (b *Block) Uncles() []*Header { return b.uncles }
|
||||
func (b *Block) Transactions() Transactions { return b.transactions }
|
||||
func (b *Block) Withdrawals() Withdrawals { return b.withdrawals }
|
||||
func (b *Block) Requests() Requests { return b.requests }
|
||||
|
||||
func (b *Block) Transaction(hash common.Hash) *Transaction {
|
||||
for _, transaction := range b.transactions {
|
||||
|
|
@ -419,7 +402,8 @@ func (b *Block) BaseFee() *big.Int {
|
|||
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 {
|
||||
var excessBlobGas *uint64
|
||||
|
|
@ -474,6 +458,19 @@ func CalcUncleHash(uncles []*Header) common.Hash {
|
|||
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
|
||||
// header data is copied, changes to header and to the field values
|
||||
// will not affect the block.
|
||||
|
|
@ -501,7 +498,6 @@ func (b *Block) WithBody(body Body) *Block {
|
|||
transactions: slices.Clone(body.Transactions),
|
||||
uncles: make([]*Header, len(body.Uncles)),
|
||||
withdrawals: slices.Clone(body.Withdrawals),
|
||||
requests: slices.Clone(body.Requests),
|
||||
witness: b.witness,
|
||||
}
|
||||
for i := range body.Uncles {
|
||||
|
|
@ -516,7 +512,6 @@ func (b *Block) WithWitness(witness *ExecutionWitness) *Block {
|
|||
transactions: b.transactions,
|
||||
uncles: b.uncles,
|
||||
withdrawals: b.withdrawals,
|
||||
requests: b.requests,
|
||||
witness: witness,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,52 +17,27 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"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
|
||||
|
||||
// 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])
|
||||
}
|
||||
const (
|
||||
depositRequestSize = 192
|
||||
)
|
||||
|
||||
// UnpackIntoDeposit unpacks a serialized DepositEvent.
|
||||
func UnpackIntoDeposit(data []byte) (*Deposit, error) {
|
||||
func DepositLogToRequest(data []byte) ([]byte, error) {
|
||||
if len(data) != 576 {
|
||||
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
|
||||
// elements, skip over the positional data. The first 32 bytes of dynamic
|
||||
// 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
|
||||
// despite BLS public keys being length 48, the value length here is 64. Then
|
||||
// skip over the next length value.
|
||||
copy(d.PublicKey[:], data[b:b+48])
|
||||
copy(request[pubkeyOffset:], data[b:b+48])
|
||||
b += 48 + 16 + 32
|
||||
// WithdrawalCredentials is 32 bytes. Read that value then skip over next
|
||||
// length.
|
||||
copy(d.WithdrawalCredentials[:], data[b:b+32])
|
||||
copy(request[withdrawalCredOffset:], data[b:b+32])
|
||||
b += 32 + 32
|
||||
// Amount is 8 bytes, but it is padded to 32. Skip over it and the next
|
||||
// length.
|
||||
d.Amount = binary.LittleEndian.Uint64(data[b : b+8])
|
||||
copy(request[amountOffset:], data[b:b+8])
|
||||
b += 8 + 24 + 32
|
||||
// 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
|
||||
// Amount is 8 bytes.
|
||||
d.Index = binary.LittleEndian.Uint64(data[b : b+8])
|
||||
|
||||
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,
|
||||
}
|
||||
// Index is 8 bytes.
|
||||
copy(request[indexOffset:], data[b:b+8])
|
||||
return request, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,8 +17,7 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"reflect"
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||
|
|
@ -71,23 +70,26 @@ func FuzzUnpackIntoDeposit(f *testing.F) {
|
|||
copy(sig[:], s)
|
||||
copy(index[:], i)
|
||||
|
||||
want := Deposit{
|
||||
PublicKey: pubkey,
|
||||
WithdrawalCredentials: wxCred,
|
||||
Amount: binary.LittleEndian.Uint64(amount[:]),
|
||||
Signature: sig,
|
||||
Index: binary.LittleEndian.Uint64(index[:]),
|
||||
}
|
||||
out, err := depositABI.Pack("DepositEvent", want.PublicKey[:], want.WithdrawalCredentials[:], amount[:], want.Signature[:], index[:])
|
||||
var enc []byte
|
||||
enc = append(enc, pubkey[:]...)
|
||||
enc = append(enc, wxCred[:]...)
|
||||
enc = append(enc, amount[:]...)
|
||||
enc = append(enc, sig[:]...)
|
||||
enc = append(enc, index[:]...)
|
||||
|
||||
out, err := depositABI.Pack("DepositEvent", pubkey[:], wxCred[:], amount[:], sig[:], index[:])
|
||||
if err != nil {
|
||||
t.Fatalf("error packing deposit: %v", err)
|
||||
}
|
||||
got, err := UnpackIntoDeposit(out[4:])
|
||||
got, err := DepositLogToRequest(out[4:])
|
||||
if err != nil {
|
||||
t.Errorf("error unpacking deposit: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(want, *got) {
|
||||
t.Errorf("roundtrip failed: want %v, got %v", want, got)
|
||||
if len(got) != depositRequestSize {
|
||||
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 = 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 = 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 {
|
||||
keep := make(Transactions, 0, len(a))
|
||||
|
||||
remove := make(map[common.Hash]struct{})
|
||||
remove := make(map[common.Hash]struct{}, b.Len())
|
||||
for _, tx := range b {
|
||||
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
|
||||
// have the current block number available, use MakeSigner instead.
|
||||
func LatestSigner(config *params.ChainConfig) Signer {
|
||||
var signer Signer
|
||||
if config.ChainID != nil {
|
||||
if config.CancunTime != nil {
|
||||
return NewCancunSigner(config.ChainID)
|
||||
}
|
||||
if config.LondonBlock != nil {
|
||||
return NewLondonSigner(config.ChainID)
|
||||
}
|
||||
if config.BerlinBlock != nil {
|
||||
return NewEIP2930Signer(config.ChainID)
|
||||
}
|
||||
if config.EIP155Block != nil {
|
||||
return NewEIP155Signer(config.ChainID)
|
||||
switch {
|
||||
case config.CancunTime != nil:
|
||||
signer = NewCancunSigner(config.ChainID)
|
||||
case config.LondonBlock != nil:
|
||||
signer = NewLondonSigner(config.ChainID)
|
||||
case config.BerlinBlock != nil:
|
||||
signer = NewEIP2930Signer(config.ChainID)
|
||||
case config.EIP155Block != nil:
|
||||
signer = NewEIP155Signer(config.ChainID)
|
||||
default:
|
||||
signer = HomesteadSigner{}
|
||||
}
|
||||
} else {
|
||||
signer = HomesteadSigner{}
|
||||
}
|
||||
return HomesteadSigner{}
|
||||
return signer
|
||||
}
|
||||
|
||||
// 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.
|
||||
// If you have a ChainConfig and know the current block number, use MakeSigner instead.
|
||||
func LatestSignerForChainID(chainID *big.Int) Signer {
|
||||
if chainID == nil {
|
||||
return HomesteadSigner{}
|
||||
var signer Signer
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -546,9 +546,7 @@ func TestYParityJSONUnmarshalling(t *testing.T) {
|
|||
DynamicFeeTxType,
|
||||
BlobTxType,
|
||||
} {
|
||||
txType := txType
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(fmt.Sprintf("txType=%d: %s", txType, test.name), func(t *testing.T) {
|
||||
// Copy the base json
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
func (e *ErrStackUnderflow) Error() string {
|
||||
func (e ErrStackUnderflow) Error() string {
|
||||
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
|
||||
// the maximum allowance.
|
||||
type ErrStackOverflow struct {
|
||||
|
|
@ -62,10 +66,14 @@ type ErrStackOverflow struct {
|
|||
limit int
|
||||
}
|
||||
|
||||
func (e *ErrStackOverflow) Error() string {
|
||||
func (e ErrStackOverflow) Error() string {
|
||||
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.
|
||||
type ErrInvalidOpCode struct {
|
||||
opcode OpCode
|
||||
|
|
|
|||
|
|
@ -448,6 +448,18 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
|
|||
}
|
||||
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
|
||||
// creation fails, the access-list change should not be rolled back.
|
||||
if evm.chainRules.IsEIP2929 {
|
||||
|
|
@ -484,6 +496,17 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
|
|||
if evm.chainRules.IsEIP158 {
|
||||
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)
|
||||
|
||||
// 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
|
||||
// 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) {
|
||||
// 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)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
|
|
@ -533,11 +549,6 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address, valu
|
|||
return ret, ErrCodeStoreOutOfGas
|
||||
}
|
||||
} 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) {
|
||||
return ret, ErrCodeStoreOutOfGas
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import (
|
|||
const (
|
||||
GasQuickStep uint64 = 2
|
||||
GasFastestStep uint64 = 3
|
||||
GasFastishStep uint64 = 4
|
||||
GasFastStep uint64 = 5
|
||||
GasMidStep uint64 = 8
|
||||
GasSlowStep uint64 = 10
|
||||
|
|
|
|||
|
|
@ -502,3 +502,20 @@ func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me
|
|||
}
|
||||
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)
|
||||
EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages
|
||||
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,
|
||||
|
|
@ -83,6 +84,11 @@ func (ctx *ScopeContext) CallInput() []byte {
|
|||
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
|
||||
type EVMInterpreter struct {
|
||||
evm *EVM
|
||||
|
|
|
|||
|
|
@ -42,6 +42,9 @@ type operation struct {
|
|||
|
||||
// memorySize returns the memory size required for the operation
|
||||
memorySize memorySizeFunc
|
||||
|
||||
// undefined denotes if the instruction is not officially defined in the jump table
|
||||
undefined bool
|
||||
}
|
||||
|
||||
var (
|
||||
|
|
@ -58,6 +61,7 @@ var (
|
|||
shanghaiInstructionSet = newShanghaiInstructionSet()
|
||||
cancunInstructionSet = newCancunInstructionSet()
|
||||
verkleInstructionSet = newVerkleInstructionSet()
|
||||
pragueEOFInstructionSet = newPragueEOFInstructionSet()
|
||||
)
|
||||
|
||||
// JumpTable contains the EVM opcodes supported at a given fork.
|
||||
|
|
@ -87,6 +91,16 @@ func newVerkleInstructionSet() JumpTable {
|
|||
return validate(instructionSet)
|
||||
}
|
||||
|
||||
func NewPragueEOFInstructionSetForTesting() JumpTable {
|
||||
return newPragueEOFInstructionSet()
|
||||
}
|
||||
|
||||
func newPragueEOFInstructionSet() JumpTable {
|
||||
instructionSet := newCancunInstructionSet()
|
||||
enableEOF(&instructionSet)
|
||||
return validate(instructionSet)
|
||||
}
|
||||
|
||||
func newCancunInstructionSet() JumpTable {
|
||||
instructionSet := newShanghaiInstructionSet()
|
||||
enable4844(&instructionSet) // EIP-4844 (BLOBHASH opcode)
|
||||
|
|
@ -1059,12 +1073,17 @@ func newFrontierInstructionSet() JumpTable {
|
|||
minStack: minStack(1, 0),
|
||||
maxStack: maxStack(1, 0),
|
||||
},
|
||||
INVALID: {
|
||||
execute: opUndefined,
|
||||
minStack: minStack(0, 0),
|
||||
maxStack: maxStack(0, 0),
|
||||
},
|
||||
}
|
||||
|
||||
// Fill all unassigned slots with opUndefined.
|
||||
for i, entry := range tbl {
|
||||
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