From 28ecf4f450f4a46d36ea1e40026e05791465b592 Mon Sep 17 00:00:00 2001 From: tac0turtle Date: Fri, 5 Sep 2025 18:32:28 +0200 Subject: [PATCH 1/4] create a blobdecoder tool --- tools/blob-decoder/main.go | 1062 ++++++++++++++++++++++++++++++++++++ 1 file changed, 1062 insertions(+) create mode 100644 tools/blob-decoder/main.go diff --git a/tools/blob-decoder/main.go b/tools/blob-decoder/main.go new file mode 100644 index 0000000000..084be8aed7 --- /dev/null +++ b/tools/blob-decoder/main.go @@ -0,0 +1,1062 @@ +package main + +import ( + "encoding/base64" + "encoding/hex" + "encoding/json" + "fmt" + "html/template" + "log" + "net/http" + "strings" + "time" + + "google.golang.org/protobuf/proto" + + "github.com/evstack/ev-node/types" + pb "github.com/evstack/ev-node/types/pb/evnode/v1" +) + +// Removed embed since we're serving HTML inline + +// DecodedBlob represents the result of decoding a blob +type DecodedBlob struct { + Type string `json:"type"` + Data interface{} `json:"data"` + RawHex string `json:"rawHex"` + Size int `json:"size"` + Timestamp time.Time `json:"timestamp"` + Error string `json:"error,omitempty"` +} + +func main() { + mux := http.NewServeMux() + + // Serve the main page + mux.HandleFunc("/", handleIndex) + + // API endpoint for decoding blobs + mux.HandleFunc("/api/decode", handleDecode) + + // Static files removed - HTML is served inline + + port := "8090" + fmt.Printf(` +╔════════════════════════════════════════════╗ +║ Evolve Blob Decoder ║ +║ by ev.xyz ║ +╚════════════════════════════════════════════╝ + +🚀 Server running at: http://localhost:%s +⚡ Using native Evolve protobuf decoding + +Press Ctrl+C to stop the server + +`, port) + + log.Fatal(http.ListenAndServe(":"+port, corsMiddleware(mux))) +} + +func corsMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS") + w.Header().Set("Access-Control-Allow-Headers", "Content-Type") + + if r.Method == "OPTIONS" { + w.WriteHeader(http.StatusOK) + return + } + + next.ServeHTTP(w, r) + }) +} + +func handleIndex(w http.ResponseWriter, r *http.Request) { + tmpl := template.Must(template.New("index").Parse(indexHTML)) + tmpl.Execute(w, nil) +} + +func handleDecode(w http.ResponseWriter, r *http.Request) { + if r.Method != "POST" { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + var request struct { + Data string `json:"data"` + Encoding string `json:"encoding"` // "hex" or "base64" + } + + if err := json.NewDecoder(r.Body).Decode(&request); err != nil { + http.Error(w, "Invalid request body", http.StatusBadRequest) + return + } + + // Decode the input data + var blobData []byte + var err error + + switch request.Encoding { + case "hex": + cleanHex := strings.TrimPrefix(request.Data, "0x") + blobData, err = hex.DecodeString(cleanHex) + case "base64": + blobData, err = base64.StdEncoding.DecodeString(request.Data) + default: + http.Error(w, "Invalid encoding type", http.StatusBadRequest) + return + } + + if err != nil { + sendJSONResponse(w, DecodedBlob{ + Error: fmt.Sprintf("Failed to decode %s: %v", request.Encoding, err), + Timestamp: time.Now(), + }) + return + } + + // Try to decode the blob + result := decodeBlob(blobData) + result.RawHex = hex.EncodeToString(blobData) + result.Size = len(blobData) + result.Timestamp = time.Now() + + sendJSONResponse(w, result) +} + +func decodeBlob(data []byte) DecodedBlob { + // Try to decode as SignedHeader + if header := tryDecodeHeader(data); header != nil { + return DecodedBlob{ + Type: "SignedHeader", + Data: header, + } + } + + // Try to decode as SignedData + if signedData := tryDecodeSignedData(data); signedData != nil { + return DecodedBlob{ + Type: "SignedData", + Data: signedData, + } + } + + // Check if it's JSON + var jsonData interface{} + if err := json.Unmarshal(data, &jsonData); err == nil { + return DecodedBlob{ + Type: "JSON", + Data: jsonData, + } + } + + // Return as unknown binary + return DecodedBlob{ + Type: "Unknown", + Data: map[string]interface{}{ + "message": "Unable to decode blob format", + "preview": hex.EncodeToString(data[:min(100, len(data))]), + }, + } +} + +func tryDecodeHeader(data []byte) interface{} { + var headerPb pb.SignedHeader + if err := proto.Unmarshal(data, &headerPb); err != nil { + return nil + } + + var signedHeader types.SignedHeader + if err := signedHeader.FromProto(&headerPb); err != nil { + return nil + } + + // Basic validation + if err := signedHeader.Header.ValidateBasic(); err != nil { + return nil + } + + // Return a map with the actual header fields + return map[string]interface{}{ + // BaseHeader fields + "height": signedHeader.Height(), + "time": signedHeader.Time().Format(time.RFC3339Nano), + "chainId": signedHeader.ChainID(), + + // Version + "version": map[string]interface{}{ + "block": signedHeader.Version.Block, + "app": signedHeader.Version.App, + }, + + // Hash fields (convert [32]byte arrays to hex strings) + "lastHeaderHash": bytesToHex(signedHeader.LastHeaderHash[:]), + "lastCommitHash": bytesToHex(signedHeader.LastCommitHash[:]), + "dataHash": bytesToHex(signedHeader.DataHash[:]), + "consensusHash": bytesToHex(signedHeader.ConsensusHash[:]), + "appHash": bytesToHex(signedHeader.AppHash[:]), + "lastResultsHash": bytesToHex(signedHeader.LastResultsHash[:]), + "validatorHash": bytesToHex(signedHeader.ValidatorHash[:]), + + // Proposer + "proposerAddress": bytesToHex(signedHeader.ProposerAddress), + + // Signature fields + "signature": bytesToHex(signedHeader.Signature), + "signer": map[string]interface{}{ + "address": bytesToHex(signedHeader.Signer.Address), + "pubKey": "", + }, + } +} + +func tryDecodeSignedData(data []byte) interface{} { + var signedData types.SignedData + if err := signedData.UnmarshalBinary(data); err != nil { + return nil + } + + // Create transaction list + transactions := make([]map[string]interface{}, len(signedData.Txs)) + for i, tx := range signedData.Txs { + transactions[i] = map[string]interface{}{ + "index": i, + "size": len(tx), + "data": bytesToHex(tx), + } + } + + result := map[string]interface{}{ + "transactions": transactions, + "transactionCount": len(signedData.Txs), + "signature": bytesToHex(signedData.Signature), + } + + // Add Metadata fields if present + if signedData.Metadata != nil { + result["chainId"] = signedData.ChainID() + result["height"] = signedData.Height() + result["time"] = signedData.Time().Format(time.RFC3339Nano) + result["lastDataHash"] = bytesToHex(signedData.LastDataHash[:]) + } + + // Add DACommitment hash + dataHash := signedData.Data.DACommitment() + result["dataHash"] = bytesToHex(dataHash[:]) + + return result +} + +func bytesToHex(b []byte) string { + if b == nil || len(b) == 0 { + return "" + } + return hex.EncodeToString(b) +} + +func sendJSONResponse(w http.ResponseWriter, data interface{}) { + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(data) +} + +func min(a, b int) int { + if a < b { + return a + } + return b +} + +const indexHTML = ` + + + + + Evolve Blob Decoder | ev.xyz + + + +
+
+ +
ev.xyz
+

