Skip to content

Commit 234ac27

Browse files
committed
Initial commit
0 parents  commit 234ac27

File tree

12 files changed

+2910
-0
lines changed

12 files changed

+2910
-0
lines changed

.github/workflows/checks.yaml

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
name: Checks
2+
3+
on:
4+
workflow_dispatch:
5+
pull_request:
6+
merge_group:
7+
push:
8+
branches: [main]
9+
10+
jobs:
11+
test:
12+
name: E2E test
13+
runs-on: ubuntu-latest
14+
steps:
15+
- name: Check out code
16+
uses: actions/checkout@v2
17+
18+
- name: Set up Go
19+
uses: actions/setup-go@v2
20+
with:
21+
go-version: 1.22
22+
23+
- name: Run playground
24+
run: go run main.go --output /tmp/playground & > /tmp/playground.log 2>&1
25+
26+
- name: Validate that blocks are created
27+
run: go run main.go validate
28+
29+
- name: Move playground logs
30+
if: ${{ failure() }}
31+
run: mv /tmp/playground.log /tmp/playground/logs
32+
33+
- name: Archive playground logs
34+
uses: actions/upload-artifact@v4
35+
if: ${{ failure() }}
36+
with:
37+
name: playground-logs
38+
path: /tmp/playground/logs
39+
retention-days: 5
40+
artifacts:
41+
name: Artifacts
42+
strategy:
43+
matrix:
44+
os: [ubuntu-latest, macos-13]
45+
runs-on: ${{ matrix.os }}
46+
steps:
47+
- name: Check out code
48+
uses: actions/checkout@v2
49+
50+
- name: Set up Go
51+
uses: actions/setup-go@v2
52+
with:
53+
go-version: 1.22
54+
55+
- name: Download and test artifacts
56+
run: go run main.go download-artifacts --validate

.github/workflows/release.yaml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# .github/workflows/release.yml
2+
name: release
3+
4+
on:
5+
workflow_dispatch:
6+
push:
7+
tags:
8+
- "*"
9+
10+
jobs:
11+
release:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- name: Checkout
15+
uses: actions/checkout@v4
16+
with:
17+
fetch-depth: 0
18+
19+
- name: setup dependencies
20+
uses: actions/setup-go@v2
21+
22+
- name: Log tag name
23+
run: echo "Build for tag ${{ github.ref_name }}"
24+
25+
- name: Create release
26+
run: make ci-release
27+
env:
28+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
29+
TAG: ${{ github.ref_name }}

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
local-testnet
2+
output

.goreleaser.yaml

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
env:
2+
- CGO_ENABLED=1
3+
builds:
4+
- id: builder-playground-darwin-amd64
5+
binary: builder-playground
6+
goarch:
7+
- amd64
8+
goos:
9+
- darwin
10+
env:
11+
- CC=o64-clang
12+
- CXX=o64-clang++
13+
flags:
14+
- -trimpath
15+
- id: builder-playground-darwin-arm64
16+
binary: builder-playground
17+
goarch:
18+
- arm64
19+
goos:
20+
- darwin
21+
env:
22+
- CC=oa64-clang
23+
- CXX=oa64-clang++
24+
flags:
25+
- -trimpath
26+
- id: builder-playground-linux-amd64
27+
binary: builder-playground
28+
env:
29+
- CC=x86_64-linux-gnu-gcc
30+
- CXX=x86_64-linux-gnu-g++
31+
goarch:
32+
- amd64
33+
goos:
34+
- linux
35+
flags:
36+
- -trimpath
37+
ldflags:
38+
- -extldflags "-Wl,-z,stack-size=0x800000 --static"
39+
tags:
40+
- netgo
41+
- osusergo
42+
- id: builder-playground-linux-arm64
43+
binary: builder-playground
44+
goarch:
45+
- arm64
46+
goos:
47+
- linux
48+
env:
49+
- CC=aarch64-linux-gnu-gcc
50+
- CXX=aarch64-linux-gnu-g++
51+
flags:
52+
- -trimpath
53+
ldflags:
54+
- -extldflags "-Wl,-z,stack-size=0x800000 --static"
55+
tags:
56+
- netgo
57+
- osusergo
58+
59+
archives:
60+
- id: w/version
61+
builds:
62+
- builder-playground-darwin-amd64
63+
- builder-playground-darwin-arm64
64+
- builder-playground-linux-amd64
65+
- builder-playground-linux-arm64
66+
name_template: "builder-playground_v{{ .Version }}_{{ .Os }}_{{ .Arch }}"
67+
wrap_in_directory: false
68+
format: zip
69+
files:
70+
- none*
71+
72+
checksum:
73+
name_template: "checksums.txt"
74+
75+
release:
76+
draft: true

