diff --git a/core/types/block.go b/core/types/block.go index 9803d344a0..24472a2830 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -178,7 +178,23 @@ type Body struct { Uncles []*Header } -// 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 @@ -270,8 +286,7 @@ func NewBlock(header *Header, body *Body, receipts []*Receipt, hasher TrieHasher return b } -// 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 { @@ -302,7 +317,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() @@ -314,7 +329,7 @@ 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{ Header: b.header, @@ -333,7 +348,14 @@ func (b *StorageBlock) DecodeRLP(s *rlp.Stream) error { return nil } -// 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} +} + +// 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 } @@ -347,6 +369,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 } @@ -374,11 +403,6 @@ func (b *Block) BaseFee() *big.Int { return new(big.Int).Set(b.header.BaseFee) } -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) HashNoNonce() common.Hash { return b.header.HashNoNonce() } @@ -419,10 +443,8 @@ func NewBlockWithHeader(header *Header) *Block { // 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, }