Skip to content

Commit f2db7c5

Browse files
authored
Merge pull request #548 from ethpandaops/pk910/fix-blob-loading
fix blob data loading (new fulu blobs api)
2 parents 6afcf4e + e5eed77 commit f2db7c5

File tree

5 files changed

+149
-65
lines changed

5 files changed

+149
-65
lines changed

clients/consensus/rpc/beaconapi.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,20 @@ func (bc *BeaconClient) GetBlobSidecarsByBlockroot(ctx context.Context, blockroo
439439
return result.Data, nil
440440
}
441441

442+
func (bc *BeaconClient) GetBlobsByBlockroot(ctx context.Context, blockroot []byte) ([]*deneb.Blob, error) {
443+
provider, isProvider := bc.clientSvc.(eth2client.BlobsProvider)
444+
if !isProvider {
445+
return nil, fmt.Errorf("get beacon block blobs not supported")
446+
}
447+
result, err := provider.Blobs(ctx, &api.BlobsOpts{
448+
Block: fmt.Sprintf("0x%x", blockroot),
449+
})
450+
if err != nil {
451+
return nil, err
452+
}
453+
return result.Data, nil
454+
}
455+
442456
func (bc *BeaconClient) GetForkState(ctx context.Context, stateRef string) (*phase0.Fork, error) {
443457
provider, isProvider := bc.clientSvc.(eth2client.ForkProvider)
444458
if !isProvider {

cmd/dora-explorer/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ func startFrontend(router *mux.Router) {
196196
router.HandleFunc("/slots", handlers.Slots).Methods("GET")
197197
router.HandleFunc("/slots/filtered", handlers.SlotsFiltered).Methods("GET")
198198
router.HandleFunc("/slot/{slotOrHash}", handlers.Slot).Methods("GET")
199-
router.HandleFunc("/slot/{root}/blob/{commitment}", handlers.SlotBlob).Methods("GET")
199+
router.HandleFunc("/slot/{root}/blob/{index}", handlers.SlotBlob).Methods("GET")
200200
router.HandleFunc("/blocks", handlers.Blocks).Methods("GET")
201201
router.HandleFunc("/blobs", handlers.Blobs).Methods("GET")
202202
router.HandleFunc("/mev/blocks", handlers.MevBlocks).Methods("GET")

handlers/slot.go

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package handlers
22

33
import (
4-
"bytes"
54
"context"
65
"encoding/hex"
76
"encoding/json"
@@ -15,7 +14,6 @@ import (
1514

1615
"github.com/attestantio/go-eth2-client/spec"
1716
"github.com/attestantio/go-eth2-client/spec/bellatrix"
18-
"github.com/attestantio/go-eth2-client/spec/deneb"
1917
"github.com/attestantio/go-eth2-client/spec/electra"
2018
"github.com/attestantio/go-eth2-client/spec/phase0"
2119
ethtypes "github.com/ethereum/go-ethereum/core/types"
@@ -99,20 +97,13 @@ func Slot(w http.ResponseWriter, r *http.Request) {
9997
}
10098

10199
if urlArgs.Has("blob") && pageData.Block != nil {
102-
commitment, err1 := hex.DecodeString(strings.Replace(urlArgs.Get("blob"), "0x", "", -1))
103-
blobData, err2 := services.GlobalBeaconService.GetBlockBlob(r.Context(), phase0.Root(pageData.Block.BlockRoot), deneb.KZGCommitment(commitment))
100+
blobIndex, err1 := strconv.ParseUint(urlArgs.Get("blob"), 10, 64)
101+
blobData, err2 := services.GlobalBeaconService.GetBlockBlob(r.Context(), phase0.Root(pageData.Block.BlockRoot), blobIndex)
104102
if err1 == nil && err2 == nil && blobData != nil {
105-
var blobModel *models.SlotPageBlob
106-
for _, blob := range pageData.Block.Blobs {
107-
if bytes.Equal(blob.KzgCommitment, commitment) {
108-
blobModel = blob
109-
break
110-
}
111-
}
112-
if blobModel != nil {
113-
blobModel.KzgProof = blobData.KZGProof[:]
103+
if int(blobIndex) < len(pageData.Block.Blobs) {
104+
blobModel := pageData.Block.Blobs[blobIndex]
114105
blobModel.HaveData = true
115-
blobModel.Blob = blobData.Blob[:]
106+
blobModel.Blob = blobData[:]
116107
if len(blobModel.Blob) > 512 {
117108
blobModel.BlobShort = blobModel.Blob[0:512]
118109
blobModel.IsShort = true
@@ -137,32 +128,50 @@ func SlotBlob(w http.ResponseWriter, r *http.Request) {
137128
w.Header().Set("Content-Type", "application/json")
138129

139130
vars := mux.Vars(r)
140-
commitment, err := hex.DecodeString(strings.Replace(vars["commitment"], "0x", "", -1))
141-
if err != nil || len(commitment) != 48 {
142-
http.Error(w, "Internal server error", http.StatusServiceUnavailable)
131+
blobIndex, err := strconv.ParseUint(vars["index"], 10, 64)
132+
if err != nil {
133+
http.Error(w, "Invalid blob index", http.StatusBadRequest)
143134
return
144135
}
145136

146137
blockRoot, err := hex.DecodeString(strings.Replace(vars["root"], "0x", "", -1))
147138
if err != nil || len(blockRoot) != 32 {
148-
http.Error(w, "Internal server error", http.StatusServiceUnavailable)
139+
http.Error(w, "Invalid block root", http.StatusBadRequest)
140+
return
141+
}
142+
143+
// Get the block to retrieve the KZG commitment
144+
blockData, err := services.GlobalBeaconService.GetSlotDetailsByBlockroot(r.Context(), phase0.Root(blockRoot))
145+
if err != nil || blockData == nil || blockData.Block == nil {
146+
http.Error(w, "Block not found", http.StatusNotFound)
147+
return
148+
}
149+
150+
commitments, err := blockData.Block.BlobKZGCommitments()
151+
if err != nil || int(blobIndex) >= len(commitments) {
152+
http.Error(w, "Blob index out of range", http.StatusBadRequest)
149153
return
150154
}
151155

152-
blobData, err := services.GlobalBeaconService.GetBlockBlob(r.Context(), phase0.Root(blockRoot), deneb.KZGCommitment(commitment))
156+
blobData, err := services.GlobalBeaconService.GetBlockBlob(r.Context(), phase0.Root(blockRoot), blobIndex)
153157
if err != nil {
154158
logrus.WithError(err).Error("error loading blob data")
155159
http.Error(w, "Internal server error", http.StatusServiceUnavailable)
156160
return
157161
}
162+
if blobData == nil {
163+
http.Error(w, "Blob not found", http.StatusNotFound)
164+
return
165+
}
166+
158167
result := &models.SlotPageBlobDetails{
159-
KzgCommitment: fmt.Sprintf("%x", blobData.KZGCommitment),
160-
KzgProof: fmt.Sprintf("%x", blobData.KZGProof),
161-
Blob: fmt.Sprintf("%x", blobData.Blob),
168+
Index: blobIndex,
169+
KzgCommitment: fmt.Sprintf("%x", commitments[blobIndex][:]),
170+
Blob: fmt.Sprintf("%x", blobData[:]),
162171
}
163172
err = json.NewEncoder(w).Encode(result)
164173
if err != nil {
165-
logrus.WithError(err).Error("error encoding blob sidecar")
174+
logrus.WithError(err).Error("error encoding blob data")
166175
http.Error(w, "Internal server error", http.StatusServiceUnavailable)
167176
}
168177
}

services/chainservice_blocks.go

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,9 @@ type CombinedBlockResponse struct {
2525
Orphaned bool
2626
}
2727

28-
// GetBlockBlob retrieves the blob sidecar for a given block root and commitment.
29-
// It first tries to find a client that has the block root in its cache, and if not found,
30-
// it falls back to a random ready client. It then retrieves the blob sidecars for the block root
31-
// and checks if any of them match the given commitment. If a match is found, it returns the blob sidecar,
32-
// otherwise it returns nil.
33-
func (bs *ChainService) GetBlockBlob(ctx context.Context, blockroot phase0.Root, commitment deneb.KZGCommitment) (*deneb.BlobSidecar, error) {
28+
// GetBlockBlob retrieves the blob data for a given block root and blob index.
29+
// It uses GetBlobsByBlockroot which works for both pre-Fulu and Fulu+ blocks.
30+
func (bs *ChainService) GetBlockBlob(ctx context.Context, blockroot phase0.Root, blobIndex uint64) (*deneb.Blob, error) {
3431
client := bs.beaconIndexer.GetReadyClientByBlockRoot(blockroot, true)
3532
if client == nil {
3633
client = bs.beaconIndexer.GetReadyClient(true)
@@ -40,18 +37,16 @@ func (bs *ChainService) GetBlockBlob(ctx context.Context, blockroot phase0.Root,
4037
return nil, fmt.Errorf("no clients available")
4138
}
4239

43-
blobs, err := client.GetClient().GetRPCClient().GetBlobSidecarsByBlockroot(ctx, blockroot[:])
40+
blobs, err := client.GetClient().GetRPCClient().GetBlobsByBlockroot(ctx, blockroot[:])
4441
if err != nil {
4542
return nil, err
4643
}
4744

48-
for _, blob := range blobs {
49-
if bytes.Equal(blob.KZGCommitment[:], commitment[:]) {
50-
return blob, nil
51-
}
45+
if int(blobIndex) >= len(blobs) {
46+
return nil, nil
5247
}
5348

54-
return nil, nil
49+
return blobs[blobIndex], nil
5550
}
5651

5752
// GetSlotDetailsByBlockroot retrieves the combined block details for a given block root.

templates/slot/blobs.html

Lines changed: 95 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -7,32 +7,32 @@
77
</div>
88
<div class="row border-bottom p-1 mx-0">
99
<div class="col-md-2"><span data-bs-toggle="tooltip" data-bs-placement="top" title="KZG Commitment">KZG Commitment:</span></div>
10-
<div class="col-md-10 text-monospace">
10+
<div class="col-md-10 text-monospace text-break">
1111
0x{{ printf "%x" $blob.KzgCommitment }}
1212
<i class="fa fa-copy text-muted p-1" role="button" data-bs-toggle="tooltip" title="Copy to clipboard" data-clipboard-text="0x{{ printf "%x" $blob.KzgCommitment }}"></i>
1313
</div>
1414
</div>
1515
{{ if $blob.HaveData }}
1616
<div class="row border-bottom p-1 mx-0">
17-
<div class="col-md-2"><span data-bs-toggle="tooltip" data-bs-placement="top" title="KZG Proof">KZG Proof:</span></div>
18-
<div class="col-md-10 text-monospace">
19-
0x{{ printf "%x" $blob.KzgProof }}
20-
<i class="fa fa-copy text-muted p-1" role="button" data-bs-toggle="tooltip" title="Copy to clipboard" data-clipboard-text="0x{{ printf "%x" $blob.KzgProof }}"></i>
17+
<div class="col-md-2">
18+
<span data-bs-toggle="tooltip" data-bs-placement="top" title="Blob Data">Data:</span>
19+
<div class="btn-group btn-group-sm ms-2" role="group">
20+
<button type="button" class="btn btn-outline-secondary btn-sm blob-view-toggle active" data-mode="hex" data-blob-idx="{{ $blob.Index }}">Hex</button>
21+
<button type="button" class="btn btn-outline-secondary btn-sm blob-view-toggle" data-mode="ascii" data-blob-idx="{{ $blob.Index }}">ASCII</button>
22+
</div>
2123
</div>
22-
</div>
23-
<div class="row border-bottom p-1 mx-0">
24-
<div class="col-md-2"><span data-bs-toggle="tooltip" data-bs-placement="top" title="Blob Data">Data:</span></div>
25-
<div class="col-md-10 text-monospace">
26-
0x{{ printf "%x" $blob.BlobShort }}
27-
{{- if $blob.IsShort -}}...{{ end }}
24+
<div class="col-md-10">
25+
<div class="blob-data-container" data-blob-idx="{{ $blob.Index }}" data-blob-hex="{{ printf "%x" $blob.Blob }}">
26+
<pre class="blob-data-view text-monospace mb-0" style="max-height: 300px; overflow-y: auto; white-space: pre-wrap; word-break: break-all; background: var(--bs-tertiary-bg); padding: 0.5rem; border-radius: 0.25rem;">0x{{ printf "%x" $blob.BlobShort }}{{- if $blob.IsShort -}}...{{ end }}</pre>
27+
</div>
2828
<i class="fa fa-copy text-muted p-1" role="button" data-bs-toggle="tooltip" title="Copy to clipboard" data-clipboard-text="0x{{ printf "%x" $blob.Blob }}"></i>
2929
</div>
3030
</div>
3131
{{ else }}
32-
<div class="blobloader-container" data-commitment="0x{{ printf "%x" $blob.KzgCommitment }}">
32+
<div class="blobloader-container" data-blob-idx="{{ $blob.Index }}">
3333
<div class="row border-bottom p-1 mx-0">
3434
<div class="col text-center">
35-
<a class="btn btn-primary blobloader-button" href="?blob=0x{{ printf "%x" $blob.KzgCommitment }}#blobSidecars" role="button">Load Blob Data</a>
35+
<a class="btn btn-primary blobloader-button" href="?blob={{ $blob.Index }}#blobSidecars" role="button">Load Blob Data</a>
3636
</div>
3737
</div>
3838
</div>
@@ -42,15 +42,53 @@
4242
{{ end }}
4343
<script type="text/javascript">
4444
$(function() {
45+
function hexToAscii(hex) {
46+
var str = '';
47+
for (var i = 0; i < hex.length; i += 2) {
48+
var code = parseInt(hex.substr(i, 2), 16);
49+
str += (code >= 32 && code <= 126) ? String.fromCharCode(code) : '.';
50+
}
51+
return str;
52+
}
53+
54+
function formatBlobData(hex, mode, maxLen) {
55+
if (mode === 'ascii') {
56+
var ascii = hexToAscii(hex);
57+
if (maxLen && ascii.length > maxLen) {
58+
return ascii.substring(0, maxLen) + '...';
59+
}
60+
return ascii;
61+
} else {
62+
if (maxLen && hex.length > maxLen * 2) {
63+
return '0x' + hex.substring(0, maxLen * 2) + '...';
64+
}
65+
return '0x' + hex;
66+
}
67+
}
68+
69+
$(".blob-view-toggle").on("click", function() {
70+
var btn = $(this);
71+
var mode = btn.data("mode");
72+
var blobIdx = btn.data("blob-idx");
73+
var container = $(".blob-data-container[data-blob-idx='" + blobIdx + "']");
74+
var hex = container.data("blob-hex");
75+
var view = container.find(".blob-data-view");
76+
77+
btn.siblings().removeClass("active");
78+
btn.addClass("active");
79+
80+
view.text(formatBlobData(hex, mode, 512));
81+
});
82+
4583
$(".blobloader-button").each(function() {
4684
var button = $(this);
4785
var container = button.closest(".blobloader-container");
86+
var blobIdx = container.data("blob-idx");
4887
button.on("click", function(evt) {
4988
evt.preventDefault();
5089
if(button.hasClass("disabled")) return;
5190
button.attr("disabled", "disabled").addClass("disabled");
52-
var commitment = container.data("commitment");
53-
jQuery.get("/slot/0x{{ printf "%x" .Block.BlockRoot }}/blob/" + commitment).then(function(data, status) {
91+
jQuery.get("/slot/0x{{ printf "%x" .Block.BlockRoot }}/blob/" + blobIdx).then(function(data, status) {
5492
if(status == "success")
5593
onSuccess(data);
5694
else
@@ -60,32 +98,60 @@
6098
button.attr("disabled", "").removeClass("disabled");
6199
}
62100
function onSuccess(data) {
63-
var blobShort = data.blob;
64-
if(blobShort.length > 1024 + 2) {
65-
blobShort = blobShort.substring(0, 1024 + 2) + "...";
101+
var blobHex = data.blob;
102+
var blobShort = blobHex;
103+
if(blobShort.length > 1024) {
104+
blobShort = blobShort.substring(0, 1024) + "...";
66105
}
67106
var rowHtml = [
68107
'<div class="row border-bottom p-1 mx-0">',
69-
'<div class="col-md-2"><span data-bs-toggle="tooltip" data-bs-placement="top" title="KZG Proof">KZG Proof:</span></div>',
70-
'<div class="col-md-10 text-monospace">',
71-
data.kzg_proof,
72-
'<i class="fa fa-copy text-muted p-1" role="button" data-bs-toggle="tooltip" title="Copy to clipboard" data-clipboard-text="' + data.kzg_proof + '"></i>',
108+
'<div class="col-md-2">',
109+
'<span data-bs-toggle="tooltip" data-bs-placement="top" title="Blob Data">Data:</span>',
110+
'<div class="btn-group btn-group-sm ms-2" role="group">',
111+
'<button type="button" class="btn btn-outline-secondary btn-sm blob-view-toggle active" data-mode="hex" data-blob-idx="' + blobIdx + '">Hex</button>',
112+
'<button type="button" class="btn btn-outline-secondary btn-sm blob-view-toggle" data-mode="ascii" data-blob-idx="' + blobIdx + '">ASCII</button>',
113+
'</div>',
73114
'</div>',
74-
'</div>',
75-
'<div class="row border-bottom p-1 mx-0">',
76-
'<div class="col-md-2"><span data-bs-toggle="tooltip" data-bs-placement="top" title="Blob Data">Data:</span></div>',
77-
'<div class="col-md-10 text-monospace">',
78-
blobShort,
79-
'<i class="fa fa-copy text-muted p-1" role="button" data-bs-toggle="tooltip" title="Copy to clipboard" data-clipboard-text="' + data.blob + '"></i>',
115+
'<div class="col-md-10">',
116+
'<div class="blob-data-container" data-blob-idx="' + blobIdx + '" data-blob-hex="' + blobHex + '">',
117+
'<pre class="blob-data-view text-monospace mb-0" style="max-height: 300px; overflow-y: auto; white-space: pre-wrap; word-break: break-all; background: var(--bs-tertiary-bg); padding: 0.5rem; border-radius: 0.25rem;">0x' + blobShort + '</pre>',
118+
'</div>',
119+
'<i class="fa fa-copy text-muted p-1" role="button" data-bs-toggle="tooltip" title="Copy to clipboard" data-clipboard-text="0x' + blobHex + '"></i>',
80120
'</div>',
81121
'</div>',
82122
].join("");
83123
container.html(rowHtml);
124+
125+
// Re-bind click handlers for the new toggle buttons
126+
container.find(".blob-view-toggle").on("click", function() {
127+
var btn = $(this);
128+
var mode = btn.data("mode");
129+
var idx = btn.data("blob-idx");
130+
var cont = $(".blob-data-container[data-blob-idx='" + idx + "']");
131+
var hex = cont.data("blob-hex");
132+
var view = cont.find(".blob-data-view");
133+
134+
btn.siblings().removeClass("active");
135+
btn.addClass("active");
136+
137+
if (mode === 'ascii') {
138+
var ascii = '';
139+
for (var i = 0; i < hex.length; i += 2) {
140+
var code = parseInt(hex.substr(i, 2), 16);
141+
ascii += (code >= 32 && code <= 126) ? String.fromCharCode(code) : '.';
142+
}
143+
if (ascii.length > 512) ascii = ascii.substring(0, 512) + '...';
144+
view.text(ascii);
145+
} else {
146+
var short = hex.length > 1024 ? hex.substring(0, 1024) + '...' : hex;
147+
view.text('0x' + short);
148+
}
149+
});
150+
84151
explorer.initControls();
85152
}
86153
});
87154
});
88-
89155
});
90156
</script>
91157
{{ end }}

0 commit comments

Comments
 (0)