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: deferred verification of stateRoot #36

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
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
38 changes: 36 additions & 2 deletions consensus/parlia/parlia.go
Original file line number Diff line number Diff line change
Expand Up @@ -619,9 +619,43 @@ func (p *Parlia) verifyHeader(chain consensus.ChainHeaderReader, header *types.H
if header.ParentBeaconRoot != nil {
return fmt.Errorf("invalid parentBeaconRoot, have %#x, expected nil", header.ParentBeaconRoot)
}
if header.ParentRoot != nil {
return fmt.Errorf("invalid ParentRoot, have %#x, expected nil", header.ParentRoot)
}
if header.ParentReceiptHash != nil {
return fmt.Errorf("invalid ParentReceiptHash, have %#x, expected nil", header.ParentReceiptHash)
}
if header.ParentBloom != nil {
return fmt.Errorf("invalid ParentBloom, have %#x, expected nil", header.ParentBloom)
}
if header.ParentGasUsed != nil {
return fmt.Errorf("invalid ParentGasUsed, have %#x, expected nil", header.ParentGasUsed)
}
} else {
if header.ParentBeaconRoot == nil || *header.ParentBeaconRoot != (common.Hash{}) {
return fmt.Errorf("invalid parentBeaconRoot, have %#x, expected zero hash", header.ParentBeaconRoot)
if header.ParentBeaconRoot == nil {
return fmt.Errorf("invalid parentBeaconRoot, have nil, expected %s", common.Hash{})
} else if *header.ParentBeaconRoot != (common.Hash{}) {
return fmt.Errorf("invalid parentBeaconRoot, have %s, expected %s", *header.ParentBeaconRoot, common.Hash{})
}
if header.ParentRoot == nil {
return fmt.Errorf("invalid ParentRoot, have nil, expected %s", parent.Root)
} else if *header.ParentRoot != parent.Root {
return fmt.Errorf("invalid ParentRoot, have %s, expected %s", *header.ParentRoot, parent.Root)
}
if header.ParentReceiptHash == nil {
return fmt.Errorf("invalid ParentReceiptHash, have nil, expected %s", parent.ReceiptHash)
} else if *header.ParentRoot != parent.Root {
return fmt.Errorf("invalid ParentReceiptHash, have %s, expected %s", *header.ParentReceiptHash, parent.ReceiptHash)
}
if header.ParentBloom == nil {
return fmt.Errorf("invalid ParentBloom, have nil, expected %s", parent.Bloom)
} else if *header.ParentRoot != parent.Root {
return fmt.Errorf("invalid ParentBloom, have %s, expected %s", *header.ParentBloom, parent.Bloom)
}
if header.ParentGasUsed == nil {
return fmt.Errorf("invalid ParentGasUsed, have nil, expected %d", parent.GasUsed)
} else if *header.ParentGasUsed != parent.GasUsed {
return fmt.Errorf("invalid ParentGasUsed, have %d, expected %d", *header.ParentGasUsed, parent.GasUsed)
}
}

Expand Down
100 changes: 84 additions & 16 deletions core/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import (
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/triedb"
"github.com/ethereum/go-ethereum/triedb/hashdb"
"github.com/ethereum/go-ethereum/triedb/pathdb"
Expand Down Expand Up @@ -259,17 +260,18 @@ type BlockChain struct {
triesInMemory uint64
txIndexer *txIndexer // Transaction indexer, might be nil if not enabled

hc *HeaderChain
rmLogsFeed event.Feed
chainFeed event.Feed
chainSideFeed event.Feed
chainHeadFeed event.Feed
chainBlockFeed event.Feed
logsFeed event.Feed
blockProcFeed event.Feed
finalizedHeaderFeed event.Feed
scope event.SubscriptionScope
genesisBlock *types.Block
hc *HeaderChain
rmLogsFeed event.Feed
chainFeed event.Feed
chainSideFeed event.Feed
chainHeadFeed event.Feed
chainBlockFeed event.Feed
logsFeed event.Feed
blockProcFeed event.Feed
finalizedHeaderFeed event.Feed
highestVerifiedHeaderFeed event.Feed
scope event.SubscriptionScope
genesisBlock *types.Block

// This mutex synchronizes chain write operations.
// Readers don't need to take it, they can just read the database.
Expand Down Expand Up @@ -1962,6 +1964,7 @@ func (bc *BlockChain) writeBlockAndSetHead(block *types.Block, receipts []*types
}
}
if emitHeadEvent {
bc.highestVerifiedHeaderFeed.Send(HighestVerifiedHeaderEvent{Header: block.Header()})
bc.chainHeadFeed.Send(ChainHeadEvent{Block: block})
if finalizedHeader != nil {
bc.finalizedHeaderFeed.Send(FinalizedHeaderEvent{finalizedHeader})
Expand Down Expand Up @@ -2266,11 +2269,21 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)

// Validate the state using the default validator
vstart := time.Now()
if err := bc.validator.ValidateState(block, statedb, receipts, usedGas); err != nil {
log.Error("validate state failed", "error", err)
bc.reportBlock(block, receipts, err)
statedb.StopPrefetcher()
return it.index, err
bohr := bc.chainConfig.IsBohr(block.Number(), block.Time())
if !bohr {
if err := bc.validator.ValidateState(block, statedb, receipts, usedGas); err != nil {
log.Error("validate state failed", "error", err)
bc.reportBlock(block, receipts, err)
statedb.StopPrefetcher()
return it.index, err
}
} else {
if err := bc.fillStateFields(block, statedb, receipts, usedGas); err != nil {
log.Error("fill state failed", "error", err)
bc.reportBlock(block, receipts, err)
statedb.StopPrefetcher()
return it.index, err
}
}
vtime := time.Since(vstart)
proctime := time.Since(start) // processing + validation
Expand Down Expand Up @@ -2391,6 +2404,7 @@ func (bc *BlockChain) updateHighestVerifiedHeader(header *types.Header) {
reorg, err := bc.forker.ReorgNeededWithFastFinality(currentBlock, header)
if err == nil && reorg {
bc.highestVerifiedHeader.Store(types.CopyHeader(header))
bc.highestVerifiedHeaderFeed.Send(HighestVerifiedHeaderEvent{Header: header})
log.Trace("updateHighestVerifiedHeader", "number", header.Number.Uint64(), "hash", header.Hash())
}
}
Expand Down Expand Up @@ -3253,3 +3267,57 @@ func (bc *BlockChain) SetTrieFlushInterval(interval time.Duration) {
func (bc *BlockChain) GetTrieFlushInterval() time.Duration {
return time.Duration(bc.flushInterval.Load())
}

func (bc *BlockChain) fillStateFields(block *types.Block, statedb *state.StateDB, receipts types.Receipts, usedGas uint64) error {
header := block.Header()
if block.GasUsed() != usedGas {
log.Error("invalid gasUsed", "remote", block.GasUsed(), "local", usedGas)
header.GasUsed = usedGas
}
CheckAndFillFuns := []func() error{
func() error {
rbloom := types.CreateBloom(receipts)
if rbloom != header.Bloom {
log.Error("invalid logsBloom", "remote", header.Bloom, "local", rbloom)
header.Bloom = rbloom
}
return nil
},
func() error {
receiptSha := types.DeriveSha(receipts, trie.NewStackTrie(nil))
if receiptSha != header.ReceiptHash {
log.Error("invalid receiptsRoot", "remote", header.ReceiptHash, "local", receiptSha)
header.ReceiptHash = receiptSha
}
return nil
},
func() error {
if root := statedb.IntermediateRoot(bc.chainConfig.IsEIP158(header.Number)); header.Root != root {
if statedb.Error() != nil {
return fmt.Errorf("invalid stateRoott dberr: %w", statedb.Error())
} else {
log.Error("invalid stateRoot", "remote", header.Root, "local", root)
header.Root = root
}
}
return nil
},
}

CheckAndFillRes := make(chan error, len(CheckAndFillFuns))
for _, f := range CheckAndFillFuns {
tmpFunc := f
go func() {
CheckAndFillRes <- tmpFunc()
}()
}

var err error
for i := 0; i < len(CheckAndFillFuns); i++ {
r := <-CheckAndFillRes
if r != nil && err == nil {
err = r
}
}
return err
}
14 changes: 14 additions & 0 deletions core/blockchain_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,15 @@ func (bc *BlockChain) GetHeader(hash common.Hash, number uint64) *types.Header {
return bc.hc.GetHeader(hash, number)
}

// GetVerifiedHeaderByHash retrieves a verified block header, it may be only in memory.
func (bc *BlockChain) GetVerifiedHeaderByHash(hash common.Hash) *types.Header {
highestVerifiedHeader := bc.highestVerifiedHeader.Load()
if highestVerifiedHeader != nil && highestVerifiedHeader.Hash() == hash {
return highestVerifiedHeader
}
return bc.hc.GetHeaderByHash(hash)
}

// GetHeaderByHash retrieves a block header from the database by hash, caching it if
// found.
func (bc *BlockChain) GetHeaderByHash(hash common.Hash) *types.Header {
Expand Down Expand Up @@ -486,6 +495,11 @@ func (bc *BlockChain) SubscribeChainHeadEvent(ch chan<- ChainHeadEvent) event.Su
return bc.scope.Track(bc.chainHeadFeed.Subscribe(ch))
}

// SubscribeHighestVerifiedHeaderEvent registers a subscription of HighestVerifiedHeaderEvent.
func (bc *BlockChain) SubscribeHighestVerifiedHeaderEvent(ch chan<- HighestVerifiedHeaderEvent) event.Subscription {
return bc.scope.Track(bc.highestVerifiedHeaderFeed.Subscribe(ch))
}

// SubscribeChainBlockEvent registers a subscription of ChainBlockEvent.
func (bc *BlockChain) SubscribeChainBlockEvent(ch chan<- ChainHeadEvent) event.Subscription {
return bc.scope.Track(bc.chainBlockFeed.Subscribe(ch))
Expand Down
4 changes: 4 additions & 0 deletions core/chain_makers.go
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,10 @@ func (cm *chainMaker) makeHeader(parent *types.Block, state *state.StateDB, engi
}
if cm.config.Parlia == nil || cm.config.IsBohr(header.Number, header.Time) {
header.ParentBeaconRoot = new(common.Hash)
header.ParentRoot = &parent.Header().Root
header.ParentReceiptHash = &parent.Header().ReceiptHash
header.ParentBloom = &parent.Header().Bloom
header.ParentGasUsed = &parent.Header().GasUsed
}
}
return header
Expand Down
2 changes: 2 additions & 0 deletions core/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,5 @@ type ChainSideEvent struct {
}

type ChainHeadEvent struct{ Block *types.Block }

type HighestVerifiedHeaderEvent struct{ Header *types.Header }
4 changes: 4 additions & 0 deletions core/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,10 @@ func (g *Genesis) ToBlock() *types.Block {
// by definition.
if conf.Parlia == nil || conf.IsBohr(num, g.Timestamp) {
head.ParentBeaconRoot = new(common.Hash)
head.ParentRoot = nil
head.ParentReceiptHash = nil
head.ParentBloom = nil
head.ParentGasUsed = nil
}

// EIP-4844 fields
Expand Down
50 changes: 49 additions & 1 deletion core/types/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
)

Expand Down Expand Up @@ -121,6 +122,11 @@ type Header struct {

// ParentBeaconRoot was added by EIP-4788 and is ignored in legacy headers.
ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`

ParentRoot *common.Hash `json:"parentStateRoot" rlp:"optional"`
ParentReceiptHash *common.Hash `json:"parentReceiptsRoot" rlp:"optional"`
ParentBloom *Bloom `json:"parentLogsBloom" rlp:"optional"`
ParentGasUsed *uint64 `json:"parentGasUsed" rlp:"optional"`
}

// field type overrides for gencodec
Expand All @@ -140,7 +146,7 @@ type headerMarshaling struct {
// Hash returns the block hash of the header, which is simply the keccak256 hash of its
// RLP encoding.
func (h *Header) Hash() common.Hash {
return rlpHash(h)
return rlpForBlockHash(h)
}

var headerSize = common.StorageSize(reflect.TypeOf(Header{}).Size())
Expand Down Expand Up @@ -708,6 +714,10 @@ func EncodeSigHeader(w io.Writer, header *Header, chainId *big.Int) {
header.BlobGasUsed,
header.ExcessBlobGas,
header.ParentBeaconRoot,
header.ParentRoot,
header.ParentReceiptHash,
header.ParentBloom,
header.ParentGasUsed,
})
} else {
err = rlp.Encode(w, []interface{}{
Expand All @@ -733,3 +743,41 @@ func EncodeSigHeader(w io.Writer, header *Header, chainId *big.Int) {
panic("can't encode: " + err.Error())
}
}

func rlpForBlockHash(header *Header) (h common.Hash) {
if header.ParentBeaconRoot != nil && *header.ParentBeaconRoot == (common.Hash{}) {
sha := hasherPool.Get().(crypto.KeccakState)
defer hasherPool.Put(sha)
sha.Reset()
rlp.Encode(sha, []interface{}{
header.ParentHash,
header.UncleHash,
header.Coinbase,
// header.Root,
header.TxHash,
// header.ReceiptHash,
// header.Bloom,
header.Difficulty,
header.Number,
header.GasLimit,
// header.GasUsed,
header.Time,
header.Extra,
header.MixDigest,
header.Nonce,
header.BaseFee,
header.WithdrawalsHash,
header.BlobGasUsed,
header.ExcessBlobGas,
header.ParentBeaconRoot,
header.ParentRoot,
header.ParentReceiptHash,
header.ParentBloom,
header.ParentGasUsed,
})
sha.Read(h[:])
return h
} else {
return rlpHash(header)
}
}
Loading
Loading