diff --git a/beacon/engine/gen_ed.go b/beacon/engine/gen_ed.go
index b2eb1dc9822..ebab7c0cf8b 100644
--- a/beacon/engine/gen_ed.go
+++ b/beacon/engine/gen_ed.go
@@ -17,25 +17,27 @@ var _ = (*executableDataMarshaling)(nil)
// MarshalJSON marshals as JSON.
func (e ExecutableData) MarshalJSON() ([]byte, error) {
type ExecutableData struct {
- ParentHash common.Hash `json:"parentHash" gencodec:"required"`
- FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"`
- StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
- ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
- LogsBloom hexutil.Bytes `json:"logsBloom" gencodec:"required"`
- Random common.Hash `json:"prevRandao" gencodec:"required"`
- Number hexutil.Uint64 `json:"blockNumber" gencodec:"required"`
- GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
- GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
- Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"`
- ExtraData hexutil.Bytes `json:"extraData" gencodec:"required"`
- BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"`
- BlockHash common.Hash `json:"blockHash" gencodec:"required"`
- Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
- Withdrawals []*types.Withdrawal `json:"withdrawals"`
- BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
- ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
- Deposits types.Deposits `json:"depositRequests"`
- ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"`
+ ParentHash common.Hash `json:"parentHash" gencodec:"required"`
+ FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"`
+ StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
+ ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
+ LogsBloom hexutil.Bytes `json:"logsBloom" gencodec:"required"`
+ Random common.Hash `json:"prevRandao" gencodec:"required"`
+ Number hexutil.Uint64 `json:"blockNumber" gencodec:"required"`
+ GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
+ GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
+ Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"`
+ ExtraData hexutil.Bytes `json:"extraData" gencodec:"required"`
+ BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"`
+ BlockHash common.Hash `json:"blockHash" gencodec:"required"`
+ Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
+ Withdrawals []*types.Withdrawal `json:"withdrawals"`
+ BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
+ ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
+ Deposits types.Deposits `json:"depositRequests"`
+ WithdrawalRequests types.WithdrawalRequests `json:"withdrawalRequests"`
+ ConsolidationRequests types.ConsolidationRequests `json:"consolidationRequests"`
+ ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"`
}
var enc ExecutableData
enc.ParentHash = e.ParentHash
@@ -61,6 +63,8 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) {
enc.BlobGasUsed = (*hexutil.Uint64)(e.BlobGasUsed)
enc.ExcessBlobGas = (*hexutil.Uint64)(e.ExcessBlobGas)
enc.Deposits = e.Deposits
+ enc.WithdrawalRequests = e.WithdrawalRequests
+ enc.ConsolidationRequests = e.ConsolidationRequests
enc.ExecutionWitness = e.ExecutionWitness
return json.Marshal(&enc)
}
@@ -68,25 +72,27 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) {
// UnmarshalJSON unmarshals from JSON.
func (e *ExecutableData) UnmarshalJSON(input []byte) error {
type ExecutableData struct {
- ParentHash *common.Hash `json:"parentHash" gencodec:"required"`
- FeeRecipient *common.Address `json:"feeRecipient" gencodec:"required"`
- StateRoot *common.Hash `json:"stateRoot" gencodec:"required"`
- ReceiptsRoot *common.Hash `json:"receiptsRoot" gencodec:"required"`
- LogsBloom *hexutil.Bytes `json:"logsBloom" gencodec:"required"`
- Random *common.Hash `json:"prevRandao" gencodec:"required"`
- Number *hexutil.Uint64 `json:"blockNumber" gencodec:"required"`
- GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
- GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
- Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"`
- ExtraData *hexutil.Bytes `json:"extraData" gencodec:"required"`
- BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"`
- BlockHash *common.Hash `json:"blockHash" gencodec:"required"`
- Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
- Withdrawals []*types.Withdrawal `json:"withdrawals"`
- BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
- ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
- Deposits *types.Deposits `json:"depositRequests"`
- ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"`
+ ParentHash *common.Hash `json:"parentHash" gencodec:"required"`
+ FeeRecipient *common.Address `json:"feeRecipient" gencodec:"required"`
+ StateRoot *common.Hash `json:"stateRoot" gencodec:"required"`
+ ReceiptsRoot *common.Hash `json:"receiptsRoot" gencodec:"required"`
+ LogsBloom *hexutil.Bytes `json:"logsBloom" gencodec:"required"`
+ Random *common.Hash `json:"prevRandao" gencodec:"required"`
+ Number *hexutil.Uint64 `json:"blockNumber" gencodec:"required"`
+ GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
+ GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
+ Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"`
+ ExtraData *hexutil.Bytes `json:"extraData" gencodec:"required"`
+ BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"`
+ BlockHash *common.Hash `json:"blockHash" gencodec:"required"`
+ Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
+ Withdrawals []*types.Withdrawal `json:"withdrawals"`
+ BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
+ ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
+ Deposits *types.Deposits `json:"depositRequests"`
+ WithdrawalRequests *types.WithdrawalRequests `json:"withdrawalRequests"`
+ ConsolidationRequests *types.ConsolidationRequests `json:"consolidationRequests"`
+ ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"`
}
var dec ExecutableData
if err := json.Unmarshal(input, &dec); err != nil {
@@ -163,6 +169,12 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error {
if dec.Deposits != nil {
e.Deposits = *dec.Deposits
}
+ if dec.WithdrawalRequests != nil {
+ e.WithdrawalRequests = *dec.WithdrawalRequests
+ }
+ if dec.ConsolidationRequests != nil {
+ e.ConsolidationRequests = *dec.ConsolidationRequests
+ }
if dec.ExecutionWitness != nil {
e.ExecutionWitness = dec.ExecutionWitness
}
diff --git a/beacon/engine/types.go b/beacon/engine/types.go
index 19d2b2823e3..bb24f223df3 100644
--- a/beacon/engine/types.go
+++ b/beacon/engine/types.go
@@ -60,25 +60,27 @@ type payloadAttributesMarshaling struct {
// ExecutableData is the data necessary to execute an EL payload.
type ExecutableData struct {
- ParentHash common.Hash `json:"parentHash" gencodec:"required"`
- FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"`
- StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
- ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
- LogsBloom []byte `json:"logsBloom" gencodec:"required"`
- Random common.Hash `json:"prevRandao" gencodec:"required"`
- Number uint64 `json:"blockNumber" gencodec:"required"`
- GasLimit uint64 `json:"gasLimit" gencodec:"required"`
- GasUsed uint64 `json:"gasUsed" gencodec:"required"`
- Timestamp uint64 `json:"timestamp" gencodec:"required"`
- ExtraData []byte `json:"extraData" gencodec:"required"`
- BaseFeePerGas *big.Int `json:"baseFeePerGas" gencodec:"required"`
- BlockHash common.Hash `json:"blockHash" gencodec:"required"`
- Transactions [][]byte `json:"transactions" gencodec:"required"`
- Withdrawals []*types.Withdrawal `json:"withdrawals"`
- BlobGasUsed *uint64 `json:"blobGasUsed"`
- ExcessBlobGas *uint64 `json:"excessBlobGas"`
- Deposits types.Deposits `json:"depositRequests"`
- ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"`
+ ParentHash common.Hash `json:"parentHash" gencodec:"required"`
+ FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"`
+ StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
+ ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
+ LogsBloom []byte `json:"logsBloom" gencodec:"required"`
+ Random common.Hash `json:"prevRandao" gencodec:"required"`
+ Number uint64 `json:"blockNumber" gencodec:"required"`
+ GasLimit uint64 `json:"gasLimit" gencodec:"required"`
+ GasUsed uint64 `json:"gasUsed" gencodec:"required"`
+ Timestamp uint64 `json:"timestamp" gencodec:"required"`
+ ExtraData []byte `json:"extraData" gencodec:"required"`
+ BaseFeePerGas *big.Int `json:"baseFeePerGas" gencodec:"required"`
+ BlockHash common.Hash `json:"blockHash" gencodec:"required"`
+ Transactions [][]byte `json:"transactions" gencodec:"required"`
+ Withdrawals []*types.Withdrawal `json:"withdrawals"`
+ BlobGasUsed *uint64 `json:"blobGasUsed"`
+ ExcessBlobGas *uint64 `json:"excessBlobGas"`
+ Deposits types.Deposits `json:"depositRequests"`
+ WithdrawalRequests types.WithdrawalRequests `json:"withdrawalRequests"`
+ ConsolidationRequests types.ConsolidationRequests `json:"consolidationRequests"`
+ ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"`
}
// JSON type overrides for executableData.
@@ -233,7 +235,9 @@ func ExecutableDataToBlock(data ExecutableData, versionedHashes []common.Hash, b
h := types.DeriveSha(types.Withdrawals(data.Withdrawals), trie.NewStackTrie(nil))
withdrawalsRoot = &h
}
- // Compute requestsHash if any requests are non-nil.
+ // Only set requestsHash if there exists requests in the ExecutableData. This
+ // allows CLs to continue using the data structure before requests are
+ // enabled.
var (
requestsHash *common.Hash
requests types.Requests
@@ -243,9 +247,20 @@ func ExecutableDataToBlock(data ExecutableData, versionedHashes []common.Hash, b
for _, d := range data.Deposits {
requests = append(requests, types.NewRequest(d))
}
+ }
+ if data.WithdrawalRequests != nil {
+ for _, w := range data.WithdrawalRequests {
+ requests = append(requests, types.NewRequest(w))
+ }
+ }
+ if data.ConsolidationRequests != nil {
+ requests = append(requests, data.ConsolidationRequests.Requests()...)
+ }
+ if requests != nil {
h := types.DeriveSha(requests, trie.NewStackTrie(nil))
requestsHash = &h
}
+
header := &types.Header{
ParentHash: data.ParentHash,
UncleHash: types.EmptyUncleHash,
@@ -320,22 +335,31 @@ func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types.
// assigns them to the associated fields in ExecutableData.
func setRequests(requests types.Requests, data *ExecutableData) {
if requests != nil {
- // If requests is non-nil, it means deposits are available in block and we
- // should return an empty slice instead of nil if there are no deposits.
+ // If requests is non-nil, it means the requests are available in block and
+ // we should return an empty slice instead of nil.
data.Deposits = make(types.Deposits, 0)
+ data.WithdrawalRequests = make(types.WithdrawalRequests, 0)
+ data.ConsolidationRequests = make(types.ConsolidationRequests, 0)
}
for _, r := range requests {
- if d, ok := r.Inner().(*types.Deposit); ok {
- data.Deposits = append(data.Deposits, d)
+ switch v := r.Inner().(type) {
+ case *types.Deposit:
+ data.Deposits = append(data.Deposits, v)
+ case *types.WithdrawalRequest:
+ data.WithdrawalRequests = append(data.WithdrawalRequests, v)
+ case *types.ConsolidationRequest:
+ data.ConsolidationRequests = append(data.ConsolidationRequests, v)
}
}
}
// ExecutionPayloadBody is used in the response to GetPayloadBodiesByHash and GetPayloadBodiesByRange
type ExecutionPayloadBody struct {
- TransactionData []hexutil.Bytes `json:"transactions"`
- Withdrawals []*types.Withdrawal `json:"withdrawals"`
- Deposits types.Deposits `json:"depositRequests"`
+ TransactionData []hexutil.Bytes `json:"transactions"`
+ Withdrawals []*types.Withdrawal `json:"withdrawals"`
+ Deposits types.Deposits `json:"depositRequests"`
+ WithdrawalRequests types.WithdrawalRequests `json:"withdrawalRequests"`
+ ConsolidationRequests types.ConsolidationRequests `json:"consolidationRequests"`
}
// Client identifiers to support ClientVersionV1.
diff --git a/cmd/evm/blockrunner.go b/cmd/evm/blockrunner.go
index d5cd8d8e3de..8cc715c5a32 100644
--- a/cmd/evm/blockrunner.go
+++ b/cmd/evm/blockrunner.go
@@ -86,7 +86,7 @@ func blockTestCmd(ctx *cli.Context) error {
continue
}
test := tests[name]
- if err := test.Run(false, rawdb.HashScheme, false, tracer, func(res error, chain *core.BlockChain) {
+ if err := test.Run(false, rawdb.HashScheme, true, tracer, func(res error, chain *core.BlockChain) {
if ctx.Bool(DumpFlag.Name) {
if state, _ := chain.State(); state != nil {
fmt.Println(string(state.Dump(nil)))
diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go
index a0805722d66..f414eaf2a4a 100644
--- a/cmd/evm/internal/t8ntool/execution.go
+++ b/cmd/evm/internal/t8ntool/execution.go
@@ -53,21 +53,23 @@ type Prestate struct {
// ExecutionResult contains the execution status after running a state test, any
// error that might have occurred and a dump of the final state if requested.
type ExecutionResult struct {
- StateRoot common.Hash `json:"stateRoot"`
- TxRoot common.Hash `json:"txRoot"`
- ReceiptRoot common.Hash `json:"receiptsRoot"`
- LogsHash common.Hash `json:"logsHash"`
- Bloom types.Bloom `json:"logsBloom" gencodec:"required"`
- Receipts types.Receipts `json:"receipts"`
- Rejected []*rejectedTx `json:"rejected,omitempty"`
- Difficulty *math.HexOrDecimal256 `json:"currentDifficulty" gencodec:"required"`
- GasUsed math.HexOrDecimal64 `json:"gasUsed"`
- BaseFee *math.HexOrDecimal256 `json:"currentBaseFee,omitempty"`
- WithdrawalsRoot *common.Hash `json:"withdrawalsRoot,omitempty"`
- CurrentExcessBlobGas *math.HexOrDecimal64 `json:"currentExcessBlobGas,omitempty"`
- CurrentBlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed,omitempty"`
- RequestsHash *common.Hash `json:"requestsRoot,omitempty"`
- DepositRequests *types.Deposits `json:"depositRequests,omitempty"`
+ StateRoot common.Hash `json:"stateRoot"`
+ TxRoot common.Hash `json:"txRoot"`
+ ReceiptRoot common.Hash `json:"receiptsRoot"`
+ LogsHash common.Hash `json:"logsHash"`
+ Bloom types.Bloom `json:"logsBloom" gencodec:"required"`
+ Receipts types.Receipts `json:"receipts"`
+ Rejected []*rejectedTx `json:"rejected,omitempty"`
+ Difficulty *math.HexOrDecimal256 `json:"currentDifficulty" gencodec:"required"`
+ GasUsed math.HexOrDecimal64 `json:"gasUsed"`
+ BaseFee *math.HexOrDecimal256 `json:"currentBaseFee,omitempty"`
+ WithdrawalsRoot *common.Hash `json:"withdrawalsRoot,omitempty"`
+ CurrentExcessBlobGas *math.HexOrDecimal64 `json:"currentExcessBlobGas,omitempty"`
+ CurrentBlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed,omitempty"`
+ RequestsHash *common.Hash `json:"requestsRoot,omitempty"`
+ DepositRequests types.Deposits `json:"depositRequests,omitempty"`
+ WithdrawalRequests types.WithdrawalRequests `json:"withdrawalRequests,omitempty"`
+ ConsolidationRequests types.ConsolidationRequests `json:"consolidationRequests,omitempty"`
}
type ommer struct {
@@ -354,33 +356,15 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
amount := new(big.Int).Mul(new(big.Int).SetUint64(w.Amount), big.NewInt(params.GWei))
statedb.AddBalance(w.Address, uint256.MustFromBig(amount), tracing.BalanceIncreaseWithdrawal)
}
- // Commit block
- root, err := statedb.Commit(vmContext.BlockNumber.Uint64(), chainConfig.IsEIP158(vmContext.BlockNumber))
- if err != nil {
- return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not commit state: %v", err))
- }
- execRs := &ExecutionResult{
- StateRoot: root,
- TxRoot: types.DeriveSha(includedTxs, trie.NewStackTrie(nil)),
- ReceiptRoot: types.DeriveSha(receipts, trie.NewStackTrie(nil)),
- Bloom: types.CreateBloom(receipts),
- LogsHash: rlpHash(statedb.Logs()),
- Receipts: receipts,
- Rejected: rejectedTxs,
- Difficulty: (*math.HexOrDecimal256)(vmContext.Difficulty),
- GasUsed: (math.HexOrDecimal64)(gasUsed),
- BaseFee: (*math.HexOrDecimal256)(vmContext.BaseFee),
- }
- if pre.Env.Withdrawals != nil {
- h := types.DeriveSha(types.Withdrawals(pre.Env.Withdrawals), trie.NewStackTrie(nil))
- execRs.WithdrawalsRoot = &h
- }
- if vmContext.BlobBaseFee != nil {
- execRs.CurrentExcessBlobGas = (*math.HexOrDecimal64)(&excessBlobGas)
- execRs.CurrentBlobGasUsed = (*math.HexOrDecimal64)(&blobGasUsed)
- }
+ // Retrieve deposit and withdrawal requests
+ var (
+ depositRequests types.Deposits
+ withdrawalRequests types.WithdrawalRequests
+ consolidationRequests types.ConsolidationRequests
+ requestsHash *common.Hash
+ )
if chainConfig.IsPrague(vmContext.BlockNumber, vmContext.Time) {
- // Parse the requests from the logs
+ // Parse deposit requests from the logs
var allLogs []*types.Log
for _, receipt := range receipts {
allLogs = append(allLogs, receipt.Logs...)
@@ -389,17 +373,60 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
if err != nil {
return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not parse requests logs: %v", err))
}
+ // Process the withdrawal requests contract execution
+ vmenv := vm.NewEVM(vmContext, vm.TxContext{}, statedb, chainConfig, vmConfig)
+ wxs := core.ProcessDequeueWithdrawalRequests(vmenv, statedb)
+ requests = append(requests, wxs...)
+ // Process the consolidation requests contract execution
+ cxs := core.ProcessDequeueConsolidationRequests(vmenv, statedb)
+ requests = append(requests, cxs...)
// Calculate the requests root
h := types.DeriveSha(requests, trie.NewStackTrie(nil))
- execRs.RequestsHash = &h
- // Get the deposits from the requests
- deposits := make(types.Deposits, 0)
+ requestsHash = &h
+
+ // Break out individual request types.
+ depositRequests = make(types.Deposits, 0)
+ withdrawalRequests = make(types.WithdrawalRequests, 0)
+ consolidationRequests = make(types.ConsolidationRequests, 0)
for _, req := range requests {
- if dep, ok := req.Inner().(*types.Deposit); ok {
- deposits = append(deposits, dep)
+ switch v := req.Inner().(type) {
+ case *types.Deposit:
+ depositRequests = append(depositRequests, v)
+ case *types.WithdrawalRequest:
+ withdrawalRequests = append(withdrawalRequests, v)
+ case *types.ConsolidationRequest:
+ consolidationRequests = append(consolidationRequests, v)
}
}
- execRs.DepositRequests = &deposits
+ }
+ // Commit block
+ root, err := statedb.Commit(vmContext.BlockNumber.Uint64(), chainConfig.IsEIP158(vmContext.BlockNumber))
+ if err != nil {
+ return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not commit state: %v", err))
+ }
+ execRs := &ExecutionResult{
+ StateRoot: root,
+ TxRoot: types.DeriveSha(includedTxs, trie.NewStackTrie(nil)),
+ ReceiptRoot: types.DeriveSha(receipts, trie.NewStackTrie(nil)),
+ Bloom: types.CreateBloom(receipts),
+ LogsHash: rlpHash(statedb.Logs()),
+ Receipts: receipts,
+ Rejected: rejectedTxs,
+ Difficulty: (*math.HexOrDecimal256)(vmContext.Difficulty),
+ GasUsed: (math.HexOrDecimal64)(gasUsed),
+ BaseFee: (*math.HexOrDecimal256)(vmContext.BaseFee),
+ RequestsHash: requestsHash,
+ DepositRequests: depositRequests,
+ WithdrawalRequests: withdrawalRequests,
+ ConsolidationRequests: consolidationRequests,
+ }
+ if pre.Env.Withdrawals != nil {
+ h := types.DeriveSha(types.Withdrawals(pre.Env.Withdrawals), trie.NewStackTrie(nil))
+ execRs.WithdrawalsRoot = &h
+ }
+ if vmContext.BlobBaseFee != nil {
+ execRs.CurrentExcessBlobGas = (*math.HexOrDecimal64)(&excessBlobGas)
+ execRs.CurrentBlobGasUsed = (*math.HexOrDecimal64)(&blobGasUsed)
}
// Re-create statedb instance with new root upon the updated database
// for accessing latest states.
diff --git a/cmd/evm/internal/t8ntool/transaction.go b/cmd/evm/internal/t8ntool/transaction.go
index 7f66ba4d85d..64e21b24fd6 100644
--- a/cmd/evm/internal/t8ntool/transaction.go
+++ b/cmd/evm/internal/t8ntool/transaction.go
@@ -133,7 +133,7 @@ func Transaction(ctx *cli.Context) error {
r.Address = sender
}
// Check intrinsic gas
- if gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil,
+ if gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.AuthList(), tx.To() == nil,
chainConfig.IsHomestead(new(big.Int)), chainConfig.IsIstanbul(new(big.Int)), chainConfig.IsShanghai(new(big.Int), 0)); err != nil {
r.Error = err
results = append(results, r)
diff --git a/cmd/evm/staterunner.go b/cmd/evm/staterunner.go
index fc2bf8223f3..f7fe31337bf 100644
--- a/cmd/evm/staterunner.go
+++ b/cmd/evm/staterunner.go
@@ -91,7 +91,7 @@ func runStateTest(fname string, cfg vm.Config, dump bool) error {
}
var testsByName map[string]tests.StateTest
if err := json.Unmarshal(src, &testsByName); err != nil {
- return err
+ return fmt.Errorf("unable to read test file: %w", err)
}
// Iterate over all the tests, run them and aggregate the results
diff --git a/core/bench_test.go b/core/bench_test.go
index 639d36e9aea..aade2b36b9e 100644
--- a/core/bench_test.go
+++ b/core/bench_test.go
@@ -83,7 +83,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) {
return func(i int, gen *BlockGen) {
toaddr := common.Address{}
data := make([]byte, nbytes)
- gas, _ := IntrinsicGas(data, nil, false, false, false, false)
+ gas, _ := IntrinsicGas(data, nil, nil, false, false, false, false)
signer := gen.Signer()
gasPrice := big.NewInt(0)
if gen.header.BaseFee != nil {
diff --git a/core/blockchain_test.go b/core/blockchain_test.go
index fe0c527a009..bb20e03b3a9 100644
--- a/core/blockchain_test.go
+++ b/core/blockchain_test.go
@@ -17,6 +17,7 @@
package core
import (
+ "encoding/binary"
"errors"
"fmt"
"math/big"
@@ -4314,3 +4315,167 @@ func TestEIP6110(t *testing.T) {
}
}
}
+
+// TestRequests verifies that Prague requests are processed correctly.
+func TestRequests(t *testing.T) {
+ var (
+ engine = beacon.NewFaker()
+
+ // A sender who makes transactions, has some funds
+ key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
+ addr = crypto.PubkeyToAddress(key.PublicKey)
+ funds = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether))
+ config = *params.AllEthashProtocolChanges
+ gspec = &Genesis{
+ Config: &config,
+ Alloc: types.GenesisAlloc{
+ addr: {Balance: funds},
+ params.WithdrawalRequestsAddress: {Code: common.FromHex("3373fffffffffffffffffffffffffffffffffffffffe146090573615156028575f545f5260205ff35b366038141561012e5760115f54600182026001905f5b5f82111560595781019083028483029004916001019190603e565b90939004341061012e57600154600101600155600354806003026004013381556001015f3581556001016020359055600101600355005b6003546002548082038060101160a4575060105b5f5b81811460dd5780604c02838201600302600401805490600101805490600101549160601b83528260140152906034015260010160a6565b910180921460ed579060025560f8565b90505f6002555f6003555b5f548061049d141561010757505f5b60015460028282011161011c5750505f610122565b01600290035b5f555f600155604c025ff35b5f5ffd")},
+ params.ConsolidationRequestsAddress: {Code: common.FromHex("3373fffffffffffffffffffffffffffffffffffffffe146098573615156028575f545f5260205ff35b36606014156101445760115f54600182026001905f5b5f82111560595781019083028483029004916001019190603e565b90939004341061014457600154600101600155600354806004026004013381556001015f35815560010160203581556001016040359055600101600355005b6003546002548082038060011160ac575060015b5f5b81811460f15780607402838201600402600401805490600101805490600101805490600101549260601b84529083601401528260340152906054015260010160ae565b9101809214610103579060025561010e565b90505f6002555f6003555b5f548061049d141561011d57505f5b6001546001828201116101325750505f610138565b01600190035b5f555f6001556074025ff35b5f5ffd")},
+ },
+ }
+ )
+ gspec.Config.BerlinBlock = common.Big0
+ gspec.Config.LondonBlock = common.Big0
+ gspec.Config.TerminalTotalDifficulty = common.Big0
+ gspec.Config.TerminalTotalDifficultyPassed = true
+ gspec.Config.ShanghaiTime = u64(0)
+ gspec.Config.CancunTime = u64(0)
+ gspec.Config.PragueTime = u64(0)
+ signer := types.LatestSigner(gspec.Config)
+
+ // Withdrawal requests to send.
+ wxs := types.WithdrawalRequests{
+ {
+ Source: addr,
+ PublicKey: [48]byte{42},
+ Amount: 42,
+ },
+ {
+ Source: addr,
+ PublicKey: [48]byte{13, 37},
+ Amount: 1337,
+ },
+ }
+ cxs := types.ConsolidationRequests{
+ {
+ Source: addr,
+ SourcePublicKey: [48]byte{13, 37},
+ TargetPublicKey: [48]byte{11, 11},
+ },
+ {
+ Source: addr,
+ SourcePublicKey: [48]byte{42, 42},
+ TargetPublicKey: [48]byte{11, 11},
+ },
+ }
+
+ _, blocks, _ := GenerateChainWithGenesis(gspec, engine, 3, func(i int, b *BlockGen) {
+ switch i {
+ case 0:
+ // Block 1: submit withdrawal requests
+ for _, wx := range wxs {
+ data := make([]byte, 56)
+ copy(data, wx.PublicKey[:])
+ binary.BigEndian.PutUint64(data[48:], wx.Amount)
+ txdata := &types.DynamicFeeTx{
+ ChainID: gspec.Config.ChainID,
+ Nonce: b.TxNonce(addr),
+ To: ¶ms.WithdrawalRequestsAddress,
+ Value: big.NewInt(1),
+ Gas: 500000,
+ GasFeeCap: newGwei(5),
+ GasTipCap: big.NewInt(2),
+ AccessList: nil,
+ Data: data,
+ }
+ tx := types.NewTx(txdata)
+ tx, _ = types.SignTx(tx, signer, key)
+ b.AddTx(tx)
+ }
+ case 1:
+ // Block 2: submit consolidation requests
+ for _, cx := range cxs {
+ data := make([]byte, 96)
+ copy(data, cx.SourcePublicKey[:])
+ copy(data[48:], cx.TargetPublicKey[:])
+ txdata := &types.DynamicFeeTx{
+ ChainID: gspec.Config.ChainID,
+ Nonce: b.TxNonce(addr),
+ To: ¶ms.ConsolidationRequestsAddress,
+ Value: big.NewInt(1),
+ Gas: 500000,
+ GasFeeCap: newGwei(5),
+ GasTipCap: big.NewInt(2),
+ AccessList: nil,
+ Data: data,
+ }
+ tx := types.NewTx(txdata)
+ tx, _ = types.SignTx(tx, signer, key)
+ b.AddTx(tx)
+ }
+ }
+ })
+
+ chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{Tracer: logger.NewMarkdownLogger(&logger.Config{}, os.Stderr).Hooks()}, nil, nil)
+ if err != nil {
+ t.Fatalf("failed to create tester chain: %v", err)
+ }
+ defer chain.Stop()
+ if n, err := chain.InsertChain(blocks); err != nil {
+ t.Fatalf("block %d: failed to insert into chain: %v", n, err)
+ }
+
+ // Verify the withdrawal requests match.
+ block := chain.GetBlockByNumber(1)
+ if block == nil {
+ t.Fatalf("failed to retrieve block 1")
+ }
+
+ // Verify the withdrawal requests match.
+ got := block.Requests()
+ if len(got) != 2 {
+ t.Fatalf("wrong number of withdrawal requests: wanted 2, got %d", len(got))
+ }
+ for i, want := range wxs {
+ got, ok := got[i].Inner().(*types.WithdrawalRequest)
+ if !ok {
+ t.Fatalf("expected withdrawal request")
+ }
+ if want.Source != got.Source {
+ t.Fatalf("wrong source address: want %s, got %s", want.Source, got.Source)
+ }
+ if want.PublicKey != got.PublicKey {
+ t.Fatalf("wrong public key: want %s, got %s", common.Bytes2Hex(want.PublicKey[:]), common.Bytes2Hex(got.PublicKey[:]))
+ }
+ if want.Amount != got.Amount {
+ t.Fatalf("wrong amount: want %d, got %d", want.Amount, got.Amount)
+ }
+ }
+
+ // Verify the consolidation requests match. Even though both requests are sent
+ // in block two, only one is dequeued at a time.
+ for i, want := range cxs {
+ block := chain.GetBlockByNumber(uint64(i + 2))
+ if block == nil {
+ t.Fatalf("failed to retrieve block")
+ }
+ requests := block.Requests()
+ if len(requests) != 1 {
+ t.Fatalf("wrong number of consolidation requests: wanted 1, got %d", len(got))
+ }
+ got, ok := requests[0].Inner().(*types.ConsolidationRequest)
+ if !ok {
+ t.Fatalf("expected consolidation request")
+ }
+ if want.Source != got.Source {
+ t.Fatalf("wrong source address: want %s, got %s", want.Source, got.Source)
+ }
+ if want.SourcePublicKey != got.SourcePublicKey {
+ t.Fatalf("wrong source public key: want %s, got %s", common.Bytes2Hex(want.SourcePublicKey[:]), common.Bytes2Hex(got.SourcePublicKey[:]))
+ }
+ if want.TargetPublicKey != got.TargetPublicKey {
+ t.Fatalf("wrong target public key: want %s, got %s", common.Bytes2Hex(want.TargetPublicKey[:]), common.Bytes2Hex(got.TargetPublicKey[:]))
+ }
+ }
+}
diff --git a/core/chain_makers.go b/core/chain_makers.go
index dcaa36e08cc..60e59e057c2 100644
--- a/core/chain_makers.go
+++ b/core/chain_makers.go
@@ -355,6 +355,15 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
}
requests = append(requests, d...)
}
+
+ var (
+ blockContext = NewEVMBlockContext(b.header, b.cm, &b.header.Coinbase)
+ vmenv = vm.NewEVM(blockContext, vm.TxContext{}, b.statedb, b.cm.config, vm.Config{})
+ )
+ wxs := ProcessDequeueWithdrawalRequests(vmenv, statedb)
+ requests = append(requests, wxs...)
+ cxs := ProcessDequeueConsolidationRequests(vmenv, statedb)
+ requests = append(requests, cxs...)
}
body := types.Body{Transactions: b.txs, Uncles: b.uncles, Withdrawals: b.withdrawals, Requests: requests}
diff --git a/core/error.go b/core/error.go
index 161538fe432..f00a8c075a8 100644
--- a/core/error.go
+++ b/core/error.go
@@ -112,4 +112,7 @@ var (
// ErrBlobTxCreate is returned if a blob transaction has no explicit to field.
ErrBlobTxCreate = errors.New("blob transaction of type create")
+
+ // ErrEmptyAuthList is returned if a set code transaction has an empty auth list.
+ ErrEmptyAuthList = errors.New("set code transaction with empty auth list")
)
diff --git a/core/setcode_test.go b/core/setcode_test.go
new file mode 100644
index 00000000000..72a95f7859d
--- /dev/null
+++ b/core/setcode_test.go
@@ -0,0 +1,131 @@
+package core
+
+import (
+ "bytes"
+ "math/big"
+ "os"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/consensus/beacon"
+ "github.com/ethereum/go-ethereum/core/rawdb"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/core/vm"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/eth/tracers/logger"
+ "github.com/ethereum/go-ethereum/params"
+ "github.com/holiman/uint256"
+)
+
+func TestEIP7702(t *testing.T) {
+ var (
+ aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa")
+ bb = common.HexToAddress("0x000000000000000000000000000000000000bbbb")
+ engine = beacon.NewFaker()
+
+ // A sender who makes transactions, has some funds
+ key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
+ key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
+ addr1 = crypto.PubkeyToAddress(key1.PublicKey)
+ addr2 = crypto.PubkeyToAddress(key2.PublicKey)
+ funds = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether))
+ config = *params.AllEthashProtocolChanges
+ gspec = &Genesis{
+ Config: &config,
+ Alloc: types.GenesisAlloc{
+ addr1: {Balance: funds},
+ addr2: {Balance: funds},
+ // The address 0xAAAA sstores 1 into slot 2.
+ aa: {
+ Code: []byte{
+ byte(vm.PC), // [0]
+ byte(vm.DUP1), // [0,0]
+ byte(vm.DUP1), // [0,0,0]
+ byte(vm.DUP1), // [0,0,0,0]
+ byte(vm.PUSH1), 0x01, // [0,0,0,0,1] (value)
+ byte(vm.PUSH20), addr2[0], addr2[1], addr2[2], addr2[3], addr2[4], addr2[5], addr2[6], addr2[7], addr2[8], addr2[9], addr2[10], addr2[11], addr2[12], addr2[13], addr2[14], addr2[15], addr2[16], addr2[17], addr2[18], addr2[19],
+ byte(vm.GAS),
+ byte(vm.CALL),
+ byte(vm.STOP),
+ },
+ Nonce: 0,
+ Balance: big.NewInt(0),
+ },
+ // The address 0xBBBB sstores 42 into slot 42.
+ bb: {
+ Code: []byte{
+ byte(vm.PUSH1), 0x42,
+ byte(vm.DUP1),
+ byte(vm.SSTORE),
+ byte(vm.STOP),
+ },
+ Nonce: 0,
+ Balance: big.NewInt(0),
+ },
+ },
+ }
+ )
+
+ gspec.Config.BerlinBlock = common.Big0
+ gspec.Config.LondonBlock = common.Big0
+ gspec.Config.TerminalTotalDifficulty = common.Big0
+ gspec.Config.TerminalTotalDifficultyPassed = true
+ gspec.Config.ShanghaiTime = u64(0)
+ gspec.Config.CancunTime = u64(0)
+ gspec.Config.PragueTime = u64(0)
+ signer := types.LatestSigner(gspec.Config)
+
+ auth1, _ := types.SignAuth(&types.Authorization{
+ ChainID: new(big.Int).Set(gspec.Config.ChainID),
+ Address: aa,
+ Nonce: 1,
+ }, key1)
+
+ auth2, _ := types.SignAuth(&types.Authorization{
+ ChainID: new(big.Int),
+ Address: bb,
+ Nonce: 0,
+ }, key2)
+
+ _, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) {
+ b.SetCoinbase(aa)
+ txdata := &types.SetCodeTx{
+ ChainID: uint256.MustFromBig(gspec.Config.ChainID),
+ Nonce: 0,
+ To: addr1,
+ Gas: 500000,
+ GasFeeCap: uint256.MustFromBig(newGwei(5)),
+ GasTipCap: uint256.NewInt(2),
+ AuthList: []*types.Authorization{auth1, auth2},
+ }
+ tx := types.NewTx(txdata)
+ tx, err := types.SignTx(tx, signer, key1)
+ if err != nil {
+ t.Fatalf("%s", err)
+ }
+ b.AddTx(tx)
+ })
+ chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{Tracer: logger.NewMarkdownLogger(&logger.Config{}, os.Stderr).Hooks()}, nil, nil)
+ if err != nil {
+ t.Fatalf("failed to create tester chain: %v", err)
+ }
+ defer chain.Stop()
+ if n, err := chain.InsertChain(blocks); err != nil {
+ t.Fatalf("block %d: failed to insert into chain: %v", n, err)
+ }
+
+ var (
+ state, _ = chain.State()
+ fortyTwo = common.BytesToHash([]byte{0x42})
+ actual = state.GetState(addr2, fortyTwo)
+ )
+ if code, want := state.GetCode(addr1), types.AddressToDelegation(auth1.Address); !bytes.Equal(code, want) {
+ t.Fatalf("addr1 code incorrect: got %s, want %s", common.Bytes2Hex(code), common.Bytes2Hex(want))
+ }
+ if code, want := state.GetCode(addr2), types.AddressToDelegation(auth2.Address); !bytes.Equal(code, want) {
+ t.Fatalf("addr2 code incorrect: got %s, want %s", common.Bytes2Hex(code), common.Bytes2Hex(want))
+ }
+ if actual.Cmp(fortyTwo) != 0 {
+ t.Fatalf("addr2 storage wrong: expected %d, got %d", fortyTwo, actual)
+ }
+}
diff --git a/core/state/statedb.go b/core/state/statedb.go
index f81c8a055b0..dd88ba9799d 100644
--- a/core/state/statedb.go
+++ b/core/state/statedb.go
@@ -384,6 +384,22 @@ func (s *StateDB) GetCodeHash(addr common.Address) common.Hash {
return common.Hash{}
}
+func (s *StateDB) ResolveCode(addr common.Address) []byte {
+ stateObject := s.resolveStateObject(addr)
+ if stateObject != nil {
+ return stateObject.Code()
+ }
+ return nil
+}
+
+func (s *StateDB) ResolveCodeHash(addr common.Address) common.Hash {
+ stateObject := s.resolveStateObject(addr)
+ if stateObject != nil {
+ return common.BytesToHash(stateObject.CodeHash())
+ }
+ return common.Hash{}
+}
+
// GetState retrieves the value associated with the specific key.
func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash {
stateObject := s.getStateObject(addr)
@@ -639,6 +655,18 @@ func (s *StateDB) getStateObject(addr common.Address) *stateObject {
return obj
}
+func (s *StateDB) resolveStateObject(addr common.Address) *stateObject {
+ obj := s.getStateObject(addr)
+ if obj == nil {
+ return nil
+ }
+ addr, ok := types.ParseDelegation(obj.Code())
+ if !ok {
+ return obj
+ }
+ return s.getStateObject(addr)
+}
+
func (s *StateDB) setStateObject(object *stateObject) {
s.stateObjects[object.Address()] = object
}
@@ -1381,6 +1409,12 @@ func (s *StateDB) Prepare(rules params.Rules, sender, coinbase common.Address, d
al.AddAddress(sender)
if dst != nil {
al.AddAddress(*dst)
+ if rules.IsPrague {
+ // Add delegation target
+ if addr, ok := types.ParseDelegation(s.GetCode(*dst)); ok {
+ al.AddAddress(addr)
+ }
+ }
// If it's a create-tx, the destination will be added inside evm.create
}
for _, addr := range precompiles {
diff --git a/core/state_processor.go b/core/state_processor.go
index 2844693a9e5..b4e61f5fcfb 100644
--- a/core/state_processor.go
+++ b/core/state_processor.go
@@ -17,6 +17,7 @@
package core
import (
+ "encoding/binary"
"fmt"
"math/big"
@@ -103,6 +104,10 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
if err != nil {
return nil, err
}
+ wxs := ProcessDequeueWithdrawalRequests(vmenv, statedb)
+ requests = append(requests, wxs...)
+ cxs := ProcessDequeueConsolidationRequests(vmenv, statedb)
+ requests = append(requests, cxs...)
}
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
@@ -270,3 +275,81 @@ func ParseDepositLogs(logs []*types.Log, config *params.ChainConfig) (types.Requ
}
return deposits, nil
}
+
+// ProcessDequeueWithdrawalRequests applies the EIP-7002 system call to the withdrawal requests contract.
+func ProcessDequeueWithdrawalRequests(vmenv *vm.EVM, statedb *state.StateDB) types.Requests {
+ if vmenv.Config.Tracer != nil && vmenv.Config.Tracer.OnSystemCallStart != nil {
+ vmenv.Config.Tracer.OnSystemCallStart()
+ }
+ if vmenv.Config.Tracer != nil && vmenv.Config.Tracer.OnSystemCallEnd != nil {
+ defer vmenv.Config.Tracer.OnSystemCallEnd()
+ }
+ msg := &Message{
+ From: params.SystemAddress,
+ GasLimit: 30_000_000,
+ GasPrice: common.Big0,
+ GasFeeCap: common.Big0,
+ GasTipCap: common.Big0,
+ To: ¶ms.WithdrawalRequestsAddress,
+ }
+ vmenv.Reset(NewEVMTxContext(msg), statedb)
+ statedb.AddAddressToAccessList(params.WithdrawalRequestsAddress)
+ ret, _, _ := vmenv.Call(vm.AccountRef(msg.From), *msg.To, msg.Data, 30_000_000, common.U2560)
+ statedb.Finalise(true)
+
+ // Parse out the exits.
+ var reqs types.Requests
+ for i := 0; i < len(ret)/76; i++ {
+ start := i * 76
+ var pubkey [48]byte
+ copy(pubkey[:], ret[start+20:start+68])
+ wx := &types.WithdrawalRequest{
+ Source: common.BytesToAddress(ret[start : start+20]),
+ PublicKey: pubkey,
+ Amount: binary.BigEndian.Uint64(ret[start+68:]),
+ }
+ reqs = append(reqs, types.NewRequest(wx))
+ }
+ return reqs
+}
+
+// ProcessDequeueConsolidationRequests applies the EIP-7251 system call to the consolidation requests contract.
+func ProcessDequeueConsolidationRequests(vmenv *vm.EVM, statedb *state.StateDB) types.Requests {
+ if vmenv.Config.Tracer != nil && vmenv.Config.Tracer.OnSystemCallStart != nil {
+ vmenv.Config.Tracer.OnSystemCallStart()
+ }
+ if vmenv.Config.Tracer != nil && vmenv.Config.Tracer.OnSystemCallEnd != nil {
+ defer vmenv.Config.Tracer.OnSystemCallEnd()
+ }
+ msg := &Message{
+ From: params.SystemAddress,
+ GasLimit: 30_000_000,
+ GasPrice: common.Big0,
+ GasFeeCap: common.Big0,
+ GasTipCap: common.Big0,
+ To: ¶ms.ConsolidationRequestsAddress,
+ }
+ vmenv.Reset(NewEVMTxContext(msg), statedb)
+ statedb.AddAddressToAccessList(params.ConsolidationRequestsAddress)
+ ret, _, _ := vmenv.Call(vm.AccountRef(msg.From), *msg.To, msg.Data, 30_000_000, common.U2560)
+ statedb.Finalise(true)
+
+ // Parse out the exits.
+ var reqs types.Requests
+ for i := 0; i < len(ret)/116; i++ {
+ start := i * 116
+ var (
+ sourcePubkey [48]byte
+ targetPubkey [48]byte
+ )
+ copy(sourcePubkey[:], ret[start+20:start+20+48])
+ copy(targetPubkey[:], ret[start+20+48:start+20+48+48])
+ cx := &types.ConsolidationRequest{
+ Source: common.BytesToAddress(ret[start : start+20]),
+ SourcePublicKey: sourcePubkey,
+ TargetPublicKey: targetPubkey,
+ }
+ reqs = append(reqs, types.NewRequest(cx))
+ }
+ return reqs
+}
diff --git a/core/state_processor_test.go b/core/state_processor_test.go
index 3ed0eb75799..15ed89a85ae 100644
--- a/core/state_processor_test.go
+++ b/core/state_processor_test.go
@@ -431,12 +431,12 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr
var (
code = common.FromHex(`6060604052600a8060106000396000f360606040526008565b00`)
- intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, true, true, true, true)
+ intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, nil, true, true, true, true)
// 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, true, true, true, true)
+ intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, nil, true, true, true, true)
)
func TestProcessVerkle(t *testing.T) {
diff --git a/core/state_transition.go b/core/state_transition.go
index 37807d3d2b6..31764f43569 100644
--- a/core/state_transition.go
+++ b/core/state_transition.go
@@ -17,6 +17,7 @@
package core
import (
+ "bytes"
"fmt"
"math"
"math/big"
@@ -27,6 +28,7 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
+ "github.com/ethereum/go-ethereum/crypto/secp256k1"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
)
@@ -68,7 +70,7 @@ func (result *ExecutionResult) Revert() []byte {
}
// IntrinsicGas computes the 'intrinsic gas' for a message with the given data.
-func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation, isHomestead, isEIP2028, isEIP3860 bool) (uint64, error) {
+func IntrinsicGas(data []byte, accessList types.AccessList, authList types.AuthorizationList, isContractCreation, isHomestead, isEIP2028, isEIP3860 bool) (uint64, error) {
// Set the starting gas for the raw transaction
var gas uint64
if isContractCreation && isHomestead {
@@ -114,6 +116,9 @@ func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation,
gas += uint64(len(accessList)) * params.TxAccessListAddressGas
gas += uint64(accessList.StorageKeys()) * params.TxAccessListStorageKeyGas
}
+ if authList != nil {
+ gas += uint64(len(authList)) * params.CallNewAccountGas
+ }
return gas, nil
}
@@ -141,6 +146,7 @@ type Message struct {
AccessList types.AccessList
BlobGasFeeCap *big.Int
BlobHashes []common.Hash
+ AuthList types.AuthorizationList
// When SkipAccountChecks is true, the message nonce is not checked against the
// account nonce in state. It also disables checking that the sender is an EOA.
@@ -160,6 +166,7 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In
Value: tx.Value(),
Data: tx.Data(),
AccessList: tx.AccessList(),
+ AuthList: tx.AuthList(),
SkipAccountChecks: false,
BlobHashes: tx.BlobHashes(),
BlobGasFeeCap: tx.BlobGasFeeCap(),
@@ -294,10 +301,10 @@ func (st *StateTransition) preCheck() error {
msg.From.Hex(), stNonce)
}
// Make sure the sender is an EOA
- codeHash := st.state.GetCodeHash(msg.From)
- if codeHash != (common.Hash{}) && codeHash != types.EmptyCodeHash {
+ code := st.state.GetCode(msg.From)
+ if 0 < len(code) && !bytes.HasPrefix(code, []byte{0xef, 0x01, 0x00}) {
return fmt.Errorf("%w: address %v, codehash: %s", ErrSenderNoEOA,
- msg.From.Hex(), codeHash)
+ msg.From.Hex(), st.state.GetCodeHash(msg.From))
}
}
// Make sure that transaction gasFeeCap is greater than the baseFee (post london)
@@ -357,6 +364,27 @@ func (st *StateTransition) preCheck() error {
}
}
}
+
+ // Check that auth list isn't empty.
+ if msg.AuthList != nil && len(msg.AuthList) == 0 {
+ return fmt.Errorf("%w: address %v", ErrEmptyAuthList, msg.From.Hex())
+ }
+
+ // TODO: remove after this spec change is merged:
+ // https://github.com/ethereum/EIPs/pull/8845
+ if msg.AuthList != nil {
+ var (
+ secp256k1N = secp256k1.S256().Params().N
+ secp256k1halfN = new(big.Int).Div(secp256k1N, big.NewInt(2))
+ )
+ for _, auth := range msg.AuthList {
+ if auth.V.Cmp(common.Big1) > 0 || auth.S.Cmp(secp256k1halfN) > 0 {
+ w := fmt.Errorf("set code transaction with invalid auth signature")
+ return fmt.Errorf("%w: address %v", w, msg.From.Hex())
+ }
+ }
+ }
+
return st.buyGas()
}
@@ -394,7 +422,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
)
// Check clauses 4-5, subtract intrinsic gas if everything is correct
- gas, err := IntrinsicGas(msg.Data, msg.AccessList, contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai)
+ gas, err := IntrinsicGas(msg.Data, msg.AccessList, msg.AuthList, contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai)
if err != nil {
return nil, err
}
@@ -433,6 +461,51 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
// - reset transient storage(eip 1153)
st.state.Prepare(rules, msg.From, st.evm.Context.Coinbase, msg.To, vm.ActivePrecompiles(rules), msg.AccessList)
+ if !contractCreation {
+ // Increment the nonce for the next transaction
+ st.state.SetNonce(msg.From, st.state.GetNonce(sender.Address())+1)
+ }
+
+ // Check authorizations list validity.
+ if msg.AuthList != nil {
+ for _, auth := range msg.AuthList {
+ // Verify chain ID is 0 or equal to current chain ID.
+ if auth.ChainID.Sign() != 0 && st.evm.ChainConfig().ChainID.Cmp(auth.ChainID) != 0 {
+ continue
+ }
+ authority, err := auth.Authority()
+ if err != nil {
+ continue
+ }
+ // Check the authority account 1) doesn't have code or has exisiting
+ // delegation 2) matches the auth's nonce
+ code := st.state.GetCode(authority)
+ st.state.AddAddressToAccessList(authority)
+ if witness := st.state.Witness(); witness != nil {
+ witness.AddCode(code)
+ }
+ if _, ok := types.ParseDelegation(code); len(code) != 0 && !ok {
+ continue
+ }
+ if have := st.state.GetNonce(authority); have != auth.Nonce {
+ continue
+ }
+ // If the account already exists in state, refund the new account cost
+ // charged in the initrinsic calculation.
+ if exists := st.state.Exist(authority); exists {
+ st.state.AddRefund(params.CallNewAccountGas - params.TxAuthTupleGas)
+ }
+ // Special case, when msg.To is to an authority being set in the tx, we
+ // need to add it to the access list. Usually this happens in StateDB
+ // Prepare(..).
+ if st.msg.To != nil && *st.msg.To == authority {
+ st.state.AddAddressToAccessList(auth.Address)
+ }
+ st.state.SetNonce(authority, auth.Nonce+1)
+ st.state.SetCode(authority, types.AddressToDelegation(auth.Address))
+ }
+ }
+
var (
ret []byte
vmerr error // vm errors do not effect consensus and are therefore not assigned to err
@@ -440,8 +513,6 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
if contractCreation {
ret, _, st.gasRemaining, vmerr = st.evm.Create(sender, msg.Data, st.gasRemaining, value)
} else {
- // Increment the nonce for the next transaction
- st.state.SetNonce(msg.From, st.state.GetNonce(sender.Address())+1)
ret, st.gasRemaining, vmerr = st.evm.Call(sender, st.to(), msg.Data, st.gasRemaining, value)
}
diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go
index 5506ecc31fc..8bc9007daf1 100644
--- a/core/txpool/legacypool/legacypool.go
+++ b/core/txpool/legacypool/legacypool.go
@@ -278,7 +278,7 @@ func New(config Config, chain BlockChain) *LegacyPool {
// pool, specifically, whether it is a Legacy, AccessList or Dynamic transaction.
func (pool *LegacyPool) Filter(tx *types.Transaction) bool {
switch tx.Type() {
- case types.LegacyTxType, types.AccessListTxType, types.DynamicFeeTxType:
+ case types.SetCodeTxType, types.LegacyTxType, types.AccessListTxType, types.DynamicFeeTxType:
return true
default:
return false
@@ -610,7 +610,8 @@ func (pool *LegacyPool) validateTxBasics(tx *types.Transaction, local bool) erro
Accept: 0 |
1<.
+package types
+
+import (
+ "bytes"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/ethereum/go-ethereum/rlp"
+)
+
+//go:generate go run github.com/fjl/gencodec -type ConsolidationRequest -field-override consolidationRequestMarshaling -out gen_consolidation_request_json.go
+
+// ConsolidationRequest represents an EIP-7251 consolidation request from source for
+// the validator associated with the source public key to a target public key.
+type ConsolidationRequest struct {
+ Source common.Address `json:"sourceAddress"`
+ SourcePublicKey [48]byte `json:"sourcePubkey"`
+ TargetPublicKey [48]byte `json:"targetPubkey"`
+}
+
+// field type overrides for gencodec
+type consolidationRequestMarshaling struct {
+ SourcePublicKey hexutil.Bytes
+ TargetPublicKey hexutil.Bytes
+}
+
+func (c *ConsolidationRequest) Bytes() []byte {
+ out := make([]byte, 116)
+ copy(out, c.Source.Bytes())
+ copy(out[20:], c.SourcePublicKey[:])
+ copy(out[68:], c.TargetPublicKey[:])
+ return out
+}
+
+// ConsolidationRequests implements DerivableList for consolidation requests.
+type ConsolidationRequests []*ConsolidationRequest
+
+// Len returns the length of s.
+func (s ConsolidationRequests) Len() int { return len(s) }
+
+// EncodeIndex encodes the i'th consolidation request to c.
+func (s ConsolidationRequests) EncodeIndex(i int, c *bytes.Buffer) {
+ rlp.Encode(c, s[i])
+}
+
+// Requests creates a deep copy of each deposit and returns a slice of the
+// withdrwawal requests as Request objects.
+func (s ConsolidationRequests) Requests() (reqs Requests) {
+ for _, d := range s {
+ reqs = append(reqs, NewRequest(d))
+ }
+ return
+}
+
+func (c *ConsolidationRequest) requestType() byte { return ConsolidationRequestType }
+func (c *ConsolidationRequest) encode(b *bytes.Buffer) error { return rlp.Encode(b, c) }
+func (c *ConsolidationRequest) decode(input []byte) error { return rlp.DecodeBytes(input, c) }
+func (c *ConsolidationRequest) copy() RequestData {
+ return &ConsolidationRequest{
+ Source: c.Source,
+ SourcePublicKey: c.SourcePublicKey,
+ TargetPublicKey: c.TargetPublicKey,
+ }
+}
diff --git a/core/types/gen_authorization.go b/core/types/gen_authorization.go
new file mode 100644
index 00000000000..c629a2a4d6f
--- /dev/null
+++ b/core/types/gen_authorization.go
@@ -0,0 +1,74 @@
+// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
+
+package types
+
+import (
+ "encoding/json"
+ "errors"
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/hexutil"
+)
+
+var _ = (*authorizationMarshaling)(nil)
+
+// MarshalJSON marshals as JSON.
+func (a Authorization) MarshalJSON() ([]byte, error) {
+ type Authorization struct {
+ ChainID *hexutil.Big
+ Address common.Address `json:"address" gencodec:"required"`
+ Nonce hexutil.Uint64 `json:"nonce" gencodec:"required"`
+ V *hexutil.Big `json:"v" gencodec:"required"`
+ R *hexutil.Big `json:"r" gencodec:"required"`
+ S *hexutil.Big `json:"s" gencodec:"required"`
+ }
+ var enc Authorization
+ enc.ChainID = (*hexutil.Big)(a.ChainID)
+ enc.Address = a.Address
+ enc.Nonce = hexutil.Uint64(a.Nonce)
+ enc.V = (*hexutil.Big)(a.V)
+ enc.R = (*hexutil.Big)(a.R)
+ enc.S = (*hexutil.Big)(a.S)
+ return json.Marshal(&enc)
+}
+
+// UnmarshalJSON unmarshals from JSON.
+func (a *Authorization) UnmarshalJSON(input []byte) error {
+ type Authorization struct {
+ ChainID *hexutil.Big
+ Address *common.Address `json:"address" gencodec:"required"`
+ Nonce *hexutil.Uint64 `json:"nonce" gencodec:"required"`
+ V *hexutil.Big `json:"v" gencodec:"required"`
+ R *hexutil.Big `json:"r" gencodec:"required"`
+ S *hexutil.Big `json:"s" gencodec:"required"`
+ }
+ var dec Authorization
+ if err := json.Unmarshal(input, &dec); err != nil {
+ return err
+ }
+ if dec.ChainID != nil {
+ a.ChainID = (*big.Int)(dec.ChainID)
+ }
+ if dec.Address == nil {
+ return errors.New("missing required field 'address' for Authorization")
+ }
+ a.Address = *dec.Address
+ if dec.Nonce == nil {
+ return errors.New("missing required field 'nonce' for Authorization")
+ }
+ a.Nonce = uint64(*dec.Nonce)
+ if dec.V == nil {
+ return errors.New("missing required field 'v' for Authorization")
+ }
+ a.V = (*big.Int)(dec.V)
+ if dec.R == nil {
+ return errors.New("missing required field 'r' for Authorization")
+ }
+ a.R = (*big.Int)(dec.R)
+ if dec.S == nil {
+ return errors.New("missing required field 's' for Authorization")
+ }
+ a.S = (*big.Int)(dec.S)
+ return nil
+}
diff --git a/core/types/gen_consolidation_request_json.go b/core/types/gen_consolidation_request_json.go
new file mode 100644
index 00000000000..699abb4a7d9
--- /dev/null
+++ b/core/types/gen_consolidation_request_json.go
@@ -0,0 +1,56 @@
+// 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"
+)
+
+var _ = (*consolidationRequestMarshaling)(nil)
+
+// MarshalJSON marshals as JSON.
+func (c ConsolidationRequest) MarshalJSON() ([]byte, error) {
+ type ConsolidationRequest struct {
+ Source common.Address `json:"sourceAddress"`
+ SourcePublicKey hexutil.Bytes `json:"sourcePubkey"`
+ TargetPublicKey hexutil.Bytes `json:"targetPubkey"`
+ }
+ var enc ConsolidationRequest
+ enc.Source = c.Source
+ enc.SourcePublicKey = c.SourcePublicKey[:]
+ enc.TargetPublicKey = c.TargetPublicKey[:]
+ return json.Marshal(&enc)
+}
+
+// UnmarshalJSON unmarshals from JSON.
+func (c *ConsolidationRequest) UnmarshalJSON(input []byte) error {
+ type ConsolidationRequest struct {
+ Source *common.Address `json:"sourceAddress"`
+ SourcePublicKey *hexutil.Bytes `json:"sourcePubkey"`
+ TargetPublicKey *hexutil.Bytes `json:"targetPubkey"`
+ }
+ var dec ConsolidationRequest
+ if err := json.Unmarshal(input, &dec); err != nil {
+ return err
+ }
+ if dec.Source != nil {
+ c.Source = *dec.Source
+ }
+ if dec.SourcePublicKey != nil {
+ if len(*dec.SourcePublicKey) != len(c.SourcePublicKey) {
+ return errors.New("field 'sourcePubkey' has wrong length, need 48 items")
+ }
+ copy(c.SourcePublicKey[:], *dec.SourcePublicKey)
+ }
+ if dec.TargetPublicKey != nil {
+ if len(*dec.TargetPublicKey) != len(c.TargetPublicKey) {
+ return errors.New("field 'targetPubkey' has wrong length, need 48 items")
+ }
+ copy(c.TargetPublicKey[:], *dec.TargetPublicKey)
+ }
+ return nil
+}
diff --git a/core/types/gen_withdrawal_request_json.go b/core/types/gen_withdrawal_request_json.go
new file mode 100644
index 00000000000..655e1199170
--- /dev/null
+++ b/core/types/gen_withdrawal_request_json.go
@@ -0,0 +1,53 @@
+// 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"
+)
+
+var _ = (*withdrawalRequestMarshaling)(nil)
+
+// MarshalJSON marshals as JSON.
+func (w WithdrawalRequest) MarshalJSON() ([]byte, error) {
+ type WithdrawalRequest struct {
+ Source common.Address `json:"sourceAddress"`
+ PublicKey hexutil.Bytes `json:"validatorPubkey"`
+ Amount hexutil.Uint64 `json:"amount"`
+ }
+ var enc WithdrawalRequest
+ enc.Source = w.Source
+ enc.PublicKey = w.PublicKey[:]
+ enc.Amount = hexutil.Uint64(w.Amount)
+ return json.Marshal(&enc)
+}
+
+// UnmarshalJSON unmarshals from JSON.
+func (w *WithdrawalRequest) UnmarshalJSON(input []byte) error {
+ type WithdrawalRequest struct {
+ Source *common.Address `json:"sourceAddress"`
+ PublicKey *hexutil.Bytes `json:"validatorPubkey"`
+ Amount *hexutil.Uint64 `json:"amount"`
+ }
+ var dec WithdrawalRequest
+ if err := json.Unmarshal(input, &dec); err != nil {
+ return err
+ }
+ if dec.Source != nil {
+ w.Source = *dec.Source
+ }
+ if dec.PublicKey != nil {
+ if len(*dec.PublicKey) != len(w.PublicKey) {
+ return errors.New("field 'validatorPubkey' has wrong length, need 48 items")
+ }
+ copy(w.PublicKey[:], *dec.PublicKey)
+ }
+ if dec.Amount != nil {
+ w.Amount = uint64(*dec.Amount)
+ }
+ return nil
+}
diff --git a/core/types/receipt.go b/core/types/receipt.go
index 4f96fde59c4..47f16d950ae 100644
--- a/core/types/receipt.go
+++ b/core/types/receipt.go
@@ -204,7 +204,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 {
@@ -312,7 +312,7 @@ func (rs Receipts) EncodeIndex(i int, w *bytes.Buffer) {
}
w.WriteByte(r.Type)
switch r.Type {
- case AccessListTxType, DynamicFeeTxType, BlobTxType:
+ case AccessListTxType, DynamicFeeTxType, BlobTxType, SetCodeTxType:
rlp.Encode(w, data)
default:
// For unsupported types, write nothing. Since this is for
diff --git a/core/types/request.go b/core/types/request.go
index 7b1cade26e7..b8a0ef7afcc 100644
--- a/core/types/request.go
+++ b/core/types/request.go
@@ -32,7 +32,9 @@ var (
// Request types.
const (
- DepositRequestType = 0x00
+ DepositRequestType = 0x00
+ WithdrawalRequestType = 0x01
+ ConsolidationRequestType = 0x02
)
// Request is an EIP-7685 request object. It represents execution layer
@@ -149,6 +151,10 @@ func (r *Request) decode(b []byte) (RequestData, error) {
switch b[0] {
case DepositRequestType:
inner = new(Deposit)
+ case WithdrawalRequestType:
+ inner = new(WithdrawalRequest)
+ case ConsolidationRequestType:
+ inner = new(ConsolidationRequest)
default:
return nil, ErrRequestTypeNotSupported
}
diff --git a/core/types/transaction.go b/core/types/transaction.go
index 4ac9187bdbf..b1ea9332d89 100644
--- a/core/types/transaction.go
+++ b/core/types/transaction.go
@@ -49,6 +49,7 @@ const (
AccessListTxType = 0x01
DynamicFeeTxType = 0x02
BlobTxType = 0x03
+ SetCodeTxType = 0x04
)
// Transaction is an Ethereum transaction.
@@ -206,6 +207,8 @@ func (tx *Transaction) decodeTyped(b []byte) (TxData, error) {
inner = new(DynamicFeeTx)
case BlobTxType:
inner = new(BlobTx)
+ case SetCodeTxType:
+ inner = new(SetCodeTx)
default:
return nil, ErrTxTypeNotSupported
}
@@ -466,6 +469,15 @@ func (tx *Transaction) WithBlobTxSidecar(sideCar *BlobTxSidecar) *Transaction {
return cpy
}
+// AuthList returns the authorizations list of the transaction.
+func (tx *Transaction) AuthList() AuthorizationList {
+ setcodetx, ok := tx.inner.(*SetCodeTx)
+ if !ok {
+ return nil
+ }
+ return setcodetx.AuthList
+}
+
// SetTime sets the decoding time of a transaction. This is used by tests to set
// arbitrary times and by persistent transaction pools when loading old txs from
// disk.
diff --git a/core/types/transaction_marshalling.go b/core/types/transaction_marshalling.go
index 4d5b2bcdd4c..7493b72712f 100644
--- a/core/types/transaction_marshalling.go
+++ b/core/types/transaction_marshalling.go
@@ -31,22 +31,23 @@ import (
type txJSON struct {
Type hexutil.Uint64 `json:"type"`
- ChainID *hexutil.Big `json:"chainId,omitempty"`
- Nonce *hexutil.Uint64 `json:"nonce"`
- To *common.Address `json:"to"`
- Gas *hexutil.Uint64 `json:"gas"`
- GasPrice *hexutil.Big `json:"gasPrice"`
- MaxPriorityFeePerGas *hexutil.Big `json:"maxPriorityFeePerGas"`
- MaxFeePerGas *hexutil.Big `json:"maxFeePerGas"`
- MaxFeePerBlobGas *hexutil.Big `json:"maxFeePerBlobGas,omitempty"`
- Value *hexutil.Big `json:"value"`
- Input *hexutil.Bytes `json:"input"`
- AccessList *AccessList `json:"accessList,omitempty"`
- 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"`
+ ChainID *hexutil.Big `json:"chainId,omitempty"`
+ Nonce *hexutil.Uint64 `json:"nonce"`
+ To *common.Address `json:"to"`
+ Gas *hexutil.Uint64 `json:"gas"`
+ GasPrice *hexutil.Big `json:"gasPrice"`
+ MaxPriorityFeePerGas *hexutil.Big `json:"maxPriorityFeePerGas"`
+ MaxFeePerGas *hexutil.Big `json:"maxFeePerGas"`
+ MaxFeePerBlobGas *hexutil.Big `json:"maxFeePerBlobGas,omitempty"`
+ Value *hexutil.Big `json:"value"`
+ Input *hexutil.Bytes `json:"input"`
+ AccessList *AccessList `json:"accessList,omitempty"`
+ BlobVersionedHashes []common.Hash `json:"blobVersionedHashes,omitempty"`
+ AuthorizationList *AuthorizationList `json:"authorizationList,omitempty"`
+ V *hexutil.Big `json:"v"`
+ R *hexutil.Big `json:"r"`
+ S *hexutil.Big `json:"s"`
+ YParity *hexutil.Uint64 `json:"yParity,omitempty"`
// Blob transaction sidecar encoding:
Blobs []kzg4844.Blob `json:"blobs,omitempty"`
@@ -153,6 +154,23 @@ func (tx *Transaction) MarshalJSON() ([]byte, error) {
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.Input = (*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)
+
}
return json.Marshal(&enc)
}
@@ -409,6 +427,81 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error {
}
}
+ case SetCodeTxType:
+ var itx SetCodeTx
+ inner = &itx
+ if dec.ChainID == nil {
+ return errors.New("missing required field 'chainId' in transaction")
+ }
+ itx.ChainID = uint256.MustFromBig((*big.Int)(dec.ChainID))
+ 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.Input == nil {
+ return errors.New("missing required field 'input' in transaction")
+ }
+ itx.Data = *dec.Input
+ 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
+ var overflow bool
+ 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
+ }
+ }
+
default:
return ErrTxTypeNotSupported
}
diff --git a/core/types/transaction_signing.go b/core/types/transaction_signing.go
index dd25f081f79..59a03e32946 100644
--- a/core/types/transaction_signing.go
+++ b/core/types/transaction_signing.go
@@ -40,6 +40,8 @@ type sigCache struct {
func MakeSigner(config *params.ChainConfig, blockNumber *big.Int, blockTime uint64) Signer {
var signer Signer
switch {
+ case config.IsPrague(blockNumber, blockTime):
+ signer = NewPragueSigner(config.ChainID)
case config.IsCancun(blockNumber, blockTime):
signer = NewCancunSigner(config.ChainID)
case config.IsLondon(blockNumber):
@@ -65,6 +67,9 @@ func MakeSigner(config *params.ChainConfig, blockNumber *big.Int, blockTime uint
// have the current block number available, use MakeSigner instead.
func LatestSigner(config *params.ChainConfig) Signer {
if config.ChainID != nil {
+ if config.PragueTime != nil {
+ return NewPragueSigner(config.ChainID)
+ }
if config.CancunTime != nil {
return NewCancunSigner(config.ChainID)
}
@@ -92,7 +97,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.
@@ -168,6 +173,76 @@ 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 {
+ return pragueSigner{cancunSigner{londonSigner{eip2930Signer{NewEIP155Signer(chainId)}}}}
+}
+
+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.ToBig().Cmp(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.AuthList(),
+ })
+}
+
type cancunSigner struct{ londonSigner }
// NewCancunSigner returns a signer that accepts
diff --git a/core/types/tx_setcode.go b/core/types/tx_setcode.go
new file mode 100644
index 00000000000..e0bf1756ab4
--- /dev/null
+++ b/core/types/tx_setcode.go
@@ -0,0 +1,256 @@
+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
+ }
+ var addr common.Address
+ copy(addr[:], b[len(DelegationPrefix):])
+ return addr, 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 AuthorizationList
+
+ // Signature values
+ V *uint256.Int `json:"v" gencodec:"required"`
+ R *uint256.Int `json:"r" gencodec:"required"`
+ S *uint256.Int `json:"s" gencodec:"required"`
+}
+
+//go:generate go run github.com/fjl/gencodec -type Authorization -field-override authorizationMarshaling -out gen_authorization.go
+
+// Authorization is an authorization from an account to deploy code at it's
+// address.
+type Authorization struct {
+ ChainID *big.Int `json:"chainId" gencodec:"required"`
+ Address common.Address `json:"address" gencodec:"required"`
+ Nonce uint64 `json:"nonce" gencodec:"required"`
+ V *big.Int `json:"v" gencodec:"required"`
+ R *big.Int `json:"r" gencodec:"required"`
+ S *big.Int `json:"s" gencodec:"required"`
+}
+
+// field type overrides for gencodec
+type authorizationMarshaling struct {
+ ChainID *hexutil.Big
+ Nonce hexutil.Uint64
+ V *hexutil.Big
+ R *hexutil.Big
+ S *hexutil.Big
+}
+
+func SignAuth(auth *Authorization, prv *ecdsa.PrivateKey) (*Authorization, error) {
+ h := prefixedRlpHash(
+ 0x05,
+ []interface{}{
+ auth.ChainID,
+ auth.Address,
+ auth.Nonce,
+ })
+
+ sig, err := crypto.Sign(h[:], prv)
+ if err != nil {
+ return nil, err
+ }
+ return auth.WithSignature(sig), nil
+}
+
+func (a *Authorization) WithSignature(sig []byte) *Authorization {
+ r, s, _ := decodeSignature(sig)
+ v := big.NewInt(int64(sig[64]))
+ cpy := Authorization{
+ ChainID: a.ChainID,
+ Address: a.Address,
+ Nonce: a.Nonce,
+ V: v,
+ R: r,
+ S: s,
+ }
+ return &cpy
+}
+
+type AuthorizationList []*Authorization
+
+func (a Authorization) Authority() (common.Address, error) {
+ sighash := prefixedRlpHash(
+ 0x05,
+ []interface{}{
+ a.ChainID,
+ a.Address,
+ a.Nonce,
+ })
+
+ v := byte(a.V.Uint64())
+ if !crypto.ValidateSignatureValues(v, a.R, a.S, true) {
+ return common.Address{}, ErrInvalidSig
+ }
+ // encode the signature in uncompressed format
+ r, s := a.R.Bytes(), a.S.Bytes()
+ sig := make([]byte, crypto.SignatureLength)
+ copy(sig[32-len(r):32], r)
+ copy(sig[64-len(s):64], s)
+ sig[64] = 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
+}
+
+type SetCodeDelegation struct {
+ From common.Address
+ Nonce uint64
+ Target common.Address
+}
+
+/*
+func (a AuthorizationList) WithAddress() []AuthorizationTuple {
+ var (
+ list = make([]AuthorizationTuple, len(a))
+ sha = hasherPool.Get().(crypto.KeccakState)
+ h = common.Hash{}
+ )
+ defer hasherPool.Put(sha)
+ for i, auth := range a {
+ sha.Reset()
+ sha.Write([]byte{0x05})
+ sha.Write(auth.Code)
+ sha.Read(h[:])
+ addr, err := recoverPlain(h, auth.R, auth.S, auth.V, false)
+ if err != nil {
+ continue
+ }
+ list[i].Address = addr
+ copy(list[i].Code, auth.Code)
+ }
+ return list
+}
+*/
+
+// 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(AuthorizationList, 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) 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.SetFromBig(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 00000000000..3016ecb45a6
--- /dev/null
+++ b/core/types/tx_setcode_test.go
@@ -0,0 +1,15 @@
+package types
+
+import (
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+)
+
+func TestParseDelegation(t *testing.T) {
+ addr := common.Address{0x42}
+ d := append(DelegationPrefix, addr.Bytes()...)
+ if got, ok := ParseDelegation(d); !ok || addr != got {
+ t.Fatalf("failed to parse, got %s %v", got.Hex(), ok)
+ }
+}
diff --git a/core/types/withdrawal_request.go b/core/types/withdrawal_request.go
new file mode 100644
index 00000000000..8a451f3f5f3
--- /dev/null
+++ b/core/types/withdrawal_request.go
@@ -0,0 +1,80 @@
+// 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"
+ "encoding/binary"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/ethereum/go-ethereum/rlp"
+)
+
+//go:generate go run github.com/fjl/gencodec -type WithdrawalRequest -field-override withdrawalRequestMarshaling -out gen_withdrawal_request_json.go
+
+// WithdrawalRequest represents an EIP-7002 withdrawal request from source for
+// the validator associated with the public key for amount.
+type WithdrawalRequest struct {
+ Source common.Address `json:"sourceAddress"`
+ PublicKey [48]byte `json:"validatorPubkey"`
+ Amount uint64 `json:"amount"`
+}
+
+// field type overrides for gencodec
+type withdrawalRequestMarshaling struct {
+ PublicKey hexutil.Bytes
+ Amount hexutil.Uint64
+}
+
+func (w *WithdrawalRequest) Bytes() []byte {
+ out := make([]byte, 76)
+ copy(out, w.Source.Bytes())
+ copy(out[20:], w.PublicKey[:])
+ binary.LittleEndian.PutUint64(out, w.Amount)
+ return out
+}
+
+// WithdrawalRequests implements DerivableList for withdrawal requests.
+type WithdrawalRequests []*WithdrawalRequest
+
+// Len returns the length of s.
+func (s WithdrawalRequests) Len() int { return len(s) }
+
+// EncodeIndex encodes the i'th withdrawal request to w.
+func (s WithdrawalRequests) EncodeIndex(i int, w *bytes.Buffer) {
+ rlp.Encode(w, s[i])
+}
+
+// Requests creates a deep copy of each deposit and returns a slice of the
+// withdrwawal requests as Request objects.
+func (s WithdrawalRequests) Requests() (reqs Requests) {
+ for _, d := range s {
+ reqs = append(reqs, NewRequest(d))
+ }
+ return
+}
+
+func (w *WithdrawalRequest) requestType() byte { return WithdrawalRequestType }
+func (w *WithdrawalRequest) encode(b *bytes.Buffer) error { return rlp.Encode(b, w) }
+func (w *WithdrawalRequest) decode(input []byte) error { return rlp.DecodeBytes(input, w) }
+func (w *WithdrawalRequest) copy() RequestData {
+ return &WithdrawalRequest{
+ Source: w.Source,
+ PublicKey: w.PublicKey,
+ Amount: w.Amount,
+ }
+}
diff --git a/core/vm/eips.go b/core/vm/eips.go
index edd6ec8d0a2..7eef4c59a3b 100644
--- a/core/vm/eips.go
+++ b/core/vm/eips.go
@@ -40,6 +40,7 @@ var activators = map[int]func(*JumpTable){
1344: enable1344,
1153: enable1153,
4762: enable4762,
+ 7702: enable7702,
}
// EnableEIP enables the given EIP on the config.
@@ -533,3 +534,26 @@ func enable4762(jt *JumpTable) {
}
}
}
+
+func enable7702(jt *JumpTable) {
+ jt[EXTCODECOPY].constantGas = params.WarmStorageReadCostEIP2929
+ jt[EXTCODECOPY].dynamicGas = gasExtCodeCopyEIP7702
+
+ jt[EXTCODESIZE].constantGas = params.WarmStorageReadCostEIP2929
+ jt[EXTCODESIZE].dynamicGas = gasEip7702CodeCheck
+
+ jt[EXTCODEHASH].constantGas = params.WarmStorageReadCostEIP2929
+ jt[EXTCODEHASH].dynamicGas = gasEip7702CodeCheck
+
+ jt[CALL].constantGas = params.WarmStorageReadCostEIP2929
+ jt[CALL].dynamicGas = gasCallEIP7702
+
+ jt[CALLCODE].constantGas = params.WarmStorageReadCostEIP2929
+ jt[CALLCODE].dynamicGas = gasCallCodeEIP7702
+
+ jt[STATICCALL].constantGas = params.WarmStorageReadCostEIP2929
+ jt[STATICCALL].dynamicGas = gasStaticCallEIP7702
+
+ jt[DELEGATECALL].constantGas = params.WarmStorageReadCostEIP2929
+ jt[DELEGATECALL].dynamicGas = gasDelegateCallEIP7702
+}
diff --git a/core/vm/evm.go b/core/vm/evm.go
index 7617d843c7f..164a3d91ea2 100644
--- a/core/vm/evm.go
+++ b/core/vm/evm.go
@@ -230,8 +230,9 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
} else {
// Initialise a new contract and set the code that is to be used by the EVM.
// The contract is a scoped environment for this execution context only.
- code := evm.StateDB.GetCode(addr)
+ code := evm.StateDB.ResolveCode(addr)
if witness := evm.StateDB.Witness(); witness != nil {
+ witness.AddCode(evm.StateDB.GetCode(addr))
witness.AddCode(code)
}
if len(code) == 0 {
@@ -241,7 +242,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
// If the account has no code, we can abort here
// The depth-check is already done, and precompiles handled above
contract := NewContract(caller, AccountRef(addrCopy), value, gas)
- contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code)
+ contract.SetCallCode(&addrCopy, evm.StateDB.ResolveCodeHash(addrCopy), code)
ret, err = evm.interpreter.Run(contract, input, false)
gas = contract.Gas
}
@@ -303,8 +304,9 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
contract := NewContract(caller, AccountRef(caller.Address()), value, gas)
if witness := evm.StateDB.Witness(); witness != nil {
witness.AddCode(evm.StateDB.GetCode(addrCopy))
+ witness.AddCode(evm.StateDB.ResolveCode(addrCopy))
}
- contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
+ contract.SetCallCode(&addrCopy, evm.StateDB.ResolveCodeHash(addrCopy), evm.StateDB.ResolveCode(addrCopy))
ret, err = evm.interpreter.Run(contract, input, false)
gas = contract.Gas
}
@@ -353,8 +355,9 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate()
if witness := evm.StateDB.Witness(); witness != nil {
witness.AddCode(evm.StateDB.GetCode(addrCopy))
+ witness.AddCode(evm.StateDB.ResolveCode(addrCopy))
}
- contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
+ contract.SetCallCode(&addrCopy, evm.StateDB.ResolveCodeHash(addrCopy), evm.StateDB.ResolveCode(addrCopy))
ret, err = evm.interpreter.Run(contract, input, false)
gas = contract.Gas
}
@@ -411,8 +414,9 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
contract := NewContract(caller, AccountRef(addrCopy), new(uint256.Int), gas)
if witness := evm.StateDB.Witness(); witness != nil {
witness.AddCode(evm.StateDB.GetCode(addrCopy))
+ witness.AddCode(evm.StateDB.ResolveCode(addrCopy))
}
- contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
+ contract.SetCallCode(&addrCopy, evm.StateDB.ResolveCodeHash(addrCopy), evm.StateDB.ResolveCode(addrCopy))
// When an error was returned by the EVM or when setting the creation code
// above we revert to the snapshot and consume any gas remaining. Additionally
// when we're in Homestead this also counts for code storage gas errors.
@@ -476,7 +480,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
// - the nonce is non-zero
// - the code is non-empty
// - the storage is non-empty
- contractHash := evm.StateDB.GetCodeHash(address)
+ contractHash := evm.StateDB.ResolveCodeHash(address)
storageRoot := evm.StateDB.GetStorageRoot(address)
if evm.StateDB.GetNonce(address) != 0 ||
(contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) || // non-empty code
diff --git a/core/vm/instructions.go b/core/vm/instructions.go
index 35d6393fba0..da312e82607 100644
--- a/core/vm/instructions.go
+++ b/core/vm/instructions.go
@@ -343,8 +343,9 @@ func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)
address := slot.Bytes20()
if witness := interpreter.evm.StateDB.Witness(); witness != nil {
witness.AddCode(interpreter.evm.StateDB.GetCode(address))
+ witness.AddCode(interpreter.evm.StateDB.ResolveCode(address))
}
- slot.SetUint64(uint64(interpreter.evm.StateDB.GetCodeSize(slot.Bytes20())))
+ slot.SetUint64(uint64(len(interpreter.evm.StateDB.ResolveCode(slot.Bytes20()))))
return nil, nil
}
@@ -382,8 +383,9 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)
uint64CodeOffset = math.MaxUint64
}
addr := common.Address(a.Bytes20())
- code := interpreter.evm.StateDB.GetCode(addr)
+ code := interpreter.evm.StateDB.ResolveCode(addr)
if witness := interpreter.evm.StateDB.Witness(); witness != nil {
+ witness.AddCode(interpreter.evm.StateDB.GetCode(addr))
witness.AddCode(code)
}
codeCopy := getData(code, uint64CodeOffset, length.Uint64())
@@ -394,7 +396,7 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)
// opExtCodeHash returns the code hash of a specified account.
// There are several cases when the function is called, while we can relay everything
-// to `state.GetCodeHash` function to ensure the correctness.
+// to `state.ResolveCodeHash` function to ensure the correctness.
//
// 1. Caller tries to get the code hash of a normal contract account, state
// should return the relative code hash and set it as the result.
@@ -408,6 +410,9 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)
// 4. Caller tries to get the code hash of a precompiled account, the result should be
// zero or emptyCodeHash.
//
+// 4. Caller tries to get the code hash of a delegated account, the result should be
+// equal the result of calling extcodehash on the account directly.
+//
// It is worth noting that in order to avoid unnecessary create and clean, all precompile
// accounts on mainnet have been transferred 1 wei, so the return here should be
// emptyCodeHash. If the precompile account is not transferred any amount on a private or
@@ -424,7 +429,7 @@ func opExtCodeHash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)
if interpreter.evm.StateDB.Empty(address) {
slot.Clear()
} else {
- slot.SetBytes(interpreter.evm.StateDB.GetCodeHash(address).Bytes())
+ slot.SetBytes(interpreter.evm.StateDB.ResolveCodeHash(address).Bytes())
}
return nil, nil
}
diff --git a/core/vm/interface.go b/core/vm/interface.go
index 5f426435650..6079ca7847e 100644
--- a/core/vm/interface.go
+++ b/core/vm/interface.go
@@ -45,6 +45,9 @@ type StateDB interface {
SetCode(common.Address, []byte)
GetCodeSize(common.Address) int
+ ResolveCodeHash(common.Address) common.Hash
+ ResolveCode(common.Address) []byte
+
AddRefund(uint64)
SubRefund(uint64)
GetRefund() uint64
diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go
index 6b2950194d7..0b16f9fd496 100644
--- a/core/vm/jump_table.go
+++ b/core/vm/jump_table.go
@@ -94,6 +94,7 @@ func newCancunInstructionSet() JumpTable {
enable1153(&instructionSet) // EIP-1153 "Transient Storage"
enable5656(&instructionSet) // EIP-5656 (MCOPY opcode)
enable6780(&instructionSet) // EIP-6780 SELFDESTRUCT only in same transaction
+ enable7702(&instructionSet)
return validate(instructionSet)
}
diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go
index 289da44be3a..f5ed13f55b2 100644
--- a/core/vm/operations_acl.go
+++ b/core/vm/operations_acl.go
@@ -22,6 +22,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/tracing"
+ "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"
)
@@ -248,3 +249,121 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc {
}
return gasFunc
}
+
+var (
+ gasCallEIP7702 = makeCallVariantGasCallEIP7702(gasCall)
+ gasDelegateCallEIP7702 = makeCallVariantGasCallEIP7702(gasDelegateCall)
+ gasStaticCallEIP7702 = makeCallVariantGasCallEIP7702(gasStaticCall)
+ gasCallCodeEIP7702 = makeCallVariantGasCallEIP7702(gasCallCode)
+ gasSelfdestructEIP7702 = makeSelfdestructGasFn(true)
+)
+
+func makeCallVariantGasCallEIP7702(oldCalculator gasFunc) gasFunc {
+ return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
+ addr := common.Address(stack.Back(1).Bytes20())
+ // Check slot presence in the access list
+ warmAccess := evm.StateDB.AddressInAccessList(addr)
+ // The WarmStorageReadCostEIP2929 (100) is already deducted in the form of a constant cost, so
+ // the cost to charge for cold access, if any, is Cold - Warm
+ coldCost := params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929
+ if !warmAccess {
+ evm.StateDB.AddAddressToAccessList(addr)
+ // Charge the remaining difference here already, to correctly calculate available
+ // gas for call
+ if !contract.UseGas(coldCost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
+ return 0, ErrOutOfGas
+ }
+ }
+
+ // Check if code is a delegation and if so, charge for resolution.
+ if addr, ok := types.ParseDelegation(evm.StateDB.GetCode(addr)); ok {
+ var cost uint64
+ if evm.StateDB.AddressInAccessList(addr) {
+ cost += params.WarmStorageReadCostEIP2929
+ } else {
+ evm.StateDB.AddAddressToAccessList(addr)
+ cost += params.ColdAccountAccessCostEIP2929
+ }
+ if !contract.UseGas(cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
+ return 0, ErrOutOfGas
+ }
+ coldCost += cost
+ }
+ // Now call the old calculator, which takes into account
+ // - create new account
+ // - transfer value
+ // - memory expansion
+ // - 63/64ths rule
+ gas, err := oldCalculator(evm, contract, stack, mem, memorySize)
+ if warmAccess || err != nil {
+ return gas, err
+ }
+ // In case of a cold access, we temporarily add the cold charge back, and also
+ // add it to the returned gas. By adding it to the return, it will be charged
+ // outside of this function, as part of the dynamic gas, and that will make it
+ // also become correctly reported to tracers.
+ contract.Gas += coldCost
+
+ var overflow bool
+ if gas, overflow = math.SafeAdd(gas, coldCost); overflow {
+ return 0, ErrGasUintOverflow
+ }
+ return gas, nil
+ }
+}
+
+func gasEip7702CodeCheck(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
+ addr := common.Address(stack.peek().Bytes20())
+ cost := uint64(0)
+ // fmt.Println("checking", addr, evm.StateDB.AddressInAccessList(addr))
+ // Check slot presence in the access list
+ if !evm.StateDB.AddressInAccessList(addr) {
+ // If the caller cannot afford the cost, this change will be rolled back
+ evm.StateDB.AddAddressToAccessList(addr)
+ // The warm storage read cost is already charged as constantGas
+ cost = params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929
+ }
+ // Check if code is a delegation and if so, charge for resolution
+ if addr, ok := types.ParseDelegation(evm.StateDB.GetCode(addr)); ok {
+ if evm.StateDB.AddressInAccessList(addr) {
+ cost += params.WarmStorageReadCostEIP2929
+ } else {
+ evm.StateDB.AddAddressToAccessList(addr)
+ cost += params.ColdAccountAccessCostEIP2929
+ }
+ }
+ return cost, nil
+}
+
+func gasExtCodeCopyEIP7702(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
+ // memory expansion first (dynamic part of pre-2929 implementation)
+ gas, err := gasExtCodeCopy(evm, contract, stack, mem, memorySize)
+ if err != nil {
+ return 0, err
+ }
+ addr := common.Address(stack.peek().Bytes20())
+ // Check slot presence in the access list
+ if !evm.StateDB.AddressInAccessList(addr) {
+ evm.StateDB.AddAddressToAccessList(addr)
+ var overflow bool
+ // We charge (cold-warm), since 'warm' is already charged as constantGas
+ if gas, overflow = math.SafeAdd(gas, params.ColdAccountAccessCostEIP2929-params.WarmStorageReadCostEIP2929); overflow {
+ return 0, ErrGasUintOverflow
+ }
+ }
+ // Check if code is a delegation and if so, charge for resolution
+ if addr, ok := types.ParseDelegation(evm.StateDB.GetCode(addr)); ok {
+ var overflow bool
+ if evm.StateDB.AddressInAccessList(addr) {
+ if gas, overflow = math.SafeAdd(gas, params.WarmStorageReadCostEIP2929); overflow {
+ return 0, ErrGasUintOverflow
+ }
+ } else {
+ evm.StateDB.AddAddressToAccessList(addr)
+ if gas, overflow = math.SafeAdd(gas, params.ColdAccountAccessCostEIP2929); overflow {
+ return 0, ErrGasUintOverflow
+ }
+ }
+ }
+ return gas, nil
+}
diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go
index 7ea1c9ee076..9fee0fed2c2 100644
--- a/eth/catalyst/api.go
+++ b/eth/catalyst/api.go
@@ -449,7 +449,7 @@ func (api *ConsensusAPI) GetPayloadV3(payloadID engine.PayloadID) (*engine.Execu
// GetPayloadV4 returns a cached payload by id.
func (api *ConsensusAPI) GetPayloadV4(payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) {
- if !payloadID.Is(engine.PayloadV4) {
+ if !payloadID.Is(engine.PayloadV3, engine.PayloadV4) {
return nil, engine.UnsupportedFork
}
return api.getPayload(payloadID, false)
@@ -872,12 +872,14 @@ func (api *ConsensusAPI) GetPayloadBodiesByHashV1(hashes []common.Hash) []*engin
for i, hash := range hashes {
block := api.eth.BlockChain().GetBlockByHash(hash)
body := getBody(block)
+ // Nil out the V2 values, clients should know to not request V1 objects
+ // after Prague.
if body != nil {
- // Nil out the V2 values, clients should know to not request V1 objects
- // after Prague.
body.Deposits = nil
+ bodies[i].WithdrawalRequests = nil
+ bodies[i].ConsolidationRequests = nil
+ bodies[i] = body
}
- bodies[i] = body
}
return bodies
}
@@ -905,6 +907,8 @@ func (api *ConsensusAPI) GetPayloadBodiesByRangeV1(start, count hexutil.Uint64)
for i := range bodies {
if bodies[i] != nil {
bodies[i].Deposits = nil
+ bodies[i].WithdrawalRequests = nil
+ bodies[i].ConsolidationRequests = nil
}
}
return bodies, nil
@@ -943,10 +947,12 @@ func getBody(block *types.Block) *engine.ExecutionPayloadBody {
}
var (
- body = block.Body()
- txs = make([]hexutil.Bytes, len(body.Transactions))
- withdrawals = body.Withdrawals
- depositRequests types.Deposits
+ body = block.Body()
+ txs = make([]hexutil.Bytes, len(body.Transactions))
+ withdrawals = body.Withdrawals
+ depositRequests types.Deposits
+ withdrawalRequests types.WithdrawalRequests
+ consolidationRequests types.ConsolidationRequests
)
for j, tx := range body.Transactions {
@@ -962,16 +968,26 @@ func getBody(block *types.Block) *engine.ExecutionPayloadBody {
// TODO: this isn't future proof because we can't determine if a request
// type has activated yet or if there are just no requests of that type from
// only the block.
+ depositRequests = make(types.Deposits, 0)
+ withdrawalRequests = make(types.WithdrawalRequests, 0)
+ consolidationRequests = make(types.ConsolidationRequests, 0)
+
for _, req := range block.Requests() {
- if d, ok := req.Inner().(*types.Deposit); ok {
- depositRequests = append(depositRequests, d)
+ switch v := req.Inner().(type) {
+ case *types.Deposit:
+ depositRequests = append(depositRequests, v)
+ case *types.WithdrawalRequest:
+ withdrawalRequests = append(withdrawalRequests, v)
+ case *types.ConsolidationRequest:
+ consolidationRequests = append(consolidationRequests, v)
}
}
}
-
return &engine.ExecutionPayloadBody{
- TransactionData: txs,
- Withdrawals: withdrawals,
- Deposits: depositRequests,
+ TransactionData: txs,
+ Withdrawals: withdrawals,
+ Deposits: depositRequests,
+ WithdrawalRequests: withdrawalRequests,
+ ConsolidationRequests: consolidationRequests,
}
}
diff --git a/eth/catalyst/api_test.go b/eth/catalyst/api_test.go
index 0a58e1eaee0..c7b5a17f535 100644
--- a/eth/catalyst/api_test.go
+++ b/eth/catalyst/api_test.go
@@ -80,6 +80,8 @@ func generateMergeChain(n int, merged bool) (*core.Genesis, []*types.Block) {
Nonce: 0,
Balance: big.NewInt(0),
},
+ params.WithdrawalRequestsAddress: {Code: common.FromHex("3373fffffffffffffffffffffffffffffffffffffffe146090573615156028575f545f5260205ff35b366038141561012e5760115f54600182026001905f5b5f82111560595781019083028483029004916001019190603e565b90939004341061012e57600154600101600155600354806003026004013381556001015f3581556001016020359055600101600355005b6003546002548082038060101160a4575060105b5f5b81811460dd5780604c02838201600302600401805490600101805490600101549160601b83528260140152906034015260010160a6565b910180921460ed579060025560f8565b90505f6002555f6003555b5f548061049d141561010757505f5b60015460028282011161011c5750505f610122565b01600290035b5f555f600155604c025ff35b5f5ffd")},
+ params.ConsolidationRequestsAddress: {Code: common.FromHex("3373fffffffffffffffffffffffffffffffffffffffe146098573615156028575f545f5260205ff35b36606014156101445760115f54600182026001905f5b5f82111560595781019083028483029004916001019190603e565b90939004341061014457600154600101600155600354806004026004013381556001015f35815560010160203581556001016040359055600101600355005b6003546002548082038060011160ac575060015b5f5b81811460f15780607402838201600402600401805490600101805490600101805490600101549260601b84529083601401528260340152906054015260010160ae565b9101809214610103579060025561010e565b90505f6002555f6003555b5f548061049d141561011d57505f5b6001546001828201116101325750505f610138565b01600190035b5f555f6001556074025ff35b5f5ffd")},
},
ExtraData: []byte("test genesis"),
Timestamp: 9000,
@@ -1313,15 +1315,23 @@ func setupBodies(t *testing.T) (*node.Node, *eth.Ethereum, []*types.Block) {
// Each block, this callback will include two txs that generate body values like logs and requests.
callback := func(parent *types.Header) {
+ if parent.Number.Cmp(big.NewInt(10)) == 0 {
+ // Make a block with empty requests to ensure nil / non-nil distinction is
+ // being maintained.
+ return
+ }
var (
statedb, _ = ethservice.BlockChain().StateAt(parent.Root)
// Create tx to trigger log generator.
tx1, _ = types.SignTx(types.NewContractCreation(statedb.GetNonce(testAddr), new(big.Int), 1000000, big.NewInt(2*params.InitialBaseFee), logCode), types.LatestSigner(ethservice.BlockChain().Config()), testKey)
// Create tx to trigger deposit generator.
tx2, _ = types.SignTx(types.NewTransaction(statedb.GetNonce(testAddr)+1, ethservice.APIBackend.ChainConfig().DepositContractAddress, new(big.Int), 500000, big.NewInt(2*params.InitialBaseFee), nil), types.LatestSigner(ethservice.BlockChain().Config()), testKey)
+ // Create tx to trigger withdrawal request.
+ tx3, _ = types.SignTx(types.NewTransaction(statedb.GetNonce(testAddr)+2, params.WithdrawalRequestsAddress, big.NewInt(42), 500000, big.NewInt(2*params.InitialBaseFee), make([]byte, 56)), types.LatestSigner(ethservice.BlockChain().Config()), testKey)
+ // Create tx to trigger consolidation request.
+ tx4, _ = types.SignTx(types.NewTransaction(statedb.GetNonce(testAddr)+3, params.ConsolidationRequestsAddress, big.NewInt(42), 500000, big.NewInt(2*params.InitialBaseFee), make([]byte, 96)), types.LatestSigner(ethservice.BlockChain().Config()), testKey)
)
- ethservice.TxPool().Add([]*types.Transaction{tx1}, false, false)
- ethservice.TxPool().Add([]*types.Transaction{tx2}, false, false)
+ ethservice.TxPool().Add([]*types.Transaction{tx1, tx2, tx3, tx4}, false, false)
}
// Make some withdrawals to include.
@@ -1569,18 +1579,31 @@ func equalBody(a *types.Body, b *engine.ExecutionPayloadBody) bool {
return false
}
- var deposits types.Deposits
+ var (
+ dxs types.Deposits
+ wxs types.WithdrawalRequests
+ cxs types.ConsolidationRequests
+ )
if a.Requests != nil {
// If requests is non-nil, it means deposits are available in block and we
// should return an empty slice instead of nil if there are no deposits.
- deposits = make(types.Deposits, 0)
- }
- for _, r := range a.Requests {
- if d, ok := r.Inner().(*types.Deposit); ok {
- deposits = append(deposits, d)
+ dxs = make(types.Deposits, 0)
+ wxs = make(types.WithdrawalRequests, 0)
+ cxs = make(types.ConsolidationRequests, 0)
+ }
+ for _, req := range a.Requests {
+ switch v := req.Inner().(type) {
+ case *types.Deposit:
+ dxs = append(dxs, v)
+ case *types.WithdrawalRequest:
+ wxs = append(wxs, v)
+ case *types.ConsolidationRequest:
+ cxs = append(cxs, v)
}
}
- return reflect.DeepEqual(deposits, b.Deposits)
+ return reflect.DeepEqual(dxs, b.Deposits) &&
+ reflect.DeepEqual(wxs, b.WithdrawalRequests) &&
+ reflect.DeepEqual(cxs, b.ConsolidationRequests)
}
func TestBlockToPayloadWithBlobs(t *testing.T) {
diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go
index aeb3e8adc28..eda37b483ba 100644
--- a/internal/ethapi/api.go
+++ b/internal/ethapi/api.go
@@ -1332,28 +1332,29 @@ func (api *BlockChainAPI) rpcMarshalBlock(ctx context.Context, b *types.Block, i
// RPCTransaction represents a transaction that will serialize to the RPC representation of a transaction
type RPCTransaction struct {
- BlockHash *common.Hash `json:"blockHash"`
- BlockNumber *hexutil.Big `json:"blockNumber"`
- From common.Address `json:"from"`
- Gas hexutil.Uint64 `json:"gas"`
- GasPrice *hexutil.Big `json:"gasPrice"`
- GasFeeCap *hexutil.Big `json:"maxFeePerGas,omitempty"`
- GasTipCap *hexutil.Big `json:"maxPriorityFeePerGas,omitempty"`
- MaxFeePerBlobGas *hexutil.Big `json:"maxFeePerBlobGas,omitempty"`
- Hash common.Hash `json:"hash"`
- Input hexutil.Bytes `json:"input"`
- Nonce hexutil.Uint64 `json:"nonce"`
- To *common.Address `json:"to"`
- TransactionIndex *hexutil.Uint64 `json:"transactionIndex"`
- Value *hexutil.Big `json:"value"`
- Type hexutil.Uint64 `json:"type"`
- Accesses *types.AccessList `json:"accessList,omitempty"`
- ChainID *hexutil.Big `json:"chainId,omitempty"`
- 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"`
+ BlockHash *common.Hash `json:"blockHash"`
+ BlockNumber *hexutil.Big `json:"blockNumber"`
+ From common.Address `json:"from"`
+ Gas hexutil.Uint64 `json:"gas"`
+ GasPrice *hexutil.Big `json:"gasPrice"`
+ GasFeeCap *hexutil.Big `json:"maxFeePerGas,omitempty"`
+ GasTipCap *hexutil.Big `json:"maxPriorityFeePerGas,omitempty"`
+ MaxFeePerBlobGas *hexutil.Big `json:"maxFeePerBlobGas,omitempty"`
+ Hash common.Hash `json:"hash"`
+ Input hexutil.Bytes `json:"input"`
+ Nonce hexutil.Uint64 `json:"nonce"`
+ To *common.Address `json:"to"`
+ TransactionIndex *hexutil.Uint64 `json:"transactionIndex"`
+ Value *hexutil.Big `json:"value"`
+ Type hexutil.Uint64 `json:"type"`
+ Accesses *types.AccessList `json:"accessList,omitempty"`
+ ChainID *hexutil.Big `json:"chainId,omitempty"`
+ BlobVersionedHashes []common.Hash `json:"blobVersionedHashes,omitempty"`
+ AuthorizationList types.AuthorizationList `json:"authorizationList,omitempty"`
+ V *hexutil.Big `json:"v"`
+ R *hexutil.Big `json:"r"`
+ S *hexutil.Big `json:"s"`
+ YParity *hexutil.Uint64 `json:"yParity,omitempty"`
}
// newRPCTransaction returns a transaction that will serialize to the RPC
@@ -1428,6 +1429,24 @@ func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber
}
result.MaxFeePerBlobGas = (*hexutil.Big)(tx.BlobGasFeeCap())
result.BlobVersionedHashes = tx.BlobHashes()
+
+ case types.SetCodeTxType:
+ al := tx.AccessList()
+ yparity := hexutil.Uint64(v.Sign())
+ result.Accesses = &al
+ result.ChainID = (*hexutil.Big)(tx.ChainId())
+ result.YParity = &yparity
+ result.GasFeeCap = (*hexutil.Big)(tx.GasFeeCap())
+ result.GasTipCap = (*hexutil.Big)(tx.GasTipCap())
+ // if the transaction has been mined, compute the effective gas price
+ if baseFee != nil && blockHash != (common.Hash{}) {
+ result.GasPrice = (*hexutil.Big)(effectiveGasPrice(tx, baseFee))
+ } else {
+ result.GasPrice = (*hexutil.Big)(tx.GasFeeCap())
+ }
+ result.MaxFeePerBlobGas = (*hexutil.Big)(tx.BlobGasFeeCap())
+ result.BlobVersionedHashes = tx.BlobHashes()
+ result.AuthorizationList = tx.AuthList()
}
return result
}
diff --git a/miner/worker.go b/miner/worker.go
index 1f49118c474..51e69898bec 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -113,10 +113,21 @@ func (miner *Miner) generateWork(params *generateParams) *newPayloadResult {
}
// Read requests if Prague is enabled.
if miner.chainConfig.IsPrague(work.header.Number, work.header.Time) {
- requests, err := core.ParseDepositLogs(allLogs, miner.chainConfig)
+ // Parse Deposits
+ requests := make([]*types.Request, 0)
+ reqs, err := core.ParseDepositLogs(allLogs, miner.chainConfig)
if err != nil {
return &newPayloadResult{err: err}
}
+ requests = append(requests, reqs...)
+ // Process WithdrawalRequests
+ context := core.NewEVMBlockContext(work.header, miner.chain, nil)
+ vmenv := vm.NewEVM(context, vm.TxContext{}, work.state, miner.chainConfig, vm.Config{})
+ wxs := core.ProcessDequeueWithdrawalRequests(vmenv, work.state)
+ requests = append(requests, wxs...)
+ // Process ConsolidationRequests
+ cxs := core.ProcessDequeueConsolidationRequests(vmenv, work.state)
+ requests = append(requests, cxs...)
body.Requests = requests
}
block, err := miner.engine.FinalizeAndAssemble(miner.chain, work.header, work.state, &body, work.receipts)
diff --git a/params/protocol_params.go b/params/protocol_params.go
index 638f58a3399..273bb3161c4 100644
--- a/params/protocol_params.go
+++ b/params/protocol_params.go
@@ -94,6 +94,7 @@ const (
TxDataNonZeroGasEIP2028 uint64 = 16 // Per byte of non zero data attached to a transaction after EIP 2028 (part in Istanbul)
TxAccessListAddressGas uint64 = 2400 // Per address specified in EIP 2930 access list
TxAccessListStorageKeyGas uint64 = 1900 // Per storage key specified in EIP 2930 access list
+ TxAuthTupleGas uint64 = 2500 // Per auth tuple code specified in EIP-7702
// These have been changed during the course of the chain
CallGasFrontier uint64 = 40 // Once per CALL operation & message call transaction.
@@ -189,10 +190,12 @@ var (
// BeaconRootsAddress is the address where historical beacon roots are stored as per EIP-4788
BeaconRootsAddress = common.HexToAddress("0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02")
-
// BeaconRootsCode is the code where historical beacon roots are stored as per EIP-4788
BeaconRootsCode = common.FromHex("3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500")
-
+ // WithdrawalRequests is the address where the EIP-7002 withdrawal requests queue is maintained.
+ WithdrawalRequestsAddress = common.HexToAddress("0x00A3ca265EBcb825B45F985A16CEFB49958cE017")
+ // ConsolidationRequests is the address where the EIP-7251 consolidation requests queue is maintained.
+ ConsolidationRequestsAddress = common.HexToAddress("0x00b42dbF2194e931E80326D950320f7d9Dbeac02")
// SystemAddress is where the system-transaction is sent from as per EIP-4788
SystemAddress = common.HexToAddress("0xfffffffffffffffffffffffffffffffffffffffe")
// HistoryStorageAddress is where the historical block hashes are stored.
diff --git a/tests/gen_stauthorization.go b/tests/gen_stauthorization.go
new file mode 100644
index 00000000000..6254600b2bb
--- /dev/null
+++ b/tests/gen_stauthorization.go
@@ -0,0 +1,74 @@
+// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
+
+package tests
+
+import (
+ "encoding/json"
+ "errors"
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/math"
+)
+
+var _ = (*stAuthorizationMarshaling)(nil)
+
+// MarshalJSON marshals as JSON.
+func (s stAuthorization) MarshalJSON() ([]byte, error) {
+ type stAuthorization struct {
+ ChainID *math.HexOrDecimal256
+ Address common.Address `json:"address" gencodec:"required"`
+ Nonce math.HexOrDecimal64 `json:"nonce" gencodec:"required"`
+ V *math.HexOrDecimal256 `json:"v" gencodec:"required"`
+ R *math.HexOrDecimal256 `json:"r" gencodec:"required"`
+ S *math.HexOrDecimal256 `json:"s" gencodec:"required"`
+ }
+ var enc stAuthorization
+ enc.ChainID = (*math.HexOrDecimal256)(s.ChainID)
+ enc.Address = s.Address
+ enc.Nonce = math.HexOrDecimal64(s.Nonce)
+ enc.V = (*math.HexOrDecimal256)(s.V)
+ enc.R = (*math.HexOrDecimal256)(s.R)
+ enc.S = (*math.HexOrDecimal256)(s.S)
+ return json.Marshal(&enc)
+}
+
+// UnmarshalJSON unmarshals from JSON.
+func (s *stAuthorization) UnmarshalJSON(input []byte) error {
+ type stAuthorization struct {
+ ChainID *math.HexOrDecimal256
+ Address *common.Address `json:"address" gencodec:"required"`
+ Nonce *math.HexOrDecimal64 `json:"nonce" gencodec:"required"`
+ V *math.HexOrDecimal256 `json:"v" gencodec:"required"`
+ R *math.HexOrDecimal256 `json:"r" gencodec:"required"`
+ S *math.HexOrDecimal256 `json:"s" gencodec:"required"`
+ }
+ var dec stAuthorization
+ if err := json.Unmarshal(input, &dec); err != nil {
+ return err
+ }
+ if dec.ChainID != nil {
+ s.ChainID = (*big.Int)(dec.ChainID)
+ }
+ if dec.Address == nil {
+ return errors.New("missing required field 'address' for stAuthorization")
+ }
+ s.Address = *dec.Address
+ if dec.Nonce == nil {
+ return errors.New("missing required field 'nonce' for stAuthorization")
+ }
+ s.Nonce = uint64(*dec.Nonce)
+ if dec.V == nil {
+ return errors.New("missing required field 'v' for stAuthorization")
+ }
+ s.V = (*big.Int)(dec.V)
+ if dec.R == nil {
+ return errors.New("missing required field 'r' for stAuthorization")
+ }
+ s.R = (*big.Int)(dec.R)
+ if dec.S == nil {
+ return errors.New("missing required field 's' for stAuthorization")
+ }
+ s.S = (*big.Int)(dec.S)
+ return nil
+}
diff --git a/tests/gen_sttransaction.go b/tests/gen_sttransaction.go
index 9b5aecbfe6b..b25ce76166a 100644
--- a/tests/gen_sttransaction.go
+++ b/tests/gen_sttransaction.go
@@ -30,6 +30,7 @@ func (s stTransaction) MarshalJSON() ([]byte, error) {
Sender *common.Address `json:"sender"`
BlobVersionedHashes []common.Hash `json:"blobVersionedHashes,omitempty"`
BlobGasFeeCap *math.HexOrDecimal256 `json:"maxFeePerBlobGas,omitempty"`
+ AuthorizationList []*stAuthorization `json:"authorizationList,omitempty"`
}
var enc stTransaction
enc.GasPrice = (*math.HexOrDecimal256)(s.GasPrice)
@@ -50,6 +51,7 @@ func (s stTransaction) MarshalJSON() ([]byte, error) {
enc.Sender = s.Sender
enc.BlobVersionedHashes = s.BlobVersionedHashes
enc.BlobGasFeeCap = (*math.HexOrDecimal256)(s.BlobGasFeeCap)
+ enc.AuthorizationList = s.AuthorizationList
return json.Marshal(&enc)
}
@@ -69,6 +71,7 @@ func (s *stTransaction) UnmarshalJSON(input []byte) error {
Sender *common.Address `json:"sender"`
BlobVersionedHashes []common.Hash `json:"blobVersionedHashes,omitempty"`
BlobGasFeeCap *math.HexOrDecimal256 `json:"maxFeePerBlobGas,omitempty"`
+ AuthorizationList []*stAuthorization `json:"authorizationList,omitempty"`
}
var dec stTransaction
if err := json.Unmarshal(input, &dec); err != nil {
@@ -116,5 +119,8 @@ func (s *stTransaction) UnmarshalJSON(input []byte) error {
if dec.BlobGasFeeCap != nil {
s.BlobGasFeeCap = (*big.Int)(dec.BlobGasFeeCap)
}
+ if dec.AuthorizationList != nil {
+ s.AuthorizationList = dec.AuthorizationList
+ }
return nil
}
diff --git a/tests/state_test.go b/tests/state_test.go
index 76fec97de0e..06e7c805851 100644
--- a/tests/state_test.go
+++ b/tests/state_test.go
@@ -96,6 +96,7 @@ func TestExecutionSpecState(t *testing.T) {
t.Skipf("directory %s does not exist", executionSpecStateTestDir)
}
st := new(testMatcher)
+ st.runonly("eip7702")
st.walk(t, executionSpecStateTestDir, func(t *testing.T, name string, test *StateTest) {
execStateTest(t, st, test)
diff --git a/tests/state_test_util.go b/tests/state_test_util.go
index 2bf0056cbc9..b681986915d 100644
--- a/tests/state_test_util.go
+++ b/tests/state_test_util.go
@@ -123,6 +123,7 @@ type stTransaction struct {
Sender *common.Address `json:"sender"`
BlobVersionedHashes []common.Hash `json:"blobVersionedHashes,omitempty"`
BlobGasFeeCap *big.Int `json:"maxFeePerBlobGas,omitempty"`
+ AuthorizationList []*stAuthorization `json:"authorizationList,omitempty"`
}
type stTransactionMarshaling struct {
@@ -135,6 +136,28 @@ type stTransactionMarshaling struct {
BlobGasFeeCap *math.HexOrDecimal256
}
+//go:generate go run github.com/fjl/gencodec -type stAuthorization -field-override stAuthorizationMarshaling -out gen_stauthorization.go
+
+// Authorization is an authorization from an account to deploy code at it's
+// address.
+type stAuthorization struct {
+ ChainID *big.Int
+ Address common.Address `json:"address" gencodec:"required"`
+ Nonce uint64 `json:"nonce" gencodec:"required"`
+ V *big.Int `json:"v" gencodec:"required"`
+ R *big.Int `json:"r" gencodec:"required"`
+ S *big.Int `json:"s" gencodec:"required"`
+}
+
+// field type overrides for gencodec
+type stAuthorizationMarshaling struct {
+ ChainID *math.HexOrDecimal256
+ Nonce math.HexOrDecimal64
+ V *math.HexOrDecimal256
+ R *math.HexOrDecimal256
+ S *math.HexOrDecimal256
+}
+
// GetChainConfig takes a fork definition and returns a chain config.
// The fork definition can be
// - a plain forkname, e.g. `Byzantium`,
@@ -417,6 +440,24 @@ func (tx *stTransaction) toMessage(ps stPostState, baseFee *big.Int) (*core.Mess
if gasPrice == nil {
return nil, errors.New("no gas price provided")
}
+ var authList types.AuthorizationList
+ if tx.AuthorizationList != nil {
+ authList = make(types.AuthorizationList, 0)
+ for _, auth := range tx.AuthorizationList {
+ chainID := auth.ChainID
+ if chainID == nil {
+ chainID = big.NewInt(0)
+ }
+ authList = append(authList, &types.Authorization{
+ ChainID: chainID,
+ Address: auth.Address,
+ Nonce: auth.Nonce,
+ V: auth.V,
+ R: auth.R,
+ S: auth.S,
+ })
+ }
+ }
msg := &core.Message{
From: from,
@@ -431,6 +472,7 @@ func (tx *stTransaction) toMessage(ps stPostState, baseFee *big.Int) (*core.Mess
AccessList: accessList,
BlobHashes: tx.BlobVersionedHashes,
BlobGasFeeCap: tx.BlobGasFeeCap,
+ AuthList: authList,
}
return msg, nil
}
diff --git a/tests/transaction_test_util.go b/tests/transaction_test_util.go
index d3dbbd5db29..4da27ff9433 100644
--- a/tests/transaction_test_util.go
+++ b/tests/transaction_test_util.go
@@ -59,7 +59,7 @@ func (tt *TransactionTest) Run(config *params.ChainConfig) error {
return nil, nil, err
}
// Intrinsic gas
- requiredGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, isHomestead, isIstanbul, false)
+ requiredGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.AuthList(), tx.To() == nil, isHomestead, isIstanbul, false)
if err != nil {
return nil, nil, err
}