Skip to content

Commit 07fcbfb

Browse files
authored
feat: add op-faucet component (#228)
This adds preliminary support for op-faucet. see https://github.com/ethereum-optimism/optimism/tree/develop/op-faucet An official image doesn't exist for it yet, so there will need to be a followup once this is addressed. This is a test-only construct. When enabled, we have an unrestricted faucet tied to the (funded) faucet wallets. Each chain in the devnet (l1, and all l2s) are mapped to this faucet.
1 parent e76bd10 commit 07fcbfb

7 files changed

Lines changed: 279 additions & 0 deletions

File tree

main.star

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ op_challenger_launcher = import_module(
88
"./src/challenger/op-challenger/op_challenger_launcher.star"
99
)
1010

11+
faucet = import_module("./src/faucet/op-faucet/op_faucet_launcher.star")
1112
observability = import_module("./src/observability/observability.star")
13+
util = import_module("./src/util.star")
1214

1315
wait_for_sync = import_module("./src/wait/wait_for_sync.star")
1416
input_parser = import_module("./src/package_io/input_parser.star")
@@ -196,6 +198,16 @@ def run(plan, args={}):
196198
observability_helper=observability_helper,
197199
)
198200

201+
if optimism_args.faucet.enabled:
202+
_install_faucet(
203+
plan=plan,
204+
faucet_params=optimism_args.faucet,
205+
l1_config_env_vars=l1_config_env_vars,
206+
l1_priv_key=l1_priv_key,
207+
deployment_output=deployment_output,
208+
l2s=l2s,
209+
)
210+
199211
observability.launch(
200212
plan, observability_helper, global_node_selectors, observability_params
201213
)
@@ -211,3 +223,50 @@ def get_l1_config(all_l1_participants, l1_network_params, l1_network_id):
211223
env_vars["L1_CHAIN_ID"] = str(l1_network_id)
212224
env_vars["L1_BLOCK_TIME"] = str(l1_network_params.seconds_per_slot)
213225
return env_vars
226+
227+
228+
def _install_faucet(
229+
plan,
230+
faucet_params,
231+
l1_config_env_vars,
232+
l1_priv_key,
233+
deployment_output,
234+
l2s,
235+
):
236+
faucets = [
237+
faucet.faucet_data(
238+
name="l1",
239+
chain_id=l1_config_env_vars["L1_CHAIN_ID"],
240+
el_rpc=l1_config_env_vars["L1_RPC_URL"],
241+
private_key=l1_priv_key,
242+
),
243+
]
244+
for l2 in l2s:
245+
chain_id = l2.network_id
246+
247+
private_key = util.read_network_config_value(
248+
plan,
249+
deployment_output,
250+
"wallets",
251+
'."{0}" | .["l2FaucetPrivateKey"]'.format(chain_id),
252+
)
253+
faucets.append(
254+
faucet.faucet_data(
255+
name=l2.name,
256+
chain_id=chain_id,
257+
el_rpc=l2.participants[0].el_context.rpc_http_url,
258+
private_key=private_key,
259+
)
260+
)
261+
262+
faucet_image = (
263+
faucet_params.image
264+
if faucet_params.image != ""
265+
else input_parser.DEFAULT_FAUCET_IMAGES["op-faucet"]
266+
)
267+
faucet.launch(
268+
plan,
269+
"op-faucet",
270+
faucet_image,
271+
faucets,
272+
)

