mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-05-24 08:49:29 +00:00
Merge branch 'master' of https://github.com/0xjvn/go-ethereum into fix/rewind
This commit is contained in:
commit
f09eefedbf
265 changed files with 9780 additions and 5139 deletions
|
|
@ -145,7 +145,7 @@ jobs:
|
|||
|
||||
windows:
|
||||
name: Windows Build
|
||||
runs-on: "win-11"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
|
|
@ -155,24 +155,49 @@ 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
|
||||
go run build/ci.go install -dlgo -os windows -arch amd64 -cc x86_64-w64-mingw32-gcc
|
||||
|
||||
- name: "Create/upload archive (amd64)"
|
||||
run: |
|
||||
go run build/ci.go archive -os windows -arch amd64 -type zip -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
|
||||
env:
|
||||
GETH_MINGW: 'C:\msys64\mingw64'
|
||||
WINDOWS_SIGNING_KEY: ${{ secrets.WINDOWS_SIGNING_KEY }}
|
||||
AZURE_BLOBSTORE_TOKEN: ${{ secrets.AZURE_BLOBSTORE_TOKEN }}
|
||||
|
||||
- name: "Create/upload NSIS installer (amd64)"
|
||||
run: |
|
||||
go run build/ci.go nsis -arch amd64 -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
|
||||
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
|
||||
go run build/ci.go install -dlgo -os windows -arch 386 -cc i686-w64-mingw32-gcc
|
||||
|
||||
- name: "Create/upload archive (386)"
|
||||
run: |
|
||||
go run build/ci.go archive -os windows -arch 386 -type zip -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
|
||||
env:
|
||||
GETH_MINGW: 'C:\msys64\mingw32'
|
||||
WINDOWS_SIGNING_KEY: ${{ secrets.WINDOWS_SIGNING_KEY }}
|
||||
AZURE_BLOBSTORE_TOKEN: ${{ secrets.AZURE_BLOBSTORE_TOKEN }}
|
||||
|
||||
- name: "Create/upload NSIS installer (386)"
|
||||
run: |
|
||||
go run build/ci.go nsis -arch 386 -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
|
||||
rm -f build/bin/*
|
||||
env:
|
||||
WINDOWS_SIGNING_KEY: ${{ secrets.WINDOWS_SIGNING_KEY }}
|
||||
AZURE_BLOBSTORE_TOKEN: ${{ secrets.AZURE_BLOBSTORE_TOKEN }}
|
||||
|
||||
docker:
|
||||
name: Docker Image
|
||||
|
|
|
|||
29
.github/workflows/freebsd.yml
vendored
Normal file
29
.github/workflows/freebsd.yml
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
on:
|
||||
push:
|
||||
branches:
|
||||
- freebsd-github-action
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: FreeBSD-build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: false
|
||||
|
||||
- name: Test in FreeBSD
|
||||
id: test
|
||||
uses: vmactions/freebsd-vm@v1
|
||||
with:
|
||||
release: "15.0"
|
||||
usesh: true
|
||||
prepare: |
|
||||
pkg install -y go
|
||||
run: |
|
||||
freebsd-version
|
||||
uname -a
|
||||
go version
|
||||
go run ./build/ci.go test -p 8
|
||||
41
.github/workflows/go.yml
vendored
41
.github/workflows/go.yml
vendored
|
|
@ -97,3 +97,44 @@ jobs:
|
|||
|
||||
- name: Run tests
|
||||
run: go run build/ci.go test -p 8
|
||||
|
||||
windows:
|
||||
name: Windows ${{ matrix.arch }}
|
||||
needs: lint
|
||||
runs-on: [self-hosted, windows, x64]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- arch: amd64
|
||||
mingw: 'C:\msys64\mingw64'
|
||||
test: true
|
||||
- arch: '386'
|
||||
mingw: 'C:\msys64\mingw32'
|
||||
test: false
|
||||
env:
|
||||
GETH_MINGW: ${{ matrix.mingw }}
|
||||
GETH_CC: ${{ matrix.mingw }}\bin\gcc.exe
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.25'
|
||||
cache: false
|
||||
|
||||
- name: Build
|
||||
shell: cmd
|
||||
run: |
|
||||
set PATH=%GETH_MINGW%\bin;%PATH%
|
||||
go run build/ci.go install -arch ${{ matrix.arch }} -cc %GETH_CC%
|
||||
|
||||
- name: Run tests
|
||||
if: matrix.test
|
||||
shell: cmd
|
||||
run: |
|
||||
set PATH=%GETH_MINGW%\bin;%PATH%
|
||||
go run build/ci.go test -arch ${{ matrix.arch }} -cc %GETH_CC% -short -p 8
|
||||
|
|
|
|||
|
|
@ -183,8 +183,11 @@ var (
|
|||
// Solidity: {{.Original.String}}
|
||||
func ({{ decapitalise $contract.Type}} *{{$contract.Type}}) Unpack{{.Normalized.Name}}Event(log *types.Log) (*{{$contract.Type}}{{.Normalized.Name}}, error) {
|
||||
event := "{{.Original.Name}}"
|
||||
if len(log.Topics) == 0 || log.Topics[0] != {{ decapitalise $contract.Type}}.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
if len(log.Topics) == 0 {
|
||||
return nil, bind.ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != {{ decapitalise $contract.Type}}.abi.Events[event].ID {
|
||||
return nil, bind.ErrEventSignatureMismatch
|
||||
}
|
||||
out := new({{$contract.Type}}{{.Normalized.Name}})
|
||||
if len(log.Data) > 0 {
|
||||
|
|
|
|||
|
|
@ -360,8 +360,11 @@ func (CrowdsaleFundTransfer) ContractEventName() string {
|
|||
// Solidity: event FundTransfer(address backer, uint256 amount, bool isContribution)
|
||||
func (crowdsale *Crowdsale) UnpackFundTransferEvent(log *types.Log) (*CrowdsaleFundTransfer, error) {
|
||||
event := "FundTransfer"
|
||||
if len(log.Topics) == 0 || log.Topics[0] != crowdsale.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
if len(log.Topics) == 0 {
|
||||
return nil, bind.ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != crowdsale.abi.Events[event].ID {
|
||||
return nil, bind.ErrEventSignatureMismatch
|
||||
}
|
||||
out := new(CrowdsaleFundTransfer)
|
||||
if len(log.Data) > 0 {
|
||||
|
|
|
|||
35
accounts/abi/abigen/testdata/v2/dao.go.txt
vendored
35
accounts/abi/abigen/testdata/v2/dao.go.txt
vendored
|
|
@ -606,8 +606,11 @@ func (DAOChangeOfRules) ContractEventName() string {
|
|||
// Solidity: event ChangeOfRules(uint256 minimumQuorum, uint256 debatingPeriodInMinutes, int256 majorityMargin)
|
||||
func (dAO *DAO) UnpackChangeOfRulesEvent(log *types.Log) (*DAOChangeOfRules, error) {
|
||||
event := "ChangeOfRules"
|
||||
if len(log.Topics) == 0 || log.Topics[0] != dAO.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
if len(log.Topics) == 0 {
|
||||
return nil, bind.ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != dAO.abi.Events[event].ID {
|
||||
return nil, bind.ErrEventSignatureMismatch
|
||||
}
|
||||
out := new(DAOChangeOfRules)
|
||||
if len(log.Data) > 0 {
|
||||
|
|
@ -648,8 +651,11 @@ func (DAOMembershipChanged) ContractEventName() string {
|
|||
// Solidity: event MembershipChanged(address member, bool isMember)
|
||||
func (dAO *DAO) UnpackMembershipChangedEvent(log *types.Log) (*DAOMembershipChanged, error) {
|
||||
event := "MembershipChanged"
|
||||
if len(log.Topics) == 0 || log.Topics[0] != dAO.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
if len(log.Topics) == 0 {
|
||||
return nil, bind.ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != dAO.abi.Events[event].ID {
|
||||
return nil, bind.ErrEventSignatureMismatch
|
||||
}
|
||||
out := new(DAOMembershipChanged)
|
||||
if len(log.Data) > 0 {
|
||||
|
|
@ -692,8 +698,11 @@ func (DAOProposalAdded) ContractEventName() string {
|
|||
// Solidity: event ProposalAdded(uint256 proposalID, address recipient, uint256 amount, string description)
|
||||
func (dAO *DAO) UnpackProposalAddedEvent(log *types.Log) (*DAOProposalAdded, error) {
|
||||
event := "ProposalAdded"
|
||||
if len(log.Topics) == 0 || log.Topics[0] != dAO.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
if len(log.Topics) == 0 {
|
||||
return nil, bind.ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != dAO.abi.Events[event].ID {
|
||||
return nil, bind.ErrEventSignatureMismatch
|
||||
}
|
||||
out := new(DAOProposalAdded)
|
||||
if len(log.Data) > 0 {
|
||||
|
|
@ -736,8 +745,11 @@ func (DAOProposalTallied) ContractEventName() string {
|
|||
// Solidity: event ProposalTallied(uint256 proposalID, int256 result, uint256 quorum, bool active)
|
||||
func (dAO *DAO) UnpackProposalTalliedEvent(log *types.Log) (*DAOProposalTallied, error) {
|
||||
event := "ProposalTallied"
|
||||
if len(log.Topics) == 0 || log.Topics[0] != dAO.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
if len(log.Topics) == 0 {
|
||||
return nil, bind.ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != dAO.abi.Events[event].ID {
|
||||
return nil, bind.ErrEventSignatureMismatch
|
||||
}
|
||||
out := new(DAOProposalTallied)
|
||||
if len(log.Data) > 0 {
|
||||
|
|
@ -780,8 +792,11 @@ func (DAOVoted) ContractEventName() string {
|
|||
// Solidity: event Voted(uint256 proposalID, bool position, address voter, string justification)
|
||||
func (dAO *DAO) UnpackVotedEvent(log *types.Log) (*DAOVoted, error) {
|
||||
event := "Voted"
|
||||
if len(log.Topics) == 0 || log.Topics[0] != dAO.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
if len(log.Topics) == 0 {
|
||||
return nil, bind.ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != dAO.abi.Events[event].ID {
|
||||
return nil, bind.ErrEventSignatureMismatch
|
||||
}
|
||||
out := new(DAOVoted)
|
||||
if len(log.Data) > 0 {
|
||||
|
|
|
|||
|
|
@ -72,8 +72,11 @@ func (EventCheckerDynamic) ContractEventName() string {
|
|||
// Solidity: event dynamic(string indexed idxStr, bytes indexed idxDat, string str, bytes dat)
|
||||
func (eventChecker *EventChecker) UnpackDynamicEvent(log *types.Log) (*EventCheckerDynamic, error) {
|
||||
event := "dynamic"
|
||||
if len(log.Topics) == 0 || log.Topics[0] != eventChecker.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
if len(log.Topics) == 0 {
|
||||
return nil, bind.ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != eventChecker.abi.Events[event].ID {
|
||||
return nil, bind.ErrEventSignatureMismatch
|
||||
}
|
||||
out := new(EventCheckerDynamic)
|
||||
if len(log.Data) > 0 {
|
||||
|
|
@ -112,8 +115,11 @@ func (EventCheckerEmpty) ContractEventName() string {
|
|||
// Solidity: event empty()
|
||||
func (eventChecker *EventChecker) UnpackEmptyEvent(log *types.Log) (*EventCheckerEmpty, error) {
|
||||
event := "empty"
|
||||
if len(log.Topics) == 0 || log.Topics[0] != eventChecker.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
if len(log.Topics) == 0 {
|
||||
return nil, bind.ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != eventChecker.abi.Events[event].ID {
|
||||
return nil, bind.ErrEventSignatureMismatch
|
||||
}
|
||||
out := new(EventCheckerEmpty)
|
||||
if len(log.Data) > 0 {
|
||||
|
|
@ -154,8 +160,11 @@ func (EventCheckerIndexed) ContractEventName() string {
|
|||
// Solidity: event indexed(address indexed addr, int256 indexed num)
|
||||
func (eventChecker *EventChecker) UnpackIndexedEvent(log *types.Log) (*EventCheckerIndexed, error) {
|
||||
event := "indexed"
|
||||
if len(log.Topics) == 0 || log.Topics[0] != eventChecker.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
if len(log.Topics) == 0 {
|
||||
return nil, bind.ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != eventChecker.abi.Events[event].ID {
|
||||
return nil, bind.ErrEventSignatureMismatch
|
||||
}
|
||||
out := new(EventCheckerIndexed)
|
||||
if len(log.Data) > 0 {
|
||||
|
|
@ -196,8 +205,11 @@ func (EventCheckerMixed) ContractEventName() string {
|
|||
// Solidity: event mixed(address indexed addr, int256 num)
|
||||
func (eventChecker *EventChecker) UnpackMixedEvent(log *types.Log) (*EventCheckerMixed, error) {
|
||||
event := "mixed"
|
||||
if len(log.Topics) == 0 || log.Topics[0] != eventChecker.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
if len(log.Topics) == 0 {
|
||||
return nil, bind.ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != eventChecker.abi.Events[event].ID {
|
||||
return nil, bind.ErrEventSignatureMismatch
|
||||
}
|
||||
out := new(EventCheckerMixed)
|
||||
if len(log.Data) > 0 {
|
||||
|
|
@ -238,8 +250,11 @@ func (EventCheckerUnnamed) ContractEventName() string {
|
|||
// Solidity: event unnamed(uint256 indexed arg0, uint256 indexed arg1)
|
||||
func (eventChecker *EventChecker) UnpackUnnamedEvent(log *types.Log) (*EventCheckerUnnamed, error) {
|
||||
event := "unnamed"
|
||||
if len(log.Topics) == 0 || log.Topics[0] != eventChecker.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
if len(log.Topics) == 0 {
|
||||
return nil, bind.ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != eventChecker.abi.Events[event].ID {
|
||||
return nil, bind.ErrEventSignatureMismatch
|
||||
}
|
||||
out := new(EventCheckerUnnamed)
|
||||
if len(log.Data) > 0 {
|
||||
|
|
|
|||
|
|
@ -134,8 +134,11 @@ func (NameConflictLog) ContractEventName() string {
|
|||
// Solidity: event log(int256 msg, int256 _msg)
|
||||
func (nameConflict *NameConflict) UnpackLogEvent(log *types.Log) (*NameConflictLog, error) {
|
||||
event := "log"
|
||||
if len(log.Topics) == 0 || log.Topics[0] != nameConflict.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
if len(log.Topics) == 0 {
|
||||
return nil, bind.ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != nameConflict.abi.Events[event].ID {
|
||||
return nil, bind.ErrEventSignatureMismatch
|
||||
}
|
||||
out := new(NameConflictLog)
|
||||
if len(log.Data) > 0 {
|
||||
|
|
|
|||
|
|
@ -136,8 +136,11 @@ func (NumericMethodNameE1TestEvent) ContractEventName() string {
|
|||
// Solidity: event _1TestEvent(address _param)
|
||||
func (numericMethodName *NumericMethodName) UnpackE1TestEventEvent(log *types.Log) (*NumericMethodNameE1TestEvent, error) {
|
||||
event := "_1TestEvent"
|
||||
if len(log.Topics) == 0 || log.Topics[0] != numericMethodName.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
if len(log.Topics) == 0 {
|
||||
return nil, bind.ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != numericMethodName.abi.Events[event].ID {
|
||||
return nil, bind.ErrEventSignatureMismatch
|
||||
}
|
||||
out := new(NumericMethodNameE1TestEvent)
|
||||
if len(log.Data) > 0 {
|
||||
|
|
|
|||
14
accounts/abi/abigen/testdata/v2/overload.go.txt
vendored
14
accounts/abi/abigen/testdata/v2/overload.go.txt
vendored
|
|
@ -114,8 +114,11 @@ func (OverloadBar) ContractEventName() string {
|
|||
// Solidity: event bar(uint256 i)
|
||||
func (overload *Overload) UnpackBarEvent(log *types.Log) (*OverloadBar, error) {
|
||||
event := "bar"
|
||||
if len(log.Topics) == 0 || log.Topics[0] != overload.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
if len(log.Topics) == 0 {
|
||||
return nil, bind.ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != overload.abi.Events[event].ID {
|
||||
return nil, bind.ErrEventSignatureMismatch
|
||||
}
|
||||
out := new(OverloadBar)
|
||||
if len(log.Data) > 0 {
|
||||
|
|
@ -156,8 +159,11 @@ func (OverloadBar0) ContractEventName() string {
|
|||
// Solidity: event bar(uint256 i, uint256 j)
|
||||
func (overload *Overload) UnpackBar0Event(log *types.Log) (*OverloadBar0, error) {
|
||||
event := "bar0"
|
||||
if len(log.Topics) == 0 || log.Topics[0] != overload.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
if len(log.Topics) == 0 {
|
||||
return nil, bind.ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != overload.abi.Events[event].ID {
|
||||
return nil, bind.ErrEventSignatureMismatch
|
||||
}
|
||||
out := new(OverloadBar0)
|
||||
if len(log.Data) > 0 {
|
||||
|
|
|
|||
7
accounts/abi/abigen/testdata/v2/token.go.txt
vendored
7
accounts/abi/abigen/testdata/v2/token.go.txt
vendored
|
|
@ -386,8 +386,11 @@ func (TokenTransfer) ContractEventName() string {
|
|||
// Solidity: event Transfer(address indexed from, address indexed to, uint256 value)
|
||||
func (token *Token) UnpackTransferEvent(log *types.Log) (*TokenTransfer, error) {
|
||||
event := "Transfer"
|
||||
if len(log.Topics) == 0 || log.Topics[0] != token.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
if len(log.Topics) == 0 {
|
||||
return nil, bind.ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != token.abi.Events[event].ID {
|
||||
return nil, bind.ErrEventSignatureMismatch
|
||||
}
|
||||
out := new(TokenTransfer)
|
||||
if len(log.Data) > 0 {
|
||||
|
|
|
|||
14
accounts/abi/abigen/testdata/v2/tuple.go.txt
vendored
14
accounts/abi/abigen/testdata/v2/tuple.go.txt
vendored
|
|
@ -193,8 +193,11 @@ func (TupleTupleEvent) ContractEventName() string {
|
|||
// Solidity: event TupleEvent((uint256,uint256[],(uint256,uint256)[]) a, (uint256,uint256)[2][] b, (uint256,uint256)[][2] c, (uint256,uint256[],(uint256,uint256)[])[] d, uint256[] e)
|
||||
func (tuple *Tuple) UnpackTupleEventEvent(log *types.Log) (*TupleTupleEvent, error) {
|
||||
event := "TupleEvent"
|
||||
if len(log.Topics) == 0 || log.Topics[0] != tuple.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
if len(log.Topics) == 0 {
|
||||
return nil, bind.ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != tuple.abi.Events[event].ID {
|
||||
return nil, bind.ErrEventSignatureMismatch
|
||||
}
|
||||
out := new(TupleTupleEvent)
|
||||
if len(log.Data) > 0 {
|
||||
|
|
@ -234,8 +237,11 @@ func (TupleTupleEvent2) ContractEventName() string {
|
|||
// Solidity: event TupleEvent2((uint8,uint8)[] arg0)
|
||||
func (tuple *Tuple) UnpackTupleEvent2Event(log *types.Log) (*TupleTupleEvent2, error) {
|
||||
event := "TupleEvent2"
|
||||
if len(log.Topics) == 0 || log.Topics[0] != tuple.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
if len(log.Topics) == 0 {
|
||||
return nil, bind.ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != tuple.abi.Events[event].ID {
|
||||
return nil, bind.ErrEventSignatureMismatch
|
||||
}
|
||||
out := new(TupleTupleEvent2)
|
||||
if len(log.Data) > 0 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -35,8 +35,8 @@ import (
|
|||
const basefeeWiggleMultiplier = 2
|
||||
|
||||
var (
|
||||
errNoEventSignature = errors.New("no event signature")
|
||||
errEventSignatureMismatch = errors.New("event signature mismatch")
|
||||
ErrNoEventSignature = errors.New("no event signature")
|
||||
ErrEventSignatureMismatch = errors.New("event signature mismatch")
|
||||
)
|
||||
|
||||
// SignerFn is a signer function callback when a contract requires a method to
|
||||
|
|
@ -536,10 +536,10 @@ func (c *BoundContract) WatchLogs(opts *WatchOpts, name string, query ...[]any)
|
|||
func (c *BoundContract) UnpackLog(out any, event string, log types.Log) error {
|
||||
// Anonymous events are not supported.
|
||||
if len(log.Topics) == 0 {
|
||||
return errNoEventSignature
|
||||
return ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != c.abi.Events[event].ID {
|
||||
return errEventSignatureMismatch
|
||||
return ErrEventSignatureMismatch
|
||||
}
|
||||
if len(log.Data) > 0 {
|
||||
if err := c.abi.UnpackIntoInterface(out, event, log.Data); err != nil {
|
||||
|
|
@ -559,10 +559,10 @@ func (c *BoundContract) UnpackLog(out any, event string, log types.Log) error {
|
|||
func (c *BoundContract) UnpackLogIntoMap(out map[string]any, event string, log types.Log) error {
|
||||
// Anonymous events are not supported.
|
||||
if len(log.Topics) == 0 {
|
||||
return errNoEventSignature
|
||||
return ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != c.abi.Events[event].ID {
|
||||
return errEventSignatureMismatch
|
||||
return ErrEventSignatureMismatch
|
||||
}
|
||||
if len(log.Data) > 0 {
|
||||
if err := c.abi.UnpackIntoMap(out, event, log.Data); err != nil {
|
||||
|
|
|
|||
|
|
@ -276,8 +276,11 @@ func (DBInsert) ContractEventName() string {
|
|||
// Solidity: event Insert(uint256 key, uint256 value, uint256 length)
|
||||
func (dB *DB) UnpackInsertEvent(log *types.Log) (*DBInsert, error) {
|
||||
event := "Insert"
|
||||
if len(log.Topics) == 0 || log.Topics[0] != dB.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
if len(log.Topics) == 0 {
|
||||
return nil, bind.ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != dB.abi.Events[event].ID {
|
||||
return nil, bind.ErrEventSignatureMismatch
|
||||
}
|
||||
out := new(DBInsert)
|
||||
if len(log.Data) > 0 {
|
||||
|
|
@ -318,8 +321,11 @@ func (DBKeyedInsert) ContractEventName() string {
|
|||
// Solidity: event KeyedInsert(uint256 indexed key, uint256 value)
|
||||
func (dB *DB) UnpackKeyedInsertEvent(log *types.Log) (*DBKeyedInsert, error) {
|
||||
event := "KeyedInsert"
|
||||
if len(log.Topics) == 0 || log.Topics[0] != dB.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
if len(log.Topics) == 0 {
|
||||
return nil, bind.ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != dB.abi.Events[event].ID {
|
||||
return nil, bind.ErrEventSignatureMismatch
|
||||
}
|
||||
out := new(DBKeyedInsert)
|
||||
if len(log.Data) > 0 {
|
||||
|
|
|
|||
|
|
@ -115,8 +115,11 @@ func (CBasic1) ContractEventName() string {
|
|||
// Solidity: event basic1(uint256 indexed id, uint256 data)
|
||||
func (c *C) UnpackBasic1Event(log *types.Log) (*CBasic1, error) {
|
||||
event := "basic1"
|
||||
if len(log.Topics) == 0 || log.Topics[0] != c.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
if len(log.Topics) == 0 {
|
||||
return nil, bind.ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != c.abi.Events[event].ID {
|
||||
return nil, bind.ErrEventSignatureMismatch
|
||||
}
|
||||
out := new(CBasic1)
|
||||
if len(log.Data) > 0 {
|
||||
|
|
@ -157,8 +160,11 @@ func (CBasic2) ContractEventName() string {
|
|||
// Solidity: event basic2(bool indexed flag, uint256 data)
|
||||
func (c *C) UnpackBasic2Event(log *types.Log) (*CBasic2, error) {
|
||||
event := "basic2"
|
||||
if len(log.Topics) == 0 || log.Topics[0] != c.abi.Events[event].ID {
|
||||
return nil, errors.New("event signature mismatch")
|
||||
if len(log.Topics) == 0 {
|
||||
return nil, bind.ErrNoEventSignature
|
||||
}
|
||||
if log.Topics[0] != c.abi.Events[event].ID {
|
||||
return nil, bind.ErrEventSignatureMismatch
|
||||
}
|
||||
out := new(CBasic2)
|
||||
if len(log.Data) > 0 {
|
||||
|
|
|
|||
|
|
@ -379,16 +379,16 @@ func TestEventUnpackEmptyTopics(t *testing.T) {
|
|||
if err == nil {
|
||||
t.Fatal("expected error when unpacking event with empty topics, got nil")
|
||||
}
|
||||
if err.Error() != "event signature mismatch" {
|
||||
t.Fatalf("expected 'event signature mismatch' error, got: %v", err)
|
||||
if err != bind.ErrNoEventSignature {
|
||||
t.Fatalf("expected 'no event signature' error, got: %v", err)
|
||||
}
|
||||
|
||||
_, err = c.UnpackBasic2Event(log)
|
||||
if err == nil {
|
||||
t.Fatal("expected error when unpacking event with empty topics, got nil")
|
||||
}
|
||||
if err.Error() != "event signature mismatch" {
|
||||
t.Fatalf("expected 'event signature mismatch' error, got: %v", err)
|
||||
if err != bind.ErrNoEventSignature {
|
||||
t.Fatalf("expected 'no event signature' error, got: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,18 +68,27 @@ func waitWatcherStart(ks *KeyStore) bool {
|
|||
|
||||
func waitForAccounts(wantAccounts []accounts.Account, ks *KeyStore) error {
|
||||
var list []accounts.Account
|
||||
haveAccounts := false
|
||||
haveChange := false
|
||||
for t0 := time.Now(); time.Since(t0) < 5*time.Second; time.Sleep(100 * time.Millisecond) {
|
||||
list = ks.Accounts()
|
||||
if reflect.DeepEqual(list, wantAccounts) {
|
||||
// ks should have also received change notifications
|
||||
if !haveAccounts {
|
||||
list = ks.Accounts()
|
||||
haveAccounts = reflect.DeepEqual(list, wantAccounts)
|
||||
}
|
||||
if !haveChange {
|
||||
select {
|
||||
case <-ks.changes:
|
||||
haveChange = true
|
||||
default:
|
||||
return errors.New("wasn't notified of new accounts")
|
||||
}
|
||||
}
|
||||
if haveAccounts && haveChange {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if haveAccounts {
|
||||
return errors.New("wasn't notified of new accounts")
|
||||
}
|
||||
return fmt.Errorf("\ngot %v\nwant %v", list, wantAccounts)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@
|
|||
// 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/>.
|
||||
|
||||
//go:build (darwin && !ios && cgo) || freebsd || (linux && !arm64) || netbsd || solaris
|
||||
// +build darwin,!ios,cgo freebsd linux,!arm64 netbsd solaris
|
||||
//go:build (darwin && !ios && cgo) || freebsd || linux || netbsd || solaris
|
||||
// +build darwin,!ios,cgo freebsd linux netbsd solaris
|
||||
|
||||
package keystore
|
||||
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@
|
|||
// 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/>.
|
||||
|
||||
//go:build (darwin && !cgo) || ios || (linux && arm64) || windows || (!darwin && !freebsd && !linux && !netbsd && !solaris)
|
||||
// +build darwin,!cgo ios linux,arm64 windows !darwin,!freebsd,!linux,!netbsd,!solaris
|
||||
//go:build (darwin && !cgo) || ios || windows || (!darwin && !freebsd && !linux && !netbsd && !solaris)
|
||||
// +build darwin,!cgo ios windows !darwin,!freebsd,!linux,!netbsd,!solaris
|
||||
|
||||
// This is the fallback implementation of directory watching.
|
||||
// It is used on unsupported platforms.
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ func (hub *Hub) readPairings() error {
|
|||
}
|
||||
|
||||
func (hub *Hub) writePairings() error {
|
||||
pairingFile, err := os.OpenFile(filepath.Join(hub.datadir, "smartcards.json"), os.O_RDWR|os.O_CREATE, 0755)
|
||||
pairingFile, err := os.OpenFile(filepath.Join(hub.datadir, "smartcards.json"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -129,11 +129,8 @@ func (hub *Hub) writePairings() error {
|
|||
return err
|
||||
}
|
||||
|
||||
if _, err := pairingFile.Write(pairingData); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
_, err = pairingFile.Write(pairingData)
|
||||
return err
|
||||
}
|
||||
|
||||
func (hub *Hub) pairing(wallet *Wallet) *smartcardPairing {
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/karalabe/hid"
|
||||
"github.com/ethereum/hid"
|
||||
)
|
||||
|
||||
// LedgerScheme is the protocol scheme prefixing account and wallet URLs.
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/karalabe/hid"
|
||||
"github.com/ethereum/hid"
|
||||
)
|
||||
|
||||
// Maximum time between wallet health checks to detect USB unplugs.
|
||||
|
|
|
|||
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}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) {
|
|||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
|
||||
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
|
||||
SlotNumber *hexutil.Uint64 `json:"slotNumber"`
|
||||
SlotNumber *hexutil.Uint64 `json:"slotNumber,omitempty"`
|
||||
}
|
||||
var enc ExecutableData
|
||||
enc.ParentHash = e.ParentHash
|
||||
|
|
@ -83,7 +83,7 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error {
|
|||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
|
||||
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
|
||||
SlotNumber *hexutil.Uint64 `json:"slotNumber"`
|
||||
SlotNumber *hexutil.Uint64 `json:"slotNumber,omitempty"`
|
||||
}
|
||||
var dec ExecutableData
|
||||
if err := json.Unmarshal(input, &dec); err != nil {
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ type ExecutableData struct {
|
|||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||
BlobGasUsed *uint64 `json:"blobGasUsed"`
|
||||
ExcessBlobGas *uint64 `json:"excessBlobGas"`
|
||||
SlotNumber *uint64 `json:"slotNumber"`
|
||||
SlotNumber *uint64 `json:"slotNumber,omitempty"`
|
||||
}
|
||||
|
||||
// JSON type overrides for executableData.
|
||||
|
|
@ -276,7 +276,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()...)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.7
|
||||
# version:golang 1.25.10
|
||||
# https://go.dev/dl/
|
||||
178f2832820274b43e177d32f06a3ebb0129e427dd20a5e4c88df2c1763cf10a go1.25.7.src.tar.gz
|
||||
81bf2a1f20633f62d55d826d82dde3b0570cf1408a91e15781b266037299285b go1.25.7.aix-ppc64.tar.gz
|
||||
bf5050a2152f4053837b886e8d9640c829dbacbc3370f913351eb0904cb706f5 go1.25.7.darwin-amd64.tar.gz
|
||||
ff18369ffad05c57d5bed888b660b31385f3c913670a83ef557cdfd98ea9ae1b go1.25.7.darwin-arm64.tar.gz
|
||||
c5dccd7f192dd7b305dc209fb316ac1917776d74bd8e4d532ef2772f305bf42a go1.25.7.dragonfly-amd64.tar.gz
|
||||
a2de97c8ac74bf64b0ae73fe9d379e61af530e061bc7f8f825044172ffe61a8b go1.25.7.freebsd-386.tar.gz
|
||||
055f9e138787dcafa81eb0314c8ff70c6dd0f6dba1e8a6957fef5d5efd1ab8fd go1.25.7.freebsd-amd64.tar.gz
|
||||
60e7f7a7c990f0b9539ac8ed668155746997d404643a4eecd47b3dee1b7e710b go1.25.7.freebsd-arm.tar.gz
|
||||
631e03d5fd4c526e2f499154d8c6bf4cb081afb2fff171c428722afc9539d53a go1.25.7.freebsd-arm64.tar.gz
|
||||
8a264fd685823808140672812e3ad9c43f6ad59444c0dc14cdd3a1351839ddd5 go1.25.7.freebsd-riscv64.tar.gz
|
||||
57c672447d906a1bcab98f2b11492d54521a791aacbb4994a25169e59cbe289a go1.25.7.illumos-amd64.tar.gz
|
||||
2866517e9ca81e6a2e85a930e9b11bc8a05cfeb2fc6dc6cb2765e7fb3c14b715 go1.25.7.linux-386.tar.gz
|
||||
12e6d6a191091ae27dc31f6efc630e3a3b8ba409baf3573d955b196fdf086005 go1.25.7.linux-amd64.tar.gz
|
||||
ba611a53534135a81067240eff9508cd7e256c560edd5d8c2fef54f083c07129 go1.25.7.linux-arm64.tar.gz
|
||||
1ba07e0eb86b839e72467f4b5c7a5597d07f30bcf5563c951410454f7cda5266 go1.25.7.linux-armv6l.tar.gz
|
||||
775753fc5952a334c415f08768df2f0b73a3228a16e8f5f63d545daacb4e3357 go1.25.7.linux-loong64.tar.gz
|
||||
1a023bb367c5fbb4c637a2f6dc23ff17c6591ad929ce16ea88c74d857153b307 go1.25.7.linux-mips.tar.gz
|
||||
a8e97223d8aa6fdfd45f132a4784d2f536bbac5f3d63a24b63d33b6bfe1549af go1.25.7.linux-mips64.tar.gz
|
||||
eb9edb6223330d5e20275667c65dea076b064c08e595fe4eba5d7d6055cfaccf go1.25.7.linux-mips64le.tar.gz
|
||||
9c1e693552a5f9bb9e0012d1c5e01456ecefbc59bef53a77305222ce10aba368 go1.25.7.linux-mipsle.tar.gz
|
||||
28a788798e7329acbbc0ac2caa5e4368b1e5ede646cc24429c991214cfb45c63 go1.25.7.linux-ppc64.tar.gz
|
||||
42124c0edc92464e2b37b2d7fcd3658f0c47ebd6a098732415a522be8cb88e3f go1.25.7.linux-ppc64le.tar.gz
|
||||
88d59c6893c8425875d6eef8e3434bc2fa2552e5ad4c058c6cd8cd710a0301c8 go1.25.7.linux-riscv64.tar.gz
|
||||
c6b77facf666dc68195ecab05dbf0ebb4e755b2a8b7734c759880557f1c29b0c go1.25.7.linux-s390x.tar.gz
|
||||
f14c184d9ade0ee04c7735d4071257b90896ecbde1b32adae84135f055e6399b go1.25.7.netbsd-386.tar.gz
|
||||
7e7389e404dca1088c31f0fc07f1dd60891d7182bcd621469c14f7e79eceb3ff go1.25.7.netbsd-amd64.tar.gz
|
||||
70388bb3ef2f03dbf1357e9056bd09034a67e018262557354f8cf549766b3f9d go1.25.7.netbsd-arm.tar.gz
|
||||
8c1cda9d25bfc9b18d24d5f95fc23949dd3ff99fa408a6cfa40e2cf12b07e362 go1.25.7.netbsd-arm64.tar.gz
|
||||
42f0d1bfbe39b8401cccb84dd66b30795b97bfc9620dfdc17c5cd4fcf6495cb0 go1.25.7.openbsd-386.tar.gz
|
||||
e514879c0a28bc32123cd52c4c093de912477fe83f36a6d07517d066ef55391a go1.25.7.openbsd-amd64.tar.gz
|
||||
8cd22530695a0218232bf7efea8f162df1697a3106942ac4129b8c3de39ce4ef go1.25.7.openbsd-arm.tar.gz
|
||||
938720f6ebc0d1c53d7840321d3a31f29fd02496e84a6538f442a9311dc1cc9a go1.25.7.openbsd-arm64.tar.gz
|
||||
a4c378b73b98f89a3596c2ef51aabbb28783d9ca29f7e317d8ca07939660ce6f go1.25.7.openbsd-ppc64.tar.gz
|
||||
937b58734fbeaa8c7941a0e4285e7e84b7885396e8d11c23f9ab1a8ff10ff20e go1.25.7.openbsd-riscv64.tar.gz
|
||||
61a093c8c5244916f25740316386bb9f141545dcf01b06a79d1c78ece488403e go1.25.7.plan9-386.tar.gz
|
||||
7fc8f6689c9de8ccb7689d2278035fa83c2d601409101840df6ddfe09ba58699 go1.25.7.plan9-amd64.tar.gz
|
||||
9661dff8eaeeb62f1c3aadbc5ff189a2e6744e1ec885e32dbcb438f58a34def5 go1.25.7.plan9-arm.tar.gz
|
||||
28ecba0e1d7950c8b29a4a04962dd49c3bf5221f55a44f17d98f369f82859cf4 go1.25.7.solaris-amd64.tar.gz
|
||||
baa6b488291801642fa620026169e38bec2da2ac187cd3ae2145721cf826bbc3 go1.25.7.windows-386.zip
|
||||
c75e5f4ff62d085cc0017be3ad19d5536f46825fa05db06ec468941f847e3228 go1.25.7.windows-amd64.zip
|
||||
807033f85931bc4a589ca8497535dcbeb1f30d506e47fa200f5f04c4a71c3d9f go1.25.7.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
|
||||
|
|
|
|||
|
|
@ -257,34 +257,50 @@ that they are returned by FINDNODE.`)
|
|||
|
||||
// Create bystanders.
|
||||
nodes := make([]*bystander, 5)
|
||||
added := make(chan enode.ID, len(nodes))
|
||||
liveCh := make(chan enode.ID, len(nodes))
|
||||
for i := range nodes {
|
||||
nodes[i] = newBystander(t, s, added)
|
||||
nodes[i] = newBystander(t, s, liveCh)
|
||||
defer nodes[i].close()
|
||||
}
|
||||
|
||||
// Get them added to the remote table.
|
||||
// Prefill each bystander with the full bystander set so background FINDNODE
|
||||
// lookups see useful routing data instead of empty responses.
|
||||
known := make([]*enode.Node, 0, len(nodes))
|
||||
for _, bn := range nodes {
|
||||
known = append(known, bn.conn.localNode.Node())
|
||||
}
|
||||
for _, bn := range nodes {
|
||||
bn.known = append([]*enode.Node(nil), known...)
|
||||
}
|
||||
|
||||
// Wait until enough bystanders have actually become live, i.e. the remote node
|
||||
// has revalidated them by sending PING and receiving our PONG.
|
||||
requiredLiveNodes := len(nodes)
|
||||
timeout := 60 * time.Second
|
||||
timeoutCh := time.After(timeout)
|
||||
for count := 0; count < len(nodes); {
|
||||
liveSet := make(map[enode.ID]*enode.Node)
|
||||
for len(liveSet) < requiredLiveNodes {
|
||||
select {
|
||||
case id := <-added:
|
||||
t.Logf("bystander node %v added to remote table", id)
|
||||
count++
|
||||
case id := <-liveCh:
|
||||
for _, bn := range nodes {
|
||||
if bn.id() == id {
|
||||
liveSet[id] = bn.conn.localNode.Node()
|
||||
break
|
||||
}
|
||||
}
|
||||
t.Logf("bystander node %v became live", id)
|
||||
case <-timeoutCh:
|
||||
t.Errorf("remote added %d bystander nodes in %v, need %d to continue", count, timeout, len(nodes))
|
||||
t.Logf("this can happen if the node has a non-empty table from previous runs")
|
||||
t.Errorf("remote revalidated %d bystander nodes in %v, need %d to continue", len(liveSet), timeout, requiredLiveNodes)
|
||||
return
|
||||
}
|
||||
}
|
||||
t.Logf("all %d bystander nodes were added", len(nodes))
|
||||
t.Logf("continuing after all %d bystander nodes became live", len(liveSet))
|
||||
|
||||
// Collect our nodes by distance.
|
||||
// Collect live nodes by distance.
|
||||
var dists []uint
|
||||
expect := make(map[enode.ID]*enode.Node)
|
||||
for _, bn := range nodes {
|
||||
n := bn.conn.localNode.Node()
|
||||
expect[n.ID()] = n
|
||||
for id, n := range liveSet {
|
||||
expect[id] = n
|
||||
d := uint(enode.LogDist(n.ID(), s.Dest.ID()))
|
||||
if !slices.Contains(dists, d) {
|
||||
dists = append(dists, d)
|
||||
|
|
@ -295,42 +311,63 @@ that they are returned by FINDNODE.`)
|
|||
t.Log("requesting nodes")
|
||||
conn, l1 := s.listen1(t)
|
||||
defer conn.close()
|
||||
foundNodes, err := conn.findnode(l1, dists)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Logf("remote returned %d nodes for distance list %v", len(foundNodes), dists)
|
||||
for _, n := range foundNodes {
|
||||
delete(expect, n.ID())
|
||||
}
|
||||
if len(expect) > 0 {
|
||||
t.Errorf("missing %d nodes in FINDNODE result", len(expect))
|
||||
t.Logf("this can happen if the test is run multiple times in quick succession")
|
||||
t.Logf("and the remote node hasn't removed dead nodes from previous runs yet")
|
||||
} else {
|
||||
t.Logf("all %d expected nodes were returned", len(nodes))
|
||||
|
||||
const maxAttempts = 5
|
||||
const retryInterval = 2 * time.Second
|
||||
|
||||
for attempt := 1; attempt <= maxAttempts; attempt++ {
|
||||
foundNodes, err := conn.findnode(l1, dists)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
missing := make(map[enode.ID]struct{})
|
||||
for id := range expect {
|
||||
missing[id] = struct{}{}
|
||||
}
|
||||
for _, n := range foundNodes {
|
||||
delete(missing, n.ID())
|
||||
}
|
||||
t.Logf("attempt %d: remote returned %d nodes for distance list %v, missing %d", attempt, len(foundNodes), dists, len(missing))
|
||||
if len(missing) == 0 {
|
||||
t.Logf("all %d expected live nodes were returned", len(expect))
|
||||
return
|
||||
}
|
||||
if attempt < maxAttempts {
|
||||
time.Sleep(retryInterval)
|
||||
}
|
||||
}
|
||||
t.Errorf("missing nodes in FINDNODE result after %d attempts", maxAttempts)
|
||||
t.Logf("this can happen if the node has a non-empty table from previous runs")
|
||||
}
|
||||
|
||||
// A bystander is a node whose only purpose is filling a spot in the remote table.
|
||||
type bystander struct {
|
||||
dest *enode.Node
|
||||
conn *conn
|
||||
l net.PacketConn
|
||||
dest *enode.Node
|
||||
conn *conn
|
||||
l net.PacketConn
|
||||
known []*enode.Node
|
||||
|
||||
addedCh chan enode.ID
|
||||
done sync.WaitGroup
|
||||
liveCh chan enode.ID
|
||||
sent map[v5wire.Nonce]v5wire.Packet
|
||||
done sync.WaitGroup
|
||||
}
|
||||
|
||||
func newBystander(t *utesting.T, s *Suite, added chan enode.ID) *bystander {
|
||||
func newBystander(t *utesting.T, s *Suite, live chan enode.ID) *bystander {
|
||||
conn, l := s.listen1(t)
|
||||
conn.setEndpoint(l) // bystander nodes need IP/port to get pinged
|
||||
bn := &bystander{
|
||||
conn: conn,
|
||||
l: l,
|
||||
dest: s.Dest,
|
||||
addedCh: added,
|
||||
conn: conn,
|
||||
l: l,
|
||||
dest: s.Dest,
|
||||
liveCh: live,
|
||||
sent: make(map[v5wire.Nonce]v5wire.Packet),
|
||||
}
|
||||
// Establish an initial session and let the remote learn this node before
|
||||
// switching to the passive responder loop below.
|
||||
conn.reqresp(l, &v5wire.Ping{
|
||||
ReqID: conn.nextReqID(),
|
||||
ENRSeq: conn.localNode.Seq(),
|
||||
})
|
||||
bn.done.Add(1)
|
||||
go bn.loop()
|
||||
return bn
|
||||
|
|
@ -351,48 +388,57 @@ func (bn *bystander) close() {
|
|||
func (bn *bystander) loop() {
|
||||
defer bn.done.Done()
|
||||
|
||||
var (
|
||||
lastPing time.Time
|
||||
wasAdded bool
|
||||
)
|
||||
for {
|
||||
// Ping the remote node.
|
||||
if !wasAdded && time.Since(lastPing) > 10*time.Second {
|
||||
bn.conn.reqresp(bn.l, &v5wire.Ping{
|
||||
ReqID: bn.conn.nextReqID(),
|
||||
ENRSeq: bn.dest.Seq(),
|
||||
})
|
||||
lastPing = time.Now()
|
||||
}
|
||||
// Answer packets.
|
||||
switch p := bn.conn.read(bn.l).(type) {
|
||||
case *v5wire.Ping:
|
||||
bn.conn.write(bn.l, &v5wire.Pong{
|
||||
ReqID: p.ReqID,
|
||||
ENRSeq: bn.conn.localNode.Seq(),
|
||||
ToIP: bn.dest.IP(),
|
||||
ToPort: uint16(bn.dest.UDP()),
|
||||
}, nil)
|
||||
wasAdded = true
|
||||
bn.notifyAdded()
|
||||
case *v5wire.Findnode:
|
||||
bn.conn.write(bn.l, &v5wire.Nodes{ReqID: p.ReqID, RespCount: 1}, nil)
|
||||
wasAdded = true
|
||||
bn.notifyAdded()
|
||||
case *v5wire.TalkRequest:
|
||||
bn.conn.write(bn.l, &v5wire.TalkResponse{ReqID: p.ReqID}, nil)
|
||||
case *readError:
|
||||
if !netutil.IsTemporaryError(p.err) {
|
||||
bn.conn.logf("shutting down: %v", p.err)
|
||||
return
|
||||
p, from := bn.conn.readFrom(bn.l)
|
||||
switch p := p.(type) {
|
||||
case *v5wire.Whoareyou:
|
||||
p.Node = bn.dest
|
||||
if resp, ok := bn.sent[p.Nonce]; ok {
|
||||
nonce := bn.conn.writeTo(bn.l, resp, p, from)
|
||||
delete(bn.sent, p.Nonce)
|
||||
bn.sent[nonce] = resp
|
||||
} else {
|
||||
bn.conn.writeTo(bn.l, &v5wire.Ping{
|
||||
ReqID: bn.conn.nextReqID(),
|
||||
ENRSeq: bn.conn.localNode.Seq(),
|
||||
}, p, from)
|
||||
}
|
||||
case *v5wire.Ping:
|
||||
resp := &v5wire.Pong{
|
||||
ReqID: append([]byte(nil), p.ReqID...),
|
||||
ENRSeq: bn.conn.localNode.Seq(),
|
||||
ToIP: from.IP,
|
||||
ToPort: uint16(from.Port),
|
||||
}
|
||||
nonce := bn.conn.writeTo(bn.l, resp, nil, from)
|
||||
bn.sent[nonce] = resp
|
||||
bn.notifyLive()
|
||||
case *v5wire.Findnode:
|
||||
resp := &v5wire.Nodes{ReqID: append([]byte(nil), p.ReqID...), RespCount: 1}
|
||||
for _, n := range bn.known {
|
||||
if slices.Contains(p.Distances, uint(enode.LogDist(n.ID(), bn.id()))) {
|
||||
resp.Nodes = append(resp.Nodes, n.Record())
|
||||
}
|
||||
}
|
||||
nonce := bn.conn.writeTo(bn.l, resp, nil, from)
|
||||
bn.sent[nonce] = resp
|
||||
case *v5wire.TalkRequest:
|
||||
resp := &v5wire.TalkResponse{ReqID: append([]byte(nil), p.ReqID...)}
|
||||
nonce := bn.conn.writeTo(bn.l, resp, nil, from)
|
||||
bn.sent[nonce] = resp
|
||||
case *readError:
|
||||
if netutil.IsTemporaryError(p.err) || v5wire.IsInvalidHeader(p.err) {
|
||||
continue
|
||||
}
|
||||
bn.conn.logf("shutting down: %v", p.err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (bn *bystander) notifyAdded() {
|
||||
if bn.addedCh != nil {
|
||||
bn.addedCh <- bn.id()
|
||||
bn.addedCh = nil
|
||||
func (bn *bystander) notifyLive() {
|
||||
if bn.liveCh != nil {
|
||||
bn.liveCh <- bn.id()
|
||||
bn.liveCh = nil
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -127,14 +127,16 @@ func (tc *conn) nextReqID() []byte {
|
|||
// The request is retried if a handshake is requested.
|
||||
func (tc *conn) reqresp(c net.PacketConn, req v5wire.Packet) v5wire.Packet {
|
||||
reqnonce := tc.write(c, req, nil)
|
||||
switch resp := tc.read(c).(type) {
|
||||
resp, from := tc.readFrom(c)
|
||||
switch resp := resp.(type) {
|
||||
case *v5wire.Whoareyou:
|
||||
if resp.Nonce != reqnonce {
|
||||
return readErrorf("wrong nonce %x in WHOAREYOU (want %x)", resp.Nonce[:], reqnonce[:])
|
||||
}
|
||||
resp.Node = tc.remote
|
||||
tc.write(c, req, resp)
|
||||
return tc.read(c)
|
||||
tc.writeTo(c, req, resp, from)
|
||||
resp2, _ := tc.readFrom(c)
|
||||
return resp2
|
||||
default:
|
||||
return resp
|
||||
}
|
||||
|
|
@ -150,21 +152,24 @@ func (tc *conn) findnode(c net.PacketConn, dists []uint) ([]*enode.Node, error)
|
|||
results []*enode.Node
|
||||
)
|
||||
for n := 1; n > 0; {
|
||||
switch resp := tc.read(c).(type) {
|
||||
resp, from := tc.readFrom(c)
|
||||
switch resp := resp.(type) {
|
||||
case *v5wire.Whoareyou:
|
||||
// Handle handshake.
|
||||
if resp.Nonce == reqnonce {
|
||||
resp.Node = tc.remote
|
||||
tc.write(c, findnode, resp)
|
||||
tc.writeTo(c, findnode, resp, from)
|
||||
} else {
|
||||
return nil, fmt.Errorf("unexpected WHOAREYOU (nonce %x), waiting for NODES", resp.Nonce[:])
|
||||
}
|
||||
case *v5wire.Ping:
|
||||
// Handle ping from remote.
|
||||
tc.write(c, &v5wire.Pong{
|
||||
tc.writeTo(c, &v5wire.Pong{
|
||||
ReqID: resp.ReqID,
|
||||
ENRSeq: tc.localNode.Seq(),
|
||||
}, nil)
|
||||
ToIP: from.IP,
|
||||
ToPort: uint16(from.Port),
|
||||
}, nil, from)
|
||||
case *v5wire.Nodes:
|
||||
// Got NODES! Check request ID.
|
||||
if !bytes.Equal(resp.ReqID, findnode.ReqID) {
|
||||
|
|
@ -200,11 +205,16 @@ func (tc *conn) findnode(c net.PacketConn, dists []uint) ([]*enode.Node, error)
|
|||
|
||||
// write sends a packet on the given connection.
|
||||
func (tc *conn) write(c net.PacketConn, p v5wire.Packet, challenge *v5wire.Whoareyou) v5wire.Nonce {
|
||||
return tc.writeTo(c, p, challenge, tc.remoteAddr)
|
||||
}
|
||||
|
||||
// writeTo sends a packet on the given connection to the given UDP address.
|
||||
func (tc *conn) writeTo(c net.PacketConn, p v5wire.Packet, challenge *v5wire.Whoareyou, to *net.UDPAddr) v5wire.Nonce {
|
||||
packet, nonce, err := tc.codec.Encode(tc.remote.ID(), tc.remoteAddr.String(), p, challenge)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("can't encode %v packet: %v", p.Name(), err))
|
||||
}
|
||||
if _, err := c.WriteTo(packet, tc.remoteAddr); err != nil {
|
||||
if _, err := c.WriteTo(packet, to); err != nil {
|
||||
tc.logf("Can't send %s: %v", p.Name(), err)
|
||||
} else {
|
||||
tc.logf(">> %s", p.Name())
|
||||
|
|
@ -214,24 +224,30 @@ func (tc *conn) write(c net.PacketConn, p v5wire.Packet, challenge *v5wire.Whoar
|
|||
|
||||
// read waits for an incoming packet on the given connection.
|
||||
func (tc *conn) read(c net.PacketConn) v5wire.Packet {
|
||||
p, _ := tc.readFrom(c)
|
||||
return p
|
||||
}
|
||||
|
||||
// readFrom waits for an incoming packet and returns its source address.
|
||||
func (tc *conn) readFrom(c net.PacketConn) (v5wire.Packet, *net.UDPAddr) {
|
||||
buf := make([]byte, 1280)
|
||||
if err := c.SetReadDeadline(time.Now().Add(waitTime)); err != nil {
|
||||
return &readError{err}
|
||||
return &readError{err}, nil
|
||||
}
|
||||
n, _, err := c.ReadFrom(buf)
|
||||
n, from, err := c.ReadFrom(buf)
|
||||
if err != nil {
|
||||
return &readError{err}
|
||||
return &readError{err}, nil
|
||||
}
|
||||
// Always use tc.remoteAddr for session lookup. The actual source address of
|
||||
// the packet may differ from tc.remoteAddr when the remote node is reachable
|
||||
// via multiple networks (e.g. Docker bridge vs. overlay), but the codec's
|
||||
// session cache is keyed by the address used during Encode.
|
||||
udpFrom, _ := from.(*net.UDPAddr)
|
||||
// Use tc.remoteAddr for codec/session lookup because the fixture keys sessions
|
||||
// by the advertised endpoint, but return the actual UDP source so responses can
|
||||
// comply with the spec and go back to the request envelope address.
|
||||
_, _, p, err := tc.codec.Decode(buf[:n], tc.remoteAddr.String())
|
||||
if err != nil {
|
||||
return &readError{err}
|
||||
return &readError{err}, udpFrom
|
||||
}
|
||||
tc.logf("<< %s", p.Name())
|
||||
return p
|
||||
return p, udpFrom
|
||||
}
|
||||
|
||||
// logf prints to the test log.
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
|
|
@ -30,6 +31,31 @@ import (
|
|||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// decodeRLPxDisconnect parses a disconnect message payload. Per the RLPx spec
|
||||
// the payload is a list containing a single reason, but some implementations
|
||||
// (including older geth) sent the reason as a bare byte. Accept both forms.
|
||||
func decodeRLPxDisconnect(data []byte) (p2p.DiscReason, error) {
|
||||
s := rlp.NewStream(bytes.NewReader(data), uint64(len(data)))
|
||||
k, _, err := s.Kind()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
var reason p2p.DiscReason
|
||||
if k == rlp.List {
|
||||
if _, err := s.List(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err := s.Decode(&reason); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return reason, nil
|
||||
}
|
||||
if err := s.Decode(&reason); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return reason, nil
|
||||
}
|
||||
|
||||
var (
|
||||
rlpxCommand = &cli.Command{
|
||||
Name: "rlpx",
|
||||
|
|
@ -103,11 +129,15 @@ func rlpxPing(ctx *cli.Context) error {
|
|||
}
|
||||
fmt.Printf("%+v\n", h)
|
||||
case 1:
|
||||
var msg []p2p.DiscReason
|
||||
if rlp.DecodeBytes(data, &msg); len(msg) == 0 {
|
||||
return errors.New("invalid disconnect message")
|
||||
// The disconnect message is specified as a list containing the reason,
|
||||
// but some implementations (including older geth) send the reason as a
|
||||
// single byte. Handle both forms, and on failure include the raw payload
|
||||
// so the operator can see what was actually sent.
|
||||
reason, decErr := decodeRLPxDisconnect(data)
|
||||
if decErr != nil {
|
||||
return fmt.Errorf("invalid disconnect message: %v (raw=0x%x)", decErr, data)
|
||||
}
|
||||
return fmt.Errorf("received disconnect message: %v", msg[0])
|
||||
return fmt.Errorf("received disconnect message: %v", reason)
|
||||
default:
|
||||
return fmt.Errorf("invalid message code %d, expected handshake (code zero) or disconnect (code one)", code)
|
||||
}
|
||||
|
|
|
|||
75
cmd/devp2p/rlpxcmd_test.go
Normal file
75
cmd/devp2p/rlpxcmd_test.go
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
// Copyright 2026 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
)
|
||||
|
||||
func TestDecodeRLPxDisconnect(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
payload []byte
|
||||
want p2p.DiscReason
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "list form (spec-compliant)",
|
||||
payload: []byte{0xc1, 0x04}, // [4] = TooManyPeers
|
||||
want: p2p.DiscTooManyPeers,
|
||||
},
|
||||
{
|
||||
name: "list form with reason zero",
|
||||
payload: []byte{0xc1, 0x80}, // [0] = Requested
|
||||
want: p2p.DiscRequested,
|
||||
},
|
||||
{
|
||||
name: "bare byte form (legacy geth)",
|
||||
payload: []byte{0x04}, // 4 = TooManyPeers
|
||||
want: p2p.DiscTooManyPeers,
|
||||
},
|
||||
{
|
||||
name: "bare byte form zero",
|
||||
payload: []byte{0x80}, // 0 = Requested
|
||||
want: p2p.DiscRequested,
|
||||
},
|
||||
{
|
||||
name: "empty payload",
|
||||
payload: []byte{},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got, err := decodeRLPxDisconnect(tc.payload)
|
||||
if tc.wantErr {
|
||||
if err == nil {
|
||||
t.Fatalf("expected error, got reason=%v", got)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if got != tc.want {
|
||||
t.Fatalf("got reason %v, want %v", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -337,9 +337,6 @@ func checkAccumulator(e era.Era) error {
|
|||
// accumulation across the entire set and are verified at the end.
|
||||
for it.Next() {
|
||||
// 1) next() walks the block index, so we're able to implicitly verify it.
|
||||
if it.Error() != nil {
|
||||
return fmt.Errorf("error reading block %d: %w", it.Number(), it.Error())
|
||||
}
|
||||
block, receipts, err := it.BlockAndReceipts()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading block %d: %w", it.Number(), err)
|
||||
|
|
|
|||
|
|
@ -17,9 +17,12 @@
|
|||
package t8ntool
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
stdmath "math"
|
||||
"math/big"
|
||||
"os"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
|
|
@ -47,6 +50,9 @@ type Prestate struct {
|
|||
Env stEnv `json:"env"`
|
||||
Pre types.GenesisAlloc `json:"pre"`
|
||||
TreeLeaves map[common.Hash]hexutil.Bytes `json:"vkt,omitempty"`
|
||||
// AllocPath, when non-empty, causes Apply to stream the alloc from disk
|
||||
// instead of reading Pre, so the full map never materializes in memory.
|
||||
AllocPath string `json:"-"`
|
||||
}
|
||||
|
||||
//go:generate go run github.com/fjl/gencodec -type ExecutionResult -field-override executionResultMarshaling -out gen_execresult.go
|
||||
|
|
@ -146,8 +152,19 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
|||
return h
|
||||
}
|
||||
var (
|
||||
isEIP4762 = chainConfig.IsVerkle(big.NewInt(int64(pre.Env.Number)), pre.Env.Timestamp)
|
||||
statedb = MakePreState(rawdb.NewMemoryDatabase(), pre.Pre, isEIP4762)
|
||||
isEIP4762 = chainConfig.IsUBT(big.NewInt(int64(pre.Env.Number)), pre.Env.Timestamp)
|
||||
statedb *state.StateDB
|
||||
)
|
||||
if pre.AllocPath != "" {
|
||||
var err error
|
||||
statedb, err = MakePreStateStreaming(rawdb.NewMemoryDatabase(), pre.AllocPath, isEIP4762)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
} else {
|
||||
statedb = MakePreState(rawdb.NewMemoryDatabase(), pre.Pre, isEIP4762)
|
||||
}
|
||||
var (
|
||||
signer = types.MakeSigner(chainConfig, new(big.Int).SetUint64(pre.Env.Number), pre.Env.Timestamp)
|
||||
gaspool = core.NewGasPool(pre.Env.GasLimit)
|
||||
blockHash = common.Hash{0x13, 0x37}
|
||||
|
|
@ -253,7 +270,7 @@ 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()
|
||||
|
|
@ -315,27 +332,14 @@ 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, 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))
|
||||
}
|
||||
|
||||
// Commit block
|
||||
root, err := statedb.Commit(vmContext.BlockNumber.Uint64(), chainConfig.IsEIP158(vmContext.BlockNumber), chainConfig.IsCancun(vmContext.BlockNumber, vmContext.Time))
|
||||
if err != nil {
|
||||
|
|
@ -378,7 +382,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
|||
}
|
||||
|
||||
func MakePreState(db ethdb.Database, accounts types.GenesisAlloc, isBintrie bool) *state.StateDB {
|
||||
tdb := triedb.NewDatabase(db, &triedb.Config{Preimages: true, IsVerkle: isBintrie})
|
||||
tdb := triedb.NewDatabase(db, &triedb.Config{Preimages: true, IsUBT: isBintrie})
|
||||
sdb := state.NewDatabase(tdb, nil)
|
||||
|
||||
root := types.EmptyRootHash
|
||||
|
|
@ -414,6 +418,76 @@ func MakePreState(db ethdb.Database, accounts types.GenesisAlloc, isBintrie bool
|
|||
return statedb
|
||||
}
|
||||
|
||||
// MakePreStateStreaming is like MakePreState, but decodes the alloc from disk
|
||||
// one account at a time so the full map is never held in memory.
|
||||
func MakePreStateStreaming(db ethdb.Database, allocPath string, isBintrie bool) (*state.StateDB, error) {
|
||||
tdb := triedb.NewDatabase(db, &triedb.Config{Preimages: true, IsUBT: isBintrie})
|
||||
sdb := state.NewDatabase(tdb, nil)
|
||||
|
||||
root := types.EmptyRootHash
|
||||
if isBintrie {
|
||||
root = types.EmptyBinaryHash
|
||||
}
|
||||
statedb, err := state.New(root, sdb)
|
||||
if err != nil {
|
||||
return nil, NewError(ErrorEVM, fmt.Errorf("failed to create initial statedb: %v", err))
|
||||
}
|
||||
|
||||
f, err := os.Open(allocPath)
|
||||
if err != nil {
|
||||
return nil, NewError(ErrorIO, fmt.Errorf("failed reading alloc file: %v", err))
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
dec := json.NewDecoder(f)
|
||||
tok, err := dec.Token()
|
||||
if err != nil {
|
||||
return nil, NewError(ErrorJson, fmt.Errorf("failed reading alloc opening token: %v", err))
|
||||
}
|
||||
if d, ok := tok.(json.Delim); !ok || d != '{' {
|
||||
return nil, NewError(ErrorJson, fmt.Errorf("expected alloc object, got %v", tok))
|
||||
}
|
||||
for dec.More() {
|
||||
keyTok, err := dec.Token()
|
||||
if err != nil {
|
||||
return nil, NewError(ErrorJson, fmt.Errorf("failed reading alloc key: %v", err))
|
||||
}
|
||||
keyStr, ok := keyTok.(string)
|
||||
if !ok {
|
||||
return nil, NewError(ErrorJson, fmt.Errorf("alloc key not a string: %v", keyTok))
|
||||
}
|
||||
addr := common.HexToAddress(keyStr)
|
||||
var acct types.Account
|
||||
if err := dec.Decode(&acct); err != nil {
|
||||
return nil, NewError(ErrorJson, fmt.Errorf("failed decoding account %s: %v", keyStr, err))
|
||||
}
|
||||
statedb.SetCode(addr, acct.Code, tracing.CodeChangeUnspecified)
|
||||
statedb.SetNonce(addr, acct.Nonce, tracing.NonceChangeGenesis)
|
||||
if acct.Balance != nil {
|
||||
statedb.SetBalance(addr, uint256.MustFromBig(acct.Balance), tracing.BalanceIncreaseGenesisBalance)
|
||||
}
|
||||
for k, v := range acct.Storage {
|
||||
statedb.SetState(addr, k, v)
|
||||
}
|
||||
}
|
||||
if _, err := dec.Token(); err != nil {
|
||||
return nil, NewError(ErrorJson, fmt.Errorf("failed reading alloc closing token: %v", err))
|
||||
}
|
||||
|
||||
root, err = statedb.Commit(0, false, false)
|
||||
if err != nil {
|
||||
return nil, NewError(ErrorEVM, fmt.Errorf("failed to commit initial state: %v", err))
|
||||
}
|
||||
if isBintrie {
|
||||
return statedb, nil
|
||||
}
|
||||
statedb, err = state.New(root, sdb)
|
||||
if err != nil {
|
||||
return nil, NewError(ErrorEVM, fmt.Errorf("failed to reopen state after commit: %v", err))
|
||||
}
|
||||
return statedb, nil
|
||||
}
|
||||
|
||||
func rlpHash(x any) (h common.Hash) {
|
||||
hw := keccak.NewLegacyKeccak256()
|
||||
rlp.Encode(hw, x)
|
||||
|
|
|
|||
|
|
@ -133,21 +133,21 @@ func Transaction(ctx *cli.Context) error {
|
|||
}
|
||||
// Check intrinsic gas
|
||||
rules := chainConfig.Rules(common.Big0, true, 0)
|
||||
gas, 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)
|
||||
continue
|
||||
}
|
||||
r.IntrinsicGas = gas
|
||||
if tx.Gas() < gas {
|
||||
r.Error = fmt.Errorf("%w: have %d, want %d", core.ErrIntrinsicGas, tx.Gas(), gas)
|
||||
r.IntrinsicGas = cost.RegularGas
|
||||
if tx.Gas() < cost.RegularGas {
|
||||
r.Error = fmt.Errorf("%w: have %d, want %d", core.ErrIntrinsicGas, tx.Gas(), cost.RegularGas)
|
||||
results = append(results, r)
|
||||
continue
|
||||
}
|
||||
// For Prague txs, validate the floor data gas.
|
||||
if rules.IsPrague {
|
||||
floorDataGas, err := core.FloorDataGas(tx.Data())
|
||||
floorDataGas, err := core.FloorDataGas(rules, tx.Data(), tx.AccessList())
|
||||
if err != nil {
|
||||
r.Error = err
|
||||
results = append(results, r)
|
||||
|
|
@ -185,7 +185,9 @@ func Transaction(ctx *cli.Context) error {
|
|||
}
|
||||
}
|
||||
|
||||
if chainConfig.IsOsaka(new(big.Int), 0) && tx.Gas() > params.MaxTxGas {
|
||||
isOsaka := chainConfig.IsOsaka(new(big.Int), 0)
|
||||
isAmsterdam := chainConfig.IsAmsterdam(new(big.Int), 0)
|
||||
if isOsaka && !isAmsterdam && tx.Gas() > params.MaxTxGas {
|
||||
r.Error = errors.New("gas limit exceeds maximum")
|
||||
}
|
||||
results = append(results, r)
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package t8ntool
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
|
@ -115,11 +116,10 @@ func Transition(ctx *cli.Context) error {
|
|||
}
|
||||
}
|
||||
if allocStr != stdinSelector {
|
||||
if err := readFile(allocStr, "alloc", &inputData.Alloc); err != nil {
|
||||
return err
|
||||
}
|
||||
prestate.AllocPath = allocStr
|
||||
} else {
|
||||
prestate.Pre = inputData.Alloc
|
||||
}
|
||||
prestate.Pre = inputData.Alloc
|
||||
|
||||
if btStr != stdinSelector && btStr != "" {
|
||||
if err := readFile(btStr, "BT", &inputData.BT); err != nil {
|
||||
|
|
@ -223,22 +223,57 @@ func Transition(ctx *cli.Context) error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
// Dump the execution result
|
||||
// Dump the execution result.
|
||||
var (
|
||||
collector = make(Alloc)
|
||||
collector Alloc
|
||||
btleaves map[common.Hash]hexutil.Bytes
|
||||
)
|
||||
isBinary := chainConfig.IsVerkle(big.NewInt(int64(prestate.Env.Number)), prestate.Env.Timestamp)
|
||||
if !isBinary {
|
||||
isBinary := chainConfig.IsUBT(big.NewInt(int64(prestate.Env.Number)), prestate.Env.Timestamp)
|
||||
allocOutput := ctx.String(OutputAllocFlag.Name)
|
||||
switch {
|
||||
case !isBinary && allocOutput != "" && allocOutput != "stdout" && allocOutput != "stderr":
|
||||
// Stream directly to the output file to avoid materializing the
|
||||
// whole post-state in memory. dispatchOutput is told to skip alloc
|
||||
// by clearing the output name.
|
||||
if err := writeStreamedAlloc(filepath.Join(baseDir, allocOutput), s); err != nil {
|
||||
return err
|
||||
}
|
||||
allocOutput = ""
|
||||
case !isBinary:
|
||||
collector = make(Alloc)
|
||||
s.DumpToCollector(collector, nil)
|
||||
} else {
|
||||
default:
|
||||
btleaves = make(map[common.Hash]hexutil.Bytes)
|
||||
if err := s.DumpBinTrieLeaves(btleaves); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return dispatchOutput(ctx, baseDir, result, collector, allocOutput, body, btleaves)
|
||||
}
|
||||
|
||||
return dispatchOutput(ctx, baseDir, result, collector, body, btleaves)
|
||||
// writeStreamedAlloc writes the post-state alloc to path one account at a
|
||||
// time, producing the same JSON shape as saveFile on an Alloc map.
|
||||
func writeStreamedAlloc(path string, s *state.StateDB) error {
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
return NewError(ErrorIO, fmt.Errorf("failed creating alloc output file: %v", err))
|
||||
}
|
||||
bw := bufio.NewWriter(f)
|
||||
sa := newStreamingAlloc(bw)
|
||||
s.DumpToCollector(sa, nil)
|
||||
if err := sa.Close(); err != nil {
|
||||
f.Close()
|
||||
return NewError(ErrorIO, fmt.Errorf("failed writing alloc output: %v", err))
|
||||
}
|
||||
if err := bw.Flush(); err != nil {
|
||||
f.Close()
|
||||
return NewError(ErrorIO, fmt.Errorf("failed flushing alloc output: %v", err))
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
return NewError(ErrorIO, fmt.Errorf("failed closing alloc output file: %v", err))
|
||||
}
|
||||
log.Info("Wrote file", "file", path)
|
||||
return nil
|
||||
}
|
||||
|
||||
func applyLondonChecks(env *stEnv, chainConfig *params.ChainConfig) error {
|
||||
|
|
@ -327,6 +362,10 @@ func (g Alloc) OnAccount(addr *common.Address, dumpAccount state.DumpAccount) {
|
|||
if addr == nil {
|
||||
return
|
||||
}
|
||||
g[*addr] = dumpAccountToTypesAccount(dumpAccount)
|
||||
}
|
||||
|
||||
func dumpAccountToTypesAccount(dumpAccount state.DumpAccount) types.Account {
|
||||
balance, _ := new(big.Int).SetString(dumpAccount.Balance, 0)
|
||||
var storage map[common.Hash]common.Hash
|
||||
if dumpAccount.Storage != nil {
|
||||
|
|
@ -335,13 +374,64 @@ func (g Alloc) OnAccount(addr *common.Address, dumpAccount state.DumpAccount) {
|
|||
storage[k] = common.HexToHash(v)
|
||||
}
|
||||
}
|
||||
genesisAccount := types.Account{
|
||||
return types.Account{
|
||||
Code: dumpAccount.Code,
|
||||
Storage: storage,
|
||||
Balance: balance,
|
||||
Nonce: dumpAccount.Nonce,
|
||||
}
|
||||
g[*addr] = genesisAccount
|
||||
}
|
||||
|
||||
// streamingAlloc is a DumpCollector that writes each account to w as it is
|
||||
// visited, emitting a single JSON object keyed by address. Close must be
|
||||
// called to emit the closing brace.
|
||||
type streamingAlloc struct {
|
||||
w io.Writer
|
||||
wroteOne bool
|
||||
err error
|
||||
}
|
||||
|
||||
func newStreamingAlloc(w io.Writer) *streamingAlloc {
|
||||
return &streamingAlloc{w: w}
|
||||
}
|
||||
|
||||
func (s *streamingAlloc) write(b []byte) {
|
||||
if s.err != nil {
|
||||
return
|
||||
}
|
||||
_, s.err = s.w.Write(b)
|
||||
}
|
||||
|
||||
func (s *streamingAlloc) OnRoot(common.Hash) {
|
||||
s.write([]byte{'{'})
|
||||
}
|
||||
|
||||
func (s *streamingAlloc) OnAccount(addr *common.Address, dumpAccount state.DumpAccount) {
|
||||
if s.err != nil || addr == nil {
|
||||
return
|
||||
}
|
||||
keyJSON, err := json.Marshal(*addr)
|
||||
if err != nil {
|
||||
s.err = err
|
||||
return
|
||||
}
|
||||
valueJSON, err := json.Marshal(dumpAccountToTypesAccount(dumpAccount))
|
||||
if err != nil {
|
||||
s.err = err
|
||||
return
|
||||
}
|
||||
if s.wroteOne {
|
||||
s.write([]byte{','})
|
||||
}
|
||||
s.write(keyJSON)
|
||||
s.write([]byte{':'})
|
||||
s.write(valueJSON)
|
||||
s.wroteOne = true
|
||||
}
|
||||
|
||||
func (s *streamingAlloc) Close() error {
|
||||
s.write([]byte{'}'})
|
||||
return s.err
|
||||
}
|
||||
|
||||
// saveFile marshals the object to the given file
|
||||
|
|
@ -359,8 +449,9 @@ func saveFile(baseDir, filename string, data interface{}) error {
|
|||
}
|
||||
|
||||
// dispatchOutput writes the output data to either stderr or stdout, or to the specified
|
||||
// files
|
||||
func dispatchOutput(ctx *cli.Context, baseDir string, result *ExecutionResult, alloc Alloc, body hexutil.Bytes, bt map[common.Hash]hexutil.Bytes) error {
|
||||
// files. An empty allocOutput skips the alloc dispatch, which is used when the
|
||||
// alloc has already been streamed to disk by the caller.
|
||||
func dispatchOutput(ctx *cli.Context, baseDir string, result *ExecutionResult, alloc Alloc, allocOutput string, body hexutil.Bytes, bt map[common.Hash]hexutil.Bytes) error {
|
||||
stdOutObject := make(map[string]interface{})
|
||||
stdErrObject := make(map[string]interface{})
|
||||
dispatch := func(baseDir, fName, name string, obj interface{}) error {
|
||||
|
|
@ -378,7 +469,7 @@ func dispatchOutput(ctx *cli.Context, baseDir string, result *ExecutionResult, a
|
|||
}
|
||||
return nil
|
||||
}
|
||||
if err := dispatch(baseDir, ctx.String(OutputAllocFlag.Name), "alloc", alloc); err != nil {
|
||||
if err := dispatch(baseDir, allocOutput, "alloc", alloc); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := dispatch(baseDir, ctx.String(OutputResultFlag.Name), "result", result); err != nil {
|
||||
|
|
@ -452,10 +543,10 @@ func BinKeys(ctx *cli.Context) error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
db := triedb.NewDatabase(rawdb.NewMemoryDatabase(), triedb.VerkleDefaults)
|
||||
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)
|
||||
}
|
||||
|
|
@ -496,10 +587,10 @@ func BinTrieRoot(ctx *cli.Context) error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
db := triedb.NewDatabase(rawdb.NewMemoryDatabase(), triedb.VerkleDefaults)
|
||||
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)
|
||||
}
|
||||
|
|
@ -509,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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ var (
|
|||
Name: "trace.noreturndata",
|
||||
Aliases: []string{"noreturndata"},
|
||||
Value: true,
|
||||
Usage: "enable return data output",
|
||||
Usage: "disable return data output",
|
||||
Category: traceCategory,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -166,8 +166,11 @@ func timedExec(bench bool, execFunc func() ([]byte, uint64, error)) ([]byte, exe
|
|||
if haveGasUsed != gasUsed {
|
||||
panic(fmt.Sprintf("gas differs, have %v want %v", haveGasUsed, gasUsed))
|
||||
}
|
||||
if haveErr != err {
|
||||
panic(fmt.Sprintf("err differs, have %v want %v", haveErr, err))
|
||||
if (haveErr == nil) != (err == nil) {
|
||||
panic(fmt.Sprintf("err differs in nil-ness, have %v want %v", haveErr, err))
|
||||
}
|
||||
if haveErr != nil && err != nil && haveErr.Error() != err.Error() {
|
||||
panic(fmt.Sprintf("err differs, have %q want %q", haveErr.Error(), err.Error()))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -318,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 {
|
||||
|
|
|
|||
|
|
@ -144,14 +144,14 @@ func convertToBinaryTrie(ctx *cli.Context) error {
|
|||
defer srcTriedb.Close()
|
||||
|
||||
destTriedb := triedb.NewDatabase(chaindb, &triedb.Config{
|
||||
IsVerkle: true,
|
||||
IsUBT: true,
|
||||
PathDB: &pathdb.Config{
|
||||
JournalDirectory: stack.ResolvePath("triedb-bintrie"),
|
||||
},
|
||||
})
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,12 +82,12 @@ func TestBintrieConvert(t *testing.T) {
|
|||
defer srcTriedb2.Close()
|
||||
|
||||
destTriedb := triedb.NewDatabase(chaindb, &triedb.Config{
|
||||
IsVerkle: true,
|
||||
PathDB: pathdb.Defaults,
|
||||
IsUBT: true,
|
||||
PathDB: pathdb.Defaults,
|
||||
})
|
||||
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)
|
||||
}
|
||||
|
|
@ -190,11 +190,11 @@ func TestBintrieConvertDeleteSource(t *testing.T) {
|
|||
})
|
||||
|
||||
destTriedb := triedb.NewDatabase(chaindb, &triedb.Config{
|
||||
IsVerkle: true,
|
||||
PathDB: pathdb.Defaults,
|
||||
IsUBT: true,
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ var (
|
|||
utils.OverrideOsaka,
|
||||
utils.OverrideBPO1,
|
||||
utils.OverrideBPO2,
|
||||
utils.OverrideVerkle,
|
||||
utils.OverrideUBT,
|
||||
}, utils.DatabaseFlags),
|
||||
Description: `
|
||||
The init command initializes a new genesis block and definition for the network.
|
||||
|
|
@ -297,15 +297,15 @@ func initGenesis(ctx *cli.Context) error {
|
|||
v := ctx.Uint64(utils.OverrideBPO2.Name)
|
||||
overrides.OverrideBPO2 = &v
|
||||
}
|
||||
if ctx.IsSet(utils.OverrideVerkle.Name) {
|
||||
v := ctx.Uint64(utils.OverrideVerkle.Name)
|
||||
overrides.OverrideVerkle = &v
|
||||
if ctx.IsSet(utils.OverrideUBT.Name) {
|
||||
v := ctx.Uint64(utils.OverrideUBT.Name)
|
||||
overrides.OverrideUBT = &v
|
||||
}
|
||||
|
||||
chaindb := utils.MakeChainDatabase(ctx, stack, false)
|
||||
defer chaindb.Close()
|
||||
|
||||
triedb := utils.MakeTrieDatabase(ctx, stack, chaindb, ctx.Bool(utils.CachePreimagesFlag.Name), false, genesis.IsVerkle())
|
||||
triedb := utils.MakeTrieDatabase(ctx, stack, chaindb, ctx.Bool(utils.CachePreimagesFlag.Name), false, genesis.IsUBT())
|
||||
defer triedb.Close()
|
||||
|
||||
_, hash, compatErr, err := core.SetupGenesisBlockWithOverride(chaindb, triedb, genesis, &overrides, nil)
|
||||
|
|
@ -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"
|
||||
|
|
@ -235,9 +236,9 @@ func makeFullNode(ctx *cli.Context) *node.Node {
|
|||
v := ctx.Uint64(utils.OverrideBPO2.Name)
|
||||
cfg.Eth.OverrideBPO2 = &v
|
||||
}
|
||||
if ctx.IsSet(utils.OverrideVerkle.Name) {
|
||||
v := ctx.Uint64(utils.OverrideVerkle.Name)
|
||||
cfg.Eth.OverrideVerkle = &v
|
||||
if ctx.IsSet(utils.OverrideUBT.Name) {
|
||||
v := ctx.Uint64(utils.OverrideUBT.Name)
|
||||
cfg.Eth.OverrideUBT = &v
|
||||
}
|
||||
|
||||
// Start metrics export if enabled.
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -806,6 +806,24 @@ func (iter *snapshotIterator) Release() {
|
|||
iter.storage.Release()
|
||||
}
|
||||
|
||||
type codeIterator struct {
|
||||
iter ethdb.Iterator
|
||||
}
|
||||
|
||||
func (iter *codeIterator) Next() (byte, []byte, []byte, bool) {
|
||||
for iter.iter.Next() {
|
||||
key := iter.iter.Key()
|
||||
if bytes.HasPrefix(key, rawdb.CodePrefix) && len(key) == (len(rawdb.CodePrefix)+common.HashLength) {
|
||||
return utils.OpBatchAdd, key, iter.iter.Value(), true
|
||||
}
|
||||
}
|
||||
return 0, nil, nil, false
|
||||
}
|
||||
|
||||
func (iter *codeIterator) Release() {
|
||||
iter.iter.Release()
|
||||
}
|
||||
|
||||
// chainExporters defines the export scheme for all exportable chain data.
|
||||
var chainExporters = map[string]func(db ethdb.Database) utils.ChainDataIterator{
|
||||
"preimage": func(db ethdb.Database) utils.ChainDataIterator {
|
||||
|
|
@ -817,6 +835,10 @@ var chainExporters = map[string]func(db ethdb.Database) utils.ChainDataIterator{
|
|||
storage := db.NewIterator(rawdb.SnapshotStoragePrefix, nil)
|
||||
return &snapshotIterator{account: account, storage: storage}
|
||||
},
|
||||
"code": func(db ethdb.Database) utils.ChainDataIterator {
|
||||
iter := db.NewIterator(rawdb.CodePrefix, nil)
|
||||
return &codeIterator{iter: iter}
|
||||
},
|
||||
}
|
||||
|
||||
func exportChaindata(ctx *cli.Context) error {
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -64,7 +61,7 @@ var (
|
|||
utils.OverrideOsaka,
|
||||
utils.OverrideBPO1,
|
||||
utils.OverrideBPO2,
|
||||
utils.OverrideVerkle,
|
||||
utils.OverrideUBT,
|
||||
utils.OverrideGenesisFlag,
|
||||
utils.EnablePersonal, // deprecated
|
||||
utils.TxPoolLocalsFlag,
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,15 +18,18 @@ package main
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"slices"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/state/pruner"
|
||||
|
|
@ -168,6 +171,22 @@ block is used.
|
|||
Description: `
|
||||
The export-preimages command exports hash preimages to a flat file, in exactly
|
||||
the expected order for the overlay tree migration.
|
||||
`,
|
||||
},
|
||||
{
|
||||
Name: "list-eip-7610-accounts",
|
||||
Aliases: []string{"eip7610"},
|
||||
Usage: "list EIP7610 eligible accounts",
|
||||
Action: listEIP7610EligibleAccounts,
|
||||
Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags),
|
||||
Description: `
|
||||
geth snapshot list-eip-7610-accounts
|
||||
traverses the post–EIP-161 state and returns all accounts that are eligible
|
||||
under EIP-7610: accounts with zero nonce, empty runtime code, and non-empty
|
||||
storage. The traversal will be aborted immediately if the state is prior to
|
||||
EIP-161.
|
||||
|
||||
The exported accounts are identified by their address.
|
||||
`,
|
||||
},
|
||||
},
|
||||
|
|
@ -801,3 +820,92 @@ func checkAccount(ctx *cli.Context) error {
|
|||
log.Info("Checked the snapshot journalled storage", "time", common.PrettyDuration(time.Since(start)))
|
||||
return nil
|
||||
}
|
||||
|
||||
// listEIP7610EligibleAccounts traverses the post–EIP-161 state and returns all
|
||||
// accounts that are eligible under EIP-7610: accounts with zero nonce, empty
|
||||
// runtime code, and non-empty storage.
|
||||
//
|
||||
// Such accounts could only have been created before EIP-161, since after that
|
||||
// all newly created contracts are initialized with a nonce of one.
|
||||
//
|
||||
// This helper should be generally applicable to all networks, including the
|
||||
// Ethereum mainnet. For most networks where EIP-161 was enabled from genesis,
|
||||
// the resulting set is expected to be empty. Otherwise, network operators are
|
||||
// responsible for generating the eligible account set themselves.
|
||||
//
|
||||
// Notably, the exported accounts are identified by their address.
|
||||
func listEIP7610EligibleAccounts(ctx *cli.Context) error {
|
||||
stack, _ := makeConfigNode(ctx)
|
||||
defer stack.Close()
|
||||
|
||||
chaindb := utils.MakeChainDatabase(ctx, stack, true)
|
||||
defer chaindb.Close()
|
||||
|
||||
headBlock := rawdb.ReadHeadBlock(chaindb)
|
||||
if headBlock == nil {
|
||||
log.Error("Failed to load head block")
|
||||
return nil
|
||||
}
|
||||
config, _, err := core.LoadChainConfig(chaindb, utils.MakeGenesis(ctx))
|
||||
if err != nil {
|
||||
log.Error("Failed to load chain config", "err", err)
|
||||
return err
|
||||
}
|
||||
if !config.IsEIP158(headBlock.Number()) {
|
||||
log.Info("Local head is prior to EIP-161", "head", headBlock.Number(), "eip-161", *config.EIP158Block)
|
||||
return nil
|
||||
}
|
||||
triedb := utils.MakeTrieDatabase(ctx, stack, chaindb, false, true, false)
|
||||
defer triedb.Close()
|
||||
|
||||
if triedb.Scheme() != rawdb.PathScheme {
|
||||
log.Error("Hash scheme is not supported")
|
||||
return nil
|
||||
}
|
||||
iter, err := triedb.AccountIterator(headBlock.Root(), common.Hash{})
|
||||
if err != nil {
|
||||
log.Error("Failed to get account iterator", "err", err)
|
||||
return err
|
||||
}
|
||||
var (
|
||||
start = time.Now()
|
||||
accounts []common.Address
|
||||
)
|
||||
for iter.Next() {
|
||||
blob := iter.Account()
|
||||
if blob == nil {
|
||||
log.Error("Failed to get account blob")
|
||||
return nil
|
||||
}
|
||||
var account types.SlimAccount
|
||||
if err := rlp.DecodeBytes(blob, &account); err != nil {
|
||||
log.Error("Failed to decode", "err", err)
|
||||
return err
|
||||
}
|
||||
// EIP-7610 account eligibility:
|
||||
// - account.nonce == 0
|
||||
// - account.runtime_code == empty
|
||||
// - account.storage != empty
|
||||
if len(account.CodeHash) == 0 && account.Nonce == 0 && len(account.Root) != 0 {
|
||||
preimage := rawdb.ReadPreimage(chaindb, iter.Hash())
|
||||
if preimage == nil {
|
||||
log.Error("Failed to read preimage", "hash", iter.Hash().Hex())
|
||||
return nil
|
||||
}
|
||||
accounts = append(accounts, common.BytesToAddress(preimage))
|
||||
}
|
||||
}
|
||||
if len(accounts) == 0 {
|
||||
log.Info("Traversed state", "eligible", len(accounts), "elapsed", common.PrettyDuration(time.Since(start)))
|
||||
} else {
|
||||
sort.Slice(accounts, func(i, j int) bool {
|
||||
return accounts[i].Cmp(accounts[j]) < 0
|
||||
})
|
||||
buf := make([]byte, len(accounts)*common.AddressLength)
|
||||
for i, h := range accounts {
|
||||
copy(buf[i*common.AddressLength:], h[:])
|
||||
}
|
||||
log.Info("Traversed state", "eligible", len(accounts), "elapsed", common.PrettyDuration(time.Since(start)), "output", hex.EncodeToString(buf))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,8 +38,8 @@ require (
|
|||
go.opentelemetry.io/otel v1.40.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.40.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.40.0 // indirect
|
||||
golang.org/x/crypto v0.44.0 // indirect
|
||||
golang.org/x/sync v0.18.0 // indirect
|
||||
golang.org/x/crypto v0.47.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/sys v0.40.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
|
|
|
|||
|
|
@ -127,20 +127,20 @@ go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ7
|
|||
go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE=
|
||||
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
|
||||
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
|
||||
golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
|
||||
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
|
||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
||||
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
|
||||
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
|
|
|||
|
|
@ -264,9 +264,9 @@ var (
|
|||
Usage: "Manually specify the bpo2 fork timestamp, overriding the bundled setting",
|
||||
Category: flags.EthCategory,
|
||||
}
|
||||
OverrideVerkle = &cli.Uint64Flag{
|
||||
Name: "override.verkle",
|
||||
Usage: "Manually specify the Verkle fork timestamp, overriding the bundled setting",
|
||||
OverrideUBT = &cli.Uint64Flag{
|
||||
Name: "override.ubt",
|
||||
Usage: "Manually specify the UBT fork timestamp, overriding the bundled setting",
|
||||
Category: flags.EthCategory,
|
||||
}
|
||||
OverrideGenesisFlag = &cli.StringFlag{
|
||||
|
|
@ -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)",
|
||||
|
|
@ -1067,19 +1073,19 @@ Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server.
|
|||
|
||||
RPCTelemetryEndpointFlag = &cli.StringFlag{
|
||||
Name: "rpc.telemetry.endpoint",
|
||||
Usage: "Defines where RPC telemetry is sent (e.g., http://localhost:4318)",
|
||||
Usage: "Defines where RPC telemetry is sent (e.g., http://localhost:4318 or grpc://localhost:4317)",
|
||||
Category: flags.APICategory,
|
||||
}
|
||||
|
||||
RPCTelemetryUserFlag = &cli.StringFlag{
|
||||
Name: "rpc.telemetry.username",
|
||||
Usage: "HTTP Basic Auth username for OpenTelemetry",
|
||||
Usage: "Basic Auth username for OpenTelemetry",
|
||||
Category: flags.APICategory,
|
||||
}
|
||||
|
||||
RPCTelemetryPasswordFlag = &cli.StringFlag{
|
||||
Name: "rpc.telemetry.password",
|
||||
Usage: "HTTP Basic Auth password for OpenTelemetry",
|
||||
Usage: "Basic Auth password for OpenTelemetry",
|
||||
Category: flags.APICategory,
|
||||
}
|
||||
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -1899,7 +1908,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
|
|||
cfg.StatelessSelfValidation = ctx.Bool(VMStatelessSelfValidationFlag.Name)
|
||||
}
|
||||
// Auto-enable StatelessSelfValidation when witness stats are enabled
|
||||
if ctx.Bool(VMWitnessStatsFlag.Name) {
|
||||
if cfg.EnableWitnessStats {
|
||||
cfg.StatelessSelfValidation = true
|
||||
}
|
||||
|
||||
|
|
@ -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,
|
||||
|
|
@ -2516,10 +2526,10 @@ func MakeConsolePreloads(ctx *cli.Context) []string {
|
|||
}
|
||||
|
||||
// MakeTrieDatabase constructs a trie database based on the configured scheme.
|
||||
func MakeTrieDatabase(ctx *cli.Context, stack *node.Node, disk ethdb.Database, preimage bool, readOnly bool, isVerkle bool) *triedb.Database {
|
||||
func MakeTrieDatabase(ctx *cli.Context, stack *node.Node, disk ethdb.Database, preimage bool, readOnly bool, isUBT bool) *triedb.Database {
|
||||
config := &triedb.Config{
|
||||
Preimages: preimage,
|
||||
IsVerkle: isVerkle,
|
||||
IsUBT: isUBT,
|
||||
}
|
||||
scheme, err := rawdb.ParseStateScheme(ctx.String(StateSchemeFlag.Name), disk)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
package beacon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
|
@ -26,13 +25,10 @@ import (
|
|||
"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/state"
|
||||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/internal/telemetry"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
|
|
@ -361,48 +357,6 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types.
|
|||
// No block reward which is issued by consensus layer instead.
|
||||
}
|
||||
|
||||
// FinalizeAndAssemble implements consensus.Engine, setting the final state and
|
||||
// assembling the block.
|
||||
func (beacon *Beacon) FinalizeAndAssemble(ctx context.Context, chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (result *types.Block, err error) {
|
||||
ctx, _, spanEnd := telemetry.StartSpan(ctx, "consensus.beacon.FinalizeAndAssemble",
|
||||
telemetry.Int64Attribute("block.number", int64(header.Number.Uint64())),
|
||||
telemetry.Int64Attribute("txs.count", int64(len(body.Transactions))),
|
||||
telemetry.Int64Attribute("withdrawals.count", int64(len(body.Withdrawals))),
|
||||
)
|
||||
defer spanEnd(&err)
|
||||
|
||||
if !beacon.IsPoSHeader(header) {
|
||||
block, delegateErr := beacon.ethone.FinalizeAndAssemble(ctx, chain, header, state, body, receipts)
|
||||
return block, delegateErr
|
||||
}
|
||||
shanghai := chain.Config().IsShanghai(header.Number, header.Time)
|
||||
if shanghai {
|
||||
// All blocks after Shanghai must include a withdrawals root.
|
||||
if body.Withdrawals == nil {
|
||||
body.Withdrawals = make([]*types.Withdrawal, 0)
|
||||
}
|
||||
} else {
|
||||
if len(body.Withdrawals) > 0 {
|
||||
return nil, errors.New("withdrawals set before Shanghai activation")
|
||||
}
|
||||
}
|
||||
// Finalize and assemble the block.
|
||||
_, _, finalizeSpanEnd := telemetry.StartSpan(ctx, "consensus.beacon.Finalize")
|
||||
beacon.Finalize(chain, header, state, body)
|
||||
finalizeSpanEnd(nil)
|
||||
|
||||
// Assign the final state root to header.
|
||||
_, _, rootSpanEnd := telemetry.StartSpan(ctx, "consensus.beacon.IntermediateRoot")
|
||||
header.Root = state.IntermediateRoot(true)
|
||||
rootSpanEnd(nil)
|
||||
|
||||
// Assemble the final block.
|
||||
_, _, blockSpanEnd := telemetry.StartSpan(ctx, "consensus.beacon.NewBlock")
|
||||
block := types.NewBlock(header, body, receipts, trie.NewStackTrie(nil))
|
||||
blockSpanEnd(nil)
|
||||
return block, nil
|
||||
}
|
||||
|
||||
// Seal generates a new sealing request for the given input block and pushes
|
||||
// the result into the given channel.
|
||||
//
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ package clique
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
|
@ -34,7 +33,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/consensus/misc"
|
||||
"github.com/ethereum/go-ethereum/consensus/misc/eip1559"
|
||||
"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/crypto"
|
||||
|
|
@ -43,7 +41,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -580,22 +577,6 @@ func (c *Clique) Finalize(chain consensus.ChainHeaderReader, header *types.Heade
|
|||
// No block rewards in PoA, so the state remains as is
|
||||
}
|
||||
|
||||
// FinalizeAndAssemble implements consensus.Engine, ensuring no uncles are set,
|
||||
// nor block rewards given, and returns the final block.
|
||||
func (c *Clique) FinalizeAndAssemble(ctx context.Context, chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error) {
|
||||
if len(body.Withdrawals) > 0 {
|
||||
return nil, errors.New("clique does not support withdrawals")
|
||||
}
|
||||
// Finalize block
|
||||
c.Finalize(chain, header, state, body)
|
||||
|
||||
// Assign the final state root to header.
|
||||
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
|
||||
|
||||
// Assemble and return the final block for sealing.
|
||||
return types.NewBlock(header, &types.Body{Transactions: body.Transactions}, receipts, trie.NewStackTrie(nil)), nil
|
||||
}
|
||||
|
||||
// Authorize injects a private key into the consensus engine to mint new blocks
|
||||
// with.
|
||||
func (c *Clique) Authorize(signer common.Address) {
|
||||
|
|
|
|||
|
|
@ -18,11 +18,9 @@
|
|||
package consensus
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"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/params"
|
||||
|
|
@ -88,13 +86,6 @@ type Engine interface {
|
|||
// that happen at finalization (e.g. block rewards).
|
||||
Finalize(chain ChainHeaderReader, header *types.Header, state vm.StateDB, body *types.Body)
|
||||
|
||||
// FinalizeAndAssemble runs any post-transaction state modifications (e.g. block
|
||||
// rewards or process withdrawals) and assembles the final block.
|
||||
//
|
||||
// Note: The block header and state database might be updated to reflect any
|
||||
// consensus rules that happen at finalization (e.g. block rewards).
|
||||
FinalizeAndAssemble(ctx context.Context, chain ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error)
|
||||
|
||||
// Seal generates a new sealing request for the given input block and pushes
|
||||
// the result into the given channel.
|
||||
//
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
package ethash
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
|
@ -28,14 +27,12 @@ import (
|
|||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/consensus/misc"
|
||||
"github.com/ethereum/go-ethereum/consensus/misc/eip1559"
|
||||
"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/vm"
|
||||
"github.com/ethereum/go-ethereum/crypto/keccak"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
|
|
@ -512,22 +509,6 @@ func (ethash *Ethash) Finalize(chain consensus.ChainHeaderReader, header *types.
|
|||
accumulateRewards(chain.Config(), state, header, body.Uncles)
|
||||
}
|
||||
|
||||
// FinalizeAndAssemble implements consensus.Engine, accumulating the block and
|
||||
// uncle rewards, setting the final state and assembling the block.
|
||||
func (ethash *Ethash) FinalizeAndAssemble(ctx context.Context, chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error) {
|
||||
if len(body.Withdrawals) > 0 {
|
||||
return nil, errors.New("ethash does not support withdrawals")
|
||||
}
|
||||
// Finalize block
|
||||
ethash.Finalize(chain, header, state, body)
|
||||
|
||||
// Assign the final state root to header.
|
||||
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
|
||||
|
||||
// Header seems complete, assemble into a block and return
|
||||
return types.NewBlock(header, &types.Body{Transactions: body.Transactions, Uncles: body.Uncles}, receipts, trie.NewStackTrie(nil)), nil
|
||||
}
|
||||
|
||||
// SealHash returns the hash of a block prior to it being sealed.
|
||||
func (ethash *Ethash) SealHash(header *types.Header) (hash common.Hash) {
|
||||
hasher := keccak.NewLegacyKeccak256()
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) {
|
|||
data := make([]byte, nbytes)
|
||||
return func(i int, gen *BlockGen) {
|
||||
toaddr := common.Address{}
|
||||
gas, _ := 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 {
|
||||
|
|
@ -99,7 +99,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) {
|
|||
Nonce: gen.TxNonce(benchRootAddr),
|
||||
To: &toaddr,
|
||||
Value: big.NewInt(1),
|
||||
Gas: gas,
|
||||
Gas: cost.RegularGas,
|
||||
Data: data,
|
||||
GasPrice: gasPrice,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
testVerkleChainConfig = ¶ms.ChainConfig{
|
||||
testUBTChainConfig = ¶ms.ChainConfig{
|
||||
ChainID: big.NewInt(1),
|
||||
HomesteadBlock: big.NewInt(0),
|
||||
EIP150Block: big.NewInt(0),
|
||||
|
|
@ -51,30 +51,30 @@ var (
|
|||
LondonBlock: big.NewInt(0),
|
||||
Ethash: new(params.EthashConfig),
|
||||
ShanghaiTime: u64(0),
|
||||
VerkleTime: u64(0),
|
||||
UBTTime: u64(0),
|
||||
TerminalTotalDifficulty: common.Big0,
|
||||
EnableVerkleAtGenesis: true,
|
||||
EnableUBTAtGenesis: true,
|
||||
BlobScheduleConfig: ¶ms.BlobScheduleConfig{
|
||||
Verkle: params.DefaultPragueBlobConfig,
|
||||
UBT: params.DefaultPragueBlobConfig,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func TestProcessVerkle(t *testing.T) {
|
||||
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)
|
||||
signer = types.LatestSigner(testVerkleChainConfig)
|
||||
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
|
||||
coinbase = common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7")
|
||||
gspec = &Genesis{
|
||||
Config: testVerkleChainConfig,
|
||||
Config: testUBTChainConfig,
|
||||
Alloc: GenesisAlloc{
|
||||
coinbase: {
|
||||
Balance: big.NewInt(1000000000000000000), // 1 ether
|
||||
|
|
@ -87,21 +87,22 @@ func TestProcessVerkle(t *testing.T) {
|
|||
},
|
||||
}
|
||||
)
|
||||
// Verkle trees use the snapshot, which must be enabled before the
|
||||
// UBTs use the snapshot, which must be enabled before the
|
||||
// data is saved into the tree+database.
|
||||
// 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()
|
||||
|
||||
txCost1 := params.TxGas
|
||||
txCost2 := params.TxGas
|
||||
contractCreationCost := intrinsicContractCreationGas +
|
||||
contractCreationCost := intrinsicContractCreationGas.RegularGas +
|
||||
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + params.WitnessBranchReadCost + params.WitnessBranchWriteCost + /* creation */
|
||||
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* creation with value */
|
||||
739 /* execution costs */
|
||||
codeWithExtCodeCopyGas := intrinsicCodeWithExtCodeCopyGas +
|
||||
codeWithExtCodeCopyGas := intrinsicCodeWithExtCodeCopyGas.RegularGas +
|
||||
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + params.WitnessBranchReadCost + params.WitnessBranchWriteCost + /* creation (tx) */
|
||||
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + params.WitnessBranchReadCost + params.WitnessBranchWriteCost + /* creation (CREATE at pc=0x20) */
|
||||
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* write code hash */
|
||||
|
|
@ -188,7 +189,7 @@ func TestProcessParentBlockHash(t *testing.T) {
|
|||
// block 1 parent hash is 0x0100....
|
||||
// block 2 parent hash is 0x0200....
|
||||
// etc
|
||||
checkBlockHashes := func(statedb *state.StateDB, isVerkle bool) {
|
||||
checkBlockHashes := func(statedb *state.StateDB, isUBT bool) {
|
||||
statedb.SetNonce(params.HistoryStorageAddress, 1, tracing.NonceChangeUnspecified)
|
||||
statedb.SetCode(params.HistoryStorageAddress, params.HistoryStorageCode, tracing.CodeChangeUnspecified)
|
||||
// Process n blocks, from 1 .. num
|
||||
|
|
@ -196,8 +197,8 @@ func TestProcessParentBlockHash(t *testing.T) {
|
|||
for i := 1; i <= num; i++ {
|
||||
header := &types.Header{ParentHash: common.Hash{byte(i)}, Number: big.NewInt(int64(i)), Difficulty: new(big.Int)}
|
||||
chainConfig := params.MergedTestChainConfig
|
||||
if isVerkle {
|
||||
chainConfig = testVerkleChainConfig
|
||||
if isUBT {
|
||||
chainConfig = testUBTChainConfig
|
||||
}
|
||||
vmContext := NewEVMBlockContext(header, nil, new(common.Address))
|
||||
evm := vm.NewEVM(vmContext, statedb, chainConfig, vm.Config{})
|
||||
|
|
@ -205,9 +206,9 @@ func TestProcessParentBlockHash(t *testing.T) {
|
|||
}
|
||||
// Read block hashes for block 0 .. num-1
|
||||
for i := 0; i < num; i++ {
|
||||
have, want := getContractStoredBlockHash(statedb, uint64(i), isVerkle), common.Hash{byte(i + 1)}
|
||||
have, want := getContractStoredBlockHash(statedb, uint64(i), isUBT), common.Hash{byte(i + 1)}
|
||||
if have != want {
|
||||
t.Errorf("block %d, verkle=%v, have parent hash %v, want %v", i, isVerkle, have, want)
|
||||
t.Errorf("block %d, verkle=%v, have parent hash %v, want %v", i, isUBT, have, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -215,22 +216,23 @@ func TestProcessParentBlockHash(t *testing.T) {
|
|||
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
|
||||
checkBlockHashes(statedb, false)
|
||||
})
|
||||
t.Run("Verkle", func(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.EmptyVerkleHash, state.NewDatabase(triedb, nil))
|
||||
statedb, _ := state.New(types.EmptyBinaryHash, state.NewDatabase(triedb, nil))
|
||||
checkBlockHashes(statedb, true)
|
||||
})
|
||||
}
|
||||
|
||||
// getContractStoredBlockHash is a utility method which reads the stored parent blockhash for block 'number'
|
||||
func getContractStoredBlockHash(statedb *state.StateDB, number uint64, isVerkle bool) common.Hash {
|
||||
func getContractStoredBlockHash(statedb *state.StateDB, number uint64, isUBT bool) common.Hash {
|
||||
ringIndex := number % params.HistoryServeWindow
|
||||
var key common.Hash
|
||||
binary.BigEndian.PutUint64(key[24:], ringIndex)
|
||||
if isVerkle {
|
||||
if isUBT {
|
||||
return statedb.GetState(params.HistoryStorageAddress, key)
|
||||
}
|
||||
return statedb.GetState(params.HistoryStorageAddress, key)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -258,10 +259,11 @@ func (cfg BlockChainConfig) WithNoAsyncFlush(on bool) *BlockChainConfig {
|
|||
}
|
||||
|
||||
// triedbConfig derives the configures for trie database.
|
||||
func (cfg *BlockChainConfig) triedbConfig(isVerkle bool) *triedb.Config {
|
||||
func (cfg *BlockChainConfig) triedbConfig(isUBT bool) *triedb.Config {
|
||||
config := &triedb.Config{
|
||||
Preimages: cfg.Preimages,
|
||||
IsVerkle: isVerkle,
|
||||
Preimages: cfg.Preimages,
|
||||
IsUBT: isUBT,
|
||||
BinTrieGroupDepth: cfg.BinTrieGroupDepth,
|
||||
}
|
||||
if cfg.StateScheme == rawdb.HashScheme {
|
||||
config.HashDB = &hashdb.Config{
|
||||
|
|
@ -378,7 +380,7 @@ func NewBlockChain(db ethdb.Database, genesis *Genesis, engine consensus.Engine,
|
|||
}
|
||||
|
||||
// Open trie database with provided config
|
||||
enableVerkle, err := EnableVerkleAtGenesis(db, genesis)
|
||||
enableVerkle, err := EnableUBTAtGenesis(db, genesis)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1188,6 +1190,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()))
|
||||
|
||||
|
|
@ -2120,11 +2123,29 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
|
|||
startTime = time.Now()
|
||||
statedb *state.StateDB
|
||||
interrupt atomic.Bool
|
||||
sdb = state.NewDatabase(bc.triedb, bc.codedb).WithSnapshot(bc.snaps)
|
||||
sdb state.Database
|
||||
)
|
||||
defer interrupt.Store(true) // terminate the prefetch at the end
|
||||
|
||||
if bc.cfg.NoPrefetch {
|
||||
if bc.chainConfig.IsUBT(block.Number(), block.Time()) {
|
||||
sdb = state.NewUBTDatabase(bc.triedb, bc.codedb)
|
||||
} else {
|
||||
sdb = state.NewMPTDatabase(bc.triedb, bc.codedb).WithSnapshot(bc.snaps)
|
||||
}
|
||||
// If prefetching is enabled, run that against the current state to pre-cache
|
||||
// transactions and probabilistically some of the account/storage trie nodes.
|
||||
//
|
||||
// Note: the main processor and prefetcher share the same reader with a local
|
||||
// cache for mitigating the overhead of state access.
|
||||
type prewarmReader interface {
|
||||
// ReadersWithCacheStats creates a pair of state readers that share the
|
||||
// same underlying state reader and internal state cache, while maintaining
|
||||
// separate statistics respectively.
|
||||
ReadersWithCacheStats(stateRoot common.Hash) (state.Reader, state.Reader, error)
|
||||
}
|
||||
warmer, ok := sdb.(prewarmReader)
|
||||
|
||||
if bc.cfg.NoPrefetch || !ok {
|
||||
statedb, err = state.New(parentRoot, sdb)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -2135,7 +2156,7 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
|
|||
//
|
||||
// Note: the main processor and prefetcher share the same reader with a local
|
||||
// cache for mitigating the overhead of state access.
|
||||
prefetch, process, err := sdb.ReadersWithCacheStats(parentRoot)
|
||||
prefetch, process, err := warmer.ReadersWithCacheStats(parentRoot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -2580,8 +2601,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
|
||||
|
|
|
|||
|
|
@ -416,19 +416,42 @@ func (bc *BlockChain) ContractCodeWithPrefix(hash common.Hash) []byte {
|
|||
|
||||
// State returns a new mutable state based on the current HEAD block.
|
||||
func (bc *BlockChain) State() (*state.StateDB, error) {
|
||||
return bc.StateAt(bc.CurrentBlock().Root)
|
||||
return bc.StateAt(bc.CurrentBlock())
|
||||
}
|
||||
|
||||
// StateAt returns a new mutable state based on a particular point in time.
|
||||
func (bc *BlockChain) StateAt(root common.Hash) (*state.StateDB, error) {
|
||||
return state.New(root, state.NewDatabase(bc.triedb, bc.codedb).WithSnapshot(bc.snaps))
|
||||
func (bc *BlockChain) StateAt(header *types.Header) (*state.StateDB, error) {
|
||||
if bc.chainConfig.IsUBT(header.Number, header.Time) {
|
||||
return state.New(header.Root, state.NewUBTDatabase(bc.triedb, bc.codedb))
|
||||
}
|
||||
return state.New(header.Root, state.NewMPTDatabase(bc.triedb, bc.codedb).WithSnapshot(bc.snaps))
|
||||
}
|
||||
|
||||
// HistoricState returns a historic state specified by the given root.
|
||||
// StateAtForkBoundary returns a new mutable state based on the parent state
|
||||
// and the given header, handling the transition across the UBT fork.
|
||||
func (bc *BlockChain) StateAtForkBoundary(parent *types.Header, header *types.Header) (*state.StateDB, error) {
|
||||
// The parent is already in the UBT fork.
|
||||
if bc.chainConfig.IsUBT(parent.Number, parent.Time) {
|
||||
return state.New(parent.Root, state.NewUBTDatabase(bc.triedb, bc.codedb))
|
||||
}
|
||||
// The current block is the first block in the UBT fork
|
||||
// (i.e., the parent is the last MPT block).
|
||||
if bc.chainConfig.IsUBT(header.Number, header.Time) {
|
||||
// TODO(gballet): register chain context if needed
|
||||
return state.New(parent.Root, state.NewUBTDatabase(bc.triedb, bc.codedb))
|
||||
}
|
||||
// Both the parent and current block are in the MPT fork.
|
||||
return state.New(parent.Root, state.NewMPTDatabase(bc.triedb, bc.codedb).WithSnapshot(bc.snaps))
|
||||
}
|
||||
|
||||
// HistoricState returns a historic state specified by the given header.
|
||||
// Live states are not available and won't be served, please use `State`
|
||||
// or `StateAt` instead.
|
||||
func (bc *BlockChain) HistoricState(root common.Hash) (*state.StateDB, error) {
|
||||
return state.New(root, state.NewHistoricDatabase(bc.triedb, bc.codedb))
|
||||
func (bc *BlockChain) HistoricState(header *types.Header) (*state.StateDB, error) {
|
||||
if bc.chainConfig.IsUBT(header.Number, header.Time) {
|
||||
return nil, errors.New("historical state over ubt is not yet supported")
|
||||
}
|
||||
return state.New(header.Root, state.NewHistoricDatabase(bc.triedb, bc.codedb))
|
||||
}
|
||||
|
||||
// Config retrieves the chain's fork configuration.
|
||||
|
|
|
|||
|
|
@ -3890,7 +3890,7 @@ func TestTransientStorageReset(t *testing.T) {
|
|||
t.Fatalf("failed to insert into chain: %v", err)
|
||||
}
|
||||
// Check the storage
|
||||
state, err := chain.StateAt(chain.CurrentHeader().Root)
|
||||
state, err := chain.StateAt(chain.CurrentHeader())
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to load state %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ 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))
|
||||
b.statedb.SetTxContext(tx.Hash(), len(b.txs), uint32(len(b.txs)+1))
|
||||
receipt, err := ApplyTransaction(evm, b.gasPool, b.statedb, b.header, tx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
|
@ -126,7 +126,7 @@ func (b *BlockGen) addTx(bc *BlockChain, vmConfig vm.Config, tx *types.Transacti
|
|||
|
||||
// Merge the tx-local access event into the "block-local" one, in order to collect
|
||||
// all values, so that the witness can be built.
|
||||
if b.statedb.Database().TrieDB().IsVerkle() {
|
||||
if b.statedb.Database().Type().Is(state.TypeUBT) {
|
||||
b.statedb.AccessEvents().Merge(evm.AccessEvents)
|
||||
}
|
||||
b.txs = append(b.txs, tx)
|
||||
|
|
@ -315,28 +315,17 @@ func (b *BlockGen) collectRequests(readonly bool) (requests [][]byte) {
|
|||
// off the statedb before executing the system calls.
|
||||
statedb = statedb.Copy()
|
||||
}
|
||||
var blockLogs []*types.Log
|
||||
for _, r := range b.receipts {
|
||||
blockLogs = append(blockLogs, r.Logs...)
|
||||
}
|
||||
// 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{})
|
||||
|
||||
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))
|
||||
}
|
||||
requests, 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
|
||||
}
|
||||
|
|
@ -392,7 +381,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
|
|||
misc.ApplyDAOHardFork(statedb)
|
||||
}
|
||||
|
||||
if config.IsPrague(b.header.Number, b.header.Time) || config.IsVerkle(b.header.Number, b.header.Time) {
|
||||
if config.IsPrague(b.header.Number, b.header.Time) || config.IsUBT(b.header.Number, b.header.Time) {
|
||||
// EIP-2935
|
||||
blockContext := NewEVMBlockContext(b.header, cm, &b.header.Coinbase)
|
||||
blockContext.Random = &common.Hash{} // enable post-merge instruction set
|
||||
|
|
@ -411,11 +400,22 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
|
|||
b.header.RequestsHash = &reqHash
|
||||
}
|
||||
|
||||
body := types.Body{Transactions: b.txs, Uncles: b.uncles, Withdrawals: b.withdrawals}
|
||||
block, err := b.engine.FinalizeAndAssemble(context.Background(), cm, b.header, statedb, &body, b.receipts)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
body := types.Body{
|
||||
Transactions: b.txs,
|
||||
Uncles: b.uncles,
|
||||
Withdrawals: b.withdrawals,
|
||||
}
|
||||
if !config.IsShanghai(b.header.Number, b.header.Time) {
|
||||
if body.Withdrawals != nil {
|
||||
panic("unexpected withdrawal before shanghai")
|
||||
}
|
||||
} else {
|
||||
if body.Withdrawals == nil {
|
||||
body.Withdrawals = make([]*types.Withdrawal, 0)
|
||||
}
|
||||
}
|
||||
// Assemble the block for delivery.
|
||||
block := AssembleBlock(b.engine, cm, b.header, statedb, &body, b.receipts)
|
||||
|
||||
// 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))
|
||||
|
|
@ -430,8 +430,8 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
|
|||
|
||||
// Forcibly use hash-based state scheme for retaining all nodes in disk.
|
||||
var triedbConfig *triedb.Config = triedb.HashDefaults
|
||||
if config.IsVerkle(config.ChainID, 0) {
|
||||
triedbConfig = triedb.VerkleDefaults
|
||||
if config.IsUBT(config.ChainID, 0) {
|
||||
triedbConfig = triedb.UBTDefaults
|
||||
}
|
||||
triedb := triedb.NewDatabase(db, triedbConfig)
|
||||
defer triedb.Close()
|
||||
|
|
@ -479,8 +479,8 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
|
|||
func GenerateChainWithGenesis(genesis *Genesis, engine consensus.Engine, n int, gen func(int, *BlockGen)) (ethdb.Database, []*types.Block, []types.Receipts) {
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
var triedbConfig *triedb.Config = triedb.HashDefaults
|
||||
if genesis.Config != nil && genesis.Config.IsVerkle(genesis.Config.ChainID, 0) {
|
||||
triedbConfig = triedb.VerkleDefaults
|
||||
if genesis.Config != nil && genesis.Config.IsUBT(genesis.Config.ChainID, 0) {
|
||||
triedbConfig = triedb.UBTDefaults
|
||||
}
|
||||
genesisTriedb := triedb.NewDatabase(db, triedbConfig)
|
||||
block, err := genesis.Commit(db, genesisTriedb, nil)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -129,22 +129,23 @@ func ReadGenesis(db ethdb.Database) (*Genesis, error) {
|
|||
}
|
||||
|
||||
// hashAlloc computes the state root according to the genesis specification.
|
||||
func hashAlloc(ga *types.GenesisAlloc, isVerkle bool) (common.Hash, error) {
|
||||
func hashAlloc(ga *types.GenesisAlloc, isUBT bool) (common.Hash, error) {
|
||||
// If a genesis-time verkle trie is requested, create a trie config
|
||||
// with the verkle trie enabled so that the tree can be initialized
|
||||
// as such.
|
||||
var config *triedb.Config
|
||||
if isVerkle {
|
||||
if isUBT {
|
||||
config = &triedb.Config{
|
||||
PathDB: pathdb.Defaults,
|
||||
IsVerkle: true,
|
||||
PathDB: pathdb.Defaults,
|
||||
IsUBT: true,
|
||||
BinTrieGroupDepth: triedb.UBTDefaults.BinTrieGroupDepth,
|
||||
}
|
||||
}
|
||||
// Create an ephemeral in-memory database for computing hash,
|
||||
// all the derived states will be discarded to not pollute disk.
|
||||
emptyRoot := types.EmptyRootHash
|
||||
if isVerkle {
|
||||
emptyRoot = types.EmptyVerkleHash
|
||||
if isUBT {
|
||||
emptyRoot = types.EmptyBinaryHash
|
||||
}
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
statedb, err := state.New(emptyRoot, state.NewDatabase(triedb.NewDatabase(db, config), nil))
|
||||
|
|
@ -168,8 +169,8 @@ func hashAlloc(ga *types.GenesisAlloc, isVerkle bool) (common.Hash, error) {
|
|||
// generated states will be persisted into the given database.
|
||||
func flushAlloc(ga *types.GenesisAlloc, triedb *triedb.Database, tracer *tracing.Hooks) (common.Hash, error) {
|
||||
emptyRoot := types.EmptyRootHash
|
||||
if triedb.IsVerkle() {
|
||||
emptyRoot = types.EmptyVerkleHash
|
||||
if triedb.IsUBT() {
|
||||
emptyRoot = types.EmptyBinaryHash
|
||||
}
|
||||
statedb, err := state.New(emptyRoot, state.NewDatabase(triedb, nil))
|
||||
if err != nil {
|
||||
|
|
@ -276,10 +277,10 @@ func (e *GenesisMismatchError) Error() string {
|
|||
|
||||
// ChainOverrides contains the changes to chain config.
|
||||
type ChainOverrides struct {
|
||||
OverrideOsaka *uint64
|
||||
OverrideBPO1 *uint64
|
||||
OverrideBPO2 *uint64
|
||||
OverrideVerkle *uint64
|
||||
OverrideOsaka *uint64
|
||||
OverrideBPO1 *uint64
|
||||
OverrideBPO2 *uint64
|
||||
OverrideUBT *uint64
|
||||
}
|
||||
|
||||
// apply applies the chain overrides on the supplied chain config.
|
||||
|
|
@ -296,8 +297,8 @@ func (o *ChainOverrides) apply(cfg *params.ChainConfig) error {
|
|||
if o.OverrideBPO2 != nil {
|
||||
cfg.BPO2Time = o.OverrideBPO2
|
||||
}
|
||||
if o.OverrideVerkle != nil {
|
||||
cfg.VerkleTime = o.OverrideVerkle
|
||||
if o.OverrideUBT != nil {
|
||||
cfg.UBTTime = o.OverrideUBT
|
||||
}
|
||||
return cfg.CheckConfigForkOrder()
|
||||
}
|
||||
|
|
@ -469,15 +470,15 @@ func (g *Genesis) chainConfigOrDefault(ghash common.Hash, stored *params.ChainCo
|
|||
}
|
||||
}
|
||||
|
||||
// IsVerkle indicates whether the state is already stored in a verkle
|
||||
// IsUBT indicates whether the state is already stored in a verkle
|
||||
// tree at genesis time.
|
||||
func (g *Genesis) IsVerkle() bool {
|
||||
return g.Config.IsVerkleGenesis()
|
||||
func (g *Genesis) IsUBT() bool {
|
||||
return g.Config.IsUBTGenesis()
|
||||
}
|
||||
|
||||
// ToBlock returns the genesis block according to genesis specification.
|
||||
func (g *Genesis) ToBlock() *types.Block {
|
||||
root, err := hashAlloc(&g.Alloc, g.IsVerkle())
|
||||
root, err := hashAlloc(&g.Alloc, g.IsUBT())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
|
@ -609,24 +610,24 @@ func (g *Genesis) MustCommit(db ethdb.Database, triedb *triedb.Database) *types.
|
|||
return block
|
||||
}
|
||||
|
||||
// EnableVerkleAtGenesis indicates whether the verkle fork should be activated
|
||||
// EnableUBTAtGenesis indicates whether the verkle fork should be activated
|
||||
// at genesis. This is a temporary solution only for verkle devnet testing, where
|
||||
// verkle fork is activated at genesis, and the configured activation date has
|
||||
// already passed.
|
||||
//
|
||||
// In production networks (mainnet and public testnets), verkle activation always
|
||||
// occurs after the genesis block, making this function irrelevant in those cases.
|
||||
func EnableVerkleAtGenesis(db ethdb.Database, genesis *Genesis) (bool, error) {
|
||||
func EnableUBTAtGenesis(db ethdb.Database, genesis *Genesis) (bool, error) {
|
||||
if genesis != nil {
|
||||
if genesis.Config == nil {
|
||||
return false, errGenesisNoConfig
|
||||
}
|
||||
return genesis.Config.EnableVerkleAtGenesis, nil
|
||||
return genesis.Config.EnableUBTAtGenesis, nil
|
||||
}
|
||||
if ghash := rawdb.ReadCanonicalHash(db, 0); ghash != (common.Hash{}) {
|
||||
chainCfg := rawdb.ReadChainConfig(db, ghash)
|
||||
if chainCfg != nil {
|
||||
return chainCfg.EnableVerkleAtGenesis, nil
|
||||
return chainCfg.EnableUBTAtGenesis, nil
|
||||
}
|
||||
}
|
||||
return false, 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,34 +281,34 @@ func TestVerkleGenesisCommit(t *testing.T) {
|
|||
ArrowGlacierBlock: big.NewInt(0),
|
||||
GrayGlacierBlock: big.NewInt(0),
|
||||
MergeNetsplitBlock: nil,
|
||||
ShanghaiTime: &verkleTime,
|
||||
CancunTime: &verkleTime,
|
||||
PragueTime: &verkleTime,
|
||||
OsakaTime: &verkleTime,
|
||||
VerkleTime: &verkleTime,
|
||||
ShanghaiTime: &ubtTime,
|
||||
CancunTime: &ubtTime,
|
||||
PragueTime: &ubtTime,
|
||||
OsakaTime: &ubtTime,
|
||||
UBTTime: &ubtTime,
|
||||
TerminalTotalDifficulty: big.NewInt(0),
|
||||
EnableVerkleAtGenesis: true,
|
||||
EnableUBTAtGenesis: true,
|
||||
Ethash: nil,
|
||||
Clique: nil,
|
||||
BlobScheduleConfig: ¶ms.BlobScheduleConfig{
|
||||
Cancun: params.DefaultCancunBlobConfig,
|
||||
Prague: params.DefaultPragueBlobConfig,
|
||||
Osaka: params.DefaultOsakaBlobConfig,
|
||||
Verkle: params.DefaultPragueBlobConfig,
|
||||
UBT: params.DefaultPragueBlobConfig,
|
||||
},
|
||||
}
|
||||
|
||||
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}}},
|
||||
},
|
||||
}
|
||||
|
||||
expected := common.FromHex("1fd154971d9a386c4ec75fe7138c17efb569bfc2962e46e94a376ba997e3fadc")
|
||||
expected := common.FromHex("0870fd587c41dc778019de8c5cb3193fe4ef1f417976461952d3712ba39163f5")
|
||||
got := genesis.ToBlock().Root().Bytes()
|
||||
if !bytes.Equal(got, expected) {
|
||||
t.Fatalf("invalid genesis state root, expected %x, got %x", expected, got)
|
||||
|
|
@ -320,17 +320,18 @@ func TestVerkleGenesisCommit(t *testing.T) {
|
|||
config.NoAsyncFlush = true
|
||||
|
||||
triedb := triedb.NewDatabase(db, &triedb.Config{
|
||||
IsVerkle: 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
|
||||
if !triedb.IsVerkle() {
|
||||
t.Fatalf("expected trie to be verkle")
|
||||
// Test that the trie is a unified binary trie
|
||||
if !triedb.IsUBT() {
|
||||
t.Fatalf("expected trie to be a unified binary trie")
|
||||
}
|
||||
vdb := rawdb.NewTable(db, string(rawdb.VerklePrefix))
|
||||
if !rawdb.HasAccountTrieNode(vdb, nil) {
|
||||
|
|
|
|||
|
|
@ -107,6 +107,10 @@ var staticPrunePoints = map[HistoryMode]map[common.Hash]*PrunePoint{
|
|||
BlockNumber: 7836331,
|
||||
BlockHash: common.HexToHash("0xe6571beb68bf24dbd8a6ba354518996920c55a3f8d8fdca423e391b8ad071f22"),
|
||||
},
|
||||
params.HoodiGenesisHash: {
|
||||
BlockNumber: 60412,
|
||||
BlockHash: common.HexToHash("0x1562792812ef418eaafc8f1f093d84d9634971e9dd6b0771302eb5b9fd4d2c46"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ func (ts *TransitionState) Copy() *TransitionState {
|
|||
|
||||
// LoadTransitionState retrieves the Verkle transition state associated with
|
||||
// the given state root hash from the database.
|
||||
func LoadTransitionState(db ethdb.KeyValueReader, root common.Hash, isVerkle bool) *TransitionState {
|
||||
func LoadTransitionState(db ethdb.KeyValueReader, root common.Hash, isUBT bool) *TransitionState {
|
||||
var ts *TransitionState
|
||||
|
||||
data, _ := rawdb.ReadVerkleTransitionState(db, root)
|
||||
|
|
@ -97,10 +97,10 @@ func LoadTransitionState(db ethdb.KeyValueReader, root common.Hash, isVerkle boo
|
|||
// Initialize the first transition state, with the "ended"
|
||||
// field set to true if the database was created
|
||||
// as a verkle database.
|
||||
log.Debug("no transition state found, starting fresh", "verkle", isVerkle)
|
||||
log.Debug("no transition state found, starting fresh", "verkle", isUBT)
|
||||
|
||||
// Start with a fresh state
|
||||
ts = &TransitionState{Ended: isVerkle}
|
||||
ts = &TransitionState{Ended: isUBT}
|
||||
}
|
||||
return ts
|
||||
}
|
||||
|
|
|
|||
|
|
@ -175,7 +175,9 @@ func WriteFinalizedBlockHash(db ethdb.KeyValueWriter, hash common.Hash) {
|
|||
}
|
||||
|
||||
// ReadLastPivotNumber retrieves the number of the last pivot block. If the node
|
||||
// full synced, the last pivot will always be nil.
|
||||
// has never attempted snap sync, the last pivot will always be nil. The marker
|
||||
// is written during snap sync and never cleared, so that a rollback past the
|
||||
// pivot can re-enable snap sync.
|
||||
func ReadLastPivotNumber(db ethdb.KeyValueReader) *uint64 {
|
||||
data, _ := db.Get(lastPivotKey)
|
||||
if len(data) == 0 {
|
||||
|
|
|
|||
|
|
@ -157,6 +157,7 @@ func newTable(path string, name string, readMeter, writeMeter *metrics.Meter, si
|
|||
}
|
||||
meta, err = openFreezerFileForReadOnly(filepath.Join(path, fmt.Sprintf("%s.meta", name)))
|
||||
if err != nil {
|
||||
index.Close()
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
|
|
@ -166,6 +167,7 @@ func newTable(path string, name string, readMeter, writeMeter *metrics.Meter, si
|
|||
}
|
||||
meta, err = openFreezerFileForAppend(filepath.Join(path, fmt.Sprintf("%s.meta", name)))
|
||||
if err != nil {
|
||||
index.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
|
@ -173,6 +175,8 @@ func newTable(path string, name string, readMeter, writeMeter *metrics.Meter, si
|
|||
// is detected.
|
||||
metadata, err := newMetadata(meta)
|
||||
if err != nil {
|
||||
meta.Close()
|
||||
index.Close()
|
||||
return nil, err
|
||||
}
|
||||
// Create the table and repair any past inconsistency
|
||||
|
|
|
|||
|
|
@ -76,6 +76,11 @@ func copyFrom(srcPath, destPath string, offset uint64, before func(f *os.File) e
|
|||
// we do the final move.
|
||||
src.Close()
|
||||
|
||||
// Permanently persist the content into disk
|
||||
if err := f.Sync(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := f.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -129,6 +134,7 @@ func openFreezerFileForAppend(filename string) (*os.File, error) {
|
|||
}
|
||||
// Seek to end for append
|
||||
if _, err = file.Seek(0, io.SeekEnd); err != nil {
|
||||
file.Close()
|
||||
return nil, err
|
||||
}
|
||||
return file, nil
|
||||
|
|
|
|||
|
|
@ -20,13 +20,9 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/overlay"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/state/snapshot"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"github.com/ethereum/go-ethereum/trie/bintrie"
|
||||
"github.com/ethereum/go-ethereum/trie/transitiontrie"
|
||||
|
|
@ -34,8 +30,27 @@ import (
|
|||
"github.com/ethereum/go-ethereum/triedb"
|
||||
)
|
||||
|
||||
// DatabaseType represents the type of trie backing the state database.
|
||||
type DatabaseType int
|
||||
|
||||
const (
|
||||
// TypeMPT indicates a Merkle Patricia Trie (MPT) backed database.
|
||||
TypeMPT DatabaseType = iota
|
||||
|
||||
// TypeUBT indicates a Unified Binary Trie (UBT) backed database.
|
||||
TypeUBT
|
||||
)
|
||||
|
||||
// Is returns the flag indicating the database type equals to the given one.
|
||||
func (typ DatabaseType) Is(t DatabaseType) bool {
|
||||
return typ == t
|
||||
}
|
||||
|
||||
// Database wraps access to tries and contract code.
|
||||
type Database interface {
|
||||
// Type returns the trie type backing this database (MPT or UBT).
|
||||
Type() DatabaseType
|
||||
|
||||
// Reader returns a state reader associated with the specified state root.
|
||||
Reader(root common.Hash) (Reader, error)
|
||||
|
||||
|
|
@ -55,7 +70,7 @@ type Database interface {
|
|||
// Commit flushes all pending writes and finalizes the state transition,
|
||||
// committing the changes to the underlying storage. It returns an error
|
||||
// if the commit fails.
|
||||
Commit(update *stateUpdate) error
|
||||
Commit(update *StateUpdate) error
|
||||
}
|
||||
|
||||
// Trie is a Ethereum Merkle Patricia trie.
|
||||
|
|
@ -139,184 +154,27 @@ type Trie interface {
|
|||
// with the node that proves the absence of the key.
|
||||
Prove(key []byte, proofDb ethdb.KeyValueWriter) error
|
||||
|
||||
// IsVerkle returns true if the trie is verkle-tree based
|
||||
IsVerkle() bool
|
||||
}
|
||||
|
||||
// CachingDB is an implementation of Database interface. It leverages both trie and
|
||||
// state snapshot to provide functionalities for state access. It's meant to be a
|
||||
// long-live object and has a few caches inside for sharing between blocks.
|
||||
type CachingDB struct {
|
||||
triedb *triedb.Database
|
||||
codedb *CodeDB
|
||||
snap *snapshot.Tree
|
||||
// IsUBT returns true if the trie is unified binary trie based.
|
||||
IsUBT() bool
|
||||
}
|
||||
|
||||
// NewDatabase creates a state database with the provided data sources.
|
||||
func NewDatabase(triedb *triedb.Database, codedb *CodeDB) *CachingDB {
|
||||
if codedb == nil {
|
||||
codedb = NewCodeDB(triedb.Disk())
|
||||
}
|
||||
return &CachingDB{
|
||||
triedb: triedb,
|
||||
codedb: codedb,
|
||||
//
|
||||
// Deprecated, please use NewMPTDatabase or NewUBTDatabase directly.
|
||||
func NewDatabase(tdb *triedb.Database, codedb *CodeDB) Database {
|
||||
if tdb.IsUBT() {
|
||||
return NewUBTDatabase(tdb, codedb)
|
||||
}
|
||||
return NewMPTDatabase(tdb, codedb)
|
||||
}
|
||||
|
||||
// NewDatabaseForTesting is similar to NewDatabase, but it initializes the caching
|
||||
// db by using an ephemeral memory db with default config for testing.
|
||||
func NewDatabaseForTesting() *CachingDB {
|
||||
func NewDatabaseForTesting() Database {
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
return NewDatabase(triedb.NewDatabase(db, nil), NewCodeDB(db))
|
||||
}
|
||||
|
||||
// WithSnapshot configures the provided contract code cache. Note that this
|
||||
// registration must be performed before the cachingDB is used.
|
||||
func (db *CachingDB) WithSnapshot(snapshot *snapshot.Tree) *CachingDB {
|
||||
db.snap = snapshot
|
||||
return db
|
||||
}
|
||||
|
||||
// StateReader returns a state reader associated with the specified state root.
|
||||
func (db *CachingDB) StateReader(stateRoot common.Hash) (StateReader, error) {
|
||||
var readers []StateReader
|
||||
|
||||
// Configure the state reader using the standalone snapshot in hash mode.
|
||||
// This reader offers improved performance but is optional and only
|
||||
// partially useful if the snapshot is not fully generated.
|
||||
if db.TrieDB().Scheme() == rawdb.HashScheme && db.snap != nil {
|
||||
snap := db.snap.Snapshot(stateRoot)
|
||||
if snap != nil {
|
||||
readers = append(readers, newFlatReader(snap))
|
||||
}
|
||||
}
|
||||
// Configure the state reader using the path database in path mode.
|
||||
// This reader offers improved performance but is optional and only
|
||||
// partially useful if the snapshot data in path database is not
|
||||
// fully generated.
|
||||
if db.TrieDB().Scheme() == rawdb.PathScheme {
|
||||
reader, err := db.triedb.StateReader(stateRoot)
|
||||
if err == nil {
|
||||
readers = append(readers, newFlatReader(reader))
|
||||
}
|
||||
}
|
||||
// Configure the trie reader, which is expected to be available as the
|
||||
// gatekeeper unless the state is corrupted.
|
||||
tr, err := newTrieReader(stateRoot, db.triedb)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
readers = append(readers, tr)
|
||||
|
||||
return newMultiStateReader(readers...)
|
||||
}
|
||||
|
||||
// Reader implements Database, returning a reader associated with the specified
|
||||
// state root.
|
||||
func (db *CachingDB) Reader(stateRoot common.Hash) (Reader, error) {
|
||||
sr, err := db.StateReader(stateRoot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newReader(db.codedb.Reader(), sr), nil
|
||||
}
|
||||
|
||||
// ReadersWithCacheStats creates a pair of state readers that share the same
|
||||
// underlying state reader and internal state cache, while maintaining separate
|
||||
// statistics respectively.
|
||||
func (db *CachingDB) ReadersWithCacheStats(stateRoot common.Hash) (Reader, Reader, error) {
|
||||
r, err := db.StateReader(stateRoot)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
sr := newStateReaderWithCache(r)
|
||||
ra := newReader(db.codedb.Reader(), newStateReaderWithStats(sr))
|
||||
rb := newReader(db.codedb.Reader(), newStateReaderWithStats(sr))
|
||||
return ra, rb, nil
|
||||
}
|
||||
|
||||
// OpenTrie opens the main account trie at a specific root hash.
|
||||
func (db *CachingDB) OpenTrie(root common.Hash) (Trie, error) {
|
||||
if db.triedb.IsVerkle() {
|
||||
ts := overlay.LoadTransitionState(db.TrieDB().Disk(), root, db.triedb.IsVerkle())
|
||||
if ts.InTransition() {
|
||||
panic("state tree transition isn't supported yet")
|
||||
}
|
||||
if ts.Transitioned() {
|
||||
// Use BinaryTrie instead of VerkleTrie when IsVerkle is set
|
||||
// (IsVerkle actually means Binary Trie mode in this codebase)
|
||||
return bintrie.NewBinaryTrie(root, db.triedb)
|
||||
}
|
||||
}
|
||||
tr, err := trie.NewStateTrie(trie.StateTrieID(root), db.triedb)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tr, nil
|
||||
}
|
||||
|
||||
// OpenStorageTrie opens the storage trie of an account.
|
||||
func (db *CachingDB) OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash, self Trie) (Trie, error) {
|
||||
if db.triedb.IsVerkle() {
|
||||
return self, nil
|
||||
}
|
||||
tr, err := trie.NewStateTrie(trie.StorageTrieID(stateRoot, crypto.Keccak256Hash(address.Bytes()), root), db.triedb)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tr, nil
|
||||
}
|
||||
|
||||
// TrieDB retrieves any intermediate trie-node caching layer.
|
||||
func (db *CachingDB) TrieDB() *triedb.Database {
|
||||
return db.triedb
|
||||
}
|
||||
|
||||
// Snapshot returns the underlying state snapshot.
|
||||
func (db *CachingDB) Snapshot() *snapshot.Tree {
|
||||
return db.snap
|
||||
}
|
||||
|
||||
// Commit flushes all pending writes and finalizes the state transition,
|
||||
// committing the changes to the underlying storage. It returns an error
|
||||
// if the commit fails.
|
||||
func (db *CachingDB) Commit(update *stateUpdate) error {
|
||||
// Short circuit if nothing to commit
|
||||
if update.empty() {
|
||||
return nil
|
||||
}
|
||||
// Commit dirty contract code if any exists
|
||||
if len(update.codes) > 0 {
|
||||
batch := db.codedb.NewBatchWithSize(len(update.codes))
|
||||
for _, code := range update.codes {
|
||||
batch.Put(code.hash, code.blob)
|
||||
}
|
||||
if err := batch.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// If snapshotting is enabled, update the snapshot tree with this new version
|
||||
if db.snap != nil && db.snap.Snapshot(update.originRoot) != nil {
|
||||
if err := db.snap.Update(update.root, update.originRoot, update.accounts, update.storages); err != nil {
|
||||
log.Warn("Failed to update snapshot tree", "from", update.originRoot, "to", update.root, "err", err)
|
||||
}
|
||||
// Keep 128 diff layers in the memory, persistent layer is 129th.
|
||||
// - head layer is paired with HEAD state
|
||||
// - head-1 layer is paired with HEAD-1 state
|
||||
// - head-127 layer(bottom-most diff layer) is paired with HEAD-127 state
|
||||
if err := db.snap.Cap(update.root, TriesInMemory); err != nil {
|
||||
log.Warn("Failed to cap snapshot tree", "root", update.root, "layers", TriesInMemory, "err", err)
|
||||
}
|
||||
}
|
||||
return db.triedb.Update(update.root, update.originRoot, update.blockNumber, update.nodes, update.stateSet())
|
||||
}
|
||||
|
||||
// Iteratee returns a state iteratee associated with the specified state root,
|
||||
// through which the account iterator and storage iterator can be created.
|
||||
func (db *CachingDB) Iteratee(root common.Hash) (Iteratee, error) {
|
||||
return newStateIteratee(!db.triedb.IsVerkle(), root, db.triedb, db.snap)
|
||||
}
|
||||
|
||||
// mustCopyTrie returns a deep-copied trie.
|
||||
func mustCopyTrie(t Trie) Trie {
|
||||
switch t := t.(type) {
|
||||
|
|
@ -324,6 +182,8 @@ func mustCopyTrie(t Trie) Trie {
|
|||
return t.Copy()
|
||||
case *transitiontrie.TransitionTrie:
|
||||
return t.Copy()
|
||||
case *bintrie.BinaryTrie:
|
||||
return t.Copy()
|
||||
default:
|
||||
panic(fmt.Errorf("unknown trie type %T", t))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -223,6 +223,12 @@ type HistoricDB struct {
|
|||
codedb *CodeDB
|
||||
}
|
||||
|
||||
// Type returns the trie type of the underlying database.
|
||||
func (db *HistoricDB) Type() DatabaseType {
|
||||
// TODO(rjl493456442) support UBT in the future
|
||||
return TypeMPT
|
||||
}
|
||||
|
||||
// NewHistoricDatabase creates a historic state database.
|
||||
func NewHistoricDatabase(triedb *triedb.Database, codedb *CodeDB) *HistoricDB {
|
||||
return &HistoricDB{
|
||||
|
|
@ -291,7 +297,7 @@ func (db *HistoricDB) TrieDB() *triedb.Database {
|
|||
// Commit flushes all pending writes and finalizes the state transition,
|
||||
// committing the changes to the underlying storage. It returns an error
|
||||
// if the commit fails.
|
||||
func (db *HistoricDB) Commit(update *stateUpdate) error {
|
||||
func (db *HistoricDB) Commit(update *StateUpdate) error {
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/state/snapshot"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"github.com/ethereum/go-ethereum/triedb"
|
||||
)
|
||||
|
|
@ -57,9 +58,9 @@ type AccountIterator interface {
|
|||
// An error will be returned if the preimage is not available.
|
||||
Address() (common.Address, error)
|
||||
|
||||
// Account returns the RLP encoded account the iterator is currently at.
|
||||
// Account returns the account the iterator is currently at.
|
||||
// An error will be retained if the iterator becomes invalid.
|
||||
Account() []byte
|
||||
Account() *types.StateAccount
|
||||
}
|
||||
|
||||
// StorageIterator is an iterator to step over the specific storage in the
|
||||
|
|
@ -73,7 +74,7 @@ type StorageIterator interface {
|
|||
|
||||
// Slot returns the storage slot the iterator is currently at. An error will
|
||||
// be retained if the iterator becomes invalid.
|
||||
Slot() []byte
|
||||
Slot() common.Hash
|
||||
}
|
||||
|
||||
// Iteratee wraps the NewIterator methods for traversing the accounts and
|
||||
|
|
@ -131,10 +132,7 @@ func (ai *flatAccountIterator) Next() bool {
|
|||
// Error returns any failure that occurred during iteration, which might have
|
||||
// caused a premature iteration exit.
|
||||
func (ai *flatAccountIterator) Error() error {
|
||||
if ai.err != nil {
|
||||
return ai.err
|
||||
}
|
||||
return ai.it.Error()
|
||||
return errors.Join(ai.err, ai.it.Error())
|
||||
}
|
||||
|
||||
// Hash returns the hash of the account or storage slot the iterator is
|
||||
|
|
@ -165,8 +163,8 @@ func (ai *flatAccountIterator) Address() (common.Address, error) {
|
|||
// Account returns the account data the iterator is currently at. The account
|
||||
// data is encoded as slim format from the underlying iterator, the conversion
|
||||
// is required.
|
||||
func (ai *flatAccountIterator) Account() []byte {
|
||||
data, err := types.FullAccountRLP(ai.it.Account())
|
||||
func (ai *flatAccountIterator) Account() *types.StateAccount {
|
||||
data, err := types.FullAccount(ai.it.Account())
|
||||
if err != nil {
|
||||
ai.err = err
|
||||
return nil
|
||||
|
|
@ -176,6 +174,7 @@ func (ai *flatAccountIterator) Account() []byte {
|
|||
|
||||
// flatStorageIterator is a wrapper around the underlying flat state iterator.
|
||||
type flatStorageIterator struct {
|
||||
err error
|
||||
it snapshot.StorageIterator
|
||||
preimage PreimageReader
|
||||
}
|
||||
|
|
@ -190,13 +189,16 @@ func newFlatStorageIterator(it snapshot.StorageIterator, preimage PreimageReader
|
|||
// is exhausted or if an error occurs. Any error encountered is retained and
|
||||
// can be retrieved via Error().
|
||||
func (si *flatStorageIterator) Next() bool {
|
||||
if si.err != nil {
|
||||
return false
|
||||
}
|
||||
return si.it.Next()
|
||||
}
|
||||
|
||||
// Error returns any failure that occurred during iteration, which might have
|
||||
// caused a premature iteration exit.
|
||||
func (si *flatStorageIterator) Error() error {
|
||||
return si.it.Error()
|
||||
return errors.Join(si.err, si.it.Error())
|
||||
}
|
||||
|
||||
// Hash returns the hash of the account or storage slot the iterator is
|
||||
|
|
@ -225,14 +227,24 @@ func (si *flatStorageIterator) Key() (common.Hash, error) {
|
|||
}
|
||||
|
||||
// Slot returns the storage slot data the iterator is currently at.
|
||||
func (si *flatStorageIterator) Slot() []byte {
|
||||
return si.it.Slot()
|
||||
func (si *flatStorageIterator) Slot() common.Hash {
|
||||
// Perform the rlp-decode as the slot value is RLP-encoded
|
||||
blob := si.it.Slot()
|
||||
_, content, _, err := rlp.Split(blob)
|
||||
if err != nil {
|
||||
si.err = err
|
||||
return common.Hash{}
|
||||
}
|
||||
var value common.Hash
|
||||
value.SetBytes(content)
|
||||
return value
|
||||
}
|
||||
|
||||
// merkleIterator implements the Iterator interface, providing functions to traverse
|
||||
// the accounts or storages with the manner of Merkle-Patricia-Trie.
|
||||
type merkleIterator struct {
|
||||
tr Trie
|
||||
err error
|
||||
it *trie.Iterator
|
||||
account bool
|
||||
}
|
||||
|
|
@ -254,13 +266,16 @@ func newMerkleTrieIterator(tr Trie, start common.Hash, account bool) (*merkleIte
|
|||
// is exhausted or if an error occurs. Any error encountered is retained and
|
||||
// can be retrieved via Error().
|
||||
func (ti *merkleIterator) Next() bool {
|
||||
if ti.err != nil {
|
||||
return false
|
||||
}
|
||||
return ti.it.Next()
|
||||
}
|
||||
|
||||
// Error returns any failure that occurred during iteration, which might have
|
||||
// caused a premature iteration exit.
|
||||
func (ti *merkleIterator) Error() error {
|
||||
return ti.it.Err
|
||||
return errors.Join(ti.err, ti.it.Err)
|
||||
}
|
||||
|
||||
// Hash returns the hash of the account or storage slot the iterator is
|
||||
|
|
@ -287,11 +302,16 @@ func (ti *merkleIterator) Address() (common.Address, error) {
|
|||
}
|
||||
|
||||
// Account returns the account data the iterator is currently at.
|
||||
func (ti *merkleIterator) Account() []byte {
|
||||
func (ti *merkleIterator) Account() *types.StateAccount {
|
||||
if !ti.account {
|
||||
return nil
|
||||
}
|
||||
return ti.it.Value
|
||||
var account types.StateAccount
|
||||
if err := rlp.DecodeBytes(ti.it.Value, &account); err != nil {
|
||||
ti.err = err
|
||||
return nil
|
||||
}
|
||||
return &account
|
||||
}
|
||||
|
||||
// Key returns the raw storage slot key the iterator is currently at.
|
||||
|
|
@ -308,11 +328,19 @@ func (ti *merkleIterator) Key() (common.Hash, error) {
|
|||
}
|
||||
|
||||
// Slot returns the storage slot the iterator is currently at.
|
||||
func (ti *merkleIterator) Slot() []byte {
|
||||
func (ti *merkleIterator) Slot() common.Hash {
|
||||
if ti.account {
|
||||
return nil
|
||||
return common.Hash{}
|
||||
}
|
||||
return ti.it.Value
|
||||
// Perform the rlp-decode as the slot value is RLP-encoded
|
||||
_, content, _, err := rlp.Split(ti.it.Value)
|
||||
if err != nil {
|
||||
ti.err = err
|
||||
return common.Hash{}
|
||||
}
|
||||
var value common.Hash
|
||||
value.SetBytes(content)
|
||||
return value
|
||||
}
|
||||
|
||||
// stateIteratee implements Iteratee interface, providing the state traversal
|
||||
|
|
@ -430,6 +458,6 @@ func (e exhaustedIterator) Key() (common.Hash, error) {
|
|||
return common.Hash{}, nil
|
||||
}
|
||||
|
||||
func (e exhaustedIterator) Slot() []byte {
|
||||
return nil
|
||||
func (e exhaustedIterator) Slot() common.Hash {
|
||||
return common.Hash{}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
|
||||
|
|
@ -45,7 +44,7 @@ func TestExhaustedIterator(t *testing.T) {
|
|||
if key, err := it.Key(); key != (common.Hash{}) || err != nil {
|
||||
t.Fatalf("Key() = %x, %v; want zero, nil", key, err)
|
||||
}
|
||||
if slot := it.Slot(); slot != nil {
|
||||
if slot := it.Slot(); slot != (common.Hash{}) {
|
||||
t.Fatalf("Slot() = %x, want nil", slot)
|
||||
}
|
||||
it.Release()
|
||||
|
|
@ -95,20 +94,16 @@ func testAccountIterator(t *testing.T, scheme string) {
|
|||
hashes = append(hashes, hash)
|
||||
|
||||
// Decode and verify account data.
|
||||
blob := acctIt.Account()
|
||||
if blob == nil {
|
||||
got := acctIt.Account()
|
||||
if got == nil {
|
||||
t.Fatalf("(%s) nil account at %x", scheme, hash)
|
||||
}
|
||||
var decoded types.StateAccount
|
||||
if err := rlp.DecodeBytes(blob, &decoded); err != nil {
|
||||
t.Fatalf("(%s) bad RLP at %x: %v", scheme, hash, err)
|
||||
}
|
||||
acc := addrByHash[hash]
|
||||
if decoded.Nonce != acc.nonce {
|
||||
t.Fatalf("(%s) nonce %x: got %d, want %d", scheme, hash, decoded.Nonce, acc.nonce)
|
||||
if got.Nonce != acc.nonce {
|
||||
t.Fatalf("(%s) nonce %x: got %d, want %d", scheme, hash, got.Nonce, acc.nonce)
|
||||
}
|
||||
if decoded.Balance.Cmp(acc.balance) != 0 {
|
||||
t.Fatalf("(%s) balance %x: got %v, want %v", scheme, hash, decoded.Balance, acc.balance)
|
||||
if got.Balance.Cmp(acc.balance) != 0 {
|
||||
t.Fatalf("(%s) balance %x: got %v, want %v", scheme, hash, got.Balance, acc.balance)
|
||||
}
|
||||
// Verify address preimage resolution.
|
||||
addr, err := acctIt.Address()
|
||||
|
|
@ -183,7 +178,7 @@ func testStorageIterator(t *testing.T, scheme string) {
|
|||
t.Fatalf("(%s) storage hashes not ascending for %x", scheme, acc.address)
|
||||
}
|
||||
prevHash = hash
|
||||
if storageIt.Slot() == nil {
|
||||
if storageIt.Slot() == (common.Hash{}) {
|
||||
t.Fatalf("(%s) nil slot at %x", scheme, hash)
|
||||
}
|
||||
// Check key preimage resolution on first slot.
|
||||
|
|
|
|||
187
core/state/database_mpt.go
Normal file
187
core/state/database_mpt.go
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
// 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 (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/state/snapshot"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"github.com/ethereum/go-ethereum/triedb"
|
||||
)
|
||||
|
||||
// MPTDatabase is an implementation of Database interface for Merkle Patricia Tries.
|
||||
// It leverages both trie and state snapshot to provide functionalities for state
|
||||
// access.
|
||||
type MPTDatabase struct {
|
||||
triedb *triedb.Database
|
||||
codedb *CodeDB
|
||||
snap *snapshot.Tree
|
||||
}
|
||||
|
||||
// Type returns Merkle, indicating this database is backed by a Merkle Patricia Trie.
|
||||
func (db *MPTDatabase) Type() DatabaseType { return TypeMPT }
|
||||
|
||||
// NewMPTDatabase creates a state database with the Merkle Patricia Trie manner.
|
||||
func NewMPTDatabase(tdb *triedb.Database, codedb *CodeDB) *MPTDatabase {
|
||||
if codedb == nil {
|
||||
codedb = NewCodeDB(tdb.Disk())
|
||||
}
|
||||
return &MPTDatabase{
|
||||
triedb: tdb,
|
||||
codedb: codedb,
|
||||
}
|
||||
}
|
||||
|
||||
// WithSnapshot configures the provided state snapshot. Note that this
|
||||
// registration must be performed before the MPTDatabase is used.
|
||||
func (db *MPTDatabase) WithSnapshot(snapshot *snapshot.Tree) Database {
|
||||
db.snap = snapshot
|
||||
return db
|
||||
}
|
||||
|
||||
// StateReader returns a state reader associated with the specified state root.
|
||||
func (db *MPTDatabase) StateReader(stateRoot common.Hash) (StateReader, error) {
|
||||
var readers []StateReader
|
||||
|
||||
// Configure the state reader using the standalone snapshot in hash mode.
|
||||
// This reader offers improved performance but is optional and only
|
||||
// partially useful if the snapshot is not fully generated.
|
||||
if db.TrieDB().Scheme() == rawdb.HashScheme && db.snap != nil {
|
||||
snap := db.snap.Snapshot(stateRoot)
|
||||
if snap != nil {
|
||||
readers = append(readers, newFlatReader(snap))
|
||||
}
|
||||
}
|
||||
// Configure the state reader using the path database in path mode.
|
||||
// This reader offers improved performance but is optional and only
|
||||
// partially useful if the snapshot data in path database is not
|
||||
// fully generated.
|
||||
if db.TrieDB().Scheme() == rawdb.PathScheme {
|
||||
reader, err := db.triedb.StateReader(stateRoot)
|
||||
if err == nil {
|
||||
readers = append(readers, newFlatReader(reader))
|
||||
}
|
||||
}
|
||||
// Configure the trie reader, which is expected to be available as the
|
||||
// gatekeeper unless the state is corrupted.
|
||||
tr, err := newMPTTrieReader(stateRoot, db.triedb)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
readers = append(readers, tr)
|
||||
|
||||
return newMultiStateReader(readers...)
|
||||
}
|
||||
|
||||
// Reader implements Database, returning a reader associated with the specified
|
||||
// state root.
|
||||
func (db *MPTDatabase) Reader(stateRoot common.Hash) (Reader, error) {
|
||||
sr, err := db.StateReader(stateRoot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newReader(db.codedb.Reader(), sr), nil
|
||||
}
|
||||
|
||||
// ReadersWithCacheStats creates a pair of state readers that share the same
|
||||
// underlying state reader and internal state cache, while maintaining separate
|
||||
// statistics respectively.
|
||||
func (db *MPTDatabase) ReadersWithCacheStats(stateRoot common.Hash) (Reader, Reader, error) {
|
||||
r, err := db.StateReader(stateRoot)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
sr := newStateReaderWithCache(r)
|
||||
ra := newReader(db.codedb.Reader(), newStateReaderWithStats(sr))
|
||||
rb := newReader(db.codedb.Reader(), newStateReaderWithStats(sr))
|
||||
return ra, rb, nil
|
||||
}
|
||||
|
||||
// OpenTrie opens the main account trie at a specific root hash.
|
||||
func (db *MPTDatabase) OpenTrie(root common.Hash) (Trie, error) {
|
||||
tr, err := trie.NewStateTrie(trie.StateTrieID(root), db.triedb)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tr, nil
|
||||
}
|
||||
|
||||
// OpenStorageTrie opens the storage trie of an account.
|
||||
func (db *MPTDatabase) OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash, self Trie) (Trie, error) {
|
||||
tr, err := trie.NewStateTrie(trie.StorageTrieID(stateRoot, crypto.Keccak256Hash(address.Bytes()), root), db.triedb)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tr, nil
|
||||
}
|
||||
|
||||
// TrieDB retrieves any intermediate trie-node caching layer.
|
||||
func (db *MPTDatabase) TrieDB() *triedb.Database {
|
||||
return db.triedb
|
||||
}
|
||||
|
||||
// Commit flushes all pending writes and finalizes the state transition,
|
||||
// committing the changes to the underlying storage. It returns an error
|
||||
// if the commit fails.
|
||||
func (db *MPTDatabase) Commit(update *StateUpdate) error {
|
||||
// Short circuit if nothing to commit
|
||||
if update.Empty() {
|
||||
return nil
|
||||
}
|
||||
// Commit dirty contract code if any exists
|
||||
if len(update.Codes) > 0 {
|
||||
batch := db.codedb.NewBatchWithSize(len(update.Codes))
|
||||
for _, code := range update.Codes {
|
||||
batch.Put(code.Hash, code.Blob)
|
||||
}
|
||||
if err := batch.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Encode the state mutations in the MPT format
|
||||
accounts, accountOrigin, storages, storageOrigin := update.EncodeMPTState()
|
||||
|
||||
// If snapshotting is enabled, update the snapshot tree with this new version
|
||||
if db.snap != nil && db.snap.Snapshot(update.OriginRoot) != nil {
|
||||
if err := db.snap.Update(update.Root, update.OriginRoot, accounts, storages); err != nil {
|
||||
log.Warn("Failed to update snapshot tree", "from", update.OriginRoot, "to", update.Root, "err", err)
|
||||
}
|
||||
// Keep 128 diff layers in the memory, persistent layer is 129th.
|
||||
// - head layer is paired with HEAD state
|
||||
// - head-1 layer is paired with HEAD-1 state
|
||||
// - head-127 layer(bottom-most diff layer) is paired with HEAD-127 state
|
||||
if err := db.snap.Cap(update.Root, TriesInMemory); err != nil {
|
||||
log.Warn("Failed to cap snapshot tree", "root", update.Root, "layers", TriesInMemory, "err", err)
|
||||
}
|
||||
}
|
||||
return db.triedb.Update(update.Root, update.OriginRoot, update.BlockNumber, update.Nodes, &triedb.StateSet{
|
||||
Accounts: accounts,
|
||||
AccountsOrigin: accountOrigin,
|
||||
Storages: storages,
|
||||
StoragesOrigin: storageOrigin,
|
||||
RawStorageKey: update.StorageKeyType == StorageKeyPlain,
|
||||
})
|
||||
}
|
||||
|
||||
// Iteratee returns a state iteratee associated with the specified state root,
|
||||
// through which the account iterator and storage iterator can be created.
|
||||
func (db *MPTDatabase) Iteratee(root common.Hash) (Iteratee, error) {
|
||||
return newStateIteratee(true, root, db.triedb, db.snap)
|
||||
}
|
||||
147
core/state/database_ubt.go
Normal file
147
core/state/database_ubt.go
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
// 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 (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/trie/bintrie"
|
||||
"github.com/ethereum/go-ethereum/triedb"
|
||||
)
|
||||
|
||||
// UBTDatabase is an implementation of Database interface for Unified Binary Trie.
|
||||
// It provides the same functionality as MPTDatabase but uses unified binary
|
||||
// trie for state hashing instead of Merkle Patricia Tries.
|
||||
type UBTDatabase struct {
|
||||
triedb *triedb.Database
|
||||
codedb *CodeDB
|
||||
}
|
||||
|
||||
// Type returns Binary, indicating this database is backed by a Universal Binary Trie.
|
||||
func (db *UBTDatabase) Type() DatabaseType { return TypeUBT }
|
||||
|
||||
// NewUBTDatabase creates a state database with the Unified binary trie manner.
|
||||
func NewUBTDatabase(triedb *triedb.Database, codedb *CodeDB) *UBTDatabase {
|
||||
if codedb == nil {
|
||||
codedb = NewCodeDB(triedb.Disk())
|
||||
}
|
||||
return &UBTDatabase{
|
||||
triedb: triedb,
|
||||
codedb: codedb,
|
||||
}
|
||||
}
|
||||
|
||||
// StateReader returns a state reader associated with the specified state root.
|
||||
func (db *UBTDatabase) StateReader(stateRoot common.Hash) (StateReader, error) {
|
||||
var readers []StateReader
|
||||
|
||||
// Configure the state reader using the path database in path mode.
|
||||
// This reader offers improved performance but is optional and only
|
||||
// partially useful if the snapshot data in path database is not
|
||||
// fully generated.
|
||||
if db.TrieDB().Scheme() == rawdb.PathScheme {
|
||||
reader, err := db.triedb.StateReader(stateRoot)
|
||||
if err == nil {
|
||||
readers = append(readers, newFlatReader(reader))
|
||||
}
|
||||
}
|
||||
// Configure the trie reader, which is expected to be available as the
|
||||
// gatekeeper unless the state is corrupted.
|
||||
tr, err := newUBTTrieReader(stateRoot, db.triedb)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
readers = append(readers, tr)
|
||||
|
||||
return newMultiStateReader(readers...)
|
||||
}
|
||||
|
||||
// Reader implements Database, returning a reader associated with the specified
|
||||
// state root.
|
||||
func (db *UBTDatabase) Reader(stateRoot common.Hash) (Reader, error) {
|
||||
sr, err := db.StateReader(stateRoot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newReader(db.codedb.Reader(), sr), nil
|
||||
}
|
||||
|
||||
// ReadersWithCacheStats creates a pair of state readers that share the same
|
||||
// underlying state reader and internal state cache, while maintaining separate
|
||||
// statistics respectively.
|
||||
func (db *UBTDatabase) ReadersWithCacheStats(stateRoot common.Hash) (Reader, Reader, error) {
|
||||
r, err := db.StateReader(stateRoot)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
sr := newStateReaderWithCache(r)
|
||||
ra := newReader(db.codedb.Reader(), newStateReaderWithStats(sr))
|
||||
rb := newReader(db.codedb.Reader(), newStateReaderWithStats(sr))
|
||||
return ra, rb, nil
|
||||
}
|
||||
|
||||
// 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, db.triedb.BinTrieGroupDepth())
|
||||
}
|
||||
|
||||
// OpenStorageTrie opens the storage trie of an account. In binary trie mode,
|
||||
// all state objects share one unified trie, so the main trie is returned.
|
||||
func (db *UBTDatabase) OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash, self Trie) (Trie, error) {
|
||||
return self, nil
|
||||
}
|
||||
|
||||
// TrieDB retrieves any intermediate trie-node caching layer.
|
||||
func (db *UBTDatabase) TrieDB() *triedb.Database {
|
||||
return db.triedb
|
||||
}
|
||||
|
||||
// Commit flushes all pending writes and finalizes the state transition,
|
||||
// committing the changes to the underlying storage. It returns an error
|
||||
// if the commit fails.
|
||||
func (db *UBTDatabase) Commit(update *StateUpdate) error {
|
||||
// Short circuit if nothing to commit
|
||||
if update.Empty() {
|
||||
return nil
|
||||
}
|
||||
// Commit dirty contract code if any exists
|
||||
if len(update.Codes) > 0 {
|
||||
batch := db.codedb.NewBatchWithSize(len(update.Codes))
|
||||
for _, code := range update.Codes {
|
||||
batch.Put(code.Hash, code.Blob)
|
||||
}
|
||||
if err := batch.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Encode the state mutations in the UBT format
|
||||
accounts, accountOrigin, storages, storageOrigin := update.EncodeUBTState()
|
||||
|
||||
return db.triedb.Update(update.Root, update.OriginRoot, update.BlockNumber, update.Nodes, &triedb.StateSet{
|
||||
Accounts: accounts,
|
||||
AccountsOrigin: accountOrigin,
|
||||
Storages: storages,
|
||||
StoragesOrigin: storageOrigin,
|
||||
RawStorageKey: update.StorageKeyType == StorageKeyPlain,
|
||||
})
|
||||
}
|
||||
|
||||
// Iteratee returns a state iteratee associated with the specified state root,
|
||||
// through which the account iterator and storage iterator can be created.
|
||||
func (db *UBTDatabase) Iteratee(root common.Hash) (Iteratee, error) {
|
||||
return newStateIteratee(false, root, db.triedb, nil)
|
||||
}
|
||||
|
|
@ -24,9 +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/log"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/trie/bintrie"
|
||||
)
|
||||
|
||||
|
|
@ -115,7 +113,7 @@ func (d iterativeDump) OnRoot(root common.Hash) {
|
|||
|
||||
// DumpToCollector iterates the state according to the given options and inserts
|
||||
// the items into a collector for aggregation or serialization.
|
||||
func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []byte) {
|
||||
func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []byte, err error) {
|
||||
// Sanitize the input to allow nil configs
|
||||
if conf == nil {
|
||||
conf = new(DumpConfig)
|
||||
|
|
@ -131,7 +129,7 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
|
|||
|
||||
iteratee, err := s.db.Iteratee(s.originalRoot)
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
var startHash common.Hash
|
||||
if conf.Start != nil {
|
||||
|
|
@ -139,14 +137,17 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
|
|||
}
|
||||
acctIt, err := iteratee.NewAccountIterator(startHash)
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
defer acctIt.Release()
|
||||
|
||||
for acctIt.Next() {
|
||||
var data types.StateAccount
|
||||
if err := rlp.DecodeBytes(acctIt.Account(), &data); err != nil {
|
||||
panic(err)
|
||||
data := acctIt.Account()
|
||||
if err := acctIt.Error(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if data == nil {
|
||||
return nil, fmt.Errorf("unexpected nil account value")
|
||||
}
|
||||
var (
|
||||
account = DumpAccount{
|
||||
|
|
@ -168,7 +169,7 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
|
|||
address = &addrBytes
|
||||
account.Address = address
|
||||
}
|
||||
obj := newObject(s, addrBytes, &data)
|
||||
obj := newObject(s, addrBytes, data)
|
||||
if !conf.SkipCode {
|
||||
account.Code = obj.Code()
|
||||
}
|
||||
|
|
@ -177,20 +178,19 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
|
|||
|
||||
storageIt, err := iteratee.NewStorageIterator(acctIt.Hash(), common.Hash{})
|
||||
if err != nil {
|
||||
log.Error("Failed to load storage trie", "err", err)
|
||||
continue
|
||||
return nil, err
|
||||
}
|
||||
for storageIt.Next() {
|
||||
_, content, _, err := rlp.Split(storageIt.Slot())
|
||||
if err != nil {
|
||||
log.Error("Failed to decode the value returned by iterator", "error", err)
|
||||
continue
|
||||
val := storageIt.Slot()
|
||||
if err := storageIt.Error(); err != nil {
|
||||
storageIt.Release()
|
||||
return nil, err
|
||||
}
|
||||
key, err := storageIt.Key()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
account.Storage[key] = common.Bytes2Hex(content)
|
||||
account.Storage[key] = common.Bytes2Hex(common.TrimLeftZeroes(val[:]))
|
||||
}
|
||||
storageIt.Release()
|
||||
}
|
||||
|
|
@ -211,7 +211,7 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
|
|||
log.Warn("Dump incomplete due to missing preimages", "missing", missingPreimages)
|
||||
}
|
||||
log.Info("Trie dumping complete", "accounts", accounts, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||
return nextKey
|
||||
return nextKey, nil
|
||||
}
|
||||
|
||||
// DumpBinTrieLeaves collects all binary trie leaf nodes into the provided map.
|
||||
|
|
@ -242,7 +242,8 @@ func (s *StateDB) RawDump(opts *DumpConfig) Dump {
|
|||
dump := &Dump{
|
||||
Accounts: make(map[string]DumpAccount),
|
||||
}
|
||||
dump.Next = s.DumpToCollector(dump, opts)
|
||||
next, _ := s.DumpToCollector(dump, opts)
|
||||
dump.Next = next
|
||||
return *dump
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -148,70 +148,28 @@ func (r *flatReader) Storage(addr common.Address, key common.Hash) (common.Hash,
|
|||
return value, nil
|
||||
}
|
||||
|
||||
// trieReader implements the StateReader interface, providing functions to access
|
||||
// state from the referenced trie.
|
||||
// mptTrieReader implements the StateReader interface, providing functions to
|
||||
// access state from the referenced Merkle-Patricia-tree.
|
||||
//
|
||||
// trieReader is safe for concurrent read.
|
||||
type trieReader struct {
|
||||
root common.Hash // State root which uniquely represent a state
|
||||
// mptTrieReader is safe for concurrent read.
|
||||
type mptTrieReader struct {
|
||||
root common.Hash // State root which uniquely represents a state
|
||||
db *triedb.Database // Database for loading trie
|
||||
|
||||
// Main trie, resolved in constructor. Note either the Merkle-Patricia-tree
|
||||
// or Verkle-tree is not safe for concurrent read.
|
||||
mainTrie Trie
|
||||
|
||||
mainTrie Trie // Main trie, resolved in constructor, not thread-safe
|
||||
subRoots map[common.Address]common.Hash // Set of storage roots, cached when the account is resolved
|
||||
subTries map[common.Address]Trie // Group of storage tries, cached when it's resolved
|
||||
lock sync.Mutex // Lock for protecting concurrent read
|
||||
}
|
||||
|
||||
// newTrieReader constructs a trie reader of the specific state. An error will be
|
||||
// returned if the associated trie specified by root is not existent.
|
||||
func newTrieReader(root common.Hash, db *triedb.Database) (*trieReader, error) {
|
||||
var (
|
||||
tr Trie
|
||||
err error
|
||||
)
|
||||
if !db.IsVerkle() {
|
||||
tr, err = trie.NewStateTrie(trie.StateTrieID(root), db)
|
||||
} else {
|
||||
// When IsVerkle() is true, create a BinaryTrie wrapped in TransitionTrie
|
||||
binTrie, binErr := bintrie.NewBinaryTrie(root, db)
|
||||
if binErr != nil {
|
||||
return nil, binErr
|
||||
}
|
||||
|
||||
// Based on the transition status, determine if the overlay
|
||||
// tree needs to be created, or if a single, target tree is
|
||||
// to be picked.
|
||||
ts := overlay.LoadTransitionState(db.Disk(), root, true)
|
||||
if ts.InTransition() {
|
||||
mpt, err := trie.NewStateTrie(trie.StateTrieID(ts.BaseRoot), db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tr = transitiontrie.NewTransitionTrie(mpt, binTrie, false)
|
||||
} else {
|
||||
// HACK: Use TransitionTrie with nil base as a wrapper to make BinaryTrie
|
||||
// satisfy the Trie interface. This works around the import cycle between
|
||||
// trie and trie/bintrie packages.
|
||||
//
|
||||
// TODO: In future PRs, refactor the package structure to avoid this hack:
|
||||
// - Option 1: Move common interfaces (Trie, NodeIterator) to a separate
|
||||
// package that both trie and trie/bintrie can import
|
||||
// - Option 2: Create a factory function in the trie package that returns
|
||||
// BinaryTrie as a Trie interface without direct import
|
||||
// - Option 3: Move BinaryTrie to the main trie package
|
||||
//
|
||||
// The current approach works but adds unnecessary overhead and complexity
|
||||
// by using TransitionTrie when there's no actual transition happening.
|
||||
tr = transitiontrie.NewTransitionTrie(nil, binTrie, false)
|
||||
}
|
||||
}
|
||||
// newMPTTrieReader constructs a Merkle-Patricia-tree reader of the specific state.
|
||||
// An error will be returned if the associated trie specified by root is not existent.
|
||||
func newMPTTrieReader(root common.Hash, db *triedb.Database) (*mptTrieReader, error) {
|
||||
tr, err := trie.NewStateTrie(trie.StateTrieID(root), db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &trieReader{
|
||||
return &mptTrieReader{
|
||||
root: root,
|
||||
db: db,
|
||||
mainTrie: tr,
|
||||
|
|
@ -221,7 +179,7 @@ func newTrieReader(root common.Hash, db *triedb.Database) (*trieReader, error) {
|
|||
}
|
||||
|
||||
// account is the inner version of Account and assumes the r.lock is already held.
|
||||
func (r *trieReader) account(addr common.Address) (*types.StateAccount, error) {
|
||||
func (r *mptTrieReader) account(addr common.Address) (*types.StateAccount, error) {
|
||||
account, err := r.mainTrie.GetAccount(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -236,9 +194,9 @@ func (r *trieReader) account(addr common.Address) (*types.StateAccount, error) {
|
|||
|
||||
// Account implements StateReader, retrieving the account specified by the address.
|
||||
//
|
||||
// An error will be returned if the trie state is corrupted. An nil account
|
||||
// An error will be returned if the trie state is corrupted. A nil account
|
||||
// will be returned if it's not existent in the trie.
|
||||
func (r *trieReader) Account(addr common.Address) (*types.StateAccount, error) {
|
||||
func (r *mptTrieReader) Account(addr common.Address) (*types.StateAccount, error) {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
|
|
@ -250,43 +208,118 @@ func (r *trieReader) Account(addr common.Address) (*types.StateAccount, error) {
|
|||
//
|
||||
// An error will be returned if the trie state is corrupted. An empty storage
|
||||
// slot will be returned if it's not existent in the trie.
|
||||
func (r *trieReader) Storage(addr common.Address, key common.Hash) (common.Hash, error) {
|
||||
func (r *mptTrieReader) Storage(addr common.Address, key common.Hash) (common.Hash, error) {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
var (
|
||||
tr Trie
|
||||
found bool
|
||||
value common.Hash
|
||||
)
|
||||
if r.db.IsVerkle() {
|
||||
tr = r.mainTrie
|
||||
} else {
|
||||
tr, found = r.subTries[addr]
|
||||
if !found {
|
||||
root, ok := r.subRoots[addr]
|
||||
tr, found := r.subTries[addr]
|
||||
if !found {
|
||||
root, ok := r.subRoots[addr]
|
||||
|
||||
// The storage slot is accessed without account caching. It's unexpected
|
||||
// behavior but try to resolve the account first anyway.
|
||||
if !ok {
|
||||
_, err := r.account(addr)
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
root = r.subRoots[addr]
|
||||
}
|
||||
var err error
|
||||
tr, err = trie.NewStateTrie(trie.StorageTrieID(r.root, crypto.Keccak256Hash(addr.Bytes()), root), r.db)
|
||||
// The storage slot is accessed without account caching. It's unexpected
|
||||
// behavior but try to resolve the account first anyway.
|
||||
if !ok {
|
||||
_, err := r.account(addr)
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
r.subTries[addr] = tr
|
||||
root = r.subRoots[addr]
|
||||
}
|
||||
var err error
|
||||
tr, err = trie.NewStateTrie(trie.StorageTrieID(r.root, crypto.Keccak256Hash(addr.Bytes()), root), r.db)
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
r.subTries[addr] = tr
|
||||
}
|
||||
ret, err := tr.GetStorage(addr, key.Bytes())
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
var value common.Hash
|
||||
value.SetBytes(ret)
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// ubtTrieReader implements the StateReader interface, providing functions to access
|
||||
// state from the referenced Unified-binary-trie.
|
||||
//
|
||||
// ubtTrieReader is safe for concurrent read.
|
||||
type ubtTrieReader struct {
|
||||
root common.Hash // State root which uniquely represents a state
|
||||
db *triedb.Database // Database for loading trie
|
||||
tr Trie // Referenced unified binary trie
|
||||
lock sync.Mutex // Lock for protecting concurrent read
|
||||
}
|
||||
|
||||
// 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, db.BinTrieGroupDepth())
|
||||
if binErr != nil {
|
||||
return nil, binErr
|
||||
}
|
||||
// Based on the transition status, determine if the overlay
|
||||
// tree needs to be created, or if a single, target tree is
|
||||
// to be picked.
|
||||
var (
|
||||
tr Trie
|
||||
ts = overlay.LoadTransitionState(db.Disk(), root, true)
|
||||
)
|
||||
if ts.InTransition() {
|
||||
mpt, err := trie.NewStateTrie(trie.StateTrieID(ts.BaseRoot), db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tr = transitiontrie.NewTransitionTrie(mpt, binTrie, false)
|
||||
} else {
|
||||
// HACK: Use TransitionTrie with nil base as a wrapper to make BinaryTrie
|
||||
// satisfy the Trie interface. This works around the import cycle between
|
||||
// trie and trie/bintrie packages.
|
||||
//
|
||||
// TODO: In future PRs, refactor the package structure to avoid this hack:
|
||||
// - Option 1: Move common interfaces (Trie, NodeIterator) to a separate
|
||||
// package that both trie and trie/bintrie can import
|
||||
// - Option 2: Create a factory function in the trie package that returns
|
||||
// BinaryTrie as a Trie interface without direct import
|
||||
// - Option 3: Move BinaryTrie to the main trie package
|
||||
//
|
||||
// The current approach works but adds unnecessary overhead and complexity
|
||||
// by using TransitionTrie when there's no actual transition happening.
|
||||
tr = transitiontrie.NewTransitionTrie(nil, binTrie, false)
|
||||
}
|
||||
return &ubtTrieReader{
|
||||
root: root,
|
||||
db: db,
|
||||
tr: tr,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Account implements StateReader, retrieving the account specified by the address.
|
||||
//
|
||||
// An error will be returned if the trie state is corrupted. A nil account
|
||||
// will be returned if it's not existent in the trie.
|
||||
func (r *ubtTrieReader) Account(addr common.Address) (*types.StateAccount, error) {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
return r.tr.GetAccount(addr)
|
||||
}
|
||||
|
||||
// Storage implements StateReader, retrieving the storage slot specified by the
|
||||
// address and slot key.
|
||||
//
|
||||
// An error will be returned if the trie state is corrupted. An empty storage
|
||||
// slot will be returned if it's not existent in the trie.
|
||||
func (r *ubtTrieReader) Storage(addr common.Address, key common.Hash) (common.Hash, error) {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
ret, err := r.tr.GetStorage(addr, key.Bytes())
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
var value common.Hash
|
||||
value.SetBytes(ret)
|
||||
return value, nil
|
||||
}
|
||||
|
|
|
|||
247
core/state/reader_eip_7928.go
Normal file
247
core/state/reader_eip_7928.go
Normal file
|
|
@ -0,0 +1,247 @@
|
|||
// 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 (
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||
)
|
||||
|
||||
// The EIP27928 reader utilizes a hierarchical architecture to optimize state
|
||||
// access during block execution:
|
||||
//
|
||||
// - Base layer: The reader is initialized with the pre-transition state root,
|
||||
// providing the access of the state.
|
||||
//
|
||||
// - Prefetching Layer: This base reader is wrapped by newPrefetchStateReader.
|
||||
// Using an Access List hint, it asynchronously fetches required state data
|
||||
// in the background, minimizing I/O blocking during transaction processing.
|
||||
//
|
||||
// - Execution Layer: To support parallel transaction execution within the EIP
|
||||
// 7928 context, readers are wrapped in ReaderWithBlockLevelAccessList.
|
||||
// This layer provides a "unified view" by merging the pre-transition state
|
||||
// with mutated states from preceding transactions in the block.
|
||||
//
|
||||
// - Tracking Layer: Finally, the readerTracker wraps the execution reader to
|
||||
// capture all state reads made during a specific transaction. These individual
|
||||
// reads are subsequently merged to construct a comprehensive access list
|
||||
// for the entire block.
|
||||
//
|
||||
// The architecture can be illustrated by the diagram below:
|
||||
//
|
||||
// ┌──────────────┴──────────────┐ ┌──────────────┴──────────────┐
|
||||
// │ ReaderWithBlockLevelAL │ │ ReaderWithBlockLevelAL │
|
||||
// │ (Pre-state + Mutations) │ │ (Pre-state + Mutations) │
|
||||
// └──────────────┬──────────────┘ └──────────────┬──────────────┘
|
||||
// │ │
|
||||
// └────────────────┬─────────────────┘
|
||||
// │
|
||||
// ┌──────────────┴──────────────┐
|
||||
// │ newPrefetchStateReader │ (Async I/O)
|
||||
// │ (Access List Hint driven) │
|
||||
// └──────────────┬──────────────┘
|
||||
// │
|
||||
// ┌──────────────┴──────────────┐
|
||||
// │ Base Reader │ (State Root)
|
||||
// │ (State & Contract Code) │
|
||||
// └─────────────────────────────┘
|
||||
|
||||
// Note: The block producer, which is responsible for generating the block
|
||||
// along with the block-level access list, does not maintain the internal
|
||||
// hierarchy (e.g., PrefetchStateReader or ReaderWithBlockLevelAL).
|
||||
// Instead, it directly utilizes the readerTracker, wrapped around the
|
||||
// base reader, to construct the access list.
|
||||
|
||||
type fetchTask struct {
|
||||
addr common.Address
|
||||
slots []common.Hash
|
||||
}
|
||||
|
||||
func (t *fetchTask) weight() int { return 1 + len(t.slots) }
|
||||
|
||||
type prefetchStateReader struct {
|
||||
StateReader
|
||||
|
||||
tasks []*fetchTask
|
||||
nThreads int
|
||||
done chan struct{}
|
||||
term chan struct{}
|
||||
closeOnce sync.Once
|
||||
}
|
||||
|
||||
// nolint:unused
|
||||
func newPrefetchStateReader(reader StateReader, accessList map[common.Address][]common.Hash, nThreads int) *prefetchStateReader {
|
||||
tasks := make([]*fetchTask, 0, len(accessList))
|
||||
for addr, slots := range accessList {
|
||||
tasks = append(tasks, &fetchTask{
|
||||
addr: addr,
|
||||
slots: slots,
|
||||
})
|
||||
}
|
||||
return newPrefetchStateReaderInternal(reader, tasks, nThreads)
|
||||
}
|
||||
|
||||
func newPrefetchStateReaderInternal(reader StateReader, tasks []*fetchTask, nThreads int) *prefetchStateReader {
|
||||
r := &prefetchStateReader{
|
||||
StateReader: reader,
|
||||
tasks: tasks,
|
||||
nThreads: nThreads,
|
||||
done: make(chan struct{}),
|
||||
term: make(chan struct{}),
|
||||
}
|
||||
go r.prefetch()
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *prefetchStateReader) Close() {
|
||||
r.closeOnce.Do(func() {
|
||||
close(r.term)
|
||||
<-r.done
|
||||
})
|
||||
}
|
||||
|
||||
func (r *prefetchStateReader) Wait() error {
|
||||
select {
|
||||
case <-r.term:
|
||||
return nil
|
||||
case <-r.done:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (r *prefetchStateReader) prefetch() {
|
||||
defer close(r.done)
|
||||
|
||||
if len(r.tasks) == 0 {
|
||||
return
|
||||
}
|
||||
var total int
|
||||
for _, t := range r.tasks {
|
||||
total += t.weight()
|
||||
}
|
||||
var (
|
||||
wg sync.WaitGroup
|
||||
unit = (total + r.nThreads - 1) / r.nThreads // round-up the per worker unit
|
||||
)
|
||||
for i := 0; i < r.nThreads; i++ {
|
||||
start := i * unit
|
||||
if start >= total {
|
||||
break
|
||||
}
|
||||
limit := (i + 1) * unit
|
||||
if i == r.nThreads-1 {
|
||||
limit = total
|
||||
}
|
||||
// Schedule the worker for prefetching, the items on the range [start, limit)
|
||||
// is exclusively assigned for this worker.
|
||||
wg.Add(1)
|
||||
go func(workerID, startW, endW int) {
|
||||
r.process(startW, endW)
|
||||
wg.Done()
|
||||
}(i, start, limit)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func (r *prefetchStateReader) process(start, limit int) {
|
||||
var total = 0
|
||||
for _, t := range r.tasks {
|
||||
tw := t.weight()
|
||||
if total+tw > start {
|
||||
s := 0
|
||||
if start > total {
|
||||
s = start - total
|
||||
}
|
||||
l := tw
|
||||
if limit < total+tw {
|
||||
l = limit - total
|
||||
}
|
||||
for j := s; j < l; j++ {
|
||||
select {
|
||||
case <-r.term:
|
||||
return
|
||||
default:
|
||||
if j == 0 {
|
||||
r.StateReader.Account(t.addr)
|
||||
} else {
|
||||
r.StateReader.Storage(t.addr, t.slots[j-1])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
total += tw
|
||||
if total >= limit {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ReaderWithBlockLevelAccessList provides state access that reflects the
|
||||
// pre-transition state combined with the mutations made by transactions
|
||||
// prior to TxIndex.
|
||||
type ReaderWithBlockLevelAccessList struct {
|
||||
Reader
|
||||
AccessList *bal.ConstructionBlockAccessList
|
||||
TxIndex int
|
||||
}
|
||||
|
||||
// NewReaderWithBlockLevelAccessList constructs a reader for accessing states
|
||||
// with the mutations made by transactions prior to txIndex.
|
||||
//
|
||||
// The txIndex refers to the call frame as such:
|
||||
// - 0 for pre‑execution system contract calls.
|
||||
// - 1 … n for transactions (in block order).
|
||||
// - n + 1 for post‑execution system contract calls.
|
||||
func NewReaderWithBlockLevelAccessList(base Reader, accessList *bal.ConstructionBlockAccessList, txIndex int) *ReaderWithBlockLevelAccessList {
|
||||
return &ReaderWithBlockLevelAccessList{
|
||||
Reader: base,
|
||||
AccessList: accessList,
|
||||
TxIndex: txIndex,
|
||||
}
|
||||
}
|
||||
|
||||
// Account implements Reader, returning the account with the specific address.
|
||||
func (r *ReaderWithBlockLevelAccessList) Account(addr common.Address) (*types.StateAccount, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
// Storage implements Reader, returning the storage slot with the specific
|
||||
// address and slot key.
|
||||
func (r *ReaderWithBlockLevelAccessList) Storage(addr common.Address, slot common.Hash) (common.Hash, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
// Has implements Reader, returning the flag indicating whether the contract
|
||||
// code with specified address and hash exists or not.
|
||||
func (r *ReaderWithBlockLevelAccessList) Has(addr common.Address, codeHash common.Hash) bool {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
// Code implements Reader, returning the contract code with specified address
|
||||
// and hash.
|
||||
func (r *ReaderWithBlockLevelAccessList) Code(addr common.Address, codeHash common.Hash) ([]byte, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
// CodeSize implements Reader, returning the contract code size with specified
|
||||
// address and hash.
|
||||
func (r *ReaderWithBlockLevelAccessList) CodeSize(addr common.Address, codeHash common.Hash) (int, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
145
core/state/reader_eip_7928_test.go
Normal file
145
core/state/reader_eip_7928_test.go
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
// 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 (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/internal/testrand"
|
||||
)
|
||||
|
||||
type countingStateReader struct {
|
||||
accounts map[common.Address]int
|
||||
storages map[common.Address]map[common.Hash]int
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
func newRefStateReader() *countingStateReader {
|
||||
return &countingStateReader{
|
||||
accounts: make(map[common.Address]int),
|
||||
storages: make(map[common.Address]map[common.Hash]int),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *countingStateReader) validate(total int) error {
|
||||
var sum int
|
||||
for addr, n := range r.accounts {
|
||||
if n != 1 {
|
||||
return fmt.Errorf("duplicated account access: %x-%d", addr, n)
|
||||
}
|
||||
sum += 1
|
||||
|
||||
slots, exists := r.storages[addr]
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
for key, n := range slots {
|
||||
if n != 1 {
|
||||
return fmt.Errorf("duplicated storage access: %x-%x-%d", addr, key, n)
|
||||
}
|
||||
sum += 1
|
||||
}
|
||||
}
|
||||
for addr := range r.storages {
|
||||
_, exists := r.accounts[addr]
|
||||
if !exists {
|
||||
return fmt.Errorf("dangling storage access: %x", addr)
|
||||
}
|
||||
}
|
||||
if sum != total {
|
||||
return fmt.Errorf("unexpected number of access, want: %d, got: %d", total, sum)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *countingStateReader) Account(addr common.Address) (*types.StateAccount, error) {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
r.accounts[addr] += 1
|
||||
return nil, nil
|
||||
}
|
||||
func (r *countingStateReader) Storage(addr common.Address, slot common.Hash) (common.Hash, error) {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
slots, exists := r.storages[addr]
|
||||
if !exists {
|
||||
slots = make(map[common.Hash]int)
|
||||
r.storages[addr] = slots
|
||||
}
|
||||
slots[slot] += 1
|
||||
return common.Hash{}, nil
|
||||
}
|
||||
|
||||
func makeFetchTasks(n int) ([]*fetchTask, int) {
|
||||
var (
|
||||
total int
|
||||
tasks []*fetchTask
|
||||
)
|
||||
for i := 0; i < n; i++ {
|
||||
var slots []common.Hash
|
||||
if rand.Intn(3) != 0 {
|
||||
for j := 0; j < rand.Intn(100); j++ {
|
||||
slots = append(slots, testrand.Hash())
|
||||
}
|
||||
}
|
||||
tasks = append(tasks, &fetchTask{
|
||||
addr: testrand.Address(),
|
||||
slots: slots,
|
||||
})
|
||||
total += len(slots) + 1
|
||||
}
|
||||
return tasks, total
|
||||
}
|
||||
|
||||
func TestPrefetchReader(t *testing.T) {
|
||||
type suite struct {
|
||||
tasks []*fetchTask
|
||||
threads int
|
||||
total int
|
||||
}
|
||||
var suites []suite
|
||||
for i := 0; i < 100; i++ {
|
||||
tasks, total := makeFetchTasks(100)
|
||||
suites = append(suites, suite{
|
||||
tasks: tasks,
|
||||
threads: rand.Intn(30) + 1,
|
||||
total: total,
|
||||
})
|
||||
}
|
||||
// num(tasks) < num(threads)
|
||||
tasks, total := makeFetchTasks(1)
|
||||
suites = append(suites, suite{
|
||||
tasks: tasks,
|
||||
threads: 100,
|
||||
total: total,
|
||||
})
|
||||
for _, s := range suites {
|
||||
r := newRefStateReader()
|
||||
pr := newPrefetchStateReaderInternal(r, s.tasks, s.threads)
|
||||
pr.Wait()
|
||||
if err := r.validate(s.total); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -342,7 +342,7 @@ func TestAccountIteratorTraversalValues(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 {
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"github.com/ethereum/go-ethereum/trie/bintrie"
|
||||
"github.com/ethereum/go-ethereum/trie/transitiontrie"
|
||||
|
|
@ -154,7 +153,7 @@ func (s *stateObject) getTrie() (Trie, error) {
|
|||
func (s *stateObject) getPrefetchedTrie() Trie {
|
||||
// If there's nothing to meaningfully return, let the user figure it out by
|
||||
// pulling the trie from disk.
|
||||
if (s.data.Root == types.EmptyRootHash && !s.db.db.TrieDB().IsVerkle()) || s.db.prefetcher == nil {
|
||||
if (s.data.Root == types.EmptyRootHash && s.db.db.Type().Is(TypeMPT)) || s.db.prefetcher == nil {
|
||||
return nil
|
||||
}
|
||||
// Attempt to retrieve the trie from the prefetcher
|
||||
|
|
@ -163,8 +162,11 @@ func (s *stateObject) getPrefetchedTrie() Trie {
|
|||
|
||||
// GetState retrieves a value associated with the given storage key.
|
||||
func (s *stateObject) GetState(key common.Hash) common.Hash {
|
||||
value, _ := s.getState(key)
|
||||
return value
|
||||
value, dirty := s.dirtyStorage[key]
|
||||
if dirty {
|
||||
return value
|
||||
}
|
||||
return s.GetCommittedState(key)
|
||||
}
|
||||
|
||||
// getState retrieves a value associated with the given storage key, along with
|
||||
|
|
@ -181,6 +183,10 @@ func (s *stateObject) getState(key common.Hash) (common.Hash, common.Hash) {
|
|||
// GetCommittedState retrieves the value associated with the specific key
|
||||
// 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.
|
||||
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
|
||||
|
|
@ -195,19 +201,6 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
|
|||
// have been handles via pendingStorage above.
|
||||
// 2) we don't have new values, and can deliver empty response back
|
||||
if _, destructed := s.db.stateObjectsDestruct[s.address]; destructed {
|
||||
// Invoke the reader regardless and discard the returned value.
|
||||
// The returned value may not be empty, as it could belong to a
|
||||
// self-destructed contract.
|
||||
//
|
||||
// The read operation is still essential for correctly building
|
||||
// the block-level access list.
|
||||
//
|
||||
// TODO(rjl493456442) the reader interface can be extended with
|
||||
// Touch, recording the read access without the actual disk load.
|
||||
_, err := s.db.reader.Storage(s.address, key)
|
||||
if err != nil {
|
||||
s.db.setError(err)
|
||||
}
|
||||
s.originStorage[key] = common.Hash{} // track the empty slot as origin value
|
||||
return common.Hash{}
|
||||
}
|
||||
|
|
@ -282,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 {
|
||||
|
|
@ -398,17 +398,8 @@ func (s *stateObject) updateRoot() {
|
|||
}
|
||||
|
||||
// commitStorage overwrites the clean storage with the storage changes and
|
||||
// fulfills the storage diffs into the given accountUpdate struct.
|
||||
func (s *stateObject) commitStorage(op *accountUpdate) {
|
||||
var (
|
||||
encode = func(val common.Hash) []byte {
|
||||
if val == (common.Hash{}) {
|
||||
return nil
|
||||
}
|
||||
blob, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(val[:]))
|
||||
return blob
|
||||
}
|
||||
)
|
||||
// fulfills the storage diffs into the given AccountUpdate struct.
|
||||
func (s *stateObject) commitStorage(op *AccountUpdate) {
|
||||
for key, val := range s.pendingStorage {
|
||||
// Skip the noop storage changes, it might be possible the value
|
||||
// of tracked slot is same in originStorage and pendingStorage
|
||||
|
|
@ -418,20 +409,20 @@ func (s *stateObject) commitStorage(op *accountUpdate) {
|
|||
continue
|
||||
}
|
||||
hash := crypto.Keccak256Hash(key[:])
|
||||
if op.storages == nil {
|
||||
op.storages = make(map[common.Hash][]byte)
|
||||
if op.Storages == nil {
|
||||
op.Storages = make(map[common.Hash]common.Hash)
|
||||
}
|
||||
op.storages[hash] = encode(val)
|
||||
op.Storages[hash] = val
|
||||
|
||||
if op.storagesOriginByKey == nil {
|
||||
op.storagesOriginByKey = make(map[common.Hash][]byte)
|
||||
if op.StoragesOriginByKey == nil {
|
||||
op.StoragesOriginByKey = make(map[common.Hash]common.Hash)
|
||||
}
|
||||
if op.storagesOriginByHash == nil {
|
||||
op.storagesOriginByHash = make(map[common.Hash][]byte)
|
||||
if op.StoragesOriginByHash == nil {
|
||||
op.StoragesOriginByHash = make(map[common.Hash]common.Hash)
|
||||
}
|
||||
origin := encode(s.originStorage[key])
|
||||
op.storagesOriginByKey[key] = origin
|
||||
op.storagesOriginByHash[hash] = origin
|
||||
origin := s.originStorage[key]
|
||||
op.StoragesOriginByKey[key] = origin
|
||||
op.StoragesOriginByHash[hash] = origin
|
||||
|
||||
// Overwrite the clean value of storage slots
|
||||
s.originStorage[key] = val
|
||||
|
|
@ -444,32 +435,32 @@ func (s *stateObject) commitStorage(op *accountUpdate) {
|
|||
//
|
||||
// Note, commit may run concurrently across all the state objects. Do not assume
|
||||
// thread-safe access to the statedb.
|
||||
func (s *stateObject) commit() (*accountUpdate, *trienode.NodeSet, error) {
|
||||
// commit the account metadata changes
|
||||
op := &accountUpdate{
|
||||
address: s.address,
|
||||
data: types.SlimAccountRLP(s.data),
|
||||
}
|
||||
if s.origin != nil {
|
||||
op.origin = types.SlimAccountRLP(*s.origin)
|
||||
func (s *stateObject) commit() (*AccountUpdate, *trienode.NodeSet, error) {
|
||||
// commit the account metadata changes, the data must be deep-copied
|
||||
// to prevent accidental mutations later on (in practice the stateDB
|
||||
// won't be modified after commit). The origin is safe to use directly.
|
||||
op := &AccountUpdate{
|
||||
Address: s.address,
|
||||
Data: s.data.Copy(),
|
||||
Origin: s.origin,
|
||||
}
|
||||
// commit the contract code if it's modified
|
||||
if s.dirtyCode {
|
||||
op.code = &contractCode{
|
||||
hash: common.BytesToHash(s.CodeHash()),
|
||||
blob: s.code,
|
||||
op.Code = &ContractCode{
|
||||
Hash: common.BytesToHash(s.CodeHash()),
|
||||
Blob: s.code,
|
||||
}
|
||||
s.dirtyCode = false // reset the dirty flag
|
||||
|
||||
if s.origin == nil {
|
||||
op.code.originHash = types.EmptyCodeHash
|
||||
op.Code.OriginHash = types.EmptyCodeHash
|
||||
} else {
|
||||
op.code.originHash = common.BytesToHash(s.origin.CodeHash)
|
||||
op.Code.OriginHash = common.BytesToHash(s.origin.CodeHash)
|
||||
}
|
||||
}
|
||||
// Commit storage changes and the associated storage trie
|
||||
s.commitStorage(op)
|
||||
if len(op.storages) == 0 {
|
||||
if len(op.Storages) == 0 {
|
||||
// nothing changed, don't bother to commit the trie
|
||||
s.origin = s.data.Copy()
|
||||
return op, nil, nil
|
||||
|
|
@ -478,12 +469,13 @@ func (s *stateObject) commit() (*accountUpdate, *trienode.NodeSet, error) {
|
|||
// The main account trie commit in stateDB.commit() already calls
|
||||
// CollectNodes on this trie, so calling Commit here again would
|
||||
// redundantly traverse and serialize the entire tree per dirty account.
|
||||
if s.db.GetTrie().IsVerkle() {
|
||||
if s.db.GetTrie().IsUBT() {
|
||||
s.origin = s.data.Copy()
|
||||
return op, nil, nil
|
||||
}
|
||||
root, nodes := s.trie.Commit(false)
|
||||
s.data.Root = root
|
||||
// The storage trie root is omitted, as it has already been updated in the
|
||||
// previous updateRoot step.
|
||||
_, nodes := s.trie.Commit(false)
|
||||
s.origin = s.data.Copy()
|
||||
return op, nodes, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -125,16 +125,17 @@ func (s SizeStats) add(diff SizeStats) SizeStats {
|
|||
}
|
||||
|
||||
// calSizeStats measures the state size changes of the provided state update.
|
||||
func calSizeStats(update *stateUpdate) (SizeStats, error) {
|
||||
func calSizeStats(update *StateUpdate) (SizeStats, error) {
|
||||
stats := SizeStats{
|
||||
BlockNumber: update.blockNumber,
|
||||
StateRoot: update.root,
|
||||
BlockNumber: update.BlockNumber,
|
||||
StateRoot: update.Root,
|
||||
}
|
||||
accounts, accountOrigin, storages, storageOrigin := update.EncodeMPTState()
|
||||
|
||||
// Measure the account changes
|
||||
for addr, oldValue := range update.accountsOrigin {
|
||||
for addr, oldValue := range accountOrigin {
|
||||
addrHash := crypto.Keccak256Hash(addr.Bytes())
|
||||
newValue, exists := update.accounts[addrHash]
|
||||
newValue, exists := accounts[addrHash]
|
||||
if !exists {
|
||||
return SizeStats{}, fmt.Errorf("account %x not found", addr)
|
||||
}
|
||||
|
|
@ -156,9 +157,9 @@ func calSizeStats(update *stateUpdate) (SizeStats, error) {
|
|||
}
|
||||
|
||||
// Measure storage changes
|
||||
for addr, slots := range update.storagesOrigin {
|
||||
for addr, slots := range storageOrigin {
|
||||
addrHash := crypto.Keccak256Hash(addr.Bytes())
|
||||
subset, exists := update.storages[addrHash]
|
||||
subset, exists := storages[addrHash]
|
||||
if !exists {
|
||||
return SizeStats{}, fmt.Errorf("storage %x not found", addr)
|
||||
}
|
||||
|
|
@ -167,7 +168,7 @@ func calSizeStats(update *stateUpdate) (SizeStats, error) {
|
|||
exists bool
|
||||
newValue []byte
|
||||
)
|
||||
if update.rawStorageKey {
|
||||
if update.StorageKeyType == StorageKeyPlain {
|
||||
newValue, exists = subset[crypto.Keccak256Hash(key.Bytes())]
|
||||
} else {
|
||||
newValue, exists = subset[key]
|
||||
|
|
@ -194,7 +195,7 @@ func calSizeStats(update *stateUpdate) (SizeStats, error) {
|
|||
}
|
||||
|
||||
// Measure trienode changes
|
||||
for owner, subset := range update.nodes.Sets {
|
||||
for owner, subset := range update.Nodes.Sets {
|
||||
var (
|
||||
keyPrefix int64
|
||||
isAccount = owner == (common.Hash{})
|
||||
|
|
@ -244,13 +245,13 @@ func calSizeStats(update *stateUpdate) (SizeStats, error) {
|
|||
}
|
||||
|
||||
codeExists := make(map[common.Hash]struct{})
|
||||
for _, code := range update.codes {
|
||||
if _, ok := codeExists[code.hash]; ok || code.duplicate {
|
||||
for _, code := range update.Codes {
|
||||
if _, ok := codeExists[code.Hash]; ok || code.Duplicate {
|
||||
continue
|
||||
}
|
||||
stats.ContractCodes += 1
|
||||
stats.ContractCodeBytes += codeKeySize + int64(len(code.blob))
|
||||
codeExists[code.hash] = struct{}{}
|
||||
stats.ContractCodeBytes += codeKeySize + int64(len(code.Blob))
|
||||
codeExists[code.Hash] = struct{}{}
|
||||
}
|
||||
return stats, nil
|
||||
}
|
||||
|
|
@ -267,7 +268,7 @@ type SizeTracker struct {
|
|||
triedb *triedb.Database
|
||||
abort chan struct{}
|
||||
aborted chan struct{}
|
||||
updateCh chan *stateUpdate
|
||||
updateCh chan *StateUpdate
|
||||
queryCh chan *stateSizeQuery
|
||||
}
|
||||
|
||||
|
|
@ -281,7 +282,7 @@ func NewSizeTracker(db ethdb.KeyValueStore, triedb *triedb.Database) (*SizeTrack
|
|||
triedb: triedb,
|
||||
abort: make(chan struct{}),
|
||||
aborted: make(chan struct{}),
|
||||
updateCh: make(chan *stateUpdate),
|
||||
updateCh: make(chan *StateUpdate),
|
||||
queryCh: make(chan *stateSizeQuery),
|
||||
}
|
||||
go t.run()
|
||||
|
|
@ -328,9 +329,9 @@ func (t *SizeTracker) run() {
|
|||
for {
|
||||
select {
|
||||
case u := <-t.updateCh:
|
||||
base, found := stats[u.originRoot]
|
||||
base, found := stats[u.OriginRoot]
|
||||
if !found {
|
||||
log.Debug("Ignored the state size without parent", "parent", u.originRoot, "root", u.root, "number", u.blockNumber)
|
||||
log.Debug("Ignored the state size without parent", "parent", u.OriginRoot, "root", u.Root, "number", u.BlockNumber)
|
||||
continue
|
||||
}
|
||||
diff, err := calSizeStats(u)
|
||||
|
|
@ -338,15 +339,15 @@ func (t *SizeTracker) run() {
|
|||
continue
|
||||
}
|
||||
stat := base.add(diff)
|
||||
stats[u.root] = stat
|
||||
last = u.root
|
||||
stats[u.Root] = stat
|
||||
last = u.Root
|
||||
|
||||
// Publish statistics to metric system
|
||||
stat.publish()
|
||||
|
||||
// Evict the stale statistics
|
||||
heap.Push(&h, stats[u.root])
|
||||
for len(h) > 0 && u.blockNumber-h[0].BlockNumber > statEvictThreshold {
|
||||
heap.Push(&h, stats[u.Root])
|
||||
for len(h) > 0 && u.BlockNumber-h[0].BlockNumber > statEvictThreshold {
|
||||
delete(stats, h[0].StateRoot)
|
||||
heap.Pop(&h)
|
||||
}
|
||||
|
|
@ -402,7 +403,7 @@ wait:
|
|||
}
|
||||
|
||||
var (
|
||||
updates = make(map[common.Hash]*stateUpdate)
|
||||
updates = make(map[common.Hash]*StateUpdate)
|
||||
children = make(map[common.Hash][]common.Hash)
|
||||
done chan buildResult
|
||||
)
|
||||
|
|
@ -410,9 +411,9 @@ wait:
|
|||
for {
|
||||
select {
|
||||
case u := <-t.updateCh:
|
||||
updates[u.root] = u
|
||||
children[u.originRoot] = append(children[u.originRoot], u.root)
|
||||
log.Debug("Received state update", "root", u.root, "blockNumber", u.blockNumber)
|
||||
updates[u.Root] = u
|
||||
children[u.OriginRoot] = append(children[u.OriginRoot], u.Root)
|
||||
log.Debug("Received state update", "root", u.Root, "blockNumber", u.BlockNumber)
|
||||
|
||||
case r := <-t.queryCh:
|
||||
r.err = errors.New("state size is not initialized yet")
|
||||
|
|
@ -432,8 +433,8 @@ wait:
|
|||
continue
|
||||
}
|
||||
done = make(chan buildResult)
|
||||
go t.build(entry.root, entry.blockNumber, done)
|
||||
log.Info("Measuring persistent state size", "root", root.Hex(), "number", entry.blockNumber)
|
||||
go t.build(entry.Root, entry.BlockNumber, done)
|
||||
log.Info("Measuring persistent state size", "root", root.Hex(), "number", entry.BlockNumber)
|
||||
|
||||
case result := <-done:
|
||||
if result.err != nil {
|
||||
|
|
@ -646,8 +647,8 @@ func (t *SizeTracker) iterateTableParallel(closed chan struct{}, prefix []byte,
|
|||
// Notify is an async method used to send the state update to the size tracker.
|
||||
// It ignores empty updates (where no state changes occurred).
|
||||
// If the channel is full, it drops the update to avoid blocking.
|
||||
func (t *SizeTracker) Notify(update *stateUpdate) {
|
||||
if update == nil || update.empty() {
|
||||
func (t *SizeTracker) Notify(update *StateUpdate) {
|
||||
if update == nil || update.Empty() {
|
||||
return
|
||||
}
|
||||
select {
|
||||
|
|
|
|||
|
|
@ -160,7 +160,7 @@ func TestSizeTracker(t *testing.T) {
|
|||
}
|
||||
tracker.Notify(ret)
|
||||
|
||||
if err := tdb.Commit(ret.root, false); err != nil {
|
||||
if err := tdb.Commit(ret.Root, false); err != nil {
|
||||
t.Fatalf("Failed to commit trie at block %d: %v", blockNum, err)
|
||||
}
|
||||
|
||||
|
|
@ -169,7 +169,7 @@ func TestSizeTracker(t *testing.T) {
|
|||
t.Fatalf("Failed to calculate size stats for block %d: %v", blockNum, err)
|
||||
}
|
||||
trackedUpdates = append(trackedUpdates, diff)
|
||||
currentRoot = ret.root
|
||||
currentRoot = ret.Root
|
||||
}
|
||||
finalRoot := rawdb.ReadSnapshotRoot(db)
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
|
|
@ -31,6 +32,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/stateless"
|
||||
"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/crypto"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
|
|
@ -126,6 +128,12 @@ type StateDB struct {
|
|||
accessList *accessList
|
||||
accessEvents *AccessEvents
|
||||
|
||||
// Per-transaction state access footprint for EIP-7928
|
||||
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
|
||||
|
||||
|
|
@ -190,7 +198,7 @@ func NewWithReader(root common.Hash, db Database, reader Reader) (*StateDB, erro
|
|||
accessList: newAccessList(),
|
||||
transientStorage: newTransientStorage(),
|
||||
}
|
||||
if db.TrieDB().IsVerkle() {
|
||||
if db.Type().Is(TypeUBT) {
|
||||
sdb.accessEvents = NewAccessEvents()
|
||||
}
|
||||
return sdb, nil
|
||||
|
|
@ -317,6 +325,11 @@ func (s *StateDB) Empty(addr common.Address) bool {
|
|||
return so == nil || so.empty()
|
||||
}
|
||||
|
||||
// Touch accesses the specific account without returning anything.
|
||||
func (s *StateDB) Touch(addr common.Address) {
|
||||
s.getStateObject(addr)
|
||||
}
|
||||
|
||||
// GetBalance retrieves the balance from the given address or 0 if object not found
|
||||
func (s *StateDB) GetBalance(addr common.Address) *uint256.Int {
|
||||
stateObject := s.getStateObject(addr)
|
||||
|
|
@ -338,6 +351,9 @@ func (s *StateDB) GetNonce(addr common.Address) uint64 {
|
|||
|
||||
// GetStorageRoot retrieves the storage root from the given address or empty
|
||||
// if object not found.
|
||||
//
|
||||
// Note: the storage root returned corresponds to the trie since last Intermediate
|
||||
// operation, some recent in-memory changes are excluded.
|
||||
func (s *StateDB) GetStorageRoot(addr common.Address) common.Hash {
|
||||
stateObject := s.getStateObject(addr)
|
||||
if stateObject != nil {
|
||||
|
|
@ -576,6 +592,10 @@ func (s *StateDB) deleteStateObject(addr common.Address) {
|
|||
// getStateObject retrieves a state object given by the address, returning nil if
|
||||
// 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.
|
||||
if s.stateAccessList != nil {
|
||||
s.stateAccessList.AccountRead(addr)
|
||||
}
|
||||
// Prefer live objects if any is available
|
||||
if obj := s.stateObjects[addr]; obj != nil {
|
||||
return obj
|
||||
|
|
@ -678,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),
|
||||
|
|
@ -722,6 +743,9 @@ func (s *StateDB) Copy() *StateDB {
|
|||
}
|
||||
state.logs[hash] = cpy
|
||||
}
|
||||
if s.stateAccessList != nil {
|
||||
state.stateAccessList = s.stateAccessList.Copy()
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
|
|
@ -757,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,
|
||||
|
|
@ -781,29 +805,69 @@ 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) {
|
||||
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()) {
|
||||
delete(s.stateObjects, obj.address)
|
||||
s.markDelete(addr)
|
||||
|
||||
// We need to maintain account deletions explicitly (will remain
|
||||
// set indefinitely). Note only the first occurred self-destruct
|
||||
// event is tracked.
|
||||
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)
|
||||
}
|
||||
|
|
@ -819,6 +883,8 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
|
|||
}
|
||||
// Invalidate journal because reverting across transactions is not allowed.
|
||||
s.clearJournalAndRefund()
|
||||
|
||||
return s.stateAccessList
|
||||
}
|
||||
|
||||
// IntermediateRoot computes the current root hash of the state trie.
|
||||
|
|
@ -858,7 +924,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
|||
start = time.Now()
|
||||
workers errgroup.Group
|
||||
)
|
||||
if s.db.TrieDB().IsVerkle() {
|
||||
if s.db.Type().Is(TypeUBT) {
|
||||
// Bypass per-account updateTrie() for binary trie. In binary trie mode
|
||||
// there is only one unified trie (OpenStorageTrie returns self), so the
|
||||
// per-account trie setup in updateTrie() (getPrefetchedTrie, getTrie,
|
||||
|
|
@ -922,9 +988,9 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
|||
}
|
||||
}
|
||||
// If witness building is enabled, gather all the read-only accesses.
|
||||
// Skip witness collection in Verkle mode, they will be gathered
|
||||
// together at the end.
|
||||
if s.witness != nil && !s.db.TrieDB().IsVerkle() {
|
||||
// Skip witness collection in Unified-binary-trie mode, they will be
|
||||
// gathered together at the end.
|
||||
if s.witness != nil && s.db.Type().Is(TypeMPT) {
|
||||
// Pull in anything that has been accessed before destruction
|
||||
for _, obj := range s.stateObjectsDestruct {
|
||||
// Skip any objects that haven't touched their storage
|
||||
|
|
@ -965,7 +1031,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
|||
// only a single trie is used for state hashing. Replacing a non-nil verkle tree
|
||||
// here could result in losing uncommitted changes from storage.
|
||||
start = time.Now()
|
||||
if s.prefetcher != nil && !s.db.TrieDB().IsVerkle() {
|
||||
if s.prefetcher != nil && s.db.Type().Is(TypeMPT) {
|
||||
if trie := s.prefetcher.trie(common.Hash{}, s.originalRoot); trie == nil {
|
||||
log.Error("Failed to retrieve account pre-fetcher trie")
|
||||
} else {
|
||||
|
|
@ -1031,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() {
|
||||
|
|
@ -1042,11 +1109,11 @@ func (s *StateDB) clearJournalAndRefund() {
|
|||
}
|
||||
|
||||
// deleteStorage is designed to delete the storage trie of a designated account.
|
||||
func (s *StateDB) deleteStorage(addrHash common.Hash, root common.Hash) (map[common.Hash][]byte, map[common.Hash][]byte, *trienode.NodeSet, error) {
|
||||
func (s *StateDB) deleteStorage(addrHash common.Hash, root common.Hash) (map[common.Hash]common.Hash, map[common.Hash]common.Hash, *trienode.NodeSet, error) {
|
||||
var (
|
||||
nodes = trienode.NewNodeSet(addrHash) // the set for trie node mutations (value is nil)
|
||||
storages = make(map[common.Hash][]byte) // the set for storage mutations (value is nil)
|
||||
storageOrigins = make(map[common.Hash][]byte) // the set for tracking the original value of slot
|
||||
nodes = trienode.NewNodeSet(addrHash) // the set for trie node mutations (value is nil)
|
||||
storages = make(map[common.Hash]common.Hash) // the set for storage mutations (value is nil)
|
||||
storageOrigins = make(map[common.Hash]common.Hash) // the set for tracking the original value of slot
|
||||
)
|
||||
iteratee, err := s.db.Iteratee(s.originalRoot)
|
||||
if err != nil {
|
||||
|
|
@ -1062,19 +1129,24 @@ func (s *StateDB) deleteStorage(addrHash common.Hash, root common.Hash) (map[com
|
|||
nodes.AddNode(path, trienode.NewDeletedWithPrev(blob))
|
||||
})
|
||||
for it.Next() {
|
||||
slot := common.CopyBytes(it.Slot())
|
||||
if err := it.Error(); err != nil { // error might occur after Slot function
|
||||
slot := it.Slot()
|
||||
// Error might occur after Slot function
|
||||
if err := it.Error(); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
if slot == (common.Hash{}) {
|
||||
return nil, nil, nil, fmt.Errorf("unexpected empty storage slot, addr: %x, slot: %x", addrHash, it.Hash())
|
||||
}
|
||||
key := it.Hash()
|
||||
storages[key] = nil
|
||||
storages[key] = common.Hash{}
|
||||
storageOrigins[key] = slot
|
||||
|
||||
if err := stack.Update(key.Bytes(), slot); err != nil {
|
||||
if err := stack.Update(key.Bytes(), encodeSlot(slot)); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
}
|
||||
if err := it.Error(); err != nil { // error might occur during iteration
|
||||
// Error might occur during iteration
|
||||
if err := it.Error(); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
if stack.Hash() != root {
|
||||
|
|
@ -1101,10 +1173,10 @@ func (s *StateDB) deleteStorage(addrHash common.Hash, root common.Hash) (map[com
|
|||
// with their values be tracked as original value.
|
||||
// In case (d), **original** account along with its storages should be deleted,
|
||||
// with their values be tracked as original value.
|
||||
func (s *StateDB) handleDestruction(noStorageWiping bool) (map[common.Hash]*accountDelete, []*trienode.NodeSet, error) {
|
||||
func (s *StateDB) handleDestruction(noStorageWiping bool) (map[common.Hash]*AccountDelete, []*trienode.NodeSet, error) {
|
||||
var (
|
||||
nodes []*trienode.NodeSet
|
||||
deletes = make(map[common.Hash]*accountDelete)
|
||||
deletes = make(map[common.Hash]*AccountDelete)
|
||||
)
|
||||
for addr, prevObj := range s.stateObjectsDestruct {
|
||||
prev := prevObj.origin
|
||||
|
|
@ -1118,15 +1190,15 @@ func (s *StateDB) handleDestruction(noStorageWiping bool) (map[common.Hash]*acco
|
|||
continue
|
||||
}
|
||||
// The account was existent, it can be either case (c) or (d).
|
||||
addrHash := crypto.Keccak256Hash(addr.Bytes())
|
||||
op := &accountDelete{
|
||||
address: addr,
|
||||
origin: types.SlimAccountRLP(*prev),
|
||||
addrHash := prevObj.addrHash()
|
||||
op := &AccountDelete{
|
||||
Address: addr,
|
||||
Origin: prev,
|
||||
}
|
||||
deletes[addrHash] = op
|
||||
|
||||
// Short circuit if the origin storage was empty.
|
||||
if prev.Root == types.EmptyRootHash || s.db.TrieDB().IsVerkle() {
|
||||
if prev.Root == types.EmptyRootHash || s.db.Type().Is(TypeUBT) {
|
||||
continue
|
||||
}
|
||||
if noStorageWiping {
|
||||
|
|
@ -1137,8 +1209,8 @@ func (s *StateDB) handleDestruction(noStorageWiping bool) (map[common.Hash]*acco
|
|||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to delete storage, err: %w", err)
|
||||
}
|
||||
op.storages = storages
|
||||
op.storagesOrigin = storagesOrigin
|
||||
op.Storages = storages
|
||||
op.StoragesOrigin = storagesOrigin
|
||||
|
||||
// Aggregate the associated trie node changes.
|
||||
nodes = append(nodes, set)
|
||||
|
|
@ -1153,13 +1225,13 @@ func (s *StateDB) GetTrie() Trie {
|
|||
|
||||
// commit gathers the state mutations accumulated along with the associated
|
||||
// trie changes, resetting all internal flags with the new state as the base.
|
||||
func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNumber uint64) (*stateUpdate, error) {
|
||||
func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNumber uint64) (*StateUpdate, error) {
|
||||
// Short circuit in case any database failure occurred earlier.
|
||||
if s.dbErr != nil {
|
||||
return nil, fmt.Errorf("commit aborted due to earlier error: %v", s.dbErr)
|
||||
}
|
||||
// Finalize any pending changes and merge everything into the tries
|
||||
s.IntermediateRoot(deleteEmptyObjects)
|
||||
root := s.IntermediateRoot(deleteEmptyObjects)
|
||||
|
||||
// Short circuit if any error occurs within the IntermediateRoot.
|
||||
if s.dbErr != nil {
|
||||
|
|
@ -1174,7 +1246,7 @@ func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNum
|
|||
|
||||
lock sync.Mutex // protect two maps below
|
||||
nodes = trienode.NewMergedNodeSet() // aggregated trie nodes
|
||||
updates = make(map[common.Hash]*accountUpdate, len(s.mutations)) // aggregated account updates
|
||||
updates = make(map[common.Hash]*AccountUpdate, len(s.mutations)) // aggregated account updates
|
||||
|
||||
// merge aggregates the dirty trie nodes into the global set.
|
||||
//
|
||||
|
|
@ -1221,7 +1293,6 @@ func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNum
|
|||
// writes to run in parallel with the computations.
|
||||
var (
|
||||
start = time.Now()
|
||||
root common.Hash
|
||||
workers errgroup.Group
|
||||
)
|
||||
// Schedule the account trie first since that will be the biggest, so give
|
||||
|
|
@ -1235,9 +1306,7 @@ func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNum
|
|||
// code didn't anticipate for.
|
||||
workers.Go(func() error {
|
||||
// Write the account trie changes, measuring the amount of wasted time
|
||||
newroot, set := s.trie.Commit(true)
|
||||
root = newroot
|
||||
|
||||
_, set := s.trie.Commit(true)
|
||||
if err := merge(set); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -1305,12 +1374,16 @@ func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNum
|
|||
origin := s.originalRoot
|
||||
s.originalRoot = root
|
||||
|
||||
return newStateUpdate(noStorageWiping, origin, root, blockNumber, deletes, updates, nodes), nil
|
||||
typ := StorageKeyHashed
|
||||
if noStorageWiping {
|
||||
typ = StorageKeyPlain
|
||||
}
|
||||
return NewStateUpdate(typ, origin, root, blockNumber, deletes, updates, nodes), nil
|
||||
}
|
||||
|
||||
// commitAndFlush is a wrapper of commit which also commits the state mutations
|
||||
// to the configured data stores.
|
||||
func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool, noStorageWiping bool, deriveCodeFields bool) (*stateUpdate, error) {
|
||||
func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool, noStorageWiping bool, deriveCodeFields bool) (*StateUpdate, error) {
|
||||
ret, err := s.commit(deleteEmptyObjects, noStorageWiping, block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -1328,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
|
||||
}
|
||||
|
||||
|
|
@ -1351,17 +1424,17 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool, noStorageWiping
|
|||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
return ret.root, nil
|
||||
return ret.Root, nil
|
||||
}
|
||||
|
||||
// CommitWithUpdate writes the state mutations and returns the state update for
|
||||
// external processing (e.g., live tracing hooks or size tracker).
|
||||
func (s *StateDB) CommitWithUpdate(block uint64, deleteEmptyObjects bool, noStorageWiping bool) (common.Hash, *stateUpdate, error) {
|
||||
func (s *StateDB) CommitWithUpdate(block uint64, deleteEmptyObjects bool, noStorageWiping bool) (common.Hash, *StateUpdate, error) {
|
||||
ret, err := s.commitAndFlush(block, deleteEmptyObjects, noStorageWiping, true)
|
||||
if err != nil {
|
||||
return common.Hash{}, nil, err
|
||||
}
|
||||
return ret.root, ret, nil
|
||||
return ret.Root, ret, nil
|
||||
}
|
||||
|
||||
// Prepare handles the preparatory steps for executing a state transition with.
|
||||
|
|
@ -1406,6 +1479,10 @@ func (s *StateDB) Prepare(rules params.Rules, sender, coinbase common.Address, d
|
|||
}
|
||||
// Reset transient storage at the beginning of transaction execution
|
||||
s.transientStorage = newTransientStorage()
|
||||
|
||||
if rules.IsAmsterdam {
|
||||
s.stateAccessList = bal.NewConstructionBlockAccessList()
|
||||
}
|
||||
}
|
||||
|
||||
// AddAddressToAccessList adds the given address to the access list
|
||||
|
|
|
|||
|
|
@ -182,11 +182,12 @@ func (test *stateTest) run() bool {
|
|||
accountOrigin []map[common.Address][]byte
|
||||
storages []map[common.Hash]map[common.Hash][]byte
|
||||
storageOrigin []map[common.Address]map[common.Hash][]byte
|
||||
copyUpdate = func(update *stateUpdate) {
|
||||
accounts = append(accounts, maps.Clone(update.accounts))
|
||||
accountOrigin = append(accountOrigin, maps.Clone(update.accountsOrigin))
|
||||
storages = append(storages, maps.Clone(update.storages))
|
||||
storageOrigin = append(storageOrigin, maps.Clone(update.storagesOrigin))
|
||||
copyUpdate = func(update *StateUpdate) {
|
||||
accts, acctOrigin, slots, slotOrigin := update.EncodeMPTState()
|
||||
accounts = append(accounts, maps.Clone(accts))
|
||||
accountOrigin = append(accountOrigin, maps.Clone(acctOrigin))
|
||||
storages = append(storages, maps.Clone(slots))
|
||||
storageOrigin = append(storageOrigin, maps.Clone(slotOrigin))
|
||||
}
|
||||
disk = rawdb.NewMemoryDatabase()
|
||||
tdb = triedb.NewDatabase(disk, &triedb.Config{PathDB: pathdb.Defaults})
|
||||
|
|
@ -209,7 +210,7 @@ func (test *stateTest) run() bool {
|
|||
if i != 0 {
|
||||
root = roots[len(roots)-1]
|
||||
}
|
||||
state, err := New(root, NewDatabase(tdb, nil).WithSnapshot(snaps))
|
||||
state, err := New(root, NewMPTDatabase(tdb, nil).WithSnapshot(snaps))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
|
@ -232,11 +233,11 @@ func (test *stateTest) run() bool {
|
|||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if ret.empty() {
|
||||
if ret.Empty() {
|
||||
return true
|
||||
}
|
||||
copyUpdate(ret)
|
||||
roots = append(roots, ret.root)
|
||||
roots = append(roots, ret.Root)
|
||||
}
|
||||
for i := 0; i < len(test.actions); i++ {
|
||||
root := types.EmptyRootHash
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/stateless"
|
||||
"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/crypto"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/holiman/uint256"
|
||||
|
|
@ -98,10 +99,6 @@ func (s *hookedStateDB) GetState(addr common.Address, hash common.Hash) common.H
|
|||
return s.inner.GetState(addr, hash)
|
||||
}
|
||||
|
||||
func (s *hookedStateDB) GetStorageRoot(addr common.Address) common.Hash {
|
||||
return s.inner.GetStorageRoot(addr)
|
||||
}
|
||||
|
||||
func (s *hookedStateDB) GetTransientState(addr common.Address, key common.Hash) common.Hash {
|
||||
return s.inner.GetTransientState(addr, key)
|
||||
}
|
||||
|
|
@ -118,6 +115,10 @@ func (s *hookedStateDB) Exist(addr common.Address) bool {
|
|||
return s.inner.Exist(addr)
|
||||
}
|
||||
|
||||
func (s *hookedStateDB) Touch(addr common.Address) {
|
||||
s.inner.Touch(addr)
|
||||
}
|
||||
|
||||
func (s *hookedStateDB) Empty(addr common.Address) bool {
|
||||
return s.inner.Empty(addr)
|
||||
}
|
||||
|
|
@ -233,18 +234,17 @@ func (s *hookedStateDB) LogsForBurnAccounts() []*types.Log {
|
|||
return s.inner.LogsForBurnAccounts()
|
||||
}
|
||||
|
||||
func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) {
|
||||
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.
|
||||
s.inner.Finalise(deleteEmptyObjects)
|
||||
return
|
||||
return s.inner.Finalise(deleteEmptyObjects)
|
||||
}
|
||||
|
||||
// Collect all self-destructed addresses first, then sort them to ensure
|
||||
// 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.
|
||||
|
|
@ -286,6 +286,9 @@ func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) {
|
|||
s.hooks.OnCodeChange(addr, prevCodeHash, s.inner.GetCode(addr), types.EmptyCodeHash, nil)
|
||||
}
|
||||
}
|
||||
|
||||
s.inner.Finalise(deleteEmptyObjects)
|
||||
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)",
|
||||
|
|
|
|||
|
|
@ -247,16 +247,16 @@ func TestCopyWithDirtyJournal(t *testing.T) {
|
|||
|
||||
orig.Finalise(true)
|
||||
for i := byte(0); i < 255; i++ {
|
||||
root := orig.GetStorageRoot(common.BytesToAddress([]byte{i}))
|
||||
if root != (common.Hash{}) {
|
||||
t.Errorf("Unexpected storage root %x", root)
|
||||
balance := orig.GetBalance(common.BytesToAddress([]byte{i}))
|
||||
if !balance.IsZero() {
|
||||
t.Errorf("Unexpected balance %x", root)
|
||||
}
|
||||
}
|
||||
cpy.Finalise(true)
|
||||
for i := byte(0); i < 255; i++ {
|
||||
root := cpy.GetStorageRoot(common.BytesToAddress([]byte{i}))
|
||||
if root != (common.Hash{}) {
|
||||
t.Errorf("Unexpected storage root %x", root)
|
||||
balance := cpy.GetBalance(common.BytesToAddress([]byte{i}))
|
||||
if !balance.IsZero() {
|
||||
t.Errorf("Unexpected balance %x", root)
|
||||
}
|
||||
}
|
||||
if cpy.IntermediateRoot(true) != orig.IntermediateRoot(true) {
|
||||
|
|
@ -394,9 +394,7 @@ func newTestAction(addr common.Address, r *rand.Rand) testAction {
|
|||
}
|
||||
contractHash := s.GetCodeHash(addr)
|
||||
emptyCode := contractHash == (common.Hash{}) || contractHash == types.EmptyCodeHash
|
||||
storageRoot := s.GetStorageRoot(addr)
|
||||
emptyStorage := storageRoot == (common.Hash{}) || storageRoot == types.EmptyRootHash
|
||||
if s.GetNonce(addr) == 0 && emptyCode && emptyStorage {
|
||||
if s.GetNonce(addr) == 0 && emptyCode {
|
||||
s.CreateContract(addr)
|
||||
// We also set some code here, to prevent the
|
||||
// CreateContract action from being performed twice in a row,
|
||||
|
|
@ -641,7 +639,7 @@ func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error {
|
|||
{
|
||||
have := state.transientStorage
|
||||
want := checkstate.transientStorage
|
||||
if !maps.EqualFunc(have, want, maps.Equal) {
|
||||
if !maps.Equal(have, want) {
|
||||
return fmt.Errorf("transient storage differs ,have\n%v\nwant\n%v",
|
||||
have.PrettyPrint(),
|
||||
want.PrettyPrint())
|
||||
|
|
@ -664,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{})
|
||||
|
|
@ -693,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")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1276,7 +1320,7 @@ func TestDeleteStorage(t *testing.T) {
|
|||
disk = rawdb.NewMemoryDatabase()
|
||||
tdb = triedb.NewDatabase(disk, nil)
|
||||
snaps, _ = snapshot.New(snapshot.Config{CacheSize: 10}, disk, tdb, types.EmptyRootHash)
|
||||
db = NewDatabase(tdb, nil).WithSnapshot(snaps)
|
||||
db = NewMPTDatabase(tdb, nil).WithSnapshot(snaps)
|
||||
state, _ = New(types.EmptyRootHash, db)
|
||||
addr = common.HexToAddress("0x1")
|
||||
)
|
||||
|
|
@ -1290,8 +1334,8 @@ func TestDeleteStorage(t *testing.T) {
|
|||
}
|
||||
root, _ := state.Commit(0, true, false)
|
||||
// Init phase done, create two states, one with snap and one without
|
||||
fastState, _ := New(root, NewDatabase(tdb, nil).WithSnapshot(snaps))
|
||||
slowState, _ := New(root, NewDatabase(tdb, nil))
|
||||
fastState, _ := New(root, NewMPTDatabase(tdb, nil).WithSnapshot(snaps))
|
||||
slowState, _ := New(root, NewMPTDatabase(tdb, nil))
|
||||
|
||||
obj := fastState.getOrNewStateObject(addr)
|
||||
storageRoot := obj.data.Root
|
||||
|
|
@ -1368,3 +1412,38 @@ func TestStorageDirtiness(t *testing.T) {
|
|||
state.RevertToSnapshot(snap)
|
||||
checkDirty(common.Hash{0x1}, common.Hash{0x1}, true)
|
||||
}
|
||||
|
||||
// TestStateDBCopyUBT exercises StateDB.Copy on a UBT-backed state database.
|
||||
// Before the mustCopyTrie fix, this panicked with "unknown trie type
|
||||
// *bintrie.BinaryTrie" because the type switch in mustCopyTrie only covered
|
||||
// *trie.StateTrie and *transitiontrie.TransitionTrie.
|
||||
func TestStateDBCopyUBT(t *testing.T) {
|
||||
disk := rawdb.NewMemoryDatabase()
|
||||
tdb := triedb.NewDatabase(disk, triedb.UBTDefaults)
|
||||
sdb := NewDatabase(tdb, nil)
|
||||
|
||||
orig, err := New(types.EmptyRootHash, sdb)
|
||||
if err != nil {
|
||||
t.Fatalf("New: %v", err)
|
||||
}
|
||||
|
||||
// Touch the trie so StateDB.Copy actually has to copy it.
|
||||
addr := common.HexToAddress("0x1111111111111111111111111111111111111111")
|
||||
orig.SetBalance(addr, uint256.NewInt(1_000), tracing.BalanceChangeUnspecified)
|
||||
|
||||
// Must not panic.
|
||||
cpy := orig.Copy()
|
||||
if cpy == nil {
|
||||
t.Fatal("Copy returned nil")
|
||||
}
|
||||
|
||||
// The copy must be independent: mutating the copy does not affect the
|
||||
// original. Use balance as an observable.
|
||||
cpy.SetBalance(addr, uint256.NewInt(2_000), tracing.BalanceChangeUnspecified)
|
||||
if got, want := orig.GetBalance(addr), uint256.NewInt(1_000); got.Cmp(want) != 0 {
|
||||
t.Fatalf("original balance mutated through copy: got %s, want %s", got, want)
|
||||
}
|
||||
if got, want := cpy.GetBalance(addr), uint256.NewInt(2_000); got.Cmp(want) != 0 {
|
||||
t.Fatalf("copy balance did not update: got %s, want %s", got, want)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,139 +26,143 @@ import (
|
|||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||
"github.com/ethereum/go-ethereum/triedb"
|
||||
)
|
||||
|
||||
// contractCode represents contract bytecode along with its associated metadata.
|
||||
type contractCode struct {
|
||||
hash common.Hash // hash is the cryptographic hash of the current contract code.
|
||||
blob []byte // blob is the binary representation of the current contract code.
|
||||
originHash common.Hash // originHash is the cryptographic hash of the code before mutation.
|
||||
// ContractCode represents contract bytecode mutation along with its
|
||||
// associated metadata.
|
||||
type ContractCode struct {
|
||||
Hash common.Hash // Hash is the cryptographic hash of the current contract code.
|
||||
Blob []byte // Blob is the binary representation of the current contract code.
|
||||
OriginHash common.Hash // OriginHash is the cryptographic hash of the code before mutation.
|
||||
|
||||
// Derived fields, populated only when state tracking is enabled.
|
||||
duplicate bool // duplicate indicates whether the updated code already exists.
|
||||
originBlob []byte // originBlob is the original binary representation of the contract code.
|
||||
Duplicate bool // Duplicate indicates whether the updated code already exists.
|
||||
OriginBlob []byte // OriginBlob is the original binary representation of the contract code.
|
||||
}
|
||||
|
||||
// accountDelete represents an operation for deleting an Ethereum account.
|
||||
type accountDelete struct {
|
||||
address common.Address // address is the unique account identifier
|
||||
origin []byte // origin is the original value of account data in slim-RLP encoding.
|
||||
|
||||
// storages stores mutated slots, the value should be nil.
|
||||
storages map[common.Hash][]byte
|
||||
|
||||
// storagesOrigin stores the original values of mutated slots in
|
||||
// prefix-zero-trimmed RLP format. The map key refers to the **HASH**
|
||||
// of the raw storage slot key.
|
||||
storagesOrigin map[common.Hash][]byte
|
||||
// AccountDelete represents a deletion operation for an Ethereum account.
|
||||
type AccountDelete struct {
|
||||
Address common.Address // Address uniquely identifies the account.
|
||||
Origin *types.StateAccount // Origin is the account state prior to deletion (never be null).
|
||||
Storages map[common.Hash]common.Hash // Storages contains mutated storage slots.
|
||||
StoragesOrigin map[common.Hash]common.Hash // StoragesOrigin holds original values of mutated slots; keys are hashes of raw storage slot keys.
|
||||
}
|
||||
|
||||
// accountUpdate represents an operation for updating an Ethereum account.
|
||||
type accountUpdate struct {
|
||||
address common.Address // address is the unique account identifier
|
||||
data []byte // data is the slim-RLP encoded account data.
|
||||
origin []byte // origin is the original value of account data in slim-RLP encoding.
|
||||
code *contractCode // code represents mutated contract code; nil means it's not modified.
|
||||
storages map[common.Hash][]byte // storages stores mutated slots in prefix-zero-trimmed RLP format.
|
||||
// AccountUpdate represents an update operation for an Ethereum account.
|
||||
type AccountUpdate struct {
|
||||
Address common.Address // Address uniquely identifies the account.
|
||||
Data *types.StateAccount // Data is the updated account state; nil indicates deletion.
|
||||
Origin *types.StateAccount // Origin is the previous account state; nil indicates non-existence.
|
||||
Code *ContractCode // Code contains updated contract code; nil if unchanged.
|
||||
Storages map[common.Hash]common.Hash // Storages contains updated storage slots.
|
||||
|
||||
// storagesOriginByKey and storagesOriginByHash both store the original values
|
||||
// of mutated slots in prefix-zero-trimmed RLP format. The difference is that
|
||||
// storagesOriginByKey uses the **raw** storage slot key as the map ID, while
|
||||
// storagesOriginByHash uses the **hash** of the storage slot key instead.
|
||||
storagesOriginByKey map[common.Hash][]byte
|
||||
storagesOriginByHash map[common.Hash][]byte
|
||||
// StoragesOriginByKey and StoragesOriginByHash both record original values
|
||||
// of mutated storage slots:
|
||||
// - StoragesOriginByKey uses raw storage slot keys.
|
||||
// - StoragesOriginByHash uses hashed storage slot keys.
|
||||
StoragesOriginByKey map[common.Hash]common.Hash
|
||||
StoragesOriginByHash map[common.Hash]common.Hash
|
||||
}
|
||||
|
||||
// stateUpdate represents the difference between two states resulting from state
|
||||
// StorageKeyEncoding specifies the encoding scheme of a storage key.
|
||||
type StorageKeyEncoding int
|
||||
|
||||
const (
|
||||
// StorageKeyHashed represents a hashed key (e.g. Keccak256).
|
||||
StorageKeyHashed StorageKeyEncoding = iota
|
||||
|
||||
// StorageKeyPlain represents a raw (unhashed) key.
|
||||
StorageKeyPlain
|
||||
)
|
||||
|
||||
// StateUpdate represents the difference between two states resulting from state
|
||||
// execution. It contains information about mutated contract codes, accounts,
|
||||
// and storage slots, along with their original values.
|
||||
type stateUpdate struct {
|
||||
originRoot common.Hash // hash of the state before applying mutation
|
||||
root common.Hash // hash of the state after applying mutation
|
||||
blockNumber uint64 // Associated block number
|
||||
type StateUpdate struct {
|
||||
OriginRoot common.Hash // Hash of the state before applying mutation
|
||||
Root common.Hash // Hash of the state after applying mutation
|
||||
BlockNumber uint64 // Associated block number
|
||||
|
||||
accounts map[common.Hash][]byte // accounts stores mutated accounts in 'slim RLP' encoding
|
||||
accountsOrigin map[common.Address][]byte // accountsOrigin stores the original values of mutated accounts in 'slim RLP' encoding
|
||||
// Accounts contains mutated accounts, keyed by address hash.
|
||||
Accounts map[common.Hash]*types.StateAccount
|
||||
|
||||
// storages stores mutated slots in 'prefix-zero-trimmed' RLP format.
|
||||
// The value is keyed by account hash and **storage slot key hash**.
|
||||
storages map[common.Hash]map[common.Hash][]byte
|
||||
// Storages contains mutated storage slots, keyed by address
|
||||
// hash and storage slot key hash.
|
||||
Storages map[common.Hash]map[common.Hash]common.Hash
|
||||
|
||||
// storagesOrigin stores the original values of mutated slots in
|
||||
// 'prefix-zero-trimmed' RLP format.
|
||||
// (a) the value is keyed by account hash and **storage slot key** if rawStorageKey is true;
|
||||
// (b) the value is keyed by account hash and **storage slot key hash** if rawStorageKey is false;
|
||||
storagesOrigin map[common.Address]map[common.Hash][]byte
|
||||
rawStorageKey bool
|
||||
// AccountsOrigin holds the original values of mutated accounts, keyed by address.
|
||||
AccountsOrigin map[common.Address]*types.StateAccount
|
||||
|
||||
codes map[common.Address]*contractCode // codes contains the set of dirty codes
|
||||
nodes *trienode.MergedNodeSet // Aggregated dirty nodes caused by state changes
|
||||
// StoragesOrigin holds the original values of mutated storage slots.
|
||||
// The key format depends on StorageKeyType:
|
||||
// - if StorageKeyType is plain: keyed by account address and plain storage slot key.
|
||||
// - if StorageKeyType is hashed: keyed by account address and storage slot key hash.
|
||||
StoragesOrigin map[common.Address]map[common.Hash]common.Hash
|
||||
StorageKeyType StorageKeyEncoding
|
||||
|
||||
Codes map[common.Address]*ContractCode // Codes contains the set of dirty codes
|
||||
Nodes *trienode.MergedNodeSet // Aggregated dirty nodes caused by state changes
|
||||
}
|
||||
|
||||
// empty returns a flag indicating the state transition is empty or not.
|
||||
func (sc *stateUpdate) empty() bool {
|
||||
return sc.originRoot == sc.root
|
||||
// Empty returns a flag indicating the state transition is empty or not.
|
||||
func (sc *StateUpdate) Empty() bool {
|
||||
return sc.OriginRoot == sc.Root
|
||||
}
|
||||
|
||||
// newStateUpdate constructs a state update object by identifying the differences
|
||||
// NewStateUpdate constructs a state update object by identifying the differences
|
||||
// between two states through state execution. It combines the specified account
|
||||
// deletions and account updates to create a complete state update.
|
||||
//
|
||||
// rawStorageKey is a flag indicating whether to use the raw storage slot key or
|
||||
// the hash of the slot key for constructing state update object.
|
||||
func newStateUpdate(rawStorageKey bool, originRoot common.Hash, root common.Hash, blockNumber uint64, deletes map[common.Hash]*accountDelete, updates map[common.Hash]*accountUpdate, nodes *trienode.MergedNodeSet) *stateUpdate {
|
||||
func NewStateUpdate(typ StorageKeyEncoding, originRoot common.Hash, root common.Hash, blockNumber uint64, deletes map[common.Hash]*AccountDelete, updates map[common.Hash]*AccountUpdate, nodes *trienode.MergedNodeSet) *StateUpdate {
|
||||
var (
|
||||
accounts = make(map[common.Hash][]byte)
|
||||
accountsOrigin = make(map[common.Address][]byte)
|
||||
storages = make(map[common.Hash]map[common.Hash][]byte)
|
||||
storagesOrigin = make(map[common.Address]map[common.Hash][]byte)
|
||||
codes = make(map[common.Address]*contractCode)
|
||||
accounts = make(map[common.Hash]*types.StateAccount)
|
||||
accountsOrigin = make(map[common.Address]*types.StateAccount)
|
||||
storages = make(map[common.Hash]map[common.Hash]common.Hash)
|
||||
storagesOrigin = make(map[common.Address]map[common.Hash]common.Hash)
|
||||
codes = make(map[common.Address]*ContractCode)
|
||||
)
|
||||
// Since some accounts might be destroyed and recreated within the same
|
||||
// Since some accounts might be deleted and recreated within the same
|
||||
// block, deletions must be aggregated first.
|
||||
for addrHash, op := range deletes {
|
||||
addr := op.address
|
||||
addr := op.Address
|
||||
accounts[addrHash] = nil
|
||||
accountsOrigin[addr] = op.origin
|
||||
accountsOrigin[addr] = op.Origin
|
||||
|
||||
// If storage wiping exists, the hash of the storage slot key must be used
|
||||
if len(op.storages) > 0 {
|
||||
storages[addrHash] = op.storages
|
||||
if len(op.Storages) > 0 {
|
||||
storages[addrHash] = op.Storages
|
||||
}
|
||||
if len(op.storagesOrigin) > 0 {
|
||||
storagesOrigin[addr] = op.storagesOrigin
|
||||
if len(op.StoragesOrigin) > 0 {
|
||||
storagesOrigin[addr] = op.StoragesOrigin
|
||||
}
|
||||
}
|
||||
// Aggregate account updates then.
|
||||
for addrHash, op := range updates {
|
||||
// Aggregate dirty contract codes if they are available.
|
||||
addr := op.address
|
||||
if op.code != nil {
|
||||
codes[addr] = op.code
|
||||
addr := op.Address
|
||||
if op.Code != nil {
|
||||
codes[addr] = op.Code
|
||||
}
|
||||
accounts[addrHash] = op.data
|
||||
accounts[addrHash] = op.Data
|
||||
|
||||
// Aggregate the account original value. If the account is already
|
||||
// present in the aggregated accountsOrigin set, skip it.
|
||||
// present in the aggregated AccountsOrigin set, skip it.
|
||||
if _, found := accountsOrigin[addr]; !found {
|
||||
accountsOrigin[addr] = op.origin
|
||||
accountsOrigin[addr] = op.Origin
|
||||
}
|
||||
// Aggregate the storage mutation list. If a slot in op.storages is
|
||||
// already present in aggregated storages set, the value will be
|
||||
// overwritten.
|
||||
if len(op.storages) > 0 {
|
||||
if len(op.Storages) > 0 {
|
||||
if _, exist := storages[addrHash]; !exist {
|
||||
storages[addrHash] = op.storages
|
||||
storages[addrHash] = op.Storages
|
||||
} else {
|
||||
maps.Copy(storages[addrHash], op.storages)
|
||||
maps.Copy(storages[addrHash], op.Storages)
|
||||
}
|
||||
}
|
||||
// Aggregate the storage original values. If the slot is already present
|
||||
// in aggregated storagesOrigin set, skip it.
|
||||
storageOriginSet := op.storagesOriginByHash
|
||||
if rawStorageKey {
|
||||
storageOriginSet = op.storagesOriginByKey
|
||||
// in aggregated StoragesOrigin set, skip it.
|
||||
storageOriginSet := op.StoragesOriginByHash
|
||||
if typ == StorageKeyPlain {
|
||||
storageOriginSet = op.StoragesOriginByKey
|
||||
}
|
||||
if len(storageOriginSet) > 0 {
|
||||
origin, exist := storagesOrigin[addr]
|
||||
|
|
@ -173,32 +177,114 @@ func newStateUpdate(rawStorageKey bool, originRoot common.Hash, root common.Hash
|
|||
}
|
||||
}
|
||||
}
|
||||
return &stateUpdate{
|
||||
originRoot: originRoot,
|
||||
root: root,
|
||||
blockNumber: blockNumber,
|
||||
accounts: accounts,
|
||||
accountsOrigin: accountsOrigin,
|
||||
storages: storages,
|
||||
storagesOrigin: storagesOrigin,
|
||||
rawStorageKey: rawStorageKey,
|
||||
codes: codes,
|
||||
nodes: nodes,
|
||||
return &StateUpdate{
|
||||
OriginRoot: originRoot,
|
||||
Root: root,
|
||||
BlockNumber: blockNumber,
|
||||
Accounts: accounts,
|
||||
AccountsOrigin: accountsOrigin,
|
||||
Storages: storages,
|
||||
StoragesOrigin: storagesOrigin,
|
||||
StorageKeyType: typ,
|
||||
Codes: codes,
|
||||
Nodes: nodes,
|
||||
}
|
||||
}
|
||||
|
||||
// stateSet converts the current stateUpdate object into a triedb.StateSet
|
||||
// object. This function extracts the necessary data from the stateUpdate
|
||||
// struct and formats it into the StateSet structure consumed by the triedb
|
||||
// package.
|
||||
func (sc *stateUpdate) stateSet() *triedb.StateSet {
|
||||
return &triedb.StateSet{
|
||||
Accounts: sc.accounts,
|
||||
AccountsOrigin: sc.accountsOrigin,
|
||||
Storages: sc.storages,
|
||||
StoragesOrigin: sc.storagesOrigin,
|
||||
RawStorageKey: sc.rawStorageKey,
|
||||
// encodeSlot encodes the storage slot value by trimming all leading zeros
|
||||
// and then RLP-encoding the result.
|
||||
func encodeSlot(value common.Hash) []byte {
|
||||
if value == (common.Hash{}) {
|
||||
return nil
|
||||
}
|
||||
blob, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(value[:]))
|
||||
return blob
|
||||
}
|
||||
|
||||
// EncodeMPTState encodes all state mutations alongside their original value
|
||||
// into the Merkle-Patricia-Trie representation.
|
||||
//
|
||||
// It transforms account and storage updates into their corresponding MPT-encoded
|
||||
// key-value mappings, using the same encoding rules as the Ethereum state trie.
|
||||
func (sc *StateUpdate) EncodeMPTState() (map[common.Hash][]byte, map[common.Address][]byte, map[common.Hash]map[common.Hash][]byte, map[common.Address]map[common.Hash][]byte) {
|
||||
var (
|
||||
accounts = make(map[common.Hash][]byte, len(sc.Accounts))
|
||||
storages = make(map[common.Hash]map[common.Hash][]byte, len(sc.Storages))
|
||||
accountOrigin = make(map[common.Address][]byte, len(sc.AccountsOrigin))
|
||||
storageOrigin = make(map[common.Address]map[common.Hash][]byte, len(sc.StoragesOrigin))
|
||||
)
|
||||
for addr, prev := range sc.AccountsOrigin {
|
||||
if prev == nil {
|
||||
accountOrigin[addr] = nil
|
||||
} else {
|
||||
accountOrigin[addr] = types.SlimAccountRLP(*prev)
|
||||
}
|
||||
}
|
||||
for addrHash, data := range sc.Accounts {
|
||||
if data == nil {
|
||||
accounts[addrHash] = nil
|
||||
} else {
|
||||
accounts[addrHash] = types.SlimAccountRLP(*data)
|
||||
}
|
||||
}
|
||||
for addr, slots := range sc.StoragesOrigin {
|
||||
subset := make(map[common.Hash][]byte)
|
||||
for key, val := range slots {
|
||||
subset[key] = encodeSlot(val)
|
||||
}
|
||||
storageOrigin[addr] = subset
|
||||
}
|
||||
for addrHash, slots := range sc.Storages {
|
||||
subset := make(map[common.Hash][]byte)
|
||||
for key, val := range slots {
|
||||
subset[key] = encodeSlot(val)
|
||||
}
|
||||
storages[addrHash] = subset
|
||||
}
|
||||
return accounts, accountOrigin, storages, storageOrigin
|
||||
}
|
||||
|
||||
// EncodeUBTState encodes all state mutations alongside their original value
|
||||
// into the Unified-Binary-Trie representation.
|
||||
//
|
||||
// It transforms account and storage updates into their corresponding UBT-encoded
|
||||
// key-value mappings, using the same encoding rules as the Ethereum state trie.
|
||||
func (sc *StateUpdate) EncodeUBTState() (map[common.Hash][]byte, map[common.Address][]byte, map[common.Hash]map[common.Hash][]byte, map[common.Address]map[common.Hash][]byte) {
|
||||
var (
|
||||
accounts = make(map[common.Hash][]byte, len(sc.Accounts))
|
||||
storages = make(map[common.Hash]map[common.Hash][]byte, len(sc.Storages))
|
||||
accountOrigin = make(map[common.Address][]byte, len(sc.AccountsOrigin))
|
||||
storageOrigin = make(map[common.Address]map[common.Hash][]byte, len(sc.StoragesOrigin))
|
||||
)
|
||||
for addr, prev := range sc.AccountsOrigin {
|
||||
if prev == nil {
|
||||
accountOrigin[addr] = nil
|
||||
} else {
|
||||
accountOrigin[addr] = types.SlimAccountRLP(*prev)
|
||||
}
|
||||
}
|
||||
for addrHash, data := range sc.Accounts {
|
||||
if data == nil {
|
||||
accounts[addrHash] = nil
|
||||
} else {
|
||||
accounts[addrHash] = types.SlimAccountRLP(*data)
|
||||
}
|
||||
}
|
||||
for addr, slots := range sc.StoragesOrigin {
|
||||
subset := make(map[common.Hash][]byte)
|
||||
for key, val := range slots {
|
||||
subset[key] = encodeSlot(val)
|
||||
}
|
||||
storageOrigin[addr] = subset
|
||||
}
|
||||
for addrHash, slots := range sc.Storages {
|
||||
subset := make(map[common.Hash][]byte)
|
||||
for key, val := range slots {
|
||||
subset[key] = encodeSlot(val)
|
||||
}
|
||||
storages[addrHash] = subset
|
||||
}
|
||||
return accounts, accountOrigin, storages, storageOrigin
|
||||
}
|
||||
|
||||
// deriveCodeFields derives the missing fields of contract code changes
|
||||
|
|
@ -207,135 +293,96 @@ func (sc *stateUpdate) stateSet() *triedb.StateSet {
|
|||
// Note: This operation is expensive and not needed during normal state
|
||||
// transitions. It is only required when SizeTracker or StateUpdate hook
|
||||
// is enabled to produce accurate state statistics.
|
||||
func (sc *stateUpdate) deriveCodeFields(reader ContractCodeReader) error {
|
||||
func (sc *StateUpdate) deriveCodeFields(reader ContractCodeReader) error {
|
||||
cache := make(map[common.Hash]bool)
|
||||
for addr, code := range sc.codes {
|
||||
if code.originHash != types.EmptyCodeHash {
|
||||
blob := reader.Code(addr, code.originHash)
|
||||
for addr, code := range sc.Codes {
|
||||
if code.OriginHash != types.EmptyCodeHash {
|
||||
blob := reader.Code(addr, code.OriginHash)
|
||||
if len(blob) == 0 {
|
||||
return fmt.Errorf("original code of %x is empty", addr)
|
||||
}
|
||||
code.originBlob = blob
|
||||
code.OriginBlob = blob
|
||||
}
|
||||
if exists, ok := cache[code.hash]; ok {
|
||||
code.duplicate = exists
|
||||
if exists, ok := cache[code.Hash]; ok {
|
||||
code.Duplicate = exists
|
||||
continue
|
||||
}
|
||||
res := reader.Has(addr, code.hash)
|
||||
cache[code.hash] = res
|
||||
code.duplicate = res
|
||||
res := reader.Has(addr, code.Hash)
|
||||
cache[code.Hash] = res
|
||||
code.Duplicate = res
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToTracingUpdate converts the internal stateUpdate to an exported tracing.StateUpdate.
|
||||
func (sc *stateUpdate) ToTracingUpdate() (*tracing.StateUpdate, error) {
|
||||
// ToTracingUpdate converts the internal StateUpdate to an exported tracing.StateUpdate.
|
||||
func (sc *StateUpdate) ToTracingUpdate() (*tracing.StateUpdate, error) {
|
||||
update := &tracing.StateUpdate{
|
||||
OriginRoot: sc.originRoot,
|
||||
Root: sc.root,
|
||||
BlockNumber: sc.blockNumber,
|
||||
AccountChanges: make(map[common.Address]*tracing.AccountChange, len(sc.accountsOrigin)),
|
||||
OriginRoot: sc.OriginRoot,
|
||||
Root: sc.Root,
|
||||
BlockNumber: sc.BlockNumber,
|
||||
AccountChanges: make(map[common.Address]*tracing.AccountChange, len(sc.AccountsOrigin)),
|
||||
StorageChanges: make(map[common.Address]map[common.Hash]*tracing.StorageChange),
|
||||
CodeChanges: make(map[common.Address]*tracing.CodeChange, len(sc.codes)),
|
||||
CodeChanges: make(map[common.Address]*tracing.CodeChange, len(sc.Codes)),
|
||||
TrieChanges: make(map[common.Hash]map[string]*tracing.TrieNodeChange),
|
||||
}
|
||||
// Gather all account changes
|
||||
for addr, oldData := range sc.accountsOrigin {
|
||||
for addr, oldData := range sc.AccountsOrigin {
|
||||
addrHash := crypto.Keccak256Hash(addr.Bytes())
|
||||
newData, exists := sc.accounts[addrHash]
|
||||
newData, exists := sc.Accounts[addrHash]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("account %x not found", addr)
|
||||
}
|
||||
change := &tracing.AccountChange{}
|
||||
|
||||
if len(oldData) > 0 {
|
||||
acct, err := types.FullAccount(oldData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
change.Prev = &types.StateAccount{
|
||||
Nonce: acct.Nonce,
|
||||
Balance: acct.Balance,
|
||||
Root: acct.Root,
|
||||
CodeHash: acct.CodeHash,
|
||||
}
|
||||
}
|
||||
if len(newData) > 0 {
|
||||
acct, err := types.FullAccount(newData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
change.New = &types.StateAccount{
|
||||
Nonce: acct.Nonce,
|
||||
Balance: acct.Balance,
|
||||
Root: acct.Root,
|
||||
CodeHash: acct.CodeHash,
|
||||
}
|
||||
change := &tracing.AccountChange{
|
||||
Prev: oldData,
|
||||
New: newData,
|
||||
}
|
||||
update.AccountChanges[addr] = change
|
||||
}
|
||||
|
||||
// Gather all storage slot changes
|
||||
for addr, slots := range sc.storagesOrigin {
|
||||
for addr, slots := range sc.StoragesOrigin {
|
||||
addrHash := crypto.Keccak256Hash(addr.Bytes())
|
||||
subset, exists := sc.storages[addrHash]
|
||||
subset, exists := sc.Storages[addrHash]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("storage %x not found", addr)
|
||||
}
|
||||
storageChanges := make(map[common.Hash]*tracing.StorageChange, len(slots))
|
||||
|
||||
for key, encPrev := range slots {
|
||||
for key, oldData := range slots {
|
||||
// Get new value - handle both raw and hashed key formats
|
||||
var (
|
||||
exists bool
|
||||
encNew []byte
|
||||
decPrev []byte
|
||||
decNew []byte
|
||||
err error
|
||||
newData common.Hash
|
||||
)
|
||||
if sc.rawStorageKey {
|
||||
encNew, exists = subset[crypto.Keccak256Hash(key.Bytes())]
|
||||
if sc.StorageKeyType == StorageKeyPlain {
|
||||
newData, exists = subset[crypto.Keccak256Hash(key.Bytes())]
|
||||
} else {
|
||||
encNew, exists = subset[key]
|
||||
newData, exists = subset[key]
|
||||
}
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("storage slot %x-%x not found", addr, key)
|
||||
}
|
||||
|
||||
// Decode the prev and new values
|
||||
if len(encPrev) > 0 {
|
||||
_, decPrev, _, err = rlp.Split(encPrev)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode prevValue: %v", err)
|
||||
}
|
||||
}
|
||||
if len(encNew) > 0 {
|
||||
_, decNew, _, err = rlp.Split(encNew)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode newValue: %v", err)
|
||||
}
|
||||
}
|
||||
storageChanges[key] = &tracing.StorageChange{
|
||||
Prev: common.BytesToHash(decPrev),
|
||||
New: common.BytesToHash(decNew),
|
||||
Prev: oldData,
|
||||
New: newData,
|
||||
}
|
||||
}
|
||||
update.StorageChanges[addr] = storageChanges
|
||||
}
|
||||
|
||||
// Gather all contract code changes
|
||||
for addr, code := range sc.codes {
|
||||
for addr, code := range sc.Codes {
|
||||
change := &tracing.CodeChange{
|
||||
New: &tracing.ContractCode{
|
||||
Hash: code.hash,
|
||||
Code: code.blob,
|
||||
Exists: code.duplicate,
|
||||
Hash: code.Hash,
|
||||
Code: code.Blob,
|
||||
Exists: code.Duplicate,
|
||||
},
|
||||
}
|
||||
if code.originHash != types.EmptyCodeHash {
|
||||
if code.OriginHash != types.EmptyCodeHash {
|
||||
change.Prev = &tracing.ContractCode{
|
||||
Hash: code.originHash,
|
||||
Code: code.originBlob,
|
||||
Hash: code.OriginHash,
|
||||
Code: code.OriginBlob,
|
||||
Exists: true,
|
||||
}
|
||||
}
|
||||
|
|
@ -343,8 +390,8 @@ func (sc *stateUpdate) ToTracingUpdate() (*tracing.StateUpdate, error) {
|
|||
}
|
||||
|
||||
// Gather all trie node changes
|
||||
if sc.nodes != nil {
|
||||
for owner, subset := range sc.nodes.Sets {
|
||||
if sc.Nodes != nil {
|
||||
for owner, subset := range sc.Nodes.Sets {
|
||||
nodeChanges := make(map[string]*tracing.TrieNodeChange, len(subset.Origins))
|
||||
for path, oldNode := range subset.Origins {
|
||||
newNode, exists := subset.Nodes[path]
|
||||
|
|
|
|||
|
|
@ -25,8 +25,13 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
type transientStorageKey struct {
|
||||
addr common.Address
|
||||
key common.Hash
|
||||
}
|
||||
|
||||
// transientStorage is a representation of EIP-1153 "Transient Storage".
|
||||
type transientStorage map[common.Address]Storage
|
||||
type transientStorage map[transientStorageKey]common.Hash
|
||||
|
||||
// newTransientStorage creates a new instance of a transientStorage.
|
||||
func newTransientStorage() transientStorage {
|
||||
|
|
@ -35,52 +40,43 @@ func newTransientStorage() transientStorage {
|
|||
|
||||
// Set sets the transient-storage `value` for `key` at the given `addr`.
|
||||
func (t transientStorage) Set(addr common.Address, key, value common.Hash) {
|
||||
tsKey := transientStorageKey{addr: addr, key: key}
|
||||
if value == (common.Hash{}) { // this is a 'delete'
|
||||
if _, ok := t[addr]; ok {
|
||||
delete(t[addr], key)
|
||||
if len(t[addr]) == 0 {
|
||||
delete(t, addr)
|
||||
}
|
||||
}
|
||||
delete(t, tsKey)
|
||||
} else {
|
||||
if _, ok := t[addr]; !ok {
|
||||
t[addr] = make(Storage)
|
||||
}
|
||||
t[addr][key] = value
|
||||
t[tsKey] = value
|
||||
}
|
||||
}
|
||||
|
||||
// Get gets the transient storage for `key` at the given `addr`.
|
||||
func (t transientStorage) Get(addr common.Address, key common.Hash) common.Hash {
|
||||
val, ok := t[addr]
|
||||
if !ok {
|
||||
return common.Hash{}
|
||||
}
|
||||
return val[key]
|
||||
tsKey := transientStorageKey{addr: addr, key: key}
|
||||
return t[tsKey]
|
||||
}
|
||||
|
||||
// Copy does a deep copy of the transientStorage
|
||||
func (t transientStorage) Copy() transientStorage {
|
||||
storage := make(transientStorage)
|
||||
for key, value := range t {
|
||||
storage[key] = value.Copy()
|
||||
}
|
||||
return storage
|
||||
return maps.Clone(t)
|
||||
}
|
||||
|
||||
// PrettyPrint prints the contents of the access list in a human-readable form
|
||||
func (t transientStorage) PrettyPrint() string {
|
||||
out := new(strings.Builder)
|
||||
sortedAddrs := slices.Collect(maps.Keys(t))
|
||||
slices.SortFunc(sortedAddrs, common.Address.Cmp)
|
||||
sortedTSKeys := slices.Collect(maps.Keys(t))
|
||||
slices.SortFunc(sortedTSKeys, func(a, b transientStorageKey) int {
|
||||
r := a.addr.Cmp(b.addr)
|
||||
if r != 0 {
|
||||
return r
|
||||
}
|
||||
return a.key.Cmp(b.key)
|
||||
})
|
||||
|
||||
for _, addr := range sortedAddrs {
|
||||
fmt.Fprintf(out, "%#x:", addr)
|
||||
storage := t[addr]
|
||||
sortedKeys := slices.Collect(maps.Keys(storage))
|
||||
slices.SortFunc(sortedKeys, common.Hash.Cmp)
|
||||
for _, key := range sortedKeys {
|
||||
fmt.Fprintf(out, " %X : %X\n", key, storage[key])
|
||||
for i := 0; i < len(sortedTSKeys); {
|
||||
tsKey := sortedTSKeys[i]
|
||||
fmt.Fprintf(out, "%#x:", tsKey.addr)
|
||||
for ; i < len(sortedTSKeys) && sortedTSKeys[i].addr == tsKey.addr; i++ {
|
||||
tsKey2 := sortedTSKeys[i]
|
||||
fmt.Fprintf(out, " %X : %X\n", tsKey2.key, t[tsKey2])
|
||||
}
|
||||
}
|
||||
return out.String()
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ var (
|
|||
//
|
||||
// Note, the prefetcher's API is not thread safe.
|
||||
type triePrefetcher struct {
|
||||
verkle bool // Flag whether the prefetcher is in verkle mode
|
||||
isUBT bool // Flag whether the prefetcher is in UBT mode
|
||||
db Database // Database to fetch trie nodes through
|
||||
root common.Hash // Root hash of the account trie for metrics
|
||||
fetchers map[string]*subfetcher // Subfetchers for each trie
|
||||
|
|
@ -67,7 +67,7 @@ type triePrefetcher struct {
|
|||
func newTriePrefetcher(db Database, root common.Hash, namespace string, noreads bool) *triePrefetcher {
|
||||
prefix := triePrefetchMetricsPrefix + namespace
|
||||
return &triePrefetcher{
|
||||
verkle: db.TrieDB().IsVerkle(),
|
||||
isUBT: db.Type().Is(TypeUBT),
|
||||
db: db,
|
||||
root: root,
|
||||
fetchers: make(map[string]*subfetcher), // Active prefetchers use the fetchers map
|
||||
|
|
@ -206,8 +206,8 @@ func (p *triePrefetcher) used(owner common.Hash, root common.Hash, usedAddr []co
|
|||
|
||||
// trieID returns an unique trie identifier consists the trie owner and root hash.
|
||||
func (p *triePrefetcher) trieID(owner common.Hash, root common.Hash) string {
|
||||
// The trie in verkle is only identified by state root
|
||||
if p.verkle {
|
||||
// The trie in ubt is only identified by state root
|
||||
if p.isUBT {
|
||||
return p.root.Hex()
|
||||
}
|
||||
// The trie in merkle is either identified by state root (account trie),
|
||||
|
|
@ -340,12 +340,12 @@ func (sf *subfetcher) terminate(async bool) {
|
|||
|
||||
// openTrie resolves the target trie from database for prefetching.
|
||||
func (sf *subfetcher) openTrie() error {
|
||||
// Open the verkle tree if the sub-fetcher is in verkle mode. Note, there is
|
||||
// only a single fetcher for verkle.
|
||||
if sf.db.TrieDB().IsVerkle() {
|
||||
// Open the ubt tree if the sub-fetcher is in ubt mode. Note, there is
|
||||
// only a single fetcher for ubt.
|
||||
if sf.db.Type().Is(TypeUBT) {
|
||||
tr, err := sf.db.OpenTrie(sf.state)
|
||||
if err != nil {
|
||||
log.Warn("Trie prefetcher failed opening verkle trie", "root", sf.root, "err", err)
|
||||
log.Warn("Trie prefetcher failed opening UBT trie", "root", sf.root, "err", err)
|
||||
return err
|
||||
}
|
||||
sf.trie = tr
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ func TestUseAfterTerminate(t *testing.T) {
|
|||
|
||||
func TestVerklePrefetcher(t *testing.T) {
|
||||
disk := rawdb.NewMemoryDatabase()
|
||||
db := triedb.NewDatabase(disk, triedb.VerkleDefaults)
|
||||
db := triedb.NewDatabase(disk, triedb.UBTDefaults)
|
||||
sdb := NewDatabase(db, nil)
|
||||
|
||||
state, err := New(types.EmptyRootHash, sdb)
|
||||
|
|
@ -86,18 +86,17 @@ func TestVerklePrefetcher(t *testing.T) {
|
|||
root, _ := state.Commit(0, true, false)
|
||||
|
||||
state, _ = New(root, sdb)
|
||||
sRoot := state.GetStorageRoot(addr)
|
||||
fetcher := newTriePrefetcher(sdb, root, "", false)
|
||||
|
||||
// Read account
|
||||
fetcher.prefetch(common.Hash{}, root, common.Address{}, []common.Address{addr}, nil, false)
|
||||
|
||||
// Read storage slot
|
||||
fetcher.prefetch(crypto.Keccak256Hash(addr.Bytes()), sRoot, addr, nil, []common.Hash{skey}, false)
|
||||
fetcher.prefetch(crypto.Keccak256Hash(addr.Bytes()), common.Hash{}, addr, nil, []common.Hash{skey}, false)
|
||||
|
||||
fetcher.terminate(false)
|
||||
accountTrie := fetcher.trie(common.Hash{}, root)
|
||||
storageTrie := fetcher.trie(crypto.Keccak256Hash(addr.Bytes()), sRoot)
|
||||
storageTrie := fetcher.trie(crypto.Keccak256Hash(addr.Bytes()), common.Hash{})
|
||||
|
||||
rootA := accountTrie.Hash()
|
||||
rootB := storageTrie.Hash()
|
||||
|
|
|
|||
|
|
@ -93,6 +93,7 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c
|
|||
}
|
||||
// Execute the message to preload the implicit touched states
|
||||
evm := vm.NewEVM(NewEVMBlockContext(header, p.chain, nil), stateCpy, p.config, cfg)
|
||||
defer evm.Release()
|
||||
|
||||
// Convert the transaction into an executable message and pre-cache its sender
|
||||
msg, err := TransactionToMessage(tx, signer, header.BaseFee)
|
||||
|
|
@ -103,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.
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import (
|
|||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/consensus/misc"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
|
|
@ -30,6 +31,8 @@ import (
|
|||
"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
|
||||
|
|
@ -62,7 +65,7 @@ func (p *StateProcessor) chainConfig() *params.ChainConfig {
|
|||
func (p *StateProcessor) Process(ctx context.Context, block *types.Block, statedb *state.StateDB, cfg vm.Config) (*ProcessResult, error) {
|
||||
var (
|
||||
config = p.chainConfig()
|
||||
receipts types.Receipts
|
||||
receipts = make(types.Receipts, 0, len(block.Transactions()))
|
||||
header = block.Header()
|
||||
blockHash = block.Hash()
|
||||
blockNumber = block.Number()
|
||||
|
|
@ -73,34 +76,25 @@ 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
|
||||
context = NewEVMBlockContext(header, p.chain, nil)
|
||||
signer = types.MakeSigner(config, header.Number, header.Time)
|
||||
evm = vm.NewEVM(context, tracingStateDB, config, cfg)
|
||||
)
|
||||
|
||||
// Apply pre-execution system calls.
|
||||
context = NewEVMBlockContext(header, p.chain, nil)
|
||||
evm := vm.NewEVM(context, tracingStateDB, config, cfg)
|
||||
|
||||
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
|
||||
ProcessBeaconBlockRoot(*beaconRoot, evm)
|
||||
}
|
||||
if config.IsPrague(block.Number(), block.Time()) || config.IsVerkle(block.Number(), block.Time()) {
|
||||
ProcessParentBlockHash(block.ParentHash(), evm)
|
||||
}
|
||||
|
||||
defer evm.Release()
|
||||
// Run the pre-execution system calls
|
||||
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() {
|
||||
msg, err := TransactionToMessage(tx, signer, header.BaseFee)
|
||||
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)),
|
||||
|
|
@ -115,11 +109,10 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated
|
|||
allLogs = append(allLogs, receipt.Logs...)
|
||||
spanEnd(nil)
|
||||
}
|
||||
requests, err := postExecution(ctx, config, block, allLogs, evm)
|
||||
requests, err := PostExecution(ctx, config, block.Number(), block.Time(), allLogs, evm, uint32(len(block.Transactions())+1))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
|
||||
p.chain.Engine().Finalize(p.chain, header, tracingStateDB, block.Body())
|
||||
|
||||
|
|
@ -131,28 +124,44 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated
|
|||
}, 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) {
|
||||
_, _, spanEnd := telemetry.StartSpan(ctx, "core.preExecution")
|
||||
defer spanEnd(nil)
|
||||
|
||||
// EIP-4788
|
||||
if beaconRoot != nil {
|
||||
ProcessBeaconBlockRoot(*beaconRoot, evm)
|
||||
}
|
||||
// EIP-2935
|
||||
if config.IsPrague(number, time) || config.IsUBT(number, time) {
|
||||
ProcessParentBlockHash(parent, evm)
|
||||
}
|
||||
}
|
||||
|
||||
// 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, err error) {
|
||||
_, _, spanEnd := telemetry.StartSpan(ctx, "core.postExecution")
|
||||
defer spanEnd(&err)
|
||||
|
||||
// Read requests if Prague is enabled.
|
||||
if config.IsPrague(block.Number(), block.Time()) {
|
||||
if config.IsPrague(number, time) {
|
||||
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, 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, evm, blockAccessIndex); err != nil {
|
||||
return 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, evm, blockAccessIndex); err != nil {
|
||||
return nil, fmt.Errorf("failed to process consolidation queue: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return requests, nil
|
||||
}
|
||||
|
||||
|
|
@ -182,7 +191,7 @@ func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb *state.StateDB,
|
|||
}
|
||||
// Merge the tx-local access event into the "block-local" one, in order to collect
|
||||
// all values, so that the witness can be built.
|
||||
if statedb.Database().TrieDB().IsVerkle() {
|
||||
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
|
||||
|
|
@ -251,15 +260,16 @@ 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.SetTxContext(common.Hash{}, 0, 0)
|
||||
evm.StateDB.AddAddressToAccessList(params.BeaconRootsAddress)
|
||||
_, _, _ = evm.Call(msg.From, *msg.To, msg.Data, 30_000_000, common.U2560)
|
||||
_, _, _ = 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)
|
||||
}
|
||||
|
|
@ -278,15 +288,16 @@ 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.SetTxContext(common.Hash{}, 0, 0)
|
||||
evm.StateDB.AddAddressToAccessList(params.HistoryStorageAddress)
|
||||
_, _, err := evm.Call(msg.From, *msg.To, msg.Data, 30_000_000, common.U2560)
|
||||
_, _, err := evm.Call(msg.From, *msg.To, msg.Data, vm.NewGasBudget(30_000_000), common.U2560)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
|
@ -298,17 +309,17 @@ func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM) {
|
|||
|
||||
// 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, evm *vm.EVM, blockAccessIndex uint32) error {
|
||||
return processRequestsSystemCall(requests, evm, 0x01, params.WithdrawalQueueAddress, blockAccessIndex)
|
||||
}
|
||||
|
||||
// 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, evm *vm.EVM, blockAccessIndex uint32) error {
|
||||
return processRequestsSystemCall(requests, evm, 0x02, params.ConsolidationQueueAddress, blockAccessIndex)
|
||||
}
|
||||
|
||||
func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte, addr common.Address) error {
|
||||
func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte, addr common.Address, blockAccessIndex uint32) error {
|
||||
if tracer := evm.Config.Tracer; tracer != nil {
|
||||
onSystemCallStart(tracer, evm.GetVMContext())
|
||||
if tracer.OnSystemCallEnd != nil {
|
||||
|
|
@ -318,14 +329,15 @@ 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.SetTxContext(common.Hash{}, 0, blockAccessIndex)
|
||||
evm.StateDB.AddAddressToAccessList(addr)
|
||||
ret, _, err := evm.Call(msg.From, *msg.To, msg.Data, 30_000_000, common.U2560)
|
||||
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)
|
||||
}
|
||||
|
|
@ -372,3 +384,11 @@ func onSystemCallStart(tracer *tracing.Hooks, ctx *tracing.VMContext) {
|
|||
tracer.OnSystemCallStart()
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
|
||||
return types.NewBlock(header, body, receipts, trie.NewStackTrie(nil))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) (uint64, 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 {
|
||||
|
|
@ -89,46 +89,110 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set
|
|||
nonZeroGas = params.TxDataNonZeroGasEIP2028
|
||||
}
|
||||
if (math.MaxUint64-gas)/nonZeroGas < nz {
|
||||
return 0, ErrGasUintOverflow
|
||||
return vm.GasCosts{}, ErrGasUintOverflow
|
||||
}
|
||||
gas += nz * nonZeroGas
|
||||
|
||||
if (math.MaxUint64-gas)/params.TxDataZeroGas < z {
|
||||
return 0, ErrGasUintOverflow
|
||||
return vm.GasCosts{}, ErrGasUintOverflow
|
||||
}
|
||||
gas += z * params.TxDataZeroGas
|
||||
|
||||
if isContractCreation && isEIP3860 {
|
||||
lenWords := toWordSize(dataLen)
|
||||
if (math.MaxUint64-gas)/params.InitCodeWordGas < lenWords {
|
||||
return 0, ErrGasUintOverflow
|
||||
return vm.GasCosts{}, ErrGasUintOverflow
|
||||
}
|
||||
gas += lenWords * params.InitCodeWordGas
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
return gas, nil
|
||||
return vm.GasCosts{RegularGas: gas}, nil
|
||||
}
|
||||
|
||||
// FloorDataGas computes the minimum gas required for a transaction based on its data tokens (EIP-7623).
|
||||
func FloorDataGas(data []byte) (uint64, error) {
|
||||
func FloorDataGas(rules params.Rules, data []byte, accessList types.AccessList) (uint64, error) {
|
||||
var (
|
||||
z = uint64(bytes.Count(data, []byte{0}))
|
||||
nz = uint64(len(data)) - z
|
||||
tokens = nz*params.TxTokenPerNonZeroByte + z
|
||||
tokens uint64
|
||||
tokenCost uint64
|
||||
)
|
||||
if rules.IsAmsterdam {
|
||||
// EIP-7976 changes how calldata is priced.
|
||||
// From 10/40 to 64/64 for zero/non-zero bytes.
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
// Check for overflow
|
||||
if (math.MaxUint64-params.TxGas)/params.TxCostFloorPerToken < tokens {
|
||||
if (math.MaxUint64-params.TxGas)/tokenCost < tokens {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
// Minimum gas required for a transaction based on its data tokens (EIP-7623).
|
||||
return params.TxGas + tokens*params.TxCostFloorPerToken, nil
|
||||
return params.TxGas + tokens*tokenCost, nil
|
||||
}
|
||||
|
||||
// toWordSize returns the ceiled word size required for init code payment calculation.
|
||||
|
|
@ -146,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
|
||||
|
||||
|
|
@ -174,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
|
||||
|
|
@ -242,12 +338,12 @@ func ApplyMessage(evm *vm.EVM, msg *Message, gp *GasPool) (*ExecutionResult, err
|
|||
// 5. Run Script section
|
||||
// 6. Derive new state root
|
||||
type stateTransition struct {
|
||||
gp *GasPool
|
||||
msg *Message
|
||||
gasRemaining uint64
|
||||
initialGas uint64
|
||||
state vm.StateDB
|
||||
evm *vm.EVM
|
||||
gp *GasPool
|
||||
msg *Message
|
||||
initialBudget vm.GasBudget
|
||||
gasRemaining vm.GasBudget
|
||||
state vm.StateDB
|
||||
evm *vm.EVM
|
||||
}
|
||||
|
||||
// newStateTransition initialises and returns a new state transition object.
|
||||
|
|
@ -269,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 = st.msg.GasLimit
|
||||
st.initialGas = st.msg.GasLimit
|
||||
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
|
||||
}
|
||||
|
||||
|
|
@ -330,9 +450,10 @@ func (st *stateTransition) preCheck() error {
|
|||
}
|
||||
}
|
||||
isOsaka := st.evm.ChainConfig().IsOsaka(st.evm.Context.BlockNumber, st.evm.Context.Time)
|
||||
isAmsterdam := st.evm.ChainConfig().IsAmsterdam(st.evm.Context.BlockNumber, st.evm.Context.Time)
|
||||
if !msg.SkipTransactionChecks {
|
||||
// Verify tx gas limit does not exceed EIP-7825 cap.
|
||||
if isOsaka && msg.GasLimit > params.MaxTxGas {
|
||||
if isOsaka && !isAmsterdam && msg.GasLimit > params.MaxTxGas {
|
||||
return fmt.Errorf("%w (cap: %d, tx: %d)", ErrGasLimitTooHigh, params.MaxTxGas, msg.GasLimit)
|
||||
}
|
||||
// Make sure the sender is an EOA
|
||||
|
|
@ -347,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)
|
||||
}
|
||||
|
|
@ -395,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)
|
||||
}
|
||||
|
|
@ -446,18 +559,21 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
contractCreation = msg.To == nil
|
||||
floorDataGas uint64
|
||||
)
|
||||
|
||||
// Check clauses 4-5, subtract intrinsic gas if everything is correct
|
||||
gas, 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
|
||||
}
|
||||
if st.gasRemaining < gas {
|
||||
return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining, gas)
|
||||
prior, sufficient := st.gasRemaining.Charge(cost)
|
||||
if !sufficient {
|
||||
return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining.RegularGas, cost.RegularGas)
|
||||
}
|
||||
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(msg.Data)
|
||||
floorDataGas, err = FloorDataGas(rules, msg.Data, msg.AccessList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -465,10 +581,6 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
return nil, fmt.Errorf("%w: have %d, want %d", ErrFloorDataGas, msg.GasLimit, floorDataGas)
|
||||
}
|
||||
}
|
||||
if t := st.evm.Config.Tracer; t != nil && t.OnGasChange != nil {
|
||||
t.OnGasChange(st.gasRemaining, st.gasRemaining-gas, tracing.GasChangeTxIntrinsicGas)
|
||||
}
|
||||
st.gasRemaining -= gas
|
||||
|
||||
if rules.IsEIP4762 {
|
||||
st.evm.AccessEvents.AddTxOrigin(msg.From)
|
||||
|
|
@ -479,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())
|
||||
|
|
@ -535,14 +647,14 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
peakGasUsed := st.gasUsed()
|
||||
|
||||
// Compute refund counter, capped to a refund quotient.
|
||||
st.gasRemaining += st.calcRefund()
|
||||
st.gasRemaining.Refund(st.calcRefund())
|
||||
|
||||
if rules.IsPrague {
|
||||
// After EIP-7623: Data-heavy transactions pay the floor gas.
|
||||
if st.gasUsed() < floorDataGas {
|
||||
prev := st.gasRemaining
|
||||
st.gasRemaining = st.initialGas - floorDataGas
|
||||
if t := st.evm.Config.Tracer; t != nil && t.OnGasChange != nil {
|
||||
t.OnGasChange(prev, st.gasRemaining, tracing.GasChangeTxDataFloor)
|
||||
if used := st.gasUsed(); used < floorDataGas {
|
||||
prior, _ := st.gasRemaining.Charge(vm.GasCosts{RegularGas: floorDataGas - used})
|
||||
if st.evm.Config.Tracer.HasGasHook() {
|
||||
st.evm.Config.Tracer.EmitGasChange(prior.AsTracing(), st.gasRemaining.AsTracing(), tracing.GasChangeTxDataFloor)
|
||||
}
|
||||
}
|
||||
if peakGasUsed < floorDataGas {
|
||||
|
|
@ -555,19 +667,22 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
// Return gas to the gas pool
|
||||
if rules.IsAmsterdam {
|
||||
// Refund is excluded for returning
|
||||
err = st.gp.ReturnGas(st.initialGas-peakGasUsed, st.gasUsed())
|
||||
err = st.gp.ReturnGas(st.initialBudget.RegularGas-peakGasUsed, st.gasUsed())
|
||||
} else {
|
||||
// Refund is included for returning
|
||||
err = st.gp.ReturnGas(st.gasRemaining, st.gasUsed())
|
||||
err = st.gp.ReturnGas(st.gasRemaining.RegularGas, st.gasUsed())
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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
|
||||
|
|
@ -575,7 +690,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
|
||||
|
|
@ -655,7 +770,7 @@ func (st *stateTransition) applyAuthorization(auth *types.SetCodeAuthorization)
|
|||
}
|
||||
|
||||
// calcRefund computes refund counter, capped to a refund quotient.
|
||||
func (st *stateTransition) calcRefund() uint64 {
|
||||
func (st *stateTransition) calcRefund() vm.GasBudget {
|
||||
var refund uint64
|
||||
if !st.evm.ChainConfig().IsLondon(st.evm.Context.BlockNumber) {
|
||||
// Before EIP-3529: refunds were capped to gasUsed / 2
|
||||
|
|
@ -667,27 +782,32 @@ func (st *stateTransition) calcRefund() uint64 {
|
|||
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, st.gasRemaining+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 refund
|
||||
return vm.NewGasBudget(refund)
|
||||
}
|
||||
|
||||
// returnGas returns ETH for remaining gas,
|
||||
// exchanged at the original rate.
|
||||
func (st *stateTransition) returnGas() {
|
||||
remaining := uint256.NewInt(st.gasRemaining)
|
||||
remaining.Mul(remaining, uint256.MustFromBig(st.msg.GasPrice))
|
||||
remaining := uint256.NewInt(st.gasRemaining.RegularGas)
|
||||
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 > 0 {
|
||||
st.evm.Config.Tracer.OnGasChange(st.gasRemaining, 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)
|
||||
}
|
||||
}
|
||||
|
||||
// gasUsed returns the amount of gas used up by the state transition.
|
||||
func (st *stateTransition) gasUsed() uint64 {
|
||||
return st.initialGas - st.gasRemaining
|
||||
return st.gasRemaining.Used(st.initialBudget)
|
||||
}
|
||||
|
||||
// blobGasUsed returns the amount of blob gas used by the message.
|
||||
|
|
|
|||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue