Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion .github/workflows/checks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -120,4 +120,24 @@ jobs:
run: |
go mod tidy
git update-index -q --really-refresh
git diff-index HEAD
git diff-index HEAD

docs-check:
name: Check documentation is up to date
runs-on: warp-ubuntu-latest-x64-8x
steps:
- name: Check out code
uses: actions/checkout@v2

- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.24

- name: Generate documentation
run: make generate-docs

- name: Check for uncommitted changes
run: |
git update-index -q --really-refresh
git diff-index --quiet HEAD -- docs/recipes/ || (echo "Documentation is out of date. Please run 'go run main.go generate-docs' and commit the changes." && exit 1)
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ test: ## Run tests
integration-test: ## Run integration tests
INTEGRATION_TESTS=true go test -v -count=1 ./playground/... -run TestRecipe

.PHONY: generate-docs
generate-docs: ## Auto-generate recipe docs
go run main.go generate-docs

.PHONY: lint
lint: ## Run linters
output=$$(gofmt -d -s .) && [ -z "$$output" ] || { echo "$$output"; exit 1; }
Expand Down
40 changes: 40 additions & 0 deletions docs/recipes/buildernet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# buildernet Recipe

Deploy a full L1 stack with mev-boost and builder-hub.

## Flags

- `block-time` (duration): Block time to use for the L1. Default to '12s'.
- `latest-fork` (bool): use the latest fork. Default to 'false'.
- `secondary-el` (string): Address or port to use for the secondary EL (execution layer); Can be a port number (e.g., '8551') in which case the full URL is derived as `http://localhost:<port>` or a complete URL (e.g., `http://docker-container-name:8551`), use `http://host.docker.internal:<port>` to reach a secondary execution client that runs on your host and not within Docker.. Default to ''.
- `use-native-reth` (bool): use the native reth binary. Default to 'false'.
- `use-reth-for-validation` (bool): use reth for validation. Default to 'false'.
- `use-separate-mev-boost` (bool): use separate mev-boost and mev-boost-relay services. Default to 'false'.

## Architecture Diagram

```mermaid
graph LR
el["el<br/>rpc:30303<br/>http:8545<br/>ws:8546<br/>authrpc:8551<br/>metrics:9090"]
el_healthmon["el_healthmon"]
beacon["beacon<br/>p2p:9000<br/>p2p:9000<br/>quic-p2p:9100<br/>http:3500"]
beacon_healthmon["beacon_healthmon"]
validator["validator"]
mev_boost_relay["mev-boost-relay<br/>http:5555"]
builder_hub_db["builder-hub-db<br/>postgres:5432"]
builder_hub_api["builder-hub-api<br/>http:8080<br/>admin:8081<br/>internal:8082<br/>metrics:8090"]
builder_hub_proxy["builder-hub-proxy<br/>http:8888"]

el_healthmon -->|http| el
beacon -->|authrpc| el
beacon -->|http| mev_boost_relay
beacon_healthmon -->|http| beacon
validator -->|http| beacon
mev_boost_relay -->|http| beacon
builder_hub_api -->|postgres| builder_hub_db
builder_hub_proxy -->|http| builder_hub_api
mev_boost_relay -.->|depends_on| beacon
builder_hub_api -.->|depends_on| builder_hub_db
builder_hub_proxy -.->|depends_on| builder_hub_api
```

33 changes: 33 additions & 0 deletions docs/recipes/l1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# l1 Recipe

Deploy a full L1 stack with mev-boost.

## Flags

- `block-time` (duration): Block time to use for the L1. Default to '12s'.
- `latest-fork` (bool): use the latest fork. Default to 'false'.
- `secondary-el` (string): Address or port to use for the secondary EL (execution layer); Can be a port number (e.g., '8551') in which case the full URL is derived as `http://localhost:<port>` or a complete URL (e.g., `http://docker-container-name:8551`), use `http://host.docker.internal:<port>` to reach a secondary execution client that runs on your host and not within Docker.. Default to ''.
- `use-native-reth` (bool): use the native reth binary. Default to 'false'.
- `use-reth-for-validation` (bool): use reth for validation. Default to 'false'.
- `use-separate-mev-boost` (bool): use separate mev-boost and mev-boost-relay services. Default to 'false'.