src/faucet/op-faucet/config.tmpl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
faucets:
2+
{{- range . }}
3+
{{ .Name }}:
4+
el_rpc: "{{ .RPC }}"
5+
chain_id: {{ .ChainID }}
6+
tx_cfg:
7+
private_key: "{{ .PrivateKey }}"
8+
{{ end }}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
"""
2+
Support for the op-faucet service.
3+
4+
TODO: op-faucet doesn't have a proper release yet. Don't use this for now
5+
unless you know what you're doing.
6+
"""
7+
8+
9+
def launch(
10+
plan,
11+
service_name,
12+
image,
13+
faucets,
14+
):
15+
"""Launch the op-faucet service.
16+
17+
Args:
18+
plan: The plan to add the service to.
19+
service_name (str): The name of the service.
20+
image (str): The image to use for the op-faucet service.
21+
faucets (list of faucet_data): The faucets to use for the op-faucet service.
22+
"""
23+
faucet_config = plan.render_templates(
24+
name="faucet_config",
25+
description="rendering op-faucet config",
26+
config={
27+
"/config.yaml": struct(
28+
template=read_file("./config.tmpl"),
29+
data=faucets,
30+
)
31+
},
32+
)
33+
34+
config = _get_config(
35+
image,
36+
faucet_config,
37+
)
38+
plan.add_service(service_name, config)
39+
40+
41+
def _get_config(
42+
image,
43+
faucet_config,
44+
):
45+
"""Get the ServiceConfig for the op-faucet service.
46+
47+
Args:
48+
image (str): The image to use for the op-faucet service.
49+
faucet_config (artifact): The config artifact for the op-faucet service.
50+
"""
51+
mount_path = "/config"
52+
cmd = [
53+
"op-faucet",
54+
"--rpc.port=9000",
55+
"--config={0}/config.yaml".format(mount_path),
56+
]
57+
58+
return ServiceConfig(
59+
image=image,
60+
cmd=cmd,
61+
ports={
62+
"rpc": PortSpec(
63+
number=9000,
64+
transport_protocol="TCP",
65+
application_protocol="http",
66+
),
67+
},
68+
files={
69+
mount_path: faucet_config,
70+
},
71+
)
72+
73+
74+
def faucet_data(
75+
chain_id,
76+
el_rpc,
77+
private_key,
78+
name=None,
79+
):
80+
"""Constructor for a faucet data struct.
81+
82+
Args:
83+
chain_id (str): The chain ID the faucet will be used on.
84+
el_rpc (str): The EL RPC the faucet will use.
85+
private_key (str): The private key of the underlying faucet wallet.
86+
name (str): The name of the faucet.
87+
"""
88+
if name == None:
89+
name = chain_id
90+
91+
return struct(
92+
# capitalization for Go template expansion
93+
Name=name,
94+
ChainID=chain_id,
95+
RPC=el_rpc,
96+
PrivateKey=private_key,
97+
)

src/package_io/input_parser.star

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@ DEFAULT_TX_FUZZER_IMAGES = {
5959
"tx-fuzzer": "ethpandaops/tx-fuzz:master",
6060
}
6161

62+
DEFAULT_FAUCET_IMAGES = {
63+
# TODO: update to use a versioned image when available
64+
# For now, we'll need users to pass the image explicitly
65+
"op-faucet": "",
66+
}
67+
6268
DEFAULT_ADDITIONAL_SERVICES = []
6369

6470

