From 3aae627db3e2af184e61a5336de5aaa8aeb34303 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Fri, 21 Jun 2024 17:41:17 +0000 Subject: [PATCH] all: implement eip-7251 consolidation requests Co-authored-by: Mario Vega Co-authored-by: lightclient --- beacon/engine/gen_ed.go | 86 +++++++------- beacon/engine/types.go | 56 +++++---- cmd/evm/internal/t8ntool/execution.go | 76 ++++++------ core/blockchain_test.go | 116 +++++++++++++++---- core/chain_makers.go | 2 + core/state_processor.go | 43 +++++++ core/types/consolidation_request.go | 79 +++++++++++++ core/types/gen_consolidation_request_json.go | 56 +++++++++ core/types/request.go | 7 +- eth/catalyst/api.go | 28 +++-- eth/catalyst/api_test.go | 41 +++++-- miner/worker.go | 3 + params/protocol_params.go | 2 + 13 files changed, 453 insertions(+), 142 deletions(-) create mode 100644 core/types/consolidation_request.go create mode 100644 core/types/gen_consolidation_request_json.go diff --git a/beacon/engine/gen_ed.go b/beacon/engine/gen_ed.go index 51595644b7..ebab7c0cf8 100644 --- a/beacon/engine/gen_ed.go +++ b/beacon/engine/gen_ed.go @@ -17,26 +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"` - WithdrawalRequests types.WithdrawalRequests `json:"withdrawalRequests"` - 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 @@ -63,6 +64,7 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) { 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) } @@ -70,26 +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"` - WithdrawalRequests *types.WithdrawalRequests `json:"withdrawalRequests"` - 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 { @@ -169,6 +172,9 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error { 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 cb735c561d..bb24f223df 100644 --- a/beacon/engine/types.go +++ b/beacon/engine/types.go @@ -60,26 +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"` - WithdrawalRequests types.WithdrawalRequests `json:"withdrawalRequests"` - 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. @@ -252,6 +253,9 @@ func ExecutableDataToBlock(data ExecutableData, versionedHashes []common.Hash, b 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 @@ -335,6 +339,7 @@ func setRequests(requests types.Requests, data *ExecutableData) { // 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 { switch v := r.Inner().(type) { @@ -342,16 +347,19 @@ func setRequests(requests types.Requests, data *ExecutableData) { 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"` - WithdrawalRequests types.WithdrawalRequests `json:"withdrawalRequests"` + 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/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index 68d9a97e26..11a57fb6fd 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -53,22 +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"` - WithdrawalRequests types.WithdrawalRequests `json:"withdrawalRequests,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 { @@ -357,9 +358,10 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, } // Retrieve deposit and withdrawal requests var ( - depositRequests types.Deposits - withdrawalRequests types.WithdrawalRequests - requestsHash *common.Hash + depositRequests types.Deposits + withdrawalRequests types.WithdrawalRequests + consolidationRequests types.ConsolidationRequests + requestsHash *common.Hash ) if chainConfig.IsPrague(vmContext.BlockNumber, vmContext.Time) { // Parse deposit requests from the logs @@ -375,18 +377,25 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, 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)) requestsHash = &h - // Get the deposits from the requests + + // Break out individual request types. depositRequests = make(types.Deposits, 0) withdrawalRequests = make(types.WithdrawalRequests, 0) + consolidationRequests = make(types.ConsolidationRequests, 0) for _, req := range requests { 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) } } } @@ -396,19 +405,20 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, 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, + 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)) diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 92611d5661..ca8ed9e805 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -4316,9 +4316,8 @@ func TestEIP6110(t *testing.T) { } } -// TestEIP7002 verifies that withdrawal requests are processed correctly in the -// pre-deploy and parsed out correctly via the system call. -func TestEIP7002(t *testing.T) { +// TestRequests verifies that Prague requests are processed correctly. +func TestRequests(t *testing.T) { var ( engine = beacon.NewFaker() @@ -4330,8 +4329,9 @@ func TestEIP7002(t *testing.T) { gspec = &Genesis{ Config: &config, Alloc: types.GenesisAlloc{ - addr: {Balance: funds}, - params.WithdrawalRequestsAddress: {Code: common.FromHex("3373fffffffffffffffffffffffffffffffffffffffe146090573615156028575f545f5260205ff35b366038141561012e5760115f54600182026001905f5b5f82111560595781019083028483029004916001019190603e565b90939004341061012e57600154600101600155600354806003026004013381556001015f3581556001016020359055600101600355005b6003546002548082038060101160a4575060105b5f5b81811460dd5780604c02838201600302600401805490600101805490600101549160601b83528260140152906034015260010160a6565b910180921460ed579060025560f8565b90505f6002555f6003555b5f548061049d141561010757505f5b60015460028282011161011c5750505f610122565b01600290035b5f555f600155604c025ff35b5f5ffd")}, + addr: {Balance: funds}, + params.WithdrawalRequestsAddress: {Code: common.FromHex("3373fffffffffffffffffffffffffffffffffffffffe146090573615156028575f545f5260205ff35b366038141561012e5760115f54600182026001905f5b5f82111560595781019083028483029004916001019190603e565b90939004341061012e57600154600101600155600354806003026004013381556001015f3581556001016020359055600101600355005b6003546002548082038060101160a4575060105b5f5b81811460dd5780604c02838201600302600401805490600101805490600101549160601b83528260140152906034015260010160a6565b910180921460ed579060025560f8565b90505f6002555f6003555b5f548061049d141561010757505f5b60015460028282011161011c5750505f610122565b01600290035b5f555f600155604c025ff35b5f5ffd")}, + params.ConsolidationRequestsAddress: {Code: common.FromHex("3373fffffffffffffffffffffffffffffffffffffffe146098573615156028575f545f5260205ff35b36606014156101445760115f54600182026001905f5b5f82111560595781019083028483029004916001019190603e565b90939004341061014457600154600101600155600354806004026004013381556001015f35815560010160203581556001016040359055600101600355005b6003546002548082038060011160ac575060015b5f5b81811460f15780607402838201600402600401805490600101805490600101805490600101549260601b84529083601401528260340152906054015260010160ae565b9101809214610103579060025561010e565b90505f6002555f6003555b5f548061049d141561011d57505f5b6001546001828201116101325750505f610138565b01600190035b5f555f6001556074025ff35b5f5ffd")}, }, } ) @@ -4357,29 +4357,67 @@ func TestEIP7002(t *testing.T) { 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, 1, func(i int, b *BlockGen) { - for i, 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: uint64(i), - To: ¶ms.WithdrawalRequestsAddress, - Value: big.NewInt(1), - Gas: 500000, - GasFeeCap: newGwei(5), - GasTipCap: big.NewInt(2), - AccessList: nil, - Data: data, + _, 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) } - 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) + + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{Tracer: logger.NewMarkdownLogger(&logger.Config{}, os.Stderr).Hooks()}, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -4387,6 +4425,8 @@ func TestEIP7002(t *testing.T) { 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") @@ -4395,7 +4435,7 @@ func TestEIP7002(t *testing.T) { // Verify the withdrawal requests match. got := block.Requests() if len(got) != 2 { - t.Fatalf("wrong number of withdrawal requests: wanted 2, got %d", len(wxs)) + 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) @@ -4412,4 +4452,30 @@ func TestEIP7002(t *testing.T) { 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 9bacc480ac..717d2e24db 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -362,6 +362,8 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse ) 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/state_processor.go b/core/state_processor.go index 64722c4875..8140983f37 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -106,6 +106,8 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg } 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) @@ -315,3 +317,44 @@ func ProcessDequeueWithdrawalRequests(vmenv *vm.EVM, statedb *state.StateDB) typ } 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/types/consolidation_request.go b/core/types/consolidation_request.go new file mode 100644 index 0000000000..4c3e8e49b2 --- /dev/null +++ b/core/types/consolidation_request.go @@ -0,0 +1,79 @@ +// 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" + + "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_consolidation_request_json.go b/core/types/gen_consolidation_request_json.go new file mode 100644 index 0000000000..699abb4a7d --- /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/request.go b/core/types/request.go index c3f6417e49..b8a0ef7afc 100644 --- a/core/types/request.go +++ b/core/types/request.go @@ -32,8 +32,9 @@ var ( // Request types. const ( - DepositRequestType = 0x00 - WithdrawalRequestType = 0x01 + DepositRequestType = 0x00 + WithdrawalRequestType = 0x01 + ConsolidationRequestType = 0x02 ) // Request is an EIP-7685 request object. It represents execution layer @@ -152,6 +153,8 @@ func (r *Request) decode(b []byte) (RequestData, error) { inner = new(Deposit) case WithdrawalRequestType: inner = new(WithdrawalRequest) + case ConsolidationRequestType: + inner = new(ConsolidationRequest) default: return nil, ErrRequestTypeNotSupported } diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index 3515799854..047b288de6 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -877,6 +877,7 @@ func (api *ConsensusAPI) GetPayloadBodiesByHashV1(hashes []common.Hash) []*engin if body != nil { body.Deposits = nil bodies[i].WithdrawalRequests = nil + bodies[i].ConsolidationRequests = nil bodies[i] = body } } @@ -907,6 +908,7 @@ func (api *ConsensusAPI) GetPayloadBodiesByRangeV1(start, count hexutil.Uint64) if bodies[i] != nil { bodies[i].Deposits = nil bodies[i].WithdrawalRequests = nil + bodies[i].ConsolidationRequests = nil } } return bodies, nil @@ -945,11 +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 - withdrawalRequests types.WithdrawalRequests + 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 { @@ -965,19 +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() { 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, - WithdrawalRequests: withdrawalRequests, + 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 0a58e1eaee..c7b5a17f53 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/miner/worker.go b/miner/worker.go index f0bdd02771..51e69898be 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -125,6 +125,9 @@ func (miner *Miner) generateWork(params *generateParams) *newPayloadResult { 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 7da77b18eb..2e1ab388a8 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -193,6 +193,8 @@ var ( 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.