Skip to content

Commit

Permalink
eth: core: implement finalized block
Browse files Browse the repository at this point in the history
  • Loading branch information
MariusVanDerWijden committed Jan 24, 2022
1 parent 4dddcc6 commit c2da34c
Show file tree
Hide file tree
Showing 10 changed files with 83 additions and 12 deletions.
33 changes: 28 additions & 5 deletions core/blockchain.go
Expand Up @@ -47,9 +47,10 @@ import (
)

var (
headBlockGauge = metrics.NewRegisteredGauge("chain/head/block", nil)
headHeaderGauge = metrics.NewRegisteredGauge("chain/head/header", nil)
headFastBlockGauge = metrics.NewRegisteredGauge("chain/head/receipt", nil)
headBlockGauge = metrics.NewRegisteredGauge("chain/head/block", nil)
headHeaderGauge = metrics.NewRegisteredGauge("chain/head/header", nil)
headFastBlockGauge = metrics.NewRegisteredGauge("chain/head/receipt", nil)
headFinalizedBlockGauge = metrics.NewRegisteredGauge("chain/head/finalized", nil)

accountReadTimer = metrics.NewRegisteredTimer("chain/account/reads", nil)
accountHashTimer = metrics.NewRegisteredTimer("chain/account/hashes", nil)
Expand Down Expand Up @@ -187,8 +188,9 @@ type BlockChain struct {
// Readers don't need to take it, they can just read the database.
chainmu *syncx.ClosableMutex

currentBlock atomic.Value // Current head of the block chain
currentFastBlock atomic.Value // Current head of the fast-sync chain (may be above the block chain!)
currentBlock atomic.Value // Current head of the block chain
currentFastBlock atomic.Value // Current head of the fast-sync chain (may be above the block chain!)
currentFinalizedBlock atomic.Value // Current finalized head

stateCache state.Database // State database to reuse between imports (contains state cache)
bodyCache *lru.Cache // Cache for the most recent block bodies
Expand Down Expand Up @@ -264,6 +266,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
var nilBlock *types.Block
bc.currentBlock.Store(nilBlock)
bc.currentFastBlock.Store(nilBlock)
bc.currentFinalizedBlock.Store(nilBlock)

// Initialize the chain with ancient data if it isn't empty.
var txIndexBlock uint64
Expand Down Expand Up @@ -460,8 +463,17 @@ func (bc *BlockChain) loadLastState() error {
headFastBlockGauge.Update(int64(block.NumberU64()))
}
}

// Restore the last known finalized block
if head := rawdb.ReadFinalizedBlockHash(bc.db); head != (common.Hash{}) {
if block := bc.GetBlockByHash(head); block != nil {
bc.currentFinalizedBlock.Store(block)
headFinalizedBlockGauge.Update(int64(block.NumberU64()))
}
}
// Issue a status log for the user
currentFastBlock := bc.CurrentFastBlock()
currentFinalizedBlock := bc.CurrentFinalizedBlock()

headerTd := bc.GetTd(currentHeader.Hash(), currentHeader.Number.Uint64())
blockTd := bc.GetTd(currentBlock.Hash(), currentBlock.NumberU64())
Expand All @@ -470,6 +482,11 @@ func (bc *BlockChain) loadLastState() error {
log.Info("Loaded most recent local header", "number", currentHeader.Number, "hash", currentHeader.Hash(), "td", headerTd, "age", common.PrettyAge(time.Unix(int64(currentHeader.Time), 0)))
log.Info("Loaded most recent local full block", "number", currentBlock.Number(), "hash", currentBlock.Hash(), "td", blockTd, "age", common.PrettyAge(time.Unix(int64(currentBlock.Time()), 0)))
log.Info("Loaded most recent local fast block", "number", currentFastBlock.Number(), "hash", currentFastBlock.Hash(), "td", fastTd, "age", common.PrettyAge(time.Unix(int64(currentFastBlock.Time()), 0)))

if currentFinalizedBlock != nil {
finalTd := bc.GetTd(currentFinalizedBlock.Hash(), currentFinalizedBlock.NumberU64())
log.Info("Loaded most recent local finalized block", "number", currentFinalizedBlock.Number(), "hash", currentFinalizedBlock.Hash(), "td", finalTd, "age", common.PrettyAge(time.Unix(int64(currentFinalizedBlock.Time()), 0)))
}
if pivot := rawdb.ReadLastPivotNumber(bc.db); pivot != nil {
log.Info("Loaded last fast-sync pivot marker", "number", *pivot)
}
Expand All @@ -484,6 +501,12 @@ func (bc *BlockChain) SetHead(head uint64) error {
return err
}

// SetFinalized sets the finalized block.
func (bc *BlockChain) SetFinalized(block *types.Block) {
bc.currentFinalizedBlock.Store(block)
rawdb.WriteFinalizedBlockHash(bc.db, block.Hash())
}

// setHeadBeyondRoot rewinds the local chain to a new head with the extra condition
// that the rewind must pass the specified state root. This method is meant to be
// used when rewinding with snapshots enabled to ensure that we go back further than
Expand Down
6 changes: 6 additions & 0 deletions core/blockchain_reader.go
Expand Up @@ -49,6 +49,12 @@ func (bc *BlockChain) CurrentFastBlock() *types.Block {
return bc.currentFastBlock.Load().(*types.Block)
}

// CurrentFinalizedBlock retrieves the current finalized block of the canonical
// chain. The block is retrieved from the blockchain's internal cache.
func (bc *BlockChain) CurrentFinalizedBlock() *types.Block {
return bc.currentFinalizedBlock.Load().(*types.Block)
}

// HasHeader checks if a block header is present in the database or not, caching
// it if present.
func (bc *BlockChain) HasHeader(hash common.Hash, number uint64) bool {
Expand Down
16 changes: 16 additions & 0 deletions core/rawdb/accessors_chain.go
Expand Up @@ -216,6 +216,22 @@ func WriteHeadFastBlockHash(db ethdb.KeyValueWriter, hash common.Hash) {
}
}

// ReadFinalizedBlockHash retrieves the hash of the finalized block.
func ReadFinalizedBlockHash(db ethdb.KeyValueReader) common.Hash {
data, _ := db.Get(headFinalizedBlockKey)
if len(data) == 0 {
return common.Hash{}
}
return common.BytesToHash(data)
}

// WriteFinalizedBlockHash stores the hash of the finalized block.
func WriteFinalizedBlockHash(db ethdb.KeyValueWriter, hash common.Hash) {
if err := db.Put(headFinalizedBlockKey, hash.Bytes()); err != nil {
log.Crit("Failed to store last fast block's hash", "err", err)
}
}

// ReadLastPivotNumber retrieves the number of the last pivot block. If the node
// full synced, the last pivot will always be nil.
func ReadLastPivotNumber(db ethdb.KeyValueReader) *uint64 {
Expand Down
4 changes: 2 additions & 2 deletions core/rawdb/database.go
Expand Up @@ -392,8 +392,8 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
default:
var accounted bool
for _, meta := range [][]byte{
databaseVersionKey, headHeaderKey, headBlockKey, headFastBlockKey, lastPivotKey,
fastTrieProgressKey, snapshotDisabledKey, SnapshotRootKey, snapshotJournalKey,
databaseVersionKey, headHeaderKey, headBlockKey, headFastBlockKey, headFinalizedBlockKey,
lastPivotKey, fastTrieProgressKey, snapshotDisabledKey, SnapshotRootKey, snapshotJournalKey,
snapshotGeneratorKey, snapshotRecoveryKey, txIndexTailKey, fastTxLookupLimitKey,
uncleanShutdownKey, badBlockKey, transitionStatusKey,
} {
Expand Down
3 changes: 3 additions & 0 deletions core/rawdb/schema.go
Expand Up @@ -39,6 +39,9 @@ var (
// headFastBlockKey tracks the latest known incomplete block's hash during fast sync.
headFastBlockKey = []byte("LastFast")

// headFinalizedBlockKey tracks the latest known finalized block hash.
headFinalizedBlockKey = []byte("LastFinalized")

// lastPivotKey tracks the last pivot block used by fast sync (to reenable on sethead).
lastPivotKey = []byte("LastPivot")

Expand Down
4 changes: 4 additions & 0 deletions eth/api.go
Expand Up @@ -285,6 +285,8 @@ func (api *PublicDebugAPI) DumpBlock(blockNr rpc.BlockNumber) (state.Dump, error
var block *types.Block
if blockNr == rpc.LatestBlockNumber {
block = api.eth.blockchain.CurrentBlock()
} else if blockNr == rpc.FinalizedBlockNumber {
block = api.eth.blockchain.CurrentFinalizedBlock()
} else {
block = api.eth.blockchain.GetBlockByNumber(uint64(blockNr))
}
Expand Down Expand Up @@ -373,6 +375,8 @@ func (api *PublicDebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, sta
var block *types.Block
if number == rpc.LatestBlockNumber {
block = api.eth.blockchain.CurrentBlock()
} else if number == rpc.FinalizedBlockNumber {
block = api.eth.blockchain.CurrentFinalizedBlock()
} else {
block = api.eth.blockchain.GetBlockByNumber(uint64(number))
}
Expand Down
4 changes: 4 additions & 0 deletions eth/api_backend.go
Expand Up @@ -71,6 +71,8 @@ func (b *EthAPIBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumb
// Otherwise resolve and return the block
if number == rpc.LatestBlockNumber {
return b.eth.blockchain.CurrentBlock().Header(), nil
} else if number == rpc.FinalizedBlockNumber {
return b.eth.blockchain.CurrentFinalizedBlock().Header(), nil
}
return b.eth.blockchain.GetHeaderByNumber(uint64(number)), nil
}
Expand Down Expand Up @@ -105,6 +107,8 @@ func (b *EthAPIBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumbe
// Otherwise resolve and return the block
if number == rpc.LatestBlockNumber {
return b.eth.blockchain.CurrentBlock(), nil
} else if number == rpc.FinalizedBlockNumber {
return b.eth.blockchain.CurrentFinalizedBlock(), nil
}
return b.eth.blockchain.GetBlockByNumber(uint64(number)), nil
}
Expand Down
4 changes: 3 additions & 1 deletion eth/catalyst/api.go
Expand Up @@ -89,10 +89,12 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(heads beacon.ForkchoiceStateV1, pay
}
// If the finalized block is set, check if it is in our blockchain
if heads.FinalizedBlockHash != (common.Hash{}) {
if block := api.eth.BlockChain().GetBlockByHash(heads.FinalizedBlockHash); block == nil {
block := api.eth.BlockChain().GetBlockByHash(heads.FinalizedBlockHash)
if block == nil {
// TODO (MariusVanDerWijden) trigger sync
return beacon.SYNCING, nil
}
api.eth.BlockChain().SetFinalized(block)
}
// SetHead
if err := api.setHead(heads.HeadBlockHash); err != nil {
Expand Down
5 changes: 4 additions & 1 deletion eth/catalyst/api_test.go
Expand Up @@ -462,7 +462,10 @@ func TestFullAPI(t *testing.T) {
t.Fatalf("Failed to insert block: %v", err)
}
if ethservice.BlockChain().CurrentBlock().NumberU64() != payload.Number {
t.Fatalf("Chain head should be updated")
t.Fatal("Chain head should be updated")
}
if ethservice.BlockChain().CurrentFinalizedBlock().NumberU64() != payload.Number-1 {
t.Fatal("Finalized block should be updated")
}
parent = ethservice.BlockChain().CurrentBlock()
}
Expand Down
16 changes: 13 additions & 3 deletions rpc/types.go
Expand Up @@ -60,9 +60,10 @@ type jsonWriter interface {
type BlockNumber int64

const (
PendingBlockNumber = BlockNumber(-2)
LatestBlockNumber = BlockNumber(-1)
EarliestBlockNumber = BlockNumber(0)
FinalizedBlockNumber = BlockNumber(-3)
PendingBlockNumber = BlockNumber(-2)
LatestBlockNumber = BlockNumber(-1)
EarliestBlockNumber = BlockNumber(0)
)

// UnmarshalJSON parses the given JSON fragment into a BlockNumber. It supports:
Expand All @@ -87,6 +88,9 @@ func (bn *BlockNumber) UnmarshalJSON(data []byte) error {
case "pending":
*bn = PendingBlockNumber
return nil
case "finalized":
*bn = FinalizedBlockNumber
return nil
}

blckNum, err := hexutil.DecodeUint64(input)
Expand All @@ -111,6 +115,8 @@ func (bn BlockNumber) MarshalText() ([]byte, error) {
return []byte("latest"), nil
case PendingBlockNumber:
return []byte("pending"), nil
case FinalizedBlockNumber:
return []byte("finalized"), nil
default:
return hexutil.Uint64(bn).MarshalText()
}
Expand Down Expand Up @@ -157,6 +163,10 @@ func (bnh *BlockNumberOrHash) UnmarshalJSON(data []byte) error {
bn := PendingBlockNumber
bnh.BlockNumber = &bn
return nil
case "finalized":
bn := FinalizedBlockNumber
bnh.BlockNumber = &bn
return nil
default:
if len(input) == 66 {
hash := common.Hash{}
Expand Down

0 comments on commit c2da34c

Please sign in to comment.