resolve merge conflict

pull/30441/head
Sina Mahmoodi 4 months ago
commit 838fc25dda
  1. 1
      .github/CODEOWNERS
  2. 25
      .travis.yml
  3. 2
      Dockerfile
  4. 9
      Dockerfile.alltools
  5. 12
      accounts/abi/bind/util_test.go
  6. 80
      beacon/engine/gen_ed.go
  7. 6
      beacon/engine/gen_epe.go
  8. 145
      beacon/engine/types.go
  9. 24
      beacon/light/api/light_api.go
  10. 14
      beacon/light/head_tracker.go
  11. 97
      build/checksums.txt
  12. 155
      build/ci.go
  13. 11
      cmd/devp2p/internal/ethtest/suite.go
  14. 200
      cmd/evm/eofparse.go
  15. 166
      cmd/evm/eofparse_test.go
  16. 33
      cmd/evm/internal/t8ntool/execution.go
  17. 133
      cmd/evm/main.go
  18. 6
      cmd/evm/runner.go
  19. 2
      cmd/evm/staterunner.go
  20. 19
      cmd/evm/testdata/eof/eof_benches.txt
  21. 1986
      cmd/evm/testdata/eof/eof_corpus_0.txt
  22. 350
      cmd/evm/testdata/eof/eof_corpus_1.txt
  23. 2336
      cmd/evm/testdata/eof/results.initcode.txt
  24. 2336
      cmd/evm/testdata/eof/results.regular.txt
  25. 64
      cmd/geth/chaincmd.go
  26. 1
      cmd/geth/main.go
  27. 3
      cmd/utils/cmd.go
  28. 18
      cmd/utils/flags.go
  29. 4
      cmd/utils/history_test.go
  30. 35
      consensus/beacon/consensus.go
  31. 6
      consensus/clique/clique_test.go
  32. 2
      consensus/clique/snapshot_test.go
  33. 44
      core/asm/asm.go
  34. 80
      core/asm/asm_test.go
  35. 4
      core/bench_test.go
  36. 44
      core/block_validator.go
  37. 6
      core/block_validator_test.go
  38. 295
      core/blockchain.go
  39. 5
      core/blockchain_insert.go
  40. 11
      core/blockchain_reader.go
  41. 14
      core/blockchain_repair_test.go
  42. 6
      core/blockchain_sethead_test.go
  43. 40
      core/blockchain_snapshot_test.go
  44. 252
      core/blockchain_test.go
  45. 22
      core/chain_makers.go
  46. 4
      core/chain_makers_test.go
  47. 12
      core/dao_test.go
  48. 113
      core/forkchoice.go
  49. 23
      core/genesis.go
  50. 4
      core/genesis_test.go
  51. 15
      core/headerchain.go
  52. 31
      core/headerchain_test.go
  53. 4
      core/rawdb/accessors_trie.go
  54. 12
      core/rawdb/freezer.go
  55. 11
      core/rawdb/freezer_batch.go
  56. 137
      core/rawdb/freezer_table.go
  57. 66
      core/rawdb/freezer_table_test.go
  58. 87
      core/state/access_events.go
  59. 40
      core/state/access_events_test.go
  60. 134
      core/state/database.go
  61. 2
      core/state/iterator_test.go
  62. 218
      core/state/journal.go
  63. 2
      core/state/metrics.go
  64. 313
      core/state/reader.go
  65. 23
      core/state/snapshot/disklayer.go
  66. 9
      core/state/snapshot/generate.go
  67. 38
      core/state/snapshot/snapshot.go
  68. 88
      core/state/state_object.go
  69. 27
      core/state/state_test.go
  70. 302
      core/state/statedb.go
  71. 5
      core/state/statedb_fuzz_test.go
  72. 106
      core/state/statedb_test.go
  73. 8
      core/state/sync_test.go
  74. 60
      core/state/trie_prefetcher.go
  75. 49
      core/state/trie_prefetcher_test.go
  76. 56
      core/state_processor.go
  77. 59
      core/state_processor_test.go
  78. 40
      core/state_transition.go
  79. 29
      core/stateless.go
  80. 7
      core/stateless/database.go
  81. 63
      core/stateless/encoding.go
  82. 74
      core/stateless/gen_encoding_json.go
  83. 85
      core/stateless/witness.go
  84. 20
      core/tracing/CHANGELOG.md
  85. 9
      core/tracing/hooks.go
  86. 16
      core/txpool/blobpool/blobpool.go
  87. 122
      core/txpool/blobpool/blobpool_test.go
  88. 9
      core/txpool/legacypool/legacypool2_test.go
  89. 41
      core/txpool/legacypool/legacypool_test.go
  90. 20
      core/types.go
  91. 67
      core/types/block.go
  92. 103
      core/types/deposit.go
  93. 93
      core/types/deposit_test.go
  94. 70
      core/types/gen_deposit_json.go
  95. 6
      core/types/gen_header_json.go
  96. 18
      core/types/gen_header_rlp.go
  97. 3
      core/types/hashes.go
  98. 157
      core/types/request.go
  99. 2
      core/types/transaction.go
  100. 36
      core/types/transaction_signing.go
  101. Some files were not shown because too many files have changed in this diff Show More

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

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

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

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

@ -82,7 +82,9 @@ func TestWaitDeployed(t *testing.T) {
}() }()
// Send and mine the transaction. // Send and mine the transaction.
backend.Client().SendTransaction(ctx, tx) if err := backend.Client().SendTransaction(ctx, tx); err != nil {
t.Errorf("test %q: failed to send transaction: %v", name, err)
}
backend.Commit() backend.Commit()
select { select {
@ -116,7 +118,9 @@ func TestWaitDeployedCornerCases(t *testing.T) {
tx, _ = types.SignTx(tx, types.LatestSigner(params.AllDevChainProtocolChanges), testKey) tx, _ = types.SignTx(tx, types.LatestSigner(params.AllDevChainProtocolChanges), testKey)
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
backend.Client().SendTransaction(ctx, tx) if err := backend.Client().SendTransaction(ctx, tx); err != nil {
t.Errorf("failed to send transaction: %q", err)
}
backend.Commit() backend.Commit()
notContractCreation := errors.New("tx is not contract creation") notContractCreation := errors.New("tx is not contract creation")
if _, err := bind.WaitDeployed(ctx, backend.Client(), tx); err.Error() != notContractCreation.Error() { if _, err := bind.WaitDeployed(ctx, backend.Client(), tx); err.Error() != notContractCreation.Error() {
@ -134,6 +138,8 @@ func TestWaitDeployedCornerCases(t *testing.T) {
} }
}() }()
backend.Client().SendTransaction(ctx, tx) if err := backend.Client().SendTransaction(ctx, tx); err != nil {
t.Errorf("failed to send transaction: %q", err)
}
cancel() cancel()
} }

@ -17,23 +17,25 @@ var _ = (*executableDataMarshaling)(nil)
// MarshalJSON marshals as JSON. // MarshalJSON marshals as JSON.
func (e ExecutableData) MarshalJSON() ([]byte, error) { func (e ExecutableData) MarshalJSON() ([]byte, error) {
type ExecutableData struct { type ExecutableData struct {
ParentHash common.Hash `json:"parentHash" gencodec:"required"` ParentHash common.Hash `json:"parentHash" gencodec:"required"`
FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"` FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"`
StateRoot common.Hash `json:"stateRoot" gencodec:"required"` StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"` ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
LogsBloom hexutil.Bytes `json:"logsBloom" gencodec:"required"` LogsBloom hexutil.Bytes `json:"logsBloom" gencodec:"required"`
Random common.Hash `json:"prevRandao" gencodec:"required"` Random common.Hash `json:"prevRandao" gencodec:"required"`
Number hexutil.Uint64 `json:"blockNumber" gencodec:"required"` Number hexutil.Uint64 `json:"blockNumber" gencodec:"required"`
GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"` GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"` GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"` Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"`
ExtraData hexutil.Bytes `json:"extraData" gencodec:"required"` ExtraData hexutil.Bytes `json:"extraData" gencodec:"required"`
BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"` BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"`
BlockHash common.Hash `json:"blockHash" gencodec:"required"` BlockHash common.Hash `json:"blockHash" gencodec:"required"`
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"` Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"` Withdrawals []*types.Withdrawal `json:"withdrawals"`
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"` BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"` ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
Deposits types.Deposits `json:"depositRequests"`
ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"`
} }
var enc ExecutableData var enc ExecutableData
enc.ParentHash = e.ParentHash enc.ParentHash = e.ParentHash
@ -58,29 +60,33 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) {
enc.Withdrawals = e.Withdrawals enc.Withdrawals = e.Withdrawals
enc.BlobGasUsed = (*hexutil.Uint64)(e.BlobGasUsed) enc.BlobGasUsed = (*hexutil.Uint64)(e.BlobGasUsed)
enc.ExcessBlobGas = (*hexutil.Uint64)(e.ExcessBlobGas) enc.ExcessBlobGas = (*hexutil.Uint64)(e.ExcessBlobGas)
enc.Deposits = e.Deposits
enc.ExecutionWitness = e.ExecutionWitness
return json.Marshal(&enc) return json.Marshal(&enc)
} }
// UnmarshalJSON unmarshals from JSON. // UnmarshalJSON unmarshals from JSON.
func (e *ExecutableData) UnmarshalJSON(input []byte) error { func (e *ExecutableData) UnmarshalJSON(input []byte) error {
type ExecutableData struct { type ExecutableData struct {
ParentHash *common.Hash `json:"parentHash" gencodec:"required"` ParentHash *common.Hash `json:"parentHash" gencodec:"required"`
FeeRecipient *common.Address `json:"feeRecipient" gencodec:"required"` FeeRecipient *common.Address `json:"feeRecipient" gencodec:"required"`
StateRoot *common.Hash `json:"stateRoot" gencodec:"required"` StateRoot *common.Hash `json:"stateRoot" gencodec:"required"`
ReceiptsRoot *common.Hash `json:"receiptsRoot" gencodec:"required"` ReceiptsRoot *common.Hash `json:"receiptsRoot" gencodec:"required"`
LogsBloom *hexutil.Bytes `json:"logsBloom" gencodec:"required"` LogsBloom *hexutil.Bytes `json:"logsBloom" gencodec:"required"`
Random *common.Hash `json:"prevRandao" gencodec:"required"` Random *common.Hash `json:"prevRandao" gencodec:"required"`
Number *hexutil.Uint64 `json:"blockNumber" gencodec:"required"` Number *hexutil.Uint64 `json:"blockNumber" gencodec:"required"`
GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"` GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"` GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"` Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"`
ExtraData *hexutil.Bytes `json:"extraData" gencodec:"required"` ExtraData *hexutil.Bytes `json:"extraData" gencodec:"required"`
BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"` BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"`
BlockHash *common.Hash `json:"blockHash" gencodec:"required"` BlockHash *common.Hash `json:"blockHash" gencodec:"required"`
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"` Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"` Withdrawals []*types.Withdrawal `json:"withdrawals"`
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"` BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"` ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
Deposits *types.Deposits `json:"depositRequests"`
ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"`
} }
var dec ExecutableData var dec ExecutableData
if err := json.Unmarshal(input, &dec); err != nil { if err := json.Unmarshal(input, &dec); err != nil {
@ -154,5 +160,11 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error {
if dec.ExcessBlobGas != nil { if dec.ExcessBlobGas != nil {
e.ExcessBlobGas = (*uint64)(dec.ExcessBlobGas) e.ExcessBlobGas = (*uint64)(dec.ExcessBlobGas)
} }
if dec.Deposits != nil {
e.Deposits = *dec.Deposits
}
if dec.ExecutionWitness != nil {
e.ExecutionWitness = dec.ExecutionWitness
}
return nil return nil
} }

@ -19,12 +19,14 @@ func (e ExecutionPayloadEnvelope) MarshalJSON() ([]byte, error) {
BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"` BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"`
BlobsBundle *BlobsBundleV1 `json:"blobsBundle"` BlobsBundle *BlobsBundleV1 `json:"blobsBundle"`
Override bool `json:"shouldOverrideBuilder"` Override bool `json:"shouldOverrideBuilder"`
Witness *hexutil.Bytes `json:"witness"`
} }
var enc ExecutionPayloadEnvelope var enc ExecutionPayloadEnvelope
enc.ExecutionPayload = e.ExecutionPayload enc.ExecutionPayload = e.ExecutionPayload
enc.BlockValue = (*hexutil.Big)(e.BlockValue) enc.BlockValue = (*hexutil.Big)(e.BlockValue)
enc.BlobsBundle = e.BlobsBundle enc.BlobsBundle = e.BlobsBundle
enc.Override = e.Override enc.Override = e.Override
enc.Witness = e.Witness
return json.Marshal(&enc) return json.Marshal(&enc)
} }
@ -35,6 +37,7 @@ func (e *ExecutionPayloadEnvelope) UnmarshalJSON(input []byte) error {
BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"` BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"`
BlobsBundle *BlobsBundleV1 `json:"blobsBundle"` BlobsBundle *BlobsBundleV1 `json:"blobsBundle"`
Override *bool `json:"shouldOverrideBuilder"` Override *bool `json:"shouldOverrideBuilder"`
Witness *hexutil.Bytes `json:"witness"`
} }
var dec ExecutionPayloadEnvelope var dec ExecutionPayloadEnvelope
if err := json.Unmarshal(input, &dec); err != nil { if err := json.Unmarshal(input, &dec); err != nil {
@ -54,5 +57,8 @@ func (e *ExecutionPayloadEnvelope) UnmarshalJSON(input []byte) error {
if dec.Override != nil { if dec.Override != nil {
e.Override = *dec.Override e.Override = *dec.Override
} }
if dec.Witness != nil {
e.Witness = dec.Witness
}
return nil return nil
} }

@ -59,23 +59,25 @@ type payloadAttributesMarshaling struct {
// ExecutableData is the data necessary to execute an EL payload. // ExecutableData is the data necessary to execute an EL payload.
type ExecutableData struct { type ExecutableData struct {
ParentHash common.Hash `json:"parentHash" gencodec:"required"` ParentHash common.Hash `json:"parentHash" gencodec:"required"`
FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"` FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"`
StateRoot common.Hash `json:"stateRoot" gencodec:"required"` StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"` ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
LogsBloom []byte `json:"logsBloom" gencodec:"required"` LogsBloom []byte `json:"logsBloom" gencodec:"required"`
Random common.Hash `json:"prevRandao" gencodec:"required"` Random common.Hash `json:"prevRandao" gencodec:"required"`
Number uint64 `json:"blockNumber" gencodec:"required"` Number uint64 `json:"blockNumber" gencodec:"required"`
GasLimit uint64 `json:"gasLimit" gencodec:"required"` GasLimit uint64 `json:"gasLimit" gencodec:"required"`
GasUsed uint64 `json:"gasUsed" gencodec:"required"` GasUsed uint64 `json:"gasUsed" gencodec:"required"`
Timestamp uint64 `json:"timestamp" gencodec:"required"` Timestamp uint64 `json:"timestamp" gencodec:"required"`
ExtraData []byte `json:"extraData" gencodec:"required"` ExtraData []byte `json:"extraData" gencodec:"required"`
BaseFeePerGas *big.Int `json:"baseFeePerGas" gencodec:"required"` BaseFeePerGas *big.Int `json:"baseFeePerGas" gencodec:"required"`
BlockHash common.Hash `json:"blockHash" gencodec:"required"` BlockHash common.Hash `json:"blockHash" gencodec:"required"`
Transactions [][]byte `json:"transactions" gencodec:"required"` Transactions [][]byte `json:"transactions" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"` Withdrawals []*types.Withdrawal `json:"withdrawals"`
BlobGasUsed *uint64 `json:"blobGasUsed"` BlobGasUsed *uint64 `json:"blobGasUsed"`
ExcessBlobGas *uint64 `json:"excessBlobGas"` ExcessBlobGas *uint64 `json:"excessBlobGas"`
Deposits types.Deposits `json:"depositRequests"`
ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"`
} }
// JSON type overrides for executableData. // JSON type overrides for executableData.
@ -92,6 +94,14 @@ type executableDataMarshaling struct {
ExcessBlobGas *hexutil.Uint64 ExcessBlobGas *hexutil.Uint64
} }
// StatelessPayloadStatusV1 is the result of a stateless payload execution.
type StatelessPayloadStatusV1 struct {
Status string `json:"status"`
StateRoot common.Hash `json:"stateRoot"`
ReceiptsRoot common.Hash `json:"receiptsRoot"`
ValidationError *string `json:"validationError"`
}
//go:generate go run github.com/fjl/gencodec -type ExecutionPayloadEnvelope -field-override executionPayloadEnvelopeMarshaling -out gen_epe.go //go:generate go run github.com/fjl/gencodec -type ExecutionPayloadEnvelope -field-override executionPayloadEnvelopeMarshaling -out gen_epe.go
type ExecutionPayloadEnvelope struct { type ExecutionPayloadEnvelope struct {
@ -99,6 +109,7 @@ type ExecutionPayloadEnvelope struct {
BlockValue *big.Int `json:"blockValue" gencodec:"required"` BlockValue *big.Int `json:"blockValue" gencodec:"required"`
BlobsBundle *BlobsBundleV1 `json:"blobsBundle"` BlobsBundle *BlobsBundleV1 `json:"blobsBundle"`
Override bool `json:"shouldOverrideBuilder"` Override bool `json:"shouldOverrideBuilder"`
Witness *hexutil.Bytes `json:"witness"`
} }
type BlobsBundleV1 struct { type BlobsBundleV1 struct {
@ -113,9 +124,10 @@ type executionPayloadEnvelopeMarshaling struct {
} }
type PayloadStatusV1 struct { type PayloadStatusV1 struct {
Status string `json:"status"` Status string `json:"status"`
LatestValidHash *common.Hash `json:"latestValidHash"` Witness *hexutil.Bytes `json:"witness"`
ValidationError *string `json:"validationError"` LatestValidHash *common.Hash `json:"latestValidHash"`
ValidationError *string `json:"validationError"`
} }
type TransitionConfigurationV1 struct { type TransitionConfigurationV1 struct {
@ -196,6 +208,20 @@ func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) {
// Withdrawals value will propagate through the returned block. Empty // Withdrawals value will propagate through the returned block. Empty
// Withdrawals value must be passed via non-nil, length 0 value in data. // Withdrawals value must be passed via non-nil, length 0 value in data.
func ExecutableDataToBlock(data ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (*types.Block, error) { func ExecutableDataToBlock(data ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (*types.Block, error) {
block, err := ExecutableDataToBlockNoHash(data, versionedHashes, beaconRoot)
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) (*types.Block, error) {
txs, err := decodeTransactions(data.Transactions) txs, err := decodeTransactions(data.Transactions)
if err != nil { if err != nil {
return nil, err return nil, err
@ -230,6 +256,19 @@ func ExecutableDataToBlock(data ExecutableData, versionedHashes []common.Hash, b
h := types.DeriveSha(types.Withdrawals(data.Withdrawals), trie.NewStackTrie(nil)) h := types.DeriveSha(types.Withdrawals(data.Withdrawals), trie.NewStackTrie(nil))
withdrawalsRoot = &h withdrawalsRoot = &h
} }
// Compute requestsHash if any requests are non-nil.
var (
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))
}
h := types.DeriveSha(requests, trie.NewStackTrie(nil))
requestsHash = &h
}
header := &types.Header{ header := &types.Header{
ParentHash: data.ParentHash, ParentHash: data.ParentHash,
UncleHash: types.EmptyUncleHash, UncleHash: types.EmptyUncleHash,
@ -250,35 +289,36 @@ func ExecutableDataToBlock(data ExecutableData, versionedHashes []common.Hash, b
ExcessBlobGas: data.ExcessBlobGas, ExcessBlobGas: data.ExcessBlobGas,
BlobGasUsed: data.BlobGasUsed, BlobGasUsed: data.BlobGasUsed,
ParentBeaconRoot: beaconRoot, ParentBeaconRoot: beaconRoot,
RequestsHash: requestsHash,
} }
block := types.NewBlockWithHeader(header).WithBody(types.Body{Transactions: txs, Uncles: nil, Withdrawals: data.Withdrawals}) return types.NewBlockWithHeader(header).
if block.Hash() != data.BlockHash { WithBody(types.Body{Transactions: txs, Uncles: nil, Withdrawals: data.Withdrawals, Requests: requests}).
return nil, fmt.Errorf("blockhash mismatch, want %x, got %x", data.BlockHash, block.Hash()) WithWitness(data.ExecutionWitness),
} nil
return block, nil
} }
// BlockToExecutableData constructs the ExecutableData structure by filling the // BlockToExecutableData constructs the ExecutableData structure by filling the
// fields from the given block. It assumes the given block is post-merge block. // fields from the given block. It assumes the given block is post-merge block.
func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types.BlobTxSidecar) *ExecutionPayloadEnvelope { func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types.BlobTxSidecar) *ExecutionPayloadEnvelope {
data := &ExecutableData{ data := &ExecutableData{
BlockHash: block.Hash(), BlockHash: block.Hash(),
ParentHash: block.ParentHash(), ParentHash: block.ParentHash(),
FeeRecipient: block.Coinbase(), FeeRecipient: block.Coinbase(),
StateRoot: block.Root(), StateRoot: block.Root(),
Number: block.NumberU64(), Number: block.NumberU64(),
GasLimit: block.GasLimit(), GasLimit: block.GasLimit(),
GasUsed: block.GasUsed(), GasUsed: block.GasUsed(),
BaseFeePerGas: block.BaseFee(), BaseFeePerGas: block.BaseFee(),
Timestamp: block.Time(), Timestamp: block.Time(),
ReceiptsRoot: block.ReceiptHash(), ReceiptsRoot: block.ReceiptHash(),
LogsBloom: block.Bloom().Bytes(), LogsBloom: block.Bloom().Bytes(),
Transactions: encodeTransactions(block.Transactions()), Transactions: encodeTransactions(block.Transactions()),
Random: block.MixDigest(), Random: block.MixDigest(),
ExtraData: block.Extra(), ExtraData: block.Extra(),
Withdrawals: block.Withdrawals(), Withdrawals: block.Withdrawals(),
BlobGasUsed: block.BlobGasUsed(), BlobGasUsed: block.BlobGasUsed(),
ExcessBlobGas: block.ExcessBlobGas(), ExcessBlobGas: block.ExcessBlobGas(),
ExecutionWitness: block.ExecutionWitness(),
} }
bundle := BlobsBundleV1{ bundle := BlobsBundleV1{
Commitments: make([]hexutil.Bytes, 0), Commitments: make([]hexutil.Bytes, 0),
@ -292,13 +332,30 @@ func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types.
bundle.Proofs = append(bundle.Proofs, hexutil.Bytes(sidecar.Proofs[j][:])) bundle.Proofs = append(bundle.Proofs, hexutil.Bytes(sidecar.Proofs[j][:]))
} }
} }
setRequests(block.Requests(), data)
return &ExecutionPayloadEnvelope{ExecutionPayload: data, BlockValue: fees, BlobsBundle: &bundle, Override: false} return &ExecutionPayloadEnvelope{ExecutionPayload: data, BlockValue: fees, BlobsBundle: &bundle, Override: false}
} }
// ExecutionPayloadBodyV1 is used in the response to GetPayloadBodiesByHashV1 and GetPayloadBodiesByRangeV1 // setRequests differentiates the different request types and
type ExecutionPayloadBodyV1 struct { // assigns them to the associated fields in ExecutableData.
func setRequests(requests types.Requests, data *ExecutableData) {
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)
}
}
}
// ExecutionPayloadBody is used in the response to GetPayloadBodiesByHash and GetPayloadBodiesByRange
type ExecutionPayloadBody struct {
TransactionData []hexutil.Bytes `json:"transactions"` TransactionData []hexutil.Bytes `json:"transactions"`
Withdrawals []*types.Withdrawal `json:"withdrawals"` Withdrawals []*types.Withdrawal `json:"withdrawals"`
Deposits types.Deposits `json:"depositRequests"`
} }
// Client identifiers to support ClientVersionV1. // Client identifiers to support ClientVersionV1.

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

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

@ -5,55 +5,56 @@
# https://github.com/ethereum/execution-spec-tests/releases/download/v2.1.0/ # https://github.com/ethereum/execution-spec-tests/releases/download/v2.1.0/
ca89c76851b0900bfcc3cbb9a26cbece1f3d7c64a3bed38723e914713290df6c fixtures_develop.tar.gz ca89c76851b0900bfcc3cbb9a26cbece1f3d7c64a3bed38723e914713290df6c fixtures_develop.tar.gz
# version:golang 1.23.0 # version:golang 1.23.2
# https://go.dev/dl/ # https://go.dev/dl/
42b7a8e80d805daa03022ed3fde4321d4c3bf2c990a144165d01eeecd6f699c6 go1.23.0.src.tar.gz 36930162a93df417d90bd22c6e14daff4705baac2b02418edda671cdfa9cd07f go1.23.2.src.tar.gz
257f8560bb4001fb81a5e0ee84f32fecbe18d4450343c9556557d296786847b6 go1.23.0.aix-ppc64.tar.gz 025d77f1780906142023a364c31a572afd7d56d3a3be1e4e562e367ca88d3267 go1.23.2.freebsd-amd64.tar.gz
bc91d2573939a01731413fac0884c329606c1c168883692131ce772669caf27b go1.23.0.darwin-amd64.pkg 0d50bade977b84e173cb350946087f5de8c75f8df19456c3b60c5d58e186089d go1.23.2.windows-arm64.zip
ffd070acf59f054e8691b838f274d540572db0bd09654af851e4e76ab88403dc go1.23.0.darwin-amd64.tar.gz 0edd985dbd6de64d9c88dbc8835bae21203c58444bf26fce0739cbec4eb1b610 go1.23.2.windows-arm64.msi
d73ae741ed449ea842238f76f4b02935277eb867689f84ace0640965b2caf700 go1.23.0.darwin-arm64.pkg 2283d12dfe7c8c8a46a41bbf7d11fe007434e7590cd1b89e221e478640b7ee3a go1.23.2.linux-mips64le.tar.gz
b770812aef17d7b2ea406588e2b97689e9557aac7e646fe76218b216e2c51406 go1.23.0.darwin-arm64.tar.gz 2293c5c3ffc595418308b4059ce214b99f0383cba83232e47a1a8c3b710c24e8 go1.23.2.linux-loong64.tar.gz
8fd2ab5ac8629fc97d25a056693e23f332446603dd3c2b764ccb496872004b0c go1.23.0.dragonfly-amd64.tar.gz 23b93144e754bbcf5eda700e9decbdbd44d29ceedb1bf1de75f95e8a6ea986bb go1.23.2.openbsd-arm64.tar.gz
2c9b76ead3c44f5b3e40e10b980075addb837f2dd05dafe7c0e4c611fd239753 go1.23.0.freebsd-386.tar.gz 2734a5b54905cea45f136c28249e626d0241b865b0637fa1db64bf533d9d843e go1.23.2.netbsd-amd64.tar.gz
2c2252902b87ba605fdc0b12b4c860fe6553c0c5483c12cc471756ebdd8249fe go1.23.0.freebsd-amd64.tar.gz 28af3c40687afdda6b33b300833b6d662716cc2d624fb9fd61a49bdad44cd869 go1.23.2.freebsd-arm.tar.gz
8ec48b8d99a515644ae00e79d093ad3b7645dcaf2a19c0a9c0d97916187f4514 go1.23.0.freebsd-arm.tar.gz 367d522b47c7ce7761a671efcb8b12c8af8f509db1cd6160c91f410ef3201987 go1.23.2.windows-arm.msi
f476bbe8efb0db18155671840545370bfb73903fec04ea897d510569dab16d9c go1.23.0.freebsd-arm64.tar.gz 36b7228bae235eee6c8193f5a956e1a9a17874955affb86b3564709b0fab5874 go1.23.2.linux-mipsle.tar.gz
b0e254b2ea5752b4f1c69934ae43a44bbabf98e0c2843af44e1b6d12390eb551 go1.23.0.freebsd-riscv64.tar.gz 3bd1130a08195d23960b154d2e6eaa80ac7325ebd9d01d74c58b6d12580e6b12 go1.23.2.linux-mips.tar.gz
09716dcc7a2e19891b3d1e2ea68a1aab22838fc664cdc5f82d5f8eef05db78cf go1.23.0.illumos-amd64.tar.gz 3bf66879b38a233c5cbb5d2eb982004117f05d6bf06279e886e087d7c504427d go1.23.2.openbsd-riscv64.tar.gz
0e8a7340c2632e6fb5088d60f95b52be1f8303143e04cd34e9b2314fafc24edd go1.23.0.linux-386.tar.gz 3e80b943d70c7e1633822b42c1aa7234e61da14f13ff8efff7ee6e1347f37648 go1.23.2.netbsd-arm64.tar.gz
905a297f19ead44780548933e0ff1a1b86e8327bb459e92f9c0012569f76f5e3 go1.23.0.linux-amd64.tar.gz 40c0b61971a1a74fd4566c536f682c9d4976fa71d40d9daabc875c06113d0fee go1.23.2.darwin-amd64.pkg
62788056693009bcf7020eedc778cdd1781941c6145eab7688bd087bce0f8659 go1.23.0.linux-arm64.tar.gz 445c0ef19d8692283f4c3a92052cc0568f5a048f4e546105f58e991d4aea54f5 go1.23.2.darwin-amd64.tar.gz
0efa1338e644d7f74064fa7f1016b5da7872b2df0070ea3b56e4fef63192e35b go1.23.0.linux-armv6l.tar.gz 542d3c1705f1c6a1c5a80d5dc62e2e45171af291e755d591c5e6531ef63b454e go1.23.2.linux-amd64.tar.gz
dc8f723ce1a236e85c8b56d1e6749e270314e99dd41b80a58355e7ffcf9ea857 go1.23.0.linux-loong64.tar.gz 560aff7fe1eeadc32248db35ed5c0a81e190d171b6ecec404cf46d808c13e92f go1.23.2.aix-ppc64.tar.gz
3332cc76c73c05b3413cdecccffc29aaa3469f87db8ed9f9b784ebb527ca5352 go1.23.0.linux-mips.tar.gz 5611cd648f5100b73a7d6fd85589a481af18fdbaf9c153a92de9a8e39a6e061f go1.23.2.darwin-arm64.pkg
0ed5cee92433d09fd0816ec5adfbf4b16d712944e833f6342bbe2df18f7826ae go1.23.0.linux-mips64.tar.gz 695aac64532da8d9a243601ffa0411cd763be891fcf7fd2e857eea4ab10b8bcc go1.23.2.plan9-386.tar.gz
06a579dd6d1f9a84bc43cab063e7c759a92a6d4dd01fec3d860f22a32df93406 go1.23.0.linux-mips64le.tar.gz 69b31edcd3d4f7d8bbf9aee2b25cafba30b444ef19bc7a033e15026f7d0cc5c2 go1.23.2.netbsd-arm.tar.gz
d522770d32d6ee963f61331a695c4f8a730f2445b965d8d56db0a2e75c62af57 go1.23.0.linux-mipsle.tar.gz 6ffa4ac1f4368a3121a032917577a4e0a3feaf696c3e98f213b74ac04c318bc4 go1.23.2.plan9-arm.tar.gz
8c884cb4f2593d897f58ec1b0f23f303acf5c78fd101e76cb48d6cb1fe5e90e7 go1.23.0.linux-ppc64.tar.gz 72a6def70300cc804c70073d8b579603d9b39b39b02b3b5d340968d9e7e0e9d4 go1.23.2.windows-386.msi
8b26e20d4d43a4d7641cddbdc0298d7ba3804d910a9e06cda7672970dbf2829d go1.23.0.linux-ppc64le.tar.gz 791ca685ee5ca0f6fe849dc078145cb1323d0ea9dd308e9cca9ba2e7186dbb3d go1.23.2.linux-ppc64.tar.gz
a87726205f1a283247f877ccae8ce147ff4e77ac802382647ac52256eb5642c7 go1.23.0.linux-riscv64.tar.gz 86b5de91fdf7bd9b52c77c62f8762518cf3fc256fe912af9bbff1d073054aa5b go1.23.2.plan9-amd64.tar.gz
003722971de02d97131a4dca2496abdab5cb175a6ee0ed9c8227c5ae9b883e69 go1.23.0.linux-s390x.tar.gz 8734c7cd464a0620f6605bd3f9256bed062f262d0d58e4f45099c329a08ed966 go1.23.2.openbsd-amd64.tar.gz
b203fa2354874c66c40d828e96a6cce1f4e4db192414050a600d0a09b16cafd3 go1.23.0.netbsd-386.tar.gz 980ceb889915695d94b166ca1300250dba76fa37a2d41eca2c5e7727dcb4fb7f go1.23.2.openbsd-arm.tar.gz
1502c82c3ba663959df99c2cc3ca5e7a5e1a75a1495fd26bef697d63bf1f291c go1.23.0.netbsd-amd64.tar.gz a0cf25f236a0fa0a465816fe7f5c930f3b0b90c5c247b09c43a6adeff654e6ae go1.23.2.linux-mips64.tar.gz
dd50c05c7f613522c8d3d74f598bfc1862c0fee9182b738225820c9b458c7be5 go1.23.0.netbsd-arm.tar.gz a13cc0d621af4f35afd90b886c60b1bf66f771939d226dc36fa61a337d90eb30 go1.23.2.openbsd-ppc64.tar.gz
728a94a648f9502cd6175adaac2b770acde6b26f5f92dcbd8c5a1a43cc44bb10 go1.23.0.netbsd-arm64.tar.gz b29ff163b34cb4943c521fcfc1d956eaa6286561089042051a3fab22e79e9283 go1.23.2.windows-arm.zip
e1ff3584778257778a4e3f0093b09044072423aebedf2015a550537853c46745 go1.23.0.openbsd-386.tar.gz bc28fe3002cd65cec65d0e4f6000584dacb8c71bfaff8801dfb532855ca42513 go1.23.2.windows-amd64.zip
d2e30cdb0de256360b51a43f5e551587a7369d8c248120010d5e9432f698a6e8 go1.23.0.openbsd-amd64.tar.gz c164ce7d894b10fd861d7d7b96f1dbea3f993663d9f0c30bc4f8ae3915db8b0c go1.23.2.linux-ppc64le.tar.gz
bd5224c8a5f195f4128c866c0d418f1b61db865a1042913fd07714ed85da28db go1.23.0.openbsd-arm.tar.gz c4ae1087dce4daf45a837f5fca36ac0e29a02ada9addf857f1c426e60bce6f21 go1.23.2.netbsd-386.tar.gz
fc0e0af3a1b4b7168455e8492a5bb6aa96ceaf46321cef1fc04187301c058890 go1.23.0.openbsd-arm64.tar.gz c80cbc5e66d6fb8b0c3300b0dda1fe925c429e199954d3327da2933d9870b041 go1.23.2.windows-amd64.msi
ce7ea9343c7c2ef2700b55b80c45549ce39d164031e4d7bb98bec7ca593ed93d go1.23.0.openbsd-ppc64.tar.gz cb1ed4410f68d8be1156cee0a74fcfbdcd9bca377c83db3a9e1b07eebc6d71ef go1.23.2.linux-386.tar.gz
93b970a8a41f6c89113daaea12e39f2580038af155e823550d0a94a5502c5e2c go1.23.0.plan9-386.tar.gz d1fde255843fec1f7f0611d468effd98e1f4309f589ac13037db07b032f9da35 go1.23.2.openbsd-386.tar.gz
6231862acbb6c1e02b1455b35446b9789b0b4b3230d249953e6957c393a53011 go1.23.0.plan9-amd64.tar.gz d47e40366cd6c6b6ee14b811554cd7dde0351309f4a8a4569ec5ba2bd7689437 go1.23.2.illumos-amd64.tar.gz
632bdd3a1f84b2fe691203423dd2c3f536d4ab250bb52a48e9b05ebf327ae594 go1.23.0.plan9-arm.tar.gz d87031194fe3e01abdcaf3c7302148ade97a7add6eac3fec26765bcb3207b80f go1.23.2.darwin-arm64.tar.gz
16773f85003d9e610960f9af67e00bc6c02359d7914de7224079538cc9c1e93d go1.23.0.solaris-amd64.tar.gz de1f94d7dd3548ba3036de1ea97eb8243881c22a88fcc04cc08c704ded769e02 go1.23.2.linux-s390x.tar.gz
803ef1d4f431d37ac8572ad9b0b65a4f945798208cd16b7f0588960e6b0949ba go1.23.0.windows-386.msi e3286bdde186077e65e961cbe18874d42a461e5b9c472c26572b8d4a98d15c40 go1.23.2.linux-armv6l.tar.gz
09448fedec0cdf98ad12397222e0c8bfc835b1d0894c0015ced653534b8d7427 go1.23.0.windows-386.zip e4d9a1319dfdaa827407855e406c43e85c878a1f93f4f3984c85dce969c8bf70 go1.23.2.freebsd-386.tar.gz
93e1cf580893303d0f6ac10647335de9f0768199d7027d8a361639cae6ab3145 go1.23.0.windows-amd64.msi ea8ab49c5c04c9f94a3f4894d1b030fbce8d10413905fa399f6c39c0a44d5556 go1.23.2.linux-riscv64.tar.gz
d4be481ef73079ee0ad46081d278923aa3fd78db1b3cf147172592f73e14c1ac go1.23.0.windows-amd64.zip eaa3bc377badbdcae144633f8b29bf2680475b72dcd4c135343d3bdc0ba7671e go1.23.2.windows-386.zip
2a361c94879258309343e88c5de5df17f6425df4d74bdf7e333b7298b29f6f29 go1.23.0.windows-arm.msi f11b9b4d4a0679909202fc5e88093d6ff720a8a417bfe6a34d502c3862367039 go1.23.2.freebsd-riscv64.tar.gz
006d93712246a672bdb57906dd5bffcab62facc36169e51a27d52340cdac661f go1.23.0.windows-arm.zip f163b99b03e4bbc64cd30363f1694a08fcd44094415db1f092f13f9d1bb7c28e go1.23.2.dragonfly-amd64.tar.gz
a876ed2bb130d9146152aaf391638abd79dcb3a4f2e9cc59b78709dcef29ced3 go1.23.0.windows-arm64.msi f45af3e1434175ff85620a74c07fb41d6844655f1f2cd2389c5fca6de000f58c go1.23.2.freebsd-arm64.tar.gz
0be62073ef8f5a2d3b9adcefddf18c417dab0a7975c71488ac2694856e2ff976 go1.23.0.windows-arm64.zip f626cdd92fc21a88b31c1251f419c17782933a42903db87a174ce74eeecc66a9 go1.23.2.linux-arm64.tar.gz
fa70d39ddeb6b55241a30b48d7af4e681c6a7d7104e8326c3bc1b12a75e091cc go1.23.2.solaris-amd64.tar.gz
# version:golangci 1.59.0 # version:golangci 1.59.0
# https://github.com/golangci/golangci-lint/releases/ # https://github.com/golangci/golangci-lint/releases/

@ -50,7 +50,6 @@ import (
"path" "path"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strconv"
"strings" "strings"
"time" "time"
@ -159,8 +158,8 @@ func main() {
doLint(os.Args[2:]) doLint(os.Args[2:])
case "archive": case "archive":
doArchive(os.Args[2:]) doArchive(os.Args[2:])
case "docker": case "dockerx":
doDocker(os.Args[2:]) doDockerBuildx(os.Args[2:])
case "debsrc": case "debsrc":
doDebianSource(os.Args[2:]) doDebianSource(os.Args[2:])
case "nsis": case "nsis":
@ -304,7 +303,7 @@ func doTest(cmdline []string) {
gotest := tc.Go("test") gotest := tc.Go("test")
// CI needs a bit more time for the statetests (default 10m). // CI needs a bit more time for the statetests (default 10m).
gotest.Args = append(gotest.Args, "-timeout=20m") gotest.Args = append(gotest.Args, "-timeout=30m")
// Enable CKZG backend in CI. // Enable CKZG backend in CI.
gotest.Args = append(gotest.Args, "-tags=ckzg") gotest.Args = append(gotest.Args, "-tags=ckzg")
@ -723,10 +722,9 @@ func maybeSkipArchive(env build.Environment) {
} }
// Builds the docker images and optionally uploads them to Docker Hub. // Builds the docker images and optionally uploads them to Docker Hub.
func doDocker(cmdline []string) { func doDockerBuildx(cmdline []string) {
var ( var (
image = flag.Bool("image", false, `Whether to build and push an arch specific docker image`) platform = flag.String("platform", "", `Push a multi-arch docker image for the specified architectures (usually "linux/amd64,linux/arm64")`)
manifest = flag.String("manifest", "", `Push a multi-arch docker image for the specified architectures (usually "amd64,arm64")`)
upload = flag.String("upload", "", `Where to upload the docker image (usually "ethereum/client-go")`) upload = flag.String("upload", "", `Where to upload the docker image (usually "ethereum/client-go")`)
) )
flag.CommandLine.Parse(cmdline) flag.CommandLine.Parse(cmdline)
@ -761,129 +759,26 @@ func doDocker(cmdline []string) {
case strings.HasPrefix(env.Tag, "v1."): case strings.HasPrefix(env.Tag, "v1."):
tags = []string{"stable", fmt.Sprintf("release-1.%d", params.VersionMinor), "v" + params.Version} tags = []string{"stable", fmt.Sprintf("release-1.%d", params.VersionMinor), "v" + params.Version}
} }
// If architecture specific image builds are requested, build and push them // Need to create a mult-arch builder
if *image { build.MustRunCommand("docker", "buildx", "create", "--use", "--name", "multi-arch-builder", "--platform", *platform)
build.MustRunCommand("docker", "build", "--build-arg", "COMMIT="+env.Commit, "--build-arg", "VERSION="+params.VersionWithMeta, "--build-arg", "BUILDNUM="+env.Buildnum, "--tag", fmt.Sprintf("%s:TAG", *upload), ".")
build.MustRunCommand("docker", "build", "--build-arg", "COMMIT="+env.Commit, "--build-arg", "VERSION="+params.VersionWithMeta, "--build-arg", "BUILDNUM="+env.Buildnum, "--tag", fmt.Sprintf("%s:alltools-TAG", *upload), "-f", "Dockerfile.alltools", ".") for _, spec := range []struct {
file string
// Tag and upload the images to Docker Hub base string
for _, tag := range tags { }{
gethImage := fmt.Sprintf("%s:%s-%s", *upload, tag, runtime.GOARCH) {file: "Dockerfile", base: fmt.Sprintf("%s:", *upload)},
toolImage := fmt.Sprintf("%s:alltools-%s-%s", *upload, tag, runtime.GOARCH) {file: "Dockerfile.alltools", base: fmt.Sprintf("%s:alltools-", *upload)},
} {
// If the image already exists (non version tag), check the build for _, tag := range tags { // latest, stable etc
// number to prevent overwriting a newer commit if concurrent builds gethImage := fmt.Sprintf("%s%s", spec.base, tag)
// are running. This is still a tiny bit racey if two published are build.MustRunCommand("docker", "buildx", "build",
// done at the same time, but that's extremely unlikely even on the "--build-arg", "COMMIT="+env.Commit,
// master branch. "--build-arg", "VERSION="+params.VersionWithMeta,
for _, img := range []string{gethImage, toolImage} { "--build-arg", "BUILDNUM="+env.Buildnum,
if exec.Command("docker", "pull", img).Run() != nil { "--tag", gethImage,
continue // Generally the only failure is a missing image, which is good "--platform", *platform,
} "--push",
buildnum, err := exec.Command("docker", "inspect", "--format", "{{index .Config.Labels \"buildnum\"}}", img).CombinedOutput() "--file", spec.file, ".")
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)
} }
} }
} }

@ -849,7 +849,16 @@ func (s *Suite) TestBlobViolations(t *utesting.T) {
if code, _, err := conn.Read(); err != nil { if code, _, err := conn.Read(); err != nil {
t.Fatalf("expected disconnect on blob violation, got err: %v", err) t.Fatalf("expected disconnect on blob violation, got err: %v", err)
} else if code != discMsg { } else if code != discMsg {
t.Fatalf("expected disconnect on blob violation, got msg code: %d", code) if code == protoOffset(ethProto)+eth.NewPooledTransactionHashesMsg {
// sometimes we'll get a blob transaction hashes announcement before the disconnect
// because blob transactions are scheduled to be fetched right away.
if code, _, err = conn.Read(); err != nil {
t.Fatalf("expected disconnect on blob violation, got err on second read: %v", err)
}
}
if code != discMsg {
t.Fatalf("expected disconnect on blob violation, got msg code: %d", code)
}
} }
conn.Close() conn.Close()
} }

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

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

@ -66,6 +66,8 @@ type ExecutionResult struct {
WithdrawalsRoot *common.Hash `json:"withdrawalsRoot,omitempty"` WithdrawalsRoot *common.Hash `json:"withdrawalsRoot,omitempty"`
CurrentExcessBlobGas *math.HexOrDecimal64 `json:"currentExcessBlobGas,omitempty"` CurrentExcessBlobGas *math.HexOrDecimal64 `json:"currentExcessBlobGas,omitempty"`
CurrentBlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed,omitempty"` CurrentBlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed,omitempty"`
RequestsHash *common.Hash `json:"requestsRoot,omitempty"`
DepositRequests *types.Deposits `json:"depositRequests,omitempty"`
} }
type ommer struct { type ommer struct {
@ -377,9 +379,31 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
execRs.CurrentExcessBlobGas = (*math.HexOrDecimal64)(&excessBlobGas) execRs.CurrentExcessBlobGas = (*math.HexOrDecimal64)(&excessBlobGas)
execRs.CurrentBlobGasUsed = (*math.HexOrDecimal64)(&blobGasUsed) execRs.CurrentBlobGasUsed = (*math.HexOrDecimal64)(&blobGasUsed)
} }
if chainConfig.IsPrague(vmContext.BlockNumber, vmContext.Time) {
// 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))
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)
}
}
execRs.DepositRequests = &deposits
}
// Re-create statedb instance with new root upon the updated database // Re-create statedb instance with new root upon the updated database
// for accessing latest states. // for accessing latest states.
statedb, err = state.New(root, statedb.Database(), nil) statedb, err = state.New(root, statedb.Database())
if err != nil { if err != nil {
return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not reopen state: %v", err)) return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not reopen state: %v", err))
} }
@ -388,8 +412,9 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
} }
func MakePreState(db ethdb.Database, accounts types.GenesisAlloc) *state.StateDB { func MakePreState(db ethdb.Database, accounts types.GenesisAlloc) *state.StateDB {
sdb := state.NewDatabaseWithConfig(db, &triedb.Config{Preimages: true}) tdb := triedb.NewDatabase(db, &triedb.Config{Preimages: true})
statedb, _ := state.New(types.EmptyRootHash, sdb, nil) sdb := state.NewDatabase(tdb, nil)
statedb, _ := state.New(types.EmptyRootHash, sdb)
for addr, a := range accounts { for addr, a := range accounts {
statedb.SetCode(addr, a.Code) statedb.SetCode(addr, a.Code)
statedb.SetNonce(addr, a.Nonce) statedb.SetNonce(addr, a.Nonce)
@ -400,7 +425,7 @@ func MakePreState(db ethdb.Database, accounts types.GenesisAlloc) *state.StateDB
} }
// Commit and re-open to start with a clean state. // Commit and re-open to start with a clean state.
root, _ := statedb.Commit(0, false) root, _ := statedb.Commit(0, false)
statedb, _ = state.New(root, sdb, nil) statedb, _ = state.New(root, sdb)
return statedb return statedb
} }

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

@ -155,8 +155,8 @@ func runCmd(ctx *cli.Context) error {
}) })
defer triedb.Close() defer triedb.Close()
genesis := genesisConfig.MustCommit(db, triedb) genesis := genesisConfig.MustCommit(db, triedb)
sdb := state.NewDatabaseWithNodeDB(db, triedb) sdb := state.NewDatabase(triedb, nil)
statedb, _ = state.New(genesis.Root(), sdb, nil) statedb, _ = state.New(genesis.Root(), sdb)
chainConfig = genesisConfig.Config chainConfig = genesisConfig.Config
if ctx.String(SenderFlag.Name) != "" { if ctx.String(SenderFlag.Name) != "" {
@ -277,7 +277,7 @@ func runCmd(ctx *cli.Context) error {
fmt.Printf("Failed to commit changes %v\n", err) fmt.Printf("Failed to commit changes %v\n", err)
return err return err
} }
dumpdb, err := state.New(root, sdb, nil) dumpdb, err := state.New(root, sdb)
if err != nil { if err != nil {
fmt.Printf("Failed to open statedb %v\n", err) fmt.Printf("Failed to open statedb %v\n", err)
return err return err

@ -107,7 +107,7 @@ func runStateTest(fname string, cfg vm.Config, dump bool) error {
result.Root = &root result.Root = &root
fmt.Fprintf(os.Stderr, "{\"stateRoot\": \"%#x\"}\n", root) fmt.Fprintf(os.Stderr, "{\"stateRoot\": \"%#x\"}\n", root)
if dump { // Dump any state to aid debugging if dump { // Dump any state to aid debugging
cpy, _ := state.New(root, tstate.StateDB.Database(), nil) cpy, _ := state.New(root, tstate.StateDB.Database())
dump := cpy.RawDump(nil) dump := cpy.RawDump(nil)
result.State = &dump result.State = &dump
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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

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

@ -262,7 +262,6 @@ func ImportHistory(chain *core.BlockChain, db ethdb.Database, dir string, networ
start = time.Now() start = time.Now()
reported = time.Now() reported = time.Now()
imported = 0 imported = 0
forker = core.NewForkChoice(chain, nil)
h = sha256.New() h = sha256.New()
buf = bytes.NewBuffer(nil) buf = bytes.NewBuffer(nil)
) )
@ -305,7 +304,7 @@ func ImportHistory(chain *core.BlockChain, db ethdb.Database, dir string, networ
if err != nil { if err != nil {
return fmt.Errorf("error reading receipts %d: %w", it.Number(), err) return fmt.Errorf("error reading receipts %d: %w", it.Number(), err)
} }
if status, err := chain.HeaderChain().InsertHeaderChain([]*types.Header{block.Header()}, start, forker); err != nil { if status, err := chain.HeaderChain().InsertHeaderChain([]*types.Header{block.Header()}, start); err != nil {
return fmt.Errorf("error inserting header %d: %w", it.Number(), err) return fmt.Errorf("error inserting header %d: %w", it.Number(), err)
} else if status != core.CanonStatTy { } else if status != core.CanonStatTy {
return fmt.Errorf("error inserting header %d, not canon: %v", it.Number(), status) return fmt.Errorf("error inserting header %d, not canon: %v", it.Number(), status)

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

@ -78,7 +78,7 @@ func TestHistoryImportAndExport(t *testing.T) {
}) })
// Initialize BlockChain. // Initialize BlockChain.
chain, err := core.NewBlockChain(db, nil, genesis, nil, ethash.NewFaker(), vm.Config{}, nil, nil) chain, err := core.NewBlockChain(db, nil, genesis, nil, ethash.NewFaker(), vm.Config{}, nil)
if err != nil { if err != nil {
t.Fatalf("unable to initialize chain: %v", err) t.Fatalf("unable to initialize chain: %v", err)
} }
@ -171,7 +171,7 @@ func TestHistoryImportAndExport(t *testing.T) {
}) })
genesis.MustCommit(db2, triedb.NewDatabase(db, triedb.HashDefaults)) genesis.MustCommit(db2, triedb.NewDatabase(db, triedb.HashDefaults))
imported, err := core.NewBlockChain(db2, nil, genesis, nil, ethash.NewFaker(), vm.Config{}, nil, nil) imported, err := core.NewBlockChain(db2, nil, genesis, nil, ethash.NewFaker(), vm.Config{}, nil)
if err != nil { if err != nil {
t.Fatalf("unable to initialize chain: %v", err) t.Fatalf("unable to initialize chain: %v", err)
} }

@ -387,8 +387,39 @@ func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea
// Assign the final state root to header. // Assign the final state root to header.
header.Root = state.IntermediateRoot(true) header.Root = state.IntermediateRoot(true)
// Assemble and return the final block. // Assemble the final block.
return types.NewBlock(header, body, receipts, trie.NewStackTrie(nil)), nil block := types.NewBlock(header, body, receipts, trie.NewStackTrie(nil))
// Create the block witness and attach to block.
// This step needs to happen as late as possible to catch all access events.
if chain.Config().IsVerkle(header.Number, header.Time) {
keys := state.AccessEvents().Keys()
// Open the pre-tree to prove the pre-state against
parent := chain.GetHeaderByNumber(header.Number.Uint64() - 1)
if parent == nil {
return nil, fmt.Errorf("nil parent header for block %d", header.Number)
}
preTrie, err := state.Database().OpenTrie(parent.Root)
if err != nil {
return nil, fmt.Errorf("error opening pre-state tree root: %w", err)
}
vktPreTrie, okpre := preTrie.(*trie.VerkleTrie)
vktPostTrie, okpost := state.GetTrie().(*trie.VerkleTrie)
if okpre && okpost {
if len(keys) > 0 {
verkleProof, stateDiff, err := vktPreTrie.Proof(vktPostTrie, keys, vktPreTrie.FlatdbNodeResolver)
if err != nil {
return nil, fmt.Errorf("error generating verkle proof for block %d: %w", header.Number, err)
}
block = block.WithWitness(&types.ExecutionWitness{StateDiff: stateDiff, VerkleProof: verkleProof})
}
}
}
return block, nil
} }
// Seal generates a new sealing request for the given input block and pushes // Seal generates a new sealing request for the given input block and pushes

@ -55,7 +55,7 @@ func TestReimportMirroredState(t *testing.T) {
copy(genspec.ExtraData[extraVanity:], addr[:]) copy(genspec.ExtraData[extraVanity:], addr[:])
// Generate a batch of blocks, each properly signed // Generate a batch of blocks, each properly signed
chain, _ := core.NewBlockChain(rawdb.NewMemoryDatabase(), nil, genspec, nil, engine, vm.Config{}, nil, nil) chain, _ := core.NewBlockChain(rawdb.NewMemoryDatabase(), nil, genspec, nil, engine, vm.Config{}, nil)
defer chain.Stop() defer chain.Stop()
_, blocks, _ := core.GenerateChainWithGenesis(genspec, engine, 3, func(i int, block *core.BlockGen) { _, blocks, _ := core.GenerateChainWithGenesis(genspec, engine, 3, func(i int, block *core.BlockGen) {
@ -87,7 +87,7 @@ func TestReimportMirroredState(t *testing.T) {
} }
// Insert the first two blocks and make sure the chain is valid // Insert the first two blocks and make sure the chain is valid
db = rawdb.NewMemoryDatabase() db = rawdb.NewMemoryDatabase()
chain, _ = core.NewBlockChain(db, nil, genspec, nil, engine, vm.Config{}, nil, nil) chain, _ = core.NewBlockChain(db, nil, genspec, nil, engine, vm.Config{}, nil)
defer chain.Stop() defer chain.Stop()
if _, err := chain.InsertChain(blocks[:2]); err != nil { if _, err := chain.InsertChain(blocks[:2]); err != nil {
@ -100,7 +100,7 @@ func TestReimportMirroredState(t *testing.T) {
// Simulate a crash by creating a new chain on top of the database, without // Simulate a crash by creating a new chain on top of the database, without
// flushing the dirty states out. Insert the last block, triggering a sidechain // flushing the dirty states out. Insert the last block, triggering a sidechain
// reimport. // reimport.
chain, _ = core.NewBlockChain(db, nil, genspec, nil, engine, vm.Config{}, nil, nil) chain, _ = core.NewBlockChain(db, nil, genspec, nil, engine, vm.Config{}, nil)
defer chain.Stop() defer chain.Stop()
if _, err := chain.InsertChain(blocks[2:]); err != nil { if _, err := chain.InsertChain(blocks[2:]); err != nil {

@ -458,7 +458,7 @@ func (tt *cliqueTest) run(t *testing.T) {
batches[len(batches)-1] = append(batches[len(batches)-1], block) batches[len(batches)-1] = append(batches[len(batches)-1], block)
} }
// Pass all the headers through clique and ensure tallying succeeds // Pass all the headers through clique and ensure tallying succeeds
chain, err := core.NewBlockChain(rawdb.NewMemoryDatabase(), nil, genesis, nil, engine, vm.Config{}, nil, nil) chain, err := core.NewBlockChain(rawdb.NewMemoryDatabase(), nil, genesis, nil, engine, vm.Config{}, nil)
if err != nil { if err != nil {
t.Fatalf("failed to create test chain: %v", err) t.Fatalf("failed to create test chain: %v", err)
} }

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

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

@ -195,7 +195,7 @@ func benchInsertChain(b *testing.B, disk bool, gen func(int, *BlockGen)) {
// Time the insertion of the new chain. // Time the insertion of the new chain.
// State and blocks are stored in the same DB. // State and blocks are stored in the same DB.
chainman, _ := NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) chainman, _ := NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil)
defer chainman.Stop() defer chainman.Stop()
b.ReportAllocs() b.ReportAllocs()
b.ResetTimer() b.ResetTimer()
@ -312,7 +312,7 @@ func benchReadChain(b *testing.B, full bool, count uint64) {
if err != nil { if err != nil {
b.Fatalf("error opening database at %v: %v", dir, err) b.Fatalf("error opening database at %v: %v", dir, err)
} }
chain, err := NewBlockChain(db, &cacheConfig, genesis, nil, ethash.NewFaker(), vm.Config{}, nil, nil) chain, err := NewBlockChain(db, &cacheConfig, genesis, nil, ethash.NewFaker(), vm.Config{}, nil)
if err != nil { if err != nil {
b.Fatalf("error creating chain: %v", err) b.Fatalf("error creating chain: %v", err)
} }

@ -20,10 +20,8 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/stateless"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
@ -121,14 +119,17 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
// ValidateState validates the various changes that happen after a state transition, // ValidateState validates the various changes that happen after a state transition,
// such as amount of used gas, the receipt roots and the state root itself. // such as amount of used gas, the receipt roots and the state root itself.
func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateDB, receipts types.Receipts, usedGas uint64, stateless bool) error { func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateDB, res *ProcessResult, stateless bool) error {
if res == nil {
return fmt.Errorf("nil ProcessResult value")
}
header := block.Header() header := block.Header()
if block.GasUsed() != usedGas { if block.GasUsed() != res.GasUsed {
return fmt.Errorf("invalid gas used (remote: %d local: %d)", block.GasUsed(), usedGas) return fmt.Errorf("invalid gas used (remote: %d local: %d)", block.GasUsed(), res.GasUsed)
} }
// Validate the received block's bloom with the one derived from the generated receipts. // Validate the received block's bloom with the one derived from the generated receipts.
// For valid blocks this should always validate to true. // For valid blocks this should always validate to true.
rbloom := types.CreateBloom(receipts) rbloom := types.CreateBloom(res.Receipts)
if rbloom != header.Bloom { if rbloom != header.Bloom {
return fmt.Errorf("invalid bloom (remote: %x local: %x)", header.Bloom, rbloom) return fmt.Errorf("invalid bloom (remote: %x local: %x)", header.Bloom, rbloom)
} }
@ -138,10 +139,17 @@ func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateD
return nil return nil
} }
// The receipt Trie's root (R = (Tr [[H1, R1], ... [Hn, Rn]])) // The receipt Trie's root (R = (Tr [[H1, R1], ... [Hn, Rn]]))
receiptSha := types.DeriveSha(receipts, trie.NewStackTrie(nil)) receiptSha := types.DeriveSha(res.Receipts, trie.NewStackTrie(nil))
if receiptSha != header.ReceiptHash { if receiptSha != header.ReceiptHash {
return fmt.Errorf("invalid receipt root hash (remote: %x local: %x)", header.ReceiptHash, receiptSha) return fmt.Errorf("invalid receipt root hash (remote: %x local: %x)", header.ReceiptHash, receiptSha)
} }
// 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)
}
}
// Validate the state root against the received state root and throw // Validate the state root against the received state root and throw
// an error if they don't match. // an error if they don't match.
if root := statedb.IntermediateRoot(v.config.IsEIP158(header.Number)); header.Root != root { if root := statedb.IntermediateRoot(v.config.IsEIP158(header.Number)); header.Root != root {
@ -150,28 +158,6 @@ func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateD
return nil return nil
} }
// ValidateWitness cross validates a block execution with stateless remote clients.
//
// Normally we'd distribute the block witness to remote cross validators, wait
// for them to respond and then merge the results. For now, however, it's only
// Geth, so do an internal stateless run.
func (v *BlockValidator) ValidateWitness(witness *stateless.Witness, receiptRoot common.Hash, stateRoot common.Hash) error {
// Run the cross client stateless execution
// TODO(karalabe): Self-stateless for now, swap with other clients
crossReceiptRoot, crossStateRoot, err := ExecuteStateless(v.config, witness)
if err != nil {
return fmt.Errorf("stateless execution failed: %v", err)
}
// Stateless cross execution suceeeded, validate the withheld computed fields
if crossReceiptRoot != receiptRoot {
return fmt.Errorf("cross validator receipt root mismatch (cross: %x local: %x)", crossReceiptRoot, receiptRoot)
}
if crossStateRoot != stateRoot {
return fmt.Errorf("cross validator state root mismatch (cross: %x local: %x)", crossStateRoot, stateRoot)
}
return nil
}
// CalcGasLimit computes the gas limit of the next block after parent. It aims // CalcGasLimit computes the gas limit of the next block after parent. It aims
// to keep the baseline gas close to the provided target, and increase it towards // to keep the baseline gas close to the provided target, and increase it towards
// the target if the baseline gas is lower. // the target if the baseline gas is lower.

@ -50,7 +50,7 @@ func testHeaderVerification(t *testing.T, scheme string) {
headers[i] = block.Header() headers[i] = block.Header()
} }
// Run the header checker for blocks one-by-one, checking for both valid and invalid nonces // Run the header checker for blocks one-by-one, checking for both valid and invalid nonces
chain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) chain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil)
defer chain.Stop() defer chain.Stop()
for i := 0; i < len(blocks); i++ { for i := 0; i < len(blocks); i++ {
@ -160,7 +160,7 @@ func testHeaderVerificationForMerging(t *testing.T, isClique bool) {
postHeaders[i] = block.Header() postHeaders[i] = block.Header()
} }
// Run the header checker for blocks one-by-one, checking for both valid and invalid nonces // Run the header checker for blocks one-by-one, checking for both valid and invalid nonces
chain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{}, nil, nil) chain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{}, nil)
defer chain.Stop() defer chain.Stop()
// Verify the blocks before the merging // Verify the blocks before the merging
@ -201,7 +201,7 @@ func testHeaderVerificationForMerging(t *testing.T, isClique bool) {
t.Fatalf("post-block %d: unexpected result returned: %v", i, result) t.Fatalf("post-block %d: unexpected result returned: %v", i, result)
case <-time.After(25 * time.Millisecond): case <-time.After(25 * time.Millisecond):
} }
chain.InsertBlockWithoutSetHead(postBlocks[i]) chain.InsertBlockWithoutSetHead(postBlocks[i], false)
} }
// Verify the blocks with pre-merge blocks and post-merge blocks // Verify the blocks with pre-merge blocks and post-merge blocks

@ -72,16 +72,17 @@ var (
storageUpdateTimer = metrics.NewRegisteredResettingTimer("chain/storage/updates", nil) storageUpdateTimer = metrics.NewRegisteredResettingTimer("chain/storage/updates", nil)
storageCommitTimer = metrics.NewRegisteredResettingTimer("chain/storage/commits", nil) storageCommitTimer = metrics.NewRegisteredResettingTimer("chain/storage/commits", nil)
snapshotAccountReadTimer = metrics.NewRegisteredResettingTimer("chain/snapshot/account/reads", nil) accountReadSingleTimer = metrics.NewRegisteredResettingTimer("chain/account/single/reads", nil)
snapshotStorageReadTimer = metrics.NewRegisteredResettingTimer("chain/snapshot/storage/reads", nil) storageReadSingleTimer = metrics.NewRegisteredResettingTimer("chain/storage/single/reads", nil)
snapshotCommitTimer = metrics.NewRegisteredResettingTimer("chain/snapshot/commits", nil)
triedbCommitTimer = metrics.NewRegisteredResettingTimer("chain/triedb/commits", nil) snapshotCommitTimer = metrics.NewRegisteredResettingTimer("chain/snapshot/commits", nil)
triedbCommitTimer = metrics.NewRegisteredResettingTimer("chain/triedb/commits", nil)
blockInsertTimer = metrics.NewRegisteredResettingTimer("chain/inserts", nil) blockInsertTimer = metrics.NewRegisteredResettingTimer("chain/inserts", nil)
blockValidationTimer = metrics.NewRegisteredResettingTimer("chain/validation", nil) blockValidationTimer = metrics.NewRegisteredResettingTimer("chain/validation", nil)
blockExecutionTimer = metrics.NewRegisteredResettingTimer("chain/execution", nil) blockCrossValidationTimer = metrics.NewRegisteredResettingTimer("chain/crossvalidation", nil)
blockWriteTimer = metrics.NewRegisteredResettingTimer("chain/write", nil) blockExecutionTimer = metrics.NewRegisteredResettingTimer("chain/execution", nil)
blockWriteTimer = metrics.NewRegisteredResettingTimer("chain/write", nil)
blockReorgMeter = metrics.NewRegisteredMeter("chain/reorg/executes", nil) blockReorgMeter = metrics.NewRegisteredMeter("chain/reorg/executes", nil)
blockReorgAddMeter = metrics.NewRegisteredMeter("chain/reorg/add", nil) blockReorgAddMeter = metrics.NewRegisteredMeter("chain/reorg/add", nil)
@ -217,7 +218,7 @@ type BlockChain struct {
lastWrite uint64 // Last block when the state was flushed lastWrite uint64 // Last block when the state was flushed
flushInterval atomic.Int64 // Time interval (processing time) after which to flush a state flushInterval atomic.Int64 // Time interval (processing time) after which to flush a state
triedb *triedb.Database // The database handler for maintaining trie nodes. triedb *triedb.Database // The database handler for maintaining trie nodes.
stateCache state.Database // State database to reuse between imports (contains state cache) statedb *state.CachingDB // State database to reuse between imports (contains state cache)
txIndexer *txIndexer // Transaction indexer, might be nil if not enabled txIndexer *txIndexer // Transaction indexer, might be nil if not enabled
hc *HeaderChain hc *HeaderChain
@ -256,7 +257,6 @@ type BlockChain struct {
validator Validator // Block and state validator interface validator Validator // Block and state validator interface
prefetcher Prefetcher prefetcher Prefetcher
processor Processor // Block transaction processor interface processor Processor // Block transaction processor interface
forker *ForkChoice
vmConfig vm.Config vmConfig vm.Config
logger *tracing.Hooks logger *tracing.Hooks
} }
@ -264,7 +264,7 @@ type BlockChain struct {
// NewBlockChain returns a fully initialised block chain using information // NewBlockChain returns a fully initialised block chain using information
// available in the database. It initialises the default Ethereum Validator // available in the database. It initialises the default Ethereum Validator
// and Processor. // and Processor.
func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis, overrides *ChainOverrides, engine consensus.Engine, vmConfig vm.Config, shouldPreserve func(header *types.Header) bool, txLookupLimit *uint64) (*BlockChain, error) { func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis, overrides *ChainOverrides, engine consensus.Engine, vmConfig vm.Config, txLookupLimit *uint64) (*BlockChain, error) {
if cacheConfig == nil { if cacheConfig == nil {
cacheConfig = defaultCacheConfig cacheConfig = defaultCacheConfig
} }
@ -309,8 +309,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
return nil, err return nil, err
} }
bc.flushInterval.Store(int64(cacheConfig.TrieTimeLimit)) bc.flushInterval.Store(int64(cacheConfig.TrieTimeLimit))
bc.forker = NewForkChoice(bc, shouldPreserve) bc.statedb = state.NewDatabase(bc.triedb, nil)
bc.stateCache = state.NewDatabaseWithNodeDB(bc.db, bc.triedb)
bc.validator = NewBlockValidator(chainConfig, bc) bc.validator = NewBlockValidator(chainConfig, bc)
bc.prefetcher = newStatePrefetcher(chainConfig, bc.hc) bc.prefetcher = newStatePrefetcher(chainConfig, bc.hc)
bc.processor = NewStateProcessor(chainConfig, bc.hc) bc.processor = NewStateProcessor(chainConfig, bc.hc)
@ -448,7 +447,11 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
AsyncBuild: !bc.cacheConfig.SnapshotWait, AsyncBuild: !bc.cacheConfig.SnapshotWait,
} }
bc.snaps, _ = snapshot.New(snapconfig, bc.db, bc.triedb, head.Root) bc.snaps, _ = snapshot.New(snapconfig, bc.db, bc.triedb, head.Root)
// Re-initialize the state database with snapshot
bc.statedb = state.NewDatabase(bc.triedb, bc.snaps)
} }
// Rewind the chain in case of an incompatible config upgrade. // Rewind the chain in case of an incompatible config upgrade.
if compat, ok := genesisErr.(*params.ConfigCompatError); ok { if compat, ok := genesisErr.(*params.ConfigCompatError); ok {
log.Warn("Rewinding chain to upgrade configuration", "err", compat) log.Warn("Rewinding chain to upgrade configuration", "err", compat)
@ -1240,13 +1243,6 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
// Rewind may have occurred, skip in that case. // Rewind may have occurred, skip in that case.
if bc.CurrentHeader().Number.Cmp(head.Number()) >= 0 { if bc.CurrentHeader().Number.Cmp(head.Number()) >= 0 {
reorg, err := bc.forker.ReorgNeeded(bc.CurrentSnapBlock(), head.Header())
if err != nil {
log.Warn("Reorg failed", "err", err)
return false
} else if !reorg {
return false
}
rawdb.WriteHeadFastBlockHash(bc.db, head.Hash()) rawdb.WriteHeadFastBlockHash(bc.db, head.Hash())
bc.currentSnapBlock.Store(head.Header()) bc.currentSnapBlock.Store(head.Header())
headFastBlockGauge.Update(int64(head.NumberU64())) headFastBlockGauge.Update(int64(head.NumberU64()))
@ -1545,42 +1541,30 @@ func (bc *BlockChain) writeBlockAndSetHead(block *types.Block, receipts []*types
return NonStatTy, err return NonStatTy, err
} }
currentBlock := bc.CurrentBlock() currentBlock := bc.CurrentBlock()
reorg, err := bc.forker.ReorgNeeded(currentBlock, block.Header())
if err != nil { // Reorganise the chain if the parent is not the head block
return NonStatTy, err if block.ParentHash() != currentBlock.Hash() {
} if err := bc.reorg(currentBlock, block); err != nil {
if reorg { return NonStatTy, err
// Reorganise the chain if the parent is not the head block
if block.ParentHash() != currentBlock.Hash() {
if err := bc.reorg(currentBlock, block); err != nil {
return NonStatTy, err
}
} }
status = CanonStatTy
} else {
status = SideStatTy
} }
// Set new head. // Set new head.
if status == CanonStatTy { bc.writeHeadBlock(block)
bc.writeHeadBlock(block)
} bc.chainFeed.Send(ChainEvent{Block: block, Hash: block.Hash(), Logs: logs})
if status == CanonStatTy { if len(logs) > 0 {
bc.chainFeed.Send(ChainEvent{Block: block, Hash: block.Hash(), Logs: logs}) bc.logsFeed.Send(logs)
if len(logs) > 0 { }
bc.logsFeed.Send(logs) // In theory, we should fire a ChainHeadEvent when we inject
} // a canonical block, but sometimes we can insert a batch of
// In theory, we should fire a ChainHeadEvent when we inject // canonical blocks. Avoid firing too many ChainHeadEvents,
// a canonical block, but sometimes we can insert a batch of // we will fire an accumulated ChainHeadEvent and disable fire
// canonical blocks. Avoid firing too many ChainHeadEvents, // event here.
// we will fire an accumulated ChainHeadEvent and disable fire if emitHeadEvent {
// event here. bc.chainHeadFeed.Send(ChainHeadEvent{Block: block})
if emitHeadEvent {
bc.chainHeadFeed.Send(ChainHeadEvent{Block: block})
}
} else {
bc.chainSideFeed.Send(ChainSideEvent{Block: block})
} }
return status, nil return CanonStatTy, nil
} }
// InsertChain attempts to insert the given batch of blocks in to the canonical // InsertChain attempts to insert the given batch of blocks in to the canonical
@ -1615,7 +1599,9 @@ func (bc *BlockChain) InsertChain(chain types.Blocks) (int, error) {
return 0, errChainStopped return 0, errChainStopped
} }
defer bc.chainmu.Unlock() defer bc.chainmu.Unlock()
return bc.insertChain(chain, true)
_, n, err := bc.insertChain(chain, true, false) // No witness collection for mass inserts (would get super large)
return n, err
} }
// insertChain is the internal implementation of InsertChain, which assumes that // insertChain is the internal implementation of InsertChain, which assumes that
@ -1626,12 +1612,11 @@ func (bc *BlockChain) InsertChain(chain types.Blocks) (int, error) {
// racey behaviour. If a sidechain import is in progress, and the historic state // racey behaviour. If a sidechain import is in progress, and the historic state
// is imported, but then new canon-head is added before the actual sidechain // is imported, but then new canon-head is added before the actual sidechain
// completes, then the historic state could be pruned again // completes, then the historic state could be pruned again
func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) { func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness bool) (*stateless.Witness, int, error) {
// If the chain is terminating, don't even bother starting up. // If the chain is terminating, don't even bother starting up.
if bc.insertStopped() { if bc.insertStopped() {
return 0, nil return nil, 0, nil
} }
// Start a parallel signature recovery (signer will fluke on fork transition, minimal perf loss) // Start a parallel signature recovery (signer will fluke on fork transition, minimal perf loss)
SenderCacher.RecoverFromBlocks(types.MakeSigner(bc.chainConfig, chain[0].Number(), chain[0].Time()), chain) SenderCacher.RecoverFromBlocks(types.MakeSigner(bc.chainConfig, chain[0].Number(), chain[0].Time()), chain)
@ -1664,24 +1649,10 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
// 2. The block is stored as a sidechain, and is lying about it's stateroot, and passes a stateroot // 2. The block is stored as a sidechain, and is lying about it's stateroot, and passes a stateroot
// from the canonical chain, which has not been verified. // from the canonical chain, which has not been verified.
// Skip all known blocks that are behind us. // Skip all known blocks that are behind us.
var ( current := bc.CurrentBlock()
reorg bool
current = bc.CurrentBlock()
)
for block != nil && bc.skipBlock(err, it) { for block != nil && bc.skipBlock(err, it) {
reorg, err = bc.forker.ReorgNeeded(current, block.Header()) if block.NumberU64() > current.Number.Uint64() || bc.GetCanonicalHash(block.NumberU64()) != block.Hash() {
if err != nil { break
return it.index, err
}
if reorg {
// Switch to import mode if the forker says the reorg is necessary
// and also the block is not on the canonical chain.
// In eth2 the forker always returns true for reorg decision (blindly trusting
// the external consensus engine), but in order to prevent the unnecessary
// reorgs when importing known blocks, the special case is handled here.
if block.NumberU64() > current.Number.Uint64() || bc.GetCanonicalHash(block.NumberU64()) != block.Hash() {
break
}
} }
log.Debug("Ignoring already known block", "number", block.Number(), "hash", block.Hash()) log.Debug("Ignoring already known block", "number", block.Number(), "hash", block.Hash())
stats.ignored++ stats.ignored++
@ -1699,7 +1670,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
for block != nil && bc.skipBlock(err, it) { for block != nil && bc.skipBlock(err, it) {
log.Debug("Writing previously known block", "number", block.Number(), "hash", block.Hash()) log.Debug("Writing previously known block", "number", block.Number(), "hash", block.Hash())
if err := bc.writeKnownBlock(block); err != nil { if err := bc.writeKnownBlock(block); err != nil {
return it.index, err return nil, it.index, err
} }
lastCanon = block lastCanon = block
@ -1713,12 +1684,12 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
if setHead { if setHead {
// First block is pruned, insert as sidechain and reorg only if TD grows enough // First block is pruned, insert as sidechain and reorg only if TD grows enough
log.Debug("Pruned ancestor, inserting as sidechain", "number", block.Number(), "hash", block.Hash()) log.Debug("Pruned ancestor, inserting as sidechain", "number", block.Number(), "hash", block.Hash())
return bc.insertSideChain(block, it) return bc.insertSideChain(block, it, makeWitness)
} else { } else {
// We're post-merge and the parent is pruned, try to recover the parent state // We're post-merge and the parent is pruned, try to recover the parent state
log.Debug("Pruned ancestor", "number", block.Number(), "hash", block.Hash()) log.Debug("Pruned ancestor", "number", block.Number(), "hash", block.Hash())
_, err := bc.recoverAncestors(block) _, err := bc.recoverAncestors(block, makeWitness)
return it.index, err return nil, it.index, err
} }
// Some other error(except ErrKnownBlock) occurred, abort. // Some other error(except ErrKnownBlock) occurred, abort.
// ErrKnownBlock is allowed here since some known blocks // ErrKnownBlock is allowed here since some known blocks
@ -1726,7 +1697,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
case err != nil && !errors.Is(err, ErrKnownBlock): case err != nil && !errors.Is(err, ErrKnownBlock):
stats.ignored += len(it.chain) stats.ignored += len(it.chain)
bc.reportBlock(block, nil, err) bc.reportBlock(block, nil, err)
return it.index, err return nil, it.index, err
} }
// No validation errors for the first block (or chain prefix skipped) // No validation errors for the first block (or chain prefix skipped)
var activeState *state.StateDB var activeState *state.StateDB
@ -1740,6 +1711,9 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
} }
}() }()
// Track the singleton witness from this chain insertion (if any)
var witness *stateless.Witness
for ; block != nil && err == nil || errors.Is(err, ErrKnownBlock); block, err = it.next() { for ; block != nil && err == nil || errors.Is(err, ErrKnownBlock); block, err = it.next() {
// If the chain is terminating, stop processing blocks // If the chain is terminating, stop processing blocks
if bc.insertStopped() { if bc.insertStopped() {
@ -1776,7 +1750,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
"hash", block.Hash(), "number", block.NumberU64()) "hash", block.Hash(), "number", block.NumberU64())
} }
if err := bc.writeKnownBlock(block); err != nil { if err := bc.writeKnownBlock(block); err != nil {
return it.index, err return nil, it.index, err
} }
stats.processed++ stats.processed++
if bc.logger != nil && bc.logger.OnSkippedBlock != nil { if bc.logger != nil && bc.logger.OnSkippedBlock != nil {
@ -1787,22 +1761,20 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
Safe: bc.CurrentSafeBlock(), Safe: bc.CurrentSafeBlock(),
}) })
} }
// We can assume that logs are empty here, since the only way for consecutive // We can assume that logs are empty here, since the only way for consecutive
// Clique blocks to have the same state is if there are no transactions. // Clique blocks to have the same state is if there are no transactions.
lastCanon = block lastCanon = block
continue continue
} }
// Retrieve the parent block and it's state to execute on top // Retrieve the parent block and it's state to execute on top
start := time.Now() start := time.Now()
parent := it.previous() parent := it.previous()
if parent == nil { if parent == nil {
parent = bc.GetHeader(block.ParentHash(), block.NumberU64()-1) parent = bc.GetHeader(block.ParentHash(), block.NumberU64()-1)
} }
statedb, err := state.New(parent.Root, bc.stateCache, bc.snaps) statedb, err := state.New(parent.Root, bc.statedb)
if err != nil { if err != nil {
return it.index, err return nil, it.index, err
} }
statedb.SetLogger(bc.logger) statedb.SetLogger(bc.logger)
@ -1810,11 +1782,13 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
// while processing transactions. Before Byzantium the prefetcher is mostly // while processing transactions. Before Byzantium the prefetcher is mostly
// useless due to the intermediate root hashing after each transaction. // useless due to the intermediate root hashing after each transaction.
if bc.chainConfig.IsByzantium(block.Number()) { if bc.chainConfig.IsByzantium(block.Number()) {
var witness *stateless.Witness // Generate witnesses either if we're self-testing, or if it's the
if bc.vmConfig.EnableWitnessCollection { // only block being inserted. A bit crude, but witnesses are huge,
witness, err = stateless.NewWitness(bc, block) // so we refuse to make an entire chain of them.
if bc.vmConfig.StatelessSelfValidation || (makeWitness && len(chain) == 1) {
witness, err = stateless.NewWitness(block.Header(), bc)
if err != nil { if err != nil {
return it.index, err return nil, it.index, err
} }
} }
statedb.StartPrefetcher("chain", witness) statedb.StartPrefetcher("chain", witness)
@ -1826,7 +1800,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
var followupInterrupt atomic.Bool var followupInterrupt atomic.Bool
if !bc.cacheConfig.TrieCleanNoPrefetch { if !bc.cacheConfig.TrieCleanNoPrefetch {
if followup, err := it.peek(); followup != nil && err == nil { if followup, err := it.peek(); followup != nil && err == nil {
throwaway, _ := state.New(parent.Root, bc.stateCache, bc.snaps) throwaway, _ := state.New(parent.Root, bc.statedb)
go func(start time.Time, followup *types.Block, throwaway *state.StateDB) { go func(start time.Time, followup *types.Block, throwaway *state.StateDB) {
// Disable tracing for prefetcher executions. // Disable tracing for prefetcher executions.
@ -1846,7 +1820,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
res, err := bc.processBlock(block, statedb, start, setHead) res, err := bc.processBlock(block, statedb, start, setHead)
followupInterrupt.Store(true) followupInterrupt.Store(true)
if err != nil { if err != nil {
return it.index, err return nil, it.index, err
} }
// Report the import stats before returning the various results // Report the import stats before returning the various results
stats.processed++ stats.processed++
@ -1863,7 +1837,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
// After merge we expect few side chains. Simply count // After merge we expect few side chains. Simply count
// all blocks the CL gives us for GC processing time // all blocks the CL gives us for GC processing time
bc.gcproc += res.procTime bc.gcproc += res.procTime
return it.index, nil // Direct block insertion of a single block return witness, it.index, nil // Direct block insertion of a single block
} }
switch res.status { switch res.status {
case CanonStatTy: case CanonStatTy:
@ -1893,7 +1867,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
} }
} }
stats.ignored += it.remaining() stats.ignored += it.remaining()
return it.index, err return witness, it.index, err
} }
// blockProcessingResult is a summary of block processing // blockProcessingResult is a summary of block processing
@ -1924,42 +1898,68 @@ func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, s
// Process block using the parent state as reference point // Process block using the parent state as reference point
pstart := time.Now() pstart := time.Now()
receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig) res, err := bc.processor.Process(block, statedb, bc.vmConfig)
if err != nil { if err != nil {
bc.reportBlock(block, receipts, err) bc.reportBlock(block, res, err)
return nil, err return nil, err
} }
ptime := time.Since(pstart) ptime := time.Since(pstart)
vstart := time.Now() vstart := time.Now()
if err := bc.validator.ValidateState(block, statedb, receipts, usedGas, false); err != nil { if err := bc.validator.ValidateState(block, statedb, res, false); err != nil {
bc.reportBlock(block, receipts, err) bc.reportBlock(block, res, err)
return nil, err return nil, err
} }
vtime := time.Since(vstart) vtime := time.Since(vstart)
if witness := statedb.Witness(); witness != nil { // If witnesses was generated and stateless self-validation requested, do
if err = bc.validator.ValidateWitness(witness, block.ReceiptHash(), block.Root()); err != nil { // that now. Self validation should *never* run in production, it's more of
bc.reportBlock(block, receipts, err) // a tight integration to enable running *all* consensus tests through the
return nil, fmt.Errorf("cross verification failed: %v", err) // witness builder/runner, which would otherwise be impossible due to the
// various invalid chain states/behaviors being contained in those tests.
xvstart := time.Now()
if witness := statedb.Witness(); witness != nil && bc.vmConfig.StatelessSelfValidation {
log.Warn("Running stateless self-validation", "block", block.Number(), "hash", block.Hash())
// Remove critical computed fields from the block to force true recalculation
context := block.Header()
context.Root = common.Hash{}
context.ReceiptHash = common.Hash{}
task := types.NewBlockWithHeader(context).WithBody(*block.Body())
// Run the stateless self-cross-validation
crossStateRoot, crossReceiptRoot, err := ExecuteStateless(bc.chainConfig, task, witness)
if err != nil {
return nil, fmt.Errorf("stateless self-validation failed: %v", err)
}
if crossStateRoot != block.Root() {
return nil, fmt.Errorf("stateless self-validation root mismatch (cross: %x local: %x)", crossStateRoot, block.Root())
}
if crossReceiptRoot != block.ReceiptHash() {
return nil, fmt.Errorf("stateless self-validation receipt root mismatch (cross: %x local: %x)", crossReceiptRoot, block.ReceiptHash())
} }
} }
proctime := time.Since(start) // processing + validation xvtime := time.Since(xvstart)
proctime := time.Since(start) // processing + validation + cross validation
// Update the metrics touched during block processing and validation // Update the metrics touched during block processing and validation
accountReadTimer.Update(statedb.AccountReads) // Account reads are complete(in processing) accountReadTimer.Update(statedb.AccountReads) // Account reads are complete(in processing)
storageReadTimer.Update(statedb.StorageReads) // Storage reads are complete(in processing) storageReadTimer.Update(statedb.StorageReads) // Storage reads are complete(in processing)
snapshotAccountReadTimer.Update(statedb.SnapshotAccountReads) // Account reads are complete(in processing) if statedb.AccountLoaded != 0 {
snapshotStorageReadTimer.Update(statedb.SnapshotStorageReads) // Storage reads are complete(in processing) accountReadSingleTimer.Update(statedb.AccountReads / time.Duration(statedb.AccountLoaded))
accountUpdateTimer.Update(statedb.AccountUpdates) // Account updates are complete(in validation) }
storageUpdateTimer.Update(statedb.StorageUpdates) // Storage updates are complete(in validation) if statedb.StorageLoaded != 0 {
accountHashTimer.Update(statedb.AccountHashes) // Account hashes are complete(in validation) storageReadSingleTimer.Update(statedb.StorageReads / time.Duration(statedb.StorageLoaded))
triehash := statedb.AccountHashes // The time spent on tries hashing }
trieUpdate := statedb.AccountUpdates + statedb.StorageUpdates // The time spent on tries update accountUpdateTimer.Update(statedb.AccountUpdates) // Account updates are complete(in validation)
trieRead := statedb.SnapshotAccountReads + statedb.AccountReads // The time spent on account read storageUpdateTimer.Update(statedb.StorageUpdates) // Storage updates are complete(in validation)
trieRead += statedb.SnapshotStorageReads + statedb.StorageReads // The time spent on storage read accountHashTimer.Update(statedb.AccountHashes) // Account hashes are complete(in validation)
blockExecutionTimer.Update(ptime - trieRead) // The time spent on EVM processing triehash := statedb.AccountHashes // The time spent on tries hashing
blockValidationTimer.Update(vtime - (triehash + trieUpdate)) // The time spent on block validation 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. // Write the block to the chain and get the status.
var ( var (
@ -1968,9 +1968,9 @@ func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, s
) )
if !setHead { if !setHead {
// Don't set the head, only insert the block // Don't set the head, only insert the block
err = bc.writeBlockWithState(block, receipts, statedb) err = bc.writeBlockWithState(block, res.Receipts, statedb)
} else { } else {
status, err = bc.writeBlockAndSetHead(block, receipts, logs, statedb, false) status, err = bc.writeBlockAndSetHead(block, res.Receipts, res.Logs, statedb, false)
} }
if err != nil { if err != nil {
return nil, err return nil, err
@ -1984,7 +1984,7 @@ func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, s
blockWriteTimer.Update(time.Since(wstart) - max(statedb.AccountCommits, statedb.StorageCommits) /* concurrent */ - statedb.SnapshotCommits - statedb.TrieDBCommits) blockWriteTimer.Update(time.Since(wstart) - max(statedb.AccountCommits, statedb.StorageCommits) /* concurrent */ - statedb.SnapshotCommits - statedb.TrieDBCommits)
blockInsertTimer.UpdateSince(start) blockInsertTimer.UpdateSince(start)
return &blockProcessingResult{usedGas: usedGas, procTime: proctime, status: status}, nil return &blockProcessingResult{usedGas: res.GasUsed, procTime: proctime, status: status}, nil
} }
// insertSideChain is called when an import batch hits upon a pruned ancestor // insertSideChain is called when an import batch hits upon a pruned ancestor
@ -1994,11 +1994,10 @@ func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, s
// The method writes all (header-and-body-valid) blocks to disk, then tries to // The method writes all (header-and-body-valid) blocks to disk, then tries to
// switch over to the new chain if the TD exceeded the current chain. // switch over to the new chain if the TD exceeded the current chain.
// insertSideChain is only used pre-merge. // insertSideChain is only used pre-merge.
func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (int, error) { func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator, makeWitness bool) (*stateless.Witness, int, error) {
var ( var (
externTd *big.Int externTd *big.Int
lastBlock = block current = bc.CurrentBlock()
current = bc.CurrentBlock()
) )
// The first sidechain block error is already verified to be ErrPrunedAncestor. // The first sidechain block error is already verified to be ErrPrunedAncestor.
// Since we don't import them here, we expect ErrUnknownAncestor for the remaining // Since we don't import them here, we expect ErrUnknownAncestor for the remaining
@ -2031,7 +2030,7 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (i
// If someone legitimately side-mines blocks, they would still be imported as usual. However, // If someone legitimately side-mines blocks, they would still be imported as usual. However,
// we cannot risk writing unverified blocks to disk when they obviously target the pruning // we cannot risk writing unverified blocks to disk when they obviously target the pruning
// mechanism. // mechanism.
return it.index, errors.New("sidechain ghost-state attack") return nil, it.index, errors.New("sidechain ghost-state attack")
} }
} }
if externTd == nil { if externTd == nil {
@ -2042,29 +2041,13 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (i
if !bc.HasBlock(block.Hash(), block.NumberU64()) { if !bc.HasBlock(block.Hash(), block.NumberU64()) {
start := time.Now() start := time.Now()
if err := bc.writeBlockWithoutState(block, externTd); err != nil { if err := bc.writeBlockWithoutState(block, externTd); err != nil {
return it.index, err return nil, it.index, err
} }
log.Debug("Injected sidechain block", "number", block.Number(), "hash", block.Hash(), log.Debug("Injected sidechain block", "number", block.Number(), "hash", block.Hash(),
"diff", block.Difficulty(), "elapsed", common.PrettyDuration(time.Since(start)), "diff", block.Difficulty(), "elapsed", common.PrettyDuration(time.Since(start)),
"txs", len(block.Transactions()), "gas", block.GasUsed(), "uncles", len(block.Uncles()), "txs", len(block.Transactions()), "gas", block.GasUsed(), "uncles", len(block.Uncles()),
"root", block.Root()) "root", block.Root())
} }
lastBlock = block
}
// At this point, we've written all sidechain blocks to database. Loop ended
// either on some other error or all were processed. If there was some other
// error, we can ignore the rest of those blocks.
//
// If the externTd was larger than our local TD, we now need to reimport the previous
// blocks to regenerate the required state
reorg, err := bc.forker.ReorgNeeded(current, lastBlock.Header())
if err != nil {
return it.index, err
}
if !reorg {
localTd := bc.GetTd(current.Hash(), current.Number.Uint64())
log.Info("Sidechain written to disk", "start", it.first().NumberU64(), "end", it.previous().Number, "sidetd", externTd, "localtd", localTd)
return it.index, err
} }
// Gather all the sidechain hashes (full blocks may be memory heavy) // Gather all the sidechain hashes (full blocks may be memory heavy)
var ( var (
@ -2075,7 +2058,7 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (i
for parent != nil && !bc.HasState(parent.Root) { for parent != nil && !bc.HasState(parent.Root) {
if bc.stateRecoverable(parent.Root) { if bc.stateRecoverable(parent.Root) {
if err := bc.triedb.Recover(parent.Root); err != nil { if err := bc.triedb.Recover(parent.Root); err != nil {
return 0, err return nil, 0, err
} }
break break
} }
@ -2085,7 +2068,7 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (i
parent = bc.GetHeader(parent.ParentHash, parent.Number.Uint64()-1) parent = bc.GetHeader(parent.ParentHash, parent.Number.Uint64()-1)
} }
if parent == nil { if parent == nil {
return it.index, errors.New("missing parent") return nil, it.index, errors.New("missing parent")
} }
// Import all the pruned blocks to make the state available // Import all the pruned blocks to make the state available
var ( var (
@ -2104,30 +2087,30 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (i
// memory here. // memory here.
if len(blocks) >= 2048 || memory > 64*1024*1024 { if len(blocks) >= 2048 || memory > 64*1024*1024 {
log.Info("Importing heavy sidechain segment", "blocks", len(blocks), "start", blocks[0].NumberU64(), "end", block.NumberU64()) log.Info("Importing heavy sidechain segment", "blocks", len(blocks), "start", blocks[0].NumberU64(), "end", block.NumberU64())
if _, err := bc.insertChain(blocks, true); err != nil { if _, _, err := bc.insertChain(blocks, true, false); err != nil {
return 0, err return nil, 0, err
} }
blocks, memory = blocks[:0], 0 blocks, memory = blocks[:0], 0
// If the chain is terminating, stop processing blocks // If the chain is terminating, stop processing blocks
if bc.insertStopped() { if bc.insertStopped() {
log.Debug("Abort during blocks processing") log.Debug("Abort during blocks processing")
return 0, nil return nil, 0, nil
} }
} }
} }
if len(blocks) > 0 { if len(blocks) > 0 {
log.Info("Importing sidechain segment", "start", blocks[0].NumberU64(), "end", blocks[len(blocks)-1].NumberU64()) log.Info("Importing sidechain segment", "start", blocks[0].NumberU64(), "end", blocks[len(blocks)-1].NumberU64())
return bc.insertChain(blocks, true) return bc.insertChain(blocks, true, makeWitness)
} }
return 0, nil return nil, 0, nil
} }
// recoverAncestors finds the closest ancestor with available state and re-execute // recoverAncestors finds the closest ancestor with available state and re-execute
// all the ancestor blocks since that. // all the ancestor blocks since that.
// recoverAncestors is only used post-merge. // recoverAncestors is only used post-merge.
// We return the hash of the latest block that we could correctly validate. // We return the hash of the latest block that we could correctly validate.
func (bc *BlockChain) recoverAncestors(block *types.Block) (common.Hash, error) { func (bc *BlockChain) recoverAncestors(block *types.Block, makeWitness bool) (common.Hash, error) {
// Gather all the sidechain hashes (full blocks may be memory heavy) // Gather all the sidechain hashes (full blocks may be memory heavy)
var ( var (
hashes []common.Hash hashes []common.Hash
@ -2167,7 +2150,7 @@ func (bc *BlockChain) recoverAncestors(block *types.Block) (common.Hash, error)
} else { } else {
b = bc.GetBlock(hashes[i], numbers[i]) b = bc.GetBlock(hashes[i], numbers[i])
} }
if _, err := bc.insertChain(types.Blocks{b}, false); err != nil { if _, _, err := bc.insertChain(types.Blocks{b}, false, makeWitness && i == 0); err != nil {
return b.ParentHash(), err return b.ParentHash(), err
} }
} }
@ -2387,14 +2370,14 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Block) error {
// The key difference between the InsertChain is it won't do the canonical chain // The key difference between the InsertChain is it won't do the canonical chain
// updating. It relies on the additional SetCanonical call to finalize the entire // updating. It relies on the additional SetCanonical call to finalize the entire
// procedure. // procedure.
func (bc *BlockChain) InsertBlockWithoutSetHead(block *types.Block) error { func (bc *BlockChain) InsertBlockWithoutSetHead(block *types.Block, makeWitness bool) (*stateless.Witness, error) {
if !bc.chainmu.TryLock() { if !bc.chainmu.TryLock() {
return errChainStopped return nil, errChainStopped
} }
defer bc.chainmu.Unlock() defer bc.chainmu.Unlock()
_, err := bc.insertChain(types.Blocks{block}, false) witness, _, err := bc.insertChain(types.Blocks{block}, false, makeWitness)
return err return witness, err
} }
// SetCanonical rewinds the chain to set the new head block as the specified // SetCanonical rewinds the chain to set the new head block as the specified
@ -2408,7 +2391,7 @@ func (bc *BlockChain) SetCanonical(head *types.Block) (common.Hash, error) {
// Re-execute the reorged chain in case the head state is missing. // Re-execute the reorged chain in case the head state is missing.
if !bc.HasState(head.Root()) { if !bc.HasState(head.Root()) {
if latestValidHash, err := bc.recoverAncestors(head); err != nil { if latestValidHash, err := bc.recoverAncestors(head, false); err != nil {
return latestValidHash, err return latestValidHash, err
} }
log.Info("Recovered head state", "number", head.Number(), "hash", head.Hash()) log.Info("Recovered head state", "number", head.Number(), "hash", head.Hash())
@ -2485,7 +2468,11 @@ func (bc *BlockChain) skipBlock(err error, it *insertIterator) bool {
} }
// reportBlock logs a bad block error. // reportBlock logs a bad block error.
func (bc *BlockChain) reportBlock(block *types.Block, receipts types.Receipts, err error) { func (bc *BlockChain) reportBlock(block *types.Block, res *ProcessResult, err error) {
var receipts types.Receipts
if res != nil {
receipts = res.Receipts
}
rawdb.WriteBadBlock(bc.db, block) rawdb.WriteBadBlock(bc.db, block)
log.Error(summarizeBadBlock(block, receipts, bc.Config(), err)) log.Error(summarizeBadBlock(block, receipts, bc.Config(), err))
} }
@ -2531,7 +2518,7 @@ func (bc *BlockChain) InsertHeaderChain(chain []*types.Header) (int, error) {
return 0, errChainStopped return 0, errChainStopped
} }
defer bc.chainmu.Unlock() defer bc.chainmu.Unlock()
_, err := bc.hc.InsertHeaderChain(chain, start, bc.forker) _, err := bc.hc.InsertHeaderChain(chain, start)
return 0, err return 0, err
} }

@ -170,11 +170,6 @@ func (it *insertIterator) current() *types.Header {
return it.chain[it.index].Header() return it.chain[it.index].Header()
} }
// first returns the first block in it.
func (it *insertIterator) first() *types.Block {
return it.chain[0]
}
// remaining returns the number of remaining blocks. // remaining returns the number of remaining blocks.
func (it *insertIterator) remaining() int { func (it *insertIterator) remaining() int {
return len(it.chain) - it.index return len(it.chain) - it.index

@ -308,7 +308,7 @@ func (bc *BlockChain) GetTd(hash common.Hash, number uint64) *big.Int {
// HasState checks if state trie is fully present in the database or not. // HasState checks if state trie is fully present in the database or not.
func (bc *BlockChain) HasState(hash common.Hash) bool { func (bc *BlockChain) HasState(hash common.Hash) bool {
_, err := bc.stateCache.OpenTrie(hash) _, err := bc.statedb.OpenTrie(hash)
return err == nil return err == nil
} }
@ -341,12 +341,9 @@ func (bc *BlockChain) stateRecoverable(root common.Hash) bool {
// If the code doesn't exist in the in-memory cache, check the storage with // If the code doesn't exist in the in-memory cache, check the storage with
// new code scheme. // new code scheme.
func (bc *BlockChain) ContractCodeWithPrefix(hash common.Hash) ([]byte, error) { func (bc *BlockChain) ContractCodeWithPrefix(hash common.Hash) ([]byte, error) {
type codeReader interface {
ContractCodeWithPrefix(address common.Address, codeHash common.Hash) ([]byte, error)
}
// TODO(rjl493456442) The associated account address is also required // TODO(rjl493456442) The associated account address is also required
// in Verkle scheme. Fix it once snap-sync is supported for Verkle. // in Verkle scheme. Fix it once snap-sync is supported for Verkle.
return bc.stateCache.(codeReader).ContractCodeWithPrefix(common.Address{}, hash) return bc.statedb.ContractCodeWithPrefix(common.Address{}, hash)
} }
// State returns a new mutable state based on the current HEAD block. // State returns a new mutable state based on the current HEAD block.
@ -356,7 +353,7 @@ func (bc *BlockChain) State() (*state.StateDB, error) {
// StateAt returns a new mutable state based on a particular point in time. // StateAt returns a new mutable state based on a particular point in time.
func (bc *BlockChain) StateAt(root common.Hash) (*state.StateDB, error) { func (bc *BlockChain) StateAt(root common.Hash) (*state.StateDB, error) {
return state.New(root, bc.stateCache, bc.snaps) return state.New(root, bc.statedb)
} }
// Config retrieves the chain's fork configuration. // Config retrieves the chain's fork configuration.
@ -382,7 +379,7 @@ func (bc *BlockChain) Processor() Processor {
// StateCache returns the caching database underpinning the blockchain instance. // StateCache returns the caching database underpinning the blockchain instance.
func (bc *BlockChain) StateCache() state.Database { func (bc *BlockChain) StateCache() state.Database {
return bc.stateCache return bc.statedb
} }
// GasLimit returns the gas limit of the current HEAD block. // GasLimit returns the gas limit of the current HEAD block.

@ -1757,8 +1757,8 @@ func testRepair(t *testing.T, tt *rewindTest, snapshots bool) {
func testRepairWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme string) { func testRepairWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme string) {
// It's hard to follow the test case, visualize the input // It's hard to follow the test case, visualize the input
// log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) // log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, true)))
// fmt.Println(tt.dump(true)) // fmt.Println(tt.dump(false))
// Create a temporary persistent database // Create a temporary persistent database
datadir := t.TempDir() datadir := t.TempDir()
@ -1794,7 +1794,7 @@ func testRepairWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme s
config.SnapshotLimit = 256 config.SnapshotLimit = 256
config.SnapshotWait = true config.SnapshotWait = true
} }
chain, err := NewBlockChain(db, config, gspec, nil, engine, vm.Config{}, nil, nil) chain, err := NewBlockChain(db, config, gspec, nil, engine, vm.Config{}, nil)
if err != nil { if err != nil {
t.Fatalf("Failed to create chain: %v", err) t.Fatalf("Failed to create chain: %v", err)
} }
@ -1859,7 +1859,7 @@ func testRepairWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme s
} }
defer db.Close() defer db.Close()
newChain, err := NewBlockChain(db, config, gspec, nil, engine, vm.Config{}, nil, nil) newChain, err := NewBlockChain(db, config, gspec, nil, engine, vm.Config{}, nil)
if err != nil { if err != nil {
t.Fatalf("Failed to recreate chain: %v", err) t.Fatalf("Failed to recreate chain: %v", err)
} }
@ -1908,7 +1908,7 @@ func TestIssue23496(t *testing.T) {
func testIssue23496(t *testing.T, scheme string) { func testIssue23496(t *testing.T, scheme string) {
// It's hard to follow the test case, visualize the input // It's hard to follow the test case, visualize the input
//log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) // log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, true)))
// Create a temporary persistent database // Create a temporary persistent database
datadir := t.TempDir() datadir := t.TempDir()
@ -1931,7 +1931,7 @@ func testIssue23496(t *testing.T, scheme string) {
} }
engine = ethash.NewFullFaker() engine = ethash.NewFullFaker()
) )
chain, err := NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil, nil) chain, err := NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil)
if err != nil { if err != nil {
t.Fatalf("Failed to create chain: %v", err) t.Fatalf("Failed to create chain: %v", err)
} }
@ -1981,7 +1981,7 @@ func testIssue23496(t *testing.T, scheme string) {
} }
defer db.Close() defer db.Close()
chain, err = NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil, nil) chain, err = NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil)
if err != nil { if err != nil {
t.Fatalf("Failed to recreate chain: %v", err) t.Fatalf("Failed to recreate chain: %v", err)
} }

@ -1961,7 +1961,7 @@ func testSetHead(t *testing.T, tt *rewindTest, snapshots bool) {
func testSetHeadWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme string) { func testSetHeadWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme string) {
// It's hard to follow the test case, visualize the input // It's hard to follow the test case, visualize the input
// log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) // log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, true)))
// fmt.Println(tt.dump(false)) // fmt.Println(tt.dump(false))
// Create a temporary persistent database // Create a temporary persistent database
@ -1997,7 +1997,7 @@ func testSetHeadWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme
config.SnapshotLimit = 256 config.SnapshotLimit = 256
config.SnapshotWait = true config.SnapshotWait = true
} }
chain, err := NewBlockChain(db, config, gspec, nil, engine, vm.Config{}, nil, nil) chain, err := NewBlockChain(db, config, gspec, nil, engine, vm.Config{}, nil)
if err != nil { if err != nil {
t.Fatalf("Failed to create chain: %v", err) t.Fatalf("Failed to create chain: %v", err)
} }
@ -2040,7 +2040,7 @@ func testSetHeadWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme
dbconfig.HashDB = hashdb.Defaults dbconfig.HashDB = hashdb.Defaults
} }
chain.triedb = triedb.NewDatabase(chain.db, dbconfig) chain.triedb = triedb.NewDatabase(chain.db, dbconfig)
chain.stateCache = state.NewDatabaseWithNodeDB(chain.db, chain.triedb) chain.statedb = state.NewDatabase(chain.triedb, chain.snaps)
// Force run a freeze cycle // Force run a freeze cycle
type freezer interface { type freezer interface {

@ -81,7 +81,7 @@ func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Blo
} }
engine = ethash.NewFullFaker() engine = ethash.NewFullFaker()
) )
chain, err := NewBlockChain(db, DefaultCacheConfigWithScheme(basic.scheme), gspec, nil, engine, vm.Config{}, nil, nil) chain, err := NewBlockChain(db, DefaultCacheConfigWithScheme(basic.scheme), gspec, nil, engine, vm.Config{}, nil)
if err != nil { if err != nil {
t.Fatalf("Failed to create chain: %v", err) t.Fatalf("Failed to create chain: %v", err)
} }
@ -222,13 +222,13 @@ type snapshotTest struct {
func (snaptest *snapshotTest) test(t *testing.T) { func (snaptest *snapshotTest) test(t *testing.T) {
// It's hard to follow the test case, visualize the input // It's hard to follow the test case, visualize the input
// log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) // log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, true)))
// fmt.Println(tt.dump()) // fmt.Println(snaptest.dump())
chain, blocks := snaptest.prepare(t) chain, blocks := snaptest.prepare(t)
// Restart the chain normally // Restart the chain normally
chain.Stop() chain.Stop()
newchain, err := NewBlockChain(snaptest.db, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) newchain, err := NewBlockChain(snaptest.db, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil)
if err != nil { if err != nil {
t.Fatalf("Failed to recreate chain: %v", err) t.Fatalf("Failed to recreate chain: %v", err)
} }
@ -245,8 +245,8 @@ type crashSnapshotTest struct {
func (snaptest *crashSnapshotTest) test(t *testing.T) { func (snaptest *crashSnapshotTest) test(t *testing.T) {
// It's hard to follow the test case, visualize the input // It's hard to follow the test case, visualize the input
// log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) // log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, true)))
// fmt.Println(tt.dump()) // fmt.Println(snaptest.dump())
chain, blocks := snaptest.prepare(t) chain, blocks := snaptest.prepare(t)
// Pull the plug on the database, simulating a hard crash // Pull the plug on the database, simulating a hard crash
@ -270,13 +270,13 @@ func (snaptest *crashSnapshotTest) test(t *testing.T) {
// the crash, we do restart twice here: one after the crash and one // the crash, we do restart twice here: one after the crash and one
// after the normal stop. It's used to ensure the broken snapshot // after the normal stop. It's used to ensure the broken snapshot
// can be detected all the time. // can be detected all the time.
newchain, err := NewBlockChain(newdb, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) newchain, err := NewBlockChain(newdb, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil)
if err != nil { if err != nil {
t.Fatalf("Failed to recreate chain: %v", err) t.Fatalf("Failed to recreate chain: %v", err)
} }
newchain.Stop() newchain.Stop()
newchain, err = NewBlockChain(newdb, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) newchain, err = NewBlockChain(newdb, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil)
if err != nil { if err != nil {
t.Fatalf("Failed to recreate chain: %v", err) t.Fatalf("Failed to recreate chain: %v", err)
} }
@ -297,8 +297,8 @@ type gappedSnapshotTest struct {
func (snaptest *gappedSnapshotTest) test(t *testing.T) { func (snaptest *gappedSnapshotTest) test(t *testing.T) {
// It's hard to follow the test case, visualize the input // It's hard to follow the test case, visualize the input
// log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) // log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, true)))
// fmt.Println(tt.dump()) // fmt.Println(snaptest.dump())
chain, blocks := snaptest.prepare(t) chain, blocks := snaptest.prepare(t)
// Insert blocks without enabling snapshot if gapping is required. // Insert blocks without enabling snapshot if gapping is required.
@ -313,7 +313,7 @@ func (snaptest *gappedSnapshotTest) test(t *testing.T) {
SnapshotLimit: 0, SnapshotLimit: 0,
StateScheme: snaptest.scheme, StateScheme: snaptest.scheme,
} }
newchain, err := NewBlockChain(snaptest.db, cacheConfig, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) newchain, err := NewBlockChain(snaptest.db, cacheConfig, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil)
if err != nil { if err != nil {
t.Fatalf("Failed to recreate chain: %v", err) t.Fatalf("Failed to recreate chain: %v", err)
} }
@ -321,7 +321,7 @@ func (snaptest *gappedSnapshotTest) test(t *testing.T) {
newchain.Stop() newchain.Stop()
// Restart the chain with enabling the snapshot // Restart the chain with enabling the snapshot
newchain, err = NewBlockChain(snaptest.db, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) newchain, err = NewBlockChain(snaptest.db, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil)
if err != nil { if err != nil {
t.Fatalf("Failed to recreate chain: %v", err) t.Fatalf("Failed to recreate chain: %v", err)
} }
@ -341,15 +341,15 @@ type setHeadSnapshotTest struct {
func (snaptest *setHeadSnapshotTest) test(t *testing.T) { func (snaptest *setHeadSnapshotTest) test(t *testing.T) {
// It's hard to follow the test case, visualize the input // It's hard to follow the test case, visualize the input
// log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) // log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, true)))
// fmt.Println(tt.dump()) // fmt.Println(snaptest.dump())
chain, blocks := snaptest.prepare(t) chain, blocks := snaptest.prepare(t)
// Rewind the chain if setHead operation is required. // Rewind the chain if setHead operation is required.
chain.SetHead(snaptest.setHead) chain.SetHead(snaptest.setHead)
chain.Stop() chain.Stop()
newchain, err := NewBlockChain(snaptest.db, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) newchain, err := NewBlockChain(snaptest.db, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil)
if err != nil { if err != nil {
t.Fatalf("Failed to recreate chain: %v", err) t.Fatalf("Failed to recreate chain: %v", err)
} }
@ -370,8 +370,8 @@ type wipeCrashSnapshotTest struct {
func (snaptest *wipeCrashSnapshotTest) test(t *testing.T) { func (snaptest *wipeCrashSnapshotTest) test(t *testing.T) {
// It's hard to follow the test case, visualize the input // It's hard to follow the test case, visualize the input
// log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) // log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, true)))
// fmt.Println(tt.dump()) // fmt.Println(snaptest.dump())
chain, blocks := snaptest.prepare(t) chain, blocks := snaptest.prepare(t)
// Firstly, stop the chain properly, with all snapshot journal // Firstly, stop the chain properly, with all snapshot journal
@ -385,7 +385,7 @@ func (snaptest *wipeCrashSnapshotTest) test(t *testing.T) {
SnapshotLimit: 0, SnapshotLimit: 0,
StateScheme: snaptest.scheme, StateScheme: snaptest.scheme,
} }
newchain, err := NewBlockChain(snaptest.db, config, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) newchain, err := NewBlockChain(snaptest.db, config, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil)
if err != nil { if err != nil {
t.Fatalf("Failed to recreate chain: %v", err) t.Fatalf("Failed to recreate chain: %v", err)
} }
@ -402,7 +402,7 @@ func (snaptest *wipeCrashSnapshotTest) test(t *testing.T) {
SnapshotWait: false, // Don't wait rebuild SnapshotWait: false, // Don't wait rebuild
StateScheme: snaptest.scheme, StateScheme: snaptest.scheme,
} }
tmp, err := NewBlockChain(snaptest.db, config, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) tmp, err := NewBlockChain(snaptest.db, config, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil)
if err != nil { if err != nil {
t.Fatalf("Failed to recreate chain: %v", err) t.Fatalf("Failed to recreate chain: %v", err)
} }
@ -411,7 +411,7 @@ func (snaptest *wipeCrashSnapshotTest) test(t *testing.T) {
tmp.triedb.Close() tmp.triedb.Close()
tmp.stopWithoutSaving() tmp.stopWithoutSaving()
newchain, err = NewBlockChain(snaptest.db, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) newchain, err = NewBlockChain(snaptest.db, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil)
if err != nil { if err != nil {
t.Fatalf("Failed to recreate chain: %v", err) t.Fatalf("Failed to recreate chain: %v", err)
} }

@ -22,6 +22,7 @@ import (
"math/big" "math/big"
"math/rand" "math/rand"
"os" "os"
"path"
"sync" "sync"
"testing" "testing"
"time" "time"
@ -61,7 +62,7 @@ func newCanonical(engine consensus.Engine, n int, full bool, scheme string) (eth
} }
) )
// Initialize a fresh chain with only a genesis block // Initialize a fresh chain with only a genesis block
blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil, nil) blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil)
// Create and inject the requested chain // Create and inject the requested chain
if n == 0 { if n == 0 {
@ -159,17 +160,18 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error {
} }
return err return err
} }
statedb, err := state.New(blockchain.GetBlockByHash(block.ParentHash()).Root(), blockchain.stateCache, nil) statedb, err := state.New(blockchain.GetBlockByHash(block.ParentHash()).Root(), blockchain.statedb)
if err != nil { if err != nil {
return err return err
} }
receipts, _, usedGas, err := blockchain.processor.Process(block, statedb, vm.Config{}) res, err := blockchain.processor.Process(block, statedb, vm.Config{})
if err != nil { if err != nil {
blockchain.reportBlock(block, receipts, err) blockchain.reportBlock(block, res, err)
return err return err
} }
if err = blockchain.validator.ValidateState(block, statedb, receipts, usedGas, false); err != nil { err = blockchain.validator.ValidateState(block, statedb, res, false)
blockchain.reportBlock(block, receipts, err) if err != nil {
blockchain.reportBlock(block, res, err)
return err return err
} }
@ -762,7 +764,7 @@ func testFastVsFullChains(t *testing.T, scheme string) {
}) })
// Import the chain as an archive node for the comparison baseline // Import the chain as an archive node for the comparison baseline
archiveDb := rawdb.NewMemoryDatabase() archiveDb := rawdb.NewMemoryDatabase()
archive, _ := NewBlockChain(archiveDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) archive, _ := NewBlockChain(archiveDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil)
defer archive.Stop() defer archive.Stop()
if n, err := archive.InsertChain(blocks); err != nil { if n, err := archive.InsertChain(blocks); err != nil {
@ -770,7 +772,7 @@ func testFastVsFullChains(t *testing.T, scheme string) {
} }
// Fast import the chain as a non-archive node to test // Fast import the chain as a non-archive node to test
fastDb := rawdb.NewMemoryDatabase() fastDb := rawdb.NewMemoryDatabase()
fast, _ := NewBlockChain(fastDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) fast, _ := NewBlockChain(fastDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil)
defer fast.Stop() defer fast.Stop()
headers := make([]*types.Header, len(blocks)) headers := make([]*types.Header, len(blocks))
@ -790,7 +792,7 @@ func testFastVsFullChains(t *testing.T, scheme string) {
} }
defer ancientDb.Close() defer ancientDb.Close()
ancient, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) ancient, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil)
defer ancient.Stop() defer ancient.Stop()
if n, err := ancient.InsertHeaderChain(headers); err != nil { if n, err := ancient.InsertHeaderChain(headers); err != nil {
@ -910,7 +912,7 @@ func testLightVsFastVsFullChainHeads(t *testing.T, scheme string) {
archiveCaching.TrieDirtyDisabled = true archiveCaching.TrieDirtyDisabled = true
archiveCaching.StateScheme = scheme archiveCaching.StateScheme = scheme
archive, _ := NewBlockChain(archiveDb, &archiveCaching, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) archive, _ := NewBlockChain(archiveDb, &archiveCaching, gspec, nil, ethash.NewFaker(), vm.Config{}, nil)
if n, err := archive.InsertChain(blocks); err != nil { if n, err := archive.InsertChain(blocks); err != nil {
t.Fatalf("failed to process block %d: %v", n, err) t.Fatalf("failed to process block %d: %v", n, err)
} }
@ -923,7 +925,7 @@ func testLightVsFastVsFullChainHeads(t *testing.T, scheme string) {
// Import the chain as a non-archive node and ensure all pointers are updated // Import the chain as a non-archive node and ensure all pointers are updated
fastDb := makeDb() fastDb := makeDb()
defer fastDb.Close() defer fastDb.Close()
fast, _ := NewBlockChain(fastDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) fast, _ := NewBlockChain(fastDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil)
defer fast.Stop() defer fast.Stop()
headers := make([]*types.Header, len(blocks)) headers := make([]*types.Header, len(blocks))
@ -943,7 +945,7 @@ func testLightVsFastVsFullChainHeads(t *testing.T, scheme string) {
// Import the chain as a ancient-first node and ensure all pointers are updated // Import the chain as a ancient-first node and ensure all pointers are updated
ancientDb := makeDb() ancientDb := makeDb()
defer ancientDb.Close() defer ancientDb.Close()
ancient, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) ancient, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil)
defer ancient.Stop() defer ancient.Stop()
if n, err := ancient.InsertHeaderChain(headers); err != nil { if n, err := ancient.InsertHeaderChain(headers); err != nil {
@ -962,7 +964,7 @@ func testLightVsFastVsFullChainHeads(t *testing.T, scheme string) {
// Import the chain as a light node and ensure all pointers are updated // Import the chain as a light node and ensure all pointers are updated
lightDb := makeDb() lightDb := makeDb()
defer lightDb.Close() defer lightDb.Close()
light, _ := NewBlockChain(lightDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) light, _ := NewBlockChain(lightDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil)
if n, err := light.InsertHeaderChain(headers); err != nil { if n, err := light.InsertHeaderChain(headers); err != nil {
t.Fatalf("failed to insert header %d: %v", n, err) t.Fatalf("failed to insert header %d: %v", n, err)
} }
@ -1035,7 +1037,7 @@ func testChainTxReorgs(t *testing.T, scheme string) {
}) })
// Import the chain. This runs all block validation rules. // Import the chain. This runs all block validation rules.
db := rawdb.NewMemoryDatabase() db := rawdb.NewMemoryDatabase()
blockchain, _ := NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) blockchain, _ := NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil)
if i, err := blockchain.InsertChain(chain); err != nil { if i, err := blockchain.InsertChain(chain); err != nil {
t.Fatalf("failed to insert original chain[%d]: %v", i, err) t.Fatalf("failed to insert original chain[%d]: %v", i, err)
} }
@ -1109,7 +1111,7 @@ func testLogReorgs(t *testing.T, scheme string) {
signer = types.LatestSigner(gspec.Config) signer = types.LatestSigner(gspec.Config)
) )
blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil)
defer blockchain.Stop() defer blockchain.Stop()
rmLogsCh := make(chan RemovedLogsEvent) rmLogsCh := make(chan RemovedLogsEvent)
@ -1165,7 +1167,7 @@ func testLogRebirth(t *testing.T, scheme string) {
gspec = &Genesis{Config: params.TestChainConfig, Alloc: types.GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000000)}}} gspec = &Genesis{Config: params.TestChainConfig, Alloc: types.GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000000)}}}
signer = types.LatestSigner(gspec.Config) signer = types.LatestSigner(gspec.Config)
engine = ethash.NewFaker() engine = ethash.NewFaker()
blockchain, _ = NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil, nil) blockchain, _ = NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil)
) )
defer blockchain.Stop() defer blockchain.Stop()
@ -1246,7 +1248,7 @@ func testSideLogRebirth(t *testing.T, scheme string) {
addr1 = crypto.PubkeyToAddress(key1.PublicKey) addr1 = crypto.PubkeyToAddress(key1.PublicKey)
gspec = &Genesis{Config: params.TestChainConfig, Alloc: types.GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000000)}}} gspec = &Genesis{Config: params.TestChainConfig, Alloc: types.GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000000)}}}
signer = types.LatestSigner(gspec.Config) signer = types.LatestSigner(gspec.Config)
blockchain, _ = NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) blockchain, _ = NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil)
) )
defer blockchain.Stop() defer blockchain.Stop()
@ -1265,7 +1267,7 @@ func testSideLogRebirth(t *testing.T, scheme string) {
} }
checkLogEvents(t, newLogCh, rmLogsCh, 0, 0) checkLogEvents(t, newLogCh, rmLogsCh, 0, 0)
// Generate side chain with lower difficulty // Generate side chain with lower difficulty, after the merge, the chain will be accepted even if it is lower difficulty
genDb, sideChain, _ := GenerateChainWithGenesis(gspec, ethash.NewFaker(), 2, func(i int, gen *BlockGen) { genDb, sideChain, _ := GenerateChainWithGenesis(gspec, ethash.NewFaker(), 2, func(i int, gen *BlockGen) {
if i == 1 { if i == 1 {
tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), 1000000, gen.header.BaseFee, logCode), signer, key1) tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), 1000000, gen.header.BaseFee, logCode), signer, key1)
@ -1278,14 +1280,14 @@ func testSideLogRebirth(t *testing.T, scheme string) {
if _, err := blockchain.InsertChain(sideChain); err != nil { if _, err := blockchain.InsertChain(sideChain); err != nil {
t.Fatalf("failed to insert forked chain: %v", err) t.Fatalf("failed to insert forked chain: %v", err)
} }
checkLogEvents(t, newLogCh, rmLogsCh, 0, 0) checkLogEvents(t, newLogCh, rmLogsCh, 1, 0)
// Generate a new block based on side chain. // Generate a new block based on side chain. Should not emit any events anymore.
newBlocks, _ := GenerateChain(gspec.Config, sideChain[len(sideChain)-1], ethash.NewFaker(), genDb, 1, func(i int, gen *BlockGen) {}) newBlocks, _ := GenerateChain(gspec.Config, sideChain[len(sideChain)-1], ethash.NewFaker(), genDb, 1, func(i int, gen *BlockGen) {})
if _, err := blockchain.InsertChain(newBlocks); err != nil { if _, err := blockchain.InsertChain(newBlocks); err != nil {
t.Fatalf("failed to insert forked chain: %v", err) t.Fatalf("failed to insert forked chain: %v", err)
} }
checkLogEvents(t, newLogCh, rmLogsCh, 1, 0) checkLogEvents(t, newLogCh, rmLogsCh, 0, 0)
} }
func checkLogEvents(t *testing.T, logsCh <-chan []*types.Log, rmLogsCh <-chan RemovedLogsEvent, wantNew, wantRemoved int) { func checkLogEvents(t *testing.T, logsCh <-chan []*types.Log, rmLogsCh <-chan RemovedLogsEvent, wantNew, wantRemoved int) {
@ -1345,7 +1347,7 @@ func testReorgSideEvent(t *testing.T, scheme string) {
} }
signer = types.LatestSigner(gspec.Config) signer = types.LatestSigner(gspec.Config)
) )
blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil)
defer blockchain.Stop() defer blockchain.Stop()
_, chain, _ := GenerateChainWithGenesis(gspec, ethash.NewFaker(), 3, func(i int, gen *BlockGen) {}) _, chain, _ := GenerateChainWithGenesis(gspec, ethash.NewFaker(), 3, func(i int, gen *BlockGen) {})
@ -1369,15 +1371,10 @@ func testReorgSideEvent(t *testing.T, scheme string) {
t.Fatalf("failed to insert chain: %v", err) t.Fatalf("failed to insert chain: %v", err)
} }
// first two block of the secondary chain are for a brief moment considered
// side chains because up to that point the first one is considered the
// heavier chain.
expectedSideHashes := map[common.Hash]bool{ expectedSideHashes := map[common.Hash]bool{
replacementBlocks[0].Hash(): true, chain[0].Hash(): true,
replacementBlocks[1].Hash(): true, chain[1].Hash(): true,
chain[0].Hash(): true, chain[2].Hash(): true,
chain[1].Hash(): true,
chain[2].Hash(): true,
} }
i := 0 i := 0
@ -1402,7 +1399,7 @@ done:
timeout.Reset(timeoutDura) timeout.Reset(timeoutDura)
case <-timeout.C: case <-timeout.C:
t.Fatal("Timeout. Possibly not all blocks were triggered for sideevent") t.Fatalf("Timeout. Possibly not all blocks were triggered for sideevent: %v", i)
} }
} }
@ -1529,7 +1526,7 @@ func testEIP155Transition(t *testing.T, scheme string) {
} }
}) })
blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil)
defer blockchain.Stop() defer blockchain.Stop()
if _, err := blockchain.InsertChain(blocks); err != nil { if _, err := blockchain.InsertChain(blocks); err != nil {
@ -1622,7 +1619,7 @@ func testEIP161AccountRemoval(t *testing.T, scheme string) {
block.AddTx(tx) block.AddTx(tx)
}) })
// account must exist pre eip 161 // account must exist pre eip 161
blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil)
defer blockchain.Stop() defer blockchain.Stop()
if _, err := blockchain.InsertChain(types.Blocks{blocks[0]}); err != nil { if _, err := blockchain.InsertChain(types.Blocks{blocks[0]}); err != nil {
@ -1680,7 +1677,7 @@ func testBlockchainHeaderchainReorgConsistency(t *testing.T, scheme string) {
} }
// Import the canonical and fork chain side by side, verifying the current block // Import the canonical and fork chain side by side, verifying the current block
// and current header consistency // and current header consistency
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil, nil) chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil)
if err != nil { if err != nil {
t.Fatalf("failed to create tester chain: %v", err) t.Fatalf("failed to create tester chain: %v", err)
} }
@ -1724,7 +1721,7 @@ func TestTrieForkGC(t *testing.T) {
forks[i] = fork[0] forks[i] = fork[0]
} }
// Import the canonical and fork chain side by side, forcing the trie cache to cache both // Import the canonical and fork chain side by side, forcing the trie cache to cache both
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, genesis, nil, engine, vm.Config{}, nil, nil) chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, genesis, nil, engine, vm.Config{}, nil)
if err != nil { if err != nil {
t.Fatalf("failed to create tester chain: %v", err) t.Fatalf("failed to create tester chain: %v", err)
} }
@ -1770,7 +1767,7 @@ func testLargeReorgTrieGC(t *testing.T, scheme string) {
db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false) db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
defer db.Close() defer db.Close()
chain, err := NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil, nil) chain, err := NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil)
if err != nil { if err != nil {
t.Fatalf("failed to create tester chain: %v", err) t.Fatalf("failed to create tester chain: %v", err)
} }
@ -1786,18 +1783,15 @@ func testLargeReorgTrieGC(t *testing.T, scheme string) {
if chain.HasState(shared[len(shared)-1].Root()) { if chain.HasState(shared[len(shared)-1].Root()) {
t.Fatalf("common-but-old ancestor still cache") t.Fatalf("common-but-old ancestor still cache")
} }
// Import the competitor chain without exceeding the canonical's TD and ensure // Import the competitor chain without exceeding the canonical's TD.
// we have not processed any of the blocks (protection against malicious blocks) // Post-merge the side chain should be executed
if _, err := chain.InsertChain(competitor[:len(competitor)-2]); err != nil { if _, err := chain.InsertChain(competitor[:len(competitor)-2]); err != nil {
t.Fatalf("failed to insert competitor chain: %v", err) t.Fatalf("failed to insert competitor chain: %v", err)
} }
for i, block := range competitor[:len(competitor)-2] { if !chain.HasState(competitor[len(competitor)-3].Root()) {
if chain.HasState(block.Root()) { t.Fatalf("failed to insert low-TD chain")
t.Fatalf("competitor %d: low TD chain became processed", i)
}
} }
// Import the head of the competitor chain, triggering the reorg and ensure we // Import the head of the competitor chain.
// successfully reprocess all the stashed away blocks.
if _, err := chain.InsertChain(competitor[len(competitor)-2:]); err != nil { if _, err := chain.InsertChain(competitor[len(competitor)-2:]); err != nil {
t.Fatalf("failed to finalize competitor chain: %v", err) t.Fatalf("failed to finalize competitor chain: %v", err)
} }
@ -1841,7 +1835,7 @@ func testBlockchainRecovery(t *testing.T, scheme string) {
t.Fatalf("failed to create temp freezer db: %v", err) t.Fatalf("failed to create temp freezer db: %v", err)
} }
defer ancientDb.Close() defer ancientDb.Close()
ancient, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) ancient, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil)
headers := make([]*types.Header, len(blocks)) headers := make([]*types.Header, len(blocks))
for i, block := range blocks { for i, block := range blocks {
@ -1861,7 +1855,7 @@ func testBlockchainRecovery(t *testing.T, scheme string) {
rawdb.WriteHeadFastBlockHash(ancientDb, midBlock.Hash()) rawdb.WriteHeadFastBlockHash(ancientDb, midBlock.Hash())
// Reopen broken blockchain again // Reopen broken blockchain again
ancient, _ = NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) ancient, _ = NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil)
defer ancient.Stop() defer ancient.Stop()
if num := ancient.CurrentBlock().Number.Uint64(); num != 0 { if num := ancient.CurrentBlock().Number.Uint64(); num != 0 {
t.Errorf("head block mismatch: have #%v, want #%v", num, 0) t.Errorf("head block mismatch: have #%v, want #%v", num, 0)
@ -1913,7 +1907,7 @@ func testInsertReceiptChainRollback(t *testing.T, scheme string) {
} }
defer ancientDb.Close() defer ancientDb.Close()
ancientChain, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) ancientChain, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil)
defer ancientChain.Stop() defer ancientChain.Stop()
// Import the canonical header chain. // Import the canonical header chain.
@ -1980,7 +1974,7 @@ func testLowDiffLongChain(t *testing.T, scheme string) {
diskdb, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false) diskdb, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
defer diskdb.Close() defer diskdb.Close()
chain, err := NewBlockChain(diskdb, DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil, nil) chain, err := NewBlockChain(diskdb, DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil)
if err != nil { if err != nil {
t.Fatalf("failed to create tester chain: %v", err) t.Fatalf("failed to create tester chain: %v", err)
} }
@ -2041,7 +2035,7 @@ func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommon
mergeBlock = math.MaxInt32 mergeBlock = math.MaxInt32
) )
// Generate and import the canonical chain // Generate and import the canonical chain
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{}, nil, nil) chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{}, nil)
if err != nil { if err != nil {
t.Fatalf("failed to create tester chain: %v", err) t.Fatalf("failed to create tester chain: %v", err)
} }
@ -2132,9 +2126,9 @@ func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommon
// [ Cn, Cn+1, Cc, Sn+3 ... Sm] // [ Cn, Cn+1, Cc, Sn+3 ... Sm]
// ^ ^ ^ pruned // ^ ^ ^ pruned
func TestPrunedImportSide(t *testing.T) { func TestPrunedImportSide(t *testing.T) {
//glogger := log.NewGlogHandler(log.StreamHandler(os.Stdout, log.TerminalFormat(false))) // glogger := log.NewGlogHandler(log.NewTerminalHandler(os.Stderr, false))
//glogger.Verbosity(3) // glogger.Verbosity(3)
//log.Root().SetHandler(log.Handler(glogger)) // log.SetDefault(log.NewLogger(glogger))
testSideImport(t, 3, 3, -1) testSideImport(t, 3, 3, -1)
testSideImport(t, 3, -3, -1) testSideImport(t, 3, -3, -1)
testSideImport(t, 10, 0, -1) testSideImport(t, 10, 0, -1)
@ -2143,9 +2137,9 @@ func TestPrunedImportSide(t *testing.T) {
} }
func TestPrunedImportSideWithMerging(t *testing.T) { func TestPrunedImportSideWithMerging(t *testing.T) {
//glogger := log.NewGlogHandler(log.StreamHandler(os.Stdout, log.TerminalFormat(false))) // glogger := log.NewGlogHandler(log.NewTerminalHandler(os.Stderr, false))
//glogger.Verbosity(3) // glogger.Verbosity(3)
//log.Root().SetHandler(log.Handler(glogger)) // log.SetDefault(log.NewLogger(glogger))
testSideImport(t, 3, 3, 0) testSideImport(t, 3, 3, 0)
testSideImport(t, 3, -3, 0) testSideImport(t, 3, -3, 0)
testSideImport(t, 10, 0, 0) testSideImport(t, 10, 0, 0)
@ -2195,7 +2189,7 @@ func testInsertKnownChainData(t *testing.T, typ string, scheme string) {
} }
defer chaindb.Close() defer chaindb.Close()
chain, err := NewBlockChain(chaindb, DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil, nil) chain, err := NewBlockChain(chaindb, DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil)
if err != nil { if err != nil {
t.Fatalf("failed to create tester chain: %v", err) t.Fatalf("failed to create tester chain: %v", err)
} }
@ -2279,10 +2273,10 @@ func testInsertKnownChainData(t *testing.T, typ string, scheme string) {
if err := inserter(append(blocks, blocks2...), append(receipts, receipts2...)); err != nil { if err := inserter(append(blocks, blocks2...), append(receipts, receipts2...)); err != nil {
t.Fatalf("failed to insert chain data: %v", err) t.Fatalf("failed to insert chain data: %v", err)
} }
// The head shouldn't change. // Post-merge the chain should change even if td is lower.
asserter(t, blocks3[len(blocks3)-1]) asserter(t, blocks2[len(blocks2)-1])
// Rollback the heavier chain and re-insert the longer chain again // Rollback the heavier chain and re-insert the longer chain again.
chain.SetHead(rollback - 1) chain.SetHead(rollback - 1)
if err := inserter(append(blocks, blocks2...), append(receipts, receipts2...)); err != nil { if err := inserter(append(blocks, blocks2...), append(receipts, receipts2...)); err != nil {
t.Fatalf("failed to insert chain data: %v", err) t.Fatalf("failed to insert chain data: %v", err)
@ -2366,7 +2360,7 @@ func testInsertKnownChainDataWithMerging(t *testing.T, typ string, mergeHeight i
} }
defer chaindb.Close() defer chaindb.Close()
chain, err := NewBlockChain(chaindb, nil, genesis, nil, engine, vm.Config{}, nil, nil) chain, err := NewBlockChain(chaindb, nil, genesis, nil, engine, vm.Config{}, nil)
if err != nil { if err != nil {
t.Fatalf("failed to create tester chain: %v", err) t.Fatalf("failed to create tester chain: %v", err)
} }
@ -2480,7 +2474,7 @@ func getLongAndShortChains(scheme string) (*BlockChain, []*types.Block, []*types
genDb, longChain, _ := GenerateChainWithGenesis(genesis, engine, 80, func(i int, b *BlockGen) { genDb, longChain, _ := GenerateChainWithGenesis(genesis, engine, 80, func(i int, b *BlockGen) {
b.SetCoinbase(common.Address{1}) b.SetCoinbase(common.Address{1})
}) })
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil, nil) chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil)
if err != nil { if err != nil {
return nil, nil, nil, nil, fmt.Errorf("failed to create tester chain: %v", err) return nil, nil, nil, nil, fmt.Errorf("failed to create tester chain: %v", err)
} }
@ -2656,7 +2650,7 @@ func benchmarkLargeNumberOfValueToNonexisting(b *testing.B, numTxs, numBlocks in
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
// Import the shared chain and the original canonical one // Import the shared chain and the original canonical one
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{}, nil, nil) chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{}, nil)
if err != nil { if err != nil {
b.Fatalf("failed to create tester chain: %v", err) b.Fatalf("failed to create tester chain: %v", err)
} }
@ -2743,7 +2737,21 @@ func testSideImportPrunedBlocks(t *testing.T, scheme string) {
// Generate and import the canonical chain // Generate and import the canonical chain
_, blocks, _ := GenerateChainWithGenesis(genesis, engine, 2*state.TriesInMemory, nil) _, blocks, _ := GenerateChainWithGenesis(genesis, engine, 2*state.TriesInMemory, nil)
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil, nil) // Construct a database with freezer enabled
datadir := t.TempDir()
ancient := path.Join(datadir, "ancient")
db, err := rawdb.Open(rawdb.OpenOptions{
Directory: datadir,
AncientsDirectory: ancient,
Ephemeral: true,
})
if err != nil {
t.Fatalf("Failed to create persistent database: %v", err)
}
defer db.Close()
chain, err := NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil)
if err != nil { if err != nil {
t.Fatalf("failed to create tester chain: %v", err) t.Fatalf("failed to create tester chain: %v", err)
} }
@ -2770,7 +2778,6 @@ func testSideImportPrunedBlocks(t *testing.T, scheme string) {
if !chain.HasBlockAndState(firstNonPrunedBlock.Hash(), firstNonPrunedBlock.NumberU64()) { if !chain.HasBlockAndState(firstNonPrunedBlock.Hash(), firstNonPrunedBlock.NumberU64()) {
t.Errorf("Block %d pruned", firstNonPrunedBlock.NumberU64()) t.Errorf("Block %d pruned", firstNonPrunedBlock.NumberU64())
} }
// Now re-import some old blocks
blockToReimport := blocks[5:8] blockToReimport := blocks[5:8]
_, err = chain.InsertChain(blockToReimport) _, err = chain.InsertChain(blockToReimport)
if err != nil { if err != nil {
@ -2843,7 +2850,7 @@ func testDeleteCreateRevert(t *testing.T, scheme string) {
b.AddTx(tx) b.AddTx(tx)
}) })
// Import the canonical chain // Import the canonical chain
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil, nil) chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil)
if err != nil { if err != nil {
t.Fatalf("failed to create tester chain: %v", err) t.Fatalf("failed to create tester chain: %v", err)
} }
@ -2958,7 +2965,7 @@ func testDeleteRecreateSlots(t *testing.T, scheme string) {
// Import the canonical chain // Import the canonical chain
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{ chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{
Tracer: logger.NewJSONLogger(nil, os.Stdout), Tracer: logger.NewJSONLogger(nil, os.Stdout),
}, nil, nil) }, nil)
if err != nil { if err != nil {
t.Fatalf("failed to create tester chain: %v", err) t.Fatalf("failed to create tester chain: %v", err)
} }
@ -3040,7 +3047,7 @@ func testDeleteRecreateAccount(t *testing.T, scheme string) {
// Import the canonical chain // Import the canonical chain
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{ chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{
Tracer: logger.NewJSONLogger(nil, os.Stdout), Tracer: logger.NewJSONLogger(nil, os.Stdout),
}, nil, nil) }, nil)
if err != nil { if err != nil {
t.Fatalf("failed to create tester chain: %v", err) t.Fatalf("failed to create tester chain: %v", err)
} }
@ -3216,7 +3223,7 @@ func testDeleteRecreateSlotsAcrossManyBlocks(t *testing.T, scheme string) {
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{ chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{
//Debug: true, //Debug: true,
//Tracer: vm.NewJSONLogger(nil, os.Stdout), //Tracer: vm.NewJSONLogger(nil, os.Stdout),
}, nil, nil) }, nil)
if err != nil { if err != nil {
t.Fatalf("failed to create tester chain: %v", err) t.Fatalf("failed to create tester chain: %v", err)
} }
@ -3354,7 +3361,7 @@ func testInitThenFailCreateContract(t *testing.T, scheme string) {
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{ chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{
//Debug: true, //Debug: true,
//Tracer: vm.NewJSONLogger(nil, os.Stdout), //Tracer: vm.NewJSONLogger(nil, os.Stdout),
}, nil, nil) }, nil)
if err != nil { if err != nil {
t.Fatalf("failed to create tester chain: %v", err) t.Fatalf("failed to create tester chain: %v", err)
} }
@ -3441,7 +3448,7 @@ func testEIP2718Transition(t *testing.T, scheme string) {
}) })
// Import the canonical chain // Import the canonical chain
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil, nil) chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil)
if err != nil { if err != nil {
t.Fatalf("failed to create tester chain: %v", err) t.Fatalf("failed to create tester chain: %v", err)
} }
@ -3535,7 +3542,7 @@ func testEIP1559Transition(t *testing.T, scheme string) {
b.AddTx(tx) b.AddTx(tx)
}) })
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil, nil) chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil)
if err != nil { if err != nil {
t.Fatalf("failed to create tester chain: %v", err) t.Fatalf("failed to create tester chain: %v", err)
} }
@ -3622,7 +3629,7 @@ func TestSetCanonical(t *testing.T) {
} }
func testSetCanonical(t *testing.T, scheme string) { func testSetCanonical(t *testing.T, scheme string) {
//log.Root().SetHandler(log.LvlFilterHandler(log.LvlDebug, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) // log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, true)))
var ( var (
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
@ -3648,7 +3655,7 @@ func testSetCanonical(t *testing.T, scheme string) {
diskdb, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false) diskdb, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
defer diskdb.Close() defer diskdb.Close()
chain, err := NewBlockChain(diskdb, DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil, nil) chain, err := NewBlockChain(diskdb, DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil)
if err != nil { if err != nil {
t.Fatalf("failed to create tester chain: %v", err) t.Fatalf("failed to create tester chain: %v", err)
} }
@ -3667,7 +3674,7 @@ func testSetCanonical(t *testing.T, scheme string) {
gen.AddTx(tx) gen.AddTx(tx)
}) })
for _, block := range side { for _, block := range side {
err := chain.InsertBlockWithoutSetHead(block) _, err := chain.InsertBlockWithoutSetHead(block, false)
if err != nil { if err != nil {
t.Fatalf("Failed to insert into chain: %v", err) t.Fatalf("Failed to insert into chain: %v", err)
} }
@ -3757,7 +3764,7 @@ func testCanonicalHashMarker(t *testing.T, scheme string) {
_, forkB, _ := GenerateChainWithGenesis(gspec, engine, c.forkB, func(i int, gen *BlockGen) {}) _, forkB, _ := GenerateChainWithGenesis(gspec, engine, c.forkB, func(i int, gen *BlockGen) {})
// Initialize test chain // Initialize test chain
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil, nil) chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil)
if err != nil { if err != nil {
t.Fatalf("failed to create tester chain: %v", err) t.Fatalf("failed to create tester chain: %v", err)
} }
@ -3894,7 +3901,7 @@ func testCreateThenDelete(t *testing.T, config *params.ChainConfig) {
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{ chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{
//Debug: true, //Debug: true,
//Tracer: logger.NewJSONLogger(nil, os.Stdout), //Tracer: logger.NewJSONLogger(nil, os.Stdout),
}, nil, nil) }, nil)
if err != nil { if err != nil {
t.Fatalf("failed to create tester chain: %v", err) t.Fatalf("failed to create tester chain: %v", err)
} }
@ -4006,7 +4013,7 @@ func TestDeleteThenCreate(t *testing.T) {
} }
}) })
// Import the canonical chain // Import the canonical chain
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{}, nil, nil) chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{}, nil)
if err != nil { if err != nil {
t.Fatalf("failed to create tester chain: %v", err) t.Fatalf("failed to create tester chain: %v", err)
} }
@ -4091,7 +4098,7 @@ func TestTransientStorageReset(t *testing.T) {
}) })
// Initialize the blockchain with 1153 enabled. // Initialize the blockchain with 1153 enabled.
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vmConfig, nil, nil) chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vmConfig, nil)
if err != nil { if err != nil {
t.Fatalf("failed to create tester chain: %v", err) t.Fatalf("failed to create tester chain: %v", err)
} }
@ -4186,7 +4193,7 @@ func TestEIP3651(t *testing.T) {
b.AddTx(tx) b.AddTx(tx)
}) })
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{Tracer: logger.NewMarkdownLogger(&logger.Config{}, os.Stderr).Hooks()}, nil, nil) chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{Tracer: logger.NewMarkdownLogger(&logger.Config{}, os.Stderr).Hooks()}, nil)
if err != nil { if err != nil {
t.Fatalf("failed to create tester chain: %v", err) t.Fatalf("failed to create tester chain: %v", err)
} }
@ -4220,3 +4227,90 @@ func TestEIP3651(t *testing.T) {
t.Fatalf("sender balance incorrect: expected %d, got %d", expected, actual) t.Fatalf("sender balance incorrect: expected %d, got %d", expected, actual)
} }
} }
func TestEIP6110(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),
},
},
}
)
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)
_, 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)
}
})
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{Tracer: logger.NewMarkdownLogger(&logger.Config{DisableStack: true}, os.Stderr).Hooks()}, nil)
if err != nil {
t.Fatalf("failed to create tester chain: %v", err)
}
defer chain.Stop()
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,7 +346,18 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
gen(i, b) gen(i, b)
} }
body := types.Body{Transactions: b.txs, Uncles: b.uncles, Withdrawals: b.withdrawals} var requests types.Requests
if config.IsPrague(b.header.Number, b.header.Time) {
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...)
}
}
body := types.Body{Transactions: b.txs, Uncles: b.uncles, Withdrawals: b.withdrawals, Requests: requests}
block, err := b.engine.FinalizeAndAssemble(cm, b.header, statedb, &body, b.receipts) block, err := b.engine.FinalizeAndAssemble(cm, b.header, statedb, &body, b.receipts)
if err != nil { if err != nil {
panic(err) panic(err)
@ -368,7 +379,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
defer triedb.Close() defer triedb.Close()
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
statedb, err := state.New(parent.Root(), state.NewDatabaseWithNodeDB(db, triedb), nil) statedb, err := state.New(parent.Root(), state.NewDatabase(triedb, nil))
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -467,15 +478,14 @@ func GenerateVerkleChain(config *params.ChainConfig, parent *types.Block, engine
panic(fmt.Sprintf("trie write error: %v", err)) panic(fmt.Sprintf("trie write error: %v", err))
} }
// TODO uncomment when proof generation is merged proofs = append(proofs, block.ExecutionWitness().VerkleProof)
// proofs = append(proofs, block.ExecutionWitness().VerkleProof) keyvals = append(keyvals, block.ExecutionWitness().StateDiff)
// keyvals = append(keyvals, block.ExecutionWitness().StateDiff)
return block, b.receipts return block, b.receipts
} }
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
statedb, err := state.New(parent.Root(), state.NewDatabaseWithNodeDB(db, trdb), nil) statedb, err := state.New(parent.Root(), state.NewDatabase(trdb, nil))
if err != nil { if err != nil {
panic(err) panic(err)
} }

@ -123,7 +123,7 @@ func TestGeneratePOSChain(t *testing.T) {
}) })
// Import the chain. This runs all block validation rules. // Import the chain. This runs all block validation rules.
blockchain, _ := NewBlockChain(db, nil, gspec, nil, beacon.NewFaker(), vm.Config{}, nil, nil) blockchain, _ := NewBlockChain(db, nil, gspec, nil, beacon.NewFaker(), vm.Config{}, nil)
defer blockchain.Stop() defer blockchain.Stop()
if i, err := blockchain.InsertChain(genchain); err != nil { if i, err := blockchain.InsertChain(genchain); err != nil {
@ -238,7 +238,7 @@ func ExampleGenerateChain() {
}) })
// Import the chain. This runs all block validation rules. // Import the chain. This runs all block validation rules.
blockchain, _ := NewBlockChain(db, DefaultCacheConfigWithScheme(rawdb.HashScheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) blockchain, _ := NewBlockChain(db, DefaultCacheConfigWithScheme(rawdb.HashScheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil)
defer blockchain.Stop() defer blockchain.Stop()
if i, err := blockchain.InsertChain(chain); err != nil { if i, err := blockchain.InsertChain(chain); err != nil {

@ -50,7 +50,7 @@ func TestDAOForkRangeExtradata(t *testing.T) {
BaseFee: big.NewInt(params.InitialBaseFee), BaseFee: big.NewInt(params.InitialBaseFee),
Config: &proConf, Config: &proConf,
} }
proBc, _ := NewBlockChain(proDb, nil, progspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) proBc, _ := NewBlockChain(proDb, nil, progspec, nil, ethash.NewFaker(), vm.Config{}, nil)
defer proBc.Stop() defer proBc.Stop()
conDb := rawdb.NewMemoryDatabase() conDb := rawdb.NewMemoryDatabase()
@ -62,7 +62,7 @@ func TestDAOForkRangeExtradata(t *testing.T) {
BaseFee: big.NewInt(params.InitialBaseFee), BaseFee: big.NewInt(params.InitialBaseFee),
Config: &conConf, Config: &conConf,
} }
conBc, _ := NewBlockChain(conDb, nil, congspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) conBc, _ := NewBlockChain(conDb, nil, congspec, nil, ethash.NewFaker(), vm.Config{}, nil)
defer conBc.Stop() defer conBc.Stop()
if _, err := proBc.InsertChain(prefix); err != nil { if _, err := proBc.InsertChain(prefix); err != nil {
@ -74,7 +74,7 @@ func TestDAOForkRangeExtradata(t *testing.T) {
// Try to expand both pro-fork and non-fork chains iteratively with other camp's blocks // Try to expand both pro-fork and non-fork chains iteratively with other camp's blocks
for i := int64(0); i < params.DAOForkExtraRange.Int64(); i++ { for i := int64(0); i < params.DAOForkExtraRange.Int64(); i++ {
// Create a pro-fork block, and try to feed into the no-fork chain // Create a pro-fork block, and try to feed into the no-fork chain
bc, _ := NewBlockChain(rawdb.NewMemoryDatabase(), nil, congspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) bc, _ := NewBlockChain(rawdb.NewMemoryDatabase(), nil, congspec, nil, ethash.NewFaker(), vm.Config{}, nil)
blocks := conBc.GetBlocksFromHash(conBc.CurrentBlock().Hash(), int(conBc.CurrentBlock().Number.Uint64())) blocks := conBc.GetBlocksFromHash(conBc.CurrentBlock().Hash(), int(conBc.CurrentBlock().Number.Uint64()))
for j := 0; j < len(blocks)/2; j++ { for j := 0; j < len(blocks)/2; j++ {
@ -97,7 +97,7 @@ func TestDAOForkRangeExtradata(t *testing.T) {
t.Fatalf("contra-fork chain didn't accepted no-fork block: %v", err) t.Fatalf("contra-fork chain didn't accepted no-fork block: %v", err)
} }
// Create a no-fork block, and try to feed into the pro-fork chain // Create a no-fork block, and try to feed into the pro-fork chain
bc, _ = NewBlockChain(rawdb.NewMemoryDatabase(), nil, progspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) bc, _ = NewBlockChain(rawdb.NewMemoryDatabase(), nil, progspec, nil, ethash.NewFaker(), vm.Config{}, nil)
blocks = proBc.GetBlocksFromHash(proBc.CurrentBlock().Hash(), int(proBc.CurrentBlock().Number.Uint64())) blocks = proBc.GetBlocksFromHash(proBc.CurrentBlock().Hash(), int(proBc.CurrentBlock().Number.Uint64()))
for j := 0; j < len(blocks)/2; j++ { for j := 0; j < len(blocks)/2; j++ {
@ -121,7 +121,7 @@ func TestDAOForkRangeExtradata(t *testing.T) {
} }
} }
// Verify that contra-forkers accept pro-fork extra-datas after forking finishes // Verify that contra-forkers accept pro-fork extra-datas after forking finishes
bc, _ := NewBlockChain(rawdb.NewMemoryDatabase(), nil, congspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) bc, _ := NewBlockChain(rawdb.NewMemoryDatabase(), nil, congspec, nil, ethash.NewFaker(), vm.Config{}, nil)
defer bc.Stop() defer bc.Stop()
blocks := conBc.GetBlocksFromHash(conBc.CurrentBlock().Hash(), int(conBc.CurrentBlock().Number.Uint64())) blocks := conBc.GetBlocksFromHash(conBc.CurrentBlock().Hash(), int(conBc.CurrentBlock().Number.Uint64()))
@ -139,7 +139,7 @@ func TestDAOForkRangeExtradata(t *testing.T) {
t.Fatalf("contra-fork chain didn't accept pro-fork block post-fork: %v", err) t.Fatalf("contra-fork chain didn't accept pro-fork block post-fork: %v", err)
} }
// Verify that pro-forkers accept contra-fork extra-datas after forking finishes // Verify that pro-forkers accept contra-fork extra-datas after forking finishes
bc, _ = NewBlockChain(rawdb.NewMemoryDatabase(), nil, progspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) bc, _ = NewBlockChain(rawdb.NewMemoryDatabase(), nil, progspec, nil, ethash.NewFaker(), vm.Config{}, nil)
defer bc.Stop() defer bc.Stop()
blocks = proBc.GetBlocksFromHash(proBc.CurrentBlock().Hash(), int(proBc.CurrentBlock().Number.Uint64())) blocks = proBc.GetBlocksFromHash(proBc.CurrentBlock().Hash(), int(proBc.CurrentBlock().Number.Uint64()))

@ -1,113 +0,0 @@
// Copyright 2021 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 core
import (
crand "crypto/rand"
"errors"
"math/big"
mrand "math/rand"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
)
// ChainReader defines a small collection of methods needed to access the local
// blockchain during header verification. It's implemented by both blockchain
// and lightchain.
type ChainReader interface {
// Config retrieves the header chain's chain configuration.
Config() *params.ChainConfig
// GetTd returns the total difficulty of a local block.
GetTd(common.Hash, uint64) *big.Int
}
// ForkChoice is the fork chooser based on the highest total difficulty of the
// chain(the fork choice used in the eth1) and the external fork choice (the fork
// choice used in the eth2). This main goal of this ForkChoice is not only for
// offering fork choice during the eth1/2 merge phase, but also keep the compatibility
// for all other proof-of-work networks.
type ForkChoice struct {
chain ChainReader
rand *mrand.Rand
// preserve is a helper function used in td fork choice.
// Miners will prefer to choose the local mined block if the
// local td is equal to the extern one. It can be nil for light
// client
preserve func(header *types.Header) bool
}
func NewForkChoice(chainReader ChainReader, preserve func(header *types.Header) bool) *ForkChoice {
// Seed a fast but crypto originating random generator
seed, err := crand.Int(crand.Reader, big.NewInt(math.MaxInt64))
if err != nil {
log.Crit("Failed to initialize random seed", "err", err)
}
return &ForkChoice{
chain: chainReader,
rand: mrand.New(mrand.NewSource(seed.Int64())),
preserve: preserve,
}
}
// ReorgNeeded returns whether the reorg should be applied
// based on the given external header and local canonical chain.
// In the td mode, the new head is chosen if the corresponding
// total difficulty is higher. In the extern mode, the trusted
// header is always selected as the head.
func (f *ForkChoice) ReorgNeeded(current *types.Header, extern *types.Header) (bool, error) {
var (
localTD = f.chain.GetTd(current.Hash(), current.Number.Uint64())
externTd = f.chain.GetTd(extern.Hash(), extern.Number.Uint64())
)
if localTD == nil || externTd == nil {
return false, errors.New("missing td")
}
// Accept the new header as the chain head if the transition
// is already triggered. We assume all the headers after the
// transition come from the trusted consensus layer.
if ttd := f.chain.Config().TerminalTotalDifficulty; ttd != nil && ttd.Cmp(externTd) <= 0 {
return true, nil
}
// If the total difficulty is higher than our known, add it to the canonical chain
if diff := externTd.Cmp(localTD); diff > 0 {
return true, nil
} else if diff < 0 {
return false, nil
}
// Local and external difficulty is identical.
// Second clause in the if statement reduces the vulnerability to selfish mining.
// Please refer to http://www.cs.cornell.edu/~ie53/publications/btcProcFC.pdf
reorg := false
externNum, localNum := extern.Number.Uint64(), current.Number.Uint64()
if externNum < localNum {
reorg = true
} else if externNum == localNum {
var currentPreserve, externPreserve bool
if f.preserve != nil {
currentPreserve, externPreserve = f.preserve(current), f.preserve(extern)
}
reorg = !currentPreserve && (externPreserve || f.rand.Float64() < 0.5)
}
return reorg, nil
}

@ -127,8 +127,8 @@ func hashAlloc(ga *types.GenesisAlloc, isVerkle bool) (common.Hash, error) {
} }
// Create an ephemeral in-memory database for computing hash, // Create an ephemeral in-memory database for computing hash,
// all the derived states will be discarded to not pollute disk. // all the derived states will be discarded to not pollute disk.
db := state.NewDatabaseWithConfig(rawdb.NewMemoryDatabase(), config) db := rawdb.NewMemoryDatabase()
statedb, err := state.New(types.EmptyRootHash, db, nil) statedb, err := state.New(types.EmptyRootHash, state.NewDatabase(triedb.NewDatabase(db, config), nil))
if err != nil { if err != nil {
return common.Hash{}, err return common.Hash{}, err
} }
@ -147,8 +147,8 @@ func hashAlloc(ga *types.GenesisAlloc, isVerkle bool) (common.Hash, error) {
// flushAlloc is very similar with hash, but the main difference is all the // flushAlloc is very similar with hash, but the main difference is all the
// generated states will be persisted into the given database. // generated states will be persisted into the given database.
func flushAlloc(ga *types.GenesisAlloc, db ethdb.Database, triedb *triedb.Database) (common.Hash, error) { func flushAlloc(ga *types.GenesisAlloc, triedb *triedb.Database) (common.Hash, error) {
statedb, err := state.New(types.EmptyRootHash, state.NewDatabaseWithNodeDB(db, triedb), nil) statedb, err := state.New(types.EmptyRootHash, state.NewDatabase(triedb, nil))
if err != nil { if err != nil {
return common.Hash{}, err return common.Hash{}, err
} }
@ -447,7 +447,10 @@ func (g *Genesis) toBlockWithRoot(root common.Hash) *types.Block {
head.BaseFee = new(big.Int).SetUint64(params.InitialBaseFee) head.BaseFee = new(big.Int).SetUint64(params.InitialBaseFee)
} }
} }
var withdrawals []*types.Withdrawal var (
withdrawals []*types.Withdrawal
requests types.Requests
)
if conf := g.Config; conf != nil { if conf := g.Config; conf != nil {
num := big.NewInt(int64(g.Number)) num := big.NewInt(int64(g.Number))
if conf.IsShanghai(num, g.Timestamp) { if conf.IsShanghai(num, g.Timestamp) {
@ -469,8 +472,12 @@ func (g *Genesis) toBlockWithRoot(root common.Hash) *types.Block {
head.BlobGasUsed = new(uint64) head.BlobGasUsed = new(uint64)
} }
} }
if conf.IsPrague(num, g.Timestamp) {
head.RequestsHash = &types.EmptyRequestsHash
requests = make(types.Requests, 0)
}
} }
return types.NewBlock(head, &types.Body{Withdrawals: withdrawals}, nil, trie.NewStackTrie(nil)) return types.NewBlock(head, &types.Body{Withdrawals: withdrawals, Requests: requests}, nil, trie.NewStackTrie(nil))
} }
// Commit writes the block and state of a genesis specification to the database. // Commit writes the block and state of a genesis specification to the database.
@ -490,7 +497,7 @@ func (g *Genesis) Commit(db ethdb.Database, triedb *triedb.Database) (*types.Blo
return nil, errors.New("can't start clique chain without signers") return nil, errors.New("can't start clique chain without signers")
} }
// flush the data to disk and compute the state root // flush the data to disk and compute the state root
root, err := flushAlloc(&g.Alloc, db, triedb) root, err := flushAlloc(&g.Alloc, triedb)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -584,7 +591,7 @@ func DeveloperGenesisBlock(gasLimit uint64, faucet *common.Address) *Genesis {
// Pre-deploy EIP-4788 system contract // Pre-deploy EIP-4788 system contract
params.BeaconRootsAddress: {Nonce: 1, Code: params.BeaconRootsCode, Balance: common.Big0}, params.BeaconRootsAddress: {Nonce: 1, Code: params.BeaconRootsCode, Balance: common.Big0},
// Pre-deploy EIP-2935 history contract. // Pre-deploy EIP-2935 history contract.
params.HistoryStorageAddress: {Nonce: 1, Code: params.HistoryStorageCode}, params.HistoryStorageAddress: {Nonce: 1, Code: params.HistoryStorageCode, Balance: common.Big0},
}, },
} }
if faucet != nil { if faucet != nil {

@ -124,7 +124,7 @@ func testSetupGenesis(t *testing.T, scheme string) {
tdb := triedb.NewDatabase(db, newDbConfig(scheme)) tdb := triedb.NewDatabase(db, newDbConfig(scheme))
oldcustomg.Commit(db, tdb) oldcustomg.Commit(db, tdb)
bc, _ := NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), &oldcustomg, nil, ethash.NewFullFaker(), vm.Config{}, nil, nil) bc, _ := NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), &oldcustomg, nil, ethash.NewFullFaker(), vm.Config{}, nil)
defer bc.Stop() defer bc.Stop()
_, blocks, _ := GenerateChainWithGenesis(&oldcustomg, ethash.NewFaker(), 4, nil) _, blocks, _ := GenerateChainWithGenesis(&oldcustomg, ethash.NewFaker(), 4, nil)
@ -294,7 +294,7 @@ func TestVerkleGenesisCommit(t *testing.T) {
}, },
} }
expected := common.FromHex("14398d42be3394ff8d50681816a4b7bf8d8283306f577faba2d5bc57498de23b") expected := common.FromHex("4a83dc39eb688dbcfaf581d60e82de18f875e38786ebce5833342011d6fef37b")
got := genesis.ToBlock().Root().Bytes() got := genesis.ToBlock().Root().Bytes()
if !bytes.Equal(got, expected) { if !bytes.Equal(got, expected) {
t.Fatalf("invalid genesis state root, expected %x, got %x", expected, got) t.Fatalf("invalid genesis state root, expected %x, got %x", expected, got)

@ -254,7 +254,7 @@ func (hc *HeaderChain) WriteHeaders(headers []*types.Header) (int, error) {
// without the real blocks. Hence, writing headers directly should only be done // without the real blocks. Hence, writing headers directly should only be done
// in two scenarios: pure-header mode of operation (light clients), or properly // in two scenarios: pure-header mode of operation (light clients), or properly
// separated header/block phases (non-archive clients). // separated header/block phases (non-archive clients).
func (hc *HeaderChain) writeHeadersAndSetHead(headers []*types.Header, forker *ForkChoice) (*headerWriteResult, error) { func (hc *HeaderChain) writeHeadersAndSetHead(headers []*types.Header) (*headerWriteResult, error) {
inserted, err := hc.WriteHeaders(headers) inserted, err := hc.WriteHeaders(headers)
if err != nil { if err != nil {
return nil, err return nil, err
@ -270,15 +270,6 @@ func (hc *HeaderChain) writeHeadersAndSetHead(headers []*types.Header, forker *F
lastHeader: lastHeader, lastHeader: lastHeader,
} }
) )
// Ask the fork choicer if the reorg is necessary
if reorg, err := forker.ReorgNeeded(hc.CurrentHeader(), lastHeader); err != nil {
return nil, err
} else if !reorg {
if inserted != 0 {
result.status = SideStatTy
}
return result, nil
}
// Special case, all the inserted headers are already on the canonical // Special case, all the inserted headers are already on the canonical
// header chain, skip the reorg operation. // header chain, skip the reorg operation.
if hc.GetCanonicalHash(lastHeader.Number.Uint64()) == lastHash && lastHeader.Number.Uint64() <= hc.CurrentHeader().Number.Uint64() { if hc.GetCanonicalHash(lastHeader.Number.Uint64()) == lastHash && lastHeader.Number.Uint64() <= hc.CurrentHeader().Number.Uint64() {
@ -336,11 +327,11 @@ func (hc *HeaderChain) ValidateHeaderChain(chain []*types.Header) (int, error) {
// //
// The returned 'write status' says if the inserted headers are part of the canonical chain // The returned 'write status' says if the inserted headers are part of the canonical chain
// or a side chain. // or a side chain.
func (hc *HeaderChain) InsertHeaderChain(chain []*types.Header, start time.Time, forker *ForkChoice) (WriteStatus, error) { func (hc *HeaderChain) InsertHeaderChain(chain []*types.Header, start time.Time) (WriteStatus, error) {
if hc.procInterrupt() { if hc.procInterrupt() {
return 0, errors.New("aborted") return 0, errors.New("aborted")
} }
res, err := hc.writeHeadersAndSetHead(chain, forker) res, err := hc.writeHeadersAndSetHead(chain)
if err != nil { if err != nil {
return 0, err return 0, err
} }

@ -51,10 +51,10 @@ func verifyUnbrokenCanonchain(hc *HeaderChain) error {
return nil return nil
} }
func testInsert(t *testing.T, hc *HeaderChain, chain []*types.Header, wantStatus WriteStatus, wantErr error, forker *ForkChoice) { func testInsert(t *testing.T, hc *HeaderChain, chain []*types.Header, wantStatus WriteStatus, wantErr error) {
t.Helper() t.Helper()
status, err := hc.InsertHeaderChain(chain, time.Now(), forker) status, err := hc.InsertHeaderChain(chain, time.Now())
if status != wantStatus { if status != wantStatus {
t.Errorf("wrong write status from InsertHeaderChain: got %v, want %v", status, wantStatus) t.Errorf("wrong write status from InsertHeaderChain: got %v, want %v", status, wantStatus)
} }
@ -83,34 +83,33 @@ func TestHeaderInsertion(t *testing.T) {
// chain B: G->A1->B1...B128 // chain B: G->A1->B1...B128
chainB := makeHeaderChain(gspec.Config, chainA[0], 128, ethash.NewFaker(), genDb, 10) chainB := makeHeaderChain(gspec.Config, chainA[0], 128, ethash.NewFaker(), genDb, 10)
forker := NewForkChoice(hc, nil)
// Inserting 64 headers on an empty chain, expecting // Inserting 64 headers on an empty chain, expecting
// 1 callbacks, 1 canon-status, 0 sidestatus, // 1 callbacks, 1 canon-status, 0 sidestatus,
testInsert(t, hc, chainA[:64], CanonStatTy, nil, forker) testInsert(t, hc, chainA[:64], CanonStatTy, nil)
// Inserting 64 identical headers, expecting // Inserting 64 identical headers, expecting
// 0 callbacks, 0 canon-status, 0 sidestatus, // 0 callbacks, 0 canon-status, 0 sidestatus,
testInsert(t, hc, chainA[:64], NonStatTy, nil, forker) testInsert(t, hc, chainA[:64], NonStatTy, nil)
// Inserting the same some old, some new headers // Inserting the same some old, some new headers
// 1 callbacks, 1 canon, 0 side // 1 callbacks, 1 canon, 0 side
testInsert(t, hc, chainA[32:96], CanonStatTy, nil, forker) testInsert(t, hc, chainA[32:96], CanonStatTy, nil)
// Inserting side blocks, but not overtaking the canon chain // Inserting headers from chain B, overtaking the canon chain blindly
testInsert(t, hc, chainB[0:32], SideStatTy, nil, forker) testInsert(t, hc, chainB[0:32], CanonStatTy, nil)
// Inserting more side blocks, but we don't have the parent // Inserting more headers on chain B, but we don't have the parent
testInsert(t, hc, chainB[34:36], NonStatTy, consensus.ErrUnknownAncestor, forker) testInsert(t, hc, chainB[34:36], NonStatTy, consensus.ErrUnknownAncestor)
// Inserting more sideblocks, overtaking the canon chain // Inserting more headers on chain B, extend the canon chain
testInsert(t, hc, chainB[32:97], CanonStatTy, nil, forker) testInsert(t, hc, chainB[32:97], CanonStatTy, nil)
// Inserting more A-headers, taking back the canonicality // Inserting more headers on chain A, taking back the canonicality
testInsert(t, hc, chainA[90:100], CanonStatTy, nil, forker) testInsert(t, hc, chainA[90:100], CanonStatTy, nil)
// And B becomes canon again // And B becomes canon again
testInsert(t, hc, chainB[97:107], CanonStatTy, nil, forker) testInsert(t, hc, chainB[97:107], CanonStatTy, nil)
// And B becomes even longer // And B becomes even longer
testInsert(t, hc, chainB[107:128], CanonStatTy, nil, forker) testInsert(t, hc, chainB[107:128], CanonStatTy, nil)
} }

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

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

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

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

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

@ -28,8 +28,8 @@ import (
// mode specifies how a tree location has been accessed // mode specifies how a tree location has been accessed
// for the byte value: // for the byte value:
// * the first bit is set if the branch has been edited // * the first bit is set if the branch has been read
// * the second bit is set if the branch has been read // * the second bit is set if the branch has been edited
type mode byte type mode byte
const ( const (
@ -94,11 +94,8 @@ func (ae *AccessEvents) Copy() *AccessEvents {
// member fields of an account. // member fields of an account.
func (ae *AccessEvents) AddAccount(addr common.Address, isWrite bool) uint64 { func (ae *AccessEvents) AddAccount(addr common.Address, isWrite bool) uint64 {
var gas uint64 var gas uint64
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, isWrite) gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, isWrite)
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, isWrite) gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite)
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, isWrite)
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeKeccakLeafKey, isWrite)
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey, isWrite)
return gas return gas
} }
@ -107,8 +104,7 @@ func (ae *AccessEvents) AddAccount(addr common.Address, isWrite bool) uint64 {
// call to that account. // call to that account.
func (ae *AccessEvents) MessageCallGas(destination common.Address) uint64 { func (ae *AccessEvents) MessageCallGas(destination common.Address) uint64 {
var gas uint64 var gas uint64
gas += ae.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.VersionLeafKey, false) gas += ae.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.BasicDataLeafKey, false)
gas += ae.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.CodeSizeLeafKey, false)
return gas return gas
} }
@ -116,41 +112,43 @@ func (ae *AccessEvents) MessageCallGas(destination common.Address) uint64 {
// cold balance member fields of the caller and the callee accounts. // cold balance member fields of the caller and the callee accounts.
func (ae *AccessEvents) ValueTransferGas(callerAddr, targetAddr common.Address) uint64 { func (ae *AccessEvents) ValueTransferGas(callerAddr, targetAddr common.Address) uint64 {
var gas uint64 var gas uint64
gas += ae.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BalanceLeafKey, true) gas += ae.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BasicDataLeafKey, true)
gas += ae.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BalanceLeafKey, true) gas += ae.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BasicDataLeafKey, true)
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 return gas
} }
// ContractCreateInitGas returns the access gas costs for the initialization of // ContractCreateInitGas returns the access gas costs for the initialization of
// a contract creation. // a contract creation.
func (ae *AccessEvents) ContractCreateInitGas(addr common.Address, createSendsValue bool) uint64 { func (ae *AccessEvents) ContractCreateInitGas(addr common.Address) uint64 {
var gas uint64 var gas uint64
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, true) gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, true)
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, true) gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, true)
if createSendsValue {
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, true)
}
return gas return gas
} }
// AddTxOrigin adds the member fields of the sender account to the access event list, // AddTxOrigin adds the member fields of the sender account to the access event list,
// so that cold accesses are not charged, since they are covered by the 21000 gas. // so that cold accesses are not charged, since they are covered by the 21000 gas.
func (ae *AccessEvents) AddTxOrigin(originAddr common.Address) { func (ae *AccessEvents) AddTxOrigin(originAddr common.Address) {
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.VersionLeafKey, false) ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.BasicDataLeafKey, true)
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.BalanceLeafKey, true) ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.CodeHashLeafKey, false)
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.NonceLeafKey, true)
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.CodeKeccakLeafKey, false)
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.CodeSizeLeafKey, false)
} }
// AddTxDestination adds the member fields of the sender account to the access event list, // AddTxDestination adds the member fields of the sender account to the access event list,
// so that cold accesses are not charged, since they are covered by the 21000 gas. // so that cold accesses are not charged, since they are covered by the 21000 gas.
func (ae *AccessEvents) AddTxDestination(addr common.Address, sendsValue bool) { func (ae *AccessEvents) AddTxDestination(addr common.Address, sendsValue bool) {
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, false) ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, sendsValue)
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, sendsValue) ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, false)
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, false)
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeKeccakLeafKey, false)
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey, false)
} }
// SlotGas returns the amount of gas to be charged for a cold storage access. // SlotGas returns the amount of gas to be charged for a cold storage access.
@ -275,39 +273,12 @@ func (ae *AccessEvents) CodeChunksRangeGas(contractAddr common.Address, startPC,
return statelessGasCharged return statelessGasCharged
} }
// VersionGas adds the account's version to the accessed data, and returns the // BasicDataGas adds the account's basic data to the accessed data, and returns the
// amount of gas that it costs. // amount of gas that it costs.
// Note that an access in write mode implies an access in read mode, whereas an // Note that an access in write mode implies an access in read mode, whereas an
// access in read mode does not imply an access in write mode. // access in read mode does not imply an access in write mode.
func (ae *AccessEvents) VersionGas(addr common.Address, isWrite bool) uint64 { func (ae *AccessEvents) BasicDataGas(addr common.Address, isWrite bool) uint64 {
return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, isWrite) return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, isWrite)
}
// BalanceGas adds the account's balance to the accessed data, and returns the
// amount of gas that it costs.
// in write mode. If false, the charged gas corresponds to an access in read mode.
// Note that an access in write mode implies an access in read mode, whereas an access in
// read mode does not imply an access in write mode.
func (ae *AccessEvents) BalanceGas(addr common.Address, isWrite bool) uint64 {
return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, isWrite)
}
// NonceGas adds the account's nonce to the accessed data, and returns the
// amount of gas that it costs.
// in write mode. If false, the charged gas corresponds to an access in read mode.
// Note that an access in write mode implies an access in read mode, whereas an access in
// read mode does not imply an access in write mode.
func (ae *AccessEvents) NonceGas(addr common.Address, isWrite bool) uint64 {
return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, isWrite)
}
// CodeSizeGas adds the account's code size to the accessed data, and returns the
// amount of gas that it costs.
// in write mode. If false, the charged gas corresponds to an access in read mode.
// Note that an access in write mode implies an access in read mode, whereas an access in
// read mode does not imply an access in write mode.
func (ae *AccessEvents) CodeSizeGas(addr common.Address, isWrite bool) uint64 {
return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey, isWrite)
} }
// CodeHashGas adds the account's code hash to the accessed data, and returns the // CodeHashGas adds the account's code hash to the accessed data, and returns the
@ -316,5 +287,5 @@ func (ae *AccessEvents) CodeSizeGas(addr common.Address, isWrite bool) uint64 {
// Note that an access in write mode implies an access in read mode, whereas an access in // Note that an access in write mode implies an access in read mode, whereas an access in
// read mode does not imply an access in write mode. // read mode does not imply an access in write mode.
func (ae *AccessEvents) CodeHashGas(addr common.Address, isWrite bool) uint64 { func (ae *AccessEvents) CodeHashGas(addr common.Address, isWrite bool) uint64 {
return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeKeccakLeafKey, isWrite) return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite)
} }

@ -40,55 +40,43 @@ func TestAccountHeaderGas(t *testing.T) {
ae := NewAccessEvents(utils.NewPointCache(1024)) ae := NewAccessEvents(utils.NewPointCache(1024))
// Check cold read cost // Check cold read cost
gas := ae.VersionGas(testAddr, false) gas := ae.BasicDataGas(testAddr, false)
if want := params.WitnessBranchReadCost + params.WitnessChunkReadCost; gas != want { if want := params.WitnessBranchReadCost + params.WitnessChunkReadCost; gas != want {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, want) t.Fatalf("incorrect gas computed, got %d, want %d", gas, want)
} }
// Check warm read cost // Check warm read cost
gas = ae.VersionGas(testAddr, false) gas = ae.BasicDataGas(testAddr, false)
if gas != 0 { if gas != 0 {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
} }
// Check cold read costs in the same group no longer incur the branch read cost // Check cold read costs in the same group no longer incur the branch read cost
gas = ae.BalanceGas(testAddr, false)
if gas != params.WitnessChunkReadCost {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost)
}
gas = ae.NonceGas(testAddr, false)
if gas != params.WitnessChunkReadCost {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost)
}
gas = ae.CodeSizeGas(testAddr, false)
if gas != params.WitnessChunkReadCost {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost)
}
gas = ae.CodeHashGas(testAddr, false) gas = ae.CodeHashGas(testAddr, false)
if gas != params.WitnessChunkReadCost { if gas != params.WitnessChunkReadCost {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost) t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost)
} }
// Check cold write cost // Check cold write cost
gas = ae.VersionGas(testAddr, true) gas = ae.BasicDataGas(testAddr, true)
if want := params.WitnessBranchWriteCost + params.WitnessChunkWriteCost; gas != want { if want := params.WitnessBranchWriteCost + params.WitnessChunkWriteCost; gas != want {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, want) t.Fatalf("incorrect gas computed, got %d, want %d", gas, want)
} }
// Check warm write cost // Check warm write cost
gas = ae.VersionGas(testAddr, true) gas = ae.BasicDataGas(testAddr, true)
if gas != 0 { if gas != 0 {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
} }
// Check a write without a read charges both read and write costs // Check a write without a read charges both read and write costs
gas = ae.BalanceGas(testAddr2, true) gas = ae.BasicDataGas(testAddr2, true)
if want := params.WitnessBranchReadCost + params.WitnessBranchWriteCost + params.WitnessChunkWriteCost + params.WitnessChunkReadCost; gas != want { if want := params.WitnessBranchReadCost + params.WitnessBranchWriteCost + params.WitnessChunkWriteCost + params.WitnessChunkReadCost; gas != want {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, want) t.Fatalf("incorrect gas computed, got %d, want %d", gas, want)
} }
// Check that a write followed by a read charges nothing // Check that a write followed by a read charges nothing
gas = ae.BalanceGas(testAddr2, false) gas = ae.BasicDataGas(testAddr2, false)
if gas != 0 { if gas != 0 {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
} }
@ -112,13 +100,13 @@ func TestContractCreateInitGas(t *testing.T) {
} }
// Check cold read cost, without a value // Check cold read cost, without a value
gas := ae.ContractCreateInitGas(testAddr, false) gas := ae.ContractCreateInitGas(testAddr)
if want := params.WitnessBranchWriteCost + params.WitnessBranchReadCost + params.WitnessChunkWriteCost*2 + params.WitnessChunkReadCost*2; gas != want { if want := params.WitnessBranchWriteCost + params.WitnessBranchReadCost + 2*params.WitnessChunkWriteCost + 2*params.WitnessChunkReadCost; gas != want {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, want) t.Fatalf("incorrect gas computed, got %d, want %d", gas, want)
} }
// Check warm read cost // Check warm read cost
gas = ae.ContractCreateInitGas(testAddr, false) gas = ae.ContractCreateInitGas(testAddr)
if gas != 0 { if gas != 0 {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
} }
@ -131,17 +119,17 @@ func TestMessageCallGas(t *testing.T) {
// Check cold read cost, without a value // Check cold read cost, without a value
gas := ae.MessageCallGas(testAddr) gas := ae.MessageCallGas(testAddr)
if want := params.WitnessBranchReadCost + params.WitnessChunkReadCost*2; gas != want { if want := params.WitnessBranchReadCost + params.WitnessChunkReadCost; gas != want {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, want) t.Fatalf("incorrect gas computed, got %d, want %d", gas, want)
} }
// Check that reading the version and code size of the same account does not incur the branch read cost // Check that reading the basic data and code hash of the same account does not incur the branch read cost
gas = ae.VersionGas(testAddr, false) gas = ae.BasicDataGas(testAddr, false)
if gas != 0 { if gas != 0 {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
} }
gas = ae.CodeSizeGas(testAddr, false) gas = ae.CodeHashGas(testAddr, false)
if gas != 0 { if gas != params.WitnessChunkReadCost {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
} }

@ -23,6 +23,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/lru" "github.com/ethereum/go-ethereum/common/lru"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state/snapshot"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
@ -45,29 +46,29 @@ const (
// Database wraps access to tries and contract code. // Database wraps access to tries and contract code.
type Database interface { type Database interface {
// Reader returns a state reader associated with the specified state root.
Reader(root common.Hash) (Reader, error)
// OpenTrie opens the main account trie. // OpenTrie opens the main account trie.
OpenTrie(root common.Hash) (Trie, error) OpenTrie(root common.Hash) (Trie, error)
// OpenStorageTrie opens the storage trie of an account. // OpenStorageTrie opens the storage trie of an account.
OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash, trie Trie) (Trie, error) OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash, trie Trie) (Trie, error)
// CopyTrie returns an independent copy of the given trie.
CopyTrie(Trie) Trie
// ContractCode retrieves a particular contract's code. // ContractCode retrieves a particular contract's code.
ContractCode(addr common.Address, codeHash common.Hash) ([]byte, error) ContractCode(addr common.Address, codeHash common.Hash) ([]byte, error)
// ContractCodeSize retrieves a particular contracts code's size. // ContractCodeSize retrieves a particular contracts code's size.
ContractCodeSize(addr common.Address, codeHash common.Hash) (int, error) ContractCodeSize(addr common.Address, codeHash common.Hash) (int, error)
// DiskDB returns the underlying key-value disk database.
DiskDB() ethdb.KeyValueStore
// PointCache returns the cache holding points used in verkle tree key computation // PointCache returns the cache holding points used in verkle tree key computation
PointCache() *utils.PointCache PointCache() *utils.PointCache
// TrieDB returns the underlying trie database for managing trie nodes. // TrieDB returns the underlying trie database for managing trie nodes.
TrieDB() *triedb.Database TrieDB() *triedb.Database
// Snapshot returns the underlying state snapshot.
Snapshot() *snapshot.Tree
} }
// Trie is a Ethereum Merkle Patricia trie. // Trie is a Ethereum Merkle Patricia trie.
@ -94,7 +95,7 @@ type Trie interface {
// UpdateAccount abstracts an account write to the trie. It encodes the // UpdateAccount abstracts an account write to the trie. It encodes the
// provided account object with associated algorithm and then updates it // provided account object with associated algorithm and then updates it
// in the trie with provided address. // in the trie with provided address.
UpdateAccount(address common.Address, account *types.StateAccount) error UpdateAccount(address common.Address, account *types.StateAccount, codeLen int) error
// UpdateStorage associates key with value in the trie. If value has length zero, // UpdateStorage associates key with value in the trie. If value has length zero,
// any existing value is deleted from the trie. The value bytes must not be modified // any existing value is deleted from the trie. The value bytes must not be modified
@ -147,47 +148,62 @@ type Trie interface {
IsVerkle() bool IsVerkle() bool
} }
// NewDatabase creates a backing store for state. The returned database is safe for // CachingDB is an implementation of Database interface. It leverages both trie and
// concurrent use, but does not retain any recent trie nodes in memory. To keep some // state snapshot to provide functionalities for state access. It's meant to be a
// historical state in memory, use the NewDatabaseWithConfig constructor. // long-live object and has a few caches inside for sharing between blocks.
func NewDatabase(db ethdb.Database) Database { type CachingDB struct {
return NewDatabaseWithConfig(db, nil) disk ethdb.KeyValueStore
triedb *triedb.Database
snap *snapshot.Tree
codeCache *lru.SizeConstrainedCache[common.Hash, []byte]
codeSizeCache *lru.Cache[common.Hash, int]
pointCache *utils.PointCache
} }
// NewDatabaseWithConfig creates a backing store for state. The returned database // NewDatabase creates a state database with the provided data sources.
// is safe for concurrent use and retains a lot of collapsed RLP trie nodes in a func NewDatabase(triedb *triedb.Database, snap *snapshot.Tree) *CachingDB {
// large memory cache. return &CachingDB{
func NewDatabaseWithConfig(db ethdb.Database, config *triedb.Config) Database { disk: triedb.Disk(),
return &cachingDB{ triedb: triedb,
disk: db, snap: snap,
codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize),
codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize), codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize),
triedb: triedb.NewDatabase(db, config), codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize),
pointCache: utils.NewPointCache(pointCacheSize), pointCache: utils.NewPointCache(pointCacheSize),
} }
} }
// NewDatabaseWithNodeDB creates a state database with an already initialized node database. // NewDatabaseForTesting is similar to NewDatabase, but it initializes the caching
func NewDatabaseWithNodeDB(db ethdb.Database, triedb *triedb.Database) Database { // db by using an ephemeral memory db with default config for testing.
return &cachingDB{ func NewDatabaseForTesting() *CachingDB {
disk: db, return NewDatabase(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil), nil)
codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize),
codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize),
triedb: triedb,
pointCache: utils.NewPointCache(pointCacheSize),
}
} }
type cachingDB struct { // Reader returns a state reader associated with the specified state root.
disk ethdb.KeyValueStore func (db *CachingDB) Reader(stateRoot common.Hash) (Reader, error) {
codeSizeCache *lru.Cache[common.Hash, int] var readers []Reader
codeCache *lru.SizeConstrainedCache[common.Hash, []byte]
triedb *triedb.Database // Set up the state snapshot reader if available. This feature
pointCache *utils.PointCache // is optional and may be partially useful if it's not fully
// generated.
if db.snap != nil {
sr, err := newStateReader(stateRoot, db.snap)
if err == nil {
readers = append(readers, sr) // snap reader is optional
}
}
// Set up the trie reader, which is expected to always be available
// as the gatekeeper unless the state is corrupted.
tr, err := newTrieReader(stateRoot, db.triedb, db.pointCache)
if err != nil {
return nil, err
}
readers = append(readers, tr)
return newMultiReader(readers...)
} }
// OpenTrie opens the main account trie at a specific root hash. // OpenTrie opens the main account trie at a specific root hash.
func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) { func (db *CachingDB) OpenTrie(root common.Hash) (Trie, error) {
if db.triedb.IsVerkle() { if db.triedb.IsVerkle() {
return trie.NewVerkleTrie(root, db.triedb, db.pointCache) return trie.NewVerkleTrie(root, db.triedb, db.pointCache)
} }
@ -199,7 +215,7 @@ func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) {
} }
// OpenStorageTrie opens the storage trie of an account. // OpenStorageTrie opens the storage trie of an account.
func (db *cachingDB) OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash, self Trie) (Trie, error) { func (db *CachingDB) OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash, self Trie) (Trie, error) {
// In the verkle case, there is only one tree. But the two-tree structure // In the verkle case, there is only one tree. But the two-tree structure
// is hardcoded in the codebase. So we need to return the same trie in this // is hardcoded in the codebase. So we need to return the same trie in this
// case. // case.
@ -213,20 +229,8 @@ func (db *cachingDB) OpenStorageTrie(stateRoot common.Hash, address common.Addre
return tr, nil return tr, nil
} }
// CopyTrie returns an independent copy of the given trie.
func (db *cachingDB) CopyTrie(t Trie) Trie {
switch t := t.(type) {
case *trie.StateTrie:
return t.Copy()
case *trie.VerkleTrie:
return t.Copy()
default:
panic(fmt.Errorf("unknown trie type %T", t))
}
}
// ContractCode retrieves a particular contract's code. // ContractCode retrieves a particular contract's code.
func (db *cachingDB) ContractCode(address common.Address, codeHash common.Hash) ([]byte, error) { func (db *CachingDB) ContractCode(address common.Address, codeHash common.Hash) ([]byte, error) {
code, _ := db.codeCache.Get(codeHash) code, _ := db.codeCache.Get(codeHash)
if len(code) > 0 { if len(code) > 0 {
return code, nil return code, nil
@ -243,7 +247,7 @@ func (db *cachingDB) ContractCode(address common.Address, codeHash common.Hash)
// ContractCodeWithPrefix retrieves a particular contract's code. If the // ContractCodeWithPrefix retrieves a particular contract's code. If the
// code can't be found in the cache, then check the existence with **new** // code can't be found in the cache, then check the existence with **new**
// db scheme. // db scheme.
func (db *cachingDB) ContractCodeWithPrefix(address common.Address, codeHash common.Hash) ([]byte, error) { func (db *CachingDB) ContractCodeWithPrefix(address common.Address, codeHash common.Hash) ([]byte, error) {
code, _ := db.codeCache.Get(codeHash) code, _ := db.codeCache.Get(codeHash)
if len(code) > 0 { if len(code) > 0 {
return code, nil return code, nil
@ -258,7 +262,7 @@ func (db *cachingDB) ContractCodeWithPrefix(address common.Address, codeHash com
} }
// ContractCodeSize retrieves a particular contracts code's size. // ContractCodeSize retrieves a particular contracts code's size.
func (db *cachingDB) ContractCodeSize(addr common.Address, codeHash common.Hash) (int, error) { func (db *CachingDB) ContractCodeSize(addr common.Address, codeHash common.Hash) (int, error) {
if cached, ok := db.codeSizeCache.Get(codeHash); ok { if cached, ok := db.codeSizeCache.Get(codeHash); ok {
return cached, nil return cached, nil
} }
@ -266,17 +270,29 @@ func (db *cachingDB) ContractCodeSize(addr common.Address, codeHash common.Hash)
return len(code), err return len(code), err
} }
// DiskDB returns the underlying key-value disk database.
func (db *cachingDB) DiskDB() ethdb.KeyValueStore {
return db.disk
}
// TrieDB retrieves any intermediate trie-node caching layer. // TrieDB retrieves any intermediate trie-node caching layer.
func (db *cachingDB) TrieDB() *triedb.Database { func (db *CachingDB) TrieDB() *triedb.Database {
return db.triedb return db.triedb
} }
// PointCache returns the cache of evaluated curve points. // PointCache returns the cache of evaluated curve points.
func (db *cachingDB) PointCache() *utils.PointCache { func (db *CachingDB) PointCache() *utils.PointCache {
return db.pointCache return db.pointCache
} }
// Snapshot returns the underlying state snapshot.
func (db *CachingDB) Snapshot() *snapshot.Tree {
return db.snap
}
// mustCopyTrie returns a deep-copied trie.
func mustCopyTrie(t Trie) Trie {
switch t := t.(type) {
case *trie.StateTrie:
return t.Copy()
case *trie.VerkleTrie:
return t.Copy()
default:
panic(fmt.Errorf("unknown trie type %T", t))
}
}

@ -35,7 +35,7 @@ func testNodeIteratorCoverage(t *testing.T, scheme string) {
db, sdb, ndb, root, _ := makeTestState(scheme) db, sdb, ndb, root, _ := makeTestState(scheme)
ndb.Commit(root, false) ndb.Commit(root, false)
state, err := New(root, sdb, nil) state, err := New(root, sdb)
if err != nil { if err != nil {
t.Fatalf("failed to create state trie at %x: %v", root, err) t.Fatalf("failed to create state trie at %x: %v", root, err)
} }

@ -17,12 +17,21 @@
package state package state
import ( import (
"fmt"
"maps" "maps"
"slices"
"sort"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/holiman/uint256" "github.com/holiman/uint256"
) )
type revision struct {
id int
journalIndex int
}
// journalEntry is a modification entry in the state change journal that can be // journalEntry is a modification entry in the state change journal that can be
// reverted on demand. // reverted on demand.
type journalEntry interface { type journalEntry interface {
@ -42,6 +51,9 @@ type journalEntry interface {
type journal struct { type journal struct {
entries []journalEntry // Current changes tracked by the journal entries []journalEntry // Current changes tracked by the journal
dirties map[common.Address]int // Dirty accounts and the number of changes dirties map[common.Address]int // Dirty accounts and the number of changes
validRevisions []revision
nextRevisionId int
} }
// newJournal creates a new initialized journal. // newJournal creates a new initialized journal.
@ -51,6 +63,40 @@ func newJournal() *journal {
} }
} }
// reset clears the journal, after this operation the journal can be used anew.
// It is semantically similar to calling 'newJournal', but the underlying slices
// can be reused.
func (j *journal) reset() {
j.entries = j.entries[:0]
j.validRevisions = j.validRevisions[:0]
clear(j.dirties)
j.nextRevisionId = 0
}
// snapshot returns an identifier for the current revision of the state.
func (j *journal) snapshot() int {
id := j.nextRevisionId
j.nextRevisionId++
j.validRevisions = append(j.validRevisions, revision{id, j.length()})
return id
}
// revertToSnapshot reverts all state changes made since the given revision.
func (j *journal) revertToSnapshot(revid int, s *StateDB) {
// Find the snapshot in the stack of valid snapshots.
idx := sort.Search(len(j.validRevisions), func(i int) bool {
return j.validRevisions[i].id >= revid
})
if idx == len(j.validRevisions) || j.validRevisions[idx].id != revid {
panic(fmt.Errorf("revision id %v cannot be reverted", revid))
}
snapshot := j.validRevisions[idx].journalIndex
// Replay the journal to undo changes and remove invalidated snapshots
j.revert(s, snapshot)
j.validRevisions = j.validRevisions[:idx]
}
// append inserts a new modification entry to the end of the change journal. // append inserts a new modification entry to the end of the change journal.
func (j *journal) append(entry journalEntry) { func (j *journal) append(entry journalEntry) {
j.entries = append(j.entries, entry) j.entries = append(j.entries, entry)
@ -95,48 +141,122 @@ func (j *journal) copy() *journal {
entries = append(entries, j.entries[i].copy()) entries = append(entries, j.entries[i].copy())
} }
return &journal{ return &journal{
entries: entries, entries: entries,
dirties: maps.Clone(j.dirties), dirties: maps.Clone(j.dirties),
validRevisions: slices.Clone(j.validRevisions),
nextRevisionId: j.nextRevisionId,
}
}
func (j *journal) logChange(txHash common.Hash) {
j.append(addLogChange{txhash: txHash})
}
func (j *journal) createObject(addr common.Address) {
j.append(createObjectChange{account: addr})
}
func (j *journal) createContract(addr common.Address) {
j.append(createContractChange{account: addr})
}
func (j *journal) destruct(addr common.Address) {
j.append(selfDestructChange{account: addr})
}
func (j *journal) storageChange(addr common.Address, key, prev, origin common.Hash) {
j.append(storageChange{
account: addr,
key: key,
prevvalue: prev,
origvalue: origin,
})
}
func (j *journal) transientStateChange(addr common.Address, key, prev common.Hash) {
j.append(transientStorageChange{
account: addr,
key: key,
prevalue: prev,
})
}
func (j *journal) refundChange(previous uint64) {
j.append(refundChange{prev: previous})
}
func (j *journal) balanceChange(addr common.Address, previous *uint256.Int) {
j.append(balanceChange{
account: addr,
prev: previous.Clone(),
})
}
func (j *journal) setCode(address common.Address) {
j.append(codeChange{account: address})
}
func (j *journal) nonceChange(address common.Address, prev uint64) {
j.append(nonceChange{
account: address,
prev: prev,
})
}
func (j *journal) touchChange(address common.Address) {
j.append(touchChange{
account: address,
})
if address == ripemd {
// Explicitly put it in the dirty-cache, which is otherwise generated from
// flattened journals.
j.dirty(address)
} }
} }
func (j *journal) accessListAddAccount(addr common.Address) {
j.append(accessListAddAccountChange{addr})
}
func (j *journal) accessListAddSlot(addr common.Address, slot common.Hash) {
j.append(accessListAddSlotChange{
address: addr,
slot: slot,
})
}
type ( type (
// Changes to the account trie. // Changes to the account trie.
createObjectChange struct { createObjectChange struct {
account *common.Address account common.Address
} }
// createContractChange represents an account becoming a contract-account. // createContractChange represents an account becoming a contract-account.
// This event happens prior to executing initcode. The journal-event simply // This event happens prior to executing initcode. The journal-event simply
// manages the created-flag, in order to allow same-tx destruction. // manages the created-flag, in order to allow same-tx destruction.
createContractChange struct { createContractChange struct {
account common.Address account common.Address
} }
selfDestructChange struct { selfDestructChange struct {
account *common.Address account common.Address
prev bool // whether account had already self-destructed
prevbalance *uint256.Int
} }
// Changes to individual accounts. // Changes to individual accounts.
balanceChange struct { balanceChange struct {
account *common.Address account common.Address
prev *uint256.Int prev *uint256.Int
} }
nonceChange struct { nonceChange struct {
account *common.Address account common.Address
prev uint64 prev uint64
} }
storageChange struct { storageChange struct {
account *common.Address account common.Address
key common.Hash key common.Hash
prevvalue common.Hash prevvalue common.Hash
origvalue common.Hash origvalue common.Hash
} }
codeChange struct { codeChange struct {
account *common.Address account common.Address
prevcode, prevhash []byte
} }
// Changes to other state values. // Changes to other state values.
@ -146,35 +266,32 @@ type (
addLogChange struct { addLogChange struct {
txhash common.Hash txhash common.Hash
} }
addPreimageChange struct {
hash common.Hash
}
touchChange struct { touchChange struct {
account *common.Address account common.Address
} }
// Changes to the access list // Changes to the access list
accessListAddAccountChange struct { accessListAddAccountChange struct {
address *common.Address address common.Address
} }
accessListAddSlotChange struct { accessListAddSlotChange struct {
address *common.Address address common.Address
slot *common.Hash slot common.Hash
} }
// Changes to transient storage // Changes to transient storage
transientStorageChange struct { transientStorageChange struct {
account *common.Address account common.Address
key, prevalue common.Hash key, prevalue common.Hash
} }
) )
func (ch createObjectChange) revert(s *StateDB) { func (ch createObjectChange) revert(s *StateDB) {
delete(s.stateObjects, *ch.account) delete(s.stateObjects, ch.account)
} }
func (ch createObjectChange) dirtied() *common.Address { func (ch createObjectChange) dirtied() *common.Address {
return ch.account return &ch.account
} }
func (ch createObjectChange) copy() journalEntry { func (ch createObjectChange) copy() journalEntry {
@ -198,22 +315,19 @@ func (ch createContractChange) copy() journalEntry {
} }
func (ch selfDestructChange) revert(s *StateDB) { func (ch selfDestructChange) revert(s *StateDB) {
obj := s.getStateObject(*ch.account) obj := s.getStateObject(ch.account)
if obj != nil { if obj != nil {
obj.selfDestructed = ch.prev obj.selfDestructed = false
obj.setBalance(ch.prevbalance)
} }
} }
func (ch selfDestructChange) dirtied() *common.Address { func (ch selfDestructChange) dirtied() *common.Address {
return ch.account return &ch.account
} }
func (ch selfDestructChange) copy() journalEntry { func (ch selfDestructChange) copy() journalEntry {
return selfDestructChange{ return selfDestructChange{
account: ch.account, account: ch.account,
prev: ch.prev,
prevbalance: new(uint256.Int).Set(ch.prevbalance),
} }
} }
@ -223,7 +337,7 @@ func (ch touchChange) revert(s *StateDB) {
} }
func (ch touchChange) dirtied() *common.Address { func (ch touchChange) dirtied() *common.Address {
return ch.account return &ch.account
} }
func (ch touchChange) copy() journalEntry { func (ch touchChange) copy() journalEntry {
@ -233,11 +347,11 @@ func (ch touchChange) copy() journalEntry {
} }
func (ch balanceChange) revert(s *StateDB) { func (ch balanceChange) revert(s *StateDB) {
s.getStateObject(*ch.account).setBalance(ch.prev) s.getStateObject(ch.account).setBalance(ch.prev)
} }
func (ch balanceChange) dirtied() *common.Address { func (ch balanceChange) dirtied() *common.Address {
return ch.account return &ch.account
} }
func (ch balanceChange) copy() journalEntry { func (ch balanceChange) copy() journalEntry {
@ -248,11 +362,11 @@ func (ch balanceChange) copy() journalEntry {
} }
func (ch nonceChange) revert(s *StateDB) { func (ch nonceChange) revert(s *StateDB) {
s.getStateObject(*ch.account).setNonce(ch.prev) s.getStateObject(ch.account).setNonce(ch.prev)
} }
func (ch nonceChange) dirtied() *common.Address { func (ch nonceChange) dirtied() *common.Address {
return ch.account return &ch.account
} }
func (ch nonceChange) copy() journalEntry { func (ch nonceChange) copy() journalEntry {
@ -263,27 +377,23 @@ func (ch nonceChange) copy() journalEntry {
} }
func (ch codeChange) revert(s *StateDB) { func (ch codeChange) revert(s *StateDB) {
s.getStateObject(*ch.account).setCode(common.BytesToHash(ch.prevhash), ch.prevcode) s.getStateObject(ch.account).setCode(types.EmptyCodeHash, nil)
} }
func (ch codeChange) dirtied() *common.Address { func (ch codeChange) dirtied() *common.Address {
return ch.account return &ch.account
} }
func (ch codeChange) copy() journalEntry { func (ch codeChange) copy() journalEntry {
return codeChange{ return codeChange{account: ch.account}
account: ch.account,
prevhash: common.CopyBytes(ch.prevhash),
prevcode: common.CopyBytes(ch.prevcode),
}
} }
func (ch storageChange) revert(s *StateDB) { func (ch storageChange) revert(s *StateDB) {
s.getStateObject(*ch.account).setState(ch.key, ch.prevvalue, ch.origvalue) s.getStateObject(ch.account).setState(ch.key, ch.prevvalue, ch.origvalue)
} }
func (ch storageChange) dirtied() *common.Address { func (ch storageChange) dirtied() *common.Address {
return ch.account return &ch.account
} }
func (ch storageChange) copy() journalEntry { func (ch storageChange) copy() journalEntry {
@ -295,7 +405,7 @@ func (ch storageChange) copy() journalEntry {
} }
func (ch transientStorageChange) revert(s *StateDB) { func (ch transientStorageChange) revert(s *StateDB) {
s.setTransientState(*ch.account, ch.key, ch.prevalue) s.setTransientState(ch.account, ch.key, ch.prevalue)
} }
func (ch transientStorageChange) dirtied() *common.Address { func (ch transientStorageChange) dirtied() *common.Address {
@ -344,20 +454,6 @@ func (ch addLogChange) copy() journalEntry {
} }
} }
func (ch addPreimageChange) revert(s *StateDB) {
delete(s.preimages, ch.hash)
}
func (ch addPreimageChange) dirtied() *common.Address {
return nil
}
func (ch addPreimageChange) copy() journalEntry {
return addPreimageChange{
hash: ch.hash,
}
}
func (ch accessListAddAccountChange) revert(s *StateDB) { func (ch accessListAddAccountChange) revert(s *StateDB) {
/* /*
One important invariant here, is that whenever a (addr, slot) is added, if the One important invariant here, is that whenever a (addr, slot) is added, if the
@ -368,7 +464,7 @@ func (ch accessListAddAccountChange) revert(s *StateDB) {
(addr) at this point, since no storage adds can remain when come upon (addr) at this point, since no storage adds can remain when come upon
a single (addr) change. a single (addr) change.
*/ */
s.accessList.DeleteAddress(*ch.address) s.accessList.DeleteAddress(ch.address)
} }
func (ch accessListAddAccountChange) dirtied() *common.Address { func (ch accessListAddAccountChange) dirtied() *common.Address {
@ -382,7 +478,7 @@ func (ch accessListAddAccountChange) copy() journalEntry {
} }
func (ch accessListAddSlotChange) revert(s *StateDB) { func (ch accessListAddSlotChange) revert(s *StateDB) {
s.accessList.DeleteSlot(*ch.address, *ch.slot) s.accessList.DeleteSlot(ch.address, ch.slot)
} }
func (ch accessListAddSlotChange) dirtied() *common.Address { func (ch accessListAddSlotChange) dirtied() *common.Address {

@ -19,6 +19,8 @@ package state
import "github.com/ethereum/go-ethereum/metrics" import "github.com/ethereum/go-ethereum/metrics"
var ( var (
accountReadMeters = metrics.NewRegisteredMeter("state/read/accounts", nil)
storageReadMeters = metrics.NewRegisteredMeter("state/read/storage", nil)
accountUpdatedMeter = metrics.NewRegisteredMeter("state/update/account", nil) accountUpdatedMeter = metrics.NewRegisteredMeter("state/update/account", nil)
storageUpdatedMeter = metrics.NewRegisteredMeter("state/update/storage", nil) storageUpdatedMeter = metrics.NewRegisteredMeter("state/update/storage", nil)
accountDeletedMeter = metrics.NewRegisteredMeter("state/delete/account", nil) accountDeletedMeter = metrics.NewRegisteredMeter("state/delete/account", nil)

@ -0,0 +1,313 @@
// 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 state
import (
"errors"
"maps"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state/snapshot"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/utils"
"github.com/ethereum/go-ethereum/triedb"
)
// Reader defines the interface for accessing accounts and storage slots
// associated with a specific state.
type Reader interface {
// Account retrieves the account associated with a particular address.
//
// - Returns a nil account if it does not exist
// - Returns an error only if an unexpected issue occurs
// - The returned account is safe to modify after the call
Account(addr common.Address) (*types.StateAccount, error)
// Storage retrieves the storage slot associated with a particular account
// address and slot key.
//
// - Returns an empty slot if it does not exist
// - Returns an error only if an unexpected issue occurs
// - The returned storage slot is safe to modify after the call
Storage(addr common.Address, slot common.Hash) (common.Hash, error)
// Copy returns a deep-copied state reader.
Copy() Reader
}
// stateReader is a wrapper over the state snapshot and implements the Reader
// interface. It provides an efficient way to access flat state.
type stateReader struct {
snap snapshot.Snapshot
buff crypto.KeccakState
}
// newStateReader constructs a flat state reader with on the specified state root.
func newStateReader(root common.Hash, snaps *snapshot.Tree) (*stateReader, error) {
snap := snaps.Snapshot(root)
if snap == nil {
return nil, errors.New("snapshot is not available")
}
return &stateReader{
snap: snap,
buff: crypto.NewKeccakState(),
}, nil
}
// Account implements Reader, retrieving the account specified by the address.
//
// An error will be returned if the associated snapshot is already stale or
// the requested account is not yet covered by the snapshot.
//
// The returned account might be nil if it's not existent.
func (r *stateReader) Account(addr common.Address) (*types.StateAccount, error) {
ret, err := r.snap.Account(crypto.HashData(r.buff, addr.Bytes()))
if err != nil {
return nil, err
}
if ret == nil {
return nil, nil
}
acct := &types.StateAccount{
Nonce: ret.Nonce,
Balance: ret.Balance,
CodeHash: ret.CodeHash,
Root: common.BytesToHash(ret.Root),
}
if len(acct.CodeHash) == 0 {
acct.CodeHash = types.EmptyCodeHash.Bytes()
}
if acct.Root == (common.Hash{}) {
acct.Root = types.EmptyRootHash
}
return acct, nil
}
// Storage implements Reader, retrieving the storage slot specified by the
// address and slot key.
//
// An error will be returned if the associated snapshot is already stale or
// the requested storage slot is not yet covered by the snapshot.
//
// The returned storage slot might be empty if it's not existent.
func (r *stateReader) Storage(addr common.Address, key common.Hash) (common.Hash, error) {
addrHash := crypto.HashData(r.buff, addr.Bytes())
slotHash := crypto.HashData(r.buff, key.Bytes())
ret, err := r.snap.Storage(addrHash, slotHash)
if err != nil {
return common.Hash{}, err
}
if len(ret) == 0 {
return common.Hash{}, nil
}
// Perform the rlp-decode as the slot value is RLP-encoded in the state
// snapshot.
_, content, _, err := rlp.Split(ret)
if err != nil {
return common.Hash{}, err
}
var value common.Hash
value.SetBytes(content)
return value, nil
}
// Copy implements Reader, returning a deep-copied snap reader.
func (r *stateReader) Copy() Reader {
return &stateReader{
snap: r.snap,
buff: crypto.NewKeccakState(),
}
}
// trieReader implements the Reader interface, providing functions to access
// state from the referenced trie.
type trieReader struct {
root common.Hash // State root which uniquely represent a state
db *triedb.Database // Database for loading trie
buff crypto.KeccakState // Buffer for keccak256 hashing
mainTrie Trie // Main trie, resolved in constructor
subRoots map[common.Address]common.Hash // Set of storage roots, cached when the account is resolved
subTries map[common.Address]Trie // Group of storage tries, cached when it's resolved
}
// trieReader constructs a trie reader of the specific state. An error will be
// returned if the associated trie specified by root is not existent.
func newTrieReader(root common.Hash, db *triedb.Database, cache *utils.PointCache) (*trieReader, error) {
var (
tr Trie
err error
)
if !db.IsVerkle() {
tr, err = trie.NewStateTrie(trie.StateTrieID(root), db)
} else {
tr, err = trie.NewVerkleTrie(root, db, cache)
}
if err != nil {
return nil, err
}
return &trieReader{
root: root,
db: db,
buff: crypto.NewKeccakState(),
mainTrie: tr,
subRoots: make(map[common.Address]common.Hash),
subTries: make(map[common.Address]Trie),
}, nil
}
// Account implements Reader, retrieving the account specified by the address.
//
// An error will be returned if the trie state is corrupted. An nil account
// will be returned if it's not existent in the trie.
func (r *trieReader) Account(addr common.Address) (*types.StateAccount, error) {
account, err := r.mainTrie.GetAccount(addr)
if err != nil {
return nil, err
}
if account == nil {
r.subRoots[addr] = types.EmptyRootHash
} else {
r.subRoots[addr] = account.Root
}
return account, nil
}
// Storage implements Reader, retrieving the storage slot specified by the
// address and slot key.
//
// An error will be returned if the trie state is corrupted. An empty storage
// slot will be returned if it's not existent in the trie.
func (r *trieReader) Storage(addr common.Address, key common.Hash) (common.Hash, error) {
var (
tr Trie
found bool
value common.Hash
)
if r.db.IsVerkle() {
tr = r.mainTrie
} else {
tr, found = r.subTries[addr]
if !found {
root, ok := r.subRoots[addr]
// The storage slot is accessed without account caching. It's unexpected
// behavior but try to resolve the account first anyway.
if !ok {
_, err := r.Account(addr)
if err != nil {
return common.Hash{}, err
}
root = r.subRoots[addr]
}
var err error
tr, err = trie.NewStateTrie(trie.StorageTrieID(r.root, crypto.HashData(r.buff, addr.Bytes()), root), r.db)
if err != nil {
return common.Hash{}, err
}
r.subTries[addr] = tr
}
}
ret, err := tr.GetStorage(addr, key.Bytes())
if err != nil {
return common.Hash{}, err
}
value.SetBytes(ret)
return value, nil
}
// Copy implements Reader, returning a deep-copied trie reader.
func (r *trieReader) Copy() Reader {
tries := make(map[common.Address]Trie)
for addr, tr := range r.subTries {
tries[addr] = mustCopyTrie(tr)
}
return &trieReader{
root: r.root,
db: r.db,
buff: crypto.NewKeccakState(),
mainTrie: mustCopyTrie(r.mainTrie),
subRoots: maps.Clone(r.subRoots),
subTries: tries,
}
}
// multiReader is the aggregation of a list of Reader interface, providing state
// access by leveraging all readers. The checking priority is determined by the
// position in the reader list.
type multiReader struct {
readers []Reader // List of readers, sorted by checking priority
}
// newMultiReader constructs a multiReader instance with the given readers. The
// priority among readers is assumed to be sorted. Note, it must contain at least
// one reader for constructing a multiReader.
func newMultiReader(readers ...Reader) (*multiReader, error) {
if len(readers) == 0 {
return nil, errors.New("empty reader set")
}
return &multiReader{
readers: readers,
}, nil
}
// Account implementing Reader interface, retrieving the account associated with
// a particular address.
//
// - Returns a nil account if it does not exist
// - Returns an error only if an unexpected issue occurs
// - The returned account is safe to modify after the call
func (r *multiReader) Account(addr common.Address) (*types.StateAccount, error) {
var errs []error
for _, reader := range r.readers {
acct, err := reader.Account(addr)
if err == nil {
return acct, nil
}
errs = append(errs, err)
}
return nil, errors.Join(errs...)
}
// Storage implementing Reader interface, retrieving the storage slot associated
// with a particular account address and slot key.
//
// - Returns an empty slot if it does not exist
// - Returns an error only if an unexpected issue occurs
// - The returned storage slot is safe to modify after the call
func (r *multiReader) Storage(addr common.Address, slot common.Hash) (common.Hash, error) {
var errs []error
for _, reader := range r.readers {
slot, err := reader.Storage(addr, slot)
if err == nil {
return slot, nil
}
errs = append(errs, err)
}
return common.Hash{}, errors.Join(errs...)
}
// Copy implementing Reader interface, returning a deep-copied state reader.
func (r *multiReader) Copy() Reader {
var readers []Reader
for _, reader := range r.readers {
readers = append(readers, reader.Copy())
}
return &multiReader{readers: readers}
}

@ -74,6 +74,14 @@ func (dl *diskLayer) Stale() bool {
return dl.stale return dl.stale
} }
// markStale sets the stale flag as true.
func (dl *diskLayer) markStale() {
dl.lock.Lock()
defer dl.lock.Unlock()
dl.stale = true
}
// Account directly retrieves the account associated with a particular hash in // Account directly retrieves the account associated with a particular hash in
// the snapshot slim data format. // the snapshot slim data format.
func (dl *diskLayer) Account(hash common.Hash) (*types.SlimAccount, error) { func (dl *diskLayer) Account(hash common.Hash) (*types.SlimAccount, error) {
@ -175,3 +183,18 @@ func (dl *diskLayer) Storage(accountHash, storageHash common.Hash) ([]byte, erro
func (dl *diskLayer) Update(blockHash common.Hash, destructs map[common.Hash]struct{}, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) *diffLayer { func (dl *diskLayer) Update(blockHash common.Hash, destructs map[common.Hash]struct{}, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) *diffLayer {
return newDiffLayer(dl, blockHash, destructs, accounts, storage) return newDiffLayer(dl, blockHash, destructs, accounts, storage)
} }
// stopGeneration aborts the state snapshot generation if it is currently running.
func (dl *diskLayer) stopGeneration() {
dl.lock.RLock()
generating := dl.genMarker != nil
dl.lock.RUnlock()
if !generating {
return
}
if dl.genAbort != nil {
abort := make(chan *generatorStats)
dl.genAbort <- abort
<-abort
}
}

@ -631,16 +631,10 @@ func generateAccounts(ctx *generatorContext, dl *diskLayer, accMarker []byte) er
accMarker = nil accMarker = nil
return nil return nil
} }
// Always reset the initial account range as 1 whenever recover from the
// interruption. TODO(rjl493456442) can we remove it?
var accountRange = accountCheckRange
if len(accMarker) > 0 {
accountRange = 1
}
origin := common.CopyBytes(accMarker) origin := common.CopyBytes(accMarker)
for { for {
id := trie.StateTrieID(dl.root) id := trie.StateTrieID(dl.root)
exhausted, last, err := dl.generateRange(ctx, id, rawdb.SnapshotAccountPrefix, snapAccount, origin, accountRange, onAccount, types.FullAccountRLP) exhausted, last, err := dl.generateRange(ctx, id, rawdb.SnapshotAccountPrefix, snapAccount, origin, accountCheckRange, onAccount, types.FullAccountRLP)
if err != nil { if err != nil {
return err // The procedure it aborted, either by external signal or internal error. return err // The procedure it aborted, either by external signal or internal error.
} }
@ -652,7 +646,6 @@ func generateAccounts(ctx *generatorContext, dl *diskLayer, accMarker []byte) er
ctx.removeStorageLeft() ctx.removeStorageLeft()
break break
} }
accountRange = accountCheckRange
} }
return nil return nil
} }

@ -258,24 +258,11 @@ func (t *Tree) Disable() {
for _, layer := range t.layers { for _, layer := range t.layers {
switch layer := layer.(type) { switch layer := layer.(type) {
case *diskLayer: case *diskLayer:
// TODO this function will hang if it's called twice. Will
layer.lock.RLock() // fix it in the following PRs.
generating := layer.genMarker != nil layer.stopGeneration()
layer.lock.RUnlock() layer.markStale()
if !generating { layer.Release()
// Generator is already aborted or finished
break
}
// If the base layer is generating, abort it
if layer.genAbort != nil {
abort := make(chan *generatorStats)
layer.genAbort <- abort
<-abort
}
// Layer should be inactive now, mark it as stale
layer.lock.Lock()
layer.stale = true
layer.lock.Unlock()
case *diffLayer: case *diffLayer:
// If the layer is a simple diff, simply mark as stale // If the layer is a simple diff, simply mark as stale
@ -730,16 +717,11 @@ func (t *Tree) Rebuild(root common.Hash) {
for _, layer := range t.layers { for _, layer := range t.layers {
switch layer := layer.(type) { switch layer := layer.(type) {
case *diskLayer: case *diskLayer:
// If the base layer is generating, abort it and save // TODO this function will hang if it's called twice. Will
if layer.genAbort != nil { // fix it in the following PRs.
abort := make(chan *generatorStats) layer.stopGeneration()
layer.genAbort <- abort layer.markStale()
<-abort layer.Release()
}
// Layer should be inactive now, mark it as stale
layer.lock.Lock()
layer.stale = true
layer.lock.Unlock()
case *diffLayer: case *diffLayer:
// If the layer is a simple diff, simply mark as stale // If the layer is a simple diff, simply mark as stale

@ -114,14 +114,7 @@ func (s *stateObject) markSelfdestructed() {
} }
func (s *stateObject) touch() { func (s *stateObject) touch() {
s.db.journal.append(touchChange{ s.db.journal.touchChange(s.address)
account: &s.address,
})
if s.address == ripemd {
// Explicitly put it in the dirty-cache, which is otherwise generated from
// flattened journals.
s.db.journal.dirty(s.address)
}
} }
// getTrie returns the associated storage trie. The trie will be opened if it's // getTrie returns the associated storage trie. The trie will be opened if it's
@ -150,7 +143,7 @@ func (s *stateObject) getTrie() (Trie, error) {
func (s *stateObject) getPrefetchedTrie() Trie { func (s *stateObject) getPrefetchedTrie() Trie {
// If there's nothing to meaningfully return, let the user figure it out by // If there's nothing to meaningfully return, let the user figure it out by
// pulling the trie from disk. // pulling the trie from disk.
if s.data.Root == types.EmptyRootHash || s.db.prefetcher == nil { if (s.data.Root == types.EmptyRootHash && !s.db.db.TrieDB().IsVerkle()) || s.db.prefetcher == nil {
return nil return nil
} }
// Attempt to retrieve the trie from the prefetcher // Attempt to retrieve the trie from the prefetcher
@ -194,45 +187,17 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
s.originStorage[key] = common.Hash{} // track the empty slot as origin value s.originStorage[key] = common.Hash{} // track the empty slot as origin value
return common.Hash{} return common.Hash{}
} }
// If no live objects are available, attempt to use snapshots s.db.StorageLoaded++
var (
enc []byte
err error
value common.Hash
)
if s.db.snap != nil {
start := time.Now()
enc, err = s.db.snap.Storage(s.addrHash, crypto.Keccak256Hash(key.Bytes()))
s.db.SnapshotStorageReads += time.Since(start)
if len(enc) > 0 {
_, content, _, err := rlp.Split(enc)
if err != nil {
s.db.setError(err)
}
value.SetBytes(content)
}
}
// If the snapshot is unavailable or reading from it fails, load from the database.
if s.db.snap == nil || err != nil {
start := time.Now()
tr, err := s.getTrie()
if err != nil {
s.db.setError(err)
return common.Hash{}
}
val, err := tr.GetStorage(s.address, key.Bytes())
s.db.StorageReads += time.Since(start)
if err != nil { start := time.Now()
s.db.setError(err) value, err := s.db.reader.Storage(s.address, key)
return common.Hash{} if err != nil {
} s.db.setError(err)
value.SetBytes(val) return common.Hash{}
} }
// Independent of where we loaded the data from, add it to the prefetcher. s.db.StorageReads += time.Since(start)
// Whilst this would be a bit weird if snapshots are disabled, but we still
// want the trie nodes to end up in the prefetcher too, so just push through. // Schedule the resolved storage slots for prefetching if it's enabled.
if s.db.prefetcher != nil && s.data.Root != types.EmptyRootHash { if s.db.prefetcher != nil && s.data.Root != types.EmptyRootHash {
if err = s.db.prefetcher.prefetch(s.addrHash, s.origin.Root, s.address, [][]byte{key[:]}, true); err != nil { if err = s.db.prefetcher.prefetch(s.addrHash, s.origin.Root, s.address, [][]byte{key[:]}, true); err != nil {
log.Error("Failed to prefetch storage slot", "addr", s.address, "key", key, "err", err) log.Error("Failed to prefetch storage slot", "addr", s.address, "key", key, "err", err)
@ -251,16 +216,11 @@ func (s *stateObject) SetState(key, value common.Hash) {
return return
} }
// New value is different, update and journal the change // New value is different, update and journal the change
s.db.journal.append(storageChange{ s.db.journal.storageChange(s.address, key, prev, origin)
account: &s.address, s.setState(key, value, origin)
key: key,
prevvalue: prev,
origvalue: origin,
})
if s.db.logger != nil && s.db.logger.OnStorageChange != nil { if s.db.logger != nil && s.db.logger.OnStorageChange != nil {
s.db.logger.OnStorageChange(s.address, key, prev, value) s.db.logger.OnStorageChange(s.address, key, prev, value)
} }
s.setState(key, value, origin)
} }
// setState updates a value in account dirty storage. The dirtiness will be // setState updates a value in account dirty storage. The dirtiness will be
@ -510,10 +470,7 @@ func (s *stateObject) SubBalance(amount *uint256.Int, reason tracing.BalanceChan
} }
func (s *stateObject) SetBalance(amount *uint256.Int, reason tracing.BalanceChangeReason) { func (s *stateObject) SetBalance(amount *uint256.Int, reason tracing.BalanceChangeReason) {
s.db.journal.append(balanceChange{ s.db.journal.balanceChange(s.address, s.data.Balance)
account: &s.address,
prev: new(uint256.Int).Set(s.data.Balance),
})
if s.db.logger != nil && s.db.logger.OnBalanceChange != nil { if s.db.logger != nil && s.db.logger.OnBalanceChange != nil {
s.db.logger.OnBalanceChange(s.address, s.Balance().ToBig(), amount.ToBig(), reason) s.db.logger.OnBalanceChange(s.address, s.Balance().ToBig(), amount.ToBig(), reason)
} }
@ -541,7 +498,7 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject {
newContract: s.newContract, newContract: s.newContract,
} }
if s.trie != nil { if s.trie != nil {
obj.trie = db.db.CopyTrie(s.trie) obj.trie = mustCopyTrie(s.trie)
} }
return obj return obj
} }
@ -589,14 +546,10 @@ func (s *stateObject) CodeSize() int {
} }
func (s *stateObject) SetCode(codeHash common.Hash, code []byte) { func (s *stateObject) SetCode(codeHash common.Hash, code []byte) {
prevcode := s.Code() s.db.journal.setCode(s.address)
s.db.journal.append(codeChange{
account: &s.address,
prevhash: s.CodeHash(),
prevcode: prevcode,
})
if s.db.logger != nil && s.db.logger.OnCodeChange != nil { if s.db.logger != nil && s.db.logger.OnCodeChange != nil {
s.db.logger.OnCodeChange(s.address, common.BytesToHash(s.CodeHash()), prevcode, codeHash, code) // TODO remove prevcode from this callback
s.db.logger.OnCodeChange(s.address, common.BytesToHash(s.CodeHash()), nil, codeHash, code)
} }
s.setCode(codeHash, code) s.setCode(codeHash, code)
} }
@ -608,10 +561,7 @@ func (s *stateObject) setCode(codeHash common.Hash, code []byte) {
} }
func (s *stateObject) SetNonce(nonce uint64) { func (s *stateObject) SetNonce(nonce uint64) {
s.db.journal.append(nonceChange{ s.db.journal.nonceChange(s.address, s.data.Nonce)
account: &s.address,
prev: s.data.Nonce,
})
if s.db.logger != nil && s.db.logger.OnNonceChange != nil { if s.db.logger != nil && s.db.logger.OnNonceChange != nil {
s.db.logger.OnNonceChange(s.address, s.data.Nonce, nonce) s.db.logger.OnNonceChange(s.address, s.data.Nonce, nonce)
} }

@ -26,27 +26,25 @@ import (
"github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/triedb" "github.com/ethereum/go-ethereum/triedb"
"github.com/holiman/uint256" "github.com/holiman/uint256"
) )
type stateEnv struct { type stateEnv struct {
db ethdb.Database
state *StateDB state *StateDB
} }
func newStateEnv() *stateEnv { func newStateEnv() *stateEnv {
db := rawdb.NewMemoryDatabase() sdb, _ := New(types.EmptyRootHash, NewDatabaseForTesting())
sdb, _ := New(types.EmptyRootHash, NewDatabase(db), nil) return &stateEnv{state: sdb}
return &stateEnv{db: db, state: sdb}
} }
func TestDump(t *testing.T) { func TestDump(t *testing.T) {
db := rawdb.NewMemoryDatabase() db := rawdb.NewMemoryDatabase()
tdb := NewDatabaseWithConfig(db, &triedb.Config{Preimages: true}) triedb := triedb.NewDatabase(db, &triedb.Config{Preimages: true})
sdb, _ := New(types.EmptyRootHash, tdb, nil) tdb := NewDatabase(triedb, nil)
s := &stateEnv{db: db, state: sdb} sdb, _ := New(types.EmptyRootHash, tdb)
s := &stateEnv{state: sdb}
// generate a few entries // generate a few entries
obj1 := s.state.getOrNewStateObject(common.BytesToAddress([]byte{0x01})) obj1 := s.state.getOrNewStateObject(common.BytesToAddress([]byte{0x01}))
@ -62,7 +60,7 @@ func TestDump(t *testing.T) {
root, _ := s.state.Commit(0, false) root, _ := s.state.Commit(0, false)
// check that DumpToCollector contains the state objects that are in trie // check that DumpToCollector contains the state objects that are in trie
s.state, _ = New(root, tdb, nil) s.state, _ = New(root, tdb)
got := string(s.state.Dump(nil)) got := string(s.state.Dump(nil))
want := `{ want := `{
"root": "71edff0130dd2385947095001c73d9e28d862fc286fca2b922ca6f6f3cddfdd2", "root": "71edff0130dd2385947095001c73d9e28d862fc286fca2b922ca6f6f3cddfdd2",
@ -101,9 +99,10 @@ func TestDump(t *testing.T) {
func TestIterativeDump(t *testing.T) { func TestIterativeDump(t *testing.T) {
db := rawdb.NewMemoryDatabase() db := rawdb.NewMemoryDatabase()
tdb := NewDatabaseWithConfig(db, &triedb.Config{Preimages: true}) triedb := triedb.NewDatabase(db, &triedb.Config{Preimages: true})
sdb, _ := New(types.EmptyRootHash, tdb, nil) tdb := NewDatabase(triedb, nil)
s := &stateEnv{db: db, state: sdb} sdb, _ := New(types.EmptyRootHash, tdb)
s := &stateEnv{state: sdb}
// generate a few entries // generate a few entries
obj1 := s.state.getOrNewStateObject(common.BytesToAddress([]byte{0x01})) obj1 := s.state.getOrNewStateObject(common.BytesToAddress([]byte{0x01}))
@ -119,7 +118,7 @@ func TestIterativeDump(t *testing.T) {
s.state.updateStateObject(obj1) s.state.updateStateObject(obj1)
s.state.updateStateObject(obj2) s.state.updateStateObject(obj2)
root, _ := s.state.Commit(0, false) root, _ := s.state.Commit(0, false)
s.state, _ = New(root, tdb, nil) s.state, _ = New(root, tdb)
b := &bytes.Buffer{} b := &bytes.Buffer{}
s.state.IterativeDump(nil, json.NewEncoder(b)) s.state.IterativeDump(nil, json.NewEncoder(b))
@ -195,7 +194,7 @@ func TestSnapshotEmpty(t *testing.T) {
} }
func TestCreateObjectRevert(t *testing.T) { func TestCreateObjectRevert(t *testing.T) {
state, _ := New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()), nil) state, _ := New(types.EmptyRootHash, NewDatabaseForTesting())
addr := common.BytesToAddress([]byte("so0")) addr := common.BytesToAddress([]byte("so0"))
snap := state.Snapshot() snap := state.Snapshot()

@ -23,7 +23,6 @@ import (
"maps" "maps"
"math/big" "math/big"
"slices" "slices"
"sort"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
@ -48,11 +47,6 @@ import (
// TriesInMemory represents the number of layers that are kept in RAM. // TriesInMemory represents the number of layers that are kept in RAM.
const TriesInMemory = 128 const TriesInMemory = 128
type revision struct {
id int
journalIndex int
}
type mutationType int type mutationType int
const ( const (
@ -88,10 +82,8 @@ type StateDB struct {
db Database db Database
prefetcher *triePrefetcher prefetcher *triePrefetcher
trie Trie trie Trie
hasher crypto.KeccakState
logger *tracing.Hooks logger *tracing.Hooks
snaps *snapshot.Tree // Nil if snapshot is not available reader Reader
snap snapshot.Snapshot // Nil if snapshot is not available
// originalRoot is the pre-state root, before any changes were made. // originalRoot is the pre-state root, before any changes were made.
// It will be updated when the Commit is called. // It will be updated when the Commit is called.
@ -136,50 +128,53 @@ type StateDB struct {
preimages map[common.Hash][]byte preimages map[common.Hash][]byte
// Per-transaction access list // Per-transaction access list
accessList *accessList accessList *accessList
accessEvents *AccessEvents
// Transient storage // Transient storage
transientStorage transientStorage transientStorage transientStorage
// Journal of state modifications. This is the backbone of // Journal of state modifications. This is the backbone of
// Snapshot and RevertToSnapshot. // Snapshot and RevertToSnapshot.
journal *journal journal *journal
validRevisions []revision
nextRevisionId int
// State witness if cross validation is needed // State witness if cross validation is needed
witness *stateless.Witness witness *stateless.Witness
// Measurements gathered during execution for debugging purposes // Measurements gathered during execution for debugging purposes
AccountReads time.Duration AccountReads time.Duration
AccountHashes time.Duration AccountHashes time.Duration
AccountUpdates time.Duration AccountUpdates time.Duration
AccountCommits time.Duration AccountCommits time.Duration
StorageReads time.Duration StorageReads time.Duration
StorageUpdates time.Duration StorageUpdates time.Duration
StorageCommits time.Duration StorageCommits time.Duration
SnapshotAccountReads time.Duration SnapshotCommits time.Duration
SnapshotStorageReads time.Duration TrieDBCommits time.Duration
SnapshotCommits time.Duration
TrieDBCommits time.Duration AccountLoaded int // Number of accounts retrieved from the database during the state transition
AccountUpdated int // Number of accounts updated during the state transition
AccountUpdated int AccountDeleted int // Number of accounts deleted during the state transition
StorageUpdated atomic.Int64 StorageLoaded int // Number of storage slots retrieved from the database during the state transition
AccountDeleted int StorageUpdated atomic.Int64 // Number of storage slots updated during the state transition
StorageDeleted atomic.Int64 StorageDeleted atomic.Int64 // Number of storage slots deleted during the state transition
} }
// New creates a new state from a given trie. // New creates a new state from a given trie.
func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) { func New(root common.Hash, db Database) (*StateDB, error) {
tr, err := db.OpenTrie(root) tr, err := db.OpenTrie(root)
if err != nil { if err != nil {
return nil, err return nil, err
} }
reader, err := db.Reader(root)
if err != nil {
return nil, err
}
sdb := &StateDB{ sdb := &StateDB{
db: db, db: db,
trie: tr, trie: tr,
originalRoot: root, originalRoot: root,
snaps: snaps, reader: reader,
stateObjects: make(map[common.Address]*stateObject), stateObjects: make(map[common.Address]*stateObject),
stateObjectsDestruct: make(map[common.Address]*stateObject), stateObjectsDestruct: make(map[common.Address]*stateObject),
mutations: make(map[common.Address]*mutation), mutations: make(map[common.Address]*mutation),
@ -188,10 +183,9 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error)
journal: newJournal(), journal: newJournal(),
accessList: newAccessList(), accessList: newAccessList(),
transientStorage: newTransientStorage(), transientStorage: newTransientStorage(),
hasher: crypto.NewKeccakState(),
} }
if sdb.snaps != nil { if db.TrieDB().IsVerkle() {
sdb.snap = sdb.snaps.Snapshot(root) sdb.accessEvents = NewAccessEvents(db.PointCache())
} }
return sdb, nil return sdb, nil
} }
@ -206,30 +200,23 @@ func (s *StateDB) SetLogger(l *tracing.Hooks) {
// commit phase, most of the needed data is already hot. // commit phase, most of the needed data is already hot.
func (s *StateDB) StartPrefetcher(namespace string, witness *stateless.Witness) { func (s *StateDB) StartPrefetcher(namespace string, witness *stateless.Witness) {
// Terminate any previously running prefetcher // Terminate any previously running prefetcher
if s.prefetcher != nil { s.StopPrefetcher()
s.prefetcher.terminate(false)
s.prefetcher.report()
s.prefetcher = nil
}
// Enable witness collection if requested // Enable witness collection if requested
s.witness = witness s.witness = witness
// If snapshots are enabled, start prefethers explicitly // With the switch to the Proof-of-Stake consensus algorithm, block production
if s.snap != nil { // rewards are now handled at the consensus layer. Consequently, a block may
s.prefetcher = newTriePrefetcher(s.db, s.originalRoot, namespace, witness == nil) // have no state transitions if it contains no transactions and no withdrawals.
// In such cases, the account trie won't be scheduled for prefetching, leading
// With the switch to the Proof-of-Stake consensus algorithm, block production // to unnecessary error logs.
// rewards are now handled at the consensus layer. Consequently, a block may //
// have no state transitions if it contains no transactions and no withdrawals. // To prevent this, the account trie is always scheduled for prefetching once
// In such cases, the account trie won't be scheduled for prefetching, leading // the prefetcher is constructed. For more details, see:
// to unnecessary error logs. // https://github.com/ethereum/go-ethereum/issues/29880
// s.prefetcher = newTriePrefetcher(s.db, s.originalRoot, namespace, witness == nil)
// To prevent this, the account trie is always scheduled for prefetching once if err := s.prefetcher.prefetch(common.Hash{}, s.originalRoot, common.Address{}, nil, false); err != nil {
// the prefetcher is constructed. For more details, see: log.Error("Failed to prefetch account trie", "root", s.originalRoot, "err", err)
// https://github.com/ethereum/go-ethereum/issues/29880
if err := s.prefetcher.prefetch(common.Hash{}, s.originalRoot, common.Address{}, nil, false); err != nil {
log.Error("Failed to prefetch account trie", "root", s.originalRoot, "err", err)
}
} }
} }
@ -256,7 +243,7 @@ func (s *StateDB) Error() error {
} }
func (s *StateDB) AddLog(log *types.Log) { func (s *StateDB) AddLog(log *types.Log) {
s.journal.append(addLogChange{txhash: s.thash}) s.journal.logChange(s.thash)
log.TxHash = s.thash log.TxHash = s.thash
log.TxIndex = uint(s.txIndex) log.TxIndex = uint(s.txIndex)
@ -290,7 +277,6 @@ func (s *StateDB) Logs() []*types.Log {
// AddPreimage records a SHA3 preimage seen by the VM. // AddPreimage records a SHA3 preimage seen by the VM.
func (s *StateDB) AddPreimage(hash common.Hash, preimage []byte) { func (s *StateDB) AddPreimage(hash common.Hash, preimage []byte) {
if _, ok := s.preimages[hash]; !ok { if _, ok := s.preimages[hash]; !ok {
s.journal.append(addPreimageChange{hash: hash})
s.preimages[hash] = slices.Clone(preimage) s.preimages[hash] = slices.Clone(preimage)
} }
} }
@ -302,14 +288,14 @@ func (s *StateDB) Preimages() map[common.Hash][]byte {
// AddRefund adds gas to the refund counter // AddRefund adds gas to the refund counter
func (s *StateDB) AddRefund(gas uint64) { func (s *StateDB) AddRefund(gas uint64) {
s.journal.append(refundChange{prev: s.refund}) s.journal.refundChange(s.refund)
s.refund += gas s.refund += gas
} }
// SubRefund removes gas from the refund counter. // SubRefund removes gas from the refund counter.
// This method will panic if the refund counter goes below zero // This method will panic if the refund counter goes below zero
func (s *StateDB) SubRefund(gas uint64) { func (s *StateDB) SubRefund(gas uint64) {
s.journal.append(refundChange{prev: s.refund}) s.journal.refundChange(s.refund)
if gas > s.refund { if gas > s.refund {
panic(fmt.Sprintf("Refund counter below zero (gas: %d > refund: %d)", gas, s.refund)) panic(fmt.Sprintf("Refund counter below zero (gas: %d > refund: %d)", gas, s.refund))
} }
@ -529,20 +515,17 @@ func (s *StateDB) SelfDestruct(addr common.Address) {
if stateObject == nil { if stateObject == nil {
return return
} }
var ( // Regardless of whether it is already destructed or not, we do have to
prev = new(uint256.Int).Set(stateObject.Balance()) // journal the balance-change, if we set it to zero here.
n = new(uint256.Int) if !stateObject.Balance().IsZero() {
) stateObject.SetBalance(new(uint256.Int), tracing.BalanceDecreaseSelfdestruct)
s.journal.append(selfDestructChange{ }
account: &addr, // If it is already marked as self-destructed, we do not need to add it
prev: stateObject.selfDestructed, // for journalling a second time.
prevbalance: prev, if !stateObject.selfDestructed {
}) s.journal.destruct(addr)
if s.logger != nil && s.logger.OnBalanceChange != nil && prev.Sign() > 0 { stateObject.markSelfdestructed()
s.logger.OnBalanceChange(addr, prev.ToBig(), n.ToBig(), tracing.BalanceDecreaseSelfdestruct)
} }
stateObject.markSelfdestructed()
stateObject.data.Balance = n
} }
func (s *StateDB) Selfdestruct6780(addr common.Address) { func (s *StateDB) Selfdestruct6780(addr common.Address) {
@ -563,11 +546,7 @@ func (s *StateDB) SetTransientState(addr common.Address, key, value common.Hash)
if prev == value { if prev == value {
return return
} }
s.journal.append(transientStorageChange{ s.journal.transientStateChange(addr, key, prev)
account: &addr,
key: key,
prevalue: prev,
})
s.setTransientState(addr, key, value) s.setTransientState(addr, key, value)
} }
@ -590,7 +569,7 @@ func (s *StateDB) GetTransientState(addr common.Address, key common.Hash) common
func (s *StateDB) updateStateObject(obj *stateObject) { func (s *StateDB) updateStateObject(obj *stateObject) {
// Encode the account and update the account trie // Encode the account and update the account trie
addr := obj.Address() addr := obj.Address()
if err := s.trie.UpdateAccount(addr, &obj.data); err != nil { if err := s.trie.UpdateAccount(addr, &obj.data, len(obj.code)); err != nil {
s.setError(fmt.Errorf("updateStateObject (%x) error: %v", addr[:], err)) s.setError(fmt.Errorf("updateStateObject (%x) error: %v", addr[:], err))
} }
if obj.dirtyCode { if obj.dirtyCode {
@ -616,56 +595,30 @@ func (s *StateDB) getStateObject(addr common.Address) *stateObject {
if _, ok := s.stateObjectsDestruct[addr]; ok { if _, ok := s.stateObjectsDestruct[addr]; ok {
return nil return nil
} }
// If no live objects are available, attempt to use snapshots s.AccountLoaded++
var data *types.StateAccount
if s.snap != nil { start := time.Now()
start := time.Now() acct, err := s.reader.Account(addr)
acc, err := s.snap.Account(crypto.HashData(s.hasher, addr.Bytes())) if err != nil {
s.SnapshotAccountReads += time.Since(start) s.setError(fmt.Errorf("getStateObject (%x) error: %w", addr.Bytes(), err))
if err == nil { return nil
if acc == nil {
return nil
}
data = &types.StateAccount{
Nonce: acc.Nonce,
Balance: acc.Balance,
CodeHash: acc.CodeHash,
Root: common.BytesToHash(acc.Root),
}
if len(data.CodeHash) == 0 {
data.CodeHash = types.EmptyCodeHash.Bytes()
}
if data.Root == (common.Hash{}) {
data.Root = types.EmptyRootHash
}
}
} }
// If snapshot unavailable or reading from it failed, load from the database s.AccountReads += time.Since(start)
if data == nil {
start := time.Now()
var err error
data, err = s.trie.GetAccount(addr)
s.AccountReads += time.Since(start)
if err != nil { // Short circuit if the account is not found
s.setError(fmt.Errorf("getDeleteStateObject (%x) error: %w", addr.Bytes(), err)) if acct == nil {
return nil return nil
}
if data == nil {
return nil
}
} }
// Independent of where we loaded the data from, add it to the prefetcher. // Schedule the resolved account for prefetching if it's enabled.
// Whilst this would be a bit weird if snapshots are disabled, but we still
// want the trie nodes to end up in the prefetcher too, so just push through.
if s.prefetcher != nil { if s.prefetcher != nil {
if err := s.prefetcher.prefetch(common.Hash{}, s.originalRoot, common.Address{}, [][]byte{addr[:]}, true); err != nil { if err := s.prefetcher.prefetch(common.Hash{}, s.originalRoot, common.Address{}, [][]byte{addr[:]}, true); err != nil {
log.Error("Failed to prefetch account", "addr", addr, "err", err) log.Error("Failed to prefetch account", "addr", addr, "err", err)
} }
} }
// Insert into the live set // Insert into the live set
obj := newObject(s, addr, data) obj := newObject(s, addr, acct)
s.setStateObject(obj) s.setStateObject(obj)
s.AccountLoaded++
return obj return obj
} }
@ -686,7 +639,7 @@ func (s *StateDB) getOrNewStateObject(addr common.Address) *stateObject {
// existing account with the given address, otherwise it will be silently overwritten. // existing account with the given address, otherwise it will be silently overwritten.
func (s *StateDB) createObject(addr common.Address) *stateObject { func (s *StateDB) createObject(addr common.Address) *stateObject {
obj := newObject(s, addr, nil) obj := newObject(s, addr, nil)
s.journal.append(createObjectChange{account: &addr}) s.journal.createObject(addr)
s.setStateObject(obj) s.setStateObject(obj)
return obj return obj
} }
@ -708,7 +661,7 @@ func (s *StateDB) CreateContract(addr common.Address) {
obj := s.getStateObject(addr) obj := s.getStateObject(addr)
if !obj.newContract { if !obj.newContract {
obj.newContract = true obj.newContract = true
s.journal.append(createContractChange{account: addr}) s.journal.createContract(addr)
} }
} }
@ -718,8 +671,8 @@ func (s *StateDB) Copy() *StateDB {
// Copy all the basic fields, initialize the memory ones // Copy all the basic fields, initialize the memory ones
state := &StateDB{ state := &StateDB{
db: s.db, db: s.db,
trie: s.db.CopyTrie(s.trie), trie: mustCopyTrie(s.trie),
hasher: crypto.NewKeccakState(), reader: s.reader.Copy(),
originalRoot: s.originalRoot, originalRoot: s.originalRoot,
stateObjects: make(map[common.Address]*stateObject, len(s.stateObjects)), stateObjects: make(map[common.Address]*stateObject, len(s.stateObjects)),
stateObjectsDestruct: make(map[common.Address]*stateObject, len(s.stateObjectsDestruct)), stateObjectsDestruct: make(map[common.Address]*stateObject, len(s.stateObjectsDestruct)),
@ -731,20 +684,23 @@ func (s *StateDB) Copy() *StateDB {
logs: make(map[common.Hash][]*types.Log, len(s.logs)), logs: make(map[common.Hash][]*types.Log, len(s.logs)),
logSize: s.logSize, logSize: s.logSize,
preimages: maps.Clone(s.preimages), preimages: maps.Clone(s.preimages),
journal: s.journal.copy(),
validRevisions: slices.Clone(s.validRevisions),
nextRevisionId: s.nextRevisionId,
// In order for the block producer to be able to use and make additions // Do we need to copy the access list and transient storage?
// to the snapshot tree, we need to copy that as well. Otherwise, any // In practice: No. At the start of a transaction, these two lists are empty.
// block mined by ourselves will cause gaps in the tree, and force the // In practice, we only ever copy state _between_ transactions/blocks, never
// miner to operate trie-backed only. // in the middle of a transaction. However, it doesn't cost us much to copy
snaps: s.snaps, // empty lists, so we do it anyway to not blow up if we ever decide copy them
snap: s.snap, // in the middle of a transaction.
accessList: s.accessList.Copy(),
transientStorage: s.transientStorage.Copy(),
journal: s.journal.copy(),
} }
if s.witness != nil { if s.witness != nil {
state.witness = s.witness.Copy() state.witness = s.witness.Copy()
} }
if s.accessEvents != nil {
state.accessEvents = s.accessEvents.Copy()
}
// Deep copy cached state objects. // Deep copy cached state objects.
for addr, obj := range s.stateObjects { for addr, obj := range s.stateObjects {
state.stateObjects[addr] = obj.deepCopy(state) state.stateObjects[addr] = obj.deepCopy(state)
@ -766,39 +722,17 @@ func (s *StateDB) Copy() *StateDB {
} }
state.logs[hash] = cpy state.logs[hash] = cpy
} }
// Do we need to copy the access list and transient storage?
// In practice: No. At the start of a transaction, these two lists are empty.
// In practice, we only ever copy state _between_ transactions/blocks, never
// in the middle of a transaction. However, it doesn't cost us much to copy
// empty lists, so we do it anyway to not blow up if we ever decide copy them
// in the middle of a transaction.
state.accessList = s.accessList.Copy()
state.transientStorage = s.transientStorage.Copy()
return state return state
} }
// Snapshot returns an identifier for the current revision of the state. // Snapshot returns an identifier for the current revision of the state.
func (s *StateDB) Snapshot() int { func (s *StateDB) Snapshot() int {
id := s.nextRevisionId return s.journal.snapshot()
s.nextRevisionId++
s.validRevisions = append(s.validRevisions, revision{id, s.journal.length()})
return id
} }
// RevertToSnapshot reverts all state changes made since the given revision. // RevertToSnapshot reverts all state changes made since the given revision.
func (s *StateDB) RevertToSnapshot(revid int) { func (s *StateDB) RevertToSnapshot(revid int) {
// Find the snapshot in the stack of valid snapshots. s.journal.revertToSnapshot(revid, s)
idx := sort.Search(len(s.validRevisions), func(i int) bool {
return s.validRevisions[i].id >= revid
})
if idx == len(s.validRevisions) || s.validRevisions[idx].id != revid {
panic(fmt.Errorf("revision id %v cannot be reverted", revid))
}
snapshot := s.validRevisions[idx].journalIndex
// Replay the journal to undo changes and remove invalidated snapshots
s.journal.revert(s, snapshot)
s.validRevisions = s.validRevisions[:idx]
} }
// GetRefund returns the current value of the refund counter. // GetRefund returns the current value of the refund counter.
@ -905,8 +839,10 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
return nil return nil
}) })
} }
// If witness building is enabled, gather all the read-only accesses // If witness building is enabled, gather all the read-only accesses.
if s.witness != nil { // Skip witness collection in Verkle mode, they will be gathered
// together at the end.
if s.witness != nil && !s.db.TrieDB().IsVerkle() {
// Pull in anything that has been accessed before destruction // Pull in anything that has been accessed before destruction
for _, obj := range s.stateObjectsDestruct { for _, obj := range s.stateObjectsDestruct {
// Skip any objects that haven't touched their storage // Skip any objects that haven't touched their storage
@ -947,7 +883,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
// only a single trie is used for state hashing. Replacing a non-nil verkle tree // only a single trie is used for state hashing. Replacing a non-nil verkle tree
// here could result in losing uncommitted changes from storage. // here could result in losing uncommitted changes from storage.
start = time.Now() start = time.Now()
if s.prefetcher != nil && (s.trie == nil || !s.trie.IsVerkle()) { if s.prefetcher != nil {
if trie := s.prefetcher.trie(common.Hash{}, s.originalRoot); trie == nil { if trie := s.prefetcher.trie(common.Hash{}, s.originalRoot); trie == nil {
log.Error("Failed to retrieve account pre-fetcher trie") log.Error("Failed to retrieve account pre-fetcher trie")
} else { } else {
@ -1012,19 +948,16 @@ func (s *StateDB) SetTxContext(thash common.Hash, ti int) {
} }
func (s *StateDB) clearJournalAndRefund() { func (s *StateDB) clearJournalAndRefund() {
if len(s.journal.entries) > 0 { s.journal.reset()
s.journal = newJournal() s.refund = 0
s.refund = 0
}
s.validRevisions = s.validRevisions[:0] // Snapshots can be created without journal entries
} }
// fastDeleteStorage is the function that efficiently deletes the storage trie // fastDeleteStorage is the function that efficiently deletes the storage trie
// of a specific account. It leverages the associated state snapshot for fast // of a specific account. It leverages the associated state snapshot for fast
// storage iteration and constructs trie node deletion markers by creating // storage iteration and constructs trie node deletion markers by creating
// stack trie with iterated slots. // stack trie with iterated slots.
func (s *StateDB) fastDeleteStorage(addrHash common.Hash, root common.Hash) (map[common.Hash][]byte, *trienode.NodeSet, error) { func (s *StateDB) fastDeleteStorage(snaps *snapshot.Tree, addrHash common.Hash, root common.Hash) (map[common.Hash][]byte, *trienode.NodeSet, error) {
iter, err := s.snaps.StorageIterator(s.originalRoot, addrHash, common.Hash{}) iter, err := snaps.StorageIterator(s.originalRoot, addrHash, common.Hash{})
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -1102,10 +1035,11 @@ func (s *StateDB) deleteStorage(addr common.Address, addrHash common.Hash, root
// The fast approach can be failed if the snapshot is not fully // The fast approach can be failed if the snapshot is not fully
// generated, or it's internally corrupted. Fallback to the slow // generated, or it's internally corrupted. Fallback to the slow
// one just in case. // one just in case.
if s.snap != nil { snaps := s.db.Snapshot()
slots, nodes, err = s.fastDeleteStorage(addrHash, root) if snaps != nil {
slots, nodes, err = s.fastDeleteStorage(snaps, addrHash, root)
} }
if s.snap == nil || err != nil { if snaps == nil || err != nil {
slots, nodes, err = s.slowDeleteStorage(addr, addrHash, root) slots, nodes, err = s.slowDeleteStorage(addr, addrHash, root)
} }
if err != nil { if err != nil {
@ -1309,6 +1243,8 @@ func (s *StateDB) commit(deleteEmptyObjects bool) (*stateUpdate, error) {
if err := workers.Wait(); err != nil { if err := workers.Wait(); err != nil {
return nil, err return nil, err
} }
accountReadMeters.Mark(int64(s.AccountLoaded))
storageReadMeters.Mark(int64(s.StorageLoaded))
accountUpdatedMeter.Mark(int64(s.AccountUpdated)) accountUpdatedMeter.Mark(int64(s.AccountUpdated))
storageUpdatedMeter.Mark(s.StorageUpdated.Load()) storageUpdatedMeter.Mark(s.StorageUpdated.Load())
accountDeletedMeter.Mark(int64(s.AccountDeleted)) accountDeletedMeter.Mark(int64(s.AccountDeleted))
@ -1317,7 +1253,10 @@ func (s *StateDB) commit(deleteEmptyObjects bool) (*stateUpdate, error) {
accountTrieDeletedMeter.Mark(int64(accountTrieNodesDeleted)) accountTrieDeletedMeter.Mark(int64(accountTrieNodesDeleted))
storageTriesUpdatedMeter.Mark(int64(storageTrieNodesUpdated)) storageTriesUpdatedMeter.Mark(int64(storageTrieNodesUpdated))
storageTriesDeletedMeter.Mark(int64(storageTrieNodesDeleted)) storageTriesDeletedMeter.Mark(int64(storageTrieNodesDeleted))
s.AccountUpdated, s.AccountDeleted = 0, 0
// Clear the metric markers
s.AccountLoaded, s.AccountUpdated, s.AccountDeleted = 0, 0, 0
s.StorageLoaded = 0
s.StorageUpdated.Store(0) s.StorageUpdated.Store(0)
s.StorageDeleted.Store(0) s.StorageDeleted.Store(0)
@ -1338,7 +1277,7 @@ func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool) (*stateU
return nil, err return nil, err
} }
// Commit dirty contract code if any exists // Commit dirty contract code if any exists
if db := s.db.DiskDB(); db != nil && len(ret.codes) > 0 { if db := s.db.TrieDB().Disk(); db != nil && len(ret.codes) > 0 {
batch := db.NewBatch() batch := db.NewBatch()
for _, code := range ret.codes { for _, code := range ret.codes {
rawdb.WriteCode(batch, code.hash, code.blob) rawdb.WriteCode(batch, code.hash, code.blob)
@ -1349,18 +1288,16 @@ func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool) (*stateU
} }
if !ret.empty() { if !ret.empty() {
// If snapshotting is enabled, update the snapshot tree with this new version // If snapshotting is enabled, update the snapshot tree with this new version
if s.snap != nil { if snap := s.db.Snapshot(); snap != nil && snap.Snapshot(ret.originRoot) != nil {
s.snap = nil
start := time.Now() start := time.Now()
if err := s.snaps.Update(ret.root, ret.originRoot, ret.destructs, ret.accounts, ret.storages); err != nil { if err := snap.Update(ret.root, ret.originRoot, ret.destructs, ret.accounts, ret.storages); err != nil {
log.Warn("Failed to update snapshot tree", "from", ret.originRoot, "to", ret.root, "err", err) log.Warn("Failed to update snapshot tree", "from", ret.originRoot, "to", ret.root, "err", err)
} }
// Keep 128 diff layers in the memory, persistent layer is 129th. // Keep 128 diff layers in the memory, persistent layer is 129th.
// - head layer is paired with HEAD state // - head layer is paired with HEAD state
// - head-1 layer is paired with HEAD-1 state // - head-1 layer is paired with HEAD-1 state
// - head-127 layer(bottom-most diff layer) is paired with HEAD-127 state // - head-127 layer(bottom-most diff layer) is paired with HEAD-127 state
if err := s.snaps.Cap(ret.root, TriesInMemory); err != nil { if err := snap.Cap(ret.root, TriesInMemory); err != nil {
log.Warn("Failed to cap snapshot tree", "root", ret.root, "layers", TriesInMemory, "err", err) log.Warn("Failed to cap snapshot tree", "root", ret.root, "layers", TriesInMemory, "err", err)
} }
s.SnapshotCommits += time.Since(start) s.SnapshotCommits += time.Since(start)
@ -1375,6 +1312,7 @@ func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool) (*stateU
s.TrieDBCommits += time.Since(start) s.TrieDBCommits += time.Since(start)
} }
} }
s.reader, _ = s.db.Reader(s.originalRoot)
return ret, err return ret, err
} }
@ -1442,7 +1380,7 @@ func (s *StateDB) Prepare(rules params.Rules, sender, coinbase common.Address, d
// AddAddressToAccessList adds the given address to the access list // AddAddressToAccessList adds the given address to the access list
func (s *StateDB) AddAddressToAccessList(addr common.Address) { func (s *StateDB) AddAddressToAccessList(addr common.Address) {
if s.accessList.AddAddress(addr) { if s.accessList.AddAddress(addr) {
s.journal.append(accessListAddAccountChange{&addr}) s.journal.accessListAddAccount(addr)
} }
} }
@ -1454,13 +1392,10 @@ func (s *StateDB) AddSlotToAccessList(addr common.Address, slot common.Hash) {
// scope of 'address' without having the 'address' become already added // scope of 'address' without having the 'address' become already added
// to the access list (via call-variant, create, etc). // to the access list (via call-variant, create, etc).
// Better safe than sorry, though // Better safe than sorry, though
s.journal.append(accessListAddAccountChange{&addr}) s.journal.accessListAddAccount(addr)
} }
if slotMod { if slotMod {
s.journal.append(accessListAddSlotChange{ s.journal.accessListAddSlot(addr, slot)
address: &addr,
slot: &slot,
})
} }
} }
@ -1493,6 +1428,7 @@ func (s *StateDB) markUpdate(addr common.Address) {
s.mutations[addr].typ = update s.mutations[addr].typ = update
} }
// PointCache returns the point cache used by verkle tree.
func (s *StateDB) PointCache() *utils.PointCache { func (s *StateDB) PointCache() *utils.PointCache {
return s.db.PointCache() return s.db.PointCache()
} }
@ -1501,3 +1437,7 @@ func (s *StateDB) PointCache() *utils.PointCache {
func (s *StateDB) Witness() *stateless.Witness { func (s *StateDB) Witness() *stateless.Witness {
return s.witness return s.witness
} }
func (s *StateDB) AccessEvents() *AccessEvents {
return s.accessEvents
}

@ -73,7 +73,7 @@ func newStateTestAction(addr common.Address, r *rand.Rand, index int) testAction
args: make([]int64, 1), args: make([]int64, 1),
}, },
{ {
name: "SetState", name: "SetStorage",
fn: func(a testAction, s *StateDB) { fn: func(a testAction, s *StateDB) {
var key, val common.Hash var key, val common.Hash
binary.BigEndian.PutUint16(key[:], uint16(a.args[0])) binary.BigEndian.PutUint16(key[:], uint16(a.args[0]))
@ -197,7 +197,6 @@ func (test *stateTest) run() bool {
} }
disk = rawdb.NewMemoryDatabase() disk = rawdb.NewMemoryDatabase()
tdb = triedb.NewDatabase(disk, &triedb.Config{PathDB: pathdb.Defaults}) tdb = triedb.NewDatabase(disk, &triedb.Config{PathDB: pathdb.Defaults})
sdb = NewDatabaseWithNodeDB(disk, tdb)
byzantium = rand.Intn(2) == 0 byzantium = rand.Intn(2) == 0
) )
defer disk.Close() defer disk.Close()
@ -217,7 +216,7 @@ func (test *stateTest) run() bool {
if i != 0 { if i != 0 {
root = roots[len(roots)-1] root = roots[len(roots)-1]
} }
state, err := New(root, sdb, snaps) state, err := New(root, NewDatabase(tdb, snaps))
if err != nil { if err != nil {
panic(err) panic(err)
} }

@ -19,7 +19,6 @@ package state
import ( import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"errors"
"fmt" "fmt"
"maps" "maps"
"math" "math"
@ -53,8 +52,9 @@ func TestUpdateLeaks(t *testing.T) {
var ( var (
db = rawdb.NewMemoryDatabase() db = rawdb.NewMemoryDatabase()
tdb = triedb.NewDatabase(db, nil) tdb = triedb.NewDatabase(db, nil)
sdb = NewDatabase(tdb, nil)
) )
state, _ := New(types.EmptyRootHash, NewDatabaseWithNodeDB(db, tdb), nil) state, _ := New(types.EmptyRootHash, sdb)
// Update it with some accounts // Update it with some accounts
for i := byte(0); i < 255; i++ { for i := byte(0); i < 255; i++ {
@ -90,8 +90,8 @@ func TestIntermediateLeaks(t *testing.T) {
finalDb := rawdb.NewMemoryDatabase() finalDb := rawdb.NewMemoryDatabase()
transNdb := triedb.NewDatabase(transDb, nil) transNdb := triedb.NewDatabase(transDb, nil)
finalNdb := triedb.NewDatabase(finalDb, nil) finalNdb := triedb.NewDatabase(finalDb, nil)
transState, _ := New(types.EmptyRootHash, NewDatabaseWithNodeDB(transDb, transNdb), nil) transState, _ := New(types.EmptyRootHash, NewDatabase(transNdb, nil))
finalState, _ := New(types.EmptyRootHash, NewDatabaseWithNodeDB(finalDb, finalNdb), nil) finalState, _ := New(types.EmptyRootHash, NewDatabase(finalNdb, nil))
modify := func(state *StateDB, addr common.Address, i, tweak byte) { modify := func(state *StateDB, addr common.Address, i, tweak byte) {
state.SetBalance(addr, uint256.NewInt(uint64(11*i)+uint64(tweak)), tracing.BalanceChangeUnspecified) state.SetBalance(addr, uint256.NewInt(uint64(11*i)+uint64(tweak)), tracing.BalanceChangeUnspecified)
@ -166,7 +166,7 @@ func TestIntermediateLeaks(t *testing.T) {
// https://github.com/ethereum/go-ethereum/pull/15549. // https://github.com/ethereum/go-ethereum/pull/15549.
func TestCopy(t *testing.T) { func TestCopy(t *testing.T) {
// Create a random state test to copy and modify "independently" // Create a random state test to copy and modify "independently"
orig, _ := New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()), nil) orig, _ := New(types.EmptyRootHash, NewDatabaseForTesting())
for i := byte(0); i < 255; i++ { for i := byte(0); i < 255; i++ {
obj := orig.getOrNewStateObject(common.BytesToAddress([]byte{i})) obj := orig.getOrNewStateObject(common.BytesToAddress([]byte{i}))
@ -230,8 +230,8 @@ func TestCopy(t *testing.T) {
// TestCopyWithDirtyJournal tests if Copy can correct create a equal copied // TestCopyWithDirtyJournal tests if Copy can correct create a equal copied
// stateDB with dirty journal present. // stateDB with dirty journal present.
func TestCopyWithDirtyJournal(t *testing.T) { func TestCopyWithDirtyJournal(t *testing.T) {
db := NewDatabase(rawdb.NewMemoryDatabase()) db := NewDatabaseForTesting()
orig, _ := New(types.EmptyRootHash, db, nil) orig, _ := New(types.EmptyRootHash, db)
// Fill up the initial states // Fill up the initial states
for i := byte(0); i < 255; i++ { for i := byte(0); i < 255; i++ {
@ -241,7 +241,7 @@ func TestCopyWithDirtyJournal(t *testing.T) {
orig.updateStateObject(obj) orig.updateStateObject(obj)
} }
root, _ := orig.Commit(0, true) root, _ := orig.Commit(0, true)
orig, _ = New(root, db, nil) orig, _ = New(root, db)
// modify all in memory without finalizing // modify all in memory without finalizing
for i := byte(0); i < 255; i++ { for i := byte(0); i < 255; i++ {
@ -274,8 +274,8 @@ func TestCopyWithDirtyJournal(t *testing.T) {
// It then proceeds to make changes to S1. Those changes are _not_ supposed // It then proceeds to make changes to S1. Those changes are _not_ supposed
// to affect S2. This test checks that the copy properly deep-copies the objectstate // to affect S2. This test checks that the copy properly deep-copies the objectstate
func TestCopyObjectState(t *testing.T) { func TestCopyObjectState(t *testing.T) {
db := NewDatabase(rawdb.NewMemoryDatabase()) db := NewDatabaseForTesting()
orig, _ := New(types.EmptyRootHash, db, nil) orig, _ := New(types.EmptyRootHash, db)
// Fill up the initial states // Fill up the initial states
for i := byte(0); i < 5; i++ { for i := byte(0); i < 5; i++ {
@ -360,7 +360,7 @@ func newTestAction(addr common.Address, r *rand.Rand) testAction {
args: make([]int64, 1), args: make([]int64, 1),
}, },
{ {
name: "SetState", name: "SetStorage",
fn: func(a testAction, s *StateDB) { fn: func(a testAction, s *StateDB) {
var key, val common.Hash var key, val common.Hash
binary.BigEndian.PutUint16(key[:], uint16(a.args[0])) binary.BigEndian.PutUint16(key[:], uint16(a.args[0]))
@ -372,6 +372,12 @@ func newTestAction(addr common.Address, r *rand.Rand) testAction {
{ {
name: "SetCode", name: "SetCode",
fn: func(a testAction, s *StateDB) { fn: func(a testAction, s *StateDB) {
// SetCode can only be performed in case the addr does
// not already hold code
if c := s.GetCode(addr); len(c) > 0 {
// no-op
return
}
code := make([]byte, 16) code := make([]byte, 16)
binary.BigEndian.PutUint64(code, uint64(a.args[0])) binary.BigEndian.PutUint64(code, uint64(a.args[0]))
binary.BigEndian.PutUint64(code[8:], uint64(a.args[1])) binary.BigEndian.PutUint64(code[8:], uint64(a.args[1]))
@ -521,7 +527,7 @@ func (test *snapshotTest) String() string {
func (test *snapshotTest) run() bool { func (test *snapshotTest) run() bool {
// Run all actions and create snapshots. // Run all actions and create snapshots.
var ( var (
state, _ = New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()), nil) state, _ = New(types.EmptyRootHash, NewDatabaseForTesting())
snapshotRevs = make([]int, len(test.snapshots)) snapshotRevs = make([]int, len(test.snapshots))
sindex = 0 sindex = 0
checkstates = make([]*StateDB, len(test.snapshots)) checkstates = make([]*StateDB, len(test.snapshots))
@ -693,7 +699,7 @@ func TestTouchDelete(t *testing.T) {
s := newStateEnv() s := newStateEnv()
s.state.getOrNewStateObject(common.Address{}) s.state.getOrNewStateObject(common.Address{})
root, _ := s.state.Commit(0, false) root, _ := s.state.Commit(0, false)
s.state, _ = New(root, s.state.db, s.state.snaps) s.state, _ = New(root, s.state.db)
snapshot := s.state.Snapshot() snapshot := s.state.Snapshot()
s.state.AddBalance(common.Address{}, new(uint256.Int), tracing.BalanceChangeUnspecified) s.state.AddBalance(common.Address{}, new(uint256.Int), tracing.BalanceChangeUnspecified)
@ -710,7 +716,7 @@ func TestTouchDelete(t *testing.T) {
// TestCopyOfCopy tests that modified objects are carried over to the copy, and the copy of the copy. // TestCopyOfCopy tests that modified objects are carried over to the copy, and the copy of the copy.
// See https://github.com/ethereum/go-ethereum/pull/15225#issuecomment-380191512 // See https://github.com/ethereum/go-ethereum/pull/15225#issuecomment-380191512
func TestCopyOfCopy(t *testing.T) { func TestCopyOfCopy(t *testing.T) {
state, _ := New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()), nil) state, _ := New(types.EmptyRootHash, NewDatabaseForTesting())
addr := common.HexToAddress("aaaa") addr := common.HexToAddress("aaaa")
state.SetBalance(addr, uint256.NewInt(42), tracing.BalanceChangeUnspecified) state.SetBalance(addr, uint256.NewInt(42), tracing.BalanceChangeUnspecified)
@ -727,8 +733,8 @@ func TestCopyOfCopy(t *testing.T) {
// //
// See https://github.com/ethereum/go-ethereum/issues/20106. // See https://github.com/ethereum/go-ethereum/issues/20106.
func TestCopyCommitCopy(t *testing.T) { func TestCopyCommitCopy(t *testing.T) {
tdb := NewDatabase(rawdb.NewMemoryDatabase()) tdb := NewDatabaseForTesting()
state, _ := New(types.EmptyRootHash, tdb, nil) state, _ := New(types.EmptyRootHash, tdb)
// Create an account and check if the retrieved balance is correct // Create an account and check if the retrieved balance is correct
addr := common.HexToAddress("0xaffeaffeaffeaffeaffeaffeaffeaffeaffeaffe") addr := common.HexToAddress("0xaffeaffeaffeaffeaffeaffeaffeaffeaffeaffe")
@ -781,7 +787,7 @@ func TestCopyCommitCopy(t *testing.T) {
} }
// Commit state, ensure states can be loaded from disk // Commit state, ensure states can be loaded from disk
root, _ := state.Commit(0, false) root, _ := state.Commit(0, false)
state, _ = New(root, tdb, nil) state, _ = New(root, tdb)
if balance := state.GetBalance(addr); balance.Cmp(uint256.NewInt(42)) != 0 { if balance := state.GetBalance(addr); balance.Cmp(uint256.NewInt(42)) != 0 {
t.Fatalf("state post-commit balance mismatch: have %v, want %v", balance, 42) t.Fatalf("state post-commit balance mismatch: have %v, want %v", balance, 42)
} }
@ -801,7 +807,7 @@ func TestCopyCommitCopy(t *testing.T) {
// //
// See https://github.com/ethereum/go-ethereum/issues/20106. // See https://github.com/ethereum/go-ethereum/issues/20106.
func TestCopyCopyCommitCopy(t *testing.T) { func TestCopyCopyCommitCopy(t *testing.T) {
state, _ := New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()), nil) state, _ := New(types.EmptyRootHash, NewDatabaseForTesting())
// Create an account and check if the retrieved balance is correct // Create an account and check if the retrieved balance is correct
addr := common.HexToAddress("0xaffeaffeaffeaffeaffeaffeaffeaffeaffeaffe") addr := common.HexToAddress("0xaffeaffeaffeaffeaffeaffeaffeaffeaffeaffe")
@ -870,8 +876,8 @@ func TestCopyCopyCommitCopy(t *testing.T) {
// TestCommitCopy tests the copy from a committed state is not fully functional. // TestCommitCopy tests the copy from a committed state is not fully functional.
func TestCommitCopy(t *testing.T) { func TestCommitCopy(t *testing.T) {
db := NewDatabase(rawdb.NewMemoryDatabase()) db := NewDatabaseForTesting()
state, _ := New(types.EmptyRootHash, db, nil) state, _ := New(types.EmptyRootHash, db)
// Create an account and check if the retrieved balance is correct // Create an account and check if the retrieved balance is correct
addr := common.HexToAddress("0xaffeaffeaffeaffeaffeaffeaffeaffeaffeaffe") addr := common.HexToAddress("0xaffeaffeaffeaffeaffeaffeaffeaffeaffeaffe")
@ -896,7 +902,7 @@ func TestCommitCopy(t *testing.T) {
} }
root, _ := state.Commit(0, true) root, _ := state.Commit(0, true)
state, _ = New(root, db, nil) state, _ = New(root, db)
state.SetState(addr, skey2, sval2) state.SetState(addr, skey2, sval2)
state.Commit(1, true) state.Commit(1, true)
@ -909,10 +915,10 @@ func TestCommitCopy(t *testing.T) {
t.Fatalf("unexpected code: have %x", code) t.Fatalf("unexpected code: have %x", code)
} }
// Miss slots because of non-functional trie after commit // Miss slots because of non-functional trie after commit
if val := copied.GetState(addr, skey1); val != (common.Hash{}) { if val := copied.GetState(addr, skey1); val != sval1 {
t.Fatalf("unexpected storage slot: have %x", sval1) t.Fatalf("unexpected storage slot: have %x", val)
} }
if val := copied.GetCommittedState(addr, skey1); val != (common.Hash{}) { if val := copied.GetCommittedState(addr, skey1); val != sval1 {
t.Fatalf("unexpected storage slot: have %x", val) t.Fatalf("unexpected storage slot: have %x", val)
} }
// Slots cached in the stateDB, available after commit // Slots cached in the stateDB, available after commit
@ -922,9 +928,6 @@ func TestCommitCopy(t *testing.T) {
if val := copied.GetCommittedState(addr, skey2); val != sval2 { if val := copied.GetCommittedState(addr, skey2); val != sval2 {
t.Fatalf("unexpected storage slot: have %x", val) t.Fatalf("unexpected storage slot: have %x", val)
} }
if !errors.Is(copied.Error(), trie.ErrCommitted) {
t.Fatalf("unexpected state error, %v", copied.Error())
}
} }
// TestDeleteCreateRevert tests a weird state transition corner case that we hit // TestDeleteCreateRevert tests a weird state transition corner case that we hit
@ -937,13 +940,13 @@ func TestCommitCopy(t *testing.T) {
// first, but the journal wiped the entire state object on create-revert. // first, but the journal wiped the entire state object on create-revert.
func TestDeleteCreateRevert(t *testing.T) { func TestDeleteCreateRevert(t *testing.T) {
// Create an initial state with a single contract // Create an initial state with a single contract
state, _ := New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()), nil) state, _ := New(types.EmptyRootHash, NewDatabaseForTesting())
addr := common.BytesToAddress([]byte("so")) addr := common.BytesToAddress([]byte("so"))
state.SetBalance(addr, uint256.NewInt(1), tracing.BalanceChangeUnspecified) state.SetBalance(addr, uint256.NewInt(1), tracing.BalanceChangeUnspecified)
root, _ := state.Commit(0, false) root, _ := state.Commit(0, false)
state, _ = New(root, state.db, state.snaps) state, _ = New(root, state.db)
// Simulate self-destructing in one transaction, then create-reverting in another // Simulate self-destructing in one transaction, then create-reverting in another
state.SelfDestruct(addr) state.SelfDestruct(addr)
@ -955,7 +958,7 @@ func TestDeleteCreateRevert(t *testing.T) {
// Commit the entire state and make sure we don't crash and have the correct state // Commit the entire state and make sure we don't crash and have the correct state
root, _ = state.Commit(0, true) root, _ = state.Commit(0, true)
state, _ = New(root, state.db, state.snaps) state, _ = New(root, state.db)
if state.getStateObject(addr) != nil { if state.getStateObject(addr) != nil {
t.Fatalf("self-destructed contract came alive") t.Fatalf("self-destructed contract came alive")
@ -986,10 +989,10 @@ func testMissingTrieNodes(t *testing.T, scheme string) {
CleanCacheSize: 0, CleanCacheSize: 0,
}}) // disable caching }}) // disable caching
} }
db := NewDatabaseWithNodeDB(memDb, tdb) db := NewDatabase(tdb, nil)
var root common.Hash var root common.Hash
state, _ := New(types.EmptyRootHash, db, nil) state, _ := New(types.EmptyRootHash, db)
addr := common.BytesToAddress([]byte("so")) addr := common.BytesToAddress([]byte("so"))
{ {
state.SetBalance(addr, uint256.NewInt(1), tracing.BalanceChangeUnspecified) state.SetBalance(addr, uint256.NewInt(1), tracing.BalanceChangeUnspecified)
@ -1003,7 +1006,7 @@ func testMissingTrieNodes(t *testing.T, scheme string) {
tdb.Commit(root, false) tdb.Commit(root, false)
} }
// Create a new state on the old root // Create a new state on the old root
state, _ = New(root, db, nil) state, _ = New(root, db)
// Now we clear out the memdb // Now we clear out the memdb
it := memDb.NewIterator(nil, nil) it := memDb.NewIterator(nil, nil)
for it.Next() { for it.Next() {
@ -1036,9 +1039,8 @@ func TestStateDBAccessList(t *testing.T) {
return common.HexToHash(a) return common.HexToHash(a)
} }
memDb := rawdb.NewMemoryDatabase() db := NewDatabaseForTesting()
db := NewDatabase(memDb) state, _ := New(types.EmptyRootHash, db)
state, _ := New(types.EmptyRootHash, db, nil)
state.accessList = newAccessList() state.accessList = newAccessList()
verifyAddrs := func(astrings ...string) { verifyAddrs := func(astrings ...string) {
@ -1207,9 +1209,9 @@ func TestFlushOrderDataLoss(t *testing.T) {
// Create a state trie with many accounts and slots // Create a state trie with many accounts and slots
var ( var (
memdb = rawdb.NewMemoryDatabase() memdb = rawdb.NewMemoryDatabase()
triedb = triedb.NewDatabase(memdb, nil) tdb = triedb.NewDatabase(memdb, triedb.HashDefaults)
statedb = NewDatabaseWithNodeDB(memdb, triedb) statedb = NewDatabase(tdb, nil)
state, _ = New(types.EmptyRootHash, statedb, nil) state, _ = New(types.EmptyRootHash, statedb)
) )
for a := byte(0); a < 10; a++ { for a := byte(0); a < 10; a++ {
state.CreateAccount(common.Address{a}) state.CreateAccount(common.Address{a})
@ -1221,15 +1223,15 @@ func TestFlushOrderDataLoss(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("failed to commit state trie: %v", err) t.Fatalf("failed to commit state trie: %v", err)
} }
triedb.Reference(root, common.Hash{}) tdb.Reference(root, common.Hash{})
if err := triedb.Cap(1024); err != nil { if err := tdb.Cap(1024); err != nil {
t.Fatalf("failed to cap trie dirty cache: %v", err) t.Fatalf("failed to cap trie dirty cache: %v", err)
} }
if err := triedb.Commit(root, false); err != nil { if err := tdb.Commit(root, false); err != nil {
t.Fatalf("failed to commit state trie: %v", err) t.Fatalf("failed to commit state trie: %v", err)
} }
// Reopen the state trie from flushed disk and verify it // Reopen the state trie from flushed disk and verify it
state, err = New(root, NewDatabase(memdb), nil) state, err = New(root, NewDatabase(triedb.NewDatabase(memdb, triedb.HashDefaults), nil))
if err != nil { if err != nil {
t.Fatalf("failed to reopen state trie: %v", err) t.Fatalf("failed to reopen state trie: %v", err)
} }
@ -1243,9 +1245,8 @@ func TestFlushOrderDataLoss(t *testing.T) {
} }
func TestStateDBTransientStorage(t *testing.T) { func TestStateDBTransientStorage(t *testing.T) {
memDb := rawdb.NewMemoryDatabase() db := NewDatabaseForTesting()
db := NewDatabase(memDb) state, _ := New(types.EmptyRootHash, db)
state, _ := New(types.EmptyRootHash, db, nil)
key := common.Hash{0x01} key := common.Hash{0x01}
value := common.Hash{0x02} value := common.Hash{0x02}
@ -1280,9 +1281,9 @@ func TestDeleteStorage(t *testing.T) {
var ( var (
disk = rawdb.NewMemoryDatabase() disk = rawdb.NewMemoryDatabase()
tdb = triedb.NewDatabase(disk, nil) tdb = triedb.NewDatabase(disk, nil)
db = NewDatabaseWithNodeDB(disk, tdb)
snaps, _ = snapshot.New(snapshot.Config{CacheSize: 10}, disk, tdb, types.EmptyRootHash) snaps, _ = snapshot.New(snapshot.Config{CacheSize: 10}, disk, tdb, types.EmptyRootHash)
state, _ = New(types.EmptyRootHash, db, snaps) db = NewDatabase(tdb, snaps)
state, _ = New(types.EmptyRootHash, db)
addr = common.HexToAddress("0x1") addr = common.HexToAddress("0x1")
) )
// Initialize account and populate storage // Initialize account and populate storage
@ -1294,9 +1295,10 @@ func TestDeleteStorage(t *testing.T) {
state.SetState(addr, slot, value) state.SetState(addr, slot, value)
} }
root, _ := state.Commit(0, true) root, _ := state.Commit(0, true)
// Init phase done, create two states, one with snap and one without // Init phase done, create two states, one with snap and one without
fastState, _ := New(root, db, snaps) fastState, _ := New(root, NewDatabase(tdb, snaps))
slowState, _ := New(root, db, nil) slowState, _ := New(root, NewDatabase(tdb, nil))
obj := fastState.getOrNewStateObject(addr) obj := fastState.getOrNewStateObject(addr)
storageRoot := obj.data.Root storageRoot := obj.data.Root
@ -1334,8 +1336,8 @@ func TestStorageDirtiness(t *testing.T) {
var ( var (
disk = rawdb.NewMemoryDatabase() disk = rawdb.NewMemoryDatabase()
tdb = triedb.NewDatabase(disk, nil) tdb = triedb.NewDatabase(disk, nil)
db = NewDatabaseWithNodeDB(disk, tdb) db = NewDatabase(tdb, nil)
state, _ = New(types.EmptyRootHash, db, nil) state, _ = New(types.EmptyRootHash, db)
addr = common.HexToAddress("0x1") addr = common.HexToAddress("0x1")
checkDirty = func(key common.Hash, value common.Hash, dirty bool) { checkDirty = func(key common.Hash, value common.Hash, dirty bool) {
obj := state.getStateObject(addr) obj := state.getStateObject(addr)

@ -53,8 +53,8 @@ func makeTestState(scheme string) (ethdb.Database, Database, *triedb.Database, c
} }
db := rawdb.NewMemoryDatabase() db := rawdb.NewMemoryDatabase()
nodeDb := triedb.NewDatabase(db, config) nodeDb := triedb.NewDatabase(db, config)
sdb := NewDatabaseWithNodeDB(db, nodeDb) sdb := NewDatabase(nodeDb, nil)
state, _ := New(types.EmptyRootHash, sdb, nil) state, _ := New(types.EmptyRootHash, sdb)
// Fill it with some arbitrary data // Fill it with some arbitrary data
var accounts []*testAccount var accounts []*testAccount
@ -94,7 +94,7 @@ func checkStateAccounts(t *testing.T, db ethdb.Database, scheme string, root com
config.PathDB = pathdb.Defaults config.PathDB = pathdb.Defaults
} }
// Check root availability and state contents // Check root availability and state contents
state, err := New(root, NewDatabaseWithConfig(db, &config), nil) state, err := New(root, NewDatabase(triedb.NewDatabase(db, &config), nil))
if err != nil { if err != nil {
t.Fatalf("failed to create state trie at %x: %v", root, err) t.Fatalf("failed to create state trie at %x: %v", root, err)
} }
@ -120,7 +120,7 @@ func checkStateConsistency(db ethdb.Database, scheme string, root common.Hash) e
if scheme == rawdb.PathScheme { if scheme == rawdb.PathScheme {
config.PathDB = pathdb.Defaults config.PathDB = pathdb.Defaults
} }
state, err := New(root, NewDatabaseWithConfig(db, config), nil) state, err := New(root, NewDatabase(triedb.NewDatabase(db, config), nil))
if err != nil { if err != nil {
return err return err
} }

@ -40,6 +40,7 @@ var (
// //
// Note, the prefetcher's API is not thread safe. // Note, the prefetcher's API is not thread safe.
type triePrefetcher struct { type triePrefetcher struct {
verkle bool // Flag whether the prefetcher is in verkle mode
db Database // Database to fetch trie nodes through db Database // Database to fetch trie nodes through
root common.Hash // Root hash of the account trie for metrics root common.Hash // Root hash of the account trie for metrics
fetchers map[string]*subfetcher // Subfetchers for each trie fetchers map[string]*subfetcher // Subfetchers for each trie
@ -66,6 +67,7 @@ type triePrefetcher struct {
func newTriePrefetcher(db Database, root common.Hash, namespace string, noreads bool) *triePrefetcher { func newTriePrefetcher(db Database, root common.Hash, namespace string, noreads bool) *triePrefetcher {
prefix := triePrefetchMetricsPrefix + namespace prefix := triePrefetchMetricsPrefix + namespace
return &triePrefetcher{ return &triePrefetcher{
verkle: db.TrieDB().IsVerkle(),
db: db, db: db,
root: root, root: root,
fetchers: make(map[string]*subfetcher), // Active prefetchers use the fetchers map fetchers: make(map[string]*subfetcher), // Active prefetchers use the fetchers map
@ -196,12 +198,18 @@ func (p *triePrefetcher) trie(owner common.Hash, root common.Hash) Trie {
func (p *triePrefetcher) used(owner common.Hash, root common.Hash, used [][]byte) { func (p *triePrefetcher) used(owner common.Hash, root common.Hash, used [][]byte) {
if fetcher := p.fetchers[p.trieID(owner, root)]; fetcher != nil { if fetcher := p.fetchers[p.trieID(owner, root)]; fetcher != nil {
fetcher.wait() // ensure the fetcher's idle before poking in its internals fetcher.wait() // ensure the fetcher's idle before poking in its internals
fetcher.used = used fetcher.used = append(fetcher.used, used...)
} }
} }
// trieID returns an unique trie identifier consists the trie owner and root hash. // trieID returns an unique trie identifier consists the trie owner and root hash.
func (p *triePrefetcher) trieID(owner common.Hash, root common.Hash) string { func (p *triePrefetcher) trieID(owner common.Hash, root common.Hash) string {
// The trie in verkle is only identified by state root
if p.verkle {
return p.root.Hex()
}
// The trie in merkle is either identified by state root (account trie),
// or identified by the owner and trie root (storage trie)
trieID := make([]byte, common.HashLength*2) trieID := make([]byte, common.HashLength*2)
copy(trieID, owner.Bytes()) copy(trieID, owner.Bytes())
copy(trieID[common.HashLength:], root.Bytes()) copy(trieID[common.HashLength:], root.Bytes())
@ -320,29 +328,47 @@ func (sf *subfetcher) terminate(async bool) {
<-sf.term <-sf.term
} }
// openTrie resolves the target trie from database for prefetching.
func (sf *subfetcher) openTrie() error {
// Open the verkle tree if the sub-fetcher is in verkle mode. Note, there is
// only a single fetcher for verkle.
if sf.db.TrieDB().IsVerkle() {
tr, err := sf.db.OpenTrie(sf.state)
if err != nil {
log.Warn("Trie prefetcher failed opening verkle trie", "root", sf.root, "err", err)
return err
}
sf.trie = tr
return nil
}
// Open the merkle tree if the sub-fetcher is in merkle mode
if sf.owner == (common.Hash{}) {
tr, err := sf.db.OpenTrie(sf.state)
if err != nil {
log.Warn("Trie prefetcher failed opening account trie", "root", sf.root, "err", err)
return err
}
sf.trie = tr
return nil
}
tr, err := sf.db.OpenStorageTrie(sf.state, sf.addr, sf.root, nil)
if err != nil {
log.Warn("Trie prefetcher failed opening storage trie", "root", sf.root, "err", err)
return err
}
sf.trie = tr
return nil
}
// loop loads newly-scheduled trie tasks as they are received and loads them, stopping // loop loads newly-scheduled trie tasks as they are received and loads them, stopping
// when requested. // when requested.
func (sf *subfetcher) loop() { func (sf *subfetcher) loop() {
// No matter how the loop stops, signal anyone waiting that it's terminated // No matter how the loop stops, signal anyone waiting that it's terminated
defer close(sf.term) defer close(sf.term)
// Start by opening the trie and stop processing if it fails if err := sf.openTrie(); err != nil {
if sf.owner == (common.Hash{}) { return
trie, err := sf.db.OpenTrie(sf.root)
if err != nil {
log.Warn("Trie prefetcher failed opening trie", "root", sf.root, "err", err)
return
}
sf.trie = trie
} else {
trie, err := sf.db.OpenStorageTrie(sf.state, sf.addr, sf.root, nil)
if err != nil {
log.Warn("Trie prefetcher failed opening trie", "root", sf.root, "err", err)
return
}
sf.trie = trie
} }
// Trie opened successfully, keep prefetching items
for { for {
select { select {
case <-sf.wake: case <-sf.wake:

@ -24,11 +24,14 @@ import (
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/internal/testrand"
"github.com/ethereum/go-ethereum/triedb"
"github.com/holiman/uint256" "github.com/holiman/uint256"
) )
func filledStateDB() *StateDB { func filledStateDB() *StateDB {
state, _ := New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()), nil) state, _ := New(types.EmptyRootHash, NewDatabaseForTesting())
// Create an account and check if the retrieved balance is correct // Create an account and check if the retrieved balance is correct
addr := common.HexToAddress("0xaffeaffeaffeaffeaffeaffeaffeaffeaffeaffe") addr := common.HexToAddress("0xaffeaffeaffeaffeaffeaffeaffeaffeaffeaffe")
@ -62,3 +65,47 @@ func TestUseAfterTerminate(t *testing.T) {
t.Errorf("Prefetcher returned nil trie after terminate") t.Errorf("Prefetcher returned nil trie after terminate")
} }
} }
func TestVerklePrefetcher(t *testing.T) {
disk := rawdb.NewMemoryDatabase()
db := triedb.NewDatabase(disk, triedb.VerkleDefaults)
sdb := NewDatabase(db, nil)
state, err := New(types.EmptyRootHash, sdb)
if err != nil {
t.Fatalf("failed to initialize state: %v", err)
}
// Create an account and check if the retrieved balance is correct
addr := testrand.Address()
skey := testrand.Hash()
sval := testrand.Hash()
state.SetBalance(addr, uint256.NewInt(42), tracing.BalanceChangeUnspecified) // Change the account trie
state.SetCode(addr, []byte("hello")) // Change an external metadata
state.SetState(addr, skey, sval) // Change the storage trie
root, _ := state.Commit(0, true)
state, _ = New(root, sdb)
sRoot := state.GetStorageRoot(addr)
fetcher := newTriePrefetcher(sdb, root, "", false)
// Read account
fetcher.prefetch(common.Hash{}, root, common.Address{}, [][]byte{
addr.Bytes(),
}, false)
// Read storage slot
fetcher.prefetch(crypto.Keccak256Hash(addr.Bytes()), sRoot, addr, [][]byte{
skey.Bytes(),
}, false)
fetcher.terminate(false)
accountTrie := fetcher.trie(common.Hash{}, root)
storageTrie := fetcher.trie(crypto.Keccak256Hash(addr.Bytes()), sRoot)
rootA := accountTrie.Hash()
rootB := storageTrie.Hash()
if rootA != rootB {
t.Fatal("Two different tries are retrieved")
}
}

@ -53,7 +53,7 @@ func NewStateProcessor(config *params.ChainConfig, chain *HeaderChain) *StatePro
// Process returns the receipts and logs accumulated during the process and // Process returns the receipts and logs accumulated during the process and
// returns the amount of gas that was used in the process. If any of the // returns the amount of gas that was used in the process. If any of the
// transactions failed to execute due to insufficient gas it will return an error. // transactions failed to execute due to insufficient gas it will return an error.
func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) { func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (*ProcessResult, error) {
var ( var (
receipts types.Receipts receipts types.Receipts
usedGas = new(uint64) usedGas = new(uint64)
@ -71,6 +71,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
var ( var (
context vm.BlockContext context vm.BlockContext
signer = types.MakeSigner(p.config, header.Number, header.Time) signer = types.MakeSigner(p.config, header.Number, header.Time)
err error
) )
context = NewEVMBlockContext(header, p.chain, nil) context = NewEVMBlockContext(header, p.chain, nil)
vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, p.config, cfg) vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, p.config, cfg)
@ -84,21 +85,35 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
for i, tx := range block.Transactions() { for i, tx := range block.Transactions() {
msg, err := TransactionToMessage(tx, signer, header.BaseFee) msg, err := TransactionToMessage(tx, signer, header.BaseFee)
if err != nil { if err != nil {
return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) return nil, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
} }
statedb.SetTxContext(tx.Hash(), i) statedb.SetTxContext(tx.Hash(), i)
receipt, err := ApplyTransactionWithEVM(msg, p.config, gp, statedb, blockNumber, blockHash, tx, usedGas, vmenv) receipt, err := ApplyTransactionWithEVM(msg, p.config, gp, statedb, blockNumber, blockHash, tx, usedGas, vmenv)
if err != nil { if err != nil {
return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) return nil, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
} }
receipts = append(receipts, receipt) receipts = append(receipts, receipt)
allLogs = append(allLogs, receipt.Logs...) allLogs = append(allLogs, receipt.Logs...)
} }
// Read requests if Prague is enabled.
var requests types.Requests
if p.config.IsPrague(block.Number(), block.Time()) {
requests, err = ParseDepositLogs(allLogs, p.config)
if err != nil {
return nil, err
}
}
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards) // Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
p.chain.engine.Finalize(p.chain, header, statedb, block.Body()) p.chain.engine.Finalize(p.chain, header, statedb, block.Body())
return receipts, allLogs, *usedGas, nil return &ProcessResult{
Receipts: receipts,
Requests: requests,
Logs: allLogs,
GasUsed: *usedGas,
}, nil
} }
// ApplyTransactionWithEVM attempts to apply a transaction to the given state database // ApplyTransactionWithEVM attempts to apply a transaction to the given state database
@ -132,9 +147,14 @@ func ApplyTransactionWithEVM(msg *Message, config *params.ChainConfig, gp *GasPo
} }
*usedGas += result.UsedGas *usedGas += result.UsedGas
return MakeReceipt(evm, result, statedb, blockNumber, blockHash, tx, *usedGas, root), nil
}
// MakeReceipt generates the receipt object for a transaction given its execution result.
func MakeReceipt(evm *vm.EVM, result *ExecutionResult, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas uint64, root []byte) *types.Receipt {
// Create a new receipt for the transaction, storing the intermediate root and gas used // Create a new receipt for the transaction, storing the intermediate root and gas used
// by the tx. // by the tx.
receipt = &types.Receipt{Type: tx.Type(), PostState: root, CumulativeGasUsed: *usedGas} receipt := &types.Receipt{Type: tx.Type(), PostState: root, CumulativeGasUsed: usedGas}
if result.Failed() { if result.Failed() {
receipt.Status = types.ReceiptStatusFailed receipt.Status = types.ReceiptStatusFailed
} else { } else {
@ -149,17 +169,23 @@ func ApplyTransactionWithEVM(msg *Message, config *params.ChainConfig, gp *GasPo
} }
// If the transaction created a contract, store the creation address in the receipt. // If the transaction created a contract, store the creation address in the receipt.
if msg.To == nil { if tx.To() == nil {
receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, tx.Nonce()) receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, tx.Nonce())
} }
// Merge the tx-local access event into the "block-local" one, in order to collect
// all values, so that the witness can be built.
if statedb.GetTrie().IsVerkle() {
statedb.AccessEvents().Merge(evm.AccessEvents)
}
// Set the receipt logs and create the bloom filter. // Set the receipt logs and create the bloom filter.
receipt.Logs = statedb.GetLogs(tx.Hash(), blockNumber.Uint64(), blockHash) receipt.Logs = statedb.GetLogs(tx.Hash(), blockNumber.Uint64(), blockHash)
receipt.Bloom = types.CreateBloom(types.Receipts{receipt}) receipt.Bloom = types.CreateBloom(types.Receipts{receipt})
receipt.BlockHash = blockHash receipt.BlockHash = blockHash
receipt.BlockNumber = blockNumber receipt.BlockNumber = blockNumber
receipt.TransactionIndex = uint(statedb.TxIndex()) receipt.TransactionIndex = uint(statedb.TxIndex())
return receipt, err return receipt
} }
// ApplyTransaction attempts to apply a transaction to the given state database // ApplyTransaction attempts to apply a transaction to the given state database
@ -233,3 +259,19 @@ func ProcessParentBlockHash(prevHash common.Hash, vmenv *vm.EVM, statedb *state.
_, _, _ = vmenv.Call(vm.AccountRef(msg.From), *msg.To, msg.Data, 30_000_000, common.U2560) _, _, _ = vmenv.Call(vm.AccountRef(msg.From), *msg.To, msg.Data, 30_000_000, common.U2560)
statedb.Finalise(true) statedb.Finalise(true)
} }
// 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)
for _, log := range logs {
if log.Address == config.DepositContractAddress {
d, err := types.UnpackIntoDeposit(log.Data)
if err != nil {
return nil, fmt.Errorf("unable to parse deposit data: %v", err)
}
deposits = append(deposits, types.NewRequest(d))
}
}
return deposits, nil
}

@ -34,10 +34,10 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb/memorydb"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/triedb" "github.com/ethereum/go-ethereum/triedb"
"github.com/ethereum/go-verkle"
"github.com/holiman/uint256" "github.com/holiman/uint256"
"golang.org/x/crypto/sha3" "golang.org/x/crypto/sha3"
) )
@ -132,7 +132,7 @@ func TestStateProcessorErrors(t *testing.T) {
}, },
}, },
} }
blockchain, _ = NewBlockChain(db, nil, gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil, nil) blockchain, _ = NewBlockChain(db, nil, gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil)
tooBigInitCode = [params.MaxInitCodeSize + 1]byte{} tooBigInitCode = [params.MaxInitCodeSize + 1]byte{}
) )
@ -292,7 +292,7 @@ func TestStateProcessorErrors(t *testing.T) {
}, },
}, },
} }
blockchain, _ = NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) blockchain, _ = NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil)
) )
defer blockchain.Stop() defer blockchain.Stop()
for i, tt := range []struct { for i, tt := range []struct {
@ -331,7 +331,7 @@ func TestStateProcessorErrors(t *testing.T) {
}, },
}, },
} }
blockchain, _ = NewBlockChain(db, nil, gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil, nil) blockchain, _ = NewBlockChain(db, nil, gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil)
) )
defer blockchain.Stop() defer blockchain.Stop()
for i, tt := range []struct { for i, tt := range []struct {
@ -480,18 +480,45 @@ func TestProcessVerkle(t *testing.T) {
// genesis := gspec.MustCommit(bcdb, triedb) // genesis := gspec.MustCommit(bcdb, triedb)
cacheConfig := DefaultCacheConfigWithScheme("path") cacheConfig := DefaultCacheConfigWithScheme("path")
cacheConfig.SnapshotLimit = 0 cacheConfig.SnapshotLimit = 0
blockchain, _ := NewBlockChain(bcdb, cacheConfig, gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil, nil) blockchain, _ := NewBlockChain(bcdb, cacheConfig, gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil)
defer blockchain.Stop() defer blockchain.Stop()
txCost1 := params.TxGas txCost1 := params.TxGas
txCost2 := params.TxGas txCost2 := params.TxGas
contractCreationCost := intrinsicContractCreationGas + uint64(2039 /* execution costs */) contractCreationCost := intrinsicContractCreationGas +
codeWithExtCodeCopyGas := intrinsicCodeWithExtCodeCopyGas + uint64(57444 /* execution costs */) params.WitnessChunkReadCost + params.WitnessChunkWriteCost + params.WitnessBranchReadCost + params.WitnessBranchWriteCost + /* creation */
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* creation with value */
739 /* execution costs */
codeWithExtCodeCopyGas := intrinsicCodeWithExtCodeCopyGas +
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + params.WitnessBranchReadCost + params.WitnessBranchWriteCost + /* creation (tx) */
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + params.WitnessBranchReadCost + params.WitnessBranchWriteCost + /* creation (CREATE at pc=0x20) */
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* write code hash */
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #0 */
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #1 */
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #2 */
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #3 */
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #4 */
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #5 */
params.WitnessChunkReadCost + /* SLOAD in constructor */
params.WitnessChunkWriteCost + /* SSTORE in constructor */
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + params.WitnessBranchReadCost + params.WitnessBranchWriteCost + /* creation (CREATE at PC=0x121) */
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* write code hash */
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #0 */
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #1 */
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #2 */
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #3 */
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #4 */
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #5 */
params.WitnessChunkReadCost + /* SLOAD in constructor */
params.WitnessChunkWriteCost + /* SSTORE in constructor */
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* write code hash for tx creation */
15*(params.WitnessChunkReadCost+params.WitnessChunkWriteCost) + /* code chunks #0..#14 */
4844 /* execution costs */
blockGasUsagesExpected := []uint64{ blockGasUsagesExpected := []uint64{
txCost1*2 + txCost2, txCost1*2 + txCost2,
txCost1*2 + txCost2 + contractCreationCost + codeWithExtCodeCopyGas, txCost1*2 + txCost2 + contractCreationCost + codeWithExtCodeCopyGas,
} }
_, chain, _, _, _ := GenerateVerkleChainWithGenesis(gspec, beacon.New(ethash.NewFaker()), 2, func(i int, gen *BlockGen) { _, chain, _, proofs, statediffs := GenerateVerkleChainWithGenesis(gspec, beacon.New(ethash.NewFaker()), 2, func(i int, gen *BlockGen) {
gen.SetPoS() gen.SetPoS()
// TODO need to check that the tx cost provided is the exact amount used (no remaining left-over) // TODO need to check that the tx cost provided is the exact amount used (no remaining left-over)
@ -512,7 +539,17 @@ func TestProcessVerkle(t *testing.T) {
} }
}) })
t.Log("inserting blocks into the chain") // Check proof for both blocks
err := verkle.Verify(proofs[0], gspec.ToBlock().Root().Bytes(), chain[0].Root().Bytes(), statediffs[0])
if err != nil {
t.Fatal(err)
}
err = verkle.Verify(proofs[1], chain[0].Root().Bytes(), chain[1].Root().Bytes(), statediffs[1])
if err != nil {
t.Fatal(err)
}
t.Log("verified verkle proof, inserting blocks into the chain")
endnum, err := blockchain.InsertChain(chain) endnum, err := blockchain.InsertChain(chain)
if err != nil { if err != nil {
@ -564,7 +601,7 @@ func TestProcessParentBlockHash(t *testing.T) {
} }
} }
t.Run("MPT", func(t *testing.T) { t.Run("MPT", func(t *testing.T) {
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil) statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
test(statedb) test(statedb)
}) })
t.Run("Verkle", func(t *testing.T) { t.Run("Verkle", func(t *testing.T) {
@ -572,7 +609,7 @@ func TestProcessParentBlockHash(t *testing.T) {
cacheConfig := DefaultCacheConfigWithScheme(rawdb.PathScheme) cacheConfig := DefaultCacheConfigWithScheme(rawdb.PathScheme)
cacheConfig.SnapshotLimit = 0 cacheConfig.SnapshotLimit = 0
triedb := triedb.NewDatabase(db, cacheConfig.triedbConfig(true)) triedb := triedb.NewDatabase(db, cacheConfig.triedbConfig(true))
statedb, _ := state.New(types.EmptyVerkleHash, state.NewDatabaseWithNodeDB(db, triedb), nil) statedb, _ := state.New(types.EmptyVerkleHash, state.NewDatabase(triedb, nil))
test(statedb) test(statedb)
}) })
} }

@ -142,27 +142,31 @@ type Message struct {
BlobGasFeeCap *big.Int BlobGasFeeCap *big.Int
BlobHashes []common.Hash BlobHashes []common.Hash
// When SkipAccountChecks is true, the message nonce is not checked against the // When SkipNonceChecks is true, the message nonce is not checked against the
// account nonce in state. It also disables checking that the sender is an EOA. // account nonce in state.
// This field will be set to true for operations like RPC eth_call. // This field will be set to true for operations like RPC eth_call.
SkipAccountChecks bool SkipNonceChecks bool
// When SkipFromEOACheck is true, the message sender is not checked to be an EOA.
SkipFromEOACheck bool
} }
// TransactionToMessage converts a transaction into a Message. // TransactionToMessage converts a transaction into a Message.
func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.Int) (*Message, error) { func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.Int) (*Message, error) {
msg := &Message{ msg := &Message{
Nonce: tx.Nonce(), Nonce: tx.Nonce(),
GasLimit: tx.Gas(), GasLimit: tx.Gas(),
GasPrice: new(big.Int).Set(tx.GasPrice()), GasPrice: new(big.Int).Set(tx.GasPrice()),
GasFeeCap: new(big.Int).Set(tx.GasFeeCap()), GasFeeCap: new(big.Int).Set(tx.GasFeeCap()),
GasTipCap: new(big.Int).Set(tx.GasTipCap()), GasTipCap: new(big.Int).Set(tx.GasTipCap()),
To: tx.To(), To: tx.To(),
Value: tx.Value(), Value: tx.Value(),
Data: tx.Data(), Data: tx.Data(),
AccessList: tx.AccessList(), AccessList: tx.AccessList(),
SkipAccountChecks: false, SkipNonceChecks: false,
BlobHashes: tx.BlobHashes(), SkipFromEOACheck: false,
BlobGasFeeCap: tx.BlobGasFeeCap(), BlobHashes: tx.BlobHashes(),
BlobGasFeeCap: tx.BlobGasFeeCap(),
} }
// If baseFee provided, set gasPrice to effectiveGasPrice. // If baseFee provided, set gasPrice to effectiveGasPrice.
if baseFee != nil { if baseFee != nil {
@ -280,7 +284,7 @@ func (st *StateTransition) buyGas() error {
func (st *StateTransition) preCheck() error { func (st *StateTransition) preCheck() error {
// Only check transactions that are not fake // Only check transactions that are not fake
msg := st.msg msg := st.msg
if !msg.SkipAccountChecks { if !msg.SkipNonceChecks {
// Make sure this transaction's nonce is correct. // Make sure this transaction's nonce is correct.
stNonce := st.state.GetNonce(msg.From) stNonce := st.state.GetNonce(msg.From)
if msgNonce := msg.Nonce; stNonce < msgNonce { if msgNonce := msg.Nonce; stNonce < msgNonce {
@ -293,6 +297,8 @@ func (st *StateTransition) preCheck() error {
return fmt.Errorf("%w: address %v, nonce: %d", ErrNonceMax, return fmt.Errorf("%w: address %v, nonce: %d", ErrNonceMax,
msg.From.Hex(), stNonce) msg.From.Hex(), stNonce)
} }
}
if !msg.SkipFromEOACheck {
// Make sure the sender is an EOA // Make sure the sender is an EOA
codeHash := st.state.GetCodeHash(msg.From) codeHash := st.state.GetCodeHash(msg.From)
if codeHash != (common.Hash{}) && codeHash != types.EmptyCodeHash { if codeHash != (common.Hash{}) && codeHash != types.EmptyCodeHash {
@ -470,7 +476,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
// add the coinbase to the witness iff the fee is greater than 0 // add the coinbase to the witness iff the fee is greater than 0
if rules.IsEIP4762 && fee.Sign() != 0 { if rules.IsEIP4762 && fee.Sign() != 0 {
st.evm.AccessEvents.BalanceGas(st.evm.Context.Coinbase, true) st.evm.AccessEvents.AddAccount(st.evm.Context.Coinbase, true)
} }
} }

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

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

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

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

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

@ -15,6 +15,7 @@ The tracing interface has been extended with backwards-compatible changes to sup
- `OnCodeSizeRead(addr common.Address, size int)`: This hook is called when an account code size is read. - `OnCodeSizeRead(addr common.Address, size int)`: This hook is called when an account code size is read.
- `OnCodeHashRead(addr common.Address, codeHash common.Hash)`: This hook is called when an account code hash is read. - `OnCodeHashRead(addr common.Address, codeHash common.Hash)`: This hook is called when an account code hash is read.
- `OnStorageRead(addr common.Address, slot common.Hash, value common.Hash)`: This hook is called when an account storage slot is read. - `OnStorageRead(addr common.Address, slot common.Hash, value common.Hash)`: This hook is called when an account storage slot is read.
- `OnBlockHashRead(blockNum uint64, hash common.Hash)`: This hook is called when a block hash is read by EVM.
### Modified methods ### Modified methods
@ -48,6 +49,24 @@ The state changes that are covered by the journaling library are:
- `OnCodeChange` - `OnCodeChange`
- `OnStorageChange` - `OnStorageChange`
## [v1.14.9](https://github.com/ethereum/go-ethereum/releases/tag/v1.14.9)
### Modified types
- `GasChangeReason` has been extended with the following reasons which will be enabled only post-Verkle. There shouldn't be any gas changes with those reasons prior to the fork.
- `GasChangeWitnessContractCollisionCheck` flags the event of adding to the witness when checking for contract address collision.
## [v1.14.4]
This release contained only minor extensions to the tracing interface.
### Modified types
- `GasChangeReason` has been extended with the following reasons that will only be active post-Verkle.
- `GasChangeWitnessContractInit` flags the event of adding to the witness during the contract creation initialization step.
- `GasChangeWitnessContractCreation` flags the event of adding to the witness during the contract creation finalization step.
- `GasChangeWitnessCodeChunk` flags the event of adding one or more contract code chunks to the witness.
## [v1.14.3] ## [v1.14.3]
There have been minor backwards-compatible changes to the tracing interface to explicitly mark the execution of **system** contracts. As of now the only system call updates the parent beacon block root as per [EIP-4788](https://eips.ethereum.org/EIPS/eip-4788). Other system calls are being considered for the future hardfork. There have been minor backwards-compatible changes to the tracing interface to explicitly mark the execution of **system** contracts. As of now the only system call updates the parent beacon block root as per [EIP-4788](https://eips.ethereum.org/EIPS/eip-4788). Other system calls are being considered for the future hardfork.
@ -124,3 +143,4 @@ The hooks `CaptureStart` and `CaptureEnd` have been removed. These hooks signale
[unreleased]: https://github.com/ethereum/go-ethereum/compare/v1.14.8...master [unreleased]: https://github.com/ethereum/go-ethereum/compare/v1.14.8...master
[v1.14.0]: https://github.com/ethereum/go-ethereum/releases/tag/v1.14.0 [v1.14.0]: https://github.com/ethereum/go-ethereum/releases/tag/v1.14.0
[v1.14.3]: https://github.com/ethereum/go-ethereum/releases/tag/v1.14.3 [v1.14.3]: https://github.com/ethereum/go-ethereum/releases/tag/v1.14.3
[v1.14.4]: https://github.com/ethereum/go-ethereum/releases/tag/v1.14.4

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

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

@ -27,20 +27,17 @@ import (
"path/filepath" "path/filepath"
"sync" "sync"
"testing" "testing"
"time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/misc/eip1559" "github.com/ethereum/go-ethereum/consensus/misc/eip1559"
"github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/consensus/misc/eip4844"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/txpool"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/ethdb/memorydb"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
@ -55,25 +52,14 @@ var (
emptyBlobVHash = kzg4844.CalcBlobHashV1(sha256.New(), &emptyBlobCommit) emptyBlobVHash = kzg4844.CalcBlobHashV1(sha256.New(), &emptyBlobCommit)
) )
// Chain configuration with Cancun enabled.
//
// TODO(karalabe): replace with params.MainnetChainConfig after Cancun.
var testChainConfig *params.ChainConfig
func init() {
testChainConfig = new(params.ChainConfig)
*testChainConfig = *params.MainnetChainConfig
testChainConfig.CancunTime = new(uint64)
*testChainConfig.CancunTime = uint64(time.Now().Unix())
}
// testBlockChain is a mock of the live chain for testing the pool. // testBlockChain is a mock of the live chain for testing the pool.
type testBlockChain struct { type testBlockChain struct {
config *params.ChainConfig config *params.ChainConfig
basefee *uint256.Int basefee *uint256.Int
blobfee *uint256.Int blobfee *uint256.Int
statedb *state.StateDB statedb *state.StateDB
blocks map[uint64]*types.Block
} }
func (bc *testBlockChain) Config() *params.ChainConfig { func (bc *testBlockChain) Config() *params.ChainConfig {
@ -81,7 +67,7 @@ func (bc *testBlockChain) Config() *params.ChainConfig {
} }
func (bc *testBlockChain) CurrentBlock() *types.Header { func (bc *testBlockChain) CurrentBlock() *types.Header {
// Yolo, life is too short to invert mist.CalcBaseFee and misc.CalcBlobFee, // Yolo, life is too short to invert misc.CalcBaseFee and misc.CalcBlobFee,
// just binary search it them. // just binary search it them.
// The base fee at 5714 ETH translates into the 21000 base gas higher than // The base fee at 5714 ETH translates into the 21000 base gas higher than
@ -144,7 +130,14 @@ func (bc *testBlockChain) CurrentFinalBlock() *types.Header {
} }
func (bc *testBlockChain) GetBlock(hash common.Hash, number uint64) *types.Block { func (bc *testBlockChain) GetBlock(hash common.Hash, number uint64) *types.Block {
return nil // This is very yolo for the tests. If the number is the origin block we use
// to init the pool, return an empty block with the correct starting header.
//
// If it is something else, return some baked in mock data.
if number == bc.config.LondonBlock.Uint64()+1 {
return types.NewBlockWithHeader(bc.CurrentBlock())
}
return bc.blocks[number]
} }
func (bc *testBlockChain) StateAt(common.Hash) (*state.StateDB, error) { func (bc *testBlockChain) StateAt(common.Hash) (*state.StateDB, error) {
@ -183,14 +176,14 @@ func makeAddressReserver() txpool.AddressReserver {
// the blob pool. // the blob pool.
func makeTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCap uint64, key *ecdsa.PrivateKey) *types.Transaction { func makeTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCap uint64, key *ecdsa.PrivateKey) *types.Transaction {
blobtx := makeUnsignedTx(nonce, gasTipCap, gasFeeCap, blobFeeCap) blobtx := makeUnsignedTx(nonce, gasTipCap, gasFeeCap, blobFeeCap)
return types.MustSignNewTx(key, types.LatestSigner(testChainConfig), blobtx) return types.MustSignNewTx(key, types.LatestSigner(params.MainnetChainConfig), blobtx)
} }
// makeUnsignedTx is a utility method to construct a random blob transaction // makeUnsignedTx is a utility method to construct a random blob transaction
// without signing it. // without signing it.
func makeUnsignedTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCap uint64) *types.BlobTx { func makeUnsignedTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCap uint64) *types.BlobTx {
return &types.BlobTx{ return &types.BlobTx{
ChainID: uint256.MustFromBig(testChainConfig.ChainID), ChainID: uint256.MustFromBig(params.MainnetChainConfig.ChainID),
Nonce: nonce, Nonce: nonce,
GasTipCap: uint256.NewInt(gasTipCap), GasTipCap: uint256.NewInt(gasTipCap),
GasFeeCap: uint256.NewInt(gasFeeCap), GasFeeCap: uint256.NewInt(gasFeeCap),
@ -231,13 +224,17 @@ func verifyPoolInternals(t *testing.T, pool *BlobPool) {
for hash := range seen { for hash := range seen {
t.Errorf("indexed transaction hash #%x missing from lookup table", hash) t.Errorf("indexed transaction hash #%x missing from lookup table", hash)
} }
// Verify that transactions are sorted per account and contain no nonce gaps // Verify that transactions are sorted per account and contain no nonce gaps,
// and that the first nonce is the next expected one based on the state.
for addr, txs := range pool.index { for addr, txs := range pool.index {
for i := 1; i < len(txs); i++ { for i := 1; i < len(txs); i++ {
if txs[i].nonce != txs[i-1].nonce+1 { if txs[i].nonce != txs[i-1].nonce+1 {
t.Errorf("addr %v, tx %d nonce mismatch: have %d, want %d", addr, i, txs[i].nonce, txs[i-1].nonce+1) t.Errorf("addr %v, tx %d nonce mismatch: have %d, want %d", addr, i, txs[i].nonce, txs[i-1].nonce+1)
} }
} }
if txs[0].nonce != pool.state.GetNonce(addr) {
t.Errorf("addr %v, first tx nonce mismatch: have %d, want %d", addr, txs[0].nonce, pool.state.GetNonce(addr))
}
} }
// Verify that calculated evacuation thresholds are correct // Verify that calculated evacuation thresholds are correct
for addr, txs := range pool.index { for addr, txs := range pool.index {
@ -333,7 +330,7 @@ func TestOpenDrops(t *testing.T) {
// Insert a transaction with a bad signature to verify that stale junk after // Insert a transaction with a bad signature to verify that stale junk after
// potential hard-forks can get evicted (case 2) // potential hard-forks can get evicted (case 2)
tx := types.NewTx(&types.BlobTx{ tx := types.NewTx(&types.BlobTx{
ChainID: uint256.MustFromBig(testChainConfig.ChainID), ChainID: uint256.MustFromBig(params.MainnetChainConfig.ChainID),
GasTipCap: new(uint256.Int), GasTipCap: new(uint256.Int),
GasFeeCap: new(uint256.Int), GasFeeCap: new(uint256.Int),
Gas: 0, Gas: 0,
@ -545,7 +542,7 @@ func TestOpenDrops(t *testing.T) {
store.Close() store.Close()
// Create a blob pool out of the pre-seeded data // Create a blob pool out of the pre-seeded data
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil) statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
statedb.AddBalance(crypto.PubkeyToAddress(gapper.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified) statedb.AddBalance(crypto.PubkeyToAddress(gapper.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified)
statedb.AddBalance(crypto.PubkeyToAddress(dangler.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified) statedb.AddBalance(crypto.PubkeyToAddress(dangler.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified)
statedb.AddBalance(crypto.PubkeyToAddress(filler.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified) statedb.AddBalance(crypto.PubkeyToAddress(filler.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified)
@ -562,7 +559,7 @@ func TestOpenDrops(t *testing.T) {
statedb.Commit(0, true) statedb.Commit(0, true)
chain := &testBlockChain{ chain := &testBlockChain{
config: testChainConfig, config: params.MainnetChainConfig,
basefee: uint256.NewInt(params.InitialBaseFee), basefee: uint256.NewInt(params.InitialBaseFee),
blobfee: uint256.NewInt(params.BlobTxMinBlobGasprice), blobfee: uint256.NewInt(params.BlobTxMinBlobGasprice),
statedb: statedb, statedb: statedb,
@ -676,12 +673,12 @@ func TestOpenIndex(t *testing.T) {
store.Close() store.Close()
// Create a blob pool out of the pre-seeded data // Create a blob pool out of the pre-seeded data
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil) statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
statedb.AddBalance(addr, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) statedb.AddBalance(addr, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified)
statedb.Commit(0, true) statedb.Commit(0, true)
chain := &testBlockChain{ chain := &testBlockChain{
config: testChainConfig, config: params.MainnetChainConfig,
basefee: uint256.NewInt(params.InitialBaseFee), basefee: uint256.NewInt(params.InitialBaseFee),
blobfee: uint256.NewInt(params.BlobTxMinBlobGasprice), blobfee: uint256.NewInt(params.BlobTxMinBlobGasprice),
statedb: statedb, statedb: statedb,
@ -776,14 +773,14 @@ func TestOpenHeap(t *testing.T) {
store.Close() store.Close()
// Create a blob pool out of the pre-seeded data // Create a blob pool out of the pre-seeded data
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil) statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
statedb.AddBalance(addr1, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) statedb.AddBalance(addr1, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified)
statedb.AddBalance(addr2, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) statedb.AddBalance(addr2, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified)
statedb.AddBalance(addr3, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) statedb.AddBalance(addr3, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified)
statedb.Commit(0, true) statedb.Commit(0, true)
chain := &testBlockChain{ chain := &testBlockChain{
config: testChainConfig, config: params.MainnetChainConfig,
basefee: uint256.NewInt(1050), basefee: uint256.NewInt(1050),
blobfee: uint256.NewInt(105), blobfee: uint256.NewInt(105),
statedb: statedb, statedb: statedb,
@ -856,14 +853,14 @@ func TestOpenCap(t *testing.T) {
// with a high cap to ensure everything was persisted previously // with a high cap to ensure everything was persisted previously
for _, datacap := range []uint64{2 * (txAvgSize + blobSize), 100 * (txAvgSize + blobSize)} { for _, datacap := range []uint64{2 * (txAvgSize + blobSize), 100 * (txAvgSize + blobSize)} {
// Create a blob pool out of the pre-seeded data, but cap it to 2 blob transaction // Create a blob pool out of the pre-seeded data, but cap it to 2 blob transaction
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil) statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
statedb.AddBalance(addr1, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) statedb.AddBalance(addr1, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified)
statedb.AddBalance(addr2, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) statedb.AddBalance(addr2, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified)
statedb.AddBalance(addr3, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) statedb.AddBalance(addr3, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified)
statedb.Commit(0, true) statedb.Commit(0, true)
chain := &testBlockChain{ chain := &testBlockChain{
config: testChainConfig, config: params.MainnetChainConfig,
basefee: uint256.NewInt(1050), basefee: uint256.NewInt(1050),
blobfee: uint256.NewInt(105), blobfee: uint256.NewInt(105),
statedb: statedb, statedb: statedb,
@ -910,13 +907,12 @@ func TestOpenCap(t *testing.T) {
func TestAdd(t *testing.T) { func TestAdd(t *testing.T) {
log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true))) log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true)))
// seed is a helper tumpe to seed an initial state db and pool // seed is a helper tuple to seed an initial state db and pool
type seed struct { type seed struct {
balance uint64 balance uint64
nonce uint64 nonce uint64
txs []*types.BlobTx txs []*types.BlobTx
} }
// addtx is a helper sender/tx tuple to represent a new tx addition // addtx is a helper sender/tx tuple to represent a new tx addition
type addtx struct { type addtx struct {
from string from string
@ -927,6 +923,7 @@ func TestAdd(t *testing.T) {
tests := []struct { tests := []struct {
seeds map[string]seed seeds map[string]seed
adds []addtx adds []addtx
block []addtx
}{ }{
// Transactions from new accounts should be accepted if their initial // Transactions from new accounts should be accepted if their initial
// nonce matches the expected one from the statedb. Higher or lower must // nonce matches the expected one from the statedb. Higher or lower must
@ -1252,6 +1249,25 @@ func TestAdd(t *testing.T) {
}, },
}, },
}, },
// Tests issue #30518 where a refactor broke internal state invariants,
// causing included transactions not to be properly accounted and thus
// account states going our of sync with the chain.
{
seeds: map[string]seed{
"alice": {
balance: 1000000,
txs: []*types.BlobTx{
makeUnsignedTx(0, 1, 1, 1),
},
},
},
block: []addtx{
{
from: "alice",
tx: makeUnsignedTx(0, 1, 1, 1),
},
},
},
} }
for i, tt := range tests { for i, tt := range tests {
// Create a temporary folder for the persistent backend // Create a temporary folder for the persistent backend
@ -1266,7 +1282,7 @@ func TestAdd(t *testing.T) {
keys = make(map[string]*ecdsa.PrivateKey) keys = make(map[string]*ecdsa.PrivateKey)
addrs = make(map[string]common.Address) addrs = make(map[string]common.Address)
) )
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil) statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
for acc, seed := range tt.seeds { for acc, seed := range tt.seeds {
// Generate a new random key/address for the seed account // Generate a new random key/address for the seed account
keys[acc], _ = crypto.GenerateKey() keys[acc], _ = crypto.GenerateKey()
@ -1278,7 +1294,7 @@ func TestAdd(t *testing.T) {
// Sign the seed transactions and store them in the data store // Sign the seed transactions and store them in the data store
for _, tx := range seed.txs { for _, tx := range seed.txs {
signed := types.MustSignNewTx(keys[acc], types.LatestSigner(testChainConfig), tx) signed := types.MustSignNewTx(keys[acc], types.LatestSigner(params.MainnetChainConfig), tx)
blob, _ := rlp.EncodeToBytes(signed) blob, _ := rlp.EncodeToBytes(signed)
store.Put(blob) store.Put(blob)
} }
@ -1288,7 +1304,7 @@ func TestAdd(t *testing.T) {
// Create a blob pool out of the pre-seeded dats // Create a blob pool out of the pre-seeded dats
chain := &testBlockChain{ chain := &testBlockChain{
config: testChainConfig, config: params.MainnetChainConfig,
basefee: uint256.NewInt(1050), basefee: uint256.NewInt(1050),
blobfee: uint256.NewInt(105), blobfee: uint256.NewInt(105),
statedb: statedb, statedb: statedb,
@ -1301,14 +1317,40 @@ func TestAdd(t *testing.T) {
// Add each transaction one by one, verifying the pool internals in between // Add each transaction one by one, verifying the pool internals in between
for j, add := range tt.adds { for j, add := range tt.adds {
signed, _ := types.SignNewTx(keys[add.from], types.LatestSigner(testChainConfig), add.tx) signed, _ := types.SignNewTx(keys[add.from], types.LatestSigner(params.MainnetChainConfig), add.tx)
if err := pool.add(signed); !errors.Is(err, add.err) { if err := pool.add(signed); !errors.Is(err, add.err) {
t.Errorf("test %d, tx %d: adding transaction error mismatch: have %v, want %v", i, j, err, add.err) t.Errorf("test %d, tx %d: adding transaction error mismatch: have %v, want %v", i, j, err, add.err)
} }
verifyPoolInternals(t, pool) verifyPoolInternals(t, pool)
} }
// Verify the pool internals and close down the test
verifyPoolInternals(t, pool) verifyPoolInternals(t, pool)
// If the test contains a chain head event, run that and gain verify the internals
if tt.block != nil {
// Fake a header for the new set of transactions
header := &types.Header{
Number: big.NewInt(int64(chain.CurrentBlock().Number.Uint64() + 1)),
BaseFee: chain.CurrentBlock().BaseFee, // invalid, but nothing checks it, yolo
}
// Inject the fake block into the chain
txs := make([]*types.Transaction, len(tt.block))
for j, inc := range tt.block {
txs[j] = types.MustSignNewTx(keys[inc.from], types.LatestSigner(params.MainnetChainConfig), inc.tx)
}
chain.blocks = map[uint64]*types.Block{
header.Number.Uint64(): types.NewBlockWithHeader(header).WithBody(types.Body{
Transactions: txs,
}),
}
// Apply the nonce updates to the state db
for _, tx := range txs {
sender, _ := types.Sender(types.LatestSigner(params.MainnetChainConfig), tx)
chain.statedb.SetNonce(sender, tx.Nonce()+1)
}
pool.Reset(chain.CurrentBlock(), header)
verifyPoolInternals(t, pool)
}
// Close down the test
pool.Close() pool.Close()
} }
} }
@ -1327,10 +1369,10 @@ func benchmarkPoolPending(b *testing.B, datacap uint64) {
var ( var (
basefee = uint64(1050) basefee = uint64(1050)
blobfee = uint64(105) blobfee = uint64(105)
signer = types.LatestSigner(testChainConfig) signer = types.LatestSigner(params.MainnetChainConfig)
statedb, _ = state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil) statedb, _ = state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
chain = &testBlockChain{ chain = &testBlockChain{
config: testChainConfig, config: params.MainnetChainConfig,
basefee: uint256.NewInt(basefee), basefee: uint256.NewInt(basefee),
blobfee: uint256.NewInt(blobfee), blobfee: uint256.NewInt(blobfee),
statedb: statedb, statedb: statedb,

@ -21,7 +21,6 @@ import (
"testing" "testing"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
@ -80,7 +79,7 @@ func TestTransactionFutureAttack(t *testing.T) {
t.Parallel() t.Parallel()
// Create the pool to test the limit enforcement with // Create the pool to test the limit enforcement with
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
blockchain := newTestBlockChain(eip1559Config, 1000000, statedb, new(event.Feed)) blockchain := newTestBlockChain(eip1559Config, 1000000, statedb, new(event.Feed))
config := testTxPoolConfig config := testTxPoolConfig
config.GlobalQueue = 100 config.GlobalQueue = 100
@ -117,7 +116,7 @@ func TestTransactionFutureAttack(t *testing.T) {
func TestTransactionFuture1559(t *testing.T) { func TestTransactionFuture1559(t *testing.T) {
t.Parallel() t.Parallel()
// Create the pool to test the pricing enforcement with // Create the pool to test the pricing enforcement with
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
blockchain := newTestBlockChain(eip1559Config, 1000000, statedb, new(event.Feed)) blockchain := newTestBlockChain(eip1559Config, 1000000, statedb, new(event.Feed))
pool := New(testTxPoolConfig, blockchain) pool := New(testTxPoolConfig, blockchain)
pool.Init(testTxPoolConfig.PriceLimit, blockchain.CurrentBlock(), makeAddressReserver()) pool.Init(testTxPoolConfig.PriceLimit, blockchain.CurrentBlock(), makeAddressReserver())
@ -150,7 +149,7 @@ func TestTransactionFuture1559(t *testing.T) {
func TestTransactionZAttack(t *testing.T) { func TestTransactionZAttack(t *testing.T) {
t.Parallel() t.Parallel()
// Create the pool to test the pricing enforcement with // Create the pool to test the pricing enforcement with
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
blockchain := newTestBlockChain(eip1559Config, 1000000, statedb, new(event.Feed)) blockchain := newTestBlockChain(eip1559Config, 1000000, statedb, new(event.Feed))
pool := New(testTxPoolConfig, blockchain) pool := New(testTxPoolConfig, blockchain)
pool.Init(testTxPoolConfig.PriceLimit, blockchain.CurrentBlock(), makeAddressReserver()) pool.Init(testTxPoolConfig.PriceLimit, blockchain.CurrentBlock(), makeAddressReserver())
@ -218,7 +217,7 @@ func TestTransactionZAttack(t *testing.T) {
func BenchmarkFutureAttack(b *testing.B) { func BenchmarkFutureAttack(b *testing.B) {
// Create the pool to test the limit enforcement with // Create the pool to test the limit enforcement with
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
blockchain := newTestBlockChain(eip1559Config, 1000000, statedb, new(event.Feed)) blockchain := newTestBlockChain(eip1559Config, 1000000, statedb, new(event.Feed))
config := testTxPoolConfig config := testTxPoolConfig
config.GlobalQueue = 100 config.GlobalQueue = 100

@ -31,7 +31,6 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/txpool"
@ -160,7 +159,7 @@ func setupPool() (*LegacyPool, *ecdsa.PrivateKey) {
} }
func setupPoolWithConfig(config *params.ChainConfig) (*LegacyPool, *ecdsa.PrivateKey) { func setupPoolWithConfig(config *params.ChainConfig) (*LegacyPool, *ecdsa.PrivateKey) {
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
blockchain := newTestBlockChain(config, 10000000, statedb, new(event.Feed)) blockchain := newTestBlockChain(config, 10000000, statedb, new(event.Feed))
key, _ := crypto.GenerateKey() key, _ := crypto.GenerateKey()
@ -251,7 +250,7 @@ func (c *testChain) State() (*state.StateDB, error) {
// a state change between those fetches. // a state change between those fetches.
stdb := c.statedb stdb := c.statedb
if *c.trigger { if *c.trigger {
c.statedb, _ = state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) c.statedb, _ = state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
// simulate that the new head block included tx0 and tx1 // simulate that the new head block included tx0 and tx1
c.statedb.SetNonce(c.address, 2) c.statedb.SetNonce(c.address, 2)
c.statedb.SetBalance(c.address, new(uint256.Int).SetUint64(params.Ether), tracing.BalanceChangeUnspecified) c.statedb.SetBalance(c.address, new(uint256.Int).SetUint64(params.Ether), tracing.BalanceChangeUnspecified)
@ -269,7 +268,7 @@ func TestStateChangeDuringReset(t *testing.T) {
var ( var (
key, _ = crypto.GenerateKey() key, _ = crypto.GenerateKey()
address = crypto.PubkeyToAddress(key.PublicKey) address = crypto.PubkeyToAddress(key.PublicKey)
statedb, _ = state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) statedb, _ = state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
trigger = false trigger = false
) )
@ -468,7 +467,7 @@ func TestChainFork(t *testing.T) {
addr := crypto.PubkeyToAddress(key.PublicKey) addr := crypto.PubkeyToAddress(key.PublicKey)
resetState := func() { resetState := func() {
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
statedb.AddBalance(addr, uint256.NewInt(100000000000000), tracing.BalanceChangeUnspecified) statedb.AddBalance(addr, uint256.NewInt(100000000000000), tracing.BalanceChangeUnspecified)
pool.chain = newTestBlockChain(pool.chainconfig, 1000000, statedb, new(event.Feed)) pool.chain = newTestBlockChain(pool.chainconfig, 1000000, statedb, new(event.Feed))
@ -497,7 +496,7 @@ func TestDoubleNonce(t *testing.T) {
addr := crypto.PubkeyToAddress(key.PublicKey) addr := crypto.PubkeyToAddress(key.PublicKey)
resetState := func() { resetState := func() {
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
statedb.AddBalance(addr, uint256.NewInt(100000000000000), tracing.BalanceChangeUnspecified) statedb.AddBalance(addr, uint256.NewInt(100000000000000), tracing.BalanceChangeUnspecified)
pool.chain = newTestBlockChain(pool.chainconfig, 1000000, statedb, new(event.Feed)) pool.chain = newTestBlockChain(pool.chainconfig, 1000000, statedb, new(event.Feed))
@ -697,7 +696,7 @@ func TestPostponing(t *testing.T) {
t.Parallel() t.Parallel()
// Create the pool to test the postponing with // Create the pool to test the postponing with
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed)) blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed))
pool := New(testTxPoolConfig, blockchain) pool := New(testTxPoolConfig, blockchain)
@ -910,7 +909,7 @@ func testQueueGlobalLimiting(t *testing.T, nolocals bool) {
t.Parallel() t.Parallel()
// Create the pool to test the limit enforcement with // Create the pool to test the limit enforcement with
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed)) blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed))
config := testTxPoolConfig config := testTxPoolConfig
@ -1003,7 +1002,7 @@ func testQueueTimeLimiting(t *testing.T, nolocals bool) {
evictionInterval = time.Millisecond * 100 evictionInterval = time.Millisecond * 100
// Create the pool to test the non-expiration enforcement // Create the pool to test the non-expiration enforcement
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed)) blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed))
config := testTxPoolConfig config := testTxPoolConfig
@ -1189,7 +1188,7 @@ func TestPendingGlobalLimiting(t *testing.T) {
t.Parallel() t.Parallel()
// Create the pool to test the limit enforcement with // Create the pool to test the limit enforcement with
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed)) blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed))
config := testTxPoolConfig config := testTxPoolConfig
@ -1291,7 +1290,7 @@ func TestCapClearsFromAll(t *testing.T) {
t.Parallel() t.Parallel()
// Create the pool to test the limit enforcement with // Create the pool to test the limit enforcement with
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed)) blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed))
config := testTxPoolConfig config := testTxPoolConfig
@ -1326,7 +1325,7 @@ func TestPendingMinimumAllowance(t *testing.T) {
t.Parallel() t.Parallel()
// Create the pool to test the limit enforcement with // Create the pool to test the limit enforcement with
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed)) blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed))
config := testTxPoolConfig config := testTxPoolConfig
@ -1375,7 +1374,7 @@ func TestRepricing(t *testing.T) {
t.Parallel() t.Parallel()
// Create the pool to test the pricing enforcement with // Create the pool to test the pricing enforcement with
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed)) blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed))
pool := New(testTxPoolConfig, blockchain) pool := New(testTxPoolConfig, blockchain)
@ -1495,7 +1494,7 @@ func TestMinGasPriceEnforced(t *testing.T) {
t.Parallel() t.Parallel()
// Create the pool to test the pricing enforcement with // Create the pool to test the pricing enforcement with
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
blockchain := newTestBlockChain(eip1559Config, 10000000, statedb, new(event.Feed)) blockchain := newTestBlockChain(eip1559Config, 10000000, statedb, new(event.Feed))
txPoolConfig := DefaultConfig txPoolConfig := DefaultConfig
@ -1668,7 +1667,7 @@ func TestRepricingKeepsLocals(t *testing.T) {
t.Parallel() t.Parallel()
// Create the pool to test the pricing enforcement with // Create the pool to test the pricing enforcement with
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
blockchain := newTestBlockChain(eip1559Config, 1000000, statedb, new(event.Feed)) blockchain := newTestBlockChain(eip1559Config, 1000000, statedb, new(event.Feed))
pool := New(testTxPoolConfig, blockchain) pool := New(testTxPoolConfig, blockchain)
@ -1742,7 +1741,7 @@ func TestUnderpricing(t *testing.T) {
t.Parallel() t.Parallel()
// Create the pool to test the pricing enforcement with // Create the pool to test the pricing enforcement with
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed)) blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed))
config := testTxPoolConfig config := testTxPoolConfig
@ -1857,7 +1856,7 @@ func TestStableUnderpricing(t *testing.T) {
t.Parallel() t.Parallel()
// Create the pool to test the pricing enforcement with // Create the pool to test the pricing enforcement with
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed)) blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed))
config := testTxPoolConfig config := testTxPoolConfig
@ -2090,7 +2089,7 @@ func TestDeduplication(t *testing.T) {
t.Parallel() t.Parallel()
// Create the pool to test the pricing enforcement with // Create the pool to test the pricing enforcement with
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed)) blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed))
pool := New(testTxPoolConfig, blockchain) pool := New(testTxPoolConfig, blockchain)
@ -2157,7 +2156,7 @@ func TestReplacement(t *testing.T) {
t.Parallel() t.Parallel()
// Create the pool to test the pricing enforcement with // Create the pool to test the pricing enforcement with
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed)) blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed))
pool := New(testTxPoolConfig, blockchain) pool := New(testTxPoolConfig, blockchain)
@ -2363,7 +2362,7 @@ func testJournaling(t *testing.T, nolocals bool) {
os.Remove(journal) os.Remove(journal)
// Create the original pool to inject transaction into the journal // Create the original pool to inject transaction into the journal
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed)) blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed))
config := testTxPoolConfig config := testTxPoolConfig
@ -2464,7 +2463,7 @@ func TestStatusCheck(t *testing.T) {
t.Parallel() t.Parallel()
// Create the pool to test the status retrievals with // Create the pool to test the status retrievals with
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed)) blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed))
pool := New(testTxPoolConfig, blockchain) pool := New(testTxPoolConfig, blockchain)

@ -19,9 +19,7 @@ package core
import ( import (
"sync/atomic" "sync/atomic"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/stateless"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
) )
@ -33,12 +31,8 @@ type Validator interface {
// ValidateBody validates the given block's content. // ValidateBody validates the given block's content.
ValidateBody(block *types.Block) error ValidateBody(block *types.Block) error
// ValidateState validates the given statedb and optionally the receipts and // ValidateState validates the given statedb and optionally the process result.
// gas used. ValidateState(block *types.Block, state *state.StateDB, res *ProcessResult, stateless bool) error
ValidateState(block *types.Block, state *state.StateDB, receipts types.Receipts, usedGas uint64, stateless bool) error
// ValidateWitness cross validates a block execution with stateless remote clients.
ValidateWitness(witness *stateless.Witness, receiptRoot common.Hash, stateRoot common.Hash) error
} }
// Prefetcher is an interface for pre-caching transaction signatures and state. // Prefetcher is an interface for pre-caching transaction signatures and state.
@ -54,5 +48,13 @@ type Processor interface {
// Process processes the state changes according to the Ethereum rules by running // Process processes the state changes according to the Ethereum rules by running
// the transaction messages using the statedb and applying any rewards to both // the transaction messages using the statedb and applying any rewards to both
// the processor (coinbase) and any included uncles. // the processor (coinbase) and any included uncles.
Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (*ProcessResult, error)
}
// ProcessResult contains the values computed by Process.
type ProcessResult struct {
Receipts types.Receipts
Requests types.Requests
Logs []*types.Log
GasUsed uint64
} }

@ -30,6 +30,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-verkle"
) )
// A BlockNonce is a 64-bit hash which proves (combined with the // A BlockNonce is a 64-bit hash which proves (combined with the
@ -59,6 +60,13 @@ func (n *BlockNonce) UnmarshalText(input []byte) error {
return hexutil.UnmarshalFixedText("BlockNonce", input, n[:]) return hexutil.UnmarshalFixedText("BlockNonce", input, n[:])
} }
// ExecutionWitness represents the witness + proof used in a verkle context,
// to provide the ability to execute a block statelessly.
type ExecutionWitness struct {
StateDiff verkle.StateDiff `json:"stateDiff"`
VerkleProof *verkle.VerkleProof `json:"verkleProof"`
}
//go:generate go run github.com/fjl/gencodec -type Header -field-override headerMarshaling -out gen_header_json.go //go:generate go run github.com/fjl/gencodec -type Header -field-override headerMarshaling -out gen_header_json.go
//go:generate go run ../../rlp/rlpgen -type Header -out gen_header_rlp.go //go:generate go run ../../rlp/rlpgen -type Header -out gen_header_rlp.go
@ -94,6 +102,9 @@ type Header struct {
// ParentBeaconRoot was added by EIP-4788 and is ignored in legacy headers. // ParentBeaconRoot was added by EIP-4788 and is ignored in legacy headers.
ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
// RequestsHash was added by EIP-7685 and is ignored in legacy headers.
RequestsHash *common.Hash `json:"requestsRoot" rlp:"optional"`
} }
// field type overrides for gencodec // field type overrides for gencodec
@ -155,10 +166,11 @@ func (h *Header) SanityCheck() error {
// EmptyBody returns true if there is no additional 'body' to complete the header // EmptyBody returns true if there is no additional 'body' to complete the header
// that is: no transactions, no uncles and no withdrawals. // that is: no transactions, no uncles and no withdrawals.
func (h *Header) EmptyBody() bool { func (h *Header) EmptyBody() bool {
if h.WithdrawalsHash != nil { var (
return h.TxHash == EmptyTxsHash && *h.WithdrawalsHash == EmptyWithdrawalsHash emptyWithdrawals = h.WithdrawalsHash == nil || *h.WithdrawalsHash == EmptyWithdrawalsHash
} emptyRequests = h.RequestsHash == nil || *h.RequestsHash == EmptyReceiptsHash
return h.TxHash == EmptyTxsHash && h.UncleHash == EmptyUncleHash )
return h.TxHash == EmptyTxsHash && h.UncleHash == EmptyUncleHash && emptyWithdrawals && emptyRequests
} }
// EmptyReceipts returns true if there are no receipts for this header/block. // EmptyReceipts returns true if there are no receipts for this header/block.
@ -172,6 +184,7 @@ type Body struct {
Transactions []*Transaction Transactions []*Transaction
Uncles []*Header Uncles []*Header
Withdrawals []*Withdrawal `rlp:"optional"` Withdrawals []*Withdrawal `rlp:"optional"`
Requests []*Request `rlp:"optional"`
} }
// Block represents an Ethereum block. // Block represents an Ethereum block.
@ -196,6 +209,12 @@ type Block struct {
uncles []*Header uncles []*Header
transactions Transactions transactions Transactions
withdrawals Withdrawals withdrawals Withdrawals
requests Requests
// witness is not an encoded part of the block body.
// It is held in Block in order for easy relaying to the places
// that process it.
witness *ExecutionWitness
// caches // caches
hash atomic.Pointer[common.Hash] hash atomic.Pointer[common.Hash]
@ -213,6 +232,7 @@ type extblock struct {
Txs []*Transaction Txs []*Transaction
Uncles []*Header Uncles []*Header
Withdrawals []*Withdrawal `rlp:"optional"` Withdrawals []*Withdrawal `rlp:"optional"`
Requests []*Request `rlp:"optional"`
} }
// NewBlock creates a new block. The input data is copied, changes to header and to the // NewBlock creates a new block. The input data is copied, changes to header and to the
@ -229,6 +249,7 @@ func NewBlock(header *Header, body *Body, receipts []*Receipt, hasher TrieHasher
txs = body.Transactions txs = body.Transactions
uncles = body.Uncles uncles = body.Uncles
withdrawals = body.Withdrawals withdrawals = body.Withdrawals
requests = body.Requests
) )
if len(txs) == 0 { if len(txs) == 0 {
@ -267,6 +288,17 @@ func NewBlock(header *Header, body *Body, receipts []*Receipt, hasher TrieHasher
b.withdrawals = slices.Clone(withdrawals) b.withdrawals = slices.Clone(withdrawals)
} }
if requests == nil {
b.header.RequestsHash = nil
} else if len(requests) == 0 {
b.header.RequestsHash = &EmptyRequestsHash
b.requests = Requests{}
} else {
h := DeriveSha(Requests(requests), hasher)
b.header.RequestsHash = &h
b.requests = slices.Clone(requests)
}
return b return b
} }
@ -302,6 +334,10 @@ func CopyHeader(h *Header) *Header {
cpy.ParentBeaconRoot = new(common.Hash) cpy.ParentBeaconRoot = new(common.Hash)
*cpy.ParentBeaconRoot = *h.ParentBeaconRoot *cpy.ParentBeaconRoot = *h.ParentBeaconRoot
} }
if h.RequestsHash != nil {
cpy.RequestsHash = new(common.Hash)
*cpy.RequestsHash = *h.RequestsHash
}
return &cpy return &cpy
} }
@ -312,7 +348,7 @@ func (b *Block) DecodeRLP(s *rlp.Stream) error {
if err := s.Decode(&eb); err != nil { if err := s.Decode(&eb); err != nil {
return err return err
} }
b.header, b.uncles, b.transactions, b.withdrawals = eb.Header, eb.Uncles, eb.Txs, eb.Withdrawals b.header, b.uncles, b.transactions, b.withdrawals, b.requests = eb.Header, eb.Uncles, eb.Txs, eb.Withdrawals, eb.Requests
b.size.Store(rlp.ListSize(size)) b.size.Store(rlp.ListSize(size))
return nil return nil
} }
@ -324,13 +360,14 @@ func (b *Block) EncodeRLP(w io.Writer) error {
Txs: b.transactions, Txs: b.transactions,
Uncles: b.uncles, Uncles: b.uncles,
Withdrawals: b.withdrawals, Withdrawals: b.withdrawals,
Requests: b.requests,
}) })
} }
// Body returns the non-header content of the block. // Body returns the non-header content of the block.
// Note the returned data is not an independent copy. // Note the returned data is not an independent copy.
func (b *Block) Body() *Body { func (b *Block) Body() *Body {
return &Body{b.transactions, b.uncles, b.withdrawals} return &Body{b.transactions, b.uncles, b.withdrawals, b.requests}
} }
// Accessors for body data. These do not return a copy because the content // Accessors for body data. These do not return a copy because the content
@ -339,6 +376,7 @@ func (b *Block) Body() *Body {
func (b *Block) Uncles() []*Header { return b.uncles } func (b *Block) Uncles() []*Header { return b.uncles }
func (b *Block) Transactions() Transactions { return b.transactions } func (b *Block) Transactions() Transactions { return b.transactions }
func (b *Block) Withdrawals() Withdrawals { return b.withdrawals } func (b *Block) Withdrawals() Withdrawals { return b.withdrawals }
func (b *Block) Requests() Requests { return b.requests }
func (b *Block) Transaction(hash common.Hash) *Transaction { func (b *Block) Transaction(hash common.Hash) *Transaction {
for _, transaction := range b.transactions { for _, transaction := range b.transactions {
@ -401,6 +439,9 @@ func (b *Block) BlobGasUsed() *uint64 {
return blobGasUsed return blobGasUsed
} }
// ExecutionWitness returns the verkle execution witneess + proof for a block
func (b *Block) ExecutionWitness() *ExecutionWitness { return b.witness }
// Size returns the true RLP encoded storage size of the block, either by encoding // Size returns the true RLP encoded storage size of the block, either by encoding
// and returning it, or returning a previously cached value. // and returning it, or returning a previously cached value.
func (b *Block) Size() uint64 { func (b *Block) Size() uint64 {
@ -448,6 +489,7 @@ func (b *Block) WithSeal(header *Header) *Block {
transactions: b.transactions, transactions: b.transactions,
uncles: b.uncles, uncles: b.uncles,
withdrawals: b.withdrawals, withdrawals: b.withdrawals,
witness: b.witness,
} }
} }
@ -459,6 +501,8 @@ func (b *Block) WithBody(body Body) *Block {
transactions: slices.Clone(body.Transactions), transactions: slices.Clone(body.Transactions),
uncles: make([]*Header, len(body.Uncles)), uncles: make([]*Header, len(body.Uncles)),
withdrawals: slices.Clone(body.Withdrawals), withdrawals: slices.Clone(body.Withdrawals),
requests: slices.Clone(body.Requests),
witness: b.witness,
} }
for i := range body.Uncles { for i := range body.Uncles {
block.uncles[i] = CopyHeader(body.Uncles[i]) block.uncles[i] = CopyHeader(body.Uncles[i])
@ -466,6 +510,17 @@ func (b *Block) WithBody(body Body) *Block {
return block return block
} }
func (b *Block) WithWitness(witness *ExecutionWitness) *Block {
return &Block{
header: b.header,
transactions: b.transactions,
uncles: b.uncles,
withdrawals: b.withdrawals,
requests: b.requests,
witness: witness,
}
}
// Hash returns the keccak256 hash of b's header. // Hash returns the keccak256 hash of b's header.
// The hash is computed on the first call and cached thereafter. // The hash is computed on the first call and cached thereafter.
func (b *Block) Hash() common.Hash { func (b *Block) Hash() common.Hash {

@ -0,0 +1,103 @@
// 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"
"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])
}
// UnpackIntoDeposit unpacks a serialized DepositEvent.
func UnpackIntoDeposit(data []byte) (*Deposit, error) {
if len(data) != 576 {
return nil, fmt.Errorf("deposit wrong length: want 576, have %d", len(data))
}
var d Deposit
// 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.
b := 32*5 + 32
// 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])
b += 48 + 16 + 32
// WithdrawalCredentials is 32 bytes. Read that value then skip over next
// length.
copy(d.WithdrawalCredentials[:], 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])
b += 8 + 24 + 32
// Signature is 96 bytes. Skip over it and the next length.
copy(d.Signature[:], 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,
}
}

@ -0,0 +1,93 @@
// 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 (
"encoding/binary"
"reflect"
"testing"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
)
var (
depositABI = abi.ABI{Methods: map[string]abi.Method{"DepositEvent": depositEvent}}
bytesT, _ = abi.NewType("bytes", "", nil)
depositEvent = abi.NewMethod("DepositEvent", "DepositEvent", abi.Function, "", false, false, []abi.Argument{
{Name: "pubkey", Type: bytesT, Indexed: false},
{Name: "withdrawal_credentials", Type: bytesT, Indexed: false},
{Name: "amount", Type: bytesT, Indexed: false},
{Name: "signature", Type: bytesT, Indexed: false},
{Name: "index", Type: bytesT, Indexed: false}}, nil,
)
)
// FuzzUnpackIntoDeposit tries roundtrip packing and unpacking of deposit events.
func FuzzUnpackIntoDeposit(f *testing.F) {
for _, tt := range []struct {
pubkey string
wxCred string
amount string
sig string
index string
}{
{
pubkey: "111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111",
wxCred: "2222222222222222222222222222222222222222222222222222222222222222",
amount: "3333333333333333",
sig: "444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444",
index: "5555555555555555",
},
} {
f.Add(common.FromHex(tt.pubkey), common.FromHex(tt.wxCred), common.FromHex(tt.amount), common.FromHex(tt.sig), common.FromHex(tt.index))
}
f.Fuzz(func(t *testing.T, p []byte, w []byte, a []byte, s []byte, i []byte) {
var (
pubkey [48]byte
wxCred [32]byte
amount [8]byte
sig [96]byte
index [8]byte
)
copy(pubkey[:], p)
copy(wxCred[:], w)
copy(amount[:], a)
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[:])
if err != nil {
t.Fatalf("error packing deposit: %v", err)
}
got, err := UnpackIntoDeposit(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)
}
})
}

@ -0,0 +1,70 @@
// 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
}

@ -36,6 +36,7 @@ func (h Header) MarshalJSON() ([]byte, error) {
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed" rlp:"optional"` BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed" rlp:"optional"`
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"` ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"`
ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
RequestsHash *common.Hash `json:"requestsRoot" rlp:"optional"`
Hash common.Hash `json:"hash"` Hash common.Hash `json:"hash"`
} }
var enc Header var enc Header
@ -59,6 +60,7 @@ func (h Header) MarshalJSON() ([]byte, error) {
enc.BlobGasUsed = (*hexutil.Uint64)(h.BlobGasUsed) enc.BlobGasUsed = (*hexutil.Uint64)(h.BlobGasUsed)
enc.ExcessBlobGas = (*hexutil.Uint64)(h.ExcessBlobGas) enc.ExcessBlobGas = (*hexutil.Uint64)(h.ExcessBlobGas)
enc.ParentBeaconRoot = h.ParentBeaconRoot enc.ParentBeaconRoot = h.ParentBeaconRoot
enc.RequestsHash = h.RequestsHash
enc.Hash = h.Hash() enc.Hash = h.Hash()
return json.Marshal(&enc) return json.Marshal(&enc)
} }
@ -86,6 +88,7 @@ func (h *Header) UnmarshalJSON(input []byte) error {
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed" rlp:"optional"` BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed" rlp:"optional"`
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"` ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"`
ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
RequestsHash *common.Hash `json:"requestsRoot" rlp:"optional"`
} }
var dec Header var dec Header
if err := json.Unmarshal(input, &dec); err != nil { if err := json.Unmarshal(input, &dec); err != nil {
@ -163,5 +166,8 @@ func (h *Header) UnmarshalJSON(input []byte) error {
if dec.ParentBeaconRoot != nil { if dec.ParentBeaconRoot != nil {
h.ParentBeaconRoot = dec.ParentBeaconRoot h.ParentBeaconRoot = dec.ParentBeaconRoot
} }
if dec.RequestsHash != nil {
h.RequestsHash = dec.RequestsHash
}
return nil return nil
} }

@ -42,7 +42,8 @@ func (obj *Header) EncodeRLP(_w io.Writer) error {
_tmp3 := obj.BlobGasUsed != nil _tmp3 := obj.BlobGasUsed != nil
_tmp4 := obj.ExcessBlobGas != nil _tmp4 := obj.ExcessBlobGas != nil
_tmp5 := obj.ParentBeaconRoot != nil _tmp5 := obj.ParentBeaconRoot != nil
if _tmp1 || _tmp2 || _tmp3 || _tmp4 || _tmp5 { _tmp6 := obj.RequestsHash != nil
if _tmp1 || _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 {
if obj.BaseFee == nil { if obj.BaseFee == nil {
w.Write(rlp.EmptyString) w.Write(rlp.EmptyString)
} else { } else {
@ -52,34 +53,41 @@ func (obj *Header) EncodeRLP(_w io.Writer) error {
w.WriteBigInt(obj.BaseFee) w.WriteBigInt(obj.BaseFee)
} }
} }
if _tmp2 || _tmp3 || _tmp4 || _tmp5 { if _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 {
if obj.WithdrawalsHash == nil { if obj.WithdrawalsHash == nil {
w.Write([]byte{0x80}) w.Write([]byte{0x80})
} else { } else {
w.WriteBytes(obj.WithdrawalsHash[:]) w.WriteBytes(obj.WithdrawalsHash[:])
} }
} }
if _tmp3 || _tmp4 || _tmp5 { if _tmp3 || _tmp4 || _tmp5 || _tmp6 {
if obj.BlobGasUsed == nil { if obj.BlobGasUsed == nil {
w.Write([]byte{0x80}) w.Write([]byte{0x80})
} else { } else {
w.WriteUint64((*obj.BlobGasUsed)) w.WriteUint64((*obj.BlobGasUsed))
} }
} }
if _tmp4 || _tmp5 { if _tmp4 || _tmp5 || _tmp6 {
if obj.ExcessBlobGas == nil { if obj.ExcessBlobGas == nil {
w.Write([]byte{0x80}) w.Write([]byte{0x80})
} else { } else {
w.WriteUint64((*obj.ExcessBlobGas)) w.WriteUint64((*obj.ExcessBlobGas))
} }
} }
if _tmp5 { if _tmp5 || _tmp6 {
if obj.ParentBeaconRoot == nil { if obj.ParentBeaconRoot == nil {
w.Write([]byte{0x80}) w.Write([]byte{0x80})
} else { } else {
w.WriteBytes(obj.ParentBeaconRoot[:]) w.WriteBytes(obj.ParentBeaconRoot[:])
} }
} }
if _tmp6 {
if obj.RequestsHash == nil {
w.Write([]byte{0x80})
} else {
w.WriteBytes(obj.RequestsHash[:])
}
}
w.ListEnd(_tmp0) w.ListEnd(_tmp0)
return w.Flush() return w.Flush()
} }

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

@ -0,0 +1,157 @@
// Copyright 2024 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package types
import (
"bytes"
"errors"
"fmt"
"io"
"github.com/ethereum/go-ethereum/rlp"
)
var (
ErrRequestTypeNotSupported = errors.New("request type not supported")
errShortTypedRequest = errors.New("typed request too short")
)
// Request types.
const (
DepositRequestType = 0x00
)
// Request is an EIP-7685 request object. It represents execution layer
// triggered messages bound for the consensus layer.
type Request struct {
inner RequestData
}
// Type returns the EIP-7685 type of the request.
func (r *Request) Type() byte {
return r.inner.requestType()
}
// Inner returns the inner request data.
func (r *Request) Inner() RequestData {
return r.inner
}
// NewRequest creates a new request.
func NewRequest(inner RequestData) *Request {
req := new(Request)
req.inner = inner.copy()
return req
}
// Requests implements DerivableList for requests.
type Requests []*Request
// Len returns the length of s.
func (s Requests) Len() int { return len(s) }
// EncodeIndex encodes the i'th request to s.
func (s Requests) EncodeIndex(i int, w *bytes.Buffer) {
s[i].encode(w)
}
// RequestData is the underlying data of a request.
type RequestData interface {
requestType() byte
encode(*bytes.Buffer) error
decode([]byte) error
copy() RequestData // creates a deep copy and initializes all fields
}
// EncodeRLP implements rlp.Encoder
func (r *Request) EncodeRLP(w io.Writer) error {
buf := encodeBufferPool.Get().(*bytes.Buffer)
defer encodeBufferPool.Put(buf)
buf.Reset()
if err := r.encode(buf); err != nil {
return err
}
return rlp.Encode(w, buf.Bytes())
}
// encode writes the canonical encoding of a request to w.
func (r *Request) encode(w *bytes.Buffer) error {
w.WriteByte(r.Type())
return r.inner.encode(w)
}
// MarshalBinary returns the canonical encoding of the request.
func (r *Request) MarshalBinary() ([]byte, error) {
var buf bytes.Buffer
err := r.encode(&buf)
return buf.Bytes(), err
}
// DecodeRLP implements rlp.Decoder
func (r *Request) DecodeRLP(s *rlp.Stream) error {
kind, size, err := s.Kind()
switch {
case err != nil:
return err
case kind == rlp.List:
return fmt.Errorf("untyped request")
case kind == rlp.Byte:
return errShortTypedRequest
default:
// First read the request payload bytes into a temporary buffer.
b, buf, err := getPooledBuffer(size)
if err != nil {
return err
}
defer encodeBufferPool.Put(buf)
if err := s.ReadBytes(b); err != nil {
return err
}
// Now decode the inner request.
inner, err := r.decode(b)
if err == nil {
r.inner = inner
}
return err
}
}
// UnmarshalBinary decodes the canonical encoding of requests.
func (r *Request) UnmarshalBinary(b []byte) error {
inner, err := r.decode(b)
if err != nil {
return err
}
r.inner = inner
return nil
}
// decode decodes a request from the canonical format.
func (r *Request) decode(b []byte) (RequestData, error) {
if len(b) <= 1 {
return nil, errShortTypedRequest
}
var inner RequestData
switch b[0] {
case DepositRequestType:
inner = new(Deposit)
default:
return nil, ErrRequestTypeNotSupported
}
err := inner.decode(b[1:])
return inner, err
}

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

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

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

Loading…
Cancel
Save