@@ -120,6 +126,10 @@ def input_parser(plan, input_args):
120126
max_mem=results["observability"]["grafana_params"]["max_mem"],
121127
),
122128
),
129+
faucet=struct(
130+
enabled=results["faucet"]["enabled"],
131+
image=results["faucet"]["image"],
132+
),
123133
interop=struct(
124134
enabled=results["interop"]["enabled"],
125135
supervisor_params=struct(
@@ -308,6 +318,9 @@ def parse_network_params(plan, input_args):
308318
results["observability"] = default_observability_params()
309319
results["observability"].update(input_args.get("observability", {}))
310320

321+
results["faucet"] = default_faucet_params()
322+
results["faucet"].update(input_args.get("faucet", {}))
323+
311324
results["observability"]["prometheus_params"] = default_prometheus_params()
312325
results["observability"]["prometheus_params"].update(
313326
input_args.get("observability", {}).get("prometheus_params", {})
@@ -482,6 +495,13 @@ def default_observability_params():
482495
}
483496

484497

498+
def default_faucet_params():
499+
return {
500+
"enabled": False,
501+
"image": DEFAULT_FAUCET_IMAGES["op-faucet"],
502+
}
503+
504+
485505
def default_prometheus_params():
486506
return {
487507
"image": "prom/prometheus:v3.1.0",

src/package_io/sanity_check.star

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ ROOT_PARAMS = [
88
"global_node_selectors",
99
"global_tolerations",
1010
"persistent",
11+
"faucet",
1112
]
1213

1314
OBSERVABILITY_PARAMS = [
@@ -19,6 +20,11 @@ OBSERVABILITY_PARAMS = [
1920
"grafana_params",
2021
]
2122

23+
FAUCET_PARAMS = [
24+
"enabled",
25+
"image",
26+
]
27+
2228
PROMETHEUS_PARAMS = [
2329
"image",
2430
"storage_tsdb_retention_time",
@@ -256,6 +262,14 @@ def sanity_check(plan, optimism_config):
256262
GRAFANA_PARAMS,
257263
)
258264

265+
if "faucet" in optimism_config:
266+
validate_params(
267+
plan,
268+
optimism_config["faucet"],
269+
"faucet",
270+
FAUCET_PARAMS,
271+
)
272+
259273
if "interop" in optimism_config:
260274
validate_params(
261275
plan,

src/participant_network.star

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ def launch_participant_network(
136136
)
137137

138138
return struct(
139+
name=network_params.name,
139140
network_id=network_params.network_id,
140141
participants=all_participants,
141142
)

test/op_faucet_launcher_test.star

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
"""
2+
Tests for the op-faucet launcher.
3+
"""
4+
5+
op_faucet_launcher = import_module("/src/faucet/op-faucet/op_faucet_launcher.star")
6+
input_parser = import_module("/src/package_io/input_parser.star")
7+
constants = import_module("/src/package_io/constants.star")
8+
9+
10+
def test_launch_with_defaults(plan):
11+
"""Test launching the op-faucet service with default parameters."""
12+
faucet_image = input_parser.DEFAULT_FAUCET_IMAGES["op-faucet"]
13+
service_name = "op-faucet"
14+
15+
# Create test faucet data
16+
faucets = [
17+
op_faucet_launcher.faucet_data(
18+
chain_id="1",
19+
el_rpc="http://l1-rpc",
20+
private_key=constants.dev_accounts[0]["private_key"],
21+
name="l1",
22+
),
23+
op_faucet_launcher.faucet_data(
24+
chain_id="10",
25+
el_rpc="http://l2-rpc",
26+
private_key=constants.dev_accounts[1]["private_key"],
27+
name="l2",
28+
),
29+
]
30+
31+
op_faucet_launcher.launch(
32+
plan=plan,
33+
service_name=service_name,
34+
image=faucet_image,
35+
faucets=faucets,
36+
)
37+
38+
# Verify service configuration
39+
faucet_service_config = kurtosistest.get_service_config(service_name=service_name)
40+
expect.ne(faucet_service_config, None)
41+
expect.eq(faucet_service_config.image, faucet_image)
42+
expect.eq(faucet_service_config.env_vars, {})
43+
expect.eq(faucet_service_config.entrypoint, [])
44+
expect.eq(
45+
faucet_service_config.cmd,
46+
[
47+
"op-faucet",
48+
"--rpc.port=9000",
49+
"--config=/config/config.yaml",
50+
],
51+
)
52+
expect.eq(faucet_service_config.ports["rpc"].number, 9000)
53+
expect.eq(faucet_service_config.ports["rpc"].transport_protocol, "TCP")
54+
expect.eq(faucet_service_config.ports["rpc"].application_protocol, "http")
55+
56+
57+
def test_launch_with_custom_image(plan):
58+
"""Test launching the op-faucet service with a custom image."""
59+
custom_image = "custom-op-faucet:latest"
60+
service_name = "op-faucet"
61+
62+
# Create test faucet data
63+
faucets = [
64+
op_faucet_launcher.faucet_data(
65+
chain_id="1",
66+
el_rpc="http://l1-rpc",
67+
private_key=constants.dev_accounts[0]["private_key"],
68+
),
69+
]
70+
71+
op_faucet_launcher.launch(
72+
plan=plan,
73+
service_name=service_name,
74+
image=custom_image,
75+
faucets=faucets,
76+
)
77+
78+
# Verify service configuration
79+
faucet_service_config = kurtosistest.get_service_config(service_name=service_name)
80+
expect.eq(faucet_service_config.image, custom_image)

0 commit comments

Comments
 (0)