diff --git a/beacon/engine/gen_ed.go b/beacon/engine/gen_ed.go
index 6893d64a1626..c3db18022640 100644
--- a/beacon/engine/gen_ed.go
+++ b/beacon/engine/gen_ed.go
@@ -34,6 +34,7 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) {
Withdrawals []*types.Withdrawal `json:"withdrawals"`
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
+ Deposits types.Deposits `json:"depositRequests"`
}
var enc ExecutableData
enc.ParentHash = e.ParentHash
@@ -58,6 +59,7 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) {
enc.Withdrawals = e.Withdrawals
enc.BlobGasUsed = (*hexutil.Uint64)(e.BlobGasUsed)
enc.ExcessBlobGas = (*hexutil.Uint64)(e.ExcessBlobGas)
+ enc.Deposits = e.Deposits
return json.Marshal(&enc)
}
@@ -81,6 +83,7 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error {
Withdrawals []*types.Withdrawal `json:"withdrawals"`
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
+ Deposits *types.Deposits `json:"depositRequests"`
}
var dec ExecutableData
if err := json.Unmarshal(input, &dec); err != nil {
@@ -154,5 +157,8 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error {
if dec.ExcessBlobGas != nil {
e.ExcessBlobGas = (*uint64)(dec.ExcessBlobGas)
}
+ if dec.Deposits != nil {
+ e.Deposits = *dec.Deposits
+ }
return nil
}
diff --git a/beacon/engine/types.go b/beacon/engine/types.go
index d1b3aa22abdf..3c8dd9f3b3f0 100644
--- a/beacon/engine/types.go
+++ b/beacon/engine/types.go
@@ -36,6 +36,7 @@ var (
PayloadV1 PayloadVersion = 0x1
PayloadV2 PayloadVersion = 0x2
PayloadV3 PayloadVersion = 0x3
+ PayloadV4 PayloadVersion = 0x4
)
//go:generate go run github.com/fjl/gencodec -type PayloadAttributes -field-override payloadAttributesMarshaling -out gen_blockparams.go
@@ -48,6 +49,7 @@ type PayloadAttributes struct {
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"`
BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"`
+ TargetBlobCount *uint64 `json:"targetBlobCount"`
}
// JSON type overrides for PayloadAttributes.
@@ -76,6 +78,7 @@ type ExecutableData struct {
Withdrawals []*types.Withdrawal `json:"withdrawals"`
BlobGasUsed *uint64 `json:"blobGasUsed"`
ExcessBlobGas *uint64 `json:"excessBlobGas"`
+ Deposits types.Deposits `json:"depositRequests"`
}
// JSON type overrides for executableData.
@@ -195,7 +198,7 @@ func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) {
// and that the blockhash of the constructed block matches the parameters. Nil
// Withdrawals value will propagate through the returned block. Empty
// Withdrawals value must be passed via non-nil, length 0 value in data.
-func ExecutableDataToBlock(data ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (*types.Block, error) {
+func ExecutableDataToBlock(data ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, targetBlobCount *uint64) (*types.Block, error) {
txs, err := decodeTransactions(data.Transactions)
if err != nil {
return nil, err
@@ -230,6 +233,19 @@ 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.
+ var (
+ requestsHash *common.Hash
+ requests types.Requests
+ )
+ if data.Deposits != nil {
+ requests = make(types.Requests, 0)
+ for _, d := range data.Deposits {
+ requests = append(requests, types.NewRequest(d))
+ }
+ h := types.DeriveSha(requests, trie.NewStackTrie(nil))
+ requestsHash = &h
+ }
header := &types.Header{
ParentHash: data.ParentHash,
UncleHash: types.EmptyUncleHash,
@@ -250,8 +266,13 @@ func ExecutableDataToBlock(data ExecutableData, versionedHashes []common.Hash, b
ExcessBlobGas: data.ExcessBlobGas,
BlobGasUsed: data.BlobGasUsed,
ParentBeaconRoot: beaconRoot,
+ RequestsHash: requestsHash,
+ TargetBlobCount: targetBlobCount,
}
- block := types.NewBlockWithHeader(header).WithBody(types.Body{Transactions: txs, Uncles: nil, Withdrawals: data.Withdrawals})
+ var (
+ body = types.Body{Transactions: txs, Uncles: nil, Withdrawals: data.Withdrawals, Requests: requests}
+ block = types.NewBlockWithHeader(header).WithBody(body)
+ )
if block.Hash() != data.BlockHash {
return nil, fmt.Errorf("blockhash mismatch, want %x, got %x", data.BlockHash, block.Hash())
}
@@ -292,13 +313,30 @@ func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types.
bundle.Proofs = append(bundle.Proofs, hexutil.Bytes(sidecar.Proofs[j][:]))
}
}
+ requestsToExecutableData(block.Requests(), data)
return &ExecutionPayloadEnvelope{ExecutionPayload: data, BlockValue: fees, BlobsBundle: &bundle, Override: false}
}
-// ExecutionPayloadBodyV1 is used in the response to GetPayloadBodiesByHashV1 and GetPayloadBodiesByRangeV1
-type ExecutionPayloadBodyV1 struct {
+// requestsToExecutableData differentiates the different request types and
+// assigns them to the associated fields in ExecutableData.
+func requestsToExecutableData(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.
+ data.Deposits = make(types.Deposits, 0)
+ }
+ for _, r := range requests {
+ if d, ok := r.Inner().(*types.Deposit); ok {
+ data.Deposits = append(data.Deposits, d)
+ }
+ }
+}
+
+// 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"`
}
// Client identifiers to support ClientVersionV1.
diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go
index a4c5f6efcb0a..b5d8f08de03c 100644
--- a/cmd/evm/internal/t8ntool/execution.go
+++ b/cmd/evm/internal/t8ntool/execution.go
@@ -66,6 +66,8 @@ type ExecutionResult struct {
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"`
}
type ommer struct {
@@ -370,6 +372,28 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
execRs.CurrentExcessBlobGas = (*math.HexOrDecimal64)(&excessBlobGas)
execRs.CurrentBlobGasUsed = (*math.HexOrDecimal64)(&blobGasUsed)
}
+ if chainConfig.IsPrague(vmContext.BlockNumber, vmContext.Time) {
+ // Parse the requests from the logs
+ var allLogs []*types.Log
+ for _, receipt := range receipts {
+ allLogs = append(allLogs, receipt.Logs...)
+ }
+ requests, err := core.ParseDepositLogs(allLogs, chainConfig)
+ if err != nil {
+ return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not parse requests logs: %v", err))
+ }
+ // 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)
+ for _, req := range requests {
+ if dep, ok := req.Inner().(*types.Deposit); ok {
+ deposits = append(deposits, dep)
+ }
+ }
+ execRs.DepositRequests = &deposits
+ }
// Re-create statedb instance with new root upon the updated database
// for accessing latest states.
statedb, err = state.New(root, statedb.Database(), nil)
diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go
index 19763ed303f1..090ee5ae9985 100644
--- a/consensus/beacon/consensus.go
+++ b/consensus/beacon/consensus.go
@@ -25,6 +25,7 @@ import (
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/misc/eip1559"
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
+ "github.com/ethereum/go-ethereum/consensus/misc/eip7742"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
@@ -286,6 +287,13 @@ func (beacon *Beacon) verifyHeader(chain consensus.ChainHeaderReader, header, pa
if header.ParentBeaconRoot == nil {
return errors.New("header is missing beaconRoot")
}
+ }
+ // Apply blob checks to header.
+ if chain.Config().IsPrague(header.Number, header.Time) {
+ if err := eip7742.VerifyEIP7742Header(parent, header); err != nil {
+ return err
+ }
+ } else if cancun {
if err := eip4844.VerifyEIP4844Header(parent, header); err != nil {
return err
}
diff --git a/consensus/misc/eip7742/eip7742.go b/consensus/misc/eip7742/eip7742.go
new file mode 100644
index 000000000000..22a31e3ad89e
--- /dev/null
+++ b/consensus/misc/eip7742/eip7742.go
@@ -0,0 +1,102 @@
+// Copyright 2023 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 eip7742
+
+import (
+ "errors"
+ "fmt"
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/params"
+)
+
+var (
+ minBlobGasPrice = big.NewInt(params.BlobTxMinBlobGasprice)
+ blobGaspriceUpdateFraction = big.NewInt(params.BlobTxBlobGaspriceUpdateFraction)
+)
+
+// VerifyEIP7742Header verifies the presence of the excessBlobGas field and that
+// if the current block contains no transactions, the excessBlobGas is updated
+// accordingly.
+func VerifyEIP7742Header(parent, header *types.Header) error {
+ // Verify the header is not malformed
+ if header.ExcessBlobGas == nil {
+ return errors.New("header is missing excessBlobGas")
+ }
+ if header.BlobGasUsed == nil {
+ return errors.New("header is missing blobGasUsed")
+ }
+ if header.TargetBlobCount == nil {
+ return errors.New("header is missing target blob count")
+ }
+ // Verify that the blob gas used remains within reasonable limits.
+ maxBlobGas := (*header.TargetBlobCount * params.BlobTargetQuotient) * params.BlobTxBlobGasPerBlob
+ if *header.BlobGasUsed > maxBlobGas {
+ return fmt.Errorf("blob gas used %d exceeds maximum allowance %d", *header.BlobGasUsed, maxBlobGas)
+ }
+ if *header.BlobGasUsed%params.BlobTxBlobGasPerBlob != 0 {
+ return fmt.Errorf("blob gas used %d not a multiple of blob gas per blob %d", header.BlobGasUsed, params.BlobTxBlobGasPerBlob)
+ }
+ // Verify the excessBlobGas is correct based on the parent header
+ var (
+ parentExcessBlobGas uint64
+ parentBlobGasUsed uint64
+ )
+ if parent.ExcessBlobGas != nil {
+ parentExcessBlobGas = *parent.ExcessBlobGas
+ parentBlobGasUsed = *parent.BlobGasUsed
+ }
+ expectedExcessBlobGas := CalcExcessBlobGas(parentExcessBlobGas, parentBlobGasUsed, *header.TargetBlobCount)
+ if *header.ExcessBlobGas != expectedExcessBlobGas {
+ return fmt.Errorf("invalid excessBlobGas: have %d, want %d, parent excessBlobGas %d, parent blobDataUsed %d",
+ *header.ExcessBlobGas, expectedExcessBlobGas, parentExcessBlobGas, parentBlobGasUsed)
+ }
+ return nil
+}
+
+// CalcExcessBlobGas calculates the excess blob gas after applying the set of
+// blobs on top of the excess blob gas.
+func CalcExcessBlobGas(parentExcessBlobGas, parentBlobGasUsed, targetBlobCount uint64) uint64 {
+ excessBlobGas := parentExcessBlobGas + parentBlobGasUsed
+ if excessBlobGas < targetBlobCount*params.BlobTxBlobGasPerBlob {
+ return 0
+ }
+ return excessBlobGas - (targetBlobCount * params.BlobTxBlobGasPerBlob)
+}
+
+// CalcBlobFee calculates the blobfee from the header's excess blob gas field.
+func CalcBlobFee(excessBlobGas uint64) *big.Int {
+ return fakeExponential(minBlobGasPrice, new(big.Int).SetUint64(excessBlobGas), blobGaspriceUpdateFraction)
+}
+
+// fakeExponential approximates factor * e ** (numerator / denominator) using
+// Taylor expansion.
+func fakeExponential(factor, numerator, denominator *big.Int) *big.Int {
+ var (
+ output = new(big.Int)
+ accum = new(big.Int).Mul(factor, denominator)
+ )
+ for i := 1; accum.Sign() > 0; i++ {
+ output.Add(output, accum)
+
+ accum.Mul(accum, numerator)
+ accum.Div(accum, denominator)
+ accum.Div(accum, big.NewInt(int64(i)))
+ }
+ return output.Div(output, denominator)
+}
diff --git a/consensus/misc/eip7742/eip7742_test.go b/consensus/misc/eip7742/eip7742_test.go
new file mode 100644
index 000000000000..627d17d1f3c7
--- /dev/null
+++ b/consensus/misc/eip7742/eip7742_test.go
@@ -0,0 +1,114 @@
+// Copyright 2023 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 eip7742
+
+import (
+ "fmt"
+ "math/big"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/params"
+)
+
+func TestCalcExcessBlobGas(t *testing.T) {
+ var tests = []struct {
+ excess uint64
+ blobs uint64
+ want uint64
+ }{
+ // The excess blob gas should not increase from zero if the used blob
+ // slots are below - or equal - to the target.
+ {0, 0, 0},
+ {0, 1, 0},
+ {0, params.BlobTxTargetBlobGasPerBlock / params.BlobTxBlobGasPerBlob, 0},
+
+ // If the target blob gas is exceeded, the excessBlobGas should increase
+ // by however much it was overshot
+ {0, (params.BlobTxTargetBlobGasPerBlock / params.BlobTxBlobGasPerBlob) + 1, params.BlobTxBlobGasPerBlob},
+ {1, (params.BlobTxTargetBlobGasPerBlock / params.BlobTxBlobGasPerBlob) + 1, params.BlobTxBlobGasPerBlob + 1},
+ {1, (params.BlobTxTargetBlobGasPerBlock / params.BlobTxBlobGasPerBlob) + 2, 2*params.BlobTxBlobGasPerBlob + 1},
+
+ // The excess blob gas should decrease by however much the target was
+ // under-shot, capped at zero.
+ {params.BlobTxTargetBlobGasPerBlock, params.BlobTxTargetBlobGasPerBlock / params.BlobTxBlobGasPerBlob, params.BlobTxTargetBlobGasPerBlock},
+ {params.BlobTxTargetBlobGasPerBlock, (params.BlobTxTargetBlobGasPerBlock / params.BlobTxBlobGasPerBlob) - 1, params.BlobTxTargetBlobGasPerBlock - params.BlobTxBlobGasPerBlob},
+ {params.BlobTxTargetBlobGasPerBlock, (params.BlobTxTargetBlobGasPerBlock / params.BlobTxBlobGasPerBlob) - 2, params.BlobTxTargetBlobGasPerBlock - (2 * params.BlobTxBlobGasPerBlob)},
+ {params.BlobTxBlobGasPerBlob - 1, (params.BlobTxTargetBlobGasPerBlock / params.BlobTxBlobGasPerBlob) - 1, 0},
+ }
+ for i, tt := range tests {
+ result := CalcExcessBlobGas(tt.excess, tt.blobs*params.BlobTxBlobGasPerBlob, params.BlobTxTargetBlobGasPerBlock/params.BlobTxBlobGasPerBlob)
+ if result != tt.want {
+ t.Errorf("test %d: excess blob gas mismatch: have %v, want %v", i, result, tt.want)
+ }
+ }
+}
+
+func TestCalcBlobFee(t *testing.T) {
+ tests := []struct {
+ excessBlobGas uint64
+ blobfee int64
+ }{
+ {0, 1},
+ {2314057, 1},
+ {2314058, 2},
+ {10 * 1024 * 1024, 23},
+ }
+ for i, tt := range tests {
+ have := CalcBlobFee(tt.excessBlobGas)
+ if have.Int64() != tt.blobfee {
+ t.Errorf("test %d: blobfee mismatch: have %v want %v", i, have, tt.blobfee)
+ }
+ }
+}
+
+func TestFakeExponential(t *testing.T) {
+ tests := []struct {
+ factor int64
+ numerator int64
+ denominator int64
+ want int64
+ }{
+ // When numerator == 0 the return value should always equal the value of factor
+ {1, 0, 1, 1},
+ {38493, 0, 1000, 38493},
+ {0, 1234, 2345, 0}, // should be 0
+ {1, 2, 1, 6}, // approximate 7.389
+ {1, 4, 2, 6},
+ {1, 3, 1, 16}, // approximate 20.09
+ {1, 6, 2, 18},
+ {1, 4, 1, 49}, // approximate 54.60
+ {1, 8, 2, 50},
+ {10, 8, 2, 542}, // approximate 540.598
+ {11, 8, 2, 596}, // approximate 600.58
+ {1, 5, 1, 136}, // approximate 148.4
+ {1, 5, 2, 11}, // approximate 12.18
+ {2, 5, 2, 23}, // approximate 24.36
+ {1, 50000000, 2225652, 5709098764},
+ }
+ for i, tt := range tests {
+ f, n, d := big.NewInt(tt.factor), big.NewInt(tt.numerator), big.NewInt(tt.denominator)
+ original := fmt.Sprintf("%d %d %d", f, n, d)
+ have := fakeExponential(f, n, d)
+ if have.Int64() != tt.want {
+ t.Errorf("test %d: fake exponential mismatch: have %v want %v", i, have, tt.want)
+ }
+ later := fmt.Sprintf("%d %d %d", f, n, d)
+ if original != later {
+ t.Errorf("test %d: fake exponential modified arguments: have\n%v\nwant\n%v", i, later, original)
+ }
+ }
+}
diff --git a/core/block_validator.go b/core/block_validator.go
index 75f7f8a94b68..a944db0bf896 100644
--- a/core/block_validator.go
+++ b/core/block_validator.go
@@ -121,14 +121,17 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
// ValidateState validates the various changes that happen after a state transition,
// such as amount of used gas, the receipt roots and the state root itself.
-func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateDB, receipts types.Receipts, usedGas uint64, stateless bool) error {
+func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateDB, res *ProcessResult, stateless bool) error {
+ if res == nil {
+ return fmt.Errorf("nil ProcessResult value")
+ }
header := block.Header()
- if block.GasUsed() != usedGas {
- return fmt.Errorf("invalid gas used (remote: %d local: %d)", block.GasUsed(), usedGas)
+ if block.GasUsed() != res.GasUsed {
+ return fmt.Errorf("invalid gas used (remote: %d local: %d)", block.GasUsed(), res.GasUsed)
}
// Validate the received block's bloom with the one derived from the generated receipts.
// For valid blocks this should always validate to true.
- rbloom := types.CreateBloom(receipts)
+ rbloom := types.CreateBloom(res.Receipts)
if rbloom != header.Bloom {
return fmt.Errorf("invalid bloom (remote: %x local: %x)", header.Bloom, rbloom)
}
@@ -138,10 +141,17 @@ func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateD
return nil
}
// The receipt Trie's root (R = (Tr [[H1, R1], ... [Hn, Rn]]))
- receiptSha := types.DeriveSha(receipts, trie.NewStackTrie(nil))
+ receiptSha := types.DeriveSha(res.Receipts, trie.NewStackTrie(nil))
if receiptSha != header.ReceiptHash {
return fmt.Errorf("invalid receipt root hash (remote: %x local: %x)", header.ReceiptHash, receiptSha)
}
+ // Validate the parsed requests match the expected header value.
+ if header.RequestsHash != nil {
+ depositSha := types.DeriveSha(res.Requests, trie.NewStackTrie(nil))
+ if depositSha != *header.RequestsHash {
+ return fmt.Errorf("invalid deposit root hash (remote: %x local: %x)", *header.RequestsHash, depositSha)
+ }
+ }
// Validate the state root against the received state root and throw
// an error if they don't match.
if root := statedb.IntermediateRoot(v.config.IsEIP158(header.Number)); header.Root != root {
diff --git a/core/blockchain.go b/core/blockchain.go
index 05ebfd18b830..1fae4c7bc4b5 100644
--- a/core/blockchain.go
+++ b/core/blockchain.go
@@ -1924,23 +1924,23 @@ func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, s
// Process block using the parent state as reference point
pstart := time.Now()
- receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig)
+ res, err := bc.processor.Process(block, statedb, bc.vmConfig)
if err != nil {
- bc.reportBlock(block, receipts, err)
+ bc.reportBlock(block, res, err)
return nil, err
}
ptime := time.Since(pstart)
vstart := time.Now()
- if err := bc.validator.ValidateState(block, statedb, receipts, usedGas, false); err != nil {
- bc.reportBlock(block, receipts, err)
+ if err := bc.validator.ValidateState(block, statedb, res, false); err != nil {
+ bc.reportBlock(block, res, err)
return nil, err
}
vtime := time.Since(vstart)
if witness := statedb.Witness(); witness != nil {
if err = bc.validator.ValidateWitness(witness, block.ReceiptHash(), block.Root()); err != nil {
- bc.reportBlock(block, receipts, err)
+ bc.reportBlock(block, res, err)
return nil, fmt.Errorf("cross verification failed: %v", err)
}
}
@@ -1968,9 +1968,9 @@ func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, s
)
if !setHead {
// Don't set the head, only insert the block
- err = bc.writeBlockWithState(block, receipts, statedb)
+ err = bc.writeBlockWithState(block, res.Receipts, statedb)
} else {
- status, err = bc.writeBlockAndSetHead(block, receipts, logs, statedb, false)
+ status, err = bc.writeBlockAndSetHead(block, res.Receipts, res.Logs, statedb, false)
}
if err != nil {
return nil, err
@@ -1984,7 +1984,7 @@ func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, s
blockWriteTimer.Update(time.Since(wstart) - max(statedb.AccountCommits, statedb.StorageCommits) /* concurrent */ - statedb.SnapshotCommits - statedb.TrieDBCommits)
blockInsertTimer.UpdateSince(start)
- return &blockProcessingResult{usedGas: usedGas, procTime: proctime, status: status}, nil
+ return &blockProcessingResult{usedGas: res.GasUsed, procTime: proctime, status: status}, nil
}
// insertSideChain is called when an import batch hits upon a pruned ancestor
@@ -2481,7 +2481,11 @@ func (bc *BlockChain) skipBlock(err error, it *insertIterator) bool {
}
// reportBlock logs a bad block error.
-func (bc *BlockChain) reportBlock(block *types.Block, receipts types.Receipts, err error) {
+func (bc *BlockChain) reportBlock(block *types.Block, res *ProcessResult, err error) {
+ var receipts types.Receipts
+ if res != nil {
+ receipts = res.Receipts
+ }
rawdb.WriteBadBlock(bc.db, block)
log.Error(summarizeBadBlock(block, receipts, bc.Config(), err))
}
diff --git a/core/blockchain_test.go b/core/blockchain_test.go
index 4f28c6f5e681..9148dd6f08d2 100644
--- a/core/blockchain_test.go
+++ b/core/blockchain_test.go
@@ -163,13 +163,14 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error {
if err != nil {
return err
}
- receipts, _, usedGas, err := blockchain.processor.Process(block, statedb, vm.Config{})
+ res, err := blockchain.processor.Process(block, statedb, vm.Config{})
if err != nil {
- blockchain.reportBlock(block, receipts, err)
+ blockchain.reportBlock(block, res, err)
return err
}
- if err = blockchain.validator.ValidateState(block, statedb, receipts, usedGas, false); err != nil {
- blockchain.reportBlock(block, receipts, err)
+ err = blockchain.validator.ValidateState(block, statedb, res, false)
+ if err != nil {
+ blockchain.reportBlock(block, res, err)
return err
}
@@ -4220,3 +4221,90 @@ func TestEIP3651(t *testing.T) {
t.Fatalf("sender balance incorrect: expected %d, got %d", expected, actual)
}
}
+
+func TestEIP6110(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},
+ config.DepositContractAddress: {
+ // Simple deposit generator, source: https://gist.github.com/lightclient/54abb2af2465d6969fa6d1920b9ad9d7
+ Code: common.Hex2Bytes("6080604052366103aa575f603067ffffffffffffffff811115610025576100246103ae565b5b6040519080825280601f01601f1916602001820160405280156100575781602001600182028036833780820191505090505b5090505f8054906101000a900460ff1660f81b815f8151811061007d5761007c6103db565b5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690815f1a9053505f602067ffffffffffffffff8111156100c7576100c66103ae565b5b6040519080825280601f01601f1916602001820160405280156100f95781602001600182028036833780820191505090505b5090505f8054906101000a900460ff1660f81b815f8151811061011f5761011e6103db565b5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690815f1a9053505f600867ffffffffffffffff811115610169576101686103ae565b5b6040519080825280601f01601f19166020018201604052801561019b5781602001600182028036833780820191505090505b5090505f8054906101000a900460ff1660f81b815f815181106101c1576101c06103db565b5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690815f1a9053505f606067ffffffffffffffff81111561020b5761020a6103ae565b5b6040519080825280601f01601f19166020018201604052801561023d5781602001600182028036833780820191505090505b5090505f8054906101000a900460ff1660f81b815f81518110610263576102626103db565b5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690815f1a9053505f600867ffffffffffffffff8111156102ad576102ac6103ae565b5b6040519080825280601f01601f1916602001820160405280156102df5781602001600182028036833780820191505090505b5090505f8054906101000a900460ff1660f81b815f81518110610305576103046103db565b5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690815f1a9053505f8081819054906101000a900460ff168092919061035090610441565b91906101000a81548160ff021916908360ff160217905550507f649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c585858585856040516103a09594939291906104d9565b60405180910390a1005b5f80fd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f60ff82169050919050565b5f61044b82610435565b915060ff820361045e5761045d610408565b5b600182019050919050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f6104ab82610469565b6104b58185610473565b93506104c5818560208601610483565b6104ce81610491565b840191505092915050565b5f60a0820190508181035f8301526104f181886104a1565b9050818103602083015261050581876104a1565b9050818103604083015261051981866104a1565b9050818103606083015261052d81856104a1565b9050818103608083015261054181846104a1565b9050969550505050505056fea26469706673582212208569967e58690162d7d6fe3513d07b393b4c15e70f41505cbbfd08f53eba739364736f6c63430008190033"),
+ 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)
+
+ _, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) {
+ for i := 0; i < 5; i++ {
+ txdata := &types.DynamicFeeTx{
+ ChainID: gspec.Config.ChainID,
+ Nonce: uint64(i),
+ To: &config.DepositContractAddress,
+ Gas: 500000,
+ GasFeeCap: newGwei(5),
+ GasTipCap: big.NewInt(2),
+ AccessList: nil,
+ Data: []byte{},
+ }
+ 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{DisableStack: true}, 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)
+ }
+
+ block := chain.GetBlockByNumber(1)
+ if len(block.Requests()) != 5 {
+ t.Fatalf("failed to retrieve deposits: have %d, want %d", len(block.Requests()), 5)
+ }
+
+ // Verify each index is correct.
+ for want, req := range block.Requests() {
+ d, ok := req.Inner().(*types.Deposit)
+ if !ok {
+ t.Fatalf("expected deposit object")
+ }
+ if got := int(d.PublicKey[0]); got != want {
+ t.Fatalf("invalid pubkey: have %d, want %d", got, want)
+ }
+ if got := int(d.WithdrawalCredentials[0]); got != want {
+ t.Fatalf("invalid withdrawal credentials: have %d, want %d", got, want)
+ }
+ if d.Amount != uint64(want) {
+ t.Fatalf("invalid amounbt: have %d, want %d", d.Amount, want)
+ }
+ if got := int(d.Signature[0]); got != want {
+ t.Fatalf("invalid signature: have %d, want %d", got, want)
+ }
+ if d.Index != uint64(want) {
+ t.Fatalf("invalid index: have %d, want %d", d.Index, want)
+ }
+ }
+}
diff --git a/core/chain_makers.go b/core/chain_makers.go
index 58985347bb31..da3e148508c7 100644
--- a/core/chain_makers.go
+++ b/core/chain_makers.go
@@ -346,7 +346,18 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
gen(i, b)
}
- body := types.Body{Transactions: b.txs, Uncles: b.uncles, Withdrawals: b.withdrawals}
+ var requests types.Requests
+ if config.IsPrague(b.header.Number, b.header.Time) {
+ for _, r := range b.receipts {
+ d, err := ParseDepositLogs(r.Logs, config)
+ if err != nil {
+ panic(fmt.Sprintf("failed to parse deposit log: %v", err))
+ }
+ requests = append(requests, d...)
+ }
+ }
+
+ body := types.Body{Transactions: b.txs, Uncles: b.uncles, Withdrawals: b.withdrawals, Requests: requests}
block, err := b.engine.FinalizeAndAssemble(cm, b.header, statedb, &body, b.receipts)
if err != nil {
panic(err)
diff --git a/core/genesis.go b/core/genesis.go
index 9e213e5163b0..c4aaefb5ce22 100644
--- a/core/genesis.go
+++ b/core/genesis.go
@@ -451,7 +451,10 @@ func (g *Genesis) toBlockWithRoot(root common.Hash) *types.Block {
head.BaseFee = new(big.Int).SetUint64(params.InitialBaseFee)
}
}
- var withdrawals []*types.Withdrawal
+ var (
+ withdrawals []*types.Withdrawal
+ requests types.Requests
+ )
if conf := g.Config; conf != nil {
num := big.NewInt(int64(g.Number))
if conf.IsShanghai(num, g.Timestamp) {
@@ -473,8 +476,12 @@ func (g *Genesis) toBlockWithRoot(root common.Hash) *types.Block {
head.BlobGasUsed = new(uint64)
}
}
+ if conf.IsPrague(num, g.Timestamp) {
+ head.RequestsHash = &types.EmptyRequestsHash
+ requests = make(types.Requests, 0)
+ }
}
- return types.NewBlock(head, &types.Body{Withdrawals: withdrawals}, nil, trie.NewStackTrie(nil))
+ return types.NewBlock(head, &types.Body{Withdrawals: withdrawals, Requests: requests}, nil, trie.NewStackTrie(nil))
}
// Commit writes the block and state of a genesis specification to the database.
diff --git a/core/state_processor.go b/core/state_processor.go
index cc415831659d..30e8f2a080b6 100644
--- a/core/state_processor.go
+++ b/core/state_processor.go
@@ -17,6 +17,7 @@
package core
import (
+ "errors"
"fmt"
"math/big"
@@ -53,7 +54,7 @@ func NewStateProcessor(config *params.ChainConfig, chain *HeaderChain) *StatePro
// Process returns the receipts and logs accumulated during the process and
// returns the amount of gas that was used in the process. If any of the
// transactions failed to execute due to insufficient gas it will return an error.
-func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) {
+func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (*ProcessResult, error) {
var (
receipts types.Receipts
usedGas = new(uint64)
@@ -71,6 +72,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
var (
context vm.BlockContext
signer = types.MakeSigner(p.config, header.Number, header.Time)
+ err error
)
context = NewEVMBlockContext(header, p.chain, nil)
vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, p.config, cfg)
@@ -81,21 +83,40 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
for i, tx := range block.Transactions() {
msg, err := TransactionToMessage(tx, signer, header.BaseFee)
if err != nil {
- return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
+ return nil, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
}
statedb.SetTxContext(tx.Hash(), i)
receipt, err := ApplyTransactionWithEVM(msg, p.config, gp, statedb, blockNumber, blockHash, tx, usedGas, vmenv)
if err != nil {
- return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
+ return nil, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
}
receipts = append(receipts, receipt)
allLogs = append(allLogs, receipt.Logs...)
}
+ // Fail if Shanghai not enabled and len(withdrawals) is non-zero.
+ withdrawals := block.Withdrawals()
+ if len(withdrawals) > 0 && !p.config.IsShanghai(block.Number(), block.Time()) {
+ return nil, errors.New("withdrawals before shanghai")
+ }
+ // Read requests if Prague is enabled.
+ var requests types.Requests
+ if p.config.IsPrague(block.Number(), block.Time()) {
+ requests, err = ParseDepositLogs(allLogs, p.config)
+ if err != nil {
+ return nil, err
+ }
+ }
+
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
p.chain.engine.Finalize(p.chain, header, statedb, block.Body())
- return receipts, allLogs, *usedGas, nil
+ return &ProcessResult{
+ Receipts: receipts,
+ Requests: requests,
+ Logs: allLogs,
+ GasUsed: *usedGas,
+ }, nil
}
// ApplyTransactionWithEVM attempts to apply a transaction to the given state database
@@ -201,3 +222,19 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, vmenv *vm.EVM, statedb *stat
_, _, _ = vmenv.Call(vm.AccountRef(msg.From), *msg.To, msg.Data, 30_000_000, common.U2560)
statedb.Finalise(true)
}
+
+// ParseDepositLogs extracts the EIP-6110 deposit values from logs emitted by
+// BeaconDepositContract.
+func ParseDepositLogs(logs []*types.Log, config *params.ChainConfig) (types.Requests, error) {
+ deposits := make(types.Requests, 0)
+ for _, log := range logs {
+ if log.Address == config.DepositContractAddress {
+ d, err := types.UnpackIntoDeposit(log.Data)
+ if err != nil {
+ return nil, fmt.Errorf("unable to parse deposit data: %v", err)
+ }
+ deposits = append(deposits, types.NewRequest(d))
+ }
+ }
+ return deposits, nil
+}
diff --git a/core/stateless.go b/core/stateless.go
index 4c7e6f31027f..e3168747cf27 100644
--- a/core/stateless.go
+++ b/core/stateless.go
@@ -58,15 +58,15 @@ func ExecuteStateless(config *params.ChainConfig, witness *stateless.Witness) (c
validator := NewBlockValidator(config, nil) // No chain, we only validate the state, not the block
// Run the stateless blocks processing and self-validate certain fields
- receipts, _, usedGas, err := processor.Process(witness.Block, db, vm.Config{})
+ res, err := processor.Process(witness.Block, db, vm.Config{})
if err != nil {
return common.Hash{}, common.Hash{}, err
}
- if err = validator.ValidateState(witness.Block, db, receipts, usedGas, true); err != nil {
+ if err = validator.ValidateState(witness.Block, db, res, true); err != nil {
return common.Hash{}, common.Hash{}, err
}
// Almost everything validated, but receipt and state root needs to be returned
- receiptRoot := types.DeriveSha(receipts, trie.NewStackTrie(nil))
+ receiptRoot := types.DeriveSha(res.Receipts, trie.NewStackTrie(nil))
stateRoot := db.IntermediateRoot(config.IsEIP158(witness.Block.Number()))
return receiptRoot, stateRoot, nil
diff --git a/core/types.go b/core/types.go
index dc13de52ce2b..97db46176766 100644
--- a/core/types.go
+++ b/core/types.go
@@ -35,7 +35,7 @@ type Validator interface {
// ValidateState validates the given statedb and optionally the receipts and
// gas used.
- ValidateState(block *types.Block, state *state.StateDB, receipts types.Receipts, usedGas uint64, stateless bool) error
+ ValidateState(block *types.Block, state *state.StateDB, res *ProcessResult, stateless bool) error
// ValidateWitness cross validates a block execution with stateless remote clients.
ValidateWitness(witness *stateless.Witness, receiptRoot common.Hash, stateRoot common.Hash) error
@@ -54,5 +54,13 @@ type Processor interface {
// Process processes the state changes according to the Ethereum rules by running
// the transaction messages using the statedb and applying any rewards to both
// the processor (coinbase) and any included uncles.
- Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error)
+ Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (*ProcessResult, error)
+}
+
+// ProcessResult contains the values computed by Process.
+type ProcessResult struct {
+ Receipts types.Receipts
+ Requests types.Requests
+ Logs []*types.Log
+ GasUsed uint64
}
diff --git a/core/types/block.go b/core/types/block.go
index 4857cd6e50c8..849f13c53910 100644
--- a/core/types/block.go
+++ b/core/types/block.go
@@ -94,6 +94,12 @@ type Header struct {
// ParentBeaconRoot was added by EIP-4788 and is ignored in legacy headers.
ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
+
+ // RequestsHash was added by EIP-7685 and is ignored in legacy headers.
+ RequestsHash *common.Hash `json:"requestsRoot" rlp:"optional"`
+
+ // TargetBlobCount was added by EIP-7742 and is ignored in legacy headers.
+ TargetBlobCount *uint64 `json:"targetBlobCount" rlp:"optional"`
}
// field type overrides for gencodec
@@ -155,10 +161,11 @@ func (h *Header) SanityCheck() error {
// EmptyBody returns true if there is no additional 'body' to complete the header
// that is: no transactions, no uncles and no withdrawals.
func (h *Header) EmptyBody() bool {
- if h.WithdrawalsHash != nil {
- return h.TxHash == EmptyTxsHash && *h.WithdrawalsHash == EmptyWithdrawalsHash
- }
- return h.TxHash == EmptyTxsHash && h.UncleHash == EmptyUncleHash
+ var (
+ emptyWithdrawals = h.WithdrawalsHash == nil || *h.WithdrawalsHash == EmptyWithdrawalsHash
+ emptyRequests = h.RequestsHash == nil || *h.RequestsHash == EmptyReceiptsHash
+ )
+ return h.TxHash == EmptyTxsHash && h.UncleHash == EmptyUncleHash && emptyWithdrawals && emptyRequests
}
// EmptyReceipts returns true if there are no receipts for this header/block.
@@ -172,6 +179,7 @@ type Body struct {
Transactions []*Transaction
Uncles []*Header
Withdrawals []*Withdrawal `rlp:"optional"`
+ Requests []*Request `rlp:"optional"`
}
// Block represents an Ethereum block.
@@ -196,6 +204,7 @@ type Block struct {
uncles []*Header
transactions Transactions
withdrawals Withdrawals
+ requests Requests
// caches
hash atomic.Pointer[common.Hash]
@@ -213,6 +222,7 @@ type extblock struct {
Txs []*Transaction
Uncles []*Header
Withdrawals []*Withdrawal `rlp:"optional"`
+ Requests []*Request `rlp:"optional"`
}
// NewBlock creates a new block. The input data is copied, changes to header and to the
@@ -229,6 +239,7 @@ func NewBlock(header *Header, body *Body, receipts []*Receipt, hasher TrieHasher
txs = body.Transactions
uncles = body.Uncles
withdrawals = body.Withdrawals
+ requests = body.Requests
)
if len(txs) == 0 {
@@ -267,6 +278,17 @@ func NewBlock(header *Header, body *Body, receipts []*Receipt, hasher TrieHasher
b.withdrawals = slices.Clone(withdrawals)
}
+ if requests == nil {
+ b.header.RequestsHash = nil
+ } else if len(requests) == 0 {
+ b.header.RequestsHash = &EmptyRequestsHash
+ b.requests = Requests{}
+ } else {
+ h := DeriveSha(Requests(requests), hasher)
+ b.header.RequestsHash = &h
+ b.requests = slices.Clone(requests)
+ }
+
return b
}
@@ -302,6 +324,10 @@ func CopyHeader(h *Header) *Header {
cpy.ParentBeaconRoot = new(common.Hash)
*cpy.ParentBeaconRoot = *h.ParentBeaconRoot
}
+ if h.RequestsHash != nil {
+ cpy.RequestsHash = new(common.Hash)
+ *cpy.RequestsHash = *h.RequestsHash
+ }
return &cpy
}
@@ -312,7 +338,7 @@ func (b *Block) DecodeRLP(s *rlp.Stream) error {
if err := s.Decode(&eb); err != nil {
return err
}
- b.header, b.uncles, b.transactions, b.withdrawals = eb.Header, eb.Uncles, eb.Txs, eb.Withdrawals
+ b.header, b.uncles, b.transactions, b.withdrawals, b.requests = eb.Header, eb.Uncles, eb.Txs, eb.Withdrawals, eb.Requests
b.size.Store(rlp.ListSize(size))
return nil
}
@@ -324,13 +350,14 @@ func (b *Block) EncodeRLP(w io.Writer) error {
Txs: b.transactions,
Uncles: b.uncles,
Withdrawals: b.withdrawals,
+ Requests: b.requests,
})
}
// Body returns the non-header content of the block.
// Note the returned data is not an independent copy.
func (b *Block) Body() *Body {
- return &Body{b.transactions, b.uncles, b.withdrawals}
+ return &Body{b.transactions, b.uncles, b.withdrawals, b.requests}
}
// Accessors for body data. These do not return a copy because the content
@@ -339,6 +366,7 @@ func (b *Block) Body() *Body {
func (b *Block) Uncles() []*Header { return b.uncles }
func (b *Block) Transactions() Transactions { return b.transactions }
func (b *Block) Withdrawals() Withdrawals { return b.withdrawals }
+func (b *Block) Requests() Requests { return b.requests }
func (b *Block) Transaction(hash common.Hash) *Transaction {
for _, transaction := range b.transactions {
@@ -459,6 +487,7 @@ func (b *Block) WithBody(body Body) *Block {
transactions: slices.Clone(body.Transactions),
uncles: make([]*Header, len(body.Uncles)),
withdrawals: slices.Clone(body.Withdrawals),
+ requests: slices.Clone(body.Requests),
}
for i := range body.Uncles {
block.uncles[i] = CopyHeader(body.Uncles[i])
diff --git a/core/types/deposit.go b/core/types/deposit.go
new file mode 100644
index 000000000000..172acc36ed3e
--- /dev/null
+++ b/core/types/deposit.go
@@ -0,0 +1,103 @@
+// 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"
+ "fmt"
+
+ "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 Deposit -field-override depositMarshaling -out gen_deposit_json.go
+
+// Deposit contains EIP-6110 deposit data.
+type Deposit struct {
+ PublicKey [48]byte `json:"pubkey"` // public key of validator
+ WithdrawalCredentials common.Hash `json:"withdrawalCredentials"` // beneficiary of the validator funds
+ Amount uint64 `json:"amount"` // deposit size in Gwei
+ Signature [96]byte `json:"signature"` // signature over deposit msg
+ Index uint64 `json:"index"` // deposit count value
+}
+
+// field type overrides for gencodec
+type depositMarshaling struct {
+ PublicKey hexutil.Bytes
+ WithdrawalCredentials hexutil.Bytes
+ Amount hexutil.Uint64
+ Signature hexutil.Bytes
+ Index hexutil.Uint64
+}
+
+// Deposits implements DerivableList for requests.
+type Deposits []*Deposit
+
+// Len returns the length of s.
+func (s Deposits) Len() int { return len(s) }
+
+// EncodeIndex encodes the i'th deposit to s.
+func (s Deposits) EncodeIndex(i int, w *bytes.Buffer) {
+ rlp.Encode(w, s[i])
+}
+
+// UnpackIntoDeposit unpacks a serialized DepositEvent.
+func UnpackIntoDeposit(data []byte) (*Deposit, error) {
+ if len(data) != 576 {
+ return nil, fmt.Errorf("deposit wrong length: want 576, have %d", len(data))
+ }
+ var d Deposit
+ // The ABI encodes the position of dynamic elements first. Since there are 5
+ // elements, skip over the positional data. The first 32 bytes of dynamic
+ // elements also encode their actual length. Skip over that value too.
+ b := 32*5 + 32
+ // PublicKey is the first element. ABI encoding pads values to 32 bytes, so
+ // despite BLS public keys being length 48, the value length here is 64. Then
+ // skip over the next length value.
+ copy(d.PublicKey[:], data[b:b+48])
+ b += 48 + 16 + 32
+ // WithdrawalCredentials is 32 bytes. Read that value then skip over next
+ // length.
+ copy(d.WithdrawalCredentials[:], data[b:b+32])
+ b += 32 + 32
+ // Amount is 8 bytes, but it is padded to 32. Skip over it and the next
+ // length.
+ d.Amount = binary.LittleEndian.Uint64(data[b : b+8])
+ b += 8 + 24 + 32
+ // Signature is 96 bytes. Skip over it and the next length.
+ copy(d.Signature[:], data[b:b+96])
+ b += 96 + 32
+ // Amount is 8 bytes.
+ d.Index = binary.LittleEndian.Uint64(data[b : b+8])
+
+ return &d, nil
+}
+
+func (d *Deposit) requestType() byte { return DepositRequestType }
+func (d *Deposit) encode(b *bytes.Buffer) error { return rlp.Encode(b, d) }
+func (d *Deposit) decode(input []byte) error { return rlp.DecodeBytes(input, d) }
+func (d *Deposit) copy() RequestData {
+ return &Deposit{
+ PublicKey: d.PublicKey,
+ WithdrawalCredentials: d.WithdrawalCredentials,
+ Amount: d.Amount,
+ Signature: d.Signature,
+ Index: d.Index,
+ }
+}
diff --git a/core/types/deposit_test.go b/core/types/deposit_test.go
new file mode 100644
index 000000000000..ed2e18445d3f
--- /dev/null
+++ b/core/types/deposit_test.go
@@ -0,0 +1,93 @@
+// 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 (
+ "encoding/binary"
+ "reflect"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/accounts/abi"
+ "github.com/ethereum/go-ethereum/common"
+)
+
+var (
+ depositABI = abi.ABI{Methods: map[string]abi.Method{"DepositEvent": depositEvent}}
+ bytesT, _ = abi.NewType("bytes", "", nil)
+ depositEvent = abi.NewMethod("DepositEvent", "DepositEvent", abi.Function, "", false, false, []abi.Argument{
+ {Name: "pubkey", Type: bytesT, Indexed: false},
+ {Name: "withdrawal_credentials", Type: bytesT, Indexed: false},
+ {Name: "amount", Type: bytesT, Indexed: false},
+ {Name: "signature", Type: bytesT, Indexed: false},
+ {Name: "index", Type: bytesT, Indexed: false}}, nil,
+ )
+)
+
+// FuzzUnpackIntoDeposit tries roundtrip packing and unpacking of deposit events.
+func FuzzUnpackIntoDeposit(f *testing.F) {
+ for _, tt := range []struct {
+ pubkey string
+ wxCred string
+ amount string
+ sig string
+ index string
+ }{
+ {
+ pubkey: "111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111",
+ wxCred: "2222222222222222222222222222222222222222222222222222222222222222",
+ amount: "3333333333333333",
+ sig: "444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444",
+ index: "5555555555555555",
+ },
+ } {
+ f.Add(common.FromHex(tt.pubkey), common.FromHex(tt.wxCred), common.FromHex(tt.amount), common.FromHex(tt.sig), common.FromHex(tt.index))
+ }
+
+ f.Fuzz(func(t *testing.T, p []byte, w []byte, a []byte, s []byte, i []byte) {
+ var (
+ pubkey [48]byte
+ wxCred [32]byte
+ amount [8]byte
+ sig [96]byte
+ index [8]byte
+ )
+ copy(pubkey[:], p)
+ copy(wxCred[:], w)
+ copy(amount[:], a)
+ copy(sig[:], s)
+ copy(index[:], i)
+
+ want := Deposit{
+ PublicKey: pubkey,
+ WithdrawalCredentials: wxCred,
+ Amount: binary.LittleEndian.Uint64(amount[:]),
+ Signature: sig,
+ Index: binary.LittleEndian.Uint64(index[:]),
+ }
+ out, err := depositABI.Pack("DepositEvent", want.PublicKey[:], want.WithdrawalCredentials[:], amount[:], want.Signature[:], index[:])
+ if err != nil {
+ t.Fatalf("error packing deposit: %v", err)
+ }
+ got, err := UnpackIntoDeposit(out[4:])
+ if err != nil {
+ t.Errorf("error unpacking deposit: %v", err)
+ }
+ if !reflect.DeepEqual(want, *got) {
+ t.Errorf("roundtrip failed: want %v, got %v", want, got)
+ }
+ })
+}
diff --git a/core/types/gen_deposit_json.go b/core/types/gen_deposit_json.go
new file mode 100644
index 000000000000..a65691188f58
--- /dev/null
+++ b/core/types/gen_deposit_json.go
@@ -0,0 +1,70 @@
+// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
+
+package types
+
+import (
+ "encoding/json"
+ "errors"
+
+ "github.com/ethereum/go-ethereum/common/hexutil"
+)
+
+var _ = (*depositMarshaling)(nil)
+
+// MarshalJSON marshals as JSON.
+func (d Deposit) MarshalJSON() ([]byte, error) {
+ type Deposit struct {
+ PublicKey hexutil.Bytes `json:"pubkey"`
+ WithdrawalCredentials hexutil.Bytes `json:"withdrawalCredentials"`
+ Amount hexutil.Uint64 `json:"amount"`
+ Signature hexutil.Bytes `json:"signature"`
+ Index hexutil.Uint64 `json:"index"`
+ }
+ var enc Deposit
+ enc.PublicKey = d.PublicKey[:]
+ enc.WithdrawalCredentials = d.WithdrawalCredentials[:]
+ enc.Amount = hexutil.Uint64(d.Amount)
+ enc.Signature = d.Signature[:]
+ enc.Index = hexutil.Uint64(d.Index)
+ return json.Marshal(&enc)
+}
+
+// UnmarshalJSON unmarshals from JSON.
+func (d *Deposit) UnmarshalJSON(input []byte) error {
+ type Deposit struct {
+ PublicKey *hexutil.Bytes `json:"pubkey"`
+ WithdrawalCredentials *hexutil.Bytes `json:"withdrawalCredentials"`
+ Amount *hexutil.Uint64 `json:"amount"`
+ Signature *hexutil.Bytes `json:"signature"`
+ Index *hexutil.Uint64 `json:"index"`
+ }
+ var dec Deposit
+ if err := json.Unmarshal(input, &dec); err != nil {
+ return err
+ }
+ if dec.PublicKey != nil {
+ if len(*dec.PublicKey) != len(d.PublicKey) {
+ return errors.New("field 'pubkey' has wrong length, need 48 items")
+ }
+ copy(d.PublicKey[:], *dec.PublicKey)
+ }
+ if dec.WithdrawalCredentials != nil {
+ if len(*dec.WithdrawalCredentials) != len(d.WithdrawalCredentials) {
+ return errors.New("field 'withdrawalCredentials' has wrong length, need 32 items")
+ }
+ copy(d.WithdrawalCredentials[:], *dec.WithdrawalCredentials)
+ }
+ if dec.Amount != nil {
+ d.Amount = uint64(*dec.Amount)
+ }
+ if dec.Signature != nil {
+ if len(*dec.Signature) != len(d.Signature) {
+ return errors.New("field 'signature' has wrong length, need 96 items")
+ }
+ copy(d.Signature[:], *dec.Signature)
+ }
+ if dec.Index != nil {
+ d.Index = uint64(*dec.Index)
+ }
+ return nil
+}
diff --git a/core/types/gen_header_json.go b/core/types/gen_header_json.go
index fb1f915d01d9..028507f4be43 100644
--- a/core/types/gen_header_json.go
+++ b/core/types/gen_header_json.go
@@ -36,6 +36,8 @@ func (h Header) MarshalJSON() ([]byte, error) {
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed" rlp:"optional"`
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"`
ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
+ RequestsHash *common.Hash `json:"requestsRoot" rlp:"optional"`
+ TargetBlobCount *uint64 `json:"targetBlobCount" rlp:"optional"`
Hash common.Hash `json:"hash"`
}
var enc Header
@@ -59,6 +61,8 @@ func (h Header) MarshalJSON() ([]byte, error) {
enc.BlobGasUsed = (*hexutil.Uint64)(h.BlobGasUsed)
enc.ExcessBlobGas = (*hexutil.Uint64)(h.ExcessBlobGas)
enc.ParentBeaconRoot = h.ParentBeaconRoot
+ enc.RequestsHash = h.RequestsHash
+ enc.TargetBlobCount = h.TargetBlobCount
enc.Hash = h.Hash()
return json.Marshal(&enc)
}
@@ -86,6 +90,8 @@ func (h *Header) UnmarshalJSON(input []byte) error {
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed" rlp:"optional"`
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"`
ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
+ RequestsHash *common.Hash `json:"requestsRoot" rlp:"optional"`
+ TargetBlobCount *uint64 `json:"targetBlobCount" rlp:"optional"`
}
var dec Header
if err := json.Unmarshal(input, &dec); err != nil {
@@ -163,5 +169,11 @@ func (h *Header) UnmarshalJSON(input []byte) error {
if dec.ParentBeaconRoot != nil {
h.ParentBeaconRoot = dec.ParentBeaconRoot
}
+ if dec.RequestsHash != nil {
+ h.RequestsHash = dec.RequestsHash
+ }
+ if dec.TargetBlobCount != nil {
+ h.TargetBlobCount = dec.TargetBlobCount
+ }
return nil
}
diff --git a/core/types/gen_header_rlp.go b/core/types/gen_header_rlp.go
index ed6a1a002cdb..b25f26a2eac6 100644
--- a/core/types/gen_header_rlp.go
+++ b/core/types/gen_header_rlp.go
@@ -42,7 +42,9 @@ func (obj *Header) EncodeRLP(_w io.Writer) error {
_tmp3 := obj.BlobGasUsed != nil
_tmp4 := obj.ExcessBlobGas != nil
_tmp5 := obj.ParentBeaconRoot != nil
- if _tmp1 || _tmp2 || _tmp3 || _tmp4 || _tmp5 {
+ _tmp6 := obj.RequestsHash != nil
+ _tmp7 := obj.TargetBlobCount != nil
+ if _tmp1 || _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 {
if obj.BaseFee == nil {
w.Write(rlp.EmptyString)
} else {
@@ -52,34 +54,48 @@ func (obj *Header) EncodeRLP(_w io.Writer) error {
w.WriteBigInt(obj.BaseFee)
}
}
- if _tmp2 || _tmp3 || _tmp4 || _tmp5 {
+ if _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 {
if obj.WithdrawalsHash == nil {
w.Write([]byte{0x80})
} else {
w.WriteBytes(obj.WithdrawalsHash[:])
}
}
- if _tmp3 || _tmp4 || _tmp5 {
+ if _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 {
if obj.BlobGasUsed == nil {
w.Write([]byte{0x80})
} else {
w.WriteUint64((*obj.BlobGasUsed))
}
}
- if _tmp4 || _tmp5 {
+ if _tmp4 || _tmp5 || _tmp6 || _tmp7 {
if obj.ExcessBlobGas == nil {
w.Write([]byte{0x80})
} else {
w.WriteUint64((*obj.ExcessBlobGas))
}
}
- if _tmp5 {
+ if _tmp5 || _tmp6 || _tmp7 {
if obj.ParentBeaconRoot == nil {
w.Write([]byte{0x80})
} else {
w.WriteBytes(obj.ParentBeaconRoot[:])
}
}
+ if _tmp6 || _tmp7 {
+ if obj.RequestsHash == nil {
+ w.Write([]byte{0x80})
+ } else {
+ w.WriteBytes(obj.RequestsHash[:])
+ }
+ }
+ if _tmp7 {
+ if obj.TargetBlobCount == nil {
+ w.Write([]byte{0x80})
+ } else {
+ w.WriteUint64((*obj.TargetBlobCount))
+ }
+ }
w.ListEnd(_tmp0)
return w.Flush()
}
diff --git a/core/types/hashes.go b/core/types/hashes.go
index 43e9130fd170..cbd197072e5e 100644
--- a/core/types/hashes.go
+++ b/core/types/hashes.go
@@ -41,6 +41,9 @@ var (
// EmptyWithdrawalsHash is the known hash of the empty withdrawal set.
EmptyWithdrawalsHash = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
+ // EmptyRequestsHash is the known hash of the empty requests set.
+ EmptyRequestsHash = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
+
// EmptyVerkleHash is the known hash of an empty verkle trie.
EmptyVerkleHash = common.Hash{}
)
diff --git a/core/types/request.go b/core/types/request.go
new file mode 100644
index 000000000000..306a2a10b713
--- /dev/null
+++ b/core/types/request.go
@@ -0,0 +1,157 @@
+// 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"
+ "errors"
+ "fmt"
+ "io"
+
+ "github.com/ethereum/go-ethereum/rlp"
+)
+
+var (
+ ErrRequestTypeNotSupported = errors.New("request type not supported")
+ errShortTypedRequest = errors.New("typed request too short")
+)
+
+// Request types.
+const (
+ DepositRequestType = 0x00
+)
+
+// Request is an EIP-7685 request object. It represents execution layer
+// triggered messages bound for the consesus layer.
+type Request struct {
+ inner RequestData
+}
+
+// Type returns the EIP-7685 type of the request.
+func (r *Request) Type() byte {
+ return r.inner.requestType()
+}
+
+// Inner returns the inner request data.
+func (r *Request) Inner() RequestData {
+ return r.inner
+}
+
+// NewRequest creates a new request.
+func NewRequest(inner RequestData) *Request {
+ req := new(Request)
+ req.inner = inner.copy()
+ return req
+}
+
+// Requests implements DerivableList for requests.
+type Requests []*Request
+
+// Len returns the length of s.
+func (s Requests) Len() int { return len(s) }
+
+// EncodeIndex encodes the i'th request to s.
+func (s Requests) EncodeIndex(i int, w *bytes.Buffer) {
+ s[i].encode(w)
+}
+
+// RequestData is the underlying data of a request.
+type RequestData interface {
+ requestType() byte
+ encode(*bytes.Buffer) error
+ decode([]byte) error
+ copy() RequestData // creates a deep copy and initializes all fields
+}
+
+// EncodeRLP implements rlp.Encoder
+func (r *Request) EncodeRLP(w io.Writer) error {
+ buf := encodeBufferPool.Get().(*bytes.Buffer)
+ defer encodeBufferPool.Put(buf)
+ buf.Reset()
+ if err := r.encode(buf); err != nil {
+ return err
+ }
+ return rlp.Encode(w, buf.Bytes())
+}
+
+// encode writes the canonical encoding of a request to w.
+func (r *Request) encode(w *bytes.Buffer) error {
+ w.WriteByte(r.Type())
+ return r.inner.encode(w)
+}
+
+// MarshalBinary returns the canonical encoding of the request.
+func (r *Request) MarshalBinary() ([]byte, error) {
+ var buf bytes.Buffer
+ err := r.encode(&buf)
+ return buf.Bytes(), err
+}
+
+// DecodeRLP implements rlp.Decoder
+func (r *Request) DecodeRLP(s *rlp.Stream) error {
+ kind, size, err := s.Kind()
+ switch {
+ case err != nil:
+ return err
+ case kind == rlp.List:
+ return fmt.Errorf("untyped request")
+ case kind == rlp.Byte:
+ return errShortTypedRequest
+ default:
+ // First read the request payload bytes into a temporary buffer.
+ b, buf, err := getPooledBuffer(size)
+ if err != nil {
+ return err
+ }
+ defer encodeBufferPool.Put(buf)
+ if err := s.ReadBytes(b); err != nil {
+ return err
+ }
+ // Now decode the inner request.
+ inner, err := r.decode(b)
+ if err == nil {
+ r.inner = inner
+ }
+ return err
+ }
+}
+
+// UnmarshalBinary decodes the canonical encoding of requests.
+func (r *Request) UnmarshalBinary(b []byte) error {
+ inner, err := r.decode(b)
+ if err != nil {
+ return err
+ }
+ r.inner = inner
+ return nil
+}
+
+// decode decodes a request from the canonical format.
+func (r *Request) decode(b []byte) (RequestData, error) {
+ if len(b) <= 1 {
+ return nil, errShortTypedRequest
+ }
+ var inner RequestData
+ switch b[0] {
+ case DepositRequestType:
+ inner = new(Deposit)
+ default:
+ return nil, ErrRequestTypeNotSupported
+ }
+ err := inner.decode(b[1:])
+ return inner, err
+}
diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go
index 5a001e1ee836..f5ba4a2d008c 100644
--- a/eth/catalyst/api.go
+++ b/eth/catalyst/api.go
@@ -82,15 +82,20 @@ var caps = []string{
"engine_forkchoiceUpdatedV1",
"engine_forkchoiceUpdatedV2",
"engine_forkchoiceUpdatedV3",
+ "engine_forkchoiceUpdatedV4",
"engine_exchangeTransitionConfigurationV1",
"engine_getPayloadV1",
"engine_getPayloadV2",
"engine_getPayloadV3",
+ "engine_getPayloadV4",
"engine_newPayloadV1",
"engine_newPayloadV2",
"engine_newPayloadV3",
+ "engine_newPayloadV4",
"engine_getPayloadBodiesByHashV1",
+ "engine_getPayloadBodiesByHashV2",
"engine_getPayloadBodiesByRangeV1",
+ "engine_getPayloadBodiesByRangeV2",
"engine_getClientVersionV1",
}
@@ -220,7 +225,7 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV3(update engine.ForkchoiceStateV1, pa
if params.BeaconRoot == nil {
return engine.STATUS_INVALID, engine.InvalidPayloadAttributes.With(errors.New("missing beacon root"))
}
- if api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Cancun {
+ if api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Cancun && api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Prague {
return engine.STATUS_INVALID, engine.UnsupportedFork.With(errors.New("forkchoiceUpdatedV3 must only be called for cancun payloads"))
}
}
@@ -231,6 +236,26 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV3(update engine.ForkchoiceStateV1, pa
return api.forkchoiceUpdated(update, params, engine.PayloadV3, false)
}
+// ForkchoiceUpdatedV4 is equivalent to V3 with the addition of target blob
+// count in the payload attributes. It supports only PayloadAttributesV4.
+func (api *ConsensusAPI) ForkchoiceUpdatedV4(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
+ if params != nil {
+ if params.Withdrawals == nil {
+ return engine.STATUS_INVALID, engine.InvalidPayloadAttributes.With(errors.New("missing withdrawals"))
+ }
+ if params.BeaconRoot == nil {
+ return engine.STATUS_INVALID, engine.InvalidPayloadAttributes.With(errors.New("missing beacon root"))
+ }
+ if params.TargetBlobCount == nil {
+ return engine.STATUS_INVALID, engine.InvalidPayloadAttributes.With(errors.New("missing target blob count"))
+ }
+ if api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Prague && api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Prague {
+ return engine.STATUS_INVALID, engine.UnsupportedFork.With(errors.New("forkchoiceUpdatedV4 must only be called for prague payloads"))
+ }
+ }
+ return api.forkchoiceUpdated(update, params, engine.PayloadV4, false)
+}
+
func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes, payloadVersion engine.PayloadVersion, simulatorMode bool) (engine.ForkChoiceResponse, error) {
api.forkchoiceLock.Lock()
defer api.forkchoiceLock.Unlock()
@@ -456,6 +481,14 @@ func (api *ConsensusAPI) GetPayloadV3(payloadID engine.PayloadID) (*engine.Execu
return api.getPayload(payloadID, false)
}
+// GetPayloadV4 returns a cached payload by id.
+func (api *ConsensusAPI) GetPayloadV4(payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) {
+ if !payloadID.Is(engine.PayloadV4) {
+ return nil, engine.UnsupportedFork
+ }
+ return api.getPayload(payloadID, false)
+}
+
func (api *ConsensusAPI) getPayload(payloadID engine.PayloadID, full bool) (*engine.ExecutionPayloadEnvelope, error) {
log.Trace("Engine API request received", "method", "GetPayload", "id", payloadID)
data := api.localBlocks.get(payloadID, full)
@@ -470,7 +503,7 @@ func (api *ConsensusAPI) NewPayloadV1(params engine.ExecutableData) (engine.Payl
if params.Withdrawals != nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("withdrawals not supported in V1"))
}
- return api.newPayload(params, nil, nil)
+ return api.newPayload(params, nil, nil, nil)
}
// NewPayloadV2 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
@@ -493,7 +526,7 @@ func (api *ConsensusAPI) NewPayloadV2(params engine.ExecutableData) (engine.Payl
if params.BlobGasUsed != nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("non-nil blobGasUsed pre-cancun"))
}
- return api.newPayload(params, nil, nil)
+ return api.newPayload(params, nil, nil, nil)
}
// NewPayloadV3 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
@@ -518,10 +551,38 @@ func (api *ConsensusAPI) NewPayloadV3(params engine.ExecutableData, versionedHas
if api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Cancun {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.UnsupportedFork.With(errors.New("newPayloadV3 must only be called for cancun payloads"))
}
- return api.newPayload(params, versionedHashes, beaconRoot)
+ return api.newPayload(params, versionedHashes, beaconRoot, nil)
}
-func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (engine.PayloadStatusV1, error) {
+// NewPayloadV4 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
+func (api *ConsensusAPI) NewPayloadV4(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, targetBlobCount uint64) (engine.PayloadStatusV1, error) {
+ if params.Withdrawals == nil {
+ return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil withdrawals post-shanghai"))
+ }
+ if params.ExcessBlobGas == nil {
+ return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil excessBlobGas post-cancun"))
+ }
+ if params.BlobGasUsed == nil {
+ return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil blobGasUsed post-cancun"))
+ }
+ if params.Deposits == nil {
+ return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil deposits post-prague"))
+ }
+
+ if versionedHashes == nil {
+ return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil versionedHashes post-cancun"))
+ }
+ if beaconRoot == nil {
+ return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil beaconRoot post-cancun"))
+ }
+
+ if api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Prague {
+ return engine.PayloadStatusV1{Status: engine.INVALID}, engine.UnsupportedFork.With(errors.New("newPayloadV4 must only be called for prague payloads"))
+ }
+ return api.newPayload(params, versionedHashes, beaconRoot, &targetBlobCount)
+}
+
+func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, targetBlobCount *uint64) (engine.PayloadStatusV1, error) {
// The locking here is, strictly, not required. Without these locks, this can happen:
//
// 1. NewPayload( execdata-N ) is invoked from the CL. It goes all the way down to
@@ -539,7 +600,7 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashe
defer api.newPayloadLock.Unlock()
log.Trace("Engine API request received", "method", "NewPayload", "number", params.Number, "hash", params.BlockHash)
- block, err := engine.ExecutableDataToBlock(params, versionedHashes, beaconRoot)
+ block, err := engine.ExecutableDataToBlock(params, versionedHashes, beaconRoot, targetBlobCount)
if err != nil {
bgu := "nil"
if params.BlobGasUsed != nil {
@@ -566,6 +627,7 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashe
"params.ExcessBlobGas", ebg,
"len(params.Transactions)", len(params.Transactions),
"len(params.Withdrawals)", len(params.Withdrawals),
+ "len(params.Deposits)", len(params.Deposits),
"beaconRoot", beaconRoot,
"error", err)
return api.invalid(err, nil), nil
@@ -839,8 +901,25 @@ func (api *ConsensusAPI) GetClientVersionV1(info engine.ClientVersionV1) []engin
// GetPayloadBodiesByHashV1 implements engine_getPayloadBodiesByHashV1 which allows for retrieval of a list
// of block bodies by the engine api.
-func (api *ConsensusAPI) GetPayloadBodiesByHashV1(hashes []common.Hash) []*engine.ExecutionPayloadBodyV1 {
- bodies := make([]*engine.ExecutionPayloadBodyV1, len(hashes))
+func (api *ConsensusAPI) GetPayloadBodiesByHashV1(hashes []common.Hash) []*engine.ExecutionPayloadBody {
+ bodies := make([]*engine.ExecutionPayloadBody, len(hashes))
+ for i, hash := range hashes {
+ block := api.eth.BlockChain().GetBlockByHash(hash)
+ body := getBody(block)
+ if body != nil {
+ // Nil out the V2 values, clients should know to not request V1 objects
+ // after Prague.
+ body.Deposits = nil
+ }
+ bodies[i] = body
+ }
+ return bodies
+}
+
+// GetPayloadBodiesByHashV2 implements engine_getPayloadBodiesByHashV1 which allows for retrieval of a list
+// of block bodies by the engine api.
+func (api *ConsensusAPI) GetPayloadBodiesByHashV2(hashes []common.Hash) []*engine.ExecutionPayloadBody {
+ bodies := make([]*engine.ExecutionPayloadBody, len(hashes))
for i, hash := range hashes {
block := api.eth.BlockChain().GetBlockByHash(hash)
bodies[i] = getBody(block)
@@ -850,7 +929,28 @@ func (api *ConsensusAPI) GetPayloadBodiesByHashV1(hashes []common.Hash) []*engin
// GetPayloadBodiesByRangeV1 implements engine_getPayloadBodiesByRangeV1 which allows for retrieval of a range
// of block bodies by the engine api.
-func (api *ConsensusAPI) GetPayloadBodiesByRangeV1(start, count hexutil.Uint64) ([]*engine.ExecutionPayloadBodyV1, error) {
+func (api *ConsensusAPI) GetPayloadBodiesByRangeV1(start, count hexutil.Uint64) ([]*engine.ExecutionPayloadBody, error) {
+ bodies, err := api.getBodiesByRange(start, count)
+ if err != nil {
+ return nil, err
+ }
+ // Nil out the V2 values, clients should know to not request V1 objects
+ // after Prague.
+ for i := range bodies {
+ if bodies[i] != nil {
+ bodies[i].Deposits = nil
+ }
+ }
+ return bodies, nil
+}
+
+// GetPayloadBodiesByRangeV2 implements engine_getPayloadBodiesByRangeV1 which allows for retrieval of a range
+// of block bodies by the engine api.
+func (api *ConsensusAPI) GetPayloadBodiesByRangeV2(start, count hexutil.Uint64) ([]*engine.ExecutionPayloadBody, error) {
+ return api.getBodiesByRange(start, count)
+}
+
+func (api *ConsensusAPI) getBodiesByRange(start, count hexutil.Uint64) ([]*engine.ExecutionPayloadBody, error) {
if start == 0 || count == 0 {
return nil, engine.InvalidParams.With(fmt.Errorf("invalid start or count, start: %v count: %v", start, count))
}
@@ -863,7 +963,7 @@ func (api *ConsensusAPI) GetPayloadBodiesByRangeV1(start, count hexutil.Uint64)
if last > current {
last = current
}
- bodies := make([]*engine.ExecutionPayloadBodyV1, 0, uint64(count))
+ bodies := make([]*engine.ExecutionPayloadBody, 0, uint64(count))
for i := uint64(start); i <= last; i++ {
block := api.eth.BlockChain().GetBlockByNumber(i)
bodies = append(bodies, getBody(block))
@@ -871,15 +971,16 @@ func (api *ConsensusAPI) GetPayloadBodiesByRangeV1(start, count hexutil.Uint64)
return bodies, nil
}
-func getBody(block *types.Block) *engine.ExecutionPayloadBodyV1 {
+func getBody(block *types.Block) *engine.ExecutionPayloadBody {
if block == nil {
return nil
}
var (
- body = block.Body()
- txs = make([]hexutil.Bytes, len(body.Transactions))
- withdrawals = body.Withdrawals
+ body = block.Body()
+ txs = make([]hexutil.Bytes, len(body.Transactions))
+ withdrawals = body.Withdrawals
+ depositRequests types.Deposits
)
for j, tx := range body.Transactions {
@@ -891,8 +992,20 @@ func getBody(block *types.Block) *engine.ExecutionPayloadBodyV1 {
withdrawals = make([]*types.Withdrawal, 0)
}
- return &engine.ExecutionPayloadBodyV1{
+ if block.Header().RequestsHash != nil {
+ // 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.
+ for _, req := range block.Requests() {
+ if d, ok := req.Inner().(*types.Deposit); ok {
+ depositRequests = append(depositRequests, d)
+ }
+ }
+ }
+
+ return &engine.ExecutionPayloadBody{
TransactionData: txs,
Withdrawals: withdrawals,
+ Deposits: depositRequests,
}
}
diff --git a/eth/catalyst/api_test.go b/eth/catalyst/api_test.go
index 64e6684be155..cd6b948c23b2 100644
--- a/eth/catalyst/api_test.go
+++ b/eth/catalyst/api_test.go
@@ -74,6 +74,12 @@ func generateMergeChain(n int, merged bool) (*core.Genesis, []*types.Block) {
Alloc: types.GenesisAlloc{
testAddr: {Balance: testBalance},
params.BeaconRootsAddress: {Balance: common.Big0, Code: common.Hex2Bytes("3373fffffffffffffffffffffffffffffffffffffffe14604457602036146024575f5ffd5b620180005f350680545f35146037575f5ffd5b6201800001545f5260205ff35b6201800042064281555f359062018000015500")},
+ config.DepositContractAddress: {
+ // Simple deposit generator, source: https://gist.github.com/lightclient/54abb2af2465d6969fa6d1920b9ad9d7
+ Code: common.Hex2Bytes("6080604052366103aa575f603067ffffffffffffffff811115610025576100246103ae565b5b6040519080825280601f01601f1916602001820160405280156100575781602001600182028036833780820191505090505b5090505f8054906101000a900460ff1660f81b815f8151811061007d5761007c6103db565b5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690815f1a9053505f602067ffffffffffffffff8111156100c7576100c66103ae565b5b6040519080825280601f01601f1916602001820160405280156100f95781602001600182028036833780820191505090505b5090505f8054906101000a900460ff1660f81b815f8151811061011f5761011e6103db565b5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690815f1a9053505f600867ffffffffffffffff811115610169576101686103ae565b5b6040519080825280601f01601f19166020018201604052801561019b5781602001600182028036833780820191505090505b5090505f8054906101000a900460ff1660f81b815f815181106101c1576101c06103db565b5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690815f1a9053505f606067ffffffffffffffff81111561020b5761020a6103ae565b5b6040519080825280601f01601f19166020018201604052801561023d5781602001600182028036833780820191505090505b5090505f8054906101000a900460ff1660f81b815f81518110610263576102626103db565b5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690815f1a9053505f600867ffffffffffffffff8111156102ad576102ac6103ae565b5b6040519080825280601f01601f1916602001820160405280156102df5781602001600182028036833780820191505090505b5090505f8054906101000a900460ff1660f81b815f81518110610305576103046103db565b5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690815f1a9053505f8081819054906101000a900460ff168092919061035090610441565b91906101000a81548160ff021916908360ff160217905550507f649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c585858585856040516103a09594939291906104d9565b60405180910390a1005b5f80fd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f60ff82169050919050565b5f61044b82610435565b915060ff820361045e5761045d610408565b5b600182019050919050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f6104ab82610469565b6104b58185610473565b93506104c5818560208601610483565b6104ce81610491565b840191505092915050565b5f60a0820190508181035f8301526104f181886104a1565b9050818103602083015261050581876104a1565b9050818103604083015261051981866104a1565b9050818103606083015261052d81856104a1565b9050818103608083015261054181846104a1565b9050969550505050505056fea26469706673582212208569967e58690162d7d6fe3513d07b393b4c15e70f41505cbbfd08f53eba739364736f6c63430008190033"),
+ Nonce: 0,
+ Balance: big.NewInt(0),
+ },
},
ExtraData: []byte("test genesis"),
Timestamp: 9000,
@@ -318,7 +324,7 @@ func TestEth2NewBlock(t *testing.T) {
if err != nil {
t.Fatalf("Failed to create the executable data, block %d: %v", i, err)
}
- block, err := engine.ExecutableDataToBlock(*execData, nil, nil)
+ block, err := engine.ExecutableDataToBlock(*execData, nil, nil, nil)
if err != nil {
t.Fatalf("Failed to convert executable data to block %v", err)
}
@@ -360,7 +366,7 @@ func TestEth2NewBlock(t *testing.T) {
if err != nil {
t.Fatalf("Failed to create the executable data %v", err)
}
- block, err := engine.ExecutableDataToBlock(*execData, nil, nil)
+ block, err := engine.ExecutableDataToBlock(*execData, nil, nil, nil)
if err != nil {
t.Fatalf("Failed to convert executable data to block %v", err)
}
@@ -483,10 +489,10 @@ func TestFullAPI(t *testing.T) {
ethservice.TxPool().Add([]*types.Transaction{tx}, true, false)
}
- setupBlocks(t, ethservice, 10, parent, callback, nil)
+ setupBlocks(t, ethservice, 10, parent, callback, nil, nil, nil)
}
-func setupBlocks(t *testing.T, ethservice *eth.Ethereum, n int, parent *types.Header, callback func(parent *types.Header), withdrawals [][]*types.Withdrawal) []*types.Header {
+func setupBlocks(t *testing.T, ethservice *eth.Ethereum, n int, parent *types.Header, callback func(parent *types.Header), withdrawals [][]*types.Withdrawal, beaconRoots []common.Hash, targetBlobCount *uint64) []*types.Header {
api := NewConsensusAPI(ethservice)
var blocks []*types.Header
for i := 0; i < n; i++ {
@@ -495,14 +501,18 @@ func setupBlocks(t *testing.T, ethservice *eth.Ethereum, n int, parent *types.He
if withdrawals != nil {
w = withdrawals[i]
}
+ var h *common.Hash
+ if beaconRoots != nil {
+ h = &beaconRoots[i]
+ }
- payload := getNewPayload(t, api, parent, w)
- execResp, err := api.NewPayloadV2(*payload)
+ payload := getNewPayload(t, api, parent, w, h, targetBlobCount)
+ execResp, err := api.newPayload(*payload, []common.Hash{}, h, targetBlobCount)
if err != nil {
t.Fatalf("can't execute payload: %v", err)
}
if execResp.Status != engine.VALID {
- t.Fatalf("invalid status: %v", execResp.Status)
+ t.Fatalf("invalid status: %v %s", execResp.Status, *execResp.ValidationError)
}
fcState := engine.ForkchoiceStateV1{
HeadBlockHash: payload.BlockHash,
@@ -667,12 +677,13 @@ func TestNewPayloadOnInvalidChain(t *testing.T) {
func assembleBlock(api *ConsensusAPI, parentHash common.Hash, params *engine.PayloadAttributes) (*engine.ExecutableData, error) {
args := &miner.BuildPayloadArgs{
- Parent: parentHash,
- Timestamp: params.Timestamp,
- FeeRecipient: params.SuggestedFeeRecipient,
- Random: params.Random,
- Withdrawals: params.Withdrawals,
- BeaconRoot: params.BeaconRoot,
+ Parent: parentHash,
+ Timestamp: params.Timestamp,
+ FeeRecipient: params.SuggestedFeeRecipient,
+ Random: params.Random,
+ Withdrawals: params.Withdrawals,
+ BeaconRoot: params.BeaconRoot,
+ TargetBlobCount: params.TargetBlobCount,
}
payload, err := api.eth.Miner().BuildPayload(args)
if err != nil {
@@ -690,10 +701,10 @@ func TestEmptyBlocks(t *testing.T) {
api := NewConsensusAPI(ethservice)
// Setup 10 blocks on the canonical chain
- setupBlocks(t, ethservice, 10, commonAncestor, func(parent *types.Header) {}, nil)
+ setupBlocks(t, ethservice, 10, commonAncestor, func(parent *types.Header) {}, nil, nil, nil)
// (1) check LatestValidHash by sending a normal payload (P1'')
- payload := getNewPayload(t, api, commonAncestor, nil)
+ payload := getNewPayload(t, api, commonAncestor, nil, nil, nil)
status, err := api.NewPayloadV1(*payload)
if err != nil {
@@ -707,7 +718,7 @@ func TestEmptyBlocks(t *testing.T) {
}
// (2) Now send P1' which is invalid
- payload = getNewPayload(t, api, commonAncestor, nil)
+ payload = getNewPayload(t, api, commonAncestor, nil, nil, nil)
payload.GasUsed += 1
payload = setBlockhash(payload)
// Now latestValidHash should be the common ancestor
@@ -725,7 +736,7 @@ func TestEmptyBlocks(t *testing.T) {
}
// (3) Now send a payload with unknown parent
- payload = getNewPayload(t, api, commonAncestor, nil)
+ payload = getNewPayload(t, api, commonAncestor, nil, nil, nil)
payload.ParentHash = common.Hash{1}
payload = setBlockhash(payload)
// Now latestValidHash should be the common ancestor
@@ -741,12 +752,14 @@ func TestEmptyBlocks(t *testing.T) {
}
}
-func getNewPayload(t *testing.T, api *ConsensusAPI, parent *types.Header, withdrawals []*types.Withdrawal) *engine.ExecutableData {
+func getNewPayload(t *testing.T, api *ConsensusAPI, parent *types.Header, withdrawals []*types.Withdrawal, beaconRoot *common.Hash, targetBlobCount *uint64) *engine.ExecutableData {
params := engine.PayloadAttributes{
Timestamp: parent.Time + 1,
Random: crypto.Keccak256Hash([]byte{byte(1)}),
SuggestedFeeRecipient: parent.Coinbase,
Withdrawals: withdrawals,
+ BeaconRoot: beaconRoot,
+ TargetBlobCount: targetBlobCount,
}
payload, err := assembleBlock(api, parent.Hash(), ¶ms)
@@ -814,7 +827,7 @@ func TestTrickRemoteBlockCache(t *testing.T) {
commonAncestor := ethserviceA.BlockChain().CurrentBlock()
// Setup 10 blocks on the canonical chain
- setupBlocks(t, ethserviceA, 10, commonAncestor, func(parent *types.Header) {}, nil)
+ setupBlocks(t, ethserviceA, 10, commonAncestor, func(parent *types.Header) {}, nil, nil, nil)
commonAncestor = ethserviceA.BlockChain().CurrentBlock()
var invalidChain []*engine.ExecutableData
@@ -823,7 +836,7 @@ func TestTrickRemoteBlockCache(t *testing.T) {
//invalidChain = append(invalidChain, payload1)
// create an invalid payload2 (P2)
- payload2 := getNewPayload(t, apiA, commonAncestor, nil)
+ payload2 := getNewPayload(t, apiA, commonAncestor, nil, nil, nil)
//payload2.ParentHash = payload1.BlockHash
payload2.GasUsed += 1
payload2 = setBlockhash(payload2)
@@ -832,7 +845,7 @@ func TestTrickRemoteBlockCache(t *testing.T) {
head := payload2
// create some valid payloads on top
for i := 0; i < 10; i++ {
- payload := getNewPayload(t, apiA, commonAncestor, nil)
+ payload := getNewPayload(t, apiA, commonAncestor, nil, nil, nil)
payload.ParentHash = head.BlockHash
payload = setBlockhash(payload)
invalidChain = append(invalidChain, payload)
@@ -869,10 +882,10 @@ func TestInvalidBloom(t *testing.T) {
api := NewConsensusAPI(ethservice)
// Setup 10 blocks on the canonical chain
- setupBlocks(t, ethservice, 10, commonAncestor, func(parent *types.Header) {}, nil)
+ setupBlocks(t, ethservice, 10, commonAncestor, func(parent *types.Header) {}, nil, nil, nil)
// (1) check LatestValidHash by sending a normal payload (P1'')
- payload := getNewPayload(t, api, commonAncestor, nil)
+ payload := getNewPayload(t, api, commonAncestor, nil, nil, nil)
payload.LogsBloom = append(payload.LogsBloom, byte(1))
status, err := api.NewPayloadV1(*payload)
if err != nil {
@@ -993,7 +1006,7 @@ func TestSimultaneousNewBlock(t *testing.T) {
t.Fatal(testErr)
}
}
- block, err := engine.ExecutableDataToBlock(*execData, nil, nil)
+ block, err := engine.ExecutableDataToBlock(*execData, nil, nil, nil)
if err != nil {
t.Fatalf("Failed to convert executable data to block %v", err)
}
@@ -1285,24 +1298,35 @@ func TestNilWithdrawals(t *testing.T) {
func setupBodies(t *testing.T) (*node.Node, *eth.Ethereum, []*types.Block) {
genesis, blocks := generateMergeChain(10, true)
- // enable shanghai on the last block
+
+ // Enable next forks on the last block.
time := blocks[len(blocks)-1].Header().Time + 1
genesis.Config.ShanghaiTime = &time
+ genesis.Config.CancunTime = &time
+ genesis.Config.PragueTime = &time
+
n, ethservice := startEthService(t, genesis, blocks)
var (
- parent = ethservice.BlockChain().CurrentBlock()
// This EVM code generates a log when the contract is created.
logCode = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00")
+ parent = ethservice.BlockChain().CurrentBlock()
)
+ // Each block, this callback will include two txs that generate body values like logs and requests.
callback := func(parent *types.Header) {
- statedb, _ := ethservice.BlockChain().StateAt(parent.Root)
- nonce := statedb.GetNonce(testAddr)
- tx, _ := types.SignTx(types.NewContractCreation(nonce, new(big.Int), 1000000, big.NewInt(2*params.InitialBaseFee), logCode), types.LatestSigner(ethservice.BlockChain().Config()), testKey)
- ethservice.TxPool().Add([]*types.Transaction{tx}, false, false)
+ 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)
+ )
+ ethservice.TxPool().Add([]*types.Transaction{tx1}, false, false)
+ ethservice.TxPool().Add([]*types.Transaction{tx2}, false, false)
}
+ // Make some withdrawals to include.
withdrawals := make([][]*types.Withdrawal, 10)
withdrawals[0] = nil // should be filtered out by miner
withdrawals[1] = make([]*types.Withdrawal, 0)
@@ -1314,12 +1338,21 @@ func setupBodies(t *testing.T) (*node.Node, *eth.Ethereum, []*types.Block) {
}
}
- postShanghaiHeaders := setupBlocks(t, ethservice, 10, parent, callback, withdrawals)
- postShanghaiBlocks := make([]*types.Block, len(postShanghaiHeaders))
- for i, header := range postShanghaiHeaders {
- postShanghaiBlocks[i] = ethservice.BlockChain().GetBlock(header.Hash(), header.Number.Uint64())
+ // Make beacon root update for each block.
+ beaconRoots := make([]common.Hash, 10)
+ for i := 0; i < 10; i++ {
+ beaconRoots[i] = common.Hash{byte(i)}
+ }
+
+ // Create the blocks.
+ target := uint64(2)
+ newHeaders := setupBlocks(t, ethservice, 10, parent, callback, withdrawals, beaconRoots, &target)
+ newBlocks := make([]*types.Block, len(newHeaders))
+ for i, header := range newHeaders {
+ newBlocks[i] = ethservice.BlockChain().GetBlock(header.Hash(), header.Number.Uint64())
}
- return n, ethservice, append(blocks, postShanghaiBlocks...)
+
+ return n, ethservice, append(blocks, newBlocks...)
}
func allHashes(blocks []*types.Block) []common.Hash {
@@ -1384,7 +1417,7 @@ func TestGetBlockBodiesByHash(t *testing.T) {
}
for k, test := range tests {
- result := api.GetPayloadBodiesByHashV1(test.hashes)
+ result := api.GetPayloadBodiesByHashV2(test.hashes)
for i, r := range result {
if !equalBody(test.results[i], r) {
t.Fatalf("test %v: invalid response: expected %+v got %+v", k, test.results[i], r)
@@ -1458,7 +1491,7 @@ func TestGetBlockBodiesByRange(t *testing.T) {
}
for k, test := range tests {
- result, err := api.GetPayloadBodiesByRangeV1(test.start, test.count)
+ result, err := api.GetPayloadBodiesByRangeV2(test.start, test.count)
if err != nil {
t.Fatal(err)
}
@@ -1509,7 +1542,7 @@ func TestGetBlockBodiesByRangeInvalidParams(t *testing.T) {
},
}
for i, tc := range tests {
- result, err := api.GetPayloadBodiesByRangeV1(tc.start, tc.count)
+ result, err := api.GetPayloadBodiesByRangeV2(tc.start, tc.count)
if err == nil {
t.Fatalf("test %d: expected error, got %v", i, result)
}
@@ -1519,7 +1552,7 @@ func TestGetBlockBodiesByRangeInvalidParams(t *testing.T) {
}
}
-func equalBody(a *types.Body, b *engine.ExecutionPayloadBodyV1) bool {
+func equalBody(a *types.Body, b *engine.ExecutionPayloadBody) bool {
if a == nil && b == nil {
return true
} else if a == nil || b == nil {
@@ -1534,7 +1567,23 @@ func equalBody(a *types.Body, b *engine.ExecutionPayloadBodyV1) bool {
return false
}
}
- return reflect.DeepEqual(a.Withdrawals, b.Withdrawals)
+
+ if !reflect.DeepEqual(a.Withdrawals, b.Withdrawals) {
+ return false
+ }
+
+ var deposits types.Deposits
+ 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)
+ }
+ }
+ return reflect.DeepEqual(deposits, b.Deposits)
}
func TestBlockToPayloadWithBlobs(t *testing.T) {
@@ -1569,7 +1618,7 @@ func TestBlockToPayloadWithBlobs(t *testing.T) {
if got := len(envelope.BlobsBundle.Blobs); got != want {
t.Fatalf("invalid number of blobs: got %v, want %v", got, want)
}
- _, err := engine.ExecutableDataToBlock(*envelope.ExecutionPayload, make([]common.Hash, 1), nil)
+ _, err := engine.ExecutableDataToBlock(*envelope.ExecutionPayload, make([]common.Hash, 1), nil, nil)
if err != nil {
t.Error(err)
}
diff --git a/eth/catalyst/simulated_beacon.go b/eth/catalyst/simulated_beacon.go
index 8bdf94b80e81..6473bd658b75 100644
--- a/eth/catalyst/simulated_beacon.go
+++ b/eth/catalyst/simulated_beacon.go
@@ -101,7 +101,7 @@ func NewSimulatedBeacon(period uint64, eth *eth.Ethereum) (*SimulatedBeacon, err
// if genesis block, send forkchoiceUpdated to trigger transition to PoS
if block.Number.Sign() == 0 {
- if _, err := engineAPI.ForkchoiceUpdatedV2(current, nil); err != nil {
+ if _, err := engineAPI.ForkchoiceUpdatedV3(current, nil); err != nil {
return nil, err
}
}
@@ -208,7 +208,7 @@ func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal, timestamp u
c.setCurrentState(payload.BlockHash, finalizedHash)
// Mark the block containing the payload as canonical
- if _, err = c.engineAPI.ForkchoiceUpdatedV2(c.curForkchoiceState, nil); err != nil {
+ if _, err = c.engineAPI.ForkchoiceUpdatedV3(c.curForkchoiceState, nil); err != nil {
return err
}
c.lastBlockTime = payload.Timestamp
diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go
index 0cbddee6bf7a..c440b40e9dc7 100644
--- a/eth/downloader/downloader_test.go
+++ b/eth/downloader/downloader_test.go
@@ -230,6 +230,7 @@ func (dlp *downloadTesterPeer) RequestBodies(hashes []common.Hash, sink chan *et
txsHashes = make([]common.Hash, len(bodies))
uncleHashes = make([]common.Hash, len(bodies))
withdrawalHashes = make([]common.Hash, len(bodies))
+ requestsHashes = make([]common.Hash, len(bodies))
)
hasher := trie.NewStackTrie(nil)
for i, body := range bodies {
@@ -248,7 +249,7 @@ func (dlp *downloadTesterPeer) RequestBodies(hashes []common.Hash, sink chan *et
res := ð.Response{
Req: req,
Res: (*eth.BlockBodiesResponse)(&bodies),
- Meta: [][]common.Hash{txsHashes, uncleHashes, withdrawalHashes},
+ Meta: [][]common.Hash{txsHashes, uncleHashes, withdrawalHashes, requestsHashes},
Time: 1,
Done: make(chan error, 1), // Ignore the returned status
}
diff --git a/eth/downloader/fetchers_concurrent_bodies.go b/eth/downloader/fetchers_concurrent_bodies.go
index 56359b33c94e..709df7757507 100644
--- a/eth/downloader/fetchers_concurrent_bodies.go
+++ b/eth/downloader/fetchers_concurrent_bodies.go
@@ -88,10 +88,10 @@ func (q *bodyQueue) request(peer *peerConnection, req *fetchRequest, resCh chan
// deliver is responsible for taking a generic response packet from the concurrent
// fetcher, unpacking the body data and delivering it to the downloader's queue.
func (q *bodyQueue) deliver(peer *peerConnection, packet *eth.Response) (int, error) {
- txs, uncles, withdrawals := packet.Res.(*eth.BlockBodiesResponse).Unpack()
- hashsets := packet.Meta.([][]common.Hash) // {txs hashes, uncle hashes, withdrawal hashes}
+ txs, uncles, withdrawals, requests := packet.Res.(*eth.BlockBodiesResponse).Unpack()
+ hashsets := packet.Meta.([][]common.Hash) // {txs hashes, uncle hashes, withdrawal hashes, requests hashes}
- accepted, err := q.queue.DeliverBodies(peer.id, txs, hashsets[0], uncles, hashsets[1], withdrawals, hashsets[2])
+ accepted, err := q.queue.DeliverBodies(peer.id, txs, hashsets[0], uncles, hashsets[1], withdrawals, hashsets[2], requests, hashsets[3])
switch {
case err == nil && len(txs) == 0:
peer.log.Trace("Requested bodies delivered")
diff --git a/eth/downloader/queue.go b/eth/downloader/queue.go
index 5441ad118791..adad45020040 100644
--- a/eth/downloader/queue.go
+++ b/eth/downloader/queue.go
@@ -784,7 +784,8 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, hashes []comm
// also wakes any threads waiting for data delivery.
func (q *queue) DeliverBodies(id string, txLists [][]*types.Transaction, txListHashes []common.Hash,
uncleLists [][]*types.Header, uncleListHashes []common.Hash,
- withdrawalLists [][]*types.Withdrawal, withdrawalListHashes []common.Hash) (int, error) {
+ withdrawalLists [][]*types.Withdrawal, withdrawalListHashes []common.Hash,
+ requestsLists [][]*types.Request, requestsListHashes []common.Hash) (int, error) {
q.lock.Lock()
defer q.lock.Unlock()
@@ -808,6 +809,19 @@ func (q *queue) DeliverBodies(id string, txLists [][]*types.Transaction, txListH
return errInvalidBody
}
}
+ if header.RequestsHash == nil {
+ // nil hash means that requests should not be present in body
+ if requestsLists[index] != nil {
+ return errInvalidBody
+ }
+ } else { // non-nil hash: body must have requests
+ if requestsLists[index] == nil {
+ return errInvalidBody
+ }
+ if requestsListHashes[index] != *header.RequestsHash {
+ return errInvalidBody
+ }
+ }
// Blocks must have a number of blobs corresponding to the header gas usage,
// and zero before the Cancun hardfork.
var blobs int
diff --git a/eth/downloader/queue_test.go b/eth/downloader/queue_test.go
index 857ac4813a7d..e29d23f80b7a 100644
--- a/eth/downloader/queue_test.go
+++ b/eth/downloader/queue_test.go
@@ -341,7 +341,7 @@ func XTestDelivery(t *testing.T) {
uncleHashes[i] = types.CalcUncleHash(uncles)
}
time.Sleep(100 * time.Millisecond)
- _, err := q.DeliverBodies(peer.id, txset, txsHashes, uncleset, uncleHashes, nil, nil)
+ _, err := q.DeliverBodies(peer.id, txset, txsHashes, uncleset, uncleHashes, nil, nil, nil, nil)
if err != nil {
fmt.Printf("delivered %d bodies %v\n", len(txset), err)
}
diff --git a/eth/protocols/eth/handlers.go b/eth/protocols/eth/handlers.go
index bdc630a9f467..377b85865337 100644
--- a/eth/protocols/eth/handlers.go
+++ b/eth/protocols/eth/handlers.go
@@ -313,6 +313,7 @@ func handleBlockBodies(backend Backend, msg Decoder, peer *Peer) error {
txsHashes = make([]common.Hash, len(res.BlockBodiesResponse))
uncleHashes = make([]common.Hash, len(res.BlockBodiesResponse))
withdrawalHashes = make([]common.Hash, len(res.BlockBodiesResponse))
+ requestsHashes = make([]common.Hash, len(res.BlockBodiesResponse))
)
hasher := trie.NewStackTrie(nil)
for i, body := range res.BlockBodiesResponse {
@@ -321,8 +322,11 @@ func handleBlockBodies(backend Backend, msg Decoder, peer *Peer) error {
if body.Withdrawals != nil {
withdrawalHashes[i] = types.DeriveSha(types.Withdrawals(body.Withdrawals), hasher)
}
+ if body.Requests != nil {
+ requestsHashes[i] = types.DeriveSha(types.Requests(body.Requests), hasher)
+ }
}
- return [][]common.Hash{txsHashes, uncleHashes, withdrawalHashes}
+ return [][]common.Hash{txsHashes, uncleHashes, withdrawalHashes, requestsHashes}
}
return peer.dispatchResponse(&Response{
id: res.RequestId,
diff --git a/eth/protocols/eth/protocol.go b/eth/protocols/eth/protocol.go
index c5cb2dd1dca4..cbc895eabb8e 100644
--- a/eth/protocols/eth/protocol.go
+++ b/eth/protocols/eth/protocol.go
@@ -224,21 +224,22 @@ type BlockBody struct {
Transactions []*types.Transaction // Transactions contained within a block
Uncles []*types.Header // Uncles contained within a block
Withdrawals []*types.Withdrawal `rlp:"optional"` // Withdrawals contained within a block
+ Requests []*types.Request `rlp:"optional"` // Requests contained within a block
}
// Unpack retrieves the transactions and uncles from the range packet and returns
// them in a split flat format that's more consistent with the internal data structures.
-func (p *BlockBodiesResponse) Unpack() ([][]*types.Transaction, [][]*types.Header, [][]*types.Withdrawal) {
- // TODO(matt): add support for withdrawals to fetchers
+func (p *BlockBodiesResponse) Unpack() ([][]*types.Transaction, [][]*types.Header, [][]*types.Withdrawal, [][]*types.Request) {
var (
txset = make([][]*types.Transaction, len(*p))
uncleset = make([][]*types.Header, len(*p))
withdrawalset = make([][]*types.Withdrawal, len(*p))
+ requestset = make([][]*types.Request, len(*p))
)
for i, body := range *p {
- txset[i], uncleset[i], withdrawalset[i] = body.Transactions, body.Uncles, body.Withdrawals
+ txset[i], uncleset[i], withdrawalset[i], requestset[i] = body.Transactions, body.Uncles, body.Withdrawals, body.Requests
}
- return txset, uncleset, withdrawalset
+ return txset, uncleset, withdrawalset, requestset
}
// GetReceiptsRequest represents a block receipts query.
diff --git a/eth/state_accessor.go b/eth/state_accessor.go
index 372c76f49692..991bc67d4a7c 100644
--- a/eth/state_accessor.go
+++ b/eth/state_accessor.go
@@ -146,7 +146,7 @@ func (eth *Ethereum) hashState(ctx context.Context, block *types.Block, reexec u
if current = eth.blockchain.GetBlockByNumber(next); current == nil {
return nil, nil, fmt.Errorf("block #%d not found", next)
}
- _, _, _, err := eth.blockchain.Processor().Process(current, statedb, vm.Config{})
+ _, err := eth.blockchain.Processor().Process(current, statedb, vm.Config{})
if err != nil {
return nil, nil, fmt.Errorf("processing block %d failed: %v", current.NumberU64(), err)
}
diff --git a/eth/tracers/logger/logger.go b/eth/tracers/logger/logger.go
index ef1d47146682..d7c956317f67 100644
--- a/eth/tracers/logger/logger.go
+++ b/eth/tracers/logger/logger.go
@@ -388,7 +388,7 @@ func (t *mdLogger) OnExit(depth int, output []byte, gasUsed uint64, err error, r
// OnOpcode also tracks SLOAD/SSTORE ops to track storage change.
func (t *mdLogger) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
stack := scope.StackData()
- fmt.Fprintf(t.out, "| %4d | %10v | %3d |", pc, op, cost)
+ fmt.Fprintf(t.out, "| %4d | %10v | %3d |", pc, vm.OpCode(op).String(), cost)
if !t.cfg.DisableStack {
// format stack
diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go
index f769ee847554..a1148bcedbc6 100644
--- a/ethclient/ethclient.go
+++ b/ethclient/ethclient.go
@@ -123,6 +123,7 @@ type rpcBlock struct {
Transactions []rpcTransaction `json:"transactions"`
UncleHashes []common.Hash `json:"uncles"`
Withdrawals []*types.Withdrawal `json:"withdrawals,omitempty"`
+ Requests []*types.Request `json:"requests,omitempty"`
}
func (ec *Client) getBlock(ctx context.Context, method string, args ...interface{}) (*types.Block, error) {
@@ -196,6 +197,7 @@ func (ec *Client) getBlock(ctx context.Context, method string, args ...interface
Transactions: txs,
Uncles: uncles,
Withdrawals: body.Withdrawals,
+ Requests: body.Requests,
}), nil
}
diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go
index 1c3cb4adf936..aeb3e8adc289 100644
--- a/internal/ethapi/api.go
+++ b/internal/ethapi/api.go
@@ -1268,6 +1268,9 @@ func RPCMarshalHeader(head *types.Header) map[string]interface{} {
if head.ParentBeaconRoot != nil {
result["parentBeaconBlockRoot"] = head.ParentBeaconRoot
}
+ if head.RequestsHash != nil {
+ result["requestsRoot"] = head.RequestsHash
+ }
return result
}
@@ -1303,6 +1306,9 @@ func RPCMarshalBlock(block *types.Block, inclTx bool, fullTx bool, config *param
if block.Header().WithdrawalsHash != nil {
fields["withdrawals"] = block.Withdrawals()
}
+ if block.Header().RequestsHash != nil {
+ fields["requests"] = block.Requests()
+ }
return fields
}
diff --git a/miner/payload_building.go b/miner/payload_building.go
index d027cd1e1f3a..c29fa14546b0 100644
--- a/miner/payload_building.go
+++ b/miner/payload_building.go
@@ -35,13 +35,14 @@ import (
// Check engine-api specification for more details.
// https://github.com/ethereum/execution-apis/blob/main/src/engine/cancun.md#payloadattributesv3
type BuildPayloadArgs struct {
- Parent common.Hash // The parent block to build payload on top
- Timestamp uint64 // The provided timestamp of generated payload
- FeeRecipient common.Address // The provided recipient address for collecting transaction fee
- Random common.Hash // The provided randomness value
- Withdrawals types.Withdrawals // The provided withdrawals
- BeaconRoot *common.Hash // The provided beaconRoot (Cancun)
- Version engine.PayloadVersion // Versioning byte for payload id calculation.
+ Parent common.Hash // The parent block to build payload on top
+ Timestamp uint64 // The provided timestamp of generated payload
+ FeeRecipient common.Address // The provided recipient address for collecting transaction fee
+ Random common.Hash // The provided randomness value
+ Withdrawals types.Withdrawals // The provided withdrawals
+ BeaconRoot *common.Hash // The provided beaconRoot (Cancun)
+ TargetBlobCount *uint64
+ Version engine.PayloadVersion // Versioning byte for payload id calculation.
}
// Id computes an 8-byte identifier by hashing the components of the payload arguments.
@@ -55,6 +56,9 @@ func (args *BuildPayloadArgs) Id() engine.PayloadID {
if args.BeaconRoot != nil {
hasher.Write(args.BeaconRoot[:])
}
+ if args.TargetBlobCount != nil {
+ binary.Write(hasher, binary.BigEndian, *args.TargetBlobCount)
+ }
var out engine.PayloadID
copy(out[:], hasher.Sum(nil)[:8])
out[0] = byte(args.Version)
@@ -188,6 +192,7 @@ func (miner *Miner) buildPayload(args *BuildPayloadArgs) (*Payload, error) {
random: args.Random,
withdrawals: args.Withdrawals,
beaconRoot: args.BeaconRoot,
+ blobTarget: args.TargetBlobCount,
noTxs: true,
}
empty := miner.generateWork(emptyParams)
@@ -219,6 +224,7 @@ func (miner *Miner) buildPayload(args *BuildPayloadArgs) (*Payload, error) {
random: args.Random,
withdrawals: args.Withdrawals,
beaconRoot: args.BeaconRoot,
+ blobTarget: args.TargetBlobCount,
noTxs: false,
}
diff --git a/miner/worker.go b/miner/worker.go
index 9aae6e16099d..30f887a0c4cb 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -84,6 +84,7 @@ type generateParams struct {
random common.Hash // The randomness generated by beacon chain, empty before the merge
withdrawals types.Withdrawals // List of withdrawals to include in block (shanghai field)
beaconRoot *common.Hash // The beacon root (cancun field).
+ blobTarget *uint64 // Target number of blobs.
noTxs bool // Flag whether an empty block without any transaction is expected
}
@@ -105,7 +106,20 @@ func (miner *Miner) generateWork(params *generateParams) *newPayloadResult {
log.Warn("Block building is interrupted", "allowance", common.PrettyDuration(miner.config.Recommit))
}
}
+
body := types.Body{Transactions: work.txs, Withdrawals: params.withdrawals}
+ allLogs := make([]*types.Log, 0)
+ for _, r := range work.receipts {
+ allLogs = append(allLogs, r.Logs...)
+ }
+ // Read requests if Prague is enabled.
+ if miner.chainConfig.IsPrague(work.header.Number, work.header.Time) {
+ requests, err := core.ParseDepositLogs(allLogs, miner.chainConfig)
+ if err != nil {
+ return &newPayloadResult{err: err}
+ }
+ body.Requests = requests
+ }
block, err := miner.engine.FinalizeAndAssemble(miner.chain, work.header, work.state, &body, work.receipts)
if err != nil {
return &newPayloadResult{err: err}
@@ -187,6 +201,10 @@ func (miner *Miner) prepareWork(genParams *generateParams) (*environment, error)
header.ExcessBlobGas = &excessBlobGas
header.ParentBeaconRoot = genParams.beaconRoot
}
+ // Apply EIP-7742.
+ if miner.chainConfig.IsPrague(header.Number, header.Time) {
+ header.TargetBlobCount = genParams.blobTarget
+ }
// Could potentially happen if starting to mine in an odd state.
// Note genParams.coinbase can be different with header.Coinbase
// since clique algorithm can modify the coinbase field in header.
diff --git a/params/config.go b/params/config.go
index 871782399d14..925a186a909e 100644
--- a/params/config.go
+++ b/params/config.go
@@ -59,6 +59,7 @@ var (
TerminalTotalDifficultyPassed: true,
ShanghaiTime: newUint64(1681338455),
CancunTime: newUint64(1710338135),
+ DepositContractAddress: common.HexToAddress("0x00000000219ab540356cbb839cbe05303d7705fa"),
Ethash: new(EthashConfig),
}
// HoleskyChainConfig contains the chain parameters to run a node on the Holesky test network.
@@ -365,6 +366,8 @@ type ChainConfig struct {
// TODO(karalabe): Drop this field eventually (always assuming PoS mode)
TerminalTotalDifficultyPassed bool `json:"terminalTotalDifficultyPassed,omitempty"`
+ DepositContractAddress common.Address `json:"depositContractAddress,omitempty"`
+
// Various consensus engines
Ethash *EthashConfig `json:"ethash,omitempty"`
Clique *CliqueConfig `json:"clique,omitempty"`
diff --git a/params/protocol_params.go b/params/protocol_params.go
index 8ffe8ee75db1..d1b386abdca6 100644
--- a/params/protocol_params.go
+++ b/params/protocol_params.go
@@ -174,6 +174,7 @@ const (
BlobTxTargetBlobGasPerBlock = 3 * BlobTxBlobGasPerBlob // Target consumable blob gas for data blobs per block (for 1559-like pricing)
MaxBlobGasPerBlock = 6 * BlobTxBlobGasPerBlob // Maximum consumable blob gas for data blobs per block
+ BlobTargetQuotient = 2
)
// Gas discount table for BLS12-381 G1 and G2 multi exponentiation operations
diff --git a/tests/init.go b/tests/init.go
index c85e714c0023..4bb83f9300bc 100644
--- a/tests/init.go
+++ b/tests/init.go
@@ -373,6 +373,7 @@ var Forks = map[string]*params.ChainConfig{
ShanghaiTime: u64(0),
CancunTime: u64(0),
PragueTime: u64(0),
+ DepositContractAddress: params.MainnetChainConfig.DepositContractAddress,
},
"CancunToPragueAtTime15k": {
ChainID: big.NewInt(1),
@@ -393,6 +394,7 @@ var Forks = map[string]*params.ChainConfig{
ShanghaiTime: u64(0),
CancunTime: u64(0),
PragueTime: u64(15_000),
+ DepositContractAddress: params.MainnetChainConfig.DepositContractAddress,
},
}