core: eth: rpc: implement safe rpc block (#25165)

* core: eth: rpc: implement safe rpc block

* core: fix setHead, panics
This commit is contained in:
Marius van der Wijden 2022-07-25 17:42:05 +02:00 committed by GitHub
parent f543e6b065
commit c6dcd018d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 73 additions and 5 deletions

@ -51,6 +51,7 @@ var (
headHeaderGauge = metrics.NewRegisteredGauge("chain/head/header", nil)
headFastBlockGauge = metrics.NewRegisteredGauge("chain/head/receipt", nil)
headFinalizedBlockGauge = metrics.NewRegisteredGauge("chain/head/finalized", nil)
headSafeBlockGauge = metrics.NewRegisteredGauge("chain/head/safe", nil)
accountReadTimer = metrics.NewRegisteredTimer("chain/account/reads", nil)
accountHashTimer = metrics.NewRegisteredTimer("chain/account/hashes", nil)
@ -191,6 +192,7 @@ type BlockChain struct {
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
currentSafeBlock atomic.Value // Current safe head
stateCache state.Database // State database to reuse between imports (contains state cache)
bodyCache *lru.Cache // Cache for the most recent block bodies
@ -267,6 +269,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
bc.currentBlock.Store(nilBlock)
bc.currentFastBlock.Store(nilBlock)
bc.currentFinalizedBlock.Store(nilBlock)
bc.currentSafeBlock.Store(nilBlock)
// Initialize the chain with ancient data if it isn't empty.
var txIndexBlock uint64
@ -464,11 +467,15 @@ func (bc *BlockChain) loadLastState() error {
}
}
// Restore the last known finalized block
// Restore the last known finalized block and safe block
// Note: the safe block is not stored on disk and it is set to the last
// known finalized block on startup
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()))
bc.currentSafeBlock.Store(block)
headSafeBlockGauge.Update(int64(block.NumberU64()))
}
}
// Issue a status log for the user
@ -504,8 +511,23 @@ func (bc *BlockChain) SetHead(head uint64) error {
// SetFinalized sets the finalized block.
func (bc *BlockChain) SetFinalized(block *types.Block) {
bc.currentFinalizedBlock.Store(block)
rawdb.WriteFinalizedBlockHash(bc.db, block.Hash())
headFinalizedBlockGauge.Update(int64(block.NumberU64()))
if block != nil {
rawdb.WriteFinalizedBlockHash(bc.db, block.Hash())
headFinalizedBlockGauge.Update(int64(block.NumberU64()))
} else {
rawdb.WriteFinalizedBlockHash(bc.db, common.Hash{})
headFinalizedBlockGauge.Update(0)
}
}
// SetSafe sets the safe block.
func (bc *BlockChain) SetSafe(block *types.Block) {
bc.currentSafeBlock.Store(block)
if block != nil {
headSafeBlockGauge.Update(int64(block.NumberU64()))
} else {
headSafeBlockGauge.Update(0)
}
}
// setHeadBeyondRoot rewinds the local chain to a new head with the extra condition
@ -663,6 +685,16 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, root common.Hash, repair bo
bc.txLookupCache.Purge()
bc.futureBlocks.Purge()
// Clear safe block, finalized block if needed
if safe := bc.CurrentSafeBlock(); safe != nil && head < safe.NumberU64() {
log.Warn("SetHead invalidated safe block")
bc.SetSafe(nil)
}
if finalized := bc.CurrentFinalizedBlock(); finalized != nil && head < finalized.NumberU64() {
log.Error("SetHead invalidated finalized block")
bc.SetFinalized(nil)
}
return rootNumber, bc.loadLastState()
}