## Architecture Diagram

```mermaid
graph LR
el["el<br/>rpc:30303<br/>http:8545<br/>ws:8546<br/>authrpc:8551<br/>metrics:9090"]
el_healthmon["el_healthmon"]
beacon["beacon<br/>p2p:9000<br/>p2p:9000<br/>quic-p2p:9100<br/>http:3500"]
beacon_healthmon["beacon_healthmon"]
validator["validator"]
mev_boost_relay["mev-boost-relay<br/>http:5555"]

el_healthmon -->|http| el
beacon -->|authrpc| el
beacon -->|http| mev_boost_relay
beacon_healthmon -->|http| beacon
validator -->|http| beacon
mev_boost_relay -->|http| beacon
mev_boost_relay -.->|depends_on| beacon
```

43 changes: 43 additions & 0 deletions docs/recipes/opstack.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# opstack Recipe

Deploy an OP stack.

## Flags

- `base-overlay` (bool): Whether to use base implementation for flashblocks-rpc. Default to 'false'.
- `batcher-max-channel-duration` (uint64): Maximum channel duration to use for the batcher. Default to '2'.
- `block-time` (uint64): Block time to use for the rollup. Default to '2'.
- `chain-monitor` (bool): Whether to enable chain-monitor. Default to 'false'.
- `enable-latest-fork` (uint64): Enable latest fork isthmus (nil or empty = disabled, otherwise enabled at specified block). Default to 'nil'.
- `enable-websocket-proxy` (bool): Whether to enable websocket proxy. Default to 'false'.
- `external-builder` (string): External builder URL. Default to ''.
- `flashblocks` (bool): Whether to enable flashblocks. Default to 'false'.
- `flashblocks-builder` (string): External URL of builder flashblocks stream. Default to ''.

## Architecture Diagram

```mermaid
graph LR
el["el<br/>rpc:30303<br/>http:8545<br/>ws:8546<br/>authrpc:8551<br/>metrics:9090"]
el_healthmon["el_healthmon"]
beacon["beacon<br/>p2p:9000<br/>p2p:9000<br/>quic-p2p:9100<br/>http:3500"]
beacon_healthmon["beacon_healthmon"]
validator["validator"]
op_geth["op-geth<br/>http:8545<br/>ws:8546<br/>authrpc:8551<br/>rpc:30303<br/>metrics:6061"]
op_geth_healthmon["op-geth_healthmon"]
op_node["op-node<br/>metrics:7300<br/>http:8549<br/>p2p:9003<br/>p2p:9003"]
op_batcher["op-batcher"]

el_healthmon -->|http| el
beacon -->|authrpc| el
beacon_healthmon -->|http| beacon
validator -->|http| beacon
op_geth_healthmon -->|http| op_geth
op_node -->|http| el
op_node -->|http| beacon
op_node -->|authrpc| op_geth
op_batcher -->|http| el
op_batcher -->|http| op_geth
op_batcher -->|http| op_node
```

9 changes: 9 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,14 @@ var listCmd = &cobra.Command{
},
}

var generateDocsCmd = &cobra.Command{
Use: "generate-docs",
Short: "Generate documentation for all recipes",
RunE: func(cmd *cobra.Command, args []string) error {
return playground.GenerateDocs(recipes)
},
}

