diff --git a/.hack/devnet/run.sh b/.hack/devnet/run.sh index fe76aae6..174faf7a 100755 --- a/.hack/devnet/run.sh +++ b/.hack/devnet/run.sh @@ -42,19 +42,26 @@ fi ## Generate Dora config ENCLAVE_UUID=$(kurtosis enclave inspect "$ENCLAVE_NAME" --full-uuids | grep 'UUID:' | awk '{print $2}') -# Local kurtosis buildoor container, when present, overrides the network-derived URL. -BUILDOOR_CONTAINER=$(docker ps -aq -f "label=kurtosis_enclave_uuid=$ENCLAVE_UUID" \ +# Local kurtosis buildoor containers, when present, override the network-derived URL. +# Matches both the legacy shared "buildoor" service and per-participant instances +# named "buildoor---" (ethereum-package buildoor_params.instances). +BUILDOOR_CONTAINERS=$(docker ps -aq -f "label=kurtosis_enclave_uuid=$ENCLAVE_UUID" \ -f "label=com.kurtosistech.app-id=kurtosis" \ - -f "label=com.kurtosistech.id=buildoor" | head -1) -if [ -n "$BUILDOOR_CONTAINER" ]; then - BUILDOOR_PORT=$(docker inspect --format='{{ (index (index .NetworkSettings.Ports "8080/tcp") 0).HostPort }}' "$BUILDOOR_CONTAINER" 2>/dev/null) - # kurtosis service name (e.g. "buildoor") so dora shows it instead of the forwarded IP:port - BUILDOOR_NAME=$(docker inspect --format='{{ index .Config.Labels "kurtosis_service_name" }}' "$BUILDOOR_CONTAINER" 2>/dev/null) + -f "name=buildoor") +BUILDOOR_URLS="" +for container in $BUILDOOR_CONTAINERS; do + BUILDOOR_PORT=$(docker inspect --format='{{ (index (index .NetworkSettings.Ports "8080/tcp") 0).HostPort }}' "$container" 2>/dev/null) + if [ -z "$BUILDOOR_PORT" ]; then + continue + fi + # kurtosis service name (e.g. "buildoor-lighthouse-geth-1") so dora shows it instead of the forwarded IP:port + BUILDOOR_NAME=$(docker inspect --format='{{ index .Config.Labels "com.kurtosistech.id" }}' "$container" 2>/dev/null) if [ -z "$BUILDOOR_NAME" ]; then BUILDOOR_NAME="buildoor"; fi - if [ -n "$BUILDOOR_PORT" ]; then - BUILDOOR_CONFIG="buildoorUrls: + BUILDOOR_URLS="${BUILDOOR_URLS} - \"${BUILDOOR_NAME}|http://127.0.0.1:${BUILDOOR_PORT}\"" - fi +done +if [ -n "$BUILDOOR_URLS" ]; then + BUILDOOR_CONFIG="buildoorUrls:${BUILDOOR_URLS}" fi BEACON_NODES=$(docker ps -aq -f "label=kurtosis_enclave_uuid=$ENCLAVE_UUID" \ diff --git a/handlers/index.go b/handlers/index.go index 885260b0..ce97e468 100644 --- a/handlers/index.go +++ b/handlers/index.go @@ -399,15 +399,17 @@ func buildIndexPageRecentEpochsData(ctx context.Context, pageData *models.IndexP // 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) { +func resolveBuildSource(dbBuilderIndex int64) (hasBuilder bool, builderIndex uint64, builderName string, builderURL string) { if dbBuilderIndex == -1 { - return true, math.MaxUint64, "" + return true, math.MaxUint64, "", "" } if dbBuilderIndex < 0 { - return false, 0, "" + return false, 0, "", "" } builderIndex = uint64(dbBuilderIndex) - return true, builderIndex, services.GlobalBeaconService.GetBuilderURL(builderIndex) + builderName = services.GlobalBeaconService.GetValidatorName(builderIndex | services.BuilderIndexFlag) + builderURL = services.GlobalBeaconService.GetBuilderURL(builderIndex) + return true, builderIndex, builderName, builderURL } func buildIndexPageRecentBlocksData(ctx context.Context, pageData *models.IndexPageData, recentBlockCount int) { @@ -447,7 +449,7 @@ func buildIndexPageRecentBlocksData(ctx context.Context, pageData *models.IndexP PayloadStatus: uint8(payloadStatus), BlockRoot: blockData.Root, } - blockModel.HasBuilder, blockModel.BuilderIndex, blockModel.BuilderURL = resolveBuildSource(blockData.BuilderIndex) + blockModel.HasBuilder, blockModel.BuilderIndex, blockModel.BuilderName, blockModel.BuilderURL = resolveBuildSource(blockData.BuilderIndex) if blockData.EthBlockNumber != nil { blockModel.WithEthBlock = true blockModel.EthBlock = *blockData.EthBlockNumber @@ -507,7 +509,7 @@ func buildIndexPageRecentSlotsData(ctx context.Context, pageData *models.IndexPa ForkGraph: make([]*models.IndexPageDataForkGraph, 0), } if dbSlot.Status > 0 { - slotData.HasBuilder, slotData.BuilderIndex, slotData.BuilderURL = resolveBuildSource(dbSlot.BuilderIndex) + slotData.HasBuilder, slotData.BuilderIndex, slotData.BuilderName, slotData.BuilderURL = resolveBuildSource(dbSlot.BuilderIndex) } pageData.RecentSlots = append(pageData.RecentSlots, slotData) blockCount++ diff --git a/static/js/page-index.js b/static/js/page-index.js index 31ed78da..70dc5f06 100644 --- a/static/js/page-index.js +++ b/static/js/page-index.js @@ -225,7 +225,7 @@ // mirrors utils.FormatProposerWithBuildSource: house = self-built payload, // hard-hat (linking to the builder) = builder-built payload - function formatProposerWithBuildSource(status, idx, name, hasBuilder, builderIdx, builderUrl) { + function formatProposerWithBuildSource(status, idx, name, hasBuilder, builderIdx, builderName) { if(status == 0 || idx >= 9223372036854775807n) { if(idx >= 9223372036854775807n) { return `unknown`; @@ -246,13 +246,9 @@ if(builderIdx >= 18446744073709551615n) { iconHtml = ``; } else { - var builderLink = "/builder/" + builderIdx; - var external = ""; - if(builderUrl) { - builderLink = escapeHtml(builderUrl); - external = ` target="_blank" rel="noopener noreferrer"`; - } - iconHtml = ``; + // the icon links to the builder details page; the tooltip names the builder when known + var builderLabel = builderName ? escapeHtml(builderName) + " (" + builderIdx + ")" : "builder " + builderIdx; + iconHtml = ``; } if(name != "") { diff --git a/templates/blocks/blocks.html b/templates/blocks/blocks.html index 54e31944..b5314f70 100644 --- a/templates/blocks/blocks.html +++ b/templates/blocks/blocks.html @@ -156,7 +156,7 @@

