From 44e6be921788798b5f82900b6a8ffbab2e819dbc Mon Sep 17 00:00:00 2001 From: Mitch Date: Tue, 20 Aug 2024 19:32:42 -0400 Subject: [PATCH] feat: basic helm chart --- .github/workflows/ci.yml | 2 +- helm-charts/aztec-network/.helmignore | 23 ++++ helm-charts/aztec-network/Chart.yaml | 6 + .../aztec-network/templates/_helpers.tpl | 59 ++++++++++ .../templates/anvil.deployment.yaml | 58 ++++++++++ .../templates/anvil.service.yaml | 18 +++ .../templates/boot-node.service.yaml | 16 +++ .../templates/boot-node.stateful-set.yaml | 103 ++++++++++++++++++ .../configure-validator-env.config-map.yaml | 38 +++++++ .../deploy-contracts.config-map.yaml | 37 +++++++ .../templates/pxe.deployment.yaml | 53 +++++++++ .../aztec-network/templates/pxe.service.yaml | 18 +++ .../templates/tests/run-tests.yaml | 19 ++++ .../templates/validator.service.yaml | 16 +++ .../templates/validator.stateful-set.yaml | 86 +++++++++++++++ helm-charts/aztec-network/values.yaml | 70 ++++++++++++ yarn-project/Earthfile | 7 +- .../aztec-node/src/aztec-node/server.ts | 4 + .../aztec.js/src/contract/contract.test.ts | 1 + .../src/interfaces/aztec-node.ts | 5 + .../cli/src/cmds/l1/deploy_l1_contracts.ts | 2 +- .../cli/src/cmds/pxe/get_node_info.ts | 21 +++- yarn-project/end-to-end/Earthfile | 43 +++++++- .../end-to-end/scripts/build_images.sh | 47 ++++++++ .../scripts/forward_k8s_dashboard.sh | 6 + .../end-to-end/scripts/setup_local_k8s.sh | 54 +++++++++ .../src/shared/gas_portal_test_harness.ts | 2 +- .../end-to-end/src/spartan/smoke.test.ts | 22 ++++ .../ethereum/src/deploy_l1_contracts.ts | 2 +- yarn-project/p2p/src/client/index.ts | 9 +- .../p2p/src/client/p2p_client.test.ts | 1 + yarn-project/p2p/src/client/p2p_client.ts | 11 ++ .../p2p/src/service/discV5_service.ts | 5 +- yarn-project/p2p/src/service/dummy_service.ts | 8 ++ .../p2p/src/service/libp2p_service.ts | 5 + yarn-project/p2p/src/service/service.ts | 4 + yarn-project/p2p/src/util.ts | 40 +++++-- .../pxe/src/pxe_service/pxe_service.ts | 17 +-- .../types/src/interfaces/node-info.ts | 4 + yarn-project/validator-client/src/factory.ts | 11 +- 40 files changed, 920 insertions(+), 33 deletions(-) create mode 100644 helm-charts/aztec-network/.helmignore create mode 100644 helm-charts/aztec-network/Chart.yaml create mode 100644 helm-charts/aztec-network/templates/_helpers.tpl create mode 100644 helm-charts/aztec-network/templates/anvil.deployment.yaml create mode 100644 helm-charts/aztec-network/templates/anvil.service.yaml create mode 100644 helm-charts/aztec-network/templates/boot-node.service.yaml create mode 100644 helm-charts/aztec-network/templates/boot-node.stateful-set.yaml create mode 100644 helm-charts/aztec-network/templates/configure-validator-env.config-map.yaml create mode 100644 helm-charts/aztec-network/templates/deploy-contracts.config-map.yaml create mode 100644 helm-charts/aztec-network/templates/pxe.deployment.yaml create mode 100644 helm-charts/aztec-network/templates/pxe.service.yaml create mode 100644 helm-charts/aztec-network/templates/tests/run-tests.yaml create mode 100644 helm-charts/aztec-network/templates/validator.service.yaml create mode 100644 helm-charts/aztec-network/templates/validator.stateful-set.yaml create mode 100644 helm-charts/aztec-network/values.yaml create mode 100644 yarn-project/end-to-end/scripts/build_images.sh create mode 100755 yarn-project/end-to-end/scripts/forward_k8s_dashboard.sh create mode 100755 yarn-project/end-to-end/scripts/setup_local_k8s.sh create mode 100644 yarn-project/end-to-end/src/spartan/smoke.test.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 994ff3e40e1..f7cab8933a8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -126,7 +126,7 @@ jobs: # (Note ARM uses just 2 tests as a smoketest) - name: Create list of non-bench end-to-end jobs id: e2e_list - run: echo "list=$(earthly ls ./yarn-project/end-to-end | grep -v '+base' | grep -v '+bench' | grep -v 'devnet' | sed 's/+//' | jq -R . | jq -cs .)" >> $GITHUB_OUTPUT + run: echo "list=$(earthly ls ./yarn-project/end-to-end | grep -v '+base' | grep -v '+bench' | grep -v "+network" | grep -v 'devnet' | sed 's/+//' | jq -R . | jq -cs .)" >> $GITHUB_OUTPUT - name: Create list of bench end-to-end jobs id: bench_list run: echo "list=$(earthly ls ./yarn-project/end-to-end | grep '+bench' | sed 's/+//' | jq -R . | jq -cs .)" >> $GITHUB_OUTPUT diff --git a/helm-charts/aztec-network/.helmignore b/helm-charts/aztec-network/.helmignore new file mode 100644 index 00000000000..0e8a0eb36f4 --- /dev/null +++ b/helm-charts/aztec-network/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/helm-charts/aztec-network/Chart.yaml b/helm-charts/aztec-network/Chart.yaml new file mode 100644 index 00000000000..02c2b26fc80 --- /dev/null +++ b/helm-charts/aztec-network/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: aztec-network +description: A Helm chart for deploying the aztec network +type: application +version: 0.1.0 +appVersion: "1.0.0" diff --git a/helm-charts/aztec-network/templates/_helpers.tpl b/helm-charts/aztec-network/templates/_helpers.tpl new file mode 100644 index 00000000000..44febac82a7 --- /dev/null +++ b/helm-charts/aztec-network/templates/_helpers.tpl @@ -0,0 +1,59 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "aztec-network.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "aztec-network.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "aztec-network.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "aztec-network.labels" -}} +helm.sh/chart: {{ include "aztec-network.chart" . }} +{{ include "aztec-network.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "aztec-network.selectorLabels" -}} +app.kubernetes.io/name: {{ include "aztec-network.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{- define "aztec-network.ethereumHost" -}} +http://{{ include "aztec-network.fullname" . }}-ethereum.{{ .Release.Namespace }}:{{ .Values.ethereum.service.port }} +{{- end -}} + +{{- define "aztec-network.pxeUrl" -}} +http://{{ include "aztec-network.fullname" . }}-pxe.{{ .Release.Namespace }}:{{ .Values.pxe.service.port }} +{{- end -}} diff --git a/helm-charts/aztec-network/templates/anvil.deployment.yaml b/helm-charts/aztec-network/templates/anvil.deployment.yaml new file mode 100644 index 00000000000..9aa0ee859be --- /dev/null +++ b/helm-charts/aztec-network/templates/anvil.deployment.yaml @@ -0,0 +1,58 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "aztec-network.fullname" . }}-ethereum + labels: + {{- include "aztec-network.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.ethereum.replicas }} + selector: + matchLabels: + {{- include "aztec-network.selectorLabels" . | nindent 6 }} + app: ethereum + template: + metadata: + labels: + {{- include "aztec-network.selectorLabels" . | nindent 8 }} + app: ethereum + spec: + containers: + - name: ethereum + image: "{{ .Values.images.foundry.image }}" + imagePullPolicy: {{ .Values.images.foundry.pullPolicy }} + command: ["/bin/sh", "-c"] + args: + - | + [ -n "$FORK_URL" ] && ARGS="$ARGS --fork-url $FORK_URL"; + [ -n "$FORK_BLOCK_NUMBER" ] && ARGS="$ARGS --fork-block-number $FORK_BLOCK_NUMBER"; + echo anvil -p $ANVIL_PORT --host 0.0.0.0 --chain-id {{ .Values.ethereum.chainId }} $ARGS; + anvil -p $ANVIL_PORT --host 0.0.0.0 --chain-id {{ .Values.ethereum.chainId }} $ARGS; + ports: + - containerPort: {{ .Values.ethereum.service.port }} + name: anvil + env: + - name: FORK_URL + value: {{ .Values.ethereum.forkUrl | quote }} + - name: FORK_BLOCK_NUMBER + value: {{ .Values.ethereum.forkBlockNumber | quote }} + - name: ANVIL_PORT + value: {{ .Values.ethereum.service.port | quote }} + - name: ARGS + value: {{ .Values.ethereum.args | quote }} + readinessProbe: + exec: + command: + - sh + - -c + - | + wget -qO- --post-data='{"jsonrpc":"2.0","method":"web3_clientVersion","params":[],"id":67}' \ + --header='Content-Type: application/json' \ + 127.0.0.1:{{ .Values.ethereum.service.port }} \ + | grep -q '"result":"anvil' + initialDelaySeconds: {{ .Values.ethereum.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.ethereum.readinessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.ethereum.readinessProbe.timeoutSeconds }} + successThreshold: {{ .Values.ethereum.readinessProbe.successThreshold }} + failureThreshold: {{ .Values.ethereum.readinessProbe.failureThreshold }} + resources: + {{- toYaml .Values.ethereum.resources | nindent 12 }} \ No newline at end of file diff --git a/helm-charts/aztec-network/templates/anvil.service.yaml b/helm-charts/aztec-network/templates/anvil.service.yaml new file mode 100644 index 00000000000..c0eca3456c7 --- /dev/null +++ b/helm-charts/aztec-network/templates/anvil.service.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "aztec-network.fullname" . }}-ethereum + labels: + {{- include "aztec-network.labels" . | nindent 4 }} +spec: + type: {{ .Values.ethereum.service.type }} + selector: + {{- include "aztec-network.selectorLabels" . | nindent 4 }} + app: ethereum + ports: + - protocol: TCP + port: {{ .Values.ethereum.service.port }} + targetPort: {{ .Values.ethereum.service.targetPort }} + {{- if and (eq .Values.ethereum.service.type "NodePort") .Values.ethereum.service.nodePort }} + nodePort: {{ .Values.ethereum.service.nodePort }} + {{- end }} \ No newline at end of file diff --git a/helm-charts/aztec-network/templates/boot-node.service.yaml b/helm-charts/aztec-network/templates/boot-node.service.yaml new file mode 100644 index 00000000000..ccaf38c0c06 --- /dev/null +++ b/helm-charts/aztec-network/templates/boot-node.service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "aztec-network.fullname" . }}-boot-node + labels: + {{- include "aztec-network.labels" . | nindent 4 }} +spec: + clusterIP: None + selector: + {{- include "aztec-network.selectorLabels" . | nindent 4 }} + app: boot-node + ports: + - port: {{ .Values.bootNode.service.p2pPort }} + name: p2p + - port: {{ .Values.bootNode.service.nodePort }} + name: node \ No newline at end of file diff --git a/helm-charts/aztec-network/templates/boot-node.stateful-set.yaml b/helm-charts/aztec-network/templates/boot-node.stateful-set.yaml new file mode 100644 index 00000000000..875e2354154 --- /dev/null +++ b/helm-charts/aztec-network/templates/boot-node.stateful-set.yaml @@ -0,0 +1,103 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "aztec-network.fullname" . }}-boot-node + labels: + {{- include "aztec-network.labels" . | nindent 4 }} +spec: + serviceName: {{ include "aztec-network.fullname" . }}-boot-node + replicas: {{ .Values.bootNode.replicas }} + selector: + matchLabels: + {{- include "aztec-network.selectorLabels" . | nindent 6 }} + app: boot-node + template: + metadata: + labels: + {{- include "aztec-network.selectorLabels" . | nindent 8 }} + app: boot-node + spec: + initContainers: + - name: wait-for-ethereum + image: {{ .Values.images.curl.image }} + command: + - /bin/sh + - -c + - | + until curl -s -X POST -H 'Content-Type: application/json' \ + -d '{"jsonrpc":"2.0","method":"web3_clientVersion","params":[],"id":67}' \ + {{ include "aztec-network.ethereumHost" . }} | grep -q anvil; do + echo "Waiting for Ethereum node..." + sleep 5 + done + echo "Ethereum node is ready!" + - name: deploy-contracts + image: {{ .Values.images.aztec.image }} + command: + [ + "/bin/sh", + "-c", + "cp /scripts/deploy-contracts.sh /tmp/deploy-contracts.sh && chmod +x /tmp/deploy-contracts.sh && /tmp/deploy-contracts.sh", + ] + volumeMounts: + - name: shared-volume + mountPath: /shared + - name: scripts + mountPath: /scripts + env: + - name: ETHEREUM_HOST + value: {{ include "aztec-network.ethereumHost" . | quote }} + containers: + - name: aztec + image: {{ .Values.images.aztec.image }} + command: + [ + "/bin/bash", + "-c", + "source /shared/contracts.env && env && node --no-warnings /usr/src/yarn-project/aztec/dest/bin/index.js start --node p2pEnabled=true --archiver --sequencer --prover --pxe", + ] + volumeMounts: + - name: shared-volume + mountPath: /shared + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_DNS_NAME + value: "$(POD_NAME).{{ include "aztec-network.fullname" . }}-boot-node.$(POD_NAMESPACE).svc.cluster.local" + - name: PORT + value: "{{ .Values.bootNode.service.nodePort }}" + - name: LOG_LEVEL + value: "{{ .Values.bootNode.logLevel }}" + - name: DEBUG + value: "{{ .Values.bootNode.debug }}" + - name: ETHEREUM_HOST + value: {{ include "aztec-network.ethereumHost" . | quote }} + - name: P2P_ENABLED + value: "{{ .Values.bootNode.p2p.enabled }}" + - name: P2P_TCP_ANNOUNCE_ADDR + value: "$(POD_DNS_NAME):{{ .Values.bootNode.service.p2pPort }}" + - name: P2P_UDP_ANNOUNCE_ADDR + value: "$(POD_DNS_NAME):{{ .Values.bootNode.service.p2pPort }}" + - name: P2P_TCP_LISTEN_ADDR + value: "0.0.0.0:{{ .Values.bootNode.service.p2pPort }}" + - name: P2P_UDP_LISTEN_ADDR + value: "0.0.0.0:{{ .Values.bootNode.service.p2pPort }}" + - name: VALIDATOR_PRIVATE_KEY + value: "0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a" + ports: + - containerPort: 8080 + - containerPort: 40400 + resources: + {{- toYaml .Values.bootNode.resources | nindent 12 }} + volumes: + - name: shared-volume + emptyDir: {} + - name: scripts + configMap: + name: {{ include "aztec-network.fullname" . }}-deploy-contracts-script diff --git a/helm-charts/aztec-network/templates/configure-validator-env.config-map.yaml b/helm-charts/aztec-network/templates/configure-validator-env.config-map.yaml new file mode 100644 index 00000000000..28904f5f5b4 --- /dev/null +++ b/helm-charts/aztec-network/templates/configure-validator-env.config-map.yaml @@ -0,0 +1,38 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "aztec-network.fullname" . }}-configure-validator-env + labels: + {{- include "aztec-network.labels" . | nindent 4 }} +data: + configure-validator-env.sh: | + #!/bin/sh + set -e + + # Ask the bootnode for l1 contract addresses + output=$(node --no-warnings /usr/src/yarn-project/aztec/dest/bin/index.js get-node-info -u http://{{ include "aztec-network.fullname" . }}-boot-node-0.{{ include "aztec-network.fullname" . }}-boot-node.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.bootNode.service.nodePort }}) + + echo "$output" + + boot_node_enr=$(echo "$output" | grep -oP 'Node ENR: \Kenr:[a-zA-Z0-9\-\_\.]+') + rollup_address=$(echo "$output" | grep -oP 'Rollup Address: \K0x[a-fA-F0-9]{40}') + registry_address=$(echo "$output" | grep -oP 'Registry Address: \K0x[a-fA-F0-9]{40}') + inbox_address=$(echo "$output" | grep -oP 'L1 -> L2 Inbox Address: \K0x[a-fA-F0-9]{40}') + outbox_address=$(echo "$output" | grep -oP 'L2 -> L1 Outbox Address: \K0x[a-fA-F0-9]{40}') + availability_oracle_address=$(echo "$output" | grep -oP 'Availability Oracle Address: \K0x[a-fA-F0-9]{40}') + fee_juice_address=$(echo "$output" | grep -oP 'Fee Juice Address: \K0x[a-fA-F0-9]{40}') + fee_juice_portal_address=$(echo "$output" | grep -oP 'Fee Juice Portal Address: \K0x[a-fA-F0-9]{40}') + + # Write the addresses to a file in the shared volume + cat < /shared/contracts.env + export BOOTSTRAP_NODES=$boot_node_enr + export ROLLUP_CONTRACT_ADDRESS=$rollup_address + export REGISTRY_CONTRACT_ADDRESS=$registry_address + export INBOX_CONTRACT_ADDRESS=$inbox_address + export OUTBOX_CONTRACT_ADDRESS=$outbox_address + export AVAILABILITY_ORACLE_CONTRACT_ADDRESS=$availability_oracle_address + export FEE_JUICE_CONTRACT_ADDRESS=$fee_juice_address + export FEE_JUICE_PORTAL_CONTRACT_ADDRESS=$fee_juice_portal_address + EOF + + cat /shared/contracts.env \ No newline at end of file diff --git a/helm-charts/aztec-network/templates/deploy-contracts.config-map.yaml b/helm-charts/aztec-network/templates/deploy-contracts.config-map.yaml new file mode 100644 index 00000000000..157cb822dbd --- /dev/null +++ b/helm-charts/aztec-network/templates/deploy-contracts.config-map.yaml @@ -0,0 +1,37 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "aztec-network.fullname" . }}-deploy-contracts-script + labels: + {{- include "aztec-network.labels" . | nindent 4 }} +data: + deploy-contracts.sh: | + #!/bin/sh + set -e + + # Run the deploy-l1-contracts command and capture the output + output=$(node --no-warnings /usr/src/yarn-project/aztec/dest/bin/index.js deploy-l1-contracts) + + echo "$output" + + # Extract contract addresses using grep and regex + rollup_address=$(echo "$output" | grep -oP 'Rollup Address: \K0x[a-fA-F0-9]{40}') + registry_address=$(echo "$output" | grep -oP 'Registry Address: \K0x[a-fA-F0-9]{40}') + inbox_address=$(echo "$output" | grep -oP 'L1 -> L2 Inbox Address: \K0x[a-fA-F0-9]{40}') + outbox_address=$(echo "$output" | grep -oP 'L2 -> L1 Outbox Address: \K0x[a-fA-F0-9]{40}') + availability_oracle_address=$(echo "$output" | grep -oP 'Availability Oracle Address: \K0x[a-fA-F0-9]{40}') + fee_juice_address=$(echo "$output" | grep -oP 'Fee Juice Address: \K0x[a-fA-F0-9]{40}') + fee_juice_portal_address=$(echo "$output" | grep -oP 'Fee Juice Portal Address: \K0x[a-fA-F0-9]{40}') + + # Write the addresses to a file in the shared volume + cat < /shared/contracts.env + export ROLLUP_CONTRACT_ADDRESS=$rollup_address + export REGISTRY_CONTRACT_ADDRESS=$registry_address + export INBOX_CONTRACT_ADDRESS=$inbox_address + export OUTBOX_CONTRACT_ADDRESS=$outbox_address + export AVAILABILITY_ORACLE_CONTRACT_ADDRESS=$availability_oracle_address + export FEE_JUICE_CONTRACT_ADDRESS=$fee_juice_address + export FEE_JUICE_PORTAL_CONTRACT_ADDRESS=$fee_juice_portal_address + EOF + + cat /shared/contracts.env diff --git a/helm-charts/aztec-network/templates/pxe.deployment.yaml b/helm-charts/aztec-network/templates/pxe.deployment.yaml new file mode 100644 index 00000000000..1dc77d49df2 --- /dev/null +++ b/helm-charts/aztec-network/templates/pxe.deployment.yaml @@ -0,0 +1,53 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "aztec-network.fullname" . }}-pxe + labels: + {{- include "aztec-network.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.pxe.replicas }} + selector: + matchLabels: + {{- include "aztec-network.selectorLabels" . | nindent 6 }} + app: pxe + template: + metadata: + labels: + {{- include "aztec-network.selectorLabels" . | nindent 8 }} + app: pxe + spec: + containers: + - name: aztec + image: "{{ .Values.images.aztec.image }}" + command: + - "/bin/bash" + - "-c" + - > + node --no-warnings /usr/src/yarn-project/aztec/dest/bin/index.js start --pxe + env: + - name: ETHEREUM_HOST + value: "http://{{ include "aztec-network.fullname" . }}-ethereum.{{ .Release.Namespace }}:{{ .Values.ethereum.service.port }}" + - name: AZTEC_NODE_URL + value: "http://{{ include "aztec-network.fullname" . }}-boot-node-0.{{ include "aztec-network.fullname" . }}-boot-node.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.bootNode.service.nodePort }}" + ports: + - name: http + containerPort: {{ .Values.pxe.service.port }} + protocol: TCP + readinessProbe: + exec: + command: + - /bin/bash + - -c + - | + curl -s -X POST -H 'content-type: application/json' \ + -d '{"jsonrpc":"2.0","method":"pxe_getNodeInfo","params":[],"id":67}' \ + 127.0.0.1:{{ .Values.pxe.service.port }} > /tmp/probe_output.txt && \ + cat /tmp/probe_output.txt && \ + grep -q '"enr:-' /tmp/probe_output.txt + initialDelaySeconds: {{ .Values.pxe.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.pxe.readinessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.pxe.readinessProbe.timeoutSeconds }} + successThreshold: {{ .Values.pxe.readinessProbe.successThreshold }} + failureThreshold: {{ .Values.pxe.readinessProbe.failureThreshold }} + resources: + {{- toYaml .Values.pxe.resources | nindent 12 }} \ No newline at end of file diff --git a/helm-charts/aztec-network/templates/pxe.service.yaml b/helm-charts/aztec-network/templates/pxe.service.yaml new file mode 100644 index 00000000000..09e5d2c6db3 --- /dev/null +++ b/helm-charts/aztec-network/templates/pxe.service.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "aztec-network.fullname" . }}-pxe + labels: + {{- include "aztec-network.labels" . | nindent 4 }} +spec: + type: {{ .Values.pxe.service.type }} + selector: + {{- include "aztec-network.selectorLabels" . | nindent 4 }} + app: pxe + ports: + - protocol: TCP + port: {{ .Values.pxe.service.port }} + targetPort: {{ .Values.pxe.service.targetPort }} + {{- if and (eq .Values.pxe.service.type "NodePort") .Values.pxe.service.nodePort }} + nodePort: {{ .Values.pxe.service.nodePort }} + {{- end }} \ No newline at end of file diff --git a/helm-charts/aztec-network/templates/tests/run-tests.yaml b/helm-charts/aztec-network/templates/tests/run-tests.yaml new file mode 100644 index 00000000000..2608b24ffbe --- /dev/null +++ b/helm-charts/aztec-network/templates/tests/run-tests.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ .Release.Name }}-test" + labels: {{- include "aztec-network.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + restartPolicy: Never + containers: + - name: test + image: {{ .Values.images.test.image }} + args: + - {{ .Values.test }} + env: + - name: SCENARIO + value: {{ .Values.scenario }} + - name: PXE_URL + value: {{ include "aztec-network.pxeUrl" . | quote }} diff --git a/helm-charts/aztec-network/templates/validator.service.yaml b/helm-charts/aztec-network/templates/validator.service.yaml new file mode 100644 index 00000000000..0a49e7d4e6b --- /dev/null +++ b/helm-charts/aztec-network/templates/validator.service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "aztec-network.fullname" . }}-validator + labels: + {{- include "aztec-network.labels" . | nindent 4 }} +spec: + clusterIP: None + selector: + {{- include "aztec-network.selectorLabels" . | nindent 4 }} + app: validator + ports: + - port: {{ .Values.validator.service.p2pPort }} + name: p2p + - port: {{ .Values.validator.service.nodePort }} + name: node \ No newline at end of file diff --git a/helm-charts/aztec-network/templates/validator.stateful-set.yaml b/helm-charts/aztec-network/templates/validator.stateful-set.yaml new file mode 100644 index 00000000000..4a3281d55d5 --- /dev/null +++ b/helm-charts/aztec-network/templates/validator.stateful-set.yaml @@ -0,0 +1,86 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "aztec-network.fullname" . }}-validator + labels: + {{- include "aztec-network.labels" . | nindent 4 }} +spec: + serviceName: {{ include "aztec-network.fullname" . }}-validator + replicas: {{ .Values.validator.replicas }} + selector: + matchLabels: + {{- include "aztec-network.selectorLabels" . | nindent 6 }} + app: validator + template: + metadata: + labels: + {{- include "aztec-network.selectorLabels" . | nindent 8 }} + app: validator + spec: + initContainers: + - name: configure-validator-env + image: "{{ .Values.images.aztec.image }}" + imagePullPolicy: {{ .Values.images.aztec.pullPolicy }} + command: + - "/bin/sh" + - "-c" + - "cp /scripts/configure-validator-env.sh /tmp/configure-validator-env.sh && chmod +x /tmp/configure-validator-env.sh && /tmp/configure-validator-env.sh" + volumeMounts: + - name: shared-volume + mountPath: /shared + - name: scripts + mountPath: /scripts + env: + - name: ETHEREUM_HOST + value: {{ include "aztec-network.ethereumHost" . | quote }} + containers: + - name: aztec + image: "{{ .Values.images.aztec.image }}" + imagePullPolicy: {{ .Values.images.aztec.pullPolicy }} + command: + - "/bin/bash" + - "-c" + - "source /shared/contracts.env && env && node --no-warnings /usr/src/yarn-project/aztec/dest/bin/index.js start --node --archiver --sequencer --prover" + volumeMounts: + - name: shared-volume + mountPath: /shared + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_DNS_NAME + value: "$(POD_NAME).{{ include "aztec-network.fullname" . }}-validator.$(POD_NAMESPACE).svc.cluster.local" + - name: PORT + value: "{{ .Values.validator.service.nodePort }}" + - name: LOG_LEVEL + value: "{{ .Values.validator.logLevel }}" + - name: DEBUG + value: "{{ .Values.validator.debug }}" + - name: ETHEREUM_HOST + value: {{ include "aztec-network.ethereumHost" . | quote }} + - name: P2P_ENABLED + value: "{{ .Values.validator.p2p.enabled }}" + - name: P2P_TCP_ANNOUNCE_ADDR + value: "$(POD_DNS_NAME):{{ .Values.validator.service.p2pPort }}" + - name: P2P_UDP_ANNOUNCE_ADDR + value: "$(POD_DNS_NAME):{{ .Values.validator.service.p2pPort }}" + - name: P2P_TCP_LISTEN_ADDR + value: "0.0.0.0:{{ .Values.validator.service.p2pPort }}" + - name: P2P_UDP_LISTEN_ADDR + value: "0.0.0.0:{{ .Values.validator.service.p2pPort }}" + ports: + - containerPort: {{ .Values.validator.service.nodePort }} + - containerPort: {{ .Values.validator.service.p2pPort }} + resources: + {{- toYaml .Values.validator.resources | nindent 12 }} + volumes: + - name: shared-volume + emptyDir: {} + - name: scripts + configMap: + name: {{ include "aztec-network.fullname" . }}-configure-validator-env \ No newline at end of file diff --git a/helm-charts/aztec-network/values.yaml b/helm-charts/aztec-network/values.yaml new file mode 100644 index 00000000000..8bc6f9ef73e --- /dev/null +++ b/helm-charts/aztec-network/values.yaml @@ -0,0 +1,70 @@ +images: + test: + image: aztecprotocol/end-to-end + pullPolicy: IfNotPresent + aztec: + image: aztecprotocol/aztec + pullPolicy: IfNotPresent + curl: + image: curlimages/curl:7.81.0 + pullPolicy: IfNotPresent + foundry: + image: ghcr.io/foundry-rs/foundry@sha256:29ba6e34379e79c342ec02d437beb7929c9e254261e8032b17e187be71a2609f + pullPolicy: IfNotPresent + +bootNode: + replicas: 1 + service: + p2pPort: 40400 + nodePort: 8080 + logLevel: "debug" + debug: "discv5:*,aztec:*" + p2p: + enabled: "true" + resources: {} + +validator: + replicas: 1 + service: + p2pPort: 40400 + nodePort: 8080 + logLevel: "debug" + debug: "discv5:*,aztec:*" + p2p: + enabled: "true" + resources: {} + +pxe: + replicas: 1 + service: + type: ClusterIP + port: 8080 + targetPort: 8080 + readinessProbe: + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 3 + resources: {} + +ethereum: + replicas: 1 + chainId: 31337 + forkUrl: "" + forkBlockNumber: "" + args: "" + service: + type: ClusterIP + port: 8545 + targetPort: 8545 + readinessProbe: + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 3 + resources: {} + +test: "sample" +scenario: "default" diff --git a/yarn-project/Earthfile b/yarn-project/Earthfile index 3d5f3864c43..82e19d58bdf 100644 --- a/yarn-project/Earthfile +++ b/yarn-project/Earthfile @@ -206,7 +206,12 @@ end-to-end-base: && apt-get install -y wget gnupg \ && wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \ && echo "deb [arch=$(dpkg --print-architecture)] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list \ - && apt update && apt install curl chromium nodejs netcat-openbsd -y \ + && wget -O kubectl "https://dl.k8s.io/release/$(wget -qO- https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" \ + && chmod +x kubectl && mv kubectl /usr/local/bin/kubectl \ + && wget -O ./kind https://kind.sigs.k8s.io/dl/v0.23.0/kind-$(uname)-amd64 \ + && chmod +x ./kind && mv ./kind /usr/local/bin/kind \ + && wget -O - https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash \ + && apt update && apt install wget chromium nodejs netcat-openbsd -y \ && rm -rf /var/lib/apt/lists/* ENV CHROME_BIN="/usr/bin/chromium" ENV PATH=/opt/foundry/bin:$PATH diff --git a/yarn-project/aztec-node/src/aztec-node/server.ts b/yarn-project/aztec-node/src/aztec-node/server.ts index d8e0ae765ab..fd53a10894e 100644 --- a/yarn-project/aztec-node/src/aztec-node/server.ts +++ b/yarn-project/aztec-node/src/aztec-node/server.ts @@ -223,6 +223,10 @@ export class AztecNodeService implements AztecNode { return Promise.resolve(this.config.l1Contracts); } + public getEncodedEnr(): Promise { + return Promise.resolve(this.p2pClient.getEnr()?.encodeTxt()); + } + /** * Method to determine if the node is ready to accept transactions. * @returns - Flag indicating the readiness for tx submission. diff --git a/yarn-project/aztec.js/src/contract/contract.test.ts b/yarn-project/aztec.js/src/contract/contract.test.ts index 49aad6296de..f13bbfd954a 100644 --- a/yarn-project/aztec.js/src/contract/contract.test.ts +++ b/yarn-project/aztec.js/src/contract/contract.test.ts @@ -35,6 +35,7 @@ describe('Contract Class', () => { l1ChainId: 1, protocolVersion: 2, l1ContractAddresses: l1Addresses, + enr: undefined, protocolContractAddresses: { classRegisterer: AztecAddress.random(), feeJuice: AztecAddress.random(), diff --git a/yarn-project/circuit-types/src/interfaces/aztec-node.ts b/yarn-project/circuit-types/src/interfaces/aztec-node.ts index d99d95c9a13..70dbc3fb8b4 100644 --- a/yarn-project/circuit-types/src/interfaces/aztec-node.ts +++ b/yarn-project/circuit-types/src/interfaces/aztec-node.ts @@ -350,4 +350,9 @@ export interface AztecNode { /** Forces the next block to be built bypassing all time and pending checks. Useful for testing. */ flushTxs(): Promise; + + /** + * Returns the ENR of this node for peer discovery, if available. + */ + getEncodedEnr(): Promise; } diff --git a/yarn-project/cli/src/cmds/l1/deploy_l1_contracts.ts b/yarn-project/cli/src/cmds/l1/deploy_l1_contracts.ts index 659d1c1e187..b57bae95ee9 100644 --- a/yarn-project/cli/src/cmds/l1/deploy_l1_contracts.ts +++ b/yarn-project/cli/src/cmds/l1/deploy_l1_contracts.ts @@ -29,6 +29,6 @@ export async function deployL1Contracts( log(`L2 -> L1 Outbox Address: ${l1ContractAddresses.outboxAddress.toString()}`); log(`Availability Oracle Address: ${l1ContractAddresses.availabilityOracleAddress.toString()}`); log(`Fee Juice Address: ${l1ContractAddresses.feeJuiceAddress.toString()}`); - log(`Gas Portal Address: ${l1ContractAddresses.feeJuicePortalAddress.toString()}`); + log(`Fee Juice Portal Address: ${l1ContractAddresses.feeJuicePortalAddress.toString()}`); } } diff --git a/yarn-project/cli/src/cmds/pxe/get_node_info.ts b/yarn-project/cli/src/cmds/pxe/get_node_info.ts index 55084e76898..8128de79104 100644 --- a/yarn-project/cli/src/cmds/pxe/get_node_info.ts +++ b/yarn-project/cli/src/cmds/pxe/get_node_info.ts @@ -7,11 +7,20 @@ export async function getNodeInfo(rpcUrl: string, debugLogger: DebugLogger, log: log(`Node Version: ${info.nodeVersion}`); log(`Chain Id: ${info.l1ChainId}`); log(`Protocol Version: ${info.protocolVersion}`); - log(`Rollup Address: ${info.l1ContractAddresses.rollupAddress.toString()}`); - log(`Protocol Contract Addresses:`); - log(` Class Registerer: ${info.protocolContractAddresses.classRegisterer.toString()}`); - log(` Fee Juice: ${info.protocolContractAddresses.feeJuice.toString()}`); + log(`Node ENR: ${info.enr}`); + log(`L1 Contract Addresses:`); + log(` Rollup Address: ${info.l1ContractAddresses.rollupAddress.toString()}`); + log(` Registry Address: ${info.l1ContractAddresses.registryAddress.toString()}`); + log(` L1 -> L2 Inbox Address: ${info.l1ContractAddresses.inboxAddress.toString()}`); + log(` L2 -> L1 Outbox Address: ${info.l1ContractAddresses.outboxAddress.toString()}`); + log(` Availability Oracle Address: ${info.l1ContractAddresses.availabilityOracleAddress.toString()}`); + log(` Fee Juice Address: ${info.l1ContractAddresses.feeJuiceAddress.toString()}`); + log(` Fee Juice Portal Address: ${info.l1ContractAddresses.feeJuicePortalAddress.toString()}`); + + log(`L2 Contract Addresses:`); + log(` Class Registerer: ${info.protocolContractAddresses.classRegisterer.toString()}`); + log(` Fee Juice: ${info.protocolContractAddresses.feeJuice.toString()}`); log(` Instance Deployer: ${info.protocolContractAddresses.instanceDeployer.toString()}`); - log(` Key Registry: ${info.protocolContractAddresses.keyRegistry.toString()}`); - log(` MultiCall: ${info.protocolContractAddresses.multiCallEntrypoint.toString()}`); + log(` Key Registry: ${info.protocolContractAddresses.keyRegistry.toString()}`); + log(` MultiCall: ${info.protocolContractAddresses.multiCallEntrypoint.toString()}`); } diff --git a/yarn-project/end-to-end/Earthfile b/yarn-project/end-to-end/Earthfile index 3f094fccbbb..827c50c20bc 100644 --- a/yarn-project/end-to-end/Earthfile +++ b/yarn-project/end-to-end/Earthfile @@ -46,6 +46,44 @@ E2E_TEST: # Run our docker compose, ending whenever sandbox ends, filtering out noisy eth_getLogs RUN docker run -e HARDWARE_CONCURRENCY=$hardware_concurrency --rm aztecprotocol/end-to-end:$AZTEC_DOCKER_TAG $test || $allow_fail +NETWORK_TEST: + FUNCTION + ARG hardware_concurrency="" + ARG namespace + ARG test + ARG chaos_values + ARG fresh_install + ARG force_build + LOCALLY + # Let docker compose know about the pushed tags above + ENV AZTEC_DOCKER_TAG=$(git rev-parse HEAD) + IF [ "$force_build" = "true" ] || \ + ! docker image ls --format '{{.Repository}}:{{.Tag}}' | grep "aztecprotocol/aztec:$AZTEC_DOCKER_TAG" || \ + ! docker image ls --format '{{.Repository}}:{{.Tag}}' | grep "aztecprotocol/end-to-end:$AZTEC_DOCKER_TAG" + WAIT + BUILD ../+export-e2e-test-images + END + END + # load the docker image into kind + RUN kind load docker-image aztecprotocol/end-to-end:$AZTEC_DOCKER_TAG + RUN kind load docker-image aztecprotocol/aztec:$AZTEC_DOCKER_TAG + + # if fresh_install is true, delete the namespace + IF [ "$fresh_install" = "true" ] + RUN kubectl delete namespace $namespace --ignore-not-found=true --wait=true --now --timeout=10m + END + + + RUN helm install spartan ../../helm-charts/aztec-network \ + --namespace $namespace --create-namespace \ + --set images.test.image="aztecprotocol/end-to-end:$AZTEC_DOCKER_TAG" \ + --set images.aztec.image="aztecprotocol/aztec:$AZTEC_DOCKER_TAG" \ + --set test="$test" + + RUN kubectl wait pod -l app==pxe --for=condition=Ready -n $namespace --timeout=10m + + RUN helm test spartan --namespace $namespace --timeout 30m + e2e-p2p: DO +E2E_TEST --test=./src/e2e_p2p_network.test.ts @@ -255,4 +293,7 @@ e2e-devnet-smoke: DO +E2E_COMPOSE_TEST --test=devnet/e2e_smoke.test.ts --compose_file=scripts/docker-compose-devnet.yml e2e-cli-wallet: - DO +E2E_COMPOSE_TEST --test=e2e_cli_wallet --compose_file=scripts/docker-compose-wallet.yml \ No newline at end of file + DO +E2E_COMPOSE_TEST --test=e2e_cli_wallet --compose_file=scripts/docker-compose-wallet.yml + +network-smoke: + DO +NETWORK_TEST --fresh_install=true --namespace=smoke --test=./src/spartan/smoke.test.ts diff --git a/yarn-project/end-to-end/scripts/build_images.sh b/yarn-project/end-to-end/scripts/build_images.sh new file mode 100644 index 00000000000..d1ac7278e98 --- /dev/null +++ b/yarn-project/end-to-end/scripts/build_images.sh @@ -0,0 +1,47 @@ +#!/bin/bash +set -e + +SCRIPT_DIR=$(dirname "$(realpath "$0")") + +CREDENTIALS_FILE="$HOME/.aws/credentials" +AWS_ACCESS_KEY_ID=$(grep -oP '(?<=aws_access_key_id=).*' "$CREDENTIALS_FILE") +AWS_SECRET_ACCESS_KEY=$(grep -oP '(?<=aws_secret_access_key=).*' "$CREDENTIALS_FILE") + +TARGETS=( + "$SCRIPT_DIR/../+export-aztec" + "$SCRIPT_DIR/+export-spartan-test" +) + +for TARGET in "${TARGETS[@]}"; do + # make temp file for build logs within the script dir + LOG_FILE="$SCRIPT_DIR/build_$(basename "$TARGET").log" + rm -f "$LOG_FILE" + touch "$LOG_FILE" + + echo "Building image for $TARGET" + echo "Logging to $LOG_FILE" + + earthly \ + --secret AWS_ACCESS_KEY_ID="$AWS_ACCESS_KEY_ID" \ + --secret AWS_SECRET_ACCESS_KEY="$AWS_SECRET_ACCESS_KEY" \ + "$TARGET" > "$LOG_FILE" 2>&1 + + if [ $? -ne 0 ]; then + echo "Build failed for $TARGET. Check $LOG_FILE for more information" + continue + fi + + # the \\ is becuase the target starts with a + and we need to escape it + IMAGE_NAME=$(grep -oP "Image .*\\$(basename "$TARGET") output as \K.*" "$LOG_FILE") + + if [ -z "$IMAGE_NAME" ]; then + echo "Failed to extract image name for $TARGET. Check $LOG_FILE for more information" + continue + fi + + echo "Built image $IMAGE_NAME" + + kind load docker-image "$IMAGE_NAME" + + echo "Loaded image into kind cluster" +done \ No newline at end of file diff --git a/yarn-project/end-to-end/scripts/forward_k8s_dashboard.sh b/yarn-project/end-to-end/scripts/forward_k8s_dashboard.sh new file mode 100755 index 00000000000..3cfef4b8241 --- /dev/null +++ b/yarn-project/end-to-end/scripts/forward_k8s_dashboard.sh @@ -0,0 +1,6 @@ +#!/bin/bash +set -e +# spit out the token for the dashboard +kubectl -n kubernetes-dashboard create token admin-user --duration 300000m +# forward the dashboard +kubectl -n kubernetes-dashboard port-forward svc/kubernetes-dashboard-kong-proxy 8443:443 diff --git a/yarn-project/end-to-end/scripts/setup_local_k8s.sh b/yarn-project/end-to-end/scripts/setup_local_k8s.sh new file mode 100755 index 00000000000..0aa132c8808 --- /dev/null +++ b/yarn-project/end-to-end/scripts/setup_local_k8s.sh @@ -0,0 +1,54 @@ +#!/bin/bash + +set -e + +# exit if we are not on linux amd64 +if [ "$(uname)" != "Linux" ] || [ "$(uname -m)" != "x86_64" ]; then + echo "This script is only supported on Linux amd64" + exit 1 +fi + +curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" +chmod +x kubectl +sudo mv kubectl /usr/local/bin/kubectl + +# Install kind +[ $(uname -m) = x86_64 ] && curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.23.0/kind-$(uname)-amd64 +chmod +x ./kind +sudo mv ./kind /usr/local/bin/kind + +# Install helm +curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash + +kind create cluster + +# Install the Kubernetes Dashboard +helm repo add kubernetes-dashboard https://kubernetes.github.io/dashboard/ +helm upgrade --install kubernetes-dashboard kubernetes-dashboard/kubernetes-dashboard --create-namespace --namespace kubernetes-dashboard + + +# Create the ServiceAccount for the Kubernetes Dashboard +kubectl apply -f - < { + let pxe: PXE; + beforeAll(async () => { + pxe = await createCompatibleClient(PXE_URL, debugLogger); + }); + it('should be able to get node enr', async () => { + const info = await pxe.getNodeInfo(); + expect(info).toBeDefined(); + // expect enr to be a string starting with 'enr:-' + expect(info.enr).toMatch(/^enr:-/); + }); +}); diff --git a/yarn-project/ethereum/src/deploy_l1_contracts.ts b/yarn-project/ethereum/src/deploy_l1_contracts.ts index 8897a4d1811..4bc595fcc16 100644 --- a/yarn-project/ethereum/src/deploy_l1_contracts.ts +++ b/yarn-project/ethereum/src/deploy_l1_contracts.ts @@ -89,7 +89,7 @@ export interface L1ContractArtifactsForDeployment { */ feeJuice: ContractArtifacts; /** - * Gas portal contract artifacts. Optional for now as gas is not strictly enforced + * Fee juice portal contract artifacts. Optional for now as gas is not strictly enforced */ feeJuicePortal: ContractArtifacts; } diff --git a/yarn-project/p2p/src/client/index.ts b/yarn-project/p2p/src/client/index.ts index 56f6618a762..cd891dfa03f 100644 --- a/yarn-project/p2p/src/client/index.ts +++ b/yarn-project/p2p/src/client/index.ts @@ -8,7 +8,7 @@ import { DiscV5Service } from '../service/discV5_service.js'; import { DummyP2PService } from '../service/dummy_service.js'; import { LibP2PService, createLibP2PPeerId } from '../service/index.js'; import { type TxPool } from '../tx_pool/index.js'; -import { getPublicIp, splitAddressPort } from '../util.js'; +import { getPublicIp, resolveAddressIfNecessary, splitAddressPort } from '../util.js'; export * from './p2p_client.js'; @@ -29,6 +29,13 @@ export const createP2PClient = async ( queryForIp, } = config; + config.tcpAnnounceAddress = configTcpAnnounceAddress + ? await resolveAddressIfNecessary(configTcpAnnounceAddress) + : undefined; + config.udpAnnounceAddress = configUdpAnnounceAddress + ? await resolveAddressIfNecessary(configUdpAnnounceAddress) + : undefined; + // create variable for re-use if needed let publicIp; diff --git a/yarn-project/p2p/src/client/p2p_client.test.ts b/yarn-project/p2p/src/client/p2p_client.test.ts index 7f58fa9734f..5d9138d961d 100644 --- a/yarn-project/p2p/src/client/p2p_client.test.ts +++ b/yarn-project/p2p/src/client/p2p_client.test.ts @@ -44,6 +44,7 @@ describe('In-Memory P2P Client', () => { stop: jest.fn(), propagate: jest.fn(), registerBlockReceivedCallback: jest.fn(), + getEnr: jest.fn(), }; attestationPool = { diff --git a/yarn-project/p2p/src/client/p2p_client.ts b/yarn-project/p2p/src/client/p2p_client.ts index 11bc75b06e4..6af0180251e 100644 --- a/yarn-project/p2p/src/client/p2p_client.ts +++ b/yarn-project/p2p/src/client/p2p_client.ts @@ -11,6 +11,8 @@ import { INITIAL_L2_BLOCK_NUM } from '@aztec/circuits.js/constants'; import { createDebugLogger } from '@aztec/foundation/log'; import { type AztecKVStore, type AztecSingleton } from '@aztec/kv-store'; +import { type ENR } from '@chainsafe/enr'; + import { type AttestationPool } from '../attestation_pool/attestation_pool.js'; import { getP2PConfigEnvVars } from '../config.js'; import type { P2PService } from '../service/service.js'; @@ -124,6 +126,11 @@ export interface P2P { * Returns the current status of the p2p client. */ getStatus(): Promise; + + /** + * Returns the ENR for this node, if any. + */ + getEnr(): ENR | undefined; } /** @@ -324,6 +331,10 @@ export class P2PClient implements P2P { return this.txPool.getTxStatus(txHash); } + public getEnr(): ENR | undefined { + return this.p2pService.getEnr(); + } + /** * Deletes the 'txs' from the pool. * NOT used if we use sendTx as reconcileTxPool will handle this. diff --git a/yarn-project/p2p/src/service/discV5_service.ts b/yarn-project/p2p/src/service/discV5_service.ts index 557a431e19b..70983cb6656 100644 --- a/yarn-project/p2p/src/service/discV5_service.ts +++ b/yarn-project/p2p/src/service/discV5_service.ts @@ -84,12 +84,11 @@ export class DiscV5Service extends EventEmitter implements PeerDiscoveryService const multiAddrTcp = await enr.getFullMultiaddr('tcp'); const multiAddrUdp = await enr.getFullMultiaddr('udp'); this.logger.debug(`ENR multiaddr: ${multiAddrTcp?.toString()}, ${multiAddrUdp?.toString()}`); + this.onDiscovered(enr); }); } public async start(): Promise { - // Do this conversion once since it involves an async function call - this.bootstrapNodePeerIds = await Promise.all(this.bootstrapNodes.map(enr => ENR.decodeTxt(enr).peerId())); if (this.currentState === PeerDiscoveryState.RUNNING) { throw new Error('DiscV5Service already started'); } @@ -102,6 +101,8 @@ export class DiscV5Service extends EventEmitter implements PeerDiscoveryService // Add bootnode ENR if provided if (this.bootstrapNodes?.length) { + // Do this conversion once since it involves an async function call + this.bootstrapNodePeerIds = await Promise.all(this.bootstrapNodes.map(enr => ENR.decodeTxt(enr).peerId())); this.logger.info(`Adding bootstrap ENRs: ${this.bootstrapNodes.join(', ')}`); try { this.bootstrapNodes.forEach(enr => { diff --git a/yarn-project/p2p/src/service/dummy_service.ts b/yarn-project/p2p/src/service/dummy_service.ts index 7f322c908b1..507b3d515e4 100644 --- a/yarn-project/p2p/src/service/dummy_service.ts +++ b/yarn-project/p2p/src/service/dummy_service.ts @@ -41,6 +41,10 @@ export class DummyP2PService implements P2PService { * Register a callback into the validator client for when a block proposal is received */ public registerBlockReceivedCallback(_: (block: BlockProposal) => Promise) {} + + public getEnr(): undefined { + return undefined; + } } /** @@ -83,4 +87,8 @@ export class DummyPeerDiscoveryService extends EventEmitter implements PeerDisco public getStatus(): PeerDiscoveryState { return this.currentState; } + + public getEnr(): undefined { + return undefined; + } } diff --git a/yarn-project/p2p/src/service/libp2p_service.ts b/yarn-project/p2p/src/service/libp2p_service.ts index 5c153beaec0..bc3a7c55594 100644 --- a/yarn-project/p2p/src/service/libp2p_service.ts +++ b/yarn-project/p2p/src/service/libp2p_service.ts @@ -12,6 +12,7 @@ import { SerialQueue } from '@aztec/foundation/queue'; import { RunningPromise } from '@aztec/foundation/running-promise'; import type { AztecKVStore } from '@aztec/kv-store'; +import { type ENR } from '@chainsafe/enr'; import { type GossipsubEvents, gossipsub } from '@chainsafe/libp2p-gossipsub'; import { noise } from '@chainsafe/libp2p-noise'; import { yamux } from '@chainsafe/libp2p-yamux'; @@ -208,6 +209,10 @@ export class LibP2PService implements P2PService { return new LibP2PService(config, node, peerDiscoveryService, txPool, attestationPool); } + public getEnr(): ENR | undefined { + return this.peerDiscoveryService.getEnr(); + } + public registerBlockReceivedCallback(callback: (block: BlockProposal) => Promise) { this.blockReceivedCallback = callback; this.logger.verbose('Block received callback registered'); diff --git a/yarn-project/p2p/src/service/service.ts b/yarn-project/p2p/src/service/service.ts index 0980515f768..5cf0525778c 100644 --- a/yarn-project/p2p/src/service/service.ts +++ b/yarn-project/p2p/src/service/service.ts @@ -33,6 +33,8 @@ export interface P2PService { // Leaky abstraction: fix https://github.com/AztecProtocol/aztec-packages/issues/7963 registerBlockReceivedCallback(callback: (block: BlockProposal) => Promise): void; + + getEnr(): ENR | undefined; } /** @@ -74,4 +76,6 @@ export interface PeerDiscoveryService extends EventEmitter { emit(event: 'peer:discovered', enr: ENR): boolean; getStatus(): PeerDiscoveryState; + + getEnr(): ENR | undefined; } diff --git a/yarn-project/p2p/src/util.ts b/yarn-project/p2p/src/util.ts index 520d47cef46..07a71b5fda2 100644 --- a/yarn-project/p2p/src/util.ts +++ b/yarn-project/p2p/src/util.ts @@ -1,9 +1,10 @@ +import { resolve } from 'dns/promises'; + /** * Converts an address string to a multiaddr string. * Example usage: * const tcpAddr = '123.456.7.8:80' -> /ip4/123.456.7.8/tcp/80 * const udpAddr = '[2001:db8::1]:8080' -> /ip6/2001:db8::1/udp/8080 - * const dnsAddr = 'example.com:443' -> /dns4/example.com/tcp/443 * @param address - The address string to convert. Has to be in the format :. * @param protocol - The protocol to use in the multiaddr string. * @returns A multiaddr compliant string. @@ -11,15 +12,8 @@ export function convertToMultiaddr(address: string, protocol: 'tcp' | 'udp'): string { const [addr, port] = splitAddressPort(address, false); - let multiaddrPrefix: string; - - if (addr.includes(':')) { - // IPv6 address - multiaddrPrefix = 'ip6'; - } else if (addr.match(/^[\d.]+$/)) { - // IPv4 address - multiaddrPrefix = 'ip4'; - } else { + const multiaddrPrefix = addressToMultiAddressType(addr); + if (multiaddrPrefix === 'dns') { throw new Error('Invalid address format. Expected an IPv4 or IPv6 address.'); } @@ -60,3 +54,29 @@ export async function getPublicIp(): Promise { const text = await resp.text(); return text.trim(); } + +export async function resolveAddressIfNecessary(address: string): Promise { + const [addr, port] = splitAddressPort(address, false); + const multiaddrPrefix = addressToMultiAddressType(addr); + if (multiaddrPrefix === 'dns') { + const resolvedAddresses = await resolve(addr); + if (resolvedAddresses.length === 0) { + throw new Error(`Could not resolve address: ${addr}`); + } + return `${resolvedAddresses[0]}:${port}`; + } else { + return address; + } +} + +// Not public because it is not used outside of this file. +// Plus, it relies on `splitAddressPort` being called on the address first. +function addressToMultiAddressType(address: string): 'ip4' | 'ip6' | 'dns' { + if (address.includes(':')) { + return 'ip6'; + } else if (address.match(/^[\d.]+$/)) { + return 'ip4'; + } else { + return 'dns'; + } +} diff --git a/yarn-project/pxe/src/pxe_service/pxe_service.ts b/yarn-project/pxe/src/pxe_service/pxe_service.ts index 277e97c3814..d35e27808d5 100644 --- a/yarn-project/pxe/src/pxe_service/pxe_service.ts +++ b/yarn-project/pxe/src/pxe_service/pxe_service.ts @@ -636,18 +636,21 @@ export class PXEService implements PXE { } public async getNodeInfo(): Promise { - const [nodeVersion, protocolVersion, chainId, contractAddresses, protocolContractAddresses] = await Promise.all([ - this.node.getNodeVersion(), - this.node.getVersion(), - this.node.getChainId(), - this.node.getL1ContractAddresses(), - this.node.getProtocolContractAddresses(), - ]); + const [nodeVersion, protocolVersion, chainId, enr, contractAddresses, protocolContractAddresses] = + await Promise.all([ + this.node.getNodeVersion(), + this.node.getVersion(), + this.node.getChainId(), + this.node.getEncodedEnr(), + this.node.getL1ContractAddresses(), + this.node.getProtocolContractAddresses(), + ]); const nodeInfo: NodeInfo = { nodeVersion, l1ChainId: chainId, protocolVersion, + enr, l1ContractAddresses: contractAddresses, protocolContractAddresses: protocolContractAddresses, }; diff --git a/yarn-project/types/src/interfaces/node-info.ts b/yarn-project/types/src/interfaces/node-info.ts index 60edf9e8c6a..fd10048f5f3 100644 --- a/yarn-project/types/src/interfaces/node-info.ts +++ b/yarn-project/types/src/interfaces/node-info.ts @@ -18,6 +18,10 @@ export interface NodeInfo { * Protocol version. */ protocolVersion: number; + /** + * The node's ENR. + */ + enr: string | undefined; /** * The deployed l1 contract addresses */ diff --git a/yarn-project/validator-client/src/factory.ts b/yarn-project/validator-client/src/factory.ts index adca375b2b2..1438448d850 100644 --- a/yarn-project/validator-client/src/factory.ts +++ b/yarn-project/validator-client/src/factory.ts @@ -1,8 +1,17 @@ import { type P2P } from '@aztec/p2p'; +import { generatePrivateKey } from 'viem/accounts'; + import { type ValidatorClientConfig } from './config.js'; import { ValidatorClient } from './validator.js'; export function createValidatorClient(config: ValidatorClientConfig, p2pClient: P2P) { - return config.disableValidator ? undefined : ValidatorClient.new(config, p2pClient); + if (config.disableValidator) { + return undefined; + } + // TODO: should this be exposed via a flag? + if (config.validatorPrivateKey === undefined || config.validatorPrivateKey === '') { + config.validatorPrivateKey = generatePrivateKey(); + } + return ValidatorClient.new(config, p2pClient); }