var versionCmd = &cobra.Command{
Use: "version",
Short: "Print the version",
Expand Down Expand Up @@ -312,6 +320,7 @@ func main() {
debugCmd.AddCommand(probeCmd)
debugCmd.AddCommand(inspectCmd)
rootCmd.AddCommand(debugCmd)
rootCmd.AddCommand(generateDocsCmd)

if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
Expand Down
67 changes: 67 additions & 0 deletions playground/docs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package playground

import (
"fmt"
"os"
"path/filepath"
"strings"

flag "github.com/spf13/pflag"
)

func GenerateDocs(recipes []Recipe) error {
// Create docs/recipes directory
recipesDir := filepath.Join("docs", "recipes")
if err := os.MkdirAll(recipesDir, 0o755); err != nil {
return fmt.Errorf("failed to create recipes directory: %w", err)
}

for _, recipe := range recipes {
output, err := NewOutput("/tmp/playground-docs")
if err != nil {
return err
}

manifest := NewManifest(&ExContext{Contender: &ContenderContext{}}, output, "")
recipe.Apply(manifest)

// Generate markdown content
var md strings.Builder

// Title and description
md.WriteString(fmt.Sprintf("# %s Recipe\n\n", recipe.Name()))
md.WriteString(fmt.Sprintf("%s.\n\n", recipe.Description()))

// Flags section
md.WriteString("## Flags\n\n")
flags := recipe.Flags()
if flags != nil && flags.HasFlags() {
flags.VisitAll(func(f *flag.Flag) {
flagType := f.Value.Type()
defaultVal := f.DefValue
usage := f.Usage

md.WriteString(fmt.Sprintf("- `%s` (%s): %s. Default to '%s'.\n", f.Name, flagType, usage, defaultVal))
})
md.WriteString("\n")
} else {
md.WriteString("No flags available.\n\n")
}

// Dot graph section
md.WriteString("## Architecture Diagram\n\n")
md.WriteString("```mermaid\n")
md.WriteString(manifest.GenerateMermaidGraph())
md.WriteString("```\n\n")

// Write to file
filename := filepath.Join(recipesDir, fmt.Sprintf("%s.md", recipe.Name()))
if err := os.WriteFile(filename, []byte(md.String()), 0o644); err != nil {
return fmt.Errorf("failed to write docs for recipe %s: %w", recipe.Name(), err)
}

fmt.Printf("Generated documentation: %s\n", filename)
}

return nil
}
49 changes: 49 additions & 0 deletions playground/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,55 @@ func applyTemplate(templateStr string) (string, []Port, []NodeRef) {
return res, portRef, nodeRef
}

func (s *Manifest) GenerateMermaidGraph() string {
var b strings.Builder
b.WriteString("graph LR\n")

// Add nodes (services) with their ports as labels
for _, ss := range s.Services {
var ports []string
for _, p := range ss.Ports {
ports = append(ports, fmt.Sprintf("%s:%d", p.Name, p.Port))
}
portLabel := ""
if len(ports) > 0 {
portLabel = "<br/>" + strings.Join(ports, "<br/>")
}
// Sanitize node name for Mermaid
nodeName := strings.ReplaceAll(ss.Name, "-", "_")
b.WriteString(fmt.Sprintf(" %s[\"%s%s\"]\n", nodeName, ss.Name, portLabel))
}

b.WriteString("\n")

// Add edges (connections between services)
for _, ss := range s.Services {
sourceNode := strings.ReplaceAll(ss.Name, "-", "_")
for _, ref := range ss.NodeRefs {
targetNode := strings.ReplaceAll(ref.Service, "-", "_")
b.WriteString(fmt.Sprintf(" %s -->|%s| %s\n",
sourceNode,
ref.PortLabel,
targetNode,
))
}
}

// Add edges for depends_on
for _, ss := range s.Services {
for _, dep := range ss.DependsOn {
sourceNode := strings.ReplaceAll(ss.Name, "-", "_")
targetNode := strings.ReplaceAll(dep.Name, "-", "_")
b.WriteString(fmt.Sprintf(" %s -.->|depends_on| %s\n",
sourceNode,
targetNode,
))
}
}

return b.String()
}

func (s *Manifest) GenerateDotGraph() string {
var b strings.Builder
b.WriteString("digraph G {\n")
Expand Down