Blocks

{{ end }} {{ if $g.DisplayTime }}{{ formatRecentTimeShort $slot.Ts }}{{ end }} {{ if $slot.Synchronized }} - {{ if $g.DisplayProposer }}{{ if gt $slot.Slot 0 }}{{ formatProposerWithBuildSource $slot.Status $slot.Proposer $slot.ProposerName $slot.HasBuilder $slot.BuilderIndex $slot.BuilderURL }}{{ end }}{{ end }} + {{ if $g.DisplayProposer }}{{ if gt $slot.Slot 0 }}{{ formatProposerWithBuildSource $slot.Status $slot.Proposer $slot.ProposerName $slot.HasBuilder $slot.BuilderIndex $slot.BuilderName }}{{ end }}{{ end }} {{ if $g.DisplayAttestations }}{{ if not (eq $slot.Status 0) }}{{ $slot.AttestationCount }}{{ end }}{{ end }} {{ if $g.DisplayDeposits }}{{ if not (eq $slot.Status 0) }}{{ $slot.DepositCount }} / {{ $slot.ExitCount }}{{ end }}{{ end }} {{ if $g.DisplaySlashings }}{{ if not (eq $slot.Status 0) }}{{ $slot.ProposerSlashingCount }} / {{ $slot.AttesterSlashingCount }}{{ end }}{{ end }} diff --git a/templates/index/recentBlocks.html b/templates/index/recentBlocks.html index 54ee688d..3a6c1b49 100644 --- a/templates/index/recentBlocks.html +++ b/templates/index/recentBlocks.html @@ -48,7 +48,7 @@
- + {{ html "" }} {{ html "" }} @@ -82,7 +82,7 @@
{{ formatRecentTimeShort $block.Ts }} - {{ formatProposerWithBuildSource $block.Status $block.Proposer $block.ProposerName $block.HasBuilder $block.BuilderIndex $block.BuilderURL }} + {{ formatProposerWithBuildSource $block.Status $block.Proposer $block.ProposerName $block.HasBuilder $block.BuilderIndex $block.BuilderName }} {{ end }} {{ else }} diff --git a/templates/index/recentSlots.html b/templates/index/recentSlots.html index 70b9edf8..a0c379e4 100644 --- a/templates/index/recentSlots.html +++ b/templates/index/recentSlots.html @@ -52,7 +52,7 @@
0"> - + @@ -109,7 +109,7 @@
{{ formatRecentTimeShort $slot.Ts }} - {{ if gt $slot.Slot 0 }}{{ formatProposerWithBuildSource $slot.Status $slot.Proposer $slot.ProposerName $slot.HasBuilder $slot.BuilderIndex $slot.BuilderURL }}{{ end }} + {{ if gt $slot.Slot 0 }}{{ formatProposerWithBuildSource $slot.Status $slot.Proposer $slot.ProposerName $slot.HasBuilder $slot.BuilderIndex $slot.BuilderName }}{{ end }} {{ end }} {{ else }} diff --git a/templates/slots/slots.html b/templates/slots/slots.html index c842db3b..c5c658c4 100644 --- a/templates/slots/slots.html +++ b/templates/slots/slots.html @@ -155,7 +155,7 @@