Makefile

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
2+
ci-release:
3+
docker run \
4+
--rm \
5+
-e CGO_ENABLED=1 \
6+
-e GITHUB_TOKEN="$(GITHUB_TOKEN)" \
7+
-v /var/run/docker.sock:/var/run/docker.sock \
8+
-v $(HOME)/.docker/config.json:/root/.docker/config.json \
9+
-v `pwd`:/go/src/$(PACKAGE_NAME) \
10+
-v `pwd`/sysroot:/sysroot \
11+
-w /go/src/$(PACKAGE_NAME) \
12+
ghcr.io/goreleaser/goreleaser-cross:v1.21.12 \
13+
release --clean --auto-snapshot

README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Builder Playground
2+
3+
The builder playground is a tool to deploy an end-to-end environment to locally test an Ethereum L1 builder. It deploys:
4+
5+
- A beacon node + validator client ([lighthouse](https://github.com/sigp/lighthouse)).
6+
- An execution client ([reth](https://github.com/paradigmxyz/reth)).
7+
- An in-memory [mev-boost-relay](https://github.com/flashbots/mev-boost-relay).
8+
9+
## Usage
10+
11+
Clone the repository and run the following command:
12+
13+
```bash
14+
$ go run main.go
15+
```
16+
17+
The playground performs the following steps:
18+
19+
1. It attempts to download the `lighthouse` and `reth` binaries from the GitHub releases page if they are not found locally.
20+
2. It generates the genesis artifacts for the chain.
21+
22+
- 100 validators with 32 ETH each.
23+
- 10 prefunded accounts with 100 ETH each, generated with the mnemonic `test test test test test test test test test test test junk`.
24+
- It enables the Deneb fork at startup.
25+
26+
3. It deploys the chain services and the relay.
27+
28+
- `Reth` node.
29+
- `Lighthouse` beacon node.
30+
- `Lighthouse` validator client.
31+
- `Mev-boost-relay`.
32+
33+
To stop the playground, press `Ctrl+C`.

artifacts/artifacts.go

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
package artifacts
2+
3+
import (
4+
"archive/tar"
5+
"compress/gzip"
6+
"fmt"
7+
"io"
8+
"net/http"
9+
"os"
10+
"os/exec"
11+
"path/filepath"
12+
"runtime"
13+
)
14+
15+
type release struct {
16+
Name string
17+
Org string
18+
Version string
19+
Arch func(string, string) string
20+
}
21+
22+
func DownloadArtifacts() (map[string]string, error) {
23+
var artifacts = []release{
24+
{
25+
Name: "reth",
26+
Org: "paradigmxyz",
27+
Version: "v1.0.2",
28+
Arch: func(goos, goarch string) string {
29+
if goos == "linux" {
30+
return "x86_64-unknown-linux-gnu"
31+
} else if goos == "darwin" && goarch == "arm64" { // Apple M1
32+
return "aarch64-apple-darwin"
33+
} else if goos == "darwin" && goarch == "amd64" {
34+
return "x86_64-apple-darwin"
35+
}
36+
return ""
37+
},
38+
},
39+
{
40+
Name: "lighthouse",
41+
Org: "sigp",
42+
Version: "v5.2.1",
43+
Arch: func(goos, goarch string) string {
44+
if goos == "linux" {
45+
return "x86_64-unknown-linux-gnu"
46+
} else if goos == "darwin" && goarch == "arm64" { // Apple M1
47+
return "x86_64-apple-darwin-portable"
48+
} else if goos == "darwin" && goarch == "amd64" {
49+
return "x86_64-apple-darwin"
50+
}
51+
return ""
52+
},
53+
},
54+
}
55+
56+
homeDir, err := os.UserHomeDir()
57+
if err != nil {
58+
return nil, fmt.Errorf("error getting user home directory: %w", err)
59+
}
60+
61+
// Define the path for our custom home directory
62+
customHomeDir := filepath.Join(homeDir, ".playground")
63+
64+
// Create output directory if it doesn't exist
65+
if err := os.MkdirAll(customHomeDir, 0755); err != nil {
66+
return nil, fmt.Errorf("error creating output directory: %v", err)
67+
}
68+
69+
goos := runtime.GOOS
70+
goarch := runtime.GOARCH
71+
72+
fmt.Printf("Architecture detected: %s/%s\n", goos, goarch)
73+
74+
// Try to download the release binaries for 'reth' and 'lighthouse'. It works as follows:
75+
// 1. Check under $HOME/.playground if the binary-<version> exists. If exists, use it.
76+
// 2. If the binary does not exists, use the arch and os to download the binary from the release page.
77+
// 3. If the architecture is not supported, check if the binary is found in PATH.
78+
releases := make(map[string]string)
79+
for _, artifact := range artifacts {
80+
outPath := filepath.Join(customHomeDir, artifact.Name+"-"+artifact.Version)
81+
_, err := os.Stat(outPath)
82+
if err != nil && !os.IsNotExist(err) {
83+
return nil, fmt.Errorf("error checking file existence: %v", err)
84+
}
85+
86+
if err != nil {
87+
archVersion := artifact.Arch(goos, goarch)
88+
if archVersion == "" {
89+
// Case 2. The architecture is not supported.
90+
fmt.Printf("unsupported OS/Arch: %s/%s\n", goos, goarch)
91+
if _, err := exec.LookPath(artifact.Name); err != nil {
92+
return nil, fmt.Errorf("error looking up binary in PATH: %v", err)
93+
} else {
94+
outPath = artifact.Name
95+
fmt.Printf("Using %s from PATH\n", artifact.Name)
96+
}
97+
} else {
98+
// Case 3. Download the binary from the release page
99+
releasesURL := fmt.Sprintf("https://github.com/%s/%s/releases/download/%s/%s-%s-%s.tar.gz", artifact.Org, artifact.Name, artifact.Version, artifact.Name, artifact.Version, archVersion)
100+
fmt.Printf("Downloading %s: %s\n", outPath, releasesURL)
101+
102+
if err := downloadArtifact(releasesURL, artifact.Name, outPath); err != nil {
103+
return nil, fmt.Errorf("error downloading artifact: %v", err)
104+
}
105+
}
106+
} else {
107+
// Case 1. Use the binary in $HOME/.playground
108+
fmt.Printf("%s already exists, skipping download\n", outPath)
109+
}
110+
111+
releases[artifact.Name] = outPath
112+
}
113+
114+
return releases, nil
115+
}
116+
117+
func downloadArtifact(url string, expectedFile string, outPath string) error {
118+
// Download the file
119+
resp, err := http.Get(url)
120+
if err != nil {
121+
return fmt.Errorf("error downloading file: %v", err)
122+
}
123+
defer resp.Body.Close()
124+
125+
// Create a gzip reader
126+
gzipReader, err := gzip.NewReader(resp.Body)
127+
if err != nil {
128+
return fmt.Errorf("error creating gzip reader: %v", err)
129+
}
130+
defer gzipReader.Close()
131+
132+
// Create a tar reader
133+
tarReader := tar.NewReader(gzipReader)
134+
135+
// Extract the file
136+
var found bool
137+
for {
138+
header, err := tarReader.Next()
139+
if err == io.EOF {
140+
break
141+
}
142+
if err != nil {
143+
return fmt.Errorf("error reading tar: %v", err)
144+
}
145+
146+
if header.Typeflag == tar.TypeReg {
147+
if header.Name != expectedFile {
148+
return fmt.Errorf("unexpected file in archive: %s", header.Name)
149+
}
150+
outFile, err := os.Create(outPath)
151+
if err != nil {
152+
return fmt.Errorf("error creating output file: %v", err)
153+
}
154+
defer outFile.Close()
155+
156+
if _, err := io.Copy(outFile, tarReader); err != nil {
157+
return fmt.Errorf("error writing output file: %v", err)
158+
}
159+
160+
// change permissions
161+
if err := os.Chmod(outPath, 0755); err != nil {
162+
return fmt.Errorf("error changing permissions: %v", err)
163+
}
164+
found = true
165+
break // Assuming there's only one file per repo
166+
}
167+
}
168+
169+
if !found {
170+
return fmt.Errorf("file not found in archive: %s", expectedFile)
171+
}
172+
return nil
173+
}

0 commit comments

Comments
 (0)