diff --git a/handlers/index.go b/handlers/index.go index 2bc171c9..10e3ec6f 100644 --- a/handlers/index.go +++ b/handlers/index.go @@ -445,6 +445,19 @@ func buildIndexPageRecentEpochsData(ctx context.Context, pageData *models.IndexP pageData.RecentEpochCount = uint64(len(pageData.RecentEpochs)) } +// resolveBuildSource maps a db builder index (-1 = self-built) to the model fields +// driving the proposer build-source icon (house / hard-hat), matching the slots page. +func resolveBuildSource(dbBuilderIndex int64) (hasBuilder bool, builderIndex uint64, builderURL string) { + if dbBuilderIndex == -1 { + return true, math.MaxUint64, "" + } + if dbBuilderIndex < 0 { + return false, 0, "" + } + builderIndex = uint64(dbBuilderIndex) + return true, builderIndex, services.GlobalBeaconService.GetBuilderURL(builderIndex) +} + func buildIndexPageRecentBlocksData(ctx context.Context, pageData *models.IndexPageData, recentBlockCount int) { pageData.RecentBlocks = make([]*models.IndexPageDataBlocks, 0) @@ -478,10 +491,11 @@ func buildIndexPageRecentBlocksData(ctx context.Context, pageData *models.IndexP Ts: chainState.SlotToTime(phase0.Slot(blockData.Slot)), Proposer: blockData.Proposer, ProposerName: services.GlobalBeaconService.GetValidatorNameAt(blockData.Proposer, phase0.Slot(blockData.Slot)), - Status: uint64(blockData.Status), + Status: uint8(blockData.Status), PayloadStatus: uint8(payloadStatus), BlockRoot: blockData.Root, } + blockModel.HasBuilder, blockModel.BuilderIndex, blockModel.BuilderURL = resolveBuildSource(blockData.BuilderIndex) if blockData.EthBlockNumber != nil { blockModel.WithEthBlock = true blockModel.EthBlock = *blockData.EthBlockNumber @@ -531,7 +545,7 @@ func buildIndexPageRecentSlotsData(ctx context.Context, pageData *models.IndexPa Slot: slot, Epoch: uint64(epoch), Ts: chainState.SlotToTime(phase0.Slot(slot)), - Status: uint64(dbSlot.Status), + Status: uint8(dbSlot.Status), PayloadStatus: uint8(payloadStatus), Safe: fcrEnabled && dbSlot.Status == dbtypes.Canonical && slot <= uint64(safeSlot), Proposer: dbSlot.Proposer, @@ -540,6 +554,9 @@ func buildIndexPageRecentSlotsData(ctx context.Context, pageData *models.IndexPa ParentRoot: dbSlot.ParentRoot, ForkGraph: make([]*models.IndexPageDataForkGraph, 0), } + if dbSlot.Status > 0 { + slotData.HasBuilder, slotData.BuilderIndex, slotData.BuilderURL = resolveBuildSource(dbSlot.BuilderIndex) + } pageData.RecentSlots = append(pageData.RecentSlots, slotData) blockCount++ buildIndexPageSlotGraph(slotData, &maxOpenFork, openForks) diff --git a/static/js/page-index.js b/static/js/page-index.js index ce67e0a5..6142b4cf 100644 --- a/static/js/page-index.js +++ b/static/js/page-index.js @@ -88,6 +88,7 @@ formatEth: function(x) { return formatFloat(x / 1000000000, 4); }, formatFloat: function(x) { return formatFloat(x, 2); }, formatValidator: function(idx, name) { return formatValidator(idx, name); }, + formatProposerWithBuildSource: function(status, idx, name, hasBuilder, builderIdx, builderUrl) { return formatProposerWithBuildSource(status, idx, name, hasBuilder, builderIdx, builderUrl); }, hexstr: function(x) { return "0x" + base64ToHex(x); }, slotStatusTooltip: function(status, payloadStatus) { var bs = ["Missed", "Canonical", "Orphaned"][status] || "Unknown"; @@ -210,6 +211,8 @@ function formatValidator(idx, name) { var icon = "fa-male mr-2"; + // MaxInt64 = "unknown proposer" sentinel. It exceeds 2^53, so JSON.parse rounds it; + // >= against the exact BigInt matches the rounded value (== would never match). if(idx >= 9223372036854775807n) { return ` unknown`; } @@ -219,6 +222,44 @@ return ` ` + idx + `` } + // mirrors utils.FormatProposerWithBuildSource: house = self-built payload, + // hard-hat (linking to the builder) = builder-built payload + function formatProposerWithBuildSource(status, idx, name, hasBuilder, builderIdx, builderUrl) { + if(status == 0 || idx >= 9223372036854775807n) { + if(idx >= 9223372036854775807n) { + return `unknown`; + } + if(name != "") { + return `` + escapeHtml(name) + ``; + } + return `` + idx + ``; + } + + if(!hasBuilder) { + return formatValidator(idx, name); + } + + var iconHtml; + // MaxUint64 = "self-built payload" sentinel (db builder_index -1); rounded by JSON.parse + // above 2^53 like the proposer sentinel, hence >= against the exact BigInt. + if(builderIdx >= 18446744073709551615n) { + iconHtml = ``; + } else { + var builderLink = "/builder/" + builderIdx; + var external = ""; + if(builderUrl) { + builderLink = escapeHtml(builderUrl); + external = ` target="_blank" rel="noopener noreferrer"`; + } + iconHtml = ``; + } + + if(name != "") { + return `` + iconHtml + ` ` + escapeHtml(name) + ``; + } + return `` + iconHtml + ` ` + idx + ``; + } + function base64ToHex(str) { const raw = atob(str); let result = ''; diff --git a/templates/index/recentBlocks.html b/templates/index/recentBlocks.html index 64115430..54ee688d 100644 --- a/templates/index/recentBlocks.html +++ b/templates/index/recentBlocks.html @@ -48,7 +48,7 @@