@ -55,6 +55,12 @@ func (bc *BlockChain) CurrentFinalizedBlock() *types.Block {
return bc.currentFinalizedBlock.Load().(*types.Block)
}
// CurrentSafeBlock retrieves the current safe block of the canonical
// chain. The block is retrieved from the blockchain's internal cache.
func (bc *BlockChain) CurrentSafeBlock() *types.Block {
return bc.currentSafeBlock.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 {

@ -272,6 +272,8 @@ func (api *DebugAPI) DumpBlock(blockNr rpc.BlockNumber) (state.Dump, error) {
block = api.eth.blockchain.CurrentBlock()
} else if blockNr == rpc.FinalizedBlockNumber {
block = api.eth.blockchain.CurrentFinalizedBlock()
} else if blockNr == rpc.SafeBlockNumber {
block = api.eth.blockchain.CurrentSafeBlock()
} else {
block = api.eth.blockchain.GetBlockByNumber(uint64(blockNr))
}
@ -350,6 +352,8 @@ func (api *DebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, start hex
block = api.eth.blockchain.CurrentBlock()
} else if number == rpc.FinalizedBlockNumber {
block = api.eth.blockchain.CurrentFinalizedBlock()
} else if number == rpc.SafeBlockNumber {
block = api.eth.blockchain.CurrentSafeBlock()
} else {
block = api.eth.blockchain.GetBlockByNumber(uint64(number))
}

@ -74,7 +74,18 @@ func (b *EthAPIBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumb
return b.eth.blockchain.CurrentBlock().Header(), nil
}
if number == rpc.FinalizedBlockNumber {
return b.eth.blockchain.CurrentFinalizedBlock().Header(), nil
block := b.eth.blockchain.CurrentFinalizedBlock()
if block != nil {
return block.Header(), nil
}
return nil, errors.New("finalized block not found")
}
if number == rpc.SafeBlockNumber {
block := b.eth.blockchain.CurrentSafeBlock()
if block != nil {
return block.Header(), nil
}
return nil, errors.New("safe block not found")
}
return b.eth.blockchain.GetHeaderByNumber(uint64(number)), nil
}
@ -113,6 +124,9 @@ func (b *EthAPIBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumbe
if number == rpc.FinalizedBlockNumber {
return b.eth.blockchain.CurrentFinalizedBlock(), nil
}
if number == rpc.SafeBlockNumber {
return b.eth.blockchain.CurrentSafeBlock(), nil
}
return b.eth.blockchain.GetBlockByNumber(uint64(number)), nil
}

@ -235,6 +235,8 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(update beacon.ForkchoiceStateV1, pa
log.Warn("Safe block not in canonical chain")
return beacon.STATUS_INVALID, beacon.InvalidForkChoiceState.With(errors.New("safe block not in canonical chain"))
}
// Set the safe block
api.eth.BlockChain().SetSafe(safeBlock)
}
// If payload generation was requested, create a new block to be potentially
// sealed by the beacon client. The payload will be requested later, and we

@ -3696,7 +3696,7 @@ var outputBigNumberFormatter = function (number) {
};
var isPredefinedBlockNumber = function (blockNumber) {
return blockNumber === 'latest' || blockNumber === 'pending' || blockNumber === 'earliest' || blockNumber === 'finalized';
return blockNumber === 'latest' || blockNumber === 'pending' || blockNumber === 'earliest' || blockNumber === 'finalized' || blockNumber === 'safe';
};
var inputDefaultBlockNumberFormatter = function (blockNumber) {

@ -61,6 +61,7 @@ type jsonWriter interface {
type BlockNumber int64
const (
SafeBlockNumber = BlockNumber(-4)
FinalizedBlockNumber = BlockNumber(-3)
PendingBlockNumber = BlockNumber(-2)
LatestBlockNumber = BlockNumber(-1)
@ -92,6 +93,9 @@ func (bn *BlockNumber) UnmarshalJSON(data []byte) error {
case "finalized":
*bn = FinalizedBlockNumber
return nil
case "safe":
*bn = SafeBlockNumber
return nil
}
blckNum, err := hexutil.DecodeUint64(input)
@ -118,6 +122,8 @@ func (bn BlockNumber) MarshalText() ([]byte, error) {
return []byte("pending"), nil
case FinalizedBlockNumber:
return []byte("finalized"), nil
case SafeBlockNumber:
return []byte("safe"), nil
default:
return hexutil.Uint64(bn).MarshalText()
}
@ -168,6 +174,10 @@ func (bnh *BlockNumberOrHash) UnmarshalJSON(data []byte) error {
bn := FinalizedBlockNumber
bnh.BlockNumber = &bn
return nil
case "safe":
bn := SafeBlockNumber
bnh.BlockNumber = &bn
return nil
default:
if len(input) == 66 {
hash := common.Hash{}