Commit b6093d05 authored by Yann Hodique's avatar Yann Hodique Committed by GitHub

feat(kurtosis-devnet): build/serve prestate proofs (#13560)

This prepares the ground for op-challenger needing a URL locator
serving prestate proof files.

A {{localPrestate}} placeholder will expand to a URL that's usable by
op-challenger.
parent 083a4686
...@@ -55,7 +55,7 @@ type staticServer struct { ...@@ -55,7 +55,7 @@ type staticServer struct {
func launchStaticServer(ctx context.Context, cfg *config) (*staticServer, func(), error) { func launchStaticServer(ctx context.Context, cfg *config) (*staticServer, func(), error) {
// we will serve content from this tmpDir for the duration of the devnet creation // we will serve content from this tmpDir for the duration of the devnet creation
tmpDir, err := os.MkdirTemp("", "contracts-bundle") tmpDir, err := os.MkdirTemp("", cfg.enclave)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("error creating temporary directory: %w", err) return nil, nil, fmt.Errorf("error creating temporary directory: %w", err)
} }
...@@ -124,10 +124,74 @@ func localContractArtifactsOption(cfg *config, server *staticServer) tmpl.Templa ...@@ -124,10 +124,74 @@ func localContractArtifactsOption(cfg *config, server *staticServer) tmpl.Templa
}) })
} }
func localPrestateOption(cfg *config, server *staticServer) tmpl.TemplateContextOptions {
prestateBuilder := build.NewPrestateBuilder(
build.WithPrestateBaseDir(cfg.baseDir),
build.WithPrestateDryRun(cfg.dryRun),
)
return tmpl.WithFunction("localPrestate", func() (string, error) {
// Create build directory with the final path structure
buildDir := filepath.Join(server.dir, "proofs", "op-program", "cannon")
if err := os.MkdirAll(buildDir, 0755); err != nil {
return "", fmt.Errorf("failed to create prestate build directory: %w", err)
}
// Build all prestate files directly in the target directory
if err := prestateBuilder.Build(buildDir); err != nil {
return "", fmt.Errorf("failed to build prestates: %w", err)
}
// Get the relative path from server.dir to buildDir for the URL
relPath, err := filepath.Rel(server.dir, buildDir)
if err != nil {
return "", fmt.Errorf("failed to get relative path: %w", err)
}
url := fmt.Sprintf("%s/%s", server.URL(), relPath)
if cfg.dryRun {
return url, nil
}
// Find all prestate-proof*.json files
matches, err := filepath.Glob(filepath.Join(buildDir, "prestate-proof*.json"))
if err != nil {
return "", fmt.Errorf("failed to find prestate files: %w", err)
}
// Process each file to rename it to its hash
for _, filePath := range matches {
content, err := os.ReadFile(filePath)
if err != nil {
return "", fmt.Errorf("failed to read prestate %s: %w", filepath.Base(filePath), err)
}
var data struct {
Pre string `json:"pre"`
}
if err := json.Unmarshal(content, &data); err != nil {
return "", fmt.Errorf("failed to parse prestate %s: %w", filepath.Base(filePath), err)
}
// Rename the file to just the hash
hashedPath := filepath.Join(buildDir, data.Pre)
if err := os.Rename(filePath, hashedPath); err != nil {
return "", fmt.Errorf("failed to rename prestate %s: %w", filepath.Base(filePath), err)
}
log.Printf("%s available at: %s/%s/%s\n", filepath.Base(filePath), server.URL(), relPath, data.Pre)
}
return url, nil
})
}
func renderTemplate(cfg *config, server *staticServer) (*bytes.Buffer, error) { func renderTemplate(cfg *config, server *staticServer) (*bytes.Buffer, error) {
opts := []tmpl.TemplateContextOptions{ opts := []tmpl.TemplateContextOptions{
localDockerImageOption(cfg), localDockerImageOption(cfg),
localContractArtifactsOption(cfg, server), localContractArtifactsOption(cfg, server),
localPrestateOption(cfg, server),
} }
// Read and parse the data file if provided // Read and parse the data file if provided
......
...@@ -9,6 +9,7 @@ import ( ...@@ -9,6 +9,7 @@ import (
"testing" "testing"
"github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis" "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis"
"github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/tmpl"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
...@@ -215,3 +216,77 @@ func TestMainFunc(t *testing.T) { ...@@ -215,3 +216,77 @@ func TestMainFunc(t *testing.T) {
// Verify the environment file was created // Verify the environment file was created
assert.FileExists(t, envPath) assert.FileExists(t, envPath)
} }
func TestLocalPrestate(t *testing.T) {
// Create a temporary directory for test files
tmpDir, err := os.MkdirTemp("", "prestate-test")
require.NoError(t, err)
defer os.RemoveAll(tmpDir)
// Create a mock justfile
err = os.WriteFile(filepath.Join(tmpDir, "justfile"), []byte(`
_prestate-build target:
@echo "Mock prestate build"
`), 0644)
require.NoError(t, err)
tests := []struct {
name string
dryRun bool
wantErr bool
}{
{
name: "dry run mode",
dryRun: true,
wantErr: false,
},
{
name: "normal mode",
dryRun: false,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := &config{
baseDir: tmpDir,
dryRun: tt.dryRun,
}
ctx := context.Background()
server, cleanup, err := launchStaticServer(ctx, cfg)
require.NoError(t, err)
defer cleanup()
// Create template context with just the prestate function
tmplCtx := tmpl.NewTemplateContext(localPrestateOption(cfg, server))
// Test template
template := `{"prestate": "{{localPrestate}}"}`
buf := bytes.NewBuffer(nil)
err = tmplCtx.InstantiateTemplate(bytes.NewBufferString(template), buf)
if tt.wantErr {
assert.Error(t, err)
return
}
require.NoError(t, err)
// Parse the output
var output struct {
Prestate string `json:"prestate"`
}
err = json.Unmarshal(buf.Bytes(), &output)
require.NoError(t, err)
// Verify the URL structure
assert.Contains(t, output.Prestate, server.URL())
assert.Contains(t, output.Prestate, "/proofs/op-program/cannon")
// Verify the directory was created
prestateDir := filepath.Join(server.dir, "proofs", "op-program", "cannon")
assert.DirExists(t, prestateDir)
})
}
}
...@@ -8,6 +8,10 @@ _contracts-build BUNDLE='contracts-bundle.tar.gz': ...@@ -8,6 +8,10 @@ _contracts-build BUNDLE='contracts-bundle.tar.gz':
just ../packages/contracts-bedrock/forge-build just ../packages/contracts-bedrock/forge-build
tar -czf {{BUNDLE}} -C ../packages/contracts-bedrock artifacts forge-artifacts cache tar -czf {{BUNDLE}} -C ../packages/contracts-bedrock artifacts forge-artifacts cache
_prestate-build PATH='.':
make -C ../op-program reproducible-prestate
cp ../op-program/bin/prestate-proof*.json {{PATH}}
_docker_build TAG TARGET='' CONTEXT='.' DOCKERFILE='Dockerfile': _docker_build TAG TARGET='' CONTEXT='.' DOCKERFILE='Dockerfile':
docker buildx build -t {{TAG}} \ docker buildx build -t {{TAG}} \
-f {{CONTEXT}}/{{DOCKERFILE}} \ -f {{CONTEXT}}/{{DOCKERFILE}} \
......
package build
import (
"bytes"
"fmt"
"log"
"os/exec"
"text/template"
)
// PrestateBuilder handles building prestates using just commands
type PrestateBuilder struct {
baseDir string
cmdTemplate *template.Template
dryRun bool
}
const (
prestateCmdTemplateStr = "just _prestate-build {{.Path}}"
)
var defaultPrestateTemplate *template.Template
func init() {
defaultPrestateTemplate = template.Must(template.New("prestate_build_cmd").Parse(prestateCmdTemplateStr))
}
type PrestateBuilderOptions func(*PrestateBuilder)
func WithPrestateBaseDir(baseDir string) PrestateBuilderOptions {
return func(b *PrestateBuilder) {
b.baseDir = baseDir
}
}
func WithPrestateTemplate(cmdTemplate *template.Template) PrestateBuilderOptions {
return func(b *PrestateBuilder) {
b.cmdTemplate = cmdTemplate
}
}
func WithPrestateDryRun(dryRun bool) PrestateBuilderOptions {
return func(b *PrestateBuilder) {
b.dryRun = dryRun
}
}
// NewPrestateBuilder creates a new PrestateBuilder instance
func NewPrestateBuilder(opts ...PrestateBuilderOptions) *PrestateBuilder {
b := &PrestateBuilder{
baseDir: ".",
cmdTemplate: defaultPrestateTemplate,
dryRun: false,
}
for _, opt := range opts {
opt(b)
}
return b
}
// templateData holds the data for the command template
type prestateTemplateData struct {
Path string
}
// Build executes the prestate build command
func (b *PrestateBuilder) Build(path string) error {
log.Printf("Building prestate: %s", path)
// Prepare template data
data := prestateTemplateData{
Path: path,
}
// Execute template to get command string
var cmdBuf bytes.Buffer
if err := b.cmdTemplate.Execute(&cmdBuf, data); err != nil {
return fmt.Errorf("failed to execute command template: %w", err)
}
// Create command
cmd := exec.Command("sh", "-c", cmdBuf.String())
cmd.Dir = b.baseDir
if !b.dryRun {
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("prestate build command failed: %w\nOutput: %s", err, string(output))
}
}
return nil
}
...@@ -7,7 +7,7 @@ import ( ...@@ -7,7 +7,7 @@ import (
) )
// TemplateFunc represents a function that can be used in templates // TemplateFunc represents a function that can be used in templates
type TemplateFunc func(string) (string, error) type TemplateFunc any
// TemplateContext contains data and functions to be passed to templates // TemplateContext contains data and functions to be passed to templates
type TemplateContext struct { type TemplateContext struct {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment