Skip to content

Commit 0048a05

Browse files
authored
Merge pull request #747 from ethpandaops/bbusa/slots-proposer-build-source
feat(slots): show payload build source icon in proposer column
2 parents eb72c7d + 84d88ed commit 0048a05

8 files changed

Lines changed: 146 additions & 95 deletions

File tree

handlers/blocks.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -300,8 +300,9 @@ func buildBlocksPageData(ctx context.Context, firstSlot uint64, pageSize uint64,
300300
}
301301
}
302302

303-
// Add builder info
304-
if pageData.DisplayBuilder {
303+
// Add builder info (needed for the Builder column and the proposer build-source icon).
304+
// Only blocks that actually exist (proposed or orphaned) carry a build source.
305+
if (pageData.DisplayBuilder || pageData.DisplayProposer) && dbSlot.Status > 0 {
305306
if dbSlot.BuilderIndex == -1 {
306307
slotData.HasBuilder = true
307308
slotData.BuilderIndex = math.MaxUint64

handlers/slots.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -309,8 +309,10 @@ func buildSlotsPageData(ctx context.Context, firstSlot uint64, pageSize uint64,
309309
}
310310
}
311311

312-
// Add builder info
313-
if pageData.DisplayBuilder {
312+
// Add builder info (needed for the Builder column and the proposer build-source icon).
313+
// Only blocks that actually exist (proposed or orphaned) carry a build source;
314+
// scheduled/missing slots have no payload yet.
315+
if (pageData.DisplayBuilder || pageData.DisplayProposer) && dbSlot.Status > 0 {
314316
if dbSlot.BuilderIndex == -1 {
315317
slotData.HasBuilder = true
316318
slotData.BuilderIndex = math.MaxUint64

templates/blocks/blocks.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ <h1 class="h4 mb-1 mb-md-0"><i class="fas fa-cube mx-2"></i>Blocks</h1>
150150
{{ end }}
151151
{{ if $g.DisplayTime }}<td data-timer="{{ $slot.Ts.Unix }}"><span data-bs-toggle="tooltip" data-bs-placement="top" data-bs-title="{{ $slot.Ts }}">{{ formatRecentTimeShort $slot.Ts }}</span></td>{{ end }}
152152
{{ if $slot.Synchronized }}
153-
{{ if $g.DisplayProposer }}<td>{{ if gt $slot.Slot 0 }}{{ formatValidator $slot.Proposer $slot.ProposerName }}{{ end }}</td>{{ end }}
153+
{{ if $g.DisplayProposer }}<td>{{ if gt $slot.Slot 0 }}{{ formatProposerWithBuildSource $slot.Status $slot.Proposer $slot.ProposerName $slot.HasBuilder $slot.BuilderIndex $slot.BuilderURL }}{{ end }}</td>{{ end }}
154154
{{ if $g.DisplayAttestations }}<td class="d-none d-md-table-cell">{{ if not (eq $slot.Status 0) }}{{ $slot.AttestationCount }}{{ end }}</td>{{ end }}
155155
{{ if $g.DisplayDeposits }}<td>{{ if not (eq $slot.Status 0) }}{{ $slot.DepositCount }} / {{ $slot.ExitCount }}{{ end }}</td>{{ end }}
156156
{{ if $g.DisplaySlashings }}<td>{{ if not (eq $slot.Status 0) }}{{ $slot.ProposerSlashingCount }} / {{ $slot.AttesterSlashingCount }}{{ end }}</td>{{ end }}

templates/slots/slots.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ <h1 class="h4 mb-1 mb-md-0"><i class="fas fa-table-list mx-2"></i>Slots</h1>
150150
{{ end }}
151151
{{ if $g.DisplayTime }}<td data-timer="{{ $slot.Ts.Unix }}"><span data-bs-toggle="tooltip" data-bs-placement="top" data-bs-title="{{ $slot.Ts }}">{{ formatRecentTimeShort $slot.Ts }}</span></td>{{ end }}
152152
{{ if $slot.Synchronized }}
153-
{{ if $g.DisplayProposer }}<td>{{ if gt $slot.Slot 0 }}{{ formatValidator $slot.Proposer $slot.ProposerName }}{{ end }}</td>{{ end }}
153+
{{ if $g.DisplayProposer }}<td>{{ if gt $slot.Slot 0 }}{{ formatProposerWithBuildSource $slot.Status $slot.Proposer $slot.ProposerName $slot.HasBuilder $slot.BuilderIndex $slot.BuilderURL }}{{ end }}</td>{{ end }}
154154
{{ if $g.DisplayAttestations }}<td class="d-none d-md-table-cell">{{ if not (eq $slot.Status 0) }}{{ $slot.AttestationCount }}{{ end }}</td>{{ end }}
155155
{{ if $g.DisplayDeposits }}<td>{{ if not (eq $slot.Status 0) }}{{ $slot.DepositCount }} / {{ $slot.ExitCount }}{{ end }}</td>{{ end }}
156156
{{ if $g.DisplaySlashings }}<td>{{ if not (eq $slot.Status 0) }}{{ $slot.ProposerSlashingCount }} / {{ $slot.AttesterSlashingCount }}{{ end }}</td>{{ end }}

ui-package/package-lock.json

Lines changed: 24 additions & 24 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ui-package/package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"css-loader": "^7.1.4",
1919
"mini-css-extract-plugin": "^2.10.2",
2020
"postcss-loader": "^8.2.1",
21-
"sass": "^1.100.0",
21+
"sass": "^1.101.0",
2222
"sass-loader": "^17.0.0",
2323
"style-loader": "^4.0.0",
2424
"ts-loader": "^9.6.0",
@@ -32,14 +32,14 @@
3232
"@chainsafe/bls": "^8.2.0",
3333
"@chainsafe/ssz": "^1.6.0",
3434
"@rainbow-me/rainbowkit": "^2.2.11",
35-
"@tanstack/react-query": "^5.100.14",
36-
"@types/react": "^19.2.16",
35+
"@tanstack/react-query": "^5.101.0",
36+
"@types/react": "^19.2.17",
3737
"@types/react-dom": "^19.2.3",
3838
"react": "^19.2.7",
3939
"react-bootstrap": "^2.10.10",
4040
"react-dom": "^19.2.7",
4141
"react-select": "^5.10.2",
42-
"viem": "^2.52.0",
42+
"viem": "^2.52.2",
4343
"wagmi": "^2.19.5"
4444
},
4545
"scripts": {

utils/format.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -832,6 +832,53 @@ func formatValidator(index uint64, name string, icon string, withIndex bool) tem
832832
return template.HTML(fmt.Sprintf("<span class=\"validator-label validator-index\"><i class=\"fas %v\"></i> <a href=\"/validator/%v\">%v</a></span>", icon, index, index))
833833
}
834834

835+
// FormatProposerWithBuildSource renders a proposer label whose leading icon
836+
// reflects the payload build source on Gloas+ blocks: a house for self-built
837+
// payloads and a hard-hat (linking to the builder) for builder-built payloads.
838+
// Pre-Gloas blocks (hasBuilder == false) fall back to the default validator icon.
839+
//
840+
// Scheduled/missing slots (status == 0) and unknown proposers have no
841+
// determinable build source and are rendered without any leading icon.
842+
func FormatProposerWithBuildSource(status uint8, index uint64, name string, hasBuilder bool, builderIndex uint64, builderURL string) template.HTML {
843+
if status == 0 || index == math.MaxInt64 {
844+
if index == math.MaxInt64 {
845+
return template.HTML(`<span class="validator-label validator-index">unknown</span>`)
846+
}
847+
if name != "" {
848+
return template.HTML(fmt.Sprintf(`<span class="validator-label validator-name"><a href="/validator/%v">%v</a></span>`, index, html.EscapeString(name)))
849+
}
850+
return template.HTML(fmt.Sprintf(`<span class="validator-label validator-index"><a href="/validator/%v">%v</a></span>`, index, index))
851+
}
852+
853+
if !hasBuilder {
854+
return FormatValidator(index, name)
855+
}
856+
857+
var iconHTML string
858+
if builderIndex == math.MaxUint64 {
859+
// self-built payload
860+
iconHTML = `<i class="fas fa-house mr-2" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-title="Self-built payload"></i>`
861+
} else {
862+
// builder-built payload - link the icon to the builder URL when known,
863+
// otherwise to the internal builder page
864+
builderLink := fmt.Sprintf("/builder/%v", builderIndex)
865+
external := ""
866+
if builderURL != "" {
867+
builderLink = html.EscapeString(builderURL)
868+
external = ` target="_blank" rel="noopener noreferrer"`
869+
}
870+
iconHTML = fmt.Sprintf(`<a href="%v"%v class="builder-source-link" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-title="Builder-built payload (builder %v)"><i class="fas fa-hard-hat mr-2"></i></a>`, builderLink, external, builderIndex)
871+
}
872+
873+
nameLabel := fmt.Sprintf("%v", index)
874+
labelClass := "validator-index"
875+
if name != "" {
876+
nameLabel = html.EscapeString(name)
877+
labelClass = "validator-name"
878+
}
879+
return template.HTML(fmt.Sprintf(`<span class="validator-label %v">%v <a href="/validator/%v">%v</a></span>`, labelClass, iconHTML, index, nameLabel))
880+
}
881+
835882
func FormatValidatorNameWithIndex(index uint64, name string) template.HTML {
836883
if name != "" {
837884
return template.HTML(fmt.Sprintf("<span class=\"validator-label validator-name\">%v (%v)</span>", html.EscapeString(name), index))

utils/templateFucs.go

Lines changed: 62 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -109,67 +109,68 @@ func GetTemplateFuncs() template.FuncMap {
109109
"round": func(i float64, n int) float64 {
110110
return math.Round(i*math.Pow10(n)) / math.Pow10(n)
111111
},
112-
"uint64ToTime": func(i uint64) time.Time { return time.Unix(int64(i), 0).UTC() },
113-
"percent": func(i float64) float64 { return i * 100 },
114-
"contains": strings.Contains,
115-
"formatAddCommas": FormatAddCommas,
116-
"formatFloat": FormatFloat,
117-
"formatTokenAmount": FormatTokenAmount,
118-
"formatBaseFee": FormatBaseFee,
119-
"formatBlobFeeDifference": FormatBlobFeeDifference,
120-
"formatTransactionValue": FormatTransactionValue,
121-
"formatTransactionFee": FormatTransactionFee,
122-
"formatBitlist": FormatBitlist,
123-
"formatBitvectorValidators": formatBitvectorValidators,
124-
"formatParticipation": FormatParticipation,
125-
"formatEthFromGwei": FormatETHFromGwei,
126-
"formatEthFromGweiP": FormatETHFromGweiP,
127-
"formatEthFromGweiShort": FormatETHFromGweiShort,
128-
"formatFullEthFromGwei": FormatFullEthFromGwei,
129-
"formatEthAddCommasFromGwei": FormatETHAddCommasFromGwei,
130-
"formatBytesAmount": FormatBytesAmount,
131-
"formatAmount": FormatAmount,
132-
"formatBigAmount": FormatBigAmount,
133-
"formatAmountFormatted": FormatAmountFormatted,
134-
"formatGwei": FormatGweiValue,
135-
"formatByteAmount": FormatByteAmount,
136-
"percentage": CalculatePercentage,
137-
"ethBlockLink": FormatEthBlockLink,
138-
"ethBlockHashLink": FormatEthBlockHashLink,
139-
"ethAddressLink": FormatEthAddressLink,
140-
"ethTransactionLink": FormatEthTransactionLink,
141-
"formatEthAddress": FormatEthAddress,
142-
"formatEthAddressShort": FormatEthAddressShort,
143-
"formatEthAddressShortLink": FormatEthAddressShortLink,
144-
"formatEthAddressFull": FormatEthAddressFull,
145-
"formatEthAddressFullLink": FormatEthAddressFullLink,
146-
"formatHexBytes": FormatHexBytes,
147-
"formatHexBytesShort": FormatHexBytesShort,
148-
"formatWeiAmount": FormatWeiAmount,
149-
"formatWeiDeltaAmount": FormatWeiDeltaAmount,
150-
"formatNFTTokenID": FormatNFTTokenID,
151-
"formatEthHashShort": FormatEthHashShort,
152-
"formatContractCreationLink": FormatContractCreationLink,
153-
"formatValidator": FormatValidator,
154-
"formatValidatorWithIndex": FormatValidatorWithIndex,
155-
"formatValidatorNameWithIndex": FormatValidatorNameWithIndex,
156-
"formatSlashedValidator": FormatSlashedValidator,
157-
"formatBuilder": FormatBuilder,
158-
"formatBuilderWithIndex": FormatBuilderWithIndex,
159-
"formatInactiveBuilder": FormatInactiveBuilder,
160-
"formatBuilderWithURL": FormatBuilderWithURL,
161-
"formatBuilderWithIndexAndURL": FormatBuilderWithIndexAndURL,
162-
"formatWithdawalCredentials": FormatWithdawalCredentials,
163-
"formatRecentTimeShort": FormatRecentTimeShort,
164-
"formatGraffiti": FormatGraffiti,
165-
"formatSlotStatusTooltip": FormatSlotStatusTooltip,
166-
"formatRecvDelay": FormatRecvDelay,
167-
"formatPercentageAlert": formatPercentageAlert,
168-
"formatAlertNumber": formatAlertNumber,
169-
"isSystemContract": IsSystemContract,
170-
"getSystemContractName": GetSystemContractName,
171-
"calculateBalanceDiff": CalculateBalanceDiff,
172-
"bitwiseAnd": func(a, b interface{}) int64 { return toInt64(a) & toInt64(b) },
112+
"uint64ToTime": func(i uint64) time.Time { return time.Unix(int64(i), 0).UTC() },
113+
"percent": func(i float64) float64 { return i * 100 },
114+
"contains": strings.Contains,
115+
"formatAddCommas": FormatAddCommas,
116+
"formatFloat": FormatFloat,
117+
"formatTokenAmount": FormatTokenAmount,
118+
"formatBaseFee": FormatBaseFee,
119+
"formatBlobFeeDifference": FormatBlobFeeDifference,
120+
"formatTransactionValue": FormatTransactionValue,
121+
"formatTransactionFee": FormatTransactionFee,
122+
"formatBitlist": FormatBitlist,
123+
"formatBitvectorValidators": formatBitvectorValidators,
124+
"formatParticipation": FormatParticipation,
125+
"formatEthFromGwei": FormatETHFromGwei,
126+
"formatEthFromGweiP": FormatETHFromGweiP,
127+
"formatEthFromGweiShort": FormatETHFromGweiShort,
128+
"formatFullEthFromGwei": FormatFullEthFromGwei,
129+
"formatEthAddCommasFromGwei": FormatETHAddCommasFromGwei,
130+
"formatBytesAmount": FormatBytesAmount,
131+
"formatAmount": FormatAmount,
132+
"formatBigAmount": FormatBigAmount,
133+
"formatAmountFormatted": FormatAmountFormatted,
134+
"formatGwei": FormatGweiValue,
135+
"formatByteAmount": FormatByteAmount,
136+
"percentage": CalculatePercentage,
137+
"ethBlockLink": FormatEthBlockLink,
138+
"ethBlockHashLink": FormatEthBlockHashLink,
139+
"ethAddressLink": FormatEthAddressLink,
140+
"ethTransactionLink": FormatEthTransactionLink,
141+
"formatEthAddress": FormatEthAddress,
142+
"formatEthAddressShort": FormatEthAddressShort,
143+
"formatEthAddressShortLink": FormatEthAddressShortLink,
144+
"formatEthAddressFull": FormatEthAddressFull,
145+
"formatEthAddressFullLink": FormatEthAddressFullLink,
146+
"formatHexBytes": FormatHexBytes,
147+
"formatHexBytesShort": FormatHexBytesShort,
148+
"formatWeiAmount": FormatWeiAmount,
149+
"formatWeiDeltaAmount": FormatWeiDeltaAmount,
150+
"formatNFTTokenID": FormatNFTTokenID,
151+
"formatEthHashShort": FormatEthHashShort,
152+
"formatContractCreationLink": FormatContractCreationLink,
153+
"formatValidator": FormatValidator,
154+
"formatValidatorWithIndex": FormatValidatorWithIndex,
155+
"formatValidatorNameWithIndex": FormatValidatorNameWithIndex,
156+
"formatProposerWithBuildSource": FormatProposerWithBuildSource,
157+
"formatSlashedValidator": FormatSlashedValidator,
158+
"formatBuilder": FormatBuilder,
159+
"formatBuilderWithIndex": FormatBuilderWithIndex,
160+
"formatInactiveBuilder": FormatInactiveBuilder,
161+
"formatBuilderWithURL": FormatBuilderWithURL,
162+
"formatBuilderWithIndexAndURL": FormatBuilderWithIndexAndURL,
163+
"formatWithdawalCredentials": FormatWithdawalCredentials,
164+
"formatRecentTimeShort": FormatRecentTimeShort,
165+
"formatGraffiti": FormatGraffiti,
166+
"formatSlotStatusTooltip": FormatSlotStatusTooltip,
167+
"formatRecvDelay": FormatRecvDelay,
168+
"formatPercentageAlert": formatPercentageAlert,
169+
"formatAlertNumber": formatAlertNumber,
170+
"isSystemContract": IsSystemContract,
171+
"getSystemContractName": GetSystemContractName,
172+
"calculateBalanceDiff": CalculateBalanceDiff,
173+
"bitwiseAnd": func(a, b interface{}) int64 { return toInt64(a) & toInt64(b) },
173174
"formatByteSize": func(v any) template.HTML {
174175
n := toInt64(v)
175176
if n < 0 {

0 commit comments

Comments
 (0)