mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-04 22:18:40 +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:
|
windows:
|
||||||
name: Windows Build
|
name: Windows Build
|
||||||
runs-on: "win-11"
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
|
@ -155,24 +155,49 @@ jobs:
|
||||||
go-version: 1.24
|
go-version: 1.24
|
||||||
cache: false
|
cache: false
|
||||||
|
|
||||||
# Note: gcc.exe only works properly if the corresponding bin/ directory is
|
- name: Install cross toolchain
|
||||||
# contained in PATH.
|
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)"
|
- name: "Build (amd64)"
|
||||||
shell: cmd
|
|
||||||
run: |
|
run: |
|
||||||
set PATH=%GETH_MINGW%\bin;%PATH%
|
go run build/ci.go install -dlgo -os windows -arch amd64 -cc x86_64-w64-mingw32-gcc
|
||||||
go run build/ci.go install -dlgo -arch amd64 -cc %GETH_MINGW%\bin\gcc.exe
|
|
||||||
|
- 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:
|
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)"
|
- name: "Build (386)"
|
||||||
shell: cmd
|
|
||||||
run: |
|
run: |
|
||||||
set PATH=%GETH_MINGW%\bin;%PATH%
|
go run build/ci.go install -dlgo -os windows -arch 386 -cc i686-w64-mingw32-gcc
|
||||||
go run build/ci.go install -dlgo -arch 386 -cc %GETH_MINGW%\bin\gcc.exe
|
|
||||||
|
- 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:
|
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:
|
docker:
|
||||||
name: Docker Image
|
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
|
- name: Run tests
|
||||||
run: go run build/ci.go test -p 8
|
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}}
|
// Solidity: {{.Original.String}}
|
||||||
func ({{ decapitalise $contract.Type}} *{{$contract.Type}}) Unpack{{.Normalized.Name}}Event(log *types.Log) (*{{$contract.Type}}{{.Normalized.Name}}, error) {
|
func ({{ decapitalise $contract.Type}} *{{$contract.Type}}) Unpack{{.Normalized.Name}}Event(log *types.Log) (*{{$contract.Type}}{{.Normalized.Name}}, error) {
|
||||||
event := "{{.Original.Name}}"
|
event := "{{.Original.Name}}"
|
||||||
if len(log.Topics) == 0 || log.Topics[0] != {{ decapitalise $contract.Type}}.abi.Events[event].ID {
|
if len(log.Topics) == 0 {
|
||||||
return nil, errors.New("event signature mismatch")
|
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}})
|
out := new({{$contract.Type}}{{.Normalized.Name}})
|
||||||
if len(log.Data) > 0 {
|
if len(log.Data) > 0 {
|
||||||
|
|
|
||||||
|
|
@ -360,8 +360,11 @@ func (CrowdsaleFundTransfer) ContractEventName() string {
|
||||||
// Solidity: event FundTransfer(address backer, uint256 amount, bool isContribution)
|
// Solidity: event FundTransfer(address backer, uint256 amount, bool isContribution)
|
||||||
func (crowdsale *Crowdsale) UnpackFundTransferEvent(log *types.Log) (*CrowdsaleFundTransfer, error) {
|
func (crowdsale *Crowdsale) UnpackFundTransferEvent(log *types.Log) (*CrowdsaleFundTransfer, error) {
|
||||||
event := "FundTransfer"
|
event := "FundTransfer"
|
||||||
if len(log.Topics) == 0 || log.Topics[0] != crowdsale.abi.Events[event].ID {
|
if len(log.Topics) == 0 {
|
||||||
return nil, errors.New("event signature mismatch")
|
return nil, bind.ErrNoEventSignature
|
||||||
|
}
|
||||||
|
if log.Topics[0] != crowdsale.abi.Events[event].ID {
|
||||||
|
return nil, bind.ErrEventSignatureMismatch
|
||||||
}
|
}
|
||||||
out := new(CrowdsaleFundTransfer)
|
out := new(CrowdsaleFundTransfer)
|
||||||
if len(log.Data) > 0 {
|
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)
|
// Solidity: event ChangeOfRules(uint256 minimumQuorum, uint256 debatingPeriodInMinutes, int256 majorityMargin)
|
||||||
func (dAO *DAO) UnpackChangeOfRulesEvent(log *types.Log) (*DAOChangeOfRules, error) {
|
func (dAO *DAO) UnpackChangeOfRulesEvent(log *types.Log) (*DAOChangeOfRules, error) {
|
||||||
event := "ChangeOfRules"
|
event := "ChangeOfRules"
|
||||||
if len(log.Topics) == 0 || log.Topics[0] != dAO.abi.Events[event].ID {
|
if len(log.Topics) == 0 {
|
||||||
return nil, errors.New("event signature mismatch")
|
return nil, bind.ErrNoEventSignature
|
||||||
|
}
|
||||||
|
if log.Topics[0] != dAO.abi.Events[event].ID {
|
||||||
|
return nil, bind.ErrEventSignatureMismatch
|
||||||
}
|
}
|
||||||
out := new(DAOChangeOfRules)
|
out := new(DAOChangeOfRules)
|
||||||
if len(log.Data) > 0 {
|
if len(log.Data) > 0 {
|
||||||
|
|
@ -648,8 +651,11 @@ func (DAOMembershipChanged) ContractEventName() string {
|
||||||
// Solidity: event MembershipChanged(address member, bool isMember)
|
// Solidity: event MembershipChanged(address member, bool isMember)
|
||||||
func (dAO *DAO) UnpackMembershipChangedEvent(log *types.Log) (*DAOMembershipChanged, error) {
|
func (dAO *DAO) UnpackMembershipChangedEvent(log *types.Log) (*DAOMembershipChanged, error) {
|
||||||
event := "MembershipChanged"
|
event := "MembershipChanged"
|
||||||
if len(log.Topics) == 0 || log.Topics[0] != dAO.abi.Events[event].ID {
|
if len(log.Topics) == 0 {
|
||||||
return nil, errors.New("event signature mismatch")
|
return nil, bind.ErrNoEventSignature
|
||||||
|
}
|
||||||
|
if log.Topics[0] != dAO.abi.Events[event].ID {
|
||||||
|
return nil, bind.ErrEventSignatureMismatch
|
||||||
}
|
}
|
||||||
out := new(DAOMembershipChanged)
|
out := new(DAOMembershipChanged)
|
||||||
if len(log.Data) > 0 {
|
if len(log.Data) > 0 {
|
||||||
|
|
@ -692,8 +698,11 @@ func (DAOProposalAdded) ContractEventName() string {
|
||||||
// Solidity: event ProposalAdded(uint256 proposalID, address recipient, uint256 amount, string description)
|
// Solidity: event ProposalAdded(uint256 proposalID, address recipient, uint256 amount, string description)
|
||||||
func (dAO *DAO) UnpackProposalAddedEvent(log *types.Log) (*DAOProposalAdded, error) {
|
func (dAO *DAO) UnpackProposalAddedEvent(log *types.Log) (*DAOProposalAdded, error) {
|
||||||
event := "ProposalAdded"
|
event := "ProposalAdded"
|
||||||
if len(log.Topics) == 0 || log.Topics[0] != dAO.abi.Events[event].ID {
|
if len(log.Topics) == 0 {
|
||||||
return nil, errors.New("event signature mismatch")
|
return nil, bind.ErrNoEventSignature
|
||||||
|
}
|
||||||
|
if log.Topics[0] != dAO.abi.Events[event].ID {
|
||||||
|
return nil, bind.ErrEventSignatureMismatch
|
||||||
}
|
}
|
||||||
out := new(DAOProposalAdded)
|
out := new(DAOProposalAdded)
|
||||||
if len(log.Data) > 0 {
|
if len(log.Data) > 0 {
|
||||||
|
|
@ -736,8 +745,11 @@ func (DAOProposalTallied) ContractEventName() string {
|
||||||
// Solidity: event ProposalTallied(uint256 proposalID, int256 result, uint256 quorum, bool active)
|
// Solidity: event ProposalTallied(uint256 proposalID, int256 result, uint256 quorum, bool active)
|
||||||
func (dAO *DAO) UnpackProposalTalliedEvent(log *types.Log) (*DAOProposalTallied, error) {
|
func (dAO *DAO) UnpackProposalTalliedEvent(log *types.Log) (*DAOProposalTallied, error) {
|
||||||
event := "ProposalTallied"
|
event := "ProposalTallied"
|
||||||
if len(log.Topics) == 0 || log.Topics[0] != dAO.abi.Events[event].ID {
|
if len(log.Topics) == 0 {
|
||||||
return nil, errors.New("event signature mismatch")
|
return nil, bind.ErrNoEventSignature
|
||||||
|
}
|
||||||
|
if log.Topics[0] != dAO.abi.Events[event].ID {
|
||||||
|
return nil, bind.ErrEventSignatureMismatch
|
||||||
}
|
}
|
||||||
out := new(DAOProposalTallied)
|
out := new(DAOProposalTallied)
|
||||||
if len(log.Data) > 0 {
|
if len(log.Data) > 0 {
|
||||||
|
|
@ -780,8 +792,11 @@ func (DAOVoted) ContractEventName() string {
|
||||||
// Solidity: event Voted(uint256 proposalID, bool position, address voter, string justification)
|
// Solidity: event Voted(uint256 proposalID, bool position, address voter, string justification)
|
||||||
func (dAO *DAO) UnpackVotedEvent(log *types.Log) (*DAOVoted, error) {
|
func (dAO *DAO) UnpackVotedEvent(log *types.Log) (*DAOVoted, error) {
|
||||||
event := "Voted"
|
event := "Voted"
|
||||||
if len(log.Topics) == 0 || log.Topics[0] != dAO.abi.Events[event].ID {
|
if len(log.Topics) == 0 {
|
||||||
return nil, errors.New("event signature mismatch")
|
return nil, bind.ErrNoEventSignature
|
||||||
|
}
|
||||||
|
if log.Topics[0] != dAO.abi.Events[event].ID {
|
||||||
|
return nil, bind.ErrEventSignatureMismatch
|
||||||
}
|
}
|
||||||
out := new(DAOVoted)
|
out := new(DAOVoted)
|
||||||
if len(log.Data) > 0 {
|
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)
|
// Solidity: event dynamic(string indexed idxStr, bytes indexed idxDat, string str, bytes dat)
|
||||||
func (eventChecker *EventChecker) UnpackDynamicEvent(log *types.Log) (*EventCheckerDynamic, error) {
|
func (eventChecker *EventChecker) UnpackDynamicEvent(log *types.Log) (*EventCheckerDynamic, error) {
|
||||||
event := "dynamic"
|
event := "dynamic"
|
||||||
if len(log.Topics) == 0 || log.Topics[0] != eventChecker.abi.Events[event].ID {
|
if len(log.Topics) == 0 {
|
||||||
return nil, errors.New("event signature mismatch")
|
return nil, bind.ErrNoEventSignature
|
||||||
|
}
|
||||||
|
if log.Topics[0] != eventChecker.abi.Events[event].ID {
|
||||||
|
return nil, bind.ErrEventSignatureMismatch
|
||||||
}
|
}
|
||||||
out := new(EventCheckerDynamic)
|
out := new(EventCheckerDynamic)
|
||||||
if len(log.Data) > 0 {
|
if len(log.Data) > 0 {
|
||||||
|
|
@ -112,8 +115,11 @@ func (EventCheckerEmpty) ContractEventName() string {
|
||||||
// Solidity: event empty()
|
// Solidity: event empty()
|
||||||
func (eventChecker *EventChecker) UnpackEmptyEvent(log *types.Log) (*EventCheckerEmpty, error) {
|
func (eventChecker *EventChecker) UnpackEmptyEvent(log *types.Log) (*EventCheckerEmpty, error) {
|
||||||
event := "empty"
|
event := "empty"
|
||||||
if len(log.Topics) == 0 || log.Topics[0] != eventChecker.abi.Events[event].ID {
|
if len(log.Topics) == 0 {
|
||||||
return nil, errors.New("event signature mismatch")
|
return nil, bind.ErrNoEventSignature
|
||||||
|
}
|
||||||
|
if log.Topics[0] != eventChecker.abi.Events[event].ID {
|
||||||
|
return nil, bind.ErrEventSignatureMismatch
|
||||||
}
|
}
|
||||||
out := new(EventCheckerEmpty)
|
out := new(EventCheckerEmpty)
|
||||||
if len(log.Data) > 0 {
|
if len(log.Data) > 0 {
|
||||||
|
|
@ -154,8 +160,11 @@ func (EventCheckerIndexed) ContractEventName() string {
|
||||||
// Solidity: event indexed(address indexed addr, int256 indexed num)
|
// Solidity: event indexed(address indexed addr, int256 indexed num)
|
||||||
func (eventChecker *EventChecker) UnpackIndexedEvent(log *types.Log) (*EventCheckerIndexed, error) {
|
func (eventChecker *EventChecker) UnpackIndexedEvent(log *types.Log) (*EventCheckerIndexed, error) {
|
||||||
event := "indexed"
|
event := "indexed"
|
||||||
if len(log.Topics) == 0 || log.Topics[0] != eventChecker.abi.Events[event].ID {
|
if len(log.Topics) == 0 {
|
||||||
return nil, errors.New("event signature mismatch")
|
return nil, bind.ErrNoEventSignature
|
||||||
|
}
|
||||||
|
if log.Topics[0] != eventChecker.abi.Events[event].ID {
|
||||||
|
return nil, bind.ErrEventSignatureMismatch
|
||||||
}
|
}
|
||||||
out := new(EventCheckerIndexed)
|
out := new(EventCheckerIndexed)
|
||||||
if len(log.Data) > 0 {
|
if len(log.Data) > 0 {
|
||||||
|
|
@ -196,8 +205,11 @@ func (EventCheckerMixed) ContractEventName() string {
|
||||||
// Solidity: event mixed(address indexed addr, int256 num)
|
// Solidity: event mixed(address indexed addr, int256 num)
|
||||||
func (eventChecker *EventChecker) UnpackMixedEvent(log *types.Log) (*EventCheckerMixed, error) {
|
func (eventChecker *EventChecker) UnpackMixedEvent(log *types.Log) (*EventCheckerMixed, error) {
|
||||||
event := "mixed"
|
event := "mixed"
|
||||||
if len(log.Topics) == 0 || log.Topics[0] != eventChecker.abi.Events[event].ID {
|
if len(log.Topics) == 0 {
|
||||||
return nil, errors.New("event signature mismatch")
|
return nil, bind.ErrNoEventSignature
|
||||||
|
}
|
||||||
|
if log.Topics[0] != eventChecker.abi.Events[event].ID {
|
||||||
|
return nil, bind.ErrEventSignatureMismatch
|
||||||
}
|
}
|
||||||
out := new(EventCheckerMixed)
|
out := new(EventCheckerMixed)
|
||||||
if len(log.Data) > 0 {
|
if len(log.Data) > 0 {
|
||||||
|
|
@ -238,8 +250,11 @@ func (EventCheckerUnnamed) ContractEventName() string {
|
||||||
// Solidity: event unnamed(uint256 indexed arg0, uint256 indexed arg1)
|
// Solidity: event unnamed(uint256 indexed arg0, uint256 indexed arg1)
|
||||||
func (eventChecker *EventChecker) UnpackUnnamedEvent(log *types.Log) (*EventCheckerUnnamed, error) {
|
func (eventChecker *EventChecker) UnpackUnnamedEvent(log *types.Log) (*EventCheckerUnnamed, error) {
|
||||||
event := "unnamed"
|
event := "unnamed"
|
||||||
if len(log.Topics) == 0 || log.Topics[0] != eventChecker.abi.Events[event].ID {
|
if len(log.Topics) == 0 {
|
||||||
return nil, errors.New("event signature mismatch")
|
return nil, bind.ErrNoEventSignature
|
||||||
|
}
|
||||||
|
if log.Topics[0] != eventChecker.abi.Events[event].ID {
|
||||||
|
return nil, bind.ErrEventSignatureMismatch
|
||||||
}
|
}
|
||||||
out := new(EventCheckerUnnamed)
|
out := new(EventCheckerUnnamed)
|
||||||
if len(log.Data) > 0 {
|
if len(log.Data) > 0 {
|
||||||
|
|
|
||||||
|
|
@ -134,8 +134,11 @@ func (NameConflictLog) ContractEventName() string {
|
||||||
// Solidity: event log(int256 msg, int256 _msg)
|
// Solidity: event log(int256 msg, int256 _msg)
|
||||||
func (nameConflict *NameConflict) UnpackLogEvent(log *types.Log) (*NameConflictLog, error) {
|
func (nameConflict *NameConflict) UnpackLogEvent(log *types.Log) (*NameConflictLog, error) {
|
||||||
event := "log"
|
event := "log"
|
||||||
if len(log.Topics) == 0 || log.Topics[0] != nameConflict.abi.Events[event].ID {
|
if len(log.Topics) == 0 {
|
||||||
return nil, errors.New("event signature mismatch")
|
return nil, bind.ErrNoEventSignature
|
||||||
|
}
|
||||||
|
if log.Topics[0] != nameConflict.abi.Events[event].ID {
|
||||||
|
return nil, bind.ErrEventSignatureMismatch
|
||||||
}
|
}
|
||||||
out := new(NameConflictLog)
|
out := new(NameConflictLog)
|
||||||
if len(log.Data) > 0 {
|
if len(log.Data) > 0 {
|
||||||
|
|
|
||||||
|
|
@ -136,8 +136,11 @@ func (NumericMethodNameE1TestEvent) ContractEventName() string {
|
||||||
// Solidity: event _1TestEvent(address _param)
|
// Solidity: event _1TestEvent(address _param)
|
||||||
func (numericMethodName *NumericMethodName) UnpackE1TestEventEvent(log *types.Log) (*NumericMethodNameE1TestEvent, error) {
|
func (numericMethodName *NumericMethodName) UnpackE1TestEventEvent(log *types.Log) (*NumericMethodNameE1TestEvent, error) {
|
||||||
event := "_1TestEvent"
|
event := "_1TestEvent"
|
||||||
if len(log.Topics) == 0 || log.Topics[0] != numericMethodName.abi.Events[event].ID {
|
if len(log.Topics) == 0 {
|
||||||
return nil, errors.New("event signature mismatch")
|
return nil, bind.ErrNoEventSignature
|
||||||
|
}
|
||||||
|
if log.Topics[0] != numericMethodName.abi.Events[event].ID {
|
||||||
|
return nil, bind.ErrEventSignatureMismatch
|
||||||
}
|
}
|
||||||
out := new(NumericMethodNameE1TestEvent)
|
out := new(NumericMethodNameE1TestEvent)
|
||||||
if len(log.Data) > 0 {
|
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)
|
// Solidity: event bar(uint256 i)
|
||||||
func (overload *Overload) UnpackBarEvent(log *types.Log) (*OverloadBar, error) {
|
func (overload *Overload) UnpackBarEvent(log *types.Log) (*OverloadBar, error) {
|
||||||
event := "bar"
|
event := "bar"
|
||||||
if len(log.Topics) == 0 || log.Topics[0] != overload.abi.Events[event].ID {
|
if len(log.Topics) == 0 {
|
||||||
return nil, errors.New("event signature mismatch")
|
return nil, bind.ErrNoEventSignature
|
||||||
|
}
|
||||||
|
if log.Topics[0] != overload.abi.Events[event].ID {
|
||||||
|
return nil, bind.ErrEventSignatureMismatch
|
||||||
}
|
}
|
||||||
out := new(OverloadBar)
|
out := new(OverloadBar)
|
||||||
if len(log.Data) > 0 {
|
if len(log.Data) > 0 {
|
||||||
|
|
@ -156,8 +159,11 @@ func (OverloadBar0) ContractEventName() string {
|
||||||
// Solidity: event bar(uint256 i, uint256 j)
|
// Solidity: event bar(uint256 i, uint256 j)
|
||||||
func (overload *Overload) UnpackBar0Event(log *types.Log) (*OverloadBar0, error) {
|
func (overload *Overload) UnpackBar0Event(log *types.Log) (*OverloadBar0, error) {
|
||||||
event := "bar0"
|
event := "bar0"
|
||||||
if len(log.Topics) == 0 || log.Topics[0] != overload.abi.Events[event].ID {
|
if len(log.Topics) == 0 {
|
||||||
return nil, errors.New("event signature mismatch")
|
return nil, bind.ErrNoEventSignature
|
||||||
|
}
|
||||||
|
if log.Topics[0] != overload.abi.Events[event].ID {
|
||||||
|
return nil, bind.ErrEventSignatureMismatch
|
||||||
}
|
}
|
||||||
out := new(OverloadBar0)
|
out := new(OverloadBar0)
|
||||||
if len(log.Data) > 0 {
|
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)
|
// Solidity: event Transfer(address indexed from, address indexed to, uint256 value)
|
||||||
func (token *Token) UnpackTransferEvent(log *types.Log) (*TokenTransfer, error) {
|
func (token *Token) UnpackTransferEvent(log *types.Log) (*TokenTransfer, error) {
|
||||||
event := "Transfer"
|
event := "Transfer"
|
||||||
if len(log.Topics) == 0 || log.Topics[0] != token.abi.Events[event].ID {
|
if len(log.Topics) == 0 {
|
||||||
return nil, errors.New("event signature mismatch")
|
return nil, bind.ErrNoEventSignature
|
||||||
|
}
|
||||||
|
if log.Topics[0] != token.abi.Events[event].ID {
|
||||||
|
return nil, bind.ErrEventSignatureMismatch
|
||||||
}
|
}
|
||||||
out := new(TokenTransfer)
|
out := new(TokenTransfer)
|
||||||
if len(log.Data) > 0 {
|
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)
|
// 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) {
|
func (tuple *Tuple) UnpackTupleEventEvent(log *types.Log) (*TupleTupleEvent, error) {
|
||||||
event := "TupleEvent"
|
event := "TupleEvent"
|
||||||
if len(log.Topics) == 0 || log.Topics[0] != tuple.abi.Events[event].ID {
|
if len(log.Topics) == 0 {
|
||||||
return nil, errors.New("event signature mismatch")
|
return nil, bind.ErrNoEventSignature
|
||||||
|
}
|
||||||
|
if log.Topics[0] != tuple.abi.Events[event].ID {
|
||||||
|
return nil, bind.ErrEventSignatureMismatch
|
||||||
}
|
}
|
||||||
out := new(TupleTupleEvent)
|
out := new(TupleTupleEvent)
|
||||||
if len(log.Data) > 0 {
|
if len(log.Data) > 0 {
|
||||||
|
|
@ -234,8 +237,11 @@ func (TupleTupleEvent2) ContractEventName() string {
|
||||||
// Solidity: event TupleEvent2((uint8,uint8)[] arg0)
|
// Solidity: event TupleEvent2((uint8,uint8)[] arg0)
|
||||||
func (tuple *Tuple) UnpackTupleEvent2Event(log *types.Log) (*TupleTupleEvent2, error) {
|
func (tuple *Tuple) UnpackTupleEvent2Event(log *types.Log) (*TupleTupleEvent2, error) {
|
||||||
event := "TupleEvent2"
|
event := "TupleEvent2"
|
||||||
if len(log.Topics) == 0 || log.Topics[0] != tuple.abi.Events[event].ID {
|
if len(log.Topics) == 0 {
|
||||||
return nil, errors.New("event signature mismatch")
|
return nil, bind.ErrNoEventSignature
|
||||||
|
}
|
||||||
|
if log.Topics[0] != tuple.abi.Events[event].ID {
|
||||||
|
return nil, bind.ErrEventSignatureMismatch
|
||||||
}
|
}
|
||||||
out := new(TupleTupleEvent2)
|
out := new(TupleTupleEvent2)
|
||||||
if len(log.Data) > 0 {
|
if len(log.Data) > 0 {
|
||||||
|
|
|
||||||
|
|
@ -176,6 +176,13 @@ var (
|
||||||
// ErrNoCodeAfterDeploy is returned by WaitDeployed if contract creation leaves
|
// ErrNoCodeAfterDeploy is returned by WaitDeployed if contract creation leaves
|
||||||
// an empty contract behind.
|
// an empty contract behind.
|
||||||
ErrNoCodeAfterDeploy = bind2.ErrNoCodeAfterDeploy
|
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
|
// ContractCaller defines the methods needed to allow operating with a contract on a read
|
||||||
|
|
|
||||||
|
|
@ -35,8 +35,8 @@ import (
|
||||||
const basefeeWiggleMultiplier = 2
|
const basefeeWiggleMultiplier = 2
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errNoEventSignature = errors.New("no event signature")
|
ErrNoEventSignature = errors.New("no event signature")
|
||||||
errEventSignatureMismatch = errors.New("event signature mismatch")
|
ErrEventSignatureMismatch = errors.New("event signature mismatch")
|
||||||
)
|
)
|
||||||
|
|
||||||
// SignerFn is a signer function callback when a contract requires a method to
|
// 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 {
|
func (c *BoundContract) UnpackLog(out any, event string, log types.Log) error {
|
||||||
// Anonymous events are not supported.
|
// Anonymous events are not supported.
|
||||||
if len(log.Topics) == 0 {
|
if len(log.Topics) == 0 {
|
||||||
return errNoEventSignature
|
return ErrNoEventSignature
|
||||||
}
|
}
|
||||||
if log.Topics[0] != c.abi.Events[event].ID {
|
if log.Topics[0] != c.abi.Events[event].ID {
|
||||||
return errEventSignatureMismatch
|
return ErrEventSignatureMismatch
|
||||||
}
|
}
|
||||||
if len(log.Data) > 0 {
|
if len(log.Data) > 0 {
|
||||||
if err := c.abi.UnpackIntoInterface(out, event, log.Data); err != nil {
|
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 {
|
func (c *BoundContract) UnpackLogIntoMap(out map[string]any, event string, log types.Log) error {
|
||||||
// Anonymous events are not supported.
|
// Anonymous events are not supported.
|
||||||
if len(log.Topics) == 0 {
|
if len(log.Topics) == 0 {
|
||||||
return errNoEventSignature
|
return ErrNoEventSignature
|
||||||
}
|
}
|
||||||
if log.Topics[0] != c.abi.Events[event].ID {
|
if log.Topics[0] != c.abi.Events[event].ID {
|
||||||
return errEventSignatureMismatch
|
return ErrEventSignatureMismatch
|
||||||
}
|
}
|
||||||
if len(log.Data) > 0 {
|
if len(log.Data) > 0 {
|
||||||
if err := c.abi.UnpackIntoMap(out, event, log.Data); err != nil {
|
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)
|
// Solidity: event Insert(uint256 key, uint256 value, uint256 length)
|
||||||
func (dB *DB) UnpackInsertEvent(log *types.Log) (*DBInsert, error) {
|
func (dB *DB) UnpackInsertEvent(log *types.Log) (*DBInsert, error) {
|
||||||
event := "Insert"
|
event := "Insert"
|
||||||
if len(log.Topics) == 0 || log.Topics[0] != dB.abi.Events[event].ID {
|
if len(log.Topics) == 0 {
|
||||||
return nil, errors.New("event signature mismatch")
|
return nil, bind.ErrNoEventSignature
|
||||||
|
}
|
||||||
|
if log.Topics[0] != dB.abi.Events[event].ID {
|
||||||
|
return nil, bind.ErrEventSignatureMismatch
|
||||||
}
|
}
|
||||||
out := new(DBInsert)
|
out := new(DBInsert)
|
||||||
if len(log.Data) > 0 {
|
if len(log.Data) > 0 {
|
||||||
|
|
@ -318,8 +321,11 @@ func (DBKeyedInsert) ContractEventName() string {
|
||||||
// Solidity: event KeyedInsert(uint256 indexed key, uint256 value)
|
// Solidity: event KeyedInsert(uint256 indexed key, uint256 value)
|
||||||
func (dB *DB) UnpackKeyedInsertEvent(log *types.Log) (*DBKeyedInsert, error) {
|
func (dB *DB) UnpackKeyedInsertEvent(log *types.Log) (*DBKeyedInsert, error) {
|
||||||
event := "KeyedInsert"
|
event := "KeyedInsert"
|
||||||
if len(log.Topics) == 0 || log.Topics[0] != dB.abi.Events[event].ID {
|
if len(log.Topics) == 0 {
|
||||||
return nil, errors.New("event signature mismatch")
|
return nil, bind.ErrNoEventSignature
|
||||||
|
}
|
||||||
|
if log.Topics[0] != dB.abi.Events[event].ID {
|
||||||
|
return nil, bind.ErrEventSignatureMismatch
|
||||||
}
|
}
|
||||||
out := new(DBKeyedInsert)
|
out := new(DBKeyedInsert)
|
||||||
if len(log.Data) > 0 {
|
if len(log.Data) > 0 {
|
||||||
|
|
|
||||||
|
|
@ -115,8 +115,11 @@ func (CBasic1) ContractEventName() string {
|
||||||
// Solidity: event basic1(uint256 indexed id, uint256 data)
|
// Solidity: event basic1(uint256 indexed id, uint256 data)
|
||||||
func (c *C) UnpackBasic1Event(log *types.Log) (*CBasic1, error) {
|
func (c *C) UnpackBasic1Event(log *types.Log) (*CBasic1, error) {
|
||||||
event := "basic1"
|
event := "basic1"
|
||||||
if len(log.Topics) == 0 || log.Topics[0] != c.abi.Events[event].ID {
|
if len(log.Topics) == 0 {
|
||||||
return nil, errors.New("event signature mismatch")
|
return nil, bind.ErrNoEventSignature
|
||||||
|
}
|
||||||
|
if log.Topics[0] != c.abi.Events[event].ID {
|
||||||
|
return nil, bind.ErrEventSignatureMismatch
|
||||||
}
|
}
|
||||||
out := new(CBasic1)
|
out := new(CBasic1)
|
||||||
if len(log.Data) > 0 {
|
if len(log.Data) > 0 {
|
||||||
|
|
@ -157,8 +160,11 @@ func (CBasic2) ContractEventName() string {
|
||||||
// Solidity: event basic2(bool indexed flag, uint256 data)
|
// Solidity: event basic2(bool indexed flag, uint256 data)
|
||||||
func (c *C) UnpackBasic2Event(log *types.Log) (*CBasic2, error) {
|
func (c *C) UnpackBasic2Event(log *types.Log) (*CBasic2, error) {
|
||||||
event := "basic2"
|
event := "basic2"
|
||||||
if len(log.Topics) == 0 || log.Topics[0] != c.abi.Events[event].ID {
|
if len(log.Topics) == 0 {
|
||||||
return nil, errors.New("event signature mismatch")
|
return nil, bind.ErrNoEventSignature
|
||||||
|
}
|
||||||
|
if log.Topics[0] != c.abi.Events[event].ID {
|
||||||
|
return nil, bind.ErrEventSignatureMismatch
|
||||||
}
|
}
|
||||||
out := new(CBasic2)
|
out := new(CBasic2)
|
||||||
if len(log.Data) > 0 {
|
if len(log.Data) > 0 {
|
||||||
|
|
|
||||||
|
|
@ -379,16 +379,16 @@ func TestEventUnpackEmptyTopics(t *testing.T) {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("expected error when unpacking event with empty topics, got nil")
|
t.Fatal("expected error when unpacking event with empty topics, got nil")
|
||||||
}
|
}
|
||||||
if err.Error() != "event signature mismatch" {
|
if err != bind.ErrNoEventSignature {
|
||||||
t.Fatalf("expected 'event signature mismatch' error, got: %v", err)
|
t.Fatalf("expected 'no event signature' error, got: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = c.UnpackBasic2Event(log)
|
_, err = c.UnpackBasic2Event(log)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("expected error when unpacking event with empty topics, got nil")
|
t.Fatal("expected error when unpacking event with empty topics, got nil")
|
||||||
}
|
}
|
||||||
if err.Error() != "event signature mismatch" {
|
if err != bind.ErrNoEventSignature {
|
||||||
t.Fatalf("expected 'event signature mismatch' error, got: %v", err)
|
t.Fatalf("expected 'no event signature' error, got: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -910,7 +910,7 @@ func TestUnpackTuple(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
FieldT: T{
|
FieldT: T{
|
||||||
big.NewInt(0), big.NewInt(1),
|
big.NewInt(0).SetBits([]big.Word{}), big.NewInt(1),
|
||||||
},
|
},
|
||||||
A: big.NewInt(1),
|
A: big.NewInt(1),
|
||||||
}
|
}
|
||||||
|
|
@ -919,7 +919,7 @@ func TestUnpackTuple(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
if reflect.DeepEqual(ret, expected) {
|
if !reflect.DeepEqual(ret, expected) {
|
||||||
t.Error("unexpected unpack value")
|
t.Error("unexpected unpack value")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,18 +68,27 @@ func waitWatcherStart(ks *KeyStore) bool {
|
||||||
|
|
||||||
func waitForAccounts(wantAccounts []accounts.Account, ks *KeyStore) error {
|
func waitForAccounts(wantAccounts []accounts.Account, ks *KeyStore) error {
|
||||||
var list []accounts.Account
|
var list []accounts.Account
|
||||||
|
haveAccounts := false
|
||||||
|
haveChange := false
|
||||||
for t0 := time.Now(); time.Since(t0) < 5*time.Second; time.Sleep(100 * time.Millisecond) {
|
for t0 := time.Now(); time.Since(t0) < 5*time.Second; time.Sleep(100 * time.Millisecond) {
|
||||||
list = ks.Accounts()
|
if !haveAccounts {
|
||||||
if reflect.DeepEqual(list, wantAccounts) {
|
list = ks.Accounts()
|
||||||
// ks should have also received change notifications
|
haveAccounts = reflect.DeepEqual(list, wantAccounts)
|
||||||
|
}
|
||||||
|
if !haveChange {
|
||||||
select {
|
select {
|
||||||
case <-ks.changes:
|
case <-ks.changes:
|
||||||
|
haveChange = true
|
||||||
default:
|
default:
|
||||||
return errors.New("wasn't notified of new accounts")
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if haveAccounts && haveChange {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if haveAccounts {
|
||||||
|
return errors.New("wasn't notified of new accounts")
|
||||||
|
}
|
||||||
return fmt.Errorf("\ngot %v\nwant %v", list, wantAccounts)
|
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
|
// 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/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
//go:build (darwin && !ios && cgo) || freebsd || (linux && !arm64) || netbsd || solaris
|
//go:build (darwin && !ios && cgo) || freebsd || linux || netbsd || solaris
|
||||||
// +build darwin,!ios,cgo freebsd linux,!arm64 netbsd solaris
|
// +build darwin,!ios,cgo freebsd linux netbsd solaris
|
||||||
|
|
||||||
package keystore
|
package keystore
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,8 @@
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
// 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/>.
|
// 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)
|
//go:build (darwin && !cgo) || ios || windows || (!darwin && !freebsd && !linux && !netbsd && !solaris)
|
||||||
// +build darwin,!cgo ios linux,arm64 windows !darwin,!freebsd,!linux,!netbsd,!solaris
|
// +build darwin,!cgo ios windows !darwin,!freebsd,!linux,!netbsd,!solaris
|
||||||
|
|
||||||
// This is the fallback implementation of directory watching.
|
// This is the fallback implementation of directory watching.
|
||||||
// It is used on unsupported platforms.
|
// It is used on unsupported platforms.
|
||||||
|
|
|
||||||
|
|
@ -113,7 +113,7 @@ func (hub *Hub) readPairings() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hub *Hub) writePairings() 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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -129,11 +129,8 @@ func (hub *Hub) writePairings() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := pairingFile.Write(pairingData); err != nil {
|
_, err = pairingFile.Write(pairingData)
|
||||||
return err
|
return err
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hub *Hub) pairing(wallet *Wallet) *smartcardPairing {
|
func (hub *Hub) pairing(wallet *Wallet) *smartcardPairing {
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
"github.com/ethereum/go-ethereum/event"
|
"github.com/ethereum/go-ethereum/event"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/karalabe/hid"
|
"github.com/ethereum/hid"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LedgerScheme is the protocol scheme prefixing account and wallet URLs.
|
// 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/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/karalabe/hid"
|
"github.com/ethereum/hid"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Maximum time between wallet health checks to detect USB unplugs.
|
// 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"}
|
TooLargeRequest = &EngineAPIError{code: -38004, msg: "Too large request"}
|
||||||
InvalidParams = &EngineAPIError{code: -32602, msg: "Invalid parameters"}
|
InvalidParams = &EngineAPIError{code: -32602, msg: "Invalid parameters"}
|
||||||
UnsupportedFork = &EngineAPIError{code: -38005, msg: "Unsupported fork"}
|
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_INVALID = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: INVALID}, PayloadID: nil}
|
||||||
STATUS_SYNCING = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: SYNCING}, 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"`
|
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||||
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
|
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
|
||||||
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
|
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
|
||||||
SlotNumber *hexutil.Uint64 `json:"slotNumber"`
|
SlotNumber *hexutil.Uint64 `json:"slotNumber,omitempty"`
|
||||||
}
|
}
|
||||||
var enc ExecutableData
|
var enc ExecutableData
|
||||||
enc.ParentHash = e.ParentHash
|
enc.ParentHash = e.ParentHash
|
||||||
|
|
@ -83,7 +83,7 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error {
|
||||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||||
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
|
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
|
||||||
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
|
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
|
||||||
SlotNumber *hexutil.Uint64 `json:"slotNumber"`
|
SlotNumber *hexutil.Uint64 `json:"slotNumber,omitempty"`
|
||||||
}
|
}
|
||||||
var dec ExecutableData
|
var dec ExecutableData
|
||||||
if err := json.Unmarshal(input, &dec); err != nil {
|
if err := json.Unmarshal(input, &dec); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,7 @@ type ExecutableData struct {
|
||||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||||
BlobGasUsed *uint64 `json:"blobGasUsed"`
|
BlobGasUsed *uint64 `json:"blobGasUsed"`
|
||||||
ExcessBlobGas *uint64 `json:"excessBlobGas"`
|
ExcessBlobGas *uint64 `json:"excessBlobGas"`
|
||||||
SlotNumber *uint64 `json:"slotNumber"`
|
SlotNumber *uint64 `json:"slotNumber,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// JSON type overrides for executableData.
|
// 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) {
|
if data.BaseFeePerGas != nil && (data.BaseFeePerGas.Sign() == -1 || data.BaseFeePerGas.BitLen() > 256) {
|
||||||
return nil, fmt.Errorf("invalid baseFeePerGas: %v", data.BaseFeePerGas)
|
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 {
|
for _, tx := range txs {
|
||||||
blobHashes = append(blobHashes, tx.BlobHashes()...)
|
blobHashes = append(blobHashes, tx.BlobHashes()...)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,49 +5,49 @@
|
||||||
# https://github.com/ethereum/execution-spec-tests/releases/download/v5.1.0
|
# https://github.com/ethereum/execution-spec-tests/releases/download/v5.1.0
|
||||||
a3192784375acec7eaec492799d5c5d0c47a2909a3cc40178898e4ecd20cc416 fixtures_develop.tar.gz
|
a3192784375acec7eaec492799d5c5d0c47a2909a3cc40178898e4ecd20cc416 fixtures_develop.tar.gz
|
||||||
|
|
||||||
# version:golang 1.25.7
|
# version:golang 1.25.10
|
||||||
# https://go.dev/dl/
|
# https://go.dev/dl/
|
||||||
178f2832820274b43e177d32f06a3ebb0129e427dd20a5e4c88df2c1763cf10a go1.25.7.src.tar.gz
|
20cf04a92e5af99748e341bc8996fa28090c9ac98765fa115ec5ddf41d7af41d go1.25.10.src.tar.gz
|
||||||
81bf2a1f20633f62d55d826d82dde3b0570cf1408a91e15781b266037299285b go1.25.7.aix-ppc64.tar.gz
|
a194e767c2ab4216a60acc068b9dbe6bf4fae05c14bb52d6bbdcb5b3ea521308 go1.25.10.aix-ppc64.tar.gz
|
||||||
bf5050a2152f4053837b886e8d9640c829dbacbc3370f913351eb0904cb706f5 go1.25.7.darwin-amd64.tar.gz
|
52321165a3146cd91865ef98371506a846ed4dc4f9f1c9323e5ad90d2a411e06 go1.25.10.darwin-amd64.tar.gz
|
||||||
ff18369ffad05c57d5bed888b660b31385f3c913670a83ef557cdfd98ea9ae1b go1.25.7.darwin-arm64.tar.gz
|
795691a425de7e7cdba3544f354dcd2cebcf52e87dc6898193878f34eb6d634f go1.25.10.darwin-arm64.tar.gz
|
||||||
c5dccd7f192dd7b305dc209fb316ac1917776d74bd8e4d532ef2772f305bf42a go1.25.7.dragonfly-amd64.tar.gz
|
e37b4544ba9e9e9a7ab2ed3116b3fc4d39a88da854baa5a566d9d6d3a9de7d4c go1.25.10.dragonfly-amd64.tar.gz
|
||||||
a2de97c8ac74bf64b0ae73fe9d379e61af530e061bc7f8f825044172ffe61a8b go1.25.7.freebsd-386.tar.gz
|
2a70d1fdabab637aa442ca94599a56e381238efa20cb995d5433b8579bfe482c go1.25.10.freebsd-386.tar.gz
|
||||||
055f9e138787dcafa81eb0314c8ff70c6dd0f6dba1e8a6957fef5d5efd1ab8fd go1.25.7.freebsd-amd64.tar.gz
|
9cdf522d87d47d82fec4a313cc4f8c3c94a7770426e8d443e4150a1f330cba71 go1.25.10.freebsd-amd64.tar.gz
|
||||||
60e7f7a7c990f0b9539ac8ed668155746997d404643a4eecd47b3dee1b7e710b go1.25.7.freebsd-arm.tar.gz
|
6da6183633e9e59ffd9edefab68b5059c89b605596d94aaba650b1681fccd35f go1.25.10.freebsd-arm.tar.gz
|
||||||
631e03d5fd4c526e2f499154d8c6bf4cb081afb2fff171c428722afc9539d53a go1.25.7.freebsd-arm64.tar.gz
|
7adcefeebdd05331f4d45f1ad2dddb5c53537cff6552e82f6595b3b833b95371 go1.25.10.freebsd-arm64.tar.gz
|
||||||
8a264fd685823808140672812e3ad9c43f6ad59444c0dc14cdd3a1351839ddd5 go1.25.7.freebsd-riscv64.tar.gz
|
285f80a1ace21a7d94035cd753196eeada8cacd48e6396fd116ad5eb67aea957 go1.25.10.freebsd-riscv64.tar.gz
|
||||||
57c672447d906a1bcab98f2b11492d54521a791aacbb4994a25169e59cbe289a go1.25.7.illumos-amd64.tar.gz
|
de7461bf0e5068a4f6e7f8713026d70516be6dbd5de5d21f9ced1c182f2f326e go1.25.10.illumos-amd64.tar.gz
|
||||||
2866517e9ca81e6a2e85a930e9b11bc8a05cfeb2fc6dc6cb2765e7fb3c14b715 go1.25.7.linux-386.tar.gz
|
2f574f2e2e19ead5b280fec0e7af5c81b76632685f03b6ac42dfa34c4b773c52 go1.25.10.linux-386.tar.gz
|
||||||
12e6d6a191091ae27dc31f6efc630e3a3b8ba409baf3573d955b196fdf086005 go1.25.7.linux-amd64.tar.gz
|
42d4f7a32316aa66591eca7e89867256057a4264451aca10570a715b3637ba70 go1.25.10.linux-amd64.tar.gz
|
||||||
ba611a53534135a81067240eff9508cd7e256c560edd5d8c2fef54f083c07129 go1.25.7.linux-arm64.tar.gz
|
654da1f9b50a5d1c2a85ccf8ed405aa89c06e94d18384628bf186f7712677b08 go1.25.10.linux-arm64.tar.gz
|
||||||
1ba07e0eb86b839e72467f4b5c7a5597d07f30bcf5563c951410454f7cda5266 go1.25.7.linux-armv6l.tar.gz
|
39f168f158e693887d3ad006168af1b1a3007b19c5993cae4d9d57f82f52aaf8 go1.25.10.linux-armv6l.tar.gz
|
||||||
775753fc5952a334c415f08768df2f0b73a3228a16e8f5f63d545daacb4e3357 go1.25.7.linux-loong64.tar.gz
|
05401fe5ea50ad2bafb9c797ef9bf21574b0661f19ef4d0dd66af8a0fb7323f3 go1.25.10.linux-loong64.tar.gz
|
||||||
1a023bb367c5fbb4c637a2f6dc23ff17c6591ad929ce16ea88c74d857153b307 go1.25.7.linux-mips.tar.gz
|
d5bc2d6155d394a3aae41f21eb7c60da5595a6147aa0f30ed6b27da25e06c3f7 go1.25.10.linux-mips.tar.gz
|
||||||
a8e97223d8aa6fdfd45f132a4784d2f536bbac5f3d63a24b63d33b6bfe1549af go1.25.7.linux-mips64.tar.gz
|
8c64e7493e5953c3ba3153487d2fddd7f8ed142392c77f138e6792a6c1930db4 go1.25.10.linux-mips64.tar.gz
|
||||||
eb9edb6223330d5e20275667c65dea076b064c08e595fe4eba5d7d6055cfaccf go1.25.7.linux-mips64le.tar.gz
|
bd53aa2d558b7c1eadfc6bf01132e1859203a92f458ed7ba75b7f3230f14b095 go1.25.10.linux-mips64le.tar.gz
|
||||||
9c1e693552a5f9bb9e0012d1c5e01456ecefbc59bef53a77305222ce10aba368 go1.25.7.linux-mipsle.tar.gz
|
120b254e2e2980bb06687175db5c4064a85696c53001dc9f59934ad18f74a6bc go1.25.10.linux-mipsle.tar.gz
|
||||||
28a788798e7329acbbc0ac2caa5e4368b1e5ede646cc24429c991214cfb45c63 go1.25.7.linux-ppc64.tar.gz
|
8a6acb21295b0ec974a44608361920ea8dbff5666631a6f556bd7d5f1d56535f go1.25.10.linux-ppc64.tar.gz
|
||||||
42124c0edc92464e2b37b2d7fcd3658f0c47ebd6a098732415a522be8cb88e3f go1.25.7.linux-ppc64le.tar.gz
|
778925fdcdf9a272f823d147fad51545c3334b7ccd8652b2ccaaf2b01800280a go1.25.10.linux-ppc64le.tar.gz
|
||||||
88d59c6893c8425875d6eef8e3434bc2fa2552e5ad4c058c6cd8cd710a0301c8 go1.25.7.linux-riscv64.tar.gz
|
b4f04ad0db48bcfea946db5323919cd21034e0bd2821a557dacd29c1b1013a4b go1.25.10.linux-riscv64.tar.gz
|
||||||
c6b77facf666dc68195ecab05dbf0ebb4e755b2a8b7734c759880557f1c29b0c go1.25.7.linux-s390x.tar.gz
|
936b953e43921a64c12da871f76871ebbeb6d2092a7b8bdc307f5246f3c662cc go1.25.10.linux-s390x.tar.gz
|
||||||
f14c184d9ade0ee04c7735d4071257b90896ecbde1b32adae84135f055e6399b go1.25.7.netbsd-386.tar.gz
|
061470e0bc7132146a5925a3cc28d5bc498eb1b1ff09dedcfaae10f781ff2274 go1.25.10.netbsd-386.tar.gz
|
||||||
7e7389e404dca1088c31f0fc07f1dd60891d7182bcd621469c14f7e79eceb3ff go1.25.7.netbsd-amd64.tar.gz
|
63b2d50d7f8f269a9c82d42a4060e90cffb7f9102299818bb071b067aac8da8f go1.25.10.netbsd-amd64.tar.gz
|
||||||
70388bb3ef2f03dbf1357e9056bd09034a67e018262557354f8cf549766b3f9d go1.25.7.netbsd-arm.tar.gz
|
c35129f68796526aa4dc4b6f481e2d995ef312aedadc88b659b945cc00e1f8f0 go1.25.10.netbsd-arm.tar.gz
|
||||||
8c1cda9d25bfc9b18d24d5f95fc23949dd3ff99fa408a6cfa40e2cf12b07e362 go1.25.7.netbsd-arm64.tar.gz
|
2f541da4e2b298154d992d1f11bbb38c89d0821d91cc50a46776d42bb5e63bca go1.25.10.netbsd-arm64.tar.gz
|
||||||
42f0d1bfbe39b8401cccb84dd66b30795b97bfc9620dfdc17c5cd4fcf6495cb0 go1.25.7.openbsd-386.tar.gz
|
2d42e569b07f1b99fdbfd008e7c22f967d165e2ce02464f46818fbed2aec43f5 go1.25.10.openbsd-386.tar.gz
|
||||||
e514879c0a28bc32123cd52c4c093de912477fe83f36a6d07517d066ef55391a go1.25.7.openbsd-amd64.tar.gz
|
0ad05960e8c9f867328151308c87f938433bec8f22f6a9437a896e22169fc840 go1.25.10.openbsd-amd64.tar.gz
|
||||||
8cd22530695a0218232bf7efea8f162df1697a3106942ac4129b8c3de39ce4ef go1.25.7.openbsd-arm.tar.gz
|
099cc11473f99461c77161912740945308f08f6834980afb262c72bdc915f2d7 go1.25.10.openbsd-arm.tar.gz
|
||||||
938720f6ebc0d1c53d7840321d3a31f29fd02496e84a6538f442a9311dc1cc9a go1.25.7.openbsd-arm64.tar.gz
|
bdf3335d5008c1ddc81fa94892283e4f1fee22566f5351d4e726d9f55a67c838 go1.25.10.openbsd-arm64.tar.gz
|
||||||
a4c378b73b98f89a3596c2ef51aabbb28783d9ca29f7e317d8ca07939660ce6f go1.25.7.openbsd-ppc64.tar.gz
|
0933d418da0a61e0f29de717a77498f16b9b5b50dbe2205e20b2ed7fd4067f75 go1.25.10.openbsd-ppc64.tar.gz
|
||||||
937b58734fbeaa8c7941a0e4285e7e84b7885396e8d11c23f9ab1a8ff10ff20e go1.25.7.openbsd-riscv64.tar.gz
|
191e6f3e75712f8c13d189d53b668e2cac6449f26474c1d86fbd04f6e9846f9c go1.25.10.openbsd-riscv64.tar.gz
|
||||||
61a093c8c5244916f25740316386bb9f141545dcf01b06a79d1c78ece488403e go1.25.7.plan9-386.tar.gz
|
68c053c8acd76c50fc430e92f4a86110ec3d97dd03d27b9339b4eaf793caff5f go1.25.10.plan9-386.tar.gz
|
||||||
7fc8f6689c9de8ccb7689d2278035fa83c2d601409101840df6ddfe09ba58699 go1.25.7.plan9-amd64.tar.gz
|
42e2c46638ae22d93402e79efb40faee5c42cf7c56a01bb3ab47c6bb2512b745 go1.25.10.plan9-amd64.tar.gz
|
||||||
9661dff8eaeeb62f1c3aadbc5ff189a2e6744e1ec885e32dbcb438f58a34def5 go1.25.7.plan9-arm.tar.gz
|
3ef1d5838b1648da16724a07b72e839ccbd7cb8899c3e0426afd6b79d494b91c go1.25.10.plan9-arm.tar.gz
|
||||||
28ecba0e1d7950c8b29a4a04962dd49c3bf5221f55a44f17d98f369f82859cf4 go1.25.7.solaris-amd64.tar.gz
|
631e3716017fbec06500a628d97e1155daec3593f0a7812c2ebfe8fc8c96b2ab go1.25.10.solaris-amd64.tar.gz
|
||||||
baa6b488291801642fa620026169e38bec2da2ac187cd3ae2145721cf826bbc3 go1.25.7.windows-386.zip
|
ddc693d2d9d7cc671ebb72d1d50aa05670f95b059b7d90440611af57976871d5 go1.25.10.windows-386.zip
|
||||||
c75e5f4ff62d085cc0017be3ad19d5536f46825fa05db06ec468941f847e3228 go1.25.7.windows-amd64.zip
|
ca37af2dadd8544464f1a9ca7c3886499d1cdfcb263855d0a1d71f194b2bd222 go1.25.10.windows-amd64.zip
|
||||||
807033f85931bc4a589ca8497535dcbeb1f30d506e47fa200f5f04c4a71c3d9f go1.25.7.windows-arm64.zip
|
38be57e0398bd93673d65bcae6dc7ee3cf151d7038d0dba5c60a5153022872da go1.25.10.windows-arm64.zip
|
||||||
|
|
||||||
# version:golangci 2.10.1
|
# version:golangci 2.10.1
|
||||||
# https://github.com/golangci/golangci-lint/releases/
|
# https://github.com/golangci/golangci-lint/releases/
|
||||||
|
|
|
||||||
125
build/ci.go
125
build/ci.go
|
|
@ -73,21 +73,9 @@ var (
|
||||||
"./cmd/keeper",
|
"./cmd/keeper",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Files that end up in the geth*.zip archive.
|
// Files that end up in the geth-alltools*.zip archive (and the NSIS installer
|
||||||
gethArchiveFiles = []string{
|
// dev-tools section). Order matches the historical layout produced by ci.go.
|
||||||
"COPYING",
|
allToolsBinaries = []string{"abigen", "evm", "geth", "rlpdump", "clef"}
|
||||||
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"),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keeper build targets with their configurations
|
// Keeper build targets with their configurations
|
||||||
keeperTargets = []struct {
|
keeperTargets = []struct {
|
||||||
|
|
@ -180,13 +168,35 @@ var (
|
||||||
|
|
||||||
var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin"))
|
var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin"))
|
||||||
|
|
||||||
func executablePath(name string) string {
|
// executablePath returns the path to a built binary in GOBIN, applying the
|
||||||
if runtime.GOOS == "windows" {
|
// platform-specific extension for the given target OS.
|
||||||
|
func executablePath(name, targetOS string) string {
|
||||||
|
if targetOS == "windows" {
|
||||||
name += ".exe"
|
name += ".exe"
|
||||||
}
|
}
|
||||||
return filepath.Join(GOBIN, name)
|
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() {
|
func main() {
|
||||||
log.SetFlags(log.Lshortfile)
|
log.SetFlags(log.Lshortfile)
|
||||||
|
|
||||||
|
|
@ -233,6 +243,7 @@ func main() {
|
||||||
func doInstall(cmdline []string) {
|
func doInstall(cmdline []string) {
|
||||||
var (
|
var (
|
||||||
dlgo = flag.Bool("dlgo", false, "Download Go and build with it")
|
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")
|
arch = flag.String("arch", "", "Architecture to cross build for")
|
||||||
cc = flag.String("cc", "", "C compiler to cross build with")
|
cc = flag.String("cc", "", "C compiler to cross build with")
|
||||||
staticlink = flag.Bool("static", false, "Create statically-linked executable")
|
staticlink = flag.Bool("static", false, "Create statically-linked executable")
|
||||||
|
|
@ -241,7 +252,7 @@ func doInstall(cmdline []string) {
|
||||||
env := build.Env()
|
env := build.Env()
|
||||||
|
|
||||||
// Configure the toolchain.
|
// Configure the toolchain.
|
||||||
tc := build.GoToolchain{GOARCH: *arch, CC: *cc}
|
tc := build.GoToolchain{GOOS: *targetOS, GOARCH: *arch, CC: *cc}
|
||||||
if *dlgo {
|
if *dlgo {
|
||||||
csdb := download.MustLoadChecksums("build/checksums.txt")
|
csdb := download.MustLoadChecksums("build/checksums.txt")
|
||||||
tc.Root = build.DownloadGo(csdb)
|
tc.Root = build.DownloadGo(csdb)
|
||||||
|
|
@ -255,7 +266,7 @@ func doInstall(cmdline []string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure the build.
|
// Configure the build.
|
||||||
gobuild := tc.Go("build", buildFlags(env, *staticlink, buildTags)...)
|
gobuild := tc.Go("build", buildFlags(env, *staticlink, buildTags, *targetOS)...)
|
||||||
|
|
||||||
// Show packages during build.
|
// Show packages during build.
|
||||||
gobuild.Args = append(gobuild.Args, "-v")
|
gobuild.Args = append(gobuild.Args, "-v")
|
||||||
|
|
@ -270,7 +281,7 @@ func doInstall(cmdline []string) {
|
||||||
// Do the build!
|
// Do the build!
|
||||||
for _, pkg := range packages {
|
for _, pkg := range packages {
|
||||||
args := slices.Clone(gobuild.Args)
|
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)
|
args = append(args, pkg)
|
||||||
build.MustRun(&exec.Cmd{Path: gobuild.Path, Args: args, Env: gobuild.Env})
|
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.GOARCH = target.GOARCH
|
||||||
tc.GOOS = target.GOOS
|
tc.GOOS = target.GOOS
|
||||||
tc.CC = target.CC
|
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.Dir = "./cmd/keeper"
|
||||||
gobuild.Args = append(gobuild.Args, "-v")
|
gobuild.Args = append(gobuild.Args, "-v")
|
||||||
|
|
||||||
|
|
@ -307,14 +324,15 @@ func doInstallKeeper(cmdline []string) {
|
||||||
outputName := fmt.Sprintf("keeper-%s", target.Name)
|
outputName := fmt.Sprintf("keeper-%s", target.Name)
|
||||||
|
|
||||||
args := slices.Clone(gobuild.Args)
|
args := slices.Clone(gobuild.Args)
|
||||||
args = append(args, "-o", executablePath(outputName))
|
args = append(args, "-o", executablePath(outputName, targetOS))
|
||||||
args = append(args, ".")
|
args = append(args, ".")
|
||||||
build.MustRun(&exec.Cmd{Path: gobuild.Path, Args: args, Env: gobuild.Env, Dir: gobuild.Dir})
|
build.MustRun(&exec.Cmd{Path: gobuild.Path, Args: args, Env: gobuild.Env, Dir: gobuild.Dir})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildFlags returns the go tool flags for building.
|
// buildFlags returns the go tool flags for building. targetOS is the OS we
|
||||||
func buildFlags(env build.Environment, staticLinking bool, buildTags []string) (flags []string) {
|
// are producing binaries for.
|
||||||
|
func buildFlags(env build.Environment, staticLinking bool, buildTags []string, targetOS string) (flags []string) {
|
||||||
var ld []string
|
var ld []string
|
||||||
// See https://github.com/golang/go/issues/33772#issuecomment-528176001
|
// 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
|
// 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,
|
// 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.
|
// and there is no downside to this, so we just keep doing it.
|
||||||
if runtime.GOOS == "darwin" {
|
if targetOS == "darwin" {
|
||||||
ld = append(ld, "-s")
|
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
|
// Enforce the stacksize to 8M, which is the case on most platforms apart from
|
||||||
// alpine Linux.
|
// alpine Linux.
|
||||||
// See https://sourceware.org/binutils/docs-2.23.1/ld/Options.html#Options
|
// See https://sourceware.org/binutils/docs-2.23.1/ld/Options.html#Options
|
||||||
|
|
@ -682,12 +700,13 @@ func downloadProtoc(cachedir string) string {
|
||||||
// Release Packaging
|
// Release Packaging
|
||||||
func doArchive(cmdline []string) {
|
func doArchive(cmdline []string) {
|
||||||
var (
|
var (
|
||||||
arch = flag.String("arch", runtime.GOARCH, "Architecture cross packaging")
|
targetOS = flag.String("os", runtime.GOOS, "Target OS the binaries were built for")
|
||||||
atype = flag.String("type", "zip", "Type of archive to write (zip|tar)")
|
arch = flag.String("arch", runtime.GOARCH, "Architecture cross packaging")
|
||||||
signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. LINUX_SIGNING_KEY)`)
|
atype = flag.String("type", "zip", "Type of archive to write (zip|tar)")
|
||||||
signify = flag.String("signify", "", `Environment variable holding the signify key (e.g. LINUX_SIGNIFY_KEY)`)
|
signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. LINUX_SIGNING_KEY)`)
|
||||||
upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`)
|
signify = flag.String("signify", "", `Environment variable holding the signify key (e.g. LINUX_SIGNIFY_KEY)`)
|
||||||
ext string
|
upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`)
|
||||||
|
ext string
|
||||||
)
|
)
|
||||||
flag.CommandLine.Parse(cmdline)
|
flag.CommandLine.Parse(cmdline)
|
||||||
switch *atype {
|
switch *atype {
|
||||||
|
|
@ -701,15 +720,15 @@ func doArchive(cmdline []string) {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
env = build.Env()
|
env = build.Env()
|
||||||
basegeth = archiveBasename(*arch, version.Archive(env.Commit))
|
basegeth = archiveBasename(*targetOS, *arch, version.Archive(env.Commit))
|
||||||
geth = "geth-" + basegeth + ext
|
geth = "geth-" + basegeth + ext
|
||||||
alltools = "geth-alltools-" + basegeth + ext
|
alltools = "geth-alltools-" + basegeth + ext
|
||||||
)
|
)
|
||||||
maybeSkipArchive(env)
|
maybeSkipArchive(env)
|
||||||
if err := build.WriteArchive(geth, gethArchiveFiles); err != nil {
|
if err := build.WriteArchive(geth, gethArchiveFiles(*targetOS)); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := build.WriteArchive(alltools, allToolsArchiveFiles); err != nil {
|
if err := build.WriteArchive(alltools, allToolsArchiveFiles(*targetOS)); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
for _, archive := range []string{geth, alltools} {
|
for _, archive := range []string{geth, alltools} {
|
||||||
|
|
@ -735,7 +754,11 @@ func doKeeperArchive(cmdline []string) {
|
||||||
maybeSkipArchive(env)
|
maybeSkipArchive(env)
|
||||||
files := []string{"COPYING"}
|
files := []string{"COPYING"}
|
||||||
for _, target := range keeperTargets {
|
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 {
|
if err := build.WriteArchive(keeper, files); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
|
@ -745,8 +768,8 @@ func doKeeperArchive(cmdline []string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func archiveBasename(arch string, archiveVersion string) string {
|
func archiveBasename(targetOS, arch, archiveVersion string) string {
|
||||||
platform := runtime.GOOS + "-" + arch
|
platform := targetOS + "-" + arch
|
||||||
if arch == "arm" {
|
if arch == "arm" {
|
||||||
platform += os.Getenv("GOARM")
|
platform += os.Getenv("GOARM")
|
||||||
}
|
}
|
||||||
|
|
@ -1209,13 +1232,13 @@ func doWindowsInstaller(cmdline []string) {
|
||||||
env := build.Env()
|
env := build.Env()
|
||||||
maybeSkipArchive(env)
|
maybeSkipArchive(env)
|
||||||
|
|
||||||
// Aggregate binaries that are included in the installer
|
// Aggregate binaries that are included in the installer.
|
||||||
var (
|
var (
|
||||||
devTools []string
|
devTools []string
|
||||||
allTools []string
|
allTools []string
|
||||||
gethTool string
|
gethTool string
|
||||||
)
|
)
|
||||||
for _, file := range allToolsArchiveFiles {
|
for _, file := range allToolsArchiveFiles("windows") {
|
||||||
if file == "COPYING" { // license, copied later
|
if file == "COPYING" { // license, copied later
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -1252,16 +1275,24 @@ func doWindowsInstaller(cmdline []string) {
|
||||||
if env.Commit != "" {
|
if env.Commit != "" {
|
||||||
ver[2] += "-" + env.Commit[:8]
|
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 {
|
if err != nil {
|
||||||
log.Fatalf("Failed to convert installer file path: %v", err)
|
log.Fatalf("Failed to convert installer file path: %v", err)
|
||||||
}
|
}
|
||||||
build.MustRunCommand("makensis.exe",
|
// makensis on Windows is "makensis.exe" with /D-style defines; on Linux
|
||||||
"/DOUTPUTFILE="+installer,
|
// (and other Unixes) the binary is "makensis" and accepts -D.
|
||||||
"/DMAJORVERSION="+ver[0],
|
makensisCmd := "makensis"
|
||||||
"/DMINORVERSION="+ver[1],
|
defineFlag := "-D"
|
||||||
"/DBUILDVERSION="+ver[2],
|
if runtime.GOOS == "windows" {
|
||||||
"/DARCH="+*arch,
|
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"),
|
filepath.Join(*workdir, "geth.nsi"),
|
||||||
)
|
)
|
||||||
// Sign and publish installer.
|
// Sign and publish installer.
|
||||||
|
|
|
||||||
|
|
@ -215,7 +215,7 @@ func generate(c *cli.Context) error {
|
||||||
code string
|
code string
|
||||||
err error
|
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)
|
code, err = abigen.BindV2(types, abis, bins, c.String(pkgFlag.Name), libs, aliases)
|
||||||
} else {
|
} else {
|
||||||
code, err = abigen.Bind(types, abis, bins, sigs, c.String(pkgFlag.Name), libs, aliases)
|
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) {
|
func formatAttrIP(v rlp.RawValue) (string, bool) {
|
||||||
content, _, err := rlp.SplitString(v)
|
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 "", false
|
||||||
}
|
}
|
||||||
return net.IP(content).String(), true
|
return net.IP(content).String(), true
|
||||||
|
|
|
||||||
|
|
@ -257,34 +257,50 @@ that they are returned by FINDNODE.`)
|
||||||
|
|
||||||
// Create bystanders.
|
// Create bystanders.
|
||||||
nodes := make([]*bystander, 5)
|
nodes := make([]*bystander, 5)
|
||||||
added := make(chan enode.ID, len(nodes))
|
liveCh := make(chan enode.ID, len(nodes))
|
||||||
for i := range nodes {
|
for i := range nodes {
|
||||||
nodes[i] = newBystander(t, s, added)
|
nodes[i] = newBystander(t, s, liveCh)
|
||||||
defer nodes[i].close()
|
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
|
timeout := 60 * time.Second
|
||||||
timeoutCh := time.After(timeout)
|
timeoutCh := time.After(timeout)
|
||||||
for count := 0; count < len(nodes); {
|
liveSet := make(map[enode.ID]*enode.Node)
|
||||||
|
for len(liveSet) < requiredLiveNodes {
|
||||||
select {
|
select {
|
||||||
case id := <-added:
|
case id := <-liveCh:
|
||||||
t.Logf("bystander node %v added to remote table", id)
|
for _, bn := range nodes {
|
||||||
count++
|
if bn.id() == id {
|
||||||
|
liveSet[id] = bn.conn.localNode.Node()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Logf("bystander node %v became live", id)
|
||||||
case <-timeoutCh:
|
case <-timeoutCh:
|
||||||
t.Errorf("remote added %d bystander nodes in %v, need %d to continue", count, timeout, len(nodes))
|
t.Errorf("remote revalidated %d bystander nodes in %v, need %d to continue", len(liveSet), timeout, requiredLiveNodes)
|
||||||
t.Logf("this can happen if the node has a non-empty table from previous runs")
|
|
||||||
return
|
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
|
var dists []uint
|
||||||
expect := make(map[enode.ID]*enode.Node)
|
expect := make(map[enode.ID]*enode.Node)
|
||||||
for _, bn := range nodes {
|
for id, n := range liveSet {
|
||||||
n := bn.conn.localNode.Node()
|
expect[id] = n
|
||||||
expect[n.ID()] = n
|
|
||||||
d := uint(enode.LogDist(n.ID(), s.Dest.ID()))
|
d := uint(enode.LogDist(n.ID(), s.Dest.ID()))
|
||||||
if !slices.Contains(dists, d) {
|
if !slices.Contains(dists, d) {
|
||||||
dists = append(dists, d)
|
dists = append(dists, d)
|
||||||
|
|
@ -295,42 +311,63 @@ that they are returned by FINDNODE.`)
|
||||||
t.Log("requesting nodes")
|
t.Log("requesting nodes")
|
||||||
conn, l1 := s.listen1(t)
|
conn, l1 := s.listen1(t)
|
||||||
defer conn.close()
|
defer conn.close()
|
||||||
foundNodes, err := conn.findnode(l1, dists)
|
|
||||||
if err != nil {
|
const maxAttempts = 5
|
||||||
t.Fatal(err)
|
const retryInterval = 2 * time.Second
|
||||||
}
|
|
||||||
t.Logf("remote returned %d nodes for distance list %v", len(foundNodes), dists)
|
for attempt := 1; attempt <= maxAttempts; attempt++ {
|
||||||
for _, n := range foundNodes {
|
foundNodes, err := conn.findnode(l1, dists)
|
||||||
delete(expect, n.ID())
|
if err != nil {
|
||||||
}
|
t.Fatal(err)
|
||||||
if len(expect) > 0 {
|
}
|
||||||
t.Errorf("missing %d nodes in FINDNODE result", len(expect))
|
missing := make(map[enode.ID]struct{})
|
||||||
t.Logf("this can happen if the test is run multiple times in quick succession")
|
for id := range expect {
|
||||||
t.Logf("and the remote node hasn't removed dead nodes from previous runs yet")
|
missing[id] = struct{}{}
|
||||||
} else {
|
}
|
||||||
t.Logf("all %d expected nodes were returned", len(nodes))
|
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.
|
// A bystander is a node whose only purpose is filling a spot in the remote table.
|
||||||
type bystander struct {
|
type bystander struct {
|
||||||
dest *enode.Node
|
dest *enode.Node
|
||||||
conn *conn
|
conn *conn
|
||||||
l net.PacketConn
|
l net.PacketConn
|
||||||
|
known []*enode.Node
|
||||||
|
|
||||||
addedCh chan enode.ID
|
liveCh chan enode.ID
|
||||||
done sync.WaitGroup
|
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, l := s.listen1(t)
|
||||||
conn.setEndpoint(l) // bystander nodes need IP/port to get pinged
|
conn.setEndpoint(l) // bystander nodes need IP/port to get pinged
|
||||||
bn := &bystander{
|
bn := &bystander{
|
||||||
conn: conn,
|
conn: conn,
|
||||||
l: l,
|
l: l,
|
||||||
dest: s.Dest,
|
dest: s.Dest,
|
||||||
addedCh: added,
|
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)
|
bn.done.Add(1)
|
||||||
go bn.loop()
|
go bn.loop()
|
||||||
return bn
|
return bn
|
||||||
|
|
@ -351,48 +388,57 @@ func (bn *bystander) close() {
|
||||||
func (bn *bystander) loop() {
|
func (bn *bystander) loop() {
|
||||||
defer bn.done.Done()
|
defer bn.done.Done()
|
||||||
|
|
||||||
var (
|
|
||||||
lastPing time.Time
|
|
||||||
wasAdded bool
|
|
||||||
)
|
|
||||||
for {
|
for {
|
||||||
// Ping the remote node.
|
p, from := bn.conn.readFrom(bn.l)
|
||||||
if !wasAdded && time.Since(lastPing) > 10*time.Second {
|
switch p := p.(type) {
|
||||||
bn.conn.reqresp(bn.l, &v5wire.Ping{
|
case *v5wire.Whoareyou:
|
||||||
ReqID: bn.conn.nextReqID(),
|
p.Node = bn.dest
|
||||||
ENRSeq: bn.dest.Seq(),
|
if resp, ok := bn.sent[p.Nonce]; ok {
|
||||||
})
|
nonce := bn.conn.writeTo(bn.l, resp, p, from)
|
||||||
lastPing = time.Now()
|
delete(bn.sent, p.Nonce)
|
||||||
}
|
bn.sent[nonce] = resp
|
||||||
// Answer packets.
|
} else {
|
||||||
switch p := bn.conn.read(bn.l).(type) {
|
bn.conn.writeTo(bn.l, &v5wire.Ping{
|
||||||
case *v5wire.Ping:
|
ReqID: bn.conn.nextReqID(),
|
||||||
bn.conn.write(bn.l, &v5wire.Pong{
|
ENRSeq: bn.conn.localNode.Seq(),
|
||||||
ReqID: p.ReqID,
|
}, p, from)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
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() {
|
func (bn *bystander) notifyLive() {
|
||||||
if bn.addedCh != nil {
|
if bn.liveCh != nil {
|
||||||
bn.addedCh <- bn.id()
|
bn.liveCh <- bn.id()
|
||||||
bn.addedCh = nil
|
bn.liveCh = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -127,14 +127,16 @@ func (tc *conn) nextReqID() []byte {
|
||||||
// The request is retried if a handshake is requested.
|
// The request is retried if a handshake is requested.
|
||||||
func (tc *conn) reqresp(c net.PacketConn, req v5wire.Packet) v5wire.Packet {
|
func (tc *conn) reqresp(c net.PacketConn, req v5wire.Packet) v5wire.Packet {
|
||||||
reqnonce := tc.write(c, req, nil)
|
reqnonce := tc.write(c, req, nil)
|
||||||
switch resp := tc.read(c).(type) {
|
resp, from := tc.readFrom(c)
|
||||||
|
switch resp := resp.(type) {
|
||||||
case *v5wire.Whoareyou:
|
case *v5wire.Whoareyou:
|
||||||
if resp.Nonce != reqnonce {
|
if resp.Nonce != reqnonce {
|
||||||
return readErrorf("wrong nonce %x in WHOAREYOU (want %x)", resp.Nonce[:], reqnonce[:])
|
return readErrorf("wrong nonce %x in WHOAREYOU (want %x)", resp.Nonce[:], reqnonce[:])
|
||||||
}
|
}
|
||||||
resp.Node = tc.remote
|
resp.Node = tc.remote
|
||||||
tc.write(c, req, resp)
|
tc.writeTo(c, req, resp, from)
|
||||||
return tc.read(c)
|
resp2, _ := tc.readFrom(c)
|
||||||
|
return resp2
|
||||||
default:
|
default:
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
@ -150,21 +152,24 @@ func (tc *conn) findnode(c net.PacketConn, dists []uint) ([]*enode.Node, error)
|
||||||
results []*enode.Node
|
results []*enode.Node
|
||||||
)
|
)
|
||||||
for n := 1; n > 0; {
|
for n := 1; n > 0; {
|
||||||
switch resp := tc.read(c).(type) {
|
resp, from := tc.readFrom(c)
|
||||||
|
switch resp := resp.(type) {
|
||||||
case *v5wire.Whoareyou:
|
case *v5wire.Whoareyou:
|
||||||
// Handle handshake.
|
// Handle handshake.
|
||||||
if resp.Nonce == reqnonce {
|
if resp.Nonce == reqnonce {
|
||||||
resp.Node = tc.remote
|
resp.Node = tc.remote
|
||||||
tc.write(c, findnode, resp)
|
tc.writeTo(c, findnode, resp, from)
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("unexpected WHOAREYOU (nonce %x), waiting for NODES", resp.Nonce[:])
|
return nil, fmt.Errorf("unexpected WHOAREYOU (nonce %x), waiting for NODES", resp.Nonce[:])
|
||||||
}
|
}
|
||||||
case *v5wire.Ping:
|
case *v5wire.Ping:
|
||||||
// Handle ping from remote.
|
// Handle ping from remote.
|
||||||
tc.write(c, &v5wire.Pong{
|
tc.writeTo(c, &v5wire.Pong{
|
||||||
ReqID: resp.ReqID,
|
ReqID: resp.ReqID,
|
||||||
ENRSeq: tc.localNode.Seq(),
|
ENRSeq: tc.localNode.Seq(),
|
||||||
}, nil)
|
ToIP: from.IP,
|
||||||
|
ToPort: uint16(from.Port),
|
||||||
|
}, nil, from)
|
||||||
case *v5wire.Nodes:
|
case *v5wire.Nodes:
|
||||||
// Got NODES! Check request ID.
|
// Got NODES! Check request ID.
|
||||||
if !bytes.Equal(resp.ReqID, findnode.ReqID) {
|
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.
|
// write sends a packet on the given connection.
|
||||||
func (tc *conn) write(c net.PacketConn, p v5wire.Packet, challenge *v5wire.Whoareyou) v5wire.Nonce {
|
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)
|
packet, nonce, err := tc.codec.Encode(tc.remote.ID(), tc.remoteAddr.String(), p, challenge)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Errorf("can't encode %v packet: %v", p.Name(), err))
|
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)
|
tc.logf("Can't send %s: %v", p.Name(), err)
|
||||||
} else {
|
} else {
|
||||||
tc.logf(">> %s", p.Name())
|
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.
|
// read waits for an incoming packet on the given connection.
|
||||||
func (tc *conn) read(c net.PacketConn) v5wire.Packet {
|
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)
|
buf := make([]byte, 1280)
|
||||||
if err := c.SetReadDeadline(time.Now().Add(waitTime)); err != nil {
|
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 {
|
if err != nil {
|
||||||
return &readError{err}
|
return &readError{err}, nil
|
||||||
}
|
}
|
||||||
// Always use tc.remoteAddr for session lookup. The actual source address of
|
udpFrom, _ := from.(*net.UDPAddr)
|
||||||
// the packet may differ from tc.remoteAddr when the remote node is reachable
|
// Use tc.remoteAddr for codec/session lookup because the fixture keys sessions
|
||||||
// via multiple networks (e.g. Docker bridge vs. overlay), but the codec's
|
// by the advertised endpoint, but return the actual UDP source so responses can
|
||||||
// session cache is keyed by the address used during Encode.
|
// comply with the spec and go back to the request envelope address.
|
||||||
_, _, p, err := tc.codec.Decode(buf[:n], tc.remoteAddr.String())
|
_, _, p, err := tc.codec.Decode(buf[:n], tc.remoteAddr.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &readError{err}
|
return &readError{err}, udpFrom
|
||||||
}
|
}
|
||||||
tc.logf("<< %s", p.Name())
|
tc.logf("<< %s", p.Name())
|
||||||
return p
|
return p, udpFrom
|
||||||
}
|
}
|
||||||
|
|
||||||
// logf prints to the test log.
|
// logf prints to the test log.
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
|
@ -30,6 +31,31 @@ import (
|
||||||
"github.com/urfave/cli/v2"
|
"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 (
|
var (
|
||||||
rlpxCommand = &cli.Command{
|
rlpxCommand = &cli.Command{
|
||||||
Name: "rlpx",
|
Name: "rlpx",
|
||||||
|
|
@ -103,11 +129,15 @@ func rlpxPing(ctx *cli.Context) error {
|
||||||
}
|
}
|
||||||
fmt.Printf("%+v\n", h)
|
fmt.Printf("%+v\n", h)
|
||||||
case 1:
|
case 1:
|
||||||
var msg []p2p.DiscReason
|
// The disconnect message is specified as a list containing the reason,
|
||||||
if rlp.DecodeBytes(data, &msg); len(msg) == 0 {
|
// but some implementations (including older geth) send the reason as a
|
||||||
return errors.New("invalid disconnect message")
|
// 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:
|
default:
|
||||||
return fmt.Errorf("invalid message code %d, expected handshake (code zero) or disconnect (code one)", code)
|
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.
|
// accumulation across the entire set and are verified at the end.
|
||||||
for it.Next() {
|
for it.Next() {
|
||||||
// 1) next() walks the block index, so we're able to implicitly verify it.
|
// 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()
|
block, receipts, err := it.BlockAndReceipts()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error reading block %d: %w", it.Number(), err)
|
return fmt.Errorf("error reading block %d: %w", it.Number(), err)
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,12 @@
|
||||||
package t8ntool
|
package t8ntool
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
stdmath "math"
|
stdmath "math"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
|
@ -47,6 +50,9 @@ type Prestate struct {
|
||||||
Env stEnv `json:"env"`
|
Env stEnv `json:"env"`
|
||||||
Pre types.GenesisAlloc `json:"pre"`
|
Pre types.GenesisAlloc `json:"pre"`
|
||||||
TreeLeaves map[common.Hash]hexutil.Bytes `json:"vkt,omitempty"`
|
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
|
//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
|
return h
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
isEIP4762 = chainConfig.IsVerkle(big.NewInt(int64(pre.Env.Number)), pre.Env.Timestamp)
|
isEIP4762 = chainConfig.IsUBT(big.NewInt(int64(pre.Env.Number)), pre.Env.Timestamp)
|
||||||
statedb = MakePreState(rawdb.NewMemoryDatabase(), pre.Pre, isEIP4762)
|
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)
|
signer = types.MakeSigner(chainConfig, new(big.Int).SetUint64(pre.Env.Number), pre.Env.Timestamp)
|
||||||
gaspool = core.NewGasPool(pre.Env.GasLimit)
|
gaspool = core.NewGasPool(pre.Env.GasLimit)
|
||||||
blockHash = common.Hash{0x13, 0x37}
|
blockHash = common.Hash{0x13, 0x37}
|
||||||
|
|
@ -253,7 +270,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
statedb.SetTxContext(tx.Hash(), len(receipts))
|
statedb.SetTxContext(tx.Hash(), len(receipts), uint32(len(receipts)+1))
|
||||||
var (
|
var (
|
||||||
snapshot = statedb.Snapshot()
|
snapshot = statedb.Snapshot()
|
||||||
gp = gaspool.Snapshot()
|
gp = gaspool.Snapshot()
|
||||||
|
|
@ -315,27 +332,14 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gather the execution-layer triggered requests.
|
// Gather the execution-layer triggered requests.
|
||||||
var requests [][]byte
|
var allLogs []*types.Log
|
||||||
if chainConfig.IsPrague(vmContext.BlockNumber, vmContext.Time) {
|
for _, receipt := range receipts {
|
||||||
requests = [][]byte{}
|
allLogs = append(allLogs, receipt.Logs...)
|
||||||
// EIP-6110
|
}
|
||||||
var allLogs []*types.Log
|
requests, err := core.PostExecution(context.Background(), chainConfig, vmContext.BlockNumber, vmContext.Time, allLogs, evm, uint32(len(receipts)+1))
|
||||||
for _, receipt := range receipts {
|
if err != nil {
|
||||||
allLogs = append(allLogs, receipt.Logs...)
|
return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("failed to process post-execution: %v", err))
|
||||||
}
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Commit block
|
// Commit block
|
||||||
root, err := statedb.Commit(vmContext.BlockNumber.Uint64(), chainConfig.IsEIP158(vmContext.BlockNumber), chainConfig.IsCancun(vmContext.BlockNumber, vmContext.Time))
|
root, err := statedb.Commit(vmContext.BlockNumber.Uint64(), chainConfig.IsEIP158(vmContext.BlockNumber), chainConfig.IsCancun(vmContext.BlockNumber, vmContext.Time))
|
||||||
if err != nil {
|
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 {
|
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)
|
sdb := state.NewDatabase(tdb, nil)
|
||||||
|
|
||||||
root := types.EmptyRootHash
|
root := types.EmptyRootHash
|
||||||
|
|
@ -414,6 +418,76 @@ func MakePreState(db ethdb.Database, accounts types.GenesisAlloc, isBintrie bool
|
||||||
return statedb
|
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) {
|
func rlpHash(x any) (h common.Hash) {
|
||||||
hw := keccak.NewLegacyKeccak256()
|
hw := keccak.NewLegacyKeccak256()
|
||||||
rlp.Encode(hw, x)
|
rlp.Encode(hw, x)
|
||||||
|
|
|
||||||
|
|
@ -133,21 +133,21 @@ func Transaction(ctx *cli.Context) error {
|
||||||
}
|
}
|
||||||
// Check intrinsic gas
|
// Check intrinsic gas
|
||||||
rules := chainConfig.Rules(common.Big0, true, 0)
|
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 {
|
if err != nil {
|
||||||
r.Error = err
|
r.Error = err
|
||||||
results = append(results, r)
|
results = append(results, r)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
r.IntrinsicGas = gas
|
r.IntrinsicGas = cost.RegularGas
|
||||||
if tx.Gas() < gas {
|
if tx.Gas() < cost.RegularGas {
|
||||||
r.Error = fmt.Errorf("%w: have %d, want %d", core.ErrIntrinsicGas, tx.Gas(), gas)
|
r.Error = fmt.Errorf("%w: have %d, want %d", core.ErrIntrinsicGas, tx.Gas(), cost.RegularGas)
|
||||||
results = append(results, r)
|
results = append(results, r)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// For Prague txs, validate the floor data gas.
|
// For Prague txs, validate the floor data gas.
|
||||||
if rules.IsPrague {
|
if rules.IsPrague {
|
||||||
floorDataGas, err := core.FloorDataGas(tx.Data())
|
floorDataGas, err := core.FloorDataGas(rules, tx.Data(), tx.AccessList())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.Error = err
|
r.Error = err
|
||||||
results = append(results, r)
|
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")
|
r.Error = errors.New("gas limit exceeds maximum")
|
||||||
}
|
}
|
||||||
results = append(results, r)
|
results = append(results, r)
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package t8ntool
|
package t8ntool
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
@ -115,11 +116,10 @@ func Transition(ctx *cli.Context) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if allocStr != stdinSelector {
|
if allocStr != stdinSelector {
|
||||||
if err := readFile(allocStr, "alloc", &inputData.Alloc); err != nil {
|
prestate.AllocPath = allocStr
|
||||||
return err
|
} else {
|
||||||
}
|
prestate.Pre = inputData.Alloc
|
||||||
}
|
}
|
||||||
prestate.Pre = inputData.Alloc
|
|
||||||
|
|
||||||
if btStr != stdinSelector && btStr != "" {
|
if btStr != stdinSelector && btStr != "" {
|
||||||
if err := readFile(btStr, "BT", &inputData.BT); err != nil {
|
if err := readFile(btStr, "BT", &inputData.BT); err != nil {
|
||||||
|
|
@ -223,22 +223,57 @@ func Transition(ctx *cli.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Dump the execution result
|
// Dump the execution result.
|
||||||
var (
|
var (
|
||||||
collector = make(Alloc)
|
collector Alloc
|
||||||
btleaves map[common.Hash]hexutil.Bytes
|
btleaves map[common.Hash]hexutil.Bytes
|
||||||
)
|
)
|
||||||
isBinary := chainConfig.IsVerkle(big.NewInt(int64(prestate.Env.Number)), prestate.Env.Timestamp)
|
isBinary := chainConfig.IsUBT(big.NewInt(int64(prestate.Env.Number)), prestate.Env.Timestamp)
|
||||||
if !isBinary {
|
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)
|
s.DumpToCollector(collector, nil)
|
||||||
} else {
|
default:
|
||||||
btleaves = make(map[common.Hash]hexutil.Bytes)
|
btleaves = make(map[common.Hash]hexutil.Bytes)
|
||||||
if err := s.DumpBinTrieLeaves(btleaves); err != nil {
|
if err := s.DumpBinTrieLeaves(btleaves); err != nil {
|
||||||
return err
|
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 {
|
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 {
|
if addr == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
g[*addr] = dumpAccountToTypesAccount(dumpAccount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func dumpAccountToTypesAccount(dumpAccount state.DumpAccount) types.Account {
|
||||||
balance, _ := new(big.Int).SetString(dumpAccount.Balance, 0)
|
balance, _ := new(big.Int).SetString(dumpAccount.Balance, 0)
|
||||||
var storage map[common.Hash]common.Hash
|
var storage map[common.Hash]common.Hash
|
||||||
if dumpAccount.Storage != nil {
|
if dumpAccount.Storage != nil {
|
||||||
|
|
@ -335,13 +374,64 @@ func (g Alloc) OnAccount(addr *common.Address, dumpAccount state.DumpAccount) {
|
||||||
storage[k] = common.HexToHash(v)
|
storage[k] = common.HexToHash(v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
genesisAccount := types.Account{
|
return types.Account{
|
||||||
Code: dumpAccount.Code,
|
Code: dumpAccount.Code,
|
||||||
Storage: storage,
|
Storage: storage,
|
||||||
Balance: balance,
|
Balance: balance,
|
||||||
Nonce: dumpAccount.Nonce,
|
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
|
// 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
|
// dispatchOutput writes the output data to either stderr or stdout, or to the specified
|
||||||
// files
|
// files. An empty allocOutput skips the alloc dispatch, which is used when the
|
||||||
func dispatchOutput(ctx *cli.Context, baseDir string, result *ExecutionResult, alloc Alloc, body hexutil.Bytes, bt map[common.Hash]hexutil.Bytes) error {
|
// 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{})
|
stdOutObject := make(map[string]interface{})
|
||||||
stdErrObject := make(map[string]interface{})
|
stdErrObject := make(map[string]interface{})
|
||||||
dispatch := func(baseDir, fName, name string, obj interface{}) error {
|
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
|
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
|
return err
|
||||||
}
|
}
|
||||||
if err := dispatch(baseDir, ctx.String(OutputResultFlag.Name), "result", result); err != nil {
|
if err := dispatch(baseDir, ctx.String(OutputResultFlag.Name), "result", result); err != nil {
|
||||||
|
|
@ -452,10 +543,10 @@ func BinKeys(ctx *cli.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
db := triedb.NewDatabase(rawdb.NewMemoryDatabase(), triedb.VerkleDefaults)
|
db := triedb.NewDatabase(rawdb.NewMemoryDatabase(), triedb.UBTDefaults)
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
|
||||||
bt, err := genBinTrieFromAlloc(alloc, db)
|
bt, err := genBinTrieFromAlloc(alloc, db, triedb.UBTDefaults.BinTrieGroupDepth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error generating bt: %w", err)
|
return fmt.Errorf("error generating bt: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -496,10 +587,10 @@ func BinTrieRoot(ctx *cli.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
db := triedb.NewDatabase(rawdb.NewMemoryDatabase(), triedb.VerkleDefaults)
|
db := triedb.NewDatabase(rawdb.NewMemoryDatabase(), triedb.UBTDefaults)
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
|
||||||
bt, err := genBinTrieFromAlloc(alloc, db)
|
bt, err := genBinTrieFromAlloc(alloc, db, triedb.UBTDefaults.BinTrieGroupDepth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error generating bt: %w", err)
|
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?
|
// TODO(@CPerezz): Should this go to `bintrie` module?
|
||||||
func genBinTrieFromAlloc(alloc core.GenesisAlloc, db database.NodeDatabase) (*bintrie.BinaryTrie, error) {
|
func genBinTrieFromAlloc(alloc core.GenesisAlloc, db database.NodeDatabase, groupDepth int) (*bintrie.BinaryTrie, error) {
|
||||||
bt, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, db)
|
bt, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, db, groupDepth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,7 @@ var (
|
||||||
Name: "trace.noreturndata",
|
Name: "trace.noreturndata",
|
||||||
Aliases: []string{"noreturndata"},
|
Aliases: []string{"noreturndata"},
|
||||||
Value: true,
|
Value: true,
|
||||||
Usage: "enable return data output",
|
Usage: "disable return data output",
|
||||||
Category: traceCategory,
|
Category: traceCategory,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -166,8 +166,11 @@ func timedExec(bench bool, execFunc func() ([]byte, uint64, error)) ([]byte, exe
|
||||||
if haveGasUsed != gasUsed {
|
if haveGasUsed != gasUsed {
|
||||||
panic(fmt.Sprintf("gas differs, have %v want %v", haveGasUsed, gasUsed))
|
panic(fmt.Sprintf("gas differs, have %v want %v", haveGasUsed, gasUsed))
|
||||||
}
|
}
|
||||||
if haveErr != err {
|
if (haveErr == nil) != (err == nil) {
|
||||||
panic(fmt.Sprintf("err differs, have %v want %v", haveErr, err))
|
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!
|
// don't mutate the state!
|
||||||
runtimeConfig.State = prestate.Copy()
|
runtimeConfig.State = prestate.Copy()
|
||||||
output, _, gasLeft, err := runtime.Create(input, &runtimeConfig)
|
output, _, gasLeft, err := runtime.Create(input, &runtimeConfig)
|
||||||
return output, gasLeft, err
|
return output, initialGas - gasLeft, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if len(code) > 0 {
|
if len(code) > 0 {
|
||||||
|
|
|
||||||
|
|
@ -144,14 +144,14 @@ func convertToBinaryTrie(ctx *cli.Context) error {
|
||||||
defer srcTriedb.Close()
|
defer srcTriedb.Close()
|
||||||
|
|
||||||
destTriedb := triedb.NewDatabase(chaindb, &triedb.Config{
|
destTriedb := triedb.NewDatabase(chaindb, &triedb.Config{
|
||||||
IsVerkle: true,
|
IsUBT: true,
|
||||||
PathDB: &pathdb.Config{
|
PathDB: &pathdb.Config{
|
||||||
JournalDirectory: stack.ResolvePath("triedb-bintrie"),
|
JournalDirectory: stack.ResolvePath("triedb-bintrie"),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
defer destTriedb.Close()
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create binary trie: %w", err)
|
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()
|
runtime.GC()
|
||||||
debug.FreeOSMemory()
|
debug.FreeOSMemory()
|
||||||
|
|
||||||
bt, err := bintrie.NewBinaryTrie(newRoot, destDB)
|
bt, err := bintrie.NewBinaryTrie(newRoot, destDB, bt.GroupDepth())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, common.Hash{}, fmt.Errorf("failed to reload binary trie: %w", err)
|
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()
|
defer srcTriedb2.Close()
|
||||||
|
|
||||||
destTriedb := triedb.NewDatabase(chaindb, &triedb.Config{
|
destTriedb := triedb.NewDatabase(chaindb, &triedb.Config{
|
||||||
IsVerkle: true,
|
IsUBT: true,
|
||||||
PathDB: pathdb.Defaults,
|
PathDB: pathdb.Defaults,
|
||||||
})
|
})
|
||||||
defer destTriedb.Close()
|
defer destTriedb.Close()
|
||||||
|
|
||||||
bt, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, destTriedb)
|
bt, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, destTriedb, 8)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create binary trie: %v", err)
|
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)
|
t.Logf("Binary trie root: %x", currentRoot)
|
||||||
|
|
||||||
bt2, err := bintrie.NewBinaryTrie(currentRoot, destTriedb)
|
bt2, err := bintrie.NewBinaryTrie(currentRoot, destTriedb, 8)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to reload binary trie: %v", err)
|
t.Fatalf("failed to reload binary trie: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -190,11 +190,11 @@ func TestBintrieConvertDeleteSource(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
destTriedb := triedb.NewDatabase(chaindb, &triedb.Config{
|
destTriedb := triedb.NewDatabase(chaindb, &triedb.Config{
|
||||||
IsVerkle: true,
|
IsUBT: true,
|
||||||
PathDB: pathdb.Defaults,
|
PathDB: pathdb.Defaults,
|
||||||
})
|
})
|
||||||
|
|
||||||
bt, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, destTriedb)
|
bt, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, destTriedb, 8)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create binary trie: %v", err)
|
t.Fatalf("failed to create binary trie: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -209,7 +209,7 @@ func TestBintrieConvertDeleteSource(t *testing.T) {
|
||||||
}
|
}
|
||||||
srcTriedb2.Close()
|
srcTriedb2.Close()
|
||||||
|
|
||||||
bt2, err := bintrie.NewBinaryTrie(newRoot, destTriedb)
|
bt2, err := bintrie.NewBinaryTrie(newRoot, destTriedb, 8)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to reload binary trie after deletion: %v", err)
|
t.Fatalf("failed to reload binary trie after deletion: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ var (
|
||||||
utils.OverrideOsaka,
|
utils.OverrideOsaka,
|
||||||
utils.OverrideBPO1,
|
utils.OverrideBPO1,
|
||||||
utils.OverrideBPO2,
|
utils.OverrideBPO2,
|
||||||
utils.OverrideVerkle,
|
utils.OverrideUBT,
|
||||||
}, utils.DatabaseFlags),
|
}, utils.DatabaseFlags),
|
||||||
Description: `
|
Description: `
|
||||||
The init command initializes a new genesis block and definition for the network.
|
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)
|
v := ctx.Uint64(utils.OverrideBPO2.Name)
|
||||||
overrides.OverrideBPO2 = &v
|
overrides.OverrideBPO2 = &v
|
||||||
}
|
}
|
||||||
if ctx.IsSet(utils.OverrideVerkle.Name) {
|
if ctx.IsSet(utils.OverrideUBT.Name) {
|
||||||
v := ctx.Uint64(utils.OverrideVerkle.Name)
|
v := ctx.Uint64(utils.OverrideUBT.Name)
|
||||||
overrides.OverrideVerkle = &v
|
overrides.OverrideUBT = &v
|
||||||
}
|
}
|
||||||
|
|
||||||
chaindb := utils.MakeChainDatabase(ctx, stack, false)
|
chaindb := utils.MakeChainDatabase(ctx, stack, false)
|
||||||
defer chaindb.Close()
|
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()
|
defer triedb.Close()
|
||||||
|
|
||||||
_, hash, compatErr, err := core.SetupGenesisBlockWithOverride(chaindb, triedb, genesis, &overrides, nil)
|
_, hash, compatErr, err := core.SetupGenesisBlockWithOverride(chaindb, triedb, genesis, &overrides, nil)
|
||||||
|
|
@ -325,7 +325,7 @@ func dumpGenesis(ctx *cli.Context) error {
|
||||||
var genesis *core.Genesis
|
var genesis *core.Genesis
|
||||||
if utils.IsNetworkPreset(ctx) {
|
if utils.IsNetworkPreset(ctx) {
|
||||||
genesis = utils.MakeGenesis(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)
|
genesis = core.DeveloperGenesisBlock(11_500_000, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/eth/catalyst"
|
"github.com/ethereum/go-ethereum/eth/catalyst"
|
||||||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
"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/flags"
|
||||||
"github.com/ethereum/go-ethereum/internal/telemetry/tracesetup"
|
"github.com/ethereum/go-ethereum/internal/telemetry/tracesetup"
|
||||||
"github.com/ethereum/go-ethereum/internal/version"
|
"github.com/ethereum/go-ethereum/internal/version"
|
||||||
|
|
@ -235,9 +236,9 @@ func makeFullNode(ctx *cli.Context) *node.Node {
|
||||||
v := ctx.Uint64(utils.OverrideBPO2.Name)
|
v := ctx.Uint64(utils.OverrideBPO2.Name)
|
||||||
cfg.Eth.OverrideBPO2 = &v
|
cfg.Eth.OverrideBPO2 = &v
|
||||||
}
|
}
|
||||||
if ctx.IsSet(utils.OverrideVerkle.Name) {
|
if ctx.IsSet(utils.OverrideUBT.Name) {
|
||||||
v := ctx.Uint64(utils.OverrideVerkle.Name)
|
v := ctx.Uint64(utils.OverrideUBT.Name)
|
||||||
cfg.Eth.OverrideVerkle = &v
|
cfg.Eth.OverrideUBT = &v
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start metrics export if enabled.
|
// Start metrics export if enabled.
|
||||||
|
|
@ -269,25 +270,28 @@ func makeFullNode(ctx *cli.Context) *node.Node {
|
||||||
filterSystem := utils.RegisterFilterAPI(stack, backend, &cfg.Eth)
|
filterSystem := utils.RegisterFilterAPI(stack, backend, &cfg.Eth)
|
||||||
|
|
||||||
// Configure GraphQL if requested.
|
// Configure GraphQL if requested.
|
||||||
if ctx.IsSet(utils.GraphQLEnabledFlag.Name) {
|
if ctx.Bool(utils.GraphQLEnabledFlag.Name) {
|
||||||
utils.RegisterGraphQLService(stack, backend, filterSystem, &cfg.Node)
|
utils.RegisterGraphQLService(stack, backend, filterSystem, &cfg.Node)
|
||||||
}
|
}
|
||||||
// Add the Ethereum Stats daemon if requested.
|
// Add the Ethereum Stats daemon if requested.
|
||||||
if cfg.Ethstats.URL != "" {
|
if cfg.Ethstats.URL != "" {
|
||||||
utils.RegisterEthStatsService(stack, backend, cfg.Ethstats.URL)
|
utils.RegisterEthStatsService(stack, backend, cfg.Ethstats.URL)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure synchronization override service
|
// Configure synchronization override service
|
||||||
var synctarget common.Hash
|
syncConfig := syncer.Config{
|
||||||
|
ExitWhenSynced: ctx.Bool(utils.ExitWhenSyncedFlag.Name),
|
||||||
|
}
|
||||||
if ctx.IsSet(utils.SyncTargetFlag.Name) {
|
if ctx.IsSet(utils.SyncTargetFlag.Name) {
|
||||||
target := ctx.String(utils.SyncTargetFlag.Name)
|
target := ctx.String(utils.SyncTargetFlag.Name)
|
||||||
if !common.IsHexHash(target) {
|
if !common.IsHexHash(target) {
|
||||||
utils.Fatalf("sync target hash is not a valid hex hash: %s", 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.
|
// Start dev mode.
|
||||||
simBeacon, err := catalyst.NewSimulatedBeacon(ctx.Uint64(utils.DeveloperPeriodFlag.Name), cfg.Eth.Miner.PendingFeeRecipient, eth)
|
simBeacon, err := catalyst.NewSimulatedBeacon(ctx.Uint64(utils.DeveloperPeriodFlag.Name), cfg.Eth.Miner.PendingFeeRecipient, eth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -806,6 +806,24 @@ func (iter *snapshotIterator) Release() {
|
||||||
iter.storage.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.
|
// chainExporters defines the export scheme for all exportable chain data.
|
||||||
var chainExporters = map[string]func(db ethdb.Database) utils.ChainDataIterator{
|
var chainExporters = map[string]func(db ethdb.Database) utils.ChainDataIterator{
|
||||||
"preimage": 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)
|
storage := db.NewIterator(rawdb.SnapshotStoragePrefix, nil)
|
||||||
return &snapshotIterator{account: account, storage: storage}
|
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 {
|
func exportChaindata(ctx *cli.Context) error {
|
||||||
|
|
|
||||||
|
|
@ -22,13 +22,10 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"slices"
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
"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/console/prompt"
|
||||||
"github.com/ethereum/go-ethereum/eth/downloader"
|
|
||||||
"github.com/ethereum/go-ethereum/ethclient"
|
"github.com/ethereum/go-ethereum/ethclient"
|
||||||
"github.com/ethereum/go-ethereum/internal/debug"
|
"github.com/ethereum/go-ethereum/internal/debug"
|
||||||
"github.com/ethereum/go-ethereum/internal/flags"
|
"github.com/ethereum/go-ethereum/internal/flags"
|
||||||
|
|
@ -64,7 +61,7 @@ var (
|
||||||
utils.OverrideOsaka,
|
utils.OverrideOsaka,
|
||||||
utils.OverrideBPO1,
|
utils.OverrideBPO1,
|
||||||
utils.OverrideBPO2,
|
utils.OverrideBPO2,
|
||||||
utils.OverrideVerkle,
|
utils.OverrideUBT,
|
||||||
utils.OverrideGenesisFlag,
|
utils.OverrideGenesisFlag,
|
||||||
utils.EnablePersonal, // deprecated
|
utils.EnablePersonal, // deprecated
|
||||||
utils.TxPoolLocalsFlag,
|
utils.TxPoolLocalsFlag,
|
||||||
|
|
@ -95,6 +92,7 @@ var (
|
||||||
utils.StateHistoryFlag,
|
utils.StateHistoryFlag,
|
||||||
utils.TrienodeHistoryFlag,
|
utils.TrienodeHistoryFlag,
|
||||||
utils.TrienodeHistoryFullValueCheckpointFlag,
|
utils.TrienodeHistoryFullValueCheckpointFlag,
|
||||||
|
utils.BinTrieGroupDepthFlag,
|
||||||
utils.LightKDFFlag,
|
utils.LightKDFFlag,
|
||||||
utils.EthRequiredBlocksFlag,
|
utils.EthRequiredBlocksFlag,
|
||||||
utils.LegacyWhitelistFlag, // deprecated
|
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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"slices"
|
"slices"
|
||||||
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"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/rawdb"
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"github.com/ethereum/go-ethereum/core/state/pruner"
|
"github.com/ethereum/go-ethereum/core/state/pruner"
|
||||||
|
|
@ -168,6 +171,22 @@ block is used.
|
||||||
Description: `
|
Description: `
|
||||||
The export-preimages command exports hash preimages to a flat file, in exactly
|
The export-preimages command exports hash preimages to a flat file, in exactly
|
||||||
the expected order for the overlay tree migration.
|
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)))
|
log.Info("Checked the snapshot journalled storage", "time", common.PrettyDuration(time.Since(start)))
|
||||||
return nil
|
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 v1.40.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.40.0 // indirect
|
go.opentelemetry.io/otel/metric v1.40.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.40.0 // indirect
|
go.opentelemetry.io/otel/trace v1.40.0 // indirect
|
||||||
golang.org/x/crypto v0.44.0 // indirect
|
golang.org/x/crypto v0.47.0 // indirect
|
||||||
golang.org/x/sync v0.18.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/sys v0.40.0 // indirect
|
golang.org/x/sys v0.40.0 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.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/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 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
|
||||||
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
|
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.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
||||||
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
|
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 h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
|
||||||
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
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.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
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.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.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.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.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
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.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
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 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
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=
|
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",
|
Usage: "Manually specify the bpo2 fork timestamp, overriding the bundled setting",
|
||||||
Category: flags.EthCategory,
|
Category: flags.EthCategory,
|
||||||
}
|
}
|
||||||
OverrideVerkle = &cli.Uint64Flag{
|
OverrideUBT = &cli.Uint64Flag{
|
||||||
Name: "override.verkle",
|
Name: "override.ubt",
|
||||||
Usage: "Manually specify the Verkle fork timestamp, overriding the bundled setting",
|
Usage: "Manually specify the UBT fork timestamp, overriding the bundled setting",
|
||||||
Category: flags.EthCategory,
|
Category: flags.EthCategory,
|
||||||
}
|
}
|
||||||
OverrideGenesisFlag = &cli.StringFlag{
|
OverrideGenesisFlag = &cli.StringFlag{
|
||||||
|
|
@ -297,6 +297,12 @@ var (
|
||||||
Value: ethconfig.Defaults.EnableStateSizeTracking,
|
Value: ethconfig.Defaults.EnableStateSizeTracking,
|
||||||
Category: flags.StateCategory,
|
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{
|
StateHistoryFlag = &cli.Uint64Flag{
|
||||||
Name: "history.state",
|
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)",
|
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{
|
RPCTelemetryEndpointFlag = &cli.StringFlag{
|
||||||
Name: "rpc.telemetry.endpoint",
|
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,
|
Category: flags.APICategory,
|
||||||
}
|
}
|
||||||
|
|
||||||
RPCTelemetryUserFlag = &cli.StringFlag{
|
RPCTelemetryUserFlag = &cli.StringFlag{
|
||||||
Name: "rpc.telemetry.username",
|
Name: "rpc.telemetry.username",
|
||||||
Usage: "HTTP Basic Auth username for OpenTelemetry",
|
Usage: "Basic Auth username for OpenTelemetry",
|
||||||
Category: flags.APICategory,
|
Category: flags.APICategory,
|
||||||
}
|
}
|
||||||
|
|
||||||
RPCTelemetryPasswordFlag = &cli.StringFlag{
|
RPCTelemetryPasswordFlag = &cli.StringFlag{
|
||||||
Name: "rpc.telemetry.password",
|
Name: "rpc.telemetry.password",
|
||||||
Usage: "HTTP Basic Auth password for OpenTelemetry",
|
Usage: "Basic Auth password for OpenTelemetry",
|
||||||
Category: flags.APICategory,
|
Category: flags.APICategory,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1098,7 +1104,7 @@ Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server.
|
||||||
RPCTelemetrySampleRatioFlag = &cli.Float64Flag{
|
RPCTelemetrySampleRatioFlag = &cli.Float64Flag{
|
||||||
Name: "rpc.telemetry.sample-ratio",
|
Name: "rpc.telemetry.sample-ratio",
|
||||||
Usage: "Defines the sampling ratio for RPC telemetry (0.0 to 1.0)",
|
Usage: "Defines the sampling ratio for RPC telemetry (0.0 to 1.0)",
|
||||||
Value: 1.0,
|
Value: node.DefaultConfig.OpenTelemetry.SampleRatio,
|
||||||
Category: flags.APICategory,
|
Category: flags.APICategory,
|
||||||
}
|
}
|
||||||
// Era flags are a group of flags related to the era archive format.
|
// 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) {
|
if ctx.IsSet(TrienodeHistoryFullValueCheckpointFlag.Name) {
|
||||||
cfg.NodeFullValueCheckpoint = uint32(ctx.Uint(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) {
|
if ctx.IsSet(StateSchemeFlag.Name) {
|
||||||
cfg.StateScheme = ctx.String(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)
|
cfg.StatelessSelfValidation = ctx.Bool(VMStatelessSelfValidationFlag.Name)
|
||||||
}
|
}
|
||||||
// Auto-enable StatelessSelfValidation when witness stats are enabled
|
// Auto-enable StatelessSelfValidation when witness stats are enabled
|
||||||
if ctx.Bool(VMWitnessStatsFlag.Name) {
|
if cfg.EnableWitnessStats {
|
||||||
cfg.StatelessSelfValidation = true
|
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.
|
// RegisterSyncOverrideService adds the synchronization override service into node.
|
||||||
func RegisterSyncOverrideService(stack *node.Node, eth *eth.Ethereum, target common.Hash, exitWhenSynced bool) {
|
func RegisterSyncOverrideService(stack *node.Node, eth *eth.Ethereum, config syncer.Config) {
|
||||||
if target != (common.Hash{}) {
|
if config.TargetBlock != (common.Hash{}) {
|
||||||
log.Info("Registered sync override service", "hash", target, "exitWhenSynced", exitWhenSynced)
|
log.Info("Registered sync override service", "hash", config.TargetBlock, "exitWhenSynced", config.ExitWhenSynced)
|
||||||
} else {
|
} else {
|
||||||
log.Info("Registered sync override service")
|
log.Info("Registered sync override service")
|
||||||
}
|
}
|
||||||
syncer.Register(stack, eth, target, exitWhenSynced)
|
syncer.Register(stack, eth, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetupMetrics configures the metrics system.
|
// 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),
|
StateHistory: ctx.Uint64(StateHistoryFlag.Name),
|
||||||
TrienodeHistory: ctx.Int64(TrienodeHistoryFlag.Name),
|
TrienodeHistory: ctx.Int64(TrienodeHistoryFlag.Name),
|
||||||
NodeFullValueCheckpoint: uint32(ctx.Uint(TrienodeHistoryFullValueCheckpointFlag.Name)),
|
NodeFullValueCheckpoint: uint32(ctx.Uint(TrienodeHistoryFullValueCheckpointFlag.Name)),
|
||||||
|
BinTrieGroupDepth: ctx.Int(BinTrieGroupDepthFlag.Name),
|
||||||
|
|
||||||
// Disable transaction indexing/unindexing.
|
// Disable transaction indexing/unindexing.
|
||||||
TxLookupLimit: -1,
|
TxLookupLimit: -1,
|
||||||
|
|
@ -2516,10 +2526,10 @@ func MakeConsolePreloads(ctx *cli.Context) []string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeTrieDatabase constructs a trie database based on the configured scheme.
|
// 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{
|
config := &triedb.Config{
|
||||||
Preimages: preimage,
|
Preimages: preimage,
|
||||||
IsVerkle: isVerkle,
|
IsUBT: isUBT,
|
||||||
}
|
}
|
||||||
scheme, err := rawdb.ParseStateScheme(ctx.String(StateSchemeFlag.Name), disk)
|
scheme, err := rawdb.ParseStateScheme(ctx.String(StateSchemeFlag.Name), disk)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -204,6 +204,10 @@ func (b *Big) ToInt() *big.Int {
|
||||||
return (*big.Int)(b)
|
return (*big.Int)(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *Big) ToUint256() (*uint256.Int, bool) {
|
||||||
|
return uint256.FromBig((*big.Int)(b))
|
||||||
|
}
|
||||||
|
|
||||||
// String returns the hex encoding of b.
|
// String returns the hex encoding of b.
|
||||||
func (b *Big) String() string {
|
func (b *Big) String() string {
|
||||||
return EncodeBig(b.ToInt())
|
return EncodeBig(b.ToInt())
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@
|
||||||
package beacon
|
package beacon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
@ -26,13 +25,10 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
"github.com/ethereum/go-ethereum/consensus"
|
||||||
"github.com/ethereum/go-ethereum/consensus/misc/eip1559"
|
"github.com/ethereum/go-ethereum/consensus/misc/eip1559"
|
||||||
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
|
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
|
||||||
"github.com/ethereum/go-ethereum/core/tracing"
|
"github.com/ethereum/go-ethereum/core/tracing"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
"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/params"
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
|
||||||
"github.com/holiman/uint256"
|
"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.
|
// 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
|
// Seal generates a new sealing request for the given input block and pushes
|
||||||
// the result into the given channel.
|
// the result into the given channel.
|
||||||
//
|
//
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ package clique
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
@ -34,7 +33,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
"github.com/ethereum/go-ethereum/consensus"
|
||||||
"github.com/ethereum/go-ethereum/consensus/misc"
|
"github.com/ethereum/go-ethereum/consensus/misc"
|
||||||
"github.com/ethereum/go-ethereum/consensus/misc/eip1559"
|
"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/types"
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
|
@ -43,7 +41,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
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
|
// 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
|
// Authorize injects a private key into the consensus engine to mint new blocks
|
||||||
// with.
|
// with.
|
||||||
func (c *Clique) Authorize(signer common.Address) {
|
func (c *Clique) Authorize(signer common.Address) {
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,9 @@
|
||||||
package consensus
|
package consensus
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"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/types"
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
|
@ -88,13 +86,6 @@ type Engine interface {
|
||||||
// that happen at finalization (e.g. block rewards).
|
// that happen at finalization (e.g. block rewards).
|
||||||
Finalize(chain ChainHeaderReader, header *types.Header, state vm.StateDB, body *types.Body)
|
Finalize(chain ChainHeaderReader, header *types.Header, state vm.StateDB, body *types.Body)
|
||||||
|
|
||||||
// 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
|
// Seal generates a new sealing request for the given input block and pushes
|
||||||
// the result into the given channel.
|
// the result into the given channel.
|
||||||
//
|
//
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@
|
||||||
package ethash
|
package ethash
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
@ -28,14 +27,12 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
"github.com/ethereum/go-ethereum/consensus"
|
||||||
"github.com/ethereum/go-ethereum/consensus/misc"
|
"github.com/ethereum/go-ethereum/consensus/misc"
|
||||||
"github.com/ethereum/go-ethereum/consensus/misc/eip1559"
|
"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/tracing"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
"github.com/ethereum/go-ethereum/crypto/keccak"
|
"github.com/ethereum/go-ethereum/crypto/keccak"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -512,22 +509,6 @@ func (ethash *Ethash) Finalize(chain consensus.ChainHeaderReader, header *types.
|
||||||
accumulateRewards(chain.Config(), state, header, body.Uncles)
|
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.
|
// SealHash returns the hash of a block prior to it being sealed.
|
||||||
func (ethash *Ethash) SealHash(header *types.Header) (hash common.Hash) {
|
func (ethash *Ethash) SealHash(header *types.Header) (hash common.Hash) {
|
||||||
hasher := keccak.NewLegacyKeccak256()
|
hasher := keccak.NewLegacyKeccak256()
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) {
|
||||||
data := make([]byte, nbytes)
|
data := make([]byte, nbytes)
|
||||||
return func(i int, gen *BlockGen) {
|
return func(i int, gen *BlockGen) {
|
||||||
toaddr := common.Address{}
|
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()
|
signer := gen.Signer()
|
||||||
gasPrice := big.NewInt(0)
|
gasPrice := big.NewInt(0)
|
||||||
if gen.header.BaseFee != nil {
|
if gen.header.BaseFee != nil {
|
||||||
|
|
@ -99,7 +99,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) {
|
||||||
Nonce: gen.TxNonce(benchRootAddr),
|
Nonce: gen.TxNonce(benchRootAddr),
|
||||||
To: &toaddr,
|
To: &toaddr,
|
||||||
Value: big.NewInt(1),
|
Value: big.NewInt(1),
|
||||||
Gas: gas,
|
Gas: cost.RegularGas,
|
||||||
Data: data,
|
Data: data,
|
||||||
GasPrice: gasPrice,
|
GasPrice: gasPrice,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
testVerkleChainConfig = ¶ms.ChainConfig{
|
testUBTChainConfig = ¶ms.ChainConfig{
|
||||||
ChainID: big.NewInt(1),
|
ChainID: big.NewInt(1),
|
||||||
HomesteadBlock: big.NewInt(0),
|
HomesteadBlock: big.NewInt(0),
|
||||||
EIP150Block: big.NewInt(0),
|
EIP150Block: big.NewInt(0),
|
||||||
|
|
@ -51,30 +51,30 @@ var (
|
||||||
LondonBlock: big.NewInt(0),
|
LondonBlock: big.NewInt(0),
|
||||||
Ethash: new(params.EthashConfig),
|
Ethash: new(params.EthashConfig),
|
||||||
ShanghaiTime: u64(0),
|
ShanghaiTime: u64(0),
|
||||||
VerkleTime: u64(0),
|
UBTTime: u64(0),
|
||||||
TerminalTotalDifficulty: common.Big0,
|
TerminalTotalDifficulty: common.Big0,
|
||||||
EnableVerkleAtGenesis: true,
|
EnableUBTAtGenesis: true,
|
||||||
BlobScheduleConfig: ¶ms.BlobScheduleConfig{
|
BlobScheduleConfig: ¶ms.BlobScheduleConfig{
|
||||||
Verkle: params.DefaultPragueBlobConfig,
|
UBT: params.DefaultPragueBlobConfig,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestProcessVerkle(t *testing.T) {
|
func TestProcessUBT(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
code = common.FromHex(`6060604052600a8060106000396000f360606040526008565b00`)
|
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
|
// A contract creation that calls EXTCODECOPY in the constructor. Used to ensure that the witness
|
||||||
// will not contain that copied data.
|
// will not contain that copied data.
|
||||||
// Source: https://gist.github.com/gballet/a23db1e1cb4ed105616b5920feb75985
|
// Source: https://gist.github.com/gballet/a23db1e1cb4ed105616b5920feb75985
|
||||||
codeWithExtCodeCopy = common.FromHex(`0x60806040526040516100109061017b565b604051809103906000f08015801561002c573d6000803e3d6000fd5b506000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561007857600080fd5b5060008067ffffffffffffffff8111156100955761009461024a565b5b6040519080825280601f01601f1916602001820160405280156100c75781602001600182028036833780820191505090505b50905060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690506020600083833c81610101906101e3565b60405161010d90610187565b61011791906101a3565b604051809103906000f080158015610133573d6000803e3d6000fd5b50600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550505061029b565b60d58061046783390190565b6102068061053c83390190565b61019d816101d9565b82525050565b60006020820190506101b86000830184610194565b92915050565b6000819050602082019050919050565b600081519050919050565b6000819050919050565b60006101ee826101ce565b826101f8846101be565b905061020381610279565b925060208210156102435761023e7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8360200360080261028e565b831692505b5050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600061028582516101d9565b80915050919050565b600082821b905092915050565b6101bd806102aa6000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f566852414610030575b600080fd5b61003861004e565b6040516100459190610146565b60405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166381ca91d36040518163ffffffff1660e01b815260040160206040518083038186803b1580156100b857600080fd5b505afa1580156100cc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100f0919061010a565b905090565b60008151905061010481610170565b92915050565b6000602082840312156101205761011f61016b565b5b600061012e848285016100f5565b91505092915050565b61014081610161565b82525050565b600060208201905061015b6000830184610137565b92915050565b6000819050919050565b600080fd5b61017981610161565b811461018457600080fd5b5056fea2646970667358221220a6a0e11af79f176f9c421b7b12f441356b25f6489b83d38cc828a701720b41f164736f6c63430008070033608060405234801561001057600080fd5b5060b68061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063ab5ed15014602d575b600080fd5b60336047565b604051603e9190605d565b60405180910390f35b60006001905090565b6057816076565b82525050565b6000602082019050607060008301846050565b92915050565b600081905091905056fea26469706673582212203a14eb0d5cd07c277d3e24912f110ddda3e553245a99afc4eeefb2fbae5327aa64736f6c63430008070033608060405234801561001057600080fd5b5060405161020638038061020683398181016040528101906100329190610063565b60018160001c6100429190610090565b60008190555050610145565b60008151905061005d8161012e565b92915050565b60006020828403121561007957610078610129565b5b60006100878482850161004e565b91505092915050565b600061009b826100f0565b91506100a6836100f0565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156100db576100da6100fa565b5b828201905092915050565b6000819050919050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600080fd5b610137816100e6565b811461014257600080fd5b50565b60b3806101536000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806381ca91d314602d575b600080fd5b60336047565b604051603e9190605a565b60405180910390f35b60005481565b6054816073565b82525050565b6000602082019050606d6000830184604d565b92915050565b600081905091905056fea26469706673582212209bff7098a2f526de1ad499866f27d6d0d6f17b74a413036d6063ca6a0998ca4264736f6c63430008070033`)
|
codeWithExtCodeCopy = common.FromHex(`0x60806040526040516100109061017b565b604051809103906000f08015801561002c573d6000803e3d6000fd5b506000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561007857600080fd5b5060008067ffffffffffffffff8111156100955761009461024a565b5b6040519080825280601f01601f1916602001820160405280156100c75781602001600182028036833780820191505090505b50905060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690506020600083833c81610101906101e3565b60405161010d90610187565b61011791906101a3565b604051809103906000f080158015610133573d6000803e3d6000fd5b50600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550505061029b565b60d58061046783390190565b6102068061053c83390190565b61019d816101d9565b82525050565b60006020820190506101b86000830184610194565b92915050565b6000819050602082019050919050565b600081519050919050565b6000819050919050565b60006101ee826101ce565b826101f8846101be565b905061020381610279565b925060208210156102435761023e7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8360200360080261028e565b831692505b5050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600061028582516101d9565b80915050919050565b600082821b905092915050565b6101bd806102aa6000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f566852414610030575b600080fd5b61003861004e565b6040516100459190610146565b60405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166381ca91d36040518163ffffffff1660e01b815260040160206040518083038186803b1580156100b857600080fd5b505afa1580156100cc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100f0919061010a565b905090565b60008151905061010481610170565b92915050565b6000602082840312156101205761011f61016b565b5b600061012e848285016100f5565b91505092915050565b61014081610161565b82525050565b600060208201905061015b6000830184610137565b92915050565b6000819050919050565b600080fd5b61017981610161565b811461018457600080fd5b5056fea2646970667358221220a6a0e11af79f176f9c421b7b12f441356b25f6489b83d38cc828a701720b41f164736f6c63430008070033608060405234801561001057600080fd5b5060b68061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063ab5ed15014602d575b600080fd5b60336047565b604051603e9190605d565b60405180910390f35b60006001905090565b6057816076565b82525050565b6000602082019050607060008301846050565b92915050565b600081905091905056fea26469706673582212203a14eb0d5cd07c277d3e24912f110ddda3e553245a99afc4eeefb2fbae5327aa64736f6c63430008070033608060405234801561001057600080fd5b5060405161020638038061020683398181016040528101906100329190610063565b60018160001c6100429190610090565b60008190555050610145565b60008151905061005d8161012e565b92915050565b60006020828403121561007957610078610129565b5b60006100878482850161004e565b91505092915050565b600061009b826100f0565b91506100a6836100f0565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156100db576100da6100fa565b5b828201905092915050565b6000819050919050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600080fd5b610137816100e6565b811461014257600080fd5b50565b60b3806101536000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806381ca91d314602d575b600080fd5b60336047565b604051603e9190605a565b60405180910390f35b60005481565b6054816073565b82525050565b6000602082019050606d6000830184604d565b92915050565b600081905091905056fea26469706673582212209bff7098a2f526de1ad499866f27d6d0d6f17b74a413036d6063ca6a0998ca4264736f6c63430008070033`)
|
||||||
intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, nil, true, true, true, true)
|
intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, nil, true, true, true, true, false)
|
||||||
signer = types.LatestSigner(testVerkleChainConfig)
|
signer = types.LatestSigner(testUBTChainConfig)
|
||||||
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||||
bcdb = rawdb.NewMemoryDatabase() // Database for the blockchain
|
bcdb = rawdb.NewMemoryDatabase() // Database for the blockchain
|
||||||
coinbase = common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7")
|
coinbase = common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7")
|
||||||
gspec = &Genesis{
|
gspec = &Genesis{
|
||||||
Config: testVerkleChainConfig,
|
Config: testUBTChainConfig,
|
||||||
Alloc: GenesisAlloc{
|
Alloc: GenesisAlloc{
|
||||||
coinbase: {
|
coinbase: {
|
||||||
Balance: big.NewInt(1000000000000000000), // 1 ether
|
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.
|
// data is saved into the tree+database.
|
||||||
// genesis := gspec.MustCommit(bcdb, triedb)
|
// genesis := gspec.MustCommit(bcdb, triedb)
|
||||||
options := DefaultConfig().WithStateScheme(rawdb.PathScheme)
|
options := DefaultConfig().WithStateScheme(rawdb.PathScheme)
|
||||||
options.SnapshotLimit = 0
|
options.SnapshotLimit = 0
|
||||||
|
options.BinTrieGroupDepth = triedb.DefaultBinTrieGroupDepth
|
||||||
blockchain, _ := NewBlockChain(bcdb, gspec, beacon.New(ethash.NewFaker()), options)
|
blockchain, _ := NewBlockChain(bcdb, gspec, beacon.New(ethash.NewFaker()), options)
|
||||||
defer blockchain.Stop()
|
defer blockchain.Stop()
|
||||||
|
|
||||||
txCost1 := params.TxGas
|
txCost1 := params.TxGas
|
||||||
txCost2 := params.TxGas
|
txCost2 := params.TxGas
|
||||||
contractCreationCost := intrinsicContractCreationGas +
|
contractCreationCost := intrinsicContractCreationGas.RegularGas +
|
||||||
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + params.WitnessBranchReadCost + params.WitnessBranchWriteCost + /* creation */
|
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + params.WitnessBranchReadCost + params.WitnessBranchWriteCost + /* creation */
|
||||||
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* creation with value */
|
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* creation with value */
|
||||||
739 /* execution costs */
|
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 (tx) */
|
||||||
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + params.WitnessBranchReadCost + params.WitnessBranchWriteCost + /* creation (CREATE at pc=0x20) */
|
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + params.WitnessBranchReadCost + params.WitnessBranchWriteCost + /* creation (CREATE at pc=0x20) */
|
||||||
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* write code hash */
|
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* write code hash */
|
||||||
|
|
@ -188,7 +189,7 @@ func TestProcessParentBlockHash(t *testing.T) {
|
||||||
// block 1 parent hash is 0x0100....
|
// block 1 parent hash is 0x0100....
|
||||||
// block 2 parent hash is 0x0200....
|
// block 2 parent hash is 0x0200....
|
||||||
// etc
|
// etc
|
||||||
checkBlockHashes := func(statedb *state.StateDB, isVerkle bool) {
|
checkBlockHashes := func(statedb *state.StateDB, isUBT bool) {
|
||||||
statedb.SetNonce(params.HistoryStorageAddress, 1, tracing.NonceChangeUnspecified)
|
statedb.SetNonce(params.HistoryStorageAddress, 1, tracing.NonceChangeUnspecified)
|
||||||
statedb.SetCode(params.HistoryStorageAddress, params.HistoryStorageCode, tracing.CodeChangeUnspecified)
|
statedb.SetCode(params.HistoryStorageAddress, params.HistoryStorageCode, tracing.CodeChangeUnspecified)
|
||||||
// Process n blocks, from 1 .. num
|
// Process n blocks, from 1 .. num
|
||||||
|
|
@ -196,8 +197,8 @@ func TestProcessParentBlockHash(t *testing.T) {
|
||||||
for i := 1; i <= num; i++ {
|
for i := 1; i <= num; i++ {
|
||||||
header := &types.Header{ParentHash: common.Hash{byte(i)}, Number: big.NewInt(int64(i)), Difficulty: new(big.Int)}
|
header := &types.Header{ParentHash: common.Hash{byte(i)}, Number: big.NewInt(int64(i)), Difficulty: new(big.Int)}
|
||||||
chainConfig := params.MergedTestChainConfig
|
chainConfig := params.MergedTestChainConfig
|
||||||
if isVerkle {
|
if isUBT {
|
||||||
chainConfig = testVerkleChainConfig
|
chainConfig = testUBTChainConfig
|
||||||
}
|
}
|
||||||
vmContext := NewEVMBlockContext(header, nil, new(common.Address))
|
vmContext := NewEVMBlockContext(header, nil, new(common.Address))
|
||||||
evm := vm.NewEVM(vmContext, statedb, chainConfig, vm.Config{})
|
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
|
// Read block hashes for block 0 .. num-1
|
||||||
for i := 0; i < num; i++ {
|
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 {
|
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())
|
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
|
||||||
checkBlockHashes(statedb, false)
|
checkBlockHashes(statedb, false)
|
||||||
})
|
})
|
||||||
t.Run("Verkle", func(t *testing.T) {
|
t.Run("UBT", func(t *testing.T) {
|
||||||
db := rawdb.NewMemoryDatabase()
|
db := rawdb.NewMemoryDatabase()
|
||||||
cacheConfig := DefaultConfig().WithStateScheme(rawdb.PathScheme)
|
cacheConfig := DefaultConfig().WithStateScheme(rawdb.PathScheme)
|
||||||
|
cacheConfig.BinTrieGroupDepth = triedb.DefaultBinTrieGroupDepth
|
||||||
cacheConfig.SnapshotLimit = 0
|
cacheConfig.SnapshotLimit = 0
|
||||||
triedb := triedb.NewDatabase(db, cacheConfig.triedbConfig(true))
|
triedb := triedb.NewDatabase(db, cacheConfig.triedbConfig(true))
|
||||||
statedb, _ := state.New(types.EmptyVerkleHash, state.NewDatabase(triedb, nil))
|
statedb, _ := state.New(types.EmptyBinaryHash, state.NewDatabase(triedb, nil))
|
||||||
checkBlockHashes(statedb, true)
|
checkBlockHashes(statedb, true)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// getContractStoredBlockHash is a utility method which reads the stored parent blockhash for block 'number'
|
// 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
|
ringIndex := number % params.HistoryServeWindow
|
||||||
var key common.Hash
|
var key common.Hash
|
||||||
binary.BigEndian.PutUint64(key[24:], ringIndex)
|
binary.BigEndian.PutUint64(key[24:], ringIndex)
|
||||||
if isVerkle {
|
if isUBT {
|
||||||
return statedb.GetState(params.HistoryStorageAddress, key)
|
return statedb.GetState(params.HistoryStorageAddress, key)
|
||||||
}
|
}
|
||||||
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
|
TrieNoAsyncFlush bool // Whether the asynchronous buffer flushing is disallowed
|
||||||
TrieJournalDirectory string // Directory path to the journal used for persisting trie data across node restarts
|
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
|
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
|
StateScheme string // Scheme used to store ethereum states and merkle tree nodes on top
|
||||||
ArchiveMode bool // Whether to enable the archive mode
|
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.
|
// 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;
|
// 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.
|
// 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{
|
config := &triedb.Config{
|
||||||
Preimages: cfg.Preimages,
|
Preimages: cfg.Preimages,
|
||||||
IsVerkle: isVerkle,
|
IsUBT: isUBT,
|
||||||
|
BinTrieGroupDepth: cfg.BinTrieGroupDepth,
|
||||||
}
|
}
|
||||||
if cfg.StateScheme == rawdb.HashScheme {
|
if cfg.StateScheme == rawdb.HashScheme {
|
||||||
config.HashDB = &hashdb.Config{
|
config.HashDB = &hashdb.Config{
|
||||||
|
|
@ -378,7 +380,7 @@ func NewBlockChain(db ethdb.Database, genesis *Genesis, engine consensus.Engine,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open trie database with provided config
|
// Open trie database with provided config
|
||||||
enableVerkle, err := EnableVerkleAtGenesis(db, genesis)
|
enableVerkle, err := EnableUBTAtGenesis(db, genesis)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -1188,6 +1190,7 @@ func (bc *BlockChain) SnapSyncComplete(hash common.Hash) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// If all checks out, manually set the head block.
|
// If all checks out, manually set the head block.
|
||||||
|
rawdb.WriteHeadBlockHash(bc.db, hash)
|
||||||
bc.currentBlock.Store(block.Header())
|
bc.currentBlock.Store(block.Header())
|
||||||
headBlockGauge.Update(int64(block.NumberU64()))
|
headBlockGauge.Update(int64(block.NumberU64()))
|
||||||
|
|
||||||
|
|
@ -2120,11 +2123,29 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
|
||||||
startTime = time.Now()
|
startTime = time.Now()
|
||||||
statedb *state.StateDB
|
statedb *state.StateDB
|
||||||
interrupt atomic.Bool
|
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
|
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)
|
statedb, err = state.New(parentRoot, sdb)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// Note: the main processor and prefetcher share the same reader with a local
|
||||||
// cache for mitigating the overhead of state access.
|
// cache for mitigating the overhead of state access.
|
||||||
prefetch, process, err := sdb.ReadersWithCacheStats(parentRoot)
|
prefetch, process, err := warmer.ReadersWithCacheStats(parentRoot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -2580,8 +2601,13 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Header) error
|
||||||
blockReorgAddMeter.Mark(int64(len(newChain)))
|
blockReorgAddMeter.Mark(int64(len(newChain)))
|
||||||
} else {
|
} else {
|
||||||
// len(newChain) == 0 && len(oldChain) > 0
|
// len(newChain) == 0 && len(oldChain) > 0
|
||||||
// rewind the canonical chain to a lower point.
|
// Rewind the canonical chain to a lower point. In EPBs we can reorg to
|
||||||
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))
|
// 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
|
// Acquire the tx-lookup lock before mutation. This step is essential
|
||||||
// as the txlookups should be changed atomically, and all subsequent
|
// 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.
|
// State returns a new mutable state based on the current HEAD block.
|
||||||
func (bc *BlockChain) State() (*state.StateDB, error) {
|
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.
|
// StateAt returns a new mutable state based on a particular point in time.
|
||||||
func (bc *BlockChain) StateAt(root common.Hash) (*state.StateDB, error) {
|
func (bc *BlockChain) StateAt(header *types.Header) (*state.StateDB, error) {
|
||||||
return state.New(root, state.NewDatabase(bc.triedb, bc.codedb).WithSnapshot(bc.snaps))
|
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`
|
// Live states are not available and won't be served, please use `State`
|
||||||
// or `StateAt` instead.
|
// or `StateAt` instead.
|
||||||
func (bc *BlockChain) HistoricState(root common.Hash) (*state.StateDB, error) {
|
func (bc *BlockChain) HistoricState(header *types.Header) (*state.StateDB, error) {
|
||||||
return state.New(root, state.NewHistoricDatabase(bc.triedb, bc.codedb))
|
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.
|
// 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)
|
t.Fatalf("failed to insert into chain: %v", err)
|
||||||
}
|
}
|
||||||
// Check the storage
|
// Check the storage
|
||||||
state, err := chain.StateAt(chain.CurrentHeader().Root)
|
state, err := chain.StateAt(chain.CurrentHeader())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to load state %v", err)
|
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)
|
blockContext = NewEVMBlockContext(b.header, bc, &b.header.Coinbase)
|
||||||
evm = vm.NewEVM(blockContext, b.statedb, b.cm.config, vmConfig)
|
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)
|
receipt, err := ApplyTransaction(evm, b.gasPool, b.statedb, b.header, tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
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
|
// Merge the tx-local access event into the "block-local" one, in order to collect
|
||||||
// all values, so that the witness can be built.
|
// 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.statedb.AccessEvents().Merge(evm.AccessEvents)
|
||||||
}
|
}
|
||||||
b.txs = append(b.txs, tx)
|
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.
|
// off the statedb before executing the system calls.
|
||||||
statedb = statedb.Copy()
|
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, err := PostExecution(context.Background(), b.cm.config, b.header.Number, b.header.Time, blockLogs, evm, uint32(len(b.txs)+1))
|
||||||
requests = [][]byte{}
|
if err != nil {
|
||||||
// EIP-6110 deposits
|
panic(fmt.Sprintf("failed to run post-execution: %v", err))
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return requests
|
return requests
|
||||||
}
|
}
|
||||||
|
|
@ -392,7 +381,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
|
||||||
misc.ApplyDAOHardFork(statedb)
|
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
|
// EIP-2935
|
||||||
blockContext := NewEVMBlockContext(b.header, cm, &b.header.Coinbase)
|
blockContext := NewEVMBlockContext(b.header, cm, &b.header.Coinbase)
|
||||||
blockContext.Random = &common.Hash{} // enable post-merge instruction set
|
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
|
b.header.RequestsHash = &reqHash
|
||||||
}
|
}
|
||||||
|
|
||||||
body := types.Body{Transactions: b.txs, Uncles: b.uncles, Withdrawals: b.withdrawals}
|
body := types.Body{
|
||||||
block, err := b.engine.FinalizeAndAssemble(context.Background(), cm, b.header, statedb, &body, b.receipts)
|
Transactions: b.txs,
|
||||||
if err != nil {
|
Uncles: b.uncles,
|
||||||
panic(err)
|
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
|
// 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))
|
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.
|
// Forcibly use hash-based state scheme for retaining all nodes in disk.
|
||||||
var triedbConfig *triedb.Config = triedb.HashDefaults
|
var triedbConfig *triedb.Config = triedb.HashDefaults
|
||||||
if config.IsVerkle(config.ChainID, 0) {
|
if config.IsUBT(config.ChainID, 0) {
|
||||||
triedbConfig = triedb.VerkleDefaults
|
triedbConfig = triedb.UBTDefaults
|
||||||
}
|
}
|
||||||
triedb := triedb.NewDatabase(db, triedbConfig)
|
triedb := triedb.NewDatabase(db, triedbConfig)
|
||||||
defer triedb.Close()
|
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) {
|
func GenerateChainWithGenesis(genesis *Genesis, engine consensus.Engine, n int, gen func(int, *BlockGen)) (ethdb.Database, []*types.Block, []types.Receipts) {
|
||||||
db := rawdb.NewMemoryDatabase()
|
db := rawdb.NewMemoryDatabase()
|
||||||
var triedbConfig *triedb.Config = triedb.HashDefaults
|
var triedbConfig *triedb.Config = triedb.HashDefaults
|
||||||
if genesis.Config != nil && genesis.Config.IsVerkle(genesis.Config.ChainID, 0) {
|
if genesis.Config != nil && genesis.Config.IsUBT(genesis.Config.ChainID, 0) {
|
||||||
triedbConfig = triedb.VerkleDefaults
|
triedbConfig = triedb.UBTDefaults
|
||||||
}
|
}
|
||||||
genesisTriedb := triedb.NewDatabase(db, triedbConfig)
|
genesisTriedb := triedb.NewDatabase(db, triedbConfig)
|
||||||
block, err := genesis.Commit(db, genesisTriedb, nil)
|
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 {
|
func NewEVMTxContext(msg *Message) vm.TxContext {
|
||||||
ctx := vm.TxContext{
|
ctx := vm.TxContext{
|
||||||
Origin: msg.From,
|
Origin: msg.From,
|
||||||
GasPrice: uint256.MustFromBig(msg.GasPrice),
|
GasPrice: msg.GasPrice,
|
||||||
BlobHashes: msg.BlobHashes,
|
BlobHashes: msg.BlobHashes,
|
||||||
}
|
}
|
||||||
return ctx
|
return ctx
|
||||||
|
|
|
||||||
|
|
@ -129,22 +129,23 @@ func ReadGenesis(db ethdb.Database) (*Genesis, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// hashAlloc computes the state root according to the genesis specification.
|
// 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
|
// If a genesis-time verkle trie is requested, create a trie config
|
||||||
// with the verkle trie enabled so that the tree can be initialized
|
// with the verkle trie enabled so that the tree can be initialized
|
||||||
// as such.
|
// as such.
|
||||||
var config *triedb.Config
|
var config *triedb.Config
|
||||||
if isVerkle {
|
if isUBT {
|
||||||
config = &triedb.Config{
|
config = &triedb.Config{
|
||||||
PathDB: pathdb.Defaults,
|
PathDB: pathdb.Defaults,
|
||||||
IsVerkle: true,
|
IsUBT: true,
|
||||||
|
BinTrieGroupDepth: triedb.UBTDefaults.BinTrieGroupDepth,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Create an ephemeral in-memory database for computing hash,
|
// Create an ephemeral in-memory database for computing hash,
|
||||||
// all the derived states will be discarded to not pollute disk.
|
// all the derived states will be discarded to not pollute disk.
|
||||||
emptyRoot := types.EmptyRootHash
|
emptyRoot := types.EmptyRootHash
|
||||||
if isVerkle {
|
if isUBT {
|
||||||
emptyRoot = types.EmptyVerkleHash
|
emptyRoot = types.EmptyBinaryHash
|
||||||
}
|
}
|
||||||
db := rawdb.NewMemoryDatabase()
|
db := rawdb.NewMemoryDatabase()
|
||||||
statedb, err := state.New(emptyRoot, state.NewDatabase(triedb.NewDatabase(db, config), nil))
|
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.
|
// generated states will be persisted into the given database.
|
||||||
func flushAlloc(ga *types.GenesisAlloc, triedb *triedb.Database, tracer *tracing.Hooks) (common.Hash, error) {
|
func flushAlloc(ga *types.GenesisAlloc, triedb *triedb.Database, tracer *tracing.Hooks) (common.Hash, error) {
|
||||||
emptyRoot := types.EmptyRootHash
|
emptyRoot := types.EmptyRootHash
|
||||||
if triedb.IsVerkle() {
|
if triedb.IsUBT() {
|
||||||
emptyRoot = types.EmptyVerkleHash
|
emptyRoot = types.EmptyBinaryHash
|
||||||
}
|
}
|
||||||
statedb, err := state.New(emptyRoot, state.NewDatabase(triedb, nil))
|
statedb, err := state.New(emptyRoot, state.NewDatabase(triedb, nil))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -276,10 +277,10 @@ func (e *GenesisMismatchError) Error() string {
|
||||||
|
|
||||||
// ChainOverrides contains the changes to chain config.
|
// ChainOverrides contains the changes to chain config.
|
||||||
type ChainOverrides struct {
|
type ChainOverrides struct {
|
||||||
OverrideOsaka *uint64
|
OverrideOsaka *uint64
|
||||||
OverrideBPO1 *uint64
|
OverrideBPO1 *uint64
|
||||||
OverrideBPO2 *uint64
|
OverrideBPO2 *uint64
|
||||||
OverrideVerkle *uint64
|
OverrideUBT *uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// apply applies the chain overrides on the supplied chain config.
|
// 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 {
|
if o.OverrideBPO2 != nil {
|
||||||
cfg.BPO2Time = o.OverrideBPO2
|
cfg.BPO2Time = o.OverrideBPO2
|
||||||
}
|
}
|
||||||
if o.OverrideVerkle != nil {
|
if o.OverrideUBT != nil {
|
||||||
cfg.VerkleTime = o.OverrideVerkle
|
cfg.UBTTime = o.OverrideUBT
|
||||||
}
|
}
|
||||||
return cfg.CheckConfigForkOrder()
|
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.
|
// tree at genesis time.
|
||||||
func (g *Genesis) IsVerkle() bool {
|
func (g *Genesis) IsUBT() bool {
|
||||||
return g.Config.IsVerkleGenesis()
|
return g.Config.IsUBTGenesis()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToBlock returns the genesis block according to genesis specification.
|
// ToBlock returns the genesis block according to genesis specification.
|
||||||
func (g *Genesis) ToBlock() *types.Block {
|
func (g *Genesis) ToBlock() *types.Block {
|
||||||
root, err := hashAlloc(&g.Alloc, g.IsVerkle())
|
root, err := hashAlloc(&g.Alloc, g.IsUBT())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
@ -609,24 +610,24 @@ func (g *Genesis) MustCommit(db ethdb.Database, triedb *triedb.Database) *types.
|
||||||
return block
|
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
|
// 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
|
// verkle fork is activated at genesis, and the configured activation date has
|
||||||
// already passed.
|
// already passed.
|
||||||
//
|
//
|
||||||
// In production networks (mainnet and public testnets), verkle activation always
|
// In production networks (mainnet and public testnets), verkle activation always
|
||||||
// occurs after the genesis block, making this function irrelevant in those cases.
|
// 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 != nil {
|
||||||
if genesis.Config == nil {
|
if genesis.Config == nil {
|
||||||
return false, errGenesisNoConfig
|
return false, errGenesisNoConfig
|
||||||
}
|
}
|
||||||
return genesis.Config.EnableVerkleAtGenesis, nil
|
return genesis.Config.EnableUBTAtGenesis, nil
|
||||||
}
|
}
|
||||||
if ghash := rawdb.ReadCanonicalHash(db, 0); ghash != (common.Hash{}) {
|
if ghash := rawdb.ReadCanonicalHash(db, 0); ghash != (common.Hash{}) {
|
||||||
chainCfg := rawdb.ReadChainConfig(db, ghash)
|
chainCfg := rawdb.ReadChainConfig(db, ghash)
|
||||||
if chainCfg != nil {
|
if chainCfg != nil {
|
||||||
return chainCfg.EnableVerkleAtGenesis, nil
|
return chainCfg.EnableUBTAtGenesis, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false, nil
|
return false, nil
|
||||||
|
|
|
||||||
|
|
@ -261,9 +261,9 @@ func newDbConfig(scheme string) *triedb.Config {
|
||||||
return &triedb.Config{PathDB: &config}
|
return &triedb.Config{PathDB: &config}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVerkleGenesisCommit(t *testing.T) {
|
func TestBinaryGenesisCommit(t *testing.T) {
|
||||||
var verkleTime uint64 = 0
|
var ubtTime uint64 = 0
|
||||||
verkleConfig := ¶ms.ChainConfig{
|
ubtConfig := ¶ms.ChainConfig{
|
||||||
ChainID: big.NewInt(1),
|
ChainID: big.NewInt(1),
|
||||||
HomesteadBlock: big.NewInt(0),
|
HomesteadBlock: big.NewInt(0),
|
||||||
DAOForkBlock: nil,
|
DAOForkBlock: nil,
|
||||||
|
|
@ -281,34 +281,34 @@ func TestVerkleGenesisCommit(t *testing.T) {
|
||||||
ArrowGlacierBlock: big.NewInt(0),
|
ArrowGlacierBlock: big.NewInt(0),
|
||||||
GrayGlacierBlock: big.NewInt(0),
|
GrayGlacierBlock: big.NewInt(0),
|
||||||
MergeNetsplitBlock: nil,
|
MergeNetsplitBlock: nil,
|
||||||
ShanghaiTime: &verkleTime,
|
ShanghaiTime: &ubtTime,
|
||||||
CancunTime: &verkleTime,
|
CancunTime: &ubtTime,
|
||||||
PragueTime: &verkleTime,
|
PragueTime: &ubtTime,
|
||||||
OsakaTime: &verkleTime,
|
OsakaTime: &ubtTime,
|
||||||
VerkleTime: &verkleTime,
|
UBTTime: &ubtTime,
|
||||||
TerminalTotalDifficulty: big.NewInt(0),
|
TerminalTotalDifficulty: big.NewInt(0),
|
||||||
EnableVerkleAtGenesis: true,
|
EnableUBTAtGenesis: true,
|
||||||
Ethash: nil,
|
Ethash: nil,
|
||||||
Clique: nil,
|
Clique: nil,
|
||||||
BlobScheduleConfig: ¶ms.BlobScheduleConfig{
|
BlobScheduleConfig: ¶ms.BlobScheduleConfig{
|
||||||
Cancun: params.DefaultCancunBlobConfig,
|
Cancun: params.DefaultCancunBlobConfig,
|
||||||
Prague: params.DefaultPragueBlobConfig,
|
Prague: params.DefaultPragueBlobConfig,
|
||||||
Osaka: params.DefaultOsakaBlobConfig,
|
Osaka: params.DefaultOsakaBlobConfig,
|
||||||
Verkle: params.DefaultPragueBlobConfig,
|
UBT: params.DefaultPragueBlobConfig,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
genesis := &Genesis{
|
genesis := &Genesis{
|
||||||
BaseFee: big.NewInt(params.InitialBaseFee),
|
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||||
Config: verkleConfig,
|
Config: ubtConfig,
|
||||||
Timestamp: verkleTime,
|
Timestamp: ubtTime,
|
||||||
Difficulty: big.NewInt(0),
|
Difficulty: big.NewInt(0),
|
||||||
Alloc: types.GenesisAlloc{
|
Alloc: types.GenesisAlloc{
|
||||||
{1}: {Balance: big.NewInt(1), Storage: map[common.Hash]common.Hash{{1}: {1}}},
|
{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()
|
got := genesis.ToBlock().Root().Bytes()
|
||||||
if !bytes.Equal(got, expected) {
|
if !bytes.Equal(got, expected) {
|
||||||
t.Fatalf("invalid genesis state root, expected %x, got %x", expected, got)
|
t.Fatalf("invalid genesis state root, expected %x, got %x", expected, got)
|
||||||
|
|
@ -320,17 +320,18 @@ func TestVerkleGenesisCommit(t *testing.T) {
|
||||||
config.NoAsyncFlush = true
|
config.NoAsyncFlush = true
|
||||||
|
|
||||||
triedb := triedb.NewDatabase(db, &triedb.Config{
|
triedb := triedb.NewDatabase(db, &triedb.Config{
|
||||||
IsVerkle: true,
|
IsUBT: true,
|
||||||
PathDB: &config,
|
PathDB: &config,
|
||||||
|
BinTrieGroupDepth: triedb.DefaultBinTrieGroupDepth,
|
||||||
})
|
})
|
||||||
block := genesis.MustCommit(db, triedb)
|
block := genesis.MustCommit(db, triedb)
|
||||||
if !bytes.Equal(block.Root().Bytes(), expected) {
|
if !bytes.Equal(block.Root().Bytes(), expected) {
|
||||||
t.Fatalf("invalid genesis state root, expected %x, got %x", expected, block.Root())
|
t.Fatalf("invalid genesis state root, expected %x, got %x", expected, block.Root())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test that the trie is verkle
|
// Test that the trie is a unified binary trie
|
||||||
if !triedb.IsVerkle() {
|
if !triedb.IsUBT() {
|
||||||
t.Fatalf("expected trie to be verkle")
|
t.Fatalf("expected trie to be a unified binary trie")
|
||||||
}
|
}
|
||||||
vdb := rawdb.NewTable(db, string(rawdb.VerklePrefix))
|
vdb := rawdb.NewTable(db, string(rawdb.VerklePrefix))
|
||||||
if !rawdb.HasAccountTrieNode(vdb, nil) {
|
if !rawdb.HasAccountTrieNode(vdb, nil) {
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,10 @@ var staticPrunePoints = map[HistoryMode]map[common.Hash]*PrunePoint{
|
||||||
BlockNumber: 7836331,
|
BlockNumber: 7836331,
|
||||||
BlockHash: common.HexToHash("0xe6571beb68bf24dbd8a6ba354518996920c55a3f8d8fdca423e391b8ad071f22"),
|
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
|
// LoadTransitionState retrieves the Verkle transition state associated with
|
||||||
// the given state root hash from the database.
|
// 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
|
var ts *TransitionState
|
||||||
|
|
||||||
data, _ := rawdb.ReadVerkleTransitionState(db, root)
|
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"
|
// Initialize the first transition state, with the "ended"
|
||||||
// field set to true if the database was created
|
// field set to true if the database was created
|
||||||
// as a verkle database.
|
// 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
|
// Start with a fresh state
|
||||||
ts = &TransitionState{Ended: isVerkle}
|
ts = &TransitionState{Ended: isUBT}
|
||||||
}
|
}
|
||||||
return ts
|
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
|
// 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 {
|
func ReadLastPivotNumber(db ethdb.KeyValueReader) *uint64 {
|
||||||
data, _ := db.Get(lastPivotKey)
|
data, _ := db.Get(lastPivotKey)
|
||||||
if len(data) == 0 {
|
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)))
|
meta, err = openFreezerFileForReadOnly(filepath.Join(path, fmt.Sprintf("%s.meta", name)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
index.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else {
|
} 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)))
|
meta, err = openFreezerFileForAppend(filepath.Join(path, fmt.Sprintf("%s.meta", name)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
index.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -173,6 +175,8 @@ func newTable(path string, name string, readMeter, writeMeter *metrics.Meter, si
|
||||||
// is detected.
|
// is detected.
|
||||||
metadata, err := newMetadata(meta)
|
metadata, err := newMetadata(meta)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
meta.Close()
|
||||||
|
index.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Create the table and repair any past inconsistency
|
// 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.
|
// we do the final move.
|
||||||
src.Close()
|
src.Close()
|
||||||
|
|
||||||
|
// Permanently persist the content into disk
|
||||||
|
if err := f.Sync(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if err := f.Close(); err != nil {
|
if err := f.Close(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -129,6 +134,7 @@ func openFreezerFileForAppend(filename string) (*os.File, error) {
|
||||||
}
|
}
|
||||||
// Seek to end for append
|
// Seek to end for append
|
||||||
if _, err = file.Seek(0, io.SeekEnd); err != nil {
|
if _, err = file.Seek(0, io.SeekEnd); err != nil {
|
||||||
|
file.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return file, nil
|
return file, nil
|
||||||
|
|
|
||||||
|
|
@ -20,13 +20,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"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/rawdb"
|
||||||
"github.com/ethereum/go-ethereum/core/state/snapshot"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
"github.com/ethereum/go-ethereum/trie/bintrie"
|
"github.com/ethereum/go-ethereum/trie/bintrie"
|
||||||
"github.com/ethereum/go-ethereum/trie/transitiontrie"
|
"github.com/ethereum/go-ethereum/trie/transitiontrie"
|
||||||
|
|
@ -34,8 +30,27 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/triedb"
|
"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.
|
// Database wraps access to tries and contract code.
|
||||||
type Database interface {
|
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 returns a state reader associated with the specified state root.
|
||||||
Reader(root common.Hash) (Reader, error)
|
Reader(root common.Hash) (Reader, error)
|
||||||
|
|
||||||
|
|
@ -55,7 +70,7 @@ type Database interface {
|
||||||
// Commit flushes all pending writes and finalizes the state transition,
|
// Commit flushes all pending writes and finalizes the state transition,
|
||||||
// committing the changes to the underlying storage. It returns an error
|
// committing the changes to the underlying storage. It returns an error
|
||||||
// if the commit fails.
|
// if the commit fails.
|
||||||
Commit(update *stateUpdate) error
|
Commit(update *StateUpdate) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trie is a Ethereum Merkle Patricia trie.
|
// Trie is a Ethereum Merkle Patricia trie.
|
||||||
|
|
@ -139,184 +154,27 @@ type Trie interface {
|
||||||
// with the node that proves the absence of the key.
|
// with the node that proves the absence of the key.
|
||||||
Prove(key []byte, proofDb ethdb.KeyValueWriter) error
|
Prove(key []byte, proofDb ethdb.KeyValueWriter) error
|
||||||
|
|
||||||
// IsVerkle returns true if the trie is verkle-tree based
|
// IsUBT returns true if the trie is unified binary trie based.
|
||||||
IsVerkle() bool
|
IsUBT() 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDatabase creates a state database with the provided data sources.
|
// NewDatabase creates a state database with the provided data sources.
|
||||||
func NewDatabase(triedb *triedb.Database, codedb *CodeDB) *CachingDB {
|
//
|
||||||
if codedb == nil {
|
// Deprecated, please use NewMPTDatabase or NewUBTDatabase directly.
|
||||||
codedb = NewCodeDB(triedb.Disk())
|
func NewDatabase(tdb *triedb.Database, codedb *CodeDB) Database {
|
||||||
}
|
if tdb.IsUBT() {
|
||||||
return &CachingDB{
|
return NewUBTDatabase(tdb, codedb)
|
||||||
triedb: triedb,
|
|
||||||
codedb: codedb,
|
|
||||||
}
|
}
|
||||||
|
return NewMPTDatabase(tdb, codedb)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDatabaseForTesting is similar to NewDatabase, but it initializes the caching
|
// NewDatabaseForTesting is similar to NewDatabase, but it initializes the caching
|
||||||
// db by using an ephemeral memory db with default config for testing.
|
// db by using an ephemeral memory db with default config for testing.
|
||||||
func NewDatabaseForTesting() *CachingDB {
|
func NewDatabaseForTesting() Database {
|
||||||
db := rawdb.NewMemoryDatabase()
|
db := rawdb.NewMemoryDatabase()
|
||||||
return NewDatabase(triedb.NewDatabase(db, nil), NewCodeDB(db))
|
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.
|
// mustCopyTrie returns a deep-copied trie.
|
||||||
func mustCopyTrie(t Trie) Trie {
|
func mustCopyTrie(t Trie) Trie {
|
||||||
switch t := t.(type) {
|
switch t := t.(type) {
|
||||||
|
|
@ -324,6 +182,8 @@ func mustCopyTrie(t Trie) Trie {
|
||||||
return t.Copy()
|
return t.Copy()
|
||||||
case *transitiontrie.TransitionTrie:
|
case *transitiontrie.TransitionTrie:
|
||||||
return t.Copy()
|
return t.Copy()
|
||||||
|
case *bintrie.BinaryTrie:
|
||||||
|
return t.Copy()
|
||||||
default:
|
default:
|
||||||
panic(fmt.Errorf("unknown trie type %T", t))
|
panic(fmt.Errorf("unknown trie type %T", t))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -223,6 +223,12 @@ type HistoricDB struct {
|
||||||
codedb *CodeDB
|
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.
|
// NewHistoricDatabase creates a historic state database.
|
||||||
func NewHistoricDatabase(triedb *triedb.Database, codedb *CodeDB) *HistoricDB {
|
func NewHistoricDatabase(triedb *triedb.Database, codedb *CodeDB) *HistoricDB {
|
||||||
return &HistoricDB{
|
return &HistoricDB{
|
||||||
|
|
@ -291,7 +297,7 @@ func (db *HistoricDB) TrieDB() *triedb.Database {
|
||||||
// Commit flushes all pending writes and finalizes the state transition,
|
// Commit flushes all pending writes and finalizes the state transition,
|
||||||
// committing the changes to the underlying storage. It returns an error
|
// committing the changes to the underlying storage. It returns an error
|
||||||
// if the commit fails.
|
// if the commit fails.
|
||||||
func (db *HistoricDB) Commit(update *stateUpdate) error {
|
func (db *HistoricDB) Commit(update *StateUpdate) error {
|
||||||
return errors.New("not implemented")
|
return errors.New("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/state/snapshot"
|
"github.com/ethereum/go-ethereum/core/state/snapshot"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
"github.com/ethereum/go-ethereum/triedb"
|
"github.com/ethereum/go-ethereum/triedb"
|
||||||
)
|
)
|
||||||
|
|
@ -57,9 +58,9 @@ type AccountIterator interface {
|
||||||
// An error will be returned if the preimage is not available.
|
// An error will be returned if the preimage is not available.
|
||||||
Address() (common.Address, error)
|
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.
|
// 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
|
// 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
|
// Slot returns the storage slot the iterator is currently at. An error will
|
||||||
// be retained if the iterator becomes invalid.
|
// be retained if the iterator becomes invalid.
|
||||||
Slot() []byte
|
Slot() common.Hash
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iteratee wraps the NewIterator methods for traversing the accounts and
|
// 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
|
// Error returns any failure that occurred during iteration, which might have
|
||||||
// caused a premature iteration exit.
|
// caused a premature iteration exit.
|
||||||
func (ai *flatAccountIterator) Error() error {
|
func (ai *flatAccountIterator) Error() error {
|
||||||
if ai.err != nil {
|
return errors.Join(ai.err, ai.it.Error())
|
||||||
return ai.err
|
|
||||||
}
|
|
||||||
return ai.it.Error()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hash returns the hash of the account or storage slot the iterator is
|
// 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
|
// Account returns the account data the iterator is currently at. The account
|
||||||
// data is encoded as slim format from the underlying iterator, the conversion
|
// data is encoded as slim format from the underlying iterator, the conversion
|
||||||
// is required.
|
// is required.
|
||||||
func (ai *flatAccountIterator) Account() []byte {
|
func (ai *flatAccountIterator) Account() *types.StateAccount {
|
||||||
data, err := types.FullAccountRLP(ai.it.Account())
|
data, err := types.FullAccount(ai.it.Account())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ai.err = err
|
ai.err = err
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -176,6 +174,7 @@ func (ai *flatAccountIterator) Account() []byte {
|
||||||
|
|
||||||
// flatStorageIterator is a wrapper around the underlying flat state iterator.
|
// flatStorageIterator is a wrapper around the underlying flat state iterator.
|
||||||
type flatStorageIterator struct {
|
type flatStorageIterator struct {
|
||||||
|
err error
|
||||||
it snapshot.StorageIterator
|
it snapshot.StorageIterator
|
||||||
preimage PreimageReader
|
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
|
// is exhausted or if an error occurs. Any error encountered is retained and
|
||||||
// can be retrieved via Error().
|
// can be retrieved via Error().
|
||||||
func (si *flatStorageIterator) Next() bool {
|
func (si *flatStorageIterator) Next() bool {
|
||||||
|
if si.err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return si.it.Next()
|
return si.it.Next()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error returns any failure that occurred during iteration, which might have
|
// Error returns any failure that occurred during iteration, which might have
|
||||||
// caused a premature iteration exit.
|
// caused a premature iteration exit.
|
||||||
func (si *flatStorageIterator) Error() error {
|
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
|
// 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.
|
// Slot returns the storage slot data the iterator is currently at.
|
||||||
func (si *flatStorageIterator) Slot() []byte {
|
func (si *flatStorageIterator) Slot() common.Hash {
|
||||||
return si.it.Slot()
|
// 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
|
// merkleIterator implements the Iterator interface, providing functions to traverse
|
||||||
// the accounts or storages with the manner of Merkle-Patricia-Trie.
|
// the accounts or storages with the manner of Merkle-Patricia-Trie.
|
||||||
type merkleIterator struct {
|
type merkleIterator struct {
|
||||||
tr Trie
|
tr Trie
|
||||||
|
err error
|
||||||
it *trie.Iterator
|
it *trie.Iterator
|
||||||
account bool
|
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
|
// is exhausted or if an error occurs. Any error encountered is retained and
|
||||||
// can be retrieved via Error().
|
// can be retrieved via Error().
|
||||||
func (ti *merkleIterator) Next() bool {
|
func (ti *merkleIterator) Next() bool {
|
||||||
|
if ti.err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return ti.it.Next()
|
return ti.it.Next()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error returns any failure that occurred during iteration, which might have
|
// Error returns any failure that occurred during iteration, which might have
|
||||||
// caused a premature iteration exit.
|
// caused a premature iteration exit.
|
||||||
func (ti *merkleIterator) Error() error {
|
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
|
// 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.
|
// Account returns the account data the iterator is currently at.
|
||||||
func (ti *merkleIterator) Account() []byte {
|
func (ti *merkleIterator) Account() *types.StateAccount {
|
||||||
if !ti.account {
|
if !ti.account {
|
||||||
return nil
|
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.
|
// 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.
|
// Slot returns the storage slot the iterator is currently at.
|
||||||
func (ti *merkleIterator) Slot() []byte {
|
func (ti *merkleIterator) Slot() common.Hash {
|
||||||
if ti.account {
|
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
|
// stateIteratee implements Iteratee interface, providing the state traversal
|
||||||
|
|
@ -430,6 +458,6 @@ func (e exhaustedIterator) Key() (common.Hash, error) {
|
||||||
return common.Hash{}, nil
|
return common.Hash{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e exhaustedIterator) Slot() []byte {
|
func (e exhaustedIterator) Slot() common.Hash {
|
||||||
return nil
|
return common.Hash{}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
"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 {
|
if key, err := it.Key(); key != (common.Hash{}) || err != nil {
|
||||||
t.Fatalf("Key() = %x, %v; want zero, nil", key, err)
|
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)
|
t.Fatalf("Slot() = %x, want nil", slot)
|
||||||
}
|
}
|
||||||
it.Release()
|
it.Release()
|
||||||
|
|
@ -95,20 +94,16 @@ func testAccountIterator(t *testing.T, scheme string) {
|
||||||
hashes = append(hashes, hash)
|
hashes = append(hashes, hash)
|
||||||
|
|
||||||
// Decode and verify account data.
|
// Decode and verify account data.
|
||||||
blob := acctIt.Account()
|
got := acctIt.Account()
|
||||||
if blob == nil {
|
if got == nil {
|
||||||
t.Fatalf("(%s) nil account at %x", scheme, hash)
|
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]
|
acc := addrByHash[hash]
|
||||||
if decoded.Nonce != acc.nonce {
|
if got.Nonce != acc.nonce {
|
||||||
t.Fatalf("(%s) nonce %x: got %d, want %d", scheme, hash, decoded.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 {
|
if got.Balance.Cmp(acc.balance) != 0 {
|
||||||
t.Fatalf("(%s) balance %x: got %v, want %v", scheme, hash, decoded.Balance, acc.balance)
|
t.Fatalf("(%s) balance %x: got %v, want %v", scheme, hash, got.Balance, acc.balance)
|
||||||
}
|
}
|
||||||
// Verify address preimage resolution.
|
// Verify address preimage resolution.
|
||||||
addr, err := acctIt.Address()
|
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)
|
t.Fatalf("(%s) storage hashes not ascending for %x", scheme, acc.address)
|
||||||
}
|
}
|
||||||
prevHash = hash
|
prevHash = hash
|
||||||
if storageIt.Slot() == nil {
|
if storageIt.Slot() == (common.Hash{}) {
|
||||||
t.Fatalf("(%s) nil slot at %x", scheme, hash)
|
t.Fatalf("(%s) nil slot at %x", scheme, hash)
|
||||||
}
|
}
|
||||||
// Check key preimage resolution on first slot.
|
// 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"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"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/log"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
|
||||||
"github.com/ethereum/go-ethereum/trie/bintrie"
|
"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
|
// DumpToCollector iterates the state according to the given options and inserts
|
||||||
// the items into a collector for aggregation or serialization.
|
// 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
|
// Sanitize the input to allow nil configs
|
||||||
if conf == nil {
|
if conf == nil {
|
||||||
conf = new(DumpConfig)
|
conf = new(DumpConfig)
|
||||||
|
|
@ -131,7 +129,7 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
|
||||||
|
|
||||||
iteratee, err := s.db.Iteratee(s.originalRoot)
|
iteratee, err := s.db.Iteratee(s.originalRoot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil, err
|
||||||
}
|
}
|
||||||
var startHash common.Hash
|
var startHash common.Hash
|
||||||
if conf.Start != nil {
|
if conf.Start != nil {
|
||||||
|
|
@ -139,14 +137,17 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
|
||||||
}
|
}
|
||||||
acctIt, err := iteratee.NewAccountIterator(startHash)
|
acctIt, err := iteratee.NewAccountIterator(startHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil, err
|
||||||
}
|
}
|
||||||
defer acctIt.Release()
|
defer acctIt.Release()
|
||||||
|
|
||||||
for acctIt.Next() {
|
for acctIt.Next() {
|
||||||
var data types.StateAccount
|
data := acctIt.Account()
|
||||||
if err := rlp.DecodeBytes(acctIt.Account(), &data); err != nil {
|
if err := acctIt.Error(); err != nil {
|
||||||
panic(err)
|
return nil, err
|
||||||
|
}
|
||||||
|
if data == nil {
|
||||||
|
return nil, fmt.Errorf("unexpected nil account value")
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
account = DumpAccount{
|
account = DumpAccount{
|
||||||
|
|
@ -168,7 +169,7 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
|
||||||
address = &addrBytes
|
address = &addrBytes
|
||||||
account.Address = address
|
account.Address = address
|
||||||
}
|
}
|
||||||
obj := newObject(s, addrBytes, &data)
|
obj := newObject(s, addrBytes, data)
|
||||||
if !conf.SkipCode {
|
if !conf.SkipCode {
|
||||||
account.Code = obj.Code()
|
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{})
|
storageIt, err := iteratee.NewStorageIterator(acctIt.Hash(), common.Hash{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed to load storage trie", "err", err)
|
return nil, err
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
for storageIt.Next() {
|
for storageIt.Next() {
|
||||||
_, content, _, err := rlp.Split(storageIt.Slot())
|
val := storageIt.Slot()
|
||||||
if err != nil {
|
if err := storageIt.Error(); err != nil {
|
||||||
log.Error("Failed to decode the value returned by iterator", "error", err)
|
storageIt.Release()
|
||||||
continue
|
return nil, err
|
||||||
}
|
}
|
||||||
key, err := storageIt.Key()
|
key, err := storageIt.Key()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
account.Storage[key] = common.Bytes2Hex(content)
|
account.Storage[key] = common.Bytes2Hex(common.TrimLeftZeroes(val[:]))
|
||||||
}
|
}
|
||||||
storageIt.Release()
|
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.Warn("Dump incomplete due to missing preimages", "missing", missingPreimages)
|
||||||
}
|
}
|
||||||
log.Info("Trie dumping complete", "accounts", accounts, "elapsed", common.PrettyDuration(time.Since(start)))
|
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.
|
// DumpBinTrieLeaves collects all binary trie leaf nodes into the provided map.
|
||||||
|
|
@ -242,7 +242,8 @@ func (s *StateDB) RawDump(opts *DumpConfig) Dump {
|
||||||
dump := &Dump{
|
dump := &Dump{
|
||||||
Accounts: make(map[string]DumpAccount),
|
Accounts: make(map[string]DumpAccount),
|
||||||
}
|
}
|
||||||
dump.Next = s.DumpToCollector(dump, opts)
|
next, _ := s.DumpToCollector(dump, opts)
|
||||||
|
dump.Next = next
|
||||||
return *dump
|
return *dump
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"maps"
|
|
||||||
"slices"
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
|
|
@ -32,26 +31,163 @@ type revision struct {
|
||||||
journalIndex int
|
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
|
// journalEntry is a modification entry in the state change journal that can be
|
||||||
// reverted on demand.
|
// reverted on demand.
|
||||||
type journalEntry interface {
|
type journalEntry interface {
|
||||||
// revert undoes the changes introduced by this journal entry.
|
// revert undoes the changes introduced by this journal entry.
|
||||||
revert(*StateDB)
|
revert(*StateDB)
|
||||||
|
|
||||||
// dirtied returns the Ethereum address modified by this journal entry.
|
// mutation returns the account mutation introduced by this entry.
|
||||||
// indicates false if no address was changed.
|
// It indicates false if no tracked account mutation was made.
|
||||||
dirtied() (common.Address, bool)
|
mutation() (common.Address, journalMutationKind, bool)
|
||||||
|
|
||||||
// copy returns a deep-copied journal entry.
|
// copy returns a deep-copied journal entry.
|
||||||
copy() journalEntry
|
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
|
// 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
|
// commit. These are tracked to be able to be reverted in the case of an execution
|
||||||
// exception or request for reversal.
|
// exception or request for reversal.
|
||||||
type journal struct {
|
type journal struct {
|
||||||
entries []journalEntry // Current changes tracked by the journal
|
entries []journalEntry // Current changes tracked by the journal
|
||||||
dirties map[common.Address]int // Dirty accounts and the number of changes
|
mutations map[common.Address]*journalMutationState // Per-account mutation kinds and pre-tx originals
|
||||||
|
|
||||||
validRevisions []revision
|
validRevisions []revision
|
||||||
nextRevisionId int
|
nextRevisionId int
|
||||||
|
|
@ -60,7 +196,7 @@ type journal struct {
|
||||||
// newJournal creates a new initialized journal.
|
// newJournal creates a new initialized journal.
|
||||||
func newJournal() *journal {
|
func newJournal() *journal {
|
||||||
return &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() {
|
func (j *journal) reset() {
|
||||||
j.entries = j.entries[:0]
|
j.entries = j.entries[:0]
|
||||||
j.validRevisions = j.validRevisions[:0]
|
j.validRevisions = j.validRevisions[:0]
|
||||||
clear(j.dirties)
|
clear(j.mutations)
|
||||||
j.nextRevisionId = 0
|
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.
|
// append inserts a new modification entry to the end of the change journal.
|
||||||
func (j *journal) append(entry journalEntry) {
|
func (j *journal) append(entry journalEntry) {
|
||||||
j.entries = append(j.entries, entry)
|
j.entries = append(j.entries, entry)
|
||||||
if addr, dirty := entry.dirtied(); dirty {
|
if addr, kind, dirty := entry.mutation(); dirty {
|
||||||
j.dirties[addr]++
|
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
|
// 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) {
|
func (j *journal) revert(statedb *StateDB, snapshot int) {
|
||||||
for i := len(j.entries) - 1; i >= snapshot; i-- {
|
for i := len(j.entries) - 1; i >= snapshot; i-- {
|
||||||
// Undo the changes made by the operation
|
// Undo the changes made by the operation
|
||||||
j.entries[i].revert(statedb)
|
j.entries[i].revert(statedb)
|
||||||
|
|
||||||
// Drop any dirty tracking induced by the change
|
// Drop any mutation tracking induced by the change.
|
||||||
if addr, dirty := j.entries[i].dirtied(); dirty {
|
if addr, kind, dirty := j.entries[i].mutation(); dirty {
|
||||||
if j.dirties[addr]--; j.dirties[addr] == 0 {
|
state := j.mutations[addr]
|
||||||
delete(j.dirties, 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]
|
j.entries = j.entries[:snapshot]
|
||||||
}
|
}
|
||||||
|
|
||||||
// dirty explicitly sets an address to dirty, even if the change entries would
|
// ripemdMagic explicitly keeps RIPEMD160 in the mutation set with a touch change.
|
||||||
// otherwise suggest it as clean. This method is an ugly hack to handle the RIPEMD
|
//
|
||||||
// precompile consensus exception.
|
// Ethereum Mainnet contains an old empty-account touch/revert quirk for address
|
||||||
func (j *journal) dirty(addr common.Address) {
|
// 0x03. If we only relied on the journal entry above, the revert path would
|
||||||
j.dirties[addr]++
|
// 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.
|
// 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++ {
|
for i := 0; i < j.length(); i++ {
|
||||||
entries = append(entries, j.entries[i].copy())
|
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{
|
return &journal{
|
||||||
entries: entries,
|
entries: entries,
|
||||||
dirties: maps.Clone(j.dirties),
|
mutations: mutations,
|
||||||
validRevisions: slices.Clone(j.validRevisions),
|
validRevisions: slices.Clone(j.validRevisions),
|
||||||
nextRevisionId: j.nextRevisionId,
|
nextRevisionId: j.nextRevisionId,
|
||||||
}
|
}
|
||||||
|
|
@ -187,13 +346,16 @@ func (j *journal) refundChange(previous uint64) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *journal) balanceChange(addr common.Address, previous *uint256.Int) {
|
func (j *journal) balanceChange(addr common.Address, previous *uint256.Int) {
|
||||||
|
prev := previous.Clone()
|
||||||
|
j.stashBalance(addr, prev)
|
||||||
j.append(balanceChange{
|
j.append(balanceChange{
|
||||||
account: addr,
|
account: addr,
|
||||||
prev: previous.Clone(),
|
prev: prev,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *journal) setCode(address common.Address, prevCode []byte) {
|
func (j *journal) setCode(address common.Address, prevCode []byte) {
|
||||||
|
j.stashCode(address, prevCode)
|
||||||
j.append(codeChange{
|
j.append(codeChange{
|
||||||
account: address,
|
account: address,
|
||||||
prevCode: prevCode,
|
prevCode: prevCode,
|
||||||
|
|
@ -201,6 +363,7 @@ func (j *journal) setCode(address common.Address, prevCode []byte) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *journal) nonceChange(address common.Address, prev uint64) {
|
func (j *journal) nonceChange(address common.Address, prev uint64) {
|
||||||
|
j.stashNonce(address, prev)
|
||||||
j.append(nonceChange{
|
j.append(nonceChange{
|
||||||
account: address,
|
account: address,
|
||||||
prev: prev,
|
prev: prev,
|
||||||
|
|
@ -212,9 +375,18 @@ func (j *journal) touchChange(address common.Address) {
|
||||||
account: address,
|
account: address,
|
||||||
})
|
})
|
||||||
if address == ripemd {
|
if address == ripemd {
|
||||||
// Explicitly put it in the dirty-cache, which is otherwise generated from
|
// Preserve the historical RIPEMD160 precompile consensus exception.
|
||||||
// flattened journals.
|
//
|
||||||
j.dirty(address)
|
// 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)
|
delete(s.stateObjects, ch.account)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch createObjectChange) dirtied() (common.Address, bool) {
|
func (ch createObjectChange) mutation() (common.Address, journalMutationKind, bool) {
|
||||||
return ch.account, true
|
return ch.account, journalMutationKindCreate, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch createObjectChange) copy() journalEntry {
|
func (ch createObjectChange) copy() journalEntry {
|
||||||
|
|
@ -309,8 +481,8 @@ func (ch createContractChange) revert(s *StateDB) {
|
||||||
s.getStateObject(ch.account).newContract = false
|
s.getStateObject(ch.account).newContract = false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch createContractChange) dirtied() (common.Address, bool) {
|
func (ch createContractChange) mutation() (common.Address, journalMutationKind, bool) {
|
||||||
return common.Address{}, false
|
return common.Address{}, journalMutationKindNone, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch createContractChange) copy() journalEntry {
|
func (ch createContractChange) copy() journalEntry {
|
||||||
|
|
@ -326,8 +498,8 @@ func (ch selfDestructChange) revert(s *StateDB) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch selfDestructChange) dirtied() (common.Address, bool) {
|
func (ch selfDestructChange) mutation() (common.Address, journalMutationKind, bool) {
|
||||||
return ch.account, true
|
return ch.account, journalMutationKindSelfDestruct, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch selfDestructChange) copy() journalEntry {
|
func (ch selfDestructChange) copy() journalEntry {
|
||||||
|
|
@ -341,8 +513,8 @@ var ripemd = common.HexToAddress("0000000000000000000000000000000000000003")
|
||||||
func (ch touchChange) revert(s *StateDB) {
|
func (ch touchChange) revert(s *StateDB) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch touchChange) dirtied() (common.Address, bool) {
|
func (ch touchChange) mutation() (common.Address, journalMutationKind, bool) {
|
||||||
return ch.account, true
|
return ch.account, journalMutationKindTouch, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch touchChange) copy() journalEntry {
|
func (ch touchChange) copy() journalEntry {
|
||||||
|
|
@ -355,8 +527,8 @@ func (ch balanceChange) revert(s *StateDB) {
|
||||||
s.getStateObject(ch.account).setBalance(ch.prev)
|
s.getStateObject(ch.account).setBalance(ch.prev)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch balanceChange) dirtied() (common.Address, bool) {
|
func (ch balanceChange) mutation() (common.Address, journalMutationKind, bool) {
|
||||||
return ch.account, true
|
return ch.account, journalMutationKindBalance, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch balanceChange) copy() journalEntry {
|
func (ch balanceChange) copy() journalEntry {
|
||||||
|
|
@ -370,8 +542,8 @@ func (ch nonceChange) revert(s *StateDB) {
|
||||||
s.getStateObject(ch.account).setNonce(ch.prev)
|
s.getStateObject(ch.account).setNonce(ch.prev)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch nonceChange) dirtied() (common.Address, bool) {
|
func (ch nonceChange) mutation() (common.Address, journalMutationKind, bool) {
|
||||||
return ch.account, true
|
return ch.account, journalMutationKindNonce, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch nonceChange) copy() journalEntry {
|
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)
|
s.getStateObject(ch.account).setCode(crypto.Keccak256Hash(ch.prevCode), ch.prevCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch codeChange) dirtied() (common.Address, bool) {
|
func (ch codeChange) mutation() (common.Address, journalMutationKind, bool) {
|
||||||
return ch.account, true
|
return ch.account, journalMutationKindCode, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch codeChange) copy() journalEntry {
|
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)
|
s.getStateObject(ch.account).setState(ch.key, ch.prevvalue, ch.origvalue)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch storageChange) dirtied() (common.Address, bool) {
|
func (ch storageChange) mutation() (common.Address, journalMutationKind, bool) {
|
||||||
return ch.account, true
|
return ch.account, journalMutationKindStorage, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch storageChange) copy() journalEntry {
|
func (ch storageChange) copy() journalEntry {
|
||||||
|
|
@ -417,8 +589,8 @@ func (ch transientStorageChange) revert(s *StateDB) {
|
||||||
s.setTransientState(ch.account, ch.key, ch.prevalue)
|
s.setTransientState(ch.account, ch.key, ch.prevalue)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch transientStorageChange) dirtied() (common.Address, bool) {
|
func (ch transientStorageChange) mutation() (common.Address, journalMutationKind, bool) {
|
||||||
return common.Address{}, false
|
return common.Address{}, journalMutationKindNone, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch transientStorageChange) copy() journalEntry {
|
func (ch transientStorageChange) copy() journalEntry {
|
||||||
|
|
@ -433,8 +605,8 @@ func (ch refundChange) revert(s *StateDB) {
|
||||||
s.refund = ch.prev
|
s.refund = ch.prev
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch refundChange) dirtied() (common.Address, bool) {
|
func (ch refundChange) mutation() (common.Address, journalMutationKind, bool) {
|
||||||
return common.Address{}, false
|
return common.Address{}, journalMutationKindNone, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch refundChange) copy() journalEntry {
|
func (ch refundChange) copy() journalEntry {
|
||||||
|
|
@ -453,8 +625,8 @@ func (ch addLogChange) revert(s *StateDB) {
|
||||||
s.logSize--
|
s.logSize--
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch addLogChange) dirtied() (common.Address, bool) {
|
func (ch addLogChange) mutation() (common.Address, journalMutationKind, bool) {
|
||||||
return common.Address{}, false
|
return common.Address{}, journalMutationKindNone, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch addLogChange) copy() journalEntry {
|
func (ch addLogChange) copy() journalEntry {
|
||||||
|
|
@ -476,8 +648,8 @@ func (ch accessListAddAccountChange) revert(s *StateDB) {
|
||||||
s.accessList.DeleteAddress(ch.address)
|
s.accessList.DeleteAddress(ch.address)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch accessListAddAccountChange) dirtied() (common.Address, bool) {
|
func (ch accessListAddAccountChange) mutation() (common.Address, journalMutationKind, bool) {
|
||||||
return common.Address{}, false
|
return common.Address{}, journalMutationKindNone, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch accessListAddAccountChange) copy() journalEntry {
|
func (ch accessListAddAccountChange) copy() journalEntry {
|
||||||
|
|
@ -490,8 +662,8 @@ func (ch accessListAddSlotChange) revert(s *StateDB) {
|
||||||
s.accessList.DeleteSlot(ch.address, ch.slot)
|
s.accessList.DeleteSlot(ch.address, ch.slot)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch accessListAddSlotChange) dirtied() (common.Address, bool) {
|
func (ch accessListAddSlotChange) mutation() (common.Address, journalMutationKind, bool) {
|
||||||
return common.Address{}, false
|
return common.Address{}, journalMutationKindNone, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch accessListAddSlotChange) copy() journalEntry {
|
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
|
return value, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// trieReader implements the StateReader interface, providing functions to access
|
// mptTrieReader implements the StateReader interface, providing functions to
|
||||||
// state from the referenced trie.
|
// access state from the referenced Merkle-Patricia-tree.
|
||||||
//
|
//
|
||||||
// trieReader is safe for concurrent read.
|
// mptTrieReader is safe for concurrent read.
|
||||||
type trieReader struct {
|
type mptTrieReader struct {
|
||||||
root common.Hash // State root which uniquely represent a state
|
root common.Hash // State root which uniquely represents a state
|
||||||
db *triedb.Database // Database for loading trie
|
db *triedb.Database // Database for loading trie
|
||||||
|
|
||||||
// Main trie, resolved in constructor. Note either the Merkle-Patricia-tree
|
mainTrie Trie // Main trie, resolved in constructor, not thread-safe
|
||||||
// or Verkle-tree is not safe for concurrent read.
|
|
||||||
mainTrie Trie
|
|
||||||
|
|
||||||
subRoots map[common.Address]common.Hash // Set of storage roots, cached when the account is resolved
|
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
|
subTries map[common.Address]Trie // Group of storage tries, cached when it's resolved
|
||||||
lock sync.Mutex // Lock for protecting concurrent read
|
lock sync.Mutex // Lock for protecting concurrent read
|
||||||
}
|
}
|
||||||
|
|
||||||
// newTrieReader constructs a trie reader of the specific state. An error will be
|
// newMPTTrieReader constructs a Merkle-Patricia-tree reader of the specific state.
|
||||||
// returned if the associated trie specified by root is not existent.
|
// 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) {
|
func newMPTTrieReader(root common.Hash, db *triedb.Database) (*mptTrieReader, error) {
|
||||||
var (
|
tr, err := trie.NewStateTrie(trie.StateTrieID(root), db)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &trieReader{
|
return &mptTrieReader{
|
||||||
root: root,
|
root: root,
|
||||||
db: db,
|
db: db,
|
||||||
mainTrie: tr,
|
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.
|
// 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)
|
account, err := r.mainTrie.GetAccount(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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.
|
// 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.
|
// 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()
|
r.lock.Lock()
|
||||||
defer r.lock.Unlock()
|
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
|
// 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.
|
// 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()
|
r.lock.Lock()
|
||||||
defer r.lock.Unlock()
|
defer r.lock.Unlock()
|
||||||
|
|
||||||
var (
|
tr, found := r.subTries[addr]
|
||||||
tr Trie
|
if !found {
|
||||||
found bool
|
root, ok := r.subRoots[addr]
|
||||||
value common.Hash
|
|
||||||
)
|
|
||||||
if r.db.IsVerkle() {
|
|
||||||
tr = r.mainTrie
|
|
||||||
} else {
|
|
||||||
tr, found = r.subTries[addr]
|
|
||||||
if !found {
|
|
||||||
root, ok := r.subRoots[addr]
|
|
||||||
|
|
||||||
// The storage slot is accessed without account caching. It's unexpected
|
// The storage slot is accessed without account caching. It's unexpected
|
||||||
// behavior but try to resolve the account first anyway.
|
// behavior but try to resolve the account first anyway.
|
||||||
if !ok {
|
if !ok {
|
||||||
_, err := r.account(addr)
|
_, 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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.Hash{}, err
|
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())
|
ret, err := tr.GetStorage(addr, key.Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.Hash{}, err
|
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)
|
value.SetBytes(ret)
|
||||||
return value, nil
|
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 {
|
if i%8 == 0 {
|
||||||
e[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 4, i)
|
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)
|
f[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 5, i)
|
||||||
}
|
}
|
||||||
if i%64 == 0 {
|
if i%64 == 0 {
|
||||||
|
|
@ -441,7 +441,7 @@ func TestStorageIteratorTraversalValues(t *testing.T) {
|
||||||
if i%8 == 0 {
|
if i%8 == 0 {
|
||||||
e[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 4, i)
|
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)
|
f[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 5, i)
|
||||||
}
|
}
|
||||||
if i%64 == 0 {
|
if i%64 == 0 {
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
"github.com/ethereum/go-ethereum/trie/bintrie"
|
"github.com/ethereum/go-ethereum/trie/bintrie"
|
||||||
"github.com/ethereum/go-ethereum/trie/transitiontrie"
|
"github.com/ethereum/go-ethereum/trie/transitiontrie"
|
||||||
|
|
@ -154,7 +153,7 @@ func (s *stateObject) getTrie() (Trie, error) {
|
||||||
func (s *stateObject) getPrefetchedTrie() Trie {
|
func (s *stateObject) getPrefetchedTrie() Trie {
|
||||||
// If there's nothing to meaningfully return, let the user figure it out by
|
// If there's nothing to meaningfully return, let the user figure it out by
|
||||||
// pulling the trie from disk.
|
// pulling the trie from disk.
|
||||||
if (s.data.Root == types.EmptyRootHash && !s.db.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
|
return nil
|
||||||
}
|
}
|
||||||
// Attempt to retrieve the trie from the prefetcher
|
// 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.
|
// GetState retrieves a value associated with the given storage key.
|
||||||
func (s *stateObject) GetState(key common.Hash) common.Hash {
|
func (s *stateObject) GetState(key common.Hash) common.Hash {
|
||||||
value, _ := s.getState(key)
|
value, dirty := s.dirtyStorage[key]
|
||||||
return value
|
if dirty {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return s.GetCommittedState(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getState retrieves a value associated with the given storage key, along with
|
// 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
|
// GetCommittedState retrieves the value associated with the specific key
|
||||||
// without any mutations caused in the current execution.
|
// without any mutations caused in the current execution.
|
||||||
func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
|
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 we have a pending write or clean cached, return that
|
||||||
if value, pending := s.pendingStorage[key]; pending {
|
if value, pending := s.pendingStorage[key]; pending {
|
||||||
return value
|
return value
|
||||||
|
|
@ -195,19 +201,6 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
|
||||||
// have been handles via pendingStorage above.
|
// have been handles via pendingStorage above.
|
||||||
// 2) we don't have new values, and can deliver empty response back
|
// 2) we don't have new values, and can deliver empty response back
|
||||||
if _, destructed := s.db.stateObjectsDestruct[s.address]; destructed {
|
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
|
s.originStorage[key] = common.Hash{} // track the empty slot as origin value
|
||||||
return common.Hash{}
|
return common.Hash{}
|
||||||
}
|
}
|
||||||
|
|
@ -282,6 +275,13 @@ func (s *stateObject) finalise() {
|
||||||
// map as the dirty slot might have been committed already (before the
|
// map as the dirty slot might have been committed already (before the
|
||||||
// byzantium fork) and entry is necessary to modify the value back.
|
// byzantium fork) and entry is necessary to modify the value back.
|
||||||
s.pendingStorage[key] = value
|
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 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 {
|
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
|
// commitStorage overwrites the clean storage with the storage changes and
|
||||||
// fulfills the storage diffs into the given accountUpdate struct.
|
// fulfills the storage diffs into the given AccountUpdate struct.
|
||||||
func (s *stateObject) commitStorage(op *accountUpdate) {
|
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
|
|
||||||
}
|
|
||||||
)
|
|
||||||
for key, val := range s.pendingStorage {
|
for key, val := range s.pendingStorage {
|
||||||
// Skip the noop storage changes, it might be possible the value
|
// Skip the noop storage changes, it might be possible the value
|
||||||
// of tracked slot is same in originStorage and pendingStorage
|
// of tracked slot is same in originStorage and pendingStorage
|
||||||
|
|
@ -418,20 +409,20 @@ func (s *stateObject) commitStorage(op *accountUpdate) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
hash := crypto.Keccak256Hash(key[:])
|
hash := crypto.Keccak256Hash(key[:])
|
||||||
if op.storages == nil {
|
if op.Storages == nil {
|
||||||
op.storages = make(map[common.Hash][]byte)
|
op.Storages = make(map[common.Hash]common.Hash)
|
||||||
}
|
}
|
||||||
op.storages[hash] = encode(val)
|
op.Storages[hash] = val
|
||||||
|
|
||||||
if op.storagesOriginByKey == nil {
|
if op.StoragesOriginByKey == nil {
|
||||||
op.storagesOriginByKey = make(map[common.Hash][]byte)
|
op.StoragesOriginByKey = make(map[common.Hash]common.Hash)
|
||||||
}
|
}
|
||||||
if op.storagesOriginByHash == nil {
|
if op.StoragesOriginByHash == nil {
|
||||||
op.storagesOriginByHash = make(map[common.Hash][]byte)
|
op.StoragesOriginByHash = make(map[common.Hash]common.Hash)
|
||||||
}
|
}
|
||||||
origin := encode(s.originStorage[key])
|
origin := s.originStorage[key]
|
||||||
op.storagesOriginByKey[key] = origin
|
op.StoragesOriginByKey[key] = origin
|
||||||
op.storagesOriginByHash[hash] = origin
|
op.StoragesOriginByHash[hash] = origin
|
||||||
|
|
||||||
// Overwrite the clean value of storage slots
|
// Overwrite the clean value of storage slots
|
||||||
s.originStorage[key] = val
|
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
|
// Note, commit may run concurrently across all the state objects. Do not assume
|
||||||
// thread-safe access to the statedb.
|
// thread-safe access to the statedb.
|
||||||
func (s *stateObject) commit() (*accountUpdate, *trienode.NodeSet, error) {
|
func (s *stateObject) commit() (*AccountUpdate, *trienode.NodeSet, error) {
|
||||||
// commit the account metadata changes
|
// commit the account metadata changes, the data must be deep-copied
|
||||||
op := &accountUpdate{
|
// to prevent accidental mutations later on (in practice the stateDB
|
||||||
address: s.address,
|
// won't be modified after commit). The origin is safe to use directly.
|
||||||
data: types.SlimAccountRLP(s.data),
|
op := &AccountUpdate{
|
||||||
}
|
Address: s.address,
|
||||||
if s.origin != nil {
|
Data: s.data.Copy(),
|
||||||
op.origin = types.SlimAccountRLP(*s.origin)
|
Origin: s.origin,
|
||||||
}
|
}
|
||||||
// commit the contract code if it's modified
|
// commit the contract code if it's modified
|
||||||
if s.dirtyCode {
|
if s.dirtyCode {
|
||||||
op.code = &contractCode{
|
op.Code = &ContractCode{
|
||||||
hash: common.BytesToHash(s.CodeHash()),
|
Hash: common.BytesToHash(s.CodeHash()),
|
||||||
blob: s.code,
|
Blob: s.code,
|
||||||
}
|
}
|
||||||
s.dirtyCode = false // reset the dirty flag
|
s.dirtyCode = false // reset the dirty flag
|
||||||
|
|
||||||
if s.origin == nil {
|
if s.origin == nil {
|
||||||
op.code.originHash = types.EmptyCodeHash
|
op.Code.OriginHash = types.EmptyCodeHash
|
||||||
} else {
|
} 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
|
// Commit storage changes and the associated storage trie
|
||||||
s.commitStorage(op)
|
s.commitStorage(op)
|
||||||
if len(op.storages) == 0 {
|
if len(op.Storages) == 0 {
|
||||||
// nothing changed, don't bother to commit the trie
|
// nothing changed, don't bother to commit the trie
|
||||||
s.origin = s.data.Copy()
|
s.origin = s.data.Copy()
|
||||||
return op, nil, nil
|
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
|
// The main account trie commit in stateDB.commit() already calls
|
||||||
// CollectNodes on this trie, so calling Commit here again would
|
// CollectNodes on this trie, so calling Commit here again would
|
||||||
// redundantly traverse and serialize the entire tree per dirty account.
|
// 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()
|
s.origin = s.data.Copy()
|
||||||
return op, nil, nil
|
return op, nil, nil
|
||||||
}
|
}
|
||||||
root, nodes := s.trie.Commit(false)
|
// The storage trie root is omitted, as it has already been updated in the
|
||||||
s.data.Root = root
|
// previous updateRoot step.
|
||||||
|
_, nodes := s.trie.Commit(false)
|
||||||
s.origin = s.data.Copy()
|
s.origin = s.data.Copy()
|
||||||
return op, nodes, nil
|
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.
|
// 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{
|
stats := SizeStats{
|
||||||
BlockNumber: update.blockNumber,
|
BlockNumber: update.BlockNumber,
|
||||||
StateRoot: update.root,
|
StateRoot: update.Root,
|
||||||
}
|
}
|
||||||
|
accounts, accountOrigin, storages, storageOrigin := update.EncodeMPTState()
|
||||||
|
|
||||||
// Measure the account changes
|
// Measure the account changes
|
||||||
for addr, oldValue := range update.accountsOrigin {
|
for addr, oldValue := range accountOrigin {
|
||||||
addrHash := crypto.Keccak256Hash(addr.Bytes())
|
addrHash := crypto.Keccak256Hash(addr.Bytes())
|
||||||
newValue, exists := update.accounts[addrHash]
|
newValue, exists := accounts[addrHash]
|
||||||
if !exists {
|
if !exists {
|
||||||
return SizeStats{}, fmt.Errorf("account %x not found", addr)
|
return SizeStats{}, fmt.Errorf("account %x not found", addr)
|
||||||
}
|
}
|
||||||
|
|
@ -156,9 +157,9 @@ func calSizeStats(update *stateUpdate) (SizeStats, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Measure storage changes
|
// Measure storage changes
|
||||||
for addr, slots := range update.storagesOrigin {
|
for addr, slots := range storageOrigin {
|
||||||
addrHash := crypto.Keccak256Hash(addr.Bytes())
|
addrHash := crypto.Keccak256Hash(addr.Bytes())
|
||||||
subset, exists := update.storages[addrHash]
|
subset, exists := storages[addrHash]
|
||||||
if !exists {
|
if !exists {
|
||||||
return SizeStats{}, fmt.Errorf("storage %x not found", addr)
|
return SizeStats{}, fmt.Errorf("storage %x not found", addr)
|
||||||
}
|
}
|
||||||
|
|
@ -167,7 +168,7 @@ func calSizeStats(update *stateUpdate) (SizeStats, error) {
|
||||||
exists bool
|
exists bool
|
||||||
newValue []byte
|
newValue []byte
|
||||||
)
|
)
|
||||||
if update.rawStorageKey {
|
if update.StorageKeyType == StorageKeyPlain {
|
||||||
newValue, exists = subset[crypto.Keccak256Hash(key.Bytes())]
|
newValue, exists = subset[crypto.Keccak256Hash(key.Bytes())]
|
||||||
} else {
|
} else {
|
||||||
newValue, exists = subset[key]
|
newValue, exists = subset[key]
|
||||||
|
|
@ -194,7 +195,7 @@ func calSizeStats(update *stateUpdate) (SizeStats, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Measure trienode changes
|
// Measure trienode changes
|
||||||
for owner, subset := range update.nodes.Sets {
|
for owner, subset := range update.Nodes.Sets {
|
||||||
var (
|
var (
|
||||||
keyPrefix int64
|
keyPrefix int64
|
||||||
isAccount = owner == (common.Hash{})
|
isAccount = owner == (common.Hash{})
|
||||||
|
|
@ -244,13 +245,13 @@ func calSizeStats(update *stateUpdate) (SizeStats, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
codeExists := make(map[common.Hash]struct{})
|
codeExists := make(map[common.Hash]struct{})
|
||||||
for _, code := range update.codes {
|
for _, code := range update.Codes {
|
||||||
if _, ok := codeExists[code.hash]; ok || code.duplicate {
|
if _, ok := codeExists[code.Hash]; ok || code.Duplicate {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
stats.ContractCodes += 1
|
stats.ContractCodes += 1
|
||||||
stats.ContractCodeBytes += codeKeySize + int64(len(code.blob))
|
stats.ContractCodeBytes += codeKeySize + int64(len(code.Blob))
|
||||||
codeExists[code.hash] = struct{}{}
|
codeExists[code.Hash] = struct{}{}
|
||||||
}
|
}
|
||||||
return stats, nil
|
return stats, nil
|
||||||
}
|
}
|
||||||
|
|
@ -267,7 +268,7 @@ type SizeTracker struct {
|
||||||
triedb *triedb.Database
|
triedb *triedb.Database
|
||||||
abort chan struct{}
|
abort chan struct{}
|
||||||
aborted chan struct{}
|
aborted chan struct{}
|
||||||
updateCh chan *stateUpdate
|
updateCh chan *StateUpdate
|
||||||
queryCh chan *stateSizeQuery
|
queryCh chan *stateSizeQuery
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -281,7 +282,7 @@ func NewSizeTracker(db ethdb.KeyValueStore, triedb *triedb.Database) (*SizeTrack
|
||||||
triedb: triedb,
|
triedb: triedb,
|
||||||
abort: make(chan struct{}),
|
abort: make(chan struct{}),
|
||||||
aborted: make(chan struct{}),
|
aborted: make(chan struct{}),
|
||||||
updateCh: make(chan *stateUpdate),
|
updateCh: make(chan *StateUpdate),
|
||||||
queryCh: make(chan *stateSizeQuery),
|
queryCh: make(chan *stateSizeQuery),
|
||||||
}
|
}
|
||||||
go t.run()
|
go t.run()
|
||||||
|
|
@ -328,9 +329,9 @@ func (t *SizeTracker) run() {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case u := <-t.updateCh:
|
case u := <-t.updateCh:
|
||||||
base, found := stats[u.originRoot]
|
base, found := stats[u.OriginRoot]
|
||||||
if !found {
|
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
|
continue
|
||||||
}
|
}
|
||||||
diff, err := calSizeStats(u)
|
diff, err := calSizeStats(u)
|
||||||
|
|
@ -338,15 +339,15 @@ func (t *SizeTracker) run() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
stat := base.add(diff)
|
stat := base.add(diff)
|
||||||
stats[u.root] = stat
|
stats[u.Root] = stat
|
||||||
last = u.root
|
last = u.Root
|
||||||
|
|
||||||
// Publish statistics to metric system
|
// Publish statistics to metric system
|
||||||
stat.publish()
|
stat.publish()
|
||||||
|
|
||||||
// Evict the stale statistics
|
// Evict the stale statistics
|
||||||
heap.Push(&h, stats[u.root])
|
heap.Push(&h, stats[u.Root])
|
||||||
for len(h) > 0 && u.blockNumber-h[0].BlockNumber > statEvictThreshold {
|
for len(h) > 0 && u.BlockNumber-h[0].BlockNumber > statEvictThreshold {
|
||||||
delete(stats, h[0].StateRoot)
|
delete(stats, h[0].StateRoot)
|
||||||
heap.Pop(&h)
|
heap.Pop(&h)
|
||||||
}
|
}
|
||||||
|
|
@ -402,7 +403,7 @@ wait:
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
updates = make(map[common.Hash]*stateUpdate)
|
updates = make(map[common.Hash]*StateUpdate)
|
||||||
children = make(map[common.Hash][]common.Hash)
|
children = make(map[common.Hash][]common.Hash)
|
||||||
done chan buildResult
|
done chan buildResult
|
||||||
)
|
)
|
||||||
|
|
@ -410,9 +411,9 @@ wait:
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case u := <-t.updateCh:
|
case u := <-t.updateCh:
|
||||||
updates[u.root] = u
|
updates[u.Root] = u
|
||||||
children[u.originRoot] = append(children[u.originRoot], u.root)
|
children[u.OriginRoot] = append(children[u.OriginRoot], u.Root)
|
||||||
log.Debug("Received state update", "root", u.root, "blockNumber", u.blockNumber)
|
log.Debug("Received state update", "root", u.Root, "blockNumber", u.BlockNumber)
|
||||||
|
|
||||||
case r := <-t.queryCh:
|
case r := <-t.queryCh:
|
||||||
r.err = errors.New("state size is not initialized yet")
|
r.err = errors.New("state size is not initialized yet")
|
||||||
|
|
@ -432,8 +433,8 @@ wait:
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
done = make(chan buildResult)
|
done = make(chan buildResult)
|
||||||
go t.build(entry.root, entry.blockNumber, done)
|
go t.build(entry.Root, entry.BlockNumber, done)
|
||||||
log.Info("Measuring persistent state size", "root", root.Hex(), "number", entry.blockNumber)
|
log.Info("Measuring persistent state size", "root", root.Hex(), "number", entry.BlockNumber)
|
||||||
|
|
||||||
case result := <-done:
|
case result := <-done:
|
||||||
if result.err != nil {
|
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.
|
// Notify is an async method used to send the state update to the size tracker.
|
||||||
// It ignores empty updates (where no state changes occurred).
|
// It ignores empty updates (where no state changes occurred).
|
||||||
// If the channel is full, it drops the update to avoid blocking.
|
// If the channel is full, it drops the update to avoid blocking.
|
||||||
func (t *SizeTracker) Notify(update *stateUpdate) {
|
func (t *SizeTracker) Notify(update *StateUpdate) {
|
||||||
if update == nil || update.empty() {
|
if update == nil || update.Empty() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
select {
|
select {
|
||||||
|
|
|
||||||
|
|
@ -160,7 +160,7 @@ func TestSizeTracker(t *testing.T) {
|
||||||
}
|
}
|
||||||
tracker.Notify(ret)
|
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)
|
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)
|
t.Fatalf("Failed to calculate size stats for block %d: %v", blockNum, err)
|
||||||
}
|
}
|
||||||
trackedUpdates = append(trackedUpdates, diff)
|
trackedUpdates = append(trackedUpdates, diff)
|
||||||
currentRoot = ret.root
|
currentRoot = ret.Root
|
||||||
}
|
}
|
||||||
finalRoot := rawdb.ReadSnapshotRoot(db)
|
finalRoot := rawdb.ReadSnapshotRoot(db)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
package state
|
package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"maps"
|
"maps"
|
||||||
|
|
@ -31,6 +32,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core/stateless"
|
"github.com/ethereum/go-ethereum/core/stateless"
|
||||||
"github.com/ethereum/go-ethereum/core/tracing"
|
"github.com/ethereum/go-ethereum/core/tracing"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
|
@ -126,6 +128,12 @@ type StateDB struct {
|
||||||
accessList *accessList
|
accessList *accessList
|
||||||
accessEvents *AccessEvents
|
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
|
// Transient storage
|
||||||
transientStorage transientStorage
|
transientStorage transientStorage
|
||||||
|
|
||||||
|
|
@ -190,7 +198,7 @@ func NewWithReader(root common.Hash, db Database, reader Reader) (*StateDB, erro
|
||||||
accessList: newAccessList(),
|
accessList: newAccessList(),
|
||||||
transientStorage: newTransientStorage(),
|
transientStorage: newTransientStorage(),
|
||||||
}
|
}
|
||||||
if db.TrieDB().IsVerkle() {
|
if db.Type().Is(TypeUBT) {
|
||||||
sdb.accessEvents = NewAccessEvents()
|
sdb.accessEvents = NewAccessEvents()
|
||||||
}
|
}
|
||||||
return sdb, nil
|
return sdb, nil
|
||||||
|
|
@ -317,6 +325,11 @@ func (s *StateDB) Empty(addr common.Address) bool {
|
||||||
return so == nil || so.empty()
|
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
|
// GetBalance retrieves the balance from the given address or 0 if object not found
|
||||||
func (s *StateDB) GetBalance(addr common.Address) *uint256.Int {
|
func (s *StateDB) GetBalance(addr common.Address) *uint256.Int {
|
||||||
stateObject := s.getStateObject(addr)
|
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
|
// GetStorageRoot retrieves the storage root from the given address or empty
|
||||||
// if object not found.
|
// 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 {
|
func (s *StateDB) GetStorageRoot(addr common.Address) common.Hash {
|
||||||
stateObject := s.getStateObject(addr)
|
stateObject := s.getStateObject(addr)
|
||||||
if stateObject != nil {
|
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
|
// getStateObject retrieves a state object given by the address, returning nil if
|
||||||
// the object is not found or was deleted in this execution context.
|
// the object is not found or was deleted in this execution context.
|
||||||
func (s *StateDB) getStateObject(addr common.Address) *stateObject {
|
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
|
// Prefer live objects if any is available
|
||||||
if obj := s.stateObjects[addr]; obj != nil {
|
if obj := s.stateObjects[addr]; obj != nil {
|
||||||
return obj
|
return obj
|
||||||
|
|
@ -678,6 +698,7 @@ func (s *StateDB) Copy() *StateDB {
|
||||||
refund: s.refund,
|
refund: s.refund,
|
||||||
thash: s.thash,
|
thash: s.thash,
|
||||||
txIndex: s.txIndex,
|
txIndex: s.txIndex,
|
||||||
|
blockAccessIndex: s.blockAccessIndex,
|
||||||
logs: make(map[common.Hash][]*types.Log, len(s.logs)),
|
logs: make(map[common.Hash][]*types.Log, len(s.logs)),
|
||||||
logSize: s.logSize,
|
logSize: s.logSize,
|
||||||
preimages: maps.Clone(s.preimages),
|
preimages: maps.Clone(s.preimages),
|
||||||
|
|
@ -722,6 +743,9 @@ func (s *StateDB) Copy() *StateDB {
|
||||||
}
|
}
|
||||||
state.logs[hash] = cpy
|
state.logs[hash] = cpy
|
||||||
}
|
}
|
||||||
|
if s.stateAccessList != nil {
|
||||||
|
state.stateAccessList = s.stateAccessList.Copy()
|
||||||
|
}
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -757,7 +781,7 @@ type removedAccountWithBalance struct {
|
||||||
// before the Finalise.
|
// before the Finalise.
|
||||||
func (s *StateDB) LogsForBurnAccounts() []*types.Log {
|
func (s *StateDB) LogsForBurnAccounts() []*types.Log {
|
||||||
var list []removedAccountWithBalance
|
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() {
|
if obj, exist := s.stateObjects[addr]; exist && obj.selfDestructed && !obj.Balance().IsZero() {
|
||||||
list = append(list, removedAccountWithBalance{
|
list = append(list, removedAccountWithBalance{
|
||||||
address: obj.address,
|
address: obj.address,
|
||||||
|
|
@ -781,29 +805,69 @@ func (s *StateDB) LogsForBurnAccounts() []*types.Log {
|
||||||
// Finalise finalises the state by removing the destructed objects and clears
|
// 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
|
// 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.
|
// into the tries just yet. Only IntermediateRoot or Commit will do that.
|
||||||
func (s *StateDB) Finalise(deleteEmptyObjects bool) {
|
func (s *StateDB) Finalise(deleteEmptyObjects bool) *bal.ConstructionBlockAccessList {
|
||||||
addressesToPrefetch := make([]common.Address, 0, len(s.journal.dirties))
|
addressesToPrefetch := make([]common.Address, 0, len(s.journal.mutations))
|
||||||
for addr := range s.journal.dirties {
|
for addr, state := range s.journal.mutations {
|
||||||
obj, exist := s.stateObjects[addr]
|
obj, exist := s.stateObjects[addr]
|
||||||
if !exist {
|
if !exist {
|
||||||
// ripeMD is 'touched' at block 1714175, in tx 0x1237f737031e40bcde4a8b7e717b2d15e3ecadfe49bb1bbc71ee9deb09c6fcf2
|
// RIPEMD160 (0x03) gets an extra dirty marker for a historical
|
||||||
// That tx goes out of gas, and although the notion of 'touched' does not exist there, the
|
// mainnet consensus exception (at block 1714175, in tx
|
||||||
// touch-event will still be recorded in the journal. Since ripeMD is a special snowflake,
|
// 0x1237f737031e40bcde4a8b7e717b2d15e3ecadfe49bb1bbc71ee9deb09c6fcf2)
|
||||||
// it will persist in the journal even though the journal is reverted. In this special circumstance,
|
// around empty-account touch/revert handling.
|
||||||
// it may exist in `s.journal.dirties` but not in `s.stateObjects`.
|
//
|
||||||
// Thus, we can safely ignore it here
|
// 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
|
continue
|
||||||
}
|
}
|
||||||
if obj.selfDestructed || (deleteEmptyObjects && obj.empty()) {
|
if obj.selfDestructed || (deleteEmptyObjects && obj.empty()) {
|
||||||
delete(s.stateObjects, obj.address)
|
delete(s.stateObjects, obj.address)
|
||||||
s.markDelete(addr)
|
s.markDelete(addr)
|
||||||
|
|
||||||
// We need to maintain account deletions explicitly (will remain
|
// We need to maintain account deletions explicitly (will remain
|
||||||
// set indefinitely). Note only the first occurred self-destruct
|
// set indefinitely). Note only the first occurred self-destruct
|
||||||
// event is tracked.
|
// event is tracked.
|
||||||
if _, ok := s.stateObjectsDestruct[obj.address]; !ok {
|
if _, ok := s.stateObjectsDestruct[obj.address]; !ok {
|
||||||
s.stateObjectsDestruct[obj.address] = obj
|
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 {
|
} 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()
|
obj.finalise()
|
||||||
s.markUpdate(addr)
|
s.markUpdate(addr)
|
||||||
}
|
}
|
||||||
|
|
@ -819,6 +883,8 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
|
||||||
}
|
}
|
||||||
// Invalidate journal because reverting across transactions is not allowed.
|
// Invalidate journal because reverting across transactions is not allowed.
|
||||||
s.clearJournalAndRefund()
|
s.clearJournalAndRefund()
|
||||||
|
|
||||||
|
return s.stateAccessList
|
||||||
}
|
}
|
||||||
|
|
||||||
// IntermediateRoot computes the current root hash of the state trie.
|
// 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()
|
start = time.Now()
|
||||||
workers errgroup.Group
|
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
|
// Bypass per-account updateTrie() for binary trie. In binary trie mode
|
||||||
// there is only one unified trie (OpenStorageTrie returns self), so the
|
// there is only one unified trie (OpenStorageTrie returns self), so the
|
||||||
// per-account trie setup in updateTrie() (getPrefetchedTrie, getTrie,
|
// 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.
|
// If witness building is enabled, gather all the read-only accesses.
|
||||||
// Skip witness collection in Verkle mode, they will be gathered
|
// Skip witness collection in Unified-binary-trie mode, they will be
|
||||||
// together at the end.
|
// gathered together at the end.
|
||||||
if s.witness != nil && !s.db.TrieDB().IsVerkle() {
|
if s.witness != nil && s.db.Type().Is(TypeMPT) {
|
||||||
// Pull in anything that has been accessed before destruction
|
// Pull in anything that has been accessed before destruction
|
||||||
for _, obj := range s.stateObjectsDestruct {
|
for _, obj := range s.stateObjectsDestruct {
|
||||||
// Skip any objects that haven't touched their storage
|
// Skip any objects that haven't touched their storage
|
||||||
|
|
@ -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
|
// only a single trie is used for state hashing. Replacing a non-nil verkle tree
|
||||||
// here could result in losing uncommitted changes from storage.
|
// here could result in losing uncommitted changes from storage.
|
||||||
start = time.Now()
|
start = time.Now()
|
||||||
if s.prefetcher != nil && !s.db.TrieDB().IsVerkle() {
|
if s.prefetcher != nil && s.db.Type().Is(TypeMPT) {
|
||||||
if trie := s.prefetcher.trie(common.Hash{}, s.originalRoot); trie == nil {
|
if trie := s.prefetcher.trie(common.Hash{}, s.originalRoot); trie == nil {
|
||||||
log.Error("Failed to retrieve account pre-fetcher trie")
|
log.Error("Failed to retrieve account pre-fetcher trie")
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1031,9 +1097,10 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
||||||
// SetTxContext sets the current transaction hash and index which are
|
// SetTxContext sets the current transaction hash and index which are
|
||||||
// used when the EVM emits new state logs. It should be invoked before
|
// used when the EVM emits new state logs. It should be invoked before
|
||||||
// transaction execution.
|
// 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.thash = thash
|
||||||
s.txIndex = ti
|
s.txIndex = ti
|
||||||
|
s.blockAccessIndex = blockAccessIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StateDB) clearJournalAndRefund() {
|
func (s *StateDB) clearJournalAndRefund() {
|
||||||
|
|
@ -1042,11 +1109,11 @@ func (s *StateDB) clearJournalAndRefund() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// deleteStorage is designed to delete the storage trie of a designated account.
|
// 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 (
|
var (
|
||||||
nodes = trienode.NewNodeSet(addrHash) // the set for trie node mutations (value is nil)
|
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)
|
storages = make(map[common.Hash]common.Hash) // the set for storage mutations (value is nil)
|
||||||
storageOrigins = make(map[common.Hash][]byte) // the set for tracking the original value of slot
|
storageOrigins = make(map[common.Hash]common.Hash) // the set for tracking the original value of slot
|
||||||
)
|
)
|
||||||
iteratee, err := s.db.Iteratee(s.originalRoot)
|
iteratee, err := s.db.Iteratee(s.originalRoot)
|
||||||
if err != nil {
|
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))
|
nodes.AddNode(path, trienode.NewDeletedWithPrev(blob))
|
||||||
})
|
})
|
||||||
for it.Next() {
|
for it.Next() {
|
||||||
slot := common.CopyBytes(it.Slot())
|
slot := it.Slot()
|
||||||
if err := it.Error(); err != nil { // error might occur after Slot function
|
// Error might occur after Slot function
|
||||||
|
if err := it.Error(); err != nil {
|
||||||
return nil, nil, nil, err
|
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()
|
key := it.Hash()
|
||||||
storages[key] = nil
|
storages[key] = common.Hash{}
|
||||||
storageOrigins[key] = slot
|
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
|
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
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
if stack.Hash() != root {
|
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.
|
// with their values be tracked as original value.
|
||||||
// In case (d), **original** account along with its storages should be deleted,
|
// In case (d), **original** account along with its storages should be deleted,
|
||||||
// with their values be tracked as original value.
|
// 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 (
|
var (
|
||||||
nodes []*trienode.NodeSet
|
nodes []*trienode.NodeSet
|
||||||
deletes = make(map[common.Hash]*accountDelete)
|
deletes = make(map[common.Hash]*AccountDelete)
|
||||||
)
|
)
|
||||||
for addr, prevObj := range s.stateObjectsDestruct {
|
for addr, prevObj := range s.stateObjectsDestruct {
|
||||||
prev := prevObj.origin
|
prev := prevObj.origin
|
||||||
|
|
@ -1118,15 +1190,15 @@ func (s *StateDB) handleDestruction(noStorageWiping bool) (map[common.Hash]*acco
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// The account was existent, it can be either case (c) or (d).
|
// The account was existent, it can be either case (c) or (d).
|
||||||
addrHash := crypto.Keccak256Hash(addr.Bytes())
|
addrHash := prevObj.addrHash()
|
||||||
op := &accountDelete{
|
op := &AccountDelete{
|
||||||
address: addr,
|
Address: addr,
|
||||||
origin: types.SlimAccountRLP(*prev),
|
Origin: prev,
|
||||||
}
|
}
|
||||||
deletes[addrHash] = op
|
deletes[addrHash] = op
|
||||||
|
|
||||||
// Short circuit if the origin storage was empty.
|
// 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
|
continue
|
||||||
}
|
}
|
||||||
if noStorageWiping {
|
if noStorageWiping {
|
||||||
|
|
@ -1137,8 +1209,8 @@ func (s *StateDB) handleDestruction(noStorageWiping bool) (map[common.Hash]*acco
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to delete storage, err: %w", err)
|
return nil, nil, fmt.Errorf("failed to delete storage, err: %w", err)
|
||||||
}
|
}
|
||||||
op.storages = storages
|
op.Storages = storages
|
||||||
op.storagesOrigin = storagesOrigin
|
op.StoragesOrigin = storagesOrigin
|
||||||
|
|
||||||
// Aggregate the associated trie node changes.
|
// Aggregate the associated trie node changes.
|
||||||
nodes = append(nodes, set)
|
nodes = append(nodes, set)
|
||||||
|
|
@ -1153,13 +1225,13 @@ func (s *StateDB) GetTrie() Trie {
|
||||||
|
|
||||||
// commit gathers the state mutations accumulated along with the associated
|
// commit gathers the state mutations accumulated along with the associated
|
||||||
// trie changes, resetting all internal flags with the new state as the base.
|
// 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.
|
// Short circuit in case any database failure occurred earlier.
|
||||||
if s.dbErr != nil {
|
if s.dbErr != nil {
|
||||||
return nil, fmt.Errorf("commit aborted due to earlier error: %v", s.dbErr)
|
return nil, fmt.Errorf("commit aborted due to earlier error: %v", s.dbErr)
|
||||||
}
|
}
|
||||||
// Finalize any pending changes and merge everything into the tries
|
// 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.
|
// Short circuit if any error occurs within the IntermediateRoot.
|
||||||
if s.dbErr != nil {
|
if s.dbErr != nil {
|
||||||
|
|
@ -1174,7 +1246,7 @@ func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNum
|
||||||
|
|
||||||
lock sync.Mutex // protect two maps below
|
lock sync.Mutex // protect two maps below
|
||||||
nodes = trienode.NewMergedNodeSet() // aggregated trie nodes
|
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.
|
// 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.
|
// writes to run in parallel with the computations.
|
||||||
var (
|
var (
|
||||||
start = time.Now()
|
start = time.Now()
|
||||||
root common.Hash
|
|
||||||
workers errgroup.Group
|
workers errgroup.Group
|
||||||
)
|
)
|
||||||
// Schedule the account trie first since that will be the biggest, so give
|
// 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.
|
// code didn't anticipate for.
|
||||||
workers.Go(func() error {
|
workers.Go(func() error {
|
||||||
// Write the account trie changes, measuring the amount of wasted time
|
// Write the account trie changes, measuring the amount of wasted time
|
||||||
newroot, set := s.trie.Commit(true)
|
_, set := s.trie.Commit(true)
|
||||||
root = newroot
|
|
||||||
|
|
||||||
if err := merge(set); err != nil {
|
if err := merge(set); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -1305,12 +1374,16 @@ func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNum
|
||||||
origin := s.originalRoot
|
origin := s.originalRoot
|
||||||
s.originalRoot = root
|
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
|
// commitAndFlush is a wrapper of commit which also commits the state mutations
|
||||||
// to the configured data stores.
|
// 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)
|
ret, err := s.commit(deleteEmptyObjects, noStorageWiping, block)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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 reader update must be performed as the final step, otherwise,
|
||||||
// the new state would not be visible before db.commit.
|
// 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
|
return ret, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1351,17 +1424,17 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool, noStorageWiping
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.Hash{}, err
|
return common.Hash{}, err
|
||||||
}
|
}
|
||||||
return ret.root, nil
|
return ret.Root, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommitWithUpdate writes the state mutations and returns the state update for
|
// CommitWithUpdate writes the state mutations and returns the state update for
|
||||||
// external processing (e.g., live tracing hooks or size tracker).
|
// 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)
|
ret, err := s.commitAndFlush(block, deleteEmptyObjects, noStorageWiping, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.Hash{}, nil, err
|
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.
|
// 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
|
// Reset transient storage at the beginning of transaction execution
|
||||||
s.transientStorage = newTransientStorage()
|
s.transientStorage = newTransientStorage()
|
||||||
|
|
||||||
|
if rules.IsAmsterdam {
|
||||||
|
s.stateAccessList = bal.NewConstructionBlockAccessList()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddAddressToAccessList adds the given address to the access list
|
// AddAddressToAccessList adds the given address to the access list
|
||||||
|
|
|
||||||
|
|
@ -182,11 +182,12 @@ func (test *stateTest) run() bool {
|
||||||
accountOrigin []map[common.Address][]byte
|
accountOrigin []map[common.Address][]byte
|
||||||
storages []map[common.Hash]map[common.Hash][]byte
|
storages []map[common.Hash]map[common.Hash][]byte
|
||||||
storageOrigin []map[common.Address]map[common.Hash][]byte
|
storageOrigin []map[common.Address]map[common.Hash][]byte
|
||||||
copyUpdate = func(update *stateUpdate) {
|
copyUpdate = func(update *StateUpdate) {
|
||||||
accounts = append(accounts, maps.Clone(update.accounts))
|
accts, acctOrigin, slots, slotOrigin := update.EncodeMPTState()
|
||||||
accountOrigin = append(accountOrigin, maps.Clone(update.accountsOrigin))
|
accounts = append(accounts, maps.Clone(accts))
|
||||||
storages = append(storages, maps.Clone(update.storages))
|
accountOrigin = append(accountOrigin, maps.Clone(acctOrigin))
|
||||||
storageOrigin = append(storageOrigin, maps.Clone(update.storagesOrigin))
|
storages = append(storages, maps.Clone(slots))
|
||||||
|
storageOrigin = append(storageOrigin, maps.Clone(slotOrigin))
|
||||||
}
|
}
|
||||||
disk = rawdb.NewMemoryDatabase()
|
disk = rawdb.NewMemoryDatabase()
|
||||||
tdb = triedb.NewDatabase(disk, &triedb.Config{PathDB: pathdb.Defaults})
|
tdb = triedb.NewDatabase(disk, &triedb.Config{PathDB: pathdb.Defaults})
|
||||||
|
|
@ -209,7 +210,7 @@ func (test *stateTest) run() bool {
|
||||||
if i != 0 {
|
if i != 0 {
|
||||||
root = roots[len(roots)-1]
|
root = roots[len(roots)-1]
|
||||||
}
|
}
|
||||||
state, err := New(root, NewDatabase(tdb, nil).WithSnapshot(snaps))
|
state, err := New(root, NewMPTDatabase(tdb, nil).WithSnapshot(snaps))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
@ -232,11 +233,11 @@ func (test *stateTest) run() bool {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
if ret.empty() {
|
if ret.Empty() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
copyUpdate(ret)
|
copyUpdate(ret)
|
||||||
roots = append(roots, ret.root)
|
roots = append(roots, ret.Root)
|
||||||
}
|
}
|
||||||
for i := 0; i < len(test.actions); i++ {
|
for i := 0; i < len(test.actions); i++ {
|
||||||
root := types.EmptyRootHash
|
root := types.EmptyRootHash
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core/stateless"
|
"github.com/ethereum/go-ethereum/core/stateless"
|
||||||
"github.com/ethereum/go-ethereum/core/tracing"
|
"github.com/ethereum/go-ethereum/core/tracing"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/holiman/uint256"
|
"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)
|
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 {
|
func (s *hookedStateDB) GetTransientState(addr common.Address, key common.Hash) common.Hash {
|
||||||
return s.inner.GetTransientState(addr, key)
|
return s.inner.GetTransientState(addr, key)
|
||||||
}
|
}
|
||||||
|
|
@ -118,6 +115,10 @@ func (s *hookedStateDB) Exist(addr common.Address) bool {
|
||||||
return s.inner.Exist(addr)
|
return s.inner.Exist(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *hookedStateDB) Touch(addr common.Address) {
|
||||||
|
s.inner.Touch(addr)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *hookedStateDB) Empty(addr common.Address) bool {
|
func (s *hookedStateDB) Empty(addr common.Address) bool {
|
||||||
return s.inner.Empty(addr)
|
return s.inner.Empty(addr)
|
||||||
}
|
}
|
||||||
|
|
@ -233,18 +234,17 @@ func (s *hookedStateDB) LogsForBurnAccounts() []*types.Log {
|
||||||
return s.inner.LogsForBurnAccounts()
|
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 {
|
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.
|
// Short circuit if no relevant hooks are set.
|
||||||
s.inner.Finalise(deleteEmptyObjects)
|
return s.inner.Finalise(deleteEmptyObjects)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect all self-destructed addresses first, then sort them to ensure
|
// Collect all self-destructed addresses first, then sort them to ensure
|
||||||
// that state change hooks will be invoked in deterministic
|
// that state change hooks will be invoked in deterministic
|
||||||
// order when the accounts are deleted below
|
// order when the accounts are deleted below
|
||||||
var selfDestructedAddrs []common.Address
|
var selfDestructedAddrs []common.Address
|
||||||
for addr := range s.inner.journal.dirties {
|
for addr := range s.inner.journal.mutations {
|
||||||
obj := s.inner.stateObjects[addr]
|
obj := s.inner.stateObjects[addr]
|
||||||
if obj == nil || !obj.selfDestructed {
|
if obj == nil || !obj.selfDestructed {
|
||||||
// Not self-destructed, keep searching.
|
// 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.hooks.OnCodeChange(addr, prevCodeHash, s.inner.GetCode(addr), types.EmptyCodeHash, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return s.inner.Finalise(deleteEmptyObjects)
|
||||||
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
|
// TestHooks is a basic sanity-check of all hooks
|
||||||
func TestHooks(t *testing.T) {
|
func TestHooks(t *testing.T) {
|
||||||
inner, _ := New(types.EmptyRootHash, NewDatabaseForTesting())
|
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 result []string
|
||||||
var wants = []string{
|
var wants = []string{
|
||||||
"0xaa00000000000000000000000000000000000000.balance: 0->100 (Unspecified)",
|
"0xaa00000000000000000000000000000000000000.balance: 0->100 (Unspecified)",
|
||||||
|
|
|
||||||
|
|
@ -247,16 +247,16 @@ func TestCopyWithDirtyJournal(t *testing.T) {
|
||||||
|
|
||||||
orig.Finalise(true)
|
orig.Finalise(true)
|
||||||
for i := byte(0); i < 255; i++ {
|
for i := byte(0); i < 255; i++ {
|
||||||
root := orig.GetStorageRoot(common.BytesToAddress([]byte{i}))
|
balance := orig.GetBalance(common.BytesToAddress([]byte{i}))
|
||||||
if root != (common.Hash{}) {
|
if !balance.IsZero() {
|
||||||
t.Errorf("Unexpected storage root %x", root)
|
t.Errorf("Unexpected balance %x", root)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cpy.Finalise(true)
|
cpy.Finalise(true)
|
||||||
for i := byte(0); i < 255; i++ {
|
for i := byte(0); i < 255; i++ {
|
||||||
root := cpy.GetStorageRoot(common.BytesToAddress([]byte{i}))
|
balance := cpy.GetBalance(common.BytesToAddress([]byte{i}))
|
||||||
if root != (common.Hash{}) {
|
if !balance.IsZero() {
|
||||||
t.Errorf("Unexpected storage root %x", root)
|
t.Errorf("Unexpected balance %x", root)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if cpy.IntermediateRoot(true) != orig.IntermediateRoot(true) {
|
if cpy.IntermediateRoot(true) != orig.IntermediateRoot(true) {
|
||||||
|
|
@ -394,9 +394,7 @@ func newTestAction(addr common.Address, r *rand.Rand) testAction {
|
||||||
}
|
}
|
||||||
contractHash := s.GetCodeHash(addr)
|
contractHash := s.GetCodeHash(addr)
|
||||||
emptyCode := contractHash == (common.Hash{}) || contractHash == types.EmptyCodeHash
|
emptyCode := contractHash == (common.Hash{}) || contractHash == types.EmptyCodeHash
|
||||||
storageRoot := s.GetStorageRoot(addr)
|
if s.GetNonce(addr) == 0 && emptyCode {
|
||||||
emptyStorage := storageRoot == (common.Hash{}) || storageRoot == types.EmptyRootHash
|
|
||||||
if s.GetNonce(addr) == 0 && emptyCode && emptyStorage {
|
|
||||||
s.CreateContract(addr)
|
s.CreateContract(addr)
|
||||||
// We also set some code here, to prevent the
|
// We also set some code here, to prevent the
|
||||||
// CreateContract action from being performed twice in a row,
|
// CreateContract action from being performed twice in a row,
|
||||||
|
|
@ -641,7 +639,7 @@ func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error {
|
||||||
{
|
{
|
||||||
have := state.transientStorage
|
have := state.transientStorage
|
||||||
want := checkstate.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",
|
return fmt.Errorf("transient storage differs ,have\n%v\nwant\n%v",
|
||||||
have.PrettyPrint(),
|
have.PrettyPrint(),
|
||||||
want.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",
|
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))
|
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) {
|
if !equalMutationSets(state.journal.mutations, checkstate.journal.mutations) {
|
||||||
getKeys := func(dirty map[common.Address]int) string {
|
return fmt.Errorf("journal mutation set mismatch.\nhave:\n%v\nwant:\n%v\n", state.journal.mutations, checkstate.journal.mutations)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
return nil
|
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) {
|
func TestTouchDelete(t *testing.T) {
|
||||||
s := newStateEnv()
|
s := newStateEnv()
|
||||||
s.state.getOrNewStateObject(common.Address{})
|
s.state.getOrNewStateObject(common.Address{})
|
||||||
|
|
@ -693,12 +695,54 @@ func TestTouchDelete(t *testing.T) {
|
||||||
snapshot := s.state.Snapshot()
|
snapshot := s.state.Snapshot()
|
||||||
s.state.AddBalance(common.Address{}, new(uint256.Int), tracing.BalanceChangeUnspecified)
|
s.state.AddBalance(common.Address{}, new(uint256.Int), tracing.BalanceChangeUnspecified)
|
||||||
|
|
||||||
if len(s.state.journal.dirties) != 1 {
|
if len(s.state.journal.mutations) != 1 {
|
||||||
t.Fatal("expected one dirty state object")
|
t.Fatal("expected one mutated state object")
|
||||||
}
|
}
|
||||||
s.state.RevertToSnapshot(snapshot)
|
s.state.RevertToSnapshot(snapshot)
|
||||||
if len(s.state.journal.dirties) != 0 {
|
if len(s.state.journal.mutations) != 0 {
|
||||||
t.Fatal("expected no dirty state object")
|
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()
|
disk = rawdb.NewMemoryDatabase()
|
||||||
tdb = triedb.NewDatabase(disk, nil)
|
tdb = triedb.NewDatabase(disk, nil)
|
||||||
snaps, _ = snapshot.New(snapshot.Config{CacheSize: 10}, disk, tdb, types.EmptyRootHash)
|
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)
|
state, _ = New(types.EmptyRootHash, db)
|
||||||
addr = common.HexToAddress("0x1")
|
addr = common.HexToAddress("0x1")
|
||||||
)
|
)
|
||||||
|
|
@ -1290,8 +1334,8 @@ func TestDeleteStorage(t *testing.T) {
|
||||||
}
|
}
|
||||||
root, _ := state.Commit(0, true, false)
|
root, _ := state.Commit(0, true, false)
|
||||||
// Init phase done, create two states, one with snap and one without
|
// Init phase done, create two states, one with snap and one without
|
||||||
fastState, _ := New(root, NewDatabase(tdb, nil).WithSnapshot(snaps))
|
fastState, _ := New(root, NewMPTDatabase(tdb, nil).WithSnapshot(snaps))
|
||||||
slowState, _ := New(root, NewDatabase(tdb, nil))
|
slowState, _ := New(root, NewMPTDatabase(tdb, nil))
|
||||||
|
|
||||||
obj := fastState.getOrNewStateObject(addr)
|
obj := fastState.getOrNewStateObject(addr)
|
||||||
storageRoot := obj.data.Root
|
storageRoot := obj.data.Root
|
||||||
|
|
@ -1368,3 +1412,38 @@ func TestStorageDirtiness(t *testing.T) {
|
||||||
state.RevertToSnapshot(snap)
|
state.RevertToSnapshot(snap)
|
||||||
checkDirty(common.Hash{0x1}, common.Hash{0x1}, true)
|
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/crypto"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||||
"github.com/ethereum/go-ethereum/triedb"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// contractCode represents contract bytecode along with its associated metadata.
|
// ContractCode represents contract bytecode mutation along with its
|
||||||
type contractCode struct {
|
// associated metadata.
|
||||||
hash common.Hash // hash is the cryptographic hash of the current contract code.
|
type ContractCode struct {
|
||||||
blob []byte // blob is the binary representation of the current contract code.
|
Hash common.Hash // Hash is the cryptographic hash of the current contract code.
|
||||||
originHash common.Hash // originHash is the cryptographic hash of the code before mutation.
|
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.
|
// Derived fields, populated only when state tracking is enabled.
|
||||||
duplicate bool // duplicate indicates whether the updated code already exists.
|
Duplicate bool // Duplicate indicates whether the updated code already exists.
|
||||||
originBlob []byte // originBlob is the original binary representation of the contract code.
|
OriginBlob []byte // OriginBlob is the original binary representation of the contract code.
|
||||||
}
|
}
|
||||||
|
|
||||||
// accountDelete represents an operation for deleting an Ethereum account.
|
// AccountDelete represents a deletion operation for an Ethereum account.
|
||||||
type accountDelete struct {
|
type AccountDelete struct {
|
||||||
address common.Address // address is the unique account identifier
|
Address common.Address // Address uniquely identifies the account.
|
||||||
origin []byte // origin is the original value of account data in slim-RLP encoding.
|
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.
|
||||||
// storages stores mutated slots, the value should be nil.
|
StoragesOrigin map[common.Hash]common.Hash // StoragesOrigin holds original values of mutated slots; keys are hashes of raw storage slot keys.
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// accountUpdate represents an operation for updating an Ethereum account.
|
// AccountUpdate represents an update operation for an Ethereum account.
|
||||||
type accountUpdate struct {
|
type AccountUpdate struct {
|
||||||
address common.Address // address is the unique account identifier
|
Address common.Address // Address uniquely identifies the account.
|
||||||
data []byte // data is the slim-RLP encoded account data.
|
Data *types.StateAccount // Data is the updated account state; nil indicates deletion.
|
||||||
origin []byte // origin is the original value of account data in slim-RLP encoding.
|
Origin *types.StateAccount // Origin is the previous account state; nil indicates non-existence.
|
||||||
code *contractCode // code represents mutated contract code; nil means it's not modified.
|
Code *ContractCode // Code contains updated contract code; nil if unchanged.
|
||||||
storages map[common.Hash][]byte // storages stores mutated slots in prefix-zero-trimmed RLP format.
|
Storages map[common.Hash]common.Hash // Storages contains updated storage slots.
|
||||||
|
|
||||||
// storagesOriginByKey and storagesOriginByHash both store the original values
|
// StoragesOriginByKey and StoragesOriginByHash both record original values
|
||||||
// of mutated slots in prefix-zero-trimmed RLP format. The difference is that
|
// of mutated storage slots:
|
||||||
// storagesOriginByKey uses the **raw** storage slot key as the map ID, while
|
// - StoragesOriginByKey uses raw storage slot keys.
|
||||||
// storagesOriginByHash uses the **hash** of the storage slot key instead.
|
// - StoragesOriginByHash uses hashed storage slot keys.
|
||||||
storagesOriginByKey map[common.Hash][]byte
|
StoragesOriginByKey map[common.Hash]common.Hash
|
||||||
storagesOriginByHash map[common.Hash][]byte
|
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,
|
// execution. It contains information about mutated contract codes, accounts,
|
||||||
// and storage slots, along with their original values.
|
// and storage slots, along with their original values.
|
||||||
type stateUpdate struct {
|
type StateUpdate struct {
|
||||||
originRoot common.Hash // hash of the state before applying mutation
|
OriginRoot common.Hash // Hash of the state before applying mutation
|
||||||
root common.Hash // hash of the state after applying mutation
|
Root common.Hash // Hash of the state after applying mutation
|
||||||
blockNumber uint64 // Associated block number
|
BlockNumber uint64 // Associated block number
|
||||||
|
|
||||||
accounts map[common.Hash][]byte // accounts stores mutated accounts in 'slim RLP' encoding
|
// Accounts contains mutated accounts, keyed by address hash.
|
||||||
accountsOrigin map[common.Address][]byte // accountsOrigin stores the original values of mutated accounts in 'slim RLP' encoding
|
Accounts map[common.Hash]*types.StateAccount
|
||||||
|
|
||||||
// storages stores mutated slots in 'prefix-zero-trimmed' RLP format.
|
// Storages contains mutated storage slots, keyed by address
|
||||||
// The value is keyed by account hash and **storage slot key hash**.
|
// hash and storage slot key hash.
|
||||||
storages map[common.Hash]map[common.Hash][]byte
|
Storages map[common.Hash]map[common.Hash]common.Hash
|
||||||
|
|
||||||
// storagesOrigin stores the original values of mutated slots in
|
// AccountsOrigin holds the original values of mutated accounts, keyed by address.
|
||||||
// 'prefix-zero-trimmed' RLP format.
|
AccountsOrigin map[common.Address]*types.StateAccount
|
||||||
// (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
|
|
||||||
|
|
||||||
codes map[common.Address]*contractCode // codes contains the set of dirty codes
|
// StoragesOrigin holds the original values of mutated storage slots.
|
||||||
nodes *trienode.MergedNodeSet // Aggregated dirty nodes caused by state changes
|
// 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.
|
// Empty returns a flag indicating the state transition is empty or not.
|
||||||
func (sc *stateUpdate) empty() bool {
|
func (sc *StateUpdate) Empty() bool {
|
||||||
return sc.originRoot == sc.root
|
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
|
// between two states through state execution. It combines the specified account
|
||||||
// deletions and account updates to create a complete state update.
|
// deletions and account updates to create a complete state update.
|
||||||
//
|
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 {
|
||||||
// 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 {
|
|
||||||
var (
|
var (
|
||||||
accounts = make(map[common.Hash][]byte)
|
accounts = make(map[common.Hash]*types.StateAccount)
|
||||||
accountsOrigin = make(map[common.Address][]byte)
|
accountsOrigin = make(map[common.Address]*types.StateAccount)
|
||||||
storages = make(map[common.Hash]map[common.Hash][]byte)
|
storages = make(map[common.Hash]map[common.Hash]common.Hash)
|
||||||
storagesOrigin = make(map[common.Address]map[common.Hash][]byte)
|
storagesOrigin = make(map[common.Address]map[common.Hash]common.Hash)
|
||||||
codes = make(map[common.Address]*contractCode)
|
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.
|
// block, deletions must be aggregated first.
|
||||||
for addrHash, op := range deletes {
|
for addrHash, op := range deletes {
|
||||||
addr := op.address
|
addr := op.Address
|
||||||
accounts[addrHash] = nil
|
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 {
|
||||||
if len(op.storages) > 0 {
|
storages[addrHash] = op.Storages
|
||||||
storages[addrHash] = op.storages
|
|
||||||
}
|
}
|
||||||
if len(op.storagesOrigin) > 0 {
|
if len(op.StoragesOrigin) > 0 {
|
||||||
storagesOrigin[addr] = op.storagesOrigin
|
storagesOrigin[addr] = op.StoragesOrigin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Aggregate account updates then.
|
// Aggregate account updates then.
|
||||||
for addrHash, op := range updates {
|
for addrHash, op := range updates {
|
||||||
// Aggregate dirty contract codes if they are available.
|
// Aggregate dirty contract codes if they are available.
|
||||||
addr := op.address
|
addr := op.Address
|
||||||
if op.code != nil {
|
if op.Code != nil {
|
||||||
codes[addr] = op.code
|
codes[addr] = op.Code
|
||||||
}
|
}
|
||||||
accounts[addrHash] = op.data
|
accounts[addrHash] = op.Data
|
||||||
|
|
||||||
// Aggregate the account original value. If the account is already
|
// 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 {
|
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
|
// Aggregate the storage mutation list. If a slot in op.storages is
|
||||||
// already present in aggregated storages set, the value will be
|
// already present in aggregated storages set, the value will be
|
||||||
// overwritten.
|
// overwritten.
|
||||||
if len(op.storages) > 0 {
|
if len(op.Storages) > 0 {
|
||||||
if _, exist := storages[addrHash]; !exist {
|
if _, exist := storages[addrHash]; !exist {
|
||||||
storages[addrHash] = op.storages
|
storages[addrHash] = op.Storages
|
||||||
} else {
|
} else {
|
||||||
maps.Copy(storages[addrHash], op.storages)
|
maps.Copy(storages[addrHash], op.Storages)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Aggregate the storage original values. If the slot is already present
|
// Aggregate the storage original values. If the slot is already present
|
||||||
// in aggregated storagesOrigin set, skip it.
|
// in aggregated StoragesOrigin set, skip it.
|
||||||
storageOriginSet := op.storagesOriginByHash
|
storageOriginSet := op.StoragesOriginByHash
|
||||||
if rawStorageKey {
|
if typ == StorageKeyPlain {
|
||||||
storageOriginSet = op.storagesOriginByKey
|
storageOriginSet = op.StoragesOriginByKey
|
||||||
}
|
}
|
||||||
if len(storageOriginSet) > 0 {
|
if len(storageOriginSet) > 0 {
|
||||||
origin, exist := storagesOrigin[addr]
|
origin, exist := storagesOrigin[addr]
|
||||||
|
|
@ -173,32 +177,114 @@ func newStateUpdate(rawStorageKey bool, originRoot common.Hash, root common.Hash
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &stateUpdate{
|
return &StateUpdate{
|
||||||
originRoot: originRoot,
|
OriginRoot: originRoot,
|
||||||
root: root,
|
Root: root,
|
||||||
blockNumber: blockNumber,
|
BlockNumber: blockNumber,
|
||||||
accounts: accounts,
|
Accounts: accounts,
|
||||||
accountsOrigin: accountsOrigin,
|
AccountsOrigin: accountsOrigin,
|
||||||
storages: storages,
|
Storages: storages,
|
||||||
storagesOrigin: storagesOrigin,
|
StoragesOrigin: storagesOrigin,
|
||||||
rawStorageKey: rawStorageKey,
|
StorageKeyType: typ,
|
||||||
codes: codes,
|
Codes: codes,
|
||||||
nodes: nodes,
|
Nodes: nodes,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// stateSet converts the current stateUpdate object into a triedb.StateSet
|
// encodeSlot encodes the storage slot value by trimming all leading zeros
|
||||||
// object. This function extracts the necessary data from the stateUpdate
|
// and then RLP-encoding the result.
|
||||||
// struct and formats it into the StateSet structure consumed by the triedb
|
func encodeSlot(value common.Hash) []byte {
|
||||||
// package.
|
if value == (common.Hash{}) {
|
||||||
func (sc *stateUpdate) stateSet() *triedb.StateSet {
|
return nil
|
||||||
return &triedb.StateSet{
|
|
||||||
Accounts: sc.accounts,
|
|
||||||
AccountsOrigin: sc.accountsOrigin,
|
|
||||||
Storages: sc.storages,
|
|
||||||
StoragesOrigin: sc.storagesOrigin,
|
|
||||||
RawStorageKey: sc.rawStorageKey,
|
|
||||||
}
|
}
|
||||||
|
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
|
// 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
|
// Note: This operation is expensive and not needed during normal state
|
||||||
// transitions. It is only required when SizeTracker or StateUpdate hook
|
// transitions. It is only required when SizeTracker or StateUpdate hook
|
||||||
// is enabled to produce accurate state statistics.
|
// 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)
|
cache := make(map[common.Hash]bool)
|
||||||
for addr, code := range sc.codes {
|
for addr, code := range sc.Codes {
|
||||||
if code.originHash != types.EmptyCodeHash {
|
if code.OriginHash != types.EmptyCodeHash {
|
||||||
blob := reader.Code(addr, code.originHash)
|
blob := reader.Code(addr, code.OriginHash)
|
||||||
if len(blob) == 0 {
|
if len(blob) == 0 {
|
||||||
return fmt.Errorf("original code of %x is empty", addr)
|
return fmt.Errorf("original code of %x is empty", addr)
|
||||||
}
|
}
|
||||||
code.originBlob = blob
|
code.OriginBlob = blob
|
||||||
}
|
}
|
||||||
if exists, ok := cache[code.hash]; ok {
|
if exists, ok := cache[code.Hash]; ok {
|
||||||
code.duplicate = exists
|
code.Duplicate = exists
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
res := reader.Has(addr, code.hash)
|
res := reader.Has(addr, code.Hash)
|
||||||
cache[code.hash] = res
|
cache[code.Hash] = res
|
||||||
code.duplicate = res
|
code.Duplicate = res
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToTracingUpdate converts the internal stateUpdate to an exported tracing.StateUpdate.
|
// ToTracingUpdate converts the internal StateUpdate to an exported tracing.StateUpdate.
|
||||||
func (sc *stateUpdate) ToTracingUpdate() (*tracing.StateUpdate, error) {
|
func (sc *StateUpdate) ToTracingUpdate() (*tracing.StateUpdate, error) {
|
||||||
update := &tracing.StateUpdate{
|
update := &tracing.StateUpdate{
|
||||||
OriginRoot: sc.originRoot,
|
OriginRoot: sc.OriginRoot,
|
||||||
Root: sc.root,
|
Root: sc.Root,
|
||||||
BlockNumber: sc.blockNumber,
|
BlockNumber: sc.BlockNumber,
|
||||||
AccountChanges: make(map[common.Address]*tracing.AccountChange, len(sc.accountsOrigin)),
|
AccountChanges: make(map[common.Address]*tracing.AccountChange, len(sc.AccountsOrigin)),
|
||||||
StorageChanges: make(map[common.Address]map[common.Hash]*tracing.StorageChange),
|
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),
|
TrieChanges: make(map[common.Hash]map[string]*tracing.TrieNodeChange),
|
||||||
}
|
}
|
||||||
// Gather all account changes
|
// Gather all account changes
|
||||||
for addr, oldData := range sc.accountsOrigin {
|
for addr, oldData := range sc.AccountsOrigin {
|
||||||
addrHash := crypto.Keccak256Hash(addr.Bytes())
|
addrHash := crypto.Keccak256Hash(addr.Bytes())
|
||||||
newData, exists := sc.accounts[addrHash]
|
newData, exists := sc.Accounts[addrHash]
|
||||||
if !exists {
|
if !exists {
|
||||||
return nil, fmt.Errorf("account %x not found", addr)
|
return nil, fmt.Errorf("account %x not found", addr)
|
||||||
}
|
}
|
||||||
change := &tracing.AccountChange{}
|
change := &tracing.AccountChange{
|
||||||
|
Prev: oldData,
|
||||||
if len(oldData) > 0 {
|
New: newData,
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
update.AccountChanges[addr] = change
|
update.AccountChanges[addr] = change
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gather all storage slot changes
|
// Gather all storage slot changes
|
||||||
for addr, slots := range sc.storagesOrigin {
|
for addr, slots := range sc.StoragesOrigin {
|
||||||
addrHash := crypto.Keccak256Hash(addr.Bytes())
|
addrHash := crypto.Keccak256Hash(addr.Bytes())
|
||||||
subset, exists := sc.storages[addrHash]
|
subset, exists := sc.Storages[addrHash]
|
||||||
if !exists {
|
if !exists {
|
||||||
return nil, fmt.Errorf("storage %x not found", addr)
|
return nil, fmt.Errorf("storage %x not found", addr)
|
||||||
}
|
}
|
||||||
storageChanges := make(map[common.Hash]*tracing.StorageChange, len(slots))
|
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
|
// Get new value - handle both raw and hashed key formats
|
||||||
var (
|
var (
|
||||||
exists bool
|
exists bool
|
||||||
encNew []byte
|
newData common.Hash
|
||||||
decPrev []byte
|
|
||||||
decNew []byte
|
|
||||||
err error
|
|
||||||
)
|
)
|
||||||
if sc.rawStorageKey {
|
if sc.StorageKeyType == StorageKeyPlain {
|
||||||
encNew, exists = subset[crypto.Keccak256Hash(key.Bytes())]
|
newData, exists = subset[crypto.Keccak256Hash(key.Bytes())]
|
||||||
} else {
|
} else {
|
||||||
encNew, exists = subset[key]
|
newData, exists = subset[key]
|
||||||
}
|
}
|
||||||
if !exists {
|
if !exists {
|
||||||
return nil, fmt.Errorf("storage slot %x-%x not found", addr, key)
|
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{
|
storageChanges[key] = &tracing.StorageChange{
|
||||||
Prev: common.BytesToHash(decPrev),
|
Prev: oldData,
|
||||||
New: common.BytesToHash(decNew),
|
New: newData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
update.StorageChanges[addr] = storageChanges
|
update.StorageChanges[addr] = storageChanges
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gather all contract code changes
|
// Gather all contract code changes
|
||||||
for addr, code := range sc.codes {
|
for addr, code := range sc.Codes {
|
||||||
change := &tracing.CodeChange{
|
change := &tracing.CodeChange{
|
||||||
New: &tracing.ContractCode{
|
New: &tracing.ContractCode{
|
||||||
Hash: code.hash,
|
Hash: code.Hash,
|
||||||
Code: code.blob,
|
Code: code.Blob,
|
||||||
Exists: code.duplicate,
|
Exists: code.Duplicate,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if code.originHash != types.EmptyCodeHash {
|
if code.OriginHash != types.EmptyCodeHash {
|
||||||
change.Prev = &tracing.ContractCode{
|
change.Prev = &tracing.ContractCode{
|
||||||
Hash: code.originHash,
|
Hash: code.OriginHash,
|
||||||
Code: code.originBlob,
|
Code: code.OriginBlob,
|
||||||
Exists: true,
|
Exists: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -343,8 +390,8 @@ func (sc *stateUpdate) ToTracingUpdate() (*tracing.StateUpdate, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gather all trie node changes
|
// Gather all trie node changes
|
||||||
if sc.nodes != nil {
|
if sc.Nodes != nil {
|
||||||
for owner, subset := range sc.nodes.Sets {
|
for owner, subset := range sc.Nodes.Sets {
|
||||||
nodeChanges := make(map[string]*tracing.TrieNodeChange, len(subset.Origins))
|
nodeChanges := make(map[string]*tracing.TrieNodeChange, len(subset.Origins))
|
||||||
for path, oldNode := range subset.Origins {
|
for path, oldNode := range subset.Origins {
|
||||||
newNode, exists := subset.Nodes[path]
|
newNode, exists := subset.Nodes[path]
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,13 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type transientStorageKey struct {
|
||||||
|
addr common.Address
|
||||||
|
key common.Hash
|
||||||
|
}
|
||||||
|
|
||||||
// transientStorage is a representation of EIP-1153 "Transient Storage".
|
// 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.
|
// newTransientStorage creates a new instance of a transientStorage.
|
||||||
func newTransientStorage() transientStorage {
|
func newTransientStorage() transientStorage {
|
||||||
|
|
@ -35,52 +40,43 @@ func newTransientStorage() transientStorage {
|
||||||
|
|
||||||
// Set sets the transient-storage `value` for `key` at the given `addr`.
|
// Set sets the transient-storage `value` for `key` at the given `addr`.
|
||||||
func (t transientStorage) Set(addr common.Address, key, value common.Hash) {
|
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 value == (common.Hash{}) { // this is a 'delete'
|
||||||
if _, ok := t[addr]; ok {
|
delete(t, tsKey)
|
||||||
delete(t[addr], key)
|
|
||||||
if len(t[addr]) == 0 {
|
|
||||||
delete(t, addr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if _, ok := t[addr]; !ok {
|
t[tsKey] = value
|
||||||
t[addr] = make(Storage)
|
|
||||||
}
|
|
||||||
t[addr][key] = value
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get gets the transient storage for `key` at the given `addr`.
|
// Get gets the transient storage for `key` at the given `addr`.
|
||||||
func (t transientStorage) Get(addr common.Address, key common.Hash) common.Hash {
|
func (t transientStorage) Get(addr common.Address, key common.Hash) common.Hash {
|
||||||
val, ok := t[addr]
|
tsKey := transientStorageKey{addr: addr, key: key}
|
||||||
if !ok {
|
return t[tsKey]
|
||||||
return common.Hash{}
|
|
||||||
}
|
|
||||||
return val[key]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy does a deep copy of the transientStorage
|
// Copy does a deep copy of the transientStorage
|
||||||
func (t transientStorage) Copy() transientStorage {
|
func (t transientStorage) Copy() transientStorage {
|
||||||
storage := make(transientStorage)
|
return maps.Clone(t)
|
||||||
for key, value := range t {
|
|
||||||
storage[key] = value.Copy()
|
|
||||||
}
|
|
||||||
return storage
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrettyPrint prints the contents of the access list in a human-readable form
|
// PrettyPrint prints the contents of the access list in a human-readable form
|
||||||
func (t transientStorage) PrettyPrint() string {
|
func (t transientStorage) PrettyPrint() string {
|
||||||
out := new(strings.Builder)
|
out := new(strings.Builder)
|
||||||
sortedAddrs := slices.Collect(maps.Keys(t))
|
sortedTSKeys := slices.Collect(maps.Keys(t))
|
||||||
slices.SortFunc(sortedAddrs, common.Address.Cmp)
|
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 {
|
for i := 0; i < len(sortedTSKeys); {
|
||||||
fmt.Fprintf(out, "%#x:", addr)
|
tsKey := sortedTSKeys[i]
|
||||||
storage := t[addr]
|
fmt.Fprintf(out, "%#x:", tsKey.addr)
|
||||||
sortedKeys := slices.Collect(maps.Keys(storage))
|
for ; i < len(sortedTSKeys) && sortedTSKeys[i].addr == tsKey.addr; i++ {
|
||||||
slices.SortFunc(sortedKeys, common.Hash.Cmp)
|
tsKey2 := sortedTSKeys[i]
|
||||||
for _, key := range sortedKeys {
|
fmt.Fprintf(out, " %X : %X\n", tsKey2.key, t[tsKey2])
|
||||||
fmt.Fprintf(out, " %X : %X\n", key, storage[key])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return out.String()
|
return out.String()
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ var (
|
||||||
//
|
//
|
||||||
// Note, the prefetcher's API is not thread safe.
|
// Note, the prefetcher's API is not thread safe.
|
||||||
type triePrefetcher struct {
|
type triePrefetcher struct {
|
||||||
verkle bool // Flag whether the prefetcher is in verkle mode
|
isUBT bool // Flag whether the prefetcher is in UBT mode
|
||||||
db Database // Database to fetch trie nodes through
|
db Database // Database to fetch trie nodes through
|
||||||
root common.Hash // Root hash of the account trie for metrics
|
root common.Hash // Root hash of the account trie for metrics
|
||||||
fetchers map[string]*subfetcher // Subfetchers for each trie
|
fetchers map[string]*subfetcher // Subfetchers for each trie
|
||||||
|
|
@ -67,7 +67,7 @@ type triePrefetcher struct {
|
||||||
func newTriePrefetcher(db Database, root common.Hash, namespace string, noreads bool) *triePrefetcher {
|
func newTriePrefetcher(db Database, root common.Hash, namespace string, noreads bool) *triePrefetcher {
|
||||||
prefix := triePrefetchMetricsPrefix + namespace
|
prefix := triePrefetchMetricsPrefix + namespace
|
||||||
return &triePrefetcher{
|
return &triePrefetcher{
|
||||||
verkle: db.TrieDB().IsVerkle(),
|
isUBT: db.Type().Is(TypeUBT),
|
||||||
db: db,
|
db: db,
|
||||||
root: root,
|
root: root,
|
||||||
fetchers: make(map[string]*subfetcher), // Active prefetchers use the fetchers map
|
fetchers: make(map[string]*subfetcher), // Active prefetchers use the fetchers map
|
||||||
|
|
@ -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.
|
// trieID returns an unique trie identifier consists the trie owner and root hash.
|
||||||
func (p *triePrefetcher) trieID(owner common.Hash, root common.Hash) string {
|
func (p *triePrefetcher) trieID(owner common.Hash, root common.Hash) string {
|
||||||
// The trie in verkle is only identified by state root
|
// The trie in ubt is only identified by state root
|
||||||
if p.verkle {
|
if p.isUBT {
|
||||||
return p.root.Hex()
|
return p.root.Hex()
|
||||||
}
|
}
|
||||||
// The trie in merkle is either identified by state root (account trie),
|
// 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.
|
// openTrie resolves the target trie from database for prefetching.
|
||||||
func (sf *subfetcher) openTrie() error {
|
func (sf *subfetcher) openTrie() error {
|
||||||
// Open the verkle tree if the sub-fetcher is in verkle mode. Note, there is
|
// Open the ubt tree if the sub-fetcher is in ubt mode. Note, there is
|
||||||
// only a single fetcher for verkle.
|
// only a single fetcher for ubt.
|
||||||
if sf.db.TrieDB().IsVerkle() {
|
if sf.db.Type().Is(TypeUBT) {
|
||||||
tr, err := sf.db.OpenTrie(sf.state)
|
tr, err := sf.db.OpenTrie(sf.state)
|
||||||
if err != nil {
|
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
|
return err
|
||||||
}
|
}
|
||||||
sf.trie = tr
|
sf.trie = tr
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ func TestUseAfterTerminate(t *testing.T) {
|
||||||
|
|
||||||
func TestVerklePrefetcher(t *testing.T) {
|
func TestVerklePrefetcher(t *testing.T) {
|
||||||
disk := rawdb.NewMemoryDatabase()
|
disk := rawdb.NewMemoryDatabase()
|
||||||
db := triedb.NewDatabase(disk, triedb.VerkleDefaults)
|
db := triedb.NewDatabase(disk, triedb.UBTDefaults)
|
||||||
sdb := NewDatabase(db, nil)
|
sdb := NewDatabase(db, nil)
|
||||||
|
|
||||||
state, err := New(types.EmptyRootHash, sdb)
|
state, err := New(types.EmptyRootHash, sdb)
|
||||||
|
|
@ -86,18 +86,17 @@ func TestVerklePrefetcher(t *testing.T) {
|
||||||
root, _ := state.Commit(0, true, false)
|
root, _ := state.Commit(0, true, false)
|
||||||
|
|
||||||
state, _ = New(root, sdb)
|
state, _ = New(root, sdb)
|
||||||
sRoot := state.GetStorageRoot(addr)
|
|
||||||
fetcher := newTriePrefetcher(sdb, root, "", false)
|
fetcher := newTriePrefetcher(sdb, root, "", false)
|
||||||
|
|
||||||
// Read account
|
// Read account
|
||||||
fetcher.prefetch(common.Hash{}, root, common.Address{}, []common.Address{addr}, nil, false)
|
fetcher.prefetch(common.Hash{}, root, common.Address{}, []common.Address{addr}, nil, false)
|
||||||
|
|
||||||
// Read storage slot
|
// 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)
|
fetcher.terminate(false)
|
||||||
accountTrie := fetcher.trie(common.Hash{}, root)
|
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()
|
rootA := accountTrie.Hash()
|
||||||
rootB := storageTrie.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
|
// Execute the message to preload the implicit touched states
|
||||||
evm := vm.NewEVM(NewEVMBlockContext(header, p.chain, nil), stateCpy, p.config, cfg)
|
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
|
// Convert the transaction into an executable message and pre-cache its sender
|
||||||
msg, err := TransactionToMessage(tx, signer, header.BaseFee)
|
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
|
// Disable the nonce check
|
||||||
msg.SkipNonceChecks = true
|
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
|
// We attempt to apply a transaction. The goal is not to execute
|
||||||
// the transaction successfully, rather to warm up touched data slots.
|
// the transaction successfully, rather to warm up touched data slots.
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/consensus"
|
||||||
"github.com/ethereum/go-ethereum/consensus/misc"
|
"github.com/ethereum/go-ethereum/consensus/misc"
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"github.com/ethereum/go-ethereum/core/tracing"
|
"github.com/ethereum/go-ethereum/core/tracing"
|
||||||
|
|
@ -30,6 +31,8 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/internal/telemetry"
|
"github.com/ethereum/go-ethereum/internal/telemetry"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"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
|
// 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) {
|
func (p *StateProcessor) Process(ctx context.Context, block *types.Block, statedb *state.StateDB, cfg vm.Config) (*ProcessResult, error) {
|
||||||
var (
|
var (
|
||||||
config = p.chainConfig()
|
config = p.chainConfig()
|
||||||
receipts types.Receipts
|
receipts = make(types.Receipts, 0, len(block.Transactions()))
|
||||||
header = block.Header()
|
header = block.Header()
|
||||||
blockHash = block.Hash()
|
blockHash = block.Hash()
|
||||||
blockNumber = block.Number()
|
blockNumber = block.Number()
|
||||||
|
|
@ -73,34 +76,25 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated
|
||||||
if hooks := cfg.Tracer; hooks != nil {
|
if hooks := cfg.Tracer; hooks != nil {
|
||||||
tracingStateDB = state.NewHookedState(statedb, hooks)
|
tracingStateDB = state.NewHookedState(statedb, hooks)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mutate the block and state according to any hard-fork specs
|
// Mutate the block and state according to any hard-fork specs
|
||||||
if config.DAOForkSupport && config.DAOForkBlock != nil && config.DAOForkBlock.Cmp(block.Number()) == 0 {
|
if config.DAOForkSupport && config.DAOForkBlock != nil && config.DAOForkBlock.Cmp(block.Number()) == 0 {
|
||||||
misc.ApplyDAOHardFork(tracingStateDB)
|
misc.ApplyDAOHardFork(tracingStateDB)
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
context vm.BlockContext
|
context = NewEVMBlockContext(header, p.chain, nil)
|
||||||
signer = types.MakeSigner(config, header.Number, header.Time)
|
signer = types.MakeSigner(config, header.Number, header.Time)
|
||||||
|
evm = vm.NewEVM(context, tracingStateDB, config, cfg)
|
||||||
)
|
)
|
||||||
|
defer evm.Release()
|
||||||
// Apply pre-execution system calls.
|
// Run the pre-execution system calls
|
||||||
context = NewEVMBlockContext(header, p.chain, nil)
|
PreExecution(ctx, block.BeaconRoot(), block.ParentHash(), config, evm, block.Number(), block.Time())
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Iterate over and process the individual transactions
|
// Iterate over and process the individual transactions
|
||||||
for i, tx := range block.Transactions() {
|
for i, tx := range block.Transactions() {
|
||||||
msg, err := TransactionToMessage(tx, signer, header.BaseFee)
|
msg, err := TransactionToMessage(tx, signer, header.BaseFee)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
|
return nil, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
|
||||||
}
|
}
|
||||||
statedb.SetTxContext(tx.Hash(), i)
|
statedb.SetTxContext(tx.Hash(), i, uint32(i+1))
|
||||||
_, _, spanEnd := telemetry.StartSpan(ctx, "core.ApplyTransactionWithEVM",
|
_, _, spanEnd := telemetry.StartSpan(ctx, "core.ApplyTransactionWithEVM",
|
||||||
telemetry.StringAttribute("tx.hash", tx.Hash().Hex()),
|
telemetry.StringAttribute("tx.hash", tx.Hash().Hex()),
|
||||||
telemetry.Int64Attribute("tx.index", int64(i)),
|
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...)
|
allLogs = append(allLogs, receipt.Logs...)
|
||||||
spanEnd(nil)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
|
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
|
||||||
p.chain.Engine().Finalize(p.chain, header, tracingStateDB, block.Body())
|
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
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// postExecution processes the post-execution system calls if Prague is enabled.
|
// PreExecution processes pre-execution system calls.
|
||||||
func postExecution(ctx context.Context, config *params.ChainConfig, block *types.Block, allLogs []*types.Log, evm *vm.EVM) (requests [][]byte, err error) {
|
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")
|
_, _, spanEnd := telemetry.StartSpan(ctx, "core.postExecution")
|
||||||
defer spanEnd(&err)
|
defer spanEnd(&err)
|
||||||
|
|
||||||
// Read requests if Prague is enabled.
|
// Read requests if Prague is enabled.
|
||||||
if config.IsPrague(block.Number(), block.Time()) {
|
if config.IsPrague(number, time) {
|
||||||
requests = [][]byte{}
|
requests = [][]byte{}
|
||||||
// EIP-6110
|
// EIP-6110
|
||||||
if err := ParseDepositLogs(&requests, allLogs, config); err != nil {
|
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
|
// EIP-7002
|
||||||
if err := ProcessWithdrawalQueue(&requests, evm); err != nil {
|
if err := ProcessWithdrawalQueue(&requests, evm, blockAccessIndex); err != nil {
|
||||||
return requests, fmt.Errorf("failed to process withdrawal queue: %w", err)
|
return nil, fmt.Errorf("failed to process withdrawal queue: %w", err)
|
||||||
}
|
}
|
||||||
// EIP-7251
|
// EIP-7251
|
||||||
if err := ProcessConsolidationQueue(&requests, evm); err != nil {
|
if err := ProcessConsolidationQueue(&requests, evm, blockAccessIndex); err != nil {
|
||||||
return requests, fmt.Errorf("failed to process consolidation queue: %w", err)
|
return nil, fmt.Errorf("failed to process consolidation queue: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return requests, nil
|
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
|
// Merge the tx-local access event into the "block-local" one, in order to collect
|
||||||
// all values, so that the witness can be built.
|
// 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)
|
statedb.AccessEvents().Merge(evm.AccessEvents)
|
||||||
}
|
}
|
||||||
return MakeReceipt(evm, result, statedb, blockNumber, blockHash, blockTime, tx, gp.CumulativeUsed(), root), nil
|
return MakeReceipt(evm, result, statedb, blockNumber, blockHash, blockTime, tx, gp.CumulativeUsed(), root), nil
|
||||||
|
|
@ -251,15 +260,16 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, evm *vm.EVM) {
|
||||||
msg := &Message{
|
msg := &Message{
|
||||||
From: params.SystemAddress,
|
From: params.SystemAddress,
|
||||||
GasLimit: 30_000_000,
|
GasLimit: 30_000_000,
|
||||||
GasPrice: common.Big0,
|
GasPrice: uint256.NewInt(0),
|
||||||
GasFeeCap: common.Big0,
|
GasFeeCap: uint256.NewInt(0),
|
||||||
GasTipCap: common.Big0,
|
GasTipCap: uint256.NewInt(0),
|
||||||
To: ¶ms.BeaconRootsAddress,
|
To: ¶ms.BeaconRootsAddress,
|
||||||
Data: beaconRoot[:],
|
Data: beaconRoot[:],
|
||||||
}
|
}
|
||||||
evm.SetTxContext(NewEVMTxContext(msg))
|
evm.SetTxContext(NewEVMTxContext(msg))
|
||||||
|
evm.StateDB.SetTxContext(common.Hash{}, 0, 0)
|
||||||
evm.StateDB.AddAddressToAccessList(params.BeaconRootsAddress)
|
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 {
|
if evm.StateDB.AccessEvents() != nil {
|
||||||
evm.StateDB.AccessEvents().Merge(evm.AccessEvents)
|
evm.StateDB.AccessEvents().Merge(evm.AccessEvents)
|
||||||
}
|
}
|
||||||
|
|
@ -278,15 +288,16 @@ func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM) {
|
||||||
msg := &Message{
|
msg := &Message{
|
||||||
From: params.SystemAddress,
|
From: params.SystemAddress,
|
||||||
GasLimit: 30_000_000,
|
GasLimit: 30_000_000,
|
||||||
GasPrice: common.Big0,
|
GasPrice: uint256.NewInt(0),
|
||||||
GasFeeCap: common.Big0,
|
GasFeeCap: uint256.NewInt(0),
|
||||||
GasTipCap: common.Big0,
|
GasTipCap: uint256.NewInt(0),
|
||||||
To: ¶ms.HistoryStorageAddress,
|
To: ¶ms.HistoryStorageAddress,
|
||||||
Data: prevHash.Bytes(),
|
Data: prevHash.Bytes(),
|
||||||
}
|
}
|
||||||
evm.SetTxContext(NewEVMTxContext(msg))
|
evm.SetTxContext(NewEVMTxContext(msg))
|
||||||
|
evm.StateDB.SetTxContext(common.Hash{}, 0, 0)
|
||||||
evm.StateDB.AddAddressToAccessList(params.HistoryStorageAddress)
|
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 {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
@ -298,17 +309,17 @@ func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM) {
|
||||||
|
|
||||||
// ProcessWithdrawalQueue calls the EIP-7002 withdrawal queue contract.
|
// ProcessWithdrawalQueue calls the EIP-7002 withdrawal queue contract.
|
||||||
// It returns the opaque request data returned by the contract.
|
// It returns the opaque request data returned by the contract.
|
||||||
func ProcessWithdrawalQueue(requests *[][]byte, evm *vm.EVM) error {
|
func ProcessWithdrawalQueue(requests *[][]byte, evm *vm.EVM, blockAccessIndex uint32) error {
|
||||||
return processRequestsSystemCall(requests, evm, 0x01, params.WithdrawalQueueAddress)
|
return processRequestsSystemCall(requests, evm, 0x01, params.WithdrawalQueueAddress, blockAccessIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProcessConsolidationQueue calls the EIP-7251 consolidation queue contract.
|
// ProcessConsolidationQueue calls the EIP-7251 consolidation queue contract.
|
||||||
// It returns the opaque request data returned by the contract.
|
// It returns the opaque request data returned by the contract.
|
||||||
func ProcessConsolidationQueue(requests *[][]byte, evm *vm.EVM) error {
|
func ProcessConsolidationQueue(requests *[][]byte, evm *vm.EVM, blockAccessIndex uint32) error {
|
||||||
return processRequestsSystemCall(requests, evm, 0x02, params.ConsolidationQueueAddress)
|
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 {
|
if tracer := evm.Config.Tracer; tracer != nil {
|
||||||
onSystemCallStart(tracer, evm.GetVMContext())
|
onSystemCallStart(tracer, evm.GetVMContext())
|
||||||
if tracer.OnSystemCallEnd != nil {
|
if tracer.OnSystemCallEnd != nil {
|
||||||
|
|
@ -318,14 +329,15 @@ func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte
|
||||||
msg := &Message{
|
msg := &Message{
|
||||||
From: params.SystemAddress,
|
From: params.SystemAddress,
|
||||||
GasLimit: 30_000_000,
|
GasLimit: 30_000_000,
|
||||||
GasPrice: common.Big0,
|
GasPrice: uint256.NewInt(0),
|
||||||
GasFeeCap: common.Big0,
|
GasFeeCap: uint256.NewInt(0),
|
||||||
GasTipCap: common.Big0,
|
GasTipCap: uint256.NewInt(0),
|
||||||
To: &addr,
|
To: &addr,
|
||||||
}
|
}
|
||||||
evm.SetTxContext(NewEVMTxContext(msg))
|
evm.SetTxContext(NewEVMTxContext(msg))
|
||||||
|
evm.StateDB.SetTxContext(common.Hash{}, 0, blockAccessIndex)
|
||||||
evm.StateDB.AddAddressToAccessList(addr)
|
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 {
|
if evm.StateDB.AccessEvents() != nil {
|
||||||
evm.StateDB.AccessEvents().Merge(evm.AccessEvents)
|
evm.StateDB.AccessEvents().Merge(evm.AccessEvents)
|
||||||
}
|
}
|
||||||
|
|
@ -372,3 +384,11 @@ func onSystemCallStart(tracer *tracing.Hooks, ctx *tracing.VMContext) {
|
||||||
tracer.OnSystemCallStart()
|
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.
|
// 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
|
// Set the starting gas for the raw transaction
|
||||||
var gas uint64
|
var gas uint64
|
||||||
if isContractCreation && isHomestead {
|
if isContractCreation && isHomestead {
|
||||||
|
|
@ -89,46 +89,110 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set
|
||||||
nonZeroGas = params.TxDataNonZeroGasEIP2028
|
nonZeroGas = params.TxDataNonZeroGasEIP2028
|
||||||
}
|
}
|
||||||
if (math.MaxUint64-gas)/nonZeroGas < nz {
|
if (math.MaxUint64-gas)/nonZeroGas < nz {
|
||||||
return 0, ErrGasUintOverflow
|
return vm.GasCosts{}, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
gas += nz * nonZeroGas
|
gas += nz * nonZeroGas
|
||||||
|
|
||||||
if (math.MaxUint64-gas)/params.TxDataZeroGas < z {
|
if (math.MaxUint64-gas)/params.TxDataZeroGas < z {
|
||||||
return 0, ErrGasUintOverflow
|
return vm.GasCosts{}, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
gas += z * params.TxDataZeroGas
|
gas += z * params.TxDataZeroGas
|
||||||
|
|
||||||
if isContractCreation && isEIP3860 {
|
if isContractCreation && isEIP3860 {
|
||||||
lenWords := toWordSize(dataLen)
|
lenWords := toWordSize(dataLen)
|
||||||
if (math.MaxUint64-gas)/params.InitCodeWordGas < lenWords {
|
if (math.MaxUint64-gas)/params.InitCodeWordGas < lenWords {
|
||||||
return 0, ErrGasUintOverflow
|
return vm.GasCosts{}, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
gas += lenWords * params.InitCodeWordGas
|
gas += lenWords * params.InitCodeWordGas
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if accessList != nil {
|
if accessList != nil {
|
||||||
gas += uint64(len(accessList)) * params.TxAccessListAddressGas
|
addresses := uint64(len(accessList))
|
||||||
gas += uint64(accessList.StorageKeys()) * params.TxAccessListStorageKeyGas
|
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 {
|
if authList != nil {
|
||||||
gas += uint64(len(authList)) * params.CallNewAccountGas
|
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).
|
// 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 (
|
var (
|
||||||
z = uint64(bytes.Count(data, []byte{0}))
|
tokens uint64
|
||||||
nz = uint64(len(data)) - z
|
tokenCost uint64
|
||||||
tokens = nz*params.TxTokenPerNonZeroByte + z
|
|
||||||
)
|
)
|
||||||
|
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
|
// Check for overflow
|
||||||
if (math.MaxUint64-params.TxGas)/params.TxCostFloorPerToken < tokens {
|
if (math.MaxUint64-params.TxGas)/tokenCost < tokens {
|
||||||
return 0, ErrGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
// Minimum gas required for a transaction based on its data tokens (EIP-7623).
|
// 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.
|
// toWordSize returns the ceiled word size required for init code payment calculation.
|
||||||
|
|
@ -146,14 +210,14 @@ type Message struct {
|
||||||
To *common.Address
|
To *common.Address
|
||||||
From common.Address
|
From common.Address
|
||||||
Nonce uint64
|
Nonce uint64
|
||||||
Value *big.Int
|
Value *uint256.Int
|
||||||
GasLimit uint64
|
GasLimit uint64
|
||||||
GasPrice *big.Int
|
GasPrice *uint256.Int
|
||||||
GasFeeCap *big.Int
|
GasFeeCap *uint256.Int
|
||||||
GasTipCap *big.Int
|
GasTipCap *uint256.Int
|
||||||
Data []byte
|
Data []byte
|
||||||
AccessList types.AccessList
|
AccessList types.AccessList
|
||||||
BlobGasFeeCap *big.Int
|
BlobGasFeeCap *uint256.Int
|
||||||
BlobHashes []common.Hash
|
BlobHashes []common.Hash
|
||||||
SetCodeAuthorizations []types.SetCodeAuthorization
|
SetCodeAuthorizations []types.SetCodeAuthorization
|
||||||
|
|
||||||
|
|
@ -174,32 +238,64 @@ type Message struct {
|
||||||
|
|
||||||
// TransactionToMessage converts a transaction into a Message.
|
// TransactionToMessage converts a transaction into a Message.
|
||||||
func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.Int) (*Message, error) {
|
func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.Int) (*Message, error) {
|
||||||
|
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{
|
msg := &Message{
|
||||||
|
From: from,
|
||||||
Nonce: tx.Nonce(),
|
Nonce: tx.Nonce(),
|
||||||
GasLimit: tx.Gas(),
|
GasLimit: tx.Gas(),
|
||||||
GasPrice: tx.GasPrice(),
|
GasPrice: gasPrice,
|
||||||
GasFeeCap: tx.GasFeeCap(),
|
GasFeeCap: gasFeeCap,
|
||||||
GasTipCap: tx.GasTipCap(),
|
GasTipCap: gasTipCap,
|
||||||
To: tx.To(),
|
To: tx.To(),
|
||||||
Value: tx.Value(),
|
Value: value,
|
||||||
Data: tx.Data(),
|
Data: tx.Data(),
|
||||||
AccessList: tx.AccessList(),
|
AccessList: tx.AccessList(),
|
||||||
SetCodeAuthorizations: tx.SetCodeAuthorizations(),
|
SetCodeAuthorizations: tx.SetCodeAuthorizations(),
|
||||||
SkipNonceChecks: false,
|
SkipNonceChecks: false,
|
||||||
SkipTransactionChecks: false,
|
SkipTransactionChecks: false,
|
||||||
BlobHashes: tx.BlobHashes(),
|
BlobHashes: tx.BlobHashes(),
|
||||||
BlobGasFeeCap: tx.BlobGasFeeCap(),
|
BlobGasFeeCap: blobGasFeeCap,
|
||||||
}
|
}
|
||||||
// If baseFee provided, set gasPrice to effectiveGasPrice.
|
// If baseFee provided, set gasPrice to effectiveGasPrice.
|
||||||
if baseFee != nil {
|
if baseFee != nil {
|
||||||
msg.GasPrice = msg.GasPrice.Add(msg.GasTipCap, baseFee)
|
effectiveGasPrice := new(big.Int).Add(baseFee, txGasTipCap)
|
||||||
if msg.GasPrice.Cmp(msg.GasFeeCap) > 0 {
|
if effectiveGasPrice.Cmp(txGasFeeCap) > 0 {
|
||||||
msg.GasPrice = msg.GasFeeCap
|
effectiveGasPrice = txGasFeeCap
|
||||||
}
|
}
|
||||||
|
// EffectiveGasPrice is already capped by txGasFeeCap, therefore
|
||||||
|
// the overflow check is not required.
|
||||||
|
msg.GasPrice = uint256.MustFromBig(effectiveGasPrice)
|
||||||
}
|
}
|
||||||
var err error
|
return msg, nil
|
||||||
msg.From, err = types.Sender(s, tx)
|
|
||||||
return msg, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApplyMessage computes the new state by applying the given message
|
// 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
|
// 5. Run Script section
|
||||||
// 6. Derive new state root
|
// 6. Derive new state root
|
||||||
type stateTransition struct {
|
type stateTransition struct {
|
||||||
gp *GasPool
|
gp *GasPool
|
||||||
msg *Message
|
msg *Message
|
||||||
gasRemaining uint64
|
initialBudget vm.GasBudget
|
||||||
initialGas uint64
|
gasRemaining vm.GasBudget
|
||||||
state vm.StateDB
|
state vm.StateDB
|
||||||
evm *vm.EVM
|
evm *vm.EVM
|
||||||
}
|
}
|
||||||
|
|
||||||
// newStateTransition initialises and returns a new state transition object.
|
// newStateTransition initialises and returns a new state transition object.
|
||||||
|
|
@ -269,46 +365,70 @@ func (st *stateTransition) to() common.Address {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *stateTransition) buyGas() error {
|
func (st *stateTransition) buyGas() error {
|
||||||
mgval := new(big.Int).SetUint64(st.msg.GasLimit)
|
mgval := new(uint256.Int).SetUint64(st.msg.GasLimit)
|
||||||
mgval.Mul(mgval, st.msg.GasPrice)
|
_, overflow := mgval.MulOverflow(mgval, st.msg.GasPrice)
|
||||||
balanceCheck := new(big.Int).Set(mgval)
|
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 {
|
if st.msg.GasFeeCap != nil {
|
||||||
balanceCheck.SetUint64(st.msg.GasLimit)
|
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 st.evm.ChainConfig().IsCancun(st.evm.Context.BlockNumber, st.evm.Context.Time) {
|
||||||
if blobGas := st.blobGasUsed(); blobGas > 0 {
|
if blobGas := st.blobGasUsed(); blobGas > 0 {
|
||||||
// Check that the user has enough funds to cover blobGasUsed * tx.BlobGasFeeCap
|
// Check that the user has enough funds to cover blobGasUsed * tx.BlobGasFeeCap
|
||||||
blobBalanceCheck := new(big.Int).SetUint64(blobGas)
|
blobBalanceCheck := new(uint256.Int).SetUint64(blobGas)
|
||||||
blobBalanceCheck.Mul(blobBalanceCheck, st.msg.BlobGasFeeCap)
|
if _, overflow := blobBalanceCheck.MulOverflow(blobBalanceCheck, st.msg.BlobGasFeeCap); overflow {
|
||||||
balanceCheck.Add(balanceCheck, blobBalanceCheck)
|
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
|
// Pay for blobGasUsed * actual blob fee
|
||||||
blobFee := new(big.Int).SetUint64(blobGas)
|
blobBaseFee, overflow := uint256.FromBig(st.evm.Context.BlobBaseFee)
|
||||||
blobFee.Mul(blobFee, st.evm.Context.BlobBaseFee)
|
if overflow {
|
||||||
mgval.Add(mgval, blobFee)
|
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 have, want := st.state.GetBalance(st.msg.From), balanceCheck; have.Cmp(want) < 0 {
|
||||||
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 {
|
|
||||||
return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want)
|
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 {
|
if err := st.gp.SubGas(st.msg.GasLimit); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil {
|
if st.evm.Config.Tracer.HasGasHook() {
|
||||||
st.evm.Config.Tracer.OnGasChange(0, st.msg.GasLimit, tracing.GasChangeTxInitialBalance)
|
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.gasRemaining = vm.NewGasBudget(st.msg.GasLimit)
|
||||||
st.initialGas = st.msg.GasLimit
|
st.initialBudget = st.gasRemaining.Copy()
|
||||||
|
|
||||||
mgvalU256, _ := uint256.FromBig(mgval)
|
st.state.SubBalance(st.msg.From, mgval, tracing.BalanceDecreaseGasBuy)
|
||||||
st.state.SubBalance(st.msg.From, mgvalU256, tracing.BalanceDecreaseGasBuy)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -330,9 +450,10 @@ func (st *stateTransition) preCheck() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
isOsaka := st.evm.ChainConfig().IsOsaka(st.evm.Context.BlockNumber, st.evm.Context.Time)
|
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 {
|
if !msg.SkipTransactionChecks {
|
||||||
// Verify tx gas limit does not exceed EIP-7825 cap.
|
// 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)
|
return fmt.Errorf("%w (cap: %d, tx: %d)", ErrGasLimitTooHigh, params.MaxTxGas, msg.GasLimit)
|
||||||
}
|
}
|
||||||
// Make sure the sender is an EOA
|
// 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)
|
// 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
|
skipCheck := st.evm.Config.NoBaseFee && msg.GasFeeCap.BitLen() == 0 && msg.GasTipCap.BitLen() == 0
|
||||||
if !skipCheck {
|
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 {
|
if msg.GasFeeCap.Cmp(msg.GasTipCap) < 0 {
|
||||||
return fmt.Errorf("%w: address %v, maxPriorityFeePerGas: %s, maxFeePerGas: %s", ErrTipAboveFeeCap,
|
return fmt.Errorf("%w: address %v, maxPriorityFeePerGas: %s, maxFeePerGas: %s", ErrTipAboveFeeCap,
|
||||||
msg.From.Hex(), msg.GasTipCap, msg.GasFeeCap)
|
msg.From.Hex(), msg.GasTipCap, msg.GasFeeCap)
|
||||||
}
|
}
|
||||||
// This will panic if baseFee is nil, but basefee presence is verified
|
// This will panic if baseFee is nil, but basefee presence is verified
|
||||||
// as part of header validation.
|
// 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,
|
return fmt.Errorf("%w: address %v, maxFeePerGas: %s, baseFee: %s", ErrFeeCapTooLow,
|
||||||
msg.From.Hex(), msg.GasFeeCap, st.evm.Context.BaseFee)
|
msg.From.Hex(), msg.GasFeeCap, st.evm.Context.BaseFee)
|
||||||
}
|
}
|
||||||
|
|
@ -395,7 +508,7 @@ func (st *stateTransition) preCheck() error {
|
||||||
if !skipCheck {
|
if !skipCheck {
|
||||||
// This will panic if blobBaseFee is nil, but blobBaseFee presence
|
// This will panic if blobBaseFee is nil, but blobBaseFee presence
|
||||||
// is verified as part of header validation.
|
// 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,
|
return fmt.Errorf("%w: address %v blobGasFeeCap: %v, blobBaseFee: %v", ErrBlobFeeCapTooLow,
|
||||||
msg.From.Hex(), msg.BlobGasFeeCap, st.evm.Context.BlobBaseFee)
|
msg.From.Hex(), msg.BlobGasFeeCap, st.evm.Context.BlobBaseFee)
|
||||||
}
|
}
|
||||||
|
|
@ -446,18 +559,21 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
||||||
contractCreation = msg.To == nil
|
contractCreation = msg.To == nil
|
||||||
floorDataGas uint64
|
floorDataGas uint64
|
||||||
)
|
)
|
||||||
|
|
||||||
// Check clauses 4-5, subtract intrinsic gas if everything is correct
|
// 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if st.gasRemaining < gas {
|
prior, sufficient := st.gasRemaining.Charge(cost)
|
||||||
return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining, gas)
|
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)
|
// Gas limit suffices for the floor data cost (EIP-7623)
|
||||||
if rules.IsPrague {
|
if rules.IsPrague {
|
||||||
floorDataGas, err = FloorDataGas(msg.Data)
|
floorDataGas, err = FloorDataGas(rules, msg.Data, msg.AccessList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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)
|
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 {
|
if rules.IsEIP4762 {
|
||||||
st.evm.AccessEvents.AddTxOrigin(msg.From)
|
st.evm.AccessEvents.AddTxOrigin(msg.From)
|
||||||
|
|
@ -479,9 +591,9 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check clause 6
|
// Check clause 6
|
||||||
value, overflow := uint256.FromBig(msg.Value)
|
value := msg.Value
|
||||||
if overflow {
|
if value == nil {
|
||||||
return nil, fmt.Errorf("%w: address %v", ErrInsufficientFundsForTransfer, msg.From.Hex())
|
value = new(uint256.Int)
|
||||||
}
|
}
|
||||||
if !value.IsZero() && !st.evm.Context.CanTransfer(st.state, msg.From, value) {
|
if !value.IsZero() && !st.evm.Context.CanTransfer(st.state, msg.From, value) {
|
||||||
return nil, fmt.Errorf("%w: address %v", ErrInsufficientFundsForTransfer, msg.From.Hex())
|
return nil, fmt.Errorf("%w: address %v", ErrInsufficientFundsForTransfer, msg.From.Hex())
|
||||||
|
|
@ -535,14 +647,14 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
||||||
peakGasUsed := st.gasUsed()
|
peakGasUsed := st.gasUsed()
|
||||||
|
|
||||||
// Compute refund counter, capped to a refund quotient.
|
// Compute refund counter, capped to a refund quotient.
|
||||||
st.gasRemaining += st.calcRefund()
|
st.gasRemaining.Refund(st.calcRefund())
|
||||||
|
|
||||||
if rules.IsPrague {
|
if rules.IsPrague {
|
||||||
// After EIP-7623: Data-heavy transactions pay the floor gas.
|
// After EIP-7623: Data-heavy transactions pay the floor gas.
|
||||||
if st.gasUsed() < floorDataGas {
|
if used := st.gasUsed(); used < floorDataGas {
|
||||||
prev := st.gasRemaining
|
prior, _ := st.gasRemaining.Charge(vm.GasCosts{RegularGas: floorDataGas - used})
|
||||||
st.gasRemaining = st.initialGas - floorDataGas
|
if st.evm.Config.Tracer.HasGasHook() {
|
||||||
if t := st.evm.Config.Tracer; t != nil && t.OnGasChange != nil {
|
st.evm.Config.Tracer.EmitGasChange(prior.AsTracing(), st.gasRemaining.AsTracing(), tracing.GasChangeTxDataFloor)
|
||||||
t.OnGasChange(prev, st.gasRemaining, tracing.GasChangeTxDataFloor)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if peakGasUsed < floorDataGas {
|
if peakGasUsed < floorDataGas {
|
||||||
|
|
@ -555,19 +667,22 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
||||||
// Return gas to the gas pool
|
// Return gas to the gas pool
|
||||||
if rules.IsAmsterdam {
|
if rules.IsAmsterdam {
|
||||||
// Refund is excluded for returning
|
// 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 {
|
} else {
|
||||||
// Refund is included for returning
|
// 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
effectiveTip := msg.GasPrice
|
effectiveTip := msg.GasPrice
|
||||||
if rules.IsLondon {
|
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 {
|
if st.evm.Config.NoBaseFee && msg.GasFeeCap.Sign() == 0 && msg.GasTipCap.Sign() == 0 {
|
||||||
// Skip fee payment when NoBaseFee is set and the fee fields
|
// 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.
|
// the coinbase when simulating calls.
|
||||||
} else {
|
} else {
|
||||||
fee := new(uint256.Int).SetUint64(st.gasUsed())
|
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)
|
st.state.AddBalance(st.evm.Context.Coinbase, fee, tracing.BalanceIncreaseRewardTransactionFee)
|
||||||
|
|
||||||
// add the coinbase to the witness iff the fee is greater than 0
|
// add the coinbase to the witness iff the fee is greater than 0
|
||||||
|
|
@ -655,7 +770,7 @@ func (st *stateTransition) applyAuthorization(auth *types.SetCodeAuthorization)
|
||||||
}
|
}
|
||||||
|
|
||||||
// calcRefund computes refund counter, capped to a refund quotient.
|
// calcRefund computes refund counter, capped to a refund quotient.
|
||||||
func (st *stateTransition) calcRefund() uint64 {
|
func (st *stateTransition) calcRefund() vm.GasBudget {
|
||||||
var refund uint64
|
var refund uint64
|
||||||
if !st.evm.ChainConfig().IsLondon(st.evm.Context.BlockNumber) {
|
if !st.evm.ChainConfig().IsLondon(st.evm.Context.BlockNumber) {
|
||||||
// Before EIP-3529: refunds were capped to gasUsed / 2
|
// Before EIP-3529: refunds were capped to gasUsed / 2
|
||||||
|
|
@ -667,27 +782,32 @@ func (st *stateTransition) calcRefund() uint64 {
|
||||||
if refund > st.state.GetRefund() {
|
if refund > st.state.GetRefund() {
|
||||||
refund = st.state.GetRefund()
|
refund = st.state.GetRefund()
|
||||||
}
|
}
|
||||||
if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil && refund > 0 {
|
if refund > 0 && st.evm.Config.Tracer.HasGasHook() {
|
||||||
st.evm.Config.Tracer.OnGasChange(st.gasRemaining, st.gasRemaining+refund, tracing.GasChangeTxRefunds)
|
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,
|
// returnGas returns ETH for remaining gas,
|
||||||
// exchanged at the original rate.
|
// exchanged at the original rate.
|
||||||
func (st *stateTransition) returnGas() {
|
func (st *stateTransition) returnGas() {
|
||||||
remaining := uint256.NewInt(st.gasRemaining)
|
remaining := uint256.NewInt(st.gasRemaining.RegularGas)
|
||||||
remaining.Mul(remaining, uint256.MustFromBig(st.msg.GasPrice))
|
remaining.Mul(remaining, st.msg.GasPrice)
|
||||||
st.state.AddBalance(st.msg.From, remaining, tracing.BalanceIncreaseGasReturn)
|
st.state.AddBalance(st.msg.From, remaining, tracing.BalanceIncreaseGasReturn)
|
||||||
|
|
||||||
if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil && st.gasRemaining > 0 {
|
if st.gasRemaining.RegularGas > 0 && st.evm.Config.Tracer.HasGasHook() {
|
||||||
st.evm.Config.Tracer.OnGasChange(st.gasRemaining, 0, tracing.GasChangeTxLeftOverReturned)
|
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.
|
// gasUsed returns the amount of gas used up by the state transition.
|
||||||
func (st *stateTransition) gasUsed() uint64 {
|
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.
|
// 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