⚡ Evolve Blob Decoder

+

Own It. Shape It. Launch It.

+

Decode and inspect DA layer blobs from your Evolve rollup

+
+ +
+
+

Input Blob Data

+
+ + +
+ + + +
+ + + + + +
+
+ + +
+ +
+
+ + + +
+ +
+
+
+
+
+
+ +
+
+ Evolve - The Modular Rollup Framework +
+
+ 🚀 Full Control Over Execution  |  + ⚡ Speed to Traction  |  + 🛡️ No Validator Overhead +
+
+ ev.xyz  |  + Documentation  |  + GitHub +
+
+
+ + + + +` From 5c8f4622c997d405f1c5cf77429030bba9c19f03 Mon Sep 17 00:00:00 2001 From: tac0turtle Date: Mon, 8 Sep 2025 10:28:04 +0200 Subject: [PATCH 2/4] fix lint issues --- tools/blob-decoder/main.go | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/tools/blob-decoder/main.go b/tools/blob-decoder/main.go index 084be8aed7..f738cdbf2c 100644 --- a/tools/blob-decoder/main.go +++ b/tools/blob-decoder/main.go @@ -54,7 +54,16 @@ Press Ctrl+C to stop the server `, port) - log.Fatal(http.ListenAndServe(":"+port, corsMiddleware(mux))) + // Create server with proper timeout configuration + srv := &http.Server{ + Addr: ":" + port, + Handler: corsMiddleware(mux), + ReadTimeout: 15 * time.Second, + WriteTimeout: 15 * time.Second, + IdleTimeout: 60 * time.Second, + } + + log.Fatal(srv.ListenAndServe()) } func corsMiddleware(next http.Handler) http.Handler { @@ -74,7 +83,10 @@ func corsMiddleware(next http.Handler) http.Handler { func handleIndex(w http.ResponseWriter, r *http.Request) { tmpl := template.Must(template.New("index").Parse(indexHTML)) - tmpl.Execute(w, nil) + if err := tmpl.Execute(w, nil); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } } func handleDecode(w http.ResponseWriter, r *http.Request) { @@ -242,14 +254,14 @@ func tryDecodeSignedData(data []byte) interface{} { } // Add DACommitment hash - dataHash := signedData.Data.DACommitment() + dataHash := signedData.DACommitment() result["dataHash"] = bytesToHex(dataHash[:]) return result } func bytesToHex(b []byte) string { - if b == nil || len(b) == 0 { + if len(b) == 0 { return "" } return hex.EncodeToString(b) @@ -257,7 +269,10 @@ func bytesToHex(b []byte) string { func sendJSONResponse(w http.ResponseWriter, data interface{}) { w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(data) + if err := json.NewEncoder(w).Encode(data); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } } func min(a, b int) int { From 65003ce6721b3fc1fd5f367444a31ebb749db4f0 Mon Sep 17 00:00:00 2001 From: tac0turtle Date: Mon, 8 Sep 2025 10:33:41 +0200 Subject: [PATCH 3/4] add docs --- docs/guides/da/blob-decoder.md | 152 +++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 docs/guides/da/blob-decoder.md diff --git a/docs/guides/da/blob-decoder.md b/docs/guides/da/blob-decoder.md new file mode 100644 index 0000000000..b37d963711 --- /dev/null +++ b/docs/guides/da/blob-decoder.md @@ -0,0 +1,152 @@ +# Blob Decoder Tool + +The blob decoder is a utility tool for decoding and inspecting blobs from Celestia (DA) layers. It provides both a web interface and API for decoding blob data into human-readable format. + +## Overview + +The blob decoder helps developers and operators inspect the contents of blobs submitted to DA layers. It can decode: +- Raw blob data (hex or base64 encoded) +- Block data structures +- Transaction payloads +- Protobuf-encoded messages + +## Usage + +### Starting the Server + +```bash +# Run with default port (8080) +go run tools/blob-decoder/main.go +``` + +The server will start and display: +- Web interface URL: `http://localhost:8080` +- API endpoint: `http://localhost:8080/api/decode` + +### Web Interface + +1. Open your browser to `http://localhost:8080` +2. Paste your blob data in the input field +3. Select the encoding format (hex or base64) +4. Click "Decode" to see the parsed output + +### API Usage + +The decoder provides a REST API for programmatic access: + +```bash +# Decode hex-encoded blob +curl -X POST http://localhost:8080/api/decode \ + -H "Content-Type: application/json" \ + -d '{ + "data": "0x1234abcd...", + "encoding": "hex" + }' + +# Decode base64-encoded blob +curl -X POST http://localhost:8080/api/decode \ + -H "Content-Type: application/json" \ + -d '{ + "data": "SGVsbG8gV29ybGQ=", + "encoding": "base64" + }' +``` + +#### API Request Format + +```json +{ + "data": "string", // The encoded blob data + "encoding": "string" // Either "hex" or "base64" +} +``` + +#### API Response Format + +```json +{ + "success": true, + "decoded": { + // Decoded data structure + }, + "error": "string" // Only present if success is false +} +``` + +## Supported Data Types + +### Block Data + +The decoder can parse ev-node block structures: +- Block height +- Timestamp +- Parent hash +- Transaction list +- Validator information +- Data commitments + +### Transaction Data + +Decodes individual transactions including: +- Transaction type +- Sender/receiver addresses +- Value/amount +- Gas parameters +- Payload data + +### Protobuf Messages + +Automatically detects and decodes protobuf-encoded messages used in ev-node: +- Block headers +- Transaction batches +- State updates +- DA commitments + +## Examples + +### Decoding a Block Blob + +```bash +# Example block blob (hex encoded) +curl -X POST http://localhost:8080/api/decode \ + -H "Content-Type: application/json" \ + -d '{ + "data": "0a2408011220...", + "encoding": "hex" + }' +``` + +Response: +```json +{ + "success": true, + "decoded": { + "height": 100, + "timestamp": "2024-01-15T10:30:00Z", + "parentHash": "0xabc123...", + "transactions": [ + { + "type": "transfer", + "from": "0x123...", + "to": "0x456...", + "value": "1000000000000000000" + } + ] + } +} +``` + +### Decoding DA Commitment + +```bash +curl -X POST http://localhost:8080/api/decode \ + -H "Content-Type: application/json" \ + -d '{ + "data": "eyJjb21taXRtZW50IjogIi4uLiJ9", + "encoding": "base64" + }' +``` + +### Celestia + +For Celestia blobs, you can decode namespace data and payment information from [celenium](https://celenium.io/namespaces). From 00dac996bf1577b14cafae872f1218119131377a Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Mon, 8 Sep 2025 11:03:39 +0200 Subject: [PATCH 4/4] simplify --- tools/blob-decoder/main.go | 836 +---------------------- tools/blob-decoder/templates/index.html | 874 ++++++++++++++++++++++++ 2 files changed, 896 insertions(+), 814 deletions(-) create mode 100644 tools/blob-decoder/templates/index.html diff --git a/tools/blob-decoder/main.go b/tools/blob-decoder/main.go index f738cdbf2c..4e59182d2a 100644 --- a/tools/blob-decoder/main.go +++ b/tools/blob-decoder/main.go @@ -1,6 +1,7 @@ package main import ( + "embed" "encoding/base64" "encoding/hex" "encoding/json" @@ -8,6 +9,8 @@ import ( "html/template" "log" "net/http" + "os" + "strconv" "strings" "time" @@ -17,7 +20,8 @@ import ( pb "github.com/evstack/ev-node/types/pb/evnode/v1" ) -// Removed embed since we're serving HTML inline +//go:embed templates/* +var templatesFS embed.FS // DecodedBlob represents the result of decoding a blob type DecodedBlob struct { @@ -32,26 +36,29 @@ type DecodedBlob struct { func main() { mux := http.NewServeMux() - // Serve the main page + // handlers mux.HandleFunc("/", handleIndex) - - // API endpoint for decoding blobs mux.HandleFunc("/api/decode", handleDecode) - // Static files removed - HTML is served inline - + // port configuration port := "8090" + if len(os.Args[1:]) > 0 { + port = os.Args[1] + if _, err := strconv.Atoi(port); err != nil { + log.Fatal("Invalid port number") + } + } + fmt.Printf(` -╔════════════════════════════════════════════╗ -║ Evolve Blob Decoder ║ -║ by ev.xyz ║ -╚════════════════════════════════════════════╝ +╔═══════════════════════════════════╗ +║ Evolve Blob Decoder ║ +║ by ev.xyz ║ +╚═══════════════════════════════════╝ 🚀 Server running at: http://localhost:%s ⚡ Using native Evolve protobuf decoding Press Ctrl+C to stop the server - `, port) // Create server with proper timeout configuration @@ -63,7 +70,9 @@ Press Ctrl+C to stop the server IdleTimeout: 60 * time.Second, } - log.Fatal(srv.ListenAndServe()) + if err := srv.ListenAndServe(); err != nil { + log.Fatal(err) + } } func corsMiddleware(next http.Handler) http.Handler { @@ -82,7 +91,7 @@ func corsMiddleware(next http.Handler) http.Handler { } func handleIndex(w http.ResponseWriter, r *http.Request) { - tmpl := template.Must(template.New("index").Parse(indexHTML)) + tmpl := template.Must(template.ParseFS(templatesFS, "templates/index.html")) if err := tmpl.Execute(w, nil); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -274,804 +283,3 @@ func sendJSONResponse(w http.ResponseWriter, data interface{}) { return } } - -func min(a, b int) int { - if a < b { - return a - } - return b -} - -const indexHTML = ` - - - - - Evolve Blob Decoder | ev.xyz - - - -
-
- -
ev.xyz
-

⚡ Evolve Blob Decoder

-

Own It. Shape It. Launch It.

-

Decode and inspect DA layer blobs from your Evolve rollup

-
- -
-
-

Input Blob Data

-
- - -
- - - -
- - - - - -
-
- - -
- -
-
- - - -
- -
-
-
-
-
-
- -
-
- Evolve - The Modular Rollup Framework -
-
- 🚀 Full Control Over Execution  |  - ⚡ Speed to Traction  |  - 🛡️ No Validator Overhead -
-
- ev.xyz  |  - Documentation  |  - GitHub -
-
-
- - - - -` diff --git a/tools/blob-decoder/templates/index.html b/tools/blob-decoder/templates/index.html new file mode 100644 index 0000000000..cc289ea5c0 --- /dev/null +++ b/tools/blob-decoder/templates/index.html @@ -0,0 +1,874 @@ + + + + + + Evolve Blob Decoder | ev.xyz + + + +
+
+ +
ev.xyz
+

⚡ Evolve Blob Decoder

+

Own It. Shape It. Launch It.

+

+ Decode and inspect DA layer blobs from your Evolve rollup +

+
+ +
+
+

Input Blob Data

+
+ + +
+ + + +
+ + + + + +
+
+ + +
+ +
+
+ + + +
+ +
+
+
+
+
+
+ +
+
+ Evolve - The + Modular Rollup Framework +
+
+ 🚀 Full Control Over Execution  |  ⚡ Speed to + Traction  |  🛡️ No Validator Overhead +
+
+ ev.xyz +  |  + GitHub +
+
+
+ + + +