Slots

{{ end }} {{ if $g.DisplayTime }}{{ formatRecentTimeShort $slot.Ts }}{{ end }} {{ if $slot.Synchronized }} - {{ if $g.DisplayProposer }}{{ if gt $slot.Slot 0 }}{{ formatProposerWithBuildSource $slot.Status $slot.Proposer $slot.ProposerName $slot.HasBuilder $slot.BuilderIndex $slot.BuilderURL }}{{ end }}{{ end }} + {{ if $g.DisplayProposer }}{{ if gt $slot.Slot 0 }}{{ formatProposerWithBuildSource $slot.Status $slot.Proposer $slot.ProposerName $slot.HasBuilder $slot.BuilderIndex $slot.BuilderName }}{{ end }}{{ end }} {{ if $g.DisplayAttestations }}{{ if not (eq $slot.Status 0) }}{{ $slot.AttestationCount }}{{ end }}{{ end }} {{ if $g.DisplayDeposits }}{{ if not (eq $slot.Status 0) }}{{ $slot.DepositCount }} / {{ $slot.ExitCount }}{{ end }}{{ end }} {{ if $g.DisplaySlashings }}{{ if not (eq $slot.Status 0) }}{{ $slot.ProposerSlashingCount }} / {{ $slot.AttesterSlashingCount }}{{ end }}{{ end }} diff --git a/templates/slots_filtered/slots_filtered.html b/templates/slots_filtered/slots_filtered.html index 15384968..85e6587b 100644 --- a/templates/slots_filtered/slots_filtered.html +++ b/templates/slots_filtered/slots_filtered.html @@ -358,7 +358,7 @@

Filtered Slots

