diff --git a/accounts/abi/abigen/source2.go.tpl b/accounts/abi/abigen/source2.go.tpl
index c517caf6f4..9285497511 100644
--- a/accounts/abi/abigen/source2.go.tpl
+++ b/accounts/abi/abigen/source2.go.tpl
@@ -59,6 +59,11 @@ var (
abi abi.ABI
}
+ // GetABI returns the ABI associated with this contract binding.
+ func (c *{{.Type}}) GetABI() abi.ABI {
+ return c.abi
+ }
+
// New{{.Type}} creates a new instance of {{.Type}}.
func New{{.Type}}() *{{.Type}} {
parsed, err := {{.Type}}MetaData.ParseABI()
diff --git a/accounts/abi/abigen/testdata/v2/callbackparam.go.txt b/accounts/abi/abigen/testdata/v2/callbackparam.go.txt
index d22b978486..a01b125acf 100644
--- a/accounts/abi/abigen/testdata/v2/callbackparam.go.txt
+++ b/accounts/abi/abigen/testdata/v2/callbackparam.go.txt
@@ -36,6 +36,11 @@ type CallbackParam struct {
abi abi.ABI
}
+// GetABI returns the ABI associated with this contract binding.
+func (c *CallbackParam) GetABI() abi.ABI {
+ return c.abi
+}
+
// NewCallbackParam creates a new instance of CallbackParam.
func NewCallbackParam() *CallbackParam {
parsed, err := CallbackParamMetaData.ParseABI()
diff --git a/accounts/abi/abigen/testdata/v2/crowdsale.go.txt b/accounts/abi/abigen/testdata/v2/crowdsale.go.txt
index f0bba246ab..66582aa5c6 100644
--- a/accounts/abi/abigen/testdata/v2/crowdsale.go.txt
+++ b/accounts/abi/abigen/testdata/v2/crowdsale.go.txt
@@ -36,6 +36,11 @@ type Crowdsale struct {
abi abi.ABI
}
+// GetABI returns the ABI associated with this contract binding.
+func (c *Crowdsale) GetABI() abi.ABI {
+ return c.abi
+}
+
// NewCrowdsale creates a new instance of Crowdsale.
func NewCrowdsale() *Crowdsale {
parsed, err := CrowdsaleMetaData.ParseABI()
diff --git a/accounts/abi/abigen/testdata/v2/dao.go.txt b/accounts/abi/abigen/testdata/v2/dao.go.txt
index 0e9adba31e..a810c65bd9 100644
--- a/accounts/abi/abigen/testdata/v2/dao.go.txt
+++ b/accounts/abi/abigen/testdata/v2/dao.go.txt
@@ -36,6 +36,11 @@ type DAO struct {
abi abi.ABI
}
+// GetABI returns the ABI associated with this contract binding.
+func (c *DAO) GetABI() abi.ABI {
+ return c.abi
+}
+
// NewDAO creates a new instance of DAO.
func NewDAO() *DAO {
parsed, err := DAOMetaData.ParseABI()
diff --git a/accounts/abi/abigen/testdata/v2/deeplynestedarray.go.txt b/accounts/abi/abigen/testdata/v2/deeplynestedarray.go.txt
index 302f1d736f..559ea83422 100644
--- a/accounts/abi/abigen/testdata/v2/deeplynestedarray.go.txt
+++ b/accounts/abi/abigen/testdata/v2/deeplynestedarray.go.txt
@@ -36,6 +36,11 @@ type DeeplyNestedArray struct {
abi abi.ABI
}
+// GetABI returns the ABI associated with this contract binding.
+func (c *DeeplyNestedArray) GetABI() abi.ABI {
+ return c.abi
+}
+
// NewDeeplyNestedArray creates a new instance of DeeplyNestedArray.
func NewDeeplyNestedArray() *DeeplyNestedArray {
parsed, err := DeeplyNestedArrayMetaData.ParseABI()
diff --git a/accounts/abi/abigen/testdata/v2/empty.go.txt b/accounts/abi/abigen/testdata/v2/empty.go.txt
index 7082e20799..7deea15570 100644
--- a/accounts/abi/abigen/testdata/v2/empty.go.txt
+++ b/accounts/abi/abigen/testdata/v2/empty.go.txt
@@ -36,6 +36,11 @@ type Empty struct {
abi abi.ABI
}
+// GetABI returns the ABI associated with this contract binding.
+func (c *Empty) GetABI() abi.ABI {
+ return c.abi
+}
+
// NewEmpty creates a new instance of Empty.
func NewEmpty() *Empty {
parsed, err := EmptyMetaData.ParseABI()
diff --git a/accounts/abi/abigen/testdata/v2/eventchecker.go.txt b/accounts/abi/abigen/testdata/v2/eventchecker.go.txt
index d0600d7c3e..213ab609f5 100644
--- a/accounts/abi/abigen/testdata/v2/eventchecker.go.txt
+++ b/accounts/abi/abigen/testdata/v2/eventchecker.go.txt
@@ -35,6 +35,11 @@ type EventChecker struct {
abi abi.ABI
}
+// GetABI returns the ABI associated with this contract binding.
+func (c *EventChecker) GetABI() abi.ABI {
+ return c.abi
+}
+
// NewEventChecker creates a new instance of EventChecker.
func NewEventChecker() *EventChecker {
parsed, err := EventCheckerMetaData.ParseABI()
diff --git a/accounts/abi/abigen/testdata/v2/getter.go.txt b/accounts/abi/abigen/testdata/v2/getter.go.txt
index 69a4fb54c8..69267767c2 100644
--- a/accounts/abi/abigen/testdata/v2/getter.go.txt
+++ b/accounts/abi/abigen/testdata/v2/getter.go.txt
@@ -36,6 +36,11 @@ type Getter struct {
abi abi.ABI
}
+// GetABI returns the ABI associated with this contract binding.
+func (c *Getter) GetABI() abi.ABI {
+ return c.abi
+}
+
// NewGetter creates a new instance of Getter.
func NewGetter() *Getter {
parsed, err := GetterMetaData.ParseABI()
diff --git a/accounts/abi/abigen/testdata/v2/identifiercollision.go.txt b/accounts/abi/abigen/testdata/v2/identifiercollision.go.txt
index e7301521f4..1d14a7109d 100644
--- a/accounts/abi/abigen/testdata/v2/identifiercollision.go.txt
+++ b/accounts/abi/abigen/testdata/v2/identifiercollision.go.txt
@@ -36,6 +36,11 @@ type IdentifierCollision struct {
abi abi.ABI
}
+// GetABI returns the ABI associated with this contract binding.
+func (c *IdentifierCollision) GetABI() abi.ABI {
+ return c.abi
+}
+
// NewIdentifierCollision creates a new instance of IdentifierCollision.
func NewIdentifierCollision() *IdentifierCollision {
parsed, err := IdentifierCollisionMetaData.ParseABI()
diff --git a/accounts/abi/abigen/testdata/v2/inputchecker.go.txt b/accounts/abi/abigen/testdata/v2/inputchecker.go.txt
index 1743f336db..4522aea52a 100644
--- a/accounts/abi/abigen/testdata/v2/inputchecker.go.txt
+++ b/accounts/abi/abigen/testdata/v2/inputchecker.go.txt
@@ -35,6 +35,11 @@ type InputChecker struct {
abi abi.ABI
}
+// GetABI returns the ABI associated with this contract binding.
+func (c *InputChecker) GetABI() abi.ABI {
+ return c.abi
+}
+
// NewInputChecker creates a new instance of InputChecker.
func NewInputChecker() *InputChecker {
parsed, err := InputCheckerMetaData.ParseABI()
diff --git a/accounts/abi/abigen/testdata/v2/interactor.go.txt b/accounts/abi/abigen/testdata/v2/interactor.go.txt
index f33c95b639..3e88b4a521 100644
--- a/accounts/abi/abigen/testdata/v2/interactor.go.txt
+++ b/accounts/abi/abigen/testdata/v2/interactor.go.txt
@@ -36,6 +36,11 @@ type Interactor struct {
abi abi.ABI
}
+// GetABI returns the ABI associated with this contract binding.
+func (c *Interactor) GetABI() abi.ABI {
+ return c.abi
+}
+
// NewInteractor creates a new instance of Interactor.
func NewInteractor() *Interactor {
parsed, err := InteractorMetaData.ParseABI()
diff --git a/accounts/abi/abigen/testdata/v2/nameconflict.go.txt b/accounts/abi/abigen/testdata/v2/nameconflict.go.txt
index 5e4a9ecaf0..13f96527ee 100644
--- a/accounts/abi/abigen/testdata/v2/nameconflict.go.txt
+++ b/accounts/abi/abigen/testdata/v2/nameconflict.go.txt
@@ -42,6 +42,11 @@ type NameConflict struct {
abi abi.ABI
}
+// GetABI returns the ABI associated with this contract binding.
+func (c *NameConflict) GetABI() abi.ABI {
+ return c.abi
+}
+
// NewNameConflict creates a new instance of NameConflict.
func NewNameConflict() *NameConflict {
parsed, err := NameConflictMetaData.ParseABI()
diff --git a/accounts/abi/abigen/testdata/v2/numericmethodname.go.txt b/accounts/abi/abigen/testdata/v2/numericmethodname.go.txt
index 0af31a1cfb..4f5392f19d 100644
--- a/accounts/abi/abigen/testdata/v2/numericmethodname.go.txt
+++ b/accounts/abi/abigen/testdata/v2/numericmethodname.go.txt
@@ -36,6 +36,11 @@ type NumericMethodName struct {
abi abi.ABI
}
+// GetABI returns the ABI associated with this contract binding.
+func (c *NumericMethodName) GetABI() abi.ABI {
+ return c.abi
+}
+
// NewNumericMethodName creates a new instance of NumericMethodName.
func NewNumericMethodName() *NumericMethodName {
parsed, err := NumericMethodNameMetaData.ParseABI()
diff --git a/accounts/abi/abigen/testdata/v2/outputchecker.go.txt b/accounts/abi/abigen/testdata/v2/outputchecker.go.txt
index f1c98f536e..8d3e475e98 100644
--- a/accounts/abi/abigen/testdata/v2/outputchecker.go.txt
+++ b/accounts/abi/abigen/testdata/v2/outputchecker.go.txt
@@ -35,6 +35,11 @@ type OutputChecker struct {
abi abi.ABI
}
+// GetABI returns the ABI associated with this contract binding.
+func (c *OutputChecker) GetABI() abi.ABI {
+ return c.abi
+}
+
// NewOutputChecker creates a new instance of OutputChecker.
func NewOutputChecker() *OutputChecker {
parsed, err := OutputCheckerMetaData.ParseABI()
diff --git a/accounts/abi/abigen/testdata/v2/overload.go.txt b/accounts/abi/abigen/testdata/v2/overload.go.txt
index 563edf7842..006a2c7c3c 100644
--- a/accounts/abi/abigen/testdata/v2/overload.go.txt
+++ b/accounts/abi/abigen/testdata/v2/overload.go.txt
@@ -36,6 +36,11 @@ type Overload struct {
abi abi.ABI
}
+// GetABI returns the ABI associated with this contract binding.
+func (c *Overload) GetABI() abi.ABI {
+ return c.abi
+}
+
// NewOverload creates a new instance of Overload.
func NewOverload() *Overload {
parsed, err := OverloadMetaData.ParseABI()
diff --git a/accounts/abi/abigen/testdata/v2/rangekeyword.go.txt b/accounts/abi/abigen/testdata/v2/rangekeyword.go.txt
index 296de1fccc..76c5d72d29 100644
--- a/accounts/abi/abigen/testdata/v2/rangekeyword.go.txt
+++ b/accounts/abi/abigen/testdata/v2/rangekeyword.go.txt
@@ -36,6 +36,11 @@ type RangeKeyword struct {
abi abi.ABI
}
+// GetABI returns the ABI associated with this contract binding.
+func (c *RangeKeyword) GetABI() abi.ABI {
+ return c.abi
+}
+
// NewRangeKeyword creates a new instance of RangeKeyword.
func NewRangeKeyword() *RangeKeyword {
parsed, err := RangeKeywordMetaData.ParseABI()
diff --git a/accounts/abi/abigen/testdata/v2/slicer.go.txt b/accounts/abi/abigen/testdata/v2/slicer.go.txt
index 379f136453..416a629cb7 100644
--- a/accounts/abi/abigen/testdata/v2/slicer.go.txt
+++ b/accounts/abi/abigen/testdata/v2/slicer.go.txt
@@ -36,6 +36,11 @@ type Slicer struct {
abi abi.ABI
}
+// GetABI returns the ABI associated with this contract binding.
+func (c *Slicer) GetABI() abi.ABI {
+ return c.abi
+}
+
// NewSlicer creates a new instance of Slicer.
func NewSlicer() *Slicer {
parsed, err := SlicerMetaData.ParseABI()
diff --git a/accounts/abi/abigen/testdata/v2/structs.go.txt b/accounts/abi/abigen/testdata/v2/structs.go.txt
index 4d8864cf6e..ee55ec5620 100644
--- a/accounts/abi/abigen/testdata/v2/structs.go.txt
+++ b/accounts/abi/abigen/testdata/v2/structs.go.txt
@@ -41,6 +41,11 @@ type Structs struct {
abi abi.ABI
}
+// GetABI returns the ABI associated with this contract binding.
+func (c *Structs) GetABI() abi.ABI {
+ return c.abi
+}
+
// NewStructs creates a new instance of Structs.
func NewStructs() *Structs {
parsed, err := StructsMetaData.ParseABI()
diff --git a/accounts/abi/abigen/testdata/v2/token.go.txt b/accounts/abi/abigen/testdata/v2/token.go.txt
index 3bd60a6cdd..1bd364a918 100644
--- a/accounts/abi/abigen/testdata/v2/token.go.txt
+++ b/accounts/abi/abigen/testdata/v2/token.go.txt
@@ -36,6 +36,11 @@ type Token struct {
abi abi.ABI
}
+// GetABI returns the ABI associated with this contract binding.
+func (c *Token) GetABI() abi.ABI {
+ return c.abi
+}
+
// NewToken creates a new instance of Token.
func NewToken() *Token {
parsed, err := TokenMetaData.ParseABI()
diff --git a/accounts/abi/abigen/testdata/v2/tuple.go.txt b/accounts/abi/abigen/testdata/v2/tuple.go.txt
index 10b634f3db..6aabdeb0fe 100644
--- a/accounts/abi/abigen/testdata/v2/tuple.go.txt
+++ b/accounts/abi/abigen/testdata/v2/tuple.go.txt
@@ -61,6 +61,11 @@ type Tuple struct {
abi abi.ABI
}
+// GetABI returns the ABI associated with this contract binding.
+func (c *Tuple) GetABI() abi.ABI {
+ return c.abi
+}
+
// NewTuple creates a new instance of Tuple.
func NewTuple() *Tuple {
parsed, err := TupleMetaData.ParseABI()
diff --git a/accounts/abi/abigen/testdata/v2/tupler.go.txt b/accounts/abi/abigen/testdata/v2/tupler.go.txt
index 8643487042..5e6eafb07e 100644
--- a/accounts/abi/abigen/testdata/v2/tupler.go.txt
+++ b/accounts/abi/abigen/testdata/v2/tupler.go.txt
@@ -36,6 +36,11 @@ type Tupler struct {
abi abi.ABI
}
+// GetABI returns the ABI associated with this contract binding.
+func (c *Tupler) GetABI() abi.ABI {
+ return c.abi
+}
+
// NewTupler creates a new instance of Tupler.
func NewTupler() *Tupler {
parsed, err := TuplerMetaData.ParseABI()
diff --git a/accounts/abi/abigen/testdata/v2/underscorer.go.txt b/accounts/abi/abigen/testdata/v2/underscorer.go.txt
index 13ec968508..abe3bdbacb 100644
--- a/accounts/abi/abigen/testdata/v2/underscorer.go.txt
+++ b/accounts/abi/abigen/testdata/v2/underscorer.go.txt
@@ -36,6 +36,11 @@ type Underscorer struct {
abi abi.ABI
}
+// GetABI returns the ABI associated with this contract binding.
+func (c *Underscorer) GetABI() abi.ABI {
+ return c.abi
+}
+
// NewUnderscorer creates a new instance of Underscorer.
func NewUnderscorer() *Underscorer {
parsed, err := UnderscorerMetaData.ParseABI()
diff --git a/accounts/abi/bind/v2/internal/contracts/db/bindings.go b/accounts/abi/bind/v2/internal/contracts/db/bindings.go
index 2fc57fba6d..b9a7e3164a 100644
--- a/accounts/abi/bind/v2/internal/contracts/db/bindings.go
+++ b/accounts/abi/bind/v2/internal/contracts/db/bindings.go
@@ -43,6 +43,11 @@ type DB struct {
abi abi.ABI
}
+// GetABI returns the ABI associated with this contract binding.
+func (c *DB) GetABI() abi.ABI {
+ return c.abi
+}
+
// NewDB creates a new instance of DB.
func NewDB() *DB {
parsed, err := DBMetaData.ParseABI()
diff --git a/accounts/abi/bind/v2/internal/contracts/events/bindings.go b/accounts/abi/bind/v2/internal/contracts/events/bindings.go
index 2eb5751f23..7acd312b65 100644
--- a/accounts/abi/bind/v2/internal/contracts/events/bindings.go
+++ b/accounts/abi/bind/v2/internal/contracts/events/bindings.go
@@ -36,6 +36,11 @@ type C struct {
abi abi.ABI
}
+// GetABI returns the ABI associated with this contract binding.
+func (c *C) GetABI() abi.ABI {
+ return c.abi
+}
+
// NewC creates a new instance of C.
func NewC() *C {
parsed, err := CMetaData.ParseABI()
diff --git a/accounts/abi/bind/v2/internal/contracts/nested_libraries/bindings.go b/accounts/abi/bind/v2/internal/contracts/nested_libraries/bindings.go
index d1cb08116b..406d71192f 100644
--- a/accounts/abi/bind/v2/internal/contracts/nested_libraries/bindings.go
+++ b/accounts/abi/bind/v2/internal/contracts/nested_libraries/bindings.go
@@ -40,6 +40,11 @@ type C1 struct {
abi abi.ABI
}
+// GetABI returns the ABI associated with this contract binding.
+func (c *C1) GetABI() abi.ABI {
+ return c.abi
+}
+
// NewC1 creates a new instance of C1.
func NewC1() *C1 {
parsed, err := C1MetaData.ParseABI()
@@ -118,6 +123,11 @@ type C2 struct {
abi abi.ABI
}
+// GetABI returns the ABI associated with this contract binding.
+func (c *C2) GetABI() abi.ABI {
+ return c.abi
+}
+
// NewC2 creates a new instance of C2.
func NewC2() *C2 {
parsed, err := C2MetaData.ParseABI()
@@ -192,6 +202,11 @@ type L1 struct {
abi abi.ABI
}
+// GetABI returns the ABI associated with this contract binding.
+func (c *L1) GetABI() abi.ABI {
+ return c.abi
+}
+
// NewL1 creates a new instance of L1.
func NewL1() *L1 {
parsed, err := L1MetaData.ParseABI()
@@ -257,6 +272,11 @@ type L2 struct {
abi abi.ABI
}
+// GetABI returns the ABI associated with this contract binding.
+func (c *L2) GetABI() abi.ABI {
+ return c.abi
+}
+
// NewL2 creates a new instance of L2.
func NewL2() *L2 {
parsed, err := L2MetaData.ParseABI()
@@ -322,6 +342,11 @@ type L2b struct {
abi abi.ABI
}
+// GetABI returns the ABI associated with this contract binding.
+func (c *L2b) GetABI() abi.ABI {
+ return c.abi
+}
+
// NewL2b creates a new instance of L2b.
func NewL2b() *L2b {
parsed, err := L2bMetaData.ParseABI()
@@ -384,6 +409,11 @@ type L3 struct {
abi abi.ABI
}
+// GetABI returns the ABI associated with this contract binding.
+func (c *L3) GetABI() abi.ABI {
+ return c.abi
+}
+
// NewL3 creates a new instance of L3.
func NewL3() *L3 {
parsed, err := L3MetaData.ParseABI()
@@ -450,6 +480,11 @@ type L4 struct {
abi abi.ABI
}
+// GetABI returns the ABI associated with this contract binding.
+func (c *L4) GetABI() abi.ABI {
+ return c.abi
+}
+
// NewL4 creates a new instance of L4.
func NewL4() *L4 {
parsed, err := L4MetaData.ParseABI()
@@ -515,6 +550,11 @@ type L4b struct {
abi abi.ABI
}
+// GetABI returns the ABI associated with this contract binding.
+func (c *L4b) GetABI() abi.ABI {
+ return c.abi
+}
+
// NewL4b creates a new instance of L4b.
func NewL4b() *L4b {
parsed, err := L4bMetaData.ParseABI()
diff --git a/accounts/abi/bind/v2/internal/contracts/solc_errors/bindings.go b/accounts/abi/bind/v2/internal/contracts/solc_errors/bindings.go
index 627b86f1b9..12d8b7f3e9 100644
--- a/accounts/abi/bind/v2/internal/contracts/solc_errors/bindings.go
+++ b/accounts/abi/bind/v2/internal/contracts/solc_errors/bindings.go
@@ -36,6 +36,11 @@ type C struct {
abi abi.ABI
}
+// GetABI returns the ABI associated with this contract binding.
+func (c *C) GetABI() abi.ABI {
+ return c.abi
+}
+
// NewC creates a new instance of C.
func NewC() *C {
parsed, err := CMetaData.ParseABI()
@@ -173,6 +178,11 @@ type C2 struct {
abi abi.ABI
}
+// GetABI returns the ABI associated with this contract binding.
+func (c *C2) GetABI() abi.ABI {
+ return c.abi
+}
+
// NewC2 creates a new instance of C2.
func NewC2() *C2 {
parsed, err := C2MetaData.ParseABI()
diff --git a/accounts/abi/bind/v2/internal/contracts/uint256arrayreturn/bindings.go b/accounts/abi/bind/v2/internal/contracts/uint256arrayreturn/bindings.go
index 19d09bdd6a..f06788633c 100644
--- a/accounts/abi/bind/v2/internal/contracts/uint256arrayreturn/bindings.go
+++ b/accounts/abi/bind/v2/internal/contracts/uint256arrayreturn/bindings.go
@@ -36,6 +36,11 @@ type MyContract struct {
abi abi.ABI
}
+// GetABI returns the ABI associated with this contract binding.
+func (c *MyContract) GetABI() abi.ABI {
+ return c.abi
+}
+
// NewMyContract creates a new instance of MyContract.
func NewMyContract() *MyContract {
parsed, err := MyContractMetaData.ParseABI()
diff --git a/accounts/external/backend.go b/accounts/external/backend.go
index 42eaf661cc..d73fb06ad0 100644
--- a/accounts/external/backend.go
+++ b/accounts/external/backend.go
@@ -237,6 +237,7 @@ func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transactio
}
if tx.Type() == types.BlobTxType {
args.BlobHashes = tx.BlobHashes()
+ args.BlobFeeCap = (*hexutil.Big)(tx.BlobGasFeeCap())
sidecar := tx.BlobTxSidecar()
if sidecar == nil {
return nil, errors.New("blobs must be present for signing")
diff --git a/accounts/keystore/key.go b/accounts/keystore/key.go
index 9b2ac14712..34712cb842 100644
--- a/accounts/keystore/key.go
+++ b/accounts/keystore/key.go
@@ -230,7 +230,7 @@ func toISO8601(t time.Time) string {
if name == "UTC" {
tz = "Z"
} else {
- tz = fmt.Sprintf("%03d00", offset/3600)
+ tz = fmt.Sprintf("%+03d00", offset/3600)
}
return fmt.Sprintf("%04d-%02d-%02dT%02d-%02d-%02d.%09d%s",
t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), tz)
diff --git a/accounts/manager.go b/accounts/manager.go
index a2218e54dd..ebf7bbd62c 100644
--- a/accounts/manager.go
+++ b/accounts/manager.go
@@ -18,6 +18,7 @@ package accounts
import (
"reflect"
+ "slices"
"sort"
"sync"
@@ -254,13 +255,12 @@ func merge(slice []Wallet, wallets ...Wallet) []Wallet {
// drop is the counterpart of merge, which looks up wallets from within the sorted
// cache and removes the ones specified.
func drop(slice []Wallet, wallets ...Wallet) []Wallet {
- for _, wallet := range wallets {
- n := sort.Search(len(slice), func(i int) bool { return slice[i].URL().Cmp(wallet.URL()) >= 0 })
- if n == len(slice) {
- // Wallet not found, may happen during startup
- continue
- }
- slice = append(slice[:n], slice[n+1:]...)
+ remove := make(map[URL]struct{}, len(wallets))
+ for _, w := range wallets {
+ remove[w.URL()] = struct{}{}
}
- return slice
+ return slices.DeleteFunc(slice, func(w Wallet) bool {
+ _, ok := remove[w.URL()]
+ return ok
+ })
}
diff --git a/accounts/manager_test.go b/accounts/manager_test.go
new file mode 100644
index 0000000000..6727be156e
--- /dev/null
+++ b/accounts/manager_test.go
@@ -0,0 +1,47 @@
+// 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 .
+
+package accounts
+
+import "testing"
+
+type managerTestWallet struct {
+ Wallet
+ url URL
+}
+
+func (w managerTestWallet) URL() URL {
+ return w.url
+}
+
+func TestDropMissingWallet(t *testing.T) {
+ t.Parallel()
+
+ wallets := []Wallet{
+ managerTestWallet{url: URL{Scheme: "test", Path: "a"}},
+ managerTestWallet{url: URL{Scheme: "test", Path: "c"}},
+ }
+ dropped := drop(wallets, managerTestWallet{url: URL{Scheme: "test", Path: "b"}})
+
+ if len(dropped) != len(wallets) {
+ t.Fatalf("drop removed wallet for missing URL: got %d wallets, want %d", len(dropped), len(wallets))
+ }
+ for i := range dropped {
+ if got, want := dropped[i].URL(), wallets[i].URL(); got != want {
+ t.Fatalf("wallet %d mismatch: got %v, want %v", i, got, want)
+ }
+ }
+}
diff --git a/cmd/devp2p/discv4cmd.go b/cmd/devp2p/discv4cmd.go
index 84c7ef0c44..98b04311b4 100644
--- a/cmd/devp2p/discv4cmd.go
+++ b/cmd/devp2p/discv4cmd.go
@@ -126,7 +126,7 @@ var (
}
extAddrFlag = &cli.StringFlag{
Name: "extaddr",
- Usage: "UDP endpoint announced in ENR. You can provide a bare IP address or IP:port as the value of this flag.",
+ Usage: "UDP endpoint announced in ENR. You can provide a bare IP address or IP:port as the value of this flag. Provide a comma-separated pair to announce both an IPv4 and an IPv6 endpoint.",
}
crawlTimeoutFlag = &cli.DurationFlag{
Name: "timeout",
@@ -344,36 +344,60 @@ func parseExtAddr(spec string) (ip net.IP, port int, ok bool) {
func listen(ctx *cli.Context, ln *enode.LocalNode) *net.UDPConn {
addr := ctx.String(listenAddrFlag.Name)
+ extAddr := ctx.String(extAddrFlag.Name)
+ var (
+ socket net.PacketConn
+ err error
+ )
if addr == "" {
- addr = "0.0.0.0:0"
+ // Dual-stack socket, falling back to IPv4-only where IPv6 is unavailable.
+ if socket, err = net.ListenPacket("udp", "[::]:0"); err != nil {
+ socket, err = net.ListenPacket("udp", "0.0.0.0:0")
+ }
+ } else {
+ socket, err = net.ListenPacket("udp", addr)
}
- socket, err := net.ListenPacket("udp4", addr)
if err != nil {
exit(err)
}
- // Configure UDP endpoint in ENR from listener address.
+ // Configure the ENR endpoint from the listener address, but only without an
+ // explicit -extaddr: otherwise we'd announce a fallback IP for an address
+ // family the user didn't specify (e.g. loopback IPv4 on an IPv6-only node).
usocket := socket.(*net.UDPConn)
uaddr := socket.LocalAddr().(*net.UDPAddr)
- if uaddr.IP.IsUnspecified() {
- ln.SetFallbackIP(net.IP{127, 0, 0, 1})
- } else {
- ln.SetFallbackIP(uaddr.IP)
+ if extAddr == "" {
+ if uaddr.IP.IsUnspecified() {
+ ln.SetFallbackIP(net.IP{127, 0, 0, 1})
+ } else {
+ ln.SetFallbackIP(uaddr.IP)
+ }
}
ln.SetFallbackUDP(uaddr.Port)
- // If an ENR endpoint is set explicitly on the command-line, override
- // the information from the listening address. Note this is careful not
- // to set the UDP port if the external address doesn't have it.
- extAddr := ctx.String(extAddrFlag.Name)
+ // Override with explicit -extaddr address(es). A static IP is set per family,
+ // and all specs share one UDP port because the node has a single socket.
if extAddr != "" {
- ip, port, ok := parseExtAddr(extAddr)
- if !ok {
- exit(fmt.Errorf("-%s: invalid external address %q", extAddrFlag.Name, extAddr))
+ var extPort int
+ for spec := range strings.SplitSeq(extAddr, ",") {
+ spec = strings.TrimSpace(spec)
+ if spec == "" {
+ continue
+ }
+ ip, port, ok := parseExtAddr(spec)
+ if !ok {
+ exit(fmt.Errorf("-%s: invalid external address %q", extAddrFlag.Name, spec))
+ }
+ ln.SetStaticIP(ip)
+ if port != 0 {
+ if extPort != 0 && port != extPort {
+ exit(fmt.Errorf("-%s: all addresses must announce the same UDP port, got %d and %d", extAddrFlag.Name, extPort, port))
+ }
+ extPort = port
+ }
}
- ln.SetStaticIP(ip)
- if port != 0 {
- ln.SetFallbackUDP(port)
+ if extPort != 0 {
+ ln.SetFallbackUDP(extPort)
}
}
@@ -406,6 +430,7 @@ type discv4API struct {
func (api *discv4API) LookupRandom(n int) (ns []*enode.Node) {
it := api.host.RandomNodes()
+ defer it.Close()
for len(ns) < n && it.Next() {
ns = append(ns, it.Node())
}
diff --git a/cmd/devp2p/discv5cmd.go b/cmd/devp2p/discv5cmd.go
index dd253dd082..8b39829e60 100644
--- a/cmd/devp2p/discv5cmd.go
+++ b/cmd/devp2p/discv5cmd.go
@@ -58,6 +58,7 @@ var (
Action: discv5Crawl,
Flags: slices.Concat(discoveryNodeFlags, []cli.Flag{
crawlTimeoutFlag,
+ crawlParallelismFlag,
}),
}
discv5TestCommand = &cli.Command{
diff --git a/cmd/devp2p/internal/ethtest/chain.go b/cmd/devp2p/internal/ethtest/chain.go
index b44e6aa36a..36c395cdef 100644
--- a/cmd/devp2p/internal/ethtest/chain.go
+++ b/cmd/devp2p/internal/ethtest/chain.go
@@ -335,7 +335,7 @@ func readAccounts(file string) (map[common.Address]*senderInfo, error) {
for addr, acc := range keys {
pk, err := crypto.HexToECDSA(common.Bytes2Hex(acc.Key))
if err != nil {
- return nil, fmt.Errorf("unable to read private key for %s: %v", err, addr)
+ return nil, fmt.Errorf("unable to read private key for %s: %v", addr, err)
}
accounts[addr] = &senderInfo{Key: pk, Nonce: 0}
}
diff --git a/cmd/devp2p/internal/ethtest/suite.go b/cmd/devp2p/internal/ethtest/suite.go
index b486eb26d9..22247a5cf6 100644
--- a/cmd/devp2p/internal/ethtest/suite.go
+++ b/cmd/devp2p/internal/ethtest/suite.go
@@ -425,7 +425,7 @@ func (s *Suite) TestGetBlockBodies(t *utesting.T) {
t.Fatalf("error reading block bodies msg: %v", err)
}
if got, want := resp.RequestId, req.RequestId; got != want {
- t.Fatalf("unexpected request id in respond", got, want)
+ t.Fatalf("unexpected request id in response: got %d, want %d", got, want)
}
if resp.List.Len() != len(req.GetBlockBodiesRequest) {
t.Fatalf("wrong bodies in response: expected %d bodies, got %d", len(req.GetBlockBodiesRequest), resp.List.Len())
@@ -469,7 +469,7 @@ func (s *Suite) TestGetReceipts(t *utesting.T) {
t.Fatalf("error reading block receipts msg: %v", err)
}
if got, want := resp.RequestId, req.RequestId; got != want {
- t.Fatalf("unexpected request id in respond", got, want)
+ t.Fatalf("unexpected request id in response: got %d, want %d", got, want)
}
if resp.List.Len() != len(req.GetReceiptsRequest) {
t.Fatalf("wrong receipts in response: expected %d receipts, got %d", len(req.GetReceiptsRequest), resp.List.Len())
@@ -490,7 +490,7 @@ func (s *Suite) TestGetReceipts(t *utesting.T) {
t.Fatalf("error reading block receipts msg: %v", err)
}
if got, want := resp.RequestId, req.RequestId; got != want {
- t.Fatalf("unexpected request id in respond", got, want)
+ t.Fatalf("unexpected request id in response: got %d, want %d", got, want)
}
if resp.List.Len() != len(req.GetReceiptsRequest) {
t.Fatalf("wrong receipts in response: expected %d receipts, got %d", len(req.GetReceiptsRequest), resp.List.Len())
diff --git a/cmd/devp2p/internal/v5test/discv5tests.go b/cmd/devp2p/internal/v5test/discv5tests.go
index 4dc2507693..17d39d5ad0 100644
--- a/cmd/devp2p/internal/v5test/discv5tests.go
+++ b/cmd/devp2p/internal/v5test/discv5tests.go
@@ -186,7 +186,7 @@ func (s *Suite) TestHandshakeResend(t *utesting.T) {
t.Fatalf("wrong nonce %x in WHOAREYOU (want %x)", resp.Nonce[:], challenge1.Nonce[:])
}
if !bytes.Equal(resp.ChallengeData, challenge1.ChallengeData) {
- t.Fatalf("wrong ChallengeData in resent WHOAREYOU (want %x)", resp.ChallengeData, challenge1.ChallengeData)
+ t.Fatalf("wrong ChallengeData in resent WHOAREYOU: got %x, want %x", resp.ChallengeData, challenge1.ChallengeData)
}
resp.Node = conn.remote
default:
diff --git a/cmd/evm/blockrunner.go b/cmd/evm/blockrunner.go
index c6fac5396e..b175f05dc7 100644
--- a/cmd/evm/blockrunner.go
+++ b/cmd/evm/blockrunner.go
@@ -78,7 +78,7 @@ func blockTestCmd(ctx *cli.Context) error {
return err
}
// During fuzzing, we report the result after every block
- if !ctx.IsSet(FuzzFlag.Name) {
+ if !ctx.Bool(FuzzFlag.Name) {
report(ctx, results)
}
}
@@ -101,7 +101,7 @@ func runBlockTest(ctx *cli.Context, fname string) ([]testResult, error) {
tracer := tracerFromFlags(ctx)
// Suppress INFO logs during fuzzing
- if ctx.IsSet(FuzzFlag.Name) {
+ if ctx.Bool(FuzzFlag.Name) {
log.SetDefault(log.NewLogger(log.DiscardHandler()))
}
@@ -140,7 +140,7 @@ func runBlockTest(ctx *cli.Context, fname string) ([]testResult, error) {
}
// When fuzzing, write results after every block
- if ctx.IsSet(FuzzFlag.Name) {
+ if ctx.Bool(FuzzFlag.Name) {
report(ctx, []testResult{*result})
}
results = append(results, *result)
diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go
index f35b28510c..d711ae56ac 100644
--- a/cmd/evm/internal/t8ntool/execution.go
+++ b/cmd/evm/internal/t8ntool/execution.go
@@ -242,6 +242,13 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
chainConfig.DAOForkBlock.Cmp(new(big.Int).SetUint64(pre.Env.Number)) == 0 {
misc.ApplyDAOHardFork(statedb)
}
+ // EIP-7997: insert the deterministic deployment factory at the Amsterdam
+ // activation block via an irregular state transition.
+ if pre.Env.Number > 0 &&
+ chainConfig.IsAmsterdam(new(big.Int).SetUint64(pre.Env.Number), pre.Env.Timestamp) &&
+ !chainConfig.IsAmsterdam(new(big.Int).SetUint64(pre.Env.Number-1), pre.Env.ParentTimestamp) {
+ misc.ApplyEIP7997(statedb)
+ }
evm := vm.NewEVM(vmContext, statedb, chainConfig, vmConfig)
if beaconRoot := pre.Env.ParentBeaconBlockRoot; beaconRoot != nil {
core.ProcessBeaconBlockRoot(*beaconRoot, evm, blockAccessList)
diff --git a/cmd/evm/internal/t8ntool/transaction.go b/cmd/evm/internal/t8ntool/transaction.go
index 9eb1bdbf5f..7207cad41d 100644
--- a/cmd/evm/internal/t8ntool/transaction.go
+++ b/cmd/evm/internal/t8ntool/transaction.go
@@ -33,6 +33,7 @@ import (
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/tests"
+ "github.com/holiman/uint256"
"github.com/urfave/cli/v2"
)
@@ -132,8 +133,14 @@ func Transaction(ctx *cli.Context) error {
r.Address = sender
}
// Check intrinsic gas
+ value, overflow := uint256.FromBig(tx.Value())
+ if overflow {
+ // A 256-bit overflow is reported by the field validation below; use a
+ // non-zero placeholder so intrinsic gas is still computed and reported.
+ value = uint256.NewInt(1)
+ }
rules := chainConfig.Rules(common.Big0, true, 0)
- cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules, params.CostPerStateByte)
+ cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), r.Address, tx.To(), value, rules, params.CostPerStateByte)
if err != nil {
r.Error = err
results = append(results, r)
@@ -147,7 +154,7 @@ func Transaction(ctx *cli.Context) error {
}
// For Prague txs, validate the floor data gas.
if rules.IsPrague {
- floorDataGas, err := core.FloorDataGas(rules, tx.Data(), tx.AccessList())
+ floorDataGas, err := core.FloorDataGas(rules, r.Address, tx.To(), value, tx.Data(), tx.AccessList())
if err != nil {
r.Error = err
results = append(results, r)
diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go
index 891507d2ed..95b0ec274e 100644
--- a/cmd/geth/chaincmd.go
+++ b/cmd/geth/chaincmd.go
@@ -62,6 +62,7 @@ var (
Flags: slices.Concat([]cli.Flag{
utils.CachePreimagesFlag,
utils.OverrideOsaka,
+ utils.OverrideAmsterdam,
utils.OverrideBPO1,
utils.OverrideBPO2,
utils.OverrideUBT,
@@ -287,6 +288,10 @@ func initGenesis(ctx *cli.Context) error {
v := ctx.Uint64(utils.OverrideOsaka.Name)
overrides.OverrideOsaka = &v
}
+ if ctx.IsSet(utils.OverrideAmsterdam.Name) {
+ v := ctx.Uint64(utils.OverrideAmsterdam.Name)
+ overrides.OverrideAmsterdam = &v
+ }
if ctx.IsSet(utils.OverrideBPO1.Name) {
v := ctx.Uint64(utils.OverrideBPO1.Name)
overrides.OverrideBPO1 = &v
@@ -792,8 +797,8 @@ func downloadEra(ctx *cli.Context) error {
var network = "mainnet"
if utils.IsNetworkPreset(ctx) {
switch {
- case ctx.IsSet(utils.MainnetFlag.Name):
- case ctx.IsSet(utils.SepoliaFlag.Name):
+ case ctx.Bool(utils.MainnetFlag.Name):
+ case ctx.Bool(utils.SepoliaFlag.Name):
network = "sepolia"
default:
return errors.New("unsupported network, no known era1 checksums")
@@ -820,7 +825,7 @@ func downloadEra(ctx *cli.Context) error {
return err
}
switch {
- case ctx.IsSet(eraAllFlag.Name):
+ case ctx.Bool(eraAllFlag.Name):
return l.DownloadAll(dir)
case ctx.IsSet(eraBlockFlag.Name):
diff --git a/cmd/geth/config.go b/cmd/geth/config.go
index ab9a336349..8ac9e7f6bd 100644
--- a/cmd/geth/config.go
+++ b/cmd/geth/config.go
@@ -228,6 +228,10 @@ func makeFullNode(ctx *cli.Context) *node.Node {
v := ctx.Uint64(utils.OverrideOsaka.Name)
cfg.Eth.OverrideOsaka = &v
}
+ if ctx.IsSet(utils.OverrideAmsterdam.Name) {
+ v := ctx.Uint64(utils.OverrideAmsterdam.Name)
+ cfg.Eth.OverrideAmsterdam = &v
+ }
if ctx.IsSet(utils.OverrideBPO1.Name) {
v := ctx.Uint64(utils.OverrideBPO1.Name)
cfg.Eth.OverrideBPO1 = &v
diff --git a/cmd/geth/main.go b/cmd/geth/main.go
index d4acb6f652..abf2b2437c 100644
--- a/cmd/geth/main.go
+++ b/cmd/geth/main.go
@@ -57,6 +57,7 @@ var (
utils.USBFlag,
utils.SmartCardDaemonPathFlag,
utils.OverrideOsaka,
+ utils.OverrideAmsterdam,
utils.OverrideBPO1,
utils.OverrideBPO2,
utils.OverrideUBT,
@@ -177,6 +178,7 @@ var (
utils.AllowUnprotectedTxs,
utils.BatchRequestLimit,
utils.BatchResponseMaxSize,
+ utils.HTTPBodyLimitFlag,
utils.RPCTxSyncDefaultTimeoutFlag,
utils.RPCTxSyncMaxTimeoutFlag,
utils.RPCGlobalRangeLimitFlag,
@@ -290,13 +292,13 @@ func main() {
func prepare(ctx *cli.Context) {
// If we're running a known preset, log it for convenience.
switch {
- case ctx.IsSet(utils.SepoliaFlag.Name):
+ case ctx.Bool(utils.SepoliaFlag.Name):
log.Info("Starting Geth on Sepolia testnet...")
- case ctx.IsSet(utils.HoleskyFlag.Name):
+ case ctx.Bool(utils.HoleskyFlag.Name):
log.Info("Starting Geth on Holesky testnet...")
- case ctx.IsSet(utils.HoodiFlag.Name):
+ case ctx.Bool(utils.HoodiFlag.Name):
log.Info("Starting Geth on Hoodi testnet...")
case !ctx.IsSet(utils.NetworkIdFlag.Name):
diff --git a/cmd/geth/snapshot.go b/cmd/geth/snapshot.go
index 8fedc21c27..c0258df68c 100644
--- a/cmd/geth/snapshot.go
+++ b/cmd/geth/snapshot.go
@@ -1054,6 +1054,7 @@ func listEIP7610EligibleAccounts(ctx *cli.Context) error {
log.Error("Failed to get account iterator", "err", err)
return err
}
+ defer iter.Release()
var (
start = time.Now()
accounts []common.Address
diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go
index d2c6c217c7..7c363ce932 100644
--- a/cmd/utils/flags.go
+++ b/cmd/utils/flags.go
@@ -255,6 +255,11 @@ var (
Usage: "Manually specify the Osaka fork timestamp, overriding the bundled setting",
Category: flags.EthCategory,
}
+ OverrideAmsterdam = &cli.Uint64Flag{
+ Name: "override.amsterdam",
+ Usage: "Manually specify the Amsterdam fork timestamp, overriding the bundled setting",
+ Category: flags.EthCategory,
+ }
OverrideBPO1 = &cli.Uint64Flag{
Name: "override.bpo1",
Usage: "Manually specify the bpo1 fork timestamp, overriding the bundled setting",
@@ -863,6 +868,12 @@ var (
Value: node.DefaultConfig.BatchResponseMaxSize,
Category: flags.APICategory,
}
+ HTTPBodyLimitFlag = &cli.IntFlag{
+ Name: "rpc.http-body-limit",
+ Usage: "Maximum size (in megabytes) of an HTTP request body",
+ Value: node.DefaultConfig.HTTPBodyLimit / (1024 * 1024),
+ Category: flags.APICategory,
+ }
// Network Settings
MaxPeersFlag = &cli.IntFlag{
@@ -1363,6 +1374,10 @@ func setHTTP(ctx *cli.Context, cfg *node.Config) {
if ctx.IsSet(BatchResponseMaxSize.Name) {
cfg.BatchResponseMaxSize = ctx.Int(BatchResponseMaxSize.Name)
}
+
+ if ctx.IsSet(HTTPBodyLimitFlag.Name) {
+ cfg.HTTPBodyLimit = ctx.Int(HTTPBodyLimitFlag.Name) * 1024 * 1024
+ }
}
// setGraphQL creates the GraphQL listener interface string from the set
@@ -1910,7 +1925,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
if ctx.IsSet(RPCGlobalTxFeeCapFlag.Name) {
cfg.RPCTxFeeCap = ctx.Float64(RPCGlobalTxFeeCapFlag.Name)
}
- if ctx.IsSet(NoDiscoverFlag.Name) {
+ if ctx.Bool(NoDiscoverFlag.Name) {
cfg.EthDiscoveryURLs, cfg.SnapDiscoveryURLs = []string{}, []string{}
} else if ctx.IsSet(DNSDiscoveryFlag.Name) {
urls := ctx.String(DNSDiscoveryFlag.Name)
@@ -2289,7 +2304,7 @@ func SplitTagsFlag(tagsFlag string) map[string]string {
for _, t := range tags {
if t != "" {
- kv := strings.Split(t, "=")
+ kv := strings.SplitN(t, "=", 2)
if len(kv) == 2 {
tagsMap[kv[0]] = kv[1]
@@ -2348,7 +2363,7 @@ func tryMakeReadOnlyDatabase(ctx *cli.Context, stack *node.Node) ethdb.Database
func IsNetworkPreset(ctx *cli.Context) bool {
for _, flag := range NetworkFlags {
bFlag, _ := flag.(*cli.BoolFlag)
- if ctx.IsSet(bFlag.Name) {
+ if ctx.Bool(bFlag.Name) {
return true
}
}
diff --git a/cmd/utils/flags_test.go b/cmd/utils/flags_test.go
index 0be3370d4a..81525ce3b2 100644
--- a/cmd/utils/flags_test.go
+++ b/cmd/utils/flags_test.go
@@ -18,8 +18,11 @@
package utils
import (
+ "flag"
"reflect"
"testing"
+
+ "github.com/urfave/cli/v2"
)
func Test_SplitTagsFlag(t *testing.T) {
@@ -52,7 +55,9 @@ func Test_SplitTagsFlag(t *testing.T) {
{
"garbage",
"smth=smthelse=123",
- map[string]string{},
+ map[string]string{
+ "smth": "smthelse=123",
+ },
},
}
for _, tt := range tests {
@@ -64,3 +69,52 @@ func Test_SplitTagsFlag(t *testing.T) {
})
}
}
+
+func TestIsNetworkPresetUsesFlagValue(t *testing.T) {
+ t.Parallel()
+
+ tests := []struct {
+ name string
+ args []string
+ want bool
+ }{
+ {
+ name: "unset",
+ want: false,
+ },
+ {
+ name: "enabled",
+ args: []string{"--sepolia"},
+ want: true,
+ },
+ {
+ name: "explicit false",
+ args: []string{"--sepolia=false"},
+ want: false,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
+ ctx := newTestContext(t, tt.args, NetworkFlags...)
+ if got := IsNetworkPreset(ctx); got != tt.want {
+ t.Fatalf("IsNetworkPreset() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func newTestContext(t *testing.T, args []string, flags ...cli.Flag) *cli.Context {
+ t.Helper()
+
+ set := flag.NewFlagSet("test", flag.ContinueOnError)
+ for _, f := range flags {
+ if err := f.Apply(set); err != nil {
+ t.Fatal(err)
+ }
+ }
+ if err := set.Parse(args); err != nil {
+ t.Fatal(err)
+ }
+ return cli.NewContext(nil, set, nil)
+}
diff --git a/cmd/workload/testsuite.go b/cmd/workload/testsuite.go
index 4e33522f1b..998dbfe165 100644
--- a/cmd/workload/testsuite.go
+++ b/cmd/workload/testsuite.go
@@ -122,7 +122,7 @@ func validateHistoryPruneErr(err error, blockNum uint64, historyPruneBlock *uint
func testConfigFromCLI(ctx *cli.Context) (cfg testConfig) {
flags.CheckExclusive(ctx, testMainnetFlag, testSepoliaFlag)
- if (ctx.IsSet(testMainnetFlag.Name) || ctx.IsSet(testSepoliaFlag.Name)) && ctx.IsSet(filterQueryFileFlag.Name) {
+ if (ctx.Bool(testMainnetFlag.Name) || ctx.Bool(testSepoliaFlag.Name)) && ctx.IsSet(filterQueryFileFlag.Name) {
exit(filterQueryFileFlag.Name + " cannot be used with " + testMainnetFlag.Name + " or " + testSepoliaFlag.Name)
}
diff --git a/consensus/misc/eip4844/eip4844.go b/consensus/misc/eip4844/eip4844.go
index 2ebf4f7155..add85092c2 100644
--- a/consensus/misc/eip4844/eip4844.go
+++ b/consensus/misc/eip4844/eip4844.go
@@ -73,8 +73,6 @@ func latestBlobConfig(cfg *params.ChainConfig, time uint64) (BlobConfig, error)
bc = s.BPO2
case cfg.IsBPO1(london, time) && s.BPO1 != nil:
bc = s.BPO1
- case cfg.IsOsaka(london, time) && s.Osaka != nil:
- bc = s.Osaka
case cfg.IsPrague(london, time) && s.Prague != nil:
bc = s.Prague
case cfg.IsCancun(london, time) && s.Cancun != nil:
diff --git a/consensus/misc/eip4844/eip4844_test.go b/consensus/misc/eip4844/eip4844_test.go
index 35934370af..4aa49c9cb1 100644
--- a/consensus/misc/eip4844/eip4844_test.go
+++ b/consensus/misc/eip4844/eip4844_test.go
@@ -120,7 +120,6 @@ func TestCalcBlobFeePostOsaka(t *testing.T) {
BlobScheduleConfig: ¶ms.BlobScheduleConfig{
Cancun: params.DefaultCancunBlobConfig,
Prague: params.DefaultPragueBlobConfig,
- Osaka: params.DefaultOsakaBlobConfig,
BPO1: ¶ms.BlobConfig{
Target: 9,
Max: 14,
@@ -191,7 +190,7 @@ func TestFakeExponential(t *testing.T) {
func TestCalcExcessBlobGasEIP7918(t *testing.T) {
var (
cfg = params.MergedTestChainConfig
- targetBlobs = cfg.BlobScheduleConfig.Osaka.Target
+ targetBlobs = cfg.BlobScheduleConfig.Prague.Target
blobGasTarget = uint64(targetBlobs) * params.BlobTxBlobGasPerBlob
)
diff --git a/consensus/misc/eip7997.go b/consensus/misc/eip7997.go
new file mode 100644
index 0000000000..8564d5283a
--- /dev/null
+++ b/consensus/misc/eip7997.go
@@ -0,0 +1,47 @@
+// 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 .
+
+package misc
+
+import (
+ "github.com/ethereum/go-ethereum/core/tracing"
+ "github.com/ethereum/go-ethereum/core/vm"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/params"
+)
+
+// ApplyEIP7997 inserts the deterministic deployment factory into the state as an
+// irregular state transition, as specified by EIP-7997. The factory is a keyless
+// CREATE2 factory that, once present at the canonical address on every EVM chain,
+// allows contracts to be deployed at identical addresses across chains.
+func ApplyEIP7997(statedb vm.StateDB) {
+ // The account must hold the canonical factory runtime code. If its code hash
+ // already matches, the chain satisfies EIP-7997 and nothing needs to change.
+ wantHash := crypto.Keccak256Hash(params.DeterministicFactoryCode)
+ if statedb.GetCodeHash(params.DeterministicFactoryAddress) == wantHash {
+ return
+ }
+ if !statedb.Exist(params.DeterministicFactoryAddress) {
+ statedb.CreateAccount(params.DeterministicFactoryAddress)
+ }
+ statedb.CreateContract(params.DeterministicFactoryAddress)
+ statedb.SetCode(params.DeterministicFactoryAddress, params.DeterministicFactoryCode, tracing.CodeChangeUnspecified)
+
+ // Preserve a pre-existing nonce; only bump the default zero nonce to 1.
+ if statedb.GetNonce(params.DeterministicFactoryAddress) == 0 {
+ statedb.SetNonce(params.DeterministicFactoryAddress, 1, tracing.NonceChangeNewContract)
+ }
+}
diff --git a/core/bal_test.go b/core/bal_test.go
index 6b79f86c43..64c2714b16 100644
--- a/core/bal_test.go
+++ b/core/bal_test.go
@@ -42,9 +42,6 @@ import (
func balChainConfig() *params.ChainConfig {
cfg := *params.MergedTestChainConfig
cfg.AmsterdamTime = new(uint64)
- blob := *cfg.BlobScheduleConfig
- blob.Amsterdam = blob.Osaka
- cfg.BlobScheduleConfig = &blob
return &cfg
}
@@ -518,6 +515,73 @@ func TestBALStaticCallTargetIncluded(t *testing.T) {
assertEmpty(t, assertPresent(t, b, target))
}
+// makeValueCaller emits a single value-transferring CALL-family op (CALL 0xf1
+// or CALLCODE 0xf2) against `target` with value=1, then STOPs. Used together
+// with a zero-balance caller to make the value transfer fail CanTransfer.
+func makeValueCaller(op byte, target common.Address) []byte {
+ code := []byte{
+ 0x60, 0x00, // retSize
+ 0x60, 0x00, // retOff
+ 0x60, 0x00, // argsSize
+ 0x60, 0x00, // argsOff
+ 0x60, 0x01, // value = 1
+ 0x73, // PUSH20 target
+ }
+ code = append(code, target.Bytes()...)
+ return append(code, 0x5a, op, 0x50, 0x00) // GAS, op, POP, STOP
+}
+
+// TestBALCallToDelegatedTargetBalanceFail asserts the EIP-7928 rule revised in
+// ethereum/EIPs#11838: when a CALL targets an EIP-7702 delegated account and the
+// delegated address passes its access_cost gas check, the delegated
+// (implementation) address MUST appear in the BAL even when the call then fails
+// its sender-balance check, because the delegation is resolved before that
+// check. CALL routes through the EIP-8037 gas path.
+func TestBALCallToDelegatedTargetBalanceFail(t *testing.T) {
+ delegated := common.HexToAddress("0xde1e9a7ed") // EOA carrying a 7702 designator
+ impl := common.HexToAddress("0x111111") // delegation target (implementation)
+ caller := common.HexToAddress("0xca11") // zero-balance contract issuing the CALL
+
+ env := newBALTestEnv(types.GenesisAlloc{
+ caller: {Code: makeValueCaller(0xf1 /* CALL */, delegated), Balance: common.Big0},
+ delegated: {Code: types.AddressToDelegation(impl), Balance: common.Big0},
+ impl: {Code: []byte{0x00}, Balance: common.Big0}, // STOP
+ })
+
+ b, _ := env.run(t, func(g *BlockGen) {
+ g.AddTx(env.tx(0, &caller, big.NewInt(0), 1_000_000, 0, nil))
+ })
+
+ assertPresent(t, b, caller)
+ assertPresent(t, b, delegated)
+ // The call failed its sender-balance check, so the implementation never
+ // executed: it is recorded with an empty change set, but it MUST be present.
+ assertEmpty(t, assertPresent(t, b, impl))
+}
+
+// TestBALCallCodeToDelegatedTargetBalanceFail is the CALLCODE analogue of
+// TestBALCallToDelegatedTargetBalanceFail, exercising the EIP-7702 gas path
+// (CALLCODE/STATICCALL/DELEGATECALL) rather than the EIP-8037 one.
+func TestBALCallCodeToDelegatedTargetBalanceFail(t *testing.T) {
+ delegated := common.HexToAddress("0xde1e9a7ed")
+ impl := common.HexToAddress("0x111111")
+ caller := common.HexToAddress("0xca11")
+
+ env := newBALTestEnv(types.GenesisAlloc{
+ caller: {Code: makeValueCaller(0xf2 /* CALLCODE */, delegated), Balance: common.Big0},
+ delegated: {Code: types.AddressToDelegation(impl), Balance: common.Big0},
+ impl: {Code: []byte{0x00}, Balance: common.Big0}, // STOP
+ })
+
+ b, _ := env.run(t, func(g *BlockGen) {
+ g.AddTx(env.tx(0, &caller, big.NewInt(0), 1_000_000, 0, nil))
+ })
+
+ assertPresent(t, b, caller)
+ assertPresent(t, b, delegated)
+ assertEmpty(t, assertPresent(t, b, impl))
+}
+
// ============================== Revert behaviour ==============================
// TestBALRevertedTxStillIncluded: a tx whose top-level call REVERTs still
@@ -946,6 +1010,62 @@ func TestBALSelfDestructPreExistingContract(t *testing.T) {
}
}
+// TestBALSelfDestructToSelfKeepsBalance: under EIP-8246 a freshly created
+// contract that self-destructs to itself keeps its balance (it is not burnt and
+// the account is not removed). The surviving balance-only account must therefore
+// be recorded in the BAL with its preserved balance.
+func TestBALSelfDestructToSelfKeepsBalance(t *testing.T) {
+ env := newBALTestEnv(nil)
+ // Init code: ADDRESS SELFDESTRUCT — the contract self-destructs to itself
+ // during its own creation transaction (satisfying EIP-6780's same-tx rule).
+ // ADDRESS (0x30) ; SELFDESTRUCT (0xff)
+ init := []byte{0x30, 0xff}
+
+ b, receipts := env.run(t, func(g *BlockGen) {
+ g.AddTx(env.tx(0, nil, big.NewInt(100), 1_000_000, 0, init))
+ })
+
+ created := receipts[0].ContractAddress
+ cc := assertPresent(t, b, created)
+ // EIP-8246: balance preserved (not burnt), account survives -> the BAL must
+ // record the created address with its retained balance.
+ if len(cc.BalanceChanges) != 1 || cc.BalanceChanges[0].PostBalance.Uint64() != 100 {
+ t.Fatalf("self-destruct-to-self must preserve balance 100 in the BAL: %+v", cc.BalanceChanges)
+ }
+}
+
+// TestBALSelfDestructToSelfPrefundedUnchanged: a pre-funded address onto which a
+// contract is deployed and which self-destructs to itself in the same
+// transaction. Under EIP-8246 the account survives with its balance unchanged,
+// so the BAL must list it only as an access (no balance/nonce/code change).
+func TestBALSelfDestructToSelfPrefundedUnchanged(t *testing.T) {
+ // The contract address created by the sender's nonce-0 transaction; it is
+ // pre-funded in genesis (balance only: nonce 0, no code, no storage), which
+ // EIP-7610 permits as a deployment target.
+ key, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
+ created := crypto.CreateAddress(crypto.PubkeyToAddress(key.PublicKey), 0)
+
+ env := newBALTestEnv(types.GenesisAlloc{
+ created: {Balance: big.NewInt(77)},
+ })
+ // Init code: ADDRESS SELFDESTRUCT, deployed with zero value so the balance is
+ // untouched (stays at the pre-funded 77).
+ init := []byte{0x30, 0xff}
+
+ b, receipts := env.run(t, func(g *BlockGen) {
+ g.AddTx(env.tx(0, nil, big.NewInt(0), 1_000_000, 0, init))
+ })
+
+ if receipts[0].ContractAddress != created {
+ t.Fatalf("unexpected created address: have %x want %x", receipts[0].ContractAddress, created)
+ }
+ aa := assertPresent(t, b, created)
+ // EIP-8246: balance preserved and equal to the pre-transaction value, so no
+ // balance change; nonce and code end where they started (0 / empty). The
+ // account is only read, with an empty change set.
+ assertEmpty(t, aa)
+}
+
// ============================== Mid-tx balance round-trip ==============================
// TestBALMidTxBalanceRoundTrip: when an address's balance changes during a
diff --git a/core/bench_test.go b/core/bench_test.go
index fe66aeae0d..79584309da 100644
--- a/core/bench_test.go
+++ b/core/bench_test.go
@@ -89,7 +89,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) {
data := make([]byte, nbytes)
return func(i int, gen *BlockGen) {
toaddr := common.Address{}
- cost, _ := IntrinsicGas(data, nil, nil, false, params.Rules{}, params.CostPerStateByte)
+ cost, _ := IntrinsicGas(data, nil, nil, common.Address{}, &toaddr, nil, params.Rules{}, params.CostPerStateByte)
signer := gen.Signer()
gasPrice := big.NewInt(0)
if gen.header.BaseFee != nil {
diff --git a/core/bintrie_witness_test.go b/core/bintrie_witness_test.go
index 5d82fc1df8..9cbb489c4e 100644
--- a/core/bintrie_witness_test.go
+++ b/core/bintrie_witness_test.go
@@ -55,8 +55,9 @@ var (
UBTTime: u64(0),
TerminalTotalDifficulty: common.Big0,
EnableUBTAtGenesis: true,
+ // UBT inherits its blob schedule; nothing to declare here.
BlobScheduleConfig: ¶ms.BlobScheduleConfig{
- UBT: params.DefaultPragueBlobConfig,
+ Prague: params.DefaultPragueBlobConfig,
},
}
)
@@ -64,12 +65,12 @@ var (
func TestProcessUBT(t *testing.T) {
var (
code = common.FromHex(`6060604052600a8060106000396000f360606040526008565b00`)
- intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, nil, true, params.Rules{IsHomestead: true, IsIstanbul: true, IsShanghai: true}, 0)
+ intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, nil, common.Address{}, nil, nil, params.Rules{IsHomestead: true, IsIstanbul: true, IsShanghai: true}, 0)
// 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, params.Rules{IsHomestead: true, IsIstanbul: true, IsShanghai: true}, 0)
+ intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, nil, common.Address{}, nil, nil, params.Rules{IsHomestead: true, IsIstanbul: true, IsShanghai: true}, 0)
signer = types.LatestSigner(testUBTChainConfig)
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
bcdb = rawdb.NewMemoryDatabase() // Database for the blockchain
diff --git a/core/chain_makers.go b/core/chain_makers.go
index d93ce80dca..f02e0341bb 100644
--- a/core/chain_makers.go
+++ b/core/chain_makers.go
@@ -385,6 +385,11 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
if config.DAOForkSupport && config.DAOForkBlock != nil && config.DAOForkBlock.Cmp(b.header.Number) == 0 {
misc.ApplyDAOHardFork(statedb)
}
+ // EIP-7997: insert the deterministic deployment factory at the Amsterdam
+ // activation block via an irregular state transition.
+ if config.IsAmsterdam(b.header.Number, b.header.Time) && !config.IsAmsterdam(parent.Number(), parent.Time()) {
+ misc.ApplyEIP7997(statedb)
+ }
if config.IsPrague(b.header.Number, b.header.Time) || config.IsUBT(b.header.Number, b.header.Time) {
// EIP-2935
diff --git a/core/eip2780_test.go b/core/eip2780_test.go
new file mode 100644
index 0000000000..d9545923ff
--- /dev/null
+++ b/core/eip2780_test.go
@@ -0,0 +1,214 @@
+// 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 .
+
+package core
+
+import (
+ "math/big"
+ "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"
+ "github.com/holiman/uint256"
+)
+
+// TestEIP2780Intrinsic checks the intrinsic-gas decomposition.
+func TestEIP2780Intrinsic(t *testing.T) {
+ var (
+ from = common.HexToAddress("0x1111111111111111111111111111111111111111")
+ to = common.HexToAddress("0x2222222222222222222222222222222222222222")
+ )
+ cases := []struct {
+ name string
+ to *common.Address
+ value *uint256.Int
+ want vm.GasCosts
+ }{
+ {
+ name: "self-transfer",
+ to: &from,
+ value: uint256.NewInt(1),
+ want: vm.GasCosts{RegularGas: params.TxBaseCost2780}, // 12,000
+ },
+ {
+ name: "self-transfer/zero-value",
+ to: &from,
+ value: uint256.NewInt(0),
+ want: vm.GasCosts{RegularGas: params.TxBaseCost2780}, // 12,000
+ },
+ {
+ name: "zero-value call",
+ to: &to,
+ value: uint256.NewInt(0),
+ // TxBaseCost + ColdAccountAccess = 15,000
+ want: vm.GasCosts{RegularGas: params.TxBaseCost2780 + params.ColdAccountAccess2780},
+ },
+ {
+ name: "value transfer to existing EOA",
+ to: &to,
+ value: uint256.NewInt(1),
+ // TxBaseCost + ColdAccountAccess + TxValueCost + TransferLogCost = 21,000
+ want: vm.GasCosts{RegularGas: params.TxBaseCost2780 + params.ColdAccountAccess2780 +
+ params.TxValueCost2780 + params.TransferLogCost2780},
+ },
+ {
+ name: "contract creation, value = 0",
+ to: nil,
+ value: uint256.NewInt(0),
+ // TxBaseCost + CreateAccess = 23,000 regular, plus one account creation in state.
+ want: vm.GasCosts{
+ RegularGas: params.TxBaseCost2780 + params.CreateAccess2780,
+ StateGas: params.AccountCreationSize * params.CostPerStateByte,
+ },
+ },
+ {
+ name: "contract creation, value > 0",
+ to: nil,
+ value: uint256.NewInt(1),
+ // TxBaseCost + CreateAccess + TransferLogCost = 24,756 regular, plus account creation.
+ want: vm.GasCosts{
+ RegularGas: params.TxBaseCost2780 + params.CreateAccess2780 + params.TransferLogCost2780,
+ StateGas: params.AccountCreationSize * params.CostPerStateByte,
+ },
+ },
+ }
+ for _, tc := range cases {
+ t.Run(tc.name, func(t *testing.T) {
+ got, err := IntrinsicGas(nil, nil, nil, from, tc.to, tc.value, rules8037, params.CostPerStateByte)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if got != tc.want {
+ t.Fatalf("gas mismatch: got %+v, want %+v", got, tc.want)
+ }
+ })
+ }
+}
+
+// TestEIP2780Gas checks every "Transaction reference case" in
+// the EIP-2780 specification end-to-end, asserting the two-dimensional charge
+// (intrinsic + top-level + execution) recorded in the block gas pool.
+func TestEIP2780Gas(t *testing.T) {
+ const (
+ cold = params.ColdAccountAccess2780
+ base = params.TxBaseCost2780
+ valueCst = params.TxValueCost2780 + params.TransferLogCost2780
+ )
+ var (
+ existingEOA = common.HexToAddress("0xe0a0000000000000000000000000000000000001")
+ stopContract = common.HexToAddress("0xc0de000000000000000000000000000000000001")
+ delegated = common.HexToAddress("0xde1e000000000000000000000000000000000001")
+ emptyTarget = common.HexToAddress("0x7a76000000000000000000000000000000000001") // never allocated
+ freshEOA = common.HexToAddress("0xbeef000000000000000000000000000000000001") // never allocated
+ )
+ // Shared world: a funded EOA, a STOP contract and an account delegated to a
+ // non-existent (codeless) target. The delegation target is intentionally
+ // absent so resolving it executes no code.
+ base7702 := types.GenesisAlloc{
+ existingEOA: {Balance: big.NewInt(1)},
+ stopContract: {Code: []byte{0x00}}, // STOP
+ delegated: {Code: types.AddressToDelegation(emptyTarget)},
+ }
+ // valueCreateTx builds a contract-creation transaction carrying value.
+ valueCreateTx := func(value int64) *types.Transaction {
+ return types.MustSignNewTx(senderKey, signer8037, &types.DynamicFeeTx{
+ ChainID: cfg8037.ChainID, Nonce: 0, To: nil, Value: big.NewInt(value),
+ Gas: 300_000, GasFeeCap: big.NewInt(0), GasTipCap: big.NewInt(0),
+ })
+ }
+
+ cases := []struct {
+ name string
+ tx *types.Transaction
+ wantRegular, wantState uint64
+ }{
+ // case 1: ETH transfer to self.
+ {"self-transfer", callTx(0, senderAddr, 1, 100_000, nil), base, 0},
+ // case 2: no-transfer to an existing EOA.
+ {"zero-value/eoa", callTx(0, existingEOA, 0, 100_000, nil), base + cold, 0},
+ // case 3: no-transfer to a contract.
+ {"zero-value/contract", callTx(0, stopContract, 0, 100_000, nil), base + cold, 0},
+ // case 4: ETH transfer to an existing EOA.
+ {"value/eoa", callTx(0, existingEOA, 1, 100_000, nil), base + cold + valueCst, 0},
+ // case 5: ETH transfer to a contract.
+ {"value/contract", callTx(0, stopContract, 1, 100_000, nil), base + cold + valueCst, 0},
+ // case 6: no-transfer to a 7702-delegated account.
+ {"zero-value/delegated", callTx(0, delegated, 0, 100_000, nil), base + 2*cold, 0},
+ // case 7: ETH transfer to a 7702-delegated account (no new-account charge).
+ {"value/delegated", callTx(0, delegated, 1, 100_000, nil), base + 2*cold + valueCst, 0},
+ // case 8: ETH transfer creating a new account.
+ {"value/new-account", callTx(0, freshEOA, 1, 300_000, nil), base + cold + valueCst, newAccountState},
+ // case 9: contract-creation transaction, value = 0.
+ {"create/zero-value", createTx(0, 300_000, nil), base + params.CreateAccess2780, newAccountState},
+ // case 10: contract-creation transaction, value > 0.
+ {"create/value", valueCreateTx(1), base + params.CreateAccess2780 + params.TransferLogCost2780, newAccountState},
+ }
+ for _, tc := range cases {
+ t.Run(tc.name, func(t *testing.T) {
+ res, gp, err := applyMsg(t, mkState(senderAlloc(base7702)), tc.tx)
+ if err != nil {
+ t.Fatalf("consensus error: %v", err)
+ }
+ if res.Err != nil {
+ t.Fatalf("execution failed: %v", res.Err)
+ }
+ if gp.cumulativeRegular != tc.wantRegular {
+ t.Errorf("regular gas = %d, want %d", gp.cumulativeRegular, tc.wantRegular)
+ }
+ if gp.cumulativeState != tc.wantState {
+ t.Errorf("state gas = %d, want %d", gp.cumulativeState, tc.wantState)
+ }
+ })
+ }
+}
+
+// TestEIP2780NewAccountFunded verifies that a value transfer creating a new
+// account both materializes and funds the recipient.
+func TestEIP2780NewAccountFunded(t *testing.T) {
+ fresh := common.HexToAddress("0xbeef000000000000000000000000000000000002")
+ sdb := mkState(senderAlloc(nil))
+ if _, _, err := applyMsg(t, sdb, callTx(0, fresh, 1, 300_000, nil)); err != nil {
+ t.Fatal(err)
+ }
+ if !sdb.Exist(fresh) || sdb.GetBalance(fresh).Cmp(uint256.NewInt(1)) != 0 {
+ t.Fatalf("recipient not funded: exist=%v balance=%v", sdb.Exist(fresh), sdb.GetBalance(fresh))
+ }
+}
+
+// TestEIP2780InsufficientGasForCallCharge verifies that a value transfer
+// creating a new account, whose gas limit only covers the 21,000 intrinsic base
+// and not the additional new-account state gas charged before the call executes,
+// halts out of gas. The transaction stays valid (no consensus error) but
+// execution fails and the recipient is not created.
+func TestEIP2780InsufficientGasForCallCharge(t *testing.T) {
+ fresh := common.HexToAddress("0xbeef000000000000000000000000000000000003")
+ sdb := mkState(senderAlloc(nil))
+ res, _, err := applyMsg(t, sdb, callTx(0, fresh, 1, 21_000, nil))
+ if err != nil {
+ t.Fatalf("transaction should remain valid: %v", err)
+ }
+ if res.Err != vm.ErrOutOfGas {
+ t.Fatalf("expected out of gas, got %v", res.Err)
+ }
+ if res.UsedGas != 21_000 {
+ t.Fatalf("expected used gas, got %v", res.UsedGas)
+ }
+ if sdb.Exist(fresh) {
+ t.Fatal("recipient should not be created when the call charge cannot be paid")
+ }
+}
diff --git a/core/eip7997_test.go b/core/eip7997_test.go
new file mode 100644
index 0000000000..4c4d26507b
--- /dev/null
+++ b/core/eip7997_test.go
@@ -0,0 +1,113 @@
+// 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 .
+
+// Tests for EIP-7997: the deterministic deployment factory inserted as an
+// irregular state transition at the Amsterdam activation block.
+
+package core
+
+import (
+ "bytes"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/consensus/misc"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/core/vm"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/params"
+ "github.com/holiman/uint256"
+)
+
+// TestApplyEIP7997 verifies the irregular state transition seeds the factory
+// account with the canonical code and nonce.
+func TestApplyEIP7997(t *testing.T) {
+ sdb := mkState(nil)
+ misc.ApplyEIP7997(sdb)
+
+ if got := sdb.GetCode(params.DeterministicFactoryAddress); !bytes.Equal(got, params.DeterministicFactoryCode) {
+ t.Fatalf("factory code mismatch:\n got %x\nwant %x", got, params.DeterministicFactoryCode)
+ }
+ if got := sdb.GetNonce(params.DeterministicFactoryAddress); got != 1 {
+ t.Fatalf("factory nonce = %d, want %d", got, 1)
+ }
+}
+
+// TestApplyEIP7997Existing checks that a chain which already hosts the factory
+// (for example via its keyless creation transaction) is left untouched, so the
+// transition never rewrites an existing nonce.
+func TestApplyEIP7997Existing(t *testing.T) {
+ sdb := mkState(types.GenesisAlloc{
+ params.DeterministicFactoryAddress: {Code: params.DeterministicFactoryCode, Nonce: 5},
+ })
+ misc.ApplyEIP7997(sdb)
+
+ if got := sdb.GetNonce(params.DeterministicFactoryAddress); got != 5 {
+ t.Fatalf("existing factory nonce overwritten: got %d, want 5", got)
+ }
+}
+
+// TestApplyEIP7997WrongCode checks that an account occupying the factory address
+// with the wrong code is force-overwritten with the canonical runtime code, while
+// a pre-existing non-zero nonce is preserved.
+func TestApplyEIP7997WrongCode(t *testing.T) {
+ sdb := mkState(types.GenesisAlloc{
+ params.DeterministicFactoryAddress: {Code: []byte{0x60, 0x00}, Nonce: 7},
+ })
+ misc.ApplyEIP7997(sdb)
+
+ if got := sdb.GetCode(params.DeterministicFactoryAddress); !bytes.Equal(got, params.DeterministicFactoryCode) {
+ t.Fatalf("factory code not overwritten:\n got %x\nwant %x", got, params.DeterministicFactoryCode)
+ }
+ if got := sdb.GetNonce(params.DeterministicFactoryAddress); got != 7 {
+ t.Fatalf("factory nonce = %d, want %d (existing nonce must be preserved)", got, 7)
+ }
+}
+
+// TestEIP7997FactoryDeploys exercises the inserted factory bytecode: calling it
+// with a salt followed by init code must CREATE2-deploy the contract at the
+// canonical deterministic address and return that address (20 bytes, unpadded).
+func TestEIP7997FactoryDeploys(t *testing.T) {
+ sdb := mkState(nil)
+ misc.ApplyEIP7997(sdb)
+
+ var (
+ caller = common.Address{0xca}
+ salt [32]byte
+ // initcode returning the single-byte runtime 0xfe:
+ // PUSH1 0xfe PUSH1 0x00 MSTORE8 PUSH1 0x01 PUSH1 0x00 RETURN
+ initcode = common.FromHex("60fe60005360016000f3")
+ )
+ salt[31] = 0x42
+
+ input := append(append([]byte{}, salt[:]...), initcode...)
+
+ ret, _, err := amsterdamCoreEVM(sdb).Call(caller, params.DeterministicFactoryAddress, input, vm.NewGasBudget(10_000_000, 0), new(uint256.Int))
+ if err != nil {
+ t.Fatalf("factory call failed: %v", err)
+ }
+
+ want := crypto.CreateAddress2(params.DeterministicFactoryAddress, salt, crypto.Keccak256(initcode))
+ if len(ret) != 20 {
+ t.Fatalf("factory returned %d bytes, want 20", len(ret))
+ }
+ if got := common.BytesToAddress(ret); got != want {
+ t.Fatalf("factory returned address %x, want %x", got, want)
+ }
+ if code := sdb.GetCode(want); !bytes.Equal(code, []byte{0xfe}) {
+ t.Fatalf("deployed runtime code = %x, want fe", code)
+ }
+}
diff --git a/core/eip8037_test.go b/core/eip8037_test.go
index 8626c0b090..6d506cba96 100644
--- a/core/eip8037_test.go
+++ b/core/eip8037_test.go
@@ -191,7 +191,7 @@ var (
// A creation tx's intrinsic gas pre-charges one account creation as state gas.
func TestCreateTxIntrinsicChargesAccountUnconditionally(t *testing.T) {
- cost, err := IntrinsicGas(nil, nil, nil, true, rules8037, params.CostPerStateByte)
+ cost, err := IntrinsicGas(nil, nil, nil, common.Address{}, nil, nil, rules8037, params.CostPerStateByte)
if err != nil {
t.Fatal(err)
}
@@ -325,7 +325,7 @@ func clearSlots(addr common.Address, n int) (types.GenesisAlloc, []byte) {
// tx_gas_used_before_refund (peak) exceeds the post-refund gas used.
func TestGasUsedBeforeRefund(t *testing.T) {
c := common.HexToAddress("0xc1ea0")
- alloc, _ := clearSlots(c, 1)
+ alloc, _ := clearSlots(c, 4)
res, _, err := applyMsg(t, mkState(senderAlloc(alloc)), callTx(0, c, 0, 1_000_000, nil))
if err != nil {
t.Fatal(err)
@@ -351,8 +351,8 @@ func TestRefundCappedAt20Percent(t *testing.T) {
// The EIP-7623 calldata floor is applied after the refund.
func TestRefundCalldataFloorAfterRefund(t *testing.T) {
data := make([]byte, 1000) // all-zero calldata: floor dominates a bare call
- floor, _ := FloorDataGas(rules8037, data, nil)
to := common.HexToAddress("0xeeee")
+ floor, _ := FloorDataGas(rules8037, senderAddr, &to, new(uint256.Int), data, nil)
res, _, err := applyMsg(t, mkState(senderAlloc(nil)), callTx(0, to, 0, 1_000_000, data))
if err != nil {
t.Fatal(err)
@@ -367,7 +367,7 @@ func TestRefundFloorNegatesRefund(t *testing.T) {
c := common.HexToAddress("0xc1ea1")
alloc, _ := clearSlots(c, 1)
data := make([]byte, 1000)
- floor, _ := FloorDataGas(rules8037, data, nil)
+ floor, _ := FloorDataGas(rules8037, senderAddr, &c, new(uint256.Int), data, nil)
res, _, err := applyMsg(t, mkState(senderAlloc(alloc)), callTx(0, c, 0, 1_000_000, data))
if err != nil {
t.Fatal(err)
@@ -474,7 +474,7 @@ var delegate8037 = common.HexToAddress("0xde1e8a7e")
// Intrinsic gas pre-charges the worst-case (account + indicator) per auth.
func TestAuthIntrinsicWorstCase(t *testing.T) {
- cost, err := IntrinsicGas(nil, nil, []types.SetCodeAuthorization{{}}, false, rules8037, params.CostPerStateByte)
+ cost, err := IntrinsicGas(nil, nil, []types.SetCodeAuthorization{{}}, common.Address{}, &delegate8037, nil, rules8037, params.CostPerStateByte)
if err != nil {
t.Fatal(err)
}
diff --git a/core/eip8038_test.go b/core/eip8038_test.go
new file mode 100644
index 0000000000..d8977c6dd2
--- /dev/null
+++ b/core/eip8038_test.go
@@ -0,0 +1,110 @@
+// 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 .
+
+// EIP-8038 authorization accounting tests. The per-authorization intrinsic gas
+// pre-charges ACCOUNT_WRITE (regular) on top of REGULAR_PER_AUTH_BASE_COST.
+// applyAuthorization refunds that ACCOUNT_WRITE to the refund counter in exactly
+// the cases where no new account leaf is written: an invalid authorization, or
+// an authority whose account already exists. These white-box tests invoke
+// applyAuthorization directly and read the raw refund counter, so they observe
+// the refund before the EIP-3529 cap is applied.
+
+package core
+
+import (
+ "math/big"
+ "testing"
+
+ "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/crypto"
+ "github.com/ethereum/go-ethereum/params"
+ "github.com/holiman/uint256"
+)
+
+// newAuthTestTransition builds a minimal stateTransition with a state reservoir,
+// suitable for calling applyAuthorization directly.
+func newAuthTestTransition(sdb *state.StateDB) *stateTransition {
+ st := newStateTransition(amsterdamCoreEVM(sdb), &Message{}, NewGasPool(30_000_000))
+ st.gasRemaining = vm.NewGasBudget(0, 1_000_000) // reservoir for state-gas refills
+ return st
+}
+
+// A net-new delegation on a fresh authority writes a new account leaf, so the
+// intrinsic ACCOUNT_WRITE stands (no refund).
+func TestAuthAccountWriteNetNewNoRefund(t *testing.T) {
+ auth, _ := signAuth(t, authKeyA, delegate8037, 0)
+ st := newAuthTestTransition(mkState(senderAlloc(nil)))
+ if err := st.applyAuthorization(rules8037, &auth, map[common.Address]bool{}); err != nil {
+ t.Fatal(err)
+ }
+ if got := st.state.GetRefund(); got != 0 {
+ t.Fatalf("refund = %d, want 0 (net-new account write)", got)
+ }
+}
+
+// A pre-existing authority writes no new account leaf, so the intrinsic
+// ACCOUNT_WRITE is refunded.
+func TestAuthAccountWriteExistsRefund(t *testing.T) {
+ auth, authority := signAuth(t, authKeyA, delegate8037, 0)
+ st := newAuthTestTransition(mkState(senderAlloc(types.GenesisAlloc{authority: {Balance: big.NewInt(1)}})))
+ if err := st.applyAuthorization(rules8037, &auth, map[common.Address]bool{}); err != nil {
+ t.Fatal(err)
+ }
+ if got := st.state.GetRefund(); got != params.AccountWriteAmsterdam {
+ t.Fatalf("refund = %d, want %d (account already exists)", got, params.AccountWriteAmsterdam)
+ }
+}
+
+// An invalid authorization is skipped without writing any account leaf, so its
+// intrinsic ACCOUNT_WRITE is refunded.
+func TestAuthAccountWriteInvalidRefund(t *testing.T) {
+ k, _ := crypto.HexToECDSA(authKeyA)
+ bad, _ := types.SignSetCode(k, types.SetCodeAuthorization{
+ ChainID: *uint256.NewInt(999), Address: delegate8037, Nonce: 0, // wrong chain id
+ })
+ st := newAuthTestTransition(mkState(senderAlloc(nil)))
+ if err := st.applyAuthorization(rules8037, &bad, map[common.Address]bool{}); err == nil {
+ t.Fatal("expected invalid-authorization error")
+ }
+ if got := st.state.GetRefund(); got != params.AccountWriteAmsterdam {
+ t.Fatalf("refund = %d, want %d (invalid authorization)", got, params.AccountWriteAmsterdam)
+ }
+}
+
+// The same authority across two authorizations writes its account leaf only
+// once: the first auth pays ACCOUNT_WRITE, the second (which now sees the
+// account as existing) is refunded.
+func TestAuthAccountWriteDuplicateOnce(t *testing.T) {
+ a0, _ := signAuth(t, authKeyA, delegate8037, 0)
+ a1, _ := signAuth(t, authKeyA, delegate8037, 1)
+ st := newAuthTestTransition(mkState(senderAlloc(nil)))
+ delegates := map[common.Address]bool{}
+ if err := st.applyAuthorization(rules8037, &a0, delegates); err != nil {
+ t.Fatal(err)
+ }
+ if got := st.state.GetRefund(); got != 0 {
+ t.Fatalf("refund after first auth = %d, want 0", got)
+ }
+ if err := st.applyAuthorization(rules8037, &a1, delegates); err != nil {
+ t.Fatal(err)
+ }
+ if got := st.state.GetRefund(); got != params.AccountWriteAmsterdam {
+ t.Fatalf("refund after duplicate auth = %d, want %d", got, params.AccountWriteAmsterdam)
+ }
+}
diff --git a/core/eip8246_test.go b/core/eip8246_test.go
new file mode 100644
index 0000000000..18049d2279
--- /dev/null
+++ b/core/eip8246_test.go
@@ -0,0 +1,102 @@
+// 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 .
+
+package core
+
+import (
+ "math/big"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/consensus/beacon"
+ "github.com/ethereum/go-ethereum/consensus/ethash"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/params"
+)
+
+// TestEIP8246SelfdestructNoBurn verifies that, once EIP-8246 is active
+// (Amsterdam), a contract that is created and self-destructs to itself within
+// the same transaction keeps its balance instead of burning it: the account
+// survives as a balance-only account (no code, zero nonce, balance preserved).
+//
+// https://eips.ethereum.org/EIPS/eip-8246
+func TestEIP8246SelfdestructNoBurn(t *testing.T) {
+ var (
+ key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
+ addr1 = crypto.PubkeyToAddress(key1.PublicKey)
+ config = *params.MergedTestChainConfig
+ signer = types.LatestSigner(&config)
+ engine = beacon.New(ethash.NewFaker())
+ value = big.NewInt(1_000_000)
+ // Init code: ADDRESS (0x30) ; SELFDESTRUCT (0xff). The created contract
+ // self-destructs to itself during its own creation transaction.
+ initcode = common.FromHex("30ff")
+ )
+ // TODO: drop this hacky Amsterdam config initialization once the final
+ // Amsterdam config is available (mirrors TestEthTransferLogs).
+ config.AmsterdamTime = new(uint64)
+
+ gspec := &Genesis{
+ Config: &config,
+ Alloc: types.GenesisAlloc{
+ addr1: {Balance: newGwei(1_000_000_000)},
+ },
+ }
+ // The contract created by addr1's first (nonce 0) transaction.
+ created := crypto.CreateAddress(addr1, 0)
+
+ db, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) {
+ tx := types.MustSignNewTx(key1, signer, &types.DynamicFeeTx{
+ ChainID: gspec.Config.ChainID,
+ Nonce: 0,
+ To: nil, // contract creation
+ Gas: 1_000_000,
+ GasFeeCap: newGwei(5),
+ GasTipCap: newGwei(5),
+ Value: value,
+ Data: initcode,
+ })
+ b.AddTx(tx)
+ })
+
+ chain, err := NewBlockChain(db, gspec, engine, nil)
+ if err != nil {
+ t.Fatalf("failed to create chain: %v", err)
+ }
+ defer chain.Stop()
+ // Read the post-state of the generated block directly. InsertChain is avoided
+ // on purpose: it would additionally verify the EIP-7928 block access list,
+ // which the chain-generation harness on this branch does not yet populate
+ // consistently — an orthogonal concern to the EIP-8246 state semantics under
+ // test here.
+ state, err := chain.StateAt(blocks[0].Header())
+ if err != nil {
+ t.Fatalf("failed to obtain block state: %v", err)
+ }
+ // EIP-8246: the self-destructed, freshly-created contract keeps its balance
+ // rather than burning it, so the account survives.
+ if got := state.GetBalance(created); got.ToBig().Cmp(value) != 0 {
+ t.Errorf("created account balance = %v, want %v (EIP-8246: balance must be preserved, not burned)", got, value)
+ }
+ // It survives as a balance-only account: nonce reset to 0 and no code.
+ if got := state.GetNonce(created); got != 0 {
+ t.Errorf("created account nonce = %d, want 0", got)
+ }
+ if got := state.GetCodeSize(created); got != 0 {
+ t.Errorf("created account code size = %d, want 0 (code must be cleared)", got)
+ }
+}
diff --git a/core/error.go b/core/error.go
index 7dd5b8a432..26b007f9d9 100644
--- a/core/error.go
+++ b/core/error.go
@@ -66,11 +66,6 @@ var (
// have enough funds for transfer(topmost call only).
ErrInsufficientFundsForTransfer = errors.New("insufficient funds for transfer")
- // ErrInsufficientBalanceWitness is returned if the transaction sender has enough
- // funds to cover the transfer, but not enough to pay for witness access/modification
- // costs for the transaction
- ErrInsufficientBalanceWitness = errors.New("insufficient funds to cover witness access costs for transaction")
-
// ErrInsufficientFunds is returned if the total cost of executing a transaction
// is higher than the balance of the user's account.
ErrInsufficientFunds = errors.New("insufficient funds for gas * price + value")
diff --git a/core/eth_transfer_logs_test.go b/core/eth_transfer_logs_test.go
index 815b56b588..a5a5f1894a 100644
--- a/core/eth_transfer_logs_test.go
+++ b/core/eth_transfer_logs_test.go
@@ -78,11 +78,7 @@ func testEthTransferLogs(t *testing.T, value uint64) {
engine = beacon.New(ethash.NewFaker())
)
- //TODO remove this hacky config initialization when final Amsterdam config is available
config.AmsterdamTime = new(uint64)
- blobConfig := *config.BlobScheduleConfig
- blobConfig.Amsterdam = blobConfig.Osaka
- config.BlobScheduleConfig = &blobConfig
gspec := &Genesis{
Config: &config,
diff --git a/core/genesis.go b/core/genesis.go
index ec3e70a975..7fad915ce2 100644
--- a/core/genesis.go
+++ b/core/genesis.go
@@ -277,10 +277,11 @@ func (e *GenesisMismatchError) Error() string {
// ChainOverrides contains the changes to chain config.
type ChainOverrides struct {
- OverrideOsaka *uint64
- OverrideBPO1 *uint64
- OverrideBPO2 *uint64
- OverrideUBT *uint64
+ OverrideOsaka *uint64
+ OverrideAmsterdam *uint64
+ OverrideBPO1 *uint64
+ OverrideBPO2 *uint64
+ OverrideUBT *uint64
}
// apply applies the chain overrides on the supplied chain config.
@@ -291,6 +292,9 @@ func (o *ChainOverrides) apply(cfg *params.ChainConfig) error {
if o.OverrideOsaka != nil {
cfg.OsakaTime = o.OverrideOsaka
}
+ if o.OverrideAmsterdam != nil {
+ cfg.AmsterdamTime = o.OverrideAmsterdam
+ }
if o.OverrideBPO1 != nil {
cfg.BPO1Time = o.OverrideBPO1
}
@@ -721,6 +725,8 @@ func DeveloperGenesisBlock(gasLimit uint64, faucet *common.Address) *Genesis {
// EIP-8282 - Builder Execution Requests
params.BuilderDepositAddress: {Nonce: 1, Code: params.BuilderDepositCode, Balance: common.Big0},
params.BuilderExitAddress: {Nonce: 1, Code: params.BuilderExitCode, Balance: common.Big0},
+ // EIP-7997 - Deterministic deployment factory
+ params.DeterministicFactoryAddress: {Nonce: 1, Code: params.DeterministicFactoryCode, Balance: common.Big0},
},
}
if faucet != nil {
diff --git a/core/genesis_test.go b/core/genesis_test.go
index 67b88fc415..80ee53493e 100644
--- a/core/genesis_test.go
+++ b/core/genesis_test.go
@@ -293,8 +293,6 @@ func TestBinaryGenesisCommit(t *testing.T) {
BlobScheduleConfig: ¶ms.BlobScheduleConfig{
Cancun: params.DefaultCancunBlobConfig,
Prague: params.DefaultPragueBlobConfig,
- Osaka: params.DefaultOsakaBlobConfig,
- UBT: params.DefaultPragueBlobConfig,
},
}
diff --git a/core/rawdb/freezer_meta.go b/core/rawdb/freezer_meta.go
index 3cda9ae45c..03c26f7daf 100644
--- a/core/rawdb/freezer_meta.go
+++ b/core/rawdb/freezer_meta.go
@@ -112,7 +112,7 @@ func decodeV2(file *os.File) *freezerTableMeta {
return nil
}
if o.Offset > math.MaxInt64 {
- log.Error("Invalid flushOffset %d in freezer metadata", o.Offset, "file", file.Name())
+ log.Error("Invalid flushOffset %d in freezer metadata", "offset", o.Offset, "file", file.Name())
return nil
}
return &freezerTableMeta{
diff --git a/core/rawdb/freezer_table.go b/core/rawdb/freezer_table.go
index c770e89989..d18b75c2f7 100644
--- a/core/rawdb/freezer_table.go
+++ b/core/rawdb/freezer_table.go
@@ -611,9 +611,18 @@ func (t *freezerTable) truncateHead(items uint64) error {
if existing <= items {
return nil
}
- if items < t.itemHidden.Load() {
+
+ hidden := t.itemHidden.Load()
+
+ if items < hidden {
+ if existing == hidden {
+ // Empty table means that it is newly added. Its tail would be
+ // at the head, so we have to align the table down to the new head.
+ return t.resetTo(items)
+ }
return errors.New("truncation below tail")
}
+
// We need to truncate, save the old size for metrics tracking
oldSize, err := t.sizeNolock()
if err != nil {
diff --git a/core/rawdb/freezer_test.go b/core/rawdb/freezer_test.go
index 0fc4f90011..2197a29772 100644
--- a/core/rawdb/freezer_test.go
+++ b/core/rawdb/freezer_test.go
@@ -516,7 +516,7 @@ func TestFreezerCloseSync(t *testing.T) {
if err := f.SyncAncient(); err == nil {
t.Fatalf("want error, have nil")
} else if have, want := err.Error(), "[closed closed]"; have != want {
- t.Fatalf("want %v, have %v", have, want)
+ t.Fatalf("want %v, have %v", want, have)
}
}
diff --git a/core/state/database.go b/core/state/database.go
index 3b1e627f28..1001a4b4cf 100644
--- a/core/state/database.go
+++ b/core/state/database.go
@@ -107,12 +107,18 @@ type Trie interface {
// in the trie with provided address.
UpdateAccount(address common.Address, account *types.StateAccount, codeLen int) error
+ // UpdateAccountBatch attempts to update a list of accounts in the batch manner.
+ UpdateAccountBatch(addresses []common.Address, accounts []*types.StateAccount, codeLengths []int) error
+
// UpdateStorage associates key with value in the trie. If value has length zero,
// any existing value is deleted from the trie. The value bytes must not be modified
// by the caller while they are stored in the trie. If a node was not found in the
// database, a trie.MissingNodeError is returned.
UpdateStorage(addr common.Address, key, value []byte) error
+ // UpdateStorageBatch attempts to update a list of storages in the batch manner.
+ UpdateStorageBatch(_ common.Address, keys [][]byte, values [][]byte) error
+
// DeleteAccount abstracts an account deletion from the trie.
DeleteAccount(address common.Address) error
diff --git a/core/state/statedb.go b/core/state/statedb.go
index 1c49d46020..09a896a89a 100644
--- a/core/state/statedb.go
+++ b/core/state/statedb.go
@@ -614,16 +614,17 @@ func (s *StateDB) getStateObject(addr common.Address) *stateObject {
}
s.AccountReads += time.Since(start)
- // Short circuit if the account is not found
- if acct == nil {
- return nil
- }
- // Schedule the resolved account for prefetching if it's enabled.
+ // Schedule the account path for prefetching if it's enabled. Even if the
+ // account is absent, the trie path proves its non-existence for witnesses.
if s.prefetcher != nil {
if err = s.prefetcher.prefetch(common.Hash{}, s.originalRoot, common.Address{}, []common.Address{addr}, nil, true); err != nil {
log.Error("Failed to prefetch account", "addr", addr, "err", err)
}
}
+ // Short circuit if the account is not found
+ if acct == nil {
+ return nil
+ }
// Insert into the live set
obj := newObject(s, addr, acct)
s.setStateObject(obj)
@@ -764,50 +765,15 @@ func (s *StateDB) GetRefund() uint64 {
return s.refund
}
-type removedAccountWithBalance struct {
- address common.Address
- balance *uint256.Int
-}
-
-// LogsForBurnAccounts returns the eth burn logs for accounts scheduled for
-// removal which still have positive balance. The purpose of this function is
-// to handle a corner case of EIP-7708 where a self-destructed account might
-// still receive funds between sending/burning its previous balance and actual
-// removal. In this case the burning of these remaining balances still need to
-// be logged.
-// Specification EIP-7708: https://eips.ethereum.org/EIPS/eip-7708
-//
-// This function should only be invoked at the transaction boundary, specifically
-// before the Finalise.
-func (s *StateDB) LogsForBurnAccounts() []*types.Log {
- var list []removedAccountWithBalance
- 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,
- balance: obj.Balance(),
- })
- }
- }
- if list == nil {
- return nil
- }
- sort.Slice(list, func(i, j int) bool {
- return list[i].address.Cmp(list[j].address) < 0
- })
- logs := make([]*types.Log, len(list))
- for i, acct := range list {
- logs[i] = types.EthBurnLog(acct.address, acct.balance)
- }
- return logs
-}
-
// Finalise finalises the state by removing the destructed objects and clears
// the journal as well as the refunds. Finalise, however, will not push any updates
// into the tries just yet. Only IntermediateRoot or Commit will do that.
func (s *StateDB) Finalise(deleteEmptyObjects bool) *bal.ConstructionBlockAccessList {
+ if s.stateAccessList != nil {
+ return s.finaliseAmsterdam(deleteEmptyObjects)
+ }
addressesToPrefetch := make([]common.Address, 0, len(s.journal.mutations))
- for addr, state := range s.journal.mutations {
+ for addr := range s.journal.mutations {
obj, exist := s.stateObjects[addr]
if !exist {
// RIPEMD160 (0x03) gets an extra dirty marker for a historical
@@ -831,46 +797,103 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) *bal.ConstructionBlockAccess
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)
}
+ addressesToPrefetch = append(addressesToPrefetch, addr) // Copy needed for closure
+ }
+ if s.prefetcher != nil && len(addressesToPrefetch) > 0 {
+ if err := s.prefetcher.prefetch(common.Hash{}, s.originalRoot, common.Address{}, addressesToPrefetch, nil, false); err != nil {
+ log.Error("Failed to prefetch addresses", "addresses", len(addressesToPrefetch), "err", err)
+ }
+ }
+ // Invalidate journal because reverting across transactions is not allowed.
+ s.clearJournalAndRefund()
+
+ return nil
+}
+
+func (s *StateDB) recordAccessListChanges(addr common.Address, state *journalMutationState) {
+ var (
+ balance = uint256.NewInt(0)
+ nonce uint64
+ )
+ obj := s.stateObjects[addr] // nil when the account was removed
+ if obj != nil {
+ balance, nonce = obj.Balance(), obj.Nonce()
+ }
+ if state.balanceSet && balance.Cmp(state.balance) != 0 {
+ s.stateAccessList.BalanceChange(s.blockAccessIndex, addr, balance)
+ }
+ if state.nonceSet && nonce != state.nonce {
+ s.stateAccessList.NonceChange(addr, s.blockAccessIndex, nonce)
+ }
+ if state.codeSet {
+ var code []byte
+ if obj != nil {
+ code = obj.Code()
+ }
+ if !bytes.Equal(code, state.code) {
+ s.stateAccessList.CodeChange(addr, s.blockAccessIndex, code)
+ }
+ }
+}
+
+// finaliseAmsterdam is the Amsterdam-and-later variant of Finalise.
+func (s *StateDB) finaliseAmsterdam(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 {
+ // 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
+ }
+ switch {
+ case obj.selfDestructed:
+ // EIP-8264: accounts marked for self-destruction, instead of
+ // being deleted, are modified as follows:
+ // - nonce is reset to 0,
+ // - balance is unchanged,
+ // - code is cleared,
+ // - all storage is cleared
+ if !obj.Balance().IsZero() {
+ o := newObject(s, obj.address, obj.origin)
+ o.setBalance(new(uint256.Int).Set(obj.Balance()))
+ s.setStateObject(o)
+ s.markUpdate(addr)
+ } else {
+ delete(s.stateObjects, obj.address)
+ s.markDelete(addr)
+ if _, ok := s.stateObjectsDestruct[obj.address]; !ok {
+ s.stateObjectsDestruct[obj.address] = obj
+ }
+ }
+
+ case deleteEmptyObjects && obj.empty():
+ // EIP-161: a touched, empty account is removed.
+ delete(s.stateObjects, obj.address)
+ s.markDelete(addr)
+ if _, ok := s.stateObjectsDestruct[obj.address]; !ok {
+ s.stateObjectsDestruct[obj.address] = obj
+ }
+
+ default:
+ obj.finalise()
+ s.markUpdate(addr)
+ }
+ // Aggregate the resulting account metadata change
+ // into the block-level access list.
+ s.recordAccessListChanges(addr, state)
+
// At this point, also ship the address off to the precacher. The precacher
// will start loading tries, and when the change is eventually committed,
// the commit-phase will be a lot faster
diff --git a/core/state/statedb_hooked.go b/core/state/statedb_hooked.go
index 98d01343a4..73e8253311 100644
--- a/core/state/statedb_hooked.go
+++ b/core/state/statedb_hooked.go
@@ -230,10 +230,6 @@ func (s *hookedStateDB) AddLog(log *types.Log) {
}
}
-func (s *hookedStateDB) LogsForBurnAccounts() []*types.Log {
- return s.inner.LogsForBurnAccounts()
-}
-
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.
@@ -256,18 +252,24 @@ func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) *bal.ConstructionBlock
return bytes.Compare(selfDestructedAddrs[i][:], selfDestructedAddrs[j][:]) < 0
})
+ // EIP-8246 (Amsterdam) removes the SELFDESTRUCT burn: a self-destructed
+ // account that retains a non-zero balance is preserved as a balance-only
+ // account rather than removed, so its balance is no longer burnt.
+ burnsBalance := s.inner.stateAccessList == nil
+
for _, addr := range selfDestructedAddrs {
obj := s.inner.stateObjects[addr]
// Bingo: state object was self-destructed, call relevant hooks.
- // If ether was sent to account post-selfdestruct, record as burnt.
- if s.hooks.OnBalanceChange != nil {
+ if burnsBalance && s.hooks.OnBalanceChange != nil {
if bal := obj.Balance(); bal.Sign() != 0 {
s.hooks.OnBalanceChange(addr, bal.ToBig(), new(big.Int), tracing.BalanceDecreaseSelfdestructBurn)
}
}
// Nonce is set to reset on self-destruct.
+ //
+ // TODO(rjl) shall we emit the nonce change if the pre-tx nonce was zero?
if s.hooks.OnNonceChangeV2 != nil {
s.hooks.OnNonceChangeV2(addr, obj.Nonce(), 0, tracing.NonceChangeSelfdestruct)
} else if s.hooks.OnNonceChange != nil {
diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go
index 0bf9b50e7b..6f4282054e 100644
--- a/core/state/statedb_test.go
+++ b/core/state/statedb_test.go
@@ -33,6 +33,7 @@ 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/core/stateless"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
@@ -998,6 +999,45 @@ func TestDeleteCreateRevert(t *testing.T) {
}
}
+func TestWitnessIncludesAbsentAccountReads(t *testing.T) {
+ db := NewDatabaseForTesting()
+ state, _ := New(types.EmptyRootHash, db)
+ for i := byte(0); i < 3; i++ {
+ addr := common.Address{i + 1}
+ state.SetBalance(addr, uint256.NewInt(uint64(i+1)), tracing.BalanceChangeUnspecified)
+ }
+ root, err := state.Commit(0, false, false)
+ if err != nil {
+ t.Fatalf("failed to commit initial state: %v", err)
+ }
+ state, err = New(root, db)
+ if err != nil {
+ t.Fatalf("failed to reopen state: %v", err)
+ }
+
+ witness := &stateless.Witness{
+ Codes: make(map[string]struct{}),
+ State: make(map[string]struct{}),
+ }
+ state.StartPrefetcher("test", witness)
+ missing := common.HexToAddress("0x017655eac00c837122cabbbc0dd604a196906648")
+ if balance := state.GetBalance(missing); balance.Sign() != 0 {
+ t.Fatalf("unexpected balance for absent account: %v", balance)
+ }
+ if err := state.Error(); err != nil {
+ t.Fatalf("unexpected state error after read: %v", err)
+ }
+ if got := state.IntermediateRoot(false); got != root {
+ t.Fatalf("unexpected root after read-only access: have %x want %x", got, root)
+ }
+ if err := state.Error(); err != nil {
+ t.Fatalf("unexpected state error after root calculation: %v", err)
+ }
+ if len(witness.State) == 0 {
+ t.Fatal("missing witness nodes for absent account read")
+ }
+}
+
// TestMissingTrieNodes tests that if the StateDB fails to load parts of the trie,
// the Commit operation fails with an error
// If we are missing trie nodes, we should not continue writing to the trie
diff --git a/core/state_processor.go b/core/state_processor.go
index 48a8da4062..27009cb49a 100644
--- a/core/state_processor.go
+++ b/core/state_processor.go
@@ -81,6 +81,10 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated
if config.DAOForkSupport && config.DAOForkBlock != nil && config.DAOForkBlock.Cmp(block.Number()) == 0 {
misc.ApplyDAOHardFork(tracingStateDB)
}
+ parent := p.chain.GetHeader(block.ParentHash(), block.NumberU64()-1)
+ if parent == nil {
+ return nil, fmt.Errorf("missing parent %#x", block.ParentHash())
+ }
var (
context = NewEVMBlockContext(header, p.chain, nil)
signer = types.MakeSigner(config, header.Number, header.Time)
@@ -88,12 +92,12 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated
blockAccessList = bal.NewConstructionBlockAccessList()
)
defer evm.Release()
+
if jumpDestCache != nil {
evm.SetJumpDestCache(jumpDestCache)
}
-
// Run the pre-execution system calls
- blockAccessList.Merge(PreExecution(ctx, block.BeaconRoot(), block.ParentHash(), config, evm, block.Number(), block.Time()))
+ blockAccessList.Merge(PreExecution(ctx, block.BeaconRoot(), parent, config, evm, block.Number(), block.Time()))
// Iterate over and process the individual transactions
for i, tx := range block.Transactions() {
@@ -137,14 +141,20 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated
}, nil
}
-// PreExecution processes pre-execution system calls.
-func PreExecution(ctx context.Context, beaconRoot *common.Hash, parent common.Hash, config *params.ChainConfig, evm *vm.EVM, number *big.Int, time uint64) *bal.ConstructionBlockAccessList {
+// PreExecution processes pre-execution state changes and system calls.
+func PreExecution(ctx context.Context, beaconRoot *common.Hash, parent *types.Header, config *params.ChainConfig, evm *vm.EVM, number *big.Int, time uint64) *bal.ConstructionBlockAccessList {
_, _, spanEnd := telemetry.StartSpan(ctx, "core.preExecution")
defer spanEnd(nil)
var blockAccessList *bal.ConstructionBlockAccessList
if config.IsAmsterdam(number, time) {
blockAccessList = bal.NewConstructionBlockAccessList()
+
+ // EIP-7997: insert the deterministic deployment factory at the Amsterdam
+ // activation block via an irregular state transition.
+ if !config.IsAmsterdam(parent.Number, parent.Time) {
+ misc.ApplyEIP7997(evm.StateDB)
+ }
}
// EIP-4788
if beaconRoot != nil {
@@ -152,7 +162,7 @@ func PreExecution(ctx context.Context, beaconRoot *common.Hash, parent common.Ha
}
// EIP-2935
if config.IsPrague(number, time) || config.IsUBT(number, time) {
- ProcessParentBlockHash(parent, evm, blockAccessList)
+ ProcessParentBlockHash(parent.Hash(), evm, blockAccessList)
}
return blockAccessList
}
diff --git a/core/state_transition.go b/core/state_transition.go
index bbeb163b16..a3a9efeaa4 100644
--- a/core/state_transition.go
+++ b/core/state_transition.go
@@ -68,23 +68,26 @@ 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 bool, rules params.Rules, costPerStateByte uint64) (vm.GasCosts, error) {
+func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.SetCodeAuthorization, from common.Address, to *common.Address, value *uint256.Int, rules params.Rules, costPerStateByte uint64) (vm.GasCosts, error) {
+ isContractCreation := to == nil
+
// Set the starting gas for the raw transaction
var gas vm.GasCosts
- if isContractCreation && rules.IsHomestead {
- if rules.IsAmsterdam {
- gas.RegularGas = params.TxGas + params.CreateGasAmsterdam
+ if rules.IsAmsterdam {
+ gas.RegularGas = intrinsicBaseGasEIP2780(from, to, value)
+ if isContractCreation {
+ // New-account creation is charged as state gas (EIP-8037).
gas.StateGas = params.AccountCreationSize * costPerStateByte
- } else {
- gas.RegularGas = params.TxGasContractCreation
}
+ } else if isContractCreation && rules.IsHomestead {
+ gas.RegularGas = params.TxGasContractCreation
} else {
gas.RegularGas = params.TxGas
}
// Add gas for authorizations
if authList != nil {
if rules.IsAmsterdam {
- gas.RegularGas += uint64(len(authList)) * params.TxAuthTupleRegularGas
+ gas.RegularGas += uint64(len(authList)) * (params.AccountWriteAmsterdam + params.RegularPerAuthBaseCost)
gas.StateGas += uint64(len(authList)) * (params.AuthorizationCreationSize + params.AccountCreationSize) * costPerStateByte
} else {
gas.RegularGas += uint64(len(authList)) * params.CallNewAccountGas
@@ -123,14 +126,22 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set
if accessList != nil {
addresses := uint64(len(accessList))
storageKeys := uint64(accessList.StorageKeys())
- if (math.MaxUint64-gas.RegularGas)/params.TxAccessListAddressGas < addresses {
+
+ // Amsterdam re-prices the per-entry access-list cost
+ addressCost := params.TxAccessListAddressGas
+ storageKeyCost := params.TxAccessListStorageKeyGas
+ if rules.IsAmsterdam {
+ addressCost = params.TxAccessListAddressGasAmsterdam
+ storageKeyCost = params.TxAccessListStorageKeyGasAmsterdam
+ }
+ if (math.MaxUint64-gas.RegularGas)/addressCost < addresses {
return vm.GasCosts{}, ErrGasUintOverflow
}
- gas.RegularGas += addresses * params.TxAccessListAddressGas
- if (math.MaxUint64-gas.RegularGas)/params.TxAccessListStorageKeyGas < storageKeys {
+ gas.RegularGas += addresses * addressCost
+ if (math.MaxUint64-gas.RegularGas)/storageKeyCost < storageKeys {
return vm.GasCosts{}, ErrGasUintOverflow
}
- gas.RegularGas += storageKeys * params.TxAccessListStorageKeyGas
+ gas.RegularGas += storageKeys * storageKeyCost
// EIP-7981: access list data is charged in addition to the base charge.
if rules.IsAmsterdam {
@@ -151,8 +162,41 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set
return gas, nil
}
+// intrinsicBaseGasEIP2780 computes the regular-gas portion of the EIP-2780
+// intrinsic base cost: the per-resource decomposition of the legacy flat 21,000.
+func intrinsicBaseGasEIP2780(from common.Address, to *common.Address, value *uint256.Int) uint64 {
+ var (
+ isContractCreation = to == nil
+ isSelfTransfer = to != nil && *to == from
+ hasValue = value != nil && !value.IsZero()
+ )
+ // tx.sender: signature recovery plus the sender account access and write.
+ gas := params.TxBaseCost2780
+
+ // tx.to charge.
+ switch {
+ case isSelfTransfer:
+ // The recipient account is already accessed and written as the sender.
+ case isContractCreation:
+ gas += params.CreateAccess2780
+ default:
+ gas += params.ColdAccountAccess2780
+ }
+
+ // tx.value charge.
+ switch {
+ case !hasValue || isSelfTransfer:
+ // No transfer log and no recipient balance write.
+ case isContractCreation:
+ gas += params.TransferLogCost2780
+ default:
+ gas += params.TransferLogCost2780 + params.TxValueCost2780
+ }
+ return gas
+}
+
// FloorDataGas computes the minimum gas required for a transaction based on its data tokens (EIP-7623).
-func FloorDataGas(rules params.Rules, data []byte, accessList types.AccessList) (uint64, error) {
+func FloorDataGas(rules params.Rules, from common.Address, to *common.Address, value *uint256.Int, data []byte, accessList types.AccessList) (uint64, error) {
var (
tokens uint64
tokenCost uint64
@@ -198,12 +242,17 @@ func FloorDataGas(rules params.Rules, data []byte, accessList types.AccessList)
tokenCost = params.TxCostFloorPerToken
}
+ // The floor is anchored to the transaction base cost.
+ floorBase := params.TxGas
+ if rules.IsAmsterdam {
+ floorBase = params.TxBaseCost2780
+ }
// Check for overflow
- if (math.MaxUint64-params.TxGas)/tokenCost < tokens {
+ if (math.MaxUint64-floorBase)/tokenCost < tokens {
return 0, ErrGasUintOverflow
}
// Minimum gas required for a transaction based on its data tokens (EIP-7623).
- return params.TxGas + tokens*tokenCost, nil
+ return floorBase + tokens*tokenCost, nil
}
// toWordSize returns the ceiled word size required for init code payment calculation.
@@ -614,7 +663,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
contractCreation = msg.To == nil
floorDataGas uint64
)
- cost, err := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, contractCreation, rules, st.evm.Context.CostPerStateByte)
+ cost, err := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, msg.From, msg.To, msg.Value, rules, st.evm.Context.CostPerStateByte)
if err != nil {
return nil, err
}
@@ -629,7 +678,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
// Validate the EIP-7623 calldata floor against the gas limit. The floor inflates
// the total gas usage at tx end, so the gas limit must be sufficient to cover that.
if rules.IsPrague {
- floorDataGas, err = FloorDataGas(rules, msg.Data, msg.AccessList)
+ floorDataGas, err = FloorDataGas(rules, msg.From, msg.To, msg.Value, msg.Data, msg.AccessList)
if err != nil {
return nil, err
}
@@ -688,7 +737,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
// If the contract creation failed, or the destination was pre-existing,
// refund the account-creation state gas pre-charged in IntrinsicGas.
if rules.IsAmsterdam && !creation {
- st.gasRemaining.RefundState(params.AccountCreationSize * st.evm.Context.CostPerStateByte)
+ st.gasRemaining.RefundStateToReservoir(params.AccountCreationSize * st.evm.Context.CostPerStateByte)
}
} else {
// Increment the nonce for the next transaction.
@@ -704,10 +753,21 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
// performing the resolution and warming.
if addr, ok := types.ParseDelegation(st.state.GetCode(*msg.To)); ok {
st.state.AddAddressToAccessList(addr)
+ // Record in BAL
+ if rules.IsAmsterdam {
+ st.state.GetCode(addr)
+ }
+ }
+ // EIP-2780: charge the transaction's top-level recipient costs. If the
+ // budget cannot cover the charge, the top frame halts out of gas.
+ if rules.IsAmsterdam && !st.chargeCallRecipientEIP2780(value) {
+ vmerr = vm.ErrOutOfGas
+ st.gasRemaining = st.gasRemaining.ExitHalt()
+ } else {
+ // Execute the transaction's call.
+ ret, result, vmerr = st.evm.Call(msg.From, st.to(), msg.Data, st.gasRemaining.ForwardAll(), value)
+ st.gasRemaining.Absorb(result)
}
- // Execute the transaction's call.
- ret, result, vmerr = st.evm.Call(msg.From, st.to(), msg.Data, st.gasRemaining.ForwardAll(), value)
- st.gasRemaining.Absorb(result)
}
// Settle down the gas usage and refund the ETH back if any remaining
@@ -740,12 +800,6 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
}
}
- // EIP-7708: Emit the ETH-burn logs
- if rules.IsAmsterdam {
- for _, log := range st.evm.StateDB.LogsForBurnAccounts() {
- st.evm.StateDB.AddLog(log)
- }
- }
return &ExecutionResult{
UsedGas: gasUsed,
MaxUsedGas: peakUsed,
@@ -754,6 +808,47 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
}, nil
}
+// chargeCallRecipientEIP2780 applies the EIP-2780 transaction top-level gas costs for
+// a message-call transaction, charged before any opcode executes:
+//
+// - if the recipient is EIP-161 non-existent and the transaction carries value,
+// charge for account creation.
+//
+// - if the recipient is an EIP-7702 delegated account, resolving the delegation
+// loads the target's code, charged an additional cold account access in
+// regular gas.
+func (st *stateTransition) chargeCallRecipientEIP2780(value *uint256.Int) bool {
+ var (
+ cost vm.GasCosts
+ to = *st.msg.To
+ )
+ // This runs in the topmost frame before any bytecode executes, so unlike the
+ // execution-level checks which must use StateDB.Empty because SELFDESTRUCT can
+ // leave a transient EIP-161-empty account, no empty account can exist here, and
+ // !Exist is equivalent to Empty.
+ if !value.IsZero() && !st.state.Exist(to) {
+ cost.StateGas += params.AccountCreationSize * st.evm.Context.CostPerStateByte
+ }
+ if _, ok := types.ParseDelegation(st.state.GetCode(to)); ok {
+ // EIP-2780: The tx.sender, tx.to, and (where applicable) delegation-target
+ // charges above are always at the cold rate.
+ //
+ // The delegation-target is already warmed before, no double warming here.
+ cost.RegularGas += params.ColdAccountAccess2780
+ }
+ if cost == (vm.GasCosts{}) {
+ return true
+ }
+ prior, ok := st.gasRemaining.Charge(cost)
+ if !ok {
+ return false
+ }
+ if st.evm.Config.Tracer.HasGasHook() {
+ st.evm.Config.Tracer.EmitGasChange(prior.AsTracing(), st.gasRemaining.AsTracing(), tracing.GasChangeTxIntrinsicGas)
+ }
+ return true
+}
+
// settleGas finalizes the per-tx gas accounting after EVM execution:
//
// - Snapshots the EIP-8037 block-level 2D figures (tx_regular_gas,
@@ -870,20 +965,13 @@ func (st *stateTransition) validateAuthorization(auth *types.SetCodeAuthorizatio
}
// applyAuthorization applies an EIP-7702 code delegation to the state and,
-// under EIP-8037, reconciles the per-authorization intrinsic state-gas pre-
-// charge so that, per authority:
-//
-// - the account portion (AccountCreationSize × CPSB) is charged at most
-// once, and only when the account did not exist before the tx
-//
-// - the delegation-indicator portion (AuthorizationCreationSize × CPSB) is
-// charged at most once, and only when the authority ends the tx delegated
-// having started it undelegated.
+// adjust the pre-charged intrinsic cost accordingly.
func (st *stateTransition) applyAuthorization(rules params.Rules, auth *types.SetCodeAuthorization, delegates map[common.Address]bool) error {
authority, err := st.validateAuthorization(auth)
if err != nil {
if rules.IsAmsterdam {
- st.gasRemaining.RefundState((params.AccountCreationSize + params.AuthorizationCreationSize) * st.evm.Context.CostPerStateByte)
+ st.gasRemaining.RefundStateToReservoir((params.AccountCreationSize + params.AuthorizationCreationSize) * st.evm.Context.CostPerStateByte)
+ st.state.AddRefund(params.AccountWriteAmsterdam)
}
return err
}
@@ -895,7 +983,8 @@ func (st *stateTransition) applyAuthorization(rules params.Rules, auth *types.Se
}
} else {
if st.state.Exist(authority) {
- st.gasRemaining.RefundState(params.AccountCreationSize * st.evm.Context.CostPerStateByte)
+ st.gasRemaining.RefundStateToReservoir(params.AccountCreationSize * st.evm.Context.CostPerStateByte)
+ st.state.AddRefund(params.AccountWriteAmsterdam)
}
authBase := params.AuthorizationCreationSize * st.evm.Context.CostPerStateByte
@@ -906,17 +995,17 @@ func (st *stateTransition) applyAuthorization(rules params.Rules, auth *types.Se
}
if auth.Address == (common.Address{}) {
// Clearing writes no indicator, refill this auth's state charge.
- st.gasRemaining.RefundState(authBase)
+ st.gasRemaining.RefundStateToReservoir(authBase)
// The indicator was created by an earlier auth within the same
// transaction, refill the state charge as it's no longer justified.
if curDelegated && !preDelegated {
- st.gasRemaining.RefundState(authBase)
+ st.gasRemaining.RefundStateToReservoir(authBase)
}
} else if curDelegated || preDelegated {
// The 23-byte slot is already occupied, overwriting it writes no
// new bytes, refill the state charge.
- st.gasRemaining.RefundState(authBase)
+ st.gasRemaining.RefundStateToReservoir(authBase)
}
}
diff --git a/core/state_transition_test.go b/core/state_transition_test.go
index be2de7f511..60edad52e5 100644
--- a/core/state_transition_test.go
+++ b/core/state_transition_test.go
@@ -24,6 +24,7 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/params"
+ "github.com/holiman/uint256"
)
func TestFloorDataGas(t *testing.T) {
@@ -73,21 +74,22 @@ func TestFloorDataGas(t *testing.T) {
{
name: "amsterdam/empty",
amsterdam: true,
- want: params.TxGas,
+ // EIP-2780 anchors the floor to the reduced base cost.
+ want: params.TxBaseCost2780,
},
{
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,
+ want: params.TxBaseCost2780 + 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,
+ want: params.TxBaseCost2780 + 1024*params.TxTokenPerNonZeroByte*params.TxCostFloorPerToken7976,
},
{
name: "amsterdam/access-list-addresses-only",
@@ -97,7 +99,7 @@ func TestFloorDataGas(t *testing.T) {
{Address: addr2},
},
// 2 * 20 bytes * 4 tokens/byte * 16 cost/token
- want: params.TxGas + 2*common.AddressLength*params.TxTokenPerNonZeroByte*params.TxCostFloorPerToken7976,
+ want: params.TxBaseCost2780 + 2*common.AddressLength*params.TxTokenPerNonZeroByte*params.TxCostFloorPerToken7976,
},
{
name: "amsterdam/access-list-with-storage-keys",
@@ -106,7 +108,7 @@ func TestFloorDataGas(t *testing.T) {
{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,
+ want: params.TxBaseCost2780 + (1*common.AddressLength+2*common.HashLength)*params.TxTokenPerNonZeroByte*params.TxCostFloorPerToken7976,
},
{
name: "amsterdam/mixed",
@@ -117,13 +119,13 @@ func TestFloorDataGas(t *testing.T) {
{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,
+ want: params.TxBaseCost2780 + (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)
+ got, err := FloorDataGas(rules, addr1, &addr1, new(uint256.Int), tt.data, tt.accessList)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
@@ -155,6 +157,7 @@ func TestIntrinsicGas(t *testing.T) {
isEIP2028 bool
isEIP3860 bool
isAmsterdam bool
+ value *uint256.Int
want vm.GasCosts
}{
{
@@ -237,9 +240,10 @@ func TestIntrinsicGas(t *testing.T) {
},
isEIP2028: true,
isAmsterdam: true,
- // base access-list charge + EIP-7981 extra
- want: vm.GasCosts{RegularGas: params.TxGas +
- 2*params.TxAccessListAddressGas + 3*params.TxAccessListStorageKeyGas +
+ // EIP-2780: zero-value call base is TxBaseCost + ColdAccountAccess
+ // (15,000). Plus base access-list charge + EIP-7981 extra.
+ want: vm.GasCosts{RegularGas: params.TxBaseCost2780 + params.ColdAccountAccess2780 +
+ 2*params.TxAccessListAddressGasAmsterdam + 3*params.TxAccessListStorageKeyGasAmsterdam +
2*amsterdamAddressCost + 3*amsterdamStorageKeyCost},
},
{
@@ -259,10 +263,10 @@ func TestIntrinsicGas(t *testing.T) {
isHomestead: true,
isEIP2028: true,
isAmsterdam: true,
- // EIP-8037: creation regular gas is TxGas + CreateGasAmsterdam (not TxGasContractCreation),
- // and account-creation cost is moved to state gas.
+ // EIP-2780: creation regular gas is TxBaseCost + CreateAccess (23,000),
+ // and account-creation cost is charged as state gas.
want: vm.GasCosts{
- RegularGas: params.TxGas + params.CreateGasAmsterdam,
+ RegularGas: params.TxBaseCost2780 + params.CreateAccess2780,
StateGas: params.AccountCreationSize * params.CostPerStateByte,
},
},
@@ -275,7 +279,7 @@ func TestIntrinsicGas(t *testing.T) {
isEIP3860: true, // Shanghai gates init-code word gas
isAmsterdam: true,
want: vm.GasCosts{
- RegularGas: params.TxGas + params.CreateGasAmsterdam +
+ RegularGas: params.TxBaseCost2780 + params.CreateAccess2780 +
64*params.TxDataZeroGas + 2*params.InitCodeWordGas,
StateGas: params.AccountCreationSize * params.CostPerStateByte,
},
@@ -292,9 +296,9 @@ func TestIntrinsicGas(t *testing.T) {
isEIP3860: true,
isAmsterdam: true,
want: vm.GasCosts{
- RegularGas: params.TxGas + params.CreateGasAmsterdam +
+ RegularGas: params.TxBaseCost2780 + params.CreateAccess2780 +
32*params.TxDataNonZeroGasEIP2028 + 1*params.InitCodeWordGas +
- 1*params.TxAccessListAddressGas + 1*params.TxAccessListStorageKeyGas +
+ 1*params.TxAccessListAddressGasAmsterdam + 1*params.TxAccessListStorageKeyGasAmsterdam +
1*amsterdamAddressCost + 1*amsterdamStorageKeyCost,
StateGas: params.AccountCreationSize * params.CostPerStateByte,
},
@@ -310,18 +314,41 @@ func TestIntrinsicGas(t *testing.T) {
},
isEIP2028: true,
isAmsterdam: true,
- // EIP-8037 splits the auth-tuple charge into regular + state gas:
- // regular: TxAuthTupleRegularGas (7500) per auth
+ // EIP-8037 splits the auth-tuple charge into regular + state gas, with
+ // the values finalized by EIP-8038:
+ // regular: ACCOUNT_WRITE (8,000) + REGULAR_PER_AUTH_BASE_COST (7,500) per auth
// state: (AuthorizationCreationSize + AccountCreationSize) * CostPerStateByte per auth
want: vm.GasCosts{
- RegularGas: params.TxGas +
+ RegularGas: params.TxBaseCost2780 + params.ColdAccountAccess2780 +
100*params.TxDataNonZeroGasEIP2028 +
- 1*params.TxAccessListAddressGas + 1*params.TxAccessListStorageKeyGas +
+ 1*params.TxAccessListAddressGasAmsterdam + 1*params.TxAccessListStorageKeyGasAmsterdam +
1*amsterdamAddressCost + 1*amsterdamStorageKeyCost +
- 1*params.TxAuthTupleRegularGas,
+ 1*(params.AccountWriteAmsterdam+params.RegularPerAuthBaseCost),
StateGas: 1 * (params.AuthorizationCreationSize + params.AccountCreationSize) * params.CostPerStateByte,
},
},
+ {
+ name: "amsterdam/value-transfer-call",
+ isEIP2028: true,
+ isAmsterdam: true,
+ value: uint256.NewInt(1),
+ // EIP-2780: TxBaseCost + ColdAccountAccess + TransferLogCost + TxValueCost = 21,000.
+ want: vm.GasCosts{RegularGas: params.TxBaseCost2780 + params.ColdAccountAccess2780 +
+ params.TransferLogCost2780 + params.TxValueCost2780},
+ },
+ {
+ name: "amsterdam/value-bearing-contract-creation",
+ creation: true,
+ isHomestead: true,
+ isEIP2028: true,
+ isAmsterdam: true,
+ value: uint256.NewInt(1),
+ // EIP-2780: TxBaseCost + CreateAccess + TransferLogCost = 24,756, plus account-creation state gas.
+ want: vm.GasCosts{
+ RegularGas: params.TxBaseCost2780 + params.CreateAccess2780 + params.TransferLogCost2780,
+ StateGas: params.AccountCreationSize * params.CostPerStateByte,
+ },
+ },
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -331,8 +358,12 @@ func TestIntrinsicGas(t *testing.T) {
IsShanghai: tt.isEIP3860,
IsAmsterdam: tt.isAmsterdam,
}
+ var to *common.Address
+ if !tt.creation {
+ to = &addr1
+ }
got, err := IntrinsicGas(tt.data, tt.accessList, tt.authList,
- tt.creation, rules, params.CostPerStateByte)
+ common.Address{}, to, tt.value, rules, params.CostPerStateByte)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
diff --git a/core/txpool/blobpool/blobpool_test.go b/core/txpool/blobpool/blobpool_test.go
index 4e5f4f5e5a..56e018d175 100644
--- a/core/txpool/blobpool/blobpool_test.go
+++ b/core/txpool/blobpool/blobpool_test.go
@@ -1235,11 +1235,6 @@ func TestBillyMigration(t *testing.T) {
Max: maxBlobs,
UpdateFraction: params.DefaultCancunBlobConfig.UpdateFraction,
},
- Osaka: ¶ms.BlobConfig{
- Target: maxBlobs / 2,
- Max: maxBlobs,
- UpdateFraction: params.DefaultCancunBlobConfig.UpdateFraction,
- },
},
}
chain := &testBlockChain{
diff --git a/core/txpool/blobpool/cache_test.go b/core/txpool/blobpool/cache_test.go
index a410208bcc..1902f9b353 100644
--- a/core/txpool/blobpool/cache_test.go
+++ b/core/txpool/blobpool/cache_test.go
@@ -94,7 +94,7 @@ func newTestCache(t *testing.T, txConfig []txSpec) *testCache {
CancunTime: &cancunTime,
OsakaTime: &cancunTime,
BlobScheduleConfig: ¶ms.BlobScheduleConfig{
- Osaka: ¶ms.BlobConfig{
+ Cancun: ¶ms.BlobConfig{
Target: 1,
Max: 1,
UpdateFraction: params.DefaultCancunBlobConfig.UpdateFraction,
diff --git a/core/txpool/blobpool/limbo.go b/core/txpool/blobpool/limbo.go
index 8e44f51fdd..e599877f2a 100644
--- a/core/txpool/blobpool/limbo.go
+++ b/core/txpool/blobpool/limbo.go
@@ -107,7 +107,8 @@ func (l *limbo) Close() error {
func (l *limbo) parseBlob(id uint64, data []byte) error {
item := new(limboBlob)
if err := rlp.DecodeBytes(data, item); err != nil {
- if isLegacyLimboBlob(data) {
+ // This entry may have been stored with the legacy limboBlob type
+ if isLegacy(data) {
return errLegacyTx
}
// This path is impossible unless the disk data representation changes
@@ -133,9 +134,9 @@ func (l *limbo) parseBlob(id uint64, data []byte) error {
return nil
}
-// isLegacyLimboBlob returns true if the data is encoded in legacy limboBlob type.
-// It checks whether the first byte of third element is blobTxType.
-func isLegacyLimboBlob(data []byte) bool {
+// isLegacy reports whether data is a limbo entry with the legacy limboBlob
+// {TxHash, Block, Tx *types.Transaction} type.
+func isLegacy(data []byte) bool {
elems, err := rlp.SplitListValues(data)
if err != nil || len(elems) < 3 {
return false
diff --git a/core/txpool/blobpool/limbo_test.go b/core/txpool/blobpool/limbo_test.go
new file mode 100644
index 0000000000..04cb5f5c20
--- /dev/null
+++ b/core/txpool/blobpool/limbo_test.go
@@ -0,0 +1,69 @@
+// Copyright 2025 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 .
+
+package blobpool
+
+import (
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/params"
+ "github.com/ethereum/go-ethereum/rlp"
+ "github.com/holiman/billy"
+)
+
+// TestLimboLegacyMigration checks that a limbo entry in the legacy limboBlob
+// layout is flagged for conversion by newLimbo instead of being dropped, so the
+// conversion queue can migrate it to the blobTxForPool layout.
+func TestLimboLegacyMigration(t *testing.T) {
+ key, _ := crypto.GenerateKey()
+ tx := makeMultiBlobTx(0, 10, 100, 100, 2, 0, key)
+
+ dir := t.TempDir()
+
+ // Write a single entry using the legacy on-disk layout.
+ store, err := billy.Open(billy.Options{Path: dir}, newSlotterEIP7594(params.BlobTxMaxBlobs), nil)
+ if err != nil {
+ t.Fatalf("failed to open store: %v", err)
+ }
+ legacy := struct {
+ TxHash common.Hash
+ Block uint64
+ Tx *types.Transaction
+ }{tx.Hash(), 42, tx}
+ data, err := rlp.EncodeToBytes(&legacy)
+ if err != nil {
+ t.Fatalf("failed to encode legacy entry: %v", err)
+ }
+ id, err := store.Put(data)
+ if err != nil {
+ t.Fatalf("failed to store legacy entry: %v", err)
+ }
+ store.Close()
+
+ // Open the limbo, which should flag the legacy entry for conversion.
+ l, convert, err := newLimbo(new(params.ChainConfig), dir)
+ if err != nil {
+ t.Fatalf("failed to open limbo: %v", err)
+ }
+ defer l.Close()
+
+ if len(convert) != 1 || convert[0] != id {
+ t.Fatalf("legacy entry not flagged for conversion: got %v, want [%d]", convert, id)
+ }
+}
diff --git a/core/txpool/locals/journal.go b/core/txpool/locals/journal.go
index cd2be8a794..aac59bd91f 100644
--- a/core/txpool/locals/journal.go
+++ b/core/txpool/locals/journal.go
@@ -119,10 +119,11 @@ func (journal *journal) load(add func([]*types.Transaction) []error) error {
func (journal *journal) setupWriter() error {
if journal.writer != nil {
- if err := journal.writer.Close(); err != nil {
+ err := journal.writer.Close()
+ journal.writer = nil
+ if err != nil {
return err
}
- journal.writer = nil
}
// Re-open the journal file for appending
@@ -152,10 +153,11 @@ func (journal *journal) insert(tx *types.Transaction) error {
func (journal *journal) rotate(all map[common.Address]types.Transactions) error {
// Close the current journal (if any is open)
if journal.writer != nil {
- if err := journal.writer.Close(); err != nil {
+ err := journal.writer.Close()
+ journal.writer = nil
+ if err != nil {
return err
}
- journal.writer = nil
}
// Generate a new journal with the contents of the current pool
replacement, err := os.OpenFile(journal.path+".new", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
diff --git a/core/txpool/validation.go b/core/txpool/validation.go
index fa148797ef..a45f60ea78 100644
--- a/core/txpool/validation.go
+++ b/core/txpool/validation.go
@@ -29,6 +29,7 @@ import (
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
+ "github.com/holiman/uint256"
)
var (
@@ -113,16 +114,22 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
return core.ErrTipAboveFeeCap
}
// Make sure the transaction is signed properly
- if _, err := types.Sender(signer, tx); err != nil {
+ from, err := types.Sender(signer, tx)
+ if err != nil {
return fmt.Errorf("%w: %v", ErrInvalidSender, err)
}
// Limit nonce to 2^64-1 per EIP-2681
if tx.Nonce()+1 < tx.Nonce() {
return core.ErrNonceMax
}
+ // Sanity check for extremely large numbers (supported by RLP or RPC)
+ value, overflow := uint256.FromBig(tx.Value())
+ if overflow {
+ return core.ErrInsufficientFunds
+ }
// Ensure the transaction has more gas than the bare minimum needed to cover
// the transaction metadata
- intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules, params.CostPerStateByte)
+ intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), from, tx.To(), value, rules, params.CostPerStateByte)
if err != nil {
return err
}
@@ -131,7 +138,7 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
}
// Ensure the transaction can cover floor data gas.
if rules.IsPrague {
- floorDataGas, err := core.FloorDataGas(rules, tx.Data(), tx.AccessList())
+ floorDataGas, err := core.FloorDataGas(rules, from, tx.To(), value, tx.Data(), tx.AccessList())
if err != nil {
return err
}
diff --git a/core/types/bal/bal_encoding.go b/core/types/bal/bal_encoding.go
index 522192cee8..db891b92d0 100644
--- a/core/types/bal/bal_encoding.go
+++ b/core/types/bal/bal_encoding.go
@@ -80,7 +80,7 @@ func (e *BlockAccessList) DecodeRLP(s *rlp.Stream) error {
// according to the spec or any code changes are contained which exceed protocol
// max code size.
func (e *BlockAccessList) Validate(blockGasLimit uint64, blockTxCount int) error {
- if !slices.IsSortedFunc(*e, func(a, b AccountAccess) int {
+ if !isStrictlySortedFunc(*e, func(a, b AccountAccess) int {
return bytes.Compare(a.Address[:], b.Address[:])
}) {
return errors.New("block access list accounts not in lexicographic order")
diff --git a/core/types/bloom9.go b/core/types/bloom9.go
index 1d57e8e4bc..1b6c961c61 100644
--- a/core/types/bloom9.go
+++ b/core/types/bloom9.go
@@ -110,9 +110,9 @@ func CreateBloom(receipt *Receipt) Bloom {
buf [6]byte
)
for _, log := range receipt.Logs {
- bin.AddWithBuffer(log.Address.Bytes(), &buf)
- for _, b := range log.Topics {
- bin.AddWithBuffer(b[:], &buf)
+ bin.AddWithBuffer(log.Address[:], &buf)
+ for k := range log.Topics {
+ bin.AddWithBuffer(log.Topics[k][:], &buf)
}
}
return bin
diff --git a/core/types/log.go b/core/types/log.go
index 487ca57b5a..3ea1e0db7c 100644
--- a/core/types/log.go
+++ b/core/types/log.go
@@ -79,17 +79,3 @@ func EthTransferLog(from, to common.Address, amount *uint256.Int) *Log {
Data: amount32[:],
}
}
-
-// EthBurnLog creates an ETH burn log according to EIP-7708.
-// Specification: https://eips.ethereum.org/EIPS/eip-7708
-func EthBurnLog(from common.Address, amount *uint256.Int) *Log {
- amount32 := amount.Bytes32()
- return &Log{
- Address: params.SystemAddress,
- Topics: []common.Hash{
- params.EthBurnLogEvent,
- common.BytesToHash(from.Bytes()),
- },
- Data: amount32[:],
- }
-}
diff --git a/core/vm/eip8037_test.go b/core/vm/eip8037_test.go
index 658096d228..0eafc3e4af 100644
--- a/core/vm/eip8037_test.go
+++ b/core/vm/eip8037_test.go
@@ -45,9 +45,6 @@ var (
func amsterdam8037Config() *params.ChainConfig {
cfg := *params.MergedTestChainConfig
cfg.AmsterdamTime = new(uint64)
- blob := *cfg.BlobScheduleConfig
- blob.Amsterdam = blob.Osaka
- cfg.BlobScheduleConfig = &blob
return &cfg
}
@@ -193,16 +190,25 @@ func TestSStoreChargedAtOpcodeEnd(t *testing.T) {
}
// The SSTORE reentrancy sentry checks gas_left only; the reservoir is excluded.
-// Uses a noop write (1->1->1) so the sentry is the sole gate.
+// Uses a noop write (1->1->1): the two PUSH1s cost 6, leaving gas_left at the
+// sentry (2300) for a 2306 budget. Under EIP-8038 the cold-slot access that
+// follows a cleared sentry costs COLD_STORAGE_ACCESS (3000).
func TestSStoreStipendExcludesReservoir(t *testing.T) {
- // regular at the sentry, huge reservoir: must still fail.
+ // regular at the sentry, huge reservoir: must still fail, proving the
+ // reservoir does not count toward the sentry.
if _, _, err := run8037(t, sstore(0, 1), NewGasBudget(2306, math.MaxUint64/2), new(uint256.Int), setSlot(0, 1)); err == nil {
t.Fatal("expected sentry failure with regular gas at the limit")
}
- // one more regular gas clears the sentry.
- if _, _, err := run8037(t, sstore(0, 1), NewGasBudget(2307, math.MaxUint64/2), new(uint256.Int), setSlot(0, 1)); err != nil {
+ // Enough regular gas to clear the sentry and pay the cold-slot access
+ // (6 for the PUSH1s + COLD_STORAGE_ACCESS) succeeds with a huge reservoir.
+ regular := 6 + params.ColdStorageAccessAmsterdam
+ if _, _, err := run8037(t, sstore(0, 1), NewGasBudget(regular, math.MaxUint64/2), new(uint256.Int), setSlot(0, 1)); err != nil {
t.Fatalf("unexpected failure above sentry: %v", err)
}
+ // One gas short of the cold-slot access still fails (now on OOG, not sentry).
+ if _, _, err := run8037(t, sstore(0, 1), NewGasBudget(regular-1, math.MaxUint64/2), new(uint256.Int), setSlot(0, 1)); err == nil {
+ t.Fatal("expected OOG when regular gas cannot cover cold-slot access")
+ }
}
// ---- CALL / CREATE bytecode helpers ----
diff --git a/core/vm/eip8038_test.go b/core/vm/eip8038_test.go
new file mode 100644
index 0000000000..f20a9d8a0e
--- /dev/null
+++ b/core/vm/eip8038_test.go
@@ -0,0 +1,254 @@
+// 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 .
+
+// Opcode-level tests for EIP-8038 (state-access gas cost update). They reuse the
+// Amsterdam harness from eip8037_test.go and assert the re-priced regular-gas,
+// state-gas and refund-counter accounting.
+
+package vm
+
+import (
+ "strings"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "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/params"
+ "github.com/holiman/uint256"
+)
+
+// run8038 executes code at a contract address under the Amsterdam ruleset and
+// returns the resulting budget together with the transaction's refund counter.
+func run8038(t *testing.T, code []byte, gas GasBudget, value *uint256.Int, setup func(*state.StateDB, common.Address)) (GasBudget, uint64, error) {
+ t.Helper()
+ self := common.BytesToAddress([]byte("self"))
+ statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
+ statedb.CreateAccount(self)
+ statedb.SetCode(self, code, tracing.CodeChangeUnspecified)
+ if setup != nil {
+ setup(statedb, self)
+ }
+ statedb.Finalise(true)
+ _, result, err := amsterdam8037EVM(statedb).Call(common.Address{}, self, nil, gas, value)
+ return result, statedb.GetRefund(), err
+}
+
+// TestEIP8038SStore exercises SSTORE under Amsterdam (EIP-8037 + EIP-8038),
+// asserting the two-dimensional charge (regular + state gas) and the net refund
+// counter. It covers single stores in isolation (the EIP-8038 cases-table rows,
+// cold access), the warm-access variants, the dirty-slot refund reversals and
+// multi-store round trips.
+//
+// Each sstore() is "PUSH1 val; PUSH1 slot; SSTORE", so the non-SSTORE overhead is
+// 6 gas (two PUSH1) per store. The first store to a slot is cold and the rest are
+// warm, so the access component is COLD_STORAGE_ACCESS + (n-1) * WARM_ACCESS for n
+// stores. STORAGE_WRITE is charged once per "first change" (current == original).
+// GAS_STORAGE_SET is state gas, charged when a slot is created from zero and
+// refilled to the reservoir when that creation is undone within the same tx.
+func TestEIP8038SStore(t *testing.T) {
+ const (
+ push = uint64(6) // two PUSH1 per SSTORE
+ cold = params.ColdStorageAccessAmsterdam
+ warm = params.WarmStorageReadCostEIP2929
+ write = params.StorageWriteAmsterdam
+ clear = params.StorageClearRefundAmsterdam
+ )
+ set := uint64(params.StorageCreationSize * params.CostPerStateByte) // GAS_STORAGE_SET
+
+ // access(n) is the access-only regular cost for n stores: cold first, warm rest.
+ access := func(n uint64) uint64 { return cold + (n-1)*warm }
+
+ cases := []struct {
+ name string
+ orig byte // committed (pre-tx) value; 0 means a fresh slot
+ vals []byte // values written to slot 0, in order
+ wantReg uint64
+ wantState int64
+ wantRfnd uint64
+ }{
+ // Single store, cold access (EIP-8038 cases table, Cold rows + noop).
+ {"noop (1->1)", 1, []byte{1}, push + cold, 0, 0},
+ {"create (0->1)", 0, []byte{1}, push + cold + write, int64(set), 0},
+ {"first change (1->2)", 1, []byte{2}, push + cold + write, 0, 0},
+ {"clear (1->0)", 1, []byte{0}, push + cold + write, 0, clear},
+ // Two stores, warm access on the second (Warm rows of the cases table).
+ {"create warm (0->0->1)", 0, []byte{0, 1}, 2*push + access(2) + write, int64(set), 0},
+ {"first change warm (1->1->2)", 1, []byte{1, 2}, 2*push + access(2) + write, 0, 0},
+ {"clear warm (1->1->0)", 1, []byte{1, 0}, 2*push + access(2) + write, 0, clear},
+ {"dirty modified again (1->2->3)", 1, []byte{2, 3}, 2*push + access(2) + write, 0, 0},
+ // Two stores, refund reversals when a slot returns toward its original.
+ {"reset to zero (0->1->0)", 0, []byte{1, 0}, 2*push + access(2) + write, 0, write},
+ {"reset to original (1->2->1)", 1, []byte{2, 1}, 2*push + access(2) + write, 0, write},
+ {"cleared then restored (1->0->1)", 1, []byte{0, 1}, 2*push + access(2) + write, 0, write},
+ {"cleared then new value (1->0->2)", 1, []byte{0, 2}, 2*push + access(2) + write, 0, 0},
+ // Three stores, round trips (note the state-gas refill on the 0-> path).
+ {"0->1->0->1", 0, []byte{1, 0, 1}, 3*push + access(3) + 2*write, int64(set), write},
+ {"1->0->1->0", 1, []byte{0, 1, 0}, 3*push + access(3) + 2*write, 0, clear + write},
+ }
+ for _, tc := range cases {
+ t.Run(tc.name, func(t *testing.T) {
+ var code []byte
+ for _, v := range tc.vals {
+ code = append(code, sstore(0, v)...)
+ }
+ var setup func(*state.StateDB, common.Address)
+ if tc.orig != 0 {
+ setup = setSlot(0, tc.orig)
+ }
+ res, refund, err := run8038(t, code, hugeBudget(), new(uint256.Int), setup)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if res.UsedRegularGas != tc.wantReg {
+ t.Errorf("regular gas = %d, want %d", res.UsedRegularGas, tc.wantReg)
+ }
+ if res.UsedStateGas != tc.wantState {
+ t.Errorf("state gas = %d, want %d", res.UsedStateGas, tc.wantState)
+ }
+ if refund != tc.wantRfnd {
+ t.Errorf("refund = %d, want %d", refund, tc.wantRfnd)
+ }
+ })
+ }
+}
+
+// TestEIP8038SLoad checks the re-priced SLOAD access costs (cold 3000, warm 100).
+func TestEIP8038SLoad(t *testing.T) {
+ push := uint64(3) // PUSH1 slot
+ // PUSH1 0x00; SLOAD
+ cold := []byte{0x60, 0x00, 0x54}
+ res, _, err := run8038(t, cold, hugeBudget(), new(uint256.Int), nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if want := push + params.ColdStorageAccessAmsterdam; res.UsedRegularGas != want {
+ t.Fatalf("cold SLOAD = %d, want %d", res.UsedRegularGas, want)
+ }
+ // PUSH1 0x00; SLOAD; PUSH1 0x00; SLOAD -> second access is warm.
+ warm := []byte{0x60, 0x00, 0x54, 0x60, 0x00, 0x54}
+ res, _, err = run8038(t, warm, hugeBudget(), new(uint256.Int), nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ want := 2*push + params.ColdStorageAccessAmsterdam + params.WarmStorageReadCostEIP2929
+ if res.UsedRegularGas != want {
+ t.Fatalf("cold+warm SLOAD = %d, want %d", res.UsedRegularGas, want)
+ }
+}
+
+// TestEIP8038AccountAccess checks the re-priced cold-account access for the
+// account-reading opcodes and the extra WARM_ACCESS surcharge for EXTCODESIZE
+// and EXTCODECOPY (their second database read).
+func TestEIP8038AccountAccess(t *testing.T) {
+ push20 := uint64(3)
+ addr := common.BytesToAddress([]byte("some-cold-account"))
+
+ // pushAddr emits PUSH20 .
+ pushAddr := func() []byte { return append([]byte{0x73}, addr.Bytes()...) }
+
+ cold := params.ColdAccountAccessAmsterdam
+ warm := params.WarmStorageReadCostEIP2929
+
+ t.Run("BALANCE", func(t *testing.T) {
+ code := append(pushAddr(), 0x31) // BALANCE
+ res, _, err := run8038(t, code, hugeBudget(), new(uint256.Int), nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if want := push20 + cold; res.UsedRegularGas != want {
+ t.Fatalf("cold BALANCE = %d, want %d", res.UsedRegularGas, want)
+ }
+ })
+ t.Run("EXTCODEHASH", func(t *testing.T) {
+ code := append(pushAddr(), 0x3f) // EXTCODEHASH
+ res, _, err := run8038(t, code, hugeBudget(), new(uint256.Int), nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if want := push20 + cold; res.UsedRegularGas != want {
+ t.Fatalf("cold EXTCODEHASH = %d, want %d", res.UsedRegularGas, want)
+ }
+ })
+ t.Run("EXTCODESIZE adds WARM_ACCESS", func(t *testing.T) {
+ code := append(pushAddr(), 0x3b) // EXTCODESIZE
+ res, _, err := run8038(t, code, hugeBudget(), new(uint256.Int), nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if want := push20 + cold + warm; res.UsedRegularGas != want {
+ t.Fatalf("cold EXTCODESIZE = %d, want %d", res.UsedRegularGas, want)
+ }
+ })
+ t.Run("EXTCODECOPY adds WARM_ACCESS", func(t *testing.T) {
+ // PUSH1 0 (length); PUSH1 0 (codeOffset); PUSH1 0 (destOffset); PUSH20 addr; EXTCODECOPY
+ code := []byte{0x60, 0x00, 0x60, 0x00, 0x60, 0x00}
+ code = append(code, pushAddr()...)
+ code = append(code, 0x3c) // EXTCODECOPY
+ res, _, err := run8038(t, code, hugeBudget(), new(uint256.Int), nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ // three PUSH1 + one PUSH20 = 12 gas, zero-length copy => no memory/copy gas.
+ if want := uint64(12) + cold + warm; res.UsedRegularGas != want {
+ t.Fatalf("cold EXTCODECOPY = %d, want %d", res.UsedRegularGas, want)
+ }
+ })
+}
+
+// TestEIP8038SelfdestructAccountWrite checks that SELFDESTRUCT sending a positive
+// balance to an empty account is charged the cold access, an additional
+// ACCOUNT_WRITE (regular) and GAS_NEW_ACCOUNT (state).
+func TestEIP8038SelfdestructAccountWrite(t *testing.T) {
+ beneficiary := common.BytesToAddress([]byte("fresh-beneficiary"))
+ // PUSH20 beneficiary; SELFDESTRUCT
+ code := append([]byte{0x73}, beneficiary.Bytes()...)
+ code = append(code, 0xff)
+
+ // Fund the contract so it sends a positive balance on self-destruct.
+ fundSelf := func(db *state.StateDB, self common.Address) {
+ db.AddBalance(self, uint256.NewInt(1), tracing.BalanceChangeUnspecified)
+ }
+ res, _, err := run8038(t, code, hugeBudget(), new(uint256.Int), fundSelf)
+ if err != nil {
+ t.Fatal(err)
+ }
+ const push20 = uint64(3)
+ wantReg := push20 + params.SelfdestructGasEIP150 + params.ColdAccountAccessAmsterdam + params.AccountWriteAmsterdam
+ if res.UsedRegularGas != wantReg {
+ t.Fatalf("regular gas = %d, want %d", res.UsedRegularGas, wantReg)
+ }
+ if want := int64(params.AccountCreationSize * params.CostPerStateByte); res.UsedStateGas != want {
+ t.Fatalf("state gas = %d, want %d", res.UsedStateGas, want)
+ }
+}
+
+// TestEIP8038SStoreAccessGuard covers the affordability check that bails out
+// before the slot is read once the gas left cannot cover the slot's access cost.
+// The two PUSH1s cost 6, so a 2506 budget leaves 2500 at the SSTORE: above the
+// reentrancy sentry (2300) yet below COLD_STORAGE_ACCESS (3000). The guard must
+// fire, distinguishable from the sentry/charge OOG by its "slot access" message.
+func TestEIP8038SStoreAccessGuard(t *testing.T) {
+ budget := NewGasBudget(6+params.SstoreSentryGasEIP2200+200, 0)
+ _, _, err := run8038(t, sstore(0, 1), budget, new(uint256.Int), nil)
+ if err == nil {
+ t.Fatal("expected failure: gas left cannot cover cold-slot access")
+ }
+ if !strings.Contains(err.Error(), "not enough gas for slot access") {
+ t.Fatalf("got %q, want the slot-access guard error", err)
+ }
+}
diff --git a/core/vm/eips.go b/core/vm/eips.go
index 814e4be788..8a09856029 100644
--- a/core/vm/eips.go
+++ b/core/vm/eips.go
@@ -43,7 +43,8 @@ var activators = map[int]func(*JumpTable){
7939: enable7939,
8024: enable8024,
7843: enable7843,
- 8037: enable8037,
+ 8037: enable8037And8038,
+ 8038: enable8037And8038,
}
// EnableEIP enables the given EIP on the config.
@@ -584,13 +585,30 @@ func enable7843(jt *JumpTable) {
}
}
-// enable8037 enables the multidimensional-metering as specified in EIP-8037.
-func enable8037(jt *JumpTable) {
- jt[CREATE].constantGas = params.CreateGasAmsterdam
+// enable8037And8038 enables EIP-8037 (multidimensional state-gas metering)
+// together with EIP-8038 (state-access gas cost update).
+func enable8037And8038(jt *JumpTable) {
+ jt[CREATE].constantGas = params.CreateAccessAmsterdam
jt[CREATE].dynamicGas = gasCreateEip8037
- jt[CREATE2].constantGas = params.CreateGasAmsterdam
+ jt[CREATE2].constantGas = params.CreateAccessAmsterdam
jt[CREATE2].dynamicGas = gasCreate2Eip8037
- jt[CALL].dynamicGas = gasCallEIP8037
- jt[SELFDESTRUCT].dynamicGas = gasSelfdestruct8037
- jt[SSTORE].dynamicGas = gasSStore8037
+
+ // Storage-access opcodes
+ jt[SLOAD].dynamicGas = gasSLoad8038
+ jt[SSTORE].dynamicGas = gasSStore8037And8038
+
+ // Account-access opcodes
+ jt[BALANCE].dynamicGas = gasEip8038AccountCheck
+ jt[EXTCODEHASH].dynamicGas = gasEip8038AccountCheck
+ jt[EXTCODESIZE].dynamicGas = gasExtCodeSize8038
+ jt[EXTCODECOPY].dynamicGas = gasExtCodeCopy8038
+
+ // Call family
+ jt[CALL].dynamicGas = gasCall8038
+ jt[CALLCODE].dynamicGas = gasCallCode8038
+ jt[STATICCALL].dynamicGas = gasStaticCall8038
+ jt[DELEGATECALL].dynamicGas = gasDelegateCall8038
+
+ // SELFDESTRUCT
+ jt[SELFDESTRUCT].dynamicGas = gasSelfdestruct8037And8038
}
diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go
index 4bd971e711..d8fbf2b461 100644
--- a/core/vm/gas_table.go
+++ b/core/vm/gas_table.go
@@ -478,6 +478,28 @@ func gasCallCodeIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memor
return gas, nil
}
+// gasCallCodeIntrinsic8038 mirrors gasCallCodeIntrinsic but charges the
+// re-priced CALL_VALUE (ACCOUNT_WRITE + CALL_STIPEND) on value transfers per
+// EIP-8038. CALLCODE executes in the caller's context, so it never creates a
+// new account and has no state-gas component.
+func gasCallCodeIntrinsic8038(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
+ memoryGas, err := memoryGasCost(mem, memorySize)
+ if err != nil {
+ return 0, err
+ }
+ var (
+ gas uint64
+ overflow bool
+ )
+ if stack.back(2).Sign() != 0 && !evm.chainRules.IsEIP4762 {
+ gas += params.CallValueTransferAmsterdam
+ }
+ if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
+ return 0, ErrGasUintOverflow
+ }
+ return gas, nil
+}
+
func gasDelegateCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
gas, err := memoryGasCost(mem, memorySize)
if err != nil {
@@ -576,10 +598,11 @@ func gasCreate2Eip8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory,
}, nil
}
-// regularGasCall8037 is the intrinsic gas calculator for CALL in Amsterdam.
-// It computes memory expansion + value transfer gas but excludes new account
-// creation, which is handled as state gas by the wrapper.
-func regularGasCall8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
+// regularGasCall8038 is the intrinsic regular-gas calculator for CALL in
+// Amsterdam. It computes memory expansion plus the re-priced CALL_VALUE
+// (ACCOUNT_WRITE + CALL_STIPEND) on value transfers, but excludes new account
+// creation, which is handled as state gas by stateGasCall8037.
+func regularGasCall8038(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
var (
gas uint64
transfersValue = !stack.back(2).IsZero()
@@ -593,7 +616,7 @@ func regularGasCall8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory,
}
var transferGas uint64
if transfersValue && !evm.chainRules.IsEIP4762 {
- transferGas = params.CallValueTransferGas
+ transferGas = params.CallValueTransferAmsterdam
}
var overflow bool
if gas, overflow = math.SafeAdd(memoryGas, transferGas); overflow {
@@ -630,7 +653,9 @@ func stateGasCall8037(evm *EVM, contract *Contract, stack *Stack) (uint64, error
return gas, nil
}
-func gasSelfdestruct8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
+// gasSelfdestruct8037And8038 implements the SELFDESTRUCT gas charging under
+// the EIP8037 and EIP-8038.
+func gasSelfdestruct8037And8038(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
if evm.readOnly {
return GasCosts{}, ErrWriteProtection
}
@@ -639,11 +664,11 @@ func gasSelfdestruct8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory
address = common.Address(stack.peek().Bytes20())
)
if !evm.StateDB.AddressInAccessList(address) {
- // If the caller cannot afford the cost, this change will be rolled back
+ // If the caller cannot afford the cost, this change will be rolled back.
evm.StateDB.AddAddressToAccessList(address)
- gas.RegularGas = params.ColdAccountAccessCostEIP2929
+ gas.RegularGas = params.ColdAccountAccessAmsterdam
}
- // Check we have enough regular gas before we add the address to the BAL
+ // Check we have enough regular gas before we add the address to the BAL.
if contract.Gas.RegularGas < gas.RegularGas {
return gas, ErrOutOfGas
}
@@ -654,79 +679,68 @@ func gasSelfdestruct8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory
//
// Funding such an account makes it permanent state growth and must be charged.
if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 {
+ gas.RegularGas += params.AccountWriteAmsterdam
gas.StateGas += params.AccountCreationSize * evm.Context.CostPerStateByte
}
return gas, nil
}
-func gasSStore8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
+// gasSStore8037And8038 implements the SSTORE gas charging under EIP-8037 and
+// EIP-8038.
+func gasSStore8037And8038(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
if evm.readOnly {
return GasCosts{}, ErrWriteProtection
}
- // If we fail the minimum gas availability invariant, fail (0)
+ // If we fail the minimum gas availability invariant, fail (0).
if contract.Gas.RegularGas <= params.SstoreSentryGasEIP2200 {
return GasCosts{}, errors.New("not enough gas for reentrancy sentry")
}
- // Gas sentry honoured, do the actual gas calculation based on the stored value
var (
- y, x = stack.back(1), stack.peek()
- slot = common.Hash(x.Bytes32())
- current, original = evm.StateDB.GetStateAndCommittedState(contract.Address(), slot)
- cost GasCosts
+ y, x = stack.back(1), stack.peek()
+ slot = common.Hash(x.Bytes32())
+ value = common.Hash(y.Bytes32())
+ stateSet = params.StorageCreationSize * evm.Context.CostPerStateByte
)
// Check slot presence in the access list
+ access := params.WarmStorageReadCostEIP2929
if _, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent {
- cost = GasCosts{RegularGas: params.ColdSloadCostEIP2929}
- // If the caller cannot afford the cost, this change will be rolled back
+ access = params.ColdStorageAccessAmsterdam
evm.StateDB.AddSlotToAccessList(contract.Address(), slot)
}
- value := common.Hash(y.Bytes32())
+ // Check access cost affordability before reading slot
+ if contract.Gas.RegularGas < access {
+ return GasCosts{}, errors.New("not enough gas for slot access")
+ }
+ // Read the slot value for gas cost measurement
+ current, original := evm.StateDB.GetStateAndCommittedState(contract.Address(), slot)
if current == value { // noop (1)
- // EIP 2200 original clause:
- // return params.SloadGasEIP2200, nil
- return GasCosts{RegularGas: cost.RegularGas + params.WarmStorageReadCostEIP2929}, nil // SLOAD_GAS
+ return GasCosts{RegularGas: access}, nil
}
- if original == current {
+ if original == current { // first change of the slot (2.1)
if original == (common.Hash{}) { // create slot (2.1.1)
return GasCosts{
- RegularGas: cost.RegularGas + params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929,
- StateGas: params.StorageCreationSize * evm.Context.CostPerStateByte,
+ RegularGas: access + params.StorageWriteAmsterdam,
+ StateGas: stateSet,
}, nil
}
if value == (common.Hash{}) { // delete slot (2.1.2b)
- evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP3529)
+ evm.StateDB.AddRefund(params.StorageClearRefundAmsterdam)
}
- // EIP-2200 original clause:
- // return params.SstoreResetGasEIP2200, nil // write existing slot (2.1.2)
- return GasCosts{RegularGas: cost.RegularGas + params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929}, nil // write existing slot (2.1.2)
+ return GasCosts{RegularGas: access + params.StorageWriteAmsterdam}, nil // write existing slot (2.1.2)
}
if original != (common.Hash{}) {
if current == (common.Hash{}) { // recreate slot (2.2.1.1)
- evm.StateDB.SubRefund(params.SstoreClearsScheduleRefundEIP3529)
+ evm.StateDB.SubRefund(params.StorageClearRefundAmsterdam)
} else if value == (common.Hash{}) { // delete slot (2.2.1.2)
- evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP3529)
+ evm.StateDB.AddRefund(params.StorageClearRefundAmsterdam)
}
}
- if original == value {
+ if original == value { // reset to original value (2.2.2)
if original == (common.Hash{}) { // reset to original inexistent slot (2.2.2.1)
- // EIP-8037 point (2): refund state gas directly to the reservoir
- // at the SSTORE restoration point (0→x→0 in same tx); not to the
- // refund counter, which is capped at gas_used/5.
- contract.Gas.RefundState(params.StorageCreationSize * evm.Context.CostPerStateByte)
-
- // Regular portion of the refund still goes through the refund counter.
- evm.StateDB.AddRefund(params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929 - params.WarmStorageReadCostEIP2929)
- } else { // reset to original existing slot (2.2.2.2)
- // EIP 2200 Original clause:
- // evm.StateDB.AddRefund(params.SstoreResetGasEIP2200 - params.SloadGasEIP2200)
- // - SSTORE_RESET_GAS redefined as (5000 - COLD_SLOAD_COST)
- // - SLOAD_GAS redefined as WARM_STORAGE_READ_COST
- // Final: (5000 - COLD_SLOAD_COST) - WARM_STORAGE_READ_COST
- evm.StateDB.AddRefund((params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929) - params.WarmStorageReadCostEIP2929)
+ contract.Gas.RefundState(stateSet)
}
+ evm.StateDB.AddRefund(params.StorageWriteAmsterdam)
}
- // EIP-2200 original clause:
- //return params.SloadGasEIP2200, nil // dirty update (2.2)
- return GasCosts{RegularGas: cost.RegularGas + params.WarmStorageReadCostEIP2929}, nil // dirty update (2.2)
+ return GasCosts{RegularGas: access}, nil // dirty update (2.2)
}
diff --git a/core/vm/gascosts.go b/core/vm/gascosts.go
index 10b8eeb033..76a2c7af60 100644
--- a/core/vm/gascosts.go
+++ b/core/vm/gascosts.go
@@ -196,6 +196,22 @@ func (g *GasBudget) RefundState(s uint64) {
g.UsedStateGas -= int64(s)
}
+// RefundStateToReservoir credits a state-gas refund directly to the
+// reservoir, without repaying spilled regular gas first.
+//
+// Per the spec's set_delegation, authorization refunds (and the post-create
+// new-account refund) are added to message.state_gas_reservoir directly, in
+// contrast to the LIFO inline refunds handled by RefundState. The usage
+// counter is decremented by the full amount, matching the spec's
+// tx_state_gas = intrinsic_state + state_gas_used - state_refund and
+// preserving the per-frame invariant:
+//
+// StateGas + UsedStateGas == initialStateGas + Spilled
+func (g *GasBudget) RefundStateToReservoir(s uint64) {
+ g.StateGas += s
+ g.UsedStateGas -= int64(s)
+}
+
// Forward drains `regular` regular gas and the entire state reservoir from
// the parent's running budget and returns the initial GasBudget for a child
// frame. The parent's UsedRegularGas is bumped by the forwarded amount so
diff --git a/core/vm/instructions.go b/core/vm/instructions.go
index b20cce4c0d..328623848e 100644
--- a/core/vm/instructions.go
+++ b/core/vm/instructions.go
@@ -928,8 +928,15 @@ func opSelfdestruct6780(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, erro
if newContract {
if this != beneficiary { // Skip no-op transfer when self-destructing to self.
evm.StateDB.AddBalance(beneficiary, balance, tracing.BalanceIncreaseSelfdestruct)
+ evm.StateDB.SubBalance(this, balance, tracing.BalanceDecreaseSelfdestruct)
+ } else if !evm.chainRules.IsAmsterdam {
+ // Self-destructing to self burns the balance prior to EIP-8246.
+ // EIP-8246 (Amsterdam) removes this burn: the balance is left
+ // untouched and the account is preserved as a balance-only account
+ // at transaction finalization (unless its balance is zero, in which
+ // case EIP-161 deletes it).
+ evm.StateDB.SubBalance(this, balance, tracing.BalanceDecreaseSelfdestruct)
}
- evm.StateDB.SubBalance(this, balance, tracing.BalanceDecreaseSelfdestruct)
evm.StateDB.SelfDestruct(this)
}
@@ -938,12 +945,10 @@ func opSelfdestruct6780(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, erro
evm.StateDB.SubBalance(this, balance, tracing.BalanceDecreaseSelfdestruct)
evm.StateDB.AddBalance(beneficiary, balance, tracing.BalanceIncreaseSelfdestruct)
}
- if evm.chainRules.IsAmsterdam && !balance.IsZero() {
- if this != beneficiary {
- evm.StateDB.AddLog(types.EthTransferLog(this, beneficiary, balance))
- } else if newContract {
- evm.StateDB.AddLog(types.EthBurnLog(this, balance))
- }
+ // EIP-7708: emit a transfer log for the moved balance. EIP-8246 removes the
+ // SELFDESTRUCT burn entirely, so there is no longer a burn to log.
+ if evm.chainRules.IsAmsterdam && !balance.IsZero() && this != beneficiary {
+ evm.StateDB.AddLog(types.EthTransferLog(this, beneficiary, balance))
}
if tracer := evm.Config.Tracer; tracer != nil {
diff --git a/core/vm/interface.go b/core/vm/interface.go
index a9938c2a28..5bba39069c 100644
--- a/core/vm/interface.go
+++ b/core/vm/interface.go
@@ -90,7 +90,6 @@ type StateDB interface {
Snapshot() int
AddLog(*types.Log)
- LogsForBurnAccounts() []*types.Log
AddPreimage(common.Hash, []byte)
Witness() *stateless.Witness
diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go
index 5cc5e34ced..6b931083fe 100644
--- a/core/vm/jump_table.go
+++ b/core/vm/jump_table.go
@@ -99,9 +99,9 @@ func newVerkleInstructionSet() JumpTable {
func newAmsterdamInstructionSet() JumpTable {
instructionSet := newOsakaInstructionSet()
- enable7843(&instructionSet) // EIP-7843 (SLOTNUM opcode)
- enable8024(&instructionSet) // EIP-8024 (Backward compatible SWAPN, DUPN, EXCHANGE)
- enable8037(&instructionSet) // EIP-8037 (State creation gas cost increase)
+ enable7843(&instructionSet) // EIP-7843 (SLOTNUM opcode)
+ enable8024(&instructionSet) // EIP-8024 (Backward compatible SWAPN, DUPN, EXCHANGE)
+ enable8037And8038(&instructionSet) // EIP-8037 (state-gas metering) + EIP-8038 (state-access repricing)
return validate(instructionSet)
}
diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go
index 8b2a627fef..3669626d5a 100644
--- a/core/vm/operations_acl.go
+++ b/core/vm/operations_acl.go
@@ -101,16 +101,25 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc {
func gasSLoadEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
loc := stack.peek()
slot := common.Hash(loc.Bytes32())
- // Check slot presence in the access list
if _, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent {
- // If the caller cannot afford the cost, this change will be rolled back
- // If he does afford it, we can skip checking the same thing later on, during execution
evm.StateDB.AddSlotToAccessList(contract.Address(), slot)
return GasCosts{RegularGas: params.ColdSloadCostEIP2929}, nil
}
return GasCosts{RegularGas: params.WarmStorageReadCostEIP2929}, nil
}
+// gasSLoad8038 mirrors gasSLoadEIP2929 but uses the EIP-8038 COLD_STORAGE_ACCESS
+// for a cold slot.
+func gasSLoad8038(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
+ loc := stack.peek()
+ slot := common.Hash(loc.Bytes32())
+ if _, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent {
+ evm.StateDB.AddSlotToAccessList(contract.Address(), slot)
+ return GasCosts{RegularGas: params.ColdStorageAccessAmsterdam}, nil
+ }
+ return GasCosts{RegularGas: params.WarmStorageReadCostEIP2929}, nil
+}
+
// gasExtCodeCopyEIP2929 implements extcodecopy according to EIP-2929
// EIP spec:
// > If the target is not in accessed_addresses,
@@ -137,6 +146,34 @@ func gasExtCodeCopyEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memo
return GasCosts{RegularGas: gas}, nil
}
+// gasExtCodeCopy8038 mirrors gasExtCodeCopyEIP2929 but uses the EIP-8038
+// COLD_ACCOUNT_ACCESS and adds an extra WARM_ACCESS for the second
+// database read EXTCODECOPY performs.
+func gasExtCodeCopy8038(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
+ // memory expansion first (dynamic part of pre-2929 implementation)
+ gasCost, err := gasExtCodeCopy(evm, contract, stack, mem, memorySize)
+ if err != nil {
+ return GasCosts{}, err
+ }
+ gas := gasCost.RegularGas
+ addr := common.Address(stack.peek().Bytes20())
+ // Check slot presence in the access list
+ if !evm.StateDB.AddressInAccessList(addr) {
+ evm.StateDB.AddAddressToAccessList(addr)
+ var overflow bool
+ // We charge (cold-warm), since 'warm' is already charged as constantGas
+ if gas, overflow = math.SafeAdd(gas, params.ColdAccountAccessAmsterdam-params.WarmStorageReadCostEIP2929); overflow {
+ return GasCosts{}, ErrGasUintOverflow
+ }
+ }
+ // Additional WARM_ACCESS for the second database read (contract code).
+ var overflow bool
+ if gas, overflow = math.SafeAdd(gas, params.WarmStorageReadCostEIP2929); overflow {
+ return GasCosts{}, ErrGasUintOverflow
+ }
+ return GasCosts{RegularGas: gas}, nil
+}
+
// gasEip2929AccountCheck checks whether the first stack item (as address) is present in the access list.
// If it is, this method returns '0', otherwise 'cold-warm' gas, presuming that the opcode using it
// is also using 'warm' as constant factor.
@@ -156,6 +193,32 @@ func gasEip2929AccountCheck(evm *EVM, contract *Contract, stack *Stack, mem *Mem
return GasCosts{}, nil
}
+// gasEip8038AccountCheck mirrors gasEip2929AccountCheck but uses the EIP-8038
+// COLD_ACCOUNT_ACCESS. Used by BALANCE and EXTCODEHASH.
+func gasEip8038AccountCheck(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
+ addr := common.Address(stack.peek().Bytes20())
+ // Check slot presence in the access list
+ if !evm.StateDB.AddressInAccessList(addr) {
+ // If the caller cannot afford the cost, this change will be rolled back
+ evm.StateDB.AddAddressToAccessList(addr)
+ // The warm storage read cost is already charged as constantGas
+ return GasCosts{RegularGas: params.ColdAccountAccessAmsterdam - params.WarmStorageReadCostEIP2929}, nil
+ }
+ return GasCosts{}, nil
+}
+
+// gasExtCodeSize8038 prices EXTCODESIZE under EIP-8038: the gasEip8038AccountCheck
+// surcharge plus an additional WARM_ACCESS for the second database read (code size).
+func gasExtCodeSize8038(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
+ cost, err := gasEip8038AccountCheck(evm, contract, stack, mem, memorySize)
+ if err != nil {
+ return GasCosts{}, err
+ }
+ // Additional WARM_ACCESS for the second database read (contract size).
+ cost.RegularGas += params.WarmStorageReadCostEIP2929
+ return cost, nil
+}
+
func makeCallVariantGasCallEIP2929(oldCalculator gasFunc, addressPosition int) gasFunc {
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
addr := common.Address(stack.back(addressPosition).Bytes20())
@@ -257,12 +320,19 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc {
return gasFunc
}
+// recordDelegationAccess records the EIP-7702 delegated target in the block
+// access list (EIP-7928).
+func recordDelegationAccess(evm *EVM, target common.Address) {
+ if evm.chainRules.IsAmsterdam {
+ evm.StateDB.GetCode(target)
+ }
+}
+
var (
- innerGasCallEIP7702 = makeCallVariantGasCallEIP7702(gasCallIntrinsic)
- gasDelegateCallEIP7702 = makeCallVariantGasCallEIP7702(gasDelegateCallIntrinsic)
- gasStaticCallEIP7702 = makeCallVariantGasCallEIP7702(gasStaticCallIntrinsic)
- gasCallCodeEIP7702 = makeCallVariantGasCallEIP7702(gasCallCodeIntrinsic)
- innerGasCallEIP8037 = makeCallVariantGasCallEIP8037(regularGasCall8037, stateGasCall8037)
+ innerGasCallEIP7702 = makeCallVariantGasCallEIP7702(gasCallIntrinsic, params.ColdAccountAccessCostEIP2929)
+ gasDelegateCallEIP7702 = makeCallVariantGasCallEIP7702(gasDelegateCallIntrinsic, params.ColdAccountAccessCostEIP2929)
+ gasStaticCallEIP7702 = makeCallVariantGasCallEIP7702(gasStaticCallIntrinsic, params.ColdAccountAccessCostEIP2929)
+ gasCallCodeEIP7702 = makeCallVariantGasCallEIP7702(gasCallCodeIntrinsic, params.ColdAccountAccessCostEIP2929)
)
func gasCallEIP7702(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
@@ -277,15 +347,24 @@ func gasCallEIP7702(evm *EVM, contract *Contract, stack *Stack, mem *Memory, mem
return innerGasCallEIP7702(evm, contract, stack, mem, memorySize)
}
-func gasCallEIP8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
+var (
+ innerGasCall8038 = makeCallVariantGasCallEIP8037(regularGasCall8038, stateGasCall8037, params.ColdAccountAccessAmsterdam)
+ gasCallCode8038 = makeCallVariantGasCallEIP7702(gasCallCodeIntrinsic8038, params.ColdAccountAccessAmsterdam)
+ gasDelegateCall8038 = makeCallVariantGasCallEIP7702(gasDelegateCallIntrinsic, params.ColdAccountAccessAmsterdam)
+ gasStaticCall8038 = makeCallVariantGasCallEIP7702(gasStaticCallIntrinsic, params.ColdAccountAccessAmsterdam)
+)
+
+// gasCall8038 prices CALL for Amsterdam, guarding against value transfers in a
+// read-only context before delegating to the state-gas-aware wrapper.
+func gasCall8038(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
transfersValue := !stack.back(2).IsZero()
if evm.readOnly && transfersValue {
return GasCosts{}, ErrWriteProtection
}
- return innerGasCallEIP8037(evm, contract, stack, mem, memorySize)
+ return innerGasCall8038(evm, contract, stack, mem, memorySize)
}
-func makeCallVariantGasCallEIP7702(intrinsicFunc intrinsicGasFunc) gasFunc {
+func makeCallVariantGasCallEIP7702(intrinsicFunc intrinsicGasFunc, coldCost uint64) gasFunc {
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
var (
eip2929Cost uint64
@@ -300,7 +379,7 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc intrinsicGasFunc) gasFunc {
// The WarmStorageReadCostEIP2929 (100) is already deducted in the form
// of a constant cost, so the cost to charge for cold access, if any,
// is Cold - Warm
- eip2929Cost = params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929
+ eip2929Cost = coldCost - params.WarmStorageReadCostEIP2929
// Charge the remaining difference here already, to correctly calculate
// available gas for call
@@ -321,7 +400,7 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc intrinsicGasFunc) gasFunc {
// Terminate the gas measurement if the leftover gas is not sufficient,
// it can effectively prevent accessing the states in the following steps.
// It's an essential safeguard before any stateful check.
- if contract.Gas.RegularGas < intrinsicCost {
+ if !contract.chargeRegular(intrinsicCost, evm.Config.Tracer, tracing.GasChangeIgnored) {
return GasCosts{}, ErrOutOfGas
}
@@ -331,15 +410,19 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc intrinsicGasFunc) gasFunc {
eip7702Cost = params.WarmStorageReadCostEIP2929
} else {
evm.StateDB.AddAddressToAccessList(target)
- eip7702Cost = params.ColdAccountAccessCostEIP2929
+ eip7702Cost = coldCost
}
if !contract.chargeRegular(eip7702Cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
return GasCosts{}, ErrOutOfGas
}
+ // The delegated address has passed its gas check; record it in the
+ // block access list now, before the call's sender-balance and
+ // call-stack-depth checks.
+ recordDelegationAccess(evm, target)
}
// Calculate the gas budget for the nested call. The costs defined by
// EIP-2929 and EIP-7702 have already been applied.
- evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas.RegularGas, intrinsicCost, stack.back(0))
+ evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas.RegularGas, 0, stack.back(0))
if err != nil {
return GasCosts{}, err
}
@@ -347,11 +430,13 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc intrinsicGasFunc) gasFunc {
// adding it to the return, it will be charged outside of this function, as
// part of the dynamic gas. This will ensure it is correctly reported to
// tracers.
- contract.Gas.RegularGas += eip2929Cost + eip7702Cost
+ contract.Gas.RegularGas += eip2929Cost + eip7702Cost + intrinsicCost
+
// Undo the RegularGasUsed increments from the direct UseGas charges,
// since this gas will be re-charged via the returned cost.
contract.Gas.UsedRegularGas -= eip2929Cost
contract.Gas.UsedRegularGas -= eip7702Cost
+ contract.Gas.UsedRegularGas -= intrinsicCost
// Aggregate the gas costs from all components, including EIP-2929, EIP-7702,
// the CALL opcode itself, and the cost incurred by nested calls.
@@ -376,7 +461,7 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc intrinsicGasFunc) gasFunc {
// It extends the EIP-7702 pattern with state gas handling and GasUsed tracking.
// intrinsicFunc computes the regular gas (memory + transfer, no new account creation).
// stateGasFunc computes the state gas (new account creation as state gas).
-func makeCallVariantGasCallEIP8037(regularFunc regularGasFunc, stateGasFunc stateGasFunc) gasFunc {
+func makeCallVariantGasCallEIP8037(regularFunc regularGasFunc, stateGasFunc stateGasFunc, coldCost uint64) gasFunc {
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
var (
eip2929Cost uint64
@@ -386,7 +471,7 @@ func makeCallVariantGasCallEIP8037(regularFunc regularGasFunc, stateGasFunc stat
// EIP-2929 cold access check.
if !evm.StateDB.AddressInAccessList(addr) {
evm.StateDB.AddAddressToAccessList(addr)
- eip2929Cost = params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929
+ eip2929Cost = coldCost - params.WarmStorageReadCostEIP2929
if !contract.chargeRegular(eip2929Cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
return GasCosts{}, ErrOutOfGas
}
@@ -411,11 +496,15 @@ func makeCallVariantGasCallEIP8037(regularFunc regularGasFunc, stateGasFunc stat
eip7702Cost = params.WarmStorageReadCostEIP2929
} else {
evm.StateDB.AddAddressToAccessList(target)
- eip7702Cost = params.ColdAccountAccessCostEIP2929
+ eip7702Cost = coldCost
}
if !contract.chargeRegular(eip7702Cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
return GasCosts{}, ErrOutOfGas
}
+ // The delegated address has passed its gas check; record it in the
+ // block access list now, before the call's sender-balance and
+ // call-stack-depth checks.
+ recordDelegationAccess(evm, target)
}
// Compute and charge state gas (new account creation) AFTER regular gas.
diff --git a/eth/backend.go b/eth/backend.go
index ef4f593e6a..fbaef000af 100644
--- a/eth/backend.go
+++ b/eth/backend.go
@@ -281,6 +281,9 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
if config.OverrideOsaka != nil {
overrides.OverrideOsaka = config.OverrideOsaka
}
+ if config.OverrideAmsterdam != nil {
+ overrides.OverrideAmsterdam = config.OverrideAmsterdam
+ }
if config.OverrideBPO1 != nil {
overrides.OverrideBPO1 = config.OverrideBPO1
}
diff --git a/eth/catalyst/api_benchmark_test.go b/eth/catalyst/api_benchmark_test.go
index ee0a0a4888..6d6ad59f7f 100644
--- a/eth/catalyst/api_benchmark_test.go
+++ b/eth/catalyst/api_benchmark_test.go
@@ -197,7 +197,6 @@ func newBenchmarkBlobEnv(b *testing.B, blobCount int, version byte, fork benchFo
config.BlobScheduleConfig = ¶ms.BlobScheduleConfig{
Cancun: ¶ms.BlobConfig{Target: 6, Max: 128, UpdateFraction: 3338477},
Prague: ¶ms.BlobConfig{Target: 6, Max: 128, UpdateFraction: 5007716},
- Osaka: ¶ms.BlobConfig{Target: 6, Max: 128, UpdateFraction: 5007716},
}
// Configure fork times based on requested fork
switch fork {
diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go
index 99a0fa89f7..378bb64a1e 100644
--- a/eth/ethconfig/config.go
+++ b/eth/ethconfig/config.go
@@ -206,6 +206,9 @@ type Config struct {
// OverrideOsaka (TODO: remove after the fork)
OverrideOsaka *uint64 `toml:",omitempty"`
+ // OverrideAmsterdam (TODO: remove after the fork)
+ OverrideAmsterdam *uint64 `toml:",omitempty"`
+
// OverrideBPO1 (TODO: remove after the fork)
OverrideBPO1 *uint64 `toml:",omitempty"`
diff --git a/eth/ethconfig/gen_config.go b/eth/ethconfig/gen_config.go
index 61d6ccb168..b24cde8a6a 100644
--- a/eth/ethconfig/gen_config.go
+++ b/eth/ethconfig/gen_config.go
@@ -64,6 +64,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
RPCEVMTimeout time.Duration
RPCTxFeeCap float64
OverrideOsaka *uint64 `toml:",omitempty"`
+ OverrideAmsterdam *uint64 `toml:",omitempty"`
OverrideBPO1 *uint64 `toml:",omitempty"`
OverrideBPO2 *uint64 `toml:",omitempty"`
OverrideUBT *uint64 `toml:",omitempty"`
@@ -119,6 +120,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
enc.RPCEVMTimeout = c.RPCEVMTimeout
enc.RPCTxFeeCap = c.RPCTxFeeCap
enc.OverrideOsaka = c.OverrideOsaka
+ enc.OverrideAmsterdam = c.OverrideAmsterdam
enc.OverrideBPO1 = c.OverrideBPO1
enc.OverrideBPO2 = c.OverrideBPO2
enc.OverrideUBT = c.OverrideUBT
@@ -178,6 +180,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
RPCEVMTimeout *time.Duration
RPCTxFeeCap *float64
OverrideOsaka *uint64 `toml:",omitempty"`
+ OverrideAmsterdam *uint64 `toml:",omitempty"`
OverrideBPO1 *uint64 `toml:",omitempty"`
OverrideBPO2 *uint64 `toml:",omitempty"`
OverrideUBT *uint64 `toml:",omitempty"`
@@ -330,6 +333,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
if dec.OverrideOsaka != nil {
c.OverrideOsaka = dec.OverrideOsaka
}
+ if dec.OverrideAmsterdam != nil {
+ c.OverrideAmsterdam = dec.OverrideAmsterdam
+ }
if dec.OverrideBPO1 != nil {
c.OverrideBPO1 = dec.OverrideBPO1
}
diff --git a/eth/handler_eth.go b/eth/handler_eth.go
index cb5e3d92a6..79fd1d118d 100644
--- a/eth/handler_eth.go
+++ b/eth/handler_eth.go
@@ -104,7 +104,7 @@ func (h *ethHandler) Handle(peer *eth.Peer, packet eth.Packet) error {
// handleTransactions marks all given transactions as known to the peer
// and performs basic validations.
func handleTransactions(peer *eth.Peer, list []*types.Transaction, directBroadcast bool) error {
- seen := make(map[common.Hash]struct{})
+ seen := make(map[common.Hash]struct{}, len(list))
for _, tx := range list {
if tx.Type() == types.BlobTxType {
if directBroadcast {
diff --git a/eth/protocols/eth/handler_test.go b/eth/protocols/eth/handler_test.go
index 76f3a828a6..c3b15b999a 100644
--- a/eth/protocols/eth/handler_test.go
+++ b/eth/protocols/eth/handler_test.go
@@ -745,7 +745,7 @@ func testGetBlockAccessLists(t *testing.T, protocol uint) {
var (
hashes []common.Hash
- expect rlp.RawList[RawBlockAccessList]
+ expect rlp.RawList[rlp.RawValue]
)
for i := uint64(0); i <= backend.chain.CurrentBlock().Number.Uint64(); i++ {
block := backend.chain.GetBlockByNumber(i)
@@ -774,6 +774,42 @@ func testGetBlockAccessLists(t *testing.T, protocol uint) {
}
}
+// TestBlockAccessListsUnavailableDecode checks that a BlockAccessLists response
+// containing the EIP-8159 unavailability marker (RLP empty string).
+func TestBlockAccessListsUnavailableDecode(t *testing.T) {
+ t.Parallel()
+
+ balRaw := makeTestBAL(t, common.Address{0x11})
+
+ // Assemble a response the way the serving side does, with the middle
+ // entry signaled as unavailable.
+ var list rlp.RawList[rlp.RawValue]
+ list.AppendRaw(balRaw)
+ list.AppendRaw(rlp.EmptyString)
+ list.AppendRaw(balRaw)
+
+ enc, err := rlp.EncodeToBytes(&BlockAccessListPacket{RequestId: 42, List: list})
+ if err != nil {
+ t.Fatalf("failed to encode packet: %v", err)
+ }
+ var packet BlockAccessListPacket
+ if err := rlp.DecodeBytes(enc, &packet); err != nil {
+ t.Fatalf("failed to decode packet: %v", err)
+ }
+ bals, err := packet.List.Items()
+ if err != nil {
+ t.Fatalf("failed to decode BAL entries: %v", err)
+ }
+ if len(bals) != 3 {
+ t.Fatalf("wrong entry count: got %d, want 3", len(bals))
+ }
+ for i, want := range [][]byte{balRaw, rlp.EmptyString, balRaw} {
+ if !bytes.Equal(bals[i], want) {
+ t.Errorf("entry %d mismatch: got %x, want %x", i, bals[i], want)
+ }
+ }
+}
+
type decoder struct {
msg []byte
}
diff --git a/eth/protocols/eth/handlers.go b/eth/protocols/eth/handlers.go
index e56f41420d..d191587812 100644
--- a/eth/protocols/eth/handlers.go
+++ b/eth/protocols/eth/handlers.go
@@ -774,10 +774,10 @@ func handleGetBlockAccessLists(backend Backend, msg Decoder, peer *Peer) error {
// serviceGetBlockAccessListsQuery assembles the response to a BAL query.
// Unavailable BALs are returned as empty list entries.
-func serviceGetBlockAccessListsQuery(chain *core.BlockChain, query GetBlockAccessListsRequest) rlp.RawList[RawBlockAccessList] {
+func serviceGetBlockAccessListsQuery(chain *core.BlockChain, query GetBlockAccessListsRequest) rlp.RawList[rlp.RawValue] {
var (
bytes int
- bals rlp.RawList[RawBlockAccessList]
+ bals rlp.RawList[rlp.RawValue]
)
for _, hash := range query {
if bytes >= softResponseLimit || bals.Len() >= maxBALsServe {
@@ -815,7 +815,12 @@ func handleBlockAccessLists(backend Backend, msg Decoder, peer *Peer) error {
metadata := func() interface{} {
hashes := make([]common.Hash, len(bals))
for i := range bals {
- hashes[i] = crypto.Keccak256Hash(bals[i].Bytes())
+ // Unavailable BALs (signaled by the empty string) are marked
+ // with the zero hash
+ if bytes.Equal(bals[i], rlp.EmptyString) {
+ continue
+ }
+ hashes[i] = crypto.Keccak256Hash(bals[i])
}
return hashes
}
diff --git a/eth/protocols/eth/peer.go b/eth/protocols/eth/peer.go
index 6d670b53c2..cd97325cf0 100644
--- a/eth/protocols/eth/peer.go
+++ b/eth/protocols/eth/peer.go
@@ -296,7 +296,7 @@ func (p *Peer) ReplyReceiptsRLP70(id uint64, receipts rlp.RawList[*ReceiptList],
}
// ReplyBlockAccessLists is the response to GetBlockAccessLists (EIP-8159).
-func (p *Peer) ReplyBlockAccessLists(id uint64, list rlp.RawList[RawBlockAccessList]) error {
+func (p *Peer) ReplyBlockAccessLists(id uint64, list rlp.RawList[rlp.RawValue]) error {
return p2p.Send(p.rw, BlockAccessListsMsg, &BlockAccessListPacket{
RequestId: id,
List: list,
diff --git a/eth/protocols/eth/protocol.go b/eth/protocols/eth/protocol.go
index b8b42204af..46434dc800 100644
--- a/eth/protocols/eth/protocol.go
+++ b/eth/protocols/eth/protocol.go
@@ -24,7 +24,6 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/forkid"
"github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/core/types/bal"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/rlp"
)
@@ -337,15 +336,14 @@ type GetBlockAccessListsPacket struct {
GetBlockAccessListsRequest
}
-type RawBlockAccessList struct {
- rlp.RawList[bal.AccountAccess]
-}
-
-type BlockAccessListResponse []RawBlockAccessList
+// BlockAccessListResponse holds one raw entry per requested hash. Entries are
+// kept as raw values because, per EIP-8159, the RLP empty string signals an
+// unavailable BAL (an empty list is itself a valid BAL).
+type BlockAccessListResponse []rlp.RawValue
type BlockAccessListPacket struct {
RequestId uint64
- List rlp.RawList[RawBlockAccessList]
+ List rlp.RawList[rlp.RawValue]
}
func (*StatusPacket) Name() string { return "Status" }
diff --git a/eth/state_accessor.go b/eth/state_accessor.go
index 3c3539dbdb..7bcd83d581 100644
--- a/eth/state_accessor.go
+++ b/eth/state_accessor.go
@@ -250,7 +250,7 @@ func (eth *Ethereum) stateAtTransaction(ctx context.Context, block *types.Block,
defer evm.Release()
// Run pre-execution system calls
- core.PreExecution(ctx, block.BeaconRoot(), block.ParentHash(), eth.blockchain.Config(), evm, block.Number(), block.Time())
+ core.PreExecution(ctx, block.BeaconRoot(), parent.Header(), eth.blockchain.Config(), evm, block.Number(), block.Time())
if txIndex == 0 && len(block.Transactions()) == 0 {
return nil, context, statedb, release, nil
diff --git a/eth/tracers/api.go b/eth/tracers/api.go
index 88132b4b63..d103b8ef35 100644
--- a/eth/tracers/api.go
+++ b/eth/tracers/api.go
@@ -373,7 +373,7 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed
context := core.NewEVMBlockContext(next.Header(), api.chainContext(ctx), nil)
evm := vm.NewEVM(context, statedb, api.backend.ChainConfig(), vm.Config{})
- core.PreExecution(ctx, next.BeaconRoot(), next.ParentHash(), api.backend.ChainConfig(), evm, next.Number(), next.Time())
+ core.PreExecution(ctx, next.BeaconRoot(), block.Header(), api.backend.ChainConfig(), evm, next.Number(), next.Time())
evm.Release()
// Clean out any pending release functions of trace state. Note this
// step must be done after constructing tracing state, because the
@@ -523,7 +523,7 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config
)
defer evm.Release()
// Run pre-execution system calls
- core.PreExecution(ctx, block.BeaconRoot(), block.ParentHash(), chainConfig, evm, block.Number(), block.Time())
+ core.PreExecution(ctx, block.BeaconRoot(), parent.Header(), chainConfig, evm, block.Number(), block.Time())
for i, tx := range block.Transactions() {
if err := ctx.Err(); err != nil {
@@ -582,7 +582,7 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
defer evm.Release()
// Run pre-execution system calls
- core.PreExecution(ctx, block.BeaconRoot(), block.ParentHash(), api.backend.ChainConfig(), evm, block.Number(), block.Time())
+ core.PreExecution(ctx, block.BeaconRoot(), parent.Header(), api.backend.ChainConfig(), evm, block.Number(), block.Time())
// JS tracers have high overhead. In this case run a parallel
// process that generates states in one thread and traces txes
@@ -754,7 +754,7 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block
defer evm.Release()
// Run pre-execution system calls
- core.PreExecution(ctx, block.BeaconRoot(), block.ParentHash(), chainConfig, evm, block.Number(), block.Time())
+ core.PreExecution(ctx, block.BeaconRoot(), parent.Header(), chainConfig, evm, block.Number(), block.Time())
for i, tx := range block.Transactions() {
// Prepare the transaction for un-traced execution
diff --git a/eth/tracers/native/erc7562.go b/eth/tracers/native/erc7562.go
index 0bf80d77b5..d0a2813381 100644
--- a/eth/tracers/native/erc7562.go
+++ b/eth/tracers/native/erc7562.go
@@ -308,6 +308,7 @@ func (t *erc7562Tracer) OnLog(log1 *types.Log) {
Address: log1.Address,
Topics: log1.Topics,
Data: log1.Data,
+ Index: hexutil.Uint(log1.Index),
Position: hexutil.Uint(len(t.callstackWithOpcodes[len(t.callstackWithOpcodes)-1].Calls)),
}
t.callstackWithOpcodes[len(t.callstackWithOpcodes)-1].Logs = append(t.callstackWithOpcodes[len(t.callstackWithOpcodes)-1].Logs, l)
diff --git a/internal/ethapi/logtracer.go b/internal/ethapi/logtracer.go
index 54d2d653ea..0b7c27079a 100644
--- a/internal/ethapi/logtracer.go
+++ b/internal/ethapi/logtracer.go
@@ -80,7 +80,7 @@ func (t *tracer) Hooks() *tracing.Hooks {
func (t *tracer) onEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
t.logs = append(t.logs, make([]*types.Log, 0))
- if vm.OpCode(typ) != vm.DELEGATECALL && value != nil && value.Cmp(common.Big0) > 0 {
+ if vm.OpCode(typ) != vm.DELEGATECALL && vm.OpCode(typ) != vm.CALLCODE && value != nil && value.Cmp(common.Big0) > 0 {
t.captureTransfer(from, to, value)
}
}
diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go
index 8462194b1d..0da2672756 100644
--- a/internal/ethapi/simulate.go
+++ b/internal/ethapi/simulate.go
@@ -322,7 +322,7 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
evm.SetPrecompiles(precompiles)
}
// Run pre-execution system calls
- blockAccessList.Merge(core.PreExecution(ctx, header.ParentBeaconRoot, header.ParentHash, sim.chainConfig, evm, header.Number, header.Time))
+ blockAccessList.Merge(core.PreExecution(ctx, header.ParentBeaconRoot, parent, sim.chainConfig, evm, header.Number, header.Time))
var allLogs []*types.Log
for i, call := range block.Calls {
diff --git a/log/handler.go b/log/handler.go
index 0b51120e51..30a0aff49c 100644
--- a/log/handler.go
+++ b/log/handler.go
@@ -7,6 +7,7 @@ import (
"log/slog"
"math/big"
"reflect"
+ "slices"
"sync"
"time"
@@ -97,7 +98,7 @@ func (h *TerminalHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
wr: h.wr,
lvl: h.lvl,
useColor: h.useColor,
- attrs: append(h.attrs, attrs...),
+ attrs: append(slices.Clone(h.attrs), attrs...),
fieldPadding: make(map[string]int),
}
}
diff --git a/miner/payload_building_test.go b/miner/payload_building_test.go
index f8e495cc99..734a5605ba 100644
--- a/miner/payload_building_test.go
+++ b/miner/payload_building_test.go
@@ -17,6 +17,7 @@
package miner
import (
+ "bytes"
"context"
"math/big"
"reflect"
@@ -26,6 +27,7 @@ import (
"github.com/ethereum/go-ethereum/beacon/engine"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
+ "github.com/ethereum/go-ethereum/consensus/beacon"
"github.com/ethereum/go-ethereum/consensus/clique"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core"
@@ -115,6 +117,7 @@ func newTestWorkerBackend(t *testing.T, chainConfig *params.ChainConfig, engine
copy(gspec.ExtraData[32:32+common.AddressLength], testBankAddress.Bytes())
e.Authorize(testBankAddress)
case *ethash.Ethash:
+ case *beacon.Beacon:
default:
t.Fatalf("unexpected consensus engine type: %T", engine)
}
@@ -194,6 +197,58 @@ func TestBuildPayload(t *testing.T) {
}
}
+// TestBuildPayloadAmsterdamTransition verifies that a locally built payload for
+// the first Amsterdam block contains the EIP-7997 deterministic deployment
+// factory, i.e. the block-building path applies the same irregular state
+// transition as block processing and the resulting block is importable.
+func TestBuildPayloadAmsterdamTransition(t *testing.T) {
+ var (
+ db = rawdb.NewMemoryDatabase()
+ recipient = common.HexToAddress("0xdeadbeef")
+ )
+ config := new(params.ChainConfig)
+ *config = *params.MergedTestChainConfig
+ config.AmsterdamTime = new(uint64)
+ *config.AmsterdamTime = 1 // genesis (t=0) is pre-Amsterdam, the first block crosses the fork
+
+ w, b := newTestWorker(t, config, beacon.New(ethash.NewFaker()), db, 0)
+
+ var (
+ beaconRoot = common.Hash{0x01}
+ slotNum = uint64(1)
+ )
+ payload, err := w.buildPayload(context.Background(), &BuildPayloadArgs{
+ Parent: b.chain.CurrentBlock().Hash(),
+ Timestamp: 1,
+ FeeRecipient: recipient,
+ Withdrawals: types.Withdrawals{},
+ BeaconRoot: &beaconRoot,
+ SlotNum: &slotNum,
+ }, false)
+ if err != nil {
+ t.Fatalf("Failed to build payload %v", err)
+ }
+ block := payload.empty
+ if !config.IsAmsterdam(block.Number(), block.Time()) {
+ t.Fatal("transition block is not an Amsterdam block")
+ }
+ // The block must be importable: Process applies EIP-7997 independently, so
+ // a payload built without the factory would fail the state root check here.
+ if _, err := b.chain.InsertChain(types.Blocks{block}); err != nil {
+ t.Fatalf("failed to insert transition block: %v", err)
+ }
+ statedb, err := b.chain.StateAt(block.Header())
+ if err != nil {
+ t.Fatalf("failed to open state at transition block: %v", err)
+ }
+ if code := statedb.GetCode(params.DeterministicFactoryAddress); !bytes.Equal(code, params.DeterministicFactoryCode) {
+ t.Fatalf("factory code missing from built payload state:\n got %x\nwant %x", code, params.DeterministicFactoryCode)
+ }
+ if nonce := statedb.GetNonce(params.DeterministicFactoryAddress); nonce != 1 {
+ t.Fatalf("factory nonce = %d, want 1", nonce)
+ }
+}
+
func TestPayloadId(t *testing.T) {
t.Parallel()
ids := make(map[string]int)
diff --git a/miner/stress/main.go b/miner/stress/main.go
index aaf0993c37..7a8436b01e 100644
--- a/miner/stress/main.go
+++ b/miner/stress/main.go
@@ -138,7 +138,10 @@ func makeGenesis(faucets []*ecdsa.PrivateKey) *core.Genesis {
blockZero := uint64(0)
config.AmsterdamTime = &blockZero
- config.BlobScheduleConfig.Amsterdam = ¶ms.BlobConfig{
+ // Amsterdam inherits its blob schedule from the most recent BPO; activate BPO1
+ // at the same time with the target/max we want exercised by the stress harness.
+ config.BPO1Time = &blockZero
+ config.BlobScheduleConfig.BPO1 = ¶ms.BlobConfig{
Target: 14,
Max: 21,
UpdateFraction: 13739630,
diff --git a/miner/worker.go b/miner/worker.go
index 01a14b8a02..7f0e11f30a 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -326,7 +326,7 @@ func (miner *Miner) prepareWork(ctx context.Context, genParams *generateParams,
return nil, err
}
// Run pre-execution system calls
- env.bal.Merge(core.PreExecution(ctx, header.ParentBeaconRoot, header.ParentHash, miner.chainConfig, env.evm, header.Number, header.Time))
+ env.bal.Merge(core.PreExecution(ctx, header.ParentBeaconRoot, parent, miner.chainConfig, env.evm, header.Number, header.Time))
return env, nil
}
diff --git a/node/config.go b/node/config.go
index 77ca78a471..b0f8753a7d 100644
--- a/node/config.go
+++ b/node/config.go
@@ -198,6 +198,9 @@ type Config struct {
// BatchResponseMaxSize is the maximum number of bytes returned from a batched rpc call.
BatchResponseMaxSize int `toml:",omitempty"`
+ // HTTPBodyLimit is the maximum size (in bytes) of an HTTP request body.
+ HTTPBodyLimit int `toml:",omitempty"`
+
// JWTSecret is the path to the hex-encoded jwt secret.
JWTSecret string `toml:",omitempty"`
diff --git a/node/defaults.go b/node/defaults.go
index 3410fa2ae5..145507ffe9 100644
--- a/node/defaults.go
+++ b/node/defaults.go
@@ -67,6 +67,7 @@ var DefaultConfig = Config{
WSModules: []string{"net", "web3"},
BatchRequestLimit: 1000,
BatchResponseMaxSize: 25 * 1000 * 1000,
+ HTTPBodyLimit: 5 * 1024 * 1024,
GraphQLVirtualHosts: []string{"localhost"},
P2P: p2p.Config{
ListenAddr: ":30303",
diff --git a/node/jwt_handler.go b/node/jwt_handler.go
index 637ae19686..4f85c12367 100644
--- a/node/jwt_handler.go
+++ b/node/jwt_handler.go
@@ -47,7 +47,7 @@ func (handler *jwtHandler) ServeHTTP(out http.ResponseWriter, r *http.Request) {
strToken string
claims jwt.RegisteredClaims
)
- if auth := r.Header.Get("Authorization"); strings.HasPrefix(auth, "Bearer ") {
+ if auth := r.Header.Get("Authorization"); len(auth) >= 7 && strings.EqualFold(auth[:7], "bearer ") {
strToken = strings.TrimPrefix(auth, "Bearer ")
}
if len(strToken) == 0 {
diff --git a/node/node.go b/node/node.go
index 7c0d69775c..3dd780f42b 100644
--- a/node/node.go
+++ b/node/node.go
@@ -392,6 +392,7 @@ func (n *Node) startRPC() error {
rpcConfig := rpcEndpointConfig{
batchItemLimit: n.config.BatchRequestLimit,
batchResponseSizeLimit: n.config.BatchResponseMaxSize,
+ httpBodyLimit: n.config.HTTPBodyLimit,
}
initHttp := func(server *httpServer, port int) error {
diff --git a/p2p/discover/v5_talk.go b/p2p/discover/v5_talk.go
index dca09870d8..648e091769 100644
--- a/p2p/discover/v5_talk.go
+++ b/p2p/discover/v5_talk.go
@@ -102,10 +102,10 @@ func (t *talkSystem) handleRequest(id enode.ID, addr netip.AddrPort, req *v5wire
}()
case <-timeout.C:
// Couldn't get it in time, drop the request.
+ t.dropCount++
if time.Since(t.lastLog) > 5*time.Second {
log.Warn("Dropping TALKREQ due to overload", "ndrop", t.dropCount)
t.lastLog = time.Now()
- t.dropCount++
}
case <-t.transport.closeCtx.Done():
// Transport closed, drop the request.
diff --git a/p2p/enode/iter.go b/p2p/enode/iter.go
index 54c2fc7258..d0ccf984d3 100644
--- a/p2p/enode/iter.go
+++ b/p2p/enode/iter.go
@@ -356,6 +356,7 @@ func (m *FairMix) AddSource(it Iterator) {
defer m.mu.Unlock()
if m.closed == nil {
+ it.Close()
return
}
m.wg.Add(1)
diff --git a/params/config.go b/params/config.go
index 1d69335f97..7b4493609f 100644
--- a/params/config.go
+++ b/params/config.go
@@ -69,7 +69,6 @@ var (
BlobScheduleConfig: &BlobScheduleConfig{
Cancun: DefaultCancunBlobConfig,
Prague: DefaultPragueBlobConfig,
- Osaka: DefaultOsakaBlobConfig,
BPO1: DefaultBPO1BlobConfig,
BPO2: DefaultBPO2BlobConfig,
},
@@ -105,7 +104,6 @@ var (
BlobScheduleConfig: &BlobScheduleConfig{
Cancun: DefaultCancunBlobConfig,
Prague: DefaultPragueBlobConfig,
- Osaka: DefaultOsakaBlobConfig,
BPO1: DefaultBPO1BlobConfig,
BPO2: DefaultBPO2BlobConfig,
},
@@ -141,7 +139,6 @@ var (
BlobScheduleConfig: &BlobScheduleConfig{
Cancun: DefaultCancunBlobConfig,
Prague: DefaultPragueBlobConfig,
- Osaka: DefaultOsakaBlobConfig,
BPO1: DefaultBPO1BlobConfig,
BPO2: DefaultBPO2BlobConfig,
},
@@ -177,7 +174,6 @@ var (
BlobScheduleConfig: &BlobScheduleConfig{
Cancun: DefaultCancunBlobConfig,
Prague: DefaultPragueBlobConfig,
- Osaka: DefaultOsakaBlobConfig,
BPO1: DefaultBPO1BlobConfig,
BPO2: DefaultBPO2BlobConfig,
},
@@ -235,7 +231,6 @@ var (
BlobScheduleConfig: &BlobScheduleConfig{
Cancun: DefaultCancunBlobConfig,
Prague: DefaultPragueBlobConfig,
- Osaka: DefaultOsakaBlobConfig,
},
}
@@ -330,7 +325,6 @@ var (
BlobScheduleConfig: &BlobScheduleConfig{
Cancun: DefaultCancunBlobConfig,
Prague: DefaultPragueBlobConfig,
- Osaka: DefaultOsakaBlobConfig,
},
}
@@ -379,12 +373,6 @@ var (
Max: 9,
UpdateFraction: 5007716,
}
- // DefaultOsakaBlobConfig is the default blob configuration for the Osaka fork.
- DefaultOsakaBlobConfig = &BlobConfig{
- Target: 6,
- Max: 9,
- UpdateFraction: 5007716,
- }
// DefaultBPO1BlobConfig is the default blob configuration for the BPO1 fork.
DefaultBPO1BlobConfig = &BlobConfig{
Target: 10,
@@ -413,7 +401,6 @@ var (
DefaultBlobSchedule = &BlobScheduleConfig{
Cancun: DefaultCancunBlobConfig,
Prague: DefaultPragueBlobConfig,
- Osaka: DefaultOsakaBlobConfig,
}
)
@@ -670,7 +657,7 @@ func (c *ChainConfig) Description() string {
banner += fmt.Sprintf(" - Prague: @%-10v blob: (%s)\n", *c.PragueTime, c.BlobScheduleConfig.Prague)
}
if c.OsakaTime != nil {
- banner += fmt.Sprintf(" - Osaka: @%-10v blob: (%s)\n", *c.OsakaTime, c.BlobScheduleConfig.Osaka)
+ banner += fmt.Sprintf(" - Osaka: @%-10v\n", *c.OsakaTime)
}
if c.BPO1Time != nil {
banner += fmt.Sprintf(" - BPO1: @%-10v blob: (%s)\n", *c.BPO1Time, c.BlobScheduleConfig.BPO1)
@@ -688,10 +675,10 @@ func (c *ChainConfig) Description() string {
banner += fmt.Sprintf(" - BPO5: @%-10v blob: (%s)\n", *c.BPO5Time, c.BlobScheduleConfig.BPO5)
}
if c.AmsterdamTime != nil {
- banner += fmt.Sprintf(" - Amsterdam: @%-10v blob: (%s)\n", *c.AmsterdamTime, c.BlobScheduleConfig.Amsterdam)
+ banner += fmt.Sprintf(" - Amsterdam: @%-10v\n", *c.AmsterdamTime)
}
if c.UBTTime != nil {
- banner += fmt.Sprintf(" - UBT: @%-10v blob: (%s)\n", *c.UBTTime, c.BlobScheduleConfig.UBT)
+ banner += fmt.Sprintf(" - UBT: @%-10v\n", *c.UBTTime)
}
banner += fmt.Sprintf("\nAll fork specifications can be found at https://ethereum.github.io/execution-specs/src/ethereum/forks/\n")
return banner
@@ -713,17 +700,18 @@ func (bc *BlobConfig) String() string {
}
// BlobScheduleConfig determines target and max number of blobs allow per fork.
+//
+// From Prague onward, the blob schedule is updated only at BPO (Blob Parameter-Only)
+// forks. Named forks such as Osaka or Amsterdam inherit the most recently configured
+// BPO entry and must not declare their own BlobConfig.
type BlobScheduleConfig struct {
- Cancun *BlobConfig `json:"cancun,omitempty"`
- Prague *BlobConfig `json:"prague,omitempty"`
- Osaka *BlobConfig `json:"osaka,omitempty"`
- BPO1 *BlobConfig `json:"bpo1,omitempty"`
- BPO2 *BlobConfig `json:"bpo2,omitempty"`
- BPO3 *BlobConfig `json:"bpo3,omitempty"`
- BPO4 *BlobConfig `json:"bpo4,omitempty"`
- BPO5 *BlobConfig `json:"bpo5,omitempty"`
- Amsterdam *BlobConfig `json:"amsterdam,omitempty"`
- UBT *BlobConfig `json:"ubt,omitempty"`
+ Cancun *BlobConfig `json:"cancun,omitempty"`
+ Prague *BlobConfig `json:"prague,omitempty"`
+ BPO1 *BlobConfig `json:"bpo1,omitempty"`
+ BPO2 *BlobConfig `json:"bpo2,omitempty"`
+ BPO3 *BlobConfig `json:"bpo3,omitempty"`
+ BPO4 *BlobConfig `json:"bpo4,omitempty"`
+ BPO5 *BlobConfig `json:"bpo5,omitempty"`
}
// IsHomestead returns whether num is either equal to the homestead block or greater.
@@ -1000,13 +988,11 @@ func (c *ChainConfig) CheckConfigForkOrder() error {
}{
{name: "cancun", timestamp: c.CancunTime, config: bsc.Cancun},
{name: "prague", timestamp: c.PragueTime, config: bsc.Prague},
- {name: "osaka", timestamp: c.OsakaTime, config: bsc.Osaka},
{name: "bpo1", timestamp: c.BPO1Time, config: bsc.BPO1},
{name: "bpo2", timestamp: c.BPO2Time, config: bsc.BPO2},
{name: "bpo3", timestamp: c.BPO3Time, config: bsc.BPO3},
{name: "bpo4", timestamp: c.BPO4Time, config: bsc.BPO4},
{name: "bpo5", timestamp: c.BPO5Time, config: bsc.BPO5},
- {name: "amsterdam", timestamp: c.AmsterdamTime, config: bsc.Amsterdam},
} {
if cur.config != nil {
if err := cur.config.validate(); err != nil {
@@ -1169,28 +1155,33 @@ func (c *ChainConfig) LatestFork(time uint64) forks.Fork {
}
}
-// BlobConfig returns the blob config associated with the provided fork.
+// BlobConfig returns the blob config active at the provided fork. Since named
+// forks (Osaka, Amsterdam, ...) no longer carry their own blob schedule, the
+// lookup walks down from fork through the BPO chain to Prague/Cancun and returns
+// the first non-nil entry.
func (c *ChainConfig) BlobConfig(fork forks.Fork) *BlobConfig {
- switch fork {
- case forks.BPO5:
- return c.BlobScheduleConfig.BPO5
- case forks.BPO4:
- return c.BlobScheduleConfig.BPO4
- case forks.BPO3:
- return c.BlobScheduleConfig.BPO3
- case forks.BPO2:
- return c.BlobScheduleConfig.BPO2
- case forks.BPO1:
- return c.BlobScheduleConfig.BPO1
- case forks.Osaka:
- return c.BlobScheduleConfig.Osaka
- case forks.Prague:
- return c.BlobScheduleConfig.Prague
- case forks.Cancun:
- return c.BlobScheduleConfig.Cancun
- default:
+ if c.BlobScheduleConfig == nil {
return nil
}
+ bsc := c.BlobScheduleConfig
+ chain := []struct {
+ at forks.Fork
+ cfg *BlobConfig
+ }{
+ {forks.BPO5, bsc.BPO5},
+ {forks.BPO4, bsc.BPO4},
+ {forks.BPO3, bsc.BPO3},
+ {forks.BPO2, bsc.BPO2},
+ {forks.BPO1, bsc.BPO1},
+ {forks.Prague, bsc.Prague},
+ {forks.Cancun, bsc.Cancun},
+ }
+ for _, e := range chain {
+ if e.at <= fork && e.cfg != nil {
+ return e.cfg
+ }
+ }
+ return nil
}
// ActiveSystemContracts returns the currently active system contracts at the
@@ -1240,6 +1231,8 @@ func (c *ChainConfig) Timestamp(fork forks.Fork) *uint64 {
return c.CancunTime
case fork == forks.Shanghai:
return c.ShanghaiTime
+ case fork == forks.Amsterdam:
+ return c.AmsterdamTime
default:
return nil
}
diff --git a/params/protocol_params.go b/params/protocol_params.go
index 84ded9df48..c4a74b794a 100644
--- a/params/protocol_params.go
+++ b/params/protocol_params.go
@@ -88,7 +88,6 @@ const (
LogTopicGas uint64 = 375 // Multiplied by the * of the LOG*, per LOG transaction. e.g. LOG0 incurs 0 * c_txLogTopicGas, LOG4 incurs 4 * c_txLogTopicGas.
CreateGas uint64 = 32000 // Once per CREATE operation & contract-creation transaction.
Create2Gas uint64 = 32000 // Once per CREATE2 operation
- CreateGasAmsterdam uint64 = 9000 // Regular gas portion of CREATE in Amsterdam (EIP-8037); state gas is charged separately.
CreateNGasEip4762 uint64 = 1000 // Once per CREATEn operations post-verkle
SelfdestructRefundGas uint64 = 24000 // Refunded following a selfdestruct operation.
MemoryGas uint64 = 3 // Times the address of the (highest referenced byte in memory + 1). NOTE: referencing happens on read, write and in instructions such as RETURN and CALL.
@@ -101,7 +100,26 @@ const (
TxAccessListAddressGas uint64 = 2400 // Per address specified in EIP 2930 access list
TxAccessListStorageKeyGas uint64 = 1900 // Per storage key specified in EIP 2930 access list
TxAuthTupleGas uint64 = 12500 // Per auth tuple code specified in EIP-7702
- TxAuthTupleRegularGas uint64 = 7500 // Per auth tuple regular gas specified in EIP-8037
+
+ RegularPerAuthBaseCost uint64 = 7816 // As defined by EIP-8037 and EIP-8038
+
+ // EIP-2780: resource-based intrinsic transaction gas.
+ TxBaseCost2780 uint64 = 12000
+ ColdAccountAccess2780 uint64 = 3000
+ CreateAccess2780 uint64 = 11000
+ TxValueCost2780 uint64 = 4244
+ TransferLogCost2780 uint64 = 1756
+
+ // EIP-8038: state-access gas cost update (Amsterdam).
+ ColdAccountAccessAmsterdam uint64 = 3000 // COLD_ACCOUNT_ACCESS: cold touch of an account
+ AccountWriteAmsterdam uint64 = 8000 // ACCOUNT_WRITE: surcharge for first-time write to an account
+ CallValueTransferAmsterdam uint64 = 10300 // CALL_VALUE = ACCOUNT_WRITE + CallStipend (2300)
+ ColdStorageAccessAmsterdam uint64 = 3000 // COLD_STORAGE_ACCESS: cold touch of a storage slot
+ StorageWriteAmsterdam uint64 = 10000 // STORAGE_WRITE: surcharge for first-time write to a storage slot
+ StorageClearRefundAmsterdam uint64 = 12480 // STORAGE_CLEAR_REFUND: refund for clearing a storage slot
+ CreateAccessAmsterdam uint64 = 11000 // CREATE_ACCESS = ACCOUNT_WRITE + COLD_STORAGE_ACCESS
+ TxAccessListAddressGasAmsterdam uint64 = 3000 // ACCESS_LIST_ADDRESS_COST
+ TxAccessListStorageKeyGasAmsterdam uint64 = 3000 // ACCESS_LIST_STORAGE_KEY_COST
// These have been changed during the course of the chain
CallGasFrontier uint64 = 40 // Once per CALL operation & message call transaction.
@@ -246,6 +264,10 @@ var (
BuilderDepositCode = common.FromHex("0x3373fffffffffffffffffffffffffffffffffffffffe146101065760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1461023457600182026001905f5b5f82111560695781019083028483029004916001019190604e565b90939004925050503660b814608957366102345734610234575f5260205ff35b8034106102345760383567ffffffffffffffff1680633b9aca001161023457633b9aca00029034031061023457600154600101600155600354806006026004015f358155600101602035815560010160403581556001016060358155600101608035815560010160a035905560b85f5f3760b85fa0600101600355005b600354600254808203806101001161011d57506101005b5f5b8181146101c3578281016006026004018160b8028154815260200181600101548152602001816002015480825260401c67ffffffffffffffff16816010018160381c81600701538160301c81600601538160281c81600501538160201c81600401538160181c81600301538160101c81600201538160081c81600101535360200181600301548152602001816004015481526020019060050154905260010161011f565b91018092146101d557906002556101e0565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff141561020d57505f5b6001546020828201116102225750505f610228565b01602090035b5f555f60015560b8025ff35b5f5ffd")
BuilderExitAddress = common.HexToAddress("0x000014574A74c805590AFF9499fc7A690f008282")
BuilderExitCode = common.FromHex("0x3373fffffffffffffffffffffffffffffffffffffffe1460cb5760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1461018857600182026001905f5b5f82111560685781019083028483029004916001019190604d565b909390049250505036603014608857366101885734610188575f5260205ff35b341061018857600154600101600155600354806003026004013381556001015f35815560010160203590553360601b5f5260305f60143760445fa0600101600355005b6003546002548082038060101160df575060105b5f5b8181146101175782810160030260040181604402815460601b8152601401816001015481526020019060020154905260010160e1565b91018092146101295790600255610134565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff141561016157505f5b6001546002828201116101765750505f61017c565b01600290035b5f555f6001556044025ff35b5f5ffd")
+
+ // EIP-7997 - Deterministic deployment factory (keyless CREATE2 factory)
+ DeterministicFactoryAddress = common.HexToAddress("0x4e59b44847b379578588920cA78FbF26c0B4956C")
+ DeterministicFactoryCode = common.FromHex("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3")
)
// System log events.
diff --git a/rpc/types.go b/rpc/types.go
index 578d3f86dd..7720a8d9c6 100644
--- a/rpc/types.go
+++ b/rpc/types.go
@@ -158,6 +158,9 @@ func (bnh *BlockNumberOrHash) UnmarshalJSON(data []byte) error {
if e.BlockNumber != nil && e.BlockHash != nil {
return errors.New("cannot specify both BlockHash and BlockNumber, choose one or the other")
}
+ if e.BlockNumber == nil && e.BlockHash == nil {
+ return errors.New("must specify either BlockHash or BlockNumber")
+ }
bnh.BlockNumber = e.BlockNumber
bnh.BlockHash = e.BlockHash
bnh.RequireCanonical = e.RequireCanonical
diff --git a/rpc/types_test.go b/rpc/types_test.go
index 9dd6fa6508..35294c3dba 100644
--- a/rpc/types_test.go
+++ b/rpc/types_test.go
@@ -109,6 +109,8 @@ func TestBlockNumberOrHash_UnmarshalJSON(t *testing.T) {
27: {`{"blockNumber":"safe"}`, false, BlockNumberOrHashWithNumber(SafeBlockNumber)},
28: {`{"blockNumber":"finalized"}`, false, BlockNumberOrHashWithNumber(FinalizedBlockNumber)},
29: {`{"blockNumber":"0x1", "blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000"}`, true, BlockNumberOrHash{}},
+ 30: {`{}`, true, BlockNumberOrHash{}},
+ 31: {`{"requireCanonical":true}`, true, BlockNumberOrHash{}},
}
for i, test := range tests {
diff --git a/tests/init.go b/tests/init.go
index 2550eb1231..67f199203f 100644
--- a/tests/init.go
+++ b/tests/init.go
@@ -434,7 +434,6 @@ var Forks = map[string]*params.ChainConfig{
BlobScheduleConfig: ¶ms.BlobScheduleConfig{
Cancun: params.DefaultCancunBlobConfig,
Prague: params.DefaultPragueBlobConfig,
- Osaka: params.DefaultOsakaBlobConfig,
},
},
"PragueToOsakaAtTime15k": {
@@ -461,7 +460,6 @@ var Forks = map[string]*params.ChainConfig{
BlobScheduleConfig: ¶ms.BlobScheduleConfig{
Cancun: params.DefaultCancunBlobConfig,
Prague: params.DefaultPragueBlobConfig,
- Osaka: params.DefaultOsakaBlobConfig,
},
},
"BPO1": {
@@ -489,7 +487,6 @@ var Forks = map[string]*params.ChainConfig{
BlobScheduleConfig: ¶ms.BlobScheduleConfig{
Cancun: params.DefaultCancunBlobConfig,
Prague: params.DefaultPragueBlobConfig,
- Osaka: params.DefaultOsakaBlobConfig,
BPO1: bpo1BlobConfig,
},
},
@@ -518,7 +515,6 @@ var Forks = map[string]*params.ChainConfig{
BlobScheduleConfig: ¶ms.BlobScheduleConfig{
Cancun: params.DefaultCancunBlobConfig,
Prague: params.DefaultPragueBlobConfig,
- Osaka: params.DefaultOsakaBlobConfig,
BPO1: bpo1BlobConfig,
},
},
@@ -548,7 +544,6 @@ var Forks = map[string]*params.ChainConfig{
BlobScheduleConfig: ¶ms.BlobScheduleConfig{
Cancun: params.DefaultCancunBlobConfig,
Prague: params.DefaultPragueBlobConfig,
- Osaka: params.DefaultOsakaBlobConfig,
BPO1: bpo1BlobConfig,
BPO2: bpo2BlobConfig,
},
@@ -579,7 +574,6 @@ var Forks = map[string]*params.ChainConfig{
BlobScheduleConfig: ¶ms.BlobScheduleConfig{
Cancun: params.DefaultCancunBlobConfig,
Prague: params.DefaultPragueBlobConfig,
- Osaka: params.DefaultOsakaBlobConfig,
BPO1: bpo1BlobConfig,
BPO2: bpo2BlobConfig,
},
@@ -611,7 +605,6 @@ var Forks = map[string]*params.ChainConfig{
BlobScheduleConfig: ¶ms.BlobScheduleConfig{
Cancun: params.DefaultCancunBlobConfig,
Prague: params.DefaultPragueBlobConfig,
- Osaka: params.DefaultOsakaBlobConfig,
BPO1: bpo1BlobConfig,
BPO2: bpo2BlobConfig,
BPO3: params.DefaultBPO3BlobConfig,
@@ -644,7 +637,6 @@ var Forks = map[string]*params.ChainConfig{
BlobScheduleConfig: ¶ms.BlobScheduleConfig{
Cancun: params.DefaultCancunBlobConfig,
Prague: params.DefaultPragueBlobConfig,
- Osaka: params.DefaultOsakaBlobConfig,
BPO1: bpo1BlobConfig,
BPO2: bpo2BlobConfig,
BPO3: params.DefaultBPO3BlobConfig,
@@ -678,7 +670,6 @@ var Forks = map[string]*params.ChainConfig{
BlobScheduleConfig: ¶ms.BlobScheduleConfig{
Cancun: params.DefaultCancunBlobConfig,
Prague: params.DefaultPragueBlobConfig,
- Osaka: params.DefaultOsakaBlobConfig,
BPO1: bpo1BlobConfig,
BPO2: bpo2BlobConfig,
BPO3: params.DefaultBPO3BlobConfig,
@@ -713,7 +704,6 @@ var Forks = map[string]*params.ChainConfig{
BlobScheduleConfig: ¶ms.BlobScheduleConfig{
Cancun: params.DefaultCancunBlobConfig,
Prague: params.DefaultPragueBlobConfig,
- Osaka: params.DefaultOsakaBlobConfig,
BPO1: bpo1BlobConfig,
BPO2: bpo2BlobConfig,
BPO3: params.DefaultBPO3BlobConfig,
@@ -747,14 +737,12 @@ var Forks = map[string]*params.ChainConfig{
AmsterdamTime: u64(0),
DepositContractAddress: params.MainnetChainConfig.DepositContractAddress,
BlobScheduleConfig: ¶ms.BlobScheduleConfig{
- Cancun: params.DefaultCancunBlobConfig,
- Prague: params.DefaultPragueBlobConfig,
- Osaka: params.DefaultOsakaBlobConfig,
- BPO1: bpo1BlobConfig,
- BPO2: bpo2BlobConfig,
- BPO3: params.DefaultBPO3BlobConfig,
- BPO4: params.DefaultBPO4BlobConfig,
- Amsterdam: params.DefaultBPO4BlobConfig, // TODO update when defined
+ Cancun: params.DefaultCancunBlobConfig,
+ Prague: params.DefaultPragueBlobConfig,
+ BPO1: bpo1BlobConfig,
+ BPO2: bpo2BlobConfig,
+ BPO3: params.DefaultBPO3BlobConfig,
+ BPO4: params.DefaultBPO4BlobConfig,
},
},
"Verkle": {
@@ -801,7 +789,6 @@ var Forks = map[string]*params.ChainConfig{
BlobScheduleConfig: ¶ms.BlobScheduleConfig{
Cancun: params.DefaultCancunBlobConfig,
Prague: params.DefaultPragueBlobConfig,
- Osaka: params.DefaultOsakaBlobConfig,
},
},
}
diff --git a/tests/transaction_test_util.go b/tests/transaction_test_util.go
index 91f7d6c3ec..010c31324b 100644
--- a/tests/transaction_test_util.go
+++ b/tests/transaction_test_util.go
@@ -27,6 +27,7 @@ import (
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"
+ "github.com/holiman/uint256"
)
// TransactionTest checks RLP decoding and sender derivation of transactions.
@@ -81,7 +82,11 @@ func (tt *TransactionTest) Run() error {
return
}
// Intrinsic cost
- cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules, params.CostPerStateByte)
+ value, overflow := uint256.FromBig(tx.Value())
+ if overflow {
+ return sender, hash, 0, errors.New("value exceeds 256 bits")
+ }
+ cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), sender, tx.To(), value, rules, params.CostPerStateByte)
if err != nil {
return
}
@@ -92,7 +97,7 @@ func (tt *TransactionTest) Run() error {
if rules.IsPrague {
var floorDataGas uint64
- floorDataGas, err = core.FloorDataGas(rules, tx.Data(), tx.AccessList())
+ floorDataGas, err = core.FloorDataGas(rules, sender, tx.To(), value, tx.Data(), tx.AccessList())
if err != nil {
return
}
diff --git a/trie/bintrie/trie.go b/trie/bintrie/trie.go
index 7b05e68df9..e8b3021284 100644
--- a/trie/bintrie/trie.go
+++ b/trie/bintrie/trie.go
@@ -474,3 +474,32 @@ func (t *BinaryTrie) PrefetchStorage(addr common.Address, keys [][]byte) error {
func (t *BinaryTrie) Witness() map[string][]byte {
return t.tracer.Values()
}
+
+// UpdateStorageBatch updates a list of storage slots sequentially.
+func (t *BinaryTrie) UpdateStorageBatch(address common.Address, keys [][]byte, values [][]byte) error {
+ if len(keys) != len(values) {
+ return fmt.Errorf("keys and values length mismatch: %d != %d", len(keys), len(values))
+ }
+ for i, key := range keys {
+ if err := t.UpdateStorage(address, key, values[i]); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// UpdateAccountBatch updates a list of accounts sequentially.
+func (t *BinaryTrie) UpdateAccountBatch(addresses []common.Address, accounts []*types.StateAccount, codeLens []int) error {
+ if len(addresses) != len(accounts) {
+ return fmt.Errorf("addresses and accounts length mismatch: %d != %d", len(addresses), len(accounts))
+ }
+ if len(addresses) != len(codeLens) {
+ return fmt.Errorf("addresses and code length mismatch: %d != %d", len(addresses), len(codeLens))
+ }
+ for i, addr := range addresses {
+ if err := t.UpdateAccount(addr, accounts[i], codeLens[i]); err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/trie/secure_trie.go b/trie/secure_trie.go
index 4d03ca45f0..9f6cea9790 100644
--- a/trie/secure_trie.go
+++ b/trie/secure_trie.go
@@ -210,6 +210,29 @@ func (t *StateTrie) UpdateStorage(_ common.Address, key, value []byte) error {
return nil
}
+// UpdateStorageBatch attempts to update a list storages in the batch manner.
+func (t *StateTrie) UpdateStorageBatch(_ common.Address, keys [][]byte, values [][]byte) error {
+ var (
+ hkeys = make([][]byte, 0, len(keys))
+ evals = make([][]byte, 0, len(values))
+ )
+ for _, key := range keys {
+ hk := crypto.Keccak256(key)
+ if t.preimages != nil {
+ t.secKeyCache[common.Hash(hk)] = common.CopyBytes(key)
+ }
+ hkeys = append(hkeys, hk)
+ }
+ for _, val := range values {
+ data, err := rlp.EncodeToBytes(val)
+ if err != nil {
+ return err
+ }
+ evals = append(evals, data)
+ }
+ return t.trie.UpdateBatch(hkeys, evals)
+}
+
// UpdateAccount will abstract the write of an account to the secure trie.
func (t *StateTrie) UpdateAccount(address common.Address, acc *types.StateAccount, _ int) error {
hk := crypto.Keccak256(address.Bytes())
@@ -226,6 +249,29 @@ func (t *StateTrie) UpdateAccount(address common.Address, acc *types.StateAccoun
return nil
}
+// UpdateAccountBatch attempts to update a list accounts in the batch manner.
+func (t *StateTrie) UpdateAccountBatch(addresses []common.Address, accounts []*types.StateAccount, _ []int) error {
+ var (
+ hkeys = make([][]byte, 0, len(addresses))
+ values = make([][]byte, 0, len(accounts))
+ )
+ for _, addr := range addresses {
+ hk := crypto.Keccak256(addr.Bytes())
+ if t.preimages != nil {
+ t.secKeyCache[common.Hash(hk)] = addr.Bytes()
+ }
+ hkeys = append(hkeys, hk)
+ }
+ for _, acc := range accounts {
+ data, err := rlp.EncodeToBytes(acc)
+ if err != nil {
+ return err
+ }
+ values = append(values, data)
+ }
+ return t.trie.UpdateBatch(hkeys, values)
+}
+
func (t *StateTrie) UpdateContractCode(_ common.Address, _ common.Hash, _ []byte) error {
return nil
}
diff --git a/trie/tracer.go b/trie/tracer.go
index 04122d1384..042fa468bf 100644
--- a/trie/tracer.go
+++ b/trie/tracer.go
@@ -33,12 +33,10 @@ import (
// while the latter is inserted/deleted in order to follow the rule of trie.
// This tool can track all of them no matter the node is embedded in its
// parent or not, but valueNode is never tracked.
-//
-// Note opTracer is not thread-safe, callers should be responsible for handling
-// the concurrency issues by themselves.
type opTracer struct {
inserts map[string]struct{}
deletes map[string]struct{}
+ lock sync.RWMutex
}
// newOpTracer initializes the tracer for capturing trie changes.
@@ -53,6 +51,9 @@ func newOpTracer() *opTracer {
// in the deletion set (resurrected node), then just wipe it from
// the deletion set as it's "untouched".
func (t *opTracer) onInsert(path []byte) {
+ t.lock.Lock()
+ defer t.lock.Unlock()
+
if _, present := t.deletes[string(path)]; present {
delete(t.deletes, string(path))
return
@@ -64,6 +65,9 @@ func (t *opTracer) onInsert(path []byte) {
// in the addition set, then just wipe it from the addition set
// as it's untouched.
func (t *opTracer) onDelete(path []byte) {
+ t.lock.Lock()
+ defer t.lock.Unlock()
+
if _, present := t.inserts[string(path)]; present {
delete(t.inserts, string(path))
return
@@ -73,12 +77,18 @@ func (t *opTracer) onDelete(path []byte) {
// reset clears the content tracked by tracer.
func (t *opTracer) reset() {
+ t.lock.Lock()
+ defer t.lock.Unlock()
+
clear(t.inserts)
clear(t.deletes)
}
// copy returns a deep copied tracer instance.
func (t *opTracer) copy() *opTracer {
+ t.lock.RLock()
+ defer t.lock.RUnlock()
+
return &opTracer{
inserts: maps.Clone(t.inserts),
deletes: maps.Clone(t.deletes),
@@ -87,6 +97,9 @@ func (t *opTracer) copy() *opTracer {
// deletedList returns a list of node paths which are deleted from the trie.
func (t *opTracer) deletedList() [][]byte {
+ t.lock.RLock()
+ defer t.lock.RUnlock()
+
paths := make([][]byte, 0, len(t.deletes))
for path := range t.deletes {
paths = append(paths, []byte(path))
diff --git a/trie/transitiontrie/transition.go b/trie/transitiontrie/transition.go
index 3e5511be9e..d939e804e3 100644
--- a/trie/transitiontrie/transition.go
+++ b/trie/transitiontrie/transition.go
@@ -144,6 +144,19 @@ func (t *TransitionTrie) UpdateStorage(address common.Address, key []byte, value
return t.overlay.UpdateStorage(address, key, v)
}
+// UpdateStorageBatch attempts to update a list storages in the batch manner.
+func (t *TransitionTrie) UpdateStorageBatch(address common.Address, keys [][]byte, values [][]byte) error {
+ if len(keys) != len(values) {
+ return fmt.Errorf("keys and values length mismatch: %d != %d", len(keys), len(values))
+ }
+ for i, key := range keys {
+ if err := t.UpdateStorage(address, key, values[i]); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
// UpdateAccount abstract an account write to the trie.
func (t *TransitionTrie) UpdateAccount(addr common.Address, account *types.StateAccount, codeLen int) error {
// NOTE: before the rebase, this was saving the state root, so that OpenStorageTrie
@@ -152,6 +165,22 @@ func (t *TransitionTrie) UpdateAccount(addr common.Address, account *types.State
return t.overlay.UpdateAccount(addr, account, codeLen)
}
+// UpdateAccountBatch attempts to update a list accounts in the batch manner.
+func (t *TransitionTrie) UpdateAccountBatch(addresses []common.Address, accounts []*types.StateAccount, codeLens []int) error {
+ if len(addresses) != len(accounts) {
+ return fmt.Errorf("address and accounts length mismatch: %d != %d", len(addresses), len(accounts))
+ }
+ if len(addresses) != len(codeLens) {
+ return fmt.Errorf("address and code length mismatch: %d != %d", len(addresses), len(codeLens))
+ }
+ for i, addr := range addresses {
+ if err := t.UpdateAccount(addr, accounts[i], codeLens[i]); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
// DeleteStorage removes any existing value for key from the trie. If a node was not
// found in the database, a trie.MissingNodeError is returned.
func (t *TransitionTrie) DeleteStorage(addr common.Address, key []byte) error {
diff --git a/trie/trie.go b/trie/trie.go
index 1ef2c2f1a6..0026c6048c 100644
--- a/trie/trie.go
+++ b/trie/trie.go
@@ -480,6 +480,95 @@ func (t *Trie) insert(n node, prefix, key []byte, value node) (bool, node, error
}
}
+// parallelUpdateThreshold is the minimum number of entries a batch must contain
+// before UpdateBatch processes them concurrently; smaller batches are applied
+// sequentially to avoid the goroutine overhead.
+const parallelUpdateThreshold = 4
+
+// UpdateBatch updates a batch of entries concurrently.
+func (t *Trie) UpdateBatch(keys [][]byte, values [][]byte) error {
+ // Short circuit if the trie is already committed and unusable.
+ if t.committed {
+ return ErrCommitted
+ }
+ if len(keys) != len(values) {
+ return fmt.Errorf("keys and values length mismatch: %d != %d", len(keys), len(values))
+ }
+ // Insert the entries sequentially if there are not too many
+ // trie nodes in the trie.
+ fn, ok := t.root.(*fullNode)
+ if !ok || len(keys) < parallelUpdateThreshold {
+ return t.updateSequential(keys, values)
+ }
+ // Group the entries by the first nibble of the (hex) key, so that each group
+ // can be applied to a distinct child of the root independently.
+ var (
+ ikeys = make(map[byte][][]byte)
+ ivals = make(map[byte][][]byte)
+ deleted [17]bool // child positions receiving at least one deletion
+ eg errgroup.Group
+ )
+ for i, key := range keys {
+ hkey := keybytesToHex(key)
+ ikeys[hkey[0]] = append(ikeys[hkey[0]], hkey)
+ ivals[hkey[0]] = append(ivals[hkey[0]], values[i])
+ if len(values[i]) == 0 {
+ deleted[hkey[0]] = true
+ }
+ }
+ // If the root may be collapsed after applying the updates, fallback
+ // to the sequential mode to eliminate the additional complexity.
+ survivors := 0
+ for i, child := range &fn.Children {
+ if child != nil && !deleted[i] {
+ survivors++
+ }
+ }
+ if survivors < 2 {
+ return t.updateSequential(keys, values)
+ }
+ // Execute the trie updates in concurrent mode
+ fn.flags = t.newFlag()
+ for pos, ks := range ikeys {
+ eg.Go(func() error {
+ vs := ivals[pos]
+ prefix := []byte{pos}
+ for i, k := range ks {
+ if len(vs[i]) != 0 {
+ _, n, err := t.insert(fn.Children[pos], prefix, k[1:], valueNode(vs[i]))
+ if err != nil {
+ return err
+ }
+ fn.Children[pos] = n
+ } else {
+ _, n, err := t.delete(fn.Children[pos], prefix, k[1:])
+ if err != nil {
+ return err
+ }
+ fn.Children[pos] = n
+ }
+ }
+ return nil
+ })
+ }
+ if err := eg.Wait(); err != nil {
+ return err
+ }
+ t.unhashed += len(keys)
+ t.uncommitted += len(keys)
+ return nil
+}
+
+// updateSequential applies the given entries to the trie one by one.
+func (t *Trie) updateSequential(keys [][]byte, values [][]byte) error {
+ for i, key := range keys {
+ if err := t.Update(key, values[i]); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
// MustDelete is a wrapper of Delete and will omit any encountered error but
// just print out an error message.
func (t *Trie) MustDelete(key []byte) {
diff --git a/trie/trie_test.go b/trie/trie_test.go
index 3661933e22..949f381f07 100644
--- a/trie/trie_test.go
+++ b/trie/trie_test.go
@@ -1580,3 +1580,57 @@ func BenchmarkTrieSeqPrefetch(b *testing.B) {
}
}
}
+
+func TestUpdateBatch(t *testing.T) {
+ testUpdateBatch(t, []kv{
+ {k: []byte("do"), v: []byte("verb")},
+ {k: []byte("ether"), v: []byte("wookiedoo")},
+ {k: []byte("horse"), v: []byte("stallion")},
+ {k: []byte("shaman"), v: []byte("horse")},
+ {k: []byte("doge"), v: []byte("coin")},
+ {k: []byte("dog"), v: []byte("puppy")},
+ })
+
+ var entries []kv
+ for i := 0; i < 256; i++ {
+ entries = append(entries, kv{k: testrand.Bytes(32), v: testrand.Bytes(32)})
+ }
+ testUpdateBatch(t, entries)
+}
+
+func testUpdateBatch(t *testing.T, entries []kv) {
+ var (
+ base = NewEmpty(nil)
+ keys [][]byte
+ vals [][]byte
+ )
+ for _, entry := range entries {
+ base.Update(entry.k, entry.v)
+ keys = append(keys, entry.k)
+ vals = append(vals, entry.v)
+ }
+ for i := 0; i < 10; i++ {
+ k, v := testrand.Bytes(32), testrand.Bytes(32)
+ base.Update(k, v)
+ keys = append(keys, k)
+ vals = append(vals, v)
+ }
+
+ cmp := NewEmpty(nil)
+ if err := cmp.UpdateBatch(keys, vals); err != nil {
+ t.Fatalf("Failed to update batch, %v", err)
+ }
+
+ // Traverse the original tree, the changes made on the copy one shouldn't
+ // affect the old one
+ for _, key := range keys {
+ v1, _ := base.Get(key)
+ v2, _ := cmp.Get(key)
+ if !bytes.Equal(v1, v2) {
+ t.Errorf("Unexpected data, key: %v, want: %v, got: %v", key, v1, v2)
+ }
+ }
+ if base.Hash() != cmp.Hash() {
+ t.Errorf("Hash mismatch: want %x, got %x", base.Hash(), cmp.Hash())
+ }
+}
diff --git a/triedb/pathdb/nodes.go b/triedb/pathdb/nodes.go
index 62c72c1953..590f7b499d 100644
--- a/triedb/pathdb/nodes.go
+++ b/triedb/pathdb/nodes.go
@@ -279,7 +279,7 @@ func (s *nodeSet) decode(r *rlp.Stream) error {
// write flushes nodes into the provided database batch as a whole.
func (s *nodeSet) write(batch ethdb.Batch, clean *fastcache.Cache) int {
- nodes := make(map[common.Hash]map[string]*trienode.Node)
+ nodes := make(map[common.Hash]map[string]*trienode.Node, len(s.storageNodes)+1)
if len(s.accountNodes) > 0 {
nodes[common.Hash{}] = s.accountNodes
}