Skip to content

Commit

Permalink
Merge pull request #4 from pk910/lazyload-blobs
Browse files Browse the repository at this point in the history
Lazyload blobs
  • Loading branch information
pk910 authored Aug 1, 2023
2 parents 7e2d41e + c4f0ae1 commit 5e59a32
Show file tree
Hide file tree
Showing 10 changed files with 204 additions and 58 deletions.
1 change: 1 addition & 0 deletions cmd/explorer/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ func startFrontend() {
router.HandleFunc("/epoch/{epoch}", handlers.Epoch).Methods("GET")
router.HandleFunc("/slots", handlers.Slots).Methods("GET")
router.HandleFunc("/slot/{slotOrHash}", handlers.Slot).Methods("GET")
router.HandleFunc("/slot/{hash}/blob/{blobIdx}", handlers.SlotBlob).Methods("GET")
router.HandleFunc("/search", handlers.Search).Methods("GET")
router.HandleFunc("/search/{type}", handlers.SearchAhead).Methods("GET")
router.HandleFunc("/validators", handlers.Validators).Methods("GET")
Expand Down
1 change: 1 addition & 0 deletions config/default.config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ server:
frontend:
enabled: true # Enable or disable to web frontend
debug: false
minimize: false # minimize html templates

# Name of the site, displayed in the title tag
siteName: "Beaconchain Light"
Expand Down
12 changes: 2 additions & 10 deletions handlers/pageData.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ var layoutTemplateFiles = []string{
}

func InitPageData(w http.ResponseWriter, r *http.Request, active, path, title string, mainTemplates []string) *types.PageData {
fullTitle := fmt.Sprintf("%v - %v - beaconchain light - %v", title, utils.Config.Frontend.SiteName, time.Now().Year())
fullTitle := fmt.Sprintf("%v - %v - %v", title, utils.Config.Frontend.SiteName, time.Now().Year())

if title == "" {
fullTitle = fmt.Sprintf("%v - beaconchain light - %v", utils.Config.Frontend.SiteName, time.Now().Year())
fullTitle = fmt.Sprintf("%v - %v", utils.Config.Frontend.SiteName, time.Now().Year())
}

isMainnet := utils.Config.Chain.Config.ConfigName == "mainnet"
Expand Down Expand Up @@ -70,14 +70,6 @@ func InitPageData(w http.ResponseWriter, r *http.Request, active, path, title st
return data
}

func SetPageDataTitle(pageData *types.PageData, title string) {
if title == "" {
pageData.Meta.Title = fmt.Sprintf("%v - light beaconchain explorer - %v", utils.Config.Frontend.SiteName, time.Now().Year())
} else {
pageData.Meta.Title = fmt.Sprintf("%v - %v - light beaconchain explorer - %v", title, utils.Config.Frontend.SiteName, time.Now().Year())
}
}

func createMenuItems(active string, isMain bool) []types.MainMenuItem {
hiddenFor := []string{"confirmation", "login", "register"}

Expand Down
109 changes: 89 additions & 20 deletions handlers/slot.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package handlers

import (
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"strconv"
Expand Down Expand Up @@ -62,6 +63,25 @@ func Slot(w http.ResponseWriter, r *http.Request) {
return
}

urlArgs := r.URL.Query()
if urlArgs.Has("blob") && pageData.Block != nil {
blobData, err := services.GlobalBeaconService.GetBlobSidecarsByBlockRoot(pageData.Block.BlockRoot)
if err == nil && blobData != nil {
for blobIdx, blob := range blobData.Data {
blobData := pageData.Block.Blobs[blobIdx]
blobData.HaveData = true
blobData.KzgProof = blob.KzgProof
blobData.Blob = blob.Blob
if len(blob.Blob) > 512 {
blobData.BlobShort = blob.Blob[0:512]
blobData.IsShort = true
} else {
blobData.BlobShort = blob.Blob
}
}
}
}

template := templates.GetTemplate(slotTemplateFiles...)
data := InitPageData(w, r, "blockchain", "/slots", fmt.Sprintf("Slot %v", slotOrHash), slotTemplateFiles)
data.Data = pageData
Expand All @@ -70,23 +90,64 @@ func Slot(w http.ResponseWriter, r *http.Request) {
}
}

// SlotBlob handles responses for the block blobs tab
func SlotBlob(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")

vars := mux.Vars(r)
blockRoot, err := hex.DecodeString(strings.Replace(vars["hash"], "0x", "", -1))
if err != nil || len(blockRoot) != 32 {
http.Error(w, "Internal server error", http.StatusServiceUnavailable)
return
}
blobIdx, err := strconv.ParseUint(vars["blobIdx"], 10, 64)
if err != nil {
http.Error(w, "Internal server error", http.StatusServiceUnavailable)
return
}
blobData, err := services.GlobalBeaconService.GetBlobSidecarsByBlockRoot(blockRoot)
if err != nil {
logrus.WithError(err).Error("error loading blob sidecar")
http.Error(w, "Internal server error", http.StatusServiceUnavailable)
return
}
var result interface{}
if blobData != nil && blobIdx < uint64(len(blobData.Data)) {
blob := blobData.Data[blobIdx]
result = &models.SlotPageBlobDetails{
Index: blobIdx,
KzgCommitment: blob.KzgCommitment.String(),
KzgProof: blob.KzgProof.String(),
Blob: blob.Blob.String(),
}
}
err = json.NewEncoder(w).Encode(result)
if err != nil {
logrus.WithError(err).Error("error encoding blob sidecar")
http.Error(w, "Internal server error", http.StatusServiceUnavailable)
}
}

func getSlotPageData(blockSlot int64, blockRoot []byte) *models.SlotPageData {
pageData := &models.SlotPageData{}
pageCacheKey := fmt.Sprintf("slot:%v:%x", blockSlot, blockRoot)
pageData = services.GlobalFrontendCache.ProcessCachedPage(pageCacheKey, false, pageData, func(pageCall *services.FrontendCacheProcessingPage) interface{} {
return buildSlotPageData(blockSlot, blockRoot)
pageData = services.GlobalFrontendCache.ProcessCachedPage(pageCacheKey, true, pageData, func(pageCall *services.FrontendCacheProcessingPage) interface{} {
pageData, cacheTimeout := buildSlotPageData(blockSlot, blockRoot)
pageCall.CacheTimeout = cacheTimeout
return pageData
}).(*models.SlotPageData)
return pageData
}

func buildSlotPageData(blockSlot int64, blockRoot []byte) *models.SlotPageData {
func buildSlotPageData(blockSlot int64, blockRoot []byte) (*models.SlotPageData, time.Duration) {
currentSlot := utils.TimeToSlot(uint64(time.Now().Unix()))
finalizedHead, err := services.GlobalBeaconService.GetFinalizedBlockHead()
var blockData *rpctypes.CombinedBlockResponse
if err == nil {
if blockSlot > -1 {
blockData, err = services.GlobalBeaconService.GetSlotDetailsBySlot(uint64(blockSlot), true)
blockData, err = services.GlobalBeaconService.GetSlotDetailsBySlot(uint64(blockSlot), false)
} else {
blockData, err = services.GlobalBeaconService.GetSlotDetailsByBlockroot(blockRoot, true)
blockData, err = services.GlobalBeaconService.GetSlotDetailsByBlockroot(blockRoot, false)
}
}

Expand All @@ -104,7 +165,7 @@ func buildSlotPageData(blockSlot int64, blockRoot []byte) *models.SlotPageData {
}

if blockData == nil {
return nil
return nil, -1
}
slot := uint64(blockData.Header.Data.Header.Message.Slot)
logrus.Printf("slot page called: %v", slot)
Expand All @@ -116,6 +177,7 @@ func buildSlotPageData(blockSlot int64, blockRoot []byte) *models.SlotPageData {
Ts: utils.SlotToTime(slot),
NextSlot: slot + 1,
PreviousSlot: slot - 1,
Future: slot >= currentSlot,
}

assignments, err := services.GlobalBeaconService.GetEpochAssignments(utils.EpochOfSlot(slot))
Expand All @@ -124,6 +186,22 @@ func buildSlotPageData(blockSlot int64, blockRoot []byte) *models.SlotPageData {
// we can safely continue here. the UI is prepared to work without epoch duties, but fields related to the duties are not shown
}

var cacheTimeout time.Duration
if pageData.Future {
timeDiff := pageData.Ts.Sub(time.Now())
if timeDiff > 10*time.Minute {
cacheTimeout = 10 * time.Minute
} else {
cacheTimeout = timeDiff
}
} else if pageData.EpochFinalized {
cacheTimeout = 10 * time.Minute
} else if blockData != nil {
cacheTimeout = 5 * time.Minute
} else {
cacheTimeout = 10 * time.Second
}

if blockData == nil {
pageData.Status = uint16(models.SlotStatusMissed)

Expand All @@ -142,7 +220,7 @@ func buildSlotPageData(blockSlot int64, blockRoot []byte) *models.SlotPageData {
pageData.Block = getSlotPageBlockData(blockData, assignments)
}

return pageData
return pageData, cacheTimeout
}

func getSlotPageBlockData(blockData *rpctypes.CombinedBlockResponse, assignments *rpctypes.EpochAssignments) *models.SlotPageBlockData {
Expand Down Expand Up @@ -338,22 +416,13 @@ func getSlotPageBlockData(blockData *rpctypes.CombinedBlockResponse, assignments
}
}

if epoch >= utils.Config.Chain.Config.DenebForkEpoch && blockData.Blobs != nil {
pageData.BlobsCount = uint64(len(blockData.Blobs.Data))
if epoch >= utils.Config.Chain.Config.DenebForkEpoch {
pageData.BlobsCount = uint64(len(blockData.Block.Data.Message.Body.BlobKzgCommitments))
pageData.Blobs = make([]*models.SlotPageBlob, pageData.BlobsCount)
for i := uint64(0); i < pageData.BlobsCount; i++ {
blob := blockData.Blobs.Data[i]
blobData := &models.SlotPageBlob{
Index: uint64(blob.Index),
KzgCommitment: blob.KzgCommitment,
KzgProof: blob.KzgProof,
Blob: blob.Blob,
}
if len(blob.Blob) > 512 {
blobData.BlobShort = blob.Blob[0:512]
blobData.IsShort = true
} else {
blobData.BlobShort = blob.Blob
Index: i,
KzgCommitment: blockData.Block.Data.Message.Body.BlobKzgCommitments[i],
}
pageData.Blobs[i] = blobData
}
Expand Down
4 changes: 4 additions & 0 deletions services/beaconservice.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,10 @@ func (bs *BeaconService) GetSlotDetailsBySlot(slot uint64, withBlobs bool) (*rpc
return result, nil
}

func (bs *BeaconService) GetBlobSidecarsByBlockRoot(blockroot []byte) (*rpctypes.StandardV1BlobSidecarsResponse, error) {
return bs.rpcClient.GetBlobSidecarsByBlockroot(blockroot)
}

func (bs *BeaconService) GetOrphanedBlock(blockroot []byte) *rpctypes.CombinedBlockResponse {
orphanedBlock := db.GetOrphanedBlock(blockroot)
if orphanedBlock == nil {
Expand Down
10 changes: 5 additions & 5 deletions static/js/explorer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@
(function() {
window.addEventListener('DOMContentLoaded', function() {
initControls();
window.setInterval(updateTimers, 1000);
initHeaderSearch();
});
window.explorer = {
initControls: initControls,
};

function initControls() {
window.setInterval(updateTimers, 1000);

// init tooltips
var tooltipEls = document.querySelectorAll('[data-bs-toggle="tooltip"]');
Array.prototype.forEach.call(tooltipEls, function(tooltipEl) {
Expand All @@ -33,9 +36,6 @@
tooltip.setContent({ '.tooltip-inner': title });
}, 1000);
});

// init type-ahead search
initHeaderSearch()
}

function updateTimers() {
Expand Down
84 changes: 71 additions & 13 deletions templates/slot/blobs.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,80 @@
<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>
</div>
</div>
<div class="row border-bottom p-1 mx-0">
<div class="col-md-2"><span data-bs-toggle="tooltip" data-bs-placement="top" title="KGZ Proof">KGZ Proof:</span></div>
<div class="col-md-10 text-monospace">
0x{{ printf "%x" $blob.KzgProof }}
<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>
{{ if $blob.HaveData }}
<div class="row border-bottom p-1 mx-0">
<div class="col-md-2"><span data-bs-toggle="tooltip" data-bs-placement="top" title="KGZ Proof">KGZ Proof:</span></div>
<div class="col-md-10 text-monospace">
0x{{ printf "%x" $blob.KzgProof }}
<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>
</div>
</div>
</div>
<div class="row border-bottom p-1 mx-0">
<div class="col-md-2"><span data-bs-toggle="tooltip" data-bs-placement="top" title="Blob Data">Data:</span></div>
<div class="col-md-10 text-monospace">
0x{{ printf "%x" $blob.BlobShort }}
{{- if $blob.IsShort -}}...{{ end }}
<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>
<div class="row border-bottom p-1 mx-0">
<div class="col-md-2"><span data-bs-toggle="tooltip" data-bs-placement="top" title="Blob Data">Data:</span></div>
<div class="col-md-10 text-monospace">
0x{{ printf "%x" $blob.BlobShort }}
{{- if $blob.IsShort -}}...{{ end }}
<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>
</div>
</div>
</div>
{{ else }}
<div class="blobloader-container" data-blobidx="{{ $blob.Index }}">
<div class="row border-bottom p-1 mx-0">
<div class="col text-center">
<a class="btn btn-primary blobloader-button" href="?blob=1#blobSidecars" role="button">Load Blob Data</a>
</div>
</div>
</div>
{{ end }}
</div>
</div>
{{ end }}
<script type="text/javascript">
$(function() {
$(".blobloader-button").each(function() {
var button = $(this);
var container = button.closest(".blobloader-container");
button.on("click", function(evt) {
evt.preventDefault();
if(button.hasClass("disabled")) return;
button.attr("disabled", "disabled").addClass("disabled");
var blobIdx = container.data("blobidx");
jQuery.get("/slot/0x{{ printf "%x" .Block.BlockRoot }}/blob/" + blobIdx).then(function(data, status) {
if(status == "success")
onSuccess(data);
else
onFail();
}, onFail);
function onFail() {
button.attr("disabled", "").removeClass("disabled");
}
function onSuccess(data) {
var blobShort = data.blob;
if(blobShort.length > 1024 + 2) {
blobShort = blobShort.substring(0, 1024 + 2) + "...";
}
var rowHtml = [
'<div class="row border-bottom p-1 mx-0">',
'<div class="col-md-2"><span data-bs-toggle="tooltip" data-bs-placement="top" title="KGZ Proof">KGZ Proof:</span></div>',
'<div class="col-md-10 text-monospace">',
data.kzg_proof,
'<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>',
'</div>',
'</div>',
'<div class="row border-bottom p-1 mx-0">',
'<div class="col-md-2"><span data-bs-toggle="tooltip" data-bs-placement="top" title="Blob Data">Data:</span></div>',
'<div class="col-md-10 text-monospace">',
blobShort,
'<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>',
'</div>',
'</div>',
].join("");
container.html(rowHtml);
explorer.initControls();
}
});
});

});
</script>
{{ end }}
Loading

0 comments on commit 5e59a32

Please sign in to comment.