Skip to content

Commit fa05da1

Browse files
authored
Merge branch 'main' into feature/more-prefunded-accounts
2 parents 8300de2 + de2eb70 commit fa05da1

18 files changed

+559
-374
lines changed

.github/workflows/checks.yaml

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -49,21 +49,3 @@ jobs:
4949
name: playground-logs-${{ matrix.flags }}
5050
path: /tmp/playground-logs
5151
retention-days: 5
52-
53-
artifacts:
54-
name: Artifacts
55-
strategy:
56-
matrix:
57-
os: [ubuntu-latest, macos-13]
58-
runs-on: ${{ matrix.os }}
59-
steps:
60-
- name: Check out code
61-
uses: actions/checkout@v2
62-
63-
- name: Set up Go
64-
uses: actions/setup-go@v2
65-
with:
66-
go-version: 1.24
67-
68-
- name: Download and test artifacts
69-
run: go run main.go artifacts-all

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
local-testnet
22
output
33
builder-playground
4+
**/.DS_Store

README.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,64 @@ The following addresses are pre-funded on L1 and to L2 (if present), all with a
9191
- `0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f` (Private key `0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97`)
9292
- `0xa0ee7a142d267c1f36714e4a8f75612f20a79720` (Private key `0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6`)
9393

94+
## Network Readiness
95+
96+
The playground can expose a `/readyz` HTTP endpoint to check if the network is ready to accept transactions (i.e., blocks are being produced).
97+
98+
### Readyz Endpoint
99+
100+
Enable the readyz server with the `--readyz-port` flag:
101+
102+
```bash
103+
$ builder-playground cook l1 --readyz-port 8080
104+
```
105+
106+
Then check readiness:
107+
108+
```bash
109+
$ curl http://localhost:8080/readyz
110+
{"ready":true}
111+
```
112+
113+
Returns:
114+
- `200 OK` with `{"ready": true}` when the network is producing blocks
115+
- `503 Service Unavailable` with `{"ready": false, "error": "..."}` otherwise
116+
117+
### Wait-Ready Command
118+
119+
Use the `wait-ready` command to block until the network is ready:
120+
121+
```bash
122+
$ builder-playground wait-ready [flags]
123+
```
124+
125+
Flags:
126+
- `--url` (string): readyz endpoint URL. Defaults to `http://localhost:8080/readyz`
127+
- `--timeout` (duration): Maximum time to wait. Defaults to `60s`
128+
- `--interval` (duration): Poll interval. Defaults to `1s`
129+
130+
Example:
131+
132+
```bash
133+
# In terminal 1: Start the playground with readyz enabled
134+
$ builder-playground cook l1 --readyz-port 8080
135+
136+
# In terminal 2: Wait for the network to be ready
137+
$ builder-playground wait-ready --timeout 120s
138+
Waiting for http://localhost:8080/readyz (timeout: 2m0s, interval: 1s)
139+
[1s] Attempt 1: 503 Service Unavailable
140+
[2s] Attempt 2: 503 Service Unavailable
141+
[3s] Ready! (200 OK)
142+
```
143+
144+
This is useful for CI/CD pipelines or scripts that need to wait for the network before deploying contracts.
145+
146+
Alternatively, use a bash one-liner:
147+
148+
```bash
149+
$ timeout 60 bash -c 'until curl -sf http://localhost:8080/readyz | grep -q "\"ready\":true"; do sleep 1; done'
150+
```
151+
94152
## Inspect
95153

96154
Builder-playground supports inspecting the connection of a service to a specific port.

main.go

Lines changed: 24 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"time"
1414

1515
"github.com/flashbots/builder-playground/playground"
16+
"github.com/flashbots/builder-playground/playground/cmd"
1617
"github.com/spf13/cobra"
1718
)
1819

@@ -34,6 +35,7 @@ var contenderEnabled bool
3435
var contenderArgs []string
3536
var contenderTarget string
3637
var prefundedAccounts []string
38+
var readyzPort int
3739

3840
var rootCmd = &cobra.Command{
3941
Use: "playground",
@@ -56,73 +58,6 @@ var cookCmd = &cobra.Command{
5658
},
5759
}
5860

