Skip to content

Commit

Permalink
track full finalization checkpoints (finalized & justified)
Browse files Browse the repository at this point in the history
  • Loading branch information
pk910 committed Sep 10, 2023
1 parent 88027d9 commit c82028a
Show file tree
Hide file tree
Showing 14 changed files with 90 additions and 55 deletions.
3 changes: 2 additions & 1 deletion handlers/epochs.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ func buildEpochsPageData(firstEpoch uint64, pageSize uint64) (*models.EpochsPage
}
pageData.LastPageEpoch = pageSize - 1

finalizedEpoch, _ := services.GlobalBeaconService.GetFinalizedEpoch()
finalizedEpoch, _, justifiedEpoch, _ := services.GlobalBeaconService.GetIndexer().GetFinalizationCheckpoints()
epochLimit := pageSize

// load epochs
Expand All @@ -111,6 +111,7 @@ func buildEpochsPageData(firstEpoch uint64, pageSize uint64) (*models.EpochsPage
Epoch: epoch,
Ts: utils.EpochToTime(epoch),
Finalized: finalized,
Justified: justifiedEpoch >= epochIdx,
}
if dbIdx < dbCnt && dbEpochs[dbIdx] != nil && dbEpochs[dbIdx].Epoch == epoch {
dbEpoch := dbEpochs[dbIdx]
Expand Down
8 changes: 5 additions & 3 deletions handlers/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func buildIndexPageData() (*models.IndexPageData, time.Duration) {
currentSlot := utils.TimeToSlot(uint64(now.Unix()))
currentSlotIndex := (currentSlot % utils.Config.Chain.Config.SlotsPerEpoch) + 1

finalizedEpoch, _ := services.GlobalBeaconService.GetFinalizedEpoch()
finalizedEpoch, _, justifiedEpoch, _ := services.GlobalBeaconService.GetIndexer().GetFinalizationCheckpoints()

syncState := dbtypes.IndexerSyncState{}
db.GetExplorerState("indexer.syncstate", &syncState)
Expand All @@ -97,6 +97,7 @@ func buildIndexPageData() (*models.IndexPageData, time.Duration) {
SlotsPerEpoch: utils.Config.Chain.Config.SlotsPerEpoch,
CurrentEpoch: uint64(currentEpoch),
CurrentFinalizedEpoch: finalizedEpoch,
CurrentJustifiedEpoch: justifiedEpoch,
CurrentSlot: currentSlot,
CurrentScheduledCount: utils.Config.Chain.Config.SlotsPerEpoch - currentSlotIndex,
CurrentEpochProgress: float64(100) * float64(currentSlotIndex) / float64(utils.Config.Chain.Config.SlotsPerEpoch),
Expand Down Expand Up @@ -175,7 +176,7 @@ func buildIndexPageData() (*models.IndexPageData, time.Duration) {
}

// load recent epochs
buildIndexPageRecentEpochsData(pageData, uint64(currentEpoch), finalizedEpoch, recentEpochCount)
buildIndexPageRecentEpochsData(pageData, uint64(currentEpoch), finalizedEpoch, justifiedEpoch, recentEpochCount)

// load recent blocks
buildIndexPageRecentBlocksData(pageData, currentSlot, recentBlockCount)
Expand All @@ -186,7 +187,7 @@ func buildIndexPageData() (*models.IndexPageData, time.Duration) {
return pageData, 12 * time.Second
}

func buildIndexPageRecentEpochsData(pageData *models.IndexPageData, currentEpoch uint64, finalizedEpoch int64, recentEpochCount int) {
func buildIndexPageRecentEpochsData(pageData *models.IndexPageData, currentEpoch uint64, finalizedEpoch int64, justifiedEpoch int64, recentEpochCount int) {
pageData.RecentEpochs = make([]*models.IndexPageDataEpochs, 0)
epochsData := services.GlobalBeaconService.GetDbEpochs(currentEpoch, uint32(recentEpochCount))
for i := 0; i < len(epochsData); i++ {
Expand All @@ -202,6 +203,7 @@ func buildIndexPageRecentEpochsData(pageData *models.IndexPageData, currentEpoch
Epoch: epochData.Epoch,
Ts: utils.EpochToTime(epochData.Epoch),
Finalized: finalizedEpoch >= int64(epochData.Epoch),
Justified: justifiedEpoch >= int64(epochData.Epoch),
EligibleEther: epochData.Eligible,
TargetVoted: epochData.VotedTarget,
VoteParticipation: voteParticipation,
Expand Down
23 changes: 15 additions & 8 deletions indexer/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ type indexerCache struct {
lowestSlot int64
finalizedEpoch int64
finalizedRoot []byte
justifiedEpoch int64
justifiedRoot []byte
processedEpoch int64
persistEpoch int64
cleanupEpoch int64
Expand Down Expand Up @@ -68,12 +70,17 @@ func (cache *indexerCache) startSynchronizer(startEpoch uint64) {
}
}

func (cache *indexerCache) setFinalizedHead(epoch int64, root []byte) {
func (cache *indexerCache) setFinalizedHead(finalizedEpoch int64, finalizedRoot []byte, justifiedEpoch int64, justifiedRoot []byte) {
cache.cacheMutex.Lock()
defer cache.cacheMutex.Unlock()
if epoch > cache.finalizedEpoch {
cache.finalizedEpoch = epoch
cache.finalizedRoot = root

if justifiedEpoch > cache.justifiedEpoch {
cache.justifiedEpoch = justifiedEpoch
cache.justifiedRoot = justifiedRoot
}
if finalizedEpoch > cache.finalizedEpoch {
cache.finalizedEpoch = finalizedEpoch
cache.finalizedRoot = finalizedRoot

// trigger processing
cache.triggerChan <- true
Expand All @@ -88,10 +95,10 @@ func (cache *indexerCache) setGenesis(genesis *rpctypes.StandardV1GenesisRespons
}
}

func (cache *indexerCache) getFinalizedHead() (int64, []byte) {
func (cache *indexerCache) getFinalizationCheckpoints() (int64, []byte, int64, []byte) {
cache.cacheMutex.RLock()
defer cache.cacheMutex.RUnlock()
return cache.finalizedEpoch, cache.finalizedRoot
return cache.finalizedEpoch, cache.finalizedRoot, cache.justifiedEpoch, cache.justifiedRoot
}

func (cache *indexerCache) setLastValidators(epoch uint64, validators *rpctypes.StandardV1StateValidatorsResponse) {
Expand Down Expand Up @@ -158,7 +165,7 @@ func (cache *indexerCache) isCanonicalBlock(blockRoot []byte, head []byte) bool

func (cache *indexerCache) getCanonicalDistance(blockRoot []byte, head []byte) (bool, uint64) {
if head == nil {
head = cache.finalizedRoot
head = cache.justifiedRoot
}
block := cache.getCachedBlock(blockRoot)
var blockSlot uint64
Expand Down Expand Up @@ -197,7 +204,7 @@ func (cache *indexerCache) getCanonicalDistance(blockRoot []byte, head []byte) (

func (cache *indexerCache) getLastCanonicalBlock(epoch uint64, head []byte) *CacheBlock {
if head == nil {
head = cache.finalizedRoot
head = cache.justifiedRoot
}
canonicalBlock := cache.getCachedBlock(head)
for canonicalBlock != nil && utils.EpochOfSlot(canonicalBlock.Slot) > epoch {
Expand Down
38 changes: 24 additions & 14 deletions indexer/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ type IndexerClient struct {
lastEpochStats int64
lastFinalizedEpoch int64
lastFinalizedRoot []byte
lastJustifiedEpoch int64
lastJustifiedRoot []byte
}

func newIndexerClient(clientIdx uint8, clientName string, rpcClient *rpc.BeaconClient, indexerCache *indexerCache, archive bool, priority int, skipValidators bool) *IndexerClient {
Expand All @@ -45,6 +47,7 @@ func newIndexerClient(clientIdx uint8, clientName string, rpcClient *rpc.BeaconC
lastHeadSlot: -1,
lastEpochStats: -1,
lastFinalizedEpoch: -1,
lastJustifiedEpoch: -1,
}
go client.runIndexerClientLoop()
return &client
Expand Down Expand Up @@ -189,18 +192,10 @@ func (client *IndexerClient) runIndexerClient() error {
}

// get finalized header
finalizedHeader, err := client.rpcClient.GetFinalizedBlockHead()
finalizedSlot, err := client.refreshFinalityCheckpoints()
if err != nil {
logger.WithField("client", client.clientName).Warnf("could not get finalized header: %v", err)
}
var finalizedSlot uint64
if finalizedHeader != nil {
client.cacheMutex.Lock()
finalizedSlot = uint64(finalizedHeader.Data.Header.Message.Slot)
client.lastFinalizedEpoch = int64(utils.EpochOfSlot(uint64(finalizedHeader.Data.Header.Message.Slot)) - 1)
client.lastFinalizedRoot = finalizedHeader.Data.Root
client.cacheMutex.Unlock()
}

logger.WithField("client", client.clientName).Debugf("endpoint %v ready: %v ", client.clientName, client.versionStr)
client.retryCounter = 0
Expand All @@ -216,9 +211,7 @@ func (client *IndexerClient) runIndexerClient() error {
}

// set finalized head and trigger epoch processing / synchronization
if finalizedHeader != nil {
client.indexerCache.setFinalizedHead(client.lastFinalizedEpoch, client.lastFinalizedRoot)
}
client.indexerCache.setFinalizedHead(client.lastFinalizedEpoch, client.lastFinalizedRoot, client.lastJustifiedEpoch, client.lastJustifiedRoot)

// process events
client.lastStreamEvent = time.Now()
Expand Down Expand Up @@ -267,6 +260,22 @@ func (client *IndexerClient) runIndexerClient() error {
}
}

func (client *IndexerClient) refreshFinalityCheckpoints() (uint64, error) {
finalizedCheckpoints, err := client.rpcClient.GetFinalityCheckpoints()
if err != nil {
return 0, err
}
var finalizedSlot uint64
client.cacheMutex.Lock()
finalizedSlot = uint64(finalizedCheckpoints.Data.Finalized.Epoch) * utils.Config.Chain.Config.SlotsPerEpoch
client.lastFinalizedEpoch = int64(finalizedCheckpoints.Data.Finalized.Epoch) - 1
client.lastFinalizedRoot = finalizedCheckpoints.Data.Finalized.Root
client.lastJustifiedEpoch = int64(finalizedCheckpoints.Data.CurrentJustified.Epoch) - 1
client.lastJustifiedRoot = finalizedCheckpoints.Data.CurrentJustified.Root
client.cacheMutex.Unlock()
return finalizedSlot, nil
}

func (client *IndexerClient) prefillCache(finalizedSlot uint64, latestHeader *rpctypes.StandardV1BeaconHeaderResponse) error {
currentBlock, isNewBlock := client.indexerCache.createOrGetCachedBlock(latestHeader.Data.Root, uint64(latestHeader.Data.Header.Message.Slot))
if isNewBlock {
Expand Down Expand Up @@ -490,7 +499,8 @@ func (client *IndexerClient) processBlockEvent(evt *rpctypes.StandardV1StreamedB
}

func (client *IndexerClient) processFinalizedEvent(evt *rpctypes.StandardV1StreamedFinalizedCheckpointEvent) error {
logger.WithField("client", client.clientName).Debugf("received finalization_checkpoint event: epoch %v [%s]", evt.Epoch, evt.Block.String())
client.indexerCache.setFinalizedHead(int64(evt.Epoch)-1, evt.Block)
client.refreshFinalityCheckpoints()
logger.WithField("client", client.clientName).Debugf("received finalization_checkpoint event: finalized %v [0x%x], justified %v [0x%x]", client.lastFinalizedEpoch, client.lastFinalizedRoot, client.lastJustifiedEpoch, client.lastJustifiedRoot)
client.indexerCache.setFinalizedHead(client.lastFinalizedEpoch, client.lastFinalizedRoot, client.lastJustifiedEpoch, client.lastJustifiedRoot)
return nil
}
16 changes: 9 additions & 7 deletions indexer/epoch_stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,14 +314,16 @@ func (epochStats *EpochStats) ensureEpochStatsLazy(client *IndexerClient, propos
if err != nil {
logger.WithField("client", client.clientName).Warnf("error retrieving sync_committees for epoch %v (state: %v): %v", epochStats.Epoch, syncCommitteeState, err)
}
epochStats.syncAssignments = make([]uint64, len(parsedSyncCommittees.Data.Validators))
for i, valIndexStr := range parsedSyncCommittees.Data.Validators {
valIndexU64, err := strconv.ParseUint(valIndexStr, 10, 64)
if err != nil {
logger.WithField("client", client.clientName).Warnf("in sync_committee for epoch %d validator %d has bad validator index: %q", epochStats.Epoch, i, valIndexStr)
continue
if parsedSyncCommittees != nil {
epochStats.syncAssignments = make([]uint64, len(parsedSyncCommittees.Data.Validators))
for i, valIndexStr := range parsedSyncCommittees.Data.Validators {
valIndexU64, err := strconv.ParseUint(valIndexStr, 10, 64)
if err != nil {
logger.WithField("client", client.clientName).Warnf("in sync_committee for epoch %d validator %d has bad validator index: %q", epochStats.Epoch, i, valIndexStr)
continue
}
epochStats.syncAssignments[i] = valIndexU64
}
epochStats.syncAssignments[i] = valIndexU64
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions indexer/indexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,8 @@ func (indexer *Indexer) GetCachedGenesis() *rpctypes.StandardV1GenesisResponse {
return indexer.indexerCache.genesisResp
}

func (indexer *Indexer) GetFinalizedEpoch() (int64, []byte) {
return indexer.indexerCache.getFinalizedHead()
func (indexer *Indexer) GetFinalizationCheckpoints() (int64, []byte, int64, []byte) {
return indexer.indexerCache.getFinalizationCheckpoints()
}

func (indexer *Indexer) GetHighestSlot() uint64 {
Expand Down
2 changes: 1 addition & 1 deletion indexer/synchronizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func (sync *synchronizerState) runSync() {
}
retryCount = 0
skipClients = nil
finalizedEpoch, _ := sync.indexer.indexerCache.getFinalizedHead()
finalizedEpoch, _, _, _ := sync.indexer.indexerCache.getFinalizationCheckpoints()
sync.stateMutex.Lock()
syncEpoch++
sync.currentEpoch = syncEpoch
Expand Down
15 changes: 5 additions & 10 deletions rpc/beaconapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,22 +226,17 @@ func (bc *BeaconClient) GetLatestBlockHead() (*rpctypes.StandardV1BeaconHeaderRe
return &parsedHeaders, nil
}

func (bc *BeaconClient) GetFinalizedBlockHead() (*rpctypes.StandardV1BeaconHeaderResponse, error) {
resHeaders, err := bc.get(fmt.Sprintf("%s/eth/v1/beacon/headers/finalized", bc.endpoint))
func (bc *BeaconClient) GetFinalityCheckpoints() (*rpctypes.StandardV1BeaconStateFinalityCheckpointsResponse, error) {
var parsedCheckpoints rpctypes.StandardV1BeaconStateFinalityCheckpointsResponse
err := bc.getJson(fmt.Sprintf("%s/eth/v1/beacon/states/head/finality_checkpoints", bc.endpoint), &parsedCheckpoints)
if err != nil {
if err == errNotFound {
// no block found
return nil, nil
}
return nil, fmt.Errorf("error retrieving finalized block header: %v", err)
return nil, fmt.Errorf("error retrieving finality checkpoints: %v", err)
}

var parsedHeaders rpctypes.StandardV1BeaconHeaderResponse
err = json.Unmarshal(resHeaders, &parsedHeaders)
if err != nil {
return nil, fmt.Errorf("error parsing header-response for finalized block: %v", err)
}
return &parsedHeaders, nil
return &parsedCheckpoints, nil
}

func (bc *BeaconClient) GetBlockHeaderByBlockroot(blockroot []byte) (*rpctypes.StandardV1BeaconHeaderResponse, error) {
Expand Down
9 changes: 9 additions & 0 deletions rpctypes/beaconapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,12 @@ type StandardV1NodeVersionResponse struct {
Version string `json:"version"`
} `json:"data"`
}

type StandardV1BeaconStateFinalityCheckpointsResponse struct {
Finalized bool `json:"finalized"`
Data struct {
PreviousJustified Checkpoint `json:"previous_justified"`
CurrentJustified Checkpoint `json:"current_justified"`
Finalized Checkpoint `json:"finalized"`
} `json:"data"`
}
17 changes: 9 additions & 8 deletions services/beaconservice.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ func (bs *BeaconService) GetCachedValidatorSet() *rpctypes.StandardV1StateValida
}

func (bs *BeaconService) GetFinalizedEpoch() (int64, []byte) {
return bs.indexer.GetFinalizedEpoch()
finalizedEpoch, finalizedRoot, _, _ := bs.indexer.GetFinalizationCheckpoints()
return finalizedEpoch, finalizedRoot
}

func (bs *BeaconService) GetCachedEpochStats(epoch uint64) *indexer.EpochStats {
Expand Down Expand Up @@ -253,7 +254,7 @@ func (bs *BeaconService) GetOrphanedBlock(blockroot []byte) *rpctypes.CombinedBl
}

func (bs *BeaconService) GetEpochAssignments(epoch uint64) (*rpctypes.EpochAssignments, error) {
finalizedEpoch, _ := bs.indexer.GetFinalizedEpoch()
finalizedEpoch, _ := bs.GetFinalizedEpoch()

if int64(epoch) > finalizedEpoch {
epochStats := bs.indexer.GetCachedEpochStats(epoch)
Expand Down Expand Up @@ -301,7 +302,7 @@ func (bs *BeaconService) GetProposerAssignments(firstEpoch uint64, lastEpoch uin
proposerAssignments = make(map[uint64]uint64)
synchronizedEpochs = make(map[uint64]bool)

finalizedEpoch, _ := bs.indexer.GetFinalizedEpoch()
finalizedEpoch, _ := bs.GetFinalizedEpoch()
idxMinEpoch := finalizedEpoch + 1
idxHeadEpoch := utils.EpochOfSlot(bs.indexer.GetHighestSlot())
if firstEpoch > idxHeadEpoch {
Expand Down Expand Up @@ -349,7 +350,7 @@ func (bs *BeaconService) GetDbEpochs(firstEpoch uint64, limit uint32) []*dbtypes
dbIdx := 0
dbCnt := len(dbEpochs)

finalizedEpoch, _ := bs.indexer.GetFinalizedEpoch()
finalizedEpoch, _ := bs.GetFinalizedEpoch()
var idxMinEpoch, idxHeadEpoch uint64
idxMinEpoch = uint64(finalizedEpoch + 1)
idxHeadEpoch = utils.EpochOfSlot(bs.indexer.GetHighestSlot())
Expand Down Expand Up @@ -381,7 +382,7 @@ func (bs *BeaconService) GetDbBlocks(firstSlot uint64, limit int32, withOrphaned
resBlocks := make([]*dbtypes.Block, limit)
resIdx := 0

finalizedEpoch, _ := bs.indexer.GetFinalizedEpoch()
finalizedEpoch, _ := bs.GetFinalizedEpoch()
idxMinSlot := (finalizedEpoch + 1) * int64(utils.Config.Chain.Config.SlotsPerEpoch)
idxHeadSlot := bs.indexer.GetHighestSlot()
if firstSlot > idxHeadSlot {
Expand Down Expand Up @@ -429,7 +430,7 @@ func (bs *BeaconService) GetDbBlocks(firstSlot uint64, limit int32, withOrphaned
func (bs *BeaconService) GetDbBlocksForSlots(firstSlot uint64, slotLimit uint32, withOrphaned bool) []*dbtypes.Block {
resBlocks := make([]*dbtypes.Block, 0)

finalizedEpoch, _ := bs.indexer.GetFinalizedEpoch()
finalizedEpoch, _ := bs.GetFinalizedEpoch()
idxMinSlot := (finalizedEpoch + 1) * int64(utils.Config.Chain.Config.SlotsPerEpoch)
idxHeadSlot := bs.indexer.GetHighestSlot()
if firstSlot > idxHeadSlot {
Expand Down Expand Up @@ -485,7 +486,7 @@ type cachedDbBlock struct {

func (bs *BeaconService) GetDbBlocksByFilter(filter *dbtypes.BlockFilter, pageIdx uint64, pageSize uint32) []*dbtypes.AssignedBlock {
cachedMatches := make([]cachedDbBlock, 0)
finalizedEpoch, _ := bs.indexer.GetFinalizedEpoch()
finalizedEpoch, _ := bs.GetFinalizedEpoch()
idxMinSlot := (finalizedEpoch + 1) * int64(utils.Config.Chain.Config.SlotsPerEpoch)
idxHeadSlot := bs.indexer.GetHighestSlot()
proposedMap := map[uint64]bool{}
Expand Down Expand Up @@ -673,7 +674,7 @@ func (bs *BeaconService) GetValidatorActivity() (map[uint64]uint8, uint64) {
return activityMap, 0
}
idxHeadEpoch--
finalizedEpoch, _ := bs.indexer.GetFinalizedEpoch()
finalizedEpoch, _ := bs.GetFinalizedEpoch()
var idxMinEpoch uint64
if finalizedEpoch < 0 {
idxMinEpoch = 0
Expand Down
2 changes: 2 additions & 0 deletions templates/epochs/epochs.html
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ <h1 class="h4 mb-1 mb-md-0">
<td>
{{ if $epoch.Finalized }}
<span class="badge badge-pill bg-success text-white" style="font-size: 12px; font-weight: 500;">Yes</span>
{{ else if $epoch.Justified }}
<span class="badge badge-pill bg-warning text-white" style="font-size: 12px; font-weight: 500;" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-title="Epoch is justified and will be finalized soon">Just</span>
{{ else }}
<span class="badge badge-pill bg-warning text-white" style="font-size: 12px; font-weight: 500;">No</span>
{{ end }}
Expand Down
5 changes: 4 additions & 1 deletion templates/index/recentEpochs.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ <h5 class="card-title d-flex justify-content-between align-items-center" style="
</td>
<td>
<span data-bind="if: finalized" class="badge badge-pill bg-success text-white" style="font-size: 12px; font-weight: 500;">Yes</span>
<span data-bind="ifnot: finalized" class="badge badge-pill bg-warning text-white" style="font-size: 12px; font-weight: 500;">No</span>
<span data-bind="if: !finalized && justified" class="badge badge-pill bg-warning text-white" style="font-size: 12px; font-weight: 500;" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-title="Epoch is justified and will be finalized soon">Just.</span>
<span data-bind="ifnot: justified" class="badge badge-pill bg-warning text-white" style="font-size: 12px; font-weight: 500;">No</span>
</td>
<td data-bind="text: $root.formatEth(eligible)"></td>
<td>
Expand Down Expand Up @@ -55,6 +56,8 @@ <h5 class="card-title d-flex justify-content-between align-items-center" style="
<td>
{{ if $epoch.Finalized }}
<span class="badge badge-pill bg-success text-white" style="font-size: 12px; font-weight: 500;">Yes</span>
{{ else if $epoch.Justified }}
<span class="badge badge-pill bg-warning text-white" style="font-size: 12px; font-weight: 500;" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-title="Epoch is justified and will be finalized soon">Just</span>
{{ else }}
<span class="badge badge-pill bg-warning text-white" style="font-size: 12px; font-weight: 500;">No</span>
{{ end }}
Expand Down
1 change: 1 addition & 0 deletions types/models/epochs.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type EpochsPageDataEpoch struct {
Epoch uint64 `json:"epoch"`
Ts time.Time `json:"ts"`
Finalized bool `json:"finalized"`
Justified bool `json:"justified"`
Synchronized bool `json:"synchronized"`
CanonicalBlockCount uint64 `json:"canonical_block_count"`
OrphanedBlockCount uint64 `json:"orphaned_block_count"`
Expand Down
Loading

0 comments on commit c82028a

Please sign in to comment.