{{ formatRecentTimeShort $slot.Ts }} {{- end }} {{- if $g.DisplayProposer }} - {{ if gt $slot.Slot 0 }}{{ formatProposerWithBuildSource $slot.Status $slot.Proposer $slot.ProposerName $slot.HasBuilder $slot.BuilderIndex $slot.BuilderURL }}{{ end }} + {{ if gt $slot.Slot 0 }}{{ formatProposerWithBuildSource $slot.Status $slot.Proposer $slot.ProposerName $slot.HasBuilder $slot.BuilderIndex $slot.BuilderName }}{{ end }} {{- end }} {{- if $g.DisplayAttestations }} {{ if not (eq $slot.Status 0) }}{{ $slot.AttestationCount }}{{ end }} diff --git a/types/models/indexPage.go b/types/models/indexPage.go index aa715d85..43a156a9 100644 --- a/types/models/indexPage.go +++ b/types/models/indexPage.go @@ -85,6 +85,7 @@ type IndexPageDataBlocks struct { PayloadStatus uint8 `json:"payload_status"` HasBuilder bool `json:"has_builder"` BuilderIndex uint64 `json:"builder_index"` + BuilderName string `json:"builder_name"` BuilderURL string `json:"builder_url"` BlockRoot []byte `json:"block_root" ssz-size:"32"` } @@ -100,6 +101,7 @@ type IndexPageDataSlots struct { PayloadStatus uint8 `json:"payload_status"` HasBuilder bool `json:"has_builder"` BuilderIndex uint64 `json:"builder_index"` + BuilderName string `json:"builder_name"` BuilderURL string `json:"builder_url"` Safe bool `json:"safe"` BlockRoot []byte `json:"block_root" ssz-size:"32"` diff --git a/utils/format.go b/utils/format.go index 88a11018..2ca66d10 100644 --- a/utils/format.go +++ b/utils/format.go @@ -894,7 +894,7 @@ func formatValidator(index uint64, name string, icon string, withIndex bool) tem // // Scheduled/missing slots (status == 0) and unknown proposers have no // determinable build source and are rendered without any leading icon. -func FormatProposerWithBuildSource(status uint8, index uint64, name string, hasBuilder bool, builderIndex uint64, builderURL string) template.HTML { +func FormatProposerWithBuildSource(status uint8, index uint64, name string, hasBuilder bool, builderIndex uint64, builderName string) template.HTML { if status == 0 || index == math.MaxInt64 { if index == math.MaxInt64 { return template.HTML(`unknown`) @@ -914,15 +914,9 @@ func FormatProposerWithBuildSource(status uint8, index uint64, name string, hasB // self-built payload iconHTML = `` } else { - // builder-built payload - link the icon to the builder URL when known, - // otherwise to the internal builder page - builderLink := fmt.Sprintf("/builder/%v", builderIndex) - external := "" - if builderURL != "" { - builderLink = html.EscapeString(builderURL) - external = ` target="_blank" rel="noopener noreferrer"` - } - iconHTML = fmt.Sprintf(``, builderLink, external, builderIndex) + // builder-built payload - the icon links to the builder details page and + // names the builder in the tooltip when the buildoor inventory knows it + iconHTML = fmt.Sprintf(``, builderIndex, html.EscapeString(builderTooltipLabel(builderIndex, builderName))) } nameLabel := fmt.Sprintf("%v", index) @@ -934,6 +928,15 @@ func FormatProposerWithBuildSource(status uint8, index uint64, name string, hasB return template.HTML(fmt.Sprintf(`%v %v`, labelClass, iconHTML, index, nameLabel)) } +// builderTooltipLabel renders "name (index)" when the builder has a known name and +// "builder " otherwise. +func builderTooltipLabel(index uint64, name string) string { + if name != "" { + return fmt.Sprintf("%v (%v)", name, index) + } + return fmt.Sprintf("builder %v", index) +} + func FormatValidatorNameWithIndex(index uint64, name string) template.HTML { if name != "" { return template.HTML(fmt.Sprintf("%v (%v)", html.EscapeString(name), index)) @@ -970,11 +973,11 @@ func formatBuilder(index uint64, name string, externalURL string, icon string, w return template.HTML(" Self-built") } - // When the builder exposes an external URL, its "name" is often the full API URL, which is - // far too long for the table columns. Collapse it to the hyperlinked builder index and show - // the full API URL on hover instead of printing it inline. + // The label always links to the builder details page. When the buildoor inventory knows the + // instance's API URL it is shown on hover only, never printed inline or linked to. + tooltip := "" if externalURL != "" { - return template.HTML(fmt.Sprintf(" %v", icon, index, html.EscapeString(externalURL), index)) + tooltip = fmt.Sprintf(" data-bs-toggle=\"tooltip\" data-bs-placement=\"top\" data-bs-title=\"%v\"", html.EscapeString(externalURL)) } if name != "" { @@ -984,9 +987,9 @@ func formatBuilder(index uint64, name string, externalURL string, icon string, w } else { nameLabel = html.EscapeString(name) } - return template.HTML(fmt.Sprintf(" %v", icon, index, nameLabel)) + return template.HTML(fmt.Sprintf(" %v", icon, index, tooltip, nameLabel)) } - return template.HTML(fmt.Sprintf(" %v", icon, index, index)) + return template.HTML(fmt.Sprintf(" %v", icon, index, tooltip, index)) } func FormatRecentTimeShort(ts time.Time) template.HTML {