Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core/types: fix immutability guarantees in Block #27844

Merged
merged 1 commit into from
Aug 4, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 63 additions & 41 deletions core/types/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,23 @@ type Body struct {
Withdrawals []*Withdrawal `rlp:"optional"`
}

// Block represents an entire block in the Ethereum blockchain.
// Block represents an Ethereum block.
//
// Note the Block type tries to be 'immutable', and contains certain caches that rely
// on that. The rules around block immutability are as follows:
//
// - We copy all data when the block is constructed. This makes references held inside
// the block independent of whatever value was passed in.
//
// - We copy all header data on access. This is because any change to the header would mess
// up the cached hash and size values in the block. Calling code is expected to take
// advantage of this to avoid over-allocating!
//
// - When new body data is attached to the block, a shallow copy of the block is returned.
// This ensures block modifications are race-free.
//
// - We do not copy body data on access because it does not affect the caches, and also
// because it would be too expensive.
type Block struct {
header *Header
uncles []*Header
Expand All @@ -195,9 +211,8 @@ type extblock struct {
Withdrawals []*Withdrawal `rlp:"optional"`
}

// NewBlock creates a new block. The input data is copied,
// changes to header and to the field values will not affect the
// block.
// NewBlock creates a new block. 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
Expand Down Expand Up @@ -234,13 +249,11 @@ func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []*
return b
}

// NewBlockWithWithdrawals creates a new block with withdrawals. The input data
// is copied, changes to header and to the field values will not
// affect the block.
// NewBlockWithWithdrawals 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.
// 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 NewBlockWithWithdrawals(header *Header, txs []*Transaction, uncles []*Header, receipts []*Receipt, withdrawals []*Withdrawal, hasher TrieHasher) *Block {
b := NewBlock(header, txs, uncles, receipts, hasher)

Expand All @@ -256,15 +269,7 @@ func NewBlockWithWithdrawals(header *Header, txs []*Transaction, uncles []*Heade
return b.WithWithdrawals(withdrawals)
}

// NewBlockWithHeader creates a block with the given header data. The
// header data is copied, changes to header and to the field values
// will not affect the block.
func NewBlockWithHeader(header *Header) *Block {
return &Block{header: CopyHeader(header)}
}

// CopyHeader creates a deep copy of a block header to prevent side effects from
// modifying a header variable.
// CopyHeader creates a deep copy of a block header.
func CopyHeader(h *Header) *Header {
cpy := *h
if cpy.Difficulty = new(big.Int); h.Difficulty != nil {
Expand Down Expand Up @@ -295,7 +300,7 @@ func CopyHeader(h *Header) *Header {
return &cpy
}

// DecodeRLP decodes the Ethereum
// DecodeRLP decodes a block from RLP.
func (b *Block) DecodeRLP(s *rlp.Stream) error {
var eb extblock
_, size, _ := s.Kind()
Expand All @@ -307,20 +312,28 @@ func (b *Block) DecodeRLP(s *rlp.Stream) error {
return nil
}

// EncodeRLP serializes b into the Ethereum RLP block format.
// EncodeRLP serializes a block as RLP.
func (b *Block) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, extblock{
return rlp.Encode(w, &extblock{
Header: b.header,
Txs: b.transactions,
Uncles: b.uncles,
Withdrawals: b.withdrawals,
})
}

// TODO: copies
// 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}
}

// Accessors for body data. These do not return a copy because the content
// of the body slices does not affect the cached hash/size in block.

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) Transaction(hash common.Hash) *Transaction {
for _, transaction := range b.transactions {
Expand All @@ -331,6 +344,13 @@ func (b *Block) Transaction(hash common.Hash) *Transaction {
return nil
}

// Header returns the block header (as a copy).
func (b *Block) Header() *Header {
return CopyHeader(b.header)
}

// Header value accessors. These do copy!

func (b *Block) Number() *big.Int { return new(big.Int).Set(b.header.Number) }
func (b *Block) GasLimit() uint64 { return b.header.GasLimit }
func (b *Block) GasUsed() uint64 { return b.header.GasUsed }
Expand All @@ -356,10 +376,6 @@ func (b *Block) BaseFee() *big.Int {
return new(big.Int).Set(b.header.BaseFee)
}

func (b *Block) Withdrawals() Withdrawals {
return b.withdrawals
}

func (b *Block) ExcessBlobGas() *uint64 {
var excessBlobGas *uint64
if b.header.ExcessBlobGas != nil {
Expand All @@ -378,11 +394,6 @@ func (b *Block) BlobGasUsed() *uint64 {
return blobGasUsed
}

func (b *Block) Header() *Header { return CopyHeader(b.header) }

// Body returns the non-header content of the block.
func (b *Block) Body() *Body { return &Body{b.transactions, b.uncles, b.withdrawals} }

// Size returns the true RLP encoded storage size of the block, either by encoding
// and returning it, or returning a previously cached value.
func (b *Block) Size() uint64 {
Expand Down Expand Up @@ -415,25 +426,31 @@ func CalcUncleHash(uncles []*Header) common.Hash {
return rlpHash(uncles)
}

// NewBlockWithHeader creates a block with the given header data. The
// header data is copied, changes to header and to the field values
// will not affect the block.
func NewBlockWithHeader(header *Header) *Block {
return &Block{header: CopyHeader(header)}
}

// WithSeal returns a new block with the data from b but the header replaced with
// the sealed one.
func (b *Block) WithSeal(header *Header) *Block {
cpy := *header

return &Block{
header: &cpy,
header: CopyHeader(header),
transactions: b.transactions,
uncles: b.uncles,
withdrawals: b.withdrawals,
}
}

// WithBody returns a new block with the given transaction and uncle contents.
// WithBody returns a copy of the block with the given transaction and uncle contents.
func (b *Block) WithBody(transactions []*Transaction, uncles []*Header) *Block {