@@ -27,6 +27,7 @@ import (
2727 "github.com/hyperledger-firefly/common/pkg/fftypes"
2828 "github.com/hyperledger-firefly/common/pkg/i18n"
2929 "github.com/hyperledger-firefly/common/pkg/log"
30+ "github.com/hyperledger-firefly/common/pkg/metric"
3031 "github.com/hyperledger-firefly/common/pkg/retry"
3132 "github.com/hyperledger-firefly/common/pkg/wsclient"
3233 "github.com/hyperledger-firefly/evmconnect/internal/msgs"
@@ -89,6 +90,7 @@ type BlockListener interface {
8990 WaitClosed ()
9091 GetBackend () rpcbackend.RPC
9192 UTSetBackend (rpcbackend.RPC )
93+ InitMetrics (ctx context.Context , registry metric.MetricsRegistry ) error
9294}
9395
9496func toMinimalBlockInfoList (blocks []* ethrpc.BlockInfoJSONRPC ) []* ethrpc.MinimalBlockInfo {
@@ -142,6 +144,10 @@ type blockListener struct {
142144
143145 // headBlockNumber mode: last head value sent on the block listener channel (only written from listenLoop)
144146 currentChainHead uint64
147+
148+ // metrics are optional - only emitted once InitMetrics has been called
149+ metricsLock sync.RWMutex
150+ metrics metric.MetricsManager
145151}
146152
147153func NewBlockListener (ctx context.Context , retry * retry.Retry , conf * BlockListenerConfig , httpConf * ffresty.Config , wsConf * wsclient.WSConfig ) (bl BlockListener , err error ) {
@@ -295,26 +301,28 @@ func (bl *blockListener) establishBlockHeightWithRetry() error {
295301 }
296302
297303 // Now get the block height
298- var hexBlockHeight ethtypes.HexInteger
299- rpcErr := bl .backend .CallRPC (bl .ctx , & hexBlockHeight , "eth_blockNumber" )
300- if rpcErr != nil {
301- log .L (bl .ctx ).Warnf ("Block height could not be obtained: %s" , rpcErr .Message )
302- return true , rpcErr .Error ()
304+ head , err := bl .queryBlockHeightFromRPC ()
305+ if err != nil {
306+ log .L (bl .ctx ).Warnf ("Block height could not be obtained: %s" , err )
307+ return true , err
303308 }
304309
305- bl .setHighestBlock (hexBlockHeight . BigInt (). Uint64 () )
310+ bl .setHighestBlock (head )
306311 return false , nil
307312 })
308313}
309314
310- // refreshHighestBlockFromRPC updates highestBlock from eth_blockNumber. Caller must not hold canonicalChainLock.
311- func (bl * blockListener ) refreshHighestBlockFromRPC () (uint64 , error ) {
315+ // queryBlockHeightFromRPC queries eth_blockNumber and returns the result, without updating any listener
316+ // state. Caller must not hold canonicalChainLock. The height the node reports is recorded on the target block height gauge.
317+ func (bl * blockListener ) queryBlockHeightFromRPC () (uint64 , error ) {
312318 var hexBlockHeight ethtypes.HexInteger
313319 rpcErr := bl .backend .CallRPC (bl .ctx , & hexBlockHeight , "eth_blockNumber" )
314320 if rpcErr != nil {
321+ bl .incPollFailureMetric ("eth_blockNumber" )
315322 return 0 , rpcErr .Error ()
316323 }
317324 head := hexBlockHeight .BigInt ().Uint64 ()
325+ bl .setBlockHeightMetric (metricTargetBlockHeight , head )
318326 return head , nil
319327}
320328
@@ -367,10 +375,17 @@ func (bl *blockListener) listenLoop() {
367375 }
368376 }
369377
378+ // In full chain tracking mode, the loop below never queries the height the node reports, so we refresh
379+ // it here for the target metric. Done ahead of the filter calls.
380+ if bl .ChainTrackingMode != ffcapi .ChainTrackingModeLight {
381+ bl .refreshTargetBlockHeightMetric ()
382+ }
383+
370384 if filter == "" {
371385 err := bl .backend .CallRPC (bl .ctx , & filter , "eth_newBlockFilter" )
372386 if err != nil {
373387 log .L (bl .ctx ).Errorf ("Failed to establish new block filter: %s" , err .Message )
388+ bl .incPollFailureMetric ("eth_newBlockFilter" )
374389 failCount ++
375390 continue
376391 }
@@ -393,24 +408,28 @@ func (bl *blockListener) listenLoop() {
393408 gapPotential = true
394409 }
395410 log .L (bl .ctx ).Errorf ("Failed to query block filter changes: %s" , rpcErr .Message )
411+ bl .incPollFailureMetric ("eth_getFilterChanges" )
396412 failCount ++
397413 continue
398414 }
399415 log .L (bl .ctx ).Debugf ("Block filter received new block hashes: %+v" , blockHashes )
400416 }
401417
402418 if bl .ChainTrackingMode == ffcapi .ChainTrackingModeLight {
403- head , err := bl .refreshHighestBlockFromRPC ()
419+ head , err := bl .queryBlockHeightFromRPC ()
404420 if err != nil {
405421 log .L (bl .ctx ).Errorf ("Failed to refresh chain head: %s" , err )
406422 failCount ++
407423 continue
408424 }
425+ // In light mode there is no canonical chain being built, so the head we dispatch to
426+ // consumers is what we report as the canonical height
409427 if head == bl .currentChainHead {
410428 failCount = 0
411429 continue
412430 }
413431 bl .currentChainHead = head
432+ bl .setBlockHeightMetric (metricCanonicalBlockHeight , bl .currentChainHead )
414433 update := & ffcapi.BlockHashEvent {GapPotential : false , Created : fftypes .Now (), HeadBlockNumber : bl .currentChainHead }
415434 bl .consumerMux .Lock ()
416435 consumers := make ([]* BlockUpdateConsumer , 0 , len (bl .consumers ))
@@ -778,6 +797,7 @@ func (bl *blockListener) GetHeadBlockNumber(_ context.Context) uint64 {
778797}
779798
780799func (bl * blockListener ) setHighestBlock (block uint64 ) {
800+ defer bl .setBlockHeightMetric (metricCanonicalBlockHeight , block )
781801 bl .canonicalChainLock .Lock ()
782802 defer bl .canonicalChainLock .Unlock ()
783803 bl .highestBlock = block
@@ -793,6 +813,9 @@ func (bl *blockListener) checkAndSetHighestBlock(bi *ethrpc.BlockInfoJSONRPC) {
793813 bl .highestBlock = block
794814 bl .highestBlockSet = true
795815 bl .headBlockInfo = bi
816+ // The gauge is bound to the same variable GetHighestBlock reports to event streams, so it is the
817+ // head we are actually tracking rather than a separate sample of it.
818+ bl .setBlockHeightMetric (metricCanonicalBlockHeight , block )
796819 } else if block == bl .highestBlock {
797820 // Height already known from eth_blockNumber. Store the first full block at that height.
798821 bl .headBlockInfo = bi
0 commit comments