Merge branch 'master' of https://github.com/0xjvn/go-ethereum into fix/rewind

This commit is contained in:
0xjvn 2026-05-18 10:14:10 +05:30
commit f09eefedbf
265 changed files with 9780 additions and 5139 deletions

View file

@ -145,7 +145,7 @@ jobs:
windows:
name: Windows Build
runs-on: "win-11"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@ -155,24 +155,49 @@ jobs:
go-version: 1.24
cache: false
# Note: gcc.exe only works properly if the corresponding bin/ directory is
# contained in PATH.
- name: Install cross toolchain
run: |
apt-get update
apt-get -yq --no-install-suggests --no-install-recommends install \
gcc-mingw-w64-x86-64 gcc-mingw-w64-i686 nsis
- name: "Build (amd64)"
shell: cmd
run: |
set PATH=%GETH_MINGW%\bin;%PATH%
go run build/ci.go install -dlgo -arch amd64 -cc %GETH_MINGW%\bin\gcc.exe
go run build/ci.go install -dlgo -os windows -arch amd64 -cc x86_64-w64-mingw32-gcc
- name: "Create/upload archive (amd64)"
run: |
go run build/ci.go archive -os windows -arch amd64 -type zip -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
env:
GETH_MINGW: 'C:\msys64\mingw64'
WINDOWS_SIGNING_KEY: ${{ secrets.WINDOWS_SIGNING_KEY }}
AZURE_BLOBSTORE_TOKEN: ${{ secrets.AZURE_BLOBSTORE_TOKEN }}
- name: "Create/upload NSIS installer (amd64)"
run: |
go run build/ci.go nsis -arch amd64 -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
rm -f build/bin/*
env:
WINDOWS_SIGNING_KEY: ${{ secrets.WINDOWS_SIGNING_KEY }}
AZURE_BLOBSTORE_TOKEN: ${{ secrets.AZURE_BLOBSTORE_TOKEN }}
- name: "Build (386)"
shell: cmd
run: |
set PATH=%GETH_MINGW%\bin;%PATH%
go run build/ci.go install -dlgo -arch 386 -cc %GETH_MINGW%\bin\gcc.exe
go run build/ci.go install -dlgo -os windows -arch 386 -cc i686-w64-mingw32-gcc
- name: "Create/upload archive (386)"
run: |
go run build/ci.go archive -os windows -arch 386 -type zip -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
env:
GETH_MINGW: 'C:\msys64\mingw32'
WINDOWS_SIGNING_KEY: ${{ secrets.WINDOWS_SIGNING_KEY }}
AZURE_BLOBSTORE_TOKEN: ${{ secrets.AZURE_BLOBSTORE_TOKEN }}
- name: "Create/upload NSIS installer (386)"
run: |
go run build/ci.go nsis -arch 386 -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
rm -f build/bin/*
env:
WINDOWS_SIGNING_KEY: ${{ secrets.WINDOWS_SIGNING_KEY }}
AZURE_BLOBSTORE_TOKEN: ${{ secrets.AZURE_BLOBSTORE_TOKEN }}
docker:
name: Docker Image

29
.github/workflows/freebsd.yml vendored Normal file
View 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

View file

@ -97,3 +97,44 @@ jobs:
- name: Run tests
run: go run build/ci.go test -p 8
windows:
name: Windows ${{ matrix.arch }}
needs: lint
runs-on: [self-hosted, windows, x64]
strategy:
fail-fast: false
matrix:
include:
- arch: amd64
mingw: 'C:\msys64\mingw64'
test: true
- arch: '386'
mingw: 'C:\msys64\mingw32'
test: false
env:
GETH_MINGW: ${{ matrix.mingw }}
GETH_CC: ${{ matrix.mingw }}\bin\gcc.exe
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.25'
cache: false
- name: Build
shell: cmd
run: |
set PATH=%GETH_MINGW%\bin;%PATH%
go run build/ci.go install -arch ${{ matrix.arch }} -cc %GETH_CC%
- name: Run tests
if: matrix.test
shell: cmd
run: |
set PATH=%GETH_MINGW%\bin;%PATH%
go run build/ci.go test -arch ${{ matrix.arch }} -cc %GETH_CC% -short -p 8

View file

@ -183,8 +183,11 @@ var (
// Solidity: {{.Original.String}}
func ({{ decapitalise $contract.Type}} *{{$contract.Type}}) Unpack{{.Normalized.Name}}Event(log *types.Log) (*{{$contract.Type}}{{.Normalized.Name}}, error) {
event := "{{.Original.Name}}"
if len(log.Topics) == 0 || log.Topics[0] != {{ decapitalise $contract.Type}}.abi.Events[event].ID {
return nil, errors.New("event signature mismatch")
if len(log.Topics) == 0 {
return nil, bind.ErrNoEventSignature
}
if log.Topics[0] != {{ decapitalise $contract.Type}}.abi.Events[event].ID {
return nil, bind.ErrEventSignatureMismatch
}
out := new({{$contract.Type}}{{.Normalized.Name}})
if len(log.Data) > 0 {

View file

@ -360,8 +360,11 @@ func (CrowdsaleFundTransfer) ContractEventName() string {
// Solidity: event FundTransfer(address backer, uint256 amount, bool isContribution)
func (crowdsale *Crowdsale) UnpackFundTransferEvent(log *types.Log) (*CrowdsaleFundTransfer, error) {
event := "FundTransfer"
if len(log.Topics) == 0 || log.Topics[0] != crowdsale.abi.Events[event].ID {
return nil, errors.New("event signature mismatch")
if len(log.Topics) == 0 {
return nil, bind.ErrNoEventSignature
}
if log.Topics[0] != crowdsale.abi.Events[event].ID {
return nil, bind.ErrEventSignatureMismatch
}
out := new(CrowdsaleFundTransfer)
if len(log.Data) > 0 {

View file

@ -606,8 +606,11 @@ func (DAOChangeOfRules) ContractEventName() string {
// Solidity: event ChangeOfRules(uint256 minimumQuorum, uint256 debatingPeriodInMinutes, int256 majorityMargin)
func (dAO *DAO) UnpackChangeOfRulesEvent(log *types.Log) (*DAOChangeOfRules, error) {
event := "ChangeOfRules"
if len(log.Topics) == 0 || log.Topics[0] != dAO.abi.Events[event].ID {
return nil, errors.New("event signature mismatch")
if len(log.Topics) == 0 {
return nil, bind.ErrNoEventSignature
}
if log.Topics[0] != dAO.abi.Events[event].ID {
return nil, bind.ErrEventSignatureMismatch
}
out := new(DAOChangeOfRules)
if len(log.Data) > 0 {
@ -648,8 +651,11 @@ func (DAOMembershipChanged) ContractEventName() string {
// Solidity: event MembershipChanged(address member, bool isMember)
func (dAO *DAO) UnpackMembershipChangedEvent(log *types.Log) (*DAOMembershipChanged, error) {
event := "MembershipChanged"
if len(log.Topics) == 0 || log.Topics[0] != dAO.abi.Events[event].ID {
return nil, errors.New("event signature mismatch")
if len(log.Topics) == 0 {
return nil, bind.ErrNoEventSignature
}
if log.Topics[0] != dAO.abi.Events[event].ID {
return nil, bind.ErrEventSignatureMismatch
}
out := new(DAOMembershipChanged)
if len(log.Data) > 0 {
@ -692,8 +698,11 @@ func (DAOProposalAdded) ContractEventName() string {
// Solidity: event ProposalAdded(uint256 proposalID, address recipient, uint256 amount, string description)
func (dAO *DAO) UnpackProposalAddedEvent(log *types.Log) (*DAOProposalAdded, error) {
event := "ProposalAdded"
if len(log.Topics) == 0 || log.Topics[0] != dAO.abi.Events[event].ID {
return nil, errors.New("event signature mismatch")
if len(log.Topics) == 0 {
return nil, bind.ErrNoEventSignature
}
if log.Topics[0] != dAO.abi.Events[event].ID {
return nil, bind.ErrEventSignatureMismatch
}
out := new(DAOProposalAdded)
if len(log.Data) > 0 {
@ -736,8 +745,11 @@ func (DAOProposalTallied) ContractEventName() string {
// Solidity: event ProposalTallied(uint256 proposalID, int256 result, uint256 quorum, bool active)
func (dAO *DAO) UnpackProposalTalliedEvent(log *types.Log) (*DAOProposalTallied, error) {
event := "ProposalTallied"
if len(log.Topics) == 0 || log.Topics[0] != dAO.abi.Events[event].ID {
return nil, errors.New("event signature mismatch")
if len(log.Topics) == 0 {
return nil, bind.ErrNoEventSignature
}
if log.Topics[0] != dAO.abi.Events[event].ID {
return nil, bind.ErrEventSignatureMismatch
}
out := new(DAOProposalTallied)
if len(log.Data) > 0 {
@ -780,8 +792,11 @@ func (DAOVoted) ContractEventName() string {
// Solidity: event Voted(uint256 proposalID, bool position, address voter, string justification)
func (dAO *DAO) UnpackVotedEvent(log *types.Log) (*DAOVoted, error) {
event := "Voted"
if len(log.Topics) == 0 || log.Topics[0] != dAO.abi.Events[event].ID {
return nil, errors.New("event signature mismatch")
if len(log.Topics) == 0 {
return nil, bind.ErrNoEventSignature
}
if log.Topics[0] != dAO.abi.Events[event].ID {
return nil, bind.ErrEventSignatureMismatch
}
out := new(DAOVoted)
if len(log.Data) > 0 {

View file

@ -72,8 +72,11 @@ func (EventCheckerDynamic) ContractEventName() string {
// Solidity: event dynamic(string indexed idxStr, bytes indexed idxDat, string str, bytes dat)
func (eventChecker *EventChecker) UnpackDynamicEvent(log *types.Log) (*EventCheckerDynamic, error) {
event := "dynamic"
if len(log.Topics) == 0 || log.Topics[0] != eventChecker.abi.Events[event].ID {
return nil, errors.New("event signature mismatch")
if len(log.Topics) == 0 {
return nil, bind.ErrNoEventSignature
}
if log.Topics[0] != eventChecker.abi.Events[event].ID {
return nil, bind.ErrEventSignatureMismatch
}
out := new(EventCheckerDynamic)
if len(log.Data) > 0 {
@ -112,8 +115,11 @@ func (EventCheckerEmpty) ContractEventName() string {
// Solidity: event empty()
func (eventChecker *EventChecker) UnpackEmptyEvent(log *types.Log) (*EventCheckerEmpty, error) {
event := "empty"
if len(log.Topics) == 0 || log.Topics[0] != eventChecker.abi.Events[event].ID {
return nil, errors.New("event signature mismatch")
if len(log.Topics) == 0 {
return nil, bind.ErrNoEventSignature
}
if log.Topics[0] != eventChecker.abi.Events[event].ID {
return nil, bind.ErrEventSignatureMismatch
}
out := new(EventCheckerEmpty)
if len(log.Data) > 0 {
@ -154,8 +160,11 @@ func (EventCheckerIndexed) ContractEventName() string {
// Solidity: event indexed(address indexed addr, int256 indexed num)
func (eventChecker *EventChecker) UnpackIndexedEvent(log *types.Log) (*EventCheckerIndexed, error) {
event := "indexed"
if len(log.Topics) == 0 || log.Topics[0] != eventChecker.abi.Events[event].ID {
return nil, errors.New("event signature mismatch")
if len(log.Topics) == 0 {
return nil, bind.ErrNoEventSignature
}
if log.Topics[0] != eventChecker.abi.Events[event].ID {
return nil, bind.ErrEventSignatureMismatch
}
out := new(EventCheckerIndexed)
if len(log.Data) > 0 {
@ -196,8 +205,11 @@ func (EventCheckerMixed) ContractEventName() string {
// Solidity: event mixed(address indexed addr, int256 num)
func (eventChecker *EventChecker) UnpackMixedEvent(log *types.Log) (*EventCheckerMixed, error) {
event := "mixed"
if len(log.Topics) == 0 || log.Topics[0] != eventChecker.abi.Events[event].ID {
return nil, errors.New("event signature mismatch")
if len(log.Topics) == 0 {
return nil, bind.ErrNoEventSignature
}
if log.Topics[0] != eventChecker.abi.Events[event].ID {
return nil, bind.ErrEventSignatureMismatch
}
out := new(EventCheckerMixed)
if len(log.Data) > 0 {
@ -238,8 +250,11 @@ func (EventCheckerUnnamed) ContractEventName() string {
// Solidity: event unnamed(uint256 indexed arg0, uint256 indexed arg1)
func (eventChecker *EventChecker) UnpackUnnamedEvent(log *types.Log) (*EventCheckerUnnamed, error) {
event := "unnamed"
if len(log.Topics) == 0 || log.Topics[0] != eventChecker.abi.Events[event].ID {
return nil, errors.New("event signature mismatch")
if len(log.Topics) == 0 {
return nil, bind.ErrNoEventSignature
}
if log.Topics[0] != eventChecker.abi.Events[event].ID {
return nil, bind.ErrEventSignatureMismatch
}
out := new(EventCheckerUnnamed)
if len(log.Data) > 0 {

View file

@ -134,8 +134,11 @@ func (NameConflictLog) ContractEventName() string {
// Solidity: event log(int256 msg, int256 _msg)
func (nameConflict *NameConflict) UnpackLogEvent(log *types.Log) (*NameConflictLog, error) {
event := "log"
if len(log.Topics) == 0 || log.Topics[0] != nameConflict.abi.Events[event].ID {
return nil, errors.New("event signature mismatch")
if len(log.Topics) == 0 {
return nil, bind.ErrNoEventSignature
}
if log.Topics[0] != nameConflict.abi.Events[event].ID {
return nil, bind.ErrEventSignatureMismatch
}
out := new(NameConflictLog)
if len(log.Data) > 0 {

View file

@ -136,8 +136,11 @@ func (NumericMethodNameE1TestEvent) ContractEventName() string {
// Solidity: event _1TestEvent(address _param)
func (numericMethodName *NumericMethodName) UnpackE1TestEventEvent(log *types.Log) (*NumericMethodNameE1TestEvent, error) {
event := "_1TestEvent"
if len(log.Topics) == 0 || log.Topics[0] != numericMethodName.abi.Events[event].ID {
return nil, errors.New("event signature mismatch")
if len(log.Topics) == 0 {
return nil, bind.ErrNoEventSignature
}
if log.Topics[0] != numericMethodName.abi.Events[event].ID {
return nil, bind.ErrEventSignatureMismatch
}
out := new(NumericMethodNameE1TestEvent)
if len(log.Data) > 0 {

View file

@ -114,8 +114,11 @@ func (OverloadBar) ContractEventName() string {
// Solidity: event bar(uint256 i)
func (overload *Overload) UnpackBarEvent(log *types.Log) (*OverloadBar, error) {
event := "bar"
if len(log.Topics) == 0 || log.Topics[0] != overload.abi.Events[event].ID {
return nil, errors.New("event signature mismatch")
if len(log.Topics) == 0 {
return nil, bind.ErrNoEventSignature
}
if log.Topics[0] != overload.abi.Events[event].ID {
return nil, bind.ErrEventSignatureMismatch
}
out := new(OverloadBar)
if len(log.Data) > 0 {
@ -156,8 +159,11 @@ func (OverloadBar0) ContractEventName() string {
// Solidity: event bar(uint256 i, uint256 j)
func (overload *Overload) UnpackBar0Event(log *types.Log) (*OverloadBar0, error) {
event := "bar0"
if len(log.Topics) == 0 || log.Topics[0] != overload.abi.Events[event].ID {
return nil, errors.New("event signature mismatch")
if len(log.Topics) == 0 {
return nil, bind.ErrNoEventSignature
}
if log.Topics[0] != overload.abi.Events[event].ID {
return nil, bind.ErrEventSignatureMismatch
}
out := new(OverloadBar0)
if len(log.Data) > 0 {

View file

@ -386,8 +386,11 @@ func (TokenTransfer) ContractEventName() string {
// Solidity: event Transfer(address indexed from, address indexed to, uint256 value)
func (token *Token) UnpackTransferEvent(log *types.Log) (*TokenTransfer, error) {
event := "Transfer"
if len(log.Topics) == 0 || log.Topics[0] != token.abi.Events[event].ID {
return nil, errors.New("event signature mismatch")
if len(log.Topics) == 0 {
return nil, bind.ErrNoEventSignature
}
if log.Topics[0] != token.abi.Events[event].ID {
return nil, bind.ErrEventSignatureMismatch
}
out := new(TokenTransfer)
if len(log.Data) > 0 {

View file

@ -193,8 +193,11 @@ func (TupleTupleEvent) ContractEventName() string {
// Solidity: event TupleEvent((uint256,uint256[],(uint256,uint256)[]) a, (uint256,uint256)[2][] b, (uint256,uint256)[][2] c, (uint256,uint256[],(uint256,uint256)[])[] d, uint256[] e)
func (tuple *Tuple) UnpackTupleEventEvent(log *types.Log) (*TupleTupleEvent, error) {
event := "TupleEvent"
if len(log.Topics) == 0 || log.Topics[0] != tuple.abi.Events[event].ID {
return nil, errors.New("event signature mismatch")
if len(log.Topics) == 0 {
return nil, bind.ErrNoEventSignature
}
if log.Topics[0] != tuple.abi.Events[event].ID {
return nil, bind.ErrEventSignatureMismatch
}
out := new(TupleTupleEvent)
if len(log.Data) > 0 {
@ -234,8 +237,11 @@ func (TupleTupleEvent2) ContractEventName() string {
// Solidity: event TupleEvent2((uint8,uint8)[] arg0)
func (tuple *Tuple) UnpackTupleEvent2Event(log *types.Log) (*TupleTupleEvent2, error) {
event := "TupleEvent2"
if len(log.Topics) == 0 || log.Topics[0] != tuple.abi.Events[event].ID {
return nil, errors.New("event signature mismatch")
if len(log.Topics) == 0 {
return nil, bind.ErrNoEventSignature
}
if log.Topics[0] != tuple.abi.Events[event].ID {
return nil, bind.ErrEventSignatureMismatch
}
out := new(TupleTupleEvent2)
if len(log.Data) > 0 {

View file

@ -176,6 +176,13 @@ var (
// ErrNoCodeAfterDeploy is returned by WaitDeployed if contract creation leaves
// an empty contract behind.
ErrNoCodeAfterDeploy = bind2.ErrNoCodeAfterDeploy
// ErrNoEventSignature is returned when a log entry has no topics.
ErrNoEventSignature = bind2.ErrNoEventSignature
// ErrEventSignatureMismatch is returned when a log's topic[0] does not match
// the expected event signature.
ErrEventSignatureMismatch = bind2.ErrEventSignatureMismatch
)
// ContractCaller defines the methods needed to allow operating with a contract on a read

View file

@ -35,8 +35,8 @@ import (
const basefeeWiggleMultiplier = 2
var (
errNoEventSignature = errors.New("no event signature")
errEventSignatureMismatch = errors.New("event signature mismatch")
ErrNoEventSignature = errors.New("no event signature")
ErrEventSignatureMismatch = errors.New("event signature mismatch")
)
// SignerFn is a signer function callback when a contract requires a method to
@ -536,10 +536,10 @@ func (c *BoundContract) WatchLogs(opts *WatchOpts, name string, query ...[]any)
func (c *BoundContract) UnpackLog(out any, event string, log types.Log) error {
// Anonymous events are not supported.
if len(log.Topics) == 0 {
return errNoEventSignature
return ErrNoEventSignature
}
if log.Topics[0] != c.abi.Events[event].ID {
return errEventSignatureMismatch
return ErrEventSignatureMismatch
}
if len(log.Data) > 0 {
if err := c.abi.UnpackIntoInterface(out, event, log.Data); err != nil {
@ -559,10 +559,10 @@ func (c *BoundContract) UnpackLog(out any, event string, log types.Log) error {
func (c *BoundContract) UnpackLogIntoMap(out map[string]any, event string, log types.Log) error {
// Anonymous events are not supported.
if len(log.Topics) == 0 {
return errNoEventSignature
return ErrNoEventSignature
}
if log.Topics[0] != c.abi.Events[event].ID {
return errEventSignatureMismatch
return ErrEventSignatureMismatch
}
if len(log.Data) > 0 {
if err := c.abi.UnpackIntoMap(out, event, log.Data); err != nil {

View file

@ -276,8 +276,11 @@ func (DBInsert) ContractEventName() string {
// Solidity: event Insert(uint256 key, uint256 value, uint256 length)
func (dB *DB) UnpackInsertEvent(log *types.Log) (*DBInsert, error) {
event := "Insert"
if len(log.Topics) == 0 || log.Topics[0] != dB.abi.Events[event].ID {
return nil, errors.New("event signature mismatch")
if len(log.Topics) == 0 {
return nil, bind.ErrNoEventSignature
}
if log.Topics[0] != dB.abi.Events[event].ID {
return nil, bind.ErrEventSignatureMismatch
}
out := new(DBInsert)
if len(log.Data) > 0 {
@ -318,8 +321,11 @@ func (DBKeyedInsert) ContractEventName() string {
// Solidity: event KeyedInsert(uint256 indexed key, uint256 value)
func (dB *DB) UnpackKeyedInsertEvent(log *types.Log) (*DBKeyedInsert, error) {
event := "KeyedInsert"
if len(log.Topics) == 0 || log.Topics[0] != dB.abi.Events[event].ID {
return nil, errors.New("event signature mismatch")
if len(log.Topics) == 0 {
return nil, bind.ErrNoEventSignature
}
if log.Topics[0] != dB.abi.Events[event].ID {
return nil, bind.ErrEventSignatureMismatch
}
out := new(DBKeyedInsert)
if len(log.Data) > 0 {

View file

@ -115,8 +115,11 @@ func (CBasic1) ContractEventName() string {
// Solidity: event basic1(uint256 indexed id, uint256 data)
func (c *C) UnpackBasic1Event(log *types.Log) (*CBasic1, error) {
event := "basic1"
if len(log.Topics) == 0 || log.Topics[0] != c.abi.Events[event].ID {
return nil, errors.New("event signature mismatch")
if len(log.Topics) == 0 {
return nil, bind.ErrNoEventSignature
}
if log.Topics[0] != c.abi.Events[event].ID {
return nil, bind.ErrEventSignatureMismatch
}
out := new(CBasic1)
if len(log.Data) > 0 {
@ -157,8 +160,11 @@ func (CBasic2) ContractEventName() string {
// Solidity: event basic2(bool indexed flag, uint256 data)
func (c *C) UnpackBasic2Event(log *types.Log) (*CBasic2, error) {
event := "basic2"
if len(log.Topics) == 0 || log.Topics[0] != c.abi.Events[event].ID {
return nil, errors.New("event signature mismatch")
if len(log.Topics) == 0 {
return nil, bind.ErrNoEventSignature
}
if log.Topics[0] != c.abi.Events[event].ID {
return nil, bind.ErrEventSignatureMismatch
}
out := new(CBasic2)
if len(log.Data) > 0 {

View file

@ -379,16 +379,16 @@ func TestEventUnpackEmptyTopics(t *testing.T) {
if err == nil {
t.Fatal("expected error when unpacking event with empty topics, got nil")
}
if err.Error() != "event signature mismatch" {
t.Fatalf("expected 'event signature mismatch' error, got: %v", err)
if err != bind.ErrNoEventSignature {
t.Fatalf("expected 'no event signature' error, got: %v", err)
}
_, err = c.UnpackBasic2Event(log)
if err == nil {
t.Fatal("expected error when unpacking event with empty topics, got nil")
}
if err.Error() != "event signature mismatch" {
t.Fatalf("expected 'event signature mismatch' error, got: %v", err)
if err != bind.ErrNoEventSignature {
t.Fatalf("expected 'no event signature' error, got: %v", err)
}
}
}

View file

@ -910,7 +910,7 @@ func TestUnpackTuple(t *testing.T) {
},
},
FieldT: T{
big.NewInt(0), big.NewInt(1),
big.NewInt(0).SetBits([]big.Word{}), big.NewInt(1),
},
A: big.NewInt(1),
}
@ -919,7 +919,7 @@ func TestUnpackTuple(t *testing.T) {
if err != nil {
t.Error(err)
}
if reflect.DeepEqual(ret, expected) {
if !reflect.DeepEqual(ret, expected) {
t.Error("unexpected unpack value")
}
}

View file

@ -68,18 +68,27 @@ func waitWatcherStart(ks *KeyStore) bool {
func waitForAccounts(wantAccounts []accounts.Account, ks *KeyStore) error {
var list []accounts.Account
haveAccounts := false
haveChange := false
for t0 := time.Now(); time.Since(t0) < 5*time.Second; time.Sleep(100 * time.Millisecond) {
list = ks.Accounts()
if reflect.DeepEqual(list, wantAccounts) {
// ks should have also received change notifications
if !haveAccounts {
list = ks.Accounts()
haveAccounts = reflect.DeepEqual(list, wantAccounts)
}
if !haveChange {
select {
case <-ks.changes:
haveChange = true
default:
return errors.New("wasn't notified of new accounts")
}
}
if haveAccounts && haveChange {
return nil
}
}
if haveAccounts {
return errors.New("wasn't notified of new accounts")
}
return fmt.Errorf("\ngot %v\nwant %v", list, wantAccounts)
}

View file

@ -14,8 +14,8 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
//go:build (darwin && !ios && cgo) || freebsd || (linux && !arm64) || netbsd || solaris
// +build darwin,!ios,cgo freebsd linux,!arm64 netbsd solaris
//go:build (darwin && !ios && cgo) || freebsd || linux || netbsd || solaris
// +build darwin,!ios,cgo freebsd linux netbsd solaris
package keystore

View file

@ -14,8 +14,8 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
//go:build (darwin && !cgo) || ios || (linux && arm64) || windows || (!darwin && !freebsd && !linux && !netbsd && !solaris)
// +build darwin,!cgo ios linux,arm64 windows !darwin,!freebsd,!linux,!netbsd,!solaris
//go:build (darwin && !cgo) || ios || windows || (!darwin && !freebsd && !linux && !netbsd && !solaris)
// +build darwin,!cgo ios windows !darwin,!freebsd,!linux,!netbsd,!solaris
// This is the fallback implementation of directory watching.
// It is used on unsupported platforms.

View file

@ -113,7 +113,7 @@ func (hub *Hub) readPairings() error {
}
func (hub *Hub) writePairings() error {
pairingFile, err := os.OpenFile(filepath.Join(hub.datadir, "smartcards.json"), os.O_RDWR|os.O_CREATE, 0755)
pairingFile, err := os.OpenFile(filepath.Join(hub.datadir, "smartcards.json"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755)
if err != nil {
return err
}
@ -129,11 +129,8 @@ func (hub *Hub) writePairings() error {
return err
}
if _, err := pairingFile.Write(pairingData); err != nil {
return err
}
return nil
_, err = pairingFile.Write(pairingData)
return err
}
func (hub *Hub) pairing(wallet *Wallet) *smartcardPairing {

View file

@ -26,7 +26,7 @@ import (
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
"github.com/karalabe/hid"
"github.com/ethereum/hid"
)
// LedgerScheme is the protocol scheme prefixing account and wallet URLs.

View file

@ -31,7 +31,7 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/karalabe/hid"
"github.com/ethereum/hid"
)
// Maximum time between wallet health checks to detect USB unplugs.

View file

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

View file

@ -81,6 +81,7 @@ var (
TooLargeRequest = &EngineAPIError{code: -38004, msg: "Too large request"}
InvalidParams = &EngineAPIError{code: -32602, msg: "Invalid parameters"}
UnsupportedFork = &EngineAPIError{code: -38005, msg: "Unsupported fork"}
TooDeepReorg = &EngineAPIError{code: -38006, msg: "Too deep reorg"}
STATUS_INVALID = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: INVALID}, PayloadID: nil}
STATUS_SYNCING = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: SYNCING}, PayloadID: nil}

View file

@ -34,7 +34,7 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) {
Withdrawals []*types.Withdrawal `json:"withdrawals"`
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
SlotNumber *hexutil.Uint64 `json:"slotNumber"`
SlotNumber *hexutil.Uint64 `json:"slotNumber,omitempty"`
}
var enc ExecutableData
enc.ParentHash = e.ParentHash
@ -83,7 +83,7 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error {
Withdrawals []*types.Withdrawal `json:"withdrawals"`
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
SlotNumber *hexutil.Uint64 `json:"slotNumber"`
SlotNumber *hexutil.Uint64 `json:"slotNumber,omitempty"`
}
var dec ExecutableData
if err := json.Unmarshal(input, &dec); err != nil {

View file

@ -99,7 +99,7 @@ type ExecutableData struct {
Withdrawals []*types.Withdrawal `json:"withdrawals"`
BlobGasUsed *uint64 `json:"blobGasUsed"`
ExcessBlobGas *uint64 `json:"excessBlobGas"`
SlotNumber *uint64 `json:"slotNumber"`
SlotNumber *uint64 `json:"slotNumber,omitempty"`
}
// JSON type overrides for executableData.
@ -276,7 +276,7 @@ func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.H
if data.BaseFeePerGas != nil && (data.BaseFeePerGas.Sign() == -1 || data.BaseFeePerGas.BitLen() > 256) {
return nil, fmt.Errorf("invalid baseFeePerGas: %v", data.BaseFeePerGas)
}
var blobHashes = make([]common.Hash, 0, len(txs))
var blobHashes = make([]common.Hash, 0, len(versionedHashes))
for _, tx := range txs {
blobHashes = append(blobHashes, tx.BlobHashes()...)
}

View file

@ -5,49 +5,49 @@
# https://github.com/ethereum/execution-spec-tests/releases/download/v5.1.0
a3192784375acec7eaec492799d5c5d0c47a2909a3cc40178898e4ecd20cc416 fixtures_develop.tar.gz
# version:golang 1.25.7
# version:golang 1.25.10
# https://go.dev/dl/
178f2832820274b43e177d32f06a3ebb0129e427dd20a5e4c88df2c1763cf10a go1.25.7.src.tar.gz
81bf2a1f20633f62d55d826d82dde3b0570cf1408a91e15781b266037299285b go1.25.7.aix-ppc64.tar.gz
bf5050a2152f4053837b886e8d9640c829dbacbc3370f913351eb0904cb706f5 go1.25.7.darwin-amd64.tar.gz
ff18369ffad05c57d5bed888b660b31385f3c913670a83ef557cdfd98ea9ae1b go1.25.7.darwin-arm64.tar.gz
c5dccd7f192dd7b305dc209fb316ac1917776d74bd8e4d532ef2772f305bf42a go1.25.7.dragonfly-amd64.tar.gz
a2de97c8ac74bf64b0ae73fe9d379e61af530e061bc7f8f825044172ffe61a8b go1.25.7.freebsd-386.tar.gz
055f9e138787dcafa81eb0314c8ff70c6dd0f6dba1e8a6957fef5d5efd1ab8fd go1.25.7.freebsd-amd64.tar.gz
60e7f7a7c990f0b9539ac8ed668155746997d404643a4eecd47b3dee1b7e710b go1.25.7.freebsd-arm.tar.gz
631e03d5fd4c526e2f499154d8c6bf4cb081afb2fff171c428722afc9539d53a go1.25.7.freebsd-arm64.tar.gz
8a264fd685823808140672812e3ad9c43f6ad59444c0dc14cdd3a1351839ddd5 go1.25.7.freebsd-riscv64.tar.gz
57c672447d906a1bcab98f2b11492d54521a791aacbb4994a25169e59cbe289a go1.25.7.illumos-amd64.tar.gz
2866517e9ca81e6a2e85a930e9b11bc8a05cfeb2fc6dc6cb2765e7fb3c14b715 go1.25.7.linux-386.tar.gz
12e6d6a191091ae27dc31f6efc630e3a3b8ba409baf3573d955b196fdf086005 go1.25.7.linux-amd64.tar.gz
ba611a53534135a81067240eff9508cd7e256c560edd5d8c2fef54f083c07129 go1.25.7.linux-arm64.tar.gz
1ba07e0eb86b839e72467f4b5c7a5597d07f30bcf5563c951410454f7cda5266 go1.25.7.linux-armv6l.tar.gz
775753fc5952a334c415f08768df2f0b73a3228a16e8f5f63d545daacb4e3357 go1.25.7.linux-loong64.tar.gz
1a023bb367c5fbb4c637a2f6dc23ff17c6591ad929ce16ea88c74d857153b307 go1.25.7.linux-mips.tar.gz
a8e97223d8aa6fdfd45f132a4784d2f536bbac5f3d63a24b63d33b6bfe1549af go1.25.7.linux-mips64.tar.gz
eb9edb6223330d5e20275667c65dea076b064c08e595fe4eba5d7d6055cfaccf go1.25.7.linux-mips64le.tar.gz
9c1e693552a5f9bb9e0012d1c5e01456ecefbc59bef53a77305222ce10aba368 go1.25.7.linux-mipsle.tar.gz
28a788798e7329acbbc0ac2caa5e4368b1e5ede646cc24429c991214cfb45c63 go1.25.7.linux-ppc64.tar.gz
42124c0edc92464e2b37b2d7fcd3658f0c47ebd6a098732415a522be8cb88e3f go1.25.7.linux-ppc64le.tar.gz
88d59c6893c8425875d6eef8e3434bc2fa2552e5ad4c058c6cd8cd710a0301c8 go1.25.7.linux-riscv64.tar.gz
c6b77facf666dc68195ecab05dbf0ebb4e755b2a8b7734c759880557f1c29b0c go1.25.7.linux-s390x.tar.gz
f14c184d9ade0ee04c7735d4071257b90896ecbde1b32adae84135f055e6399b go1.25.7.netbsd-386.tar.gz
7e7389e404dca1088c31f0fc07f1dd60891d7182bcd621469c14f7e79eceb3ff go1.25.7.netbsd-amd64.tar.gz
70388bb3ef2f03dbf1357e9056bd09034a67e018262557354f8cf549766b3f9d go1.25.7.netbsd-arm.tar.gz
8c1cda9d25bfc9b18d24d5f95fc23949dd3ff99fa408a6cfa40e2cf12b07e362 go1.25.7.netbsd-arm64.tar.gz
42f0d1bfbe39b8401cccb84dd66b30795b97bfc9620dfdc17c5cd4fcf6495cb0 go1.25.7.openbsd-386.tar.gz
e514879c0a28bc32123cd52c4c093de912477fe83f36a6d07517d066ef55391a go1.25.7.openbsd-amd64.tar.gz
8cd22530695a0218232bf7efea8f162df1697a3106942ac4129b8c3de39ce4ef go1.25.7.openbsd-arm.tar.gz
938720f6ebc0d1c53d7840321d3a31f29fd02496e84a6538f442a9311dc1cc9a go1.25.7.openbsd-arm64.tar.gz
a4c378b73b98f89a3596c2ef51aabbb28783d9ca29f7e317d8ca07939660ce6f go1.25.7.openbsd-ppc64.tar.gz
937b58734fbeaa8c7941a0e4285e7e84b7885396e8d11c23f9ab1a8ff10ff20e go1.25.7.openbsd-riscv64.tar.gz
61a093c8c5244916f25740316386bb9f141545dcf01b06a79d1c78ece488403e go1.25.7.plan9-386.tar.gz
7fc8f6689c9de8ccb7689d2278035fa83c2d601409101840df6ddfe09ba58699 go1.25.7.plan9-amd64.tar.gz
9661dff8eaeeb62f1c3aadbc5ff189a2e6744e1ec885e32dbcb438f58a34def5 go1.25.7.plan9-arm.tar.gz
28ecba0e1d7950c8b29a4a04962dd49c3bf5221f55a44f17d98f369f82859cf4 go1.25.7.solaris-amd64.tar.gz
baa6b488291801642fa620026169e38bec2da2ac187cd3ae2145721cf826bbc3 go1.25.7.windows-386.zip
c75e5f4ff62d085cc0017be3ad19d5536f46825fa05db06ec468941f847e3228 go1.25.7.windows-amd64.zip
807033f85931bc4a589ca8497535dcbeb1f30d506e47fa200f5f04c4a71c3d9f go1.25.7.windows-arm64.zip
20cf04a92e5af99748e341bc8996fa28090c9ac98765fa115ec5ddf41d7af41d go1.25.10.src.tar.gz
a194e767c2ab4216a60acc068b9dbe6bf4fae05c14bb52d6bbdcb5b3ea521308 go1.25.10.aix-ppc64.tar.gz
52321165a3146cd91865ef98371506a846ed4dc4f9f1c9323e5ad90d2a411e06 go1.25.10.darwin-amd64.tar.gz
795691a425de7e7cdba3544f354dcd2cebcf52e87dc6898193878f34eb6d634f go1.25.10.darwin-arm64.tar.gz
e37b4544ba9e9e9a7ab2ed3116b3fc4d39a88da854baa5a566d9d6d3a9de7d4c go1.25.10.dragonfly-amd64.tar.gz
2a70d1fdabab637aa442ca94599a56e381238efa20cb995d5433b8579bfe482c go1.25.10.freebsd-386.tar.gz
9cdf522d87d47d82fec4a313cc4f8c3c94a7770426e8d443e4150a1f330cba71 go1.25.10.freebsd-amd64.tar.gz
6da6183633e9e59ffd9edefab68b5059c89b605596d94aaba650b1681fccd35f go1.25.10.freebsd-arm.tar.gz
7adcefeebdd05331f4d45f1ad2dddb5c53537cff6552e82f6595b3b833b95371 go1.25.10.freebsd-arm64.tar.gz
285f80a1ace21a7d94035cd753196eeada8cacd48e6396fd116ad5eb67aea957 go1.25.10.freebsd-riscv64.tar.gz
de7461bf0e5068a4f6e7f8713026d70516be6dbd5de5d21f9ced1c182f2f326e go1.25.10.illumos-amd64.tar.gz
2f574f2e2e19ead5b280fec0e7af5c81b76632685f03b6ac42dfa34c4b773c52 go1.25.10.linux-386.tar.gz
42d4f7a32316aa66591eca7e89867256057a4264451aca10570a715b3637ba70 go1.25.10.linux-amd64.tar.gz
654da1f9b50a5d1c2a85ccf8ed405aa89c06e94d18384628bf186f7712677b08 go1.25.10.linux-arm64.tar.gz
39f168f158e693887d3ad006168af1b1a3007b19c5993cae4d9d57f82f52aaf8 go1.25.10.linux-armv6l.tar.gz
05401fe5ea50ad2bafb9c797ef9bf21574b0661f19ef4d0dd66af8a0fb7323f3 go1.25.10.linux-loong64.tar.gz
d5bc2d6155d394a3aae41f21eb7c60da5595a6147aa0f30ed6b27da25e06c3f7 go1.25.10.linux-mips.tar.gz
8c64e7493e5953c3ba3153487d2fddd7f8ed142392c77f138e6792a6c1930db4 go1.25.10.linux-mips64.tar.gz
bd53aa2d558b7c1eadfc6bf01132e1859203a92f458ed7ba75b7f3230f14b095 go1.25.10.linux-mips64le.tar.gz
120b254e2e2980bb06687175db5c4064a85696c53001dc9f59934ad18f74a6bc go1.25.10.linux-mipsle.tar.gz
8a6acb21295b0ec974a44608361920ea8dbff5666631a6f556bd7d5f1d56535f go1.25.10.linux-ppc64.tar.gz
778925fdcdf9a272f823d147fad51545c3334b7ccd8652b2ccaaf2b01800280a go1.25.10.linux-ppc64le.tar.gz
b4f04ad0db48bcfea946db5323919cd21034e0bd2821a557dacd29c1b1013a4b go1.25.10.linux-riscv64.tar.gz
936b953e43921a64c12da871f76871ebbeb6d2092a7b8bdc307f5246f3c662cc go1.25.10.linux-s390x.tar.gz
061470e0bc7132146a5925a3cc28d5bc498eb1b1ff09dedcfaae10f781ff2274 go1.25.10.netbsd-386.tar.gz
63b2d50d7f8f269a9c82d42a4060e90cffb7f9102299818bb071b067aac8da8f go1.25.10.netbsd-amd64.tar.gz
c35129f68796526aa4dc4b6f481e2d995ef312aedadc88b659b945cc00e1f8f0 go1.25.10.netbsd-arm.tar.gz
2f541da4e2b298154d992d1f11bbb38c89d0821d91cc50a46776d42bb5e63bca go1.25.10.netbsd-arm64.tar.gz
2d42e569b07f1b99fdbfd008e7c22f967d165e2ce02464f46818fbed2aec43f5 go1.25.10.openbsd-386.tar.gz
0ad05960e8c9f867328151308c87f938433bec8f22f6a9437a896e22169fc840 go1.25.10.openbsd-amd64.tar.gz
099cc11473f99461c77161912740945308f08f6834980afb262c72bdc915f2d7 go1.25.10.openbsd-arm.tar.gz
bdf3335d5008c1ddc81fa94892283e4f1fee22566f5351d4e726d9f55a67c838 go1.25.10.openbsd-arm64.tar.gz
0933d418da0a61e0f29de717a77498f16b9b5b50dbe2205e20b2ed7fd4067f75 go1.25.10.openbsd-ppc64.tar.gz
191e6f3e75712f8c13d189d53b668e2cac6449f26474c1d86fbd04f6e9846f9c go1.25.10.openbsd-riscv64.tar.gz
68c053c8acd76c50fc430e92f4a86110ec3d97dd03d27b9339b4eaf793caff5f go1.25.10.plan9-386.tar.gz
42e2c46638ae22d93402e79efb40faee5c42cf7c56a01bb3ab47c6bb2512b745 go1.25.10.plan9-amd64.tar.gz
3ef1d5838b1648da16724a07b72e839ccbd7cb8899c3e0426afd6b79d494b91c go1.25.10.plan9-arm.tar.gz
631e3716017fbec06500a628d97e1155daec3593f0a7812c2ebfe8fc8c96b2ab go1.25.10.solaris-amd64.tar.gz
ddc693d2d9d7cc671ebb72d1d50aa05670f95b059b7d90440611af57976871d5 go1.25.10.windows-386.zip
ca37af2dadd8544464f1a9ca7c3886499d1cdfcb263855d0a1d71f194b2bd222 go1.25.10.windows-amd64.zip
38be57e0398bd93673d65bcae6dc7ee3cf151d7038d0dba5c60a5153022872da go1.25.10.windows-arm64.zip
# version:golangci 2.10.1
# https://github.com/golangci/golangci-lint/releases/

View file

@ -73,21 +73,9 @@ var (
"./cmd/keeper",
}
// Files that end up in the geth*.zip archive.
gethArchiveFiles = []string{
"COPYING",
executablePath("geth"),
}
// Files that end up in the geth-alltools*.zip archive.
allToolsArchiveFiles = []string{
"COPYING",
executablePath("abigen"),
executablePath("evm"),
executablePath("geth"),
executablePath("rlpdump"),
executablePath("clef"),
}
// Files that end up in the geth-alltools*.zip archive (and the NSIS installer
// dev-tools section). Order matches the historical layout produced by ci.go.
allToolsBinaries = []string{"abigen", "evm", "geth", "rlpdump", "clef"}
// Keeper build targets with their configurations
keeperTargets = []struct {
@ -180,13 +168,35 @@ var (
var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin"))
func executablePath(name string) string {
if runtime.GOOS == "windows" {
// executablePath returns the path to a built binary in GOBIN, applying the
// platform-specific extension for the given target OS.
func executablePath(name, targetOS string) string {
if targetOS == "windows" {
name += ".exe"
}
return filepath.Join(GOBIN, name)
}
// gethArchiveFiles returns the file list for the geth-{platform}-{ver}.zip
// archive, with binary paths resolved for the target OS.
func gethArchiveFiles(targetOS string) []string {
return []string{
"COPYING",
executablePath("geth", targetOS),
}
}
// allToolsArchiveFiles returns the file list for the
// geth-alltools-{platform}-{ver}.zip archive, with binary paths resolved for
// the target OS.
func allToolsArchiveFiles(targetOS string) []string {
files := []string{"COPYING"}
for _, name := range allToolsBinaries {
files = append(files, executablePath(name, targetOS))
}
return files
}
func main() {
log.SetFlags(log.Lshortfile)
@ -233,6 +243,7 @@ func main() {
func doInstall(cmdline []string) {
var (
dlgo = flag.Bool("dlgo", false, "Download Go and build with it")
targetOS = flag.String("os", runtime.GOOS, "Target OS to cross build for")
arch = flag.String("arch", "", "Architecture to cross build for")
cc = flag.String("cc", "", "C compiler to cross build with")
staticlink = flag.Bool("static", false, "Create statically-linked executable")
@ -241,7 +252,7 @@ func doInstall(cmdline []string) {
env := build.Env()
// Configure the toolchain.
tc := build.GoToolchain{GOARCH: *arch, CC: *cc}
tc := build.GoToolchain{GOOS: *targetOS, GOARCH: *arch, CC: *cc}
if *dlgo {
csdb := download.MustLoadChecksums("build/checksums.txt")
tc.Root = build.DownloadGo(csdb)
@ -255,7 +266,7 @@ func doInstall(cmdline []string) {
}
// Configure the build.
gobuild := tc.Go("build", buildFlags(env, *staticlink, buildTags)...)
gobuild := tc.Go("build", buildFlags(env, *staticlink, buildTags, *targetOS)...)
// Show packages during build.
gobuild.Args = append(gobuild.Args, "-v")
@ -270,7 +281,7 @@ func doInstall(cmdline []string) {
// Do the build!
for _, pkg := range packages {
args := slices.Clone(gobuild.Args)
args = append(args, "-o", executablePath(path.Base(pkg)))
args = append(args, "-o", executablePath(path.Base(pkg), *targetOS))
args = append(args, pkg)
build.MustRun(&exec.Cmd{Path: gobuild.Path, Args: args, Env: gobuild.Env})
}
@ -297,7 +308,13 @@ func doInstallKeeper(cmdline []string) {
tc.GOARCH = target.GOARCH
tc.GOOS = target.GOOS
tc.CC = target.CC
gobuild := tc.Go("build", buildFlags(env, true, []string{target.Tags})...)
// An empty GOOS means "build for the host OS"; thread that through to
// buildFlags so platform-specific linker flags are picked correctly.
targetOS := target.GOOS
if targetOS == "" {
targetOS = runtime.GOOS
}
gobuild := tc.Go("build", buildFlags(env, true, []string{target.Tags}, targetOS)...)
gobuild.Dir = "./cmd/keeper"
gobuild.Args = append(gobuild.Args, "-v")
@ -307,14 +324,15 @@ func doInstallKeeper(cmdline []string) {
outputName := fmt.Sprintf("keeper-%s", target.Name)
args := slices.Clone(gobuild.Args)
args = append(args, "-o", executablePath(outputName))
args = append(args, "-o", executablePath(outputName, targetOS))
args = append(args, ".")
build.MustRun(&exec.Cmd{Path: gobuild.Path, Args: args, Env: gobuild.Env, Dir: gobuild.Dir})
}
}
// buildFlags returns the go tool flags for building.
func buildFlags(env build.Environment, staticLinking bool, buildTags []string) (flags []string) {
// buildFlags returns the go tool flags for building. targetOS is the OS we
// are producing binaries for.
func buildFlags(env build.Environment, staticLinking bool, buildTags []string, targetOS string) (flags []string) {
var ld []string
// See https://github.com/golang/go/issues/33772#issuecomment-528176001
// We need to set --buildid to the linker here, and also pass --build-id to the
@ -326,10 +344,10 @@ func buildFlags(env build.Environment, staticLinking bool, buildTags []string) (
}
// Strip DWARF on darwin. This used to be required for certain things,
// and there is no downside to this, so we just keep doing it.
if runtime.GOOS == "darwin" {
if targetOS == "darwin" {
ld = append(ld, "-s")
}
if runtime.GOOS == "linux" {
if targetOS == "linux" {
// Enforce the stacksize to 8M, which is the case on most platforms apart from
// alpine Linux.
// See https://sourceware.org/binutils/docs-2.23.1/ld/Options.html#Options
@ -682,12 +700,13 @@ func downloadProtoc(cachedir string) string {
// Release Packaging
func doArchive(cmdline []string) {
var (
arch = flag.String("arch", runtime.GOARCH, "Architecture cross packaging")
atype = flag.String("type", "zip", "Type of archive to write (zip|tar)")
signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. LINUX_SIGNING_KEY)`)
signify = flag.String("signify", "", `Environment variable holding the signify key (e.g. LINUX_SIGNIFY_KEY)`)
upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`)
ext string
targetOS = flag.String("os", runtime.GOOS, "Target OS the binaries were built for")
arch = flag.String("arch", runtime.GOARCH, "Architecture cross packaging")
atype = flag.String("type", "zip", "Type of archive to write (zip|tar)")
signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. LINUX_SIGNING_KEY)`)
signify = flag.String("signify", "", `Environment variable holding the signify key (e.g. LINUX_SIGNIFY_KEY)`)
upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`)
ext string
)
flag.CommandLine.Parse(cmdline)
switch *atype {
@ -701,15 +720,15 @@ func doArchive(cmdline []string) {
var (
env = build.Env()
basegeth = archiveBasename(*arch, version.Archive(env.Commit))
basegeth = archiveBasename(*targetOS, *arch, version.Archive(env.Commit))
geth = "geth-" + basegeth + ext
alltools = "geth-alltools-" + basegeth + ext
)
maybeSkipArchive(env)
if err := build.WriteArchive(geth, gethArchiveFiles); err != nil {
if err := build.WriteArchive(geth, gethArchiveFiles(*targetOS)); err != nil {
log.Fatal(err)
}
if err := build.WriteArchive(alltools, allToolsArchiveFiles); err != nil {
if err := build.WriteArchive(alltools, allToolsArchiveFiles(*targetOS)); err != nil {
log.Fatal(err)
}
for _, archive := range []string{geth, alltools} {
@ -735,7 +754,11 @@ func doKeeperArchive(cmdline []string) {
maybeSkipArchive(env)
files := []string{"COPYING"}
for _, target := range keeperTargets {
files = append(files, executablePath(fmt.Sprintf("keeper-%s", target.Name)))
targetOS := target.GOOS
if targetOS == "" {
targetOS = runtime.GOOS
}
files = append(files, executablePath(fmt.Sprintf("keeper-%s", target.Name), targetOS))
}
if err := build.WriteArchive(keeper, files); err != nil {
log.Fatal(err)
@ -745,8 +768,8 @@ func doKeeperArchive(cmdline []string) {
}
}
func archiveBasename(arch string, archiveVersion string) string {
platform := runtime.GOOS + "-" + arch
func archiveBasename(targetOS, arch, archiveVersion string) string {
platform := targetOS + "-" + arch
if arch == "arm" {
platform += os.Getenv("GOARM")
}
@ -1209,13 +1232,13 @@ func doWindowsInstaller(cmdline []string) {
env := build.Env()
maybeSkipArchive(env)
// Aggregate binaries that are included in the installer
// Aggregate binaries that are included in the installer.
var (
devTools []string
allTools []string
gethTool string
)
for _, file := range allToolsArchiveFiles {
for _, file := range allToolsArchiveFiles("windows") {
if file == "COPYING" { // license, copied later
continue
}
@ -1252,16 +1275,24 @@ func doWindowsInstaller(cmdline []string) {
if env.Commit != "" {
ver[2] += "-" + env.Commit[:8]
}
installer, err := filepath.Abs("geth-" + archiveBasename(*arch, version.Archive(env.Commit)) + ".exe")
installer, err := filepath.Abs("geth-" + archiveBasename("windows", *arch, version.Archive(env.Commit)) + ".exe")
if err != nil {
log.Fatalf("Failed to convert installer file path: %v", err)
}
build.MustRunCommand("makensis.exe",
"/DOUTPUTFILE="+installer,
"/DMAJORVERSION="+ver[0],
"/DMINORVERSION="+ver[1],
"/DBUILDVERSION="+ver[2],
"/DARCH="+*arch,
// makensis on Windows is "makensis.exe" with /D-style defines; on Linux
// (and other Unixes) the binary is "makensis" and accepts -D.
makensisCmd := "makensis"
defineFlag := "-D"
if runtime.GOOS == "windows" {
makensisCmd = "makensis.exe"
defineFlag = "/D"
}
build.MustRunCommand(makensisCmd,
defineFlag+"OUTPUTFILE="+installer,
defineFlag+"MAJORVERSION="+ver[0],
defineFlag+"MINORVERSION="+ver[1],
defineFlag+"BUILDVERSION="+ver[2],
defineFlag+"ARCH="+*arch,
filepath.Join(*workdir, "geth.nsi"),
)
// Sign and publish installer.

View file

@ -215,7 +215,7 @@ func generate(c *cli.Context) error {
code string
err error
)
if c.IsSet(v2Flag.Name) {
if c.Bool(v2Flag.Name) {
code, err = abigen.BindV2(types, abis, bins, c.String(pkgFlag.Name), libs, aliases)
} else {
code, err = abigen.Bind(types, abis, bins, sigs, c.String(pkgFlag.Name), libs, aliases)

View file

@ -194,7 +194,7 @@ func formatAttrString(v rlp.RawValue) (string, bool) {
func formatAttrIP(v rlp.RawValue) (string, bool) {
content, _, err := rlp.SplitString(v)
if err != nil || len(content) != 4 && len(content) != 6 {
if err != nil || len(content) != 4 && len(content) != 16 {
return "", false
}
return net.IP(content).String(), true

View file

@ -257,34 +257,50 @@ that they are returned by FINDNODE.`)
// Create bystanders.
nodes := make([]*bystander, 5)
added := make(chan enode.ID, len(nodes))
liveCh := make(chan enode.ID, len(nodes))
for i := range nodes {
nodes[i] = newBystander(t, s, added)
nodes[i] = newBystander(t, s, liveCh)
defer nodes[i].close()
}
// Get them added to the remote table.
// Prefill each bystander with the full bystander set so background FINDNODE
// lookups see useful routing data instead of empty responses.
known := make([]*enode.Node, 0, len(nodes))
for _, bn := range nodes {
known = append(known, bn.conn.localNode.Node())
}
for _, bn := range nodes {
bn.known = append([]*enode.Node(nil), known...)
}
// Wait until enough bystanders have actually become live, i.e. the remote node
// has revalidated them by sending PING and receiving our PONG.
requiredLiveNodes := len(nodes)
timeout := 60 * time.Second
timeoutCh := time.After(timeout)
for count := 0; count < len(nodes); {
liveSet := make(map[enode.ID]*enode.Node)
for len(liveSet) < requiredLiveNodes {
select {
case id := <-added:
t.Logf("bystander node %v added to remote table", id)
count++
case id := <-liveCh:
for _, bn := range nodes {
if bn.id() == id {
liveSet[id] = bn.conn.localNode.Node()
break
}
}
t.Logf("bystander node %v became live", id)
case <-timeoutCh:
t.Errorf("remote added %d bystander nodes in %v, need %d to continue", count, timeout, len(nodes))
t.Logf("this can happen if the node has a non-empty table from previous runs")
t.Errorf("remote revalidated %d bystander nodes in %v, need %d to continue", len(liveSet), timeout, requiredLiveNodes)
return
}
}
t.Logf("all %d bystander nodes were added", len(nodes))
t.Logf("continuing after all %d bystander nodes became live", len(liveSet))
// Collect our nodes by distance.
// Collect live nodes by distance.
var dists []uint
expect := make(map[enode.ID]*enode.Node)
for _, bn := range nodes {
n := bn.conn.localNode.Node()
expect[n.ID()] = n
for id, n := range liveSet {
expect[id] = n
d := uint(enode.LogDist(n.ID(), s.Dest.ID()))
if !slices.Contains(dists, d) {
dists = append(dists, d)
@ -295,42 +311,63 @@ that they are returned by FINDNODE.`)
t.Log("requesting nodes")
conn, l1 := s.listen1(t)
defer conn.close()
foundNodes, err := conn.findnode(l1, dists)
if err != nil {
t.Fatal(err)
}
t.Logf("remote returned %d nodes for distance list %v", len(foundNodes), dists)
for _, n := range foundNodes {
delete(expect, n.ID())
}
if len(expect) > 0 {
t.Errorf("missing %d nodes in FINDNODE result", len(expect))
t.Logf("this can happen if the test is run multiple times in quick succession")
t.Logf("and the remote node hasn't removed dead nodes from previous runs yet")
} else {
t.Logf("all %d expected nodes were returned", len(nodes))
const maxAttempts = 5
const retryInterval = 2 * time.Second
for attempt := 1; attempt <= maxAttempts; attempt++ {
foundNodes, err := conn.findnode(l1, dists)
if err != nil {
t.Fatal(err)
}
missing := make(map[enode.ID]struct{})
for id := range expect {
missing[id] = struct{}{}
}
for _, n := range foundNodes {
delete(missing, n.ID())
}
t.Logf("attempt %d: remote returned %d nodes for distance list %v, missing %d", attempt, len(foundNodes), dists, len(missing))
if len(missing) == 0 {
t.Logf("all %d expected live nodes were returned", len(expect))
return
}
if attempt < maxAttempts {
time.Sleep(retryInterval)
}
}
t.Errorf("missing nodes in FINDNODE result after %d attempts", maxAttempts)
t.Logf("this can happen if the node has a non-empty table from previous runs")
}
// A bystander is a node whose only purpose is filling a spot in the remote table.
type bystander struct {
dest *enode.Node
conn *conn
l net.PacketConn
dest *enode.Node
conn *conn
l net.PacketConn
known []*enode.Node
addedCh chan enode.ID
done sync.WaitGroup
liveCh chan enode.ID
sent map[v5wire.Nonce]v5wire.Packet
done sync.WaitGroup
}
func newBystander(t *utesting.T, s *Suite, added chan enode.ID) *bystander {
func newBystander(t *utesting.T, s *Suite, live chan enode.ID) *bystander {
conn, l := s.listen1(t)
conn.setEndpoint(l) // bystander nodes need IP/port to get pinged
bn := &bystander{
conn: conn,
l: l,
dest: s.Dest,
addedCh: added,
conn: conn,
l: l,
dest: s.Dest,
liveCh: live,
sent: make(map[v5wire.Nonce]v5wire.Packet),
}
// Establish an initial session and let the remote learn this node before
// switching to the passive responder loop below.
conn.reqresp(l, &v5wire.Ping{
ReqID: conn.nextReqID(),
ENRSeq: conn.localNode.Seq(),
})
bn.done.Add(1)
go bn.loop()
return bn
@ -351,48 +388,57 @@ func (bn *bystander) close() {
func (bn *bystander) loop() {
defer bn.done.Done()
var (
lastPing time.Time
wasAdded bool
)
for {
// Ping the remote node.
if !wasAdded && time.Since(lastPing) > 10*time.Second {
bn.conn.reqresp(bn.l, &v5wire.Ping{
ReqID: bn.conn.nextReqID(),
ENRSeq: bn.dest.Seq(),
})
lastPing = time.Now()
}
// Answer packets.
switch p := bn.conn.read(bn.l).(type) {
case *v5wire.Ping:
bn.conn.write(bn.l, &v5wire.Pong{
ReqID: p.ReqID,
ENRSeq: bn.conn.localNode.Seq(),
ToIP: bn.dest.IP(),
ToPort: uint16(bn.dest.UDP()),
}, nil)
wasAdded = true
bn.notifyAdded()
case *v5wire.Findnode:
bn.conn.write(bn.l, &v5wire.Nodes{ReqID: p.ReqID, RespCount: 1}, nil)
wasAdded = true
bn.notifyAdded()
case *v5wire.TalkRequest:
bn.conn.write(bn.l, &v5wire.TalkResponse{ReqID: p.ReqID}, nil)
case *readError:
if !netutil.IsTemporaryError(p.err) {
bn.conn.logf("shutting down: %v", p.err)
return
p, from := bn.conn.readFrom(bn.l)
switch p := p.(type) {
case *v5wire.Whoareyou:
p.Node = bn.dest
if resp, ok := bn.sent[p.Nonce]; ok {
nonce := bn.conn.writeTo(bn.l, resp, p, from)
delete(bn.sent, p.Nonce)
bn.sent[nonce] = resp
} else {
bn.conn.writeTo(bn.l, &v5wire.Ping{
ReqID: bn.conn.nextReqID(),
ENRSeq: bn.conn.localNode.Seq(),
}, p, from)
}
case *v5wire.Ping:
resp := &v5wire.Pong{
ReqID: append([]byte(nil), p.ReqID...),
ENRSeq: bn.conn.localNode.Seq(),
ToIP: from.IP,
ToPort: uint16(from.Port),
}
nonce := bn.conn.writeTo(bn.l, resp, nil, from)
bn.sent[nonce] = resp
bn.notifyLive()
case *v5wire.Findnode:
resp := &v5wire.Nodes{ReqID: append([]byte(nil), p.ReqID...), RespCount: 1}
for _, n := range bn.known {
if slices.Contains(p.Distances, uint(enode.LogDist(n.ID(), bn.id()))) {
resp.Nodes = append(resp.Nodes, n.Record())
}
}
nonce := bn.conn.writeTo(bn.l, resp, nil, from)
bn.sent[nonce] = resp
case *v5wire.TalkRequest:
resp := &v5wire.TalkResponse{ReqID: append([]byte(nil), p.ReqID...)}
nonce := bn.conn.writeTo(bn.l, resp, nil, from)
bn.sent[nonce] = resp
case *readError:
if netutil.IsTemporaryError(p.err) || v5wire.IsInvalidHeader(p.err) {
continue
}
bn.conn.logf("shutting down: %v", p.err)
return
}
}
}
func (bn *bystander) notifyAdded() {
if bn.addedCh != nil {
bn.addedCh <- bn.id()
bn.addedCh = nil
func (bn *bystander) notifyLive() {
if bn.liveCh != nil {
bn.liveCh <- bn.id()
bn.liveCh = nil
}
}

View file

@ -127,14 +127,16 @@ func (tc *conn) nextReqID() []byte {
// The request is retried if a handshake is requested.
func (tc *conn) reqresp(c net.PacketConn, req v5wire.Packet) v5wire.Packet {
reqnonce := tc.write(c, req, nil)
switch resp := tc.read(c).(type) {
resp, from := tc.readFrom(c)
switch resp := resp.(type) {
case *v5wire.Whoareyou:
if resp.Nonce != reqnonce {
return readErrorf("wrong nonce %x in WHOAREYOU (want %x)", resp.Nonce[:], reqnonce[:])
}
resp.Node = tc.remote
tc.write(c, req, resp)
return tc.read(c)
tc.writeTo(c, req, resp, from)
resp2, _ := tc.readFrom(c)
return resp2
default:
return resp
}
@ -150,21 +152,24 @@ func (tc *conn) findnode(c net.PacketConn, dists []uint) ([]*enode.Node, error)
results []*enode.Node
)
for n := 1; n > 0; {
switch resp := tc.read(c).(type) {
resp, from := tc.readFrom(c)
switch resp := resp.(type) {
case *v5wire.Whoareyou:
// Handle handshake.
if resp.Nonce == reqnonce {
resp.Node = tc.remote
tc.write(c, findnode, resp)
tc.writeTo(c, findnode, resp, from)
} else {
return nil, fmt.Errorf("unexpected WHOAREYOU (nonce %x), waiting for NODES", resp.Nonce[:])
}
case *v5wire.Ping:
// Handle ping from remote.
tc.write(c, &v5wire.Pong{
tc.writeTo(c, &v5wire.Pong{
ReqID: resp.ReqID,
ENRSeq: tc.localNode.Seq(),
}, nil)
ToIP: from.IP,
ToPort: uint16(from.Port),
}, nil, from)
case *v5wire.Nodes:
// Got NODES! Check request ID.
if !bytes.Equal(resp.ReqID, findnode.ReqID) {
@ -200,11 +205,16 @@ func (tc *conn) findnode(c net.PacketConn, dists []uint) ([]*enode.Node, error)
// write sends a packet on the given connection.
func (tc *conn) write(c net.PacketConn, p v5wire.Packet, challenge *v5wire.Whoareyou) v5wire.Nonce {
return tc.writeTo(c, p, challenge, tc.remoteAddr)
}
// writeTo sends a packet on the given connection to the given UDP address.
func (tc *conn) writeTo(c net.PacketConn, p v5wire.Packet, challenge *v5wire.Whoareyou, to *net.UDPAddr) v5wire.Nonce {
packet, nonce, err := tc.codec.Encode(tc.remote.ID(), tc.remoteAddr.String(), p, challenge)
if err != nil {
panic(fmt.Errorf("can't encode %v packet: %v", p.Name(), err))
}
if _, err := c.WriteTo(packet, tc.remoteAddr); err != nil {
if _, err := c.WriteTo(packet, to); err != nil {
tc.logf("Can't send %s: %v", p.Name(), err)
} else {
tc.logf(">> %s", p.Name())
@ -214,24 +224,30 @@ func (tc *conn) write(c net.PacketConn, p v5wire.Packet, challenge *v5wire.Whoar
// read waits for an incoming packet on the given connection.
func (tc *conn) read(c net.PacketConn) v5wire.Packet {
p, _ := tc.readFrom(c)
return p
}
// readFrom waits for an incoming packet and returns its source address.
func (tc *conn) readFrom(c net.PacketConn) (v5wire.Packet, *net.UDPAddr) {
buf := make([]byte, 1280)
if err := c.SetReadDeadline(time.Now().Add(waitTime)); err != nil {
return &readError{err}
return &readError{err}, nil
}
n, _, err := c.ReadFrom(buf)
n, from, err := c.ReadFrom(buf)
if err != nil {
return &readError{err}
return &readError{err}, nil
}
// Always use tc.remoteAddr for session lookup. The actual source address of
// the packet may differ from tc.remoteAddr when the remote node is reachable
// via multiple networks (e.g. Docker bridge vs. overlay), but the codec's
// session cache is keyed by the address used during Encode.
udpFrom, _ := from.(*net.UDPAddr)
// Use tc.remoteAddr for codec/session lookup because the fixture keys sessions
// by the advertised endpoint, but return the actual UDP source so responses can
// comply with the spec and go back to the request envelope address.
_, _, p, err := tc.codec.Decode(buf[:n], tc.remoteAddr.String())
if err != nil {
return &readError{err}
return &readError{err}, udpFrom
}
tc.logf("<< %s", p.Name())
return p
return p, udpFrom
}
// logf prints to the test log.

View file

@ -17,6 +17,7 @@
package main
import (
"bytes"
"errors"
"fmt"
"net"
@ -30,6 +31,31 @@ import (
"github.com/urfave/cli/v2"
)
// decodeRLPxDisconnect parses a disconnect message payload. Per the RLPx spec
// the payload is a list containing a single reason, but some implementations
// (including older geth) sent the reason as a bare byte. Accept both forms.
func decodeRLPxDisconnect(data []byte) (p2p.DiscReason, error) {
s := rlp.NewStream(bytes.NewReader(data), uint64(len(data)))
k, _, err := s.Kind()
if err != nil {
return 0, err
}
var reason p2p.DiscReason
if k == rlp.List {
if _, err := s.List(); err != nil {
return 0, err
}
if err := s.Decode(&reason); err != nil {
return 0, err
}
return reason, nil
}
if err := s.Decode(&reason); err != nil {
return 0, err
}
return reason, nil
}
var (
rlpxCommand = &cli.Command{
Name: "rlpx",
@ -103,11 +129,15 @@ func rlpxPing(ctx *cli.Context) error {
}
fmt.Printf("%+v\n", h)
case 1:
var msg []p2p.DiscReason
if rlp.DecodeBytes(data, &msg); len(msg) == 0 {
return errors.New("invalid disconnect message")
// The disconnect message is specified as a list containing the reason,
// but some implementations (including older geth) send the reason as a
// single byte. Handle both forms, and on failure include the raw payload
// so the operator can see what was actually sent.
reason, decErr := decodeRLPxDisconnect(data)
if decErr != nil {
return fmt.Errorf("invalid disconnect message: %v (raw=0x%x)", decErr, data)
}
return fmt.Errorf("received disconnect message: %v", msg[0])
return fmt.Errorf("received disconnect message: %v", reason)
default:
return fmt.Errorf("invalid message code %d, expected handshake (code zero) or disconnect (code one)", code)
}

View 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)
}
})
}
}

View file

@ -337,9 +337,6 @@ func checkAccumulator(e era.Era) error {
// accumulation across the entire set and are verified at the end.
for it.Next() {
// 1) next() walks the block index, so we're able to implicitly verify it.
if it.Error() != nil {
return fmt.Errorf("error reading block %d: %w", it.Number(), it.Error())
}
block, receipts, err := it.BlockAndReceipts()
if err != nil {
return fmt.Errorf("error reading block %d: %w", it.Number(), err)

View file

@ -17,9 +17,12 @@
package t8ntool
import (
"context"
"encoding/json"
"fmt"
stdmath "math"
"math/big"
"os"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
@ -47,6 +50,9 @@ type Prestate struct {
Env stEnv `json:"env"`
Pre types.GenesisAlloc `json:"pre"`
TreeLeaves map[common.Hash]hexutil.Bytes `json:"vkt,omitempty"`
// AllocPath, when non-empty, causes Apply to stream the alloc from disk
// instead of reading Pre, so the full map never materializes in memory.
AllocPath string `json:"-"`
}
//go:generate go run github.com/fjl/gencodec -type ExecutionResult -field-override executionResultMarshaling -out gen_execresult.go
@ -146,8 +152,19 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
return h
}
var (
isEIP4762 = chainConfig.IsVerkle(big.NewInt(int64(pre.Env.Number)), pre.Env.Timestamp)
statedb = MakePreState(rawdb.NewMemoryDatabase(), pre.Pre, isEIP4762)
isEIP4762 = chainConfig.IsUBT(big.NewInt(int64(pre.Env.Number)), pre.Env.Timestamp)
statedb *state.StateDB
)
if pre.AllocPath != "" {
var err error
statedb, err = MakePreStateStreaming(rawdb.NewMemoryDatabase(), pre.AllocPath, isEIP4762)
if err != nil {
return nil, nil, nil, err
}
} else {
statedb = MakePreState(rawdb.NewMemoryDatabase(), pre.Pre, isEIP4762)
}
var (
signer = types.MakeSigner(chainConfig, new(big.Int).SetUint64(pre.Env.Number), pre.Env.Timestamp)
gaspool = core.NewGasPool(pre.Env.GasLimit)
blockHash = common.Hash{0x13, 0x37}
@ -253,7 +270,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
continue
}
}
statedb.SetTxContext(tx.Hash(), len(receipts))
statedb.SetTxContext(tx.Hash(), len(receipts), uint32(len(receipts)+1))
var (
snapshot = statedb.Snapshot()
gp = gaspool.Snapshot()
@ -315,27 +332,14 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
}
// Gather the execution-layer triggered requests.
var requests [][]byte
if chainConfig.IsPrague(vmContext.BlockNumber, vmContext.Time) {
requests = [][]byte{}
// EIP-6110
var allLogs []*types.Log
for _, receipt := range receipts {
allLogs = append(allLogs, receipt.Logs...)
}
if err := core.ParseDepositLogs(&requests, allLogs, chainConfig); err != nil {
return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not parse requests logs: %v", err))
}
// EIP-7002
if err := core.ProcessWithdrawalQueue(&requests, evm); err != nil {
return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not process withdrawal requests: %v", err))
}
// EIP-7251
if err := core.ProcessConsolidationQueue(&requests, evm); err != nil {
return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not process consolidation requests: %v", err))
}
var allLogs []*types.Log
for _, receipt := range receipts {
allLogs = append(allLogs, receipt.Logs...)
}
requests, err := core.PostExecution(context.Background(), chainConfig, vmContext.BlockNumber, vmContext.Time, allLogs, evm, uint32(len(receipts)+1))
if err != nil {
return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("failed to process post-execution: %v", err))
}
// Commit block
root, err := statedb.Commit(vmContext.BlockNumber.Uint64(), chainConfig.IsEIP158(vmContext.BlockNumber), chainConfig.IsCancun(vmContext.BlockNumber, vmContext.Time))
if err != nil {
@ -378,7 +382,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
}
func MakePreState(db ethdb.Database, accounts types.GenesisAlloc, isBintrie bool) *state.StateDB {
tdb := triedb.NewDatabase(db, &triedb.Config{Preimages: true, IsVerkle: isBintrie})
tdb := triedb.NewDatabase(db, &triedb.Config{Preimages: true, IsUBT: isBintrie})
sdb := state.NewDatabase(tdb, nil)
root := types.EmptyRootHash
@ -414,6 +418,76 @@ func MakePreState(db ethdb.Database, accounts types.GenesisAlloc, isBintrie bool
return statedb
}
// MakePreStateStreaming is like MakePreState, but decodes the alloc from disk
// one account at a time so the full map is never held in memory.
func MakePreStateStreaming(db ethdb.Database, allocPath string, isBintrie bool) (*state.StateDB, error) {
tdb := triedb.NewDatabase(db, &triedb.Config{Preimages: true, IsUBT: isBintrie})
sdb := state.NewDatabase(tdb, nil)
root := types.EmptyRootHash
if isBintrie {
root = types.EmptyBinaryHash
}
statedb, err := state.New(root, sdb)
if err != nil {
return nil, NewError(ErrorEVM, fmt.Errorf("failed to create initial statedb: %v", err))
}
f, err := os.Open(allocPath)
if err != nil {
return nil, NewError(ErrorIO, fmt.Errorf("failed reading alloc file: %v", err))
}
defer f.Close()
dec := json.NewDecoder(f)
tok, err := dec.Token()
if err != nil {
return nil, NewError(ErrorJson, fmt.Errorf("failed reading alloc opening token: %v", err))
}
if d, ok := tok.(json.Delim); !ok || d != '{' {
return nil, NewError(ErrorJson, fmt.Errorf("expected alloc object, got %v", tok))
}
for dec.More() {
keyTok, err := dec.Token()
if err != nil {
return nil, NewError(ErrorJson, fmt.Errorf("failed reading alloc key: %v", err))
}
keyStr, ok := keyTok.(string)
if !ok {
return nil, NewError(ErrorJson, fmt.Errorf("alloc key not a string: %v", keyTok))
}
addr := common.HexToAddress(keyStr)
var acct types.Account
if err := dec.Decode(&acct); err != nil {
return nil, NewError(ErrorJson, fmt.Errorf("failed decoding account %s: %v", keyStr, err))
}
statedb.SetCode(addr, acct.Code, tracing.CodeChangeUnspecified)
statedb.SetNonce(addr, acct.Nonce, tracing.NonceChangeGenesis)
if acct.Balance != nil {
statedb.SetBalance(addr, uint256.MustFromBig(acct.Balance), tracing.BalanceIncreaseGenesisBalance)
}
for k, v := range acct.Storage {
statedb.SetState(addr, k, v)
}
}
if _, err := dec.Token(); err != nil {
return nil, NewError(ErrorJson, fmt.Errorf("failed reading alloc closing token: %v", err))
}
root, err = statedb.Commit(0, false, false)
if err != nil {
return nil, NewError(ErrorEVM, fmt.Errorf("failed to commit initial state: %v", err))
}
if isBintrie {
return statedb, nil
}
statedb, err = state.New(root, sdb)
if err != nil {
return nil, NewError(ErrorEVM, fmt.Errorf("failed to reopen state after commit: %v", err))
}
return statedb, nil
}
func rlpHash(x any) (h common.Hash) {
hw := keccak.NewLegacyKeccak256()
rlp.Encode(hw, x)

View file

@ -133,21 +133,21 @@ func Transaction(ctx *cli.Context) error {
}
// Check intrinsic gas
rules := chainConfig.Rules(common.Big0, true, 0)
gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai)
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai, rules.IsAmsterdam)
if err != nil {
r.Error = err
results = append(results, r)
continue
}
r.IntrinsicGas = gas
if tx.Gas() < gas {
r.Error = fmt.Errorf("%w: have %d, want %d", core.ErrIntrinsicGas, tx.Gas(), gas)
r.IntrinsicGas = cost.RegularGas
if tx.Gas() < cost.RegularGas {
r.Error = fmt.Errorf("%w: have %d, want %d", core.ErrIntrinsicGas, tx.Gas(), cost.RegularGas)
results = append(results, r)
continue
}
// For Prague txs, validate the floor data gas.
if rules.IsPrague {
floorDataGas, err := core.FloorDataGas(tx.Data())
floorDataGas, err := core.FloorDataGas(rules, tx.Data(), tx.AccessList())
if err != nil {
r.Error = err
results = append(results, r)
@ -185,7 +185,9 @@ func Transaction(ctx *cli.Context) error {
}
}
if chainConfig.IsOsaka(new(big.Int), 0) && tx.Gas() > params.MaxTxGas {
isOsaka := chainConfig.IsOsaka(new(big.Int), 0)
isAmsterdam := chainConfig.IsAmsterdam(new(big.Int), 0)
if isOsaka && !isAmsterdam && tx.Gas() > params.MaxTxGas {
r.Error = errors.New("gas limit exceeds maximum")
}
results = append(results, r)

View file

@ -17,6 +17,7 @@
package t8ntool
import (
"bufio"
"encoding/json"
"errors"
"fmt"
@ -115,11 +116,10 @@ func Transition(ctx *cli.Context) error {
}
}
if allocStr != stdinSelector {
if err := readFile(allocStr, "alloc", &inputData.Alloc); err != nil {
return err
}
prestate.AllocPath = allocStr
} else {
prestate.Pre = inputData.Alloc
}
prestate.Pre = inputData.Alloc
if btStr != stdinSelector && btStr != "" {
if err := readFile(btStr, "BT", &inputData.BT); err != nil {
@ -223,22 +223,57 @@ func Transition(ctx *cli.Context) error {
return err
}
}
// Dump the execution result
// Dump the execution result.
var (
collector = make(Alloc)
collector Alloc
btleaves map[common.Hash]hexutil.Bytes
)
isBinary := chainConfig.IsVerkle(big.NewInt(int64(prestate.Env.Number)), prestate.Env.Timestamp)
if !isBinary {
isBinary := chainConfig.IsUBT(big.NewInt(int64(prestate.Env.Number)), prestate.Env.Timestamp)
allocOutput := ctx.String(OutputAllocFlag.Name)
switch {
case !isBinary && allocOutput != "" && allocOutput != "stdout" && allocOutput != "stderr":
// Stream directly to the output file to avoid materializing the
// whole post-state in memory. dispatchOutput is told to skip alloc
// by clearing the output name.
if err := writeStreamedAlloc(filepath.Join(baseDir, allocOutput), s); err != nil {
return err
}
allocOutput = ""
case !isBinary:
collector = make(Alloc)
s.DumpToCollector(collector, nil)
} else {
default:
btleaves = make(map[common.Hash]hexutil.Bytes)
if err := s.DumpBinTrieLeaves(btleaves); err != nil {
return err
}
}
return dispatchOutput(ctx, baseDir, result, collector, allocOutput, body, btleaves)
}
return dispatchOutput(ctx, baseDir, result, collector, body, btleaves)
// writeStreamedAlloc writes the post-state alloc to path one account at a
// time, producing the same JSON shape as saveFile on an Alloc map.
func writeStreamedAlloc(path string, s *state.StateDB) error {
f, err := os.Create(path)
if err != nil {
return NewError(ErrorIO, fmt.Errorf("failed creating alloc output file: %v", err))
}
bw := bufio.NewWriter(f)
sa := newStreamingAlloc(bw)
s.DumpToCollector(sa, nil)
if err := sa.Close(); err != nil {
f.Close()
return NewError(ErrorIO, fmt.Errorf("failed writing alloc output: %v", err))
}
if err := bw.Flush(); err != nil {
f.Close()
return NewError(ErrorIO, fmt.Errorf("failed flushing alloc output: %v", err))
}
if err := f.Close(); err != nil {
return NewError(ErrorIO, fmt.Errorf("failed closing alloc output file: %v", err))
}
log.Info("Wrote file", "file", path)
return nil
}
func applyLondonChecks(env *stEnv, chainConfig *params.ChainConfig) error {
@ -327,6 +362,10 @@ func (g Alloc) OnAccount(addr *common.Address, dumpAccount state.DumpAccount) {
if addr == nil {
return
}
g[*addr] = dumpAccountToTypesAccount(dumpAccount)
}
func dumpAccountToTypesAccount(dumpAccount state.DumpAccount) types.Account {
balance, _ := new(big.Int).SetString(dumpAccount.Balance, 0)
var storage map[common.Hash]common.Hash
if dumpAccount.Storage != nil {
@ -335,13 +374,64 @@ func (g Alloc) OnAccount(addr *common.Address, dumpAccount state.DumpAccount) {
storage[k] = common.HexToHash(v)
}
}
genesisAccount := types.Account{
return types.Account{
Code: dumpAccount.Code,
Storage: storage,
Balance: balance,
Nonce: dumpAccount.Nonce,
}
g[*addr] = genesisAccount
}
// streamingAlloc is a DumpCollector that writes each account to w as it is
// visited, emitting a single JSON object keyed by address. Close must be
// called to emit the closing brace.
type streamingAlloc struct {
w io.Writer
wroteOne bool
err error
}
func newStreamingAlloc(w io.Writer) *streamingAlloc {
return &streamingAlloc{w: w}
}
func (s *streamingAlloc) write(b []byte) {
if s.err != nil {
return
}
_, s.err = s.w.Write(b)
}
func (s *streamingAlloc) OnRoot(common.Hash) {
s.write([]byte{'{'})
}
func (s *streamingAlloc) OnAccount(addr *common.Address, dumpAccount state.DumpAccount) {
if s.err != nil || addr == nil {
return
}
keyJSON, err := json.Marshal(*addr)
if err != nil {
s.err = err
return
}
valueJSON, err := json.Marshal(dumpAccountToTypesAccount(dumpAccount))
if err != nil {
s.err = err
return
}
if s.wroteOne {
s.write([]byte{','})
}
s.write(keyJSON)
s.write([]byte{':'})
s.write(valueJSON)
s.wroteOne = true
}
func (s *streamingAlloc) Close() error {
s.write([]byte{'}'})
return s.err
}
// saveFile marshals the object to the given file
@ -359,8 +449,9 @@ func saveFile(baseDir, filename string, data interface{}) error {
}
// dispatchOutput writes the output data to either stderr or stdout, or to the specified
// files
func dispatchOutput(ctx *cli.Context, baseDir string, result *ExecutionResult, alloc Alloc, body hexutil.Bytes, bt map[common.Hash]hexutil.Bytes) error {
// files. An empty allocOutput skips the alloc dispatch, which is used when the
// alloc has already been streamed to disk by the caller.
func dispatchOutput(ctx *cli.Context, baseDir string, result *ExecutionResult, alloc Alloc, allocOutput string, body hexutil.Bytes, bt map[common.Hash]hexutil.Bytes) error {
stdOutObject := make(map[string]interface{})
stdErrObject := make(map[string]interface{})
dispatch := func(baseDir, fName, name string, obj interface{}) error {
@ -378,7 +469,7 @@ func dispatchOutput(ctx *cli.Context, baseDir string, result *ExecutionResult, a
}
return nil
}
if err := dispatch(baseDir, ctx.String(OutputAllocFlag.Name), "alloc", alloc); err != nil {
if err := dispatch(baseDir, allocOutput, "alloc", alloc); err != nil {
return err
}
if err := dispatch(baseDir, ctx.String(OutputResultFlag.Name), "result", result); err != nil {
@ -452,10 +543,10 @@ func BinKeys(ctx *cli.Context) error {
return err
}
}
db := triedb.NewDatabase(rawdb.NewMemoryDatabase(), triedb.VerkleDefaults)
db := triedb.NewDatabase(rawdb.NewMemoryDatabase(), triedb.UBTDefaults)
defer db.Close()
bt, err := genBinTrieFromAlloc(alloc, db)
bt, err := genBinTrieFromAlloc(alloc, db, triedb.UBTDefaults.BinTrieGroupDepth)
if err != nil {
return fmt.Errorf("error generating bt: %w", err)
}
@ -496,10 +587,10 @@ func BinTrieRoot(ctx *cli.Context) error {
return err
}
}
db := triedb.NewDatabase(rawdb.NewMemoryDatabase(), triedb.VerkleDefaults)
db := triedb.NewDatabase(rawdb.NewMemoryDatabase(), triedb.UBTDefaults)
defer db.Close()
bt, err := genBinTrieFromAlloc(alloc, db)
bt, err := genBinTrieFromAlloc(alloc, db, triedb.UBTDefaults.BinTrieGroupDepth)
if err != nil {
return fmt.Errorf("error generating bt: %w", err)
}
@ -509,8 +600,8 @@ func BinTrieRoot(ctx *cli.Context) error {
}
// TODO(@CPerezz): Should this go to `bintrie` module?
func genBinTrieFromAlloc(alloc core.GenesisAlloc, db database.NodeDatabase) (*bintrie.BinaryTrie, error) {
bt, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, db)
func genBinTrieFromAlloc(alloc core.GenesisAlloc, db database.NodeDatabase, groupDepth int) (*bintrie.BinaryTrie, error) {
bt, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, db, groupDepth)
if err != nil {
return nil, err
}

View file

@ -115,7 +115,7 @@ var (
Name: "trace.noreturndata",
Aliases: []string{"noreturndata"},
Value: true,
Usage: "enable return data output",
Usage: "disable return data output",
Category: traceCategory,
}

View file

@ -166,8 +166,11 @@ func timedExec(bench bool, execFunc func() ([]byte, uint64, error)) ([]byte, exe
if haveGasUsed != gasUsed {
panic(fmt.Sprintf("gas differs, have %v want %v", haveGasUsed, gasUsed))
}
if haveErr != err {
panic(fmt.Sprintf("err differs, have %v want %v", haveErr, err))
if (haveErr == nil) != (err == nil) {
panic(fmt.Sprintf("err differs in nil-ness, have %v want %v", haveErr, err))
}
if haveErr != nil && err != nil && haveErr.Error() != err.Error() {
panic(fmt.Sprintf("err differs, have %q want %q", haveErr.Error(), err.Error()))
}
}
})
@ -318,7 +321,7 @@ func runCmd(ctx *cli.Context) error {
// don't mutate the state!
runtimeConfig.State = prestate.Copy()
output, _, gasLeft, err := runtime.Create(input, &runtimeConfig)
return output, gasLeft, err
return output, initialGas - gasLeft, err
}
} else {
if len(code) > 0 {

View file

@ -144,14 +144,14 @@ func convertToBinaryTrie(ctx *cli.Context) error {
defer srcTriedb.Close()
destTriedb := triedb.NewDatabase(chaindb, &triedb.Config{
IsVerkle: true,
IsUBT: true,
PathDB: &pathdb.Config{
JournalDirectory: stack.ResolvePath("triedb-bintrie"),
},
})
defer destTriedb.Close()
binTrie, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, destTriedb)
binTrie, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, destTriedb, ctx.Int(utils.BinTrieGroupDepthFlag.Name))
if err != nil {
return fmt.Errorf("failed to create binary trie: %w", err)
}
@ -319,7 +319,7 @@ func commitBinaryTrie(bt *bintrie.BinaryTrie, currentRoot common.Hash, destDB *t
runtime.GC()
debug.FreeOSMemory()
bt, err := bintrie.NewBinaryTrie(newRoot, destDB)
bt, err := bintrie.NewBinaryTrie(newRoot, destDB, bt.GroupDepth())
if err != nil {
return nil, common.Hash{}, fmt.Errorf("failed to reload binary trie: %w", err)
}

View file

@ -82,12 +82,12 @@ func TestBintrieConvert(t *testing.T) {
defer srcTriedb2.Close()
destTriedb := triedb.NewDatabase(chaindb, &triedb.Config{
IsVerkle: true,
PathDB: pathdb.Defaults,
IsUBT: true,
PathDB: pathdb.Defaults,
})
defer destTriedb.Close()
bt, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, destTriedb)
bt, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, destTriedb, 8)
if err != nil {
t.Fatalf("failed to create binary trie: %v", err)
}
@ -98,7 +98,7 @@ func TestBintrieConvert(t *testing.T) {
}
t.Logf("Binary trie root: %x", currentRoot)
bt2, err := bintrie.NewBinaryTrie(currentRoot, destTriedb)
bt2, err := bintrie.NewBinaryTrie(currentRoot, destTriedb, 8)
if err != nil {
t.Fatalf("failed to reload binary trie: %v", err)
}
@ -190,11 +190,11 @@ func TestBintrieConvertDeleteSource(t *testing.T) {
})
destTriedb := triedb.NewDatabase(chaindb, &triedb.Config{
IsVerkle: true,
PathDB: pathdb.Defaults,
IsUBT: true,
PathDB: pathdb.Defaults,
})
bt, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, destTriedb)
bt, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, destTriedb, 8)
if err != nil {
t.Fatalf("failed to create binary trie: %v", err)
}
@ -209,7 +209,7 @@ func TestBintrieConvertDeleteSource(t *testing.T) {
}
srcTriedb2.Close()
bt2, err := bintrie.NewBinaryTrie(newRoot, destTriedb)
bt2, err := bintrie.NewBinaryTrie(newRoot, destTriedb, 8)
if err != nil {
t.Fatalf("failed to reload binary trie after deletion: %v", err)
}

View file

@ -64,7 +64,7 @@ var (
utils.OverrideOsaka,
utils.OverrideBPO1,
utils.OverrideBPO2,
utils.OverrideVerkle,
utils.OverrideUBT,
}, utils.DatabaseFlags),
Description: `
The init command initializes a new genesis block and definition for the network.
@ -297,15 +297,15 @@ func initGenesis(ctx *cli.Context) error {
v := ctx.Uint64(utils.OverrideBPO2.Name)
overrides.OverrideBPO2 = &v
}
if ctx.IsSet(utils.OverrideVerkle.Name) {
v := ctx.Uint64(utils.OverrideVerkle.Name)
overrides.OverrideVerkle = &v
if ctx.IsSet(utils.OverrideUBT.Name) {
v := ctx.Uint64(utils.OverrideUBT.Name)
overrides.OverrideUBT = &v
}
chaindb := utils.MakeChainDatabase(ctx, stack, false)
defer chaindb.Close()
triedb := utils.MakeTrieDatabase(ctx, stack, chaindb, ctx.Bool(utils.CachePreimagesFlag.Name), false, genesis.IsVerkle())
triedb := utils.MakeTrieDatabase(ctx, stack, chaindb, ctx.Bool(utils.CachePreimagesFlag.Name), false, genesis.IsUBT())
defer triedb.Close()
_, hash, compatErr, err := core.SetupGenesisBlockWithOverride(chaindb, triedb, genesis, &overrides, nil)
@ -325,7 +325,7 @@ func dumpGenesis(ctx *cli.Context) error {
var genesis *core.Genesis
if utils.IsNetworkPreset(ctx) {
genesis = utils.MakeGenesis(ctx)
} else if ctx.IsSet(utils.DeveloperFlag.Name) && !ctx.IsSet(utils.DataDirFlag.Name) {
} else if ctx.Bool(utils.DeveloperFlag.Name) && !ctx.IsSet(utils.DataDirFlag.Name) {
genesis = core.DeveloperGenesisBlock(11_500_000, nil)
}

View file

@ -38,6 +38,7 @@ import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth/catalyst"
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/eth/syncer"
"github.com/ethereum/go-ethereum/internal/flags"
"github.com/ethereum/go-ethereum/internal/telemetry/tracesetup"
"github.com/ethereum/go-ethereum/internal/version"
@ -235,9 +236,9 @@ func makeFullNode(ctx *cli.Context) *node.Node {
v := ctx.Uint64(utils.OverrideBPO2.Name)
cfg.Eth.OverrideBPO2 = &v
}
if ctx.IsSet(utils.OverrideVerkle.Name) {
v := ctx.Uint64(utils.OverrideVerkle.Name)
cfg.Eth.OverrideVerkle = &v
if ctx.IsSet(utils.OverrideUBT.Name) {
v := ctx.Uint64(utils.OverrideUBT.Name)
cfg.Eth.OverrideUBT = &v
}
// Start metrics export if enabled.
@ -269,25 +270,28 @@ func makeFullNode(ctx *cli.Context) *node.Node {
filterSystem := utils.RegisterFilterAPI(stack, backend, &cfg.Eth)
// Configure GraphQL if requested.
if ctx.IsSet(utils.GraphQLEnabledFlag.Name) {
if ctx.Bool(utils.GraphQLEnabledFlag.Name) {
utils.RegisterGraphQLService(stack, backend, filterSystem, &cfg.Node)
}
// Add the Ethereum Stats daemon if requested.
if cfg.Ethstats.URL != "" {
utils.RegisterEthStatsService(stack, backend, cfg.Ethstats.URL)
}
// Configure synchronization override service
var synctarget common.Hash
syncConfig := syncer.Config{
ExitWhenSynced: ctx.Bool(utils.ExitWhenSyncedFlag.Name),
}
if ctx.IsSet(utils.SyncTargetFlag.Name) {
target := ctx.String(utils.SyncTargetFlag.Name)
if !common.IsHexHash(target) {
utils.Fatalf("sync target hash is not a valid hex hash: %s", target)
}
synctarget = common.HexToHash(target)
syncConfig.TargetBlock = common.HexToHash(target)
}
utils.RegisterSyncOverrideService(stack, eth, synctarget, ctx.Bool(utils.ExitWhenSyncedFlag.Name))
utils.RegisterSyncOverrideService(stack, eth, syncConfig)
if ctx.IsSet(utils.DeveloperFlag.Name) {
if ctx.Bool(utils.DeveloperFlag.Name) {
// Start dev mode.
simBeacon, err := catalyst.NewSimulatedBeacon(ctx.Uint64(utils.DeveloperPeriodFlag.Name), cfg.Eth.Miner.PendingFeeRecipient, eth)
if err != nil {

View file

@ -806,6 +806,24 @@ func (iter *snapshotIterator) Release() {
iter.storage.Release()
}
type codeIterator struct {
iter ethdb.Iterator
}
func (iter *codeIterator) Next() (byte, []byte, []byte, bool) {
for iter.iter.Next() {
key := iter.iter.Key()
if bytes.HasPrefix(key, rawdb.CodePrefix) && len(key) == (len(rawdb.CodePrefix)+common.HashLength) {
return utils.OpBatchAdd, key, iter.iter.Value(), true
}
}
return 0, nil, nil, false
}
func (iter *codeIterator) Release() {
iter.iter.Release()
}
// chainExporters defines the export scheme for all exportable chain data.
var chainExporters = map[string]func(db ethdb.Database) utils.ChainDataIterator{
"preimage": func(db ethdb.Database) utils.ChainDataIterator {
@ -817,6 +835,10 @@ var chainExporters = map[string]func(db ethdb.Database) utils.ChainDataIterator{
storage := db.NewIterator(rawdb.SnapshotStoragePrefix, nil)
return &snapshotIterator{account: account, storage: storage}
},
"code": func(db ethdb.Database) utils.ChainDataIterator {
iter := db.NewIterator(rawdb.CodePrefix, nil)
return &codeIterator{iter: iter}
},
}
func exportChaindata(ctx *cli.Context) error {

View file

@ -22,13 +22,10 @@ import (
"os"
"slices"
"sort"
"time"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/console/prompt"
"github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/internal/debug"
"github.com/ethereum/go-ethereum/internal/flags"
@ -64,7 +61,7 @@ var (
utils.OverrideOsaka,
utils.OverrideBPO1,
utils.OverrideBPO2,
utils.OverrideVerkle,
utils.OverrideUBT,
utils.OverrideGenesisFlag,
utils.EnablePersonal, // deprecated
utils.TxPoolLocalsFlag,
@ -95,6 +92,7 @@ var (
utils.StateHistoryFlag,
utils.TrienodeHistoryFlag,
utils.TrienodeHistoryFullValueCheckpointFlag,
utils.BinTrieGroupDepthFlag,
utils.LightKDFFlag,
utils.EthRequiredBlocksFlag,
utils.LegacyWhitelistFlag, // deprecated
@ -386,28 +384,4 @@ func startNode(ctx *cli.Context, stack *node.Node, isConsole bool) {
}
}
}()
// Spawn a standalone goroutine for status synchronization monitoring,
// close the node when synchronization is complete if user required.
if ctx.Bool(utils.ExitWhenSyncedFlag.Name) {
go func() {
sub := stack.EventMux().Subscribe(downloader.DoneEvent{})
defer sub.Unsubscribe()
for {
event := <-sub.Chan()
if event == nil {
continue
}
done, ok := event.Data.(downloader.DoneEvent)
if !ok {
continue
}
if timestamp := time.Unix(int64(done.Latest.Time), 0); time.Since(timestamp) < 10*time.Minute {
log.Info("Synchronisation completed", "latestnum", done.Latest.Number, "latesthash", done.Latest.Hash(),
"age", common.PrettyAge(timestamp))
stack.Close()
}
}
}()
}
}

View file

@ -18,15 +18,18 @@ package main
import (
"bytes"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"os"
"slices"
"sort"
"time"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/state/pruner"
@ -168,6 +171,22 @@ block is used.
Description: `
The export-preimages command exports hash preimages to a flat file, in exactly
the expected order for the overlay tree migration.
`,
},
{
Name: "list-eip-7610-accounts",
Aliases: []string{"eip7610"},
Usage: "list EIP7610 eligible accounts",
Action: listEIP7610EligibleAccounts,
Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags),
Description: `
geth snapshot list-eip-7610-accounts
traverses the postEIP-161 state and returns all accounts that are eligible
under EIP-7610: accounts with zero nonce, empty runtime code, and non-empty
storage. The traversal will be aborted immediately if the state is prior to
EIP-161.
The exported accounts are identified by their address.
`,
},
},
@ -801,3 +820,92 @@ func checkAccount(ctx *cli.Context) error {
log.Info("Checked the snapshot journalled storage", "time", common.PrettyDuration(time.Since(start)))
return nil
}
// listEIP7610EligibleAccounts traverses the postEIP-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
}

View file

@ -38,8 +38,8 @@ require (
go.opentelemetry.io/otel v1.40.0 // indirect
go.opentelemetry.io/otel/metric v1.40.0 // indirect
go.opentelemetry.io/otel/trace v1.40.0 // indirect
golang.org/x/crypto v0.44.0 // indirect
golang.org/x/sync v0.18.0 // indirect
golang.org/x/crypto v0.47.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.40.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

View file

@ -127,20 +127,20 @@ go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ7
go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE=
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View file

@ -264,9 +264,9 @@ var (
Usage: "Manually specify the bpo2 fork timestamp, overriding the bundled setting",
Category: flags.EthCategory,
}
OverrideVerkle = &cli.Uint64Flag{
Name: "override.verkle",
Usage: "Manually specify the Verkle fork timestamp, overriding the bundled setting",
OverrideUBT = &cli.Uint64Flag{
Name: "override.ubt",
Usage: "Manually specify the UBT fork timestamp, overriding the bundled setting",
Category: flags.EthCategory,
}
OverrideGenesisFlag = &cli.StringFlag{
@ -297,6 +297,12 @@ var (
Value: ethconfig.Defaults.EnableStateSizeTracking,
Category: flags.StateCategory,
}
BinTrieGroupDepthFlag = &cli.IntFlag{
Name: "bintrie.groupdepth",
Usage: "Number of levels per serialized group in binary trie (1-8, default 5). Lower values create smaller groups with more nodes.",
Value: 5,
Category: flags.StateCategory,
}
StateHistoryFlag = &cli.Uint64Flag{
Name: "history.state",
Usage: "Number of recent blocks to retain state history for, only relevant in state.scheme=path (default = 90,000 blocks, 0 = entire chain)",
@ -1067,19 +1073,19 @@ Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server.
RPCTelemetryEndpointFlag = &cli.StringFlag{
Name: "rpc.telemetry.endpoint",
Usage: "Defines where RPC telemetry is sent (e.g., http://localhost:4318)",
Usage: "Defines where RPC telemetry is sent (e.g., http://localhost:4318 or grpc://localhost:4317)",
Category: flags.APICategory,
}
RPCTelemetryUserFlag = &cli.StringFlag{
Name: "rpc.telemetry.username",
Usage: "HTTP Basic Auth username for OpenTelemetry",
Usage: "Basic Auth username for OpenTelemetry",
Category: flags.APICategory,
}
RPCTelemetryPasswordFlag = &cli.StringFlag{
Name: "rpc.telemetry.password",
Usage: "HTTP Basic Auth password for OpenTelemetry",
Usage: "Basic Auth password for OpenTelemetry",
Category: flags.APICategory,
}
@ -1098,7 +1104,7 @@ Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server.
RPCTelemetrySampleRatioFlag = &cli.Float64Flag{
Name: "rpc.telemetry.sample-ratio",
Usage: "Defines the sampling ratio for RPC telemetry (0.0 to 1.0)",
Value: 1.0,
Value: node.DefaultConfig.OpenTelemetry.SampleRatio,
Category: flags.APICategory,
}
// Era flags are a group of flags related to the era archive format.
@ -1817,6 +1823,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
if ctx.IsSet(TrienodeHistoryFullValueCheckpointFlag.Name) {
cfg.NodeFullValueCheckpoint = uint32(ctx.Uint(TrienodeHistoryFullValueCheckpointFlag.Name))
}
if ctx.IsSet(BinTrieGroupDepthFlag.Name) {
cfg.BinTrieGroupDepth = ctx.Int(BinTrieGroupDepthFlag.Name)
}
if ctx.IsSet(StateSchemeFlag.Name) {
cfg.StateScheme = ctx.String(StateSchemeFlag.Name)
}
@ -1899,7 +1908,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
cfg.StatelessSelfValidation = ctx.Bool(VMStatelessSelfValidationFlag.Name)
}
// Auto-enable StatelessSelfValidation when witness stats are enabled
if ctx.Bool(VMWitnessStatsFlag.Name) {
if cfg.EnableWitnessStats {
cfg.StatelessSelfValidation = true
}
@ -2228,13 +2237,13 @@ func RegisterFilterAPI(stack *node.Node, backend ethapi.Backend, ethcfg *ethconf
}
// RegisterSyncOverrideService adds the synchronization override service into node.
func RegisterSyncOverrideService(stack *node.Node, eth *eth.Ethereum, target common.Hash, exitWhenSynced bool) {
if target != (common.Hash{}) {
log.Info("Registered sync override service", "hash", target, "exitWhenSynced", exitWhenSynced)
func RegisterSyncOverrideService(stack *node.Node, eth *eth.Ethereum, config syncer.Config) {
if config.TargetBlock != (common.Hash{}) {
log.Info("Registered sync override service", "hash", config.TargetBlock, "exitWhenSynced", config.ExitWhenSynced)
} else {
log.Info("Registered sync override service")
}
syncer.Register(stack, eth, target, exitWhenSynced)
syncer.Register(stack, eth, config)
}
// SetupMetrics configures the metrics system.
@ -2433,6 +2442,7 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh
StateHistory: ctx.Uint64(StateHistoryFlag.Name),
TrienodeHistory: ctx.Int64(TrienodeHistoryFlag.Name),
NodeFullValueCheckpoint: uint32(ctx.Uint(TrienodeHistoryFullValueCheckpointFlag.Name)),
BinTrieGroupDepth: ctx.Int(BinTrieGroupDepthFlag.Name),
// Disable transaction indexing/unindexing.
TxLookupLimit: -1,
@ -2516,10 +2526,10 @@ func MakeConsolePreloads(ctx *cli.Context) []string {
}
// MakeTrieDatabase constructs a trie database based on the configured scheme.
func MakeTrieDatabase(ctx *cli.Context, stack *node.Node, disk ethdb.Database, preimage bool, readOnly bool, isVerkle bool) *triedb.Database {
func MakeTrieDatabase(ctx *cli.Context, stack *node.Node, disk ethdb.Database, preimage bool, readOnly bool, isUBT bool) *triedb.Database {
config := &triedb.Config{
Preimages: preimage,
IsVerkle: isVerkle,
IsUBT: isUBT,
}
scheme, err := rawdb.ParseStateScheme(ctx.String(StateSchemeFlag.Name), disk)
if err != nil {

View file

@ -204,6 +204,10 @@ func (b *Big) ToInt() *big.Int {
return (*big.Int)(b)
}
func (b *Big) ToUint256() (*uint256.Int, bool) {
return uint256.FromBig((*big.Int)(b))
}
// String returns the hex encoding of b.
func (b *Big) String() string {
return EncodeBig(b.ToInt())

View file

@ -17,7 +17,6 @@
package beacon
import (
"context"
"errors"
"fmt"
"math/big"
@ -26,13 +25,10 @@ import (
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/misc/eip1559"
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/internal/telemetry"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie"
"github.com/holiman/uint256"
)
@ -361,48 +357,6 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types.
// No block reward which is issued by consensus layer instead.
}
// FinalizeAndAssemble implements consensus.Engine, setting the final state and
// assembling the block.
func (beacon *Beacon) FinalizeAndAssemble(ctx context.Context, chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (result *types.Block, err error) {
ctx, _, spanEnd := telemetry.StartSpan(ctx, "consensus.beacon.FinalizeAndAssemble",
telemetry.Int64Attribute("block.number", int64(header.Number.Uint64())),
telemetry.Int64Attribute("txs.count", int64(len(body.Transactions))),
telemetry.Int64Attribute("withdrawals.count", int64(len(body.Withdrawals))),
)
defer spanEnd(&err)
if !beacon.IsPoSHeader(header) {
block, delegateErr := beacon.ethone.FinalizeAndAssemble(ctx, chain, header, state, body, receipts)
return block, delegateErr
}
shanghai := chain.Config().IsShanghai(header.Number, header.Time)
if shanghai {
// All blocks after Shanghai must include a withdrawals root.
if body.Withdrawals == nil {
body.Withdrawals = make([]*types.Withdrawal, 0)
}
} else {
if len(body.Withdrawals) > 0 {
return nil, errors.New("withdrawals set before Shanghai activation")
}
}
// Finalize and assemble the block.
_, _, finalizeSpanEnd := telemetry.StartSpan(ctx, "consensus.beacon.Finalize")
beacon.Finalize(chain, header, state, body)
finalizeSpanEnd(nil)
// Assign the final state root to header.
_, _, rootSpanEnd := telemetry.StartSpan(ctx, "consensus.beacon.IntermediateRoot")
header.Root = state.IntermediateRoot(true)
rootSpanEnd(nil)
// Assemble the final block.
_, _, blockSpanEnd := telemetry.StartSpan(ctx, "consensus.beacon.NewBlock")
block := types.NewBlock(header, body, receipts, trie.NewStackTrie(nil))
blockSpanEnd(nil)
return block, nil
}
// Seal generates a new sealing request for the given input block and pushes
// the result into the given channel.
//

View file

@ -19,7 +19,6 @@ package clique
import (
"bytes"
"context"
"errors"
"fmt"
"io"
@ -34,7 +33,6 @@ import (
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/misc"
"github.com/ethereum/go-ethereum/consensus/misc/eip1559"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
@ -43,7 +41,6 @@ import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
)
const (
@ -580,22 +577,6 @@ func (c *Clique) Finalize(chain consensus.ChainHeaderReader, header *types.Heade
// No block rewards in PoA, so the state remains as is
}
// FinalizeAndAssemble implements consensus.Engine, ensuring no uncles are set,
// nor block rewards given, and returns the final block.
func (c *Clique) FinalizeAndAssemble(ctx context.Context, chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error) {
if len(body.Withdrawals) > 0 {
return nil, errors.New("clique does not support withdrawals")
}
// Finalize block
c.Finalize(chain, header, state, body)
// Assign the final state root to header.
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
// Assemble and return the final block for sealing.
return types.NewBlock(header, &types.Body{Transactions: body.Transactions}, receipts, trie.NewStackTrie(nil)), nil
}
// Authorize injects a private key into the consensus engine to mint new blocks
// with.
func (c *Clique) Authorize(signer common.Address) {

View file

@ -18,11 +18,9 @@
package consensus
import (
"context"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/params"
@ -88,13 +86,6 @@ type Engine interface {
// that happen at finalization (e.g. block rewards).
Finalize(chain ChainHeaderReader, header *types.Header, state vm.StateDB, body *types.Body)
// FinalizeAndAssemble runs any post-transaction state modifications (e.g. block
// rewards or process withdrawals) and assembles the final block.
//
// Note: The block header and state database might be updated to reflect any
// consensus rules that happen at finalization (e.g. block rewards).
FinalizeAndAssemble(ctx context.Context, chain ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error)
// Seal generates a new sealing request for the given input block and pushes
// the result into the given channel.
//

View file

@ -17,7 +17,6 @@
package ethash
import (
"context"
"errors"
"fmt"
"math/big"
@ -28,14 +27,12 @@ import (
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/misc"
"github.com/ethereum/go-ethereum/consensus/misc/eip1559"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto/keccak"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
"github.com/holiman/uint256"
)
@ -512,22 +509,6 @@ func (ethash *Ethash) Finalize(chain consensus.ChainHeaderReader, header *types.
accumulateRewards(chain.Config(), state, header, body.Uncles)
}
// FinalizeAndAssemble implements consensus.Engine, accumulating the block and
// uncle rewards, setting the final state and assembling the block.
func (ethash *Ethash) FinalizeAndAssemble(ctx context.Context, chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error) {
if len(body.Withdrawals) > 0 {
return nil, errors.New("ethash does not support withdrawals")
}
// Finalize block
ethash.Finalize(chain, header, state, body)
// Assign the final state root to header.
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
// Header seems complete, assemble into a block and return
return types.NewBlock(header, &types.Body{Transactions: body.Transactions, Uncles: body.Uncles}, receipts, trie.NewStackTrie(nil)), nil
}
// SealHash returns the hash of a block prior to it being sealed.
func (ethash *Ethash) SealHash(header *types.Header) (hash common.Hash) {
hasher := keccak.NewLegacyKeccak256()

View file

@ -89,7 +89,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) {
data := make([]byte, nbytes)
return func(i int, gen *BlockGen) {
toaddr := common.Address{}
gas, _ := IntrinsicGas(data, nil, nil, false, false, false, false)
cost, _ := IntrinsicGas(data, nil, nil, false, false, false, false, false)
signer := gen.Signer()
gasPrice := big.NewInt(0)
if gen.header.BaseFee != nil {
@ -99,7 +99,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) {
Nonce: gen.TxNonce(benchRootAddr),
To: &toaddr,
Value: big.NewInt(1),
Gas: gas,
Gas: cost.RegularGas,
Data: data,
GasPrice: gasPrice,
})

View file

@ -36,7 +36,7 @@ import (
)
var (
testVerkleChainConfig = &params.ChainConfig{
testUBTChainConfig = &params.ChainConfig{
ChainID: big.NewInt(1),
HomesteadBlock: big.NewInt(0),
EIP150Block: big.NewInt(0),
@ -51,30 +51,30 @@ var (
LondonBlock: big.NewInt(0),
Ethash: new(params.EthashConfig),
ShanghaiTime: u64(0),
VerkleTime: u64(0),
UBTTime: u64(0),
TerminalTotalDifficulty: common.Big0,
EnableVerkleAtGenesis: true,
EnableUBTAtGenesis: true,
BlobScheduleConfig: &params.BlobScheduleConfig{
Verkle: params.DefaultPragueBlobConfig,
UBT: params.DefaultPragueBlobConfig,
},
}
)
func TestProcessVerkle(t *testing.T) {
func TestProcessUBT(t *testing.T) {
var (
code = common.FromHex(`6060604052600a8060106000396000f360606040526008565b00`)
intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, nil, true, true, true, true)
intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, nil, true, true, true, true, false)
// A contract creation that calls EXTCODECOPY in the constructor. Used to ensure that the witness
// will not contain that copied data.
// Source: https://gist.github.com/gballet/a23db1e1cb4ed105616b5920feb75985
codeWithExtCodeCopy = common.FromHex(`0x60806040526040516100109061017b565b604051809103906000f08015801561002c573d6000803e3d6000fd5b506000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561007857600080fd5b5060008067ffffffffffffffff8111156100955761009461024a565b5b6040519080825280601f01601f1916602001820160405280156100c75781602001600182028036833780820191505090505b50905060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690506020600083833c81610101906101e3565b60405161010d90610187565b61011791906101a3565b604051809103906000f080158015610133573d6000803e3d6000fd5b50600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550505061029b565b60d58061046783390190565b6102068061053c83390190565b61019d816101d9565b82525050565b60006020820190506101b86000830184610194565b92915050565b6000819050602082019050919050565b600081519050919050565b6000819050919050565b60006101ee826101ce565b826101f8846101be565b905061020381610279565b925060208210156102435761023e7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8360200360080261028e565b831692505b5050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600061028582516101d9565b80915050919050565b600082821b905092915050565b6101bd806102aa6000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f566852414610030575b600080fd5b61003861004e565b6040516100459190610146565b60405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166381ca91d36040518163ffffffff1660e01b815260040160206040518083038186803b1580156100b857600080fd5b505afa1580156100cc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100f0919061010a565b905090565b60008151905061010481610170565b92915050565b6000602082840312156101205761011f61016b565b5b600061012e848285016100f5565b91505092915050565b61014081610161565b82525050565b600060208201905061015b6000830184610137565b92915050565b6000819050919050565b600080fd5b61017981610161565b811461018457600080fd5b5056fea2646970667358221220a6a0e11af79f176f9c421b7b12f441356b25f6489b83d38cc828a701720b41f164736f6c63430008070033608060405234801561001057600080fd5b5060b68061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063ab5ed15014602d575b600080fd5b60336047565b604051603e9190605d565b60405180910390f35b60006001905090565b6057816076565b82525050565b6000602082019050607060008301846050565b92915050565b600081905091905056fea26469706673582212203a14eb0d5cd07c277d3e24912f110ddda3e553245a99afc4eeefb2fbae5327aa64736f6c63430008070033608060405234801561001057600080fd5b5060405161020638038061020683398181016040528101906100329190610063565b60018160001c6100429190610090565b60008190555050610145565b60008151905061005d8161012e565b92915050565b60006020828403121561007957610078610129565b5b60006100878482850161004e565b91505092915050565b600061009b826100f0565b91506100a6836100f0565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156100db576100da6100fa565b5b828201905092915050565b6000819050919050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600080fd5b610137816100e6565b811461014257600080fd5b50565b60b3806101536000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806381ca91d314602d575b600080fd5b60336047565b604051603e9190605a565b60405180910390f35b60005481565b6054816073565b82525050565b6000602082019050606d6000830184604d565b92915050565b600081905091905056fea26469706673582212209bff7098a2f526de1ad499866f27d6d0d6f17b74a413036d6063ca6a0998ca4264736f6c63430008070033`)
intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, nil, true, true, true, true)
signer = types.LatestSigner(testVerkleChainConfig)
intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, nil, true, true, true, true, false)
signer = types.LatestSigner(testUBTChainConfig)
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
bcdb = rawdb.NewMemoryDatabase() // Database for the blockchain
coinbase = common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7")
gspec = &Genesis{
Config: testVerkleChainConfig,
Config: testUBTChainConfig,
Alloc: GenesisAlloc{
coinbase: {
Balance: big.NewInt(1000000000000000000), // 1 ether
@ -87,21 +87,22 @@ func TestProcessVerkle(t *testing.T) {
},
}
)
// Verkle trees use the snapshot, which must be enabled before the
// UBTs use the snapshot, which must be enabled before the
// data is saved into the tree+database.
// genesis := gspec.MustCommit(bcdb, triedb)
options := DefaultConfig().WithStateScheme(rawdb.PathScheme)
options.SnapshotLimit = 0
options.BinTrieGroupDepth = triedb.DefaultBinTrieGroupDepth
blockchain, _ := NewBlockChain(bcdb, gspec, beacon.New(ethash.NewFaker()), options)
defer blockchain.Stop()
txCost1 := params.TxGas
txCost2 := params.TxGas
contractCreationCost := intrinsicContractCreationGas +
contractCreationCost := intrinsicContractCreationGas.RegularGas +
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + params.WitnessBranchReadCost + params.WitnessBranchWriteCost + /* creation */
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* creation with value */
739 /* execution costs */
codeWithExtCodeCopyGas := intrinsicCodeWithExtCodeCopyGas +
codeWithExtCodeCopyGas := intrinsicCodeWithExtCodeCopyGas.RegularGas +
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + params.WitnessBranchReadCost + params.WitnessBranchWriteCost + /* creation (tx) */
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + params.WitnessBranchReadCost + params.WitnessBranchWriteCost + /* creation (CREATE at pc=0x20) */
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* write code hash */
@ -188,7 +189,7 @@ func TestProcessParentBlockHash(t *testing.T) {
// block 1 parent hash is 0x0100....
// block 2 parent hash is 0x0200....
// etc
checkBlockHashes := func(statedb *state.StateDB, isVerkle bool) {
checkBlockHashes := func(statedb *state.StateDB, isUBT bool) {
statedb.SetNonce(params.HistoryStorageAddress, 1, tracing.NonceChangeUnspecified)
statedb.SetCode(params.HistoryStorageAddress, params.HistoryStorageCode, tracing.CodeChangeUnspecified)
// Process n blocks, from 1 .. num
@ -196,8 +197,8 @@ func TestProcessParentBlockHash(t *testing.T) {
for i := 1; i <= num; i++ {
header := &types.Header{ParentHash: common.Hash{byte(i)}, Number: big.NewInt(int64(i)), Difficulty: new(big.Int)}
chainConfig := params.MergedTestChainConfig
if isVerkle {
chainConfig = testVerkleChainConfig
if isUBT {
chainConfig = testUBTChainConfig
}
vmContext := NewEVMBlockContext(header, nil, new(common.Address))
evm := vm.NewEVM(vmContext, statedb, chainConfig, vm.Config{})
@ -205,9 +206,9 @@ func TestProcessParentBlockHash(t *testing.T) {
}
// Read block hashes for block 0 .. num-1
for i := 0; i < num; i++ {
have, want := getContractStoredBlockHash(statedb, uint64(i), isVerkle), common.Hash{byte(i + 1)}
have, want := getContractStoredBlockHash(statedb, uint64(i), isUBT), common.Hash{byte(i + 1)}
if have != want {
t.Errorf("block %d, verkle=%v, have parent hash %v, want %v", i, isVerkle, have, want)
t.Errorf("block %d, verkle=%v, have parent hash %v, want %v", i, isUBT, have, want)
}
}
}
@ -215,22 +216,23 @@ func TestProcessParentBlockHash(t *testing.T) {
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
checkBlockHashes(statedb, false)
})
t.Run("Verkle", func(t *testing.T) {
t.Run("UBT", func(t *testing.T) {
db := rawdb.NewMemoryDatabase()
cacheConfig := DefaultConfig().WithStateScheme(rawdb.PathScheme)
cacheConfig.BinTrieGroupDepth = triedb.DefaultBinTrieGroupDepth
cacheConfig.SnapshotLimit = 0
triedb := triedb.NewDatabase(db, cacheConfig.triedbConfig(true))
statedb, _ := state.New(types.EmptyVerkleHash, state.NewDatabase(triedb, nil))
statedb, _ := state.New(types.EmptyBinaryHash, state.NewDatabase(triedb, nil))
checkBlockHashes(statedb, true)
})
}
// getContractStoredBlockHash is a utility method which reads the stored parent blockhash for block 'number'
func getContractStoredBlockHash(statedb *state.StateDB, number uint64, isVerkle bool) common.Hash {
func getContractStoredBlockHash(statedb *state.StateDB, number uint64, isUBT bool) common.Hash {
ringIndex := number % params.HistoryServeWindow
var key common.Hash
binary.BigEndian.PutUint64(key[24:], ringIndex)
if isVerkle {
if isUBT {
return statedb.GetState(params.HistoryStorageAddress, key)
}
return statedb.GetState(params.HistoryStorageAddress, key)

View file

@ -170,9 +170,10 @@ type BlockChainConfig struct {
TrieNoAsyncFlush bool // Whether the asynchronous buffer flushing is disallowed
TrieJournalDirectory string // Directory path to the journal used for persisting trie data across node restarts
Preimages bool // Whether to store preimage of trie key to the disk
StateScheme string // Scheme used to store ethereum states and merkle tree nodes on top
ArchiveMode bool // Whether to enable the archive mode
Preimages bool // Whether to store preimage of trie key to the disk
StateScheme string // Scheme used to store ethereum states and merkle tree nodes on top
ArchiveMode bool // Whether to enable the archive mode
BinTrieGroupDepth int // Number of levels per serialized group in binary trie (1-8)
// Number of blocks from the chain head for which state histories are retained.
// If set to 0, all state histories across the entire chain will be retained;
@ -258,10 +259,11 @@ func (cfg BlockChainConfig) WithNoAsyncFlush(on bool) *BlockChainConfig {
}
// triedbConfig derives the configures for trie database.
func (cfg *BlockChainConfig) triedbConfig(isVerkle bool) *triedb.Config {
func (cfg *BlockChainConfig) triedbConfig(isUBT bool) *triedb.Config {
config := &triedb.Config{
Preimages: cfg.Preimages,
IsVerkle: isVerkle,
Preimages: cfg.Preimages,
IsUBT: isUBT,
BinTrieGroupDepth: cfg.BinTrieGroupDepth,
}
if cfg.StateScheme == rawdb.HashScheme {
config.HashDB = &hashdb.Config{
@ -378,7 +380,7 @@ func NewBlockChain(db ethdb.Database, genesis *Genesis, engine consensus.Engine,
}
// Open trie database with provided config
enableVerkle, err := EnableVerkleAtGenesis(db, genesis)
enableVerkle, err := EnableUBTAtGenesis(db, genesis)
if err != nil {
return nil, err
}
@ -1188,6 +1190,7 @@ func (bc *BlockChain) SnapSyncComplete(hash common.Hash) error {
}
// If all checks out, manually set the head block.
rawdb.WriteHeadBlockHash(bc.db, hash)
bc.currentBlock.Store(block.Header())
headBlockGauge.Update(int64(block.NumberU64()))
@ -2120,11 +2123,29 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
startTime = time.Now()
statedb *state.StateDB
interrupt atomic.Bool
sdb = state.NewDatabase(bc.triedb, bc.codedb).WithSnapshot(bc.snaps)
sdb state.Database
)
defer interrupt.Store(true) // terminate the prefetch at the end
if bc.cfg.NoPrefetch {
if bc.chainConfig.IsUBT(block.Number(), block.Time()) {
sdb = state.NewUBTDatabase(bc.triedb, bc.codedb)
} else {
sdb = state.NewMPTDatabase(bc.triedb, bc.codedb).WithSnapshot(bc.snaps)
}
// If prefetching is enabled, run that against the current state to pre-cache
// transactions and probabilistically some of the account/storage trie nodes.
//
// Note: the main processor and prefetcher share the same reader with a local
// cache for mitigating the overhead of state access.
type prewarmReader interface {
// ReadersWithCacheStats creates a pair of state readers that share the
// same underlying state reader and internal state cache, while maintaining
// separate statistics respectively.
ReadersWithCacheStats(stateRoot common.Hash) (state.Reader, state.Reader, error)
}
warmer, ok := sdb.(prewarmReader)
if bc.cfg.NoPrefetch || !ok {
statedb, err = state.New(parentRoot, sdb)
if err != nil {
return nil, err
@ -2135,7 +2156,7 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
//
// Note: the main processor and prefetcher share the same reader with a local
// cache for mitigating the overhead of state access.
prefetch, process, err := sdb.ReadersWithCacheStats(parentRoot)
prefetch, process, err := warmer.ReadersWithCacheStats(parentRoot)
if err != nil {
return nil, err
}
@ -2580,8 +2601,13 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Header) error
blockReorgAddMeter.Mark(int64(len(newChain)))
} else {
// len(newChain) == 0 && len(oldChain) > 0
// rewind the canonical chain to a lower point.
log.Error("Impossible reorg, please file an issue", "oldnum", oldHead.Number, "oldhash", oldHead.Hash(), "oldblocks", len(oldChain), "newnum", newHead.Number, "newhash", newHead.Hash(), "newblocks", len(newChain))
// Rewind the canonical chain to a lower point. In EPBs we can reorg to
// a parent of the head within 32 blocks.
if len(oldChain) > 32 {
log.Error("Impossible reorg, please file an issue", "oldnum", oldHead.Number, "oldhash", oldHead.Hash(), "oldblocks", len(oldChain))
} else {
log.Info("Shorten chain", "del", len(oldChain), "number", oldHead.Number, "hash", oldHead.Hash())
}
}
// Acquire the tx-lookup lock before mutation. This step is essential
// as the txlookups should be changed atomically, and all subsequent

View file

@ -416,19 +416,42 @@ func (bc *BlockChain) ContractCodeWithPrefix(hash common.Hash) []byte {
// State returns a new mutable state based on the current HEAD block.
func (bc *BlockChain) State() (*state.StateDB, error) {
return bc.StateAt(bc.CurrentBlock().Root)
return bc.StateAt(bc.CurrentBlock())
}
// StateAt returns a new mutable state based on a particular point in time.
func (bc *BlockChain) StateAt(root common.Hash) (*state.StateDB, error) {
return state.New(root, state.NewDatabase(bc.triedb, bc.codedb).WithSnapshot(bc.snaps))
func (bc *BlockChain) StateAt(header *types.Header) (*state.StateDB, error) {
if bc.chainConfig.IsUBT(header.Number, header.Time) {
return state.New(header.Root, state.NewUBTDatabase(bc.triedb, bc.codedb))
}
return state.New(header.Root, state.NewMPTDatabase(bc.triedb, bc.codedb).WithSnapshot(bc.snaps))
}
// HistoricState returns a historic state specified by the given root.
// StateAtForkBoundary returns a new mutable state based on the parent state
// and the given header, handling the transition across the UBT fork.
func (bc *BlockChain) StateAtForkBoundary(parent *types.Header, header *types.Header) (*state.StateDB, error) {
// The parent is already in the UBT fork.
if bc.chainConfig.IsUBT(parent.Number, parent.Time) {
return state.New(parent.Root, state.NewUBTDatabase(bc.triedb, bc.codedb))
}
// The current block is the first block in the UBT fork
// (i.e., the parent is the last MPT block).
if bc.chainConfig.IsUBT(header.Number, header.Time) {
// TODO(gballet): register chain context if needed
return state.New(parent.Root, state.NewUBTDatabase(bc.triedb, bc.codedb))
}
// Both the parent and current block are in the MPT fork.
return state.New(parent.Root, state.NewMPTDatabase(bc.triedb, bc.codedb).WithSnapshot(bc.snaps))
}
// HistoricState returns a historic state specified by the given header.
// Live states are not available and won't be served, please use `State`
// or `StateAt` instead.
func (bc *BlockChain) HistoricState(root common.Hash) (*state.StateDB, error) {
return state.New(root, state.NewHistoricDatabase(bc.triedb, bc.codedb))
func (bc *BlockChain) HistoricState(header *types.Header) (*state.StateDB, error) {
if bc.chainConfig.IsUBT(header.Number, header.Time) {
return nil, errors.New("historical state over ubt is not yet supported")
}
return state.New(header.Root, state.NewHistoricDatabase(bc.triedb, bc.codedb))
}
// Config retrieves the chain's fork configuration.

View file

@ -3890,7 +3890,7 @@ func TestTransientStorageReset(t *testing.T) {
t.Fatalf("failed to insert into chain: %v", err)
}
// Check the storage
state, err := chain.StateAt(chain.CurrentHeader().Root)
state, err := chain.StateAt(chain.CurrentHeader())
if err != nil {
t.Fatalf("Failed to load state %v", err)
}

View file

@ -117,7 +117,7 @@ func (b *BlockGen) addTx(bc *BlockChain, vmConfig vm.Config, tx *types.Transacti
blockContext = NewEVMBlockContext(b.header, bc, &b.header.Coinbase)
evm = vm.NewEVM(blockContext, b.statedb, b.cm.config, vmConfig)
)
b.statedb.SetTxContext(tx.Hash(), len(b.txs))
b.statedb.SetTxContext(tx.Hash(), len(b.txs), uint32(len(b.txs)+1))
receipt, err := ApplyTransaction(evm, b.gasPool, b.statedb, b.header, tx)
if err != nil {
panic(err)
@ -126,7 +126,7 @@ func (b *BlockGen) addTx(bc *BlockChain, vmConfig vm.Config, tx *types.Transacti
// Merge the tx-local access event into the "block-local" one, in order to collect
// all values, so that the witness can be built.
if b.statedb.Database().TrieDB().IsVerkle() {
if b.statedb.Database().Type().Is(state.TypeUBT) {
b.statedb.AccessEvents().Merge(evm.AccessEvents)
}
b.txs = append(b.txs, tx)
@ -315,28 +315,17 @@ func (b *BlockGen) collectRequests(readonly bool) (requests [][]byte) {
// off the statedb before executing the system calls.
statedb = statedb.Copy()
}
var blockLogs []*types.Log
for _, r := range b.receipts {
blockLogs = append(blockLogs, r.Logs...)
}
// TODO use the shared EVM throughout the entire generation cycle
blockContext := NewEVMBlockContext(b.header, b.cm, &b.header.Coinbase)
evm := vm.NewEVM(blockContext, statedb, b.cm.config, vm.Config{})
if b.cm.config.IsPrague(b.header.Number, b.header.Time) {
requests = [][]byte{}
// EIP-6110 deposits
var blockLogs []*types.Log
for _, r := range b.receipts {
blockLogs = append(blockLogs, r.Logs...)
}
if err := ParseDepositLogs(&requests, blockLogs, b.cm.config); err != nil {
panic(fmt.Sprintf("failed to parse deposit log: %v", err))
}
// create EVM for system calls
blockContext := NewEVMBlockContext(b.header, b.cm, &b.header.Coinbase)
evm := vm.NewEVM(blockContext, statedb, b.cm.config, vm.Config{})
// EIP-7002
if err := ProcessWithdrawalQueue(&requests, evm); err != nil {
panic(fmt.Sprintf("could not process withdrawal requests: %v", err))
}
// EIP-7251
if err := ProcessConsolidationQueue(&requests, evm); err != nil {
panic(fmt.Sprintf("could not process consolidation requests: %v", err))
}
requests, err := PostExecution(context.Background(), b.cm.config, b.header.Number, b.header.Time, blockLogs, evm, uint32(len(b.txs)+1))
if err != nil {
panic(fmt.Sprintf("failed to run post-execution: %v", err))
}
return requests
}
@ -392,7 +381,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
misc.ApplyDAOHardFork(statedb)
}
if config.IsPrague(b.header.Number, b.header.Time) || config.IsVerkle(b.header.Number, b.header.Time) {
if config.IsPrague(b.header.Number, b.header.Time) || config.IsUBT(b.header.Number, b.header.Time) {
// EIP-2935
blockContext := NewEVMBlockContext(b.header, cm, &b.header.Coinbase)
blockContext.Random = &common.Hash{} // enable post-merge instruction set
@ -411,11 +400,22 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
b.header.RequestsHash = &reqHash
}
body := types.Body{Transactions: b.txs, Uncles: b.uncles, Withdrawals: b.withdrawals}
block, err := b.engine.FinalizeAndAssemble(context.Background(), cm, b.header, statedb, &body, b.receipts)
if err != nil {
panic(err)
body := types.Body{
Transactions: b.txs,
Uncles: b.uncles,
Withdrawals: b.withdrawals,
}
if !config.IsShanghai(b.header.Number, b.header.Time) {
if body.Withdrawals != nil {
panic("unexpected withdrawal before shanghai")
}
} else {
if body.Withdrawals == nil {
body.Withdrawals = make([]*types.Withdrawal, 0)
}
}
// Assemble the block for delivery.
block := AssembleBlock(b.engine, cm, b.header, statedb, &body, b.receipts)
// Write state changes to db
root, err := statedb.Commit(b.header.Number.Uint64(), config.IsEIP158(b.header.Number), config.IsCancun(b.header.Number, b.header.Time))
@ -430,8 +430,8 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
// Forcibly use hash-based state scheme for retaining all nodes in disk.
var triedbConfig *triedb.Config = triedb.HashDefaults
if config.IsVerkle(config.ChainID, 0) {
triedbConfig = triedb.VerkleDefaults
if config.IsUBT(config.ChainID, 0) {
triedbConfig = triedb.UBTDefaults
}
triedb := triedb.NewDatabase(db, triedbConfig)
defer triedb.Close()
@ -479,8 +479,8 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
func GenerateChainWithGenesis(genesis *Genesis, engine consensus.Engine, n int, gen func(int, *BlockGen)) (ethdb.Database, []*types.Block, []types.Receipts) {
db := rawdb.NewMemoryDatabase()
var triedbConfig *triedb.Config = triedb.HashDefaults
if genesis.Config != nil && genesis.Config.IsVerkle(genesis.Config.ChainID, 0) {
triedbConfig = triedb.VerkleDefaults
if genesis.Config != nil && genesis.Config.IsUBT(genesis.Config.ChainID, 0) {
triedbConfig = triedb.UBTDefaults
}
genesisTriedb := triedb.NewDatabase(db, triedbConfig)
block, err := genesis.Commit(db, genesisTriedb, nil)

View file

@ -87,7 +87,7 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common
func NewEVMTxContext(msg *Message) vm.TxContext {
ctx := vm.TxContext{
Origin: msg.From,
GasPrice: uint256.MustFromBig(msg.GasPrice),
GasPrice: msg.GasPrice,
BlobHashes: msg.BlobHashes,
}
return ctx

View file

@ -129,22 +129,23 @@ func ReadGenesis(db ethdb.Database) (*Genesis, error) {
}
// hashAlloc computes the state root according to the genesis specification.
func hashAlloc(ga *types.GenesisAlloc, isVerkle bool) (common.Hash, error) {
func hashAlloc(ga *types.GenesisAlloc, isUBT bool) (common.Hash, error) {
// If a genesis-time verkle trie is requested, create a trie config
// with the verkle trie enabled so that the tree can be initialized
// as such.
var config *triedb.Config
if isVerkle {
if isUBT {
config = &triedb.Config{
PathDB: pathdb.Defaults,
IsVerkle: true,
PathDB: pathdb.Defaults,
IsUBT: true,
BinTrieGroupDepth: triedb.UBTDefaults.BinTrieGroupDepth,
}
}
// Create an ephemeral in-memory database for computing hash,
// all the derived states will be discarded to not pollute disk.
emptyRoot := types.EmptyRootHash
if isVerkle {
emptyRoot = types.EmptyVerkleHash
if isUBT {
emptyRoot = types.EmptyBinaryHash
}
db := rawdb.NewMemoryDatabase()
statedb, err := state.New(emptyRoot, state.NewDatabase(triedb.NewDatabase(db, config), nil))
@ -168,8 +169,8 @@ func hashAlloc(ga *types.GenesisAlloc, isVerkle bool) (common.Hash, error) {
// generated states will be persisted into the given database.
func flushAlloc(ga *types.GenesisAlloc, triedb *triedb.Database, tracer *tracing.Hooks) (common.Hash, error) {
emptyRoot := types.EmptyRootHash
if triedb.IsVerkle() {
emptyRoot = types.EmptyVerkleHash
if triedb.IsUBT() {
emptyRoot = types.EmptyBinaryHash
}
statedb, err := state.New(emptyRoot, state.NewDatabase(triedb, nil))
if err != nil {
@ -276,10 +277,10 @@ func (e *GenesisMismatchError) Error() string {
// ChainOverrides contains the changes to chain config.
type ChainOverrides struct {
OverrideOsaka *uint64
OverrideBPO1 *uint64
OverrideBPO2 *uint64
OverrideVerkle *uint64
OverrideOsaka *uint64
OverrideBPO1 *uint64
OverrideBPO2 *uint64
OverrideUBT *uint64
}
// apply applies the chain overrides on the supplied chain config.
@ -296,8 +297,8 @@ func (o *ChainOverrides) apply(cfg *params.ChainConfig) error {
if o.OverrideBPO2 != nil {
cfg.BPO2Time = o.OverrideBPO2
}
if o.OverrideVerkle != nil {
cfg.VerkleTime = o.OverrideVerkle
if o.OverrideUBT != nil {
cfg.UBTTime = o.OverrideUBT
}
return cfg.CheckConfigForkOrder()
}
@ -469,15 +470,15 @@ func (g *Genesis) chainConfigOrDefault(ghash common.Hash, stored *params.ChainCo
}
}
// IsVerkle indicates whether the state is already stored in a verkle
// IsUBT indicates whether the state is already stored in a verkle
// tree at genesis time.
func (g *Genesis) IsVerkle() bool {
return g.Config.IsVerkleGenesis()
func (g *Genesis) IsUBT() bool {
return g.Config.IsUBTGenesis()
}
// ToBlock returns the genesis block according to genesis specification.
func (g *Genesis) ToBlock() *types.Block {
root, err := hashAlloc(&g.Alloc, g.IsVerkle())
root, err := hashAlloc(&g.Alloc, g.IsUBT())
if err != nil {
panic(err)
}
@ -609,24 +610,24 @@ func (g *Genesis) MustCommit(db ethdb.Database, triedb *triedb.Database) *types.
return block
}
// EnableVerkleAtGenesis indicates whether the verkle fork should be activated
// EnableUBTAtGenesis indicates whether the verkle fork should be activated
// at genesis. This is a temporary solution only for verkle devnet testing, where
// verkle fork is activated at genesis, and the configured activation date has
// already passed.
//
// In production networks (mainnet and public testnets), verkle activation always
// occurs after the genesis block, making this function irrelevant in those cases.
func EnableVerkleAtGenesis(db ethdb.Database, genesis *Genesis) (bool, error) {
func EnableUBTAtGenesis(db ethdb.Database, genesis *Genesis) (bool, error) {
if genesis != nil {
if genesis.Config == nil {
return false, errGenesisNoConfig
}
return genesis.Config.EnableVerkleAtGenesis, nil
return genesis.Config.EnableUBTAtGenesis, nil
}
if ghash := rawdb.ReadCanonicalHash(db, 0); ghash != (common.Hash{}) {
chainCfg := rawdb.ReadChainConfig(db, ghash)
if chainCfg != nil {
return chainCfg.EnableVerkleAtGenesis, nil
return chainCfg.EnableUBTAtGenesis, nil
}
}
return false, nil

View file

@ -261,9 +261,9 @@ func newDbConfig(scheme string) *triedb.Config {
return &triedb.Config{PathDB: &config}
}
func TestVerkleGenesisCommit(t *testing.T) {
var verkleTime uint64 = 0
verkleConfig := &params.ChainConfig{
func TestBinaryGenesisCommit(t *testing.T) {
var ubtTime uint64 = 0
ubtConfig := &params.ChainConfig{
ChainID: big.NewInt(1),
HomesteadBlock: big.NewInt(0),
DAOForkBlock: nil,
@ -281,34 +281,34 @@ func TestVerkleGenesisCommit(t *testing.T) {
ArrowGlacierBlock: big.NewInt(0),
GrayGlacierBlock: big.NewInt(0),
MergeNetsplitBlock: nil,
ShanghaiTime: &verkleTime,
CancunTime: &verkleTime,
PragueTime: &verkleTime,
OsakaTime: &verkleTime,
VerkleTime: &verkleTime,
ShanghaiTime: &ubtTime,
CancunTime: &ubtTime,
PragueTime: &ubtTime,
OsakaTime: &ubtTime,
UBTTime: &ubtTime,
TerminalTotalDifficulty: big.NewInt(0),
EnableVerkleAtGenesis: true,
EnableUBTAtGenesis: true,
Ethash: nil,
Clique: nil,
BlobScheduleConfig: &params.BlobScheduleConfig{
Cancun: params.DefaultCancunBlobConfig,
Prague: params.DefaultPragueBlobConfig,
Osaka: params.DefaultOsakaBlobConfig,
Verkle: params.DefaultPragueBlobConfig,
UBT: params.DefaultPragueBlobConfig,
},
}
genesis := &Genesis{
BaseFee: big.NewInt(params.InitialBaseFee),
Config: verkleConfig,
Timestamp: verkleTime,
Config: ubtConfig,
Timestamp: ubtTime,
Difficulty: big.NewInt(0),
Alloc: types.GenesisAlloc{
{1}: {Balance: big.NewInt(1), Storage: map[common.Hash]common.Hash{{1}: {1}}},
},
}
expected := common.FromHex("1fd154971d9a386c4ec75fe7138c17efb569bfc2962e46e94a376ba997e3fadc")
expected := common.FromHex("0870fd587c41dc778019de8c5cb3193fe4ef1f417976461952d3712ba39163f5")
got := genesis.ToBlock().Root().Bytes()
if !bytes.Equal(got, expected) {
t.Fatalf("invalid genesis state root, expected %x, got %x", expected, got)
@ -320,17 +320,18 @@ func TestVerkleGenesisCommit(t *testing.T) {
config.NoAsyncFlush = true
triedb := triedb.NewDatabase(db, &triedb.Config{
IsVerkle: true,
PathDB: &config,
IsUBT: true,
PathDB: &config,
BinTrieGroupDepth: triedb.DefaultBinTrieGroupDepth,
})
block := genesis.MustCommit(db, triedb)
if !bytes.Equal(block.Root().Bytes(), expected) {
t.Fatalf("invalid genesis state root, expected %x, got %x", expected, block.Root())
}
// Test that the trie is verkle
if !triedb.IsVerkle() {
t.Fatalf("expected trie to be verkle")
// Test that the trie is a unified binary trie
if !triedb.IsUBT() {
t.Fatalf("expected trie to be a unified binary trie")
}
vdb := rawdb.NewTable(db, string(rawdb.VerklePrefix))
if !rawdb.HasAccountTrieNode(vdb, nil) {

View file

@ -107,6 +107,10 @@ var staticPrunePoints = map[HistoryMode]map[common.Hash]*PrunePoint{
BlockNumber: 7836331,
BlockHash: common.HexToHash("0xe6571beb68bf24dbd8a6ba354518996920c55a3f8d8fdca423e391b8ad071f22"),
},
params.HoodiGenesisHash: {
BlockNumber: 60412,
BlockHash: common.HexToHash("0x1562792812ef418eaafc8f1f093d84d9634971e9dd6b0771302eb5b9fd4d2c46"),
},
},
}

View file

@ -71,7 +71,7 @@ func (ts *TransitionState) Copy() *TransitionState {
// LoadTransitionState retrieves the Verkle transition state associated with
// the given state root hash from the database.
func LoadTransitionState(db ethdb.KeyValueReader, root common.Hash, isVerkle bool) *TransitionState {
func LoadTransitionState(db ethdb.KeyValueReader, root common.Hash, isUBT bool) *TransitionState {
var ts *TransitionState
data, _ := rawdb.ReadVerkleTransitionState(db, root)
@ -97,10 +97,10 @@ func LoadTransitionState(db ethdb.KeyValueReader, root common.Hash, isVerkle boo
// Initialize the first transition state, with the "ended"
// field set to true if the database was created
// as a verkle database.
log.Debug("no transition state found, starting fresh", "verkle", isVerkle)
log.Debug("no transition state found, starting fresh", "verkle", isUBT)
// Start with a fresh state
ts = &TransitionState{Ended: isVerkle}
ts = &TransitionState{Ended: isUBT}
}
return ts
}

View file

@ -175,7 +175,9 @@ func WriteFinalizedBlockHash(db ethdb.KeyValueWriter, hash common.Hash) {
}
// ReadLastPivotNumber retrieves the number of the last pivot block. If the node
// full synced, the last pivot will always be nil.
// has never attempted snap sync, the last pivot will always be nil. The marker
// is written during snap sync and never cleared, so that a rollback past the
// pivot can re-enable snap sync.
func ReadLastPivotNumber(db ethdb.KeyValueReader) *uint64 {
data, _ := db.Get(lastPivotKey)
if len(data) == 0 {

View file

@ -157,6 +157,7 @@ func newTable(path string, name string, readMeter, writeMeter *metrics.Meter, si
}
meta, err = openFreezerFileForReadOnly(filepath.Join(path, fmt.Sprintf("%s.meta", name)))
if err != nil {
index.Close()
return nil, err
}
} else {
@ -166,6 +167,7 @@ func newTable(path string, name string, readMeter, writeMeter *metrics.Meter, si
}
meta, err = openFreezerFileForAppend(filepath.Join(path, fmt.Sprintf("%s.meta", name)))
if err != nil {
index.Close()
return nil, err
}
}
@ -173,6 +175,8 @@ func newTable(path string, name string, readMeter, writeMeter *metrics.Meter, si
// is detected.
metadata, err := newMetadata(meta)
if err != nil {
meta.Close()
index.Close()
return nil, err
}
// Create the table and repair any past inconsistency

View file

@ -76,6 +76,11 @@ func copyFrom(srcPath, destPath string, offset uint64, before func(f *os.File) e
// we do the final move.
src.Close()
// Permanently persist the content into disk
if err := f.Sync(); err != nil {
return err
}
if err := f.Close(); err != nil {
return err
}
@ -129,6 +134,7 @@ func openFreezerFileForAppend(filename string) (*os.File, error) {
}
// Seek to end for append
if _, err = file.Seek(0, io.SeekEnd); err != nil {
file.Close()
return nil, err
}
return file, nil

View file

@ -20,13 +20,9 @@ import (
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/overlay"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state/snapshot"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/bintrie"
"github.com/ethereum/go-ethereum/trie/transitiontrie"
@ -34,8 +30,27 @@ import (
"github.com/ethereum/go-ethereum/triedb"
)
// DatabaseType represents the type of trie backing the state database.
type DatabaseType int
const (
// TypeMPT indicates a Merkle Patricia Trie (MPT) backed database.
TypeMPT DatabaseType = iota
// TypeUBT indicates a Unified Binary Trie (UBT) backed database.
TypeUBT
)
// Is returns the flag indicating the database type equals to the given one.
func (typ DatabaseType) Is(t DatabaseType) bool {
return typ == t
}
// Database wraps access to tries and contract code.
type Database interface {
// Type returns the trie type backing this database (MPT or UBT).
Type() DatabaseType
// Reader returns a state reader associated with the specified state root.
Reader(root common.Hash) (Reader, error)
@ -55,7 +70,7 @@ type Database interface {
// Commit flushes all pending writes and finalizes the state transition,
// committing the changes to the underlying storage. It returns an error
// if the commit fails.
Commit(update *stateUpdate) error
Commit(update *StateUpdate) error
}
// Trie is a Ethereum Merkle Patricia trie.
@ -139,184 +154,27 @@ type Trie interface {
// with the node that proves the absence of the key.
Prove(key []byte, proofDb ethdb.KeyValueWriter) error
// IsVerkle returns true if the trie is verkle-tree based
IsVerkle() bool
}
// CachingDB is an implementation of Database interface. It leverages both trie and
// state snapshot to provide functionalities for state access. It's meant to be a
// long-live object and has a few caches inside for sharing between blocks.
type CachingDB struct {
triedb *triedb.Database
codedb *CodeDB
snap *snapshot.Tree
// IsUBT returns true if the trie is unified binary trie based.
IsUBT() bool
}
// NewDatabase creates a state database with the provided data sources.
func NewDatabase(triedb *triedb.Database, codedb *CodeDB) *CachingDB {
if codedb == nil {
codedb = NewCodeDB(triedb.Disk())
}
return &CachingDB{
triedb: triedb,
codedb: codedb,
//
// Deprecated, please use NewMPTDatabase or NewUBTDatabase directly.
func NewDatabase(tdb *triedb.Database, codedb *CodeDB) Database {
if tdb.IsUBT() {
return NewUBTDatabase(tdb, codedb)
}
return NewMPTDatabase(tdb, codedb)
}
// NewDatabaseForTesting is similar to NewDatabase, but it initializes the caching
// db by using an ephemeral memory db with default config for testing.
func NewDatabaseForTesting() *CachingDB {
func NewDatabaseForTesting() Database {
db := rawdb.NewMemoryDatabase()
return NewDatabase(triedb.NewDatabase(db, nil), NewCodeDB(db))
}
// WithSnapshot configures the provided contract code cache. Note that this
// registration must be performed before the cachingDB is used.
func (db *CachingDB) WithSnapshot(snapshot *snapshot.Tree) *CachingDB {
db.snap = snapshot
return db
}
// StateReader returns a state reader associated with the specified state root.
func (db *CachingDB) StateReader(stateRoot common.Hash) (StateReader, error) {
var readers []StateReader
// Configure the state reader using the standalone snapshot in hash mode.
// This reader offers improved performance but is optional and only
// partially useful if the snapshot is not fully generated.
if db.TrieDB().Scheme() == rawdb.HashScheme && db.snap != nil {
snap := db.snap.Snapshot(stateRoot)
if snap != nil {
readers = append(readers, newFlatReader(snap))
}
}
// Configure the state reader using the path database in path mode.
// This reader offers improved performance but is optional and only
// partially useful if the snapshot data in path database is not
// fully generated.
if db.TrieDB().Scheme() == rawdb.PathScheme {
reader, err := db.triedb.StateReader(stateRoot)
if err == nil {
readers = append(readers, newFlatReader(reader))
}
}
// Configure the trie reader, which is expected to be available as the
// gatekeeper unless the state is corrupted.
tr, err := newTrieReader(stateRoot, db.triedb)
if err != nil {
return nil, err
}
readers = append(readers, tr)
return newMultiStateReader(readers...)
}
// Reader implements Database, returning a reader associated with the specified
// state root.
func (db *CachingDB) Reader(stateRoot common.Hash) (Reader, error) {
sr, err := db.StateReader(stateRoot)
if err != nil {
return nil, err
}
return newReader(db.codedb.Reader(), sr), nil
}
// ReadersWithCacheStats creates a pair of state readers that share the same
// underlying state reader and internal state cache, while maintaining separate
// statistics respectively.
func (db *CachingDB) ReadersWithCacheStats(stateRoot common.Hash) (Reader, Reader, error) {
r, err := db.StateReader(stateRoot)
if err != nil {
return nil, nil, err
}
sr := newStateReaderWithCache(r)
ra := newReader(db.codedb.Reader(), newStateReaderWithStats(sr))
rb := newReader(db.codedb.Reader(), newStateReaderWithStats(sr))
return ra, rb, nil
}
// OpenTrie opens the main account trie at a specific root hash.
func (db *CachingDB) OpenTrie(root common.Hash) (Trie, error) {
if db.triedb.IsVerkle() {
ts := overlay.LoadTransitionState(db.TrieDB().Disk(), root, db.triedb.IsVerkle())
if ts.InTransition() {
panic("state tree transition isn't supported yet")
}
if ts.Transitioned() {
// Use BinaryTrie instead of VerkleTrie when IsVerkle is set
// (IsVerkle actually means Binary Trie mode in this codebase)
return bintrie.NewBinaryTrie(root, db.triedb)
}
}
tr, err := trie.NewStateTrie(trie.StateTrieID(root), db.triedb)
if err != nil {
return nil, err
}
return tr, nil
}
// OpenStorageTrie opens the storage trie of an account.
func (db *CachingDB) OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash, self Trie) (Trie, error) {
if db.triedb.IsVerkle() {
return self, nil
}
tr, err := trie.NewStateTrie(trie.StorageTrieID(stateRoot, crypto.Keccak256Hash(address.Bytes()), root), db.triedb)
if err != nil {
return nil, err
}
return tr, nil
}
// TrieDB retrieves any intermediate trie-node caching layer.
func (db *CachingDB) TrieDB() *triedb.Database {
return db.triedb
}
// Snapshot returns the underlying state snapshot.
func (db *CachingDB) Snapshot() *snapshot.Tree {
return db.snap
}
// Commit flushes all pending writes and finalizes the state transition,
// committing the changes to the underlying storage. It returns an error
// if the commit fails.
func (db *CachingDB) Commit(update *stateUpdate) error {
// Short circuit if nothing to commit
if update.empty() {
return nil
}
// Commit dirty contract code if any exists
if len(update.codes) > 0 {
batch := db.codedb.NewBatchWithSize(len(update.codes))
for _, code := range update.codes {
batch.Put(code.hash, code.blob)
}
if err := batch.Commit(); err != nil {
return err
}
}
// If snapshotting is enabled, update the snapshot tree with this new version
if db.snap != nil && db.snap.Snapshot(update.originRoot) != nil {
if err := db.snap.Update(update.root, update.originRoot, update.accounts, update.storages); err != nil {
log.Warn("Failed to update snapshot tree", "from", update.originRoot, "to", update.root, "err", err)
}
// Keep 128 diff layers in the memory, persistent layer is 129th.
// - head layer is paired with HEAD state
// - head-1 layer is paired with HEAD-1 state
// - head-127 layer(bottom-most diff layer) is paired with HEAD-127 state
if err := db.snap.Cap(update.root, TriesInMemory); err != nil {
log.Warn("Failed to cap snapshot tree", "root", update.root, "layers", TriesInMemory, "err", err)
}
}
return db.triedb.Update(update.root, update.originRoot, update.blockNumber, update.nodes, update.stateSet())
}
// Iteratee returns a state iteratee associated with the specified state root,
// through which the account iterator and storage iterator can be created.
func (db *CachingDB) Iteratee(root common.Hash) (Iteratee, error) {
return newStateIteratee(!db.triedb.IsVerkle(), root, db.triedb, db.snap)
}
// mustCopyTrie returns a deep-copied trie.
func mustCopyTrie(t Trie) Trie {
switch t := t.(type) {
@ -324,6 +182,8 @@ func mustCopyTrie(t Trie) Trie {
return t.Copy()
case *transitiontrie.TransitionTrie:
return t.Copy()
case *bintrie.BinaryTrie:
return t.Copy()
default:
panic(fmt.Errorf("unknown trie type %T", t))
}

View file

@ -223,6 +223,12 @@ type HistoricDB struct {
codedb *CodeDB
}
// Type returns the trie type of the underlying database.
func (db *HistoricDB) Type() DatabaseType {
// TODO(rjl493456442) support UBT in the future
return TypeMPT
}
// NewHistoricDatabase creates a historic state database.
func NewHistoricDatabase(triedb *triedb.Database, codedb *CodeDB) *HistoricDB {
return &HistoricDB{
@ -291,7 +297,7 @@ func (db *HistoricDB) TrieDB() *triedb.Database {
// Commit flushes all pending writes and finalizes the state transition,
// committing the changes to the underlying storage. It returns an error
// if the commit fails.
func (db *HistoricDB) Commit(update *stateUpdate) error {
func (db *HistoricDB) Commit(update *StateUpdate) error {
return errors.New("not implemented")
}

View file

@ -23,6 +23,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state/snapshot"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/triedb"
)
@ -57,9 +58,9 @@ type AccountIterator interface {
// An error will be returned if the preimage is not available.
Address() (common.Address, error)
// Account returns the RLP encoded account the iterator is currently at.
// Account returns the account the iterator is currently at.
// An error will be retained if the iterator becomes invalid.
Account() []byte
Account() *types.StateAccount
}
// StorageIterator is an iterator to step over the specific storage in the
@ -73,7 +74,7 @@ type StorageIterator interface {
// Slot returns the storage slot the iterator is currently at. An error will
// be retained if the iterator becomes invalid.
Slot() []byte
Slot() common.Hash
}
// Iteratee wraps the NewIterator methods for traversing the accounts and
@ -131,10 +132,7 @@ func (ai *flatAccountIterator) Next() bool {
// Error returns any failure that occurred during iteration, which might have
// caused a premature iteration exit.
func (ai *flatAccountIterator) Error() error {
if ai.err != nil {
return ai.err
}
return ai.it.Error()
return errors.Join(ai.err, ai.it.Error())
}
// Hash returns the hash of the account or storage slot the iterator is
@ -165,8 +163,8 @@ func (ai *flatAccountIterator) Address() (common.Address, error) {
// Account returns the account data the iterator is currently at. The account
// data is encoded as slim format from the underlying iterator, the conversion
// is required.
func (ai *flatAccountIterator) Account() []byte {
data, err := types.FullAccountRLP(ai.it.Account())
func (ai *flatAccountIterator) Account() *types.StateAccount {
data, err := types.FullAccount(ai.it.Account())
if err != nil {
ai.err = err
return nil
@ -176,6 +174,7 @@ func (ai *flatAccountIterator) Account() []byte {
// flatStorageIterator is a wrapper around the underlying flat state iterator.
type flatStorageIterator struct {
err error
it snapshot.StorageIterator
preimage PreimageReader
}
@ -190,13 +189,16 @@ func newFlatStorageIterator(it snapshot.StorageIterator, preimage PreimageReader
// is exhausted or if an error occurs. Any error encountered is retained and
// can be retrieved via Error().
func (si *flatStorageIterator) Next() bool {
if si.err != nil {
return false
}
return si.it.Next()
}
// Error returns any failure that occurred during iteration, which might have
// caused a premature iteration exit.
func (si *flatStorageIterator) Error() error {
return si.it.Error()
return errors.Join(si.err, si.it.Error())
}
// Hash returns the hash of the account or storage slot the iterator is
@ -225,14 +227,24 @@ func (si *flatStorageIterator) Key() (common.Hash, error) {
}
// Slot returns the storage slot data the iterator is currently at.
func (si *flatStorageIterator) Slot() []byte {
return si.it.Slot()
func (si *flatStorageIterator) Slot() common.Hash {
// Perform the rlp-decode as the slot value is RLP-encoded
blob := si.it.Slot()
_, content, _, err := rlp.Split(blob)
if err != nil {
si.err = err
return common.Hash{}
}
var value common.Hash
value.SetBytes(content)
return value
}
// merkleIterator implements the Iterator interface, providing functions to traverse
// the accounts or storages with the manner of Merkle-Patricia-Trie.
type merkleIterator struct {
tr Trie
err error
it *trie.Iterator
account bool
}
@ -254,13 +266,16 @@ func newMerkleTrieIterator(tr Trie, start common.Hash, account bool) (*merkleIte
// is exhausted or if an error occurs. Any error encountered is retained and
// can be retrieved via Error().
func (ti *merkleIterator) Next() bool {
if ti.err != nil {
return false
}
return ti.it.Next()
}
// Error returns any failure that occurred during iteration, which might have
// caused a premature iteration exit.
func (ti *merkleIterator) Error() error {
return ti.it.Err
return errors.Join(ti.err, ti.it.Err)
}
// Hash returns the hash of the account or storage slot the iterator is
@ -287,11 +302,16 @@ func (ti *merkleIterator) Address() (common.Address, error) {
}
// Account returns the account data the iterator is currently at.
func (ti *merkleIterator) Account() []byte {
func (ti *merkleIterator) Account() *types.StateAccount {
if !ti.account {
return nil
}
return ti.it.Value
var account types.StateAccount
if err := rlp.DecodeBytes(ti.it.Value, &account); err != nil {
ti.err = err
return nil
}
return &account
}
// Key returns the raw storage slot key the iterator is currently at.
@ -308,11 +328,19 @@ func (ti *merkleIterator) Key() (common.Hash, error) {
}
// Slot returns the storage slot the iterator is currently at.
func (ti *merkleIterator) Slot() []byte {
func (ti *merkleIterator) Slot() common.Hash {
if ti.account {
return nil
return common.Hash{}
}
return ti.it.Value
// Perform the rlp-decode as the slot value is RLP-encoded
_, content, _, err := rlp.Split(ti.it.Value)
if err != nil {
ti.err = err
return common.Hash{}
}
var value common.Hash
value.SetBytes(content)
return value
}
// stateIteratee implements Iteratee interface, providing the state traversal
@ -430,6 +458,6 @@ func (e exhaustedIterator) Key() (common.Hash, error) {
return common.Hash{}, nil
}
func (e exhaustedIterator) Slot() []byte {
return nil
func (e exhaustedIterator) Slot() common.Hash {
return common.Hash{}
}

View file

@ -24,7 +24,6 @@ import (
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
)
@ -45,7 +44,7 @@ func TestExhaustedIterator(t *testing.T) {
if key, err := it.Key(); key != (common.Hash{}) || err != nil {
t.Fatalf("Key() = %x, %v; want zero, nil", key, err)
}
if slot := it.Slot(); slot != nil {
if slot := it.Slot(); slot != (common.Hash{}) {
t.Fatalf("Slot() = %x, want nil", slot)
}
it.Release()
@ -95,20 +94,16 @@ func testAccountIterator(t *testing.T, scheme string) {
hashes = append(hashes, hash)
// Decode and verify account data.
blob := acctIt.Account()
if blob == nil {
got := acctIt.Account()
if got == nil {
t.Fatalf("(%s) nil account at %x", scheme, hash)
}
var decoded types.StateAccount
if err := rlp.DecodeBytes(blob, &decoded); err != nil {
t.Fatalf("(%s) bad RLP at %x: %v", scheme, hash, err)
}
acc := addrByHash[hash]
if decoded.Nonce != acc.nonce {
t.Fatalf("(%s) nonce %x: got %d, want %d", scheme, hash, decoded.Nonce, acc.nonce)
if got.Nonce != acc.nonce {
t.Fatalf("(%s) nonce %x: got %d, want %d", scheme, hash, got.Nonce, acc.nonce)
}
if decoded.Balance.Cmp(acc.balance) != 0 {
t.Fatalf("(%s) balance %x: got %v, want %v", scheme, hash, decoded.Balance, acc.balance)
if got.Balance.Cmp(acc.balance) != 0 {
t.Fatalf("(%s) balance %x: got %v, want %v", scheme, hash, got.Balance, acc.balance)
}
// Verify address preimage resolution.
addr, err := acctIt.Address()
@ -183,7 +178,7 @@ func testStorageIterator(t *testing.T, scheme string) {
t.Fatalf("(%s) storage hashes not ascending for %x", scheme, acc.address)
}
prevHash = hash
if storageIt.Slot() == nil {
if storageIt.Slot() == (common.Hash{}) {
t.Fatalf("(%s) nil slot at %x", scheme, hash)
}
// Check key preimage resolution on first slot.

187
core/state/database_mpt.go Normal file
View 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
View 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)
}

View file

@ -24,9 +24,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie/bintrie"
)
@ -115,7 +113,7 @@ func (d iterativeDump) OnRoot(root common.Hash) {
// DumpToCollector iterates the state according to the given options and inserts
// the items into a collector for aggregation or serialization.
func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []byte) {
func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []byte, err error) {
// Sanitize the input to allow nil configs
if conf == nil {
conf = new(DumpConfig)
@ -131,7 +129,7 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
iteratee, err := s.db.Iteratee(s.originalRoot)
if err != nil {
return nil
return nil, err
}
var startHash common.Hash
if conf.Start != nil {
@ -139,14 +137,17 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
}
acctIt, err := iteratee.NewAccountIterator(startHash)
if err != nil {
return nil
return nil, err
}
defer acctIt.Release()
for acctIt.Next() {
var data types.StateAccount
if err := rlp.DecodeBytes(acctIt.Account(), &data); err != nil {
panic(err)
data := acctIt.Account()
if err := acctIt.Error(); err != nil {
return nil, err
}
if data == nil {
return nil, fmt.Errorf("unexpected nil account value")
}
var (
account = DumpAccount{
@ -168,7 +169,7 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
address = &addrBytes
account.Address = address
}
obj := newObject(s, addrBytes, &data)
obj := newObject(s, addrBytes, data)
if !conf.SkipCode {
account.Code = obj.Code()
}
@ -177,20 +178,19 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
storageIt, err := iteratee.NewStorageIterator(acctIt.Hash(), common.Hash{})
if err != nil {
log.Error("Failed to load storage trie", "err", err)
continue
return nil, err
}
for storageIt.Next() {
_, content, _, err := rlp.Split(storageIt.Slot())
if err != nil {
log.Error("Failed to decode the value returned by iterator", "error", err)
continue
val := storageIt.Slot()
if err := storageIt.Error(); err != nil {
storageIt.Release()
return nil, err
}
key, err := storageIt.Key()
if err != nil {
continue
}
account.Storage[key] = common.Bytes2Hex(content)
account.Storage[key] = common.Bytes2Hex(common.TrimLeftZeroes(val[:]))
}
storageIt.Release()
}
@ -211,7 +211,7 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
log.Warn("Dump incomplete due to missing preimages", "missing", missingPreimages)
}
log.Info("Trie dumping complete", "accounts", accounts, "elapsed", common.PrettyDuration(time.Since(start)))
return nextKey
return nextKey, nil
}
// DumpBinTrieLeaves collects all binary trie leaf nodes into the provided map.
@ -242,7 +242,8 @@ func (s *StateDB) RawDump(opts *DumpConfig) Dump {
dump := &Dump{
Accounts: make(map[string]DumpAccount),
}
dump.Next = s.DumpToCollector(dump, opts)
next, _ := s.DumpToCollector(dump, opts)
dump.Next = next
return *dump
}

View file

@ -18,7 +18,6 @@ package state
import (
"fmt"
"maps"
"slices"
"sort"
@ -32,26 +31,163 @@ type revision struct {
journalIndex int
}
// journalMutationKind indicates the type of account mutation.
type journalMutationKind uint8
const (
// journalMutationKindNone is the zero value returned by mutation() for
// entries that don't carry a tracked account mutation. The accompanying
// bool is false in that case; callers must gate on it before using the
// kind.
journalMutationKindNone journalMutationKind = iota
journalMutationKindTouch
journalMutationKindCreate
journalMutationKindSelfDestruct
journalMutationKindBalance
journalMutationKindNonce
journalMutationKindCode
journalMutationKindStorage
journalMutationKindCount // sentinel, must stay last
)
type journalMutationCounts [journalMutationKindCount]int
// journalMutationState tracks, per account, both the per-kind count of mutation
// entries currently present in the journal and the pre-tx value of each
// metadata field captured on its first touch (balance/nonce/code).
// The *Set flags indicate whether the corresponding field has been mutated
// at least once in the current tx window; they are cleared when all entries
// of that kind are reverted. Storage slots are tracked elsewhere.
type journalMutationState struct {
counts journalMutationCounts
balance *uint256.Int
balanceSet bool
nonce uint64
nonceSet bool
code []byte
codeSet bool
}
func (s *journalMutationState) add(kind journalMutationKind) {
s.counts.add(kind)
}
// remove drops one occurrence of the given mutation kind. It returns a flag
// indicating whether no entries of any kind remain.
func (s *journalMutationState) remove(kind journalMutationKind) bool {
if s.counts.remove(kind) {
// No entries of this kind remain for this account; drop the
// corresponding stashed original so the state mirrors the
// live mutation set.
s.clearKind(kind)
}
return s.counts == (journalMutationCounts{})
}
// clearKind drops the stashed original for the given mutation kind. It is
// invoked during revert once no journal entries of that kind remain for the
// account. Kinds that don't correspond to a tracked metadata field are no-ops.
func (s *journalMutationState) clearKind(kind journalMutationKind) {
switch kind {
case journalMutationKindBalance:
s.balance = nil
s.balanceSet = false
case journalMutationKindNonce:
s.nonce = 0
s.nonceSet = false
case journalMutationKindCode:
s.code = nil
s.codeSet = false
}
}
func (s *journalMutationState) copy() *journalMutationState {
cpy := *s
if s.balance != nil {
cpy.balance = new(uint256.Int).Set(s.balance)
}
if s.code != nil {
cpy.code = slices.Clone(s.code)
}
return &cpy
}
func (c *journalMutationCounts) add(kind journalMutationKind) {
c[kind]++
}
func (c *journalMutationCounts) remove(kind journalMutationKind) bool {
c[kind]--
return c[kind] == 0
}
// journalEntry is a modification entry in the state change journal that can be
// reverted on demand.
type journalEntry interface {
// revert undoes the changes introduced by this journal entry.
revert(*StateDB)
// dirtied returns the Ethereum address modified by this journal entry.
// indicates false if no address was changed.
dirtied() (common.Address, bool)
// mutation returns the account mutation introduced by this entry.
// It indicates false if no tracked account mutation was made.
mutation() (common.Address, journalMutationKind, bool)
// copy returns a deep-copied journal entry.
copy() journalEntry
}
// stashBalance records prev as the pre-tx balance of addr, iff this is the
// first balance touch seen in the current tx. Subsequent balance writes are
// ignored so the stored value remains the true pre-tx original.
func (j *journal) stashBalance(addr common.Address, prev *uint256.Int) {
s := j.mutationStateFor(addr)
if s.balanceSet {
return
}
// The balance is already deep-copied and safe to hold the object here.
s.balance = prev
s.balanceSet = true
}
// stashNonce records prev as the pre-tx nonce of addr on first touch.
func (j *journal) stashNonce(addr common.Address, prev uint64) {
s := j.mutationStateFor(addr)
if s.nonceSet {
return
}
s.nonce = prev
s.nonceSet = true
}
// stashCode records prev as the pre-tx code of addr on first touch.
func (j *journal) stashCode(addr common.Address, prev []byte) {
s := j.mutationStateFor(addr)
if s.codeSet {
return
}
// The code is already deep-copied in the StateDB, safe to
// hold the reference here.
s.code = prev
s.codeSet = true
}
// mutationStateFor returns the mutation state for addr, creating an empty one
// if absent.
func (j *journal) mutationStateFor(addr common.Address) *journalMutationState {
s := j.mutations[addr]
if s == nil {
s = new(journalMutationState)
j.mutations[addr] = s
}
return s
}
// journal contains the list of state modifications applied since the last state
// commit. These are tracked to be able to be reverted in the case of an execution
// exception or request for reversal.
type journal struct {
entries []journalEntry // Current changes tracked by the journal
dirties map[common.Address]int // Dirty accounts and the number of changes
entries []journalEntry // Current changes tracked by the journal
mutations map[common.Address]*journalMutationState // Per-account mutation kinds and pre-tx originals
validRevisions []revision
nextRevisionId int
@ -60,7 +196,7 @@ type journal struct {
// newJournal creates a new initialized journal.
func newJournal() *journal {
return &journal{
dirties: make(map[common.Address]int),
mutations: make(map[common.Address]*journalMutationState),
}
}
@ -70,7 +206,7 @@ func newJournal() *journal {
func (j *journal) reset() {
j.entries = j.entries[:0]
j.validRevisions = j.validRevisions[:0]
clear(j.dirties)
clear(j.mutations)
j.nextRevisionId = 0
}
@ -101,33 +237,52 @@ func (j *journal) revertToSnapshot(revid int, s *StateDB) {
// append inserts a new modification entry to the end of the change journal.
func (j *journal) append(entry journalEntry) {
j.entries = append(j.entries, entry)
if addr, dirty := entry.dirtied(); dirty {
j.dirties[addr]++
if addr, kind, dirty := entry.mutation(); dirty {
state := j.mutations[addr]
if state == nil {
state = new(journalMutationState)
j.mutations[addr] = state
}
state.add(kind)
}
}
// revert undoes a batch of journalled modifications along with any reverted
// dirty handling too.
// mutation tracking too.
func (j *journal) revert(statedb *StateDB, snapshot int) {
for i := len(j.entries) - 1; i >= snapshot; i-- {
// Undo the changes made by the operation
j.entries[i].revert(statedb)
// Drop any dirty tracking induced by the change
if addr, dirty := j.entries[i].dirtied(); dirty {
if j.dirties[addr]--; j.dirties[addr] == 0 {
delete(j.dirties, addr)
// Drop any mutation tracking induced by the change.
if addr, kind, dirty := j.entries[i].mutation(); dirty {
state := j.mutations[addr]
if state == nil {
panic(fmt.Errorf("journal mutation tracking missing for %x", addr[:]))
}
if state.remove(kind) {
delete(j.mutations, addr)
}
}
}
j.entries = j.entries[:snapshot]
}
// dirty explicitly sets an address to dirty, even if the change entries would
// otherwise suggest it as clean. This method is an ugly hack to handle the RIPEMD
// precompile consensus exception.
func (j *journal) dirty(addr common.Address) {
j.dirties[addr]++
// ripemdMagic explicitly keeps RIPEMD160 in the mutation set with a touch change.
//
// Ethereum Mainnet contains an old empty-account touch/revert quirk for address
// 0x03. If we only relied on the journal entry above, the revert path would
// remove the account from the mutation set together with the touch.
//
// Keep an explicit touch marker so tx finalisation still sees RIPEMD160
// on the mutation pass when replaying that historical case.
func (j *journal) ripemdMagic() {
state := j.mutations[ripemd]
if state == nil {
state = new(journalMutationState)
j.mutations[ripemd] = state
}
state.add(journalMutationKindTouch)
}
// length returns the current number of entries in the journal.
@ -141,9 +296,13 @@ func (j *journal) copy() *journal {
for i := 0; i < j.length(); i++ {
entries = append(entries, j.entries[i].copy())
}
mutations := make(map[common.Address]*journalMutationState, len(j.mutations))
for addr, state := range j.mutations {
mutations[addr] = state.copy()
}
return &journal{
entries: entries,
dirties: maps.Clone(j.dirties),
mutations: mutations,
validRevisions: slices.Clone(j.validRevisions),
nextRevisionId: j.nextRevisionId,
}
@ -187,13 +346,16 @@ func (j *journal) refundChange(previous uint64) {
}
func (j *journal) balanceChange(addr common.Address, previous *uint256.Int) {
prev := previous.Clone()
j.stashBalance(addr, prev)
j.append(balanceChange{
account: addr,
prev: previous.Clone(),
prev: prev,
})
}
func (j *journal) setCode(address common.Address, prevCode []byte) {
j.stashCode(address, prevCode)
j.append(codeChange{
account: address,
prevCode: prevCode,
@ -201,6 +363,7 @@ func (j *journal) setCode(address common.Address, prevCode []byte) {
}
func (j *journal) nonceChange(address common.Address, prev uint64) {
j.stashNonce(address, prev)
j.append(nonceChange{
account: address,
prev: prev,
@ -212,9 +375,18 @@ func (j *journal) touchChange(address common.Address) {
account: address,
})
if address == ripemd {
// Explicitly put it in the dirty-cache, which is otherwise generated from
// flattened journals.
j.dirty(address)
// Preserve the historical RIPEMD160 precompile consensus exception.
//
// Mainnet contains an old empty-account touch/revert quirk for address
// 0x03. If we only relied on the journal entry above, the revert path
// would remove the account from the dirty set together with the touch.
// Keep an explicit dirty marker so tx finalisation still sees the
// account on the dirty pass when replaying that historical case.
//
// This does not force deletion by itself: Finalise will still delete the
// account only if the state object is present at tx end and qualifies for
// deletion there.
j.ripemdMagic()
}
}
@ -295,8 +467,8 @@ func (ch createObjectChange) revert(s *StateDB) {
delete(s.stateObjects, ch.account)
}
func (ch createObjectChange) dirtied() (common.Address, bool) {
return ch.account, true
func (ch createObjectChange) mutation() (common.Address, journalMutationKind, bool) {
return ch.account, journalMutationKindCreate, true
}
func (ch createObjectChange) copy() journalEntry {
@ -309,8 +481,8 @@ func (ch createContractChange) revert(s *StateDB) {
s.getStateObject(ch.account).newContract = false
}
func (ch createContractChange) dirtied() (common.Address, bool) {
return common.Address{}, false
func (ch createContractChange) mutation() (common.Address, journalMutationKind, bool) {
return common.Address{}, journalMutationKindNone, false
}
func (ch createContractChange) copy() journalEntry {
@ -326,8 +498,8 @@ func (ch selfDestructChange) revert(s *StateDB) {
}
}
func (ch selfDestructChange) dirtied() (common.Address, bool) {
return ch.account, true
func (ch selfDestructChange) mutation() (common.Address, journalMutationKind, bool) {
return ch.account, journalMutationKindSelfDestruct, true
}
func (ch selfDestructChange) copy() journalEntry {
@ -341,8 +513,8 @@ var ripemd = common.HexToAddress("0000000000000000000000000000000000000003")
func (ch touchChange) revert(s *StateDB) {
}
func (ch touchChange) dirtied() (common.Address, bool) {
return ch.account, true
func (ch touchChange) mutation() (common.Address, journalMutationKind, bool) {
return ch.account, journalMutationKindTouch, true
}
func (ch touchChange) copy() journalEntry {
@ -355,8 +527,8 @@ func (ch balanceChange) revert(s *StateDB) {
s.getStateObject(ch.account).setBalance(ch.prev)
}
func (ch balanceChange) dirtied() (common.Address, bool) {
return ch.account, true
func (ch balanceChange) mutation() (common.Address, journalMutationKind, bool) {
return ch.account, journalMutationKindBalance, true
}
func (ch balanceChange) copy() journalEntry {
@ -370,8 +542,8 @@ func (ch nonceChange) revert(s *StateDB) {
s.getStateObject(ch.account).setNonce(ch.prev)
}
func (ch nonceChange) dirtied() (common.Address, bool) {
return ch.account, true
func (ch nonceChange) mutation() (common.Address, journalMutationKind, bool) {
return ch.account, journalMutationKindNonce, true
}
func (ch nonceChange) copy() journalEntry {
@ -385,8 +557,8 @@ func (ch codeChange) revert(s *StateDB) {
s.getStateObject(ch.account).setCode(crypto.Keccak256Hash(ch.prevCode), ch.prevCode)
}
func (ch codeChange) dirtied() (common.Address, bool) {
return ch.account, true
func (ch codeChange) mutation() (common.Address, journalMutationKind, bool) {
return ch.account, journalMutationKindCode, true
}
func (ch codeChange) copy() journalEntry {
@ -400,8 +572,8 @@ func (ch storageChange) revert(s *StateDB) {
s.getStateObject(ch.account).setState(ch.key, ch.prevvalue, ch.origvalue)
}
func (ch storageChange) dirtied() (common.Address, bool) {
return ch.account, true
func (ch storageChange) mutation() (common.Address, journalMutationKind, bool) {
return ch.account, journalMutationKindStorage, true
}
func (ch storageChange) copy() journalEntry {
@ -417,8 +589,8 @@ func (ch transientStorageChange) revert(s *StateDB) {
s.setTransientState(ch.account, ch.key, ch.prevalue)
}
func (ch transientStorageChange) dirtied() (common.Address, bool) {
return common.Address{}, false
func (ch transientStorageChange) mutation() (common.Address, journalMutationKind, bool) {
return common.Address{}, journalMutationKindNone, false
}
func (ch transientStorageChange) copy() journalEntry {
@ -433,8 +605,8 @@ func (ch refundChange) revert(s *StateDB) {
s.refund = ch.prev
}
func (ch refundChange) dirtied() (common.Address, bool) {
return common.Address{}, false
func (ch refundChange) mutation() (common.Address, journalMutationKind, bool) {
return common.Address{}, journalMutationKindNone, false
}
func (ch refundChange) copy() journalEntry {
@ -453,8 +625,8 @@ func (ch addLogChange) revert(s *StateDB) {
s.logSize--
}
func (ch addLogChange) dirtied() (common.Address, bool) {
return common.Address{}, false
func (ch addLogChange) mutation() (common.Address, journalMutationKind, bool) {
return common.Address{}, journalMutationKindNone, false
}
func (ch addLogChange) copy() journalEntry {
@ -476,8 +648,8 @@ func (ch accessListAddAccountChange) revert(s *StateDB) {
s.accessList.DeleteAddress(ch.address)
}
func (ch accessListAddAccountChange) dirtied() (common.Address, bool) {
return common.Address{}, false
func (ch accessListAddAccountChange) mutation() (common.Address, journalMutationKind, bool) {
return common.Address{}, journalMutationKindNone, false
}
func (ch accessListAddAccountChange) copy() journalEntry {
@ -490,8 +662,8 @@ func (ch accessListAddSlotChange) revert(s *StateDB) {
s.accessList.DeleteSlot(ch.address, ch.slot)
}
func (ch accessListAddSlotChange) dirtied() (common.Address, bool) {
return common.Address{}, false
func (ch accessListAddSlotChange) mutation() (common.Address, journalMutationKind, bool) {
return common.Address{}, journalMutationKindNone, false
}
func (ch accessListAddSlotChange) copy() journalEntry {

219
core/state/journal_test.go Normal file
View 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)
}
})
}

View file

@ -148,70 +148,28 @@ func (r *flatReader) Storage(addr common.Address, key common.Hash) (common.Hash,
return value, nil
}
// trieReader implements the StateReader interface, providing functions to access
// state from the referenced trie.
// mptTrieReader implements the StateReader interface, providing functions to
// access state from the referenced Merkle-Patricia-tree.
//
// trieReader is safe for concurrent read.
type trieReader struct {
root common.Hash // State root which uniquely represent a state
// mptTrieReader is safe for concurrent read.
type mptTrieReader struct {
root common.Hash // State root which uniquely represents a state
db *triedb.Database // Database for loading trie
// Main trie, resolved in constructor. Note either the Merkle-Patricia-tree
// or Verkle-tree is not safe for concurrent read.
mainTrie Trie
mainTrie Trie // Main trie, resolved in constructor, not thread-safe
subRoots map[common.Address]common.Hash // Set of storage roots, cached when the account is resolved
subTries map[common.Address]Trie // Group of storage tries, cached when it's resolved
lock sync.Mutex // Lock for protecting concurrent read
}
// newTrieReader constructs a trie reader of the specific state. An error will be
// returned if the associated trie specified by root is not existent.
func newTrieReader(root common.Hash, db *triedb.Database) (*trieReader, error) {
var (
tr Trie
err error
)
if !db.IsVerkle() {
tr, err = trie.NewStateTrie(trie.StateTrieID(root), db)
} else {
// When IsVerkle() is true, create a BinaryTrie wrapped in TransitionTrie
binTrie, binErr := bintrie.NewBinaryTrie(root, db)
if binErr != nil {
return nil, binErr
}
// Based on the transition status, determine if the overlay
// tree needs to be created, or if a single, target tree is
// to be picked.
ts := overlay.LoadTransitionState(db.Disk(), root, true)
if ts.InTransition() {
mpt, err := trie.NewStateTrie(trie.StateTrieID(ts.BaseRoot), db)
if err != nil {
return nil, err
}
tr = transitiontrie.NewTransitionTrie(mpt, binTrie, false)
} else {
// HACK: Use TransitionTrie with nil base as a wrapper to make BinaryTrie
// satisfy the Trie interface. This works around the import cycle between
// trie and trie/bintrie packages.
//
// TODO: In future PRs, refactor the package structure to avoid this hack:
// - Option 1: Move common interfaces (Trie, NodeIterator) to a separate
// package that both trie and trie/bintrie can import
// - Option 2: Create a factory function in the trie package that returns
// BinaryTrie as a Trie interface without direct import
// - Option 3: Move BinaryTrie to the main trie package
//
// The current approach works but adds unnecessary overhead and complexity
// by using TransitionTrie when there's no actual transition happening.
tr = transitiontrie.NewTransitionTrie(nil, binTrie, false)
}
}
// newMPTTrieReader constructs a Merkle-Patricia-tree reader of the specific state.
// An error will be returned if the associated trie specified by root is not existent.
func newMPTTrieReader(root common.Hash, db *triedb.Database) (*mptTrieReader, error) {
tr, err := trie.NewStateTrie(trie.StateTrieID(root), db)
if err != nil {
return nil, err
}
return &trieReader{
return &mptTrieReader{
root: root,
db: db,
mainTrie: tr,
@ -221,7 +179,7 @@ func newTrieReader(root common.Hash, db *triedb.Database) (*trieReader, error) {
}
// account is the inner version of Account and assumes the r.lock is already held.
func (r *trieReader) account(addr common.Address) (*types.StateAccount, error) {
func (r *mptTrieReader) account(addr common.Address) (*types.StateAccount, error) {
account, err := r.mainTrie.GetAccount(addr)
if err != nil {
return nil, err
@ -236,9 +194,9 @@ func (r *trieReader) account(addr common.Address) (*types.StateAccount, error) {
// Account implements StateReader, retrieving the account specified by the address.
//
// An error will be returned if the trie state is corrupted. An nil account
// An error will be returned if the trie state is corrupted. A nil account
// will be returned if it's not existent in the trie.
func (r *trieReader) Account(addr common.Address) (*types.StateAccount, error) {
func (r *mptTrieReader) Account(addr common.Address) (*types.StateAccount, error) {
r.lock.Lock()
defer r.lock.Unlock()
@ -250,43 +208,118 @@ func (r *trieReader) Account(addr common.Address) (*types.StateAccount, error) {
//
// An error will be returned if the trie state is corrupted. An empty storage
// slot will be returned if it's not existent in the trie.
func (r *trieReader) Storage(addr common.Address, key common.Hash) (common.Hash, error) {
func (r *mptTrieReader) Storage(addr common.Address, key common.Hash) (common.Hash, error) {
r.lock.Lock()
defer r.lock.Unlock()
var (
tr Trie
found bool
value common.Hash
)
if r.db.IsVerkle() {
tr = r.mainTrie
} else {
tr, found = r.subTries[addr]
if !found {
root, ok := r.subRoots[addr]
tr, found := r.subTries[addr]
if !found {
root, ok := r.subRoots[addr]
// The storage slot is accessed without account caching. It's unexpected
// behavior but try to resolve the account first anyway.
if !ok {
_, err := r.account(addr)
if err != nil {
return common.Hash{}, err
}
root = r.subRoots[addr]
}
var err error
tr, err = trie.NewStateTrie(trie.StorageTrieID(r.root, crypto.Keccak256Hash(addr.Bytes()), root), r.db)
// The storage slot is accessed without account caching. It's unexpected
// behavior but try to resolve the account first anyway.
if !ok {
_, err := r.account(addr)
if err != nil {
return common.Hash{}, err
}
r.subTries[addr] = tr
root = r.subRoots[addr]
}
var err error
tr, err = trie.NewStateTrie(trie.StorageTrieID(r.root, crypto.Keccak256Hash(addr.Bytes()), root), r.db)
if err != nil {
return common.Hash{}, err
}
r.subTries[addr] = tr
}
ret, err := tr.GetStorage(addr, key.Bytes())
if err != nil {
return common.Hash{}, err
}
var value common.Hash
value.SetBytes(ret)
return value, nil
}
// ubtTrieReader implements the StateReader interface, providing functions to access
// state from the referenced Unified-binary-trie.
//
// ubtTrieReader is safe for concurrent read.
type ubtTrieReader struct {
root common.Hash // State root which uniquely represents a state
db *triedb.Database // Database for loading trie
tr Trie // Referenced unified binary trie
lock sync.Mutex // Lock for protecting concurrent read
}
// newUBTTrieReader constructs a Unified-binary-trie reader of the specific state.
// An error will be returned if the associated trie specified by root is not existent.
func newUBTTrieReader(root common.Hash, db *triedb.Database) (*ubtTrieReader, error) {
binTrie, binErr := bintrie.NewBinaryTrie(root, db, db.BinTrieGroupDepth())
if binErr != nil {
return nil, binErr
}
// Based on the transition status, determine if the overlay
// tree needs to be created, or if a single, target tree is
// to be picked.
var (
tr Trie
ts = overlay.LoadTransitionState(db.Disk(), root, true)
)
if ts.InTransition() {
mpt, err := trie.NewStateTrie(trie.StateTrieID(ts.BaseRoot), db)
if err != nil {
return nil, err
}
tr = transitiontrie.NewTransitionTrie(mpt, binTrie, false)
} else {
// HACK: Use TransitionTrie with nil base as a wrapper to make BinaryTrie
// satisfy the Trie interface. This works around the import cycle between
// trie and trie/bintrie packages.
//
// TODO: In future PRs, refactor the package structure to avoid this hack:
// - Option 1: Move common interfaces (Trie, NodeIterator) to a separate
// package that both trie and trie/bintrie can import
// - Option 2: Create a factory function in the trie package that returns
// BinaryTrie as a Trie interface without direct import
// - Option 3: Move BinaryTrie to the main trie package
//
// The current approach works but adds unnecessary overhead and complexity
// by using TransitionTrie when there's no actual transition happening.
tr = transitiontrie.NewTransitionTrie(nil, binTrie, false)
}
return &ubtTrieReader{
root: root,
db: db,
tr: tr,
}, nil
}
// Account implements StateReader, retrieving the account specified by the address.
//
// An error will be returned if the trie state is corrupted. A nil account
// will be returned if it's not existent in the trie.
func (r *ubtTrieReader) Account(addr common.Address) (*types.StateAccount, error) {
r.lock.Lock()
defer r.lock.Unlock()
return r.tr.GetAccount(addr)
}
// Storage implements StateReader, retrieving the storage slot specified by the
// address and slot key.
//
// An error will be returned if the trie state is corrupted. An empty storage
// slot will be returned if it's not existent in the trie.
func (r *ubtTrieReader) Storage(addr common.Address, key common.Hash) (common.Hash, error) {
r.lock.Lock()
defer r.lock.Unlock()
ret, err := r.tr.GetStorage(addr, key.Bytes())
if err != nil {
return common.Hash{}, err
}
var value common.Hash
value.SetBytes(ret)
return value, nil
}

View 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 preexecution system contract calls.
// - 1 … n for transactions (in block order).
// - n + 1 for postexecution 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")
}

View 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)
}
}
}

View file

@ -342,7 +342,7 @@ func TestAccountIteratorTraversalValues(t *testing.T) {
if i%8 == 0 {
e[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 4, i)
}
if i > 50 || i < 85 {
if i > 50 && i < 85 {
f[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 5, i)
}
if i%64 == 0 {
@ -441,7 +441,7 @@ func TestStorageIteratorTraversalValues(t *testing.T) {
if i%8 == 0 {
e[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 4, i)
}
if i > 50 || i < 85 {
if i > 50 && i < 85 {
f[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 5, i)
}
if i%64 == 0 {

View file

@ -27,7 +27,6 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/bintrie"
"github.com/ethereum/go-ethereum/trie/transitiontrie"
@ -154,7 +153,7 @@ func (s *stateObject) getTrie() (Trie, error) {
func (s *stateObject) getPrefetchedTrie() Trie {
// If there's nothing to meaningfully return, let the user figure it out by
// pulling the trie from disk.
if (s.data.Root == types.EmptyRootHash && !s.db.db.TrieDB().IsVerkle()) || s.db.prefetcher == nil {
if (s.data.Root == types.EmptyRootHash && s.db.db.Type().Is(TypeMPT)) || s.db.prefetcher == nil {
return nil
}
// Attempt to retrieve the trie from the prefetcher
@ -163,8 +162,11 @@ func (s *stateObject) getPrefetchedTrie() Trie {
// GetState retrieves a value associated with the given storage key.
func (s *stateObject) GetState(key common.Hash) common.Hash {
value, _ := s.getState(key)
return value
value, dirty := s.dirtyStorage[key]
if dirty {
return value
}
return s.GetCommittedState(key)
}
// getState retrieves a value associated with the given storage key, along with
@ -181,6 +183,10 @@ func (s *stateObject) getState(key common.Hash) (common.Hash, common.Hash) {
// GetCommittedState retrieves the value associated with the specific key
// without any mutations caused in the current execution.
func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
// Record slot access regardless of whether the storage slot exists.
if s.db.stateAccessList != nil {
s.db.stateAccessList.StorageRead(s.address, key)
}
// If we have a pending write or clean cached, return that
if value, pending := s.pendingStorage[key]; pending {
return value
@ -195,19 +201,6 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
// have been handles via pendingStorage above.
// 2) we don't have new values, and can deliver empty response back
if _, destructed := s.db.stateObjectsDestruct[s.address]; destructed {
// Invoke the reader regardless and discard the returned value.
// The returned value may not be empty, as it could belong to a
// self-destructed contract.
//
// The read operation is still essential for correctly building
// the block-level access list.
//
// TODO(rjl493456442) the reader interface can be extended with
// Touch, recording the read access without the actual disk load.
_, err := s.db.reader.Storage(s.address, key)
if err != nil {
s.db.setError(err)
}
s.originStorage[key] = common.Hash{} // track the empty slot as origin value
return common.Hash{}
}
@ -282,6 +275,13 @@ func (s *stateObject) finalise() {
// map as the dirty slot might have been committed already (before the
// byzantium fork) and entry is necessary to modify the value back.
s.pendingStorage[key] = value
// Aggregate storage writes into the block-level access list.
// All slots in the dirtyStorage set must have post-transaction
// values that differ from their pre-transaction values.
if s.db.stateAccessList != nil {
s.db.stateAccessList.StorageWrite(s.db.blockAccessIndex, s.address, key, value)
}
}
if s.db.prefetcher != nil && len(slotsToPrefetch) > 0 && s.data.Root != types.EmptyRootHash {
if err := s.db.prefetcher.prefetch(s.addrHash(), s.data.Root, s.address, nil, slotsToPrefetch, false); err != nil {
@ -398,17 +398,8 @@ func (s *stateObject) updateRoot() {
}
// commitStorage overwrites the clean storage with the storage changes and
// fulfills the storage diffs into the given accountUpdate struct.
func (s *stateObject) commitStorage(op *accountUpdate) {
var (
encode = func(val common.Hash) []byte {
if val == (common.Hash{}) {
return nil
}
blob, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(val[:]))
return blob
}
)
// fulfills the storage diffs into the given AccountUpdate struct.
func (s *stateObject) commitStorage(op *AccountUpdate) {
for key, val := range s.pendingStorage {
// Skip the noop storage changes, it might be possible the value
// of tracked slot is same in originStorage and pendingStorage
@ -418,20 +409,20 @@ func (s *stateObject) commitStorage(op *accountUpdate) {
continue
}
hash := crypto.Keccak256Hash(key[:])
if op.storages == nil {
op.storages = make(map[common.Hash][]byte)
if op.Storages == nil {
op.Storages = make(map[common.Hash]common.Hash)
}
op.storages[hash] = encode(val)
op.Storages[hash] = val
if op.storagesOriginByKey == nil {
op.storagesOriginByKey = make(map[common.Hash][]byte)
if op.StoragesOriginByKey == nil {
op.StoragesOriginByKey = make(map[common.Hash]common.Hash)
}
if op.storagesOriginByHash == nil {
op.storagesOriginByHash = make(map[common.Hash][]byte)
if op.StoragesOriginByHash == nil {
op.StoragesOriginByHash = make(map[common.Hash]common.Hash)
}
origin := encode(s.originStorage[key])
op.storagesOriginByKey[key] = origin
op.storagesOriginByHash[hash] = origin
origin := s.originStorage[key]
op.StoragesOriginByKey[key] = origin
op.StoragesOriginByHash[hash] = origin
// Overwrite the clean value of storage slots
s.originStorage[key] = val
@ -444,32 +435,32 @@ func (s *stateObject) commitStorage(op *accountUpdate) {
//
// Note, commit may run concurrently across all the state objects. Do not assume
// thread-safe access to the statedb.
func (s *stateObject) commit() (*accountUpdate, *trienode.NodeSet, error) {
// commit the account metadata changes
op := &accountUpdate{
address: s.address,
data: types.SlimAccountRLP(s.data),
}
if s.origin != nil {
op.origin = types.SlimAccountRLP(*s.origin)
func (s *stateObject) commit() (*AccountUpdate, *trienode.NodeSet, error) {
// commit the account metadata changes, the data must be deep-copied
// to prevent accidental mutations later on (in practice the stateDB
// won't be modified after commit). The origin is safe to use directly.
op := &AccountUpdate{
Address: s.address,
Data: s.data.Copy(),
Origin: s.origin,
}
// commit the contract code if it's modified
if s.dirtyCode {
op.code = &contractCode{
hash: common.BytesToHash(s.CodeHash()),
blob: s.code,
op.Code = &ContractCode{
Hash: common.BytesToHash(s.CodeHash()),
Blob: s.code,
}
s.dirtyCode = false // reset the dirty flag
if s.origin == nil {
op.code.originHash = types.EmptyCodeHash
op.Code.OriginHash = types.EmptyCodeHash
} else {
op.code.originHash = common.BytesToHash(s.origin.CodeHash)
op.Code.OriginHash = common.BytesToHash(s.origin.CodeHash)
}
}
// Commit storage changes and the associated storage trie
s.commitStorage(op)
if len(op.storages) == 0 {
if len(op.Storages) == 0 {
// nothing changed, don't bother to commit the trie
s.origin = s.data.Copy()
return op, nil, nil
@ -478,12 +469,13 @@ func (s *stateObject) commit() (*accountUpdate, *trienode.NodeSet, error) {
// The main account trie commit in stateDB.commit() already calls
// CollectNodes on this trie, so calling Commit here again would
// redundantly traverse and serialize the entire tree per dirty account.
if s.db.GetTrie().IsVerkle() {
if s.db.GetTrie().IsUBT() {
s.origin = s.data.Copy()
return op, nil, nil
}
root, nodes := s.trie.Commit(false)
s.data.Root = root
// The storage trie root is omitted, as it has already been updated in the
// previous updateRoot step.
_, nodes := s.trie.Commit(false)
s.origin = s.data.Copy()
return op, nodes, nil
}

View file

@ -125,16 +125,17 @@ func (s SizeStats) add(diff SizeStats) SizeStats {
}
// calSizeStats measures the state size changes of the provided state update.
func calSizeStats(update *stateUpdate) (SizeStats, error) {
func calSizeStats(update *StateUpdate) (SizeStats, error) {
stats := SizeStats{
BlockNumber: update.blockNumber,
StateRoot: update.root,
BlockNumber: update.BlockNumber,
StateRoot: update.Root,
}
accounts, accountOrigin, storages, storageOrigin := update.EncodeMPTState()
// Measure the account changes
for addr, oldValue := range update.accountsOrigin {
for addr, oldValue := range accountOrigin {
addrHash := crypto.Keccak256Hash(addr.Bytes())
newValue, exists := update.accounts[addrHash]
newValue, exists := accounts[addrHash]
if !exists {
return SizeStats{}, fmt.Errorf("account %x not found", addr)
}
@ -156,9 +157,9 @@ func calSizeStats(update *stateUpdate) (SizeStats, error) {
}
// Measure storage changes
for addr, slots := range update.storagesOrigin {
for addr, slots := range storageOrigin {
addrHash := crypto.Keccak256Hash(addr.Bytes())
subset, exists := update.storages[addrHash]
subset, exists := storages[addrHash]
if !exists {
return SizeStats{}, fmt.Errorf("storage %x not found", addr)
}
@ -167,7 +168,7 @@ func calSizeStats(update *stateUpdate) (SizeStats, error) {
exists bool
newValue []byte
)
if update.rawStorageKey {
if update.StorageKeyType == StorageKeyPlain {
newValue, exists = subset[crypto.Keccak256Hash(key.Bytes())]
} else {
newValue, exists = subset[key]
@ -194,7 +195,7 @@ func calSizeStats(update *stateUpdate) (SizeStats, error) {
}
// Measure trienode changes
for owner, subset := range update.nodes.Sets {
for owner, subset := range update.Nodes.Sets {
var (
keyPrefix int64
isAccount = owner == (common.Hash{})
@ -244,13 +245,13 @@ func calSizeStats(update *stateUpdate) (SizeStats, error) {
}
codeExists := make(map[common.Hash]struct{})
for _, code := range update.codes {
if _, ok := codeExists[code.hash]; ok || code.duplicate {
for _, code := range update.Codes {
if _, ok := codeExists[code.Hash]; ok || code.Duplicate {
continue
}
stats.ContractCodes += 1
stats.ContractCodeBytes += codeKeySize + int64(len(code.blob))
codeExists[code.hash] = struct{}{}
stats.ContractCodeBytes += codeKeySize + int64(len(code.Blob))
codeExists[code.Hash] = struct{}{}
}
return stats, nil
}
@ -267,7 +268,7 @@ type SizeTracker struct {
triedb *triedb.Database
abort chan struct{}
aborted chan struct{}
updateCh chan *stateUpdate
updateCh chan *StateUpdate
queryCh chan *stateSizeQuery
}
@ -281,7 +282,7 @@ func NewSizeTracker(db ethdb.KeyValueStore, triedb *triedb.Database) (*SizeTrack
triedb: triedb,
abort: make(chan struct{}),
aborted: make(chan struct{}),
updateCh: make(chan *stateUpdate),
updateCh: make(chan *StateUpdate),
queryCh: make(chan *stateSizeQuery),
}
go t.run()
@ -328,9 +329,9 @@ func (t *SizeTracker) run() {
for {
select {
case u := <-t.updateCh:
base, found := stats[u.originRoot]
base, found := stats[u.OriginRoot]
if !found {
log.Debug("Ignored the state size without parent", "parent", u.originRoot, "root", u.root, "number", u.blockNumber)
log.Debug("Ignored the state size without parent", "parent", u.OriginRoot, "root", u.Root, "number", u.BlockNumber)
continue
}
diff, err := calSizeStats(u)
@ -338,15 +339,15 @@ func (t *SizeTracker) run() {
continue
}
stat := base.add(diff)
stats[u.root] = stat
last = u.root
stats[u.Root] = stat
last = u.Root
// Publish statistics to metric system
stat.publish()
// Evict the stale statistics
heap.Push(&h, stats[u.root])
for len(h) > 0 && u.blockNumber-h[0].BlockNumber > statEvictThreshold {
heap.Push(&h, stats[u.Root])
for len(h) > 0 && u.BlockNumber-h[0].BlockNumber > statEvictThreshold {
delete(stats, h[0].StateRoot)
heap.Pop(&h)
}
@ -402,7 +403,7 @@ wait:
}
var (
updates = make(map[common.Hash]*stateUpdate)
updates = make(map[common.Hash]*StateUpdate)
children = make(map[common.Hash][]common.Hash)
done chan buildResult
)
@ -410,9 +411,9 @@ wait:
for {
select {
case u := <-t.updateCh:
updates[u.root] = u
children[u.originRoot] = append(children[u.originRoot], u.root)
log.Debug("Received state update", "root", u.root, "blockNumber", u.blockNumber)
updates[u.Root] = u
children[u.OriginRoot] = append(children[u.OriginRoot], u.Root)
log.Debug("Received state update", "root", u.Root, "blockNumber", u.BlockNumber)
case r := <-t.queryCh:
r.err = errors.New("state size is not initialized yet")
@ -432,8 +433,8 @@ wait:
continue
}
done = make(chan buildResult)
go t.build(entry.root, entry.blockNumber, done)
log.Info("Measuring persistent state size", "root", root.Hex(), "number", entry.blockNumber)
go t.build(entry.Root, entry.BlockNumber, done)
log.Info("Measuring persistent state size", "root", root.Hex(), "number", entry.BlockNumber)
case result := <-done:
if result.err != nil {
@ -646,8 +647,8 @@ func (t *SizeTracker) iterateTableParallel(closed chan struct{}, prefix []byte,
// Notify is an async method used to send the state update to the size tracker.
// It ignores empty updates (where no state changes occurred).
// If the channel is full, it drops the update to avoid blocking.
func (t *SizeTracker) Notify(update *stateUpdate) {
if update == nil || update.empty() {
func (t *SizeTracker) Notify(update *StateUpdate) {
if update == nil || update.Empty() {
return
}
select {

View file

@ -160,7 +160,7 @@ func TestSizeTracker(t *testing.T) {
}
tracker.Notify(ret)
if err := tdb.Commit(ret.root, false); err != nil {
if err := tdb.Commit(ret.Root, false); err != nil {
t.Fatalf("Failed to commit trie at block %d: %v", blockNum, err)
}
@ -169,7 +169,7 @@ func TestSizeTracker(t *testing.T) {
t.Fatalf("Failed to calculate size stats for block %d: %v", blockNum, err)
}
trackedUpdates = append(trackedUpdates, diff)
currentRoot = ret.root
currentRoot = ret.Root
}
finalRoot := rawdb.ReadSnapshotRoot(db)

View file

@ -18,6 +18,7 @@
package state
import (
"bytes"
"errors"
"fmt"
"maps"
@ -31,6 +32,7 @@ import (
"github.com/ethereum/go-ethereum/core/stateless"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/types/bal"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
@ -126,6 +128,12 @@ type StateDB struct {
accessList *accessList
accessEvents *AccessEvents
// Per-transaction state access footprint for EIP-7928
stateAccessList *bal.ConstructionBlockAccessList
// Block access index (0 for pre-execution, 1..n for transactions, n+1 for post-execution)
blockAccessIndex uint32
// Transient storage
transientStorage transientStorage
@ -190,7 +198,7 @@ func NewWithReader(root common.Hash, db Database, reader Reader) (*StateDB, erro
accessList: newAccessList(),
transientStorage: newTransientStorage(),
}
if db.TrieDB().IsVerkle() {
if db.Type().Is(TypeUBT) {
sdb.accessEvents = NewAccessEvents()
}
return sdb, nil
@ -317,6 +325,11 @@ func (s *StateDB) Empty(addr common.Address) bool {
return so == nil || so.empty()
}
// Touch accesses the specific account without returning anything.
func (s *StateDB) Touch(addr common.Address) {
s.getStateObject(addr)
}
// GetBalance retrieves the balance from the given address or 0 if object not found
func (s *StateDB) GetBalance(addr common.Address) *uint256.Int {
stateObject := s.getStateObject(addr)
@ -338,6 +351,9 @@ func (s *StateDB) GetNonce(addr common.Address) uint64 {
// GetStorageRoot retrieves the storage root from the given address or empty
// if object not found.
//
// Note: the storage root returned corresponds to the trie since last Intermediate
// operation, some recent in-memory changes are excluded.
func (s *StateDB) GetStorageRoot(addr common.Address) common.Hash {
stateObject := s.getStateObject(addr)
if stateObject != nil {
@ -576,6 +592,10 @@ func (s *StateDB) deleteStateObject(addr common.Address) {
// getStateObject retrieves a state object given by the address, returning nil if
// the object is not found or was deleted in this execution context.
func (s *StateDB) getStateObject(addr common.Address) *stateObject {
// Record state access regardless of whether the account exists.
if s.stateAccessList != nil {
s.stateAccessList.AccountRead(addr)
}
// Prefer live objects if any is available
if obj := s.stateObjects[addr]; obj != nil {
return obj
@ -678,6 +698,7 @@ func (s *StateDB) Copy() *StateDB {
refund: s.refund,
thash: s.thash,
txIndex: s.txIndex,
blockAccessIndex: s.blockAccessIndex,
logs: make(map[common.Hash][]*types.Log, len(s.logs)),
logSize: s.logSize,
preimages: maps.Clone(s.preimages),
@ -722,6 +743,9 @@ func (s *StateDB) Copy() *StateDB {
}
state.logs[hash] = cpy
}
if s.stateAccessList != nil {
state.stateAccessList = s.stateAccessList.Copy()
}
return state
}
@ -757,7 +781,7 @@ type removedAccountWithBalance struct {
// before the Finalise.
func (s *StateDB) LogsForBurnAccounts() []*types.Log {
var list []removedAccountWithBalance
for addr := range s.journal.dirties {
for addr := range s.journal.mutations {
if obj, exist := s.stateObjects[addr]; exist && obj.selfDestructed && !obj.Balance().IsZero() {
list = append(list, removedAccountWithBalance{
address: obj.address,
@ -781,29 +805,69 @@ func (s *StateDB) LogsForBurnAccounts() []*types.Log {
// Finalise finalises the state by removing the destructed objects and clears
// the journal as well as the refunds. Finalise, however, will not push any updates
// into the tries just yet. Only IntermediateRoot or Commit will do that.
func (s *StateDB) Finalise(deleteEmptyObjects bool) {
addressesToPrefetch := make([]common.Address, 0, len(s.journal.dirties))
for addr := range s.journal.dirties {
func (s *StateDB) Finalise(deleteEmptyObjects bool) *bal.ConstructionBlockAccessList {
addressesToPrefetch := make([]common.Address, 0, len(s.journal.mutations))
for addr, state := range s.journal.mutations {
obj, exist := s.stateObjects[addr]
if !exist {
// ripeMD is 'touched' at block 1714175, in tx 0x1237f737031e40bcde4a8b7e717b2d15e3ecadfe49bb1bbc71ee9deb09c6fcf2
// That tx goes out of gas, and although the notion of 'touched' does not exist there, the
// touch-event will still be recorded in the journal. Since ripeMD is a special snowflake,
// it will persist in the journal even though the journal is reverted. In this special circumstance,
// it may exist in `s.journal.dirties` but not in `s.stateObjects`.
// Thus, we can safely ignore it here
// RIPEMD160 (0x03) gets an extra dirty marker for a historical
// mainnet consensus exception (at block 1714175, in tx
// 0x1237f737031e40bcde4a8b7e717b2d15e3ecadfe49bb1bbc71ee9deb09c6fcf2)
// around empty-account touch/revert handling.
//
// That marker survives journal revert, so the account may remain in
// s.journal.mutations even though its state object was rolled
// back and no longer exists. In that case there is nothing to
// finalise or delete, so ignore it here.
continue
}
if obj.selfDestructed || (deleteEmptyObjects && obj.empty()) {
delete(s.stateObjects, obj.address)
s.markDelete(addr)
// We need to maintain account deletions explicitly (will remain
// set indefinitely). Note only the first occurred self-destruct
// event is tracked.
if _, ok := s.stateObjectsDestruct[obj.address]; !ok {
s.stateObjectsDestruct[obj.address] = obj
}
// Aggregate the account mutation into the block-level accessList
// if Amsterdam has been activated.
if s.stateAccessList != nil {
// Notably, if the account is deleted during the transaction,
// its pre-transaction nonce, code, and storage must be empty.
//
// EIP-6780 restricts self-destruct to contracts deployed within
// the same transaction, while EIP-7610 rejects deployments to
// destinations with non-empty storage, non-zero nonce and non-empty
// code.
//
// Therefore, when an account is deleted, its pre-transaction nonce
// code and storage is guaranteed to be empty, leaving nothing to
// clean up here.
balance := uint256.NewInt(0)
if state.balanceSet && balance.Cmp(state.balance) != 0 {
s.stateAccessList.BalanceChange(s.blockAccessIndex, addr, balance)
}
}
} else {
// Aggregate the account mutation into the block-level accessList
// if Amsterdam has been activated.
if s.stateAccessList != nil {
balance := obj.Balance()
if state.balanceSet && balance.Cmp(state.balance) != 0 {
s.stateAccessList.BalanceChange(s.blockAccessIndex, addr, balance)
}
nonce := obj.Nonce()
if state.nonceSet && nonce != state.nonce {
s.stateAccessList.NonceChange(addr, s.blockAccessIndex, nonce)
}
if state.codeSet {
if code := obj.Code(); !bytes.Equal(code, state.code) {
s.stateAccessList.CodeChange(addr, s.blockAccessIndex, code)
}
}
}
obj.finalise()
s.markUpdate(addr)
}
@ -819,6 +883,8 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
}
// Invalidate journal because reverting across transactions is not allowed.
s.clearJournalAndRefund()
return s.stateAccessList
}
// IntermediateRoot computes the current root hash of the state trie.
@ -858,7 +924,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
start = time.Now()
workers errgroup.Group
)
if s.db.TrieDB().IsVerkle() {
if s.db.Type().Is(TypeUBT) {
// Bypass per-account updateTrie() for binary trie. In binary trie mode
// there is only one unified trie (OpenStorageTrie returns self), so the
// per-account trie setup in updateTrie() (getPrefetchedTrie, getTrie,
@ -922,9 +988,9 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
}
}
// If witness building is enabled, gather all the read-only accesses.
// Skip witness collection in Verkle mode, they will be gathered
// together at the end.
if s.witness != nil && !s.db.TrieDB().IsVerkle() {
// Skip witness collection in Unified-binary-trie mode, they will be
// gathered together at the end.
if s.witness != nil && s.db.Type().Is(TypeMPT) {
// Pull in anything that has been accessed before destruction
for _, obj := range s.stateObjectsDestruct {
// Skip any objects that haven't touched their storage
@ -965,7 +1031,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
// only a single trie is used for state hashing. Replacing a non-nil verkle tree
// here could result in losing uncommitted changes from storage.
start = time.Now()
if s.prefetcher != nil && !s.db.TrieDB().IsVerkle() {
if s.prefetcher != nil && s.db.Type().Is(TypeMPT) {
if trie := s.prefetcher.trie(common.Hash{}, s.originalRoot); trie == nil {
log.Error("Failed to retrieve account pre-fetcher trie")
} else {
@ -1031,9 +1097,10 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
// SetTxContext sets the current transaction hash and index which are
// used when the EVM emits new state logs. It should be invoked before
// transaction execution.
func (s *StateDB) SetTxContext(thash common.Hash, ti int) {
func (s *StateDB) SetTxContext(thash common.Hash, ti int, blockAccessIndex uint32) {
s.thash = thash
s.txIndex = ti
s.blockAccessIndex = blockAccessIndex
}
func (s *StateDB) clearJournalAndRefund() {
@ -1042,11 +1109,11 @@ func (s *StateDB) clearJournalAndRefund() {
}
// deleteStorage is designed to delete the storage trie of a designated account.
func (s *StateDB) deleteStorage(addrHash common.Hash, root common.Hash) (map[common.Hash][]byte, map[common.Hash][]byte, *trienode.NodeSet, error) {
func (s *StateDB) deleteStorage(addrHash common.Hash, root common.Hash) (map[common.Hash]common.Hash, map[common.Hash]common.Hash, *trienode.NodeSet, error) {
var (
nodes = trienode.NewNodeSet(addrHash) // the set for trie node mutations (value is nil)
storages = make(map[common.Hash][]byte) // the set for storage mutations (value is nil)
storageOrigins = make(map[common.Hash][]byte) // the set for tracking the original value of slot
nodes = trienode.NewNodeSet(addrHash) // the set for trie node mutations (value is nil)
storages = make(map[common.Hash]common.Hash) // the set for storage mutations (value is nil)
storageOrigins = make(map[common.Hash]common.Hash) // the set for tracking the original value of slot
)
iteratee, err := s.db.Iteratee(s.originalRoot)
if err != nil {
@ -1062,19 +1129,24 @@ func (s *StateDB) deleteStorage(addrHash common.Hash, root common.Hash) (map[com
nodes.AddNode(path, trienode.NewDeletedWithPrev(blob))
})
for it.Next() {
slot := common.CopyBytes(it.Slot())
if err := it.Error(); err != nil { // error might occur after Slot function
slot := it.Slot()
// Error might occur after Slot function
if err := it.Error(); err != nil {
return nil, nil, nil, err
}
if slot == (common.Hash{}) {
return nil, nil, nil, fmt.Errorf("unexpected empty storage slot, addr: %x, slot: %x", addrHash, it.Hash())
}
key := it.Hash()
storages[key] = nil
storages[key] = common.Hash{}
storageOrigins[key] = slot
if err := stack.Update(key.Bytes(), slot); err != nil {
if err := stack.Update(key.Bytes(), encodeSlot(slot)); err != nil {
return nil, nil, nil, err
}
}
if err := it.Error(); err != nil { // error might occur during iteration
// Error might occur during iteration
if err := it.Error(); err != nil {
return nil, nil, nil, err
}
if stack.Hash() != root {
@ -1101,10 +1173,10 @@ func (s *StateDB) deleteStorage(addrHash common.Hash, root common.Hash) (map[com
// with their values be tracked as original value.
// In case (d), **original** account along with its storages should be deleted,
// with their values be tracked as original value.
func (s *StateDB) handleDestruction(noStorageWiping bool) (map[common.Hash]*accountDelete, []*trienode.NodeSet, error) {
func (s *StateDB) handleDestruction(noStorageWiping bool) (map[common.Hash]*AccountDelete, []*trienode.NodeSet, error) {
var (
nodes []*trienode.NodeSet
deletes = make(map[common.Hash]*accountDelete)
deletes = make(map[common.Hash]*AccountDelete)
)
for addr, prevObj := range s.stateObjectsDestruct {
prev := prevObj.origin
@ -1118,15 +1190,15 @@ func (s *StateDB) handleDestruction(noStorageWiping bool) (map[common.Hash]*acco
continue
}
// The account was existent, it can be either case (c) or (d).
addrHash := crypto.Keccak256Hash(addr.Bytes())
op := &accountDelete{
address: addr,
origin: types.SlimAccountRLP(*prev),
addrHash := prevObj.addrHash()
op := &AccountDelete{
Address: addr,
Origin: prev,
}
deletes[addrHash] = op
// Short circuit if the origin storage was empty.
if prev.Root == types.EmptyRootHash || s.db.TrieDB().IsVerkle() {
if prev.Root == types.EmptyRootHash || s.db.Type().Is(TypeUBT) {
continue
}
if noStorageWiping {
@ -1137,8 +1209,8 @@ func (s *StateDB) handleDestruction(noStorageWiping bool) (map[common.Hash]*acco
if err != nil {
return nil, nil, fmt.Errorf("failed to delete storage, err: %w", err)
}
op.storages = storages
op.storagesOrigin = storagesOrigin
op.Storages = storages
op.StoragesOrigin = storagesOrigin
// Aggregate the associated trie node changes.
nodes = append(nodes, set)
@ -1153,13 +1225,13 @@ func (s *StateDB) GetTrie() Trie {
// commit gathers the state mutations accumulated along with the associated
// trie changes, resetting all internal flags with the new state as the base.
func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNumber uint64) (*stateUpdate, error) {
func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNumber uint64) (*StateUpdate, error) {
// Short circuit in case any database failure occurred earlier.
if s.dbErr != nil {
return nil, fmt.Errorf("commit aborted due to earlier error: %v", s.dbErr)
}
// Finalize any pending changes and merge everything into the tries
s.IntermediateRoot(deleteEmptyObjects)
root := s.IntermediateRoot(deleteEmptyObjects)
// Short circuit if any error occurs within the IntermediateRoot.
if s.dbErr != nil {
@ -1174,7 +1246,7 @@ func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNum
lock sync.Mutex // protect two maps below
nodes = trienode.NewMergedNodeSet() // aggregated trie nodes
updates = make(map[common.Hash]*accountUpdate, len(s.mutations)) // aggregated account updates
updates = make(map[common.Hash]*AccountUpdate, len(s.mutations)) // aggregated account updates
// merge aggregates the dirty trie nodes into the global set.
//
@ -1221,7 +1293,6 @@ func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNum
// writes to run in parallel with the computations.
var (
start = time.Now()
root common.Hash
workers errgroup.Group
)
// Schedule the account trie first since that will be the biggest, so give
@ -1235,9 +1306,7 @@ func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNum
// code didn't anticipate for.
workers.Go(func() error {
// Write the account trie changes, measuring the amount of wasted time
newroot, set := s.trie.Commit(true)
root = newroot
_, set := s.trie.Commit(true)
if err := merge(set); err != nil {
return err
}
@ -1305,12 +1374,16 @@ func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNum
origin := s.originalRoot
s.originalRoot = root
return newStateUpdate(noStorageWiping, origin, root, blockNumber, deletes, updates, nodes), nil
typ := StorageKeyHashed
if noStorageWiping {
typ = StorageKeyPlain
}
return NewStateUpdate(typ, origin, root, blockNumber, deletes, updates, nodes), nil
}
// commitAndFlush is a wrapper of commit which also commits the state mutations
// to the configured data stores.
func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool, noStorageWiping bool, deriveCodeFields bool) (*stateUpdate, error) {
func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool, noStorageWiping bool, deriveCodeFields bool) (*StateUpdate, error) {
ret, err := s.commit(deleteEmptyObjects, noStorageWiping, block)
if err != nil {
return nil, err
@ -1328,7 +1401,7 @@ func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool, noStorag
// The reader update must be performed as the final step, otherwise,
// the new state would not be visible before db.commit.
s.reader, _ = s.db.Reader(s.originalRoot)
s.reader, err = s.db.Reader(s.originalRoot)
return ret, err
}
@ -1351,17 +1424,17 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool, noStorageWiping
if err != nil {
return common.Hash{}, err
}
return ret.root, nil
return ret.Root, nil
}
// CommitWithUpdate writes the state mutations and returns the state update for
// external processing (e.g., live tracing hooks or size tracker).
func (s *StateDB) CommitWithUpdate(block uint64, deleteEmptyObjects bool, noStorageWiping bool) (common.Hash, *stateUpdate, error) {
func (s *StateDB) CommitWithUpdate(block uint64, deleteEmptyObjects bool, noStorageWiping bool) (common.Hash, *StateUpdate, error) {
ret, err := s.commitAndFlush(block, deleteEmptyObjects, noStorageWiping, true)
if err != nil {
return common.Hash{}, nil, err
}
return ret.root, ret, nil
return ret.Root, ret, nil
}
// Prepare handles the preparatory steps for executing a state transition with.
@ -1406,6 +1479,10 @@ func (s *StateDB) Prepare(rules params.Rules, sender, coinbase common.Address, d
}
// Reset transient storage at the beginning of transaction execution
s.transientStorage = newTransientStorage()
if rules.IsAmsterdam {
s.stateAccessList = bal.NewConstructionBlockAccessList()
}
}
// AddAddressToAccessList adds the given address to the access list

View file

@ -182,11 +182,12 @@ func (test *stateTest) run() bool {
accountOrigin []map[common.Address][]byte
storages []map[common.Hash]map[common.Hash][]byte
storageOrigin []map[common.Address]map[common.Hash][]byte
copyUpdate = func(update *stateUpdate) {
accounts = append(accounts, maps.Clone(update.accounts))
accountOrigin = append(accountOrigin, maps.Clone(update.accountsOrigin))
storages = append(storages, maps.Clone(update.storages))
storageOrigin = append(storageOrigin, maps.Clone(update.storagesOrigin))
copyUpdate = func(update *StateUpdate) {
accts, acctOrigin, slots, slotOrigin := update.EncodeMPTState()
accounts = append(accounts, maps.Clone(accts))
accountOrigin = append(accountOrigin, maps.Clone(acctOrigin))
storages = append(storages, maps.Clone(slots))
storageOrigin = append(storageOrigin, maps.Clone(slotOrigin))
}
disk = rawdb.NewMemoryDatabase()
tdb = triedb.NewDatabase(disk, &triedb.Config{PathDB: pathdb.Defaults})
@ -209,7 +210,7 @@ func (test *stateTest) run() bool {
if i != 0 {
root = roots[len(roots)-1]
}
state, err := New(root, NewDatabase(tdb, nil).WithSnapshot(snaps))
state, err := New(root, NewMPTDatabase(tdb, nil).WithSnapshot(snaps))
if err != nil {
panic(err)
}
@ -232,11 +233,11 @@ func (test *stateTest) run() bool {
if err != nil {
panic(err)
}
if ret.empty() {
if ret.Empty() {
return true
}
copyUpdate(ret)
roots = append(roots, ret.root)
roots = append(roots, ret.Root)
}
for i := 0; i < len(test.actions); i++ {
root := types.EmptyRootHash

View file

@ -25,6 +25,7 @@ import (
"github.com/ethereum/go-ethereum/core/stateless"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/types/bal"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
@ -98,10 +99,6 @@ func (s *hookedStateDB) GetState(addr common.Address, hash common.Hash) common.H
return s.inner.GetState(addr, hash)
}
func (s *hookedStateDB) GetStorageRoot(addr common.Address) common.Hash {
return s.inner.GetStorageRoot(addr)
}
func (s *hookedStateDB) GetTransientState(addr common.Address, key common.Hash) common.Hash {
return s.inner.GetTransientState(addr, key)
}
@ -118,6 +115,10 @@ func (s *hookedStateDB) Exist(addr common.Address) bool {
return s.inner.Exist(addr)
}
func (s *hookedStateDB) Touch(addr common.Address) {
s.inner.Touch(addr)
}
func (s *hookedStateDB) Empty(addr common.Address) bool {
return s.inner.Empty(addr)
}
@ -233,18 +234,17 @@ func (s *hookedStateDB) LogsForBurnAccounts() []*types.Log {
return s.inner.LogsForBurnAccounts()
}
func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) {
func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) *bal.ConstructionBlockAccessList {
if s.hooks.OnBalanceChange == nil && s.hooks.OnNonceChangeV2 == nil && s.hooks.OnNonceChange == nil && s.hooks.OnCodeChangeV2 == nil && s.hooks.OnCodeChange == nil {
// Short circuit if no relevant hooks are set.
s.inner.Finalise(deleteEmptyObjects)
return
return s.inner.Finalise(deleteEmptyObjects)
}
// Collect all self-destructed addresses first, then sort them to ensure
// that state change hooks will be invoked in deterministic
// order when the accounts are deleted below
var selfDestructedAddrs []common.Address
for addr := range s.inner.journal.dirties {
for addr := range s.inner.journal.mutations {
obj := s.inner.stateObjects[addr]
if obj == nil || !obj.selfDestructed {
// Not self-destructed, keep searching.
@ -286,6 +286,9 @@ func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) {
s.hooks.OnCodeChange(addr, prevCodeHash, s.inner.GetCode(addr), types.EmptyCodeHash, nil)
}
}
s.inner.Finalise(deleteEmptyObjects)
return s.inner.Finalise(deleteEmptyObjects)
}
func (s *hookedStateDB) SetTxContext(thash common.Hash, ti int, blockAccessIndex uint32) {
s.inner.SetTxContext(thash, ti, blockAccessIndex)
}

View file

@ -82,7 +82,7 @@ func TestBurn(t *testing.T) {
// TestHooks is a basic sanity-check of all hooks
func TestHooks(t *testing.T) {
inner, _ := New(types.EmptyRootHash, NewDatabaseForTesting())
inner.SetTxContext(common.Hash{0x11}, 100) // For the log
inner.SetTxContext(common.Hash{0x11}, 100, 101) // For the log
var result []string
var wants = []string{
"0xaa00000000000000000000000000000000000000.balance: 0->100 (Unspecified)",

View file

@ -247,16 +247,16 @@ func TestCopyWithDirtyJournal(t *testing.T) {
orig.Finalise(true)
for i := byte(0); i < 255; i++ {
root := orig.GetStorageRoot(common.BytesToAddress([]byte{i}))
if root != (common.Hash{}) {
t.Errorf("Unexpected storage root %x", root)
balance := orig.GetBalance(common.BytesToAddress([]byte{i}))
if !balance.IsZero() {
t.Errorf("Unexpected balance %x", root)
}
}
cpy.Finalise(true)
for i := byte(0); i < 255; i++ {
root := cpy.GetStorageRoot(common.BytesToAddress([]byte{i}))
if root != (common.Hash{}) {
t.Errorf("Unexpected storage root %x", root)
balance := cpy.GetBalance(common.BytesToAddress([]byte{i}))
if !balance.IsZero() {
t.Errorf("Unexpected balance %x", root)
}
}
if cpy.IntermediateRoot(true) != orig.IntermediateRoot(true) {
@ -394,9 +394,7 @@ func newTestAction(addr common.Address, r *rand.Rand) testAction {
}
contractHash := s.GetCodeHash(addr)
emptyCode := contractHash == (common.Hash{}) || contractHash == types.EmptyCodeHash
storageRoot := s.GetStorageRoot(addr)
emptyStorage := storageRoot == (common.Hash{}) || storageRoot == types.EmptyRootHash
if s.GetNonce(addr) == 0 && emptyCode && emptyStorage {
if s.GetNonce(addr) == 0 && emptyCode {
s.CreateContract(addr)
// We also set some code here, to prevent the
// CreateContract action from being performed twice in a row,
@ -641,7 +639,7 @@ func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error {
{
have := state.transientStorage
want := checkstate.transientStorage
if !maps.EqualFunc(have, want, maps.Equal) {
if !maps.Equal(have, want) {
return fmt.Errorf("transient storage differs ,have\n%v\nwant\n%v",
have.PrettyPrint(),
want.PrettyPrint())
@ -664,26 +662,30 @@ func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error {
return fmt.Errorf("got GetLogs(common.Hash{}) == %v, want GetLogs(common.Hash{}) == %v",
state.GetLogs(common.Hash{}, 0, common.Hash{}, 0), checkstate.GetLogs(common.Hash{}, 0, common.Hash{}, 0))
}
if !maps.Equal(state.journal.dirties, checkstate.journal.dirties) {
getKeys := func(dirty map[common.Address]int) string {
var keys []common.Address
out := new(strings.Builder)
for key := range dirty {
keys = append(keys, key)
}
slices.SortFunc(keys, common.Address.Cmp)
for i, key := range keys {
fmt.Fprintf(out, " %d. %v\n", i, key)
}
return out.String()
}
have := getKeys(state.journal.dirties)
want := getKeys(checkstate.journal.dirties)
return fmt.Errorf("dirty-journal set mismatch.\nhave:\n%v\nwant:\n%v\n", have, want)
if !equalMutationSets(state.journal.mutations, checkstate.journal.mutations) {
return fmt.Errorf("journal mutation set mismatch.\nhave:\n%v\nwant:\n%v\n", state.journal.mutations, checkstate.journal.mutations)
}
return nil
}
// equalMutationSets checks that two journal mutation maps have the same set of
// addresses and, for each address, the same per-kind counts. The stashed
// original values are ignored because comparing them across two independent
// state databases (with distinct pointer identities) isn't the point of this
// check — we only care that the two journals agree on what was touched.
func equalMutationSets(a, b map[common.Address]*journalMutationState) bool {
if len(a) != len(b) {
return false
}
for addr, sa := range a {
sb, ok := b[addr]
if !ok || sa.counts != sb.counts {
return false
}
}
return true
}
func TestTouchDelete(t *testing.T) {
s := newStateEnv()
s.state.getOrNewStateObject(common.Address{})
@ -693,12 +695,54 @@ func TestTouchDelete(t *testing.T) {
snapshot := s.state.Snapshot()
s.state.AddBalance(common.Address{}, new(uint256.Int), tracing.BalanceChangeUnspecified)
if len(s.state.journal.dirties) != 1 {
t.Fatal("expected one dirty state object")
if len(s.state.journal.mutations) != 1 {
t.Fatal("expected one mutated state object")
}
s.state.RevertToSnapshot(snapshot)
if len(s.state.journal.dirties) != 0 {
t.Fatal("expected no dirty state object")
if len(s.state.journal.mutations) != 0 {
t.Fatal("expected no journal mutations")
}
}
func TestJournalMutationTracking(t *testing.T) {
state, _ := New(types.EmptyRootHash, NewDatabaseForTesting())
addr := common.HexToAddress("0x01")
key := common.HexToHash("0x02")
if _, ok := state.journal.mutations[addr]; ok {
t.Fatal("unexpected initial mutation entry")
}
snapshot := state.Snapshot()
state.SetBalance(addr, uint256.NewInt(1), tracing.BalanceChangeUnspecified)
state.SetNonce(addr, 2, tracing.NonceChangeUnspecified)
state.SetCode(addr, []byte{0x1}, tracing.CodeChangeUnspecified)
state.SetState(addr, key, common.Hash{0x3})
want := journalMutationCounts{
journalMutationKindCreate: 1,
journalMutationKindBalance: 1,
journalMutationKindNonce: 1,
journalMutationKindCode: 1,
journalMutationKindStorage: 1,
}
checkCounts := func(got *journalMutationState, label string) {
t.Helper()
if got == nil {
t.Fatalf("%s: missing mutation entry for %x", label, addr)
}
if got.counts != want {
t.Fatalf("%s: counts=%+v, want=%+v", label, got.counts, want)
}
}
checkCounts(state.journal.mutations[addr], "state")
copy := state.Copy()
checkCounts(copy.journal.mutations[addr], "copy")
state.RevertToSnapshot(snapshot)
if _, ok := state.journal.mutations[addr]; ok {
t.Fatalf("unexpected mutation entry after revert")
}
}
@ -1276,7 +1320,7 @@ func TestDeleteStorage(t *testing.T) {
disk = rawdb.NewMemoryDatabase()
tdb = triedb.NewDatabase(disk, nil)
snaps, _ = snapshot.New(snapshot.Config{CacheSize: 10}, disk, tdb, types.EmptyRootHash)
db = NewDatabase(tdb, nil).WithSnapshot(snaps)
db = NewMPTDatabase(tdb, nil).WithSnapshot(snaps)
state, _ = New(types.EmptyRootHash, db)
addr = common.HexToAddress("0x1")
)
@ -1290,8 +1334,8 @@ func TestDeleteStorage(t *testing.T) {
}
root, _ := state.Commit(0, true, false)
// Init phase done, create two states, one with snap and one without
fastState, _ := New(root, NewDatabase(tdb, nil).WithSnapshot(snaps))
slowState, _ := New(root, NewDatabase(tdb, nil))
fastState, _ := New(root, NewMPTDatabase(tdb, nil).WithSnapshot(snaps))
slowState, _ := New(root, NewMPTDatabase(tdb, nil))
obj := fastState.getOrNewStateObject(addr)
storageRoot := obj.data.Root
@ -1368,3 +1412,38 @@ func TestStorageDirtiness(t *testing.T) {
state.RevertToSnapshot(snap)
checkDirty(common.Hash{0x1}, common.Hash{0x1}, true)
}
// TestStateDBCopyUBT exercises StateDB.Copy on a UBT-backed state database.
// Before the mustCopyTrie fix, this panicked with "unknown trie type
// *bintrie.BinaryTrie" because the type switch in mustCopyTrie only covered
// *trie.StateTrie and *transitiontrie.TransitionTrie.
func TestStateDBCopyUBT(t *testing.T) {
disk := rawdb.NewMemoryDatabase()
tdb := triedb.NewDatabase(disk, triedb.UBTDefaults)
sdb := NewDatabase(tdb, nil)
orig, err := New(types.EmptyRootHash, sdb)
if err != nil {
t.Fatalf("New: %v", err)
}
// Touch the trie so StateDB.Copy actually has to copy it.
addr := common.HexToAddress("0x1111111111111111111111111111111111111111")
orig.SetBalance(addr, uint256.NewInt(1_000), tracing.BalanceChangeUnspecified)
// Must not panic.
cpy := orig.Copy()
if cpy == nil {
t.Fatal("Copy returned nil")
}
// The copy must be independent: mutating the copy does not affect the
// original. Use balance as an observable.
cpy.SetBalance(addr, uint256.NewInt(2_000), tracing.BalanceChangeUnspecified)
if got, want := orig.GetBalance(addr), uint256.NewInt(1_000); got.Cmp(want) != 0 {
t.Fatalf("original balance mutated through copy: got %s, want %s", got, want)
}
if got, want := cpy.GetBalance(addr), uint256.NewInt(2_000); got.Cmp(want) != 0 {
t.Fatalf("copy balance did not update: got %s, want %s", got, want)
}
}

View file

@ -26,139 +26,143 @@ import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie/trienode"
"github.com/ethereum/go-ethereum/triedb"
)
// contractCode represents contract bytecode along with its associated metadata.
type contractCode struct {
hash common.Hash // hash is the cryptographic hash of the current contract code.
blob []byte // blob is the binary representation of the current contract code.
originHash common.Hash // originHash is the cryptographic hash of the code before mutation.
// ContractCode represents contract bytecode mutation along with its
// associated metadata.
type ContractCode struct {
Hash common.Hash // Hash is the cryptographic hash of the current contract code.
Blob []byte // Blob is the binary representation of the current contract code.
OriginHash common.Hash // OriginHash is the cryptographic hash of the code before mutation.
// Derived fields, populated only when state tracking is enabled.
duplicate bool // duplicate indicates whether the updated code already exists.
originBlob []byte // originBlob is the original binary representation of the contract code.
Duplicate bool // Duplicate indicates whether the updated code already exists.
OriginBlob []byte // OriginBlob is the original binary representation of the contract code.
}
// accountDelete represents an operation for deleting an Ethereum account.
type accountDelete struct {
address common.Address // address is the unique account identifier
origin []byte // origin is the original value of account data in slim-RLP encoding.
// storages stores mutated slots, the value should be nil.
storages map[common.Hash][]byte
// storagesOrigin stores the original values of mutated slots in
// prefix-zero-trimmed RLP format. The map key refers to the **HASH**
// of the raw storage slot key.
storagesOrigin map[common.Hash][]byte
// AccountDelete represents a deletion operation for an Ethereum account.
type AccountDelete struct {
Address common.Address // Address uniquely identifies the account.
Origin *types.StateAccount // Origin is the account state prior to deletion (never be null).
Storages map[common.Hash]common.Hash // Storages contains mutated storage slots.
StoragesOrigin map[common.Hash]common.Hash // StoragesOrigin holds original values of mutated slots; keys are hashes of raw storage slot keys.
}
// accountUpdate represents an operation for updating an Ethereum account.
type accountUpdate struct {
address common.Address // address is the unique account identifier
data []byte // data is the slim-RLP encoded account data.
origin []byte // origin is the original value of account data in slim-RLP encoding.
code *contractCode // code represents mutated contract code; nil means it's not modified.
storages map[common.Hash][]byte // storages stores mutated slots in prefix-zero-trimmed RLP format.
// AccountUpdate represents an update operation for an Ethereum account.
type AccountUpdate struct {
Address common.Address // Address uniquely identifies the account.
Data *types.StateAccount // Data is the updated account state; nil indicates deletion.
Origin *types.StateAccount // Origin is the previous account state; nil indicates non-existence.
Code *ContractCode // Code contains updated contract code; nil if unchanged.
Storages map[common.Hash]common.Hash // Storages contains updated storage slots.
// storagesOriginByKey and storagesOriginByHash both store the original values
// of mutated slots in prefix-zero-trimmed RLP format. The difference is that
// storagesOriginByKey uses the **raw** storage slot key as the map ID, while
// storagesOriginByHash uses the **hash** of the storage slot key instead.
storagesOriginByKey map[common.Hash][]byte
storagesOriginByHash map[common.Hash][]byte
// StoragesOriginByKey and StoragesOriginByHash both record original values
// of mutated storage slots:
// - StoragesOriginByKey uses raw storage slot keys.
// - StoragesOriginByHash uses hashed storage slot keys.
StoragesOriginByKey map[common.Hash]common.Hash
StoragesOriginByHash map[common.Hash]common.Hash
}
// stateUpdate represents the difference between two states resulting from state
// StorageKeyEncoding specifies the encoding scheme of a storage key.
type StorageKeyEncoding int
const (
// StorageKeyHashed represents a hashed key (e.g. Keccak256).
StorageKeyHashed StorageKeyEncoding = iota
// StorageKeyPlain represents a raw (unhashed) key.
StorageKeyPlain
)
// StateUpdate represents the difference between two states resulting from state
// execution. It contains information about mutated contract codes, accounts,
// and storage slots, along with their original values.
type stateUpdate struct {
originRoot common.Hash // hash of the state before applying mutation
root common.Hash // hash of the state after applying mutation
blockNumber uint64 // Associated block number
type StateUpdate struct {
OriginRoot common.Hash // Hash of the state before applying mutation
Root common.Hash // Hash of the state after applying mutation
BlockNumber uint64 // Associated block number
accounts map[common.Hash][]byte // accounts stores mutated accounts in 'slim RLP' encoding
accountsOrigin map[common.Address][]byte // accountsOrigin stores the original values of mutated accounts in 'slim RLP' encoding
// Accounts contains mutated accounts, keyed by address hash.
Accounts map[common.Hash]*types.StateAccount
// storages stores mutated slots in 'prefix-zero-trimmed' RLP format.
// The value is keyed by account hash and **storage slot key hash**.
storages map[common.Hash]map[common.Hash][]byte
// Storages contains mutated storage slots, keyed by address
// hash and storage slot key hash.
Storages map[common.Hash]map[common.Hash]common.Hash
// storagesOrigin stores the original values of mutated slots in
// 'prefix-zero-trimmed' RLP format.
// (a) the value is keyed by account hash and **storage slot key** if rawStorageKey is true;
// (b) the value is keyed by account hash and **storage slot key hash** if rawStorageKey is false;
storagesOrigin map[common.Address]map[common.Hash][]byte
rawStorageKey bool
// AccountsOrigin holds the original values of mutated accounts, keyed by address.
AccountsOrigin map[common.Address]*types.StateAccount
codes map[common.Address]*contractCode // codes contains the set of dirty codes
nodes *trienode.MergedNodeSet // Aggregated dirty nodes caused by state changes
// StoragesOrigin holds the original values of mutated storage slots.
// The key format depends on StorageKeyType:
// - if StorageKeyType is plain: keyed by account address and plain storage slot key.
// - if StorageKeyType is hashed: keyed by account address and storage slot key hash.
StoragesOrigin map[common.Address]map[common.Hash]common.Hash
StorageKeyType StorageKeyEncoding
Codes map[common.Address]*ContractCode // Codes contains the set of dirty codes
Nodes *trienode.MergedNodeSet // Aggregated dirty nodes caused by state changes
}
// empty returns a flag indicating the state transition is empty or not.
func (sc *stateUpdate) empty() bool {
return sc.originRoot == sc.root
// Empty returns a flag indicating the state transition is empty or not.
func (sc *StateUpdate) Empty() bool {
return sc.OriginRoot == sc.Root
}
// newStateUpdate constructs a state update object by identifying the differences
// NewStateUpdate constructs a state update object by identifying the differences
// between two states through state execution. It combines the specified account
// deletions and account updates to create a complete state update.
//
// rawStorageKey is a flag indicating whether to use the raw storage slot key or
// the hash of the slot key for constructing state update object.
func newStateUpdate(rawStorageKey bool, originRoot common.Hash, root common.Hash, blockNumber uint64, deletes map[common.Hash]*accountDelete, updates map[common.Hash]*accountUpdate, nodes *trienode.MergedNodeSet) *stateUpdate {
func NewStateUpdate(typ StorageKeyEncoding, originRoot common.Hash, root common.Hash, blockNumber uint64, deletes map[common.Hash]*AccountDelete, updates map[common.Hash]*AccountUpdate, nodes *trienode.MergedNodeSet) *StateUpdate {
var (
accounts = make(map[common.Hash][]byte)
accountsOrigin = make(map[common.Address][]byte)
storages = make(map[common.Hash]map[common.Hash][]byte)
storagesOrigin = make(map[common.Address]map[common.Hash][]byte)
codes = make(map[common.Address]*contractCode)
accounts = make(map[common.Hash]*types.StateAccount)
accountsOrigin = make(map[common.Address]*types.StateAccount)
storages = make(map[common.Hash]map[common.Hash]common.Hash)
storagesOrigin = make(map[common.Address]map[common.Hash]common.Hash)
codes = make(map[common.Address]*ContractCode)
)
// Since some accounts might be destroyed and recreated within the same
// Since some accounts might be deleted and recreated within the same
// block, deletions must be aggregated first.
for addrHash, op := range deletes {
addr := op.address
addr := op.Address
accounts[addrHash] = nil
accountsOrigin[addr] = op.origin
accountsOrigin[addr] = op.Origin
// If storage wiping exists, the hash of the storage slot key must be used
if len(op.storages) > 0 {
storages[addrHash] = op.storages
if len(op.Storages) > 0 {
storages[addrHash] = op.Storages
}
if len(op.storagesOrigin) > 0 {
storagesOrigin[addr] = op.storagesOrigin
if len(op.StoragesOrigin) > 0 {
storagesOrigin[addr] = op.StoragesOrigin
}
}
// Aggregate account updates then.
for addrHash, op := range updates {
// Aggregate dirty contract codes if they are available.
addr := op.address
if op.code != nil {
codes[addr] = op.code
addr := op.Address
if op.Code != nil {
codes[addr] = op.Code
}
accounts[addrHash] = op.data
accounts[addrHash] = op.Data
// Aggregate the account original value. If the account is already
// present in the aggregated accountsOrigin set, skip it.
// present in the aggregated AccountsOrigin set, skip it.
if _, found := accountsOrigin[addr]; !found {
accountsOrigin[addr] = op.origin
accountsOrigin[addr] = op.Origin
}
// Aggregate the storage mutation list. If a slot in op.storages is
// already present in aggregated storages set, the value will be
// overwritten.
if len(op.storages) > 0 {
if len(op.Storages) > 0 {
if _, exist := storages[addrHash]; !exist {
storages[addrHash] = op.storages
storages[addrHash] = op.Storages
} else {
maps.Copy(storages[addrHash], op.storages)
maps.Copy(storages[addrHash], op.Storages)
}
}
// Aggregate the storage original values. If the slot is already present
// in aggregated storagesOrigin set, skip it.
storageOriginSet := op.storagesOriginByHash
if rawStorageKey {
storageOriginSet = op.storagesOriginByKey
// in aggregated StoragesOrigin set, skip it.
storageOriginSet := op.StoragesOriginByHash
if typ == StorageKeyPlain {
storageOriginSet = op.StoragesOriginByKey
}
if len(storageOriginSet) > 0 {
origin, exist := storagesOrigin[addr]
@ -173,32 +177,114 @@ func newStateUpdate(rawStorageKey bool, originRoot common.Hash, root common.Hash
}
}
}
return &stateUpdate{
originRoot: originRoot,
root: root,
blockNumber: blockNumber,
accounts: accounts,
accountsOrigin: accountsOrigin,
storages: storages,
storagesOrigin: storagesOrigin,
rawStorageKey: rawStorageKey,
codes: codes,
nodes: nodes,
return &StateUpdate{
OriginRoot: originRoot,
Root: root,
BlockNumber: blockNumber,
Accounts: accounts,
AccountsOrigin: accountsOrigin,
Storages: storages,
StoragesOrigin: storagesOrigin,
StorageKeyType: typ,
Codes: codes,
Nodes: nodes,
}
}
// stateSet converts the current stateUpdate object into a triedb.StateSet
// object. This function extracts the necessary data from the stateUpdate
// struct and formats it into the StateSet structure consumed by the triedb
// package.
func (sc *stateUpdate) stateSet() *triedb.StateSet {
return &triedb.StateSet{
Accounts: sc.accounts,
AccountsOrigin: sc.accountsOrigin,
Storages: sc.storages,
StoragesOrigin: sc.storagesOrigin,
RawStorageKey: sc.rawStorageKey,
// encodeSlot encodes the storage slot value by trimming all leading zeros
// and then RLP-encoding the result.
func encodeSlot(value common.Hash) []byte {
if value == (common.Hash{}) {
return nil
}
blob, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(value[:]))
return blob
}
// EncodeMPTState encodes all state mutations alongside their original value
// into the Merkle-Patricia-Trie representation.
//
// It transforms account and storage updates into their corresponding MPT-encoded
// key-value mappings, using the same encoding rules as the Ethereum state trie.
func (sc *StateUpdate) EncodeMPTState() (map[common.Hash][]byte, map[common.Address][]byte, map[common.Hash]map[common.Hash][]byte, map[common.Address]map[common.Hash][]byte) {
var (
accounts = make(map[common.Hash][]byte, len(sc.Accounts))
storages = make(map[common.Hash]map[common.Hash][]byte, len(sc.Storages))
accountOrigin = make(map[common.Address][]byte, len(sc.AccountsOrigin))
storageOrigin = make(map[common.Address]map[common.Hash][]byte, len(sc.StoragesOrigin))
)
for addr, prev := range sc.AccountsOrigin {
if prev == nil {
accountOrigin[addr] = nil
} else {
accountOrigin[addr] = types.SlimAccountRLP(*prev)
}
}
for addrHash, data := range sc.Accounts {
if data == nil {
accounts[addrHash] = nil
} else {
accounts[addrHash] = types.SlimAccountRLP(*data)
}
}
for addr, slots := range sc.StoragesOrigin {
subset := make(map[common.Hash][]byte)
for key, val := range slots {
subset[key] = encodeSlot(val)
}
storageOrigin[addr] = subset
}
for addrHash, slots := range sc.Storages {
subset := make(map[common.Hash][]byte)
for key, val := range slots {
subset[key] = encodeSlot(val)
}
storages[addrHash] = subset
}
return accounts, accountOrigin, storages, storageOrigin
}
// EncodeUBTState encodes all state mutations alongside their original value
// into the Unified-Binary-Trie representation.
//
// It transforms account and storage updates into their corresponding UBT-encoded
// key-value mappings, using the same encoding rules as the Ethereum state trie.
func (sc *StateUpdate) EncodeUBTState() (map[common.Hash][]byte, map[common.Address][]byte, map[common.Hash]map[common.Hash][]byte, map[common.Address]map[common.Hash][]byte) {
var (
accounts = make(map[common.Hash][]byte, len(sc.Accounts))
storages = make(map[common.Hash]map[common.Hash][]byte, len(sc.Storages))
accountOrigin = make(map[common.Address][]byte, len(sc.AccountsOrigin))
storageOrigin = make(map[common.Address]map[common.Hash][]byte, len(sc.StoragesOrigin))
)
for addr, prev := range sc.AccountsOrigin {
if prev == nil {
accountOrigin[addr] = nil
} else {
accountOrigin[addr] = types.SlimAccountRLP(*prev)
}
}
for addrHash, data := range sc.Accounts {
if data == nil {
accounts[addrHash] = nil
} else {
accounts[addrHash] = types.SlimAccountRLP(*data)
}
}
for addr, slots := range sc.StoragesOrigin {
subset := make(map[common.Hash][]byte)
for key, val := range slots {
subset[key] = encodeSlot(val)
}
storageOrigin[addr] = subset
}
for addrHash, slots := range sc.Storages {
subset := make(map[common.Hash][]byte)
for key, val := range slots {
subset[key] = encodeSlot(val)
}
storages[addrHash] = subset
}
return accounts, accountOrigin, storages, storageOrigin
}
// deriveCodeFields derives the missing fields of contract code changes
@ -207,135 +293,96 @@ func (sc *stateUpdate) stateSet() *triedb.StateSet {
// Note: This operation is expensive and not needed during normal state
// transitions. It is only required when SizeTracker or StateUpdate hook
// is enabled to produce accurate state statistics.
func (sc *stateUpdate) deriveCodeFields(reader ContractCodeReader) error {
func (sc *StateUpdate) deriveCodeFields(reader ContractCodeReader) error {
cache := make(map[common.Hash]bool)
for addr, code := range sc.codes {
if code.originHash != types.EmptyCodeHash {
blob := reader.Code(addr, code.originHash)
for addr, code := range sc.Codes {
if code.OriginHash != types.EmptyCodeHash {
blob := reader.Code(addr, code.OriginHash)
if len(blob) == 0 {
return fmt.Errorf("original code of %x is empty", addr)
}
code.originBlob = blob
code.OriginBlob = blob
}
if exists, ok := cache[code.hash]; ok {
code.duplicate = exists
if exists, ok := cache[code.Hash]; ok {
code.Duplicate = exists
continue
}
res := reader.Has(addr, code.hash)
cache[code.hash] = res
code.duplicate = res
res := reader.Has(addr, code.Hash)
cache[code.Hash] = res
code.Duplicate = res
}
return nil
}
// ToTracingUpdate converts the internal stateUpdate to an exported tracing.StateUpdate.
func (sc *stateUpdate) ToTracingUpdate() (*tracing.StateUpdate, error) {
// ToTracingUpdate converts the internal StateUpdate to an exported tracing.StateUpdate.
func (sc *StateUpdate) ToTracingUpdate() (*tracing.StateUpdate, error) {
update := &tracing.StateUpdate{
OriginRoot: sc.originRoot,
Root: sc.root,
BlockNumber: sc.blockNumber,
AccountChanges: make(map[common.Address]*tracing.AccountChange, len(sc.accountsOrigin)),
OriginRoot: sc.OriginRoot,
Root: sc.Root,
BlockNumber: sc.BlockNumber,
AccountChanges: make(map[common.Address]*tracing.AccountChange, len(sc.AccountsOrigin)),
StorageChanges: make(map[common.Address]map[common.Hash]*tracing.StorageChange),
CodeChanges: make(map[common.Address]*tracing.CodeChange, len(sc.codes)),
CodeChanges: make(map[common.Address]*tracing.CodeChange, len(sc.Codes)),
TrieChanges: make(map[common.Hash]map[string]*tracing.TrieNodeChange),
}
// Gather all account changes
for addr, oldData := range sc.accountsOrigin {
for addr, oldData := range sc.AccountsOrigin {
addrHash := crypto.Keccak256Hash(addr.Bytes())
newData, exists := sc.accounts[addrHash]
newData, exists := sc.Accounts[addrHash]
if !exists {
return nil, fmt.Errorf("account %x not found", addr)
}
change := &tracing.AccountChange{}
if len(oldData) > 0 {
acct, err := types.FullAccount(oldData)
if err != nil {
return nil, err
}
change.Prev = &types.StateAccount{
Nonce: acct.Nonce,
Balance: acct.Balance,
Root: acct.Root,
CodeHash: acct.CodeHash,
}
}
if len(newData) > 0 {
acct, err := types.FullAccount(newData)
if err != nil {
return nil, err
}
change.New = &types.StateAccount{
Nonce: acct.Nonce,
Balance: acct.Balance,
Root: acct.Root,
CodeHash: acct.CodeHash,
}
change := &tracing.AccountChange{
Prev: oldData,
New: newData,
}
update.AccountChanges[addr] = change
}
// Gather all storage slot changes
for addr, slots := range sc.storagesOrigin {
for addr, slots := range sc.StoragesOrigin {
addrHash := crypto.Keccak256Hash(addr.Bytes())
subset, exists := sc.storages[addrHash]
subset, exists := sc.Storages[addrHash]
if !exists {
return nil, fmt.Errorf("storage %x not found", addr)
}
storageChanges := make(map[common.Hash]*tracing.StorageChange, len(slots))
for key, encPrev := range slots {
for key, oldData := range slots {
// Get new value - handle both raw and hashed key formats
var (
exists bool
encNew []byte
decPrev []byte
decNew []byte
err error
newData common.Hash
)
if sc.rawStorageKey {
encNew, exists = subset[crypto.Keccak256Hash(key.Bytes())]
if sc.StorageKeyType == StorageKeyPlain {
newData, exists = subset[crypto.Keccak256Hash(key.Bytes())]
} else {
encNew, exists = subset[key]
newData, exists = subset[key]
}
if !exists {
return nil, fmt.Errorf("storage slot %x-%x not found", addr, key)
}
// Decode the prev and new values
if len(encPrev) > 0 {
_, decPrev, _, err = rlp.Split(encPrev)
if err != nil {
return nil, fmt.Errorf("failed to decode prevValue: %v", err)
}
}
if len(encNew) > 0 {
_, decNew, _, err = rlp.Split(encNew)
if err != nil {
return nil, fmt.Errorf("failed to decode newValue: %v", err)
}
}
storageChanges[key] = &tracing.StorageChange{
Prev: common.BytesToHash(decPrev),
New: common.BytesToHash(decNew),
Prev: oldData,
New: newData,
}
}
update.StorageChanges[addr] = storageChanges
}
// Gather all contract code changes
for addr, code := range sc.codes {
for addr, code := range sc.Codes {
change := &tracing.CodeChange{
New: &tracing.ContractCode{
Hash: code.hash,
Code: code.blob,
Exists: code.duplicate,
Hash: code.Hash,
Code: code.Blob,
Exists: code.Duplicate,
},
}
if code.originHash != types.EmptyCodeHash {
if code.OriginHash != types.EmptyCodeHash {
change.Prev = &tracing.ContractCode{
Hash: code.originHash,
Code: code.originBlob,
Hash: code.OriginHash,
Code: code.OriginBlob,
Exists: true,
}
}
@ -343,8 +390,8 @@ func (sc *stateUpdate) ToTracingUpdate() (*tracing.StateUpdate, error) {
}
// Gather all trie node changes
if sc.nodes != nil {
for owner, subset := range sc.nodes.Sets {
if sc.Nodes != nil {
for owner, subset := range sc.Nodes.Sets {
nodeChanges := make(map[string]*tracing.TrieNodeChange, len(subset.Origins))
for path, oldNode := range subset.Origins {
newNode, exists := subset.Nodes[path]

View file

@ -25,8 +25,13 @@ import (
"github.com/ethereum/go-ethereum/common"
)
type transientStorageKey struct {
addr common.Address
key common.Hash
}
// transientStorage is a representation of EIP-1153 "Transient Storage".
type transientStorage map[common.Address]Storage
type transientStorage map[transientStorageKey]common.Hash
// newTransientStorage creates a new instance of a transientStorage.
func newTransientStorage() transientStorage {
@ -35,52 +40,43 @@ func newTransientStorage() transientStorage {
// Set sets the transient-storage `value` for `key` at the given `addr`.
func (t transientStorage) Set(addr common.Address, key, value common.Hash) {
tsKey := transientStorageKey{addr: addr, key: key}
if value == (common.Hash{}) { // this is a 'delete'
if _, ok := t[addr]; ok {
delete(t[addr], key)
if len(t[addr]) == 0 {
delete(t, addr)
}
}
delete(t, tsKey)
} else {
if _, ok := t[addr]; !ok {
t[addr] = make(Storage)
}
t[addr][key] = value
t[tsKey] = value
}
}
// Get gets the transient storage for `key` at the given `addr`.
func (t transientStorage) Get(addr common.Address, key common.Hash) common.Hash {
val, ok := t[addr]
if !ok {
return common.Hash{}
}
return val[key]
tsKey := transientStorageKey{addr: addr, key: key}
return t[tsKey]
}
// Copy does a deep copy of the transientStorage
func (t transientStorage) Copy() transientStorage {
storage := make(transientStorage)
for key, value := range t {
storage[key] = value.Copy()
}
return storage
return maps.Clone(t)
}
// PrettyPrint prints the contents of the access list in a human-readable form
func (t transientStorage) PrettyPrint() string {
out := new(strings.Builder)
sortedAddrs := slices.Collect(maps.Keys(t))
slices.SortFunc(sortedAddrs, common.Address.Cmp)
sortedTSKeys := slices.Collect(maps.Keys(t))
slices.SortFunc(sortedTSKeys, func(a, b transientStorageKey) int {
r := a.addr.Cmp(b.addr)
if r != 0 {
return r
}
return a.key.Cmp(b.key)
})
for _, addr := range sortedAddrs {
fmt.Fprintf(out, "%#x:", addr)
storage := t[addr]
sortedKeys := slices.Collect(maps.Keys(storage))
slices.SortFunc(sortedKeys, common.Hash.Cmp)
for _, key := range sortedKeys {
fmt.Fprintf(out, " %X : %X\n", key, storage[key])
for i := 0; i < len(sortedTSKeys); {
tsKey := sortedTSKeys[i]
fmt.Fprintf(out, "%#x:", tsKey.addr)
for ; i < len(sortedTSKeys) && sortedTSKeys[i].addr == tsKey.addr; i++ {
tsKey2 := sortedTSKeys[i]
fmt.Fprintf(out, " %X : %X\n", tsKey2.key, t[tsKey2])
}
}
return out.String()

View file

@ -40,7 +40,7 @@ var (
//
// Note, the prefetcher's API is not thread safe.
type triePrefetcher struct {
verkle bool // Flag whether the prefetcher is in verkle mode
isUBT bool // Flag whether the prefetcher is in UBT mode
db Database // Database to fetch trie nodes through
root common.Hash // Root hash of the account trie for metrics
fetchers map[string]*subfetcher // Subfetchers for each trie
@ -67,7 +67,7 @@ type triePrefetcher struct {
func newTriePrefetcher(db Database, root common.Hash, namespace string, noreads bool) *triePrefetcher {
prefix := triePrefetchMetricsPrefix + namespace
return &triePrefetcher{
verkle: db.TrieDB().IsVerkle(),
isUBT: db.Type().Is(TypeUBT),
db: db,
root: root,
fetchers: make(map[string]*subfetcher), // Active prefetchers use the fetchers map
@ -206,8 +206,8 @@ func (p *triePrefetcher) used(owner common.Hash, root common.Hash, usedAddr []co
// trieID returns an unique trie identifier consists the trie owner and root hash.
func (p *triePrefetcher) trieID(owner common.Hash, root common.Hash) string {
// The trie in verkle is only identified by state root
if p.verkle {
// The trie in ubt is only identified by state root
if p.isUBT {
return p.root.Hex()
}
// The trie in merkle is either identified by state root (account trie),
@ -340,12 +340,12 @@ func (sf *subfetcher) terminate(async bool) {
// openTrie resolves the target trie from database for prefetching.
func (sf *subfetcher) openTrie() error {
// Open the verkle tree if the sub-fetcher is in verkle mode. Note, there is
// only a single fetcher for verkle.
if sf.db.TrieDB().IsVerkle() {
// Open the ubt tree if the sub-fetcher is in ubt mode. Note, there is
// only a single fetcher for ubt.
if sf.db.Type().Is(TypeUBT) {
tr, err := sf.db.OpenTrie(sf.state)
if err != nil {
log.Warn("Trie prefetcher failed opening verkle trie", "root", sf.root, "err", err)
log.Warn("Trie prefetcher failed opening UBT trie", "root", sf.root, "err", err)
return err
}
sf.trie = tr

View file

@ -68,7 +68,7 @@ func TestUseAfterTerminate(t *testing.T) {
func TestVerklePrefetcher(t *testing.T) {
disk := rawdb.NewMemoryDatabase()
db := triedb.NewDatabase(disk, triedb.VerkleDefaults)
db := triedb.NewDatabase(disk, triedb.UBTDefaults)
sdb := NewDatabase(db, nil)
state, err := New(types.EmptyRootHash, sdb)
@ -86,18 +86,17 @@ func TestVerklePrefetcher(t *testing.T) {
root, _ := state.Commit(0, true, false)
state, _ = New(root, sdb)
sRoot := state.GetStorageRoot(addr)
fetcher := newTriePrefetcher(sdb, root, "", false)
// Read account
fetcher.prefetch(common.Hash{}, root, common.Address{}, []common.Address{addr}, nil, false)
// Read storage slot
fetcher.prefetch(crypto.Keccak256Hash(addr.Bytes()), sRoot, addr, nil, []common.Hash{skey}, false)
fetcher.prefetch(crypto.Keccak256Hash(addr.Bytes()), common.Hash{}, addr, nil, []common.Hash{skey}, false)
fetcher.terminate(false)
accountTrie := fetcher.trie(common.Hash{}, root)
storageTrie := fetcher.trie(crypto.Keccak256Hash(addr.Bytes()), sRoot)
storageTrie := fetcher.trie(crypto.Keccak256Hash(addr.Bytes()), common.Hash{})
rootA := accountTrie.Hash()
rootB := storageTrie.Hash()

View file

@ -93,6 +93,7 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c
}
// Execute the message to preload the implicit touched states
evm := vm.NewEVM(NewEVMBlockContext(header, p.chain, nil), stateCpy, p.config, cfg)
defer evm.Release()
// Convert the transaction into an executable message and pre-cache its sender
msg, err := TransactionToMessage(tx, signer, header.BaseFee)
@ -103,7 +104,7 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c
// Disable the nonce check
msg.SkipNonceChecks = true
stateCpy.SetTxContext(tx.Hash(), i)
stateCpy.SetTxContext(tx.Hash(), i, uint32(i+1))
// We attempt to apply a transaction. The goal is not to execute
// the transaction successfully, rather to warm up touched data slots.

View file

@ -22,6 +22,7 @@ import (
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/misc"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/tracing"
@ -30,6 +31,8 @@ import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/internal/telemetry"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie"
"github.com/holiman/uint256"
)
// StateProcessor is a basic Processor, which takes care of transitioning
@ -62,7 +65,7 @@ func (p *StateProcessor) chainConfig() *params.ChainConfig {
func (p *StateProcessor) Process(ctx context.Context, block *types.Block, statedb *state.StateDB, cfg vm.Config) (*ProcessResult, error) {
var (
config = p.chainConfig()
receipts types.Receipts
receipts = make(types.Receipts, 0, len(block.Transactions()))
header = block.Header()
blockHash = block.Hash()
blockNumber = block.Number()
@ -73,34 +76,25 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated
if hooks := cfg.Tracer; hooks != nil {
tracingStateDB = state.NewHookedState(statedb, hooks)
}
// Mutate the block and state according to any hard-fork specs
if config.DAOForkSupport && config.DAOForkBlock != nil && config.DAOForkBlock.Cmp(block.Number()) == 0 {
misc.ApplyDAOHardFork(tracingStateDB)
}
var (
context vm.BlockContext
context = NewEVMBlockContext(header, p.chain, nil)
signer = types.MakeSigner(config, header.Number, header.Time)
evm = vm.NewEVM(context, tracingStateDB, config, cfg)
)
// Apply pre-execution system calls.
context = NewEVMBlockContext(header, p.chain, nil)
evm := vm.NewEVM(context, tracingStateDB, config, cfg)
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
ProcessBeaconBlockRoot(*beaconRoot, evm)
}
if config.IsPrague(block.Number(), block.Time()) || config.IsVerkle(block.Number(), block.Time()) {
ProcessParentBlockHash(block.ParentHash(), evm)
}
defer evm.Release()
// Run the pre-execution system calls
PreExecution(ctx, block.BeaconRoot(), block.ParentHash(), config, evm, block.Number(), block.Time())
// Iterate over and process the individual transactions
for i, tx := range block.Transactions() {
msg, err := TransactionToMessage(tx, signer, header.BaseFee)
if err != nil {
return nil, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
}
statedb.SetTxContext(tx.Hash(), i)
statedb.SetTxContext(tx.Hash(), i, uint32(i+1))
_, _, spanEnd := telemetry.StartSpan(ctx, "core.ApplyTransactionWithEVM",
telemetry.StringAttribute("tx.hash", tx.Hash().Hex()),
telemetry.Int64Attribute("tx.index", int64(i)),
@ -115,11 +109,10 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated
allLogs = append(allLogs, receipt.Logs...)
spanEnd(nil)
}
requests, err := postExecution(ctx, config, block, allLogs, evm)
requests, err := PostExecution(ctx, config, block.Number(), block.Time(), allLogs, evm, uint32(len(block.Transactions())+1))
if err != nil {
return nil, err
}
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
p.chain.Engine().Finalize(p.chain, header, tracingStateDB, block.Body())
@ -131,28 +124,44 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated
}, nil
}
// postExecution processes the post-execution system calls if Prague is enabled.
func postExecution(ctx context.Context, config *params.ChainConfig, block *types.Block, allLogs []*types.Log, evm *vm.EVM) (requests [][]byte, err error) {
// PreExecution processes pre-execution system calls.
func PreExecution(ctx context.Context, beaconRoot *common.Hash, parent common.Hash, config *params.ChainConfig, evm *vm.EVM, number *big.Int, time uint64) {
_, _, spanEnd := telemetry.StartSpan(ctx, "core.preExecution")
defer spanEnd(nil)
// EIP-4788
if beaconRoot != nil {
ProcessBeaconBlockRoot(*beaconRoot, evm)
}
// EIP-2935
if config.IsPrague(number, time) || config.IsUBT(number, time) {
ProcessParentBlockHash(parent, evm)
}
}
// PostExecution processes post-execution system calls when Prague is enabled.
// If Prague is not activated, it returns null requests to differentiate from
// empty requests.
func PostExecution(ctx context.Context, config *params.ChainConfig, number *big.Int, time uint64, allLogs []*types.Log, evm *vm.EVM, blockAccessIndex uint32) (requests [][]byte, err error) {
_, _, spanEnd := telemetry.StartSpan(ctx, "core.postExecution")
defer spanEnd(&err)
// Read requests if Prague is enabled.
if config.IsPrague(block.Number(), block.Time()) {
if config.IsPrague(number, time) {
requests = [][]byte{}
// EIP-6110
if err := ParseDepositLogs(&requests, allLogs, config); err != nil {
return requests, fmt.Errorf("failed to parse deposit logs: %w", err)
return nil, fmt.Errorf("failed to parse deposit logs: %w", err)
}
// EIP-7002
if err := ProcessWithdrawalQueue(&requests, evm); err != nil {
return requests, fmt.Errorf("failed to process withdrawal queue: %w", err)
if err := ProcessWithdrawalQueue(&requests, evm, blockAccessIndex); err != nil {
return nil, fmt.Errorf("failed to process withdrawal queue: %w", err)
}
// EIP-7251
if err := ProcessConsolidationQueue(&requests, evm); err != nil {
return requests, fmt.Errorf("failed to process consolidation queue: %w", err)
if err := ProcessConsolidationQueue(&requests, evm, blockAccessIndex); err != nil {
return nil, fmt.Errorf("failed to process consolidation queue: %w", err)
}
}
return requests, nil
}
@ -182,7 +191,7 @@ func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb *state.StateDB,
}
// Merge the tx-local access event into the "block-local" one, in order to collect
// all values, so that the witness can be built.
if statedb.Database().TrieDB().IsVerkle() {
if statedb.Database().Type().Is(state.TypeUBT) {
statedb.AccessEvents().Merge(evm.AccessEvents)
}
return MakeReceipt(evm, result, statedb, blockNumber, blockHash, blockTime, tx, gp.CumulativeUsed(), root), nil
@ -251,15 +260,16 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, evm *vm.EVM) {
msg := &Message{
From: params.SystemAddress,
GasLimit: 30_000_000,
GasPrice: common.Big0,
GasFeeCap: common.Big0,
GasTipCap: common.Big0,
GasPrice: uint256.NewInt(0),
GasFeeCap: uint256.NewInt(0),
GasTipCap: uint256.NewInt(0),
To: &params.BeaconRootsAddress,
Data: beaconRoot[:],
}
evm.SetTxContext(NewEVMTxContext(msg))
evm.StateDB.SetTxContext(common.Hash{}, 0, 0)
evm.StateDB.AddAddressToAccessList(params.BeaconRootsAddress)
_, _, _ = evm.Call(msg.From, *msg.To, msg.Data, 30_000_000, common.U2560)
_, _, _ = evm.Call(msg.From, *msg.To, msg.Data, vm.NewGasBudget(30_000_000), common.U2560)
if evm.StateDB.AccessEvents() != nil {
evm.StateDB.AccessEvents().Merge(evm.AccessEvents)
}
@ -278,15 +288,16 @@ func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM) {
msg := &Message{
From: params.SystemAddress,
GasLimit: 30_000_000,
GasPrice: common.Big0,
GasFeeCap: common.Big0,
GasTipCap: common.Big0,
GasPrice: uint256.NewInt(0),
GasFeeCap: uint256.NewInt(0),
GasTipCap: uint256.NewInt(0),
To: &params.HistoryStorageAddress,
Data: prevHash.Bytes(),
}
evm.SetTxContext(NewEVMTxContext(msg))
evm.StateDB.SetTxContext(common.Hash{}, 0, 0)
evm.StateDB.AddAddressToAccessList(params.HistoryStorageAddress)
_, _, err := evm.Call(msg.From, *msg.To, msg.Data, 30_000_000, common.U2560)
_, _, err := evm.Call(msg.From, *msg.To, msg.Data, vm.NewGasBudget(30_000_000), common.U2560)
if err != nil {
panic(err)
}
@ -298,17 +309,17 @@ func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM) {
// ProcessWithdrawalQueue calls the EIP-7002 withdrawal queue contract.
// It returns the opaque request data returned by the contract.
func ProcessWithdrawalQueue(requests *[][]byte, evm *vm.EVM) error {
return processRequestsSystemCall(requests, evm, 0x01, params.WithdrawalQueueAddress)
func ProcessWithdrawalQueue(requests *[][]byte, evm *vm.EVM, blockAccessIndex uint32) error {
return processRequestsSystemCall(requests, evm, 0x01, params.WithdrawalQueueAddress, blockAccessIndex)
}
// ProcessConsolidationQueue calls the EIP-7251 consolidation queue contract.
// It returns the opaque request data returned by the contract.
func ProcessConsolidationQueue(requests *[][]byte, evm *vm.EVM) error {
return processRequestsSystemCall(requests, evm, 0x02, params.ConsolidationQueueAddress)
func ProcessConsolidationQueue(requests *[][]byte, evm *vm.EVM, blockAccessIndex uint32) error {
return processRequestsSystemCall(requests, evm, 0x02, params.ConsolidationQueueAddress, blockAccessIndex)
}
func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte, addr common.Address) error {
func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte, addr common.Address, blockAccessIndex uint32) error {
if tracer := evm.Config.Tracer; tracer != nil {
onSystemCallStart(tracer, evm.GetVMContext())
if tracer.OnSystemCallEnd != nil {
@ -318,14 +329,15 @@ func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte
msg := &Message{
From: params.SystemAddress,
GasLimit: 30_000_000,
GasPrice: common.Big0,
GasFeeCap: common.Big0,
GasTipCap: common.Big0,
GasPrice: uint256.NewInt(0),
GasFeeCap: uint256.NewInt(0),
GasTipCap: uint256.NewInt(0),
To: &addr,
}
evm.SetTxContext(NewEVMTxContext(msg))
evm.StateDB.SetTxContext(common.Hash{}, 0, blockAccessIndex)
evm.StateDB.AddAddressToAccessList(addr)
ret, _, err := evm.Call(msg.From, *msg.To, msg.Data, 30_000_000, common.U2560)
ret, _, err := evm.Call(msg.From, *msg.To, msg.Data, vm.NewGasBudget(30_000_000), common.U2560)
if evm.StateDB.AccessEvents() != nil {
evm.StateDB.AccessEvents().Merge(evm.AccessEvents)
}
@ -372,3 +384,11 @@ func onSystemCallStart(tracer *tracing.Hooks, ctx *tracing.VMContext) {
tracer.OnSystemCallStart()
}
}
// AssembleBlock finalizes the state and assembles the block with provided
// body and receipts.
func AssembleBlock(engine consensus.Engine, chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) *types.Block {
engine.Finalize(chain, header, state, body)
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
return types.NewBlock(header, body, receipts, trie.NewStackTrie(nil))
}

View file

@ -68,7 +68,7 @@ func (result *ExecutionResult) Revert() []byte {
}
// IntrinsicGas computes the 'intrinsic gas' for a message with the given data.
func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.SetCodeAuthorization, isContractCreation, isHomestead, isEIP2028, isEIP3860 bool) (uint64, error) {
func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.SetCodeAuthorization, isContractCreation, isHomestead, isEIP2028, isEIP3860, isAmsterdam bool) (vm.GasCosts, error) {
// Set the starting gas for the raw transaction
var gas uint64
if isContractCreation && isHomestead {
@ -89,46 +89,110 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set
nonZeroGas = params.TxDataNonZeroGasEIP2028
}
if (math.MaxUint64-gas)/nonZeroGas < nz {
return 0, ErrGasUintOverflow
return vm.GasCosts{}, ErrGasUintOverflow
}
gas += nz * nonZeroGas
if (math.MaxUint64-gas)/params.TxDataZeroGas < z {
return 0, ErrGasUintOverflow
return vm.GasCosts{}, ErrGasUintOverflow
}
gas += z * params.TxDataZeroGas
if isContractCreation && isEIP3860 {
lenWords := toWordSize(dataLen)
if (math.MaxUint64-gas)/params.InitCodeWordGas < lenWords {
return 0, ErrGasUintOverflow
return vm.GasCosts{}, ErrGasUintOverflow
}
gas += lenWords * params.InitCodeWordGas
}
}
if accessList != nil {
gas += uint64(len(accessList)) * params.TxAccessListAddressGas
gas += uint64(accessList.StorageKeys()) * params.TxAccessListStorageKeyGas
addresses := uint64(len(accessList))
storageKeys := uint64(accessList.StorageKeys())
if (math.MaxUint64-gas)/params.TxAccessListAddressGas < addresses {
return vm.GasCosts{}, ErrGasUintOverflow
}
gas += addresses * params.TxAccessListAddressGas
if (math.MaxUint64-gas)/params.TxAccessListStorageKeyGas < storageKeys {
return vm.GasCosts{}, ErrGasUintOverflow
}
gas += storageKeys * params.TxAccessListStorageKeyGas
// EIP-7981: access list data is charged in addition to the base charge.
if isAmsterdam {
const (
addressCost = common.AddressLength * params.TxCostFloorPerToken7976 * params.TxTokenPerNonZeroByte
storageKeyCost = common.HashLength * params.TxCostFloorPerToken7976 * params.TxTokenPerNonZeroByte
)
if (math.MaxUint64-gas)/addressCost < addresses {
return vm.GasCosts{}, ErrGasUintOverflow
}
gas += addresses * addressCost
if (math.MaxUint64-gas)/storageKeyCost < storageKeys {
return vm.GasCosts{}, ErrGasUintOverflow
}
gas += storageKeys * storageKeyCost
}
}
if authList != nil {
gas += uint64(len(authList)) * params.CallNewAccountGas
}
return gas, nil
return vm.GasCosts{RegularGas: gas}, nil
}
// FloorDataGas computes the minimum gas required for a transaction based on its data tokens (EIP-7623).
func FloorDataGas(data []byte) (uint64, error) {
func FloorDataGas(rules params.Rules, data []byte, accessList types.AccessList) (uint64, error) {
var (
z = uint64(bytes.Count(data, []byte{0}))
nz = uint64(len(data)) - z
tokens = nz*params.TxTokenPerNonZeroByte + z
tokens uint64
tokenCost uint64
)
if rules.IsAmsterdam {
// EIP-7976 changes how calldata is priced.
// From 10/40 to 64/64 for zero/non-zero bytes.
tokenCost = params.TxCostFloorPerToken7976
dataLen := uint64(len(data))
if math.MaxUint64/params.TxTokenPerNonZeroByte < dataLen {
return 0, ErrGasUintOverflow
}
tokens = dataLen * params.TxTokenPerNonZeroByte
// EIP-7981 adds additional tokens for every entry in the accesslist
const addressTokenCost = uint64(common.AddressLength) * params.TxTokenPerNonZeroByte
addresses := uint64(len(accessList))
if (math.MaxUint64-tokens)/addressTokenCost < addresses {
return 0, ErrGasUintOverflow
}
tokens += addresses * addressTokenCost
const storageKeyTokenCost = uint64(common.HashLength) * params.TxTokenPerNonZeroByte
storageKeys := uint64(accessList.StorageKeys())
if (math.MaxUint64-tokens)/storageKeyTokenCost < storageKeys {
return 0, ErrGasUintOverflow
}
tokens += storageKeys * storageKeyTokenCost
} else {
var (
z = uint64(bytes.Count(data, []byte{0}))
nz = uint64(len(data)) - z
)
// Pre-Amsterdam
if math.MaxUint64/params.TxTokenPerNonZeroByte < nz {
return 0, ErrGasUintOverflow
}
tokens = nz * params.TxTokenPerNonZeroByte
if math.MaxUint64-tokens < z {
return 0, ErrGasUintOverflow
}
tokens += z
tokenCost = params.TxCostFloorPerToken
}
// Check for overflow
if (math.MaxUint64-params.TxGas)/params.TxCostFloorPerToken < tokens {
if (math.MaxUint64-params.TxGas)/tokenCost < tokens {
return 0, ErrGasUintOverflow
}
// Minimum gas required for a transaction based on its data tokens (EIP-7623).
return params.TxGas + tokens*params.TxCostFloorPerToken, nil
return params.TxGas + tokens*tokenCost, nil
}
// toWordSize returns the ceiled word size required for init code payment calculation.
@ -146,14 +210,14 @@ type Message struct {
To *common.Address
From common.Address
Nonce uint64
Value *big.Int
Value *uint256.Int
GasLimit uint64
GasPrice *big.Int
GasFeeCap *big.Int
GasTipCap *big.Int
GasPrice *uint256.Int
GasFeeCap *uint256.Int
GasTipCap *uint256.Int
Data []byte
AccessList types.AccessList
BlobGasFeeCap *big.Int
BlobGasFeeCap *uint256.Int
BlobHashes []common.Hash
SetCodeAuthorizations []types.SetCodeAuthorization
@ -174,32 +238,64 @@ type Message struct {
// TransactionToMessage converts a transaction into a Message.
func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.Int) (*Message, error) {
from, err := types.Sender(s, tx)
if err != nil {
return nil, err
}
gasPrice, overflow := uint256.FromBig(tx.GasPrice())
if overflow {
return nil, fmt.Errorf("%w: address %v, maxFeePerGas bit length: %d", ErrFeeCapVeryHigh,
from.Hex(), tx.GasPrice().BitLen())
}
txGasFeeCap := tx.GasFeeCap()
gasFeeCap, overflow := uint256.FromBig(txGasFeeCap)
if overflow {
return nil, fmt.Errorf("%w: address %v, maxFeePerGas bit length: %d", ErrFeeCapVeryHigh,
from.Hex(), tx.GasFeeCap().BitLen())
}
txGasTipCap := tx.GasTipCap()
gasTipCap, overflow := uint256.FromBig(txGasTipCap)
if overflow {
return nil, fmt.Errorf("%w: address %v, maxPriorityFeePerGas bit length: %d", ErrTipVeryHigh,
from.Hex(), tx.GasTipCap().BitLen())
}
value, overflow := uint256.FromBig(tx.Value())
if overflow {
return nil, fmt.Errorf("value exceeds 256 bits: address %v", from.Hex())
}
blobGasFeeCap, overflow := uint256.FromBig(tx.BlobGasFeeCap())
if overflow {
return nil, fmt.Errorf("blobGasFeeCap exceeds 256 bits: address %v", from.Hex())
}
msg := &Message{
From: from,
Nonce: tx.Nonce(),
GasLimit: tx.Gas(),
GasPrice: tx.GasPrice(),
GasFeeCap: tx.GasFeeCap(),
GasTipCap: tx.GasTipCap(),
GasPrice: gasPrice,
GasFeeCap: gasFeeCap,
GasTipCap: gasTipCap,
To: tx.To(),
Value: tx.Value(),
Value: value,
Data: tx.Data(),
AccessList: tx.AccessList(),
SetCodeAuthorizations: tx.SetCodeAuthorizations(),
SkipNonceChecks: false,
SkipTransactionChecks: false,
BlobHashes: tx.BlobHashes(),
BlobGasFeeCap: tx.BlobGasFeeCap(),
BlobGasFeeCap: blobGasFeeCap,
}
// If baseFee provided, set gasPrice to effectiveGasPrice.
if baseFee != nil {
msg.GasPrice = msg.GasPrice.Add(msg.GasTipCap, baseFee)
if msg.GasPrice.Cmp(msg.GasFeeCap) > 0 {
msg.GasPrice = msg.GasFeeCap
effectiveGasPrice := new(big.Int).Add(baseFee, txGasTipCap)
if effectiveGasPrice.Cmp(txGasFeeCap) > 0 {
effectiveGasPrice = txGasFeeCap
}
// EffectiveGasPrice is already capped by txGasFeeCap, therefore
// the overflow check is not required.
msg.GasPrice = uint256.MustFromBig(effectiveGasPrice)
}
var err error
msg.From, err = types.Sender(s, tx)
return msg, err
return msg, nil
}
// ApplyMessage computes the new state by applying the given message
@ -242,12 +338,12 @@ func ApplyMessage(evm *vm.EVM, msg *Message, gp *GasPool) (*ExecutionResult, err
// 5. Run Script section
// 6. Derive new state root
type stateTransition struct {
gp *GasPool
msg *Message
gasRemaining uint64
initialGas uint64
state vm.StateDB
evm *vm.EVM
gp *GasPool
msg *Message
initialBudget vm.GasBudget
gasRemaining vm.GasBudget
state vm.StateDB
evm *vm.EVM
}
// newStateTransition initialises and returns a new state transition object.
@ -269,46 +365,70 @@ func (st *stateTransition) to() common.Address {
}
func (st *stateTransition) buyGas() error {
mgval := new(big.Int).SetUint64(st.msg.GasLimit)
mgval.Mul(mgval, st.msg.GasPrice)
balanceCheck := new(big.Int).Set(mgval)
mgval := new(uint256.Int).SetUint64(st.msg.GasLimit)
_, overflow := mgval.MulOverflow(mgval, st.msg.GasPrice)
if overflow {
return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex())
}
balanceCheck := new(uint256.Int).Set(mgval)
if st.msg.GasFeeCap != nil {
balanceCheck.SetUint64(st.msg.GasLimit)
balanceCheck = balanceCheck.Mul(balanceCheck, st.msg.GasFeeCap)
if _, overflow := balanceCheck.MulOverflow(balanceCheck, st.msg.GasFeeCap); overflow {
return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex())
}
}
if st.msg.Value != nil {
if _, overflow := balanceCheck.AddOverflow(balanceCheck, st.msg.Value); overflow {
return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex())
}
}
balanceCheck.Add(balanceCheck, st.msg.Value)
if st.evm.ChainConfig().IsCancun(st.evm.Context.BlockNumber, st.evm.Context.Time) {
if blobGas := st.blobGasUsed(); blobGas > 0 {
// Check that the user has enough funds to cover blobGasUsed * tx.BlobGasFeeCap
blobBalanceCheck := new(big.Int).SetUint64(blobGas)
blobBalanceCheck.Mul(blobBalanceCheck, st.msg.BlobGasFeeCap)
balanceCheck.Add(balanceCheck, blobBalanceCheck)
blobBalanceCheck := new(uint256.Int).SetUint64(blobGas)
if _, overflow := blobBalanceCheck.MulOverflow(blobBalanceCheck, st.msg.BlobGasFeeCap); overflow {
return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex())
}
if _, overflow := balanceCheck.AddOverflow(balanceCheck, blobBalanceCheck); overflow {
return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex())
}
// Pay for blobGasUsed * actual blob fee
blobFee := new(big.Int).SetUint64(blobGas)
blobFee.Mul(blobFee, st.evm.Context.BlobBaseFee)
mgval.Add(mgval, blobFee)
blobBaseFee, overflow := uint256.FromBig(st.evm.Context.BlobBaseFee)
if overflow {
return fmt.Errorf("invalid blobBaseFee: %v", st.evm.Context.BlobBaseFee)
}
blobFee := new(uint256.Int).SetUint64(blobGas)
// In practice, overflow checking is unnecessary, as blobBaseFee cannot exceed
// BlobGasFeeCap. However, in eth_call it is still possible for users to specify
// an excessively large blob base fee and bypass the blob base fee validation.
_, overflow = blobFee.MulOverflow(blobFee, blobBaseFee)
if overflow {
return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex())
}
_, overflow = mgval.AddOverflow(mgval, blobFee)
if overflow {
return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex())
}
}
}
balanceCheckU256, overflow := uint256.FromBig(balanceCheck)
if overflow {
return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex())
}
if have, want := st.state.GetBalance(st.msg.From), balanceCheckU256; have.Cmp(want) < 0 {
if have, want := st.state.GetBalance(st.msg.From), balanceCheck; have.Cmp(want) < 0 {
return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want)
}
if err := st.gp.SubGas(st.msg.GasLimit); err != nil {
return err
}
if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil {
st.evm.Config.Tracer.OnGasChange(0, st.msg.GasLimit, tracing.GasChangeTxInitialBalance)
if st.evm.Config.Tracer.HasGasHook() {
empty := vm.GasBudget{}
initial := vm.NewGasBudget(st.msg.GasLimit)
st.evm.Config.Tracer.EmitGasChange(empty.AsTracing(), initial.AsTracing(), tracing.GasChangeTxInitialBalance)
}
st.gasRemaining = st.msg.GasLimit
st.initialGas = st.msg.GasLimit
st.gasRemaining = vm.NewGasBudget(st.msg.GasLimit)
st.initialBudget = st.gasRemaining.Copy()
mgvalU256, _ := uint256.FromBig(mgval)
st.state.SubBalance(st.msg.From, mgvalU256, tracing.BalanceDecreaseGasBuy)
st.state.SubBalance(st.msg.From, mgval, tracing.BalanceDecreaseGasBuy)
return nil
}
@ -330,9 +450,10 @@ func (st *stateTransition) preCheck() error {
}
}
isOsaka := st.evm.ChainConfig().IsOsaka(st.evm.Context.BlockNumber, st.evm.Context.Time)
isAmsterdam := st.evm.ChainConfig().IsAmsterdam(st.evm.Context.BlockNumber, st.evm.Context.Time)
if !msg.SkipTransactionChecks {
// Verify tx gas limit does not exceed EIP-7825 cap.
if isOsaka && msg.GasLimit > params.MaxTxGas {
if isOsaka && !isAmsterdam && msg.GasLimit > params.MaxTxGas {
return fmt.Errorf("%w (cap: %d, tx: %d)", ErrGasLimitTooHigh, params.MaxTxGas, msg.GasLimit)
}
// Make sure the sender is an EOA
@ -347,21 +468,13 @@ func (st *stateTransition) preCheck() error {
// Skip the checks if gas fields are zero and baseFee was explicitly disabled (eth_call)
skipCheck := st.evm.Config.NoBaseFee && msg.GasFeeCap.BitLen() == 0 && msg.GasTipCap.BitLen() == 0
if !skipCheck {
if l := msg.GasFeeCap.BitLen(); l > 256 {
return fmt.Errorf("%w: address %v, maxFeePerGas bit length: %d", ErrFeeCapVeryHigh,
msg.From.Hex(), l)
}
if l := msg.GasTipCap.BitLen(); l > 256 {
return fmt.Errorf("%w: address %v, maxPriorityFeePerGas bit length: %d", ErrTipVeryHigh,
msg.From.Hex(), l)
}
if msg.GasFeeCap.Cmp(msg.GasTipCap) < 0 {
return fmt.Errorf("%w: address %v, maxPriorityFeePerGas: %s, maxFeePerGas: %s", ErrTipAboveFeeCap,
msg.From.Hex(), msg.GasTipCap, msg.GasFeeCap)
}
// This will panic if baseFee is nil, but basefee presence is verified
// as part of header validation.
if msg.GasFeeCap.Cmp(st.evm.Context.BaseFee) < 0 {
if msg.GasFeeCap.CmpBig(st.evm.Context.BaseFee) < 0 {
return fmt.Errorf("%w: address %v, maxFeePerGas: %s, baseFee: %s", ErrFeeCapTooLow,
msg.From.Hex(), msg.GasFeeCap, st.evm.Context.BaseFee)
}
@ -395,7 +508,7 @@ func (st *stateTransition) preCheck() error {
if !skipCheck {
// This will panic if blobBaseFee is nil, but blobBaseFee presence
// is verified as part of header validation.
if msg.BlobGasFeeCap.Cmp(st.evm.Context.BlobBaseFee) < 0 {
if msg.BlobGasFeeCap.CmpBig(st.evm.Context.BlobBaseFee) < 0 {
return fmt.Errorf("%w: address %v blobGasFeeCap: %v, blobBaseFee: %v", ErrBlobFeeCapTooLow,
msg.From.Hex(), msg.BlobGasFeeCap, st.evm.Context.BlobBaseFee)
}
@ -446,18 +559,21 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
contractCreation = msg.To == nil
floorDataGas uint64
)
// Check clauses 4-5, subtract intrinsic gas if everything is correct
gas, err := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai)
cost, err := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai, rules.IsAmsterdam)
if err != nil {
return nil, err
}
if st.gasRemaining < gas {
return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining, gas)
prior, sufficient := st.gasRemaining.Charge(cost)
if !sufficient {
return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining.RegularGas, cost.RegularGas)
}
if st.evm.Config.Tracer.HasGasHook() {
st.evm.Config.Tracer.EmitGasChange(prior.AsTracing(), st.gasRemaining.AsTracing(), tracing.GasChangeTxIntrinsicGas)
}
// Gas limit suffices for the floor data cost (EIP-7623)
if rules.IsPrague {
floorDataGas, err = FloorDataGas(msg.Data)
floorDataGas, err = FloorDataGas(rules, msg.Data, msg.AccessList)
if err != nil {
return nil, err
}
@ -465,10 +581,6 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
return nil, fmt.Errorf("%w: have %d, want %d", ErrFloorDataGas, msg.GasLimit, floorDataGas)
}
}
if t := st.evm.Config.Tracer; t != nil && t.OnGasChange != nil {
t.OnGasChange(st.gasRemaining, st.gasRemaining-gas, tracing.GasChangeTxIntrinsicGas)
}
st.gasRemaining -= gas
if rules.IsEIP4762 {
st.evm.AccessEvents.AddTxOrigin(msg.From)
@ -479,9 +591,9 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
}
// Check clause 6
value, overflow := uint256.FromBig(msg.Value)
if overflow {
return nil, fmt.Errorf("%w: address %v", ErrInsufficientFundsForTransfer, msg.From.Hex())
value := msg.Value
if value == nil {
value = new(uint256.Int)
}
if !value.IsZero() && !st.evm.Context.CanTransfer(st.state, msg.From, value) {
return nil, fmt.Errorf("%w: address %v", ErrInsufficientFundsForTransfer, msg.From.Hex())
@ -535,14 +647,14 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
peakGasUsed := st.gasUsed()
// Compute refund counter, capped to a refund quotient.
st.gasRemaining += st.calcRefund()
st.gasRemaining.Refund(st.calcRefund())
if rules.IsPrague {
// After EIP-7623: Data-heavy transactions pay the floor gas.
if st.gasUsed() < floorDataGas {
prev := st.gasRemaining
st.gasRemaining = st.initialGas - floorDataGas
if t := st.evm.Config.Tracer; t != nil && t.OnGasChange != nil {
t.OnGasChange(prev, st.gasRemaining, tracing.GasChangeTxDataFloor)
if used := st.gasUsed(); used < floorDataGas {
prior, _ := st.gasRemaining.Charge(vm.GasCosts{RegularGas: floorDataGas - used})
if st.evm.Config.Tracer.HasGasHook() {
st.evm.Config.Tracer.EmitGasChange(prior.AsTracing(), st.gasRemaining.AsTracing(), tracing.GasChangeTxDataFloor)
}
}
if peakGasUsed < floorDataGas {
@ -555,19 +667,22 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
// Return gas to the gas pool
if rules.IsAmsterdam {
// Refund is excluded for returning
err = st.gp.ReturnGas(st.initialGas-peakGasUsed, st.gasUsed())
err = st.gp.ReturnGas(st.initialBudget.RegularGas-peakGasUsed, st.gasUsed())
} else {
// Refund is included for returning
err = st.gp.ReturnGas(st.gasRemaining, st.gasUsed())
err = st.gp.ReturnGas(st.gasRemaining.RegularGas, st.gasUsed())
}
if err != nil {
return nil, err
}
effectiveTip := msg.GasPrice
if rules.IsLondon {
effectiveTip = new(big.Int).Sub(msg.GasPrice, st.evm.Context.BaseFee)
baseFee, overflow := uint256.FromBig(st.evm.Context.BaseFee)
if overflow {
return nil, fmt.Errorf("invalid baseFee: %v", st.evm.Context.BaseFee)
}
effectiveTip = new(uint256.Int).Sub(msg.GasPrice, baseFee)
}
effectiveTipU256, _ := uint256.FromBig(effectiveTip)
if st.evm.Config.NoBaseFee && msg.GasFeeCap.Sign() == 0 && msg.GasTipCap.Sign() == 0 {
// Skip fee payment when NoBaseFee is set and the fee fields
@ -575,7 +690,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
// the coinbase when simulating calls.
} else {
fee := new(uint256.Int).SetUint64(st.gasUsed())
fee.Mul(fee, effectiveTipU256)
fee.Mul(fee, effectiveTip)
st.state.AddBalance(st.evm.Context.Coinbase, fee, tracing.BalanceIncreaseRewardTransactionFee)
// add the coinbase to the witness iff the fee is greater than 0
@ -655,7 +770,7 @@ func (st *stateTransition) applyAuthorization(auth *types.SetCodeAuthorization)
}
// calcRefund computes refund counter, capped to a refund quotient.
func (st *stateTransition) calcRefund() uint64 {
func (st *stateTransition) calcRefund() vm.GasBudget {
var refund uint64
if !st.evm.ChainConfig().IsLondon(st.evm.Context.BlockNumber) {
// Before EIP-3529: refunds were capped to gasUsed / 2
@ -667,27 +782,32 @@ func (st *stateTransition) calcRefund() uint64 {
if refund > st.state.GetRefund() {
refund = st.state.GetRefund()
}
if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil && refund > 0 {
st.evm.Config.Tracer.OnGasChange(st.gasRemaining, st.gasRemaining+refund, tracing.GasChangeTxRefunds)
if refund > 0 && st.evm.Config.Tracer.HasGasHook() {
after := st.gasRemaining
after.RegularGas += refund
st.evm.Config.Tracer.EmitGasChange(st.gasRemaining.AsTracing(), after.AsTracing(), tracing.GasChangeTxRefunds)
}
return refund
return vm.NewGasBudget(refund)
}
// returnGas returns ETH for remaining gas,
// exchanged at the original rate.
func (st *stateTransition) returnGas() {
remaining := uint256.NewInt(st.gasRemaining)
remaining.Mul(remaining, uint256.MustFromBig(st.msg.GasPrice))
remaining := uint256.NewInt(st.gasRemaining.RegularGas)
remaining.Mul(remaining, st.msg.GasPrice)
st.state.AddBalance(st.msg.From, remaining, tracing.BalanceIncreaseGasReturn)
if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil && st.gasRemaining > 0 {
st.evm.Config.Tracer.OnGasChange(st.gasRemaining, 0, tracing.GasChangeTxLeftOverReturned)
if st.gasRemaining.RegularGas > 0 && st.evm.Config.Tracer.HasGasHook() {
after := st.gasRemaining
after.RegularGas = 0
st.evm.Config.Tracer.EmitGasChange(st.gasRemaining.AsTracing(), after.AsTracing(), tracing.GasChangeTxLeftOverReturned)
}
}
// gasUsed returns the amount of gas used up by the state transition.
func (st *stateTransition) gasUsed() uint64 {
return st.initialGas - st.gasRemaining
return st.gasRemaining.Used(st.initialBudget)
}
// blobGasUsed returns the amount of blob gas used by the message.

View 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