59-
var artifactsCmd = &cobra.Command{
60-
Use: "artifacts",
61-
Short: "List available artifacts",
62-
RunE: func(cmd *cobra.Command, args []string) error {
63-
if len(args) != 1 {
64-
return fmt.Errorf("please specify a service name")
65-
}
66-
serviceName := args[0]
67-
component := playground.FindComponent(serviceName)
68-
if component == nil {
69-
return fmt.Errorf("service %s not found", serviceName)
70-
}
71-
releaseService, ok := component.(playground.ReleaseService)
72-
if !ok {
73-
return fmt.Errorf("service %s is not a release service", serviceName)
74-
}
75-
output := outputFlag
76-
if output == "" {
77-
homeDir, err := playground.GetHomeDir()
78-
if err != nil {
79-
return fmt.Errorf("failed to get home directory: %w", err)
80-
}
81-
output = homeDir
82-
}
83-
location, err := playground.DownloadRelease(output, releaseService.ReleaseArtifact())
84-
if err != nil {
85-
return fmt.Errorf("failed to download release: %w", err)
86-
}
87-
fmt.Println(location)
88-
return nil
89-
},
90-
}
91-
92-
var artifactsAllCmd = &cobra.Command{
93-
Use: "artifacts-all",
94-
Short: "Download all the artifacts available in the catalog (Used for testing purposes)",
95-
RunE: func(cmd *cobra.Command, args []string) error {
96-
fmt.Println("Downloading all artifacts...")
97-
98-
output := outputFlag
99-
if output == "" {
100-
homeDir, err := playground.GetHomeDir()
101-
if err != nil {
102-
return fmt.Errorf("failed to get home directory: %w", err)
103-
}
104-
output = homeDir
105-
}
106-
for _, component := range playground.Components {
107-
releaseService, ok := component.(playground.ReleaseService)
108-
if !ok {
109-
continue
110-
}
111-
location, err := playground.DownloadRelease(output, releaseService.ReleaseArtifact())
112-
if err != nil {
113-
return fmt.Errorf("failed to download release: %w", err)
114-
}
115-
116-
// make sure the artifact is valid to be executed on this platform
117-
log.Printf("Downloaded %s to %s\n", releaseService.ReleaseArtifact().Name, location)
118-
if err := isExecutableValid(location); err != nil {
119-
return fmt.Errorf("failed to check if artifact is valid: %w", err)
120-
}
121-
}
122-
return nil
123-
},
124-
}
125-
12661
var inspectCmd = &cobra.Command{
12762
Use: "inspect",
12863
Short: "Inspect a connection between two services",
@@ -187,18 +122,16 @@ func main() {
187122
recipeCmd.Flags().StringArrayVar(&contenderArgs, "contender.arg", []string{}, "add/override contender CLI flags")
188123
recipeCmd.Flags().StringVar(&contenderTarget, "contender.target", "", "override the node that contender spams -- accepts names like \"el\"")
189124
recipeCmd.Flags().StringArrayVar(&prefundedAccounts, "prefunded-account", []string{}, "Fund this account in addition to static prefunded accounts, the input should the account's private key in hexadecimal format prefixed with 0x, the account is added to L1 and to L2 (if present)")
125+
recipeCmd.Flags().IntVar(&readyzPort, "readyz-port", 0, "port for readyz HTTP endpoint (0 to disable)")
190126

191127
cookCmd.AddCommand(recipeCmd)
192128
}
193129

194-
// reuse the same output flag for the artifacts command
195-
artifactsCmd.Flags().StringVar(&outputFlag, "output", "", "Output folder for the artifacts")
196-
artifactsAllCmd.Flags().StringVar(&outputFlag, "output", "", "Output folder for the artifacts")
130+
cmd.InitWaitReadyCmd()
197131

198132
rootCmd.AddCommand(cookCmd)
199-
rootCmd.AddCommand(artifactsCmd)
200-
rootCmd.AddCommand(artifactsAllCmd)
201133
rootCmd.AddCommand(inspectCmd)
134+
rootCmd.AddCommand(cmd.WaitReadyCmd)
202135

203136
if err := rootCmd.Execute(); err != nil {
204137
fmt.Println(err)
@@ -233,15 +166,17 @@ func runIt(recipe playground.Recipe) error {
233166
return err
234167
}
235168

236-
// if contender.tps is set, assume contender is enabled
237-
svcManager := recipe.Apply(&playground.ExContext{
169+
exCtx := &playground.ExContext{
238170
LogLevel: logLevel,
171+
// if contender.tps is set, assume contender is enabled
239172
Contender: &playground.ContenderContext{
240173
Enabled: contenderEnabled,
241174
ExtraArgs: contenderArgs,
242175
TargetChain: contenderTarget,
243176
},
244-
}, artifacts)
177+
}
178+
svcManager := playground.NewManifest(exCtx, artifacts.Out)
179+
recipe.Apply(svcManager)
245180
if err := svcManager.Validate(); err != nil {
246181
return fmt.Errorf("failed to validate manifest: %w", err)
247182
}
@@ -299,6 +234,16 @@ func runIt(recipe playground.Recipe) error {
299234
cancel()
300235
}()
301236

237+
var readyzServer *playground.ReadyzServer
238+
if readyzPort > 0 {
239+
readyzServer = playground.NewReadyzServer(dockerRunner.Instances(), readyzPort)
240+
if err := readyzServer.Start(); err != nil {
241+
return fmt.Errorf("failed to start readyz server: %w", err)
242+
}
243+
defer readyzServer.Stop()
244+
fmt.Printf("Readyz endpoint available at http://localhost:%d/readyz\n", readyzPort)
245+
}
246+
302247
if err := dockerRunner.Run(); err != nil {
303248
dockerRunner.Stop()
304249
return fmt.Errorf("failed to run docker: %w", err)
@@ -330,10 +275,13 @@ func runIt(recipe playground.Recipe) error {
330275
return fmt.Errorf("failed to wait for service readiness: %w", err)
331276
}
332277

278+
fmt.Printf("\nWaiting for network to be ready for transactions...\n")
279+
networkReadyStart := time.Now()
333280
if err := playground.CompleteReady(dockerRunner.Instances()); err != nil {
334281
dockerRunner.Stop()
335-
return fmt.Errorf("failed to complete ready: %w", err)
282+
return fmt.Errorf("network not ready: %w", err)
336283
}
284+
fmt.Printf("Network is ready for transactions (took %.1fs)\n", time.Since(networkReadyStart).Seconds())
337285

338286
// get the output from the recipe
339287
output := recipe.Output(svcManager)

playground/artifacts.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"fmt"
1212
"io"
1313
"log"
14+
"maps"
1415
"math/big"
1516
"os"
1617
"path/filepath"
@@ -228,9 +229,7 @@ func (b *ArtifactsBuilder) Build() (*Artifacts, error) {
228229
if err := json.Unmarshal(contents, &alloc); err != nil {
229230
return nil, fmt.Errorf("failed to unmarshal opState: %w", err)
230231
}
231-
for addr, account := range alloc {
232-
gen.Alloc[addr] = account
233-
}
232+
maps.Copy(gen.Alloc, alloc)
234233
}
235234

236235
block := gen.ToBlock()

playground/catalog.go

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,3 @@ func init() {
2828
register(&BProxy{})
2929
register(&WebsocketProxy{})
3030
}
31-
32-
func FindComponent(name string) ServiceGen {
33-
for _, component := range Components {
34-
if component.Name() == name {
35-
return component
36-
}
37-
}
38-
return nil
39-
}

playground/catalog_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package playground
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
)
8+
9+
func TestCatalogReleases(t *testing.T) {
10+
out := newTestOutput(t)
11+
12+
tests := []*release{
13+
opRethRelease,
14+
rethELRelease,
15+
}
16+
17+
for _, tc := range tests {
18+
_, err := DownloadRelease(out.dst, tc)
19+
require.NoError(t, err)
20+
}
21+
}

0 commit comments

Comments
 (0)