From 57d641b6f93821c0feb3755cb4f7e9e25963d420 Mon Sep 17 00:00:00 2001
From: TBC Dev <48684072+tbcd@users.noreply.github.com>
Date: Mon, 29 Nov 2021 16:52:36 +0800
Subject: [PATCH] Avoid redundant Block.Header() deep-copy (#3050)

* Add separate Block.Nonce() and Block.NonceU64()

* Add Block.Seal()

* Avoid redundant Block.Header() deep-copy

* Add warning comment for Block.Header()
---
 cmd/hack/hack.go                            |  4 +--
 cmd/integration/commands/state_stages.go    | 33 ++++++++++-----------
 cmd/state/commands/check_change_sets.go     |  2 +-
 cmd/state/commands/opcode_tracer.go         |  2 +-
 consensus/ethash/sealer_test.go             | 20 ++++++-------
 core/blockchain.go                          |  2 +-
 core/chain_makers.go                        | 17 ++++++++---
 core/rawdb/accessors_chain_test.go          |  8 +++--
 core/types/block.go                         | 23 +++++++++-----
 core/types/block_test.go                    |  6 ++--
 eth/stagedsync/stage_mining_finish.go       |  2 +-
 migrations/receipt_repair.go                |  4 +--
 tests/block_test_util.go                    |  2 +-
 turbo/stages/headerdownload/header_algos.go |  5 ++--
 turbo/transactions/tracing.go               |  2 +-
 15 files changed, 76 insertions(+), 56 deletions(-)

diff --git a/cmd/hack/hack.go b/cmd/hack/hack.go
index c19f9d325..abad2b7b9 100644
--- a/cmd/hack/hack.go
+++ b/cmd/hack/hack.go
@@ -3679,8 +3679,8 @@ func scanReceipts(chaindata string, block uint64) error {
 		fix := true
 		if chainConfig.IsByzantium(blockNum) {
 			receiptSha := types.DeriveSha(receipts1)
-			if receiptSha != block.Header().ReceiptHash {
-				fmt.Printf("(retrace) mismatched receipt headers for block %d: %x, %x\n", block.NumberU64(), receiptSha, block.Header().ReceiptHash)
+			if receiptSha != block.ReceiptHash() {
+				fmt.Printf("(retrace) mismatched receipt headers for block %d: %x, %x\n", block.NumberU64(), receiptSha, block.ReceiptHash())
 				fix = false
 			}
 		}
diff --git a/cmd/integration/commands/state_stages.go b/cmd/integration/commands/state_stages.go
index b21ea229b..0923330a5 100644
--- a/cmd/integration/commands/state_stages.go
+++ b/cmd/integration/commands/state_stages.go
@@ -303,9 +303,9 @@ func syncBySmallSteps(db kv.RwDB, miningConfig params.MiningConfig, ctx context.
 			panic(err)
 		}
 
-		if miner.MiningConfig.Enabled && nextBlock != nil && nextBlock.Header().Coinbase != (common.Address{}) {
-			miner.MiningConfig.Etherbase = nextBlock.Header().Coinbase
-			miner.MiningConfig.ExtraData = nextBlock.Header().Extra
+		if miner.MiningConfig.Enabled && nextBlock != nil && nextBlock.Coinbase() != (common.Address{}) {
+			miner.MiningConfig.Etherbase = nextBlock.Coinbase()
+			miner.MiningConfig.ExtraData = nextBlock.Extra()
 			miningStages.MockExecFunc(stages.MiningCreateBlock, func(firstCycle bool, badBlockUnwind bool, s *stagedsync.StageState, u stagedsync.Unwinder, tx kv.RwTx) error {
 				err = stagedsync.SpawnMiningCreateBlockStage(s, tx,
 					stagedsync.StageMiningCreateBlockCfg(db, miner, *chainConfig, engine, nil, nil, tmpDir),
@@ -314,10 +314,10 @@ func syncBySmallSteps(db kv.RwDB, miningConfig params.MiningConfig, ctx context.
 					return err
 				}
 				miner.MiningBlock.Uncles = nextBlock.Uncles()
-				miner.MiningBlock.Header.Time = nextBlock.Header().Time
-				miner.MiningBlock.Header.GasLimit = nextBlock.Header().GasLimit
-				miner.MiningBlock.Header.Difficulty = nextBlock.Header().Difficulty
-				miner.MiningBlock.Header.Nonce = nextBlock.Header().Nonce
+				miner.MiningBlock.Header.Time = nextBlock.Time()
+				miner.MiningBlock.Header.GasLimit = nextBlock.GasLimit()
+				miner.MiningBlock.Header.Difficulty = nextBlock.Difficulty()
+				miner.MiningBlock.Header.Nonce = nextBlock.Nonce()
 				miner.MiningBlock.LocalTxs = types.NewTransactionsFixedOrder(nextBlock.Transactions())
 				miner.MiningBlock.RemoteTxs = types.NewTransactionsFixedOrder(nil)
 				//debugprint.Headers(miningWorld.Block.Header, nextBlock.Header())
@@ -390,16 +390,15 @@ func checkChanges(expectedAccountChanges map[uint64]*changeset.ChangeSet, tx kv.
 }
 
 func checkMinedBlock(b1, b2 *types.Block, chainConfig *params.ChainConfig) {
-	h1 := b1.Header()
-	h2 := b2.Header()
-	if h1.Root != h2.Root ||
-		(chainConfig.IsByzantium(b1.NumberU64()) && h1.ReceiptHash != h2.ReceiptHash) ||
-		h1.TxHash != h2.TxHash ||
-		h1.ParentHash != h2.ParentHash ||
-		h1.UncleHash != h2.UncleHash ||
-		h1.GasUsed != h2.GasUsed ||
-		!bytes.Equal(h1.Extra, h2.Extra) {
-		debugprint.Headers(h1, h2)
+	if b1.Root() != b2.Root() ||
+		(chainConfig.IsByzantium(b1.NumberU64()) && b1.ReceiptHash() != b2.ReceiptHash()) ||
+		b1.TxHash() != b2.TxHash() ||
+		b1.ParentHash() != b2.ParentHash() ||
+		b1.UncleHash() != b2.UncleHash() ||
+		b1.GasUsed() != b2.GasUsed() ||
+		!bytes.Equal(b1.Extra(), b2.Extra()) { // TODO: Extra() doesn't need to be a copy for a read-only compare
+		// Header()'s deep-copy doesn't matter here since it will panic anyway
+		debugprint.Headers(b1.Header(), b2.Header())
 		panic("blocks are not same")
 	}
 }
diff --git a/cmd/state/commands/check_change_sets.go b/cmd/state/commands/check_change_sets.go
index ea9d50cc6..fae3c8c2b 100644
--- a/cmd/state/commands/check_change_sets.go
+++ b/cmd/state/commands/check_change_sets.go
@@ -147,7 +147,7 @@ func CheckChangeSets(genesis *core.Genesis, logger log.Logger, blockNum uint64,
 		if writeReceipts {
 			if chainConfig.IsByzantium(block.Number().Uint64()) {
 				receiptSha := types.DeriveSha(receipts)
-				if receiptSha != block.Header().ReceiptHash {
+				if receiptSha != block.ReceiptHash() {
 					return fmt.Errorf("mismatched receipt headers for block %d", block.NumberU64())
 				}
 			}
diff --git a/cmd/state/commands/opcode_tracer.go b/cmd/state/commands/opcode_tracer.go
index 557ee5b7c..41baa630f 100644
--- a/cmd/state/commands/opcode_tracer.go
+++ b/cmd/state/commands/opcode_tracer.go
@@ -557,7 +557,7 @@ func OpcodeTracer(genesis *core.Genesis, blockNum uint64, chaindata string, numB
 		}
 		if chainConfig.IsByzantium(block.Number().Uint64()) {
 			receiptSha := types.DeriveSha(receipts)
-			if receiptSha != block.Header().ReceiptHash {
+			if receiptSha != block.ReceiptHash() {
 				return fmt.Errorf("mismatched receipt headers for block %d", block.NumberU64())
 			}
 		}
diff --git a/consensus/ethash/sealer_test.go b/consensus/ethash/sealer_test.go
index 1691f0753..67391b0f4 100644
--- a/consensus/ethash/sealer_test.go
+++ b/consensus/ethash/sealer_test.go
@@ -293,20 +293,20 @@ func TestStaleSubmission(t *testing.T) {
 		}
 		select {
 		case res := <-results:
-			if res.Header().Nonce != fakeNonce {
-				t.Errorf("case %d block nonce mismatch, want %x, get %x", id+1, fakeNonce, res.Header().Nonce)
+			if res.Nonce() != fakeNonce {
+				t.Errorf("case %d block nonce mismatch, want %x, get %x", id+1, fakeNonce, res.Nonce())
 			}
-			if res.Header().MixDigest != fakeDigest {
-				t.Errorf("case %d block digest mismatch, want %x, get %x", id+1, fakeDigest, res.Header().MixDigest)
+			if res.MixDigest() != fakeDigest {
+				t.Errorf("case %d block digest mismatch, want %x, get %x", id+1, fakeDigest, res.MixDigest())
 			}
-			if res.Header().Difficulty.Uint64() != c.headers[c.submitIndex].Difficulty.Uint64() {
-				t.Errorf("case %d block difficulty mismatch, want %d, get %d", id+1, c.headers[c.submitIndex].Difficulty, res.Header().Difficulty)
+			if res.Difficulty().Uint64() != c.headers[c.submitIndex].Difficulty.Uint64() {
+				t.Errorf("case %d block difficulty mismatch, want %d, get %d", id+1, c.headers[c.submitIndex].Difficulty, res.Difficulty())
 			}
-			if res.Header().Number.Uint64() != c.headers[c.submitIndex].Number.Uint64() {
-				t.Errorf("case %d block number mismatch, want %d, get %d", id+1, c.headers[c.submitIndex].Number.Uint64(), res.Header().Number.Uint64())
+			if res.Number().Uint64() != c.headers[c.submitIndex].Number.Uint64() {
+				t.Errorf("case %d block number mismatch, want %d, get %d", id+1, c.headers[c.submitIndex].Number.Uint64(), res.Number().Uint64())
 			}
-			if res.Header().ParentHash != c.headers[c.submitIndex].ParentHash {
-				t.Errorf("case %d block parent hash mismatch, want %s, get %s", id+1, c.headers[c.submitIndex].ParentHash.Hex(), res.Header().ParentHash.Hex())
+			if res.ParentHash() != c.headers[c.submitIndex].ParentHash {
+				t.Errorf("case %d block parent hash mismatch, want %s, get %s", id+1, c.headers[c.submitIndex].ParentHash.Hex(), res.ParentHash().Hex())
 			}
 		case <-time.NewTimer(time.Second).C:
 			t.Errorf("case %d fetch ethash result timeout", id+1)
diff --git a/core/blockchain.go b/core/blockchain.go
index 9ad460934..8985419f3 100644
--- a/core/blockchain.go
+++ b/core/blockchain.go
@@ -152,7 +152,7 @@ func ExecuteBlockEphemerally(
 
 	if chainConfig.IsByzantium(header.Number.Uint64()) && !vmConfig.NoReceipts {
 		receiptSha := types.DeriveSha(receipts)
-		if receiptSha != block.Header().ReceiptHash {
+		if receiptSha != block.ReceiptHash() {
 			return nil, fmt.Errorf("mismatched receipt headers for block %d", block.NumberU64())
 		}
 	}
diff --git a/core/chain_makers.go b/core/chain_makers.go
index b2f7da73a..92a740e3e 100644
--- a/core/chain_makers.go
+++ b/core/chain_makers.go
@@ -185,12 +185,21 @@ func (b *BlockGen) PrevBlock(index int) *types.Block {
 // tied to chain length directly.
 func (b *BlockGen) OffsetTime(seconds int64) {
 	b.header.Time += uint64(seconds)
-	if b.header.Time <= b.parent.Header().Time {
+	parent := b.parent
+	if b.header.Time <= parent.Time() {
 		panic("block time out of range")
 	}
 	chainreader := &FakeChainReader{Cfg: b.config}
-	parent := b.parent.Header()
-	b.header.Difficulty = b.engine.CalcDifficulty(chainreader, b.header.Time, parent.Time, parent.Difficulty, parent.Number.Uint64(), parent.Hash(), parent.UncleHash, parent.Seal)
+	b.header.Difficulty = b.engine.CalcDifficulty(
+		chainreader,
+		b.header.Time,
+		parent.Time(),
+		parent.Difficulty(),
+		parent.NumberU64(),
+		parent.Hash(),
+		parent.UncleHash(),
+		parent.Seal(),
+	)
 }
 
 func (b *BlockGen) GetHeader() *types.Header {
@@ -402,7 +411,7 @@ func makeHeader(chain consensus.ChainReader, parent *types.Block, state *state.I
 			parent.Number().Uint64(),
 			parent.Hash(),
 			parent.UncleHash(),
-			parent.Header().Seal,
+			parent.Seal(),
 		),
 		GasLimit: CalcGasLimit(parent.GasUsed(), parent.GasLimit(), parent.GasLimit(), parent.GasLimit()),
 		Number:   new(big.Int).Add(parent.Number(), common.Big1),
diff --git a/core/rawdb/accessors_chain_test.go b/core/rawdb/accessors_chain_test.go
index ea7022b14..2bfde5c5b 100644
--- a/core/rawdb/accessors_chain_test.go
+++ b/core/rawdb/accessors_chain_test.go
@@ -154,7 +154,7 @@ func TestBlockStorage(t *testing.T) {
 	}
 	if entry := ReadHeader(tx, block.Hash(), block.NumberU64()); entry == nil {
 		t.Fatalf("Stored header not found")
-	} else if entry.Hash() != block.Header().Hash() {
+	} else if entry.Hash() != block.Hash() {
 		t.Fatalf("Retrieved header mismatch: have %v, want %v", entry, block.Header())
 	}
 	if entry := ReadBodyWithTransactions(tx, block.Hash(), block.NumberU64()); entry == nil {
@@ -186,8 +186,10 @@ func TestPartialBlockStorage(t *testing.T) {
 		TxHash:      types.EmptyRootHash,
 		ReceiptHash: types.EmptyRootHash,
 	})
+	header := block.Header() // Not identical to struct literal above, due to other fields
+
 	// Store a header and check that it's not recognized as a block
-	WriteHeader(tx, block.Header())
+	WriteHeader(tx, header)
 	if entry := ReadBlock(tx, block.Hash(), block.NumberU64()); entry != nil {
 		t.Fatalf("Non existent block returned: %v", entry)
 	}
@@ -203,7 +205,7 @@ func TestPartialBlockStorage(t *testing.T) {
 	DeleteBody(tx, block.Hash(), block.NumberU64())
 
 	// Store a header and a body separately and check reassembly
-	WriteHeader(tx, block.Header())
+	WriteHeader(tx, header)
 	if err := WriteBody(tx, block.Hash(), block.NumberU64(), block.Body()); err != nil {
 		t.Fatal(err)
 	}
diff --git a/core/types/block.go b/core/types/block.go
index dccd167b8..0f808920d 100644
--- a/core/types/block.go
+++ b/core/types/block.go
@@ -629,6 +629,17 @@ func (h *Header) EmptyReceipts() bool {
 	return h.ReceiptHash == EmptyRootHash
 }
 
+func (h *Header) copySeal() []rlp.RawValue {
+	seal := h.Seal
+	if len(seal) > 0 {
+		seal = make([]rlp.RawValue, len(seal))
+		for i, s := range h.Seal {
+			seal[i] = common.CopyBytes(s)
+		}
+	}
+	return seal
+}
+
 // Body is a simple (mutable, non-safe) data container for storing and moving
 // a block's data contents (transactions and uncles) together.
 type Body struct {
@@ -1016,12 +1027,7 @@ func CopyHeader(h *Header) *Header {
 		cpy.Extra = make([]byte, len(h.Extra))
 		copy(cpy.Extra, h.Extra)
 	}
-	if len(h.Seal) > 0 {
-		cpy.Seal = make([]rlp.RawValue, len(h.Seal))
-		for i := range h.Seal {
-			cpy.Seal[i] = common.CopyBytes(h.Seal[i])
-		}
-	}
+	cpy.Seal = h.copySeal()
 	return &cpy
 }
 
@@ -1203,7 +1209,8 @@ func (b *Block) Time() uint64         { return b.header.Time }
 
 func (b *Block) NumberU64() uint64        { return b.header.Number.Uint64() }
 func (b *Block) MixDigest() common.Hash   { return b.header.MixDigest }
-func (b *Block) Nonce() uint64            { return binary.BigEndian.Uint64(b.header.Nonce[:]) }
+func (b *Block) Nonce() BlockNonce        { return b.header.Nonce }
+func (b *Block) NonceU64() uint64         { return b.header.Nonce.Uint64() }
 func (b *Block) Bloom() Bloom             { return b.header.Bloom }
 func (b *Block) Coinbase() common.Address { return b.header.Coinbase }
 func (b *Block) Root() common.Hash        { return b.header.Root }
@@ -1218,7 +1225,9 @@ func (b *Block) BaseFee() *big.Int {
 	}
 	return new(big.Int).Set(b.header.BaseFee)
 }
+func (b *Block) Seal() (seal []rlp.RawValue) { return b.header.copySeal() }
 
+// Header returns a deep-copy of the entire block header using CopyHeader()
 func (b *Block) Header() *Header { return CopyHeader(b.header) }
 
 // Body returns the non-header content of the block.
diff --git a/core/types/block_test.go b/core/types/block_test.go
index e8f01f1c4..b3ecefd15 100644
--- a/core/types/block_test.go
+++ b/core/types/block_test.go
@@ -52,7 +52,7 @@ func TestBlockEncoding(t *testing.T) {
 	check("MixDigest", block.MixDigest(), common.HexToHash("bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff498"))
 	check("Root", block.Root(), common.HexToHash("ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017"))
 	check("Hash", block.Hash(), common.HexToHash("0a5843ac1cb04865017cb35a57b50b07084e5fcee39b5acadade33149f4fff9e"))
-	check("Nonce", block.Nonce(), uint64(0xa13a5a8c8f2bb1c4))
+	check("Nonce", block.NonceU64(), uint64(0xa13a5a8c8f2bb1c4))
 	check("Time", block.Time(), uint64(1426516743))
 	check("Size", block.Size(), common.StorageSize(len(blockEnc)))
 
@@ -89,7 +89,7 @@ func TestEIP1559BlockEncoding(t *testing.T) {
 	check("MixDigest", block.MixDigest(), common.HexToHash("bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff498"))
 	check("Root", block.Root(), common.HexToHash("ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017"))
 	check("Hash", block.Hash(), common.HexToHash("c7252048cd273fe0dac09650027d07f0e3da4ee0675ebbb26627cea92729c372"))
-	check("Nonce", block.Nonce(), uint64(0xa13a5a8c8f2bb1c4))
+	check("Nonce", block.NonceU64(), uint64(0xa13a5a8c8f2bb1c4))
 	check("Time", block.Time(), uint64(1426516743))
 	check("Size", block.Size(), common.StorageSize(len(blockEnc)))
 	check("BaseFee", block.BaseFee(), new(big.Int).SetUint64(params.InitialBaseFee))
@@ -154,7 +154,7 @@ func TestEIP2718BlockEncoding(t *testing.T) {
 	check("Coinbase", block.Coinbase(), common.HexToAddress("8888f1f195afa192cfee860698584c030f4c9db1"))
 	check("MixDigest", block.MixDigest(), common.HexToHash("bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff498"))
 	check("Root", block.Root(), common.HexToHash("ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017"))
-	check("Nonce", block.Nonce(), uint64(0xa13a5a8c8f2bb1c4))
+	check("Nonce", block.NonceU64(), uint64(0xa13a5a8c8f2bb1c4))
 	check("Time", block.Time(), uint64(1426516743))
 	check("Size", block.Size(), common.StorageSize(len(blockEnc)))
 
diff --git a/eth/stagedsync/stage_mining_finish.go b/eth/stagedsync/stage_mining_finish.go
index 2e293d348..14afe5e1c 100644
--- a/eth/stagedsync/stage_mining_finish.go
+++ b/eth/stagedsync/stage_mining_finish.go
@@ -55,7 +55,7 @@ func SpawnMiningFinishStage(s *StageState, tx kv.RwTx, cfg MiningFinishCfg, quit
 	//prev = sealHash
 
 	// Tests may set pre-calculated nonce
-	if block.Header().Nonce.Uint64() != 0 {
+	if block.NonceU64() != 0 {
 		cfg.miningState.MiningResultCh <- block
 		return nil
 	}
diff --git a/migrations/receipt_repair.go b/migrations/receipt_repair.go
index d1ee62d59..435a9d5cc 100644
--- a/migrations/receipt_repair.go
+++ b/migrations/receipt_repair.go
@@ -123,8 +123,8 @@ var ReceiptRepair = Migration{
 			fix := true
 			if chainConfig.IsByzantium(block.Number().Uint64()) {
 				receiptSha := types.DeriveSha(receipts1)
-				if receiptSha != block.Header().ReceiptHash {
-					fmt.Printf("(retrace) mismatched receipt headers for block %d: %x, %x\n", block.NumberU64(), receiptSha, block.Header().ReceiptHash)
+				if receiptSha != block.ReceiptHash() {
+					fmt.Printf("(retrace) mismatched receipt headers for block %d: %x, %x\n", block.NumberU64(), receiptSha, block.ReceiptHash())
 					fix = false
 				}
 			}
diff --git a/tests/block_test_util.go b/tests/block_test_util.go
index bdde49857..13885cbce 100644
--- a/tests/block_test_util.go
+++ b/tests/block_test_util.go
@@ -307,7 +307,7 @@ func (t *BlockTest) validateImportedHeaders(tx kv.Tx, validBlocks []btBlock) err
 		if err := validateHeader(bmap[b.Hash()].BlockHeader, b.Header()); err != nil {
 			return fmt.Errorf("imported block header validation failed: %w", err)
 		}
-		b, _ = rawdb.ReadBlockByHash(tx, b.Header().ParentHash)
+		b, _ = rawdb.ReadBlockByHash(tx, b.ParentHash())
 	}
 	return nil
 }
diff --git a/turbo/stages/headerdownload/header_algos.go b/turbo/stages/headerdownload/header_algos.go
index cf1ad0105..28a426eef 100644
--- a/turbo/stages/headerdownload/header_algos.go
+++ b/turbo/stages/headerdownload/header_algos.go
@@ -1024,11 +1024,12 @@ func (hd *HeaderDownload) Fetching() bool {
 }
 
 func (hd *HeaderDownload) AddMinedBlock(block *types.Block) error {
+	header := block.Header()
 	buf := bytes.NewBuffer(nil)
-	if err := block.Header().EncodeRLP(buf); err != nil {
+	if err := header.EncodeRLP(buf); err != nil {
 		return err
 	}
-	segments, _, err := hd.SingleHeaderAsSegment(buf.Bytes(), block.Header())
+	segments, _, err := hd.SingleHeaderAsSegment(buf.Bytes(), header)
 	if err != nil {
 		return err
 	}
diff --git a/turbo/transactions/tracing.go b/turbo/transactions/tracing.go
index 66ea3420f..c7341802f 100644
--- a/turbo/transactions/tracing.go
+++ b/turbo/transactions/tracing.go
@@ -54,7 +54,7 @@ func ComputeTxEnv(ctx context.Context, block *types.Block, cfg *params.ChainConf
 		statedb.Prepare(tx.Hash(), blockHash, idx)
 
 		// Assemble the transaction call message and return if the requested offset
-		msg, _ := tx.AsMessage(*signer, block.Header().BaseFee)
+		msg, _ := tx.AsMessage(*signer, block.BaseFee())
 		TxContext := core.NewEVMTxContext(msg)
 		if idx == int(txIndex) {
 			return msg, BlockContext, TxContext, statedb, reader, nil