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/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/cmd/devp2p/discv4cmd.go b/cmd/devp2p/discv4cmd.go
index 84c7ef0c44..1089210c57 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)
}
}
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/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/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/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/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/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/eip8246_test.go b/core/eip8246_test.go
new file mode 100644
index 0000000000..2fd39e3d44
--- /dev/null
+++ b/core/eip8246_test.go
@@ -0,0 +1,105 @@
+// 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)
+ blobConfig := *config.BlobScheduleConfig
+ blobConfig.Amsterdam = blobConfig.Osaka
+ config.BlobScheduleConfig = &blobConfig
+
+ 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_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/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..5b81abef6f 100644
--- a/core/state_processor.go
+++ b/core/state_processor.go
@@ -88,10 +88,10 @@ 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()))
diff --git a/core/state_transition.go b/core/state_transition.go
index bbeb163b16..2923e110c7 100644
--- a/core/state_transition.go
+++ b/core/state_transition.go
@@ -68,16 +68,19 @@ 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
}
@@ -151,8 +154,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 +234,19 @@ func FloorDataGas(rules params.Rules, data []byte, accessList types.AccessList)
tokenCost = params.TxCostFloorPerToken
}
+ // The floor is anchored to the transaction base cost. Under EIP-2780 that
+ // base is the per-resource decomposition (the same one used by the intrinsic
+ // gas), so the floor never undercuts the transaction's own base.
+ floorBase := params.TxGas
+ if rules.IsAmsterdam {
+ floorBase = intrinsicBaseGasEIP2780(from, to, value)
+ }
// 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 +657,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 +672,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
}
@@ -705,9 +748,16 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
if addr, ok := types.ParseDelegation(st.state.GetCode(*msg.To)); ok {
st.state.AddAddressToAccessList(addr)
}
- // 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)
+ // 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)
+ }
}
// Settle down the gas usage and refund the ETH back if any remaining
@@ -740,12 +790,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 +798,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,
diff --git a/core/state_transition_test.go b/core/state_transition_test.go
index be2de7f511..ace43aa13a 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,8 +240,9 @@ func TestIntrinsicGas(t *testing.T) {
},
isEIP2028: true,
isAmsterdam: true,
- // base access-list charge + EIP-7981 extra
- want: vm.GasCosts{RegularGas: params.TxGas +
+ // 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.TxAccessListAddressGas + 3*params.TxAccessListStorageKeyGas +
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,7 +296,7 @@ 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*amsterdamAddressCost + 1*amsterdamStorageKeyCost,
@@ -314,7 +318,7 @@ func TestIntrinsicGas(t *testing.T) {
// regular: TxAuthTupleRegularGas (7500) 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*amsterdamAddressCost + 1*amsterdamStorageKeyCost +
@@ -322,6 +326,28 @@ func TestIntrinsicGas(t *testing.T) {
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 +357,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 0e182947f0..a2ec06bed4 100644
--- a/core/txpool/blobpool/blobpool_test.go
+++ b/core/txpool/blobpool/blobpool_test.go
@@ -1215,11 +1215,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 da13cda4b2..3a0436ab85 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/validation.go b/core/txpool/validation.go
index 4651c06b3e..b53e3ee2bf 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 (
@@ -116,16 +117,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
}
@@ -134,7 +141,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/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/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/operations_acl.go b/core/vm/operations_acl.go
index 8b2a627fef..e07eba451f 100644
--- a/core/vm/operations_acl.go
+++ b/core/vm/operations_acl.go
@@ -257,6 +257,14 @@ 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)
@@ -336,6 +344,10 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc intrinsicGasFunc) gasFunc {
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.
@@ -416,6 +428,10 @@ func makeCallVariantGasCallEIP8037(regularFunc regularGasFunc, stateGasFunc stat
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/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/handler_eth.go b/eth/handler_eth.go
index 8704a86af4..7922687e0c 100644
--- a/eth/handler_eth.go
+++ b/eth/handler_eth.go
@@ -89,7 +89,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/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/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/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/params/config.go b/params/config.go
index 1dadf0e43f..b277fb303c 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
diff --git a/params/protocol_params.go b/params/protocol_params.go
index 84ded9df48..7416eb8cff 100644
--- a/params/protocol_params.go
+++ b/params/protocol_params.go
@@ -103,6 +103,13 @@ const (
TxAuthTupleGas uint64 = 12500 // Per auth tuple code specified in EIP-7702
TxAuthTupleRegularGas uint64 = 7500 // Per auth tuple regular gas specified in EIP-8037
+ // EIP-2780: resource-based intrinsic transaction gas.
+ TxBaseCost2780 uint64 = 12000
+ ColdAccountAccess2780 uint64 = 3000
+ CreateAccess2780 uint64 = 11000
+ TxValueCost2780 uint64 = 4244
+ TransferLogCost2780 uint64 = 1756
+
// These have been changed during the course of the chain
CallGasFrontier uint64 = 40 // Once per CALL operation & message call transaction.
CallGasEIP150 uint64 = 700 // Static portion of gas for CALL-derivates after EIP 150 (Tangerine)
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
}