diff --git a/core/beacon/gen_blockparams.go b/core/beacon/gen_blockparams.go index 0e2ea4bb1338..eace9e182928 100644 --- a/core/beacon/gen_blockparams.go +++ b/core/beacon/gen_blockparams.go @@ -15,9 +15,9 @@ var _ = (*payloadAttributesMarshaling)(nil) // MarshalJSON marshals as JSON. func (p PayloadAttributesV1) MarshalJSON() ([]byte, error) { type PayloadAttributesV1 struct { - Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"` - Random common.Hash `json:"prevRandao" gencodec:"required"` - SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"` + Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"` + Random common.Hash `json:"prevRandao" gencodec:"required"` + SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"` } var enc PayloadAttributesV1 enc.Timestamp = hexutil.Uint64(p.Timestamp) @@ -29,9 +29,9 @@ func (p PayloadAttributesV1) MarshalJSON() ([]byte, error) { // UnmarshalJSON unmarshals from JSON. func (p *PayloadAttributesV1) UnmarshalJSON(input []byte) error { type PayloadAttributesV1 struct { - Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"` - Random *common.Hash `json:"prevRandao" gencodec:"required"` - SuggestedFeeRecipient *common.Address `json:"suggestedFeeRecipient" gencodec:"required"` + Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"` + Random *common.Hash `json:"prevRandao" gencodec:"required"` + SuggestedFeeRecipient *common.Address `json:"suggestedFeeRecipient" gencodec:"required"` } var dec PayloadAttributesV1 if err := json.Unmarshal(input, &dec); err != nil { diff --git a/core/beacon/gen_blockparams_v2.go b/core/beacon/gen_blockparams_v2.go new file mode 100644 index 000000000000..80fbb0afcd1c --- /dev/null +++ b/core/beacon/gen_blockparams_v2.go @@ -0,0 +1,61 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package beacon + +import ( + "encoding/json" + "errors" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" +) + +var _ = (*payloadAttributesV2Marshaling)(nil) + +// MarshalJSON marshals as JSON. +func (p PayloadAttributesV2) MarshalJSON() ([]byte, error) { + type PayloadAttributesV2 struct { + Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"` + Random common.Hash `json:"prevRandao" gencodec:"required"` + SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"` + Withdrawals []*types.Withdrawal `json:"withdrawals" gencodec:"required"` + } + var enc PayloadAttributesV2 + enc.Timestamp = hexutil.Uint64(p.Timestamp) + enc.Random = p.Random + enc.SuggestedFeeRecipient = p.SuggestedFeeRecipient + enc.Withdrawals = p.Withdrawals + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (p *PayloadAttributesV2) UnmarshalJSON(input []byte) error { + type PayloadAttributesV2 struct { + Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"` + Random *common.Hash `json:"prevRandao" gencodec:"required"` + SuggestedFeeRecipient *common.Address `json:"suggestedFeeRecipient" gencodec:"required"` + Withdrawals []*types.Withdrawal `json:"withdrawals" gencodec:"required"` + } + var dec PayloadAttributesV2 + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.Timestamp == nil { + return errors.New("missing required field 'timestamp' for PayloadAttributesV2") + } + p.Timestamp = uint64(*dec.Timestamp) + if dec.Random == nil { + return errors.New("missing required field 'prevRandao' for PayloadAttributesV2") + } + p.Random = *dec.Random + if dec.SuggestedFeeRecipient == nil { + return errors.New("missing required field 'suggestedFeeRecipient' for PayloadAttributesV2") + } + p.SuggestedFeeRecipient = *dec.SuggestedFeeRecipient + if dec.Withdrawals == nil { + return errors.New("missing required field 'withdrawals' for PayloadAttributesV2") + } + p.Withdrawals = dec.Withdrawals + return nil +} diff --git a/core/beacon/gen_ed.go b/core/beacon/gen_ed.go index dc1bd1323357..dcee3bf18c79 100644 --- a/core/beacon/gen_ed.go +++ b/core/beacon/gen_ed.go @@ -30,7 +30,6 @@ func (e ExecutableDataV1) MarshalJSON() ([]byte, error) { BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"` BlockHash common.Hash `json:"blockHash" gencodec:"required"` Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"` - ExcessDataGas *hexutil.Big `json:"excessDataGas" gencodec:"optional"` } var enc ExecutableDataV1 enc.ParentHash = e.ParentHash @@ -52,7 +51,6 @@ func (e ExecutableDataV1) MarshalJSON() ([]byte, error) { enc.Transactions[k] = v } } - enc.ExcessDataGas = (*hexutil.Big)(e.ExcessDataGas) return json.Marshal(&enc) } @@ -73,7 +71,6 @@ func (e *ExecutableDataV1) UnmarshalJSON(input []byte) error { BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"` BlockHash *common.Hash `json:"blockHash" gencodec:"required"` Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"` - ExcessDataGas *hexutil.Big `json:"excessDataGas" gencodec:"optional"` } var dec ExecutableDataV1 if err := json.Unmarshal(input, &dec); err != nil { @@ -138,8 +135,5 @@ func (e *ExecutableDataV1) UnmarshalJSON(input []byte) error { for k, v := range dec.Transactions { e.Transactions[k] = v } - if dec.ExcessDataGas != nil { - e.ExcessDataGas = (*big.Int)(dec.ExcessDataGas) - } return nil } diff --git a/core/beacon/gen_ed_v2.go b/core/beacon/gen_ed_v2.go new file mode 100644 index 000000000000..a06bfef1f87d --- /dev/null +++ b/core/beacon/gen_ed_v2.go @@ -0,0 +1,153 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package beacon + +import ( + "encoding/json" + "errors" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" +) + +var _ = (*executableDataV2Marshaling)(nil) + +// MarshalJSON marshals as JSON. +func (e ExecutableDataV2) MarshalJSON() ([]byte, error) { + type ExecutableDataV2 struct { + ParentHash common.Hash `json:"parentHash" gencodec:"required"` + FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"` + StateRoot common.Hash `json:"stateRoot" gencodec:"required"` + ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"` + LogsBloom hexutil.Bytes `json:"logsBloom" gencodec:"required"` + Random common.Hash `json:"prevRandao" gencodec:"required"` + Number hexutil.Uint64 `json:"blockNumber" gencodec:"required"` + GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"` + GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"` + Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"` + ExtraData hexutil.Bytes `json:"extraData" gencodec:"required"` + BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"` + BlockHash common.Hash `json:"blockHash" gencodec:"required"` + Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"` + Withdrawals []*types.Withdrawal `json:"withdrawals" gencodec:"required"` + ExcessDataGas *hexutil.Big `json:"excessDataGas" gencodec:"optional"` + } + var enc ExecutableDataV2 + enc.ParentHash = e.ParentHash + enc.FeeRecipient = e.FeeRecipient + enc.StateRoot = e.StateRoot + enc.ReceiptsRoot = e.ReceiptsRoot + enc.LogsBloom = e.LogsBloom + enc.Random = e.Random + enc.Number = hexutil.Uint64(e.Number) + enc.GasLimit = hexutil.Uint64(e.GasLimit) + enc.GasUsed = hexutil.Uint64(e.GasUsed) + enc.Timestamp = hexutil.Uint64(e.Timestamp) + enc.ExtraData = e.ExtraData + enc.BaseFeePerGas = (*hexutil.Big)(e.BaseFeePerGas) + enc.BlockHash = e.BlockHash + if e.Transactions != nil { + enc.Transactions = make([]hexutil.Bytes, len(e.Transactions)) + for k, v := range e.Transactions { + enc.Transactions[k] = v + } + } + enc.Withdrawals = e.Withdrawals + enc.ExcessDataGas = (*hexutil.Big)(e.ExcessDataGas) + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (e *ExecutableDataV2) UnmarshalJSON(input []byte) error { + type ExecutableDataV2 struct { + ParentHash *common.Hash `json:"parentHash" gencodec:"required"` + FeeRecipient *common.Address `json:"feeRecipient" gencodec:"required"` + StateRoot *common.Hash `json:"stateRoot" gencodec:"required"` + ReceiptsRoot *common.Hash `json:"receiptsRoot" gencodec:"required"` + LogsBloom *hexutil.Bytes `json:"logsBloom" gencodec:"required"` + Random *common.Hash `json:"prevRandao" gencodec:"required"` + Number *hexutil.Uint64 `json:"blockNumber" gencodec:"required"` + GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"` + GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"` + Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"` + ExtraData *hexutil.Bytes `json:"extraData" gencodec:"required"` + BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"` + BlockHash *common.Hash `json:"blockHash" gencodec:"required"` + Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"` + Withdrawals []*types.Withdrawal `json:"withdrawals" gencodec:"required"` + ExcessDataGas *hexutil.Big `json:"excessDataGas" gencodec:"optional"` + } + var dec ExecutableDataV2 + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.ParentHash == nil { + return errors.New("missing required field 'parentHash' for ExecutableDataV2") + } + e.ParentHash = *dec.ParentHash + if dec.FeeRecipient == nil { + return errors.New("missing required field 'feeRecipient' for ExecutableDataV2") + } + e.FeeRecipient = *dec.FeeRecipient + if dec.StateRoot == nil { + return errors.New("missing required field 'stateRoot' for ExecutableDataV2") + } + e.StateRoot = *dec.StateRoot + if dec.ReceiptsRoot == nil { + return errors.New("missing required field 'receiptsRoot' for ExecutableDataV2") + } + e.ReceiptsRoot = *dec.ReceiptsRoot + if dec.LogsBloom == nil { + return errors.New("missing required field 'logsBloom' for ExecutableDataV2") + } + e.LogsBloom = *dec.LogsBloom + if dec.Random == nil { + return errors.New("missing required field 'prevRandao' for ExecutableDataV2") + } + e.Random = *dec.Random + if dec.Number == nil { + return errors.New("missing required field 'blockNumber' for ExecutableDataV2") + } + e.Number = uint64(*dec.Number) + if dec.GasLimit == nil { + return errors.New("missing required field 'gasLimit' for ExecutableDataV2") + } + e.GasLimit = uint64(*dec.GasLimit) + if dec.GasUsed == nil { + return errors.New("missing required field 'gasUsed' for ExecutableDataV2") + } + e.GasUsed = uint64(*dec.GasUsed) + if dec.Timestamp == nil { + return errors.New("missing required field 'timestamp' for ExecutableDataV2") + } + e.Timestamp = uint64(*dec.Timestamp) + if dec.ExtraData == nil { + return errors.New("missing required field 'extraData' for ExecutableDataV2") + } + e.ExtraData = *dec.ExtraData + if dec.BaseFeePerGas == nil { + return errors.New("missing required field 'baseFeePerGas' for ExecutableDataV2") + } + e.BaseFeePerGas = (*big.Int)(dec.BaseFeePerGas) + if dec.BlockHash == nil { + return errors.New("missing required field 'blockHash' for ExecutableDataV2") + } + e.BlockHash = *dec.BlockHash + if dec.Transactions == nil { + return errors.New("missing required field 'transactions' for ExecutableDataV2") + } + e.Transactions = make([][]byte, len(dec.Transactions)) + for k, v := range dec.Transactions { + e.Transactions[k] = v + } + if dec.Withdrawals == nil { + return errors.New("missing required field 'withdrawals' for ExecutableDataV2") + } + e.Withdrawals = dec.Withdrawals + if dec.ExcessDataGas != nil { + e.ExcessDataGas = (*big.Int)(dec.ExcessDataGas) + } + return nil +} diff --git a/core/beacon/types.go b/core/beacon/types.go index c8a113dcbe0e..21845250093a 100644 --- a/core/beacon/types.go +++ b/core/beacon/types.go @@ -31,9 +31,9 @@ import ( // PayloadAttributesV1 structure described at https://github.com/ethereum/execution-apis/pull/74 type PayloadAttributesV1 struct { - Timestamp uint64 `json:"timestamp" gencodec:"required"` - Random common.Hash `json:"prevRandao" gencodec:"required"` - SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"` + Timestamp uint64 `json:"timestamp" gencodec:"required"` + Random common.Hash `json:"prevRandao" gencodec:"required"` + SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"` } // JSON type overrides for PayloadAttributesV1. @@ -49,6 +49,21 @@ type BlobsBundleV1 struct { AggregatedProof types.KZGProof `json:"aggregatedProof" gencodec:"required"` } +//go:generate go run github.com/fjl/gencodec -type PayloadAttributesV2 -field-override payloadAttributesV2Marshaling -out gen_blockparams_v2.go + +// PayloadAttributesV1 structure described at https://github.com/ethereum/execution-apis/pull/195 +type PayloadAttributesV2 struct { + Timestamp uint64 `json:"timestamp" gencodec:"required"` + Random common.Hash `json:"prevRandao" gencodec:"required"` + SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"` + Withdrawals []*types.Withdrawal `json:"withdrawals" gencodec:"required"` +} + +// JSON type overrides for PayloadAttributesV1. +type payloadAttributesV2Marshaling struct { + Timestamp hexutil.Uint64 +} + //go:generate go run github.com/fjl/gencodec -type ExecutableDataV1 -field-override executableDataMarshaling -out gen_ed.go // ExecutableDataV1 structure described at https://github.com/ethereum/execution-apis/tree/main/src/engine/specification.md @@ -67,13 +82,44 @@ type ExecutableDataV1 struct { BaseFeePerGas *big.Int `json:"baseFeePerGas" gencodec:"required"` BlockHash common.Hash `json:"blockHash" gencodec:"required"` Transactions [][]byte `json:"transactions" gencodec:"required"` - - // New in EIP-4844 - ExcessDataGas *big.Int `json:"excessDataGas" gencodec:"optional"` } // JSON type overrides for executableData. type executableDataMarshaling struct { + Number hexutil.Uint64 + GasLimit hexutil.Uint64 + GasUsed hexutil.Uint64 + Timestamp hexutil.Uint64 + BaseFeePerGas *hexutil.Big + ExtraData hexutil.Bytes + LogsBloom hexutil.Bytes + Transactions []hexutil.Bytes +} + +//go:generate go run github.com/fjl/gencodec -type ExecutableDataV2 -field-override executableDataV2Marshaling -out gen_ed_v2.go + +// ExecutableDataV2 structure described at https://github.com/ethereum/execution-apis/tree/main/src/engine/specification.md +type ExecutableDataV2 struct { + ParentHash common.Hash `json:"parentHash" gencodec:"required"` + FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"` + StateRoot common.Hash `json:"stateRoot" gencodec:"required"` + ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"` + LogsBloom []byte `json:"logsBloom" gencodec:"required"` + Random common.Hash `json:"prevRandao" gencodec:"required"` + Number uint64 `json:"blockNumber" gencodec:"required"` + GasLimit uint64 `json:"gasLimit" gencodec:"required"` + GasUsed uint64 `json:"gasUsed" gencodec:"required"` + Timestamp uint64 `json:"timestamp" gencodec:"required"` + ExtraData []byte `json:"extraData" gencodec:"required"` + BaseFeePerGas *big.Int `json:"baseFeePerGas" gencodec:"required"` + BlockHash common.Hash `json:"blockHash" gencodec:"required"` + Transactions [][]byte `json:"transactions" gencodec:"required"` + Withdrawals []*types.Withdrawal `json:"withdrawals" gencodec:"required"` // new in Capella + ExcessDataGas *big.Int `json:"excessDataGas" gencodec:"optional"` // new in EIP-4844 +} + +// JSON type overrides for executableData. +type executableDataV2Marshaling struct { Number hexutil.Uint64 GasLimit hexutil.Uint64 GasUsed hexutil.Uint64 @@ -169,22 +215,21 @@ func ExecutableDataToBlock(params ExecutableDataV1) (*types.Block, error) { return nil, fmt.Errorf("invalid baseFeePerGas: %v", params.BaseFeePerGas) } header := &types.Header{ - ParentHash: params.ParentHash, - UncleHash: types.EmptyUncleHash, - Coinbase: params.FeeRecipient, - Root: params.StateRoot, - TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)), - ReceiptHash: params.ReceiptsRoot, - Bloom: types.BytesToBloom(params.LogsBloom), - Difficulty: common.Big0, - Number: new(big.Int).SetUint64(params.Number), - GasLimit: params.GasLimit, - GasUsed: params.GasUsed, - Time: params.Timestamp, - BaseFee: params.BaseFeePerGas, - ExcessDataGas: params.ExcessDataGas, - Extra: params.ExtraData, - MixDigest: params.Random, + ParentHash: params.ParentHash, + UncleHash: types.EmptyUncleHash, + Coinbase: params.FeeRecipient, + Root: params.StateRoot, + TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)), + ReceiptHash: params.ReceiptsRoot, + Bloom: types.BytesToBloom(params.LogsBloom), + Difficulty: common.Big0, + Number: new(big.Int).SetUint64(params.Number), + GasLimit: params.GasLimit, + GasUsed: params.GasUsed, + Time: params.Timestamp, + BaseFee: params.BaseFeePerGas, + Extra: params.ExtraData, + MixDigest: params.Random, } block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */) if block.Hash() != params.BlockHash { @@ -193,6 +238,60 @@ func ExecutableDataToBlock(params ExecutableDataV1) (*types.Block, error) { return block, nil } +// ExecutableDataToBlockV2 constructs a block from executable data V2. +// It verifies that the following fields: +// +// len(extraData) <= 32 +// uncleHash = emptyUncleHash +// difficulty = 0 +// +// and that the blockhash of the constructed block matches the parameters. +func ExecutableDataToBlockV2(params ExecutableDataV2) (*types.Block, error) { + txs, err := decodeTransactions(params.Transactions) + if err != nil { + return nil, err + } + if len(params.ExtraData) > 32 { + return nil, fmt.Errorf("invalid extradata length: %v", len(params.ExtraData)) + } + if len(params.LogsBloom) != 256 { + return nil, fmt.Errorf("invalid logsBloom length: %v", len(params.LogsBloom)) + } + // Check that baseFeePerGas is not negative or too big + if params.BaseFeePerGas != nil && (params.BaseFeePerGas.Sign() == -1 || params.BaseFeePerGas.BitLen() > 256) { + return nil, fmt.Errorf("invalid baseFeePerGas: %v", params.BaseFeePerGas) + } + var withdrawalRoot common.Hash + if params.Withdrawals != nil { + h := types.DeriveSha(types.Withdrawals(params.Withdrawals), trie.NewStackTrie(nil)) + withdrawalRoot = h + } + header := &types.Header{ + ParentHash: params.ParentHash, + UncleHash: types.EmptyUncleHash, + Coinbase: params.FeeRecipient, + Root: params.StateRoot, + TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)), + ReceiptHash: params.ReceiptsRoot, + Bloom: types.BytesToBloom(params.LogsBloom), + Difficulty: common.Big0, + Number: new(big.Int).SetUint64(params.Number), + GasLimit: params.GasLimit, + GasUsed: params.GasUsed, + Time: params.Timestamp, + BaseFee: params.BaseFeePerGas, + Extra: params.ExtraData, + MixDigest: params.Random, + WithdrawalHash: &withdrawalRoot, + ExcessDataGas: params.ExcessDataGas, + } + block := types.NewBlockWithHeader(header).WithBody2(txs, nil /* uncles */, params.Withdrawals) + if block.Hash() != params.BlockHash { + return nil, fmt.Errorf("blockhash mismatch, want %x, got %x", params.BlockHash, block.Hash()) + } + return block, nil +} + // BlockToExecutableData constructs the executableDataV1 structure by filling the // fields from the given block. It assumes the given block is post-merge block. // Additional blob contents are provided as well. @@ -206,7 +305,6 @@ func BlockToExecutableData(block *types.Block) *ExecutableDataV1 { GasLimit: block.GasLimit(), GasUsed: block.GasUsed(), BaseFeePerGas: block.BaseFee(), - ExcessDataGas: block.ExcessDataGas(), Timestamp: block.Time(), ReceiptsRoot: block.ReceiptHash(), LogsBloom: block.Bloom().Bytes(), @@ -242,3 +340,26 @@ func BlockToBlobData(block *types.Block) (*BlobsBundleV1, error) { blobsBundle.AggregatedProof = aggregatedProof return blobsBundle, nil } + +// BlockToExecutableDataV2 constructs the executableDataV2 structure by filling the +// fields from the given block. It assumes the given block is post-merge block. +func BlockToExecutableDataV2(block *types.Block) *ExecutableDataV2 { + return &ExecutableDataV2{ + BlockHash: block.Hash(), + ParentHash: block.ParentHash(), + FeeRecipient: block.Coinbase(), + StateRoot: block.Root(), + Number: block.NumberU64(), + GasLimit: block.GasLimit(), + GasUsed: block.GasUsed(), + BaseFeePerGas: block.BaseFee(), + Timestamp: block.Time(), + ReceiptsRoot: block.ReceiptHash(), + LogsBloom: block.Bloom().Bytes(), + Transactions: encodeTransactions(block.Transactions()), + Random: block.MixDigest(), + ExtraData: block.Extra(), + Withdrawals: block.Withdrawals(), + ExcessDataGas: block.ExcessDataGas(), + } +} diff --git a/core/headerchain.go b/core/headerchain.go index d8c415f336b8..0d5306f8f6fc 100644 --- a/core/headerchain.go +++ b/core/headerchain.go @@ -98,6 +98,7 @@ func NewHeaderChain(chainDb ethdb.Database, config *params.ChainConfig, engine c } hc.genesisHeader = hc.GetHeaderByNumber(0) if hc.genesisHeader == nil { + fmt.Println("Boop no genesis") return nil, ErrNoGenesis } hc.currentHeader.Store(hc.genesisHeader) diff --git a/core/types/block.go b/core/types/block.go index aa70826a32f2..9a5cd418ba92 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -88,6 +88,9 @@ type Header struct { // BaseFee was added by EIP-1559 and is ignored in legacy headers. BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"` + // WithdrawalHash was added by EIP-4895 and is ignored in legacy headers. + WithdrawalHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"` + // ExcessDataGas was added by EIP-4844 and is ignored in legacy headers. ExcessDataGas *big.Int `json:"excessDataGas" rlp:"optional"` @@ -117,6 +120,11 @@ func (h *Header) SetExcessDataGas(v *big.Int) { if v != nil { h.ExcessDataGas.Set(v) } + // Make sure WithdrawalHash is set to avoid encoding errors due to + // preceeding nil optional fields. + if h.WithdrawalHash == nil { + h.WithdrawalHash = &common.Hash{} + } } // Hash returns the block hash of the header, which is simply the keccak256 hash of its @@ -173,6 +181,7 @@ func (h *Header) EmptyReceipts() bool { type Body struct { Transactions []*Transaction Uncles []*Header + Withdrawals []*Withdrawal `rlp:"optional"` } // Block represents an entire block in the Ethereum blockchain. @@ -180,6 +189,7 @@ type Block struct { header *Header uncles []*Header transactions Transactions + withdrawals []*Withdrawal // caches hash atomic.Value @@ -267,9 +277,10 @@ func (txs *extBlockTxs) EncodeRLP(w io.Writer) error { // "external" block encoding. used for eth protocol, etc. type extblock struct { - Header *Header - Txs *extBlockTxs - Uncles []*Header + Header *Header + Txs *extBlockTxs + Uncles []*Header + Withdrawals []*Withdrawal `rlp:"optional"` } // NewBlock creates a new block. The input data is copied, @@ -280,6 +291,17 @@ type extblock struct { // are ignored and set to values derived from the given txs, uncles // and receipts. func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []*Receipt, hasher TrieHasher) *Block { + return NewBlock2(header, txs, uncles, receipts, nil, hasher) +} + +// NewBlock2 creates a new block with withdrawals. The input data +// is copied, changes to header and to the field values will not +// affect the block. +// +// The values of TxHash, UncleHash, ReceiptHash and Bloom in header +// are ignored and set to values derived from the given txs, uncles +// and receipts. +func NewBlock2(header *Header, txs []*Transaction, uncles []*Header, receipts []*Receipt, withdrawals []*Withdrawal, hasher TrieHasher) *Block { b := &Block{header: CopyHeader(header)} // TODO: panic if len(txs) != len(receipts) @@ -308,6 +330,17 @@ func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []* } } + if withdrawals != nil { + if len(withdrawals) == 0 { + b.header.WithdrawalHash = &EmptyRootHash + } else { + h := DeriveSha(Withdrawals(withdrawals), hasher) + b.header.WithdrawalHash = &h + b.withdrawals = make(Withdrawals, len(withdrawals)) + copy(b.withdrawals, withdrawals) + } + } + return b } @@ -348,12 +381,13 @@ func (b *Block) DecodeRLP(s *rlp.Stream) error { if err := s.Decode(&eb); err != nil { return err } + for i, tx := range *eb.Txs { if tx.wrapData != nil { return fmt.Errorf("transactions in blocks must not contain wrap-data, tx %d is bad", i) } } - b.header, b.uncles, b.transactions = eb.Header, eb.Uncles, []*Transaction(*eb.Txs) + b.header, b.uncles, b.transactions, b.withdrawals = eb.Header, eb.Uncles, []*Transaction(*eb.Txs), eb.Withdrawals b.size.Store(common.StorageSize(rlp.ListSize(size))) return nil } @@ -361,9 +395,10 @@ func (b *Block) DecodeRLP(s *rlp.Stream) error { // EncodeRLP serializes b into the Ethereum RLP block format. func (b *Block) EncodeRLP(w io.Writer) error { return rlp.Encode(w, extblock{ - Header: b.header, - Txs: (*extBlockTxs)(&b.transactions), - Uncles: b.uncles, + Header: b.header, + Txs: (*extBlockTxs)(&b.transactions), + Uncles: b.uncles, + Withdrawals: b.withdrawals, }) } @@ -413,10 +448,23 @@ func (b *Block) ExcessDataGas() *big.Int { return new(big.Int).Set(b.header.ExcessDataGas) } +func (b *Block) WithdrawalHash() *common.Hash { + if b.header.WithdrawalHash == nil { + return nil + } + var h common.Hash + h.SetBytes(b.header.WithdrawalHash.Bytes()) + return &h +} + +func (b *Block) Withdrawals() []*Withdrawal { + return b.withdrawals +} + func (b *Block) Header() *Header { return CopyHeader(b.header) } // Body returns the non-header content of the block. -func (b *Block) Body() *Body { return &Body{b.transactions, b.uncles} } +func (b *Block) Body() *Body { return &Body{b.transactions, b.uncles, b.withdrawals} } // Size returns the true RLP encoded storage size of the block, either by encoding // and returning it, or returning a previously cached value. @@ -476,6 +524,23 @@ func (b *Block) WithBody(transactions []*Transaction, uncles []*Header) *Block { return block } +// WithBody2 returns a new block with the given transaction, uncle, and +// withdrawal contents. +func (b *Block) WithBody2(transactions []*Transaction, uncles []*Header, withdrawals []*Withdrawal) *Block { + block := &Block{ + header: CopyHeader(b.header), + transactions: make([]*Transaction, len(transactions)), + uncles: make([]*Header, len(uncles)), + withdrawals: make([]*Withdrawal, len(withdrawals)), + } + copy(block.transactions, transactions) + for i := range uncles { + block.uncles[i] = CopyHeader(uncles[i]) + } + copy(block.withdrawals, withdrawals) + return block +} + // Hash returns the keccak256 hash of b's header. // The hash is computed on the first call and cached thereafter. func (b *Block) Hash() common.Hash { diff --git a/core/types/gen_header_json.go b/core/types/gen_header_json.go index 69ca2b643daf..2ea4b65e91f6 100644 --- a/core/types/gen_header_json.go +++ b/core/types/gen_header_json.go @@ -16,24 +16,25 @@ var _ = (*headerMarshaling)(nil) // MarshalJSON marshals as JSON. func (h Header) MarshalJSON() ([]byte, error) { type Header struct { - ParentHash common.Hash `json:"parentHash" gencodec:"required"` - UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"` - Coinbase common.Address `json:"miner"` - Root common.Hash `json:"stateRoot" gencodec:"required"` - TxHash common.Hash `json:"transactionsRoot" gencodec:"required"` - ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"` - Bloom Bloom `json:"logsBloom" gencodec:"required"` - Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"` - Number *hexutil.Big `json:"number" gencodec:"required"` - GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"` - GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"` - Time hexutil.Uint64 `json:"timestamp" gencodec:"required"` - Extra hexutil.Bytes `json:"extraData" gencodec:"required"` - MixDigest common.Hash `json:"mixHash"` - Nonce BlockNonce `json:"nonce"` - BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"` - ExcessDataGas *hexutil.Big `json:"excessDataGas" rlp:"optional"` - Hash common.Hash `json:"hash"` + ParentHash common.Hash `json:"parentHash" gencodec:"required"` + UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"` + Coinbase common.Address `json:"miner"` + Root common.Hash `json:"stateRoot" gencodec:"required"` + TxHash common.Hash `json:"transactionsRoot" gencodec:"required"` + ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"` + Bloom Bloom `json:"logsBloom" gencodec:"required"` + Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"` + Number *hexutil.Big `json:"number" gencodec:"required"` + GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"` + GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"` + Time hexutil.Uint64 `json:"timestamp" gencodec:"required"` + Extra hexutil.Bytes `json:"extraData" gencodec:"required"` + MixDigest common.Hash `json:"mixHash"` + Nonce BlockNonce `json:"nonce"` + BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"` + WithdrawalHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"` + ExcessDataGas *hexutil.Big `json:"excessDataGas" rlp:"optional"` + Hash common.Hash `json:"hash"` } var enc Header enc.ParentHash = h.ParentHash @@ -52,6 +53,7 @@ func (h Header) MarshalJSON() ([]byte, error) { enc.MixDigest = h.MixDigest enc.Nonce = h.Nonce enc.BaseFee = (*hexutil.Big)(h.BaseFee) + enc.WithdrawalHash = h.WithdrawalHash enc.ExcessDataGas = (*hexutil.Big)(h.ExcessDataGas) enc.Hash = h.Hash() return json.Marshal(&enc) @@ -60,23 +62,24 @@ func (h Header) MarshalJSON() ([]byte, error) { // UnmarshalJSON unmarshals from JSON. func (h *Header) UnmarshalJSON(input []byte) error { type Header struct { - ParentHash *common.Hash `json:"parentHash" gencodec:"required"` - UncleHash *common.Hash `json:"sha3Uncles" gencodec:"required"` - Coinbase *common.Address `json:"miner"` - Root *common.Hash `json:"stateRoot" gencodec:"required"` - TxHash *common.Hash `json:"transactionsRoot" gencodec:"required"` - ReceiptHash *common.Hash `json:"receiptsRoot" gencodec:"required"` - Bloom *Bloom `json:"logsBloom" gencodec:"required"` - Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"` - Number *hexutil.Big `json:"number" gencodec:"required"` - GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"` - GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"` - Time *hexutil.Uint64 `json:"timestamp" gencodec:"required"` - Extra *hexutil.Bytes `json:"extraData" gencodec:"required"` - MixDigest *common.Hash `json:"mixHash"` - Nonce *BlockNonce `json:"nonce"` - BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"` - ExcessDataGas *hexutil.Big `json:"excessDataGas" rlp:"optional"` + ParentHash *common.Hash `json:"parentHash" gencodec:"required"` + UncleHash *common.Hash `json:"sha3Uncles" gencodec:"required"` + Coinbase *common.Address `json:"miner"` + Root *common.Hash `json:"stateRoot" gencodec:"required"` + TxHash *common.Hash `json:"transactionsRoot" gencodec:"required"` + ReceiptHash *common.Hash `json:"receiptsRoot" gencodec:"required"` + Bloom *Bloom `json:"logsBloom" gencodec:"required"` + Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"` + Number *hexutil.Big `json:"number" gencodec:"required"` + GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"` + GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"` + Time *hexutil.Uint64 `json:"timestamp" gencodec:"required"` + Extra *hexutil.Bytes `json:"extraData" gencodec:"required"` + MixDigest *common.Hash `json:"mixHash"` + Nonce *BlockNonce `json:"nonce"` + BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"` + WithdrawalHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"` + ExcessDataGas *hexutil.Big `json:"excessDataGas" rlp:"optional"` } var dec Header if err := json.Unmarshal(input, &dec); err != nil { @@ -142,6 +145,9 @@ func (h *Header) UnmarshalJSON(input []byte) error { if dec.BaseFee != nil { h.BaseFee = (*big.Int)(dec.BaseFee) } + if dec.WithdrawalHash != nil { + h.WithdrawalHash = dec.WithdrawalHash + } if dec.ExcessDataGas != nil { h.ExcessDataGas = (*big.Int)(dec.ExcessDataGas) } diff --git a/core/types/gen_header_rlp.go b/core/types/gen_header_rlp.go index ae4f560d2605..679f6a4fe2cf 100644 --- a/core/types/gen_header_rlp.go +++ b/core/types/gen_header_rlp.go @@ -41,8 +41,9 @@ func (obj *Header) EncodeRLP(_w io.Writer) error { w.WriteBytes(obj.MixDigest[:]) w.WriteBytes(obj.Nonce[:]) _tmp1 := obj.BaseFee != nil - _tmp2 := obj.ExcessDataGas != nil - if _tmp1 || _tmp2 { + _tmp2 := obj.WithdrawalHash != nil + _tmp3 := obj.ExcessDataGas != nil + if _tmp1 || _tmp2 || _tmp3 { if obj.BaseFee == nil { w.Write(rlp.EmptyString) } else { @@ -52,7 +53,14 @@ func (obj *Header) EncodeRLP(_w io.Writer) error { w.WriteBigInt(obj.BaseFee) } } - if _tmp2 { + if _tmp2 || _tmp3 { + if obj.WithdrawalHash == nil { + w.Write([]byte{0x80}) + } else { + w.WriteBytes(obj.WithdrawalHash[:]) + } + } + if _tmp3 { if obj.ExcessDataGas == nil { w.Write(rlp.EmptyString) } else { diff --git a/core/types/gen_withdrawal_json.go b/core/types/gen_withdrawal_json.go new file mode 100644 index 000000000000..6a74129dcfcb --- /dev/null +++ b/core/types/gen_withdrawal_json.go @@ -0,0 +1,56 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package types + +import ( + "encoding/json" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +var _ = (*withdrawalMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (w Withdrawal) MarshalJSON() ([]byte, error) { + type Withdrawal struct { + Index hexutil.Uint64 `json:"index"` + Validator hexutil.Uint64 `json:"validatorIndex"` + Recipient common.Address `json:"recipient"` + Amount *hexutil.Big `json:"amount"` + } + var enc Withdrawal + enc.Index = hexutil.Uint64(w.Index) + enc.Validator = hexutil.Uint64(w.Validator) + enc.Recipient = w.Recipient + enc.Amount = (*hexutil.Big)(w.Amount) + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (w *Withdrawal) UnmarshalJSON(input []byte) error { + type Withdrawal struct { + Index *hexutil.Uint64 `json:"index"` + Validator *hexutil.Uint64 `json:"validatorIndex"` + Recipient *common.Address `json:"recipient"` + Amount *hexutil.Big `json:"amount"` + } + var dec Withdrawal + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.Index != nil { + w.Index = uint64(*dec.Index) + } + if dec.Validator != nil { + w.Validator = uint64(*dec.Validator) + } + if dec.Recipient != nil { + w.Recipient = *dec.Recipient + } + if dec.Amount != nil { + w.Amount = (*big.Int)(dec.Amount) + } + return nil +} diff --git a/core/types/gen_withdrawal_rlp.go b/core/types/gen_withdrawal_rlp.go new file mode 100644 index 000000000000..c0f6f4dab50e --- /dev/null +++ b/core/types/gen_withdrawal_rlp.go @@ -0,0 +1,27 @@ +// Code generated by rlpgen. DO NOT EDIT. + +//go:build !norlpgen +// +build !norlpgen + +package types + +import "github.com/ethereum/go-ethereum/rlp" +import "io" + +func (obj *Withdrawal) EncodeRLP(_w io.Writer) error { + w := rlp.NewEncoderBuffer(_w) + _tmp0 := w.List() + w.WriteUint64(obj.Index) + w.WriteUint64(obj.Validator) + w.WriteBytes(obj.Recipient[:]) + if obj.Amount == nil { + w.Write(rlp.EmptyString) + } else { + if obj.Amount.Sign() == -1 { + return rlp.ErrNegativeBigInt + } + w.WriteBigInt(obj.Amount) + } + w.ListEnd(_tmp0) + return w.Flush() +} diff --git a/core/types/withdrawal.go b/core/types/withdrawal.go new file mode 100644 index 000000000000..a7381cdcb507 --- /dev/null +++ b/core/types/withdrawal.go @@ -0,0 +1,57 @@ +// Copyright 2022 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" + "math/big" + + "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 Withdrawal -field-override withdrawalMarshaling -out gen_withdrawal_json.go +//go:generate go run ../../rlp/rlpgen -type Withdrawal -out gen_withdrawal_rlp.go + +// Withdrawal represents a validator withdrawal from the consensus layer. +type Withdrawal struct { + Index uint64 `json:"index"` // monotonically increasing identifier issued by consensus layer + Validator uint64 `json:"validatorIndex"` // index of validator associated with withdrawal + Recipient common.Address `json:"recipient"` // target address for withdrawn ether + Amount *big.Int `json:"amount"` // value of withdrawal in wei +} + +// field type overrides for gencodec +type withdrawalMarshaling struct { + Index hexutil.Uint64 + Validator hexutil.Uint64 + Amount *hexutil.Big +} + +// Withdrawals implements DerivableList for withdrawals. +type Withdrawals []*Withdrawal + +// Len returns the length of s. +func (s Withdrawals) Len() int { return len(s) } + +// EncodeIndex encodes the i'th withdrawal to w. Note that this does not check for errors +// because we assume that *Withdrawal will only ever contain valid withdrawals that were either +// constructed by decoding or via public API in this package. +func (s Withdrawals) EncodeIndex(i int, w *bytes.Buffer) { + rlp.Encode(w, s[i]) +} diff --git a/eth/catalyst/api_test.go b/eth/catalyst/api_test.go index 5acc6e576dd5..0d945993eb37 100644 --- a/eth/catalyst/api_test.go +++ b/eth/catalyst/api_test.go @@ -691,22 +691,21 @@ func setBlockhash(data *beacon.ExecutableDataV1) *beacon.ExecutableDataV1 { number := big.NewInt(0) number.SetUint64(data.Number) header := &types.Header{ - ParentHash: data.ParentHash, - UncleHash: types.EmptyUncleHash, - Coinbase: data.FeeRecipient, - Root: data.StateRoot, - TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)), - ReceiptHash: data.ReceiptsRoot, - Bloom: types.BytesToBloom(data.LogsBloom), - Difficulty: common.Big0, - Number: number, - GasLimit: data.GasLimit, - GasUsed: data.GasUsed, - Time: data.Timestamp, - BaseFee: data.BaseFeePerGas, - ExcessDataGas: data.ExcessDataGas, - Extra: data.ExtraData, - MixDigest: data.Random, + ParentHash: data.ParentHash, + UncleHash: types.EmptyUncleHash, + Coinbase: data.FeeRecipient, + Root: data.StateRoot, + TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)), + ReceiptHash: data.ReceiptsRoot, + Bloom: types.BytesToBloom(data.LogsBloom), + Difficulty: common.Big0, + Number: number, + GasLimit: data.GasLimit, + GasUsed: data.GasUsed, + Time: data.Timestamp, + BaseFee: data.BaseFeePerGas, + Extra: data.ExtraData, + MixDigest: data.Random, } block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */) data.BlockHash = block.Hash() diff --git a/les/catalyst/api_test.go b/les/catalyst/api_test.go index 43f1ade2dfa1..d6522583369b 100644 --- a/les/catalyst/api_test.go +++ b/les/catalyst/api_test.go @@ -177,23 +177,22 @@ func TestShardingExecutePayloadV1(t *testing.T) { block := blocks[9] fakeBlock := types.NewBlock(&types.Header{ - ParentHash: block.ParentHash(), - UncleHash: crypto.Keccak256Hash(nil), - Coinbase: block.Coinbase(), - Root: block.Root(), - TxHash: crypto.Keccak256Hash(nil), - ReceiptHash: crypto.Keccak256Hash(nil), - Bloom: block.Bloom(), - Difficulty: big.NewInt(0), - Number: block.Number(), - GasLimit: block.GasLimit(), - GasUsed: block.GasUsed(), - Time: block.Time(), - Extra: block.Extra(), - MixDigest: block.MixDigest(), - Nonce: types.BlockNonce{}, - BaseFee: block.BaseFee(), - ExcessDataGas: block.ExcessDataGas(), + ParentHash: block.ParentHash(), + UncleHash: crypto.Keccak256Hash(nil), + Coinbase: block.Coinbase(), + Root: block.Root(), + TxHash: crypto.Keccak256Hash(nil), + ReceiptHash: crypto.Keccak256Hash(nil), + Bloom: block.Bloom(), + Difficulty: big.NewInt(0), + Number: block.Number(), + GasLimit: block.GasLimit(), + GasUsed: block.GasUsed(), + Time: block.Time(), + Extra: block.Extra(), + MixDigest: block.MixDigest(), + Nonce: types.BlockNonce{}, + BaseFee: block.BaseFee(), }, nil, nil, nil, trie.NewStackTrie(nil)) _, err := api.ExecutePayloadV1(beacon.ExecutableDataV1{ @@ -209,7 +208,6 @@ func TestShardingExecutePayloadV1(t *testing.T) { Timestamp: fakeBlock.Time(), ExtraData: fakeBlock.Extra(), BaseFeePerGas: fakeBlock.BaseFee(), - ExcessDataGas: fakeBlock.ExcessDataGas(), BlockHash: fakeBlock.Hash(), Transactions: encodeTransactions(fakeBlock.Transactions()), }) diff --git a/miner/worker_test.go b/miner/worker_test.go index e2884a1bae2f..ade1d47f285a 100644 --- a/miner/worker_test.go +++ b/miner/worker_test.go @@ -138,7 +138,10 @@ func newTestWorkerBackend(t *testing.T, chainConfig *params.ChainConfig, engine } genesis := gspec.MustCommit(db) - chain, _ := core.NewBlockChain(db, &core.CacheConfig{TrieDirtyDisabled: true}, gspec.Config, engine, vm.Config{}, nil, nil) + chain, err := core.NewBlockChain(db, &core.CacheConfig{TrieDirtyDisabled: true}, gspec.Config, engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("failed to get new blockchain: %v", err) + } txpool := core.NewTxPool(testTxPoolConfig, chainConfig, chain) // Generate a small n-block chain and an uncle block for it