From f83581ff4f00a45e21b4050325ff4027c978f0f8 Mon Sep 17 00:00:00 2001 From: Yann Hodique Date: Mon, 24 Feb 2025 21:08:46 +0100 Subject: [PATCH] feat(op-program-svc): add info.json to identify prestates --- kurtosis-devnet/op-program-svc/Dockerfile | 4 +- kurtosis-devnet/op-program-svc/fs.go | 73 ++++++++++++++++++++++- kurtosis-devnet/op-program-svc/fs_test.go | 44 +++++++++++++- kurtosis-devnet/op-program-svc/main.go | 13 +++- 4 files changed, 125 insertions(+), 9 deletions(-) diff --git a/kurtosis-devnet/op-program-svc/Dockerfile b/kurtosis-devnet/op-program-svc/Dockerfile index f23e8ec51cedb..1cf559a7616ad 100644 --- a/kurtosis-devnet/op-program-svc/Dockerfile +++ b/kurtosis-devnet/op-program-svc/Dockerfile @@ -1,4 +1,4 @@ -ARG BASE_VERSION=latest +ARG BASE_IMAGE=op-program-base:latest FROM golang:1.22.7-alpine3.20 AS builder @@ -9,7 +9,7 @@ RUN go mod init op-program-svc RUN go build -o op-program-svc . -FROM op-program-base:${BASE_VERSION} AS svc +FROM ${BASE_IMAGE} AS svc ARG GIT_COMMIT ARG GIT_DATE diff --git a/kurtosis-devnet/op-program-svc/fs.go b/kurtosis-devnet/op-program-svc/fs.go index ca7b04f132c08..7408760cf67b8 100644 --- a/kurtosis-devnet/op-program-svc/fs.go +++ b/kurtosis-devnet/op-program-svc/fs.go @@ -1,6 +1,7 @@ package main import ( + "bytes" "encoding/json" "errors" "fmt" @@ -29,6 +30,53 @@ type proofFile struct { file File } +// infoFile implements http.File for the virtual info.json file +type infoFile struct { + *bytes.Reader + content []byte +} + +func newInfoFile(proofFiles map[string]string) *infoFile { + // Create inverted map + invertedMap := make(map[string]string) + for hash, variablePart := range proofFiles { + // Replace dashes with underscores in the key + key := fmt.Sprintf("prestate%s", variablePart) + key = strings.ReplaceAll(key, "-", "_") + invertedMap[key] = hash + } + + // Convert to JSON + content, err := json.MarshalIndent(invertedMap, "", " ") + if err != nil { + // Fallback to empty JSON object if marshaling fails + content = []byte("{}") + } + + return &infoFile{ + Reader: bytes.NewReader(content), + content: content, + } +} + +func (f *infoFile) Close() error { + return nil +} + +func (f *infoFile) Readdir(count int) ([]fs.FileInfo, error) { + return nil, fmt.Errorf("not a directory") +} + +func (f *infoFile) Stat() (fs.FileInfo, error) { + return virtualFileInfo{ + name: "info.json", + size: int64(len(f.content)), + mode: 0644, + modTime: time.Now(), + isDir: false, + }, nil +} + func (f *proofFile) Close() error { return f.file.Close() } @@ -73,7 +121,7 @@ func (d *proofDir) Readdir(count int) ([]fs.FileInfo, error) { defer d.proofMutex.RUnlock() // If we've already read all entries - if d.pos >= len(d.proofFiles)*2 { + if d.pos >= len(d.proofFiles)*2+1 { if count <= 0 { return nil, nil } @@ -89,11 +137,23 @@ func (d *proofDir) Readdir(count int) ([]fs.FileInfo, error) { start := d.pos end := start + count - if count <= 0 || end > len(d.proofFiles)*2 { - end = len(d.proofFiles) * 2 + if count <= 0 || end > len(d.proofFiles)*2+1 { + end = len(d.proofFiles)*2 + 1 } for i := start; i < end; i++ { + // Special case for info.json (last entry) + if i == len(d.proofFiles)*2 { + entries = append(entries, virtualFileInfo{ + name: "info.json", + size: 0, // Size will be determined when actually opening the file + mode: 0644, + modTime: time.Now(), + isDir: false, + }) + continue + } + hash := hashes[i/2] isJSON := i%2 == 0 @@ -167,6 +227,13 @@ func (fs *proofFileSystem) Open(name string) (http.File, error) { // Clean the path and remove leading slash name = strings.TrimPrefix(filepath.Clean(name), "/") + // Special case for info.json + if name == "info.json" { + fs.proofMutex.RLock() + defer fs.proofMutex.RUnlock() + return newInfoFile(fs.proofFiles), nil + } + fs.proofMutex.RLock() defer fs.proofMutex.RUnlock() diff --git a/kurtosis-devnet/op-program-svc/fs_test.go b/kurtosis-devnet/op-program-svc/fs_test.go index 3f61b1a3dfdeb..951c292c1a47a 100644 --- a/kurtosis-devnet/op-program-svc/fs_test.go +++ b/kurtosis-devnet/op-program-svc/fs_test.go @@ -78,6 +78,33 @@ func TestProofFileSystem(t *testing.T) { } }) + t.Run("OpenInfoJSONFile", func(t *testing.T) { + file, err := pfs.Open("/info.json") + if err != nil { + t.Errorf("Failed to open info.json file: %v", err) + } + defer file.Close() + + // Read contents + contents, err := io.ReadAll(file) + if err != nil { + t.Errorf("Failed to read file contents: %v", err) + } + + // Verify the contents contain the inverted map + var infoData map[string]string + err = json.Unmarshal(contents, &infoData) + if err != nil { + t.Errorf("Failed to parse info.json contents: %v", err) + } + + // Check that the key has dashes replaced with underscores + expectedKey := "prestate_test" + if hash, ok := infoData[expectedKey]; !ok || hash != "hash123" { + t.Errorf("Expected info.json to contain mapping from %s to hash123, got %v", expectedKey, hash) + } + }) + t.Run("OpenNonExistentFile", func(t *testing.T) { _, err := pfs.Open("/nonexistent.json") if err == nil { @@ -97,8 +124,21 @@ func TestProofFileSystem(t *testing.T) { t.Errorf("Failed to read directory: %v", err) } - if len(files) != 2 { // We expect both .json and .bin.gz files - t.Errorf("Expected 2 files, got %d", len(files)) + // We expect both .json and .bin.gz files for hash123, plus info.json + if len(files) != 3 { + t.Errorf("Expected 3 files, got %d", len(files)) + } + + // Verify info.json is included in the directory listing + foundInfoJson := false + for _, file := range files { + if file.Name() == "info.json" { + foundInfoJson = true + break + } + } + if !foundInfoJson { + t.Error("info.json not found in directory listing") } }) } diff --git a/kurtosis-devnet/op-program-svc/main.go b/kurtosis-devnet/op-program-svc/main.go index 53375f594c17a..d2cc28f4b8cd0 100644 --- a/kurtosis-devnet/op-program-svc/main.go +++ b/kurtosis-devnet/op-program-svc/main.go @@ -21,9 +21,18 @@ func main() { srv := createServer() + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodGet: + http.FileServer(srv.proofFS).ServeHTTP(w, r) + case http.MethodPost: + srv.handleUpload(w, r) + default: + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + } + }) // Set up routes - http.HandleFunc("/", srv.handleUpload) - http.Handle("/proofs/", http.StripPrefix("/proofs/", http.FileServer(srv.proofFS))) + http.HandleFunc("/", handler) log.Printf("Starting server on :%d with:", srv.port) log.Printf(" app-root: %s", srv.appRoot)