mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-05-24 16:59:26 +00:00
Merge branch 'master' into json-premarshal-blobsbundle
This commit is contained in:
commit
7d1ca42ab5
146 changed files with 5101 additions and 1394 deletions
|
|
@ -145,7 +145,7 @@ jobs:
|
|||
|
||||
windows:
|
||||
name: Windows Build
|
||||
runs-on: "win-11"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
|
|
@ -155,57 +155,46 @@ jobs:
|
|||
go-version: 1.24
|
||||
cache: false
|
||||
|
||||
# Note: gcc.exe only works properly if the corresponding bin/ directory is
|
||||
# contained in PATH.
|
||||
- name: Install cross toolchain
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get -yq --no-install-suggests --no-install-recommends install \
|
||||
gcc-mingw-w64-x86-64 gcc-mingw-w64-i686 nsis
|
||||
|
||||
- name: "Build (amd64)"
|
||||
shell: cmd
|
||||
run: |
|
||||
set PATH=%GETH_MINGW%\bin;%PATH%
|
||||
go run build/ci.go install -dlgo -arch amd64 -cc %GETH_MINGW%\bin\gcc.exe
|
||||
env:
|
||||
GETH_MINGW: 'C:\msys64\mingw64'
|
||||
go run build/ci.go install -dlgo -os windows -arch amd64 -cc x86_64-w64-mingw32-gcc
|
||||
|
||||
- name: "Create/upload archive (amd64)"
|
||||
shell: cmd
|
||||
run: |
|
||||
go run build/ci.go archive -arch amd64 -type zip -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
|
||||
go run build/ci.go archive -os windows -arch amd64 -type zip -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
|
||||
env:
|
||||
WINDOWS_SIGNING_KEY: ${{ secrets.WINDOWS_SIGNING_KEY }}
|
||||
AZURE_BLOBSTORE_TOKEN: ${{ secrets.AZURE_BLOBSTORE_TOKEN }}
|
||||
|
||||
- name: "Create/upload NSIS installer (amd64)"
|
||||
shell: cmd
|
||||
run: |
|
||||
set "PATH=C:\Program Files (x86)\NSIS;%PATH%"
|
||||
go run build/ci.go nsis -arch amd64 -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
|
||||
del /Q build\bin\*
|
||||
rm -f build/bin/*
|
||||
env:
|
||||
WINDOWS_SIGNING_KEY: ${{ secrets.WINDOWS_SIGNING_KEY }}
|
||||
AZURE_BLOBSTORE_TOKEN: ${{ secrets.AZURE_BLOBSTORE_TOKEN }}
|
||||
|
||||
- name: "Build (386)"
|
||||
shell: cmd
|
||||
run: |
|
||||
set PATH=%GETH_MINGW%\bin;%PATH%
|
||||
go run build/ci.go install -dlgo -arch 386 -cc %GETH_MINGW%\bin\gcc.exe
|
||||
env:
|
||||
GETH_MINGW: 'C:\msys64\mingw32'
|
||||
go run build/ci.go install -dlgo -os windows -arch 386 -cc i686-w64-mingw32-gcc
|
||||
|
||||
- name: "Create/upload archive (386)"
|
||||
shell: cmd
|
||||
run: |
|
||||
go run build/ci.go archive -arch 386 -type zip -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
|
||||
go run build/ci.go archive -os windows -arch 386 -type zip -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
|
||||
env:
|
||||
WINDOWS_SIGNING_KEY: ${{ secrets.WINDOWS_SIGNING_KEY }}
|
||||
AZURE_BLOBSTORE_TOKEN: ${{ secrets.AZURE_BLOBSTORE_TOKEN }}
|
||||
|
||||
- name: "Create/upload NSIS installer (386)"
|
||||
shell: cmd
|
||||
run: |
|
||||
set "PATH=C:\Program Files (x86)\NSIS;%PATH%"
|
||||
go run build/ci.go nsis -arch 386 -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
|
||||
del /Q build\bin\*
|
||||
rm -f build/bin/*
|
||||
env:
|
||||
WINDOWS_SIGNING_KEY: ${{ secrets.WINDOWS_SIGNING_KEY }}
|
||||
AZURE_BLOBSTORE_TOKEN: ${{ secrets.AZURE_BLOBSTORE_TOKEN }}
|
||||
|
|
|
|||
|
|
@ -176,6 +176,13 @@ var (
|
|||
// ErrNoCodeAfterDeploy is returned by WaitDeployed if contract creation leaves
|
||||
// an empty contract behind.
|
||||
ErrNoCodeAfterDeploy = bind2.ErrNoCodeAfterDeploy
|
||||
|
||||
// ErrNoEventSignature is returned when a log entry has no topics.
|
||||
ErrNoEventSignature = bind2.ErrNoEventSignature
|
||||
|
||||
// ErrEventSignatureMismatch is returned when a log's topic[0] does not match
|
||||
// the expected event signature.
|
||||
ErrEventSignatureMismatch = bind2.ErrEventSignatureMismatch
|
||||
)
|
||||
|
||||
// ContractCaller defines the methods needed to allow operating with a contract on a read
|
||||
|
|
|
|||
|
|
@ -910,7 +910,7 @@ func TestUnpackTuple(t *testing.T) {
|
|||
},
|
||||
},
|
||||
FieldT: T{
|
||||
big.NewInt(0), big.NewInt(1),
|
||||
big.NewInt(0).SetBits([]big.Word{}), big.NewInt(1),
|
||||
},
|
||||
A: big.NewInt(1),
|
||||
}
|
||||
|
|
@ -919,7 +919,7 @@ func TestUnpackTuple(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if reflect.DeepEqual(ret, expected) {
|
||||
if !reflect.DeepEqual(ret, expected) {
|
||||
t.Error("unexpected unpack value")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
39
appveyor.yml
39
appveyor.yml
|
|
@ -1,39 +0,0 @@
|
|||
clone_depth: 5
|
||||
version: "{branch}.{build}"
|
||||
|
||||
image:
|
||||
- Visual Studio 2019
|
||||
|
||||
environment:
|
||||
matrix:
|
||||
- GETH_ARCH: amd64
|
||||
GETH_MINGW: 'C:\msys64\mingw64'
|
||||
- GETH_ARCH: 386
|
||||
GETH_MINGW: 'C:\msys64\mingw32'
|
||||
|
||||
install:
|
||||
- git submodule update --init --depth 1 --recursive
|
||||
- go version
|
||||
|
||||
for:
|
||||
# Windows builds for amd64 + 386.
|
||||
- matrix:
|
||||
only:
|
||||
- image: Visual Studio 2019
|
||||
environment:
|
||||
# We use gcc from MSYS2 because it is the most recent compiler version available on
|
||||
# AppVeyor. Note: gcc.exe only works properly if the corresponding bin/ directory is
|
||||
# contained in PATH.
|
||||
GETH_CC: '%GETH_MINGW%\bin\gcc.exe'
|
||||
PATH: '%GETH_MINGW%\bin;C:\Program Files (x86)\NSIS\;%PATH%'
|
||||
build_script:
|
||||
- 'echo %GETH_ARCH%'
|
||||
- 'echo %GETH_CC%'
|
||||
- '%GETH_CC% --version'
|
||||
- go run build/ci.go install -dlgo -arch %GETH_ARCH% -cc %GETH_CC%
|
||||
after_build:
|
||||
# Upload builds. Note that ci.go makes this a no-op PR builds.
|
||||
- go run build/ci.go archive -arch %GETH_ARCH% -type zip -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
|
||||
- go run build/ci.go nsis -arch %GETH_ARCH% -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
|
||||
test_script:
|
||||
- go run build/ci.go test -dlgo -arch %GETH_ARCH% -cc %GETH_CC% -short
|
||||
|
|
@ -81,6 +81,7 @@ var (
|
|||
TooLargeRequest = &EngineAPIError{code: -38004, msg: "Too large request"}
|
||||
InvalidParams = &EngineAPIError{code: -32602, msg: "Invalid parameters"}
|
||||
UnsupportedFork = &EngineAPIError{code: -38005, msg: "Unsupported fork"}
|
||||
TooDeepReorg = &EngineAPIError{code: -38006, msg: "Too deep reorg"}
|
||||
|
||||
STATUS_INVALID = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: INVALID}, PayloadID: nil}
|
||||
STATUS_SYNCING = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: SYNCING}, PayloadID: nil}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||
)
|
||||
|
||||
var _ = (*executableDataMarshaling)(nil)
|
||||
|
|
@ -17,24 +18,25 @@ var _ = (*executableDataMarshaling)(nil)
|
|||
// MarshalJSON marshals as JSON.
|
||||
func (e ExecutableData) MarshalJSON() ([]byte, error) {
|
||||
type ExecutableData struct {
|
||||
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
|
||||
FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"`
|
||||
StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
|
||||
ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
|
||||
LogsBloom hexutil.Bytes `json:"logsBloom" gencodec:"required"`
|
||||
Random common.Hash `json:"prevRandao" gencodec:"required"`
|
||||
Number hexutil.Uint64 `json:"blockNumber" gencodec:"required"`
|
||||
GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
|
||||
GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
|
||||
Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"`
|
||||
ExtraData hexutil.Bytes `json:"extraData" gencodec:"required"`
|
||||
BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"`
|
||||
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
|
||||
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
|
||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
|
||||
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
|
||||
SlotNumber *hexutil.Uint64 `json:"slotNumber,omitempty"`
|
||||
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
|
||||
FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"`
|
||||
StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
|
||||
ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
|
||||
LogsBloom hexutil.Bytes `json:"logsBloom" gencodec:"required"`
|
||||
Random common.Hash `json:"prevRandao" gencodec:"required"`
|
||||
Number hexutil.Uint64 `json:"blockNumber" gencodec:"required"`
|
||||
GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
|
||||
GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
|
||||
Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"`
|
||||
ExtraData hexutil.Bytes `json:"extraData" gencodec:"required"`
|
||||
BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"`
|
||||
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
|
||||
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
|
||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
|
||||
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
|
||||
SlotNumber *hexutil.Uint64 `json:"slotNumber,omitempty"`
|
||||
BlockAccessList *bal.BlockAccessList `json:"blockAccessList,omitempty"`
|
||||
}
|
||||
var enc ExecutableData
|
||||
enc.ParentHash = e.ParentHash
|
||||
|
|
@ -60,30 +62,32 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) {
|
|||
enc.BlobGasUsed = (*hexutil.Uint64)(e.BlobGasUsed)
|
||||
enc.ExcessBlobGas = (*hexutil.Uint64)(e.ExcessBlobGas)
|
||||
enc.SlotNumber = (*hexutil.Uint64)(e.SlotNumber)
|
||||
enc.BlockAccessList = e.BlockAccessList
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals from JSON.
|
||||
func (e *ExecutableData) UnmarshalJSON(input []byte) error {
|
||||
type ExecutableData struct {
|
||||
ParentHash *common.Hash `json:"parentHash" gencodec:"required"`
|
||||
FeeRecipient *common.Address `json:"feeRecipient" gencodec:"required"`
|
||||
StateRoot *common.Hash `json:"stateRoot" gencodec:"required"`
|
||||
ReceiptsRoot *common.Hash `json:"receiptsRoot" gencodec:"required"`
|
||||
LogsBloom *hexutil.Bytes `json:"logsBloom" gencodec:"required"`
|
||||
Random *common.Hash `json:"prevRandao" gencodec:"required"`
|
||||
Number *hexutil.Uint64 `json:"blockNumber" gencodec:"required"`
|
||||
GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
|
||||
GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
|
||||
Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"`
|
||||
ExtraData *hexutil.Bytes `json:"extraData" gencodec:"required"`
|
||||
BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"`
|
||||
BlockHash *common.Hash `json:"blockHash" gencodec:"required"`
|
||||
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
|
||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
|
||||
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
|
||||
SlotNumber *hexutil.Uint64 `json:"slotNumber,omitempty"`
|
||||
ParentHash *common.Hash `json:"parentHash" gencodec:"required"`
|
||||
FeeRecipient *common.Address `json:"feeRecipient" gencodec:"required"`
|
||||
StateRoot *common.Hash `json:"stateRoot" gencodec:"required"`
|
||||
ReceiptsRoot *common.Hash `json:"receiptsRoot" gencodec:"required"`
|
||||
LogsBloom *hexutil.Bytes `json:"logsBloom" gencodec:"required"`
|
||||
Random *common.Hash `json:"prevRandao" gencodec:"required"`
|
||||
Number *hexutil.Uint64 `json:"blockNumber" gencodec:"required"`
|
||||
GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
|
||||
GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
|
||||
Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"`
|
||||
ExtraData *hexutil.Bytes `json:"extraData" gencodec:"required"`
|
||||
BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"`
|
||||
BlockHash *common.Hash `json:"blockHash" gencodec:"required"`
|
||||
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
|
||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
|
||||
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
|
||||
SlotNumber *hexutil.Uint64 `json:"slotNumber,omitempty"`
|
||||
BlockAccessList *bal.BlockAccessList `json:"blockAccessList,omitempty"`
|
||||
}
|
||||
var dec ExecutableData
|
||||
if err := json.Unmarshal(input, &dec); err != nil {
|
||||
|
|
@ -160,5 +164,8 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error {
|
|||
if dec.SlotNumber != nil {
|
||||
e.SlotNumber = (*uint64)(dec.SlotNumber)
|
||||
}
|
||||
if dec.BlockAccessList != nil {
|
||||
e.BlockAccessList = dec.BlockAccessList
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
|
|
@ -82,24 +83,25 @@ type payloadAttributesMarshaling struct {
|
|||
|
||||
// ExecutableData is the data necessary to execute an EL payload.
|
||||
type ExecutableData struct {
|
||||
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
|
||||
FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"`
|
||||
StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
|
||||
ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
|
||||
LogsBloom []byte `json:"logsBloom" gencodec:"required"`
|
||||
Random common.Hash `json:"prevRandao" gencodec:"required"`
|
||||
Number uint64 `json:"blockNumber" gencodec:"required"`
|
||||
GasLimit uint64 `json:"gasLimit" gencodec:"required"`
|
||||
GasUsed uint64 `json:"gasUsed" gencodec:"required"`
|
||||
Timestamp uint64 `json:"timestamp" gencodec:"required"`
|
||||
ExtraData []byte `json:"extraData" gencodec:"required"`
|
||||
BaseFeePerGas *big.Int `json:"baseFeePerGas" gencodec:"required"`
|
||||
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
|
||||
Transactions [][]byte `json:"transactions" gencodec:"required"`
|
||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||
BlobGasUsed *uint64 `json:"blobGasUsed"`
|
||||
ExcessBlobGas *uint64 `json:"excessBlobGas"`
|
||||
SlotNumber *uint64 `json:"slotNumber,omitempty"`
|
||||
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
|
||||
FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"`
|
||||
StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
|
||||
ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
|
||||
LogsBloom []byte `json:"logsBloom" gencodec:"required"`
|
||||
Random common.Hash `json:"prevRandao" gencodec:"required"`
|
||||
Number uint64 `json:"blockNumber" gencodec:"required"`
|
||||
GasLimit uint64 `json:"gasLimit" gencodec:"required"`
|
||||
GasUsed uint64 `json:"gasUsed" gencodec:"required"`
|
||||
Timestamp uint64 `json:"timestamp" gencodec:"required"`
|
||||
ExtraData []byte `json:"extraData" gencodec:"required"`
|
||||
BaseFeePerGas *big.Int `json:"baseFeePerGas" gencodec:"required"`
|
||||
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
|
||||
Transactions [][]byte `json:"transactions" gencodec:"required"`
|
||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||
BlobGasUsed *uint64 `json:"blobGasUsed"`
|
||||
ExcessBlobGas *uint64 `json:"excessBlobGas"`
|
||||
SlotNumber *uint64 `json:"slotNumber,omitempty"`
|
||||
BlockAccessList *bal.BlockAccessList `json:"blockAccessList,omitempty"`
|
||||
}
|
||||
|
||||
// JSON type overrides for executableData.
|
||||
|
|
@ -284,7 +286,7 @@ func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.H
|
|||
if data.BaseFeePerGas != nil && (data.BaseFeePerGas.Sign() == -1 || data.BaseFeePerGas.BitLen() > 256) {
|
||||
return nil, fmt.Errorf("invalid baseFeePerGas: %v", data.BaseFeePerGas)
|
||||
}
|
||||
var blobHashes = make([]common.Hash, 0, len(txs))
|
||||
var blobHashes = make([]common.Hash, 0, len(versionedHashes))
|
||||
for _, tx := range txs {
|
||||
blobHashes = append(blobHashes, tx.BlobHashes()...)
|
||||
}
|
||||
|
|
@ -311,56 +313,66 @@ func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.H
|
|||
requestsHash = &h
|
||||
}
|
||||
|
||||
header := &types.Header{
|
||||
ParentHash: data.ParentHash,
|
||||
UncleHash: types.EmptyUncleHash,
|
||||
Coinbase: data.FeeRecipient,
|
||||
Root: data.StateRoot,
|
||||
TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)),
|
||||
ReceiptHash: data.ReceiptsRoot,
|
||||
Bloom: types.BytesToBloom(data.LogsBloom),
|
||||
Difficulty: common.Big0,
|
||||
Number: new(big.Int).SetUint64(data.Number),
|
||||
GasLimit: data.GasLimit,
|
||||
GasUsed: data.GasUsed,
|
||||
Time: data.Timestamp,
|
||||
BaseFee: data.BaseFeePerGas,
|
||||
Extra: data.ExtraData,
|
||||
MixDigest: data.Random,
|
||||
WithdrawalsHash: withdrawalsRoot,
|
||||
ExcessBlobGas: data.ExcessBlobGas,
|
||||
BlobGasUsed: data.BlobGasUsed,
|
||||
ParentBeaconRoot: beaconRoot,
|
||||
RequestsHash: requestsHash,
|
||||
SlotNumber: data.SlotNumber,
|
||||
// If Amsterdam is enabled, data.BlockAccessList is always non-nil,
|
||||
// even for empty blocks with no state transitions.
|
||||
//
|
||||
// If Amsterdam is not enabled yet, blockAccessListHash is expected
|
||||
// to be nil.
|
||||
var blockAccessListHash *common.Hash
|
||||
if data.BlockAccessList != nil {
|
||||
hash := data.BlockAccessList.Hash()
|
||||
blockAccessListHash = &hash
|
||||
}
|
||||
return types.NewBlockWithHeader(header).
|
||||
WithBody(types.Body{Transactions: txs, Uncles: nil, Withdrawals: data.Withdrawals}),
|
||||
nil
|
||||
header := &types.Header{
|
||||
ParentHash: data.ParentHash,
|
||||
UncleHash: types.EmptyUncleHash,
|
||||
Coinbase: data.FeeRecipient,
|
||||
Root: data.StateRoot,
|
||||
TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)),
|
||||
ReceiptHash: data.ReceiptsRoot,
|
||||
Bloom: types.BytesToBloom(data.LogsBloom),
|
||||
Difficulty: common.Big0,
|
||||
Number: new(big.Int).SetUint64(data.Number),
|
||||
GasLimit: data.GasLimit,
|
||||
GasUsed: data.GasUsed,
|
||||
Time: data.Timestamp,
|
||||
BaseFee: data.BaseFeePerGas,
|
||||
Extra: data.ExtraData,
|
||||
MixDigest: data.Random,
|
||||
WithdrawalsHash: withdrawalsRoot,
|
||||
ExcessBlobGas: data.ExcessBlobGas,
|
||||
BlobGasUsed: data.BlobGasUsed,
|
||||
ParentBeaconRoot: beaconRoot,
|
||||
RequestsHash: requestsHash,
|
||||
SlotNumber: data.SlotNumber,
|
||||
BlockAccessListHash: blockAccessListHash,
|
||||
}
|
||||
return types.NewBlockWithHeader(header).WithBody(types.Body{Transactions: txs, Uncles: nil, Withdrawals: data.Withdrawals}), nil
|
||||
}
|
||||
|
||||
// BlockToExecutableData constructs the ExecutableData structure by filling the
|
||||
// fields from the given block. It assumes the given block is post-merge block.
|
||||
func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types.BlobTxSidecar, requests [][]byte) *ExecutionPayloadEnvelope {
|
||||
data := &ExecutableData{
|
||||
BlockHash: block.Hash(),
|
||||
ParentHash: block.ParentHash(),
|
||||
FeeRecipient: block.Coinbase(),
|
||||
StateRoot: block.Root(),
|
||||
Number: block.NumberU64(),
|
||||
GasLimit: block.GasLimit(),
|
||||
GasUsed: block.GasUsed(),
|
||||
BaseFeePerGas: block.BaseFee(),
|
||||
Timestamp: block.Time(),
|
||||
ReceiptsRoot: block.ReceiptHash(),
|
||||
LogsBloom: block.Bloom().Bytes(),
|
||||
Transactions: encodeTransactions(block.Transactions()),
|
||||
Random: block.MixDigest(),
|
||||
ExtraData: block.Extra(),
|
||||
Withdrawals: block.Withdrawals(),
|
||||
BlobGasUsed: block.BlobGasUsed(),
|
||||
ExcessBlobGas: block.ExcessBlobGas(),
|
||||
SlotNumber: block.SlotNumber(),
|
||||
BlockHash: block.Hash(),
|
||||
ParentHash: block.ParentHash(),
|
||||
FeeRecipient: block.Coinbase(),
|
||||
StateRoot: block.Root(),
|
||||
Number: block.NumberU64(),
|
||||
GasLimit: block.GasLimit(),
|
||||
GasUsed: block.GasUsed(),
|
||||
BaseFeePerGas: block.BaseFee(),
|
||||
Timestamp: block.Time(),
|
||||
ReceiptsRoot: block.ReceiptHash(),
|
||||
LogsBloom: block.Bloom().Bytes(),
|
||||
Transactions: encodeTransactions(block.Transactions()),
|
||||
Random: block.MixDigest(),
|
||||
ExtraData: block.Extra(),
|
||||
Withdrawals: block.Withdrawals(),
|
||||
BlobGasUsed: block.BlobGasUsed(),
|
||||
ExcessBlobGas: block.ExcessBlobGas(),
|
||||
SlotNumber: block.SlotNumber(),
|
||||
BlockAccessList: block.AccessList(),
|
||||
}
|
||||
|
||||
// Add blobs.
|
||||
|
|
|
|||
|
|
@ -182,6 +182,12 @@ func (s *CommitteeChain) Reset() {
|
|||
s.chainmu.Lock()
|
||||
defer s.chainmu.Unlock()
|
||||
|
||||
s.resetLocked()
|
||||
}
|
||||
|
||||
// ResetLocked resets the committee chain without locking. The caller should hold
|
||||
// the chainmu lock.
|
||||
func (s *CommitteeChain) resetLocked() {
|
||||
if err := s.rollback(0); err != nil {
|
||||
log.Error("Error writing batch into chain database", "error", err)
|
||||
}
|
||||
|
|
@ -201,22 +207,22 @@ func (s *CommitteeChain) CheckpointInit(bootstrap types.BootstrapData) error {
|
|||
}
|
||||
period := bootstrap.Header.SyncPeriod()
|
||||
if err := s.deleteFixedCommitteeRootsFrom(period + 2); err != nil {
|
||||
s.Reset()
|
||||
s.resetLocked()
|
||||
return err
|
||||
}
|
||||
if s.addFixedCommitteeRoot(period, bootstrap.CommitteeRoot) != nil {
|
||||
s.Reset()
|
||||
s.resetLocked()
|
||||
if err := s.addFixedCommitteeRoot(period, bootstrap.CommitteeRoot); err != nil {
|
||||
s.Reset()
|
||||
s.resetLocked()
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := s.addFixedCommitteeRoot(period+1, common.Hash(bootstrap.CommitteeBranch[0])); err != nil {
|
||||
s.Reset()
|
||||
s.resetLocked()
|
||||
return err
|
||||
}
|
||||
if err := s.addCommittee(period, bootstrap.Committee); err != nil {
|
||||
s.Reset()
|
||||
s.resetLocked()
|
||||
return err
|
||||
}
|
||||
s.changeCounter++
|
||||
|
|
|
|||
|
|
@ -98,7 +98,10 @@ func (s *CheckpointInit) Process(requester request.Requester, events []request.E
|
|||
case ssDefault:
|
||||
if resp != nil {
|
||||
if checkpoint := resp.(*types.BootstrapData); checkpoint.Header.Hash() == common.Hash(req.(ReqCheckpointData)) {
|
||||
s.chain.CheckpointInit(*checkpoint)
|
||||
err := s.chain.CheckpointInit(*checkpoint)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.initialized = true
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,49 +5,49 @@
|
|||
# https://github.com/ethereum/execution-spec-tests/releases/download/v5.1.0
|
||||
a3192784375acec7eaec492799d5c5d0c47a2909a3cc40178898e4ecd20cc416 fixtures_develop.tar.gz
|
||||
|
||||
# version:golang 1.25.9
|
||||
# version:golang 1.25.10
|
||||
# https://go.dev/dl/
|
||||
0ec9ef8ebcea097aac37decae9f09a7218b451cd96be7d6ed513d8e4bcf909cf go1.25.9.src.tar.gz
|
||||
b9ede6378a8f8d3d22bf52e68beb69ef7abdb65929ab2456020383002da15846 go1.25.9.aix-ppc64.tar.gz
|
||||
92cb78fba4796e218c1accb0ea0a214ef2094c382049a244ad6505505d015fbe go1.25.9.darwin-amd64.tar.gz
|
||||
9528be7329b9770631a6bd09ca2f3a73ed7332bec01d87435e75e92d8f130363 go1.25.9.darwin-arm64.tar.gz
|
||||
918e44a471c5524caa52f74185064240d5eb343aa8023d604776511fc7adffa6 go1.25.9.dragonfly-amd64.tar.gz
|
||||
2d67dbdfd09c6fcaa0e64485367ef43b8837ea200c663d6417183237bcddf83d go1.25.9.freebsd-386.tar.gz
|
||||
9152d0c0badbfeb0c0e148e47c12bec28099d8cf2db60958810c879e0b679d07 go1.25.9.freebsd-amd64.tar.gz
|
||||
437dca59604ad4a806a6a88e3d7ec1cd98ac9b402a3671629f4e553dd8b9888f go1.25.9.freebsd-arm.tar.gz
|
||||
4c0fe53977412036fc8081e8d0992bbaabe4d3e1926137271ba11c2f5753300f go1.25.9.freebsd-arm64.tar.gz
|
||||
d6087cdd1c084bd186132f29e0d032852a745f3c7619003d0fd5612c1fa58c8a go1.25.9.freebsd-riscv64.tar.gz
|
||||
f82e49037e195cb62beae6a6ad83497157b2af5a01bad2f1dcb65df41080aabb go1.25.9.illumos-amd64.tar.gz
|
||||
1e14a73bc2b19e370e0d4c57ba87aabfe8aef1e435e14d246742d48a13254f36 go1.25.9.linux-386.tar.gz
|
||||
00859d7bd6defe8bf84d9db9e57b9a4467b2887c18cd93ae7460e713db774bc1 go1.25.9.linux-amd64.tar.gz
|
||||
ec342e7389b7f489564ed5463c63b16cf8040023dabc7861256677165a8c0e2b go1.25.9.linux-arm64.tar.gz
|
||||
7d4f0d266d871301e08ef4ac31c56e66048688893b2848392e5c600276351ee8 go1.25.9.linux-armv6l.tar.gz
|
||||
f3460d901a14496bc609636e4accf9110ee1869d41c64af7e29cd567cffcf49b go1.25.9.linux-loong64.tar.gz
|
||||
1da96ea449382ff96c09c55cee74815324e01d687d5ac6d2ade58244b8574306 go1.25.9.linux-mips.tar.gz
|
||||
311a7f5f01f9a4bd51288b575eb619dc8e28e1fbc0cd78256a428b3ca668ff01 go1.25.9.linux-mips64.tar.gz
|
||||
0b4edaf9e2ba3f0a079547effda70ec6a4b51a6ca3271a1147652c87ebcf3735 go1.25.9.linux-mips64le.tar.gz
|
||||
42667340df264896f20b12261429d954e736e9772ab83ba289e68c30cf6f9628 go1.25.9.linux-mipsle.tar.gz
|
||||
b9cbb3a4894b5aca6966c23452608435e8535278ef019b18d8898fbbfab67e74 go1.25.9.linux-ppc64.tar.gz
|
||||
b0c41c7da1fc8d39020d65296a0dc54167afd9f76d67064e22c31ce3d839a739 go1.25.9.linux-ppc64le.tar.gz
|
||||
2a630be8f854177c13e5fa75f7812c721369ecb9bd6e4c0fb1bd1c708d08b37c go1.25.9.linux-riscv64.tar.gz
|
||||
0cf55136ac7eaccfc36d849054f849510ea289c2d959ffbed7b3866b4f484d17 go1.25.9.linux-s390x.tar.gz
|
||||
eaf8167ff10a6a3e5dd304ef5f2e020b3a7379e76fa1011dc49c895800bf367c go1.25.9.netbsd-386.tar.gz
|
||||
3cc6a861e62e23feae660984e0f2f14a2efb5d1f655900afee1d51af98919ae4 go1.25.9.netbsd-amd64.tar.gz
|
||||
c2c44dca10e882c30553f4aa2ab8f6722b670fb12882378c8f461a9105d40188 go1.25.9.netbsd-arm.tar.gz
|
||||
f301b71a8ec448053a5d2597df2e178120204bc9a33266c81600dd5d020a61b4 go1.25.9.netbsd-arm64.tar.gz
|
||||
c4543b7fdef9707b4896810c69b4160a43ecec210af45c300f3abd78aa0c9e72 go1.25.9.openbsd-386.tar.gz
|
||||
37275325e314f5ab7cf8ae65c4efc7cbfdaf20b41c6849549739b57a3ac97544 go1.25.9.openbsd-amd64.tar.gz
|
||||
f9c05b6b315e979ecdd47354dd287c01708d6a88dc6ae7af74c84df8fa00df94 go1.25.9.openbsd-arm.tar.gz
|
||||
4e999f42cf959ff95ca84af1ea1db3771000f5e57e157904bc2ffc72c75e29a2 go1.25.9.openbsd-arm64.tar.gz
|
||||
0c7fa6c7c2b1cc13ad32fa94fc31273b4adf39c1e0f0e5dcedac158ff526af3f go1.25.9.openbsd-ppc64.tar.gz
|
||||
347b33953a4b6e8df17719296f360f60878fe48a2d482ceb3637a3dfd4950065 go1.25.9.openbsd-riscv64.tar.gz
|
||||
889f77d567c06832e0d332fe2458653dc66d43cded7ddbca6f72ce0ca60029cc go1.25.9.plan9-386.tar.gz
|
||||
978b1f931fadec2f2516237d2649ee845d93c8eaf47dd196cfd8d26c7b2706a1 go1.25.9.plan9-amd64.tar.gz
|
||||
30b9565e5ad0a212fe00990ead700c751b416eb2ef8d7c91a204945a7ff83a48 go1.25.9.plan9-arm.tar.gz
|
||||
9e9125ff84ab3c3522ec758cab9540a17e9cba12bfcc34b6bf556cb89b522591 go1.25.9.solaris-amd64.tar.gz
|
||||
bf40515f5f4d834fa9ead31ff75581e61a38ac27bf49840b95c5c998d321c0f6 go1.25.9.windows-386.zip
|
||||
a7a710e225467b34e9e09fb432b829c86c9b2da5821ee5418f7eb2e8ae1a22cc go1.25.9.windows-amd64.zip
|
||||
33cd73cf1b3ceee655ef71bc96e94006c02ae3c617fdd67ac9be3dfae3957449 go1.25.9.windows-arm64.zip
|
||||
20cf04a92e5af99748e341bc8996fa28090c9ac98765fa115ec5ddf41d7af41d go1.25.10.src.tar.gz
|
||||
a194e767c2ab4216a60acc068b9dbe6bf4fae05c14bb52d6bbdcb5b3ea521308 go1.25.10.aix-ppc64.tar.gz
|
||||
52321165a3146cd91865ef98371506a846ed4dc4f9f1c9323e5ad90d2a411e06 go1.25.10.darwin-amd64.tar.gz
|
||||
795691a425de7e7cdba3544f354dcd2cebcf52e87dc6898193878f34eb6d634f go1.25.10.darwin-arm64.tar.gz
|
||||
e37b4544ba9e9e9a7ab2ed3116b3fc4d39a88da854baa5a566d9d6d3a9de7d4c go1.25.10.dragonfly-amd64.tar.gz
|
||||
2a70d1fdabab637aa442ca94599a56e381238efa20cb995d5433b8579bfe482c go1.25.10.freebsd-386.tar.gz
|
||||
9cdf522d87d47d82fec4a313cc4f8c3c94a7770426e8d443e4150a1f330cba71 go1.25.10.freebsd-amd64.tar.gz
|
||||
6da6183633e9e59ffd9edefab68b5059c89b605596d94aaba650b1681fccd35f go1.25.10.freebsd-arm.tar.gz
|
||||
7adcefeebdd05331f4d45f1ad2dddb5c53537cff6552e82f6595b3b833b95371 go1.25.10.freebsd-arm64.tar.gz
|
||||
285f80a1ace21a7d94035cd753196eeada8cacd48e6396fd116ad5eb67aea957 go1.25.10.freebsd-riscv64.tar.gz
|
||||
de7461bf0e5068a4f6e7f8713026d70516be6dbd5de5d21f9ced1c182f2f326e go1.25.10.illumos-amd64.tar.gz
|
||||
2f574f2e2e19ead5b280fec0e7af5c81b76632685f03b6ac42dfa34c4b773c52 go1.25.10.linux-386.tar.gz
|
||||
42d4f7a32316aa66591eca7e89867256057a4264451aca10570a715b3637ba70 go1.25.10.linux-amd64.tar.gz
|
||||
654da1f9b50a5d1c2a85ccf8ed405aa89c06e94d18384628bf186f7712677b08 go1.25.10.linux-arm64.tar.gz
|
||||
39f168f158e693887d3ad006168af1b1a3007b19c5993cae4d9d57f82f52aaf8 go1.25.10.linux-armv6l.tar.gz
|
||||
05401fe5ea50ad2bafb9c797ef9bf21574b0661f19ef4d0dd66af8a0fb7323f3 go1.25.10.linux-loong64.tar.gz
|
||||
d5bc2d6155d394a3aae41f21eb7c60da5595a6147aa0f30ed6b27da25e06c3f7 go1.25.10.linux-mips.tar.gz
|
||||
8c64e7493e5953c3ba3153487d2fddd7f8ed142392c77f138e6792a6c1930db4 go1.25.10.linux-mips64.tar.gz
|
||||
bd53aa2d558b7c1eadfc6bf01132e1859203a92f458ed7ba75b7f3230f14b095 go1.25.10.linux-mips64le.tar.gz
|
||||
120b254e2e2980bb06687175db5c4064a85696c53001dc9f59934ad18f74a6bc go1.25.10.linux-mipsle.tar.gz
|
||||
8a6acb21295b0ec974a44608361920ea8dbff5666631a6f556bd7d5f1d56535f go1.25.10.linux-ppc64.tar.gz
|
||||
778925fdcdf9a272f823d147fad51545c3334b7ccd8652b2ccaaf2b01800280a go1.25.10.linux-ppc64le.tar.gz
|
||||
b4f04ad0db48bcfea946db5323919cd21034e0bd2821a557dacd29c1b1013a4b go1.25.10.linux-riscv64.tar.gz
|
||||
936b953e43921a64c12da871f76871ebbeb6d2092a7b8bdc307f5246f3c662cc go1.25.10.linux-s390x.tar.gz
|
||||
061470e0bc7132146a5925a3cc28d5bc498eb1b1ff09dedcfaae10f781ff2274 go1.25.10.netbsd-386.tar.gz
|
||||
63b2d50d7f8f269a9c82d42a4060e90cffb7f9102299818bb071b067aac8da8f go1.25.10.netbsd-amd64.tar.gz
|
||||
c35129f68796526aa4dc4b6f481e2d995ef312aedadc88b659b945cc00e1f8f0 go1.25.10.netbsd-arm.tar.gz
|
||||
2f541da4e2b298154d992d1f11bbb38c89d0821d91cc50a46776d42bb5e63bca go1.25.10.netbsd-arm64.tar.gz
|
||||
2d42e569b07f1b99fdbfd008e7c22f967d165e2ce02464f46818fbed2aec43f5 go1.25.10.openbsd-386.tar.gz
|
||||
0ad05960e8c9f867328151308c87f938433bec8f22f6a9437a896e22169fc840 go1.25.10.openbsd-amd64.tar.gz
|
||||
099cc11473f99461c77161912740945308f08f6834980afb262c72bdc915f2d7 go1.25.10.openbsd-arm.tar.gz
|
||||
bdf3335d5008c1ddc81fa94892283e4f1fee22566f5351d4e726d9f55a67c838 go1.25.10.openbsd-arm64.tar.gz
|
||||
0933d418da0a61e0f29de717a77498f16b9b5b50dbe2205e20b2ed7fd4067f75 go1.25.10.openbsd-ppc64.tar.gz
|
||||
191e6f3e75712f8c13d189d53b668e2cac6449f26474c1d86fbd04f6e9846f9c go1.25.10.openbsd-riscv64.tar.gz
|
||||
68c053c8acd76c50fc430e92f4a86110ec3d97dd03d27b9339b4eaf793caff5f go1.25.10.plan9-386.tar.gz
|
||||
42e2c46638ae22d93402e79efb40faee5c42cf7c56a01bb3ab47c6bb2512b745 go1.25.10.plan9-amd64.tar.gz
|
||||
3ef1d5838b1648da16724a07b72e839ccbd7cb8899c3e0426afd6b79d494b91c go1.25.10.plan9-arm.tar.gz
|
||||
631e3716017fbec06500a628d97e1155daec3593f0a7812c2ebfe8fc8c96b2ab go1.25.10.solaris-amd64.tar.gz
|
||||
ddc693d2d9d7cc671ebb72d1d50aa05670f95b059b7d90440611af57976871d5 go1.25.10.windows-386.zip
|
||||
ca37af2dadd8544464f1a9ca7c3886499d1cdfcb263855d0a1d71f194b2bd222 go1.25.10.windows-amd64.zip
|
||||
38be57e0398bd93673d65bcae6dc7ee3cf151d7038d0dba5c60a5153022872da go1.25.10.windows-arm64.zip
|
||||
|
||||
# version:golangci 2.10.1
|
||||
# https://github.com/golangci/golangci-lint/releases/
|
||||
|
|
|
|||
125
build/ci.go
125
build/ci.go
|
|
@ -73,21 +73,9 @@ var (
|
|||
"./cmd/keeper",
|
||||
}
|
||||
|
||||
// Files that end up in the geth*.zip archive.
|
||||
gethArchiveFiles = []string{
|
||||
"COPYING",
|
||||
executablePath("geth"),
|
||||
}
|
||||
|
||||
// Files that end up in the geth-alltools*.zip archive.
|
||||
allToolsArchiveFiles = []string{
|
||||
"COPYING",
|
||||
executablePath("abigen"),
|
||||
executablePath("evm"),
|
||||
executablePath("geth"),
|
||||
executablePath("rlpdump"),
|
||||
executablePath("clef"),
|
||||
}
|
||||
// Files that end up in the geth-alltools*.zip archive (and the NSIS installer
|
||||
// dev-tools section). Order matches the historical layout produced by ci.go.
|
||||
allToolsBinaries = []string{"abigen", "evm", "geth", "rlpdump", "clef"}
|
||||
|
||||
// Keeper build targets with their configurations
|
||||
keeperTargets = []struct {
|
||||
|
|
@ -180,13 +168,35 @@ var (
|
|||
|
||||
var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin"))
|
||||
|
||||
func executablePath(name string) string {
|
||||
if runtime.GOOS == "windows" {
|
||||
// executablePath returns the path to a built binary in GOBIN, applying the
|
||||
// platform-specific extension for the given target OS.
|
||||
func executablePath(name, targetOS string) string {
|
||||
if targetOS == "windows" {
|
||||
name += ".exe"
|
||||
}
|
||||
return filepath.Join(GOBIN, name)
|
||||
}
|
||||
|
||||
// gethArchiveFiles returns the file list for the geth-{platform}-{ver}.zip
|
||||
// archive, with binary paths resolved for the target OS.
|
||||
func gethArchiveFiles(targetOS string) []string {
|
||||
return []string{
|
||||
"COPYING",
|
||||
executablePath("geth", targetOS),
|
||||
}
|
||||
}
|
||||
|
||||
// allToolsArchiveFiles returns the file list for the
|
||||
// geth-alltools-{platform}-{ver}.zip archive, with binary paths resolved for
|
||||
// the target OS.
|
||||
func allToolsArchiveFiles(targetOS string) []string {
|
||||
files := []string{"COPYING"}
|
||||
for _, name := range allToolsBinaries {
|
||||
files = append(files, executablePath(name, targetOS))
|
||||
}
|
||||
return files
|
||||
}
|
||||
|
||||
func main() {
|
||||
log.SetFlags(log.Lshortfile)
|
||||
|
||||
|
|
@ -233,6 +243,7 @@ func main() {
|
|||
func doInstall(cmdline []string) {
|
||||
var (
|
||||
dlgo = flag.Bool("dlgo", false, "Download Go and build with it")
|
||||
targetOS = flag.String("os", runtime.GOOS, "Target OS to cross build for")
|
||||
arch = flag.String("arch", "", "Architecture to cross build for")
|
||||
cc = flag.String("cc", "", "C compiler to cross build with")
|
||||
staticlink = flag.Bool("static", false, "Create statically-linked executable")
|
||||
|
|
@ -241,7 +252,7 @@ func doInstall(cmdline []string) {
|
|||
env := build.Env()
|
||||
|
||||
// Configure the toolchain.
|
||||
tc := build.GoToolchain{GOARCH: *arch, CC: *cc}
|
||||
tc := build.GoToolchain{GOOS: *targetOS, GOARCH: *arch, CC: *cc}
|
||||
if *dlgo {
|
||||
csdb := download.MustLoadChecksums("build/checksums.txt")
|
||||
tc.Root = build.DownloadGo(csdb)
|
||||
|
|
@ -255,7 +266,7 @@ func doInstall(cmdline []string) {
|
|||
}
|
||||
|
||||
// Configure the build.
|
||||
gobuild := tc.Go("build", buildFlags(env, *staticlink, buildTags)...)
|
||||
gobuild := tc.Go("build", buildFlags(env, *staticlink, buildTags, *targetOS)...)
|
||||
|
||||
// Show packages during build.
|
||||
gobuild.Args = append(gobuild.Args, "-v")
|
||||
|
|
@ -270,7 +281,7 @@ func doInstall(cmdline []string) {
|
|||
// Do the build!
|
||||
for _, pkg := range packages {
|
||||
args := slices.Clone(gobuild.Args)
|
||||
args = append(args, "-o", executablePath(path.Base(pkg)))
|
||||
args = append(args, "-o", executablePath(path.Base(pkg), *targetOS))
|
||||
args = append(args, pkg)
|
||||
build.MustRun(&exec.Cmd{Path: gobuild.Path, Args: args, Env: gobuild.Env})
|
||||
}
|
||||
|
|
@ -297,7 +308,13 @@ func doInstallKeeper(cmdline []string) {
|
|||
tc.GOARCH = target.GOARCH
|
||||
tc.GOOS = target.GOOS
|
||||
tc.CC = target.CC
|
||||
gobuild := tc.Go("build", buildFlags(env, true, []string{target.Tags})...)
|
||||
// An empty GOOS means "build for the host OS"; thread that through to
|
||||
// buildFlags so platform-specific linker flags are picked correctly.
|
||||
targetOS := target.GOOS
|
||||
if targetOS == "" {
|
||||
targetOS = runtime.GOOS
|
||||
}
|
||||
gobuild := tc.Go("build", buildFlags(env, true, []string{target.Tags}, targetOS)...)
|
||||
gobuild.Dir = "./cmd/keeper"
|
||||
gobuild.Args = append(gobuild.Args, "-v")
|
||||
|
||||
|
|
@ -307,14 +324,15 @@ func doInstallKeeper(cmdline []string) {
|
|||
outputName := fmt.Sprintf("keeper-%s", target.Name)
|
||||
|
||||
args := slices.Clone(gobuild.Args)
|
||||
args = append(args, "-o", executablePath(outputName))
|
||||
args = append(args, "-o", executablePath(outputName, targetOS))
|
||||
args = append(args, ".")
|
||||
build.MustRun(&exec.Cmd{Path: gobuild.Path, Args: args, Env: gobuild.Env, Dir: gobuild.Dir})
|
||||
}
|
||||
}
|
||||
|
||||
// buildFlags returns the go tool flags for building.
|
||||
func buildFlags(env build.Environment, staticLinking bool, buildTags []string) (flags []string) {
|
||||
// buildFlags returns the go tool flags for building. targetOS is the OS we
|
||||
// are producing binaries for.
|
||||
func buildFlags(env build.Environment, staticLinking bool, buildTags []string, targetOS string) (flags []string) {
|
||||
var ld []string
|
||||
// See https://github.com/golang/go/issues/33772#issuecomment-528176001
|
||||
// We need to set --buildid to the linker here, and also pass --build-id to the
|
||||
|
|
@ -326,10 +344,10 @@ func buildFlags(env build.Environment, staticLinking bool, buildTags []string) (
|
|||
}
|
||||
// Strip DWARF on darwin. This used to be required for certain things,
|
||||
// and there is no downside to this, so we just keep doing it.
|
||||
if runtime.GOOS == "darwin" {
|
||||
if targetOS == "darwin" {
|
||||
ld = append(ld, "-s")
|
||||
}
|
||||
if runtime.GOOS == "linux" {
|
||||
if targetOS == "linux" {
|
||||
// Enforce the stacksize to 8M, which is the case on most platforms apart from
|
||||
// alpine Linux.
|
||||
// See https://sourceware.org/binutils/docs-2.23.1/ld/Options.html#Options
|
||||
|
|
@ -682,12 +700,13 @@ func downloadProtoc(cachedir string) string {
|
|||
// Release Packaging
|
||||
func doArchive(cmdline []string) {
|
||||
var (
|
||||
arch = flag.String("arch", runtime.GOARCH, "Architecture cross packaging")
|
||||
atype = flag.String("type", "zip", "Type of archive to write (zip|tar)")
|
||||
signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. LINUX_SIGNING_KEY)`)
|
||||
signify = flag.String("signify", "", `Environment variable holding the signify key (e.g. LINUX_SIGNIFY_KEY)`)
|
||||
upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`)
|
||||
ext string
|
||||
targetOS = flag.String("os", runtime.GOOS, "Target OS the binaries were built for")
|
||||
arch = flag.String("arch", runtime.GOARCH, "Architecture cross packaging")
|
||||
atype = flag.String("type", "zip", "Type of archive to write (zip|tar)")
|
||||
signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. LINUX_SIGNING_KEY)`)
|
||||
signify = flag.String("signify", "", `Environment variable holding the signify key (e.g. LINUX_SIGNIFY_KEY)`)
|
||||
upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`)
|
||||
ext string
|
||||
)
|
||||
flag.CommandLine.Parse(cmdline)
|
||||
switch *atype {
|
||||
|
|
@ -701,15 +720,15 @@ func doArchive(cmdline []string) {
|
|||
|
||||
var (
|
||||
env = build.Env()
|
||||
basegeth = archiveBasename(*arch, version.Archive(env.Commit))
|
||||
basegeth = archiveBasename(*targetOS, *arch, version.Archive(env.Commit))
|
||||
geth = "geth-" + basegeth + ext
|
||||
alltools = "geth-alltools-" + basegeth + ext
|
||||
)
|
||||
maybeSkipArchive(env)
|
||||
if err := build.WriteArchive(geth, gethArchiveFiles); err != nil {
|
||||
if err := build.WriteArchive(geth, gethArchiveFiles(*targetOS)); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := build.WriteArchive(alltools, allToolsArchiveFiles); err != nil {
|
||||
if err := build.WriteArchive(alltools, allToolsArchiveFiles(*targetOS)); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
for _, archive := range []string{geth, alltools} {
|
||||
|
|
@ -735,7 +754,11 @@ func doKeeperArchive(cmdline []string) {
|
|||
maybeSkipArchive(env)
|
||||
files := []string{"COPYING"}
|
||||
for _, target := range keeperTargets {
|
||||
files = append(files, executablePath(fmt.Sprintf("keeper-%s", target.Name)))
|
||||
targetOS := target.GOOS
|
||||
if targetOS == "" {
|
||||
targetOS = runtime.GOOS
|
||||
}
|
||||
files = append(files, executablePath(fmt.Sprintf("keeper-%s", target.Name), targetOS))
|
||||
}
|
||||
if err := build.WriteArchive(keeper, files); err != nil {
|
||||
log.Fatal(err)
|
||||
|
|
@ -745,8 +768,8 @@ func doKeeperArchive(cmdline []string) {
|
|||
}
|
||||
}
|
||||
|
||||
func archiveBasename(arch string, archiveVersion string) string {
|
||||
platform := runtime.GOOS + "-" + arch
|
||||
func archiveBasename(targetOS, arch, archiveVersion string) string {
|
||||
platform := targetOS + "-" + arch
|
||||
if arch == "arm" {
|
||||
platform += os.Getenv("GOARM")
|
||||
}
|
||||
|
|
@ -1209,13 +1232,13 @@ func doWindowsInstaller(cmdline []string) {
|
|||
env := build.Env()
|
||||
maybeSkipArchive(env)
|
||||
|
||||
// Aggregate binaries that are included in the installer
|
||||
// Aggregate binaries that are included in the installer.
|
||||
var (
|
||||
devTools []string
|
||||
allTools []string
|
||||
gethTool string
|
||||
)
|
||||
for _, file := range allToolsArchiveFiles {
|
||||
for _, file := range allToolsArchiveFiles("windows") {
|
||||
if file == "COPYING" { // license, copied later
|
||||
continue
|
||||
}
|
||||
|
|
@ -1252,16 +1275,24 @@ func doWindowsInstaller(cmdline []string) {
|
|||
if env.Commit != "" {
|
||||
ver[2] += "-" + env.Commit[:8]
|
||||
}
|
||||
installer, err := filepath.Abs("geth-" + archiveBasename(*arch, version.Archive(env.Commit)) + ".exe")
|
||||
installer, err := filepath.Abs("geth-" + archiveBasename("windows", *arch, version.Archive(env.Commit)) + ".exe")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to convert installer file path: %v", err)
|
||||
}
|
||||
build.MustRunCommand("makensis.exe",
|
||||
"/DOUTPUTFILE="+installer,
|
||||
"/DMAJORVERSION="+ver[0],
|
||||
"/DMINORVERSION="+ver[1],
|
||||
"/DBUILDVERSION="+ver[2],
|
||||
"/DARCH="+*arch,
|
||||
// makensis on Windows is "makensis.exe" with /D-style defines; on Linux
|
||||
// (and other Unixes) the binary is "makensis" and accepts -D.
|
||||
makensisCmd := "makensis"
|
||||
defineFlag := "-D"
|
||||
if runtime.GOOS == "windows" {
|
||||
makensisCmd = "makensis.exe"
|
||||
defineFlag = "/D"
|
||||
}
|
||||
build.MustRunCommand(makensisCmd,
|
||||
defineFlag+"OUTPUTFILE="+installer,
|
||||
defineFlag+"MAJORVERSION="+ver[0],
|
||||
defineFlag+"MINORVERSION="+ver[1],
|
||||
defineFlag+"BUILDVERSION="+ver[2],
|
||||
defineFlag+"ARCH="+*arch,
|
||||
filepath.Join(*workdir, "geth.nsi"),
|
||||
)
|
||||
// Sign and publish installer.
|
||||
|
|
|
|||
|
|
@ -215,7 +215,7 @@ func generate(c *cli.Context) error {
|
|||
code string
|
||||
err error
|
||||
)
|
||||
if c.IsSet(v2Flag.Name) {
|
||||
if c.Bool(v2Flag.Name) {
|
||||
code, err = abigen.BindV2(types, abis, bins, c.String(pkgFlag.Name), libs, aliases)
|
||||
} else {
|
||||
code, err = abigen.Bind(types, abis, bins, sigs, c.String(pkgFlag.Name), libs, aliases)
|
||||
|
|
|
|||
|
|
@ -194,7 +194,7 @@ func formatAttrString(v rlp.RawValue) (string, bool) {
|
|||
|
||||
func formatAttrIP(v rlp.RawValue) (string, bool) {
|
||||
content, _, err := rlp.SplitString(v)
|
||||
if err != nil || len(content) != 4 && len(content) != 6 {
|
||||
if err != nil || len(content) != 4 && len(content) != 16 {
|
||||
return "", false
|
||||
}
|
||||
return net.IP(content).String(), true
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package t8ntool
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
stdmath "math"
|
||||
|
|
@ -34,6 +35,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/crypto/keccak"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
|
|
@ -171,6 +173,9 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
|||
includedTxs types.Transactions
|
||||
blobGasUsed = uint64(0)
|
||||
receipts = make(types.Receipts, 0)
|
||||
|
||||
// TODO return blockAccessList as a part of result
|
||||
blockAccessList = bal.NewConstructionBlockAccessList()
|
||||
)
|
||||
vmContext := vm.BlockContext{
|
||||
CanTransfer: core.CanTransfer,
|
||||
|
|
@ -230,14 +235,14 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
|||
}
|
||||
evm := vm.NewEVM(vmContext, statedb, chainConfig, vmConfig)
|
||||
if beaconRoot := pre.Env.ParentBeaconBlockRoot; beaconRoot != nil {
|
||||
core.ProcessBeaconBlockRoot(*beaconRoot, evm)
|
||||
core.ProcessBeaconBlockRoot(*beaconRoot, evm, blockAccessList)
|
||||
}
|
||||
if pre.Env.BlockHashes != nil && chainConfig.IsPrague(new(big.Int).SetUint64(pre.Env.Number), pre.Env.Timestamp) {
|
||||
var (
|
||||
prevNumber = pre.Env.Number - 1
|
||||
prevHash = pre.Env.BlockHashes[math.HexOrDecimal64(prevNumber)]
|
||||
)
|
||||
core.ProcessParentBlockHash(prevHash, evm)
|
||||
core.ProcessParentBlockHash(prevHash, evm, blockAccessList)
|
||||
}
|
||||
for i := 0; txIt.Next(); i++ {
|
||||
tx, err := txIt.Tx()
|
||||
|
|
@ -269,12 +274,13 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
|||
continue
|
||||
}
|
||||
}
|
||||
statedb.SetTxContext(tx.Hash(), len(receipts))
|
||||
statedb.SetTxContext(tx.Hash(), len(receipts), uint32(len(receipts)+1))
|
||||
|
||||
var (
|
||||
snapshot = statedb.Snapshot()
|
||||
gp = gaspool.Snapshot()
|
||||
)
|
||||
receipt, err := core.ApplyTransactionWithEVM(msg, gaspool, statedb, vmContext.BlockNumber, blockHash, pre.Env.Timestamp, tx, evm)
|
||||
receipt, bal, err := core.ApplyTransactionWithEVM(msg, gaspool, statedb, vmContext.BlockNumber, blockHash, pre.Env.Timestamp, tx, evm)
|
||||
if err != nil {
|
||||
statedb.RevertToSnapshot(snapshot)
|
||||
log.Info("rejected tx", "index", i, "hash", tx.Hash(), "from", msg.From, "error", err)
|
||||
|
|
@ -291,6 +297,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
|||
}
|
||||
blobGasUsed += txBlobGas
|
||||
receipts = append(receipts, receipt)
|
||||
blockAccessList.Merge(bal)
|
||||
}
|
||||
|
||||
statedb.IntermediateRoot(chainConfig.IsEIP158(vmContext.BlockNumber))
|
||||
|
|
@ -331,26 +338,15 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
|||
}
|
||||
|
||||
// Gather the execution-layer triggered requests.
|
||||
var requests [][]byte
|
||||
if chainConfig.IsPrague(vmContext.BlockNumber, vmContext.Time) {
|
||||
requests = [][]byte{}
|
||||
// EIP-6110
|
||||
var allLogs []*types.Log
|
||||
for _, receipt := range receipts {
|
||||
allLogs = append(allLogs, receipt.Logs...)
|
||||
}
|
||||
if err := core.ParseDepositLogs(&requests, allLogs, chainConfig); err != nil {
|
||||
return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not parse requests logs: %v", err))
|
||||
}
|
||||
// EIP-7002
|
||||
if err := core.ProcessWithdrawalQueue(&requests, evm); err != nil {
|
||||
return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not process withdrawal requests: %v", err))
|
||||
}
|
||||
// EIP-7251
|
||||
if err := core.ProcessConsolidationQueue(&requests, evm); err != nil {
|
||||
return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not process consolidation requests: %v", err))
|
||||
}
|
||||
var allLogs []*types.Log
|
||||
for _, receipt := range receipts {
|
||||
allLogs = append(allLogs, receipt.Logs...)
|
||||
}
|
||||
requests, bal, err := core.PostExecution(context.Background(), chainConfig, vmContext.BlockNumber, vmContext.Time, allLogs, evm, uint32(len(receipts)+1))
|
||||
if err != nil {
|
||||
return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("failed to process post-execution: %v", err))
|
||||
}
|
||||
blockAccessList.Merge(bal)
|
||||
|
||||
// Commit block
|
||||
root, err := statedb.Commit(vmContext.BlockNumber.Uint64(), chainConfig.IsEIP158(vmContext.BlockNumber), chainConfig.IsCancun(vmContext.BlockNumber, vmContext.Time))
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ func Transaction(ctx *cli.Context) error {
|
|||
}
|
||||
// Check intrinsic gas
|
||||
rules := chainConfig.Rules(common.Big0, true, 0)
|
||||
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai)
|
||||
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai, rules.IsAmsterdam)
|
||||
if err != nil {
|
||||
r.Error = err
|
||||
results = append(results, r)
|
||||
|
|
@ -147,7 +147,7 @@ func Transaction(ctx *cli.Context) error {
|
|||
}
|
||||
// For Prague txs, validate the floor data gas.
|
||||
if rules.IsPrague {
|
||||
floorDataGas, err := core.FloorDataGas(rules, tx.Data())
|
||||
floorDataGas, err := core.FloorDataGas(rules, tx.Data(), tx.AccessList())
|
||||
if err != nil {
|
||||
r.Error = err
|
||||
results = append(results, r)
|
||||
|
|
|
|||
|
|
@ -546,7 +546,7 @@ func BinKeys(ctx *cli.Context) error {
|
|||
db := triedb.NewDatabase(rawdb.NewMemoryDatabase(), triedb.UBTDefaults)
|
||||
defer db.Close()
|
||||
|
||||
bt, err := genBinTrieFromAlloc(alloc, db)
|
||||
bt, err := genBinTrieFromAlloc(alloc, db, triedb.UBTDefaults.BinTrieGroupDepth)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error generating bt: %w", err)
|
||||
}
|
||||
|
|
@ -590,7 +590,7 @@ func BinTrieRoot(ctx *cli.Context) error {
|
|||
db := triedb.NewDatabase(rawdb.NewMemoryDatabase(), triedb.UBTDefaults)
|
||||
defer db.Close()
|
||||
|
||||
bt, err := genBinTrieFromAlloc(alloc, db)
|
||||
bt, err := genBinTrieFromAlloc(alloc, db, triedb.UBTDefaults.BinTrieGroupDepth)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error generating bt: %w", err)
|
||||
}
|
||||
|
|
@ -600,8 +600,8 @@ func BinTrieRoot(ctx *cli.Context) error {
|
|||
}
|
||||
|
||||
// TODO(@CPerezz): Should this go to `bintrie` module?
|
||||
func genBinTrieFromAlloc(alloc core.GenesisAlloc, db database.NodeDatabase) (*bintrie.BinaryTrie, error) {
|
||||
bt, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, db)
|
||||
func genBinTrieFromAlloc(alloc core.GenesisAlloc, db database.NodeDatabase, groupDepth int) (*bintrie.BinaryTrie, error) {
|
||||
bt, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, db, groupDepth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -321,7 +321,7 @@ func runCmd(ctx *cli.Context) error {
|
|||
// don't mutate the state!
|
||||
runtimeConfig.State = prestate.Copy()
|
||||
output, _, gasLeft, err := runtime.Create(input, &runtimeConfig)
|
||||
return output, gasLeft, err
|
||||
return output, initialGas - gasLeft, err
|
||||
}
|
||||
} else {
|
||||
if len(code) > 0 {
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ func convertToBinaryTrie(ctx *cli.Context) error {
|
|||
})
|
||||
defer destTriedb.Close()
|
||||
|
||||
binTrie, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, destTriedb)
|
||||
binTrie, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, destTriedb, ctx.Int(utils.BinTrieGroupDepthFlag.Name))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create binary trie: %w", err)
|
||||
}
|
||||
|
|
@ -319,7 +319,7 @@ func commitBinaryTrie(bt *bintrie.BinaryTrie, currentRoot common.Hash, destDB *t
|
|||
runtime.GC()
|
||||
debug.FreeOSMemory()
|
||||
|
||||
bt, err := bintrie.NewBinaryTrie(newRoot, destDB)
|
||||
bt, err := bintrie.NewBinaryTrie(newRoot, destDB, bt.GroupDepth())
|
||||
if err != nil {
|
||||
return nil, common.Hash{}, fmt.Errorf("failed to reload binary trie: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ func TestBintrieConvert(t *testing.T) {
|
|||
})
|
||||
defer destTriedb.Close()
|
||||
|
||||
bt, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, destTriedb)
|
||||
bt, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, destTriedb, 8)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create binary trie: %v", err)
|
||||
}
|
||||
|
|
@ -98,7 +98,7 @@ func TestBintrieConvert(t *testing.T) {
|
|||
}
|
||||
t.Logf("Binary trie root: %x", currentRoot)
|
||||
|
||||
bt2, err := bintrie.NewBinaryTrie(currentRoot, destTriedb)
|
||||
bt2, err := bintrie.NewBinaryTrie(currentRoot, destTriedb, 8)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to reload binary trie: %v", err)
|
||||
}
|
||||
|
|
@ -194,7 +194,7 @@ func TestBintrieConvertDeleteSource(t *testing.T) {
|
|||
PathDB: pathdb.Defaults,
|
||||
})
|
||||
|
||||
bt, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, destTriedb)
|
||||
bt, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, destTriedb, 8)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create binary trie: %v", err)
|
||||
}
|
||||
|
|
@ -209,7 +209,7 @@ func TestBintrieConvertDeleteSource(t *testing.T) {
|
|||
}
|
||||
srcTriedb2.Close()
|
||||
|
||||
bt2, err := bintrie.NewBinaryTrie(newRoot, destTriedb)
|
||||
bt2, err := bintrie.NewBinaryTrie(newRoot, destTriedb, 8)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to reload binary trie after deletion: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -325,7 +325,7 @@ func dumpGenesis(ctx *cli.Context) error {
|
|||
var genesis *core.Genesis
|
||||
if utils.IsNetworkPreset(ctx) {
|
||||
genesis = utils.MakeGenesis(ctx)
|
||||
} else if ctx.IsSet(utils.DeveloperFlag.Name) && !ctx.IsSet(utils.DataDirFlag.Name) {
|
||||
} else if ctx.Bool(utils.DeveloperFlag.Name) && !ctx.IsSet(utils.DataDirFlag.Name) {
|
||||
genesis = core.DeveloperGenesisBlock(11_500_000, nil)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/eth/catalyst"
|
||||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||
"github.com/ethereum/go-ethereum/eth/syncer"
|
||||
"github.com/ethereum/go-ethereum/internal/flags"
|
||||
"github.com/ethereum/go-ethereum/internal/telemetry/tracesetup"
|
||||
"github.com/ethereum/go-ethereum/internal/version"
|
||||
|
|
@ -269,25 +270,28 @@ func makeFullNode(ctx *cli.Context) *node.Node {
|
|||
filterSystem := utils.RegisterFilterAPI(stack, backend, &cfg.Eth)
|
||||
|
||||
// Configure GraphQL if requested.
|
||||
if ctx.IsSet(utils.GraphQLEnabledFlag.Name) {
|
||||
if ctx.Bool(utils.GraphQLEnabledFlag.Name) {
|
||||
utils.RegisterGraphQLService(stack, backend, filterSystem, &cfg.Node)
|
||||
}
|
||||
// Add the Ethereum Stats daemon if requested.
|
||||
if cfg.Ethstats.URL != "" {
|
||||
utils.RegisterEthStatsService(stack, backend, cfg.Ethstats.URL)
|
||||
}
|
||||
|
||||
// Configure synchronization override service
|
||||
var synctarget common.Hash
|
||||
syncConfig := syncer.Config{
|
||||
ExitWhenSynced: ctx.Bool(utils.ExitWhenSyncedFlag.Name),
|
||||
}
|
||||
if ctx.IsSet(utils.SyncTargetFlag.Name) {
|
||||
target := ctx.String(utils.SyncTargetFlag.Name)
|
||||
if !common.IsHexHash(target) {
|
||||
utils.Fatalf("sync target hash is not a valid hex hash: %s", target)
|
||||
}
|
||||
synctarget = common.HexToHash(target)
|
||||
syncConfig.TargetBlock = common.HexToHash(target)
|
||||
}
|
||||
utils.RegisterSyncOverrideService(stack, eth, synctarget, ctx.Bool(utils.ExitWhenSyncedFlag.Name))
|
||||
utils.RegisterSyncOverrideService(stack, eth, syncConfig)
|
||||
|
||||
if ctx.IsSet(utils.DeveloperFlag.Name) {
|
||||
if ctx.Bool(utils.DeveloperFlag.Name) {
|
||||
// Start dev mode.
|
||||
simBeacon, err := catalyst.NewSimulatedBeacon(ctx.Uint64(utils.DeveloperPeriodFlag.Name), cfg.Eth.Miner.PendingFeeRecipient, eth)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -22,13 +22,10 @@ import (
|
|||
"os"
|
||||
"slices"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/console/prompt"
|
||||
"github.com/ethereum/go-ethereum/eth/downloader"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
"github.com/ethereum/go-ethereum/internal/debug"
|
||||
"github.com/ethereum/go-ethereum/internal/flags"
|
||||
|
|
@ -95,6 +92,7 @@ var (
|
|||
utils.StateHistoryFlag,
|
||||
utils.TrienodeHistoryFlag,
|
||||
utils.TrienodeHistoryFullValueCheckpointFlag,
|
||||
utils.BinTrieGroupDepthFlag,
|
||||
utils.LightKDFFlag,
|
||||
utils.EthRequiredBlocksFlag,
|
||||
utils.LegacyWhitelistFlag, // deprecated
|
||||
|
|
@ -386,28 +384,4 @@ func startNode(ctx *cli.Context, stack *node.Node, isConsole bool) {
|
|||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Spawn a standalone goroutine for status synchronization monitoring,
|
||||
// close the node when synchronization is complete if user required.
|
||||
if ctx.Bool(utils.ExitWhenSyncedFlag.Name) {
|
||||
go func() {
|
||||
sub := stack.EventMux().Subscribe(downloader.DoneEvent{})
|
||||
defer sub.Unsubscribe()
|
||||
for {
|
||||
event := <-sub.Chan()
|
||||
if event == nil {
|
||||
continue
|
||||
}
|
||||
done, ok := event.Data.(downloader.DoneEvent)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if timestamp := time.Unix(int64(done.Latest.Time), 0); time.Since(timestamp) < 10*time.Minute {
|
||||
log.Info("Synchronisation completed", "latestnum", done.Latest.Number, "latesthash", done.Latest.Hash(),
|
||||
"age", common.PrettyAge(timestamp))
|
||||
stack.Close()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -297,6 +297,12 @@ var (
|
|||
Value: ethconfig.Defaults.EnableStateSizeTracking,
|
||||
Category: flags.StateCategory,
|
||||
}
|
||||
BinTrieGroupDepthFlag = &cli.IntFlag{
|
||||
Name: "bintrie.groupdepth",
|
||||
Usage: "Number of levels per serialized group in binary trie (1-8, default 5). Lower values create smaller groups with more nodes.",
|
||||
Value: 5,
|
||||
Category: flags.StateCategory,
|
||||
}
|
||||
StateHistoryFlag = &cli.Uint64Flag{
|
||||
Name: "history.state",
|
||||
Usage: "Number of recent blocks to retain state history for, only relevant in state.scheme=path (default = 90,000 blocks, 0 = entire chain)",
|
||||
|
|
@ -1098,7 +1104,7 @@ Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server.
|
|||
RPCTelemetrySampleRatioFlag = &cli.Float64Flag{
|
||||
Name: "rpc.telemetry.sample-ratio",
|
||||
Usage: "Defines the sampling ratio for RPC telemetry (0.0 to 1.0)",
|
||||
Value: 1.0,
|
||||
Value: node.DefaultConfig.OpenTelemetry.SampleRatio,
|
||||
Category: flags.APICategory,
|
||||
}
|
||||
// Era flags are a group of flags related to the era archive format.
|
||||
|
|
@ -1817,6 +1823,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
|
|||
if ctx.IsSet(TrienodeHistoryFullValueCheckpointFlag.Name) {
|
||||
cfg.NodeFullValueCheckpoint = uint32(ctx.Uint(TrienodeHistoryFullValueCheckpointFlag.Name))
|
||||
}
|
||||
if ctx.IsSet(BinTrieGroupDepthFlag.Name) {
|
||||
cfg.BinTrieGroupDepth = ctx.Int(BinTrieGroupDepthFlag.Name)
|
||||
}
|
||||
if ctx.IsSet(StateSchemeFlag.Name) {
|
||||
cfg.StateScheme = ctx.String(StateSchemeFlag.Name)
|
||||
}
|
||||
|
|
@ -2228,13 +2237,13 @@ func RegisterFilterAPI(stack *node.Node, backend ethapi.Backend, ethcfg *ethconf
|
|||
}
|
||||
|
||||
// RegisterSyncOverrideService adds the synchronization override service into node.
|
||||
func RegisterSyncOverrideService(stack *node.Node, eth *eth.Ethereum, target common.Hash, exitWhenSynced bool) {
|
||||
if target != (common.Hash{}) {
|
||||
log.Info("Registered sync override service", "hash", target, "exitWhenSynced", exitWhenSynced)
|
||||
func RegisterSyncOverrideService(stack *node.Node, eth *eth.Ethereum, config syncer.Config) {
|
||||
if config.TargetBlock != (common.Hash{}) {
|
||||
log.Info("Registered sync override service", "hash", config.TargetBlock, "exitWhenSynced", config.ExitWhenSynced)
|
||||
} else {
|
||||
log.Info("Registered sync override service")
|
||||
}
|
||||
syncer.Register(stack, eth, target, exitWhenSynced)
|
||||
syncer.Register(stack, eth, config)
|
||||
}
|
||||
|
||||
// SetupMetrics configures the metrics system.
|
||||
|
|
@ -2433,6 +2442,7 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh
|
|||
StateHistory: ctx.Uint64(StateHistoryFlag.Name),
|
||||
TrienodeHistory: ctx.Int64(TrienodeHistoryFlag.Name),
|
||||
NodeFullValueCheckpoint: uint32(ctx.Uint(TrienodeHistoryFullValueCheckpointFlag.Name)),
|
||||
BinTrieGroupDepth: ctx.Int(BinTrieGroupDepthFlag.Name),
|
||||
|
||||
// Disable transaction indexing/unindexing.
|
||||
TxLookupLimit: -1,
|
||||
|
|
|
|||
|
|
@ -204,6 +204,10 @@ func (b *Big) ToInt() *big.Int {
|
|||
return (*big.Int)(b)
|
||||
}
|
||||
|
||||
func (b *Big) ToUint256() (*uint256.Int, bool) {
|
||||
return uint256.FromBig((*big.Int)(b))
|
||||
}
|
||||
|
||||
// String returns the hex encoding of b.
|
||||
func (b *Big) String() string {
|
||||
return EncodeBig(b.ToInt())
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
|
||||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/holiman/uint256"
|
||||
|
|
@ -342,9 +343,9 @@ func (beacon *Beacon) Prepare(chain consensus.ChainHeaderReader, header *types.H
|
|||
}
|
||||
|
||||
// Finalize implements consensus.Engine and processes withdrawals on top.
|
||||
func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state vm.StateDB, body *types.Body) {
|
||||
func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state vm.StateDB, body *types.Body, blockAccessIndex uint32, bal *bal.ConstructionBlockAccessList) {
|
||||
if !beacon.IsPoSHeader(header) {
|
||||
beacon.ethone.Finalize(chain, header, state, body)
|
||||
beacon.ethone.Finalize(chain, header, state, body, blockAccessIndex, bal)
|
||||
return
|
||||
}
|
||||
// Withdrawals processing.
|
||||
|
|
@ -352,7 +353,20 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types.
|
|||
// Convert amount from gwei to wei.
|
||||
amount := new(uint256.Int).SetUint64(w.Amount)
|
||||
amount = amount.Mul(amount, uint256.NewInt(params.GWei))
|
||||
state.AddBalance(w.Address, amount, tracing.BalanceIncreaseWithdrawal)
|
||||
prev := state.AddBalance(w.Address, amount, tracing.BalanceIncreaseWithdrawal)
|
||||
|
||||
// Populate the block-level accessList if Amsterdam is enabled
|
||||
if chain.Config().IsAmsterdam(header.Number, header.Time) {
|
||||
if w.Amount == 0 {
|
||||
// Zero amount withdrawal, account is accessed potential
|
||||
// without state changes.
|
||||
bal.AccountRead(w.Address)
|
||||
} else {
|
||||
// Non-zero amount withdrawal, account is accessed with
|
||||
// a balance change.
|
||||
bal.BalanceChange(blockAccessIndex, w.Address, new(uint256.Int).Add(&prev, amount))
|
||||
}
|
||||
}
|
||||
}
|
||||
// No block reward which is issued by consensus layer instead.
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/consensus/misc"
|
||||
"github.com/ethereum/go-ethereum/consensus/misc/eip1559"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/crypto/keccak"
|
||||
|
|
@ -573,7 +574,7 @@ func (c *Clique) Prepare(chain consensus.ChainHeaderReader, header *types.Header
|
|||
|
||||
// Finalize implements consensus.Engine. There is no post-transaction
|
||||
// consensus rules in clique, do nothing here.
|
||||
func (c *Clique) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state vm.StateDB, body *types.Body) {
|
||||
func (c *Clique) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state vm.StateDB, body *types.Body, blockAccessIndex uint32, bal *bal.ConstructionBlockAccessList) {
|
||||
// No block rewards in PoA, so the state remains as is
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import (
|
|||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
|
@ -79,12 +80,12 @@ type Engine interface {
|
|||
// rules of a particular engine. The changes are executed inline.
|
||||
Prepare(chain ChainHeaderReader, header *types.Header) error
|
||||
|
||||
// Finalize runs any post-transaction state modifications (e.g. block rewards
|
||||
// or process withdrawals) but does not assemble the block.
|
||||
// Finalize runs any post-transaction consensus-specific state modifications
|
||||
// (e.g. block rewards or process withdrawals) but does not assemble the block.
|
||||
//
|
||||
// Note: The state database might be updated to reflect any consensus rules
|
||||
// that happen at finalization (e.g. block rewards).
|
||||
Finalize(chain ChainHeaderReader, header *types.Header, state vm.StateDB, body *types.Body)
|
||||
Finalize(chain ChainHeaderReader, header *types.Header, state vm.StateDB, body *types.Body, blockAccessIndex uint32, bal *bal.ConstructionBlockAccessList)
|
||||
|
||||
// Seal generates a new sealing request for the given input block and pushes
|
||||
// the result into the given channel.
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/consensus/misc/eip1559"
|
||||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/crypto/keccak"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
|
|
@ -504,7 +505,7 @@ func (ethash *Ethash) Prepare(chain consensus.ChainHeaderReader, header *types.H
|
|||
}
|
||||
|
||||
// Finalize implements consensus.Engine, accumulating the block and uncle rewards.
|
||||
func (ethash *Ethash) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state vm.StateDB, body *types.Body) {
|
||||
func (ethash *Ethash) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state vm.StateDB, body *types.Body, blockAccessIndex uint32, bal *bal.ConstructionBlockAccessList) {
|
||||
// Accumulate any block and uncle rewards
|
||||
accumulateRewards(chain.Config(), state, header, body.Uncles)
|
||||
}
|
||||
|
|
|
|||
1319
core/bal_test.go
Normal file
1319
core/bal_test.go
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -89,7 +89,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) {
|
|||
data := make([]byte, nbytes)
|
||||
return func(i int, gen *BlockGen) {
|
||||
toaddr := common.Address{}
|
||||
cost, _ := IntrinsicGas(data, nil, nil, false, false, false, false)
|
||||
cost, _ := IntrinsicGas(data, nil, nil, false, false, false, false, false)
|
||||
signer := gen.Signer()
|
||||
gasPrice := big.NewInt(0)
|
||||
if gen.header.BaseFee != nil {
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
|
|
@ -63,12 +64,12 @@ var (
|
|||
func TestProcessUBT(t *testing.T) {
|
||||
var (
|
||||
code = common.FromHex(`6060604052600a8060106000396000f360606040526008565b00`)
|
||||
intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, nil, true, true, true, true)
|
||||
intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, nil, true, true, true, true, false)
|
||||
// A contract creation that calls EXTCODECOPY in the constructor. Used to ensure that the witness
|
||||
// will not contain that copied data.
|
||||
// Source: https://gist.github.com/gballet/a23db1e1cb4ed105616b5920feb75985
|
||||
codeWithExtCodeCopy = common.FromHex(`0x60806040526040516100109061017b565b604051809103906000f08015801561002c573d6000803e3d6000fd5b506000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561007857600080fd5b5060008067ffffffffffffffff8111156100955761009461024a565b5b6040519080825280601f01601f1916602001820160405280156100c75781602001600182028036833780820191505090505b50905060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690506020600083833c81610101906101e3565b60405161010d90610187565b61011791906101a3565b604051809103906000f080158015610133573d6000803e3d6000fd5b50600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550505061029b565b60d58061046783390190565b6102068061053c83390190565b61019d816101d9565b82525050565b60006020820190506101b86000830184610194565b92915050565b6000819050602082019050919050565b600081519050919050565b6000819050919050565b60006101ee826101ce565b826101f8846101be565b905061020381610279565b925060208210156102435761023e7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8360200360080261028e565b831692505b5050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600061028582516101d9565b80915050919050565b600082821b905092915050565b6101bd806102aa6000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f566852414610030575b600080fd5b61003861004e565b6040516100459190610146565b60405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166381ca91d36040518163ffffffff1660e01b815260040160206040518083038186803b1580156100b857600080fd5b505afa1580156100cc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100f0919061010a565b905090565b60008151905061010481610170565b92915050565b6000602082840312156101205761011f61016b565b5b600061012e848285016100f5565b91505092915050565b61014081610161565b82525050565b600060208201905061015b6000830184610137565b92915050565b6000819050919050565b600080fd5b61017981610161565b811461018457600080fd5b5056fea2646970667358221220a6a0e11af79f176f9c421b7b12f441356b25f6489b83d38cc828a701720b41f164736f6c63430008070033608060405234801561001057600080fd5b5060b68061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063ab5ed15014602d575b600080fd5b60336047565b604051603e9190605d565b60405180910390f35b60006001905090565b6057816076565b82525050565b6000602082019050607060008301846050565b92915050565b600081905091905056fea26469706673582212203a14eb0d5cd07c277d3e24912f110ddda3e553245a99afc4eeefb2fbae5327aa64736f6c63430008070033608060405234801561001057600080fd5b5060405161020638038061020683398181016040528101906100329190610063565b60018160001c6100429190610090565b60008190555050610145565b60008151905061005d8161012e565b92915050565b60006020828403121561007957610078610129565b5b60006100878482850161004e565b91505092915050565b600061009b826100f0565b91506100a6836100f0565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156100db576100da6100fa565b5b828201905092915050565b6000819050919050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600080fd5b610137816100e6565b811461014257600080fd5b50565b60b3806101536000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806381ca91d314602d575b600080fd5b60336047565b604051603e9190605a565b60405180910390f35b60005481565b6054816073565b82525050565b6000602082019050606d6000830184604d565b92915050565b600081905091905056fea26469706673582212209bff7098a2f526de1ad499866f27d6d0d6f17b74a413036d6063ca6a0998ca4264736f6c63430008070033`)
|
||||
intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, nil, true, true, true, true)
|
||||
intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, nil, true, true, true, true, false)
|
||||
signer = types.LatestSigner(testUBTChainConfig)
|
||||
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
bcdb = rawdb.NewMemoryDatabase() // Database for the blockchain
|
||||
|
|
@ -92,6 +93,7 @@ func TestProcessUBT(t *testing.T) {
|
|||
// genesis := gspec.MustCommit(bcdb, triedb)
|
||||
options := DefaultConfig().WithStateScheme(rawdb.PathScheme)
|
||||
options.SnapshotLimit = 0
|
||||
options.BinTrieGroupDepth = triedb.DefaultBinTrieGroupDepth
|
||||
blockchain, _ := NewBlockChain(bcdb, gspec, beacon.New(ethash.NewFaker()), options)
|
||||
defer blockchain.Stop()
|
||||
|
||||
|
|
@ -201,7 +203,7 @@ func TestProcessParentBlockHash(t *testing.T) {
|
|||
}
|
||||
vmContext := NewEVMBlockContext(header, nil, new(common.Address))
|
||||
evm := vm.NewEVM(vmContext, statedb, chainConfig, vm.Config{})
|
||||
ProcessParentBlockHash(header.ParentHash, evm)
|
||||
ProcessParentBlockHash(header.ParentHash, evm, bal.NewConstructionBlockAccessList())
|
||||
}
|
||||
// Read block hashes for block 0 .. num-1
|
||||
for i := 0; i < num; i++ {
|
||||
|
|
@ -218,6 +220,7 @@ func TestProcessParentBlockHash(t *testing.T) {
|
|||
t.Run("UBT", func(t *testing.T) {
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
cacheConfig := DefaultConfig().WithStateScheme(rawdb.PathScheme)
|
||||
cacheConfig.BinTrieGroupDepth = triedb.DefaultBinTrieGroupDepth
|
||||
cacheConfig.SnapshotLimit = 0
|
||||
triedb := triedb.NewDatabase(db, cacheConfig.triedbConfig(true))
|
||||
statedb, _ := state.New(types.EmptyBinaryHash, state.NewDatabase(triedb, nil))
|
||||
|
|
|
|||
|
|
@ -111,6 +111,28 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
|
|||
}
|
||||
}
|
||||
|
||||
// Block access list hash must be present in header after the
|
||||
// Amsterdam hard fork.
|
||||
if v.config.IsAmsterdam(block.Number(), block.Time()) {
|
||||
if block.Header().BlockAccessListHash == nil {
|
||||
return errors.New("block access list hash not set in header")
|
||||
}
|
||||
// If the block does not include an access list, compute it locally during
|
||||
// execution and validate it against the access list hash in the header.
|
||||
//
|
||||
// If the block includes an attached access list, validate it directly here.
|
||||
if block.AccessList() != nil {
|
||||
computed := block.AccessList().Hash()
|
||||
if *block.Header().BlockAccessListHash != computed {
|
||||
return fmt.Errorf("access list hash mismatch, computed: %x, remote: %x", computed, *block.Header().BlockAccessListHash)
|
||||
} else if err := block.AccessList().Validate(block.GasLimit()); err != nil {
|
||||
return fmt.Errorf("invalid block access list: %v", err)
|
||||
}
|
||||
}
|
||||
} else if block.Header().BlockAccessListHash != nil || block.AccessList() != nil {
|
||||
return errors.New("block had access list before Amsterdam")
|
||||
}
|
||||
|
||||
// Ancestor block must be known.
|
||||
if !v.bc.HasBlockAndState(block.ParentHash(), block.NumberU64()-1) {
|
||||
if !v.bc.HasBlock(block.ParentHash(), block.NumberU64()-1) {
|
||||
|
|
@ -160,6 +182,23 @@ func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateD
|
|||
} else if res.Requests != nil {
|
||||
return errors.New("block has requests before prague fork")
|
||||
}
|
||||
// Verify Block-level accessList once Amsterdam is enabled
|
||||
if v.config.IsAmsterdam(block.Number(), block.Time()) {
|
||||
if res.Bal == nil {
|
||||
return errors.New("block access list is not available in amsterdam")
|
||||
}
|
||||
if block.Header().BlockAccessListHash == nil {
|
||||
return errors.New("block access list hash not set in header")
|
||||
}
|
||||
enc := res.Bal.ToEncodingObj()
|
||||
local, remote := enc.Hash(), *block.Header().BlockAccessListHash
|
||||
if local != remote {
|
||||
return fmt.Errorf("access list hash mismatch, local: %x, remote: %x", local, remote)
|
||||
}
|
||||
if err := enc.Validate(block.GasLimit()); err != nil {
|
||||
return fmt.Errorf("invalid block access list: %v", err)
|
||||
}
|
||||
}
|
||||
// Validate the state root against the received state root and throw
|
||||
// an error if they don't match.
|
||||
if root := statedb.IntermediateRoot(v.config.IsEIP158(header.Number)); header.Root != root {
|
||||
|
|
|
|||
|
|
@ -170,9 +170,10 @@ type BlockChainConfig struct {
|
|||
TrieNoAsyncFlush bool // Whether the asynchronous buffer flushing is disallowed
|
||||
TrieJournalDirectory string // Directory path to the journal used for persisting trie data across node restarts
|
||||
|
||||
Preimages bool // Whether to store preimage of trie key to the disk
|
||||
StateScheme string // Scheme used to store ethereum states and merkle tree nodes on top
|
||||
ArchiveMode bool // Whether to enable the archive mode
|
||||
Preimages bool // Whether to store preimage of trie key to the disk
|
||||
StateScheme string // Scheme used to store ethereum states and merkle tree nodes on top
|
||||
ArchiveMode bool // Whether to enable the archive mode
|
||||
BinTrieGroupDepth int // Number of levels per serialized group in binary trie (1-8)
|
||||
|
||||
// Number of blocks from the chain head for which state histories are retained.
|
||||
// If set to 0, all state histories across the entire chain will be retained;
|
||||
|
|
@ -260,8 +261,9 @@ func (cfg BlockChainConfig) WithNoAsyncFlush(on bool) *BlockChainConfig {
|
|||
// triedbConfig derives the configures for trie database.
|
||||
func (cfg *BlockChainConfig) triedbConfig(isUBT bool) *triedb.Config {
|
||||
config := &triedb.Config{
|
||||
Preimages: cfg.Preimages,
|
||||
IsUBT: isUBT,
|
||||
Preimages: cfg.Preimages,
|
||||
IsUBT: isUBT,
|
||||
BinTrieGroupDepth: cfg.BinTrieGroupDepth,
|
||||
}
|
||||
if cfg.StateScheme == rawdb.HashScheme {
|
||||
config.HashDB = &hashdb.Config{
|
||||
|
|
@ -1184,6 +1186,7 @@ func (bc *BlockChain) SnapSyncComplete(hash common.Hash) error {
|
|||
}
|
||||
|
||||
// If all checks out, manually set the head block.
|
||||
rawdb.WriteHeadBlockHash(bc.db, hash)
|
||||
bc.currentBlock.Store(block.Header())
|
||||
headBlockGauge.Update(int64(block.NumberU64()))
|
||||
|
||||
|
|
@ -2594,8 +2597,13 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Header) error
|
|||
blockReorgAddMeter.Mark(int64(len(newChain)))
|
||||
} else {
|
||||
// len(newChain) == 0 && len(oldChain) > 0
|
||||
// rewind the canonical chain to a lower point.
|
||||
log.Error("Impossible reorg, please file an issue", "oldnum", oldHead.Number, "oldhash", oldHead.Hash(), "oldblocks", len(oldChain), "newnum", newHead.Number, "newhash", newHead.Hash(), "newblocks", len(newChain))
|
||||
// Rewind the canonical chain to a lower point. In EPBs we can reorg to
|
||||
// a parent of the head within 32 blocks.
|
||||
if len(oldChain) > 32 {
|
||||
log.Error("Impossible reorg, please file an issue", "oldnum", oldHead.Number, "oldhash", oldHead.Hash(), "oldblocks", len(oldChain))
|
||||
} else {
|
||||
log.Info("Shorten chain", "del", len(oldChain), "number", oldHead.Number, "hash", oldHead.Hash())
|
||||
}
|
||||
}
|
||||
// Acquire the tx-lookup lock before mutation. This step is essential
|
||||
// as the txlookups should be changed atomically, and all subsequent
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
|
|
@ -28,6 +29,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
|
|
@ -49,6 +51,7 @@ type BlockGen struct {
|
|||
receipts []*types.Receipt
|
||||
uncles []*types.Header
|
||||
withdrawals []*types.Withdrawal
|
||||
bal *bal.ConstructionBlockAccessList
|
||||
|
||||
engine consensus.Engine
|
||||
}
|
||||
|
|
@ -98,7 +101,7 @@ func (b *BlockGen) Difficulty() *big.Int {
|
|||
func (b *BlockGen) SetParentBeaconRoot(root common.Hash) {
|
||||
b.header.ParentBeaconRoot = &root
|
||||
blockContext := NewEVMBlockContext(b.header, b.cm, &b.header.Coinbase)
|
||||
ProcessBeaconBlockRoot(root, vm.NewEVM(blockContext, b.statedb, b.cm.config, vm.Config{}))
|
||||
ProcessBeaconBlockRoot(root, vm.NewEVM(blockContext, b.statedb, b.cm.config, vm.Config{}), b.bal)
|
||||
}
|
||||
|
||||
// addTx adds a transaction to the generated block. If no coinbase has
|
||||
|
|
@ -116,8 +119,8 @@ func (b *BlockGen) addTx(bc *BlockChain, vmConfig vm.Config, tx *types.Transacti
|
|||
blockContext = NewEVMBlockContext(b.header, bc, &b.header.Coinbase)
|
||||
evm = vm.NewEVM(blockContext, b.statedb, b.cm.config, vmConfig)
|
||||
)
|
||||
b.statedb.SetTxContext(tx.Hash(), len(b.txs))
|
||||
receipt, err := ApplyTransaction(evm, b.gasPool, b.statedb, b.header, tx)
|
||||
b.statedb.SetTxContext(tx.Hash(), len(b.txs), uint32(len(b.txs)+1))
|
||||
receipt, bal, err := ApplyTransaction(evm, b.gasPool, b.statedb, b.header, tx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
|
@ -133,6 +136,7 @@ func (b *BlockGen) addTx(bc *BlockChain, vmConfig vm.Config, tx *types.Transacti
|
|||
if b.header.BlobGasUsed != nil {
|
||||
*b.header.BlobGasUsed += receipt.BlobGasUsed
|
||||
}
|
||||
b.bal.Merge(bal)
|
||||
}
|
||||
|
||||
// AddTx adds a transaction to the generated block. If no coinbase has
|
||||
|
|
@ -303,10 +307,11 @@ func (b *BlockGen) OffsetTime(seconds int64) {
|
|||
|
||||
// ConsensusLayerRequests returns the EIP-7685 requests which have accumulated so far.
|
||||
func (b *BlockGen) ConsensusLayerRequests() [][]byte {
|
||||
return b.collectRequests(true)
|
||||
requests, _ := b.collectRequests(true)
|
||||
return requests
|
||||
}
|
||||
|
||||
func (b *BlockGen) collectRequests(readonly bool) (requests [][]byte) {
|
||||
func (b *BlockGen) collectRequests(readonly bool) (requests [][]byte, bal *bal.ConstructionBlockAccessList) {
|
||||
statedb := b.statedb
|
||||
if readonly {
|
||||
// The system contracts clear themselves on a system-initiated read.
|
||||
|
|
@ -314,30 +319,19 @@ func (b *BlockGen) collectRequests(readonly bool) (requests [][]byte) {
|
|||
// off the statedb before executing the system calls.
|
||||
statedb = statedb.Copy()
|
||||
}
|
||||
|
||||
if b.cm.config.IsPrague(b.header.Number, b.header.Time) {
|
||||
requests = [][]byte{}
|
||||
// EIP-6110 deposits
|
||||
var blockLogs []*types.Log
|
||||
for _, r := range b.receipts {
|
||||
blockLogs = append(blockLogs, r.Logs...)
|
||||
}
|
||||
if err := ParseDepositLogs(&requests, blockLogs, b.cm.config); err != nil {
|
||||
panic(fmt.Sprintf("failed to parse deposit log: %v", err))
|
||||
}
|
||||
// create EVM for system calls
|
||||
blockContext := NewEVMBlockContext(b.header, b.cm, &b.header.Coinbase)
|
||||
evm := vm.NewEVM(blockContext, statedb, b.cm.config, vm.Config{})
|
||||
// EIP-7002
|
||||
if err := ProcessWithdrawalQueue(&requests, evm); err != nil {
|
||||
panic(fmt.Sprintf("could not process withdrawal requests: %v", err))
|
||||
}
|
||||
// EIP-7251
|
||||
if err := ProcessConsolidationQueue(&requests, evm); err != nil {
|
||||
panic(fmt.Sprintf("could not process consolidation requests: %v", err))
|
||||
}
|
||||
var blockLogs []*types.Log
|
||||
for _, r := range b.receipts {
|
||||
blockLogs = append(blockLogs, r.Logs...)
|
||||
}
|
||||
return requests
|
||||
// TODO use the shared EVM throughout the entire generation cycle
|
||||
blockContext := NewEVMBlockContext(b.header, b.cm, &b.header.Coinbase)
|
||||
evm := vm.NewEVM(blockContext, statedb, b.cm.config, vm.Config{})
|
||||
|
||||
requests, bal, err := PostExecution(context.Background(), b.cm.config, b.header.Number, b.header.Time, blockLogs, evm, uint32(len(b.txs)+1))
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to run post-execution: %v", err))
|
||||
}
|
||||
return requests, bal
|
||||
}
|
||||
|
||||
// GenerateChain creates a chain of n blocks. The first block's
|
||||
|
|
@ -364,6 +358,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
|
|||
genblock := func(i int, parent *types.Block, triedb *triedb.Database, statedb *state.StateDB) (*types.Block, types.Receipts) {
|
||||
b := &BlockGen{i: i, cm: cm, parent: parent, statedb: statedb, engine: engine}
|
||||
b.header = cm.makeHeader(parent, statedb, b.engine)
|
||||
b.bal = bal.NewConstructionBlockAccessList()
|
||||
|
||||
// Set the difficulty for clique block. The chain maker doesn't have access
|
||||
// to a chain, so the difficulty will be left unset (nil). Set it here to the
|
||||
|
|
@ -396,7 +391,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
|
|||
blockContext := NewEVMBlockContext(b.header, cm, &b.header.Coinbase)
|
||||
blockContext.Random = &common.Hash{} // enable post-merge instruction set
|
||||
evm := vm.NewEVM(blockContext, statedb, cm.config, vm.Config{})
|
||||
ProcessParentBlockHash(b.header.ParentHash, evm)
|
||||
ProcessParentBlockHash(b.header.ParentHash, evm, b.bal)
|
||||
}
|
||||
|
||||
// Execute any user modifications to the block
|
||||
|
|
@ -404,11 +399,12 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
|
|||
gen(i, b)
|
||||
}
|
||||
|
||||
requests := b.collectRequests(false)
|
||||
requests, bal := b.collectRequests(false)
|
||||
if requests != nil {
|
||||
reqHash := types.CalcRequestsHash(requests)
|
||||
b.header.RequestsHash = &reqHash
|
||||
}
|
||||
b.bal.Merge(bal)
|
||||
|
||||
body := types.Body{
|
||||
Transactions: b.txs,
|
||||
|
|
@ -424,8 +420,11 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
|
|||
body.Withdrawals = make([]*types.Withdrawal, 0)
|
||||
}
|
||||
}
|
||||
// Apply the consensus-specific post-transaction changes
|
||||
b.engine.Finalize(cm, b.header, statedb, &body, uint32(len(b.txs)+1), b.bal)
|
||||
|
||||
// Assemble the block for delivery.
|
||||
block := AssembleBlock(b.engine, cm, b.header, statedb, &body, b.receipts)
|
||||
block := AssembleBlock(cm, b.header, statedb, &body, b.receipts, b.bal)
|
||||
|
||||
// Write state changes to db
|
||||
root, err := statedb.Commit(b.header.Number.Uint64(), config.IsEIP158(b.header.Number), config.IsCancun(b.header.Number, b.header.Time))
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common
|
|||
func NewEVMTxContext(msg *Message) vm.TxContext {
|
||||
ctx := vm.TxContext{
|
||||
Origin: msg.From,
|
||||
GasPrice: uint256.MustFromBig(msg.GasPrice),
|
||||
GasPrice: msg.GasPrice,
|
||||
BlobHashes: msg.BlobHashes,
|
||||
}
|
||||
return ctx
|
||||
|
|
|
|||
|
|
@ -136,8 +136,9 @@ func hashAlloc(ga *types.GenesisAlloc, isUBT bool) (common.Hash, error) {
|
|||
var config *triedb.Config
|
||||
if isUBT {
|
||||
config = &triedb.Config{
|
||||
PathDB: pathdb.Defaults,
|
||||
IsUBT: true,
|
||||
PathDB: pathdb.Defaults,
|
||||
IsUBT: true,
|
||||
BinTrieGroupDepth: triedb.UBTDefaults.BinTrieGroupDepth,
|
||||
}
|
||||
}
|
||||
// Create an ephemeral in-memory database for computing hash,
|
||||
|
|
@ -554,6 +555,7 @@ func (g *Genesis) toBlockWithRoot(root common.Hash) *types.Block {
|
|||
if head.SlotNumber == nil {
|
||||
head.SlotNumber = new(uint64)
|
||||
}
|
||||
head.BlockAccessListHash = &types.EmptyBlockAccessListHash
|
||||
}
|
||||
}
|
||||
return types.NewBlock(head, &types.Body{Withdrawals: withdrawals}, nil, trie.NewStackTrie(nil))
|
||||
|
|
|
|||
|
|
@ -261,9 +261,9 @@ func newDbConfig(scheme string) *triedb.Config {
|
|||
return &triedb.Config{PathDB: &config}
|
||||
}
|
||||
|
||||
func TestVerkleGenesisCommit(t *testing.T) {
|
||||
var verkleTime uint64 = 0
|
||||
verkleConfig := ¶ms.ChainConfig{
|
||||
func TestBinaryGenesisCommit(t *testing.T) {
|
||||
var ubtTime uint64 = 0
|
||||
ubtConfig := ¶ms.ChainConfig{
|
||||
ChainID: big.NewInt(1),
|
||||
HomesteadBlock: big.NewInt(0),
|
||||
DAOForkBlock: nil,
|
||||
|
|
@ -281,11 +281,11 @@ func TestVerkleGenesisCommit(t *testing.T) {
|
|||
ArrowGlacierBlock: big.NewInt(0),
|
||||
GrayGlacierBlock: big.NewInt(0),
|
||||
MergeNetsplitBlock: nil,
|
||||
ShanghaiTime: &verkleTime,
|
||||
CancunTime: &verkleTime,
|
||||
PragueTime: &verkleTime,
|
||||
OsakaTime: &verkleTime,
|
||||
UBTTime: &verkleTime,
|
||||
ShanghaiTime: &ubtTime,
|
||||
CancunTime: &ubtTime,
|
||||
PragueTime: &ubtTime,
|
||||
OsakaTime: &ubtTime,
|
||||
UBTTime: &ubtTime,
|
||||
TerminalTotalDifficulty: big.NewInt(0),
|
||||
EnableUBTAtGenesis: true,
|
||||
Ethash: nil,
|
||||
|
|
@ -300,8 +300,8 @@ func TestVerkleGenesisCommit(t *testing.T) {
|
|||
|
||||
genesis := &Genesis{
|
||||
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||
Config: verkleConfig,
|
||||
Timestamp: verkleTime,
|
||||
Config: ubtConfig,
|
||||
Timestamp: ubtTime,
|
||||
Difficulty: big.NewInt(0),
|
||||
Alloc: types.GenesisAlloc{
|
||||
{1}: {Balance: big.NewInt(1), Storage: map[common.Hash]common.Hash{{1}: {1}}},
|
||||
|
|
@ -320,17 +320,18 @@ func TestVerkleGenesisCommit(t *testing.T) {
|
|||
config.NoAsyncFlush = true
|
||||
|
||||
triedb := triedb.NewDatabase(db, &triedb.Config{
|
||||
IsUBT: true,
|
||||
PathDB: &config,
|
||||
IsUBT: true,
|
||||
PathDB: &config,
|
||||
BinTrieGroupDepth: triedb.DefaultBinTrieGroupDepth,
|
||||
})
|
||||
block := genesis.MustCommit(db, triedb)
|
||||
if !bytes.Equal(block.Root().Bytes(), expected) {
|
||||
t.Fatalf("invalid genesis state root, expected %x, got %x", expected, block.Root())
|
||||
}
|
||||
|
||||
// Test that the trie is verkle
|
||||
// Test that the trie is a unified binary trie
|
||||
if !triedb.IsUBT() {
|
||||
t.Fatalf("expected trie to be verkle")
|
||||
t.Fatalf("expected trie to be a unified binary trie")
|
||||
}
|
||||
vdb := rawdb.NewTable(db, string(rawdb.VerklePrefix))
|
||||
if !rawdb.HasAccountTrieNode(vdb, nil) {
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ func (db *UBTDatabase) ReadersWithCacheStats(stateRoot common.Hash) (Reader, Rea
|
|||
|
||||
// OpenTrie opens the main account trie at a specific root hash.
|
||||
func (db *UBTDatabase) OpenTrie(root common.Hash) (Trie, error) {
|
||||
return bintrie.NewBinaryTrie(root, db.triedb)
|
||||
return bintrie.NewBinaryTrie(root, db.triedb, db.triedb.BinTrieGroupDepth())
|
||||
}
|
||||
|
||||
// OpenStorageTrie opens the storage trie of an account. In binary trie mode,
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ package state
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"maps"
|
||||
"slices"
|
||||
"sort"
|
||||
|
||||
|
|
@ -32,26 +31,163 @@ type revision struct {
|
|||
journalIndex int
|
||||
}
|
||||
|
||||
// journalMutationKind indicates the type of account mutation.
|
||||
type journalMutationKind uint8
|
||||
|
||||
const (
|
||||
// journalMutationKindNone is the zero value returned by mutation() for
|
||||
// entries that don't carry a tracked account mutation. The accompanying
|
||||
// bool is false in that case; callers must gate on it before using the
|
||||
// kind.
|
||||
journalMutationKindNone journalMutationKind = iota
|
||||
journalMutationKindTouch
|
||||
journalMutationKindCreate
|
||||
journalMutationKindSelfDestruct
|
||||
journalMutationKindBalance
|
||||
journalMutationKindNonce
|
||||
journalMutationKindCode
|
||||
journalMutationKindStorage
|
||||
journalMutationKindCount // sentinel, must stay last
|
||||
)
|
||||
|
||||
type journalMutationCounts [journalMutationKindCount]int
|
||||
|
||||
// journalMutationState tracks, per account, both the per-kind count of mutation
|
||||
// entries currently present in the journal and the pre-tx value of each
|
||||
// metadata field captured on its first touch (balance/nonce/code).
|
||||
// The *Set flags indicate whether the corresponding field has been mutated
|
||||
// at least once in the current tx window; they are cleared when all entries
|
||||
// of that kind are reverted. Storage slots are tracked elsewhere.
|
||||
type journalMutationState struct {
|
||||
counts journalMutationCounts
|
||||
|
||||
balance *uint256.Int
|
||||
balanceSet bool
|
||||
nonce uint64
|
||||
nonceSet bool
|
||||
code []byte
|
||||
codeSet bool
|
||||
}
|
||||
|
||||
func (s *journalMutationState) add(kind journalMutationKind) {
|
||||
s.counts.add(kind)
|
||||
}
|
||||
|
||||
// remove drops one occurrence of the given mutation kind. It returns a flag
|
||||
// indicating whether no entries of any kind remain.
|
||||
func (s *journalMutationState) remove(kind journalMutationKind) bool {
|
||||
if s.counts.remove(kind) {
|
||||
// No entries of this kind remain for this account; drop the
|
||||
// corresponding stashed original so the state mirrors the
|
||||
// live mutation set.
|
||||
s.clearKind(kind)
|
||||
}
|
||||
return s.counts == (journalMutationCounts{})
|
||||
}
|
||||
|
||||
// clearKind drops the stashed original for the given mutation kind. It is
|
||||
// invoked during revert once no journal entries of that kind remain for the
|
||||
// account. Kinds that don't correspond to a tracked metadata field are no-ops.
|
||||
func (s *journalMutationState) clearKind(kind journalMutationKind) {
|
||||
switch kind {
|
||||
case journalMutationKindBalance:
|
||||
s.balance = nil
|
||||
s.balanceSet = false
|
||||
case journalMutationKindNonce:
|
||||
s.nonce = 0
|
||||
s.nonceSet = false
|
||||
case journalMutationKindCode:
|
||||
s.code = nil
|
||||
s.codeSet = false
|
||||
}
|
||||
}
|
||||
|
||||
func (s *journalMutationState) copy() *journalMutationState {
|
||||
cpy := *s
|
||||
if s.balance != nil {
|
||||
cpy.balance = new(uint256.Int).Set(s.balance)
|
||||
}
|
||||
if s.code != nil {
|
||||
cpy.code = slices.Clone(s.code)
|
||||
}
|
||||
return &cpy
|
||||
}
|
||||
|
||||
func (c *journalMutationCounts) add(kind journalMutationKind) {
|
||||
c[kind]++
|
||||
}
|
||||
|
||||
func (c *journalMutationCounts) remove(kind journalMutationKind) bool {
|
||||
c[kind]--
|
||||
return c[kind] == 0
|
||||
}
|
||||
|
||||
// journalEntry is a modification entry in the state change journal that can be
|
||||
// reverted on demand.
|
||||
type journalEntry interface {
|
||||
// revert undoes the changes introduced by this journal entry.
|
||||
revert(*StateDB)
|
||||
|
||||
// dirtied returns the Ethereum address modified by this journal entry.
|
||||
// indicates false if no address was changed.
|
||||
dirtied() (common.Address, bool)
|
||||
// mutation returns the account mutation introduced by this entry.
|
||||
// It indicates false if no tracked account mutation was made.
|
||||
mutation() (common.Address, journalMutationKind, bool)
|
||||
|
||||
// copy returns a deep-copied journal entry.
|
||||
copy() journalEntry
|
||||
}
|
||||
|
||||
// stashBalance records prev as the pre-tx balance of addr, iff this is the
|
||||
// first balance touch seen in the current tx. Subsequent balance writes are
|
||||
// ignored so the stored value remains the true pre-tx original.
|
||||
func (j *journal) stashBalance(addr common.Address, prev *uint256.Int) {
|
||||
s := j.mutationStateFor(addr)
|
||||
if s.balanceSet {
|
||||
return
|
||||
}
|
||||
// The balance is already deep-copied and safe to hold the object here.
|
||||
s.balance = prev
|
||||
s.balanceSet = true
|
||||
}
|
||||
|
||||
// stashNonce records prev as the pre-tx nonce of addr on first touch.
|
||||
func (j *journal) stashNonce(addr common.Address, prev uint64) {
|
||||
s := j.mutationStateFor(addr)
|
||||
if s.nonceSet {
|
||||
return
|
||||
}
|
||||
s.nonce = prev
|
||||
s.nonceSet = true
|
||||
}
|
||||
|
||||
// stashCode records prev as the pre-tx code of addr on first touch.
|
||||
func (j *journal) stashCode(addr common.Address, prev []byte) {
|
||||
s := j.mutationStateFor(addr)
|
||||
if s.codeSet {
|
||||
return
|
||||
}
|
||||
// The code is already deep-copied in the StateDB, safe to
|
||||
// hold the reference here.
|
||||
s.code = prev
|
||||
s.codeSet = true
|
||||
}
|
||||
|
||||
// mutationStateFor returns the mutation state for addr, creating an empty one
|
||||
// if absent.
|
||||
func (j *journal) mutationStateFor(addr common.Address) *journalMutationState {
|
||||
s := j.mutations[addr]
|
||||
if s == nil {
|
||||
s = new(journalMutationState)
|
||||
j.mutations[addr] = s
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// journal contains the list of state modifications applied since the last state
|
||||
// commit. These are tracked to be able to be reverted in the case of an execution
|
||||
// exception or request for reversal.
|
||||
type journal struct {
|
||||
entries []journalEntry // Current changes tracked by the journal
|
||||
dirties map[common.Address]int // Dirty accounts and the number of changes
|
||||
entries []journalEntry // Current changes tracked by the journal
|
||||
mutations map[common.Address]*journalMutationState // Per-account mutation kinds and pre-tx originals
|
||||
|
||||
validRevisions []revision
|
||||
nextRevisionId int
|
||||
|
|
@ -60,7 +196,7 @@ type journal struct {
|
|||
// newJournal creates a new initialized journal.
|
||||
func newJournal() *journal {
|
||||
return &journal{
|
||||
dirties: make(map[common.Address]int),
|
||||
mutations: make(map[common.Address]*journalMutationState),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -70,7 +206,7 @@ func newJournal() *journal {
|
|||
func (j *journal) reset() {
|
||||
j.entries = j.entries[:0]
|
||||
j.validRevisions = j.validRevisions[:0]
|
||||
clear(j.dirties)
|
||||
clear(j.mutations)
|
||||
j.nextRevisionId = 0
|
||||
}
|
||||
|
||||
|
|
@ -101,33 +237,52 @@ func (j *journal) revertToSnapshot(revid int, s *StateDB) {
|
|||
// append inserts a new modification entry to the end of the change journal.
|
||||
func (j *journal) append(entry journalEntry) {
|
||||
j.entries = append(j.entries, entry)
|
||||
if addr, dirty := entry.dirtied(); dirty {
|
||||
j.dirties[addr]++
|
||||
if addr, kind, dirty := entry.mutation(); dirty {
|
||||
state := j.mutations[addr]
|
||||
if state == nil {
|
||||
state = new(journalMutationState)
|
||||
j.mutations[addr] = state
|
||||
}
|
||||
state.add(kind)
|
||||
}
|
||||
}
|
||||
|
||||
// revert undoes a batch of journalled modifications along with any reverted
|
||||
// dirty handling too.
|
||||
// mutation tracking too.
|
||||
func (j *journal) revert(statedb *StateDB, snapshot int) {
|
||||
for i := len(j.entries) - 1; i >= snapshot; i-- {
|
||||
// Undo the changes made by the operation
|
||||
j.entries[i].revert(statedb)
|
||||
|
||||
// Drop any dirty tracking induced by the change
|
||||
if addr, dirty := j.entries[i].dirtied(); dirty {
|
||||
if j.dirties[addr]--; j.dirties[addr] == 0 {
|
||||
delete(j.dirties, addr)
|
||||
// Drop any mutation tracking induced by the change.
|
||||
if addr, kind, dirty := j.entries[i].mutation(); dirty {
|
||||
state := j.mutations[addr]
|
||||
if state == nil {
|
||||
panic(fmt.Errorf("journal mutation tracking missing for %x", addr[:]))
|
||||
}
|
||||
if state.remove(kind) {
|
||||
delete(j.mutations, addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
j.entries = j.entries[:snapshot]
|
||||
}
|
||||
|
||||
// dirty explicitly sets an address to dirty, even if the change entries would
|
||||
// otherwise suggest it as clean. This method is an ugly hack to handle the RIPEMD
|
||||
// precompile consensus exception.
|
||||
func (j *journal) dirty(addr common.Address) {
|
||||
j.dirties[addr]++
|
||||
// ripemdMagic explicitly keeps RIPEMD160 in the mutation set with a touch change.
|
||||
//
|
||||
// Ethereum Mainnet contains an old empty-account touch/revert quirk for address
|
||||
// 0x03. If we only relied on the journal entry above, the revert path would
|
||||
// remove the account from the mutation set together with the touch.
|
||||
//
|
||||
// Keep an explicit touch marker so tx finalisation still sees RIPEMD160
|
||||
// on the mutation pass when replaying that historical case.
|
||||
func (j *journal) ripemdMagic() {
|
||||
state := j.mutations[ripemd]
|
||||
if state == nil {
|
||||
state = new(journalMutationState)
|
||||
j.mutations[ripemd] = state
|
||||
}
|
||||
state.add(journalMutationKindTouch)
|
||||
}
|
||||
|
||||
// length returns the current number of entries in the journal.
|
||||
|
|
@ -141,9 +296,13 @@ func (j *journal) copy() *journal {
|
|||
for i := 0; i < j.length(); i++ {
|
||||
entries = append(entries, j.entries[i].copy())
|
||||
}
|
||||
mutations := make(map[common.Address]*journalMutationState, len(j.mutations))
|
||||
for addr, state := range j.mutations {
|
||||
mutations[addr] = state.copy()
|
||||
}
|
||||
return &journal{
|
||||
entries: entries,
|
||||
dirties: maps.Clone(j.dirties),
|
||||
mutations: mutations,
|
||||
validRevisions: slices.Clone(j.validRevisions),
|
||||
nextRevisionId: j.nextRevisionId,
|
||||
}
|
||||
|
|
@ -187,13 +346,16 @@ func (j *journal) refundChange(previous uint64) {
|
|||
}
|
||||
|
||||
func (j *journal) balanceChange(addr common.Address, previous *uint256.Int) {
|
||||
prev := previous.Clone()
|
||||
j.stashBalance(addr, prev)
|
||||
j.append(balanceChange{
|
||||
account: addr,
|
||||
prev: previous.Clone(),
|
||||
prev: prev,
|
||||
})
|
||||
}
|
||||
|
||||
func (j *journal) setCode(address common.Address, prevCode []byte) {
|
||||
j.stashCode(address, prevCode)
|
||||
j.append(codeChange{
|
||||
account: address,
|
||||
prevCode: prevCode,
|
||||
|
|
@ -201,6 +363,7 @@ func (j *journal) setCode(address common.Address, prevCode []byte) {
|
|||
}
|
||||
|
||||
func (j *journal) nonceChange(address common.Address, prev uint64) {
|
||||
j.stashNonce(address, prev)
|
||||
j.append(nonceChange{
|
||||
account: address,
|
||||
prev: prev,
|
||||
|
|
@ -212,9 +375,18 @@ func (j *journal) touchChange(address common.Address) {
|
|||
account: address,
|
||||
})
|
||||
if address == ripemd {
|
||||
// Explicitly put it in the dirty-cache, which is otherwise generated from
|
||||
// flattened journals.
|
||||
j.dirty(address)
|
||||
// Preserve the historical RIPEMD160 precompile consensus exception.
|
||||
//
|
||||
// Mainnet contains an old empty-account touch/revert quirk for address
|
||||
// 0x03. If we only relied on the journal entry above, the revert path
|
||||
// would remove the account from the dirty set together with the touch.
|
||||
// Keep an explicit dirty marker so tx finalisation still sees the
|
||||
// account on the dirty pass when replaying that historical case.
|
||||
//
|
||||
// This does not force deletion by itself: Finalise will still delete the
|
||||
// account only if the state object is present at tx end and qualifies for
|
||||
// deletion there.
|
||||
j.ripemdMagic()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -295,8 +467,8 @@ func (ch createObjectChange) revert(s *StateDB) {
|
|||
delete(s.stateObjects, ch.account)
|
||||
}
|
||||
|
||||
func (ch createObjectChange) dirtied() (common.Address, bool) {
|
||||
return ch.account, true
|
||||
func (ch createObjectChange) mutation() (common.Address, journalMutationKind, bool) {
|
||||
return ch.account, journalMutationKindCreate, true
|
||||
}
|
||||
|
||||
func (ch createObjectChange) copy() journalEntry {
|
||||
|
|
@ -309,8 +481,8 @@ func (ch createContractChange) revert(s *StateDB) {
|
|||
s.getStateObject(ch.account).newContract = false
|
||||
}
|
||||
|
||||
func (ch createContractChange) dirtied() (common.Address, bool) {
|
||||
return common.Address{}, false
|
||||
func (ch createContractChange) mutation() (common.Address, journalMutationKind, bool) {
|
||||
return common.Address{}, journalMutationKindNone, false
|
||||
}
|
||||
|
||||
func (ch createContractChange) copy() journalEntry {
|
||||
|
|
@ -326,8 +498,8 @@ func (ch selfDestructChange) revert(s *StateDB) {
|
|||
}
|
||||
}
|
||||
|
||||
func (ch selfDestructChange) dirtied() (common.Address, bool) {
|
||||
return ch.account, true
|
||||
func (ch selfDestructChange) mutation() (common.Address, journalMutationKind, bool) {
|
||||
return ch.account, journalMutationKindSelfDestruct, true
|
||||
}
|
||||
|
||||
func (ch selfDestructChange) copy() journalEntry {
|
||||
|
|
@ -341,8 +513,8 @@ var ripemd = common.HexToAddress("0000000000000000000000000000000000000003")
|
|||
func (ch touchChange) revert(s *StateDB) {
|
||||
}
|
||||
|
||||
func (ch touchChange) dirtied() (common.Address, bool) {
|
||||
return ch.account, true
|
||||
func (ch touchChange) mutation() (common.Address, journalMutationKind, bool) {
|
||||
return ch.account, journalMutationKindTouch, true
|
||||
}
|
||||
|
||||
func (ch touchChange) copy() journalEntry {
|
||||
|
|
@ -355,8 +527,8 @@ func (ch balanceChange) revert(s *StateDB) {
|
|||
s.getStateObject(ch.account).setBalance(ch.prev)
|
||||
}
|
||||
|
||||
func (ch balanceChange) dirtied() (common.Address, bool) {
|
||||
return ch.account, true
|
||||
func (ch balanceChange) mutation() (common.Address, journalMutationKind, bool) {
|
||||
return ch.account, journalMutationKindBalance, true
|
||||
}
|
||||
|
||||
func (ch balanceChange) copy() journalEntry {
|
||||
|
|
@ -370,8 +542,8 @@ func (ch nonceChange) revert(s *StateDB) {
|
|||
s.getStateObject(ch.account).setNonce(ch.prev)
|
||||
}
|
||||
|
||||
func (ch nonceChange) dirtied() (common.Address, bool) {
|
||||
return ch.account, true
|
||||
func (ch nonceChange) mutation() (common.Address, journalMutationKind, bool) {
|
||||
return ch.account, journalMutationKindNonce, true
|
||||
}
|
||||
|
||||
func (ch nonceChange) copy() journalEntry {
|
||||
|
|
@ -385,8 +557,8 @@ func (ch codeChange) revert(s *StateDB) {
|
|||
s.getStateObject(ch.account).setCode(crypto.Keccak256Hash(ch.prevCode), ch.prevCode)
|
||||
}
|
||||
|
||||
func (ch codeChange) dirtied() (common.Address, bool) {
|
||||
return ch.account, true
|
||||
func (ch codeChange) mutation() (common.Address, journalMutationKind, bool) {
|
||||
return ch.account, journalMutationKindCode, true
|
||||
}
|
||||
|
||||
func (ch codeChange) copy() journalEntry {
|
||||
|
|
@ -400,8 +572,8 @@ func (ch storageChange) revert(s *StateDB) {
|
|||
s.getStateObject(ch.account).setState(ch.key, ch.prevvalue, ch.origvalue)
|
||||
}
|
||||
|
||||
func (ch storageChange) dirtied() (common.Address, bool) {
|
||||
return ch.account, true
|
||||
func (ch storageChange) mutation() (common.Address, journalMutationKind, bool) {
|
||||
return ch.account, journalMutationKindStorage, true
|
||||
}
|
||||
|
||||
func (ch storageChange) copy() journalEntry {
|
||||
|
|
@ -417,8 +589,8 @@ func (ch transientStorageChange) revert(s *StateDB) {
|
|||
s.setTransientState(ch.account, ch.key, ch.prevalue)
|
||||
}
|
||||
|
||||
func (ch transientStorageChange) dirtied() (common.Address, bool) {
|
||||
return common.Address{}, false
|
||||
func (ch transientStorageChange) mutation() (common.Address, journalMutationKind, bool) {
|
||||
return common.Address{}, journalMutationKindNone, false
|
||||
}
|
||||
|
||||
func (ch transientStorageChange) copy() journalEntry {
|
||||
|
|
@ -433,8 +605,8 @@ func (ch refundChange) revert(s *StateDB) {
|
|||
s.refund = ch.prev
|
||||
}
|
||||
|
||||
func (ch refundChange) dirtied() (common.Address, bool) {
|
||||
return common.Address{}, false
|
||||
func (ch refundChange) mutation() (common.Address, journalMutationKind, bool) {
|
||||
return common.Address{}, journalMutationKindNone, false
|
||||
}
|
||||
|
||||
func (ch refundChange) copy() journalEntry {
|
||||
|
|
@ -453,8 +625,8 @@ func (ch addLogChange) revert(s *StateDB) {
|
|||
s.logSize--
|
||||
}
|
||||
|
||||
func (ch addLogChange) dirtied() (common.Address, bool) {
|
||||
return common.Address{}, false
|
||||
func (ch addLogChange) mutation() (common.Address, journalMutationKind, bool) {
|
||||
return common.Address{}, journalMutationKindNone, false
|
||||
}
|
||||
|
||||
func (ch addLogChange) copy() journalEntry {
|
||||
|
|
@ -476,8 +648,8 @@ func (ch accessListAddAccountChange) revert(s *StateDB) {
|
|||
s.accessList.DeleteAddress(ch.address)
|
||||
}
|
||||
|
||||
func (ch accessListAddAccountChange) dirtied() (common.Address, bool) {
|
||||
return common.Address{}, false
|
||||
func (ch accessListAddAccountChange) mutation() (common.Address, journalMutationKind, bool) {
|
||||
return common.Address{}, journalMutationKindNone, false
|
||||
}
|
||||
|
||||
func (ch accessListAddAccountChange) copy() journalEntry {
|
||||
|
|
@ -490,8 +662,8 @@ func (ch accessListAddSlotChange) revert(s *StateDB) {
|
|||
s.accessList.DeleteSlot(ch.address, ch.slot)
|
||||
}
|
||||
|
||||
func (ch accessListAddSlotChange) dirtied() (common.Address, bool) {
|
||||
return common.Address{}, false
|
||||
func (ch accessListAddSlotChange) mutation() (common.Address, journalMutationKind, bool) {
|
||||
return common.Address{}, journalMutationKindNone, false
|
||||
}
|
||||
|
||||
func (ch accessListAddSlotChange) copy() journalEntry {
|
||||
|
|
|
|||
219
core/state/journal_test.go
Normal file
219
core/state/journal_test.go
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
// Copyright 2026 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 (
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
// fuzzJournalAddrs is a small fixed pool used by the fuzz harness to force
|
||||
// repeated collisions on the same account, which exercises the multi-entry
|
||||
// path in the journal's mutation tracking and originals cleanup on revert.
|
||||
// It deliberately excludes the RIPEMD-160 precompile (0x03), which has a
|
||||
// consensus-level touch/revert exception that would complicate invariants.
|
||||
var fuzzJournalAddrs = []common.Address{
|
||||
common.BytesToAddress([]byte{0x11}),
|
||||
common.BytesToAddress([]byte{0x22}),
|
||||
common.BytesToAddress([]byte{0x44}),
|
||||
}
|
||||
|
||||
// checkJournalInvariants validates that:
|
||||
// - journal.mutations exactly reflects the dirty entries currently in
|
||||
// journal.entries (per-kind counts and mask match what you'd get by
|
||||
// walking the entries from scratch).
|
||||
// - journal.originals mirrors that set for the three tracked metadata kinds
|
||||
// (balance/nonce/code): a *Set flag is true iff the account currently has
|
||||
// at least one corresponding entry in the journal.
|
||||
// - An address is present in originals only if it also has at least one
|
||||
// tracked-kind mutation in the journal.
|
||||
func checkJournalInvariants(t *testing.T, j *journal) {
|
||||
t.Helper()
|
||||
|
||||
// Reconstruct the expected per-address counts from the live entries.
|
||||
expected := make(map[common.Address]*journalMutationCounts)
|
||||
for _, e := range j.entries {
|
||||
addr, kind, dirty := e.mutation()
|
||||
if !dirty {
|
||||
continue
|
||||
}
|
||||
c := expected[addr]
|
||||
if c == nil {
|
||||
c = &journalMutationCounts{}
|
||||
expected[addr] = c
|
||||
}
|
||||
c.add(kind)
|
||||
}
|
||||
|
||||
if len(j.mutations) != len(expected) {
|
||||
t.Fatalf("mutations size %d, want %d", len(j.mutations), len(expected))
|
||||
}
|
||||
for addr, state := range j.mutations {
|
||||
want, ok := expected[addr]
|
||||
if !ok {
|
||||
t.Fatalf("mutations has extra address %x", addr)
|
||||
}
|
||||
if state.counts != *want {
|
||||
t.Fatalf("addr %x: counts=%+v want=%+v", addr, state.counts, *want)
|
||||
}
|
||||
// First-touch *Set flags must mirror the live per-kind counts.
|
||||
if state.balanceSet != (want[journalMutationKindBalance] > 0) {
|
||||
t.Fatalf("addr %x: balanceSet=%v want=%v (balance count=%d)",
|
||||
addr, state.balanceSet, want[journalMutationKindBalance] > 0, want[journalMutationKindBalance])
|
||||
}
|
||||
if state.nonceSet != (want[journalMutationKindNonce] > 0) {
|
||||
t.Fatalf("addr %x: nonceSet=%v want=%v (nonce count=%d)",
|
||||
addr, state.nonceSet, want[journalMutationKindNonce] > 0, want[journalMutationKindNonce])
|
||||
}
|
||||
if state.codeSet != (want[journalMutationKindCode] > 0) {
|
||||
t.Fatalf("addr %x: codeSet=%v want=%v (code count=%d)",
|
||||
addr, state.codeSet, want[journalMutationKindCode] > 0, want[journalMutationKindCode])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FuzzJournal drives a randomised sequence of state mutations, snapshots and
|
||||
// reverts against a fresh StateDB and validates the journal's internal
|
||||
// bookkeeping invariants after every step. It also asserts that reverting
|
||||
// back to the root snapshot empties mutations, originals and entries
|
||||
// completely. The seed corpus ensures the test also runs as a regular unit
|
||||
// test via `go test -run FuzzJournal`.
|
||||
func FuzzJournal(f *testing.F) {
|
||||
seeds := [][]byte{
|
||||
// balance then full revert (simplest a→b→a case).
|
||||
{0x00, 0x00, 0x05, 0x05, 0x00},
|
||||
// balance+nonce+code mixed, then revert to root.
|
||||
{0x00, 0x00, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x00, 0x03, 0x05, 0x00},
|
||||
// snapshot, mutate, revert, mutate again.
|
||||
{0x04, 0x00, 0x00, 0x07, 0x05, 0x00, 0x00, 0x01, 0x05},
|
||||
// storage interleaved with metadata.
|
||||
{0x03, 0x00, 0x01, 0x00, 0x01, 0x05, 0x03, 0x02, 0x02, 0x04, 0x03, 0x01, 0x07},
|
||||
// many ops, no explicit revert — exercises steady-state invariants.
|
||||
{0x00, 0x01, 0x02, 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x02,
|
||||
0x03, 0x04, 0x00, 0x01, 0x02, 0x00, 0x06, 0x08, 0x0a, 0x0c},
|
||||
}
|
||||
for _, s := range seeds {
|
||||
f.Add(s)
|
||||
}
|
||||
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
sdb, err := New(types.EmptyRootHash, NewDatabaseForTesting())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
root := sdb.Snapshot()
|
||||
|
||||
// Stack of snapshot IDs taken during the fuzz loop.
|
||||
var pending []int
|
||||
|
||||
// readByte returns the next byte and advances the cursor. Returns
|
||||
// (0, false) if exhausted.
|
||||
i := 0
|
||||
readByte := func() (byte, bool) {
|
||||
if i >= len(data) {
|
||||
return 0, false
|
||||
}
|
||||
b := data[i]
|
||||
i++
|
||||
return b, true
|
||||
}
|
||||
|
||||
for {
|
||||
op, ok := readByte()
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
switch op % 6 {
|
||||
case 0: // SetBalance
|
||||
a, ok1 := readByte()
|
||||
v, ok2 := readByte()
|
||||
if !ok1 || !ok2 {
|
||||
break
|
||||
}
|
||||
addr := fuzzJournalAddrs[int(a)%len(fuzzJournalAddrs)]
|
||||
sdb.SetBalance(addr, uint256.NewInt(uint64(v)), tracing.BalanceChangeUnspecified)
|
||||
case 1: // SetNonce
|
||||
a, ok1 := readByte()
|
||||
n, ok2 := readByte()
|
||||
if !ok1 || !ok2 {
|
||||
break
|
||||
}
|
||||
addr := fuzzJournalAddrs[int(a)%len(fuzzJournalAddrs)]
|
||||
sdb.SetNonce(addr, uint64(n), tracing.NonceChangeUnspecified)
|
||||
case 2: // SetCode
|
||||
a, ok1 := readByte()
|
||||
l, ok2 := readByte()
|
||||
if !ok1 || !ok2 {
|
||||
break
|
||||
}
|
||||
addr := fuzzJournalAddrs[int(a)%len(fuzzJournalAddrs)]
|
||||
code := make([]byte, int(l)%8)
|
||||
for k := range code {
|
||||
b, ok := readByte()
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
code[k] = b
|
||||
}
|
||||
sdb.SetCode(addr, code, tracing.CodeChangeUnspecified)
|
||||
case 3: // SetState (storage; tracked as mutation kind, no original)
|
||||
a, ok1 := readByte()
|
||||
k, ok2 := readByte()
|
||||
v, ok3 := readByte()
|
||||
if !ok1 || !ok2 || !ok3 {
|
||||
break
|
||||
}
|
||||
addr := fuzzJournalAddrs[int(a)%len(fuzzJournalAddrs)]
|
||||
sdb.SetState(addr,
|
||||
common.BytesToHash([]byte{k}),
|
||||
common.BytesToHash([]byte{v}))
|
||||
case 4: // Snapshot
|
||||
pending = append(pending, sdb.Snapshot())
|
||||
case 5: // RevertToSnapshot
|
||||
if len(pending) == 0 {
|
||||
break
|
||||
}
|
||||
sel, ok := readByte()
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
idx := int(sel) % len(pending)
|
||||
sdb.RevertToSnapshot(pending[idx])
|
||||
pending = pending[:idx]
|
||||
}
|
||||
checkJournalInvariants(t, sdb.journal)
|
||||
}
|
||||
|
||||
// After reverting to the root snapshot, the journal must be fully
|
||||
// drained: no entries, no mutations, no originals. This is the core
|
||||
// guarantee the user cares about — "all mutations against a single
|
||||
// account reverted" taken to its limit across every account.
|
||||
sdb.RevertToSnapshot(root)
|
||||
checkJournalInvariants(t, sdb.journal)
|
||||
|
||||
if n := len(sdb.journal.entries); n != 0 {
|
||||
t.Fatalf("entries not drained after revert-to-root: %d remain", n)
|
||||
}
|
||||
if n := len(sdb.journal.mutations); n != 0 {
|
||||
t.Fatalf("mutations not drained after revert-to-root: %d remain", n)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -255,7 +255,7 @@ type ubtTrieReader struct {
|
|||
// newUBTTrieReader constructs a Unified-binary-trie reader of the specific state.
|
||||
// An error will be returned if the associated trie specified by root is not existent.
|
||||
func newUBTTrieReader(root common.Hash, db *triedb.Database) (*ubtTrieReader, error) {
|
||||
binTrie, binErr := bintrie.NewBinaryTrie(root, db)
|
||||
binTrie, binErr := bintrie.NewBinaryTrie(root, db, db.BinTrieGroupDepth())
|
||||
if binErr != nil {
|
||||
return nil, binErr
|
||||
}
|
||||
|
|
|
|||
|
|
@ -441,7 +441,7 @@ func TestStorageIteratorTraversalValues(t *testing.T) {
|
|||
if i%8 == 0 {
|
||||
e[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 4, i)
|
||||
}
|
||||
if i > 50 || i < 85 {
|
||||
if i > 50 && i < 85 {
|
||||
f[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 5, i)
|
||||
}
|
||||
if i%64 == 0 {
|
||||
|
|
|
|||
|
|
@ -184,8 +184,9 @@ func (s *stateObject) getState(key common.Hash) (common.Hash, common.Hash) {
|
|||
// without any mutations caused in the current execution.
|
||||
func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
|
||||
// Record slot access regardless of whether the storage slot exists.
|
||||
s.db.stateReadList.AddState(s.address, key)
|
||||
|
||||
if s.db.stateAccessList != nil {
|
||||
s.db.stateAccessList.StorageRead(s.address, key)
|
||||
}
|
||||
// If we have a pending write or clean cached, return that
|
||||
if value, pending := s.pendingStorage[key]; pending {
|
||||
return value
|
||||
|
|
@ -274,6 +275,13 @@ func (s *stateObject) finalise() {
|
|||
// map as the dirty slot might have been committed already (before the
|
||||
// byzantium fork) and entry is necessary to modify the value back.
|
||||
s.pendingStorage[key] = value
|
||||
|
||||
// Aggregate storage writes into the block-level access list.
|
||||
// All slots in the dirtyStorage set must have post-transaction
|
||||
// values that differ from their pre-transaction values.
|
||||
if s.db.stateAccessList != nil {
|
||||
s.db.stateAccessList.StorageWrite(s.db.blockAccessIndex, s.address, key, value)
|
||||
}
|
||||
}
|
||||
if s.db.prefetcher != nil && len(slotsToPrefetch) > 0 && s.data.Root != types.EmptyRootHash {
|
||||
if err := s.db.prefetcher.prefetch(s.addrHash(), s.data.Root, s.address, nil, slotsToPrefetch, false); err != nil {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
|
|
@ -128,7 +129,10 @@ type StateDB struct {
|
|||
accessEvents *AccessEvents
|
||||
|
||||
// Per-transaction state access footprint for EIP-7928
|
||||
stateReadList *bal.StateAccessList
|
||||
stateAccessList *bal.ConstructionBlockAccessList
|
||||
|
||||
// Block access index (0 for pre-execution, 1..n for transactions, n+1 for post-execution)
|
||||
blockAccessIndex uint32
|
||||
|
||||
// Transient storage
|
||||
transientStorage transientStorage
|
||||
|
|
@ -589,8 +593,9 @@ func (s *StateDB) deleteStateObject(addr common.Address) {
|
|||
// the object is not found or was deleted in this execution context.
|
||||
func (s *StateDB) getStateObject(addr common.Address) *stateObject {
|
||||
// Record state access regardless of whether the account exists.
|
||||
s.stateReadList.AddAccount(addr)
|
||||
|
||||
if s.stateAccessList != nil {
|
||||
s.stateAccessList.AccountRead(addr)
|
||||
}
|
||||
// Prefer live objects if any is available
|
||||
if obj := s.stateObjects[addr]; obj != nil {
|
||||
return obj
|
||||
|
|
@ -693,6 +698,7 @@ func (s *StateDB) Copy() *StateDB {
|
|||
refund: s.refund,
|
||||
thash: s.thash,
|
||||
txIndex: s.txIndex,
|
||||
blockAccessIndex: s.blockAccessIndex,
|
||||
logs: make(map[common.Hash][]*types.Log, len(s.logs)),
|
||||
logSize: s.logSize,
|
||||
preimages: maps.Clone(s.preimages),
|
||||
|
|
@ -716,9 +722,6 @@ func (s *StateDB) Copy() *StateDB {
|
|||
if s.accessEvents != nil {
|
||||
state.accessEvents = s.accessEvents.Copy()
|
||||
}
|
||||
if s.stateReadList != nil {
|
||||
state.stateReadList = s.stateReadList.Copy()
|
||||
}
|
||||
// Deep copy cached state objects.
|
||||
for addr, obj := range s.stateObjects {
|
||||
state.stateObjects[addr] = obj.deepCopy(state)
|
||||
|
|
@ -740,6 +743,9 @@ func (s *StateDB) Copy() *StateDB {
|
|||
}
|
||||
state.logs[hash] = cpy
|
||||
}
|
||||
if s.stateAccessList != nil {
|
||||
state.stateAccessList = s.stateAccessList.Copy()
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
|
|
@ -775,7 +781,7 @@ type removedAccountWithBalance struct {
|
|||
// before the Finalise.
|
||||
func (s *StateDB) LogsForBurnAccounts() []*types.Log {
|
||||
var list []removedAccountWithBalance
|
||||
for addr := range s.journal.dirties {
|
||||
for addr := range s.journal.mutations {
|
||||
if obj, exist := s.stateObjects[addr]; exist && obj.selfDestructed && !obj.Balance().IsZero() {
|
||||
list = append(list, removedAccountWithBalance{
|
||||
address: obj.address,
|
||||
|
|
@ -799,17 +805,20 @@ func (s *StateDB) LogsForBurnAccounts() []*types.Log {
|
|||
// Finalise finalises the state by removing the destructed objects and clears
|
||||
// the journal as well as the refunds. Finalise, however, will not push any updates
|
||||
// into the tries just yet. Only IntermediateRoot or Commit will do that.
|
||||
func (s *StateDB) Finalise(deleteEmptyObjects bool) *bal.StateAccessList {
|
||||
addressesToPrefetch := make([]common.Address, 0, len(s.journal.dirties))
|
||||
for addr := range s.journal.dirties {
|
||||
func (s *StateDB) Finalise(deleteEmptyObjects bool) *bal.ConstructionBlockAccessList {
|
||||
addressesToPrefetch := make([]common.Address, 0, len(s.journal.mutations))
|
||||
for addr, state := range s.journal.mutations {
|
||||
obj, exist := s.stateObjects[addr]
|
||||
if !exist {
|
||||
// ripeMD is 'touched' at block 1714175, in tx 0x1237f737031e40bcde4a8b7e717b2d15e3ecadfe49bb1bbc71ee9deb09c6fcf2
|
||||
// That tx goes out of gas, and although the notion of 'touched' does not exist there, the
|
||||
// touch-event will still be recorded in the journal. Since ripeMD is a special snowflake,
|
||||
// it will persist in the journal even though the journal is reverted. In this special circumstance,
|
||||
// it may exist in `s.journal.dirties` but not in `s.stateObjects`.
|
||||
// Thus, we can safely ignore it here
|
||||
// RIPEMD160 (0x03) gets an extra dirty marker for a historical
|
||||
// mainnet consensus exception (at block 1714175, in tx
|
||||
// 0x1237f737031e40bcde4a8b7e717b2d15e3ecadfe49bb1bbc71ee9deb09c6fcf2)
|
||||
// around empty-account touch/revert handling.
|
||||
//
|
||||
// That marker survives journal revert, so the account may remain in
|
||||
// s.journal.mutations even though its state object was rolled
|
||||
// back and no longer exists. In that case there is nothing to
|
||||
// finalise or delete, so ignore it here.
|
||||
continue
|
||||
}
|
||||
if obj.selfDestructed || (deleteEmptyObjects && obj.empty()) {
|
||||
|
|
@ -822,7 +831,43 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) *bal.StateAccessList {
|
|||
if _, ok := s.stateObjectsDestruct[obj.address]; !ok {
|
||||
s.stateObjectsDestruct[obj.address] = obj
|
||||
}
|
||||
// Aggregate the account mutation into the block-level accessList
|
||||
// if Amsterdam has been activated.
|
||||
if s.stateAccessList != nil {
|
||||
// Notably, if the account is deleted during the transaction,
|
||||
// its pre-transaction nonce, code, and storage must be empty.
|
||||
//
|
||||
// EIP-6780 restricts self-destruct to contracts deployed within
|
||||
// the same transaction, while EIP-7610 rejects deployments to
|
||||
// destinations with non-empty storage, non-zero nonce and non-empty
|
||||
// code.
|
||||
//
|
||||
// Therefore, when an account is deleted, its pre-transaction nonce
|
||||
// code and storage is guaranteed to be empty, leaving nothing to
|
||||
// clean up here.
|
||||
balance := uint256.NewInt(0)
|
||||
if state.balanceSet && balance.Cmp(state.balance) != 0 {
|
||||
s.stateAccessList.BalanceChange(s.blockAccessIndex, addr, balance)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Aggregate the account mutation into the block-level accessList
|
||||
// if Amsterdam has been activated.
|
||||
if s.stateAccessList != nil {
|
||||
balance := obj.Balance()
|
||||
if state.balanceSet && balance.Cmp(state.balance) != 0 {
|
||||
s.stateAccessList.BalanceChange(s.blockAccessIndex, addr, balance)
|
||||
}
|
||||
nonce := obj.Nonce()
|
||||
if state.nonceSet && nonce != state.nonce {
|
||||
s.stateAccessList.NonceChange(addr, s.blockAccessIndex, nonce)
|
||||
}
|
||||
if state.codeSet {
|
||||
if code := obj.Code(); !bytes.Equal(code, state.code) {
|
||||
s.stateAccessList.CodeChange(addr, s.blockAccessIndex, code)
|
||||
}
|
||||
}
|
||||
}
|
||||
obj.finalise()
|
||||
s.markUpdate(addr)
|
||||
}
|
||||
|
|
@ -839,7 +884,7 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) *bal.StateAccessList {
|
|||
// Invalidate journal because reverting across transactions is not allowed.
|
||||
s.clearJournalAndRefund()
|
||||
|
||||
return s.stateReadList
|
||||
return s.stateAccessList
|
||||
}
|
||||
|
||||
// IntermediateRoot computes the current root hash of the state trie.
|
||||
|
|
@ -1052,9 +1097,10 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
|||
// SetTxContext sets the current transaction hash and index which are
|
||||
// used when the EVM emits new state logs. It should be invoked before
|
||||
// transaction execution.
|
||||
func (s *StateDB) SetTxContext(thash common.Hash, ti int) {
|
||||
func (s *StateDB) SetTxContext(thash common.Hash, ti int, blockAccessIndex uint32) {
|
||||
s.thash = thash
|
||||
s.txIndex = ti
|
||||
s.blockAccessIndex = blockAccessIndex
|
||||
}
|
||||
|
||||
func (s *StateDB) clearJournalAndRefund() {
|
||||
|
|
@ -1355,7 +1401,7 @@ func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool, noStorag
|
|||
|
||||
// The reader update must be performed as the final step, otherwise,
|
||||
// the new state would not be visible before db.commit.
|
||||
s.reader, _ = s.db.Reader(s.originalRoot)
|
||||
s.reader, err = s.db.Reader(s.originalRoot)
|
||||
return ret, err
|
||||
}
|
||||
|
||||
|
|
@ -1435,7 +1481,7 @@ func (s *StateDB) Prepare(rules params.Rules, sender, coinbase common.Address, d
|
|||
s.transientStorage = newTransientStorage()
|
||||
|
||||
if rules.IsAmsterdam {
|
||||
s.stateReadList = bal.NewStateAccessList()
|
||||
s.stateAccessList = bal.NewConstructionBlockAccessList()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -234,7 +234,7 @@ func (s *hookedStateDB) LogsForBurnAccounts() []*types.Log {
|
|||
return s.inner.LogsForBurnAccounts()
|
||||
}
|
||||
|
||||
func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) *bal.StateAccessList {
|
||||
func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) *bal.ConstructionBlockAccessList {
|
||||
if s.hooks.OnBalanceChange == nil && s.hooks.OnNonceChangeV2 == nil && s.hooks.OnNonceChange == nil && s.hooks.OnCodeChangeV2 == nil && s.hooks.OnCodeChange == nil {
|
||||
// Short circuit if no relevant hooks are set.
|
||||
return s.inner.Finalise(deleteEmptyObjects)
|
||||
|
|
@ -244,7 +244,7 @@ func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) *bal.StateAccessList {
|
|||
// that state change hooks will be invoked in deterministic
|
||||
// order when the accounts are deleted below
|
||||
var selfDestructedAddrs []common.Address
|
||||
for addr := range s.inner.journal.dirties {
|
||||
for addr := range s.inner.journal.mutations {
|
||||
obj := s.inner.stateObjects[addr]
|
||||
if obj == nil || !obj.selfDestructed {
|
||||
// Not self-destructed, keep searching.
|
||||
|
|
@ -288,3 +288,7 @@ func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) *bal.StateAccessList {
|
|||
}
|
||||
return s.inner.Finalise(deleteEmptyObjects)
|
||||
}
|
||||
|
||||
func (s *hookedStateDB) SetTxContext(thash common.Hash, ti int, blockAccessIndex uint32) {
|
||||
s.inner.SetTxContext(thash, ti, blockAccessIndex)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ func TestBurn(t *testing.T) {
|
|||
// TestHooks is a basic sanity-check of all hooks
|
||||
func TestHooks(t *testing.T) {
|
||||
inner, _ := New(types.EmptyRootHash, NewDatabaseForTesting())
|
||||
inner.SetTxContext(common.Hash{0x11}, 100) // For the log
|
||||
inner.SetTxContext(common.Hash{0x11}, 100, 101) // For the log
|
||||
var result []string
|
||||
var wants = []string{
|
||||
"0xaa00000000000000000000000000000000000000.balance: 0->100 (Unspecified)",
|
||||
|
|
|
|||
|
|
@ -662,26 +662,30 @@ func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error {
|
|||
return fmt.Errorf("got GetLogs(common.Hash{}) == %v, want GetLogs(common.Hash{}) == %v",
|
||||
state.GetLogs(common.Hash{}, 0, common.Hash{}, 0), checkstate.GetLogs(common.Hash{}, 0, common.Hash{}, 0))
|
||||
}
|
||||
if !maps.Equal(state.journal.dirties, checkstate.journal.dirties) {
|
||||
getKeys := func(dirty map[common.Address]int) string {
|
||||
var keys []common.Address
|
||||
out := new(strings.Builder)
|
||||
for key := range dirty {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
slices.SortFunc(keys, common.Address.Cmp)
|
||||
for i, key := range keys {
|
||||
fmt.Fprintf(out, " %d. %v\n", i, key)
|
||||
}
|
||||
return out.String()
|
||||
}
|
||||
have := getKeys(state.journal.dirties)
|
||||
want := getKeys(checkstate.journal.dirties)
|
||||
return fmt.Errorf("dirty-journal set mismatch.\nhave:\n%v\nwant:\n%v\n", have, want)
|
||||
if !equalMutationSets(state.journal.mutations, checkstate.journal.mutations) {
|
||||
return fmt.Errorf("journal mutation set mismatch.\nhave:\n%v\nwant:\n%v\n", state.journal.mutations, checkstate.journal.mutations)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// equalMutationSets checks that two journal mutation maps have the same set of
|
||||
// addresses and, for each address, the same per-kind counts. The stashed
|
||||
// original values are ignored because comparing them across two independent
|
||||
// state databases (with distinct pointer identities) isn't the point of this
|
||||
// check — we only care that the two journals agree on what was touched.
|
||||
func equalMutationSets(a, b map[common.Address]*journalMutationState) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for addr, sa := range a {
|
||||
sb, ok := b[addr]
|
||||
if !ok || sa.counts != sb.counts {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func TestTouchDelete(t *testing.T) {
|
||||
s := newStateEnv()
|
||||
s.state.getOrNewStateObject(common.Address{})
|
||||
|
|
@ -691,12 +695,54 @@ func TestTouchDelete(t *testing.T) {
|
|||
snapshot := s.state.Snapshot()
|
||||
s.state.AddBalance(common.Address{}, new(uint256.Int), tracing.BalanceChangeUnspecified)
|
||||
|
||||
if len(s.state.journal.dirties) != 1 {
|
||||
t.Fatal("expected one dirty state object")
|
||||
if len(s.state.journal.mutations) != 1 {
|
||||
t.Fatal("expected one mutated state object")
|
||||
}
|
||||
s.state.RevertToSnapshot(snapshot)
|
||||
if len(s.state.journal.dirties) != 0 {
|
||||
t.Fatal("expected no dirty state object")
|
||||
if len(s.state.journal.mutations) != 0 {
|
||||
t.Fatal("expected no journal mutations")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJournalMutationTracking(t *testing.T) {
|
||||
state, _ := New(types.EmptyRootHash, NewDatabaseForTesting())
|
||||
addr := common.HexToAddress("0x01")
|
||||
key := common.HexToHash("0x02")
|
||||
|
||||
if _, ok := state.journal.mutations[addr]; ok {
|
||||
t.Fatal("unexpected initial mutation entry")
|
||||
}
|
||||
snapshot := state.Snapshot()
|
||||
|
||||
state.SetBalance(addr, uint256.NewInt(1), tracing.BalanceChangeUnspecified)
|
||||
state.SetNonce(addr, 2, tracing.NonceChangeUnspecified)
|
||||
state.SetCode(addr, []byte{0x1}, tracing.CodeChangeUnspecified)
|
||||
state.SetState(addr, key, common.Hash{0x3})
|
||||
|
||||
want := journalMutationCounts{
|
||||
journalMutationKindCreate: 1,
|
||||
journalMutationKindBalance: 1,
|
||||
journalMutationKindNonce: 1,
|
||||
journalMutationKindCode: 1,
|
||||
journalMutationKindStorage: 1,
|
||||
}
|
||||
checkCounts := func(got *journalMutationState, label string) {
|
||||
t.Helper()
|
||||
if got == nil {
|
||||
t.Fatalf("%s: missing mutation entry for %x", label, addr)
|
||||
}
|
||||
if got.counts != want {
|
||||
t.Fatalf("%s: counts=%+v, want=%+v", label, got.counts, want)
|
||||
}
|
||||
}
|
||||
checkCounts(state.journal.mutations[addr], "state")
|
||||
|
||||
copy := state.Copy()
|
||||
checkCounts(copy.journal.mutations[addr], "copy")
|
||||
|
||||
state.RevertToSnapshot(snapshot)
|
||||
if _, ok := state.journal.mutations[addr]; ok {
|
||||
t.Fatalf("unexpected mutation entry after revert")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c
|
|||
// Disable the nonce check
|
||||
msg.SkipNonceChecks = true
|
||||
|
||||
stateCpy.SetTxContext(tx.Hash(), i)
|
||||
stateCpy.SetTxContext(tx.Hash(), i, uint32(i+1))
|
||||
|
||||
// We attempt to apply a transaction. The goal is not to execute
|
||||
// the transaction successfully, rather to warm up touched data slots.
|
||||
|
|
|
|||
|
|
@ -27,11 +27,13 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/internal/telemetry"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
// StateProcessor is a basic Processor, which takes care of transitioning
|
||||
|
|
@ -75,27 +77,20 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated
|
|||
if hooks := cfg.Tracer; hooks != nil {
|
||||
tracingStateDB = state.NewHookedState(statedb, hooks)
|
||||
}
|
||||
|
||||
// Mutate the block and state according to any hard-fork specs
|
||||
if config.DAOForkSupport && config.DAOForkBlock != nil && config.DAOForkBlock.Cmp(block.Number()) == 0 {
|
||||
misc.ApplyDAOHardFork(tracingStateDB)
|
||||
}
|
||||
var (
|
||||
context vm.BlockContext
|
||||
signer = types.MakeSigner(config, header.Number, header.Time)
|
||||
context = NewEVMBlockContext(header, p.chain, nil)
|
||||
signer = types.MakeSigner(config, header.Number, header.Time)
|
||||
evm = vm.NewEVM(context, tracingStateDB, config, cfg)
|
||||
blockAccessList = bal.NewConstructionBlockAccessList()
|
||||
)
|
||||
|
||||
// Apply pre-execution system calls.
|
||||
context = NewEVMBlockContext(header, p.chain, nil)
|
||||
evm := vm.NewEVM(context, tracingStateDB, config, cfg)
|
||||
defer evm.Release()
|
||||
|
||||
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
|
||||
ProcessBeaconBlockRoot(*beaconRoot, evm)
|
||||
}
|
||||
if config.IsPrague(block.Number(), block.Time()) || config.IsUBT(block.Number(), block.Time()) {
|
||||
ProcessParentBlockHash(block.ParentHash(), evm)
|
||||
}
|
||||
// Run the pre-execution system calls
|
||||
blockAccessList.Merge(PreExecution(ctx, block.BeaconRoot(), block.ParentHash(), config, evm, block.Number(), block.Time()))
|
||||
|
||||
// Iterate over and process the individual transactions
|
||||
for i, tx := range block.Transactions() {
|
||||
|
|
@ -103,66 +98,97 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated
|
|||
if err != nil {
|
||||
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, uint32(i+1))
|
||||
_, _, spanEnd := telemetry.StartSpan(ctx, "core.ApplyTransactionWithEVM",
|
||||
telemetry.StringAttribute("tx.hash", tx.Hash().Hex()),
|
||||
telemetry.Int64Attribute("tx.index", int64(i)),
|
||||
)
|
||||
|
||||
receipt, err := ApplyTransactionWithEVM(msg, gp, statedb, blockNumber, blockHash, context.Time, tx, evm)
|
||||
receipt, bal, err := ApplyTransactionWithEVM(msg, gp, statedb, blockNumber, blockHash, context.Time, tx, evm)
|
||||
if err != nil {
|
||||
spanEnd(&err)
|
||||
return nil, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
|
||||
}
|
||||
receipts = append(receipts, receipt)
|
||||
allLogs = append(allLogs, receipt.Logs...)
|
||||
blockAccessList.Merge(bal)
|
||||
spanEnd(nil)
|
||||
}
|
||||
requests, err := postExecution(ctx, config, block, allLogs, evm)
|
||||
requests, bal, err := PostExecution(ctx, config, block.Number(), block.Time(), allLogs, evm, uint32(len(block.Transactions())+1))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
blockAccessList.Merge(bal)
|
||||
|
||||
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
|
||||
p.chain.Engine().Finalize(p.chain, header, tracingStateDB, block.Body())
|
||||
// Finalize the block, applying any consensus engine specific extras
|
||||
// (e.g. block rewards).
|
||||
//
|
||||
// TODO(rjl493456442) integrate it into the PostExecution.
|
||||
p.chain.Engine().Finalize(p.chain, header, tracingStateDB, block.Body(), uint32(len(block.Transactions())+1), blockAccessList)
|
||||
|
||||
return &ProcessResult{
|
||||
Receipts: receipts,
|
||||
Requests: requests,
|
||||
Logs: allLogs,
|
||||
GasUsed: gp.Used(),
|
||||
Bal: blockAccessList,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// postExecution processes the post-execution system calls if Prague is enabled.
|
||||
func postExecution(ctx context.Context, config *params.ChainConfig, block *types.Block, allLogs []*types.Log, evm *vm.EVM) (requests [][]byte, err error) {
|
||||
// PreExecution processes pre-execution system calls.
|
||||
func PreExecution(ctx context.Context, beaconRoot *common.Hash, parent common.Hash, config *params.ChainConfig, evm *vm.EVM, number *big.Int, time uint64) *bal.ConstructionBlockAccessList {
|
||||
_, _, spanEnd := telemetry.StartSpan(ctx, "core.preExecution")
|
||||
defer spanEnd(nil)
|
||||
|
||||
var blockAccessList *bal.ConstructionBlockAccessList
|
||||
if config.IsAmsterdam(number, time) {
|
||||
blockAccessList = bal.NewConstructionBlockAccessList()
|
||||
}
|
||||
// EIP-4788
|
||||
if beaconRoot != nil {
|
||||
ProcessBeaconBlockRoot(*beaconRoot, evm, blockAccessList)
|
||||
}
|
||||
// EIP-2935
|
||||
if config.IsPrague(number, time) || config.IsUBT(number, time) {
|
||||
ProcessParentBlockHash(parent, evm, blockAccessList)
|
||||
}
|
||||
return blockAccessList
|
||||
}
|
||||
|
||||
// PostExecution processes post-execution system calls when Prague is enabled.
|
||||
// If Prague is not activated, it returns null requests to differentiate from
|
||||
// empty requests.
|
||||
func PostExecution(ctx context.Context, config *params.ChainConfig, number *big.Int, time uint64, allLogs []*types.Log, evm *vm.EVM, blockAccessIndex uint32) (requests [][]byte, blockAccessList *bal.ConstructionBlockAccessList, err error) {
|
||||
_, _, spanEnd := telemetry.StartSpan(ctx, "core.postExecution")
|
||||
defer spanEnd(&err)
|
||||
|
||||
if config.IsAmsterdam(number, time) {
|
||||
blockAccessList = bal.NewConstructionBlockAccessList()
|
||||
}
|
||||
// Read requests if Prague is enabled.
|
||||
if config.IsPrague(block.Number(), block.Time()) {
|
||||
if config.IsPrague(number, time) {
|
||||
rules := config.Rules(number, true, time) // IsMerge is always true
|
||||
|
||||
requests = [][]byte{}
|
||||
// EIP-6110
|
||||
if err := ParseDepositLogs(&requests, allLogs, config); err != nil {
|
||||
return requests, fmt.Errorf("failed to parse deposit logs: %w", err)
|
||||
return nil, nil, fmt.Errorf("failed to parse deposit logs: %w", err)
|
||||
}
|
||||
// EIP-7002
|
||||
if err := ProcessWithdrawalQueue(&requests, evm); err != nil {
|
||||
return requests, fmt.Errorf("failed to process withdrawal queue: %w", err)
|
||||
if err := ProcessWithdrawalQueue(&requests, rules, evm, blockAccessIndex, blockAccessList); err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to process withdrawal queue: %w", err)
|
||||
}
|
||||
// EIP-7251
|
||||
if err := ProcessConsolidationQueue(&requests, evm); err != nil {
|
||||
return requests, fmt.Errorf("failed to process consolidation queue: %w", err)
|
||||
if err := ProcessConsolidationQueue(&requests, rules, evm, blockAccessIndex, blockAccessList); err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to process consolidation queue: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return requests, nil
|
||||
return requests, blockAccessList, nil
|
||||
}
|
||||
|
||||
// ApplyTransactionWithEVM attempts to apply a transaction to the given state database
|
||||
// and uses the input parameters for its environment similar to ApplyTransaction. However,
|
||||
// this method takes an already created EVM instance as input.
|
||||
func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, blockTime uint64, tx *types.Transaction, evm *vm.EVM) (receipt *types.Receipt, err error) {
|
||||
func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, blockTime uint64, tx *types.Transaction, evm *vm.EVM) (receipt *types.Receipt, bal *bal.ConstructionBlockAccessList, err error) {
|
||||
if hooks := evm.Config.Tracer; hooks != nil {
|
||||
if hooks.OnTxStart != nil {
|
||||
hooks.OnTxStart(evm.GetVMContext(), tx, msg.From)
|
||||
|
|
@ -174,12 +200,12 @@ func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb *state.StateDB,
|
|||
// Apply the transaction to the current state (included in the env).
|
||||
result, err := ApplyMessage(evm, msg, gp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
// Update the state with pending changes.
|
||||
var root []byte
|
||||
if evm.ChainConfig().IsByzantium(blockNumber) {
|
||||
evm.StateDB.Finalise(true)
|
||||
bal = evm.StateDB.Finalise(true)
|
||||
} else {
|
||||
root = statedb.IntermediateRoot(evm.ChainConfig().IsEIP158(blockNumber)).Bytes()
|
||||
}
|
||||
|
|
@ -188,7 +214,7 @@ func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb *state.StateDB,
|
|||
if statedb.Database().Type().Is(state.TypeUBT) {
|
||||
statedb.AccessEvents().Merge(evm.AccessEvents)
|
||||
}
|
||||
return MakeReceipt(evm, result, statedb, blockNumber, blockHash, blockTime, tx, gp.CumulativeUsed(), root), nil
|
||||
return MakeReceipt(evm, result, statedb, blockNumber, blockHash, blockTime, tx, gp.CumulativeUsed(), root), bal, nil
|
||||
}
|
||||
|
||||
// MakeReceipt generates the receipt object for a transaction given its execution result.
|
||||
|
|
@ -233,10 +259,10 @@ func MakeReceipt(evm *vm.EVM, result *ExecutionResult, statedb *state.StateDB, b
|
|||
// and uses the input parameters for its environment. It returns the receipt
|
||||
// for the transaction and an error if the transaction failed,
|
||||
// indicating the block was invalid.
|
||||
func ApplyTransaction(evm *vm.EVM, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction) (*types.Receipt, error) {
|
||||
func ApplyTransaction(evm *vm.EVM, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction) (*types.Receipt, *bal.ConstructionBlockAccessList, error) {
|
||||
msg, err := TransactionToMessage(tx, types.MakeSigner(evm.ChainConfig(), header.Number, header.Time), header.BaseFee)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
// Create a new context to be used in the EVM environment
|
||||
return ApplyTransactionWithEVM(msg, gp, statedb, header.Number, header.Hash(), header.Time, tx, evm)
|
||||
|
|
@ -244,7 +270,7 @@ func ApplyTransaction(evm *vm.EVM, gp *GasPool, statedb *state.StateDB, header *
|
|||
|
||||
// ProcessBeaconBlockRoot applies the EIP-4788 system call to the beacon block root
|
||||
// contract. This method is exported to be used in tests.
|
||||
func ProcessBeaconBlockRoot(beaconRoot common.Hash, evm *vm.EVM) {
|
||||
func ProcessBeaconBlockRoot(beaconRoot common.Hash, evm *vm.EVM, blockAccessList *bal.ConstructionBlockAccessList) {
|
||||
if tracer := evm.Config.Tracer; tracer != nil {
|
||||
onSystemCallStart(tracer, evm.GetVMContext())
|
||||
if tracer.OnSystemCallEnd != nil {
|
||||
|
|
@ -254,24 +280,26 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, evm *vm.EVM) {
|
|||
msg := &Message{
|
||||
From: params.SystemAddress,
|
||||
GasLimit: 30_000_000,
|
||||
GasPrice: common.Big0,
|
||||
GasFeeCap: common.Big0,
|
||||
GasTipCap: common.Big0,
|
||||
GasPrice: uint256.NewInt(0),
|
||||
GasFeeCap: uint256.NewInt(0),
|
||||
GasTipCap: uint256.NewInt(0),
|
||||
To: ¶ms.BeaconRootsAddress,
|
||||
Data: beaconRoot[:],
|
||||
}
|
||||
evm.SetTxContext(NewEVMTxContext(msg))
|
||||
evm.StateDB.Prepare(evm.GetRules(), common.Address{}, common.Address{}, nil, nil, nil)
|
||||
evm.StateDB.SetTxContext(common.Hash{}, 0, 0)
|
||||
evm.StateDB.AddAddressToAccessList(params.BeaconRootsAddress)
|
||||
_, _, _ = evm.Call(msg.From, *msg.To, msg.Data, vm.NewGasBudget(30_000_000), common.U2560)
|
||||
if evm.StateDB.AccessEvents() != nil {
|
||||
evm.StateDB.AccessEvents().Merge(evm.AccessEvents)
|
||||
}
|
||||
evm.StateDB.Finalise(true)
|
||||
blockAccessList.Merge(evm.StateDB.Finalise(true))
|
||||
}
|
||||
|
||||
// ProcessParentBlockHash stores the parent block hash in the history storage contract
|
||||
// as per EIP-2935/7709.
|
||||
func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM) {
|
||||
func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM, blockAccessList *bal.ConstructionBlockAccessList) {
|
||||
if tracer := evm.Config.Tracer; tracer != nil {
|
||||
onSystemCallStart(tracer, evm.GetVMContext())
|
||||
if tracer.OnSystemCallEnd != nil {
|
||||
|
|
@ -281,13 +309,15 @@ func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM) {
|
|||
msg := &Message{
|
||||
From: params.SystemAddress,
|
||||
GasLimit: 30_000_000,
|
||||
GasPrice: common.Big0,
|
||||
GasFeeCap: common.Big0,
|
||||
GasTipCap: common.Big0,
|
||||
GasPrice: uint256.NewInt(0),
|
||||
GasFeeCap: uint256.NewInt(0),
|
||||
GasTipCap: uint256.NewInt(0),
|
||||
To: ¶ms.HistoryStorageAddress,
|
||||
Data: prevHash.Bytes(),
|
||||
}
|
||||
evm.SetTxContext(NewEVMTxContext(msg))
|
||||
evm.StateDB.Prepare(evm.GetRules(), common.Address{}, common.Address{}, nil, nil, nil)
|
||||
evm.StateDB.SetTxContext(common.Hash{}, 0, 0)
|
||||
evm.StateDB.AddAddressToAccessList(params.HistoryStorageAddress)
|
||||
_, _, err := evm.Call(msg.From, *msg.To, msg.Data, vm.NewGasBudget(30_000_000), common.U2560)
|
||||
if err != nil {
|
||||
|
|
@ -296,22 +326,22 @@ func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM) {
|
|||
if evm.StateDB.AccessEvents() != nil {
|
||||
evm.StateDB.AccessEvents().Merge(evm.AccessEvents)
|
||||
}
|
||||
evm.StateDB.Finalise(true)
|
||||
blockAccessList.Merge(evm.StateDB.Finalise(true))
|
||||
}
|
||||
|
||||
// ProcessWithdrawalQueue calls the EIP-7002 withdrawal queue contract.
|
||||
// It returns the opaque request data returned by the contract.
|
||||
func ProcessWithdrawalQueue(requests *[][]byte, evm *vm.EVM) error {
|
||||
return processRequestsSystemCall(requests, evm, 0x01, params.WithdrawalQueueAddress)
|
||||
func ProcessWithdrawalQueue(requests *[][]byte, rules params.Rules, evm *vm.EVM, blockAccessIndex uint32, blockAccessList *bal.ConstructionBlockAccessList) error {
|
||||
return processRequestsSystemCall(requests, rules, evm, 0x01, params.WithdrawalQueueAddress, blockAccessIndex, blockAccessList)
|
||||
}
|
||||
|
||||
// ProcessConsolidationQueue calls the EIP-7251 consolidation queue contract.
|
||||
// It returns the opaque request data returned by the contract.
|
||||
func ProcessConsolidationQueue(requests *[][]byte, evm *vm.EVM) error {
|
||||
return processRequestsSystemCall(requests, evm, 0x02, params.ConsolidationQueueAddress)
|
||||
func ProcessConsolidationQueue(requests *[][]byte, rules params.Rules, evm *vm.EVM, blockAccessIndex uint32, blockAccessList *bal.ConstructionBlockAccessList) error {
|
||||
return processRequestsSystemCall(requests, rules, evm, 0x02, params.ConsolidationQueueAddress, blockAccessIndex, blockAccessList)
|
||||
}
|
||||
|
||||
func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte, addr common.Address) error {
|
||||
func processRequestsSystemCall(requests *[][]byte, rules params.Rules, evm *vm.EVM, requestType byte, addr common.Address, blockAccessIndex uint32, blockAccessList *bal.ConstructionBlockAccessList) error {
|
||||
if tracer := evm.Config.Tracer; tracer != nil {
|
||||
onSystemCallStart(tracer, evm.GetVMContext())
|
||||
if tracer.OnSystemCallEnd != nil {
|
||||
|
|
@ -321,21 +351,25 @@ func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte
|
|||
msg := &Message{
|
||||
From: params.SystemAddress,
|
||||
GasLimit: 30_000_000,
|
||||
GasPrice: common.Big0,
|
||||
GasFeeCap: common.Big0,
|
||||
GasTipCap: common.Big0,
|
||||
GasPrice: uint256.NewInt(0),
|
||||
GasFeeCap: uint256.NewInt(0),
|
||||
GasTipCap: uint256.NewInt(0),
|
||||
To: &addr,
|
||||
}
|
||||
evm.SetTxContext(NewEVMTxContext(msg))
|
||||
evm.StateDB.Prepare(rules, common.Address{}, common.Address{}, nil, nil, nil)
|
||||
evm.StateDB.SetTxContext(common.Hash{}, 0, blockAccessIndex)
|
||||
evm.StateDB.AddAddressToAccessList(addr)
|
||||
ret, _, err := evm.Call(msg.From, *msg.To, msg.Data, vm.NewGasBudget(30_000_000), common.U2560)
|
||||
if evm.StateDB.AccessEvents() != nil {
|
||||
evm.StateDB.AccessEvents().Merge(evm.AccessEvents)
|
||||
}
|
||||
evm.StateDB.Finalise(true)
|
||||
bal := evm.StateDB.Finalise(true)
|
||||
if err != nil {
|
||||
return fmt.Errorf("system call failed to execute: %v", err)
|
||||
}
|
||||
blockAccessList.Merge(bal)
|
||||
|
||||
if len(ret) == 0 {
|
||||
return nil // skip empty output
|
||||
}
|
||||
|
|
@ -378,8 +412,16 @@ func onSystemCallStart(tracer *tracing.Hooks, ctx *tracing.VMContext) {
|
|||
|
||||
// AssembleBlock finalizes the state and assembles the block with provided
|
||||
// body and receipts.
|
||||
func AssembleBlock(engine consensus.Engine, chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) *types.Block {
|
||||
engine.Finalize(chain, header, state, body)
|
||||
func AssembleBlock(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt, blockAccessList *bal.ConstructionBlockAccessList) *types.Block {
|
||||
// Assign the post-transition state root
|
||||
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
|
||||
return types.NewBlock(header, body, receipts, trie.NewStackTrie(nil))
|
||||
|
||||
if !chain.Config().IsAmsterdam(header.Number, header.Time) {
|
||||
return types.NewBlock(header, body, receipts, trie.NewStackTrie(nil))
|
||||
}
|
||||
// Assign the BlockAccessListHash if Amsterdam has been enabled
|
||||
bal := blockAccessList.ToEncodingObj()
|
||||
balHash := bal.Hash()
|
||||
header.BlockAccessListHash = &balHash
|
||||
return types.NewBlock(header, body, receipts, trie.NewStackTrie(nil)).WithAccessListUnsafe(bal)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ func (result *ExecutionResult) Revert() []byte {
|
|||
}
|
||||
|
||||
// IntrinsicGas computes the 'intrinsic gas' for a message with the given data.
|
||||
func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.SetCodeAuthorization, isContractCreation, isHomestead, isEIP2028, isEIP3860 bool) (vm.GasCosts, error) {
|
||||
func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.SetCodeAuthorization, isContractCreation, isHomestead, isEIP2028, isEIP3860, isAmsterdam bool) (vm.GasCosts, error) {
|
||||
// Set the starting gas for the raw transaction
|
||||
var gas uint64
|
||||
if isContractCreation && isHomestead {
|
||||
|
|
@ -107,8 +107,32 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set
|
|||
}
|
||||
}
|
||||
if accessList != nil {
|
||||
gas += uint64(len(accessList)) * params.TxAccessListAddressGas
|
||||
gas += uint64(accessList.StorageKeys()) * params.TxAccessListStorageKeyGas
|
||||
addresses := uint64(len(accessList))
|
||||
storageKeys := uint64(accessList.StorageKeys())
|
||||
if (math.MaxUint64-gas)/params.TxAccessListAddressGas < addresses {
|
||||
return vm.GasCosts{}, ErrGasUintOverflow
|
||||
}
|
||||
gas += addresses * params.TxAccessListAddressGas
|
||||
if (math.MaxUint64-gas)/params.TxAccessListStorageKeyGas < storageKeys {
|
||||
return vm.GasCosts{}, ErrGasUintOverflow
|
||||
}
|
||||
gas += storageKeys * params.TxAccessListStorageKeyGas
|
||||
|
||||
// EIP-7981: access list data is charged in addition to the base charge.
|
||||
if isAmsterdam {
|
||||
const (
|
||||
addressCost = common.AddressLength * params.TxCostFloorPerToken7976 * params.TxTokenPerNonZeroByte
|
||||
storageKeyCost = common.HashLength * params.TxCostFloorPerToken7976 * params.TxTokenPerNonZeroByte
|
||||
)
|
||||
if (math.MaxUint64-gas)/addressCost < addresses {
|
||||
return vm.GasCosts{}, ErrGasUintOverflow
|
||||
}
|
||||
gas += addresses * addressCost
|
||||
if (math.MaxUint64-gas)/storageKeyCost < storageKeys {
|
||||
return vm.GasCosts{}, ErrGasUintOverflow
|
||||
}
|
||||
gas += storageKeys * storageKeyCost
|
||||
}
|
||||
}
|
||||
if authList != nil {
|
||||
gas += uint64(len(authList)) * params.CallNewAccountGas
|
||||
|
|
@ -117,7 +141,7 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set
|
|||
}
|
||||
|
||||
// FloorDataGas computes the minimum gas required for a transaction based on its data tokens (EIP-7623).
|
||||
func FloorDataGas(rules params.Rules, data []byte) (uint64, error) {
|
||||
func FloorDataGas(rules params.Rules, data []byte, accessList types.AccessList) (uint64, error) {
|
||||
var (
|
||||
tokens uint64
|
||||
tokenCost uint64
|
||||
|
|
@ -125,15 +149,41 @@ func FloorDataGas(rules params.Rules, data []byte) (uint64, error) {
|
|||
if rules.IsAmsterdam {
|
||||
// EIP-7976 changes how calldata is priced.
|
||||
// From 10/40 to 64/64 for zero/non-zero bytes.
|
||||
tokens = uint64(len(data)) * params.TxTokenPerNonZeroByte
|
||||
tokenCost = params.TxCostFloorPerToken7976
|
||||
dataLen := uint64(len(data))
|
||||
if math.MaxUint64/params.TxTokenPerNonZeroByte < dataLen {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
tokens = dataLen * params.TxTokenPerNonZeroByte
|
||||
|
||||
// EIP-7981 adds additional tokens for every entry in the accesslist
|
||||
const addressTokenCost = uint64(common.AddressLength) * params.TxTokenPerNonZeroByte
|
||||
addresses := uint64(len(accessList))
|
||||
if (math.MaxUint64-tokens)/addressTokenCost < addresses {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
tokens += addresses * addressTokenCost
|
||||
|
||||
const storageKeyTokenCost = uint64(common.HashLength) * params.TxTokenPerNonZeroByte
|
||||
storageKeys := uint64(accessList.StorageKeys())
|
||||
if (math.MaxUint64-tokens)/storageKeyTokenCost < storageKeys {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
tokens += storageKeys * storageKeyTokenCost
|
||||
} else {
|
||||
var (
|
||||
z = uint64(bytes.Count(data, []byte{0}))
|
||||
nz = uint64(len(data)) - z
|
||||
)
|
||||
// Pre-Amsterdam
|
||||
tokens = nz*params.TxTokenPerNonZeroByte + z
|
||||
if math.MaxUint64/params.TxTokenPerNonZeroByte < nz {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
tokens = nz * params.TxTokenPerNonZeroByte
|
||||
if math.MaxUint64-tokens < z {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
tokens += z
|
||||
tokenCost = params.TxCostFloorPerToken
|
||||
}
|
||||
|
||||
|
|
@ -160,14 +210,14 @@ type Message struct {
|
|||
To *common.Address
|
||||
From common.Address
|
||||
Nonce uint64
|
||||
Value *big.Int
|
||||
Value *uint256.Int
|
||||
GasLimit uint64
|
||||
GasPrice *big.Int
|
||||
GasFeeCap *big.Int
|
||||
GasTipCap *big.Int
|
||||
GasPrice *uint256.Int
|
||||
GasFeeCap *uint256.Int
|
||||
GasTipCap *uint256.Int
|
||||
Data []byte
|
||||
AccessList types.AccessList
|
||||
BlobGasFeeCap *big.Int
|
||||
BlobGasFeeCap *uint256.Int
|
||||
BlobHashes []common.Hash
|
||||
SetCodeAuthorizations []types.SetCodeAuthorization
|
||||
|
||||
|
|
@ -188,32 +238,64 @@ type Message struct {
|
|||
|
||||
// TransactionToMessage converts a transaction into a Message.
|
||||
func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.Int) (*Message, error) {
|
||||
from, err := types.Sender(s, tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gasPrice, overflow := uint256.FromBig(tx.GasPrice())
|
||||
if overflow {
|
||||
return nil, fmt.Errorf("%w: address %v, maxFeePerGas bit length: %d", ErrFeeCapVeryHigh,
|
||||
from.Hex(), tx.GasPrice().BitLen())
|
||||
}
|
||||
txGasFeeCap := tx.GasFeeCap()
|
||||
gasFeeCap, overflow := uint256.FromBig(txGasFeeCap)
|
||||
if overflow {
|
||||
return nil, fmt.Errorf("%w: address %v, maxFeePerGas bit length: %d", ErrFeeCapVeryHigh,
|
||||
from.Hex(), tx.GasFeeCap().BitLen())
|
||||
}
|
||||
txGasTipCap := tx.GasTipCap()
|
||||
gasTipCap, overflow := uint256.FromBig(txGasTipCap)
|
||||
if overflow {
|
||||
return nil, fmt.Errorf("%w: address %v, maxPriorityFeePerGas bit length: %d", ErrTipVeryHigh,
|
||||
from.Hex(), tx.GasTipCap().BitLen())
|
||||
}
|
||||
value, overflow := uint256.FromBig(tx.Value())
|
||||
if overflow {
|
||||
return nil, fmt.Errorf("value exceeds 256 bits: address %v", from.Hex())
|
||||
}
|
||||
blobGasFeeCap, overflow := uint256.FromBig(tx.BlobGasFeeCap())
|
||||
if overflow {
|
||||
return nil, fmt.Errorf("blobGasFeeCap exceeds 256 bits: address %v", from.Hex())
|
||||
}
|
||||
|
||||
msg := &Message{
|
||||
From: from,
|
||||
Nonce: tx.Nonce(),
|
||||
GasLimit: tx.Gas(),
|
||||
GasPrice: tx.GasPrice(),
|
||||
GasFeeCap: tx.GasFeeCap(),
|
||||
GasTipCap: tx.GasTipCap(),
|
||||
GasPrice: gasPrice,
|
||||
GasFeeCap: gasFeeCap,
|
||||
GasTipCap: gasTipCap,
|
||||
To: tx.To(),
|
||||
Value: tx.Value(),
|
||||
Value: value,
|
||||
Data: tx.Data(),
|
||||
AccessList: tx.AccessList(),
|
||||
SetCodeAuthorizations: tx.SetCodeAuthorizations(),
|
||||
SkipNonceChecks: false,
|
||||
SkipTransactionChecks: false,
|
||||
BlobHashes: tx.BlobHashes(),
|
||||
BlobGasFeeCap: tx.BlobGasFeeCap(),
|
||||
BlobGasFeeCap: blobGasFeeCap,
|
||||
}
|
||||
// If baseFee provided, set gasPrice to effectiveGasPrice.
|
||||
if baseFee != nil {
|
||||
msg.GasPrice = msg.GasPrice.Add(msg.GasTipCap, baseFee)
|
||||
if msg.GasPrice.Cmp(msg.GasFeeCap) > 0 {
|
||||
msg.GasPrice = msg.GasFeeCap
|
||||
effectiveGasPrice := new(big.Int).Add(baseFee, txGasTipCap)
|
||||
if effectiveGasPrice.Cmp(txGasFeeCap) > 0 {
|
||||
effectiveGasPrice = txGasFeeCap
|
||||
}
|
||||
// EffectiveGasPrice is already capped by txGasFeeCap, therefore
|
||||
// the overflow check is not required.
|
||||
msg.GasPrice = uint256.MustFromBig(effectiveGasPrice)
|
||||
}
|
||||
var err error
|
||||
msg.From, err = types.Sender(s, tx)
|
||||
return msg, err
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
// ApplyMessage computes the new state by applying the given message
|
||||
|
|
@ -283,46 +365,70 @@ func (st *stateTransition) to() common.Address {
|
|||
}
|
||||
|
||||
func (st *stateTransition) buyGas() error {
|
||||
mgval := new(big.Int).SetUint64(st.msg.GasLimit)
|
||||
mgval.Mul(mgval, st.msg.GasPrice)
|
||||
balanceCheck := new(big.Int).Set(mgval)
|
||||
mgval := new(uint256.Int).SetUint64(st.msg.GasLimit)
|
||||
_, overflow := mgval.MulOverflow(mgval, st.msg.GasPrice)
|
||||
if overflow {
|
||||
return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex())
|
||||
}
|
||||
balanceCheck := new(uint256.Int).Set(mgval)
|
||||
if st.msg.GasFeeCap != nil {
|
||||
balanceCheck.SetUint64(st.msg.GasLimit)
|
||||
balanceCheck = balanceCheck.Mul(balanceCheck, st.msg.GasFeeCap)
|
||||
if _, overflow := balanceCheck.MulOverflow(balanceCheck, st.msg.GasFeeCap); overflow {
|
||||
return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex())
|
||||
}
|
||||
}
|
||||
if st.msg.Value != nil {
|
||||
if _, overflow := balanceCheck.AddOverflow(balanceCheck, st.msg.Value); overflow {
|
||||
return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex())
|
||||
}
|
||||
}
|
||||
balanceCheck.Add(balanceCheck, st.msg.Value)
|
||||
|
||||
if st.evm.ChainConfig().IsCancun(st.evm.Context.BlockNumber, st.evm.Context.Time) {
|
||||
if blobGas := st.blobGasUsed(); blobGas > 0 {
|
||||
// Check that the user has enough funds to cover blobGasUsed * tx.BlobGasFeeCap
|
||||
blobBalanceCheck := new(big.Int).SetUint64(blobGas)
|
||||
blobBalanceCheck.Mul(blobBalanceCheck, st.msg.BlobGasFeeCap)
|
||||
balanceCheck.Add(balanceCheck, blobBalanceCheck)
|
||||
blobBalanceCheck := new(uint256.Int).SetUint64(blobGas)
|
||||
if _, overflow := blobBalanceCheck.MulOverflow(blobBalanceCheck, st.msg.BlobGasFeeCap); overflow {
|
||||
return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex())
|
||||
}
|
||||
if _, overflow := balanceCheck.AddOverflow(balanceCheck, blobBalanceCheck); overflow {
|
||||
return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex())
|
||||
}
|
||||
// Pay for blobGasUsed * actual blob fee
|
||||
blobFee := new(big.Int).SetUint64(blobGas)
|
||||
blobFee.Mul(blobFee, st.evm.Context.BlobBaseFee)
|
||||
mgval.Add(mgval, blobFee)
|
||||
blobBaseFee, overflow := uint256.FromBig(st.evm.Context.BlobBaseFee)
|
||||
if overflow {
|
||||
return fmt.Errorf("invalid blobBaseFee: %v", st.evm.Context.BlobBaseFee)
|
||||
}
|
||||
blobFee := new(uint256.Int).SetUint64(blobGas)
|
||||
|
||||
// In practice, overflow checking is unnecessary, as blobBaseFee cannot exceed
|
||||
// BlobGasFeeCap. However, in eth_call it is still possible for users to specify
|
||||
// an excessively large blob base fee and bypass the blob base fee validation.
|
||||
_, overflow = blobFee.MulOverflow(blobFee, blobBaseFee)
|
||||
if overflow {
|
||||
return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex())
|
||||
}
|
||||
_, overflow = mgval.AddOverflow(mgval, blobFee)
|
||||
if overflow {
|
||||
return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex())
|
||||
}
|
||||
}
|
||||
}
|
||||
balanceCheckU256, overflow := uint256.FromBig(balanceCheck)
|
||||
if overflow {
|
||||
return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex())
|
||||
}
|
||||
if have, want := st.state.GetBalance(st.msg.From), balanceCheckU256; have.Cmp(want) < 0 {
|
||||
if have, want := st.state.GetBalance(st.msg.From), balanceCheck; have.Cmp(want) < 0 {
|
||||
return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want)
|
||||
}
|
||||
if err := st.gp.SubGas(st.msg.GasLimit); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil {
|
||||
st.evm.Config.Tracer.OnGasChange(0, st.msg.GasLimit, tracing.GasChangeTxInitialBalance)
|
||||
if st.evm.Config.Tracer.HasGasHook() {
|
||||
empty := vm.GasBudget{}
|
||||
initial := vm.NewGasBudget(st.msg.GasLimit)
|
||||
st.evm.Config.Tracer.EmitGasChange(empty.AsTracing(), initial.AsTracing(), tracing.GasChangeTxInitialBalance)
|
||||
}
|
||||
st.gasRemaining = vm.NewGasBudget(st.msg.GasLimit)
|
||||
st.initialBudget = st.gasRemaining.Copy()
|
||||
|
||||
mgvalU256, _ := uint256.FromBig(mgval)
|
||||
st.state.SubBalance(st.msg.From, mgvalU256, tracing.BalanceDecreaseGasBuy)
|
||||
st.state.SubBalance(st.msg.From, mgval, tracing.BalanceDecreaseGasBuy)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -362,21 +468,13 @@ func (st *stateTransition) preCheck() error {
|
|||
// Skip the checks if gas fields are zero and baseFee was explicitly disabled (eth_call)
|
||||
skipCheck := st.evm.Config.NoBaseFee && msg.GasFeeCap.BitLen() == 0 && msg.GasTipCap.BitLen() == 0
|
||||
if !skipCheck {
|
||||
if l := msg.GasFeeCap.BitLen(); l > 256 {
|
||||
return fmt.Errorf("%w: address %v, maxFeePerGas bit length: %d", ErrFeeCapVeryHigh,
|
||||
msg.From.Hex(), l)
|
||||
}
|
||||
if l := msg.GasTipCap.BitLen(); l > 256 {
|
||||
return fmt.Errorf("%w: address %v, maxPriorityFeePerGas bit length: %d", ErrTipVeryHigh,
|
||||
msg.From.Hex(), l)
|
||||
}
|
||||
if msg.GasFeeCap.Cmp(msg.GasTipCap) < 0 {
|
||||
return fmt.Errorf("%w: address %v, maxPriorityFeePerGas: %s, maxFeePerGas: %s", ErrTipAboveFeeCap,
|
||||
msg.From.Hex(), msg.GasTipCap, msg.GasFeeCap)
|
||||
}
|
||||
// This will panic if baseFee is nil, but basefee presence is verified
|
||||
// as part of header validation.
|
||||
if msg.GasFeeCap.Cmp(st.evm.Context.BaseFee) < 0 {
|
||||
if msg.GasFeeCap.CmpBig(st.evm.Context.BaseFee) < 0 {
|
||||
return fmt.Errorf("%w: address %v, maxFeePerGas: %s, baseFee: %s", ErrFeeCapTooLow,
|
||||
msg.From.Hex(), msg.GasFeeCap, st.evm.Context.BaseFee)
|
||||
}
|
||||
|
|
@ -410,7 +508,7 @@ func (st *stateTransition) preCheck() error {
|
|||
if !skipCheck {
|
||||
// This will panic if blobBaseFee is nil, but blobBaseFee presence
|
||||
// is verified as part of header validation.
|
||||
if msg.BlobGasFeeCap.Cmp(st.evm.Context.BlobBaseFee) < 0 {
|
||||
if msg.BlobGasFeeCap.CmpBig(st.evm.Context.BlobBaseFee) < 0 {
|
||||
return fmt.Errorf("%w: address %v blobGasFeeCap: %v, blobBaseFee: %v", ErrBlobFeeCapTooLow,
|
||||
msg.From.Hex(), msg.BlobGasFeeCap, st.evm.Context.BlobBaseFee)
|
||||
}
|
||||
|
|
@ -462,7 +560,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
floorDataGas uint64
|
||||
)
|
||||
// Check clauses 4-5, subtract intrinsic gas if everything is correct
|
||||
cost, err := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai)
|
||||
cost, err := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai, rules.IsAmsterdam)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -470,12 +568,12 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
if !sufficient {
|
||||
return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining.RegularGas, cost.RegularGas)
|
||||
}
|
||||
if t := st.evm.Config.Tracer; t != nil && t.OnGasChange != nil {
|
||||
t.OnGasChange(prior, st.gasRemaining.RegularGas, tracing.GasChangeTxIntrinsicGas)
|
||||
if st.evm.Config.Tracer.HasGasHook() {
|
||||
st.evm.Config.Tracer.EmitGasChange(prior.AsTracing(), st.gasRemaining.AsTracing(), tracing.GasChangeTxIntrinsicGas)
|
||||
}
|
||||
// Gas limit suffices for the floor data cost (EIP-7623)
|
||||
if rules.IsPrague {
|
||||
floorDataGas, err = FloorDataGas(rules, msg.Data)
|
||||
floorDataGas, err = FloorDataGas(rules, msg.Data, msg.AccessList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -493,9 +591,9 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
}
|
||||
|
||||
// Check clause 6
|
||||
value, overflow := uint256.FromBig(msg.Value)
|
||||
if overflow {
|
||||
return nil, fmt.Errorf("%w: address %v", ErrInsufficientFundsForTransfer, msg.From.Hex())
|
||||
value := msg.Value
|
||||
if value == nil {
|
||||
value = new(uint256.Int)
|
||||
}
|
||||
if !value.IsZero() && !st.evm.Context.CanTransfer(st.state, msg.From, value) {
|
||||
return nil, fmt.Errorf("%w: address %v", ErrInsufficientFundsForTransfer, msg.From.Hex())
|
||||
|
|
@ -510,7 +608,8 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
|
||||
// Execute the preparatory steps for state transition which includes:
|
||||
// - prepare accessList(post-berlin)
|
||||
// - reset transient storage(eip 1153)
|
||||
// - reset transient storage(EIP-1153)
|
||||
// - enable block-level accessList construction (EIP-7928)
|
||||
st.state.Prepare(rules, msg.From, st.evm.Context.Coinbase, msg.To, vm.ActivePrecompiles(rules), msg.AccessList)
|
||||
|
||||
var (
|
||||
|
|
@ -555,8 +654,8 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
// After EIP-7623: Data-heavy transactions pay the floor gas.
|
||||
if used := st.gasUsed(); used < floorDataGas {
|
||||
prior, _ := st.gasRemaining.Charge(vm.GasCosts{RegularGas: floorDataGas - used})
|
||||
if t := st.evm.Config.Tracer; t != nil && t.OnGasChange != nil {
|
||||
t.OnGasChange(prior, st.gasRemaining.RegularGas, tracing.GasChangeTxDataFloor)
|
||||
if st.evm.Config.Tracer.HasGasHook() {
|
||||
st.evm.Config.Tracer.EmitGasChange(prior.AsTracing(), st.gasRemaining.AsTracing(), tracing.GasChangeTxDataFloor)
|
||||
}
|
||||
}
|
||||
if peakGasUsed < floorDataGas {
|
||||
|
|
@ -579,9 +678,12 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
}
|
||||
effectiveTip := msg.GasPrice
|
||||
if rules.IsLondon {
|
||||
effectiveTip = new(big.Int).Sub(msg.GasPrice, st.evm.Context.BaseFee)
|
||||
baseFee, overflow := uint256.FromBig(st.evm.Context.BaseFee)
|
||||
if overflow {
|
||||
return nil, fmt.Errorf("invalid baseFee: %v", st.evm.Context.BaseFee)
|
||||
}
|
||||
effectiveTip = new(uint256.Int).Sub(msg.GasPrice, baseFee)
|
||||
}
|
||||
effectiveTipU256, _ := uint256.FromBig(effectiveTip)
|
||||
|
||||
if st.evm.Config.NoBaseFee && msg.GasFeeCap.Sign() == 0 && msg.GasTipCap.Sign() == 0 {
|
||||
// Skip fee payment when NoBaseFee is set and the fee fields
|
||||
|
|
@ -589,7 +691,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
// the coinbase when simulating calls.
|
||||
} else {
|
||||
fee := new(uint256.Int).SetUint64(st.gasUsed())
|
||||
fee.Mul(fee, effectiveTipU256)
|
||||
fee.Mul(fee, effectiveTip)
|
||||
st.state.AddBalance(st.evm.Context.Coinbase, fee, tracing.BalanceIncreaseRewardTransactionFee)
|
||||
|
||||
// add the coinbase to the witness iff the fee is greater than 0
|
||||
|
|
@ -681,8 +783,11 @@ func (st *stateTransition) calcRefund() vm.GasBudget {
|
|||
if refund > st.state.GetRefund() {
|
||||
refund = st.state.GetRefund()
|
||||
}
|
||||
if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil && refund > 0 {
|
||||
st.evm.Config.Tracer.OnGasChange(st.gasRemaining.RegularGas, st.gasRemaining.RegularGas+refund, tracing.GasChangeTxRefunds)
|
||||
if refund > 0 && st.evm.Config.Tracer.HasGasHook() {
|
||||
after := st.gasRemaining
|
||||
after.RegularGas += refund
|
||||
|
||||
st.evm.Config.Tracer.EmitGasChange(st.gasRemaining.AsTracing(), after.AsTracing(), tracing.GasChangeTxRefunds)
|
||||
}
|
||||
return vm.NewGasBudget(refund)
|
||||
}
|
||||
|
|
@ -691,11 +796,13 @@ func (st *stateTransition) calcRefund() vm.GasBudget {
|
|||
// exchanged at the original rate.
|
||||
func (st *stateTransition) returnGas() {
|
||||
remaining := uint256.NewInt(st.gasRemaining.RegularGas)
|
||||
remaining.Mul(remaining, uint256.MustFromBig(st.msg.GasPrice))
|
||||
remaining.Mul(remaining, st.msg.GasPrice)
|
||||
st.state.AddBalance(st.msg.From, remaining, tracing.BalanceIncreaseGasReturn)
|
||||
|
||||
if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil && st.gasRemaining.RegularGas > 0 {
|
||||
st.evm.Config.Tracer.OnGasChange(st.gasRemaining.RegularGas, 0, tracing.GasChangeTxLeftOverReturned)
|
||||
if st.gasRemaining.RegularGas > 0 && st.evm.Config.Tracer.HasGasHook() {
|
||||
after := st.gasRemaining
|
||||
after.RegularGas = 0
|
||||
st.evm.Config.Tracer.EmitGasChange(st.gasRemaining.AsTracing(), after.AsTracing(), tracing.GasChangeTxLeftOverReturned)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
287
core/state_transition_test.go
Normal file
287
core/state_transition_test.go
Normal file
|
|
@ -0,0 +1,287 @@
|
|||
// Copyright 2026 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 (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
func TestFloorDataGas(t *testing.T) {
|
||||
addr1 := common.HexToAddress("0x1111111111111111111111111111111111111111")
|
||||
addr2 := common.HexToAddress("0x2222222222222222222222222222222222222222")
|
||||
key1 := common.HexToHash("0xaa")
|
||||
key2 := common.HexToHash("0xbb")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
amsterdam bool
|
||||
data []byte
|
||||
accessList types.AccessList
|
||||
want uint64
|
||||
}{
|
||||
{
|
||||
name: "pre-amsterdam/empty",
|
||||
want: params.TxGas,
|
||||
},
|
||||
{
|
||||
name: "pre-amsterdam/zero-bytes-only",
|
||||
data: bytes.Repeat([]byte{0x00}, 100),
|
||||
// 100 zero tokens * 10 cost = 1000
|
||||
want: params.TxGas + 100*params.TxCostFloorPerToken,
|
||||
},
|
||||
{
|
||||
name: "pre-amsterdam/non-zero-bytes-only",
|
||||
data: bytes.Repeat([]byte{0xff}, 100),
|
||||
// 100 nz * 4 tokens * 10 cost = 4000
|
||||
want: params.TxGas + 100*params.TxTokenPerNonZeroByte*params.TxCostFloorPerToken,
|
||||
},
|
||||
{
|
||||
name: "pre-amsterdam/mixed",
|
||||
data: append(bytes.Repeat([]byte{0x00}, 50), bytes.Repeat([]byte{0xff}, 50)...),
|
||||
// 50 zero + 50*4 nz = 250 tokens * 10 = 2500
|
||||
want: params.TxGas + (50+50*params.TxTokenPerNonZeroByte)*params.TxCostFloorPerToken,
|
||||
},
|
||||
{
|
||||
name: "pre-amsterdam/access-list-ignored",
|
||||
data: bytes.Repeat([]byte{0xff}, 10),
|
||||
accessList: types.AccessList{
|
||||
{Address: addr1, StorageKeys: []common.Hash{key1, key2}},
|
||||
},
|
||||
// pre-amsterdam: floor calculation does not include access list
|
||||
want: params.TxGas + 10*params.TxTokenPerNonZeroByte*params.TxCostFloorPerToken,
|
||||
},
|
||||
{
|
||||
name: "amsterdam/empty",
|
||||
amsterdam: true,
|
||||
want: params.TxGas,
|
||||
},
|
||||
{
|
||||
name: "amsterdam/data-only",
|
||||
amsterdam: true,
|
||||
data: bytes.Repeat([]byte{0x00}, 1024),
|
||||
// post-amsterdam: every byte = 4 tokens regardless of value
|
||||
want: params.TxGas + 1024*params.TxTokenPerNonZeroByte*params.TxCostFloorPerToken7976,
|
||||
},
|
||||
{
|
||||
name: "amsterdam/data-non-zero",
|
||||
amsterdam: true,
|
||||
data: bytes.Repeat([]byte{0xff}, 1024),
|
||||
// same as zero data post-amsterdam
|
||||
want: params.TxGas + 1024*params.TxTokenPerNonZeroByte*params.TxCostFloorPerToken7976,
|
||||
},
|
||||
{
|
||||
name: "amsterdam/access-list-addresses-only",
|
||||
amsterdam: true,
|
||||
accessList: types.AccessList{
|
||||
{Address: addr1},
|
||||
{Address: addr2},
|
||||
},
|
||||
// 2 * 20 bytes * 4 tokens/byte * 16 cost/token
|
||||
want: params.TxGas + 2*common.AddressLength*params.TxTokenPerNonZeroByte*params.TxCostFloorPerToken7976,
|
||||
},
|
||||
{
|
||||
name: "amsterdam/access-list-with-storage-keys",
|
||||
amsterdam: true,
|
||||
accessList: types.AccessList{
|
||||
{Address: addr1, StorageKeys: []common.Hash{key1, key2}},
|
||||
},
|
||||
// 1 addr * 20 * 4 + 2 keys * 32 * 4 = 80 + 256 = 336 tokens * 16
|
||||
want: params.TxGas + (1*common.AddressLength+2*common.HashLength)*params.TxTokenPerNonZeroByte*params.TxCostFloorPerToken7976,
|
||||
},
|
||||
{
|
||||
name: "amsterdam/mixed",
|
||||
amsterdam: true,
|
||||
data: bytes.Repeat([]byte{0xff}, 100),
|
||||
accessList: types.AccessList{
|
||||
{Address: addr1, StorageKeys: []common.Hash{key1}},
|
||||
{Address: addr2, StorageKeys: []common.Hash{key1, key2}},
|
||||
},
|
||||
// data: 100*4 = 400; addrs: 2*20*4 = 160; keys: 3*32*4 = 384; total = 944 * 16
|
||||
want: params.TxGas + (100*params.TxTokenPerNonZeroByte+2*common.AddressLength*params.TxTokenPerNonZeroByte+3*common.HashLength*params.TxTokenPerNonZeroByte)*params.TxCostFloorPerToken7976,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
rules := params.Rules{IsAmsterdam: tt.amsterdam}
|
||||
got, err := FloorDataGas(rules, tt.data, tt.accessList)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Fatalf("gas mismatch: got %d, want %d", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntrinsicGas(t *testing.T) {
|
||||
addr1 := common.HexToAddress("0x1111111111111111111111111111111111111111")
|
||||
addr2 := common.HexToAddress("0x2222222222222222222222222222222222222222")
|
||||
key1 := common.HexToHash("0xaa")
|
||||
key2 := common.HexToHash("0xbb")
|
||||
|
||||
const (
|
||||
amsterdamAddressCost = uint64(common.AddressLength) * params.TxCostFloorPerToken7976 * params.TxTokenPerNonZeroByte // 1280
|
||||
amsterdamStorageKeyCost = uint64(common.HashLength) * params.TxCostFloorPerToken7976 * params.TxTokenPerNonZeroByte // 2048
|
||||
)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
data []byte
|
||||
accessList types.AccessList
|
||||
authList []types.SetCodeAuthorization
|
||||
creation bool
|
||||
isHomestead bool
|
||||
isEIP2028 bool
|
||||
isEIP3860 bool
|
||||
isAmsterdam bool
|
||||
want uint64
|
||||
}{
|
||||
{
|
||||
name: "frontier/empty-call",
|
||||
want: params.TxGas,
|
||||
},
|
||||
{
|
||||
name: "frontier/contract-creation-pre-homestead",
|
||||
creation: true,
|
||||
isHomestead: false,
|
||||
// pre-homestead, contract creation still uses TxGas
|
||||
want: params.TxGas,
|
||||
},
|
||||
{
|
||||
name: "homestead/contract-creation",
|
||||
creation: true,
|
||||
isHomestead: true,
|
||||
want: params.TxGasContractCreation,
|
||||
},
|
||||
{
|
||||
name: "frontier/non-zero-data",
|
||||
data: bytes.Repeat([]byte{0xff}, 100),
|
||||
// 100 nz bytes * 68 (frontier)
|
||||
want: params.TxGas + 100*params.TxDataNonZeroGasFrontier,
|
||||
},
|
||||
{
|
||||
name: "istanbul/non-zero-data",
|
||||
data: bytes.Repeat([]byte{0xff}, 100),
|
||||
isEIP2028: true,
|
||||
// 100 nz bytes * 16 (post-EIP2028)
|
||||
want: params.TxGas + 100*params.TxDataNonZeroGasEIP2028,
|
||||
},
|
||||
{
|
||||
name: "istanbul/zero-data",
|
||||
data: bytes.Repeat([]byte{0x00}, 100),
|
||||
isEIP2028: true,
|
||||
// 100 zero bytes * 4
|
||||
want: params.TxGas + 100*params.TxDataZeroGas,
|
||||
},
|
||||
{
|
||||
name: "istanbul/mixed-data",
|
||||
data: append(bytes.Repeat([]byte{0x00}, 50), bytes.Repeat([]byte{0xff}, 50)...),
|
||||
isEIP2028: true,
|
||||
want: params.TxGas + 50*params.TxDataZeroGas + 50*params.TxDataNonZeroGasEIP2028,
|
||||
},
|
||||
{
|
||||
name: "shanghai/init-code-word-gas",
|
||||
data: bytes.Repeat([]byte{0x00}, 64), // 2 words
|
||||
creation: true,
|
||||
isHomestead: true,
|
||||
isEIP2028: true,
|
||||
isEIP3860: true,
|
||||
// TxGasContractCreation + 64 zero bytes * 4 + 2 words * 2
|
||||
want: params.TxGasContractCreation + 64*params.TxDataZeroGas + 2*params.InitCodeWordGas,
|
||||
},
|
||||
{
|
||||
name: "shanghai/init-code-non-multiple-of-32",
|
||||
data: bytes.Repeat([]byte{0x00}, 33), // 2 words (rounded up)
|
||||
creation: true,
|
||||
isHomestead: true,
|
||||
isEIP2028: true,
|
||||
isEIP3860: true,
|
||||
want: params.TxGasContractCreation + 33*params.TxDataZeroGas + 2*params.InitCodeWordGas,
|
||||
},
|
||||
{
|
||||
name: "berlin/access-list",
|
||||
accessList: types.AccessList{
|
||||
{Address: addr1, StorageKeys: []common.Hash{key1, key2}},
|
||||
{Address: addr2, StorageKeys: []common.Hash{key1}},
|
||||
},
|
||||
isEIP2028: true,
|
||||
// 2 addrs * 2400 + 3 keys * 1900
|
||||
want: params.TxGas + 2*params.TxAccessListAddressGas + 3*params.TxAccessListStorageKeyGas,
|
||||
},
|
||||
{
|
||||
name: "amsterdam/access-list-extra-cost",
|
||||
accessList: types.AccessList{
|
||||
{Address: addr1, StorageKeys: []common.Hash{key1, key2}},
|
||||
{Address: addr2, StorageKeys: []common.Hash{key1}},
|
||||
},
|
||||
isEIP2028: true,
|
||||
isAmsterdam: true,
|
||||
// base access-list charge + EIP-7981 extra
|
||||
want: params.TxGas +
|
||||
2*params.TxAccessListAddressGas + 3*params.TxAccessListStorageKeyGas +
|
||||
2*amsterdamAddressCost + 3*amsterdamStorageKeyCost,
|
||||
},
|
||||
{
|
||||
name: "prague/auth-list",
|
||||
authList: []types.SetCodeAuthorization{
|
||||
{Address: addr1},
|
||||
{Address: addr2},
|
||||
{Address: addr1},
|
||||
},
|
||||
isEIP2028: true,
|
||||
// 3 auths * 25000
|
||||
want: params.TxGas + 3*params.CallNewAccountGas,
|
||||
},
|
||||
{
|
||||
name: "amsterdam/combined",
|
||||
data: bytes.Repeat([]byte{0xff}, 100),
|
||||
accessList: types.AccessList{
|
||||
{Address: addr1, StorageKeys: []common.Hash{key1}},
|
||||
},
|
||||
authList: []types.SetCodeAuthorization{
|
||||
{Address: addr2},
|
||||
},
|
||||
isEIP2028: true,
|
||||
isAmsterdam: true,
|
||||
want: params.TxGas +
|
||||
100*params.TxDataNonZeroGasEIP2028 +
|
||||
1*params.TxAccessListAddressGas + 1*params.TxAccessListStorageKeyGas +
|
||||
1*amsterdamAddressCost + 1*amsterdamStorageKeyCost +
|
||||
1*params.CallNewAccountGas,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := IntrinsicGas(tt.data, tt.accessList, tt.authList,
|
||||
tt.creation, tt.isHomestead, tt.isEIP2028, tt.isEIP3860, tt.isAmsterdam)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
want := vm.GasCosts{RegularGas: tt.want}
|
||||
if got != want {
|
||||
t.Fatalf("gas mismatch: got %+v, want %+v", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -164,10 +164,36 @@ type (
|
|||
// FaultHook is invoked when an error occurs during the execution of an opcode.
|
||||
FaultHook = func(pc uint64, op byte, gas, cost uint64, scope OpContext, depth int, err error)
|
||||
|
||||
// GasChangeHook is invoked when the gas changes.
|
||||
// GasChangeHook reports changes to the regular execution gas. Tracers
|
||||
// that don't need visibility into the state-access gas dimension
|
||||
// introduced by EIP-8037 (Amsterdam) can implement only this hook; it
|
||||
// will continue to fire across the Amsterdam fork unchanged.
|
||||
//
|
||||
// If both this hook and GasChangeHookV2 are implemented on the same
|
||||
// tracer, only V2 will be invoked. Implement exactly one to avoid
|
||||
// double-counting.
|
||||
GasChangeHook = func(old, new uint64, reason GasChangeReason)
|
||||
|
||||
// TODO(sina, rjl), please add GasChangeV2Hook by landing the multi-dimensional gas
|
||||
// GasChangeHookV2 is invoked when any gas dimension changes. It is the
|
||||
// multi-dimensional successor to GasChangeHook, exposing the state-access
|
||||
// gas dimension introduced by EIP-8037 (Amsterdam) alongside the regular
|
||||
// dimension.
|
||||
//
|
||||
// Compatibility:
|
||||
// - Post-Amsterdam: fires for changes to either the regular or the
|
||||
// state-access dimension. The non-changing dimension is passed through
|
||||
// unchanged in both `old` and `new` so consumers always observe the
|
||||
// complete gas vector.
|
||||
// - Pre-Amsterdam: no state-access gas events occur, so the State field
|
||||
// of both `old` and `new` is always zero. Tracers that register only
|
||||
// V2 still receive every regular-gas change as Gas{State: 0} and
|
||||
// behave identically to a V1 tracer; there is no pre-Amsterdam event
|
||||
// a V2-only tracer misses.
|
||||
//
|
||||
// V1 and V2 coexist: when both are registered on a tracer, only V2 is
|
||||
// invoked. Tracers SHOULD register at most one of the two to avoid
|
||||
// double-counting.
|
||||
GasChangeHookV2 = func(old, new Gas, reason GasChangeReason)
|
||||
|
||||
/*
|
||||
- Chain events -
|
||||
|
|
@ -250,13 +276,14 @@ type (
|
|||
|
||||
type Hooks struct {
|
||||
// VM events
|
||||
OnTxStart TxStartHook
|
||||
OnTxEnd TxEndHook
|
||||
OnEnter EnterHook
|
||||
OnExit ExitHook
|
||||
OnOpcode OpcodeHook
|
||||
OnFault FaultHook
|
||||
OnGasChange GasChangeHook
|
||||
OnTxStart TxStartHook
|
||||
OnTxEnd TxEndHook
|
||||
OnEnter EnterHook
|
||||
OnExit ExitHook
|
||||
OnOpcode OpcodeHook
|
||||
OnFault FaultHook
|
||||
OnGasChange GasChangeHook
|
||||
OnGasChangeV2 GasChangeHookV2
|
||||
// Chain events
|
||||
OnBlockchainInit BlockchainInitHook
|
||||
OnClose CloseHook
|
||||
|
|
@ -280,6 +307,35 @@ type Hooks struct {
|
|||
OnBlockHashRead BlockHashReadHook
|
||||
}
|
||||
|
||||
// HasGasHook reports whether any gas-change hook is registered. Call sites
|
||||
// should use this to short-circuit before constructing the Gas / GasBudget
|
||||
// arguments to EmitGasChange when tracing is off — the dispatch is otherwise
|
||||
// always paid the cost of evaluating those args.
|
||||
func (h *Hooks) HasGasHook() bool {
|
||||
return h != nil && (h.OnGasChangeV2 != nil || h.OnGasChange != nil)
|
||||
}
|
||||
|
||||
// EmitGasChange dispatches a gas change event to the registered hooks. If the
|
||||
// multi-dimensional OnGasChangeV2 hook is set it is invoked with the full Gas
|
||||
// vectors; otherwise the single-dimensional OnGasChange hook is invoked with
|
||||
// the regular-gas dimension only. The call is a no-op when the receiver is
|
||||
// nil, when neither hook is registered, or when the reason is GasChangeIgnored.
|
||||
//
|
||||
// Call sites SHOULD use this helper instead of invoking the hooks directly so
|
||||
// that both variants stay consistent across the Amsterdam fork boundary.
|
||||
func (h *Hooks) EmitGasChange(old, new Gas, reason GasChangeReason) {
|
||||
if h == nil || reason == GasChangeIgnored {
|
||||
return
|
||||
}
|
||||
if h.OnGasChangeV2 != nil {
|
||||
h.OnGasChangeV2(old, new, reason)
|
||||
return
|
||||
}
|
||||
if h.OnGasChange != nil {
|
||||
h.OnGasChange(old.Regular, new.Regular, reason)
|
||||
}
|
||||
}
|
||||
|
||||
// BalanceChangeReason is used to indicate the reason for a balance change, useful
|
||||
// for tracing and reporting.
|
||||
type BalanceChangeReason byte
|
||||
|
|
@ -335,6 +391,19 @@ const (
|
|||
BalanceChangeRevert BalanceChangeReason = 15
|
||||
)
|
||||
|
||||
// Gas represents a multi-dimensional gas budget introduced by EIP-8037.
|
||||
// It carries the regular execution gas and the state-access gas, which are
|
||||
// metered independently from the Amsterdam fork onwards.
|
||||
//
|
||||
// Before Amsterdam, gas metering is single-dimensional and only the Regular
|
||||
// field is meaningful; State is always zero. The struct is shaped so that
|
||||
// pre-Amsterdam call sites can populate it as Gas{Regular: g} without loss
|
||||
// of fidelity relative to the legacy single-uint64 hook.
|
||||
type Gas struct {
|
||||
Regular uint64 // Regular is the budget for ordinary execution gas.
|
||||
State uint64 // State is the budget dedicated to state-access gas (zero pre-Amsterdam).
|
||||
}
|
||||
|
||||
// GasChangeReason is used to indicate the reason for a gas change, useful
|
||||
// for tracing and reporting.
|
||||
//
|
||||
|
|
|
|||
|
|
@ -116,6 +116,8 @@ const (
|
|||
announceThreshold = -1
|
||||
)
|
||||
|
||||
var errLegacyTx = errors.New("legacy transaction format")
|
||||
|
||||
// blobTxMeta is the minimal subset of types.BlobTx necessary to validate and
|
||||
// schedule the blob transactions into the following blocks. Only ever add the
|
||||
// bare minimum needed fields to keep the size down (and thus number of entries
|
||||
|
|
@ -147,28 +149,137 @@ type blobTxMeta struct {
|
|||
evictionBlobFeeJumps float64 // Worse blob fee (converted to fee jumps) across all previous nonces
|
||||
}
|
||||
|
||||
// newBlobTxMeta retrieves the indexed metadata fields from a blob transaction
|
||||
// and assembles a helper struct to track in memory.
|
||||
// Requires the transaction to have a sidecar (or that we introduce a special version tag for no-sidecar).
|
||||
func newBlobTxMeta(id uint64, size uint64, storageSize uint32, tx *types.Transaction) *blobTxMeta {
|
||||
if tx.BlobTxSidecar() == nil {
|
||||
// This should never happen, as the pool only admits blob transactions with a sidecar
|
||||
// blobTxForPool is the storage representation of a blob transaction in the
|
||||
// blobpool.
|
||||
type blobTxForPool struct {
|
||||
Tx *types.Transaction // tx without sidecar
|
||||
Version byte
|
||||
Commitments []kzg4844.Commitment
|
||||
Proofs []kzg4844.Proof
|
||||
Blobs []kzg4844.Blob
|
||||
}
|
||||
|
||||
// Sidecar returns BlobTxSidecar of ptx.
|
||||
func (ptx *blobTxForPool) Sidecar() *types.BlobTxSidecar {
|
||||
return types.NewBlobTxSidecar(ptx.Version, ptx.Blobs, ptx.Commitments, ptx.Proofs)
|
||||
}
|
||||
|
||||
// ApplySidecar copies the sidecar's fields into the flat fields.
|
||||
func (ptx *blobTxForPool) ApplySidecar(sc *types.BlobTxSidecar) {
|
||||
ptx.Version = sc.Version
|
||||
ptx.Commitments = sc.Commitments
|
||||
ptx.Proofs = sc.Proofs
|
||||
ptx.Blobs = sc.Blobs
|
||||
}
|
||||
|
||||
// TxSize returns the transaction size on the network without
|
||||
// reconstructing the transaction.
|
||||
func (ptx *blobTxForPool) TxSize() uint64 {
|
||||
var blobs, commitments, proofs uint64
|
||||
for i := range ptx.Blobs {
|
||||
blobs += rlp.BytesSize(ptx.Blobs[i][:])
|
||||
}
|
||||
for i := range ptx.Commitments {
|
||||
commitments += rlp.BytesSize(ptx.Commitments[i][:])
|
||||
}
|
||||
for i := range ptx.Proofs {
|
||||
proofs += rlp.BytesSize(ptx.Proofs[i][:])
|
||||
}
|
||||
return ptx.Tx.Size() + rlp.ListSize(rlp.ListSize(blobs)+rlp.ListSize(commitments)+rlp.ListSize(proofs))
|
||||
}
|
||||
|
||||
// ToTx reconstructs a full Transaction with the sidecar attached.
|
||||
func (ptx *blobTxForPool) ToTx() *types.Transaction {
|
||||
return ptx.Tx.WithBlobTxSidecar(ptx.Sidecar())
|
||||
}
|
||||
|
||||
// newBlobTxForPool decomposes a blob transaction into blobTxForPool type.
|
||||
func newBlobTxForPool(tx *types.Transaction) *blobTxForPool {
|
||||
sc := tx.BlobTxSidecar()
|
||||
if sc == nil {
|
||||
panic("missing blob tx sidecar")
|
||||
}
|
||||
return &blobTxForPool{
|
||||
Tx: tx.WithoutBlobTxSidecar(),
|
||||
Version: sc.Version,
|
||||
Commitments: sc.Commitments,
|
||||
Proofs: sc.Proofs,
|
||||
Blobs: sc.Blobs,
|
||||
}
|
||||
}
|
||||
|
||||
// encodeForNetwork transforms stored blobTxForPool RLP into the standard
|
||||
// network transaction encoding. This is used for getRLP.
|
||||
//
|
||||
// Stored RLP: [type_byte || tx_fields, version, [comms], [proofs], [blobs]]
|
||||
// V0: type_byte || rlp([tx_fields, [blobs], [comms], [proofs]])
|
||||
// V1: type_byte || rlp([tx_fields, version, [blobs], [comms], [proofs]])
|
||||
func encodeForNetwork(storedRLP []byte) ([]byte, error) {
|
||||
elems, err := rlp.SplitListValues(storedRLP)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid blobTxForPool RLP: %w", err)
|
||||
}
|
||||
if len(elems) < 5 {
|
||||
return nil, fmt.Errorf("blobTxForPool has %d elements, need at least 5", len(elems))
|
||||
}
|
||||
|
||||
// 1. Extract tx byte and other tx fields
|
||||
txBytes, _, err := rlp.SplitString(elems[0])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid tx bytes: %w", err)
|
||||
}
|
||||
if len(txBytes) < 2 {
|
||||
return nil, errors.New("tx bytes too short")
|
||||
}
|
||||
typeByte := txBytes[0]
|
||||
txRLP := txBytes[1:]
|
||||
|
||||
// 2. Find the version of sidecar.
|
||||
version, _, err := rlp.SplitUint64(elems[1])
|
||||
if err != nil || version > 255 {
|
||||
return nil, fmt.Errorf("invalid version: %w", err)
|
||||
}
|
||||
versionByte := byte(version)
|
||||
// 3. Extract sidecar elements.
|
||||
commitmentsRLP := elems[2]
|
||||
proofsRLP := elems[3]
|
||||
blobsRLP := elems[4]
|
||||
|
||||
// 4. Reconstruct into the network format.
|
||||
var outer [][]byte
|
||||
if versionByte == types.BlobSidecarVersion0 {
|
||||
outer = [][]byte{txRLP, blobsRLP, commitmentsRLP, proofsRLP}
|
||||
} else {
|
||||
outer = [][]byte{txRLP, elems[1], blobsRLP, commitmentsRLP, proofsRLP}
|
||||
}
|
||||
body, err := rlp.MergeListValues(outer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Prepend type byte and wrap as an RLP string.
|
||||
inner := make([]byte, 1+len(body))
|
||||
inner[0] = typeByte
|
||||
copy(inner[1:], body)
|
||||
return rlp.EncodeToBytes(inner)
|
||||
}
|
||||
|
||||
// newBlobTxMeta retrieves the indexed metadata fields from a pooled blob
|
||||
// transaction and assembles a helper struct to track in memory.
|
||||
func newBlobTxMeta(id uint64, size uint64, storageSize uint32, ptx *blobTxForPool) *blobTxMeta {
|
||||
meta := &blobTxMeta{
|
||||
hash: tx.Hash(),
|
||||
vhashes: tx.BlobHashes(),
|
||||
version: tx.BlobTxSidecar().Version,
|
||||
hash: ptx.Tx.Hash(),
|
||||
vhashes: ptx.Tx.BlobHashes(),
|
||||
version: ptx.Version,
|
||||
id: id,
|
||||
storageSize: storageSize,
|
||||
size: size,
|
||||
nonce: tx.Nonce(),
|
||||
costCap: uint256.MustFromBig(tx.Cost()),
|
||||
execTipCap: uint256.MustFromBig(tx.GasTipCap()),
|
||||
execFeeCap: uint256.MustFromBig(tx.GasFeeCap()),
|
||||
blobFeeCap: uint256.MustFromBig(tx.BlobGasFeeCap()),
|
||||
execGas: tx.Gas(),
|
||||
blobGas: tx.BlobGas(),
|
||||
nonce: ptx.Tx.Nonce(),
|
||||
costCap: uint256.MustFromBig(ptx.Tx.Cost()),
|
||||
execTipCap: uint256.MustFromBig(ptx.Tx.GasTipCap()),
|
||||
execFeeCap: uint256.MustFromBig(ptx.Tx.GasFeeCap()),
|
||||
blobFeeCap: uint256.MustFromBig(ptx.Tx.BlobGasFeeCap()),
|
||||
execGas: ptx.Tx.Gas(),
|
||||
blobGas: ptx.Tx.BlobGas(),
|
||||
}
|
||||
meta.basefeeJumps = dynamicFeeJumps(meta.execFeeCap)
|
||||
meta.blobfeeJumps = dynamicBlobFeeJumps(meta.blobFeeCap)
|
||||
|
|
@ -460,10 +571,17 @@ func (p *BlobPool) Init(gasTip uint64, head *types.Header, reserver txpool.Reser
|
|||
return err
|
||||
}
|
||||
// Index all transactions on disk and delete anything unprocessable
|
||||
var fails []uint64
|
||||
var (
|
||||
toDelete []uint64
|
||||
convertTxs []uint64
|
||||
)
|
||||
index := func(id uint64, size uint32, blob []byte) {
|
||||
if p.parseTransaction(id, size, blob) != nil {
|
||||
fails = append(fails, id)
|
||||
err := p.parseTransaction(id, size, blob)
|
||||
if err != nil {
|
||||
toDelete = append(toDelete, id)
|
||||
}
|
||||
if errors.Is(err, errLegacyTx) {
|
||||
convertTxs = append(convertTxs, id)
|
||||
}
|
||||
}
|
||||
store, err := billy.Open(billy.Options{Path: queuedir, Repair: true}, slotter, index)
|
||||
|
|
@ -472,17 +590,58 @@ func (p *BlobPool) Init(gasTip uint64, head *types.Header, reserver txpool.Reser
|
|||
}
|
||||
p.store = store
|
||||
|
||||
if len(fails) > 0 {
|
||||
log.Warn("Dropping invalidated blob transactions", "ids", fails)
|
||||
dropInvalidMeter.Mark(int64(len(fails)))
|
||||
// Migrate legacy transactions (types.Transaction) to pooledBlobTx format.
|
||||
if len(convertTxs) > 0 {
|
||||
for _, id := range convertTxs {
|
||||
var tx types.Transaction
|
||||
data, err := p.store.Get(id)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
err = rlp.DecodeBytes(data, &tx)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if tx.BlobTxSidecar() == nil {
|
||||
continue
|
||||
}
|
||||
ptx := newBlobTxForPool(&tx)
|
||||
blob, err := rlp.EncodeToBytes(ptx)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
id, err := p.store.Put(blob)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
meta := newBlobTxMeta(id, ptx.TxSize(), p.store.Size(id), ptx)
|
||||
|
||||
for _, id := range fails {
|
||||
// If the newly inserted transaction fails to be tracked,
|
||||
// it should also be removed with those in `toDelete`
|
||||
sender, err := types.Sender(p.signer, ptx.Tx)
|
||||
if err != nil {
|
||||
toDelete = append(toDelete, id)
|
||||
continue
|
||||
}
|
||||
if err := p.trackTransaction(meta, sender); err != nil {
|
||||
toDelete = append(toDelete, id)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(toDelete) > 0 {
|
||||
log.Warn("Dropping invalidated blob transactions", "ids", toDelete)
|
||||
dropInvalidMeter.Mark(int64(len(toDelete)))
|
||||
|
||||
for _, id := range toDelete {
|
||||
if err := p.store.Delete(id); err != nil {
|
||||
p.Close()
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the indexed transactions by nonce and delete anything gapped, create
|
||||
// the eviction heap of anyone still standing
|
||||
for addr := range p.index {
|
||||
|
|
@ -558,36 +717,33 @@ func (p *BlobPool) Close() error {
|
|||
|
||||
// parseTransaction is a callback method on pool creation that gets called for
|
||||
// each transaction on disk to create the in-memory metadata index.
|
||||
// Announced state is not initialized here, it needs to be iniitalized seprately.
|
||||
// Return value `bool` is set to true when the entry has old Transaction type.
|
||||
func (p *BlobPool) parseTransaction(id uint64, size uint32, blob []byte) error {
|
||||
tx := new(types.Transaction)
|
||||
if err := rlp.DecodeBytes(blob, tx); err != nil {
|
||||
// This path is impossible unless the disk data representation changes
|
||||
// across restarts. For that ever improbable case, recover gracefully
|
||||
// by ignoring this data entry.
|
||||
log.Error("Failed to decode blob pool entry", "id", id, "err", err)
|
||||
var ptx blobTxForPool
|
||||
if err := rlp.DecodeBytes(blob, &ptx); err != nil {
|
||||
kind, content, _, splitErr := rlp.Split(blob)
|
||||
// check whether it is legacy tx type
|
||||
if splitErr == nil && kind == rlp.String && len(content) > 1 && content[0] == 3 {
|
||||
return errLegacyTx
|
||||
}
|
||||
return err
|
||||
}
|
||||
if tx.BlobTxSidecar() == nil {
|
||||
log.Error("Missing sidecar in blob pool entry", "id", id, "hash", tx.Hash())
|
||||
return errors.New("missing blob sidecar")
|
||||
meta := newBlobTxMeta(id, ptx.TxSize(), size, &ptx)
|
||||
sender, err := types.Sender(p.signer, ptx.Tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return p.trackTransaction(meta, sender)
|
||||
}
|
||||
|
||||
meta := newBlobTxMeta(id, tx.Size(), size, tx)
|
||||
// trackTransaction registers a transaction's metadata in the pool's indices.
|
||||
func (p *BlobPool) trackTransaction(meta *blobTxMeta, sender common.Address) error {
|
||||
if p.lookup.exists(meta.hash) {
|
||||
// This path is only possible after a crash, where deleted items are not
|
||||
// removed via the normal shutdown-startup procedure and thus may get
|
||||
// partially resurrected.
|
||||
log.Error("Rejecting duplicate blob pool entry", "id", id, "hash", tx.Hash())
|
||||
return errors.New("duplicate blob entry")
|
||||
}
|
||||
sender, err := types.Sender(p.signer, tx)
|
||||
if err != nil {
|
||||
// This path is impossible unless the signature validity changes across
|
||||
// restarts. For that ever improbable case, recover gracefully by ignoring
|
||||
// this data entry.
|
||||
log.Error("Failed to recover blob tx sender", "id", id, "hash", tx.Hash(), "err", err)
|
||||
return err
|
||||
log.Error("Rejecting duplicate blob pool entry", "id", meta.id, "hash", meta.hash)
|
||||
return fmt.Errorf("duplicate blob entry %d, %s", meta.id, meta.hash)
|
||||
}
|
||||
if _, ok := p.index[sender]; !ok {
|
||||
if err := p.reserver.Hold(sender); err != nil {
|
||||
|
|
@ -863,17 +1019,17 @@ func (p *BlobPool) offload(addr common.Address, nonce uint64, id uint64, inclusi
|
|||
log.Error("Blobs missing for included transaction", "from", addr, "nonce", nonce, "id", id, "err", err)
|
||||
return
|
||||
}
|
||||
var tx types.Transaction
|
||||
if err = rlp.DecodeBytes(data, &tx); err != nil {
|
||||
var ptx blobTxForPool
|
||||
if err := rlp.DecodeBytes(data, &ptx); err != nil {
|
||||
log.Error("Blobs corrupted for included transaction", "from", addr, "nonce", nonce, "id", id, "err", err)
|
||||
return
|
||||
}
|
||||
block, ok := inclusions[tx.Hash()]
|
||||
block, ok := inclusions[ptx.Tx.Hash()]
|
||||
if !ok {
|
||||
log.Warn("Blob transaction swapped out by signer", "from", addr, "nonce", nonce, "id", id)
|
||||
return
|
||||
}
|
||||
if err := p.limbo.push(&tx, block); err != nil {
|
||||
if err := p.limbo.push(&ptx, block); err != nil {
|
||||
log.Warn("Failed to offload blob tx into limbo", "err", err)
|
||||
return
|
||||
}
|
||||
|
|
@ -951,13 +1107,13 @@ func (p *BlobPool) Reset(oldHead, newHead *types.Header) {
|
|||
log.Error("Blobs missing for announcable transaction", "from", addr, "nonce", meta.nonce, "id", meta.id, "err", err)
|
||||
continue
|
||||
}
|
||||
var tx types.Transaction
|
||||
if err = rlp.DecodeBytes(data, &tx); err != nil {
|
||||
var ptx blobTxForPool
|
||||
if err = rlp.DecodeBytes(data, &ptx); err != nil {
|
||||
log.Error("Blobs corrupted for announcable transaction", "from", addr, "nonce", meta.nonce, "id", meta.id, "err", err)
|
||||
continue
|
||||
}
|
||||
announcable = append(announcable, tx.WithoutBlobTxSidecar())
|
||||
log.Trace("Blob transaction now announcable", "from", addr, "nonce", meta.nonce, "id", meta.id, "hash", tx.Hash())
|
||||
announcable = append(announcable, ptx.Tx)
|
||||
log.Trace("Blob transaction now announcable", "from", addr, "nonce", meta.nonce, "id", meta.id, "hash", ptx.Tx.Hash())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1108,7 +1264,7 @@ func (p *BlobPool) reorg(oldHead, newHead *types.Header) (map[common.Address][]*
|
|||
func (p *BlobPool) reinject(addr common.Address, txhash common.Hash) error {
|
||||
// Retrieve the associated blob from the limbo. Without the blobs, we cannot
|
||||
// add the transaction back into the pool as it is not mineable.
|
||||
tx, err := p.limbo.pull(txhash)
|
||||
ptx, err := p.limbo.pull(txhash)
|
||||
if err != nil {
|
||||
log.Error("Blobs unavailable, dropping reorged tx", "err", err)
|
||||
return err
|
||||
|
|
@ -1124,30 +1280,29 @@ func (p *BlobPool) reinject(addr common.Address, txhash common.Hash) error {
|
|||
// could theoretically halt a Geth node for ~1.2s by reorging per block. However,
|
||||
// this attack is financially inefficient to execute.
|
||||
head := p.head.Load()
|
||||
if p.chain.Config().IsOsaka(head.Number, head.Time) && tx.BlobTxSidecar().Version == types.BlobSidecarVersion0 {
|
||||
if err := tx.BlobTxSidecar().ToV1(); err != nil {
|
||||
if p.chain.Config().IsOsaka(head.Number, head.Time) && ptx.Version == types.BlobSidecarVersion0 {
|
||||
sc := ptx.Sidecar()
|
||||
if err := sc.ToV1(); err != nil {
|
||||
log.Error("Failed to convert the legacy sidecar", "err", err)
|
||||
return err
|
||||
}
|
||||
log.Info("Legacy blob transaction is reorged", "hash", tx.Hash())
|
||||
ptx.ApplySidecar(sc)
|
||||
log.Info("Legacy blob transaction is reorged", "hash", ptx.Tx.Hash())
|
||||
}
|
||||
// Serialize the transaction back into the primary datastore.
|
||||
blob, err := rlp.EncodeToBytes(tx)
|
||||
blob, err := rlp.EncodeToBytes(ptx)
|
||||
if err != nil {
|
||||
log.Error("Failed to encode transaction for storage", "hash", tx.Hash(), "err", err)
|
||||
log.Error("Failed to encode transaction for storage", "hash", ptx.Tx.Hash(), "err", err)
|
||||
return err
|
||||
}
|
||||
id, err := p.store.Put(blob)
|
||||
if err != nil {
|
||||
log.Error("Failed to write transaction into storage", "hash", tx.Hash(), "err", err)
|
||||
log.Error("Failed to write transaction into storage", "hash", ptx.Tx.Hash(), "err", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Update the indices and metrics
|
||||
meta := newBlobTxMeta(id, tx.Size(), p.store.Size(id), tx)
|
||||
meta := newBlobTxMeta(id, ptx.TxSize(), p.store.Size(id), ptx)
|
||||
if _, ok := p.index[addr]; !ok {
|
||||
if err := p.reserver.Hold(addr); err != nil {
|
||||
log.Warn("Failed to reserve account for blob pool", "tx", tx.Hash(), "from", addr, "err", err)
|
||||
log.Warn("Failed to reserve account for blob pool", "tx", ptx.Tx.Hash(), "from", addr, "err", err)
|
||||
return err
|
||||
}
|
||||
p.index[addr] = []*blobTxMeta{meta}
|
||||
|
|
@ -1404,20 +1559,32 @@ func (p *BlobPool) Get(hash common.Hash) *types.Transaction {
|
|||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
item := new(types.Transaction)
|
||||
if err := rlp.DecodeBytes(data, item); err != nil {
|
||||
var ptx blobTxForPool
|
||||
if err := rlp.DecodeBytes(data, &ptx); err != nil {
|
||||
id, _ := p.lookup.storeidOfTx(hash)
|
||||
|
||||
log.Error("Blobs corrupted for traced transaction",
|
||||
"hash", hash, "id", id, "err", err)
|
||||
return nil
|
||||
}
|
||||
return item
|
||||
return ptx.ToTx()
|
||||
}
|
||||
|
||||
// GetRLP returns a RLP-encoded transaction if it is contained in the pool.
|
||||
// GetRLP returns a RLP-encoded transaction for network if it is contained in the pool.
|
||||
// It converts the pool's internal type to the RLP format used by the eth protocol:
|
||||
// e.g. type_byte || [..., version, [blobs], [comms], [proofs]]
|
||||
func (p *BlobPool) GetRLP(hash common.Hash) []byte {
|
||||
return p.getRLP(hash)
|
||||
data := p.getRLP(hash)
|
||||
if len(data) == 0 {
|
||||
// Not in this pool, do not log.
|
||||
return nil
|
||||
}
|
||||
rlp, err := encodeForNetwork(data)
|
||||
if err != nil {
|
||||
log.Error("Failed to encode pooled tx into the network type", "hash", hash, "err", err)
|
||||
return nil
|
||||
}
|
||||
return rlp
|
||||
}
|
||||
|
||||
// GetMetadata returns the transaction type and transaction size with the
|
||||
|
|
@ -1486,18 +1653,14 @@ func (p *BlobPool) GetBlobs(vhashes []common.Hash, version byte) ([]*kzg4844.Blo
|
|||
}
|
||||
|
||||
// Decode the blob transaction
|
||||
tx := new(types.Transaction)
|
||||
if err := rlp.DecodeBytes(data, tx); err != nil {
|
||||
var ptx blobTxForPool
|
||||
if err := rlp.DecodeBytes(data, &ptx); err != nil {
|
||||
log.Error("Blobs corrupted for traced transaction", "id", txID, "err", err)
|
||||
continue
|
||||
}
|
||||
sidecar := tx.BlobTxSidecar()
|
||||
if sidecar == nil {
|
||||
log.Error("Blob tx without sidecar", "hash", tx.Hash(), "id", txID)
|
||||
continue
|
||||
}
|
||||
sidecar := ptx.Sidecar()
|
||||
// Traverse the blobs in the transaction
|
||||
for i, hash := range tx.BlobHashes() {
|
||||
for i, hash := range ptx.Tx.BlobHashes() {
|
||||
list, ok := indices[hash]
|
||||
if !ok {
|
||||
continue // non-interesting blob
|
||||
|
|
@ -1517,7 +1680,8 @@ func (p *BlobPool) GetBlobs(vhashes []common.Hash, version byte) ([]*kzg4844.Blo
|
|||
case types.BlobSidecarVersion1:
|
||||
cellProofs, err := sidecar.CellProofsAt(i)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
log.Error("Failed to get cell proofs", "id", txID, "err", err)
|
||||
continue
|
||||
}
|
||||
pf = cellProofs
|
||||
}
|
||||
|
|
@ -1596,9 +1760,10 @@ func (p *BlobPool) addLocked(tx *types.Transaction, checkGapped bool) (err error
|
|||
// Store the tx in memory, and revalidate later
|
||||
from, _ := types.Sender(p.signer, tx)
|
||||
allowance := p.gappedAllowance(from)
|
||||
if allowance >= 1 && len(p.gapped) < maxGapped {
|
||||
if allowance >= 1 && len(p.gappedSource) < maxGapped {
|
||||
p.gapped[from] = append(p.gapped[from], tx)
|
||||
p.gappedSource[tx.Hash()] = from
|
||||
gappedGauge.Update(int64(len(p.gappedSource)))
|
||||
log.Trace("added tx to gapped blob queue", "allowance", allowance, "hash", tx.Hash(), "from", from, "nonce", tx.Nonce(), "qlen", len(p.gapped[from]))
|
||||
return nil
|
||||
} else {
|
||||
|
|
@ -1606,6 +1771,7 @@ func (p *BlobPool) addLocked(tx *types.Transaction, checkGapped bool) (err error
|
|||
// transactions by keeping the old and dropping this one.
|
||||
// Thus replacing a gapped transaction with another gapped transaction
|
||||
// is discouraged.
|
||||
addGappedFullMeter.Mark(1)
|
||||
log.Trace("no gapped blob queue allowance", "allowance", allowance, "hash", tx.Hash(), "from", from, "nonce", tx.Nonce(), "qlen", len(p.gapped[from]))
|
||||
}
|
||||
case errors.Is(err, core.ErrInsufficientFunds):
|
||||
|
|
@ -1641,7 +1807,8 @@ func (p *BlobPool) addLocked(tx *types.Transaction, checkGapped bool) (err error
|
|||
}
|
||||
// Transaction permitted into the pool from a nonce and cost perspective,
|
||||
// insert it into the database and update the indices
|
||||
blob, err := rlp.EncodeToBytes(tx)
|
||||
ptx := newBlobTxForPool(tx)
|
||||
blob, err := rlp.EncodeToBytes(ptx)
|
||||
if err != nil {
|
||||
log.Error("Failed to encode transaction for storage", "hash", tx.Hash(), "err", err)
|
||||
return err
|
||||
|
|
@ -1650,7 +1817,7 @@ func (p *BlobPool) addLocked(tx *types.Transaction, checkGapped bool) (err error
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
meta := newBlobTxMeta(id, tx.Size(), p.store.Size(id), tx)
|
||||
meta := newBlobTxMeta(id, tx.Size(), p.store.Size(id), ptx)
|
||||
|
||||
var (
|
||||
next = p.state.GetNonce(from)
|
||||
|
|
@ -1791,6 +1958,7 @@ func (p *BlobPool) addLocked(tx *types.Transaction, checkGapped bool) (err error
|
|||
// We do not recurse here, but continue to loop instead.
|
||||
// We are under lock, so we can add the transaction directly.
|
||||
if err := p.addLocked(tx, false); err == nil {
|
||||
gappedPromotedMeter.Mark(1)
|
||||
log.Trace("Gapped blob transaction added to pool", "hash", tx.Hash(), "from", from, "nonce", tx.Nonce(), "qlen", len(p.gapped[from]))
|
||||
} else {
|
||||
log.Trace("Gapped blob transaction not accepted", "hash", tx.Hash(), "from", from, "nonce", tx.Nonce(), "err", err)
|
||||
|
|
@ -1802,6 +1970,7 @@ func (p *BlobPool) addLocked(tx *types.Transaction, checkGapped bool) (err error
|
|||
} else {
|
||||
p.gapped[from] = gtxs
|
||||
}
|
||||
gappedGauge.Update(int64(len(p.gappedSource)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -2069,8 +2238,9 @@ func (p *BlobPool) evictGapped() {
|
|||
keep = append(keep, gtx)
|
||||
}
|
||||
}
|
||||
if len(keep) < len(txs) {
|
||||
log.Trace("Evicting old gapped blob transactions", "count", len(txs)-len(keep), "from", from)
|
||||
if evicted := len(txs) - len(keep); evicted > 0 {
|
||||
gappedEvictedMeter.Mark(int64(evicted))
|
||||
log.Trace("Evicting old gapped blob transactions", "count", evicted, "from", from)
|
||||
}
|
||||
if len(keep) == 0 {
|
||||
delete(p.gapped, from)
|
||||
|
|
@ -2078,6 +2248,7 @@ func (p *BlobPool) evictGapped() {
|
|||
p.gapped[from] = keep
|
||||
}
|
||||
}
|
||||
gappedGauge.Update(int64(len(p.gappedSource)))
|
||||
}
|
||||
|
||||
// isAnnouncable checks whether a transaction is announcable based on its
|
||||
|
|
|
|||
|
|
@ -235,6 +235,12 @@ func makeTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCap uint64,
|
|||
return types.MustSignNewTx(key, types.LatestSigner(params.MainnetChainConfig), blobtx)
|
||||
}
|
||||
|
||||
// encodeForPool encodes a blob transaction in the blobTxForPool storage format.
|
||||
func encodeForPool(tx *types.Transaction) []byte {
|
||||
blob, _ := rlp.EncodeToBytes(newBlobTxForPool(tx))
|
||||
return blob
|
||||
}
|
||||
|
||||
// makeMultiBlobTx is a utility method to construct a ramdom blob tx with
|
||||
// certain number of blobs in its sidecar.
|
||||
func makeMultiBlobTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCap uint64, blobCount int, blobOffset int, key *ecdsa.PrivateKey, version byte) *types.Transaction {
|
||||
|
|
@ -530,7 +536,7 @@ func TestOpenDrops(t *testing.T) {
|
|||
)
|
||||
for _, nonce := range []uint64{0, 1, 3, 4, 6, 7} { // first gap at #2, another at #5
|
||||
tx := makeTx(nonce, 1, 1, 1, gapper)
|
||||
blob, _ := rlp.EncodeToBytes(tx)
|
||||
blob := encodeForPool(tx)
|
||||
|
||||
id, _ := store.Put(blob)
|
||||
if nonce < 2 {
|
||||
|
|
@ -547,7 +553,7 @@ func TestOpenDrops(t *testing.T) {
|
|||
)
|
||||
for _, nonce := range []uint64{1, 2, 3} { // first gap at #0, all set dangling
|
||||
tx := makeTx(nonce, 1, 1, 1, dangler)
|
||||
blob, _ := rlp.EncodeToBytes(tx)
|
||||
blob := encodeForPool(tx)
|
||||
|
||||
id, _ := store.Put(blob)
|
||||
dangling[id] = struct{}{}
|
||||
|
|
@ -560,7 +566,7 @@ func TestOpenDrops(t *testing.T) {
|
|||
)
|
||||
for _, nonce := range []uint64{0, 1, 2} { // account nonce at 3, all set filled
|
||||
tx := makeTx(nonce, 1, 1, 1, filler)
|
||||
blob, _ := rlp.EncodeToBytes(tx)
|
||||
blob := encodeForPool(tx)
|
||||
|
||||
id, _ := store.Put(blob)
|
||||
filled[id] = struct{}{}
|
||||
|
|
@ -573,7 +579,7 @@ func TestOpenDrops(t *testing.T) {
|
|||
)
|
||||
for _, nonce := range []uint64{0, 1, 2, 3} { // account nonce at 2, half filled
|
||||
tx := makeTx(nonce, 1, 1, 1, overlapper)
|
||||
blob, _ := rlp.EncodeToBytes(tx)
|
||||
blob := encodeForPool(tx)
|
||||
|
||||
id, _ := store.Put(blob)
|
||||
if nonce >= 2 {
|
||||
|
|
@ -595,7 +601,7 @@ func TestOpenDrops(t *testing.T) {
|
|||
} else {
|
||||
tx = makeTx(uint64(i), 1, 1, 1, underpayer)
|
||||
}
|
||||
blob, _ := rlp.EncodeToBytes(tx)
|
||||
blob := encodeForPool(tx)
|
||||
|
||||
id, _ := store.Put(blob)
|
||||
underpaid[id] = struct{}{}
|
||||
|
|
@ -614,7 +620,7 @@ func TestOpenDrops(t *testing.T) {
|
|||
} else {
|
||||
tx = makeTx(uint64(i), 1, 1, 1, outpricer)
|
||||
}
|
||||
blob, _ := rlp.EncodeToBytes(tx)
|
||||
blob := encodeForPool(tx)
|
||||
|
||||
id, _ := store.Put(blob)
|
||||
if i < 2 {
|
||||
|
|
@ -636,7 +642,7 @@ func TestOpenDrops(t *testing.T) {
|
|||
} else {
|
||||
tx = makeTx(nonce, 1, 1, 1, exceeder)
|
||||
}
|
||||
blob, _ := rlp.EncodeToBytes(tx)
|
||||
blob := encodeForPool(tx)
|
||||
|
||||
id, _ := store.Put(blob)
|
||||
exceeded[id] = struct{}{}
|
||||
|
|
@ -654,7 +660,7 @@ func TestOpenDrops(t *testing.T) {
|
|||
} else {
|
||||
tx = makeTx(nonce, 1, 1, 1, overdrafter)
|
||||
}
|
||||
blob, _ := rlp.EncodeToBytes(tx)
|
||||
blob := encodeForPool(tx)
|
||||
|
||||
id, _ := store.Put(blob)
|
||||
if nonce < 1 {
|
||||
|
|
@ -670,7 +676,7 @@ func TestOpenDrops(t *testing.T) {
|
|||
overcapped = make(map[uint64]struct{})
|
||||
)
|
||||
for nonce := uint64(0); nonce < maxTxsPerAccount+3; nonce++ {
|
||||
blob, _ := rlp.EncodeToBytes(makeTx(nonce, 1, 1, 1, overcapper))
|
||||
blob := encodeForPool(makeTx(nonce, 1, 1, 1, overcapper))
|
||||
|
||||
id, _ := store.Put(blob)
|
||||
if nonce < maxTxsPerAccount {
|
||||
|
|
@ -686,7 +692,7 @@ func TestOpenDrops(t *testing.T) {
|
|||
duplicated = make(map[uint64]struct{})
|
||||
)
|
||||
for _, nonce := range []uint64{0, 1, 2} {
|
||||
blob, _ := rlp.EncodeToBytes(makeTx(nonce, 1, 1, 1, duplicater))
|
||||
blob := encodeForPool(makeTx(nonce, 1, 1, 1, duplicater))
|
||||
|
||||
for i := 0; i < int(nonce)+1; i++ {
|
||||
id, _ := store.Put(blob)
|
||||
|
|
@ -705,7 +711,7 @@ func TestOpenDrops(t *testing.T) {
|
|||
)
|
||||
for _, nonce := range []uint64{0, 1, 2} {
|
||||
for i := 0; i < int(nonce)+1; i++ {
|
||||
blob, _ := rlp.EncodeToBytes(makeTx(nonce, 1, uint64(i)+1 /* unique hashes */, 1, repeater))
|
||||
blob := encodeForPool(makeTx(nonce, 1, uint64(i)+1 /* unique hashes */, 1, repeater))
|
||||
|
||||
id, _ := store.Put(blob)
|
||||
if i == 0 {
|
||||
|
|
@ -842,7 +848,7 @@ func TestOpenIndex(t *testing.T) {
|
|||
)
|
||||
for _, i := range []int{5, 3, 4, 2, 0, 1} { // Randomize the tx insertion order to force sorting on load
|
||||
tx := makeTx(uint64(i), txExecTipCaps[i], txExecFeeCaps[i], txBlobFeeCaps[i], key)
|
||||
blob, _ := rlp.EncodeToBytes(tx)
|
||||
blob := encodeForPool(tx)
|
||||
store.Put(blob)
|
||||
}
|
||||
store.Close()
|
||||
|
|
@ -934,9 +940,9 @@ func TestOpenHeap(t *testing.T) {
|
|||
tx2 = makeTx(0, 1, 800, 70, key2)
|
||||
tx3 = makeTx(0, 1, 1500, 110, key3)
|
||||
|
||||
blob1, _ = rlp.EncodeToBytes(tx1)
|
||||
blob2, _ = rlp.EncodeToBytes(tx2)
|
||||
blob3, _ = rlp.EncodeToBytes(tx3)
|
||||
blob1 = encodeForPool(tx1)
|
||||
blob2 = encodeForPool(tx2)
|
||||
blob3 = encodeForPool(tx3)
|
||||
|
||||
heapOrder = []common.Address{addr2, addr1, addr3}
|
||||
heapIndex = map[common.Address]int{addr2: 0, addr1: 1, addr3: 2}
|
||||
|
|
@ -1009,9 +1015,9 @@ func TestOpenCap(t *testing.T) {
|
|||
tx2 = makeTx(0, 1, 800, 70, key2)
|
||||
tx3 = makeTx(0, 1, 1500, 110, key3)
|
||||
|
||||
blob1, _ = rlp.EncodeToBytes(tx1)
|
||||
blob2, _ = rlp.EncodeToBytes(tx2)
|
||||
blob3, _ = rlp.EncodeToBytes(tx3)
|
||||
blob1 = encodeForPool(tx1)
|
||||
blob2 = encodeForPool(tx2)
|
||||
blob3 = encodeForPool(tx3)
|
||||
|
||||
keep = []common.Address{addr1, addr3}
|
||||
drop = []common.Address{addr2}
|
||||
|
|
@ -1098,8 +1104,8 @@ func TestChangingSlotterSize(t *testing.T) {
|
|||
tx2 = makeMultiBlobTx(0, 1, 800, 70, 6, 0, key2, types.BlobSidecarVersion0)
|
||||
tx3 = makeMultiBlobTx(0, 1, 800, 110, 24, 0, key3, types.BlobSidecarVersion0)
|
||||
|
||||
blob1, _ = rlp.EncodeToBytes(tx1)
|
||||
blob2, _ = rlp.EncodeToBytes(tx2)
|
||||
blob1 = encodeForPool(tx1)
|
||||
blob2 = encodeForPool(tx2)
|
||||
)
|
||||
|
||||
// Write the two safely sized txs to store. note: although the store is
|
||||
|
|
@ -1201,8 +1207,8 @@ func TestBillyMigration(t *testing.T) {
|
|||
tx2 = makeMultiBlobTx(0, 1, 800, 70, 6, 0, key2, types.BlobSidecarVersion0)
|
||||
tx3 = makeMultiBlobTx(0, 1, 800, 110, 24, 0, key3, types.BlobSidecarVersion0)
|
||||
|
||||
blob1, _ = rlp.EncodeToBytes(tx1)
|
||||
blob2, _ = rlp.EncodeToBytes(tx2)
|
||||
blob1 = encodeForPool(tx1)
|
||||
blob2 = encodeForPool(tx2)
|
||||
)
|
||||
|
||||
// Write the two safely sized txs to store. note: although the store is
|
||||
|
|
@ -1281,6 +1287,85 @@ func TestBillyMigration(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestLegacyTxConversion verifies that on Init, transactions stored in the
|
||||
// legacy *types.Transaction RLP format are detected and migrated into the new
|
||||
// blobTxForPool storage format, and that they remain retrievable via the pool
|
||||
// API after the conversion.
|
||||
func TestLegacyTxConversion(t *testing.T) {
|
||||
storage := t.TempDir()
|
||||
os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700)
|
||||
os.MkdirAll(filepath.Join(storage, limboedTransactionStore), 0700)
|
||||
|
||||
// Initialize the pending store with two blob transactions encoded in the
|
||||
// legacy format.
|
||||
queuedir := filepath.Join(storage, pendingTransactionStore)
|
||||
store, err := billy.Open(billy.Options{Path: queuedir}, newSlotter(testMaxBlobsPerBlock), nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open billy: %v", err)
|
||||
}
|
||||
|
||||
key1, _ := crypto.GenerateKey()
|
||||
key2, _ := crypto.GenerateKey()
|
||||
addr1 := crypto.PubkeyToAddress(key1.PublicKey)
|
||||
addr2 := crypto.PubkeyToAddress(key2.PublicKey)
|
||||
|
||||
tx1 := makeMultiBlobTx(0, 1, 1000, 100, 2, 0, key1, types.BlobSidecarVersion0)
|
||||
tx2 := makeMultiBlobTx(0, 1, 1000, 100, 2, 2, key2, types.BlobSidecarVersion0)
|
||||
|
||||
for _, tx := range []*types.Transaction{tx1, tx2} {
|
||||
legacy, err := rlp.EncodeToBytes(tx)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to legacy-encode tx: %v", err)
|
||||
}
|
||||
if _, err := store.Put(legacy); err != nil {
|
||||
t.Fatalf("failed to put legacy blob: %v", err)
|
||||
}
|
||||
}
|
||||
store.Close()
|
||||
|
||||
// Init should migrate the legacy entries into the new storage format.
|
||||
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
|
||||
statedb.AddBalance(addr1, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified)
|
||||
statedb.AddBalance(addr2, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified)
|
||||
statedb.Commit(0, true, false)
|
||||
|
||||
chain := &testBlockChain{
|
||||
config: params.MainnetChainConfig,
|
||||
basefee: uint256.NewInt(params.InitialBaseFee),
|
||||
blobfee: uint256.NewInt(params.BlobTxMinBlobGasprice),
|
||||
statedb: statedb,
|
||||
}
|
||||
pool := New(Config{Datadir: storage}, chain, nil)
|
||||
if err := pool.Init(1, chain.CurrentBlock(), newReserver()); err != nil {
|
||||
t.Fatalf("failed to create blob pool: %v", err)
|
||||
}
|
||||
defer pool.Close()
|
||||
|
||||
// Both transactions should be retrievable.
|
||||
for _, want := range []*types.Transaction{tx1, tx2} {
|
||||
got := pool.Get(want.Hash())
|
||||
if got == nil {
|
||||
t.Fatalf("migrated tx %s not found in pool", want.Hash())
|
||||
}
|
||||
if got.BlobTxSidecar() == nil {
|
||||
t.Fatalf("migrated tx %s lost its sidecar", want.Hash())
|
||||
}
|
||||
if got.Hash() != want.Hash() {
|
||||
t.Fatalf("migrated tx hash mismatch: have %s, want %s", got.Hash(), want.Hash())
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy formats should not exist on pool.store
|
||||
pool.store.Iterate(func(id uint64, size uint32, blob []byte) {
|
||||
var ptx blobTxForPool
|
||||
if err := rlp.DecodeBytes(blob, &ptx); err != nil {
|
||||
t.Errorf("entry %d not in new blobTxForPool format: %v", id, err)
|
||||
}
|
||||
})
|
||||
|
||||
verifyPoolInternals(t, pool)
|
||||
}
|
||||
|
||||
// TestBlobCountLimit tests the blobpool enforced limits on the max blob count.
|
||||
func TestBlobCountLimit(t *testing.T) {
|
||||
var (
|
||||
|
|
@ -1746,7 +1831,7 @@ func TestAdd(t *testing.T) {
|
|||
// Sign the seed transactions and store them in the data store
|
||||
for _, tx := range seed.txs {
|
||||
signed := types.MustSignNewTx(keys[acc], types.LatestSigner(params.MainnetChainConfig), tx)
|
||||
blob, _ := rlp.EncodeToBytes(signed)
|
||||
blob := encodeForPool(signed)
|
||||
store.Put(blob)
|
||||
}
|
||||
}
|
||||
|
|
@ -1853,9 +1938,9 @@ func TestGetBlobs(t *testing.T) {
|
|||
tx2 = makeMultiBlobTx(0, 1, 800, 70, 6, 6, key2, types.BlobSidecarVersion1) // [6, 12)
|
||||
tx3 = makeMultiBlobTx(0, 1, 800, 110, 6, 12, key3, types.BlobSidecarVersion0) // [12, 18)
|
||||
|
||||
blob1, _ = rlp.EncodeToBytes(tx1)
|
||||
blob2, _ = rlp.EncodeToBytes(tx2)
|
||||
blob3, _ = rlp.EncodeToBytes(tx3)
|
||||
blob1 = encodeForPool(tx1)
|
||||
blob2 = encodeForPool(tx2)
|
||||
blob3 = encodeForPool(tx3)
|
||||
)
|
||||
|
||||
// Write the two safely sized txs to store. note: although the store is
|
||||
|
|
@ -2055,6 +2140,32 @@ func TestGetBlobs(t *testing.T) {
|
|||
pool.Close()
|
||||
}
|
||||
|
||||
// TestEncodeForNetwork verifies that encodeForNetwork produces output identical
|
||||
// to rlp.EncodeToBytes on the original transaction, for both V0 and V1 sidecars.
|
||||
func TestEncodeForNetwork(t *testing.T) {
|
||||
t.Run("v0", func(t *testing.T) { testEncodeForNetwork(t, types.BlobSidecarVersion0) })
|
||||
t.Run("v1", func(t *testing.T) { testEncodeForNetwork(t, types.BlobSidecarVersion1) })
|
||||
}
|
||||
|
||||
func testEncodeForNetwork(t *testing.T, version byte) {
|
||||
key, _ := crypto.GenerateKey()
|
||||
tx := makeMultiBlobTx(0, 1, 1, 1, 1, 0, key, version)
|
||||
|
||||
wantRLP, err := rlp.EncodeToBytes(tx)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to encode tx: %v", err)
|
||||
}
|
||||
storedRLP := encodeForPool(tx)
|
||||
|
||||
gotRLP, err := encodeForNetwork(storedRLP)
|
||||
if err != nil {
|
||||
t.Fatalf("encodeForNetwork failed: %v", err)
|
||||
}
|
||||
if !bytes.Equal(gotRLP, wantRLP) {
|
||||
t.Fatalf("network encoding mismatch (version %d): got %d bytes, want %d bytes", version, len(gotRLP), len(wantRLP))
|
||||
}
|
||||
}
|
||||
|
||||
// fakeBilly is a billy.Database implementation which just drops data on the floor.
|
||||
type fakeBilly struct {
|
||||
billy.Database
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ import (
|
|||
type limboBlob struct {
|
||||
TxHash common.Hash // Owner transaction's hash to support resurrecting reorged txs
|
||||
Block uint64 // Block in which the blob transaction was included
|
||||
Tx *types.Transaction
|
||||
Ptx *blobTxForPool
|
||||
}
|
||||
|
||||
// limbo is a light, indexed database to temporarily store recently included
|
||||
|
|
@ -146,15 +146,14 @@ func (l *limbo) finalize(final *types.Header) {
|
|||
|
||||
// push stores a new blob transaction into the limbo, waiting until finality for
|
||||
// it to be automatically evicted.
|
||||
func (l *limbo) push(tx *types.Transaction, block uint64) error {
|
||||
// If the blobs are already tracked by the limbo, consider it a programming
|
||||
// error. There's not much to do against it, but be loud.
|
||||
if _, ok := l.index[tx.Hash()]; ok {
|
||||
log.Error("Limbo cannot push already tracked blobs", "tx", tx.Hash())
|
||||
func (l *limbo) push(ptx *blobTxForPool, block uint64) error {
|
||||
hash := ptx.Tx.Hash()
|
||||
if _, ok := l.index[hash]; ok {
|
||||
log.Error("Limbo cannot push already tracked blobs", "tx", hash)
|
||||
return errors.New("already tracked blob transaction")
|
||||
}
|
||||
if err := l.setAndIndex(tx, block); err != nil {
|
||||
log.Error("Failed to set and index limboed blobs", "tx", tx.Hash(), "err", err)
|
||||
if err := l.setAndIndex(ptx, block); err != nil {
|
||||
log.Error("Failed to set and index limboed blobs", "tx", hash, "err", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
|
@ -163,7 +162,7 @@ func (l *limbo) push(tx *types.Transaction, block uint64) error {
|
|||
// pull retrieves a previously pushed set of blobs back from the limbo, removing
|
||||
// it at the same time. This method should be used when a previously included blob
|
||||
// transaction gets reorged out.
|
||||
func (l *limbo) pull(tx common.Hash) (*types.Transaction, error) {
|
||||
func (l *limbo) pull(tx common.Hash) (*blobTxForPool, error) {
|
||||
// If the blobs are not tracked by the limbo, there's not much to do. This
|
||||
// can happen for example if a blob transaction is mined without pushing it
|
||||
// into the network first.
|
||||
|
|
@ -177,7 +176,7 @@ func (l *limbo) pull(tx common.Hash) (*types.Transaction, error) {
|
|||
log.Error("Failed to get and drop limboed blobs", "tx", tx, "id", id, "err", err)
|
||||
return nil, err
|
||||
}
|
||||
return item.Tx, nil
|
||||
return item.Ptx, nil
|
||||
}
|
||||
|
||||
// update changes the block number under which a blob transaction is tracked. This
|
||||
|
|
@ -209,7 +208,7 @@ func (l *limbo) update(txhash common.Hash, block uint64) {
|
|||
log.Error("Failed to get and drop limboed blobs", "tx", txhash, "id", id, "err", err)
|
||||
return
|
||||
}
|
||||
if err := l.setAndIndex(item.Tx, block); err != nil {
|
||||
if err := l.setAndIndex(item.Ptx, block); err != nil {
|
||||
log.Error("Failed to set and index limboed blobs", "tx", txhash, "err", err)
|
||||
return
|
||||
}
|
||||
|
|
@ -240,12 +239,12 @@ func (l *limbo) getAndDrop(id uint64) (*limboBlob, error) {
|
|||
|
||||
// setAndIndex assembles a limbo blob database entry and stores it, also updating
|
||||
// the in-memory indices.
|
||||
func (l *limbo) setAndIndex(tx *types.Transaction, block uint64) error {
|
||||
txhash := tx.Hash()
|
||||
func (l *limbo) setAndIndex(ptx *blobTxForPool, block uint64) error {
|
||||
txhash := ptx.Tx.Hash()
|
||||
item := &limboBlob{
|
||||
TxHash: txhash,
|
||||
Block: block,
|
||||
Tx: tx,
|
||||
Ptx: ptx,
|
||||
}
|
||||
data, err := rlp.EncodeToBytes(item)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -97,9 +97,15 @@ var (
|
|||
addUnderpricedMeter = metrics.NewRegisteredMeter("blobpool/add/underpriced", nil) // Gas tip too low, neutral
|
||||
addStaleMeter = metrics.NewRegisteredMeter("blobpool/add/stale", nil) // Nonce already filled, reject, bad-ish
|
||||
addGappedMeter = metrics.NewRegisteredMeter("blobpool/add/gapped", nil) // Nonce gapped, reject, bad-ish
|
||||
addGappedFullMeter = metrics.NewRegisteredMeter("blobpool/add/gappedfull", nil) // Gapped queue full, reject, neutral
|
||||
addOverdraftedMeter = metrics.NewRegisteredMeter("blobpool/add/overdrafted", nil) // Balance exceeded, reject, neutral
|
||||
addOvercappedMeter = metrics.NewRegisteredMeter("blobpool/add/overcapped", nil) // Per-account cap exceeded, reject, neutral
|
||||
addNoreplaceMeter = metrics.NewRegisteredMeter("blobpool/add/noreplace", nil) // Replacement fees or tips too low, neutral
|
||||
addNonExclusiveMeter = metrics.NewRegisteredMeter("blobpool/add/nonexclusive", nil) // Plain transaction from same account exists, reject, neutral
|
||||
addValidMeter = metrics.NewRegisteredMeter("blobpool/add/valid", nil) // Valid transaction, add, neutral
|
||||
|
||||
// Gapped queue metrics for observability
|
||||
gappedGauge = metrics.NewRegisteredGauge("blobpool/gapped/count", nil) // Current gapped queue size
|
||||
gappedPromotedMeter = metrics.NewRegisteredMeter("blobpool/gapped/promoted", nil) // Gapped txs successfully promoted to pool
|
||||
gappedEvictedMeter = metrics.NewRegisteredMeter("blobpool/gapped/evicted", nil) // Gapped txs evicted due to timeout/stale
|
||||
)
|
||||
|
|
|
|||
|
|
@ -467,8 +467,8 @@ func (pool *LegacyPool) stats() (int, int) {
|
|||
// Content retrieves the data content of the transaction pool, returning all the
|
||||
// pending as well as queued transactions, grouped by account and sorted by nonce.
|
||||
func (pool *LegacyPool) Content() (map[common.Address][]*types.Transaction, map[common.Address][]*types.Transaction) {
|
||||
pool.mu.Lock()
|
||||
defer pool.mu.Unlock()
|
||||
pool.mu.RLock()
|
||||
defer pool.mu.RUnlock()
|
||||
|
||||
pending := make(map[common.Address][]*types.Transaction, len(pool.pending))
|
||||
for addr, list := range pool.pending {
|
||||
|
|
@ -503,8 +503,8 @@ func (pool *LegacyPool) Pending(filter txpool.PendingFilter) (map[common.Address
|
|||
if filter.BlobTxs {
|
||||
return nil, 0
|
||||
}
|
||||
pool.mu.Lock()
|
||||
defer pool.mu.Unlock()
|
||||
pool.mu.RLock()
|
||||
defer pool.mu.RUnlock()
|
||||
|
||||
var count int
|
||||
pending := make(map[common.Address][]*txpool.LazyTransaction, len(pool.pending))
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
package locals
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"slices"
|
||||
"sync"
|
||||
"time"
|
||||
|
|
@ -151,7 +152,7 @@ func (tracker *TxTracker) recheck(journalCheck bool) []*types.Transaction {
|
|||
for _, list := range rejournal {
|
||||
// cmp(a, b) should return a negative number when a < b,
|
||||
slices.SortFunc(list, func(a, b *types.Transaction) int {
|
||||
return int(a.Nonce() - b.Nonce())
|
||||
return cmp.Compare(a.Nonce(), b.Nonce())
|
||||
})
|
||||
}
|
||||
// Rejournal the tracker while holding the lock. No new transactions will
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
|
|||
}
|
||||
// Ensure the transaction has more gas than the bare minimum needed to cover
|
||||
// the transaction metadata
|
||||
intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, true, rules.IsIstanbul, rules.IsShanghai)
|
||||
intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, true, rules.IsIstanbul, rules.IsShanghai, rules.IsAmsterdam)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -134,7 +134,7 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
|
|||
}
|
||||
// Ensure the transaction can cover floor data gas.
|
||||
if rules.IsPrague {
|
||||
floorDataGas, err := core.FloorDataGas(rules, tx.Data())
|
||||
floorDataGas, err := core.FloorDataGas(rules, tx.Data(), tx.AccessList())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import (
|
|||
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
)
|
||||
|
||||
|
|
@ -58,4 +59,8 @@ type ProcessResult struct {
|
|||
Requests [][]byte
|
||||
Logs []*types.Log
|
||||
GasUsed uint64
|
||||
|
||||
// BAL is only meaningful for post-Amsterdam blocks. Please ensure
|
||||
// fork validation is performed before accessing it.
|
||||
Bal *bal.ConstructionBlockAccessList
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,94 +0,0 @@
|
|||
// Copyright 2026 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 bal
|
||||
|
||||
import (
|
||||
"maps"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
// StorageAccessList represents a set of storage slots accessed within an account.
|
||||
type StorageAccessList map[common.Hash]struct{}
|
||||
|
||||
// StateAccessList records the set of accounts and storage slots that have been
|
||||
// accessed. An entry with an empty StorageAccessList denotes an account access
|
||||
// without any storage slot access.
|
||||
type StateAccessList struct {
|
||||
list map[common.Address]StorageAccessList
|
||||
}
|
||||
|
||||
// NewStateAccessList returns an empty StateAccessList ready for use.
|
||||
func NewStateAccessList() *StateAccessList {
|
||||
return &StateAccessList{
|
||||
list: make(map[common.Address]StorageAccessList),
|
||||
}
|
||||
}
|
||||
|
||||
// AddAccount records an access to the given account. It is a no-op if the
|
||||
// account is already present.
|
||||
func (s *StateAccessList) AddAccount(addr common.Address) {
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
if _, exists := s.list[addr]; !exists {
|
||||
s.list[addr] = make(StorageAccessList)
|
||||
}
|
||||
}
|
||||
|
||||
// AddState records an access to the given storage slot. The owning account is
|
||||
// implicitly recorded as well.
|
||||
func (s *StateAccessList) AddState(addr common.Address, slot common.Hash) {
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
slots, exists := s.list[addr]
|
||||
if !exists {
|
||||
slots = make(StorageAccessList)
|
||||
s.list[addr] = slots
|
||||
}
|
||||
slots[slot] = struct{}{}
|
||||
}
|
||||
|
||||
// Merge merges the entries from other into the receiver.
|
||||
func (s *StateAccessList) Merge(other *StateAccessList) {
|
||||
if s == nil || other == nil {
|
||||
return
|
||||
}
|
||||
for addr, otherSlots := range other.list {
|
||||
slots, exists := s.list[addr]
|
||||
if !exists {
|
||||
s.list[addr] = otherSlots
|
||||
continue
|
||||
}
|
||||
maps.Copy(slots, otherSlots)
|
||||
}
|
||||
}
|
||||
|
||||
// Copy returns a deep copy of the StateAccessList.
|
||||
func (s *StateAccessList) Copy() *StateAccessList {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
cpy := &StateAccessList{
|
||||
list: make(map[common.Address]StorageAccessList, len(s.list)),
|
||||
}
|
||||
for addr, slots := range s.list {
|
||||
cpy.list[addr] = maps.Clone(slots)
|
||||
}
|
||||
return cpy
|
||||
}
|
||||
|
|
@ -71,8 +71,8 @@ type ConstructionBlockAccessList struct {
|
|||
}
|
||||
|
||||
// NewConstructionBlockAccessList instantiates an empty access list.
|
||||
func NewConstructionBlockAccessList() ConstructionBlockAccessList {
|
||||
return ConstructionBlockAccessList{
|
||||
func NewConstructionBlockAccessList() *ConstructionBlockAccessList {
|
||||
return &ConstructionBlockAccessList{
|
||||
Accounts: make(map[common.Address]*ConstructionAccountAccess),
|
||||
}
|
||||
}
|
||||
|
|
@ -138,10 +138,62 @@ func (b *ConstructionBlockAccessList) BalanceChange(txIdx uint32, address common
|
|||
|
||||
// PrettyPrint returns a human-readable representation of the access list
|
||||
func (b *ConstructionBlockAccessList) PrettyPrint() string {
|
||||
enc := b.toEncodingObj()
|
||||
enc := b.ToEncodingObj()
|
||||
return enc.PrettyPrint()
|
||||
}
|
||||
|
||||
// Merge applies other on top of the local block access list. For colliding
|
||||
// entries (a (slot, txIdx) write or a txIdx-keyed balance/nonce/code change),
|
||||
// the value from other wins, matching the semantics of applying the local
|
||||
// effects first and then other's. Storage reads are unioned; any slot
|
||||
// written by either side is dropped from StorageReads.
|
||||
//
|
||||
// Typically each list covers its own tx index, so txIdx-level collisions are
|
||||
// not expected; the exception is pre/post-transition system calls, which
|
||||
// share a single tx index. In that case callers must pass block-accessList
|
||||
// in order strictly.
|
||||
//
|
||||
// other is referenced (not deep copied), after the call both lists share
|
||||
// inner maps and other must not be mutated.
|
||||
func (b *ConstructionBlockAccessList) Merge(other *ConstructionBlockAccessList) {
|
||||
if other == nil {
|
||||
return
|
||||
}
|
||||
for addr, otherAcc := range other.Accounts {
|
||||
acc, ok := b.Accounts[addr]
|
||||
if !ok {
|
||||
b.Accounts[addr] = otherAcc
|
||||
continue
|
||||
}
|
||||
for key, writes := range otherAcc.StorageWrites {
|
||||
existing, ok := acc.StorageWrites[key]
|
||||
if !ok {
|
||||
acc.StorageWrites[key] = writes
|
||||
} else {
|
||||
for txIdx, value := range writes {
|
||||
existing[txIdx] = value
|
||||
}
|
||||
}
|
||||
delete(acc.StorageReads, key)
|
||||
}
|
||||
for key := range otherAcc.StorageReads {
|
||||
if _, ok := acc.StorageWrites[key]; ok {
|
||||
continue
|
||||
}
|
||||
acc.StorageReads[key] = struct{}{}
|
||||
}
|
||||
for txIdx, balance := range otherAcc.BalanceChanges {
|
||||
acc.BalanceChanges[txIdx] = balance
|
||||
}
|
||||
for txIdx, nonce := range otherAcc.NonceChanges {
|
||||
acc.NonceChanges[txIdx] = nonce
|
||||
}
|
||||
for txIdx, code := range otherAcc.CodeChange {
|
||||
acc.CodeChange[txIdx] = code
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy returns a deep copy of the access list.
|
||||
func (b *ConstructionBlockAccessList) Copy() *ConstructionBlockAccessList {
|
||||
res := NewConstructionBlockAccessList()
|
||||
|
|
@ -169,5 +221,5 @@ func (b *ConstructionBlockAccessList) Copy() *ConstructionBlockAccessList {
|
|||
aaCopy.CodeChange = codes
|
||||
res.Accounts[addr] = &aaCopy
|
||||
}
|
||||
return &res
|
||||
return res
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,17 +78,43 @@ func (e *BlockAccessList) DecodeRLP(s *rlp.Stream) error {
|
|||
// Validate returns an error if the contents of the access list are not ordered
|
||||
// according to the spec or any code changes are contained which exceed protocol
|
||||
// max code size.
|
||||
func (e *BlockAccessList) Validate(rules params.Rules) error {
|
||||
func (e *BlockAccessList) Validate(blockGasLimit uint64) error {
|
||||
if !slices.IsSortedFunc(*e, func(a, b AccountAccess) int {
|
||||
return bytes.Compare(a.Address[:], b.Address[:])
|
||||
}) {
|
||||
return errors.New("block access list accounts not in lexicographic order")
|
||||
}
|
||||
for _, entry := range *e {
|
||||
if err := entry.validate(rules); err != nil {
|
||||
if err := entry.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return e.ValidateSize(blockGasLimit)
|
||||
}
|
||||
|
||||
// itemCount returns the number of items in the BAL for EIP-7928 size-constraint
|
||||
// purposes: the count of distinct addresses plus every storage key (writes +
|
||||
// reads) carried by those accounts. A storage slot is counted once regardless
|
||||
// of how many transactions wrote to it.
|
||||
func (e *BlockAccessList) itemCount() uint64 {
|
||||
count := uint64(len(*e)) // distinct addresses
|
||||
for i := range *e {
|
||||
count += uint64(len((*e)[i].StorageWrites)) + uint64(len((*e)[i].StorageReads))
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// ValidateSize returns an error if the BAL violates the EIP-7928 size
|
||||
// constraint for the given block gas limit:
|
||||
//
|
||||
// itemCount() <= blockGasLimit / params.BALItemCost
|
||||
func (e *BlockAccessList) ValidateSize(blockGasLimit uint64) error {
|
||||
items := e.itemCount()
|
||||
limit := blockGasLimit / params.BALItemCost
|
||||
if items > limit {
|
||||
return fmt.Errorf("block access list exceeds size constraint: items=%d, limit=%d (block gas limit %d / %d)",
|
||||
items, limit, blockGasLimit, params.BALItemCost)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -159,7 +185,7 @@ type AccountAccess struct {
|
|||
// validate converts the account accesses out of encoding format.
|
||||
// If any of the keys in the encoding object are not ordered according to the
|
||||
// spec, an error is returned.
|
||||
func (e *AccountAccess) validate(rules params.Rules) error {
|
||||
func (e *AccountAccess) validate() error {
|
||||
// Check the storage write slots are sorted in order
|
||||
if !slices.IsSortedFunc(e.StorageWrites, func(a, b encodingSlotWrites) int {
|
||||
return a.Slot.Cmp(b.Slot)
|
||||
|
|
@ -200,14 +226,7 @@ func (e *AccountAccess) validate(rules params.Rules) error {
|
|||
return errors.New("code changes not in ascending order by tx index")
|
||||
}
|
||||
for _, change := range e.CodeChanges {
|
||||
var sizeLimit int
|
||||
switch {
|
||||
case rules.IsAmsterdam:
|
||||
sizeLimit = params.MaxCodeSizeAmsterdam
|
||||
default:
|
||||
sizeLimit = params.MaxCodeSize
|
||||
}
|
||||
if len(change.Code) > sizeLimit {
|
||||
if len(change.Code) > params.MaxCodeSizeAmsterdam {
|
||||
return errors.New("code change contained oversized code")
|
||||
}
|
||||
}
|
||||
|
|
@ -257,7 +276,7 @@ func (e *AccountAccess) Copy() AccountAccess {
|
|||
|
||||
// EncodeRLP returns the RLP-encoded access list
|
||||
func (b *ConstructionBlockAccessList) EncodeRLP(wr io.Writer) error {
|
||||
return b.toEncodingObj().EncodeRLP(wr)
|
||||
return b.ToEncodingObj().EncodeRLP(wr)
|
||||
}
|
||||
|
||||
var _ rlp.Encoder = &ConstructionBlockAccessList{}
|
||||
|
|
@ -340,9 +359,9 @@ func (a *ConstructionAccountAccess) toEncodingObj(addr common.Address) AccountAc
|
|||
return res
|
||||
}
|
||||
|
||||
// toEncodingObj returns an instance of the access list expressed as the type
|
||||
// ToEncodingObj returns an instance of the access list expressed as the type
|
||||
// which is used as input for the encoding/decoding.
|
||||
func (b *ConstructionBlockAccessList) toEncodingObj() *BlockAccessList {
|
||||
func (b *ConstructionBlockAccessList) ToEncodingObj() *BlockAccessList {
|
||||
var addresses []common.Address
|
||||
for addr := range b.Accounts {
|
||||
addresses = append(addresses, addr)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package bal
|
|||
import (
|
||||
"bytes"
|
||||
"cmp"
|
||||
"math"
|
||||
"reflect"
|
||||
"slices"
|
||||
"testing"
|
||||
|
|
@ -98,14 +99,65 @@ func TestBALEncoding(t *testing.T) {
|
|||
if err := dec.DecodeRLP(rlp.NewStream(bytes.NewReader(buf.Bytes()), 0)); err != nil {
|
||||
t.Fatalf("decoding failed: %v\n", err)
|
||||
}
|
||||
if dec.Hash() != bal.toEncodingObj().Hash() {
|
||||
if dec.Hash() != bal.ToEncodingObj().Hash() {
|
||||
t.Fatalf("encoded block hash doesn't match decoded")
|
||||
}
|
||||
if !reflect.DeepEqual(bal.toEncodingObj(), &dec) {
|
||||
if !reflect.DeepEqual(bal.ToEncodingObj(), &dec) {
|
||||
t.Fatal("decoded BAL doesn't match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConstructionBALMerge(t *testing.T) {
|
||||
var (
|
||||
addrA = common.BytesToAddress([]byte{0xAA})
|
||||
addrB = common.BytesToAddress([]byte{0xBB})
|
||||
slot1 = common.BytesToHash([]byte{0x01})
|
||||
slot2 = common.BytesToHash([]byte{0x02})
|
||||
slot3 = common.BytesToHash([]byte{0x03})
|
||||
)
|
||||
a := NewConstructionBlockAccessList()
|
||||
a.StorageWrite(1, addrA, slot1, common.BytesToHash([]byte{0x11}))
|
||||
a.StorageRead(addrA, slot2) // demoted by other's write below
|
||||
a.BalanceChange(1, addrA, uint256.NewInt(100))
|
||||
a.NonceChange(addrA, 1, 7)
|
||||
|
||||
b := NewConstructionBlockAccessList()
|
||||
b.StorageWrite(2, addrA, slot1, common.BytesToHash([]byte{0x22})) // same slot, disjoint txIdx
|
||||
b.StorageWrite(2, addrA, slot2, common.BytesToHash([]byte{0x33}))
|
||||
b.StorageRead(addrA, slot3)
|
||||
b.BalanceChange(2, addrA, uint256.NewInt(200))
|
||||
b.NonceChange(addrA, 2, 8)
|
||||
b.CodeChange(addrB, 2, []byte{0xde, 0xad}) // account only in other
|
||||
|
||||
a.Merge(b)
|
||||
|
||||
accA := a.Accounts[addrA]
|
||||
wantWrites := map[common.Hash]map[uint32]common.Hash{
|
||||
slot1: {1: common.BytesToHash([]byte{0x11}), 2: common.BytesToHash([]byte{0x22})},
|
||||
slot2: {2: common.BytesToHash([]byte{0x33})},
|
||||
}
|
||||
if !reflect.DeepEqual(accA.StorageWrites, wantWrites) {
|
||||
t.Fatalf("storage writes mismatch: got %v, want %v", accA.StorageWrites, wantWrites)
|
||||
}
|
||||
wantReads := map[common.Hash]struct{}{slot3: {}}
|
||||
if !reflect.DeepEqual(accA.StorageReads, wantReads) {
|
||||
t.Fatalf("storage reads mismatch: got %v, want %v", accA.StorageReads, wantReads)
|
||||
}
|
||||
if accA.BalanceChanges[1].Uint64() != 100 || accA.BalanceChanges[2].Uint64() != 200 {
|
||||
t.Fatalf("balance changes mismatch: %v", accA.BalanceChanges)
|
||||
}
|
||||
if accA.NonceChanges[1] != 7 || accA.NonceChanges[2] != 8 {
|
||||
t.Fatalf("nonce changes mismatch: %v", accA.NonceChanges)
|
||||
}
|
||||
accB, ok := a.Accounts[addrB]
|
||||
if !ok {
|
||||
t.Fatal("account only present in other was not adopted")
|
||||
}
|
||||
if !bytes.Equal(accB.CodeChange[2], []byte{0xde, 0xad}) {
|
||||
t.Fatalf("code change for adopted account missing: %x", accB.CodeChange[2])
|
||||
}
|
||||
}
|
||||
|
||||
func makeTestAccountAccess(sort bool) AccountAccess {
|
||||
var (
|
||||
storageWrites []encodingSlotWrites
|
||||
|
|
@ -231,10 +283,82 @@ func TestBlockAccessListCopy(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestBlockAccessListItemCount(t *testing.T) {
|
||||
empty := &BlockAccessList{}
|
||||
if got := empty.itemCount(); got != 0 {
|
||||
t.Fatalf("empty BAL item count: got %d, want 0", got)
|
||||
}
|
||||
|
||||
addr1 := [20]byte(testrand.Bytes(20))
|
||||
addr2 := [20]byte(testrand.Bytes(20))
|
||||
one := func() *uint256.Int { return new(uint256.Int).SetBytes(testrand.Bytes(32)) }
|
||||
bal := &BlockAccessList{
|
||||
AccountAccess{
|
||||
Address: addr1,
|
||||
StorageWrites: []encodingSlotWrites{
|
||||
{Slot: one(), Accesses: []encodingStorageWrite{{TxIdx: 0, ValueAfter: one()}, {TxIdx: 1, ValueAfter: one()}}},
|
||||
{Slot: one()},
|
||||
},
|
||||
StorageReads: []*uint256.Int{one()},
|
||||
},
|
||||
AccountAccess{Address: addr2}, // address-only, no slots
|
||||
}
|
||||
// 2 addresses + 2 write-slots + 1 read-slot = 5 items.
|
||||
// (Multiple TxIdx writes to the same slot count as ONE item.)
|
||||
if got := bal.itemCount(); got != 5 {
|
||||
t.Fatalf("item count: got %d, want 5", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlockAccessListValidateSize(t *testing.T) {
|
||||
// Build a BAL with exactly 30 items: 3 addresses, each with 9 storage
|
||||
// slots (some writes, some reads). 3 + 9*3 = 30.
|
||||
one := func() *uint256.Int { return new(uint256.Int).SetBytes(testrand.Bytes(32)) }
|
||||
bal := make(BlockAccessList, 3)
|
||||
for i := range bal {
|
||||
bal[i].Address = [20]byte(testrand.Bytes(20))
|
||||
for j := 0; j < 5; j++ {
|
||||
bal[i].StorageWrites = append(bal[i].StorageWrites, encodingSlotWrites{
|
||||
Slot: one(), Accesses: []encodingStorageWrite{{TxIdx: 0, ValueAfter: one()}},
|
||||
})
|
||||
}
|
||||
for j := 0; j < 4; j++ {
|
||||
bal[i].StorageReads = append(bal[i].StorageReads, one())
|
||||
}
|
||||
}
|
||||
if got := bal.itemCount(); got != 30 {
|
||||
t.Fatalf("setup: item count = %d, want 30", got)
|
||||
}
|
||||
|
||||
// limit = blockGasLimit / BALItemCost.
|
||||
// 30 items requires limit >= 30, i.e. gasLimit >= 30 * 2000 = 60_000.
|
||||
tests := []struct {
|
||||
name string
|
||||
gasLimit uint64
|
||||
expectError bool
|
||||
}{
|
||||
{"exactly at limit", 30 * params.BALItemCost, false},
|
||||
{"well above limit", 60_000_000, false},
|
||||
{"one below limit", 30*params.BALItemCost - 1, true},
|
||||
{"zero gas limit", 0, true},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
err := bal.ValidateSize(tc.gasLimit)
|
||||
if (err != nil) != tc.expectError {
|
||||
t.Errorf("%s: got err=%v, expectError=%v", tc.name, err, tc.expectError)
|
||||
}
|
||||
}
|
||||
|
||||
// Empty BAL is always valid (even with 0 gas limit).
|
||||
if err := (&BlockAccessList{}).ValidateSize(0); err != nil {
|
||||
t.Fatalf("empty BAL must pass any limit: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlockAccessListValidation(t *testing.T) {
|
||||
// Validate the block access list after RLP decoding
|
||||
enc := makeTestBAL(true)
|
||||
if err := enc.Validate(params.Rules{}); err != nil {
|
||||
if err := enc.Validate(math.MaxUint64); err != nil {
|
||||
t.Fatalf("Unexpected validation error: %v", err)
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
|
|
@ -246,14 +370,14 @@ func TestBlockAccessListValidation(t *testing.T) {
|
|||
if err := dec.DecodeRLP(rlp.NewStream(bytes.NewReader(buf.Bytes()), 0)); err != nil {
|
||||
t.Fatalf("Unexpected RLP-decode error: %v", err)
|
||||
}
|
||||
if err := dec.Validate(params.Rules{}); err != nil {
|
||||
if err := dec.Validate(math.MaxUint64); err != nil {
|
||||
t.Fatalf("Unexpected validation error: %v", err)
|
||||
}
|
||||
|
||||
// Validate the derived block access list
|
||||
cBAL := makeTestConstructionBAL()
|
||||
listB := cBAL.toEncodingObj()
|
||||
if err := listB.Validate(params.Rules{}); err != nil {
|
||||
listB := cBAL.ToEncodingObj()
|
||||
if err := listB.Validate(math.MaxUint64); err != nil {
|
||||
t.Fatalf("Unexpected validation error: %v", err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -413,8 +413,9 @@ func (b *Block) BaseFee() *big.Int {
|
|||
return new(big.Int).Set(b.header.BaseFee)
|
||||
}
|
||||
|
||||
func (b *Block) BeaconRoot() *common.Hash { return b.header.ParentBeaconRoot }
|
||||
func (b *Block) RequestsHash() *common.Hash { return b.header.RequestsHash }
|
||||
func (b *Block) BeaconRoot() *common.Hash { return b.header.ParentBeaconRoot }
|
||||
func (b *Block) RequestsHash() *common.Hash { return b.header.RequestsHash }
|
||||
func (b *Block) BlockAccessListHash() *common.Hash { return b.header.BlockAccessListHash }
|
||||
|
||||
func (b *Block) ExcessBlobGas() *uint64 {
|
||||
var excessBlobGas *uint64
|
||||
|
|
|
|||
|
|
@ -43,6 +43,9 @@ var (
|
|||
// EmptyRequestsHash is the known hash of an empty request set, sha256("").
|
||||
EmptyRequestsHash = common.HexToHash("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")
|
||||
|
||||
// EmptyBlockAccessListHash is the known hash of an empty block accessList, keccak256(rlp.encode([])).
|
||||
EmptyBlockAccessListHash = common.HexToHash("0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347")
|
||||
|
||||
// EmptyBinaryHash is the known hash of an empty binary trie.
|
||||
EmptyBinaryHash = common.Hash{}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -131,8 +131,8 @@ func (c *Contract) UseGas(cost GasCosts, logger *tracing.Hooks, reason tracing.G
|
|||
if !ok {
|
||||
return false
|
||||
}
|
||||
if logger != nil && logger.OnGasChange != nil && reason != tracing.GasChangeIgnored {
|
||||
logger.OnGasChange(prior, c.Gas.RegularGas, reason)
|
||||
if logger.HasGasHook() && reason != tracing.GasChangeIgnored {
|
||||
logger.EmitGasChange(prior.AsTracing(), c.Gas.AsTracing(), reason)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
@ -143,8 +143,8 @@ func (c *Contract) RefundGas(refund GasBudget, logger *tracing.Hooks, reason tra
|
|||
if !changed {
|
||||
return
|
||||
}
|
||||
if logger != nil && logger.OnGasChange != nil && reason != tracing.GasChangeIgnored {
|
||||
logger.OnGasChange(prior, c.Gas.RegularGas, reason)
|
||||
if logger.HasGasHook() && reason != tracing.GasChangeIgnored {
|
||||
logger.EmitGasChange(prior.AsTracing(), c.Gas.AsTracing(), reason)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -269,8 +269,8 @@ func RunPrecompiledContract(stateDB StateDB, p PrecompiledContract, address comm
|
|||
gas.Exhaust()
|
||||
return nil, gas, ErrOutOfGas
|
||||
}
|
||||
if logger != nil && logger.OnGasChange != nil {
|
||||
logger.OnGasChange(prior, gas.RegularGas, tracing.GasChangeCallPrecompiledContract)
|
||||
if logger.HasGasHook() {
|
||||
logger.EmitGasChange(prior.AsTracing(), gas.AsTracing(), tracing.GasChangeCallPrecompiledContract)
|
||||
}
|
||||
// Touch the precompile for block-level accessList recording once Amsterdam
|
||||
// fork is activated.
|
||||
|
|
|
|||
|
|
@ -317,8 +317,8 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
|
|||
if err != nil {
|
||||
evm.StateDB.RevertToSnapshot(snapshot)
|
||||
if err != ErrExecutionReverted {
|
||||
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
|
||||
evm.Config.Tracer.OnGasChange(gas.RegularGas, 0, tracing.GasChangeCallFailedExecution)
|
||||
if evm.Config.Tracer.HasGasHook() {
|
||||
evm.Config.Tracer.EmitGasChange(gas.AsTracing(), tracing.Gas{}, tracing.GasChangeCallFailedExecution)
|
||||
}
|
||||
gas.Exhaust()
|
||||
}
|
||||
|
|
@ -371,8 +371,8 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt
|
|||
if err != nil {
|
||||
evm.StateDB.RevertToSnapshot(snapshot)
|
||||
if err != ErrExecutionReverted {
|
||||
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
|
||||
evm.Config.Tracer.OnGasChange(gas.RegularGas, 0, tracing.GasChangeCallFailedExecution)
|
||||
if evm.Config.Tracer.HasGasHook() {
|
||||
evm.Config.Tracer.EmitGasChange(gas.AsTracing(), tracing.Gas{}, tracing.GasChangeCallFailedExecution)
|
||||
}
|
||||
gas.Exhaust()
|
||||
}
|
||||
|
|
@ -415,8 +415,8 @@ func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address,
|
|||
if err != nil {
|
||||
evm.StateDB.RevertToSnapshot(snapshot)
|
||||
if err != ErrExecutionReverted {
|
||||
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
|
||||
evm.Config.Tracer.OnGasChange(gas.RegularGas, 0, tracing.GasChangeCallFailedExecution)
|
||||
if evm.Config.Tracer.HasGasHook() {
|
||||
evm.Config.Tracer.EmitGasChange(gas.AsTracing(), tracing.Gas{}, tracing.GasChangeCallFailedExecution)
|
||||
}
|
||||
gas.Exhaust()
|
||||
}
|
||||
|
|
@ -470,8 +470,8 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []b
|
|||
if err != nil {
|
||||
evm.StateDB.RevertToSnapshot(snapshot)
|
||||
if err != ErrExecutionReverted {
|
||||
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
|
||||
evm.Config.Tracer.OnGasChange(gas.RegularGas, 0, tracing.GasChangeCallFailedExecution)
|
||||
if evm.Config.Tracer.HasGasHook() {
|
||||
evm.Config.Tracer.EmitGasChange(gas.AsTracing(), tracing.Gas{}, tracing.GasChangeCallFailedExecution)
|
||||
}
|
||||
gas.Exhaust()
|
||||
}
|
||||
|
|
@ -509,8 +509,8 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value
|
|||
gas.Exhaust()
|
||||
return nil, common.Address{}, gas, ErrOutOfGas
|
||||
}
|
||||
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
|
||||
evm.Config.Tracer.OnGasChange(prior, gas.RegularGas, tracing.GasChangeWitnessContractCollisionCheck)
|
||||
if evm.Config.Tracer.HasGasHook() {
|
||||
evm.Config.Tracer.EmitGasChange(prior.AsTracing(), gas.AsTracing(), tracing.GasChangeWitnessContractCollisionCheck)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -528,8 +528,8 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value
|
|||
if evm.StateDB.GetNonce(address) != 0 ||
|
||||
(contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) || // non-empty code
|
||||
isEIP7610RejectedAccount(evm.ChainConfig().ChainID, address, evm.chainRules.IsEIP158) {
|
||||
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
|
||||
evm.Config.Tracer.OnGasChange(gas.RegularGas, 0, tracing.GasChangeCallFailedExecution)
|
||||
if evm.Config.Tracer.HasGasHook() {
|
||||
evm.Config.Tracer.EmitGasChange(gas.AsTracing(), tracing.Gas{}, tracing.GasChangeCallFailedExecution)
|
||||
}
|
||||
gas.Exhaust()
|
||||
return nil, common.Address{}, gas, ErrContractAddressCollision
|
||||
|
|
@ -558,8 +558,8 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value
|
|||
return nil, common.Address{}, gas, ErrOutOfGas
|
||||
}
|
||||
prior, _ := gas.Charge(GasCosts{RegularGas: consumed})
|
||||
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
|
||||
evm.Config.Tracer.OnGasChange(prior, gas.RegularGas, tracing.GasChangeWitnessContractInit)
|
||||
if evm.Config.Tracer.HasGasHook() {
|
||||
evm.Config.Tracer.EmitGasChange(prior.AsTracing(), gas.AsTracing(), tracing.GasChangeWitnessContractInit)
|
||||
}
|
||||
}
|
||||
evm.Context.Transfer(evm.StateDB, caller, address, value, &evm.chainRules)
|
||||
|
|
@ -673,15 +673,17 @@ func (evm *EVM) captureBegin(depth int, typ OpCode, from common.Address, to comm
|
|||
if tracer.OnEnter != nil {
|
||||
tracer.OnEnter(depth, byte(typ), from, to, input, startGas, value)
|
||||
}
|
||||
if tracer.OnGasChange != nil {
|
||||
tracer.OnGasChange(0, startGas, tracing.GasChangeCallInitialBalance)
|
||||
if tracer.HasGasHook() {
|
||||
initial := NewGasBudget(startGas)
|
||||
tracer.EmitGasChange(tracing.Gas{}, initial.AsTracing(), tracing.GasChangeCallInitialBalance)
|
||||
}
|
||||
}
|
||||
|
||||
func (evm *EVM) captureEnd(depth int, startGas uint64, leftOverGas uint64, ret []byte, err error) {
|
||||
tracer := evm.Config.Tracer
|
||||
if leftOverGas != 0 && tracer.OnGasChange != nil {
|
||||
tracer.OnGasChange(leftOverGas, 0, tracing.GasChangeCallLeftOverReturned)
|
||||
if leftOverGas != 0 && tracer.HasGasHook() {
|
||||
leftover := NewGasBudget(leftOverGas)
|
||||
tracer.EmitGasChange(leftover.AsTracing(), tracing.Gas{}, tracing.GasChangeCallLeftOverReturned)
|
||||
}
|
||||
var reverted bool
|
||||
if err != nil {
|
||||
|
|
@ -707,3 +709,8 @@ func (evm *EVM) GetVMContext() *tracing.VMContext {
|
|||
StateDB: evm.StateDB,
|
||||
}
|
||||
}
|
||||
|
||||
// GetRules returns the chain rules used throughout the EVM execution.
|
||||
func (evm *EVM) GetRules() params.Rules {
|
||||
return evm.chainRules
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,11 @@
|
|||
|
||||
package vm
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
)
|
||||
|
||||
// GasCosts denotes a vector of gas costs in the
|
||||
// multidimensional metering paradigm. It represents the cost
|
||||
|
|
@ -77,21 +81,26 @@ func (g GasBudget) CanAfford(cost GasCosts) bool {
|
|||
}
|
||||
|
||||
// Charge deducts the given gas cost from the budget. It returns the
|
||||
// pre-charge gas value and false if the budget does not have sufficient
|
||||
// pre-charge budget and false if the budget does not have sufficient
|
||||
// gas to cover the cost.
|
||||
func (g *GasBudget) Charge(cost GasCosts) (uint64, bool) {
|
||||
prior := g.RegularGas
|
||||
if prior < cost.RegularGas {
|
||||
func (g *GasBudget) Charge(cost GasCosts) (GasBudget, bool) {
|
||||
prior := *g
|
||||
if g.RegularGas < cost.RegularGas {
|
||||
return prior, false
|
||||
}
|
||||
g.RegularGas -= cost.RegularGas
|
||||
return prior, true
|
||||
}
|
||||
|
||||
// Refund adds the given gas budget back. It returns the pre-refund gas
|
||||
// value and whether the budget was actually changed.
|
||||
func (g *GasBudget) Refund(other GasBudget) (uint64, bool) {
|
||||
prior := g.RegularGas
|
||||
// Refund adds the given gas budget back. It returns the pre-refund budget
|
||||
// and whether the budget was actually changed.
|
||||
func (g *GasBudget) Refund(other GasBudget) (GasBudget, bool) {
|
||||
prior := *g
|
||||
g.RegularGas += other.RegularGas
|
||||
return prior, g.RegularGas != prior
|
||||
return prior, g.RegularGas != prior.RegularGas
|
||||
}
|
||||
|
||||
// AsTracing converts the GasBudget into the tracing-facing Gas vector.
|
||||
func (g GasBudget) AsTracing() tracing.Gas {
|
||||
return tracing.Gas{Regular: g.RegularGas, State: g.StateGas}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,5 +98,6 @@ type StateDB interface {
|
|||
AccessEvents() *state.AccessEvents
|
||||
|
||||
// Finalise must be invoked at the end of a transaction
|
||||
Finalise(bool) *bal.StateAccessList
|
||||
Finalise(bool) *bal.ConstructionBlockAccessList
|
||||
SetTxContext(thash common.Hash, ti int, blockAccessIndex uint32)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -234,8 +234,12 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte
|
|||
|
||||
// Do tracing before potential memory expansion
|
||||
if debug {
|
||||
if evm.Config.Tracer.OnGasChange != nil {
|
||||
evm.Config.Tracer.OnGasChange(gasCopy, gasCopy-cost, tracing.GasChangeCallOpCode)
|
||||
if evm.Config.Tracer.HasGasHook() {
|
||||
evm.Config.Tracer.EmitGasChange(
|
||||
tracing.Gas{Regular: gasCopy, State: contract.Gas.StateGas},
|
||||
tracing.Gas{Regular: gasCopy - cost, State: contract.Gas.StateGas},
|
||||
tracing.GasChangeCallOpCode,
|
||||
)
|
||||
}
|
||||
if evm.Config.Tracer.OnOpcode != nil {
|
||||
evm.Config.Tracer.OnOpcode(pc, byte(op), gasCopy, cost, callContext, evm.returnData, evm.depth, VMErrorFromErr(err))
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ func Sign(hash []byte, prv *ecdsa.PrivateKey) ([]byte, error) {
|
|||
// The public key should be in compressed (33 bytes) or uncompressed (65 bytes) format.
|
||||
// The signature should have the 64 byte [R || S] format.
|
||||
func VerifySignature(pubkey, hash, signature []byte) bool {
|
||||
if len(signature) != 64 {
|
||||
if len(signature) != 64 || len(hash) != DigestLength {
|
||||
return false
|
||||
}
|
||||
var r, s secp256k1.ModNScalar
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/consensus/misc/eip1559"
|
||||
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/filtermaps"
|
||||
|
|
@ -61,9 +62,9 @@ func (b *EthAPIBackend) CurrentBlock() *types.Header {
|
|||
return b.eth.blockchain.CurrentBlock()
|
||||
}
|
||||
|
||||
func (b *EthAPIBackend) SetHead(number uint64) {
|
||||
func (b *EthAPIBackend) SetHead(number uint64) error {
|
||||
b.eth.handler.downloader.Cancel()
|
||||
b.eth.blockchain.SetHead(number)
|
||||
return b.eth.blockchain.SetHead(number)
|
||||
}
|
||||
|
||||
func (b *EthAPIBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) {
|
||||
|
|
@ -430,6 +431,13 @@ func (b *EthAPIBackend) FeeHistory(ctx context.Context, blockCount uint64, lastB
|
|||
return b.gpo.FeeHistory(ctx, blockCount, lastBlock, rewardPercentiles)
|
||||
}
|
||||
|
||||
func (b *EthAPIBackend) BaseFee(ctx context.Context) *big.Int {
|
||||
if b.ChainConfig().IsLondon(b.CurrentHeader().Number) {
|
||||
return eip1559.CalcBaseFee(b.ChainConfig(), b.CurrentHeader())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *EthAPIBackend) BlobBaseFee(ctx context.Context) *big.Int {
|
||||
if excess := b.CurrentHeader().ExcessBlobGas; excess != nil {
|
||||
return eip4844.CalcBlobFee(b.ChainConfig(), b.CurrentHeader())
|
||||
|
|
|
|||
|
|
@ -49,7 +49,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/eth/protocols/snap"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/internal/ethapi"
|
||||
"github.com/ethereum/go-ethereum/internal/shutdowncheck"
|
||||
"github.com/ethereum/go-ethereum/internal/version"
|
||||
|
|
@ -105,7 +104,6 @@ type Ethereum struct {
|
|||
// DB interfaces
|
||||
chainDb ethdb.Database // Block chain database
|
||||
|
||||
eventMux *event.TypeMux
|
||||
engine consensus.Engine
|
||||
accountManager *accounts.Manager
|
||||
|
||||
|
|
@ -194,7 +192,6 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
|||
eth := &Ethereum{
|
||||
config: config,
|
||||
chainDb: chainDb,
|
||||
eventMux: stack.EventMux(),
|
||||
accountManager: stack.AccountManager(),
|
||||
engine: engine,
|
||||
networkID: networkID,
|
||||
|
|
@ -237,6 +234,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
|||
StateHistory: config.StateHistory,
|
||||
TrienodeHistory: config.TrienodeHistory,
|
||||
NodeFullValueCheckpoint: config.NodeFullValueCheckpoint,
|
||||
BinTrieGroupDepth: config.BinTrieGroupDepth,
|
||||
StateScheme: scheme,
|
||||
HistoryPolicy: histPolicy,
|
||||
TxLookupLimit: int64(min(config.TransactionHistory, math.MaxInt64)),
|
||||
|
|
@ -343,7 +341,6 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
|||
Network: networkID,
|
||||
Sync: config.SyncMode,
|
||||
BloomCache: uint64(cacheLimit),
|
||||
EventMux: eth.eventMux,
|
||||
RequiredBlocks: config.RequiredBlocks,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -404,7 +401,7 @@ func (s *Ethereum) APIs() []rpc.API {
|
|||
Service: NewMinerAPI(s),
|
||||
}, {
|
||||
Namespace: "eth",
|
||||
Service: downloader.NewDownloaderAPI(s.handler.downloader, s.blockchain, s.eventMux),
|
||||
Service: downloader.NewDownloaderAPI(s.handler.downloader, s.blockchain),
|
||||
}, {
|
||||
Namespace: "admin",
|
||||
Service: NewAdminAPI(s),
|
||||
|
|
@ -599,7 +596,6 @@ func (s *Ethereum) Stop() error {
|
|||
s.shutdownTracker.Stop()
|
||||
|
||||
s.chainDb.Close()
|
||||
s.eventMux.Stop()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,6 +82,9 @@ const (
|
|||
// beaconUpdateWarnFrequency is the frequency at which to warn the user that
|
||||
// the beacon client is offline.
|
||||
beaconUpdateWarnFrequency = 5 * time.Minute
|
||||
|
||||
// maxReorgDepth is the maximum reorg depth accepted via forkchoiceUpdated.
|
||||
maxReorgDepth = 32
|
||||
)
|
||||
|
||||
type ConsensusAPI struct {
|
||||
|
|
@ -237,6 +240,7 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV4(ctx context.Context, update engine.
|
|||
func (api *ConsensusAPI) forkchoiceUpdated(ctx context.Context, update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes, payloadVersion engine.PayloadVersion, payloadWitness bool) (result engine.ForkChoiceResponse, err error) {
|
||||
ctx, _, spanEnd := telemetry.StartSpan(ctx, "engine.forkchoiceUpdated")
|
||||
defer spanEnd(&err)
|
||||
|
||||
api.forkchoiceLock.Lock()
|
||||
defer api.forkchoiceLock.Unlock()
|
||||
|
||||
|
|
@ -321,10 +325,23 @@ func (api *ConsensusAPI) forkchoiceUpdated(ctx context.Context, update engine.Fo
|
|||
// generating the payload. It's a special corner case that a few slots are
|
||||
// missing and we are requested to generate the payload in slot.
|
||||
} else {
|
||||
// If the head block is already in our canonical chain, the beacon client is
|
||||
// probably resyncing. Ignore the update.
|
||||
log.Info("Ignoring beacon update to old head", "number", block.NumberU64(), "hash", update.HeadBlockHash, "age", common.PrettyAge(time.Unix(int64(block.Time()), 0)), "have", api.eth.BlockChain().CurrentBlock().Number)
|
||||
return valid(nil), nil
|
||||
if finalized := api.eth.BlockChain().CurrentFinalBlock(); finalized != nil && block.NumberU64() <= finalized.Number.Uint64() {
|
||||
log.Info("Skipping beacon update to finalized ancestor", "number", block.NumberU64(), "hash", update.HeadBlockHash)
|
||||
return valid(nil), nil
|
||||
}
|
||||
depth := api.eth.BlockChain().CurrentBlock().Number.Uint64() - block.NumberU64()
|
||||
if depth >= maxReorgDepth {
|
||||
log.Warn("Refusing too deep reorg", "depth", depth, "head", update.HeadBlockHash)
|
||||
return engine.STATUS_INVALID, engine.TooDeepReorg.With(fmt.Errorf("reorg depth %d exceeds limit %d", depth, maxReorgDepth))
|
||||
}
|
||||
if !api.eth.Synced() {
|
||||
log.Info("Ignoring beacon update to old head while syncing", "number", block.NumberU64(), "hash", update.HeadBlockHash)
|
||||
return valid(nil), nil
|
||||
}
|
||||
if latestValid, err := api.eth.BlockChain().SetCanonical(block); err != nil {
|
||||
log.Error("Error setting canonical", "number", block.NumberU64(), "hash", update.HeadBlockHash, "error", err)
|
||||
return engine.ForkChoiceResponse{PayloadStatus: engine.PayloadStatusV1{Status: engine.INVALID, LatestValidHash: &latestValid}}, err
|
||||
}
|
||||
}
|
||||
api.eth.SetSynced()
|
||||
|
||||
|
|
@ -629,6 +646,7 @@ func (api *ConsensusAPI) getBlobs(hashes []common.Hash, v2 bool) (engine.BlobAnd
|
|||
return nil, engine.InvalidParams.With(err)
|
||||
}
|
||||
// Validate the blobs from the pool and assemble the response
|
||||
filled := 0
|
||||
res := make(engine.BlobAndProofListV2, len(hashes))
|
||||
for i := range blobs {
|
||||
// The blob has been evicted since the last AvailableBlobs call.
|
||||
|
|
@ -649,10 +667,11 @@ func (api *ConsensusAPI) getBlobs(hashes []common.Hash, v2 bool) (engine.BlobAnd
|
|||
Blob: blobs[i][:],
|
||||
CellProofs: cellProofs,
|
||||
}
|
||||
filled++
|
||||
}
|
||||
if len(res) == len(hashes) {
|
||||
if filled == len(hashes) {
|
||||
getBlobsRequestCompleteHit.Inc(1)
|
||||
} else if len(res) > 0 {
|
||||
} else if filled > 0 {
|
||||
getBlobsRequestPartialHit.Inc(1)
|
||||
} else {
|
||||
getBlobsRequestMiss.Inc(1)
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import (
|
|||
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
|
|
@ -33,20 +32,18 @@ import (
|
|||
type DownloaderAPI struct {
|
||||
d *Downloader
|
||||
chain *core.BlockChain
|
||||
mux *event.TypeMux
|
||||
installSyncSubscription chan chan interface{}
|
||||
uninstallSyncSubscription chan *uninstallSyncSubscriptionRequest
|
||||
}
|
||||
|
||||
// NewDownloaderAPI creates a new DownloaderAPI. The API has an internal event loop that
|
||||
// listens for events from the downloader through the global event mux. In case it receives one of
|
||||
// listens for events from the downloader through the event feed. In case it receives one of
|
||||
// these events it broadcasts it to all syncing subscriptions that are installed through the
|
||||
// installSyncSubscription channel.
|
||||
func NewDownloaderAPI(d *Downloader, chain *core.BlockChain, m *event.TypeMux) *DownloaderAPI {
|
||||
func NewDownloaderAPI(d *Downloader, chain *core.BlockChain) *DownloaderAPI {
|
||||
api := &DownloaderAPI{
|
||||
d: d,
|
||||
chain: chain,
|
||||
mux: m,
|
||||
installSyncSubscription: make(chan chan interface{}),
|
||||
uninstallSyncSubscription: make(chan *uninstallSyncSubscriptionRequest),
|
||||
}
|
||||
|
|
@ -66,7 +63,8 @@ func NewDownloaderAPI(d *Downloader, chain *core.BlockChain, m *event.TypeMux) *
|
|||
// receive is {false}.
|
||||
func (api *DownloaderAPI) eventLoop() {
|
||||
var (
|
||||
sub = api.mux.Subscribe(StartEvent{})
|
||||
events = make(chan SyncEvent, 16)
|
||||
sub = api.d.SubscribeSyncEvents(events)
|
||||
syncSubscriptions = make(map[chan interface{}]struct{})
|
||||
checkInterval = time.Second * 60
|
||||
checkTimer = time.NewTimer(checkInterval)
|
||||
|
|
@ -90,6 +88,7 @@ func (api *DownloaderAPI) eventLoop() {
|
|||
}
|
||||
)
|
||||
defer checkTimer.Stop()
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
for {
|
||||
select {
|
||||
|
|
@ -101,14 +100,13 @@ func (api *DownloaderAPI) eventLoop() {
|
|||
case u := <-api.uninstallSyncSubscription:
|
||||
delete(syncSubscriptions, u.c)
|
||||
close(u.uninstalled)
|
||||
case event := <-sub.Chan():
|
||||
if event == nil {
|
||||
return
|
||||
}
|
||||
switch event.Data.(type) {
|
||||
case StartEvent:
|
||||
case ev := <-events:
|
||||
if ev.Type == SyncStarted {
|
||||
started = true
|
||||
}
|
||||
case <-sub.Err():
|
||||
// The downloader is terminated or other internal error occurs
|
||||
return
|
||||
case <-checkTimer.C:
|
||||
if !started {
|
||||
checkTimer.Reset(checkInterval)
|
||||
|
|
|
|||
|
|
@ -97,9 +97,12 @@ type headerTask struct {
|
|||
}
|
||||
|
||||
type Downloader struct {
|
||||
mode atomic.Uint32 // Synchronisation mode defining the strategy used (per sync cycle), use d.getMode() to get the SyncMode
|
||||
moder *syncModer // Sync mode management, deliver the appropriate sync mode choice for each cycle
|
||||
mux *event.TypeMux // Event multiplexer to announce sync operation events
|
||||
mode atomic.Uint32 // Synchronisation mode defining the strategy used (per sync cycle), use d.getMode() to get the SyncMode
|
||||
moder *syncModer // Sync mode management, deliver the appropriate sync mode choice for each cycle
|
||||
|
||||
// Event feed for downloader events
|
||||
feed event.FeedOf[SyncEvent]
|
||||
scope event.SubscriptionScope
|
||||
|
||||
queue *queue // Scheduler for selecting the hashes to download
|
||||
peers *peerSet // Set of active peers from which download can proceed
|
||||
|
|
@ -229,12 +232,11 @@ type BlockChain interface {
|
|||
}
|
||||
|
||||
// New creates a new downloader to fetch hashes and blocks from remote peers.
|
||||
func New(stateDb ethdb.Database, mode ethconfig.SyncMode, mux *event.TypeMux, chain BlockChain, dropPeer peerDropFn, success func()) *Downloader {
|
||||
func New(stateDb ethdb.Database, mode ethconfig.SyncMode, chain BlockChain, dropPeer peerDropFn, success func()) *Downloader {
|
||||
cutoffNumber, cutoffHash := chain.HistoryPruningCutoff()
|
||||
dl := &Downloader{
|
||||
stateDB: stateDb,
|
||||
moder: newSyncModer(mode, chain, stateDb),
|
||||
mux: mux,
|
||||
queue: newQueue(blockCacheMaxItems, blockCacheInitialItems),
|
||||
peers: newPeerSet(),
|
||||
blockchain: chain,
|
||||
|
|
@ -427,20 +429,25 @@ func (d *Downloader) ConfigSyncMode() SyncMode {
|
|||
return d.moder.get(false)
|
||||
}
|
||||
|
||||
// SubscribeSyncEvents creates a subscription for downloader sync events
|
||||
func (d *Downloader) SubscribeSyncEvents(ch chan<- SyncEvent) event.Subscription {
|
||||
return d.scope.Track(d.feed.Subscribe(ch))
|
||||
}
|
||||
|
||||
// syncToHead starts a block synchronization based on the hash chain from
|
||||
// the specified head hash.
|
||||
func (d *Downloader) syncToHead() (err error) {
|
||||
d.mux.Post(StartEvent{})
|
||||
mode := d.getMode()
|
||||
d.feed.Send(SyncEvent{Type: SyncStarted, Mode: mode})
|
||||
defer func() {
|
||||
// reset on error
|
||||
if err != nil {
|
||||
d.mux.Post(FailedEvent{err})
|
||||
d.feed.Send(SyncEvent{Type: SyncFailed, Mode: mode, Err: err})
|
||||
} else {
|
||||
latest := d.blockchain.CurrentHeader()
|
||||
d.mux.Post(DoneEvent{latest})
|
||||
d.feed.Send(SyncEvent{Type: SyncCompleted, Mode: mode, Latest: latest})
|
||||
}
|
||||
}()
|
||||
mode := d.getMode()
|
||||
|
||||
log.Debug("Backfilling with the network", "mode", mode)
|
||||
defer func(start time.Time) {
|
||||
|
|
@ -662,6 +669,9 @@ func (d *Downloader) Cancel() {
|
|||
// Terminate interrupts the downloader, canceling all pending operations.
|
||||
// The downloader cannot be reused after calling Terminate.
|
||||
func (d *Downloader) Terminate() {
|
||||
// Unsubscribe all subscriptions registered from downloader
|
||||
d.scope.Close()
|
||||
|
||||
// Close the termination channel (make sure double close is allowed)
|
||||
d.quitLock.Lock()
|
||||
select {
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/snap"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
|
|
@ -75,7 +74,7 @@ func newTesterWithNotification(t *testing.T, mode ethconfig.SyncMode, success fu
|
|||
chain: chain,
|
||||
peers: make(map[string]*downloadTesterPeer),
|
||||
}
|
||||
tester.downloader = New(db, mode, new(event.TypeMux), tester.chain, tester.dropPeer, success)
|
||||
tester.downloader = New(db, mode, tester.chain, tester.dropPeer, success)
|
||||
return tester
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,10 +16,24 @@
|
|||
|
||||
package downloader
|
||||
|
||||
import "github.com/ethereum/go-ethereum/core/types"
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||
)
|
||||
|
||||
type DoneEvent struct {
|
||||
Latest *types.Header
|
||||
// SyncEventType represents the type of sync event
|
||||
type SyncEventType int
|
||||
|
||||
const (
|
||||
SyncStarted SyncEventType = iota
|
||||
SyncFailed
|
||||
SyncCompleted
|
||||
)
|
||||
|
||||
// SyncEvent represents a downloader synchronization event
|
||||
type SyncEvent struct {
|
||||
Type SyncEventType
|
||||
Mode ethconfig.SyncMode
|
||||
Err error // Set when Type is SyncFailed
|
||||
Latest *types.Header // Set when Type is SyncCompleted
|
||||
}
|
||||
type StartEvent struct{}
|
||||
type FailedEvent struct{ Err error }
|
||||
|
|
|
|||
|
|
@ -689,9 +689,9 @@ func (q *queue) deliver(id string, taskPool map[common.Hash]*types.Header,
|
|||
i++
|
||||
}
|
||||
|
||||
for _, header := range request.Headers[:i] {
|
||||
for k, header := range request.Headers[:i] {
|
||||
if res, stale, err := q.resultCache.GetDeliverySlot(header.Number.Uint64()); err == nil && !stale {
|
||||
reconstruct(accepted, res)
|
||||
reconstruct(k, res)
|
||||
accepted++
|
||||
} else {
|
||||
// Between here and above, some other peer filled this result,
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/miner"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/triedb"
|
||||
"github.com/ethereum/go-ethereum/triedb/pathdb"
|
||||
)
|
||||
|
||||
|
|
@ -59,6 +60,7 @@ var Defaults = Config{
|
|||
StateHistory: pathdb.Defaults.StateHistory,
|
||||
TrienodeHistory: pathdb.Defaults.TrienodeHistory,
|
||||
NodeFullValueCheckpoint: pathdb.Defaults.FullValueCheckpoint,
|
||||
BinTrieGroupDepth: triedb.DefaultBinTrieGroupDepth,
|
||||
DatabaseCache: 2048,
|
||||
TrieCleanCache: 614,
|
||||
TrieDirtyCache: 1024,
|
||||
|
|
@ -125,6 +127,11 @@ type Config struct {
|
|||
// consistent with persistent state.
|
||||
StateScheme string `toml:",omitempty"`
|
||||
|
||||
// BinTrieGroupDepth is the number of levels per serialized group in binary trie.
|
||||
// Valid values are 1-8, with 8 being the default (byte-aligned groups).
|
||||
// Lower values create smaller groups with more nodes.
|
||||
BinTrieGroupDepth int `toml:",omitempty"`
|
||||
|
||||
// RequiredBlocks is a set of block number -> hash mappings which must be in the
|
||||
// canonical chain of all remote peers. Setting the option makes geth verify the
|
||||
// presence of these blocks for every new peer connection.
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
|||
TrienodeHistory int64 `toml:",omitempty"`
|
||||
NodeFullValueCheckpoint uint32 `toml:",omitempty"`
|
||||
StateScheme string `toml:",omitempty"`
|
||||
BinTrieGroupDepth int `toml:",omitempty"`
|
||||
RequiredBlocks map[uint64]common.Hash `toml:"-"`
|
||||
SlowBlockThreshold time.Duration `toml:",omitempty"`
|
||||
SkipBcVersionCheck bool `toml:"-"`
|
||||
|
|
@ -87,6 +88,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
|||
enc.TrienodeHistory = c.TrienodeHistory
|
||||
enc.NodeFullValueCheckpoint = c.NodeFullValueCheckpoint
|
||||
enc.StateScheme = c.StateScheme
|
||||
enc.BinTrieGroupDepth = c.BinTrieGroupDepth
|
||||
enc.RequiredBlocks = c.RequiredBlocks
|
||||
enc.SlowBlockThreshold = c.SlowBlockThreshold
|
||||
enc.SkipBcVersionCheck = c.SkipBcVersionCheck
|
||||
|
|
@ -144,6 +146,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
|||
TrienodeHistory *int64 `toml:",omitempty"`
|
||||
NodeFullValueCheckpoint *uint32 `toml:",omitempty"`
|
||||
StateScheme *string `toml:",omitempty"`
|
||||
BinTrieGroupDepth *int `toml:",omitempty"`
|
||||
RequiredBlocks map[uint64]common.Hash `toml:"-"`
|
||||
SlowBlockThreshold *time.Duration `toml:",omitempty"`
|
||||
SkipBcVersionCheck *bool `toml:"-"`
|
||||
|
|
@ -234,6 +237,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
|||
if dec.StateScheme != nil {
|
||||
c.StateScheme = *dec.StateScheme
|
||||
}
|
||||
if dec.BinTrieGroupDepth != nil {
|
||||
c.BinTrieGroupDepth = *dec.BinTrieGroupDepth
|
||||
}
|
||||
if dec.RequiredBlocks != nil {
|
||||
c.RequiredBlocks = dec.RequiredBlocks
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,13 +22,13 @@ import (
|
|||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
// Options are the contextual parameters to execute the requested call.
|
||||
|
|
@ -70,17 +70,17 @@ func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uin
|
|||
}
|
||||
|
||||
// Normalize the max fee per gas the call is willing to spend.
|
||||
var feeCap *big.Int
|
||||
var feeCap *uint256.Int
|
||||
if call.GasFeeCap != nil {
|
||||
feeCap = call.GasFeeCap
|
||||
} else if call.GasPrice != nil {
|
||||
feeCap = call.GasPrice
|
||||
} else {
|
||||
feeCap = common.Big0
|
||||
feeCap = uint256.NewInt(0)
|
||||
}
|
||||
// Recap the highest gas limit with account's available balance.
|
||||
if feeCap.BitLen() != 0 {
|
||||
balance := opts.State.GetBalance(call.From).ToBig()
|
||||
balance := opts.State.GetBalance(call.From).Clone()
|
||||
|
||||
available := balance
|
||||
if call.Value != nil {
|
||||
|
|
@ -90,8 +90,8 @@ func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uin
|
|||
available.Sub(available, call.Value)
|
||||
}
|
||||
if opts.Config.IsCancun(opts.Header.Number, opts.Header.Time) && len(call.BlobHashes) > 0 {
|
||||
blobGasPerBlob := new(big.Int).SetInt64(params.BlobTxBlobGasPerBlob)
|
||||
blobBalanceUsage := new(big.Int).SetInt64(int64(len(call.BlobHashes)))
|
||||
blobGasPerBlob := uint256.NewInt(params.BlobTxBlobGasPerBlob)
|
||||
blobBalanceUsage := uint256.NewInt(uint64(len(call.BlobHashes)))
|
||||
blobBalanceUsage.Mul(blobBalanceUsage, blobGasPerBlob)
|
||||
blobBalanceUsage.Mul(blobBalanceUsage, call.BlobGasFeeCap)
|
||||
if blobBalanceUsage.Cmp(available) >= 0 {
|
||||
|
|
@ -99,13 +99,13 @@ func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uin
|
|||
}
|
||||
available.Sub(available, blobBalanceUsage)
|
||||
}
|
||||
allowance := new(big.Int).Div(available, feeCap)
|
||||
allowance := new(uint256.Int).Div(available, feeCap)
|
||||
|
||||
// If the allowance is larger than maximum uint64, skip checking
|
||||
if allowance.IsUint64() && hi > allowance.Uint64() {
|
||||
transfer := call.Value
|
||||
if transfer == nil {
|
||||
transfer = new(big.Int)
|
||||
transfer = new(uint256.Int)
|
||||
}
|
||||
log.Debug("Gas estimation capped by limited funds", "original", hi, "balance", balance,
|
||||
"sent", transfer, "maxFeePerGas", feeCap, "fundable", allowance)
|
||||
|
|
|
|||
|
|
@ -107,7 +107,6 @@ type handlerConfig struct {
|
|||
Network uint64 // Network identifier to advertise
|
||||
Sync ethconfig.SyncMode // Whether to snap or full sync
|
||||
BloomCache uint64 // Megabytes to alloc for snap sync bloom
|
||||
EventMux *event.TypeMux // Legacy event mux, deprecate for `feed`
|
||||
RequiredBlocks map[uint64]common.Hash // Hard coded map of required block hashes for sync challenges
|
||||
}
|
||||
|
||||
|
|
@ -126,7 +125,6 @@ type handler struct {
|
|||
peers *peerSet
|
||||
txBroadcastKey [16]byte
|
||||
|
||||
eventMux *event.TypeMux
|
||||
txsCh chan core.NewTxsEvent
|
||||
txsSub event.Subscription
|
||||
blockRange *blockRangeState
|
||||
|
|
@ -144,14 +142,9 @@ type handler struct {
|
|||
|
||||
// newHandler returns a handler for all Ethereum chain management protocol.
|
||||
func newHandler(config *handlerConfig) (*handler, error) {
|
||||
// Create the protocol manager with the base fields
|
||||
if config.EventMux == nil {
|
||||
config.EventMux = new(event.TypeMux) // Nicety initialization for tests
|
||||
}
|
||||
h := &handler{
|
||||
nodeID: config.NodeID,
|
||||
networkID: config.Network,
|
||||
eventMux: config.EventMux,
|
||||
database: config.Database,
|
||||
txpool: config.TxPool,
|
||||
chain: config.Chain,
|
||||
|
|
@ -163,7 +156,7 @@ func newHandler(config *handlerConfig) (*handler, error) {
|
|||
handlerStartCh: make(chan struct{}),
|
||||
}
|
||||
// Construct the downloader (long sync)
|
||||
h.downloader = downloader.New(config.Database, config.Sync, h.eventMux, h.chain, h.removePeer, h.enableSyncedFeatures)
|
||||
h.downloader = downloader.New(config.Database, config.Sync, h.chain, h.removePeer, h.enableSyncedFeatures)
|
||||
|
||||
// If snap sync is requested but snapshots are disabled, fail loudly
|
||||
if h.downloader.ConfigSyncMode() == ethconfig.SnapSync && (config.Chain.Snapshots() == nil && config.Chain.TrieDB().Scheme() == rawdb.HashScheme) {
|
||||
|
|
@ -420,7 +413,7 @@ func (h *handler) Start(maxPeers int) {
|
|||
|
||||
// broadcast block range
|
||||
h.wg.Add(1)
|
||||
h.blockRange = newBlockRangeState(h.chain, h.eventMux)
|
||||
h.blockRange = newBlockRangeState(h.chain, h.downloader)
|
||||
go h.blockRangeLoop(h.blockRange)
|
||||
|
||||
// start sync handlers
|
||||
|
|
@ -536,16 +529,19 @@ type blockRangeState struct {
|
|||
next atomic.Pointer[eth.BlockRangeUpdatePacket]
|
||||
headCh chan core.ChainHeadEvent
|
||||
headSub event.Subscription
|
||||
syncSub *event.TypeMuxSubscription
|
||||
syncCh chan downloader.SyncEvent
|
||||
syncSub event.Subscription
|
||||
}
|
||||
|
||||
func newBlockRangeState(chain *core.BlockChain, typeMux *event.TypeMux) *blockRangeState {
|
||||
func newBlockRangeState(chain *core.BlockChain, dl *downloader.Downloader) *blockRangeState {
|
||||
headCh := make(chan core.ChainHeadEvent, chainHeadChanSize)
|
||||
headSub := chain.SubscribeChainHeadEvent(headCh)
|
||||
syncSub := typeMux.Subscribe(downloader.StartEvent{}, downloader.DoneEvent{}, downloader.FailedEvent{})
|
||||
syncCh := make(chan downloader.SyncEvent, 16)
|
||||
syncSub := dl.SubscribeSyncEvents(syncCh)
|
||||
st := &blockRangeState{
|
||||
headCh: headCh,
|
||||
headSub: headSub,
|
||||
syncCh: syncCh,
|
||||
syncSub: syncSub,
|
||||
}
|
||||
st.update(chain, chain.CurrentBlock())
|
||||
|
|
@ -561,11 +557,8 @@ func (h *handler) blockRangeLoop(st *blockRangeState) {
|
|||
|
||||
for {
|
||||
select {
|
||||
case ev := <-st.syncSub.Chan():
|
||||
if ev == nil {
|
||||
continue
|
||||
}
|
||||
if _, ok := ev.Data.(downloader.StartEvent); ok && h.downloader.ConfigSyncMode() == ethconfig.SnapSync {
|
||||
case ev := <-st.syncCh:
|
||||
if ev.Type == downloader.SyncStarted && ev.Mode == ethconfig.SnapSync {
|
||||
h.blockRangeWhileSnapSyncing(st)
|
||||
}
|
||||
case <-st.headCh:
|
||||
|
|
@ -593,12 +586,8 @@ func (h *handler) blockRangeWhileSnapSyncing(st *blockRangeState) {
|
|||
h.broadcastBlockRange(st)
|
||||
}
|
||||
// back to processing head block updates when sync is done
|
||||
case ev := <-st.syncSub.Chan():
|
||||
if ev == nil {
|
||||
continue
|
||||
}
|
||||
switch ev.Data.(type) {
|
||||
case downloader.FailedEvent, downloader.DoneEvent:
|
||||
case ev := <-st.syncCh:
|
||||
if ev.Type == downloader.SyncFailed || ev.Type == downloader.SyncCompleted {
|
||||
return
|
||||
}
|
||||
// ignore head updates, but exit when the subscription ends
|
||||
|
|
|
|||
|
|
@ -424,16 +424,20 @@ func testGetBlockBodies(t *testing.T, protocol uint) {
|
|||
{0, []common.Hash{backend.chain.CurrentBlock().Hash()}, []bool{true}, 1}, // The chains head block should be retrievable
|
||||
{0, []common.Hash{{}}, []bool{false}, 0}, // A non existent block should not be returned
|
||||
|
||||
// Existing and non-existing blocks interleaved should not cause problems
|
||||
// Existing blocks followed by a non-existing one should stop at the gap
|
||||
{0, []common.Hash{
|
||||
backend.chain.GetBlockByNumber(1).Hash(),
|
||||
backend.chain.GetBlockByNumber(10).Hash(),
|
||||
backend.chain.GetBlockByNumber(100).Hash(),
|
||||
{},
|
||||
}, []bool{true, true, true, false}, 3},
|
||||
|
||||
// A non-existing block at the start should return nothing
|
||||
{0, []common.Hash{
|
||||
{},
|
||||
backend.chain.GetBlockByNumber(1).Hash(),
|
||||
{},
|
||||
backend.chain.GetBlockByNumber(10).Hash(),
|
||||
{},
|
||||
backend.chain.GetBlockByNumber(100).Hash(),
|
||||
{},
|
||||
}, []bool{false, true, false, true, false, true, false}, 3},
|
||||
}, []bool{false, true, true}, 0},
|
||||
}
|
||||
// Run each of the tests and verify the results against the chain
|
||||
for i, tt := range tests {
|
||||
|
|
|
|||
|
|
@ -238,10 +238,12 @@ func ServiceGetBlockBodiesQuery(chain *core.BlockChain, query GetBlockBodiesRequ
|
|||
lookups >= 2*maxBodiesServe {
|
||||
break
|
||||
}
|
||||
if data := chain.GetBodyRLP(hash); len(data) != 0 {
|
||||
bodies = append(bodies, data)
|
||||
bytes += len(data)
|
||||
data := chain.GetBodyRLP(hash)
|
||||
if len(data) == 0 {
|
||||
break // If we don't have this block's body, stop serving.
|
||||
}
|
||||
bodies = append(bodies, data)
|
||||
bytes += len(data)
|
||||
}
|
||||
return bodies
|
||||
}
|
||||
|
|
@ -281,16 +283,16 @@ func ServiceGetReceiptsQuery69(chain *core.BlockChain, query GetReceiptsRequest)
|
|||
// Retrieve the requested block's receipts
|
||||
results := chain.GetReceiptsRLP(hash)
|
||||
if results == nil {
|
||||
continue // Can't retrieve the receipts, so we just skip this block.
|
||||
break // Don't have this block's receipts, stop serving.
|
||||
}
|
||||
body := chain.GetBodyRLP(hash)
|
||||
if body == nil {
|
||||
continue // The block body is missing, we also have to skip.
|
||||
break // The block body is missing, stop serving.
|
||||
}
|
||||
results, _, err := blockReceiptsToNetwork(results, body, receiptQueryParams{})
|
||||
if err != nil {
|
||||
log.Error("Error in block receipts conversion", "hash", hash, "err", err)
|
||||
continue
|
||||
break
|
||||
}
|
||||
receipts.AppendRaw(results)
|
||||
bytes += len(results)
|
||||
|
|
@ -312,12 +314,13 @@ func serviceGetReceiptsQuery70(chain *core.BlockChain, query GetReceiptsRequest,
|
|||
break
|
||||
}
|
||||
results := chain.GetReceiptsRLP(hash)
|
||||
// If we don't have this block's receipts or body, stop serving.
|
||||
if results == nil {
|
||||
continue // Can't retrieve the receipts, so we just skip this block.
|
||||
break
|
||||
}
|
||||
body := chain.GetBodyRLP(hash)
|
||||
if body == nil {
|
||||
continue // The block body is missing, we also have to skip.
|
||||
break
|
||||
}
|
||||
q := receiptQueryParams{sizeLimit: uint64(maxPacketSize - bytes)}
|
||||
if i == 0 {
|
||||
|
|
@ -326,7 +329,7 @@ func serviceGetReceiptsQuery70(chain *core.BlockChain, query GetReceiptsRequest,
|
|||
results, incomplete, err := blockReceiptsToNetwork(results, body, q)
|
||||
if err != nil {
|
||||
log.Error("Error in block receipts conversion", "hash", hash, "err", err)
|
||||
continue
|
||||
break
|
||||
}
|
||||
if results == nil {
|
||||
// This case triggers when the first receipt of the block receipts list doesn't
|
||||
|
|
|
|||
|
|
@ -248,13 +248,10 @@ func (eth *Ethereum) stateAtTransaction(ctx context.Context, block *types.Block,
|
|||
context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil)
|
||||
evm := vm.NewEVM(context, statedb, eth.blockchain.Config(), vm.Config{})
|
||||
defer evm.Release()
|
||||
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
|
||||
core.ProcessBeaconBlockRoot(*beaconRoot, evm)
|
||||
}
|
||||
// If prague hardfork, insert parent block hash in the state as per EIP-2935.
|
||||
if eth.blockchain.Config().IsPrague(block.Number(), block.Time()) {
|
||||
core.ProcessParentBlockHash(block.ParentHash(), evm)
|
||||
}
|
||||
|
||||
// Run pre-execution system calls
|
||||
core.PreExecution(ctx, block.BeaconRoot(), block.ParentHash(), eth.blockchain.Config(), evm, block.Number(), block.Time())
|
||||
|
||||
if txIndex == 0 && len(block.Transactions()) == 0 {
|
||||
return nil, context, statedb, release, nil
|
||||
}
|
||||
|
|
@ -268,7 +265,7 @@ func (eth *Ethereum) stateAtTransaction(ctx context.Context, block *types.Block,
|
|||
msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
|
||||
|
||||
// Not yet the searched for transaction, execute on top of the current state
|
||||
statedb.SetTxContext(tx.Hash(), idx)
|
||||
statedb.SetTxContext(tx.Hash(), idx, uint32(idx+1))
|
||||
if _, err := core.ApplyMessage(evm, msg, nil); err != nil {
|
||||
return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/eth"
|
||||
"github.com/ethereum/go-ethereum/eth/downloader"
|
||||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
|
|
@ -37,32 +38,40 @@ type syncReq struct {
|
|||
errc chan error
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
TargetBlock common.Hash // if set, sync is triggered at startup
|
||||
ExitWhenSynced bool // if true, the node shuts down after sync has finished
|
||||
}
|
||||
|
||||
// Syncer is an auxiliary service that allows Geth to perform full sync
|
||||
// alone without consensus-layer attached. Users must specify a valid block hash
|
||||
// as the sync target.
|
||||
//
|
||||
// Additionally, the syncer can be used to monitor state synchronization.
|
||||
// It will exit once the specified target has been reached or when the
|
||||
// most recent chain head is caught up.
|
||||
//
|
||||
// This tool can be applied to different networks, no matter it's pre-merge or
|
||||
// post-merge, but only for full-sync.
|
||||
type Syncer struct {
|
||||
stack *node.Node
|
||||
backend *eth.Ethereum
|
||||
target common.Hash
|
||||
request chan *syncReq
|
||||
closed chan struct{}
|
||||
wg sync.WaitGroup
|
||||
exitWhenSynced bool
|
||||
stack *node.Node
|
||||
backend *eth.Ethereum
|
||||
request chan *syncReq
|
||||
closed chan struct{}
|
||||
wg sync.WaitGroup
|
||||
|
||||
config Config
|
||||
}
|
||||
|
||||
// Register registers the synchronization override service into the node
|
||||
// stack for launching and stopping the service controlled by node.
|
||||
func Register(stack *node.Node, backend *eth.Ethereum, target common.Hash, exitWhenSynced bool) (*Syncer, error) {
|
||||
func Register(stack *node.Node, backend *eth.Ethereum, cfg Config) (*Syncer, error) {
|
||||
s := &Syncer{
|
||||
stack: stack,
|
||||
backend: backend,
|
||||
target: target,
|
||||
request: make(chan *syncReq),
|
||||
closed: make(chan struct{}),
|
||||
exitWhenSynced: exitWhenSynced,
|
||||
stack: stack,
|
||||
backend: backend,
|
||||
request: make(chan *syncReq),
|
||||
closed: make(chan struct{}),
|
||||
config: cfg,
|
||||
}
|
||||
stack.RegisterAPIs(s.APIs())
|
||||
stack.RegisterLifecycle(s)
|
||||
|
|
@ -88,9 +97,11 @@ func (s *Syncer) run() {
|
|||
|
||||
var (
|
||||
target *types.Header
|
||||
ticker = time.NewTicker(time.Second * 5)
|
||||
syncCh = make(chan downloader.SyncEvent, 10)
|
||||
)
|
||||
defer ticker.Stop()
|
||||
sub := s.backend.Downloader().SubscribeSyncEvents(syncCh)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
for {
|
||||
select {
|
||||
case req := <-s.request:
|
||||
|
|
@ -137,35 +148,50 @@ func (s *Syncer) run() {
|
|||
}
|
||||
}
|
||||
|
||||
case <-ticker.C:
|
||||
if target == nil {
|
||||
case ev := <-syncCh:
|
||||
if ev.Type == downloader.SyncStarted {
|
||||
log.Debug("Synchronization started")
|
||||
continue
|
||||
}
|
||||
if ev.Type == downloader.SyncFailed {
|
||||
log.Debug("Synchronization failed", "err", ev.Err)
|
||||
continue
|
||||
}
|
||||
|
||||
head := s.backend.BlockChain().CurrentHeader()
|
||||
if head != nil {
|
||||
// Set the finalized and safe markers relative to the current head.
|
||||
// The finalized marker is set two epochs behind the target,
|
||||
// and the safe marker is set one epoch behind the target.
|
||||
if header := s.backend.BlockChain().GetHeaderByNumber(head.Number.Uint64() - params.EpochLength*2); header != nil {
|
||||
if final := s.backend.BlockChain().CurrentFinalBlock(); final == nil || final.Number.Cmp(header.Number) < 0 {
|
||||
s.backend.BlockChain().SetFinalized(header)
|
||||
}
|
||||
}
|
||||
if header := s.backend.BlockChain().GetHeaderByNumber(head.Number.Uint64() - params.EpochLength); header != nil {
|
||||
if safe := s.backend.BlockChain().CurrentSafeBlock(); safe == nil || safe.Number.Cmp(header.Number) < 0 {
|
||||
s.backend.BlockChain().SetSafe(header)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Terminate the node if the target has been reached
|
||||
if s.exitWhenSynced {
|
||||
if block := s.backend.BlockChain().GetBlockByHash(target.Hash()); block != nil {
|
||||
log.Info("Sync target reached", "number", block.NumberU64(), "hash", block.Hash())
|
||||
go s.stack.Close() // async since we need to close ourselves
|
||||
return
|
||||
if s.config.ExitWhenSynced {
|
||||
var synced bool
|
||||
var block *types.Header
|
||||
if target != nil {
|
||||
tb := s.backend.BlockChain().GetBlockByHash(target.Hash())
|
||||
synced = tb != nil
|
||||
block = tb.Header()
|
||||
} else {
|
||||
timestamp := time.Unix(int64(ev.Latest.Time), 0)
|
||||
synced = time.Since(timestamp) < 10*time.Minute
|
||||
block = ev.Latest
|
||||
}
|
||||
}
|
||||
|
||||
// Set the finalized and safe markers relative to the current head.
|
||||
// The finalized marker is set two epochs behind the target,
|
||||
// and the safe marker is set one epoch behind the target.
|
||||
head := s.backend.BlockChain().CurrentHeader()
|
||||
if head == nil {
|
||||
continue
|
||||
}
|
||||
if header := s.backend.BlockChain().GetHeaderByNumber(head.Number.Uint64() - params.EpochLength*2); header != nil {
|
||||
if final := s.backend.BlockChain().CurrentFinalBlock(); final == nil || final.Number.Cmp(header.Number) < 0 {
|
||||
s.backend.BlockChain().SetFinalized(header)
|
||||
}
|
||||
}
|
||||
if header := s.backend.BlockChain().GetHeaderByNumber(head.Number.Uint64() - params.EpochLength); header != nil {
|
||||
if safe := s.backend.BlockChain().CurrentSafeBlock(); safe == nil || safe.Number.Cmp(header.Number) < 0 {
|
||||
s.backend.BlockChain().SetSafe(header)
|
||||
if synced {
|
||||
log.Info("Sync target reached", "number", block.Number.Uint64(), "hash", block.Hash())
|
||||
go s.stack.Close() // async since we need to close ourselves
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -179,10 +205,10 @@ func (s *Syncer) run() {
|
|||
func (s *Syncer) Start() error {
|
||||
s.wg.Add(1)
|
||||
go s.run()
|
||||
if s.target == (common.Hash{}) {
|
||||
if s.config.TargetBlock == (common.Hash{}) {
|
||||
return nil
|
||||
}
|
||||
return s.Sync(s.target)
|
||||
return s.Sync(s.config.TargetBlock)
|
||||
}
|
||||
|
||||
// Stop terminates the synchronization service and stop all background activities.
|
||||
|
|
|
|||
|
|
@ -372,13 +372,8 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed
|
|||
// as per EIP-4788.
|
||||
context := core.NewEVMBlockContext(next.Header(), api.chainContext(ctx), nil)
|
||||
evm := vm.NewEVM(context, statedb, api.backend.ChainConfig(), vm.Config{})
|
||||
if beaconRoot := next.BeaconRoot(); beaconRoot != nil {
|
||||
core.ProcessBeaconBlockRoot(*beaconRoot, evm)
|
||||
}
|
||||
// Insert parent hash in history contract.
|
||||
if api.backend.ChainConfig().IsPrague(next.Number(), next.Time()) {
|
||||
core.ProcessParentBlockHash(next.ParentHash(), evm)
|
||||
}
|
||||
|
||||
core.PreExecution(ctx, next.BeaconRoot(), next.ParentHash(), api.backend.ChainConfig(), evm, next.Number(), next.Time())
|
||||
evm.Release()
|
||||
// Clean out any pending release functions of trace state. Note this
|
||||
// step must be done after constructing tracing state, because the
|
||||
|
|
@ -494,8 +489,8 @@ func (api *API) StandardTraceBlockToFile(ctx context.Context, hash common.Hash,
|
|||
return api.standardTraceBlockToFile(ctx, block, config)
|
||||
}
|
||||
|
||||
// IntermediateRoots executes a block (bad- or canon- or side-), and returns a list
|
||||
// of intermediate roots: the stateroot after each transaction.
|
||||
// IntermediateRoots executes a block, and returns a list of intermediate roots:
|
||||
// the stateroot after each transaction.
|
||||
func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config *TraceConfig) ([]common.Hash, error) {
|
||||
block, _ := api.blockByHash(ctx, hash)
|
||||
if block == nil {
|
||||
|
|
@ -517,27 +512,25 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config
|
|||
return nil, err
|
||||
}
|
||||
defer release()
|
||||
|
||||
var (
|
||||
roots []common.Hash
|
||||
signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time())
|
||||
chainConfig = api.backend.ChainConfig()
|
||||
vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
|
||||
deleteEmptyObjects = chainConfig.IsEIP158(block.Number())
|
||||
evm = vm.NewEVM(vmctx, statedb, chainConfig, vm.Config{})
|
||||
)
|
||||
evm := vm.NewEVM(vmctx, statedb, chainConfig, vm.Config{})
|
||||
defer evm.Release()
|
||||
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
|
||||
core.ProcessBeaconBlockRoot(*beaconRoot, evm)
|
||||
}
|
||||
if chainConfig.IsPrague(block.Number(), block.Time()) {
|
||||
core.ProcessParentBlockHash(block.ParentHash(), evm)
|
||||
}
|
||||
// Run pre-execution system calls
|
||||
core.PreExecution(ctx, block.BeaconRoot(), block.ParentHash(), chainConfig, evm, block.Number(), block.Time())
|
||||
|
||||
for i, tx := range block.Transactions() {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
|
||||
statedb.SetTxContext(tx.Hash(), i)
|
||||
statedb.SetTxContext(tx.Hash(), i, uint32(i+1))
|
||||
if _, err := core.ApplyMessage(evm, msg, nil); err != nil {
|
||||
log.Warn("Tracing intermediate roots did not complete", "txindex", i, "txhash", tx.Hash(), "err", err)
|
||||
// We intentionally don't return the error here: if we do, then the RPC server will not
|
||||
|
|
@ -548,7 +541,7 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config
|
|||
// N.B: This should never happen while tracing canon blocks, only when tracing bad blocks.
|
||||
return roots, nil
|
||||
}
|
||||
// calling IntermediateRoot will internally call Finalize on the state
|
||||
// Calling IntermediateRoot will internally call Finalize on the state
|
||||
// so any modifications are written to the trie
|
||||
roots = append(roots, statedb.IntermediateRoot(deleteEmptyObjects))
|
||||
}
|
||||
|
|
@ -587,12 +580,9 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
|
|||
blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
|
||||
evm := vm.NewEVM(blockCtx, statedb, api.backend.ChainConfig(), vm.Config{})
|
||||
defer evm.Release()
|
||||
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
|
||||
core.ProcessBeaconBlockRoot(*beaconRoot, evm)
|
||||
}
|
||||
if api.backend.ChainConfig().IsPrague(block.Number(), block.Time()) {
|
||||
core.ProcessParentBlockHash(block.ParentHash(), evm)
|
||||
}
|
||||
|
||||
// Run pre-execution system calls
|
||||
core.PreExecution(ctx, block.BeaconRoot(), block.ParentHash(), api.backend.ChainConfig(), evm, block.Number(), block.Time())
|
||||
|
||||
// JS tracers have high overhead. In this case run a parallel
|
||||
// process that generates states in one thread and traces txes
|
||||
|
|
@ -691,7 +681,7 @@ txloop:
|
|||
|
||||
// Generate the next state snapshot fast without tracing
|
||||
msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
|
||||
statedb.SetTxContext(tx.Hash(), i)
|
||||
statedb.SetTxContext(tx.Hash(), i, uint32(i+1))
|
||||
if _, err := core.ApplyMessage(evm, msg, nil); err != nil {
|
||||
failed = err
|
||||
break txloop
|
||||
|
|
@ -760,15 +750,12 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block
|
|||
// Note: This copies the config, to not screw up the main config
|
||||
chainConfig, canon = overrideConfig(chainConfig, config.Overrides)
|
||||
}
|
||||
|
||||
evm := vm.NewEVM(vmctx, statedb, chainConfig, vm.Config{})
|
||||
defer evm.Release()
|
||||
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
|
||||
core.ProcessBeaconBlockRoot(*beaconRoot, evm)
|
||||
}
|
||||
if chainConfig.IsPrague(block.Number(), block.Time()) {
|
||||
core.ProcessParentBlockHash(block.ParentHash(), evm)
|
||||
}
|
||||
|
||||
// Run pre-execution system calls
|
||||
core.PreExecution(ctx, block.BeaconRoot(), block.ParentHash(), chainConfig, evm, block.Number(), block.Time())
|
||||
|
||||
for i, tx := range block.Transactions() {
|
||||
// Prepare the transaction for un-traced execution
|
||||
msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
|
||||
|
|
@ -795,6 +782,7 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block
|
|||
return nil, err
|
||||
}
|
||||
dumps = append(dumps, dump.Name())
|
||||
|
||||
// Set up the tracer and EVM for the transaction.
|
||||
var (
|
||||
writer = bufio.NewWriter(dump)
|
||||
|
|
@ -805,7 +793,7 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block
|
|||
})
|
||||
)
|
||||
// Execute the transaction and flush any traces to disk
|
||||
statedb.SetTxContext(tx.Hash(), i)
|
||||
statedb.SetTxContext(tx.Hash(), i, uint32(i+1))
|
||||
if tracer.OnTxStart != nil {
|
||||
tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
|
||||
}
|
||||
|
|
@ -1028,9 +1016,9 @@ func (api *API) traceTx(ctx context.Context, tx *types.Transaction, message *cor
|
|||
defer cancel()
|
||||
|
||||
// Call Prepare to clear out the statedb access list
|
||||
statedb.SetTxContext(txctx.TxHash, txctx.TxIndex)
|
||||
statedb.SetTxContext(txctx.TxHash, txctx.TxIndex, uint32(txctx.TxIndex+1))
|
||||
|
||||
_, err = core.ApplyTransactionWithEVM(message, core.NewGasPool(message.GasLimit), statedb, vmctx.BlockNumber, txctx.BlockHash, vmctx.Time, tx, evm)
|
||||
_, _, err = core.ApplyTransactionWithEVM(message, core.NewGasPool(message.GasLimit), statedb, vmctx.BlockNumber, txctx.BlockHash, vmctx.Time, tx, evm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("tracing failed: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -620,7 +620,7 @@ func TestSelfdestructStateTracer(t *testing.T) {
|
|||
}
|
||||
context := core.NewEVMBlockContext(block.Header(), blockchain, nil)
|
||||
evm := vm.NewEVM(context, hookedState, tt.genesis.Config, vm.Config{Tracer: tracer.Hooks()})
|
||||
_, err = core.ApplyTransactionWithEVM(msg, core.NewGasPool(msg.GasLimit), statedb, block.Number(), block.Hash(), block.Time(), tx, evm)
|
||||
_, _, err = core.ApplyTransactionWithEVM(msg, core.NewGasPool(msg.GasLimit), statedb, block.Number(), block.Hash(), block.Time(), tx, evm)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to execute transaction: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ func newNoopTracer(_ json.RawMessage) (*tracing.Hooks, error) {
|
|||
OnOpcode: t.OnOpcode,
|
||||
OnFault: t.OnFault,
|
||||
OnGasChange: t.OnGasChange,
|
||||
OnGasChangeV2: t.OnGasChangeV2,
|
||||
OnBlockchainInit: t.OnBlockchainInit,
|
||||
OnBlockStart: t.OnBlockStart,
|
||||
OnBlockEnd: t.OnBlockEnd,
|
||||
|
|
@ -113,3 +114,6 @@ func (t *noop) OnBlockHashRead(number uint64, hash common.Hash) {}
|
|||
|
||||
func (t *noop) OnGasChange(old, new uint64, reason tracing.GasChangeReason) {
|
||||
}
|
||||
|
||||
func (t *noop) OnGasChangeV2(old, new tracing.Gas, reason tracing.GasChangeReason) {
|
||||
}
|
||||
|
|
|
|||
|
|
@ -112,9 +112,10 @@ type AccessListTracer struct {
|
|||
func NewAccessListTracer(acl types.AccessList, addressesToExclude map[common.Address]struct{}) *AccessListTracer {
|
||||
list := newAccessList()
|
||||
for _, al := range acl {
|
||||
if _, ok := addressesToExclude[al.Address]; !ok {
|
||||
list.addAddress(al.Address)
|
||||
if _, ok := addressesToExclude[al.Address]; ok {
|
||||
continue
|
||||
}
|
||||
list.addAddress(al.Address)
|
||||
for _, slot := range al.StorageKeys {
|
||||
list.addSlot(al.Address, slot)
|
||||
}
|
||||
|
|
|
|||
39
eth/tracers/logger/access_list_tracer_test.go
Normal file
39
eth/tracers/logger/access_list_tracer_test.go
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
// Copyright 2026 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 logger
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
)
|
||||
|
||||
func TestNewAccessListTracerExcludedAddress(t *testing.T) {
|
||||
excluded := common.HexToAddress("0x2222222222222222222222222222222222222222")
|
||||
slot := common.HexToHash("0x01")
|
||||
prelude := types.AccessList{{
|
||||
Address: excluded,
|
||||
StorageKeys: []common.Hash{slot},
|
||||
}}
|
||||
excl := map[common.Address]struct{}{excluded: {}}
|
||||
tracer := NewAccessListTracer(prelude, excl)
|
||||
got := tracer.AccessList()
|
||||
if len(got) != 0 {
|
||||
t.Fatalf("excluded prelude address must not contribute tuples, got %+v", got)
|
||||
}
|
||||
}
|
||||
|
|
@ -229,9 +229,9 @@ type StructLogger struct {
|
|||
logs []json.RawMessage // buffer of json-encoded logs
|
||||
resultSize int
|
||||
|
||||
interrupt atomic.Bool // Atomic flag to signal execution interruption
|
||||
reason error // Textual reason for the interruption
|
||||
skip bool // skip processing hooks.
|
||||
interrupt atomic.Bool // Atomic flag to signal execution interruption
|
||||
reason atomic.Pointer[error] // Reason for the interruption, populated by Stop
|
||||
skip bool // skip processing hooks.
|
||||
}
|
||||
|
||||
// NewStreamingStructLogger returns a new streaming logger.
|
||||
|
|
@ -357,8 +357,8 @@ func (l *StructLogger) OnExit(depth int, output []byte, gasUsed uint64, err erro
|
|||
|
||||
func (l *StructLogger) GetResult() (json.RawMessage, error) {
|
||||
// Tracing aborted
|
||||
if l.reason != nil {
|
||||
return nil, l.reason
|
||||
if p := l.reason.Load(); p != nil {
|
||||
return nil, *p
|
||||
}
|
||||
failed := l.err != nil
|
||||
returnData := common.CopyBytes(l.output)
|
||||
|
|
@ -376,7 +376,7 @@ func (l *StructLogger) GetResult() (json.RawMessage, error) {
|
|||
|
||||
// Stop terminates execution of the tracer at the first opportune moment.
|
||||
func (l *StructLogger) Stop(err error) {
|
||||
l.reason = err
|
||||
l.reason.Store(&err)
|
||||
l.interrupt.Store(true)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -49,9 +49,9 @@ func init() {
|
|||
// 0xc281d19e-0: 1
|
||||
// }
|
||||
type fourByteTracer struct {
|
||||
ids map[string]int // ids aggregates the 4byte ids found
|
||||
interrupt atomic.Bool // Atomic flag to signal execution interruption
|
||||
reason error // Textual reason for the interruption
|
||||
ids map[string]int // ids aggregates the 4byte ids found
|
||||
interrupt atomic.Bool // Atomic flag to signal execution interruption
|
||||
reason atomic.Pointer[error] // Reason for the interruption, populated by Stop
|
||||
chainConfig *params.ChainConfig
|
||||
activePrecompiles []common.Address // Updated on tx start based on given rules
|
||||
}
|
||||
|
|
@ -124,12 +124,15 @@ func (t *fourByteTracer) GetResult() (json.RawMessage, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, t.reason
|
||||
if p := t.reason.Load(); p != nil {
|
||||
return res, *p
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Stop terminates execution of the tracer at the first opportune moment.
|
||||
func (t *fourByteTracer) Stop(err error) {
|
||||
t.reason = err
|
||||
t.reason.Store(&err)
|
||||
t.interrupt.Store(true)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -116,8 +116,8 @@ type callTracer struct {
|
|||
config callTracerConfig
|
||||
gasLimit uint64
|
||||
depth int
|
||||
interrupt atomic.Bool // Atomic flag to signal execution interruption
|
||||
reason error // Textual reason for the interruption
|
||||
interrupt atomic.Bool // Atomic flag to signal execution interruption
|
||||
reason atomic.Pointer[error] // Reason for the interruption, populated by Stop
|
||||
}
|
||||
|
||||
type callTracerConfig struct {
|
||||
|
|
@ -268,12 +268,15 @@ func (t *callTracer) GetResult() (json.RawMessage, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, t.reason
|
||||
if p := t.reason.Load(); p != nil {
|
||||
return res, *p
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Stop terminates execution of the tracer at the first opportune moment.
|
||||
func (t *callTracer) Stop(err error) {
|
||||
t.reason = err
|
||||
t.reason.Store(&err)
|
||||
t.interrupt.Store(true)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -233,7 +233,10 @@ func (t *flatCallTracer) GetResult() (json.RawMessage, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, t.tracer.reason
|
||||
if p := t.tracer.reason.Load(); p != nil {
|
||||
return res, *p
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Stop terminates execution of the tracer at the first opportune moment.
|
||||
|
|
|
|||
|
|
@ -135,8 +135,8 @@ type opcodeWithPartialStack struct {
|
|||
type erc7562Tracer struct {
|
||||
config erc7562TracerConfig
|
||||
gasLimit uint64
|
||||
interrupt atomic.Bool // Atomic flag to signal execution interruption
|
||||
reason error // Textual reason for the interruption
|
||||
interrupt atomic.Bool // Atomic flag to signal execution interruption
|
||||
reason atomic.Pointer[error] // Reason for the interruption, populated by Stop
|
||||
env *tracing.VMContext
|
||||
|
||||
ignoredOpcodes map[vm.OpCode]struct{}
|
||||
|
|
@ -317,7 +317,10 @@ func (t *erc7562Tracer) OnLog(log1 *types.Log) {
|
|||
// error arising from the encoding or forceful termination (via `Stop`).
|
||||
func (t *erc7562Tracer) GetResult() (json.RawMessage, error) {
|
||||
if t.interrupt.Load() {
|
||||
return nil, t.reason
|
||||
if p := t.reason.Load(); p != nil {
|
||||
return nil, *p
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
if len(t.callstackWithOpcodes) != 1 {
|
||||
return nil, errors.New("incorrect number of top-level calls")
|
||||
|
|
@ -337,12 +340,15 @@ func (t *erc7562Tracer) GetResult() (json.RawMessage, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return enc, t.reason
|
||||
if p := t.reason.Load(); p != nil {
|
||||
return enc, *p
|
||||
}
|
||||
return enc, nil
|
||||
}
|
||||
|
||||
// Stop terminates execution of the tracer at the first opportune moment.
|
||||
func (t *erc7562Tracer) Stop(err error) {
|
||||
t.reason = err
|
||||
t.reason.Store(&err)
|
||||
t.interrupt.Store(true)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -63,22 +63,31 @@ func newMuxTracerFromConfig(ctx *tracers.Context, cfg json.RawMessage, chainConf
|
|||
//
|
||||
// The names parameter associates a label with each tracer, used as keys in
|
||||
// the aggregated JSON result returned by GetResult.
|
||||
//
|
||||
// For hooks that have both a V1 and V2 form (OnCodeChange / OnCodeChangeV2,
|
||||
// OnNonceChange / OnNonceChangeV2, OnGasChange / OnGasChangeV2,
|
||||
// OnSystemCallStart / OnSystemCallStartV2), the mux exposes only the V2
|
||||
// variant upward. The fanout then prefers each child's V2 hook and falls
|
||||
// back to V1 if only V1 is set, mirroring the precedence already used in
|
||||
// core/state_processor.go.
|
||||
func NewMuxTracer(names []string, objects []*tracers.Tracer) (*tracers.Tracer, error) {
|
||||
t := &muxTracer{names: names, tracers: objects}
|
||||
return &tracers.Tracer{
|
||||
Hooks: &tracing.Hooks{
|
||||
OnTxStart: t.OnTxStart,
|
||||
OnTxEnd: t.OnTxEnd,
|
||||
OnEnter: t.OnEnter,
|
||||
OnExit: t.OnExit,
|
||||
OnOpcode: t.OnOpcode,
|
||||
OnFault: t.OnFault,
|
||||
OnGasChange: t.OnGasChange,
|
||||
OnBalanceChange: t.OnBalanceChange,
|
||||
OnNonceChange: t.OnNonceChange,
|
||||
OnCodeChange: t.OnCodeChange,
|
||||
OnStorageChange: t.OnStorageChange,
|
||||
OnLog: t.OnLog,
|
||||
OnTxStart: t.OnTxStart,
|
||||
OnTxEnd: t.OnTxEnd,
|
||||
OnEnter: t.OnEnter,
|
||||
OnExit: t.OnExit,
|
||||
OnOpcode: t.OnOpcode,
|
||||
OnFault: t.OnFault,
|
||||
OnGasChangeV2: t.OnGasChangeV2,
|
||||
OnBalanceChange: t.OnBalanceChange,
|
||||
OnNonceChangeV2: t.OnNonceChangeV2,
|
||||
OnCodeChangeV2: t.OnCodeChangeV2,
|
||||
OnStorageChange: t.OnStorageChange,
|
||||
OnLog: t.OnLog,
|
||||
OnSystemCallStartV2: t.OnSystemCallStart,
|
||||
OnSystemCallEnd: t.OnSystemCallEnd,
|
||||
},
|
||||
GetResult: t.GetResult,
|
||||
Stop: t.Stop,
|
||||
|
|
@ -101,10 +110,12 @@ func (t *muxTracer) OnFault(pc uint64, op byte, gas, cost uint64, scope tracing.
|
|||
}
|
||||
}
|
||||
|
||||
func (t *muxTracer) OnGasChange(old, new uint64, reason tracing.GasChangeReason) {
|
||||
func (t *muxTracer) OnGasChangeV2(old, new tracing.Gas, reason tracing.GasChangeReason) {
|
||||
for _, t := range t.tracers {
|
||||
if t.OnGasChange != nil {
|
||||
t.OnGasChange(old, new, reason)
|
||||
if t.OnGasChangeV2 != nil {
|
||||
t.OnGasChangeV2(old, new, reason)
|
||||
} else if t.OnGasChange != nil {
|
||||
t.OnGasChange(old.Regular, new.Regular, reason)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -149,26 +160,22 @@ func (t *muxTracer) OnBalanceChange(a common.Address, prev, new *big.Int, reason
|
|||
}
|
||||
}
|
||||
|
||||
func (t *muxTracer) OnNonceChange(a common.Address, prev, new uint64) {
|
||||
func (t *muxTracer) OnNonceChangeV2(a common.Address, prev, new uint64, reason tracing.NonceChangeReason) {
|
||||
for _, t := range t.tracers {
|
||||
if t.OnNonceChange != nil {
|
||||
if t.OnNonceChangeV2 != nil {
|
||||
t.OnNonceChangeV2(a, prev, new, reason)
|
||||
} else if t.OnNonceChange != nil {
|
||||
t.OnNonceChange(a, prev, new)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *muxTracer) OnCodeChange(a common.Address, prevCodeHash common.Hash, prev []byte, codeHash common.Hash, code []byte) {
|
||||
for _, t := range t.tracers {
|
||||
if t.OnCodeChange != nil {
|
||||
t.OnCodeChange(a, prevCodeHash, prev, codeHash, code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *muxTracer) OnCodeChangeV2(a common.Address, prevCodeHash common.Hash, prev []byte, codeHash common.Hash, code []byte, reason tracing.CodeChangeReason) {
|
||||
for _, t := range t.tracers {
|
||||
if t.OnCodeChangeV2 != nil {
|
||||
t.OnCodeChangeV2(a, prevCodeHash, prev, codeHash, code, reason)
|
||||
} else if t.OnCodeChange != nil {
|
||||
t.OnCodeChange(a, prevCodeHash, prev, codeHash, code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -189,6 +196,24 @@ func (t *muxTracer) OnLog(log *types.Log) {
|
|||
}
|
||||
}
|
||||
|
||||
func (t *muxTracer) OnSystemCallStart(vm *tracing.VMContext) {
|
||||
for _, t := range t.tracers {
|
||||
if t.OnSystemCallStartV2 != nil {
|
||||
t.OnSystemCallStartV2(vm)
|
||||
} else if t.OnSystemCallStart != nil {
|
||||
t.OnSystemCallStart()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *muxTracer) OnSystemCallEnd() {
|
||||
for _, t := range t.tracers {
|
||||
if t.OnSystemCallEnd != nil {
|
||||
t.OnSystemCallEnd()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetResult returns an empty json object.
|
||||
func (t *muxTracer) GetResult() (json.RawMessage, error) {
|
||||
resObject := make(map[string]json.RawMessage)
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue