diff --git a/common/hexutil/json.go b/common/hexutil/json.go
index 50db208118..e0ac98f52d 100644
--- a/common/hexutil/json.go
+++ b/common/hexutil/json.go
@@ -23,6 +23,8 @@ import (
"math/big"
"reflect"
"strconv"
+
+ "github.com/holiman/uint256"
)
var (
@@ -30,6 +32,7 @@ var (
bigT = reflect.TypeOf((*Big)(nil))
uintT = reflect.TypeOf(Uint(0))
uint64T = reflect.TypeOf(Uint64(0))
+ u256T = reflect.TypeOf((*uint256.Int)(nil))
)
// Bytes marshals/unmarshals as a JSON string with 0x prefix.
@@ -225,6 +228,48 @@ func (b *Big) UnmarshalGraphQL(input interface{}) error {
return err
}
+// U256 marshals/unmarshals as a JSON string with 0x prefix.
+// The zero value marshals as "0x0".
+type U256 uint256.Int
+
+// MarshalText implements encoding.TextMarshaler
+func (b U256) MarshalText() ([]byte, error) {
+ u256 := (*uint256.Int)(&b)
+ return []byte(u256.Hex()), nil
+}
+
+// UnmarshalJSON implements json.Unmarshaler.
+func (b *U256) UnmarshalJSON(input []byte) error {
+ // The uint256.Int.UnmarshalJSON method accepts "dec", "0xhex"; we must be
+ // more strict, hence we check string and invoke SetFromHex directly.
+ if !isString(input) {
+ return errNonString(u256T)
+ }
+ // The hex decoder needs to accept empty string ("") as '0', which uint256.Int
+ // would reject.
+ if len(input) == 2 {
+ (*uint256.Int)(b).Clear()
+ return nil
+ }
+ err := (*uint256.Int)(b).SetFromHex(string(input[1 : len(input)-1]))
+ if err != nil {
+ return &json.UnmarshalTypeError{Value: err.Error(), Type: u256T}
+ }
+ return nil
+}
+
+// UnmarshalText implements encoding.TextUnmarshaler
+func (b *U256) UnmarshalText(input []byte) error {
+ // The uint256.Int.UnmarshalText method accepts "dec", "0xhex"; we must be
+ // more strict, hence we check string and invoke SetFromHex directly.
+ return (*uint256.Int)(b).SetFromHex(string(input))
+}
+
+// String returns the hex encoding of b.
+func (b *U256) String() string {
+ return (*uint256.Int)(b).Hex()
+}
+
// Uint64 marshals/unmarshals as a JSON string with 0x prefix.
// The zero value marshals as "0x0".
type Uint64 uint64
diff --git a/core/txpool/txpool.go b/core/txpool/txpool.go
index 1a300d95db..b80fe656bd 100644
--- a/core/txpool/txpool.go
+++ b/core/txpool/txpool.go
@@ -637,6 +637,9 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
if tx.Type() == types.BlobTxType {
return errors.New("BlobTxType of transaction is currently not supported.")
}
+ if tx.Type() == types.SetCodeTxType {
+ return errors.New("SetCodeTxType of transaction is currently not supported.")
+ }
// Accept only legacy transactions until EIP-2718/2930 activates.
if !pool.eip2718 && tx.Type() != types.LegacyTxType {
return core.ErrTxTypeNotSupported
diff --git a/core/types/block.go b/core/types/block.go
index 35a7c853d6..dc970ffd51 100644
--- a/core/types/block.go
+++ b/core/types/block.go
@@ -93,6 +93,9 @@ type Header struct {
// ParentBeaconRoot was added by EIP-4788 and is ignored in legacy headers.
ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
+
+ // RequestsHash was added by EIP-7685 and is ignored in legacy headers.
+ RequestsHash *common.Hash `json:"requestsRoot" rlp:"optional"`
}
// field type overrides for gencodec
@@ -299,6 +302,10 @@ func CopyHeader(h *Header) *Header {
cpy.ParentBeaconRoot = new(common.Hash)
*cpy.ParentBeaconRoot = *h.ParentBeaconRoot
}
+ if h.RequestsHash != nil {
+ cpy.RequestsHash = new(common.Hash)
+ *cpy.RequestsHash = *h.RequestsHash
+ }
return &cpy
}
@@ -372,7 +379,8 @@ func (b *Block) Header() *Header { return CopyHeader(b.header) }
// Body returns the non-header content of the block.
func (b *Block) Body() *Body { return &Body{b.transactions, b.uncles, b.withdrawals} }
-func (b *Block) BeaconRoot() *common.Hash { return b.header.ParentBeaconRoot }
+func (b *Block) BeaconRoot() *common.Hash { return b.header.ParentBeaconRoot }
+func (b *Block) RequestsHash() *common.Hash { return b.header.RequestsHash }
func (b *Block) ExcessBlobGas() *uint64 {
var excessBlobGas *uint64
diff --git a/core/types/gen_authorization.go b/core/types/gen_authorization.go
new file mode 100644
index 0000000000..01995c2ae7
--- /dev/null
+++ b/core/types/gen_authorization.go
@@ -0,0 +1,75 @@
+// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
+
+package types
+
+import (
+ "encoding/json"
+ "errors"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/holiman/uint256"
+)
+
+var _ = (*authorizationMarshaling)(nil)
+
+// MarshalJSON marshals as JSON.
+func (s SetCodeAuthorization) MarshalJSON() ([]byte, error) {
+ type SetCodeAuthorization struct {
+ ChainID hexutil.U256 `json:"chainId" gencodec:"required"`
+ Address common.Address `json:"address" gencodec:"required"`
+ Nonce hexutil.Uint64 `json:"nonce" gencodec:"required"`
+ V hexutil.Uint64 `json:"v" gencodec:"required"`
+ R hexutil.U256 `json:"r" gencodec:"required"`
+ S hexutil.U256 `json:"s" gencodec:"required"`
+ }
+ var enc SetCodeAuthorization
+ enc.ChainID = hexutil.U256(s.ChainID)
+ enc.Address = s.Address
+ enc.Nonce = hexutil.Uint64(s.Nonce)
+ enc.V = hexutil.Uint64(s.V)
+ enc.R = hexutil.U256(s.R)
+ enc.S = hexutil.U256(s.S)
+ return json.Marshal(&enc)
+}
+
+// UnmarshalJSON unmarshals from JSON.
+func (s *SetCodeAuthorization) UnmarshalJSON(input []byte) error {
+ type SetCodeAuthorization struct {
+ ChainID *hexutil.U256 `json:"chainId" gencodec:"required"`
+ Address *common.Address `json:"address" gencodec:"required"`
+ Nonce *hexutil.Uint64 `json:"nonce" gencodec:"required"`
+ V *hexutil.Uint64 `json:"v" gencodec:"required"`
+ R *hexutil.U256 `json:"r" gencodec:"required"`
+ S *hexutil.U256 `json:"s" gencodec:"required"`
+ }
+ var dec SetCodeAuthorization
+ if err := json.Unmarshal(input, &dec); err != nil {
+ return err
+ }
+ if dec.ChainID == nil {
+ return errors.New("missing required field 'chainId' for SetCodeAuthorization")
+ }
+ s.ChainID = uint256.Int(*dec.ChainID)
+ if dec.Address == nil {
+ return errors.New("missing required field 'address' for SetCodeAuthorization")
+ }
+ s.Address = *dec.Address
+ if dec.Nonce == nil {
+ return errors.New("missing required field 'nonce' for SetCodeAuthorization")
+ }
+ s.Nonce = uint64(*dec.Nonce)
+ if dec.V == nil {
+ return errors.New("missing required field 'v' for SetCodeAuthorization")
+ }
+ s.V = uint8(*dec.V)
+ if dec.R == nil {
+ return errors.New("missing required field 'r' for SetCodeAuthorization")
+ }
+ s.R = uint256.Int(*dec.R)
+ if dec.S == nil {
+ return errors.New("missing required field 's' for SetCodeAuthorization")
+ }
+ s.S = uint256.Int(*dec.S)
+ return nil
+}
diff --git a/core/types/gen_header_json.go b/core/types/gen_header_json.go
index fb1f915d01..322c5d5642 100644
--- a/core/types/gen_header_json.go
+++ b/core/types/gen_header_json.go
@@ -36,6 +36,7 @@ func (h Header) MarshalJSON() ([]byte, error) {
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed" rlp:"optional"`
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"`
ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
+ RequestsHash *common.Hash `json:"requestsRoot" rlp:"optional"`
Hash common.Hash `json:"hash"`
}
var enc Header
@@ -59,6 +60,7 @@ func (h Header) MarshalJSON() ([]byte, error) {
enc.BlobGasUsed = (*hexutil.Uint64)(h.BlobGasUsed)
enc.ExcessBlobGas = (*hexutil.Uint64)(h.ExcessBlobGas)
enc.ParentBeaconRoot = h.ParentBeaconRoot
+ enc.RequestsHash = h.RequestsHash
enc.Hash = h.Hash()
return json.Marshal(&enc)
}
@@ -86,6 +88,7 @@ func (h *Header) UnmarshalJSON(input []byte) error {
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed" rlp:"optional"`
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"`
ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
+ RequestsHash *common.Hash `json:"requestsRoot" rlp:"optional"`
}
var dec Header
if err := json.Unmarshal(input, &dec); err != nil {
@@ -163,5 +166,8 @@ func (h *Header) UnmarshalJSON(input []byte) error {
if dec.ParentBeaconRoot != nil {
h.ParentBeaconRoot = dec.ParentBeaconRoot
}
+ if dec.RequestsHash != nil {
+ h.RequestsHash = dec.RequestsHash
+ }
return nil
}
diff --git a/core/types/gen_header_rlp.go b/core/types/gen_header_rlp.go
index b91a255a55..f42e6d996a 100644
--- a/core/types/gen_header_rlp.go
+++ b/core/types/gen_header_rlp.go
@@ -45,7 +45,8 @@ func (obj *Header) EncodeRLP(_w io.Writer) error {
_tmp3 := obj.BlobGasUsed != nil
_tmp4 := obj.ExcessBlobGas != nil
_tmp5 := obj.ParentBeaconRoot != nil
- if _tmp1 || _tmp2 || _tmp3 || _tmp4 || _tmp5 {
+ _tmp6 := obj.RequestsHash != nil
+ if _tmp1 || _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 {
if obj.BaseFee == nil {
w.Write(rlp.EmptyString)
} else {
@@ -55,34 +56,41 @@ func (obj *Header) EncodeRLP(_w io.Writer) error {
w.WriteBigInt(obj.BaseFee)
}
}
- if _tmp2 || _tmp3 || _tmp4 || _tmp5 {
+ if _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 {
if obj.WithdrawalsHash == nil {
w.Write([]byte{0x80})
} else {
w.WriteBytes(obj.WithdrawalsHash[:])
}
}
- if _tmp3 || _tmp4 || _tmp5 {
+ if _tmp3 || _tmp4 || _tmp5 || _tmp6 {
if obj.BlobGasUsed == nil {
w.Write([]byte{0x80})
} else {
w.WriteUint64((*obj.BlobGasUsed))
}
}
- if _tmp4 || _tmp5 {
+ if _tmp4 || _tmp5 || _tmp6 {
if obj.ExcessBlobGas == nil {
w.Write([]byte{0x80})
} else {
w.WriteUint64((*obj.ExcessBlobGas))
}
}
- if _tmp5 {
+ if _tmp5 || _tmp6 {
if obj.ParentBeaconRoot == nil {
w.Write([]byte{0x80})
} else {
w.WriteBytes(obj.ParentBeaconRoot[:])
}
}
+ if _tmp6 {
+ if obj.RequestsHash == nil {
+ w.Write([]byte{0x80})
+ } else {
+ w.WriteBytes(obj.RequestsHash[:])
+ }
+ }
w.ListEnd(_tmp0)
return w.Flush()
}
diff --git a/core/types/receipt.go b/core/types/receipt.go
index 73a6a79641..93afb85014 100644
--- a/core/types/receipt.go
+++ b/core/types/receipt.go
@@ -312,7 +312,7 @@ func (r *Receipt) decodeTyped(b []byte) error {
return errShortTypedReceipt
}
switch b[0] {
- case DynamicFeeTxType, AccessListTxType, BlobTxType:
+ case DynamicFeeTxType, AccessListTxType, BlobTxType, SetCodeTxType:
var data receiptRLP
err := rlp.DecodeBytes(b[1:], &data)
if err != nil {
@@ -509,6 +509,9 @@ func (rs Receipts) EncodeIndex(i int, w *bytes.Buffer) {
case BlobTxType:
w.WriteByte(BlobTxType)
rlp.Encode(w, data)
+ case SetCodeTxType:
+ w.WriteByte(SetCodeTxType)
+ rlp.Encode(w, data)
default:
// For unsupported types, write nothing. Since this is for
// DeriveSha, the error will be caught matching the derived hash
diff --git a/core/types/transaction.go b/core/types/transaction.go
index a29eeee835..f3189c47b3 100644
--- a/core/types/transaction.go
+++ b/core/types/transaction.go
@@ -50,6 +50,7 @@ const (
AccessListTxType = 0x01
DynamicFeeTxType = 0x02
BlobTxType = 0x03
+ SetCodeTxType = 0x04
)
// Transaction is an Ethereum transaction.
@@ -214,6 +215,10 @@ func (tx *Transaction) decodeTyped(b []byte) (TxData, error) {
var inner BlobTx
err := rlp.DecodeBytes(b[1:], &inner)
return &inner, err
+ case SetCodeTxType:
+ var inner SetCodeTx
+ err := rlp.DecodeBytes(b[1:], &inner)
+ return &inner, err
default:
return nil, ErrTxTypeNotSupported
}
@@ -540,6 +545,15 @@ func (tx *Transaction) WithoutBlobTxSidecar() *Transaction {
return cpy
}
+// SetCodeAuthorizations returns the authorizations list of the transaction.
+func (tx *Transaction) SetCodeAuthorizations() []SetCodeAuthorization {
+ setcodetx, ok := tx.inner.(*SetCodeTx)
+ if !ok {
+ return nil
+ }
+ return setcodetx.AuthList
+}
+
// Hash returns the transaction hash.
func (tx *Transaction) Hash() common.Hash {
if hash := tx.hash.Load(); hash != nil {
diff --git a/core/types/transaction_marshalling.go b/core/types/transaction_marshalling.go
index 4253380ec4..9a48af7e1f 100644
--- a/core/types/transaction_marshalling.go
+++ b/core/types/transaction_marshalling.go
@@ -24,6 +24,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/rlp"
"github.com/holiman/uint256"
@@ -34,20 +35,26 @@ type txJSON struct {
Type hexutil.Uint64 `json:"type"`
// Common transaction fields:
- Nonce *hexutil.Uint64 `json:"nonce"`
- GasPrice *hexutil.Big `json:"gasPrice"`
- MaxPriorityFeePerGas *hexutil.Big `json:"maxPriorityFeePerGas"`
- MaxFeePerGas *hexutil.Big `json:"maxFeePerGas"`
- MaxFeePerBlobGas *hexutil.Big `json:"maxFeePerBlobGas,omitempty"`
- Gas *hexutil.Uint64 `json:"gas"`
- Value *hexutil.Big `json:"value"`
- Data *hexutil.Bytes `json:"input"`
- BlobVersionedHashes []common.Hash `json:"blobVersionedHashes,omitempty"`
- V *hexutil.Big `json:"v"`
- R *hexutil.Big `json:"r"`
- S *hexutil.Big `json:"s"`
- YParity *hexutil.Uint64 `json:"yParity,omitempty"`
- To *common.Address `json:"to"`
+ Nonce *hexutil.Uint64 `json:"nonce"`
+ GasPrice *hexutil.Big `json:"gasPrice"`
+ MaxPriorityFeePerGas *hexutil.Big `json:"maxPriorityFeePerGas"`
+ MaxFeePerGas *hexutil.Big `json:"maxFeePerGas"`
+ MaxFeePerBlobGas *hexutil.Big `json:"maxFeePerBlobGas,omitempty"`
+ Gas *hexutil.Uint64 `json:"gas"`
+ Value *hexutil.Big `json:"value"`
+ Data *hexutil.Bytes `json:"input"`
+ BlobVersionedHashes []common.Hash `json:"blobVersionedHashes,omitempty"`
+ AuthorizationList []SetCodeAuthorization `json:"authorizationList,omitempty"`
+ V *hexutil.Big `json:"v"`
+ R *hexutil.Big `json:"r"`
+ S *hexutil.Big `json:"s"`
+ YParity *hexutil.Uint64 `json:"yParity,omitempty"`
+ To *common.Address `json:"to"`
+
+ // Blob transaction sidecar encoding:
+ Blobs []kzg4844.Blob `json:"blobs,omitempty"`
+ Commitments []kzg4844.Commitment `json:"commitments,omitempty"`
+ Proofs []kzg4844.Proof `json:"proofs,omitempty"`
// Deposit transaction fields
SourceHash *common.Hash `json:"sourceHash,omitempty"`
@@ -129,6 +136,44 @@ func (tx *Transaction) MarshalJSON() ([]byte, error) {
enc.V = (*hexutil.Big)(itx.V)
enc.R = (*hexutil.Big)(itx.R)
enc.S = (*hexutil.Big)(itx.S)
+ case *BlobTx:
+ enc.ChainID = (*hexutil.Big)(itx.ChainID.ToBig())
+ enc.Nonce = (*hexutil.Uint64)(&itx.Nonce)
+ enc.Gas = (*hexutil.Uint64)(&itx.Gas)
+ enc.MaxFeePerGas = (*hexutil.Big)(itx.GasFeeCap.ToBig())
+ enc.MaxPriorityFeePerGas = (*hexutil.Big)(itx.GasTipCap.ToBig())
+ enc.MaxFeePerBlobGas = (*hexutil.Big)(itx.BlobFeeCap.ToBig())
+ enc.Value = (*hexutil.Big)(itx.Value.ToBig())
+ enc.Data = (*hexutil.Bytes)(&itx.Data)
+ enc.AccessList = &itx.AccessList
+ enc.BlobVersionedHashes = itx.BlobHashes
+ enc.To = tx.To()
+ enc.V = (*hexutil.Big)(itx.V.ToBig())
+ enc.R = (*hexutil.Big)(itx.R.ToBig())
+ enc.S = (*hexutil.Big)(itx.S.ToBig())
+ yparity := itx.V.Uint64()
+ enc.YParity = (*hexutil.Uint64)(&yparity)
+ if sidecar := itx.Sidecar; sidecar != nil {
+ enc.Blobs = itx.Sidecar.Blobs
+ enc.Commitments = itx.Sidecar.Commitments
+ enc.Proofs = itx.Sidecar.Proofs
+ }
+ case *SetCodeTx:
+ enc.ChainID = (*hexutil.Big)(itx.ChainID.ToBig())
+ enc.Nonce = (*hexutil.Uint64)(&itx.Nonce)
+ enc.To = tx.To()
+ enc.Gas = (*hexutil.Uint64)(&itx.Gas)
+ enc.MaxFeePerGas = (*hexutil.Big)(itx.GasFeeCap.ToBig())
+ enc.MaxPriorityFeePerGas = (*hexutil.Big)(itx.GasTipCap.ToBig())
+ enc.Value = (*hexutil.Big)(itx.Value.ToBig())
+ enc.Data = (*hexutil.Bytes)(&itx.Data)
+ enc.AccessList = &itx.AccessList
+ enc.AuthorizationList = itx.AuthList
+ enc.V = (*hexutil.Big)(itx.V.ToBig())
+ enc.R = (*hexutil.Big)(itx.R.ToBig())
+ enc.S = (*hexutil.Big)(itx.S.ToBig())
+ yparity := itx.V.Uint64()
+ enc.YParity = (*hexutil.Uint64)(&yparity)
case *DepositTx:
enc.Gas = (*hexutil.Uint64)(&itx.Gas)
enc.Value = (*hexutil.Big)(itx.Value)
@@ -393,6 +438,83 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error {
return err
}
}
+ case SetCodeTxType:
+ var itx SetCodeTx
+ inner = &itx
+ if dec.ChainID == nil {
+ return errors.New("missing required field 'chainId' in transaction")
+ }
+ var overflow bool
+ itx.ChainID, overflow = uint256.FromBig(dec.ChainID.ToInt())
+ if overflow {
+ return errors.New("'chainId' value overflows uint256")
+ }
+ if dec.Nonce == nil {
+ return errors.New("missing required field 'nonce' in transaction")
+ }
+ itx.Nonce = uint64(*dec.Nonce)
+ if dec.To == nil {
+ return errors.New("missing required field 'to' in transaction")
+ }
+ itx.To = *dec.To
+ if dec.Gas == nil {
+ return errors.New("missing required field 'gas' for txdata")
+ }
+ itx.Gas = uint64(*dec.Gas)
+ if dec.MaxPriorityFeePerGas == nil {
+ return errors.New("missing required field 'maxPriorityFeePerGas' for txdata")
+ }
+ itx.GasTipCap = uint256.MustFromBig((*big.Int)(dec.MaxPriorityFeePerGas))
+ if dec.MaxFeePerGas == nil {
+ return errors.New("missing required field 'maxFeePerGas' for txdata")
+ }
+ itx.GasFeeCap = uint256.MustFromBig((*big.Int)(dec.MaxFeePerGas))
+ if dec.Value == nil {
+ return errors.New("missing required field 'value' in transaction")
+ }
+ itx.Value = uint256.MustFromBig((*big.Int)(dec.Value))
+ if dec.Data == nil {
+ return errors.New("missing required field 'input' in transaction")
+ }
+ itx.Data = *dec.Data
+ if dec.AccessList != nil {
+ itx.AccessList = *dec.AccessList
+ }
+ if dec.AuthorizationList == nil {
+ return errors.New("missing required field 'authorizationList' in transaction")
+ }
+ itx.AuthList = dec.AuthorizationList
+
+ // signature R
+ if dec.R == nil {
+ return errors.New("missing required field 'r' in transaction")
+ }
+ itx.R, overflow = uint256.FromBig((*big.Int)(dec.R))
+ if overflow {
+ return errors.New("'r' value overflows uint256")
+ }
+ // signature S
+ if dec.S == nil {
+ return errors.New("missing required field 's' in transaction")
+ }
+ itx.S, overflow = uint256.FromBig((*big.Int)(dec.S))
+ if overflow {
+ return errors.New("'s' value overflows uint256")
+ }
+ // signature V
+ vbig, err := dec.yParityValue()
+ if err != nil {
+ return err
+ }
+ itx.V, overflow = uint256.FromBig(vbig)
+ if overflow {
+ return errors.New("'v' value overflows uint256")
+ }
+ if itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0 {
+ if err := sanityCheckSignature(vbig, itx.R.ToBig(), itx.S.ToBig(), false); err != nil {
+ return err
+ }
+ }
case DepositTxType:
if dec.AccessList != nil || dec.MaxFeePerGas != nil ||
dec.MaxPriorityFeePerGas != nil {
diff --git a/core/types/transaction_marshalling_test.go b/core/types/transaction_marshalling_test.go
index 497dd8b020..1e98a5dd8d 100644
--- a/core/types/transaction_marshalling_test.go
+++ b/core/types/transaction_marshalling_test.go
@@ -9,6 +9,68 @@ import (
"github.com/stretchr/testify/require"
)
+var (
+ setCodeTx = `
+ {
+ "blockHash": "0x9e77f369a3415aef97a2577ee10d341572d6343f5204551a1ff0f0c2b57e7b3f",
+ "blockNumber": "0x86236",
+ "from": "0x1220d2767171ea3a6f4a545eff23efaad4c80221",
+ "gas": "0xf4240",
+ "gasPrice": "0xe078996",
+ "maxFeePerGas": "0xe078996",
+ "maxPriorityFeePerGas": "0xe07898f",
+ "hash": "0xa61cdf2dde1acc00b4fcd9e550d660b07ffe77082b411425cddd5fe6cc824e5f",
+ "input": "0xbeabacc8000000000000000000000000792c9e49df017c00cc265cc9c09d4130c0eaddc6000000000000000000000000cdd5134b4be40f679eac600fae02600004c94e0a0000000000000000000000000000000000000000000000000de0b6b3a7640000",
+ "nonce": "0x6",
+ "to": "0x13023d2f562b656b3c35e65956cf527c893d5fdf",
+ "transactionIndex": "0x0",
+ "value": "0x0",
+ "type": "0x4",
+ "accessList": [],
+ "chainId": "0x1a5ee289c",
+ "authorizationList": [
+ {
+ "chainId": "0x1a5ee289c",
+ "address": "0xd7eb2c2b3c979fb14b94a523a102bc2d593b9080",
+ "nonce": "0x2",
+ "v": "0x0",
+ "r": "0x928c8a2a359bd28d3b94ab450589fc34f27677d605dc37c9300572844d059275",
+ "s": "0x4b9b4025c47e9ef04c99ad702aeb8056bc4f08c960c69226c3605466f316ca27"
+ }
+ ],
+ "v": "0x0",
+ "r": "0x733c3cf5e701e6873b72c65273b290a993dc4b0612545cdc5b41950848182874",
+ "s": "0x306ce694afab15d06344334e1e89960834ec691e863753d9aea53a22902f2cdd",
+ "yParity": "0x0"
+ }
+`
+)
+
+func TestUnmarshalSetCodeTx(t *testing.T) {
+ tests := []struct {
+ name string
+ json string
+ expectedError string
+ }{
+ {
+ name: "UnmarshalSetCodeTx",
+ json: setCodeTx,
+ expectedError: "",
+ },
+ }
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ var parsedTx = &Transaction{}
+ err := json.Unmarshal([]byte(test.json), &parsedTx)
+ if test.expectedError == "" {
+ require.NoError(t, err)
+ } else {
+ require.ErrorContains(t, err, test.expectedError)
+ }
+ })
+ }
+}
+
func TestTransactionUnmarshalJsonDeposit(t *testing.T) {
tx := NewTx(&DepositTx{
SourceHash: common.HexToHash("0x1234"),
diff --git a/core/types/transaction_signing.go b/core/types/transaction_signing.go
index 7b5b0e0786..e057254867 100644
--- a/core/types/transaction_signing.go
+++ b/core/types/transaction_signing.go
@@ -87,7 +87,7 @@ func LatestSignerForChainID(chainID *big.Int) Signer {
if chainID == nil {
return HomesteadSigner{}
}
- return NewCancunSigner(chainID)
+ return NewPragueSigner(chainID)
}
// SignTx signs the transaction using the given signer and private key.
@@ -170,6 +170,77 @@ type Signer interface {
Equal(Signer) bool
}
+type pragueSigner struct{ cancunSigner }
+
+// NewPragueSigner returns a signer that accepts
+// - EIP-7702 set code transactions
+// - EIP-4844 blob transactions
+// - EIP-1559 dynamic fee transactions
+// - EIP-2930 access list transactions,
+// - EIP-155 replay protected transactions, and
+// - legacy Homestead transactions.
+func NewPragueSigner(chainId *big.Int) Signer {
+ signer, _ := NewCancunSigner(chainId).(cancunSigner)
+ return pragueSigner{signer}
+}
+
+func (s pragueSigner) Sender(tx *Transaction) (common.Address, error) {
+ if tx.Type() != SetCodeTxType {
+ return s.cancunSigner.Sender(tx)
+ }
+ V, R, S := tx.RawSignatureValues()
+
+ // Set code txs are defined to use 0 and 1 as their recovery
+ // id, add 27 to become equivalent to unprotected Homestead signatures.
+ V = new(big.Int).Add(V, big.NewInt(27))
+ if tx.ChainId().Cmp(s.chainId) != 0 {
+ return common.Address{}, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, tx.ChainId(), s.chainId)
+ }
+ return recoverPlain(s.Hash(tx), R, S, V, true)
+}
+
+func (s pragueSigner) Equal(s2 Signer) bool {
+ x, ok := s2.(pragueSigner)
+ return ok && x.chainId.Cmp(s.chainId) == 0
+}
+
+func (s pragueSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) {
+ txdata, ok := tx.inner.(*SetCodeTx)
+ if !ok {
+ return s.cancunSigner.SignatureValues(tx, sig)
+ }
+ // Check that chain ID of tx matches the signer. We also accept ID zero here,
+ // because it indicates that the chain ID was not specified in the tx.
+ if txdata.ChainID.Sign() != 0 && txdata.ChainID.CmpBig(s.chainId) != 0 {
+ return nil, nil, nil, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, txdata.ChainID, s.chainId)
+ }
+ R, S, _ = decodeSignature(sig)
+ V = big.NewInt(int64(sig[64]))
+ return R, S, V, nil
+}
+
+// Hash returns the hash to be signed by the sender.
+// It does not uniquely identify the transaction.
+func (s pragueSigner) Hash(tx *Transaction) common.Hash {
+ if tx.Type() != SetCodeTxType {
+ return s.cancunSigner.Hash(tx)
+ }
+ return prefixedRlpHash(
+ tx.Type(),
+ []interface{}{
+ s.chainId,
+ tx.Nonce(),
+ tx.GasTipCap(),
+ tx.GasFeeCap(),
+ tx.Gas(),
+ tx.To(),
+ tx.Value(),
+ tx.Data(),
+ tx.AccessList(),
+ tx.SetCodeAuthorizations(),
+ })
+}
+
type cancunSigner struct{ londonSigner }
// NewCancunSigner returns a signer that accepts
diff --git a/core/types/tx_setcoce.go b/core/types/tx_setcoce.go
new file mode 100644
index 0000000000..9711cf7f15
--- /dev/null
+++ b/core/types/tx_setcoce.go
@@ -0,0 +1,226 @@
+// Copyright 2024 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 types
+
+import (
+ "bytes"
+ "crypto/ecdsa"
+ "errors"
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/rlp"
+ "github.com/holiman/uint256"
+)
+
+// DelegationPrefix is used by code to denote the account is delegating to
+// another account.
+var DelegationPrefix = []byte{0xef, 0x01, 0x00}
+
+// ParseDelegation tries to parse the address from a delegation slice.
+func ParseDelegation(b []byte) (common.Address, bool) {
+ if len(b) != 23 || !bytes.HasPrefix(b, DelegationPrefix) {
+ return common.Address{}, false
+ }
+ return common.BytesToAddress(b[len(DelegationPrefix):]), true
+}
+
+// AddressToDelegation adds the delegation prefix to the specified address.
+func AddressToDelegation(addr common.Address) []byte {
+ return append(DelegationPrefix, addr.Bytes()...)
+}
+
+// SetCodeTx implements the EIP-7702 transaction type which temporarily installs
+// the code at the signer's address.
+type SetCodeTx struct {
+ ChainID *uint256.Int
+ Nonce uint64
+ GasTipCap *uint256.Int // a.k.a. maxPriorityFeePerGas
+ GasFeeCap *uint256.Int // a.k.a. maxFeePerGas
+ Gas uint64
+ To common.Address
+ Value *uint256.Int
+ Data []byte
+ AccessList AccessList
+ AuthList []SetCodeAuthorization
+
+ // Signature values
+ V *uint256.Int
+ R *uint256.Int
+ S *uint256.Int
+}
+
+//go:generate go run github.com/fjl/gencodec -type SetCodeAuthorization -field-override authorizationMarshaling -out gen_authorization.go
+
+// SetCodeAuthorization is an authorization from an account to deploy code at its address.
+type SetCodeAuthorization struct {
+ ChainID uint256.Int `json:"chainId" gencodec:"required"`
+ Address common.Address `json:"address" gencodec:"required"`
+ Nonce uint64 `json:"nonce" gencodec:"required"`
+ V uint8 `json:"v" gencodec:"required"`
+ R uint256.Int `json:"r" gencodec:"required"`
+ S uint256.Int `json:"s" gencodec:"required"`
+}
+
+// field type overrides for gencodec
+type authorizationMarshaling struct {
+ ChainID hexutil.U256
+ Nonce hexutil.Uint64
+ V hexutil.Uint64
+ R hexutil.U256
+ S hexutil.U256
+}
+
+// SignSetCode creates a signed the SetCode authorization.
+func SignSetCode(prv *ecdsa.PrivateKey, auth SetCodeAuthorization) (SetCodeAuthorization, error) {
+ sighash := auth.sigHash()
+ sig, err := crypto.Sign(sighash[:], prv)
+ if err != nil {
+ return SetCodeAuthorization{}, err
+ }
+ r, s, _ := decodeSignature(sig)
+ return SetCodeAuthorization{
+ ChainID: auth.ChainID,
+ Address: auth.Address,
+ Nonce: auth.Nonce,
+ V: sig[64],
+ R: *uint256.MustFromBig(r),
+ S: *uint256.MustFromBig(s),
+ }, nil
+}
+
+func (a *SetCodeAuthorization) sigHash() common.Hash {
+ return prefixedRlpHash(0x05, []any{
+ a.ChainID,
+ a.Address,
+ a.Nonce,
+ })
+}
+
+// Authority recovers the the authorizing account of an authorization.
+func (a *SetCodeAuthorization) Authority() (common.Address, error) {
+ sighash := a.sigHash()
+ if !crypto.ValidateSignatureValues(a.V, a.R.ToBig(), a.S.ToBig(), true) {
+ return common.Address{}, ErrInvalidSig
+ }
+ // encode the signature in uncompressed format
+ var sig [crypto.SignatureLength]byte
+ a.R.WriteToSlice(sig[:32])
+ a.S.WriteToSlice(sig[32:64])
+ sig[64] = a.V
+ // recover the public key from the signature
+ pub, err := crypto.Ecrecover(sighash[:], sig[:])
+ if err != nil {
+ return common.Address{}, err
+ }
+ if len(pub) == 0 || pub[0] != 4 {
+ return common.Address{}, errors.New("invalid public key")
+ }
+ var addr common.Address
+ copy(addr[:], crypto.Keccak256(pub[1:])[12:])
+ return addr, nil
+}
+
+// copy creates a deep copy of the transaction data and initializes all fields.
+func (tx *SetCodeTx) copy() TxData {
+ cpy := &SetCodeTx{
+ Nonce: tx.Nonce,
+ To: tx.To,
+ Data: common.CopyBytes(tx.Data),
+ Gas: tx.Gas,
+ // These are copied below.
+ AccessList: make(AccessList, len(tx.AccessList)),
+ AuthList: make([]SetCodeAuthorization, len(tx.AuthList)),
+ Value: new(uint256.Int),
+ ChainID: new(uint256.Int),
+ GasTipCap: new(uint256.Int),
+ GasFeeCap: new(uint256.Int),
+ V: new(uint256.Int),
+ R: new(uint256.Int),
+ S: new(uint256.Int),
+ }
+ copy(cpy.AccessList, tx.AccessList)
+ copy(cpy.AuthList, tx.AuthList)
+ if tx.Value != nil {
+ cpy.Value.Set(tx.Value)
+ }
+ if tx.ChainID != nil {
+ cpy.ChainID.Set(tx.ChainID)
+ }
+ if tx.GasTipCap != nil {
+ cpy.GasTipCap.Set(tx.GasTipCap)
+ }
+ if tx.GasFeeCap != nil {
+ cpy.GasFeeCap.Set(tx.GasFeeCap)
+ }
+ if tx.V != nil {
+ cpy.V.Set(tx.V)
+ }
+ if tx.R != nil {
+ cpy.R.Set(tx.R)
+ }
+ if tx.S != nil {
+ cpy.S.Set(tx.S)
+ }
+ return cpy
+}
+
+// accessors for innerTx.
+func (tx *SetCodeTx) txType() byte { return SetCodeTxType }
+func (tx *SetCodeTx) chainID() *big.Int { return tx.ChainID.ToBig() }
+func (tx *SetCodeTx) accessList() AccessList { return tx.AccessList }
+func (tx *SetCodeTx) data() []byte { return tx.Data }
+func (tx *SetCodeTx) gas() uint64 { return tx.Gas }
+func (tx *SetCodeTx) gasFeeCap() *big.Int { return tx.GasFeeCap.ToBig() }
+func (tx *SetCodeTx) gasTipCap() *big.Int { return tx.GasTipCap.ToBig() }
+func (tx *SetCodeTx) gasPrice() *big.Int { return tx.GasFeeCap.ToBig() }
+func (tx *SetCodeTx) value() *big.Int { return tx.Value.ToBig() }
+func (tx *SetCodeTx) nonce() uint64 { return tx.Nonce }
+func (tx *SetCodeTx) to() *common.Address { tmp := tx.To; return &tmp }
+func (tx *SetCodeTx) isSystemTx() bool { return false }
+
+func (tx *SetCodeTx) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int {
+ if baseFee == nil {
+ return dst.Set(tx.GasFeeCap.ToBig())
+ }
+ tip := dst.Sub(tx.GasFeeCap.ToBig(), baseFee)
+ if tip.Cmp(tx.GasTipCap.ToBig()) > 0 {
+ tip.Set(tx.GasTipCap.ToBig())
+ }
+ return tip.Add(tip, baseFee)
+}
+
+func (tx *SetCodeTx) rawSignatureValues() (v, r, s *big.Int) {
+ return tx.V.ToBig(), tx.R.ToBig(), tx.S.ToBig()
+}
+
+func (tx *SetCodeTx) setSignatureValues(chainID, v, r, s *big.Int) {
+ tx.ChainID = uint256.MustFromBig(chainID)
+ tx.V.SetFromBig(v)
+ tx.R.SetFromBig(r)
+ tx.S.SetFromBig(s)
+}
+
+func (tx *SetCodeTx) encode(b *bytes.Buffer) error {
+ return rlp.Encode(b, tx)
+}
+
+func (tx *SetCodeTx) decode(input []byte) error {
+ return rlp.DecodeBytes(input, tx)
+}
diff --git a/core/types/tx_setcode_test.go b/core/types/tx_setcode_test.go
new file mode 100644
index 0000000000..d0544573cf
--- /dev/null
+++ b/core/types/tx_setcode_test.go
@@ -0,0 +1,70 @@
+// Copyright 2024 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 types
+
+import (
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+)
+
+// TestParseDelegation tests a few possible delegation designator values and
+// ensures they are parsed correctly.
+func TestParseDelegation(t *testing.T) {
+ addr := common.Address{0x42}
+ for _, tt := range []struct {
+ val []byte
+ want *common.Address
+ }{
+ { // simple correct delegation
+ val: append(DelegationPrefix, addr.Bytes()...),
+ want: &addr,
+ },
+ { // wrong address size
+ val: append(DelegationPrefix, addr.Bytes()[0:19]...),
+ },
+ { // short address
+ val: append(DelegationPrefix, 0x42),
+ },
+ { // long address
+ val: append(append(DelegationPrefix, addr.Bytes()...), 0x42),
+ },
+ { // wrong prefix size
+ val: append(DelegationPrefix[:2], addr.Bytes()...),
+ },
+ { // wrong prefix
+ val: append([]byte{0xef, 0x01, 0x01}, addr.Bytes()...),
+ },
+ { // wrong prefix
+ val: append([]byte{0xef, 0x00, 0x00}, addr.Bytes()...),
+ },
+ { // no prefix
+ val: addr.Bytes(),
+ },
+ { // no address
+ val: DelegationPrefix,
+ },
+ } {
+ got, ok := ParseDelegation(tt.val)
+ if ok && tt.want == nil {
+ t.Fatalf("expected fail, got %s", got.Hex())
+ }
+ if !ok && tt.want != nil {
+ t.Fatalf("failed to parse, want %s", tt.want.Hex())
+ }
+ }
+}
diff --git a/graphql/graphql.go b/graphql/graphql.go
index 65d2b627f4..ada2def439 100644
--- a/graphql/graphql.go
+++ b/graphql/graphql.go
@@ -281,7 +281,7 @@ func (t *Transaction) MaxFeePerGas(ctx context.Context) (*hexutil.Big, error) {
switch tx.Type() {
case types.AccessListTxType:
return nil, nil
- case types.DynamicFeeTxType:
+ case types.DynamicFeeTxType, types.BlobTxType, types.SetCodeTxType:
return (*hexutil.Big)(tx.GasFeeCap()), nil
default:
return nil, nil
@@ -296,13 +296,25 @@ func (t *Transaction) MaxPriorityFeePerGas(ctx context.Context) (*hexutil.Big, e
switch tx.Type() {
case types.AccessListTxType:
return nil, nil
- case types.DynamicFeeTxType:
+ case types.DynamicFeeTxType, types.BlobTxType, types.SetCodeTxType:
return (*hexutil.Big)(tx.GasTipCap()), nil
default:
return nil, nil
}
}
+func (t *Transaction) BlobVersionedHashes(ctx context.Context) *[]common.Hash {
+ tx, _ := t.resolve(ctx)
+ if tx == nil {
+ return nil
+ }
+ if tx.Type() != types.BlobTxType {
+ return nil
+ }
+ blobHashes := tx.BlobHashes()
+ return &blobHashes
+}
+
func (t *Transaction) EffectiveTip(ctx context.Context) (*hexutil.Big, error) {
tx, err := t.resolve(ctx)
if err != nil || tx == nil {
@@ -439,6 +451,40 @@ func (t *Transaction) CumulativeGasUsed(ctx context.Context) (*Long, error) {
return &ret, nil
}
+func (t *Transaction) BlobGasUsed(ctx context.Context) (*hexutil.Uint64, error) {
+ tx, _ := t.resolve(ctx)
+ if tx == nil {
+ return nil, nil
+ }
+ if tx.Type() != types.BlobTxType {
+ return nil, nil
+ }
+
+ receipt, err := t.getReceipt(ctx)
+ if err != nil || receipt == nil {
+ return nil, err
+ }
+ ret := hexutil.Uint64(receipt.BlobGasUsed)
+ return &ret, nil
+}
+
+func (t *Transaction) BlobGasPrice(ctx context.Context) (*hexutil.Big, error) {
+ tx, _ := t.resolve(ctx)
+ if tx == nil {
+ return nil, nil
+ }
+ if tx.Type() != types.BlobTxType {
+ return nil, nil
+ }
+
+ receipt, err := t.getReceipt(ctx)
+ if err != nil || receipt == nil {
+ return nil, err
+ }
+ ret := (*hexutil.Big)(receipt.BlobGasPrice)
+ return ret, nil
+}
+
func (t *Transaction) CreatedContract(ctx context.Context, args BlockNumberArgs) (*Account, error) {
receipt, err := t.getReceipt(ctx)
if err != nil || receipt == nil || receipt.ContractAddress == (